Skip to content

Commit 0a674bb

Browse files
authored
Merge pull request #27 from NestaRizkia/Notification-Center
Adding Notification Center Feature
2 parents dd59e09 + b5ededf commit 0a674bb

28 files changed

+2426
-59
lines changed

app/_zustand/notificationStore.ts

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { create } from 'zustand';
2+
import { Notification, NotificationFilters, NotificationResponse } from '@/types/notification';
3+
4+
interface NotificationState {
5+
notifications: Notification[];
6+
unreadCount: number;
7+
total: number;
8+
page: number;
9+
totalPages: number;
10+
loading: boolean;
11+
error: string | null;
12+
filters: NotificationFilters;
13+
selectedIds: string[];
14+
15+
// Actions
16+
setNotifications: (response: NotificationResponse) => void;
17+
addNotification: (notification: Notification) => void;
18+
markAsRead: (id: string) => void;
19+
markAsUnread: (id: string) => void;
20+
deleteNotification: (id: string) => void;
21+
setFilters: (filters: Partial<NotificationFilters>) => void;
22+
toggleSelection: (id: string) => void;
23+
selectAll: () => void;
24+
clearSelection: () => void;
25+
setLoading: (loading: boolean) => void;
26+
setError: (error: string | null) => void;
27+
setUnreadCount: (count: number) => void;
28+
clearNotifications: () => void;
29+
}
30+
31+
export const useNotificationStore = create<NotificationState>((set, get) => ({
32+
// Initial state
33+
notifications: [],
34+
unreadCount: 0,
35+
total: 0,
36+
page: 1,
37+
totalPages: 0,
38+
loading: false,
39+
error: null,
40+
filters: {
41+
page: 1,
42+
limit: 10,
43+
sortBy: 'createdAt',
44+
sortOrder: 'desc'
45+
},
46+
selectedIds: [],
47+
48+
// Actions
49+
setNotifications: (response: NotificationResponse) =>
50+
set({
51+
notifications: response.notifications,
52+
unreadCount: response.unreadCount,
53+
total: response.total,
54+
page: response.page,
55+
totalPages: response.totalPages,
56+
loading: false,
57+
error: null
58+
}),
59+
60+
addNotification: (notification: Notification) =>
61+
set(state => ({
62+
notifications: [notification, ...state.notifications],
63+
unreadCount: notification.isRead ? state.unreadCount : state.unreadCount + 1,
64+
total: state.total + 1
65+
})),
66+
67+
markAsRead: (id: string) =>
68+
set(state => ({
69+
notifications: state.notifications.map(n =>
70+
n.id === id ? { ...n, isRead: true } : n
71+
),
72+
unreadCount: Math.max(0, state.unreadCount - 1)
73+
})),
74+
75+
markAsUnread: (id: string) =>
76+
set(state => ({
77+
notifications: state.notifications.map(n =>
78+
n.id === id ? { ...n, isRead: false } : n
79+
),
80+
unreadCount: state.unreadCount + 1
81+
})),
82+
83+
deleteNotification: (id: string) =>
84+
set(state => {
85+
const notification = state.notifications.find(n => n.id === id);
86+
const wasUnread = notification && !notification.isRead;
87+
88+
return {
89+
notifications: state.notifications.filter(n => n.id !== id),
90+
unreadCount: wasUnread ? Math.max(0, state.unreadCount - 1) : state.unreadCount,
91+
total: Math.max(0, state.total - 1),
92+
selectedIds: state.selectedIds.filter(selectedId => selectedId !== id)
93+
};
94+
}),
95+
96+
setFilters: (newFilters: Partial<NotificationFilters>) =>
97+
set(state => ({
98+
filters: { ...state.filters, ...newFilters },
99+
selectedIds: [] // Clear selection when filters change
100+
})),
101+
102+
toggleSelection: (id: string) =>
103+
set(state => ({
104+
selectedIds: state.selectedIds.includes(id)
105+
? state.selectedIds.filter(selectedId => selectedId !== id)
106+
: [...state.selectedIds, id]
107+
})),
108+
109+
selectAll: () =>
110+
set(state => ({
111+
selectedIds: state.notifications.map(n => n.id)
112+
})),
113+
114+
clearSelection: () =>
115+
set({ selectedIds: [] }),
116+
117+
setLoading: (loading: boolean) =>
118+
set({ loading }),
119+
120+
setError: (error: string | null) =>
121+
set({ error, loading: false }),
122+
123+
setUnreadCount: (unreadCount: number) =>
124+
set({ unreadCount }),
125+
126+
clearNotifications: () =>
127+
set({
128+
notifications: [],
129+
unreadCount: 0,
130+
total: 0,
131+
page: 1,
132+
totalPages: 0,
133+
selectedIds: []
134+
})
135+
}));

