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
3 changes: 2 additions & 1 deletion UMC-10th-mission-FE/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"react-router": "^7.15.0",
"react-router-dom": "^7.15.0",
"styled-components": "^6.4.1",
"zod": "^4.4.3"
"zod": "^4.4.3",
"zustand": "^5.0.14"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
Expand Down
28 changes: 28 additions & 0 deletions UMC-10th-mission-FE/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 5 additions & 22 deletions UMC-10th-mission-FE/src/components/CartItem.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,12 @@
import { useDispatch } from 'react-redux';
import type { AppDispatch } from '../store/store';
import { increase, decrease, removeItem, calculateTotals } from '../features/cart/cartSlice';
import useCartStore from '../store/useCartStore';
import type { CartItem as CartItemType } from '../types/cart';

interface Props {
item: CartItemType;
}

export default function CartItem({ item }: Props) {
const dispatch = useDispatch<AppDispatch>();

const handleIncrease = () => {
dispatch(increase(item.id));
dispatch(calculateTotals());
};

const handleDecrease = () => {
dispatch(decrease(item.id));
dispatch(calculateTotals());
};

const handleRemove = () => {
dispatch(removeItem(item.id));
dispatch(calculateTotals());
};
const { increase, decrease, removeItem } = useCartStore();

return (
<article className="flex items-center gap-4 bg-white rounded-xl shadow-sm p-4">
Expand All @@ -41,21 +24,21 @@ export default function CartItem({ item }: Props) {
</div>
<div className="flex flex-col items-center gap-1">
<button
onClick={handleIncrease}
onClick={() => increase(item.id)}
className="w-7 h-7 rounded-full bg-indigo-100 text-indigo-700 font-bold hover:bg-indigo-200 transition"
>
+
</button>
<span className="font-semibold text-gray-700">{item.amount}</span>
<button
onClick={handleDecrease}
onClick={() => decrease(item.id)}
className="w-7 h-7 rounded-full bg-indigo-100 text-indigo-700 font-bold hover:bg-indigo-200 transition"
>
-
</button>
</div>
<button
onClick={handleRemove}
onClick={() => removeItem(item.id)}
className="ml-2 text-gray-400 hover:text-red-500 transition text-lg"
aria-label="remove"
>
Expand Down
11 changes: 5 additions & 6 deletions UMC-10th-mission-FE/src/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { useSelector, useDispatch } from 'react-redux';
import type { RootState, AppDispatch } from '../store/store';
import { openModal } from '../features/modal/modalSlice';
import useCartStore from '../store/useCartStore';
import useModalStore from '../store/useModalStore';

export default function Footer() {
const { amount, total } = useSelector((state: RootState) => state.cart);
const dispatch = useDispatch<AppDispatch>();
const { amount, total } = useCartStore();
const openModal = useModalStore((state) => state.openModal);

return (
<footer className="bg-gray-900 text-white px-6 py-5">
Expand All @@ -20,7 +19,7 @@ export default function Footer() {
</div>
<div className="max-w-2xl mx-auto mt-4">
<button
onClick={() => dispatch(openModal())}
onClick={openModal}
className="w-full py-2 bg-red-500 hover:bg-red-600 text-white font-semibold rounded-lg transition"
>
전체 삭제
Expand Down
18 changes: 8 additions & 10 deletions UMC-10th-mission-FE/src/components/Modal.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import { useSelector, useDispatch } from 'react-redux';
import type { RootState, AppDispatch } from '../store/store';
import { closeModal } from '../features/modal/modalSlice';
import { clearCart } from '../features/cart/cartSlice';
import useCartStore from '../store/useCartStore';
import useModalStore from '../store/useModalStore';

export default function Modal() {
const isOpen = useSelector((state: RootState) => state.modal.isOpen);
const dispatch = useDispatch<AppDispatch>();
const { isOpen, closeModal } = useModalStore();
const clearCart = useCartStore((state) => state.clearCart);

if (!isOpen) return null;

return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<div
className="absolute inset-0 bg-black/50"
onClick={() => dispatch(closeModal())}
onClick={closeModal}
/>
<div className="relative bg-white rounded-2xl shadow-xl p-8 w-80 flex flex-col items-center gap-6">
<p className="text-lg font-semibold text-gray-800 text-center">
Expand All @@ -22,15 +20,15 @@ export default function Modal() {
<div className="flex gap-4 w-full">
<button
onClick={() => {
dispatch(clearCart());
dispatch(closeModal());
clearCart();
closeModal();
}}
className="flex-1 py-2 bg-red-500 hover:bg-red-600 text-white font-semibold rounded-lg transition"
>
</button>
<button
onClick={() => dispatch(closeModal())}
onClick={closeModal}
className="flex-1 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 font-semibold rounded-lg transition"
>
아니요
Expand Down
5 changes: 2 additions & 3 deletions UMC-10th-mission-FE/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { useSelector } from 'react-redux';
import type { RootState } from '../store/store';
import useCartStore from '../store/useCartStore';

export default function Navbar() {
const amount = useSelector((state: RootState) => state.cart.amount);
const amount = useCartStore((state) => state.amount);

return (
<nav className="bg-gray-900 text-white px-6 py-4 flex items-center justify-between shadow-md">
Expand Down
6 changes: 1 addition & 5 deletions UMC-10th-mission-FE/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './store/store';
import './index.css';
import App from './App.tsx';

createRoot(document.getElementById('root')!).render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
<App />
</StrictMode>
);
5 changes: 2 additions & 3 deletions UMC-10th-mission-FE/src/pages/CartPage.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { useSelector } from 'react-redux';
import type { RootState } from '../store/store';
import useCartStore from '../store/useCartStore';
import CartItem from '../components/CartItem';

export default function CartPage() {
const cartItems = useSelector((state: RootState) => state.cart.cartItems);
const cartItems = useCartStore((state) => state.cartItems);

if (cartItems.length === 0) {
return (
Expand Down
58 changes: 58 additions & 0 deletions UMC-10th-mission-FE/src/store/useCartStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { create } from 'zustand';
import cartItems from '../constants/cartItems';
import type { CartItem } from '../types/cart';

interface CartStore {
cartItems: CartItem[];
amount: number;
total: number;
increase: (id: string) => void;
decrease: (id: string) => void;
removeItem: (id: string) => void;
clearCart: () => void;
calculateTotals: () => void;
}

const useCartStore = create<CartStore>((set) => ({
cartItems,
amount: cartItems.length,
total: cartItems.reduce((sum, item) => sum + Number(item.price) * item.amount, 0),

increase: (id) =>
set((state) => {
const updated = state.cartItems.map((item) =>
item.id === id ? { ...item, amount: item.amount + 1 } : item
);
const amount = updated.reduce((sum, item) => sum + item.amount, 0);
const total = updated.reduce((sum, item) => sum + Number(item.price) * item.amount, 0);
return { cartItems: updated, amount, total };
}),

decrease: (id) =>
set((state) => {
const updated = state.cartItems
.map((item) => (item.id === id ? { ...item, amount: item.amount - 1 } : item))
.filter((item) => item.amount > 0);
const amount = updated.reduce((sum, item) => sum + item.amount, 0);
const total = updated.reduce((sum, item) => sum + Number(item.price) * item.amount, 0);
return { cartItems: updated, amount, total };
}),

removeItem: (id) =>
set((state) => {
const updated = state.cartItems.filter((item) => item.id !== id);
const amount = updated.reduce((sum, item) => sum + item.amount, 0);
const total = updated.reduce((sum, item) => sum + Number(item.price) * item.amount, 0);
return { cartItems: updated, amount, total };
}),

clearCart: () => set({ cartItems: [], amount: 0, total: 0 }),

calculateTotals: () =>
set((state) => ({
amount: state.cartItems.reduce((sum, item) => sum + item.amount, 0),
total: state.cartItems.reduce((sum, item) => sum + Number(item.price) * item.amount, 0),
})),
}));

export default useCartStore;
15 changes: 15 additions & 0 deletions UMC-10th-mission-FE/src/store/useModalStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { create } from 'zustand';

interface ModalStore {
isOpen: boolean;
openModal: () => void;
closeModal: () => void;
}

const useModalStore = create<ModalStore>((set) => ({
isOpen: false,
openModal: () => set({ isOpen: true }),
closeModal: () => set({ isOpen: false }),
}));

export default useModalStore;