Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 67 additions & 30 deletions app/(pages)/_contexts/ShoppingCartContext.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use client';

import { createContext, useState, useEffect, useCallback } from 'react';
import useLocalStorage from '@hooks/useLocalStorage';
import products from '../products/_data/products';
Expand All @@ -8,9 +9,7 @@ export const ShoppingCartContext = createContext<ShoppingCartContextInt>({
cart: [],
add_to_cart: (_: string) => {},
remove_from_cart: (_: string) => {},
compute_total: () => {
return 0;
},
compute_total: () => 0,
});

interface CartItem {
Expand All @@ -19,17 +18,25 @@ interface CartItem {
prod_name: string;
prod_price: number;
quantity: number;
selectedOptions?: Record<string, string>;
discountPercent?: number;
}

interface LocalItem {
prod_id: string;
quantity: number;
selectedOptions?: Record<string, string>;
discount?: number;
}

interface ShoppingCartContextInt {
loading: boolean;
cart: CartItem[];
add_to_cart: (id: string) => void;
add_to_cart: (
id: string,
selectedOptions?: Record<string, string>,
discountPercent?: number
) => void;
remove_from_cart: (id: string) => void;
compute_total: () => number;
}
Expand All @@ -50,64 +57,94 @@ export function ShoppingCartProvider({
useEffect(() => {
if (!cartLoading) {
try {
const local_data = JSON.parse(data);
const filtered_local_data = local_data.filter(
(local_item: LocalItem) =>
products.filter((p) => p.id === local_item.prod_id)[0]
? true
: false
const local_data: LocalItem[] =
typeof data === 'string' ? JSON.parse(data) : data;

const filtered_local_data = local_data.filter((local_item) =>
products.some((p) => p.id === local_item.prod_id)
);

if (filtered_local_data.length !== local_data.length) {
updateValue(JSON.stringify(filtered_local_data));
} else {
const expanded_data = local_data.map((local_item: LocalItem) => {
const item = products.filter((p) => p.id === local_item.prod_id)[0];
}

const expanded_data = filtered_local_data
.map((local_item: LocalItem) => {
const item = products.find((p) => p.id === local_item.prod_id);
if (!item) return null;

const discount = local_item.discount ?? item.discount;
const price = discount
? Number((item.price * (1 - discount / 100)).toFixed(2))
: item.price;

return {
prod_id: item.id,
img_url: item.src,
prod_name: item.name,
prod_price: item.price,
prod_price: price,
quantity: local_item.quantity,
selectedOptions: local_item.selectedOptions,
discountPercent: discount,
};
});
setCart(expanded_data);
}
})
.filter((item) => item !== null) as CartItem[];

setCart(expanded_data);
} catch (e) {
updateValue(JSON.stringify([]));
setCart([]);
}

setLoading(false);
}
}, [cartLoading, data, updateValue]);

const add_to_cart = useCallback(
(id: string) => {
const cart_data = JSON.parse(data);
cart_data.push({
prod_id: id,
quantity: 1,
});
(
id: string,
selectedOptions: Record<string, string> = {},
discount?: number
) => {
const cart_data: LocalItem[] =
typeof data === 'string' ? JSON.parse(data) : data;

const existingIndex = cart_data.findIndex(
(item) =>
item.prod_id === id &&
JSON.stringify(item.selectedOptions) ===
JSON.stringify(selectedOptions)
);

if (existingIndex !== -1) {
cart_data[existingIndex].quantity += 1;
} else {
cart_data.push({
prod_id: id,
quantity: 1,
selectedOptions,
discount,
});
}

updateValue(JSON.stringify(cart_data));
},
[data, updateValue]
);

const remove_from_cart = useCallback(
(id: string) => {
const cart_data = JSON.parse(data);
const new_cart = cart_data.filter(
(item: LocalItem) => item.prod_id !== id
);
const cart_data: LocalItem[] =
typeof data === 'string' ? JSON.parse(data) : data;

const new_cart = cart_data.filter((item) => item.prod_id !== id);
updateValue(JSON.stringify(new_cart));
},
[data, updateValue]
);

const compute_total = useCallback(() => {
let sum = 0;
cart.forEach((item: CartItem) => (sum += item.prod_price * item.quantity));
return sum;
return cart.reduce((sum, item) => sum + item.prod_price * item.quantity, 0);
}, [cart]);

const contextValue = {
Expand Down
1 change: 1 addition & 0 deletions app/(pages)/product/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface Product {
alt: string;
name: string;
price: number;
description: string;
}

export default function Product({ params }: { params: { id: string } }) {
Expand Down
166 changes: 148 additions & 18 deletions app/(pages)/product/_components/ProductPage/ProductPage.module.scss
Original file line number Diff line number Diff line change
@@ -1,31 +1,161 @@
$mid-gap: var(--spacer);

.container {
display: flex;
flex-direction: row;
padding: var(--medium-spacer);
gap: $mid-gap;
background-color: var(--light-foreground);
justify-content: center;
display: flex;
flex-direction: row;
padding: var(--medium-spacer);
gap: $mid-gap;
background-color: var(--light-foreground);
justify-content: center;
}

.main_img_container {
width: calc(calc(100% - $mid-gap) / 2);
aspect-ratio: calc(5/4);
position: relative;
width: calc(50% - $mid-gap);
aspect-ratio: 5 / 4;
position: relative;


.main_image {
object-fit: cover;
}
.main_image {
object-fit: cover;
}
}

.name {
font-size: 32px;
color: #3a2779;
}

.prod_information {
width: calc(calc(100% - $mid-gap) / 2);
display: flex;
flex-direction: column;
gap: var(--spacer);
width: calc(50% - $mid-gap);
display: flex;
flex-direction: column;
gap: var(--spacer);
}

.description {
color: #7a7a7a;
font-size: 16px;
line-height: 1.5;
max-width: 80%;
margin-bottom: 10px;
}


.price {
font-size: 32px;
display: flex;
gap: 0.5rem;
align-items: center;
}

.original_price {
color: #999;
text-decoration: line-through;
font-size: 16px;
}

.discounted_price {
color: #3a2779;
font-weight: 600;
font-size: 32px;
}

.line {
height: 0.8px;
background-color: #e0e0e0;
width: 80%;
margin: 1em 0;
}

.add_to_cart_button {
max-width: 400px;
}
max-width: 150px;
padding: 15px;
font-size: 1rem;
background: #3a2779;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
transition: transform 0.2s ease, background 0.2s ease;

&:active {
transform: scale(0.95);
}

&:hover {
background-color: #7a39d0;
}

&.added {
background: #3a2779;
animation: pop 0.3s ease;
}
}

@keyframes pop {
0% {
transform: scale(1);
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}

.option_buttons {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}

.option_button {
padding: 0.5rem 1rem;
border: 1.5px solid rgb(188, 188, 188);
color: grey;
background: white;
cursor: pointer;
border-radius: 4px;
transition: all 0.2s ease;

&:hover {
background: #f7f7f7;
}

&.selected {

color: #3a2779;
border: 2px solid #3a2779;
font-weight: 500;


}
}
.customization_label {
font-weight: 400;
margin-bottom: 0.5rem;
color: grey;


}

@media (max-width: 750px) {
.container {
flex-direction: column;
justify-content: center;
align-items: center;
}

.main_img_container {
width: 80%;
}

.prod_information {
width: 80%;
}


}


Loading