app/checkout/page.tsx

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import { SectionTitle } from "@/components";
33
import { useProductStore } from "../_zustand/store";
44
import Image from "next/image";
55
import { useEffect, useState } from "react";
6+
import { useSession } from "next-auth/react";
67
import toast from "react-hot-toast";
78
import { useRouter } from "next/navigation";
89
import apiClient from "@/lib/api";
910

1011
const CheckoutPage = () => {
12+
const { data: session } = useSession();
1113
const [checkoutForm, setCheckoutForm] = useState({
1214
name: "",
1315
lastname: "",
@@ -125,6 +127,24 @@ const CheckoutPage = () => {
125127
try {
126128
console.log("🚀 Starting order creation...");
127129

130+
// Get user ID if logged in
131+
let userId = null;
132+
if (session?.user?.email) {
133+
try {
134+
console.log("🔍 Getting user ID for logged-in user:", session.user.email);
135+
const userResponse = await apiClient.get(`/api/users/email/${session.user.email}`);
136+
if (userResponse.ok) {
137+
const userData = await userResponse.json();
138+
userId = userData.id;
139+
console.log("✅ Found user ID:", userId);
140+
} else {
141+
console.log("❌ Could not find user with email:", session.user.email);
142+
}
143+
} catch (userError) {
144+
console.log("⚠️ Error getting user ID:", userError);
145+
}
146+
}
147+
128148
// Prepare the order data
129149
const orderData = {
130150
name: checkoutForm.name.trim(),
@@ -140,6 +160,7 @@ const CheckoutPage = () => {
140160
city: checkoutForm.city.trim(),
141161
country: checkoutForm.country.trim(),
142162
orderNotice: checkoutForm.orderNotice.trim(),
163+
userId: userId // Add user ID for notifications
143164
};
144165

145166
console.log("📋 Order data being sent:", orderData);
@@ -163,20 +184,29 @@ const CheckoutPage = () => {
163184
const errorData = JSON.parse(errorText);
164185
console.error("Parsed error data:", errorData);
165186

166-
// Show specific validation errors
167-
if (errorData.details && Array.isArray(errorData.details)) {
187+
// Handle different error types
188+
if (response.status === 409) {
189+
// Duplicate order error
190+
toast.error(errorData.details || errorData.error || "Duplicate order detected");
191+
return; // Don't throw, just return to stop execution
192+
} else if (errorData.details && Array.isArray(errorData.details)) {
193+
// Validation errors
168194
errorData.details.forEach((detail: any) => {
169195
toast.error(`${detail.field}: ${detail.message}`);
170196
});
197+
} else if (typeof errorData.details === 'string') {
198+
// Single error message in details
199+
toast.error(errorData.details);
171200
} else {
172-
toast.error(errorData.error || "Validation failed");
201+
// Fallback error message
202+
toast.error(errorData.error || "Order creation failed");
173203
}
174204
} catch (parseError) {
175205
console.error("Could not parse error as JSON:", parseError);
176-
toast.error("Validation failed");
206+
toast.error("Order creation failed. Please try again.");
177207
}
178208

179-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
209+
return; // Stop execution instead of throwing
180210
}
181211

182212
const data = await response.json();
@@ -223,6 +253,14 @@ const CheckoutPage = () => {
223253
});
224254
clearCart();
225255

256+
// Refresh notification count if user is logged in
257+
try {
258+
// This will trigger a refresh of notifications in the background
259+
window.dispatchEvent(new CustomEvent('orderCompleted'));
260+
} catch (error) {
261+
console.log('Note: Could not trigger notification refresh');
262+
}
263+
226264
toast.success("Order created successfully! You will be contacted for payment.");
227265
setTimeout(() => {
228266
router.push("/");

0 commit comments

Comments
 (0)