@akinon/projectzero 1.99.0-rc.69 → 1.99.0-snapshot-ZERO-3640-20250919140314
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -238
- package/app-template/.env.example +0 -1
- package/app-template/CHANGELOG.md +304 -5041
- package/app-template/README.md +1 -25
- package/app-template/package.json +19 -21
- package/app-template/public/locales/en/common.json +1 -42
- package/app-template/public/locales/tr/common.json +1 -42
- package/app-template/src/app/[commerce]/[locale]/[currency]/basket/page.tsx +82 -9
- package/app-template/src/app/[commerce]/[locale]/[currency]/category/[pk]/page.tsx +4 -17
- package/app-template/src/app/[commerce]/[locale]/[currency]/flat-page/[pk]/page.tsx +1 -12
- package/app-template/src/app/[commerce]/[locale]/[currency]/group-product/[pk]/page.tsx +11 -29
- package/app-template/src/app/[commerce]/[locale]/[currency]/landing-page/[pk]/page.tsx +1 -12
- package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/page.tsx +10 -28
- package/app-template/src/app/[commerce]/[locale]/[currency]/special-page/[pk]/page.tsx +1 -12
- package/app-template/src/assets/fonts/pz-icon.css +0 -3
- package/app-template/src/components/__tests__/link.test.tsx +0 -2
- package/app-template/src/components/accordion.tsx +19 -22
- package/app-template/src/components/currency-select.tsx +0 -1
- package/app-template/src/components/file-input.tsx +7 -27
- package/app-template/src/components/input.tsx +2 -9
- package/app-template/src/components/modal.tsx +16 -32
- package/app-template/src/components/pagination.tsx +0 -1
- package/app-template/src/components/select.tsx +26 -38
- package/app-template/src/components/types/index.ts +1 -25
- package/app-template/src/hooks/index.ts +0 -2
- package/app-template/src/plugins.js +1 -3
- package/app-template/src/settings.js +2 -8
- package/app-template/src/types/index.ts +3 -74
- package/app-template/src/views/account/address-form.tsx +4 -8
- package/app-template/src/views/account/contact-form.tsx +1 -1
- package/app-template/src/views/account/content-header.tsx +2 -2
- package/app-template/src/views/account/faq/faq-tabs.tsx +2 -8
- package/app-template/src/views/basket/basket-item.tsx +14 -22
- package/app-template/src/views/basket/summary.tsx +7 -10
- package/app-template/src/views/breadcrumb.tsx +2 -2
- package/app-template/src/views/category/category-info.tsx +0 -1
- package/app-template/src/views/category/filters/index.tsx +1 -1
- package/app-template/src/views/guest-login/index.tsx +1 -6
- package/app-template/src/views/header/action-menu.tsx +1 -1
- package/app-template/src/views/header/search/index.tsx +5 -17
- package/app-template/src/views/login/index.tsx +10 -11
- package/app-template/src/views/otp-login/index.tsx +6 -11
- package/app-template/src/views/product/product-info.tsx +263 -62
- package/app-template/src/views/product/slider.tsx +73 -86
- package/app-template/src/views/register/index.tsx +11 -15
- package/app-template/src/widgets/footer-menu.tsx +2 -6
- package/commands/plugins.ts +16 -63
- package/dist/commands/plugins.js +16 -57
- package/package.json +1 -1
- package/app-template/.github/instructions/account.instructions.md +0 -749
- package/app-template/.github/instructions/edge-cases.instructions.md +0 -73
- package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/loading.tsx +0 -67
- package/app-template/src/app/api/image-proxy/route.ts +0 -1
- package/app-template/src/app/api/similar-product-list/route.ts +0 -1
- package/app-template/src/app/api/similar-products/route.ts +0 -1
- package/app-template/src/hooks/use-product-cart.ts +0 -77
- package/app-template/src/hooks/use-stock-alert.ts +0 -74
- package/app-template/src/utils/variant-validation.ts +0 -41
- package/app-template/src/views/basket/basket-content.tsx +0 -106
- package/app-template/src/views/product/product-actions.tsx +0 -165
- package/app-template/src/views/product/product-share.tsx +0 -56
- package/app-template/src/views/product/product-variants.tsx +0 -26
|
@@ -1,749 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
applyTo: 'src/app/[commerce]/[locale]/[currency]/account/**'
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# Account Pages Development Instructions
|
|
6
|
-
|
|
7
|
-
## Overview
|
|
8
|
-
|
|
9
|
-
Account pages in Project Zero Next.js provide e-commerce account functionality using Next.js 14 App Router structure. These instructions apply to files in the `src/app/[commerce]/[locale]/[currency]/account/` directory.
|
|
10
|
-
|
|
11
|
-
## App Router Page Structure
|
|
12
|
-
|
|
13
|
-
### Basic Page Pattern
|
|
14
|
-
|
|
15
|
-
```tsx
|
|
16
|
-
// src/app/[commerce]/[locale]/[currency]/account/page.tsx
|
|
17
|
-
import { Metadata } from 'next';
|
|
18
|
-
|
|
19
|
-
export const metadata: Metadata = {
|
|
20
|
-
title: 'Account | Project Zero',
|
|
21
|
-
description: 'User account page'
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
interface Props {
|
|
25
|
-
params: {
|
|
26
|
-
commerce: string;
|
|
27
|
-
locale: string;
|
|
28
|
-
currency: string;
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export default function AccountPage({ params }: Props) {
|
|
33
|
-
return (
|
|
34
|
-
<div>
|
|
35
|
-
<h1>Account Page</h1>
|
|
36
|
-
{/* Page content */}
|
|
37
|
-
</div>
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### Layout Pattern
|
|
43
|
-
|
|
44
|
-
```tsx
|
|
45
|
-
// src/app/[commerce]/[locale]/[currency]/account/layout.tsx
|
|
46
|
-
interface Props {
|
|
47
|
-
children: React.ReactNode;
|
|
48
|
-
params: {
|
|
49
|
-
commerce: string;
|
|
50
|
-
locale: string;
|
|
51
|
-
currency: string;
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export default function AccountLayout({ children, params }: Props) {
|
|
56
|
-
return <div className="account-layout">{children}</div>;
|
|
57
|
-
}
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Component Import Patterns
|
|
61
|
-
|
|
62
|
-
### Theme Components
|
|
63
|
-
|
|
64
|
-
```tsx
|
|
65
|
-
import { Button, Input, Card } from '@theme/components';
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### Akinon Next Components
|
|
69
|
-
|
|
70
|
-
```tsx
|
|
71
|
-
import { Image } from '@akinon/next/components/image';
|
|
72
|
-
import { Link } from '@akinon/next/components/link';
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## Data Fetching
|
|
76
|
-
|
|
77
|
-
### Client-Side Hooks
|
|
78
|
-
|
|
79
|
-
Hooks used for fetching account data in Project Zero:
|
|
80
|
-
|
|
81
|
-
```tsx
|
|
82
|
-
'use client';
|
|
83
|
-
|
|
84
|
-
import {
|
|
85
|
-
useGetProfileInfoQuery,
|
|
86
|
-
useUpdateProfileMutation,
|
|
87
|
-
useGetOrdersQuery,
|
|
88
|
-
useGetOrderQuery,
|
|
89
|
-
useGetOldOrdersQuery,
|
|
90
|
-
useGetQuotationsQuery,
|
|
91
|
-
useUpdateEmailMutation,
|
|
92
|
-
useUpdatePasswordMutation,
|
|
93
|
-
useSendContactMutation,
|
|
94
|
-
useCancelOrderMutation,
|
|
95
|
-
useBulkCancellationMutation,
|
|
96
|
-
useGetCancellationReasonsQuery,
|
|
97
|
-
useGetContactSubjectsQuery,
|
|
98
|
-
usePasswordResetMutation,
|
|
99
|
-
useGetBasketOffersQuery,
|
|
100
|
-
useGetFutureBasketOffersQuery,
|
|
101
|
-
useGetExpiredBasketOffersQuery,
|
|
102
|
-
useGetDiscountItemsQuery,
|
|
103
|
-
useAnonymizeMutation,
|
|
104
|
-
useGetLoyaltyBalanceQuery,
|
|
105
|
-
useGetLoyaltyTransactionsQuery
|
|
106
|
-
} from '@akinon/next/data/client/account';
|
|
107
|
-
|
|
108
|
-
// User profile information
|
|
109
|
-
const UserProfile = () => {
|
|
110
|
-
const { data: profile, isLoading, error } = useGetProfileInfoQuery();
|
|
111
|
-
|
|
112
|
-
if (isLoading) return <div>Loading...</div>;
|
|
113
|
-
if (error) return <div>Error occurred</div>;
|
|
114
|
-
|
|
115
|
-
return <div>Welcome, {profile?.first_name}</div>;
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
// Orders - with pagination support
|
|
119
|
-
const OrdersList = () => {
|
|
120
|
-
const { data: orders, isLoading } = useGetOrdersQuery({
|
|
121
|
-
page: 1,
|
|
122
|
-
limit: 10
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
return (
|
|
126
|
-
<div>
|
|
127
|
-
<h2>Orders ({orders?.count})</h2>
|
|
128
|
-
{orders?.results?.map((order) => (
|
|
129
|
-
<div key={order.id}>
|
|
130
|
-
<span>Order #{order.number}</span>
|
|
131
|
-
<span>{order.status}</span>
|
|
132
|
-
<span>{order.total} USD</span>
|
|
133
|
-
</div>
|
|
134
|
-
))}
|
|
135
|
-
</div>
|
|
136
|
-
);
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
// Order detail
|
|
140
|
-
const OrderDetail = ({ orderId }) => {
|
|
141
|
-
const { data: order, isLoading, error } = useGetOrderQuery(orderId);
|
|
142
|
-
|
|
143
|
-
if (isLoading) return <div>Loading order...</div>;
|
|
144
|
-
if (error) return <div>Order not found</div>;
|
|
145
|
-
|
|
146
|
-
return (
|
|
147
|
-
<div>
|
|
148
|
-
<h1>Order #{order?.number}</h1>
|
|
149
|
-
<p>Status: {order?.status}</p>
|
|
150
|
-
<p>Total: {order?.total} USD</p>
|
|
151
|
-
{/* Order products and details */}
|
|
152
|
-
</div>
|
|
153
|
-
);
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
// Old orders
|
|
157
|
-
const OldOrdersList = () => {
|
|
158
|
-
const { data: oldOrders, isLoading } = useGetOldOrdersQuery({
|
|
159
|
-
page: 1,
|
|
160
|
-
limit: 10
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
if (isLoading) return <div>Loading...</div>;
|
|
164
|
-
|
|
165
|
-
return (
|
|
166
|
-
<div>
|
|
167
|
-
<h2>Order History</h2>
|
|
168
|
-
{oldOrders?.results?.map((order) => (
|
|
169
|
-
<div key={order.id}>
|
|
170
|
-
<span>#{order.number}</span>
|
|
171
|
-
<span>{new Date(order.created_date).toLocaleDateString()}</span>
|
|
172
|
-
</div>
|
|
173
|
-
))}
|
|
174
|
-
</div>
|
|
175
|
-
);
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
// B2B Quotations (if available)
|
|
179
|
-
const QuotationsList = () => {
|
|
180
|
-
const { data: quotations, isLoading } = useGetQuotationsQuery({
|
|
181
|
-
page: 1,
|
|
182
|
-
limit: 10
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
if (isLoading) return <div>Loading quotations...</div>;
|
|
186
|
-
|
|
187
|
-
return (
|
|
188
|
-
<div>
|
|
189
|
-
<h2>Active Quotations</h2>
|
|
190
|
-
{quotations?.results?.map((quotation) => (
|
|
191
|
-
<div key={quotation.id}>
|
|
192
|
-
<h3>{quotation.title}</h3>
|
|
193
|
-
<p>Status: {quotation.status}</p>
|
|
194
|
-
</div>
|
|
195
|
-
))}
|
|
196
|
-
</div>
|
|
197
|
-
);
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
// Loyalty points (if available)
|
|
201
|
-
const LoyaltyInfo = () => {
|
|
202
|
-
const { data: balance, isLoading: balanceLoading } =
|
|
203
|
-
useGetLoyaltyBalanceQuery();
|
|
204
|
-
const { data: transactions, isLoading: transactionsLoading } =
|
|
205
|
-
useGetLoyaltyTransactionsQuery();
|
|
206
|
-
|
|
207
|
-
if (balanceLoading || transactionsLoading) return <div>Loading...</div>;
|
|
208
|
-
|
|
209
|
-
return (
|
|
210
|
-
<div>
|
|
211
|
-
<h2>Loyalty Points</h2>
|
|
212
|
-
<p>Current Balance: {balance?.balance || 0} points</p>
|
|
213
|
-
|
|
214
|
-
<h3>Recent Transactions</h3>
|
|
215
|
-
<div>
|
|
216
|
-
{transactions?.results?.map((transaction, index) => (
|
|
217
|
-
<div key={index} className="border-b py-2">
|
|
218
|
-
<span>{transaction.amount} points</span>
|
|
219
|
-
<span className="text-gray-500 ml-2">
|
|
220
|
-
{new Date(transaction.created_date).toLocaleDateString()}
|
|
221
|
-
</span>
|
|
222
|
-
</div>
|
|
223
|
-
))}
|
|
224
|
-
</div>
|
|
225
|
-
</div>
|
|
226
|
-
);
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
// Basket offers
|
|
230
|
-
const BasketOffers = () => {
|
|
231
|
-
const { data: currentOffers } = useGetBasketOffersQuery();
|
|
232
|
-
const { data: futureOffers } = useGetFutureBasketOffersQuery();
|
|
233
|
-
const { data: expiredOffers } = useGetExpiredBasketOffersQuery();
|
|
234
|
-
|
|
235
|
-
return (
|
|
236
|
-
<div>
|
|
237
|
-
<h2>Basket Offers</h2>
|
|
238
|
-
|
|
239
|
-
{currentOffers && (
|
|
240
|
-
<div>
|
|
241
|
-
<h3>Active Offers</h3>
|
|
242
|
-
{/* Offer list */}
|
|
243
|
-
</div>
|
|
244
|
-
)}
|
|
245
|
-
|
|
246
|
-
{futureOffers && (
|
|
247
|
-
<div>
|
|
248
|
-
<h3>Future Offers</h3>
|
|
249
|
-
{/* Future offers */}
|
|
250
|
-
</div>
|
|
251
|
-
)}
|
|
252
|
-
</div>
|
|
253
|
-
);
|
|
254
|
-
};
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
### Mutation Hooks - Correct Usage Patterns
|
|
258
|
-
|
|
259
|
-
```tsx
|
|
260
|
-
'use client';
|
|
261
|
-
|
|
262
|
-
const ProfileForm = () => {
|
|
263
|
-
const [updateProfile, { isLoading }] = useUpdateProfileMutation();
|
|
264
|
-
const [updateEmail, { isLoading: emailLoading }] = useUpdateEmailMutation();
|
|
265
|
-
const [updatePassword, { isLoading: passwordLoading }] =
|
|
266
|
-
useUpdatePasswordMutation();
|
|
267
|
-
|
|
268
|
-
const handleUpdateProfile = async (formData) => {
|
|
269
|
-
try {
|
|
270
|
-
const result = await updateProfile({
|
|
271
|
-
first_name: formData.firstName,
|
|
272
|
-
last_name: formData.lastName,
|
|
273
|
-
phone: formData.phone,
|
|
274
|
-
date_of_birth: formData.birthDate,
|
|
275
|
-
gender: formData.gender,
|
|
276
|
-
sms_allowed: formData.smsAllowed,
|
|
277
|
-
email_allowed: formData.emailAllowed
|
|
278
|
-
}).unwrap();
|
|
279
|
-
|
|
280
|
-
// Success handling
|
|
281
|
-
console.log('Profile updated:', result);
|
|
282
|
-
} catch (error) {
|
|
283
|
-
// Error handling
|
|
284
|
-
console.error('Profile update error:', error);
|
|
285
|
-
}
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
const handleChangeEmail = async (emailData) => {
|
|
289
|
-
try {
|
|
290
|
-
await updateEmail({
|
|
291
|
-
new_email: emailData.newEmail,
|
|
292
|
-
current_password: emailData.currentPassword
|
|
293
|
-
}).unwrap();
|
|
294
|
-
} catch (error) {
|
|
295
|
-
console.error('Email change error:', error);
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
const handleChangePassword = async (passwordData) => {
|
|
300
|
-
try {
|
|
301
|
-
await updatePassword({
|
|
302
|
-
old_password: passwordData.oldPassword,
|
|
303
|
-
new_password1: passwordData.newPassword,
|
|
304
|
-
new_password2: passwordData.confirmPassword
|
|
305
|
-
}).unwrap();
|
|
306
|
-
} catch (error) {
|
|
307
|
-
console.error('Password change error:', error);
|
|
308
|
-
}
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
return (
|
|
312
|
-
<div className="space-y-6">
|
|
313
|
-
{/* Profile form */}
|
|
314
|
-
<button
|
|
315
|
-
onClick={handleUpdateProfile}
|
|
316
|
-
disabled={isLoading}
|
|
317
|
-
className="bg-blue-500 text-white p-2 rounded disabled:opacity-50"
|
|
318
|
-
>
|
|
319
|
-
{isLoading ? 'Saving...' : 'Update Profile'}
|
|
320
|
-
</button>
|
|
321
|
-
</div>
|
|
322
|
-
);
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
// Order cancellation
|
|
326
|
-
const OrderActions = ({ orderId }) => {
|
|
327
|
-
const [cancelOrder, { isLoading }] = useCancelOrderMutation();
|
|
328
|
-
const { data: cancellationReasons } = useGetCancellationReasonsQuery();
|
|
329
|
-
|
|
330
|
-
const handleCancelOrder = async (reasonId) => {
|
|
331
|
-
try {
|
|
332
|
-
await cancelOrder({
|
|
333
|
-
id: orderId,
|
|
334
|
-
reason: reasonId
|
|
335
|
-
}).unwrap();
|
|
336
|
-
|
|
337
|
-
// Show success message
|
|
338
|
-
} catch (error) {
|
|
339
|
-
console.error('Order cancellation error:', error);
|
|
340
|
-
}
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
return (
|
|
344
|
-
<div>
|
|
345
|
-
<h3>Cancel Order</h3>
|
|
346
|
-
{cancellationReasons?.map((reason) => (
|
|
347
|
-
<button
|
|
348
|
-
key={reason.id}
|
|
349
|
-
onClick={() => handleCancelOrder(reason.id)}
|
|
350
|
-
disabled={isLoading}
|
|
351
|
-
className="block w-full text-left p-2 hover:bg-gray-100 disabled:opacity-50"
|
|
352
|
-
>
|
|
353
|
-
{reason.text}
|
|
354
|
-
</button>
|
|
355
|
-
))}
|
|
356
|
-
</div>
|
|
357
|
-
);
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
// Contact form
|
|
361
|
-
const ContactForm = () => {
|
|
362
|
-
const [sendContact, { isLoading }] = useSendContactMutation();
|
|
363
|
-
const { data: subjects } = useGetContactSubjectsQuery();
|
|
364
|
-
|
|
365
|
-
const handleSubmit = async (formData) => {
|
|
366
|
-
try {
|
|
367
|
-
const form = new FormData();
|
|
368
|
-
form.append('subject', formData.subject);
|
|
369
|
-
form.append('message', formData.message);
|
|
370
|
-
form.append('order_number', formData.orderNumber || '');
|
|
371
|
-
|
|
372
|
-
if (formData.attachment) {
|
|
373
|
-
form.append('attachment', formData.attachment);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
await sendContact(form).unwrap();
|
|
377
|
-
|
|
378
|
-
// Success message
|
|
379
|
-
} catch (error) {
|
|
380
|
-
console.error('Contact form submission error:', error);
|
|
381
|
-
}
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
return (
|
|
385
|
-
<form onSubmit={handleSubmit}>
|
|
386
|
-
<select name="subject" required>
|
|
387
|
-
<option value="">Select subject</option>
|
|
388
|
-
{subjects?.map((subject) => (
|
|
389
|
-
<option key={subject.id} value={subject.id}>
|
|
390
|
-
{subject.text}
|
|
391
|
-
</option>
|
|
392
|
-
))}
|
|
393
|
-
</select>
|
|
394
|
-
|
|
395
|
-
<textarea name="message" required placeholder="Your message" />
|
|
396
|
-
|
|
397
|
-
<input type="file" name="attachment" />
|
|
398
|
-
|
|
399
|
-
<button
|
|
400
|
-
type="submit"
|
|
401
|
-
disabled={isLoading}
|
|
402
|
-
className="bg-blue-500 text-white p-2 rounded disabled:opacity-50"
|
|
403
|
-
>
|
|
404
|
-
{isLoading ? 'Sending...' : 'Send'}
|
|
405
|
-
</button>
|
|
406
|
-
</form>
|
|
407
|
-
);
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
// Account anonymization (GDPR)
|
|
411
|
-
const AccountSettings = () => {
|
|
412
|
-
const [anonymize, { isLoading }] = useAnonymizeMutation();
|
|
413
|
-
|
|
414
|
-
const handleAnonymize = async () => {
|
|
415
|
-
if (confirm('Are you sure you want to permanently delete your account?')) {
|
|
416
|
-
try {
|
|
417
|
-
const result = await anonymize().unwrap();
|
|
418
|
-
|
|
419
|
-
// Redirect user to logout page
|
|
420
|
-
window.location.href = '/logout';
|
|
421
|
-
} catch (error) {
|
|
422
|
-
console.error('Account anonymization error:', error);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
};
|
|
426
|
-
|
|
427
|
-
return (
|
|
428
|
-
<div className="bg-red-50 p-4 rounded border border-red-200">
|
|
429
|
-
<h3 className="text-red-800 font-semibold">Delete Account</h3>
|
|
430
|
-
<p className="text-red-700 text-sm mb-4">
|
|
431
|
-
This action cannot be undone. All your data will be permanently deleted.
|
|
432
|
-
</p>
|
|
433
|
-
<button
|
|
434
|
-
onClick={handleAnonymize}
|
|
435
|
-
disabled={isLoading}
|
|
436
|
-
className="bg-red-500 text-white px-4 py-2 rounded disabled:opacity-50"
|
|
437
|
-
>
|
|
438
|
-
{isLoading ? 'Deleting...' : 'Delete Account'}
|
|
439
|
-
</button>
|
|
440
|
-
</div>
|
|
441
|
-
);
|
|
442
|
-
};
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
## State Management
|
|
446
|
-
|
|
447
|
-
### Redux Store
|
|
448
|
-
|
|
449
|
-
```tsx
|
|
450
|
-
'use client';
|
|
451
|
-
|
|
452
|
-
import { useAppSelector, useAppDispatch } from '@akinon/next/redux/store';
|
|
453
|
-
|
|
454
|
-
const AccountComponent = () => {
|
|
455
|
-
const dispatch = useAppDispatch();
|
|
456
|
-
const { user, isLoading } = useAppSelector((state) => state.account);
|
|
457
|
-
|
|
458
|
-
// Redux actions
|
|
459
|
-
const handleUpdate = () => {
|
|
460
|
-
dispatch(/* account action */);
|
|
461
|
-
};
|
|
462
|
-
|
|
463
|
-
return <div>{/* Component */}</div>;
|
|
464
|
-
};
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
## Form Handling
|
|
468
|
-
|
|
469
|
-
### React Hook Form
|
|
470
|
-
|
|
471
|
-
```tsx
|
|
472
|
-
'use client';
|
|
473
|
-
|
|
474
|
-
import { useForm } from 'react-hook-form';
|
|
475
|
-
import { yupResolver } from '@hookform/resolvers/yup';
|
|
476
|
-
import * as yup from 'yup';
|
|
477
|
-
import { useLocalization } from '@akinon/next/hooks/useLocalization';
|
|
478
|
-
|
|
479
|
-
const profileSchema = yup.object({
|
|
480
|
-
first_name: yup.string().required('First name is required'),
|
|
481
|
-
last_name: yup.string().required('Last name is required'),
|
|
482
|
-
phone: yup.string().required('Phone is required'),
|
|
483
|
-
date_of_birth: yup.date().nullable(),
|
|
484
|
-
gender: yup.string(),
|
|
485
|
-
sms_allowed: yup.boolean(),
|
|
486
|
-
email_allowed: yup.boolean()
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
const ProfileForm = () => {
|
|
490
|
-
const { t } = useLocalization();
|
|
491
|
-
const [updateProfile, { isLoading }] = useUpdateProfileMutation();
|
|
492
|
-
|
|
493
|
-
const {
|
|
494
|
-
register,
|
|
495
|
-
handleSubmit,
|
|
496
|
-
formState: { errors }
|
|
497
|
-
} = useForm({
|
|
498
|
-
resolver: yupResolver(profileSchema)
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
const onSubmit = async (data) => {
|
|
502
|
-
try {
|
|
503
|
-
await updateProfile(data).unwrap();
|
|
504
|
-
// Success message
|
|
505
|
-
} catch (error) {
|
|
506
|
-
// Error handling
|
|
507
|
-
}
|
|
508
|
-
};
|
|
509
|
-
|
|
510
|
-
return (
|
|
511
|
-
<form onSubmit={handleSubmit(onSubmit)}>
|
|
512
|
-
<input
|
|
513
|
-
{...register('first_name')}
|
|
514
|
-
placeholder="First Name"
|
|
515
|
-
className="border p-2 rounded"
|
|
516
|
-
/>
|
|
517
|
-
{errors.first_name && (
|
|
518
|
-
<span className="text-red-500">{errors.first_name.message}</span>
|
|
519
|
-
)}
|
|
520
|
-
|
|
521
|
-
<input
|
|
522
|
-
{...register('last_name')}
|
|
523
|
-
placeholder="Last Name"
|
|
524
|
-
className="border p-2 rounded"
|
|
525
|
-
/>
|
|
526
|
-
{errors.last_name && (
|
|
527
|
-
<span className="text-red-500">{errors.last_name.message}</span>
|
|
528
|
-
)}
|
|
529
|
-
|
|
530
|
-
<button
|
|
531
|
-
type="submit"
|
|
532
|
-
disabled={isLoading}
|
|
533
|
-
className="bg-blue-500 text-white p-2 rounded"
|
|
534
|
-
>
|
|
535
|
-
{isLoading ? 'Saving...' : 'Save'}
|
|
536
|
-
</button>
|
|
537
|
-
</form>
|
|
538
|
-
);
|
|
539
|
-
};
|
|
540
|
-
```
|
|
541
|
-
|
|
542
|
-
## Styling
|
|
543
|
-
|
|
544
|
-
### TailwindCSS
|
|
545
|
-
|
|
546
|
-
```tsx
|
|
547
|
-
const AccountPage = () => {
|
|
548
|
-
return (
|
|
549
|
-
<div className="container mx-auto px-4 py-8">
|
|
550
|
-
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
551
|
-
<div className="bg-white rounded-lg shadow p-6">
|
|
552
|
-
<h2 className="text-xl font-semibold mb-4">Profile</h2>
|
|
553
|
-
{/* Content */}
|
|
554
|
-
</div>
|
|
555
|
-
</div>
|
|
556
|
-
</div>
|
|
557
|
-
);
|
|
558
|
-
};
|
|
559
|
-
```
|
|
560
|
-
|
|
561
|
-
### Conditional Classes
|
|
562
|
-
|
|
563
|
-
```tsx
|
|
564
|
-
import { clsx } from 'clsx';
|
|
565
|
-
|
|
566
|
-
const StatusBadge = ({ status }) => {
|
|
567
|
-
return (
|
|
568
|
-
<span
|
|
569
|
-
className={clsx('px-2 py-1 rounded text-sm', {
|
|
570
|
-
'bg-green-100 text-green-800': status === 'completed',
|
|
571
|
-
'bg-yellow-100 text-yellow-800': status === 'pending',
|
|
572
|
-
'bg-red-100 text-red-800': status === 'cancelled'
|
|
573
|
-
})}
|
|
574
|
-
>
|
|
575
|
-
{status}
|
|
576
|
-
</span>
|
|
577
|
-
);
|
|
578
|
-
};
|
|
579
|
-
```
|
|
580
|
-
|
|
581
|
-
## Localization
|
|
582
|
-
|
|
583
|
-
### Usage
|
|
584
|
-
|
|
585
|
-
```tsx
|
|
586
|
-
import { useLocalization } from '@akinon/next/hooks/useLocalization';
|
|
587
|
-
|
|
588
|
-
const AccountComponent = () => {
|
|
589
|
-
const { t } = useLocalization();
|
|
590
|
-
|
|
591
|
-
return (
|
|
592
|
-
<div>
|
|
593
|
-
<h1>{t('account.title')}</h1>
|
|
594
|
-
<p>{t('account.welcome_message', { name: 'User' })}</p>
|
|
595
|
-
</div>
|
|
596
|
-
);
|
|
597
|
-
};
|
|
598
|
-
```
|
|
599
|
-
|
|
600
|
-
## Error Handling
|
|
601
|
-
|
|
602
|
-
### API Errors
|
|
603
|
-
|
|
604
|
-
```tsx
|
|
605
|
-
const AccountComponent = () => {
|
|
606
|
-
const { data, error, isLoading } = useGetProfileInfoQuery();
|
|
607
|
-
|
|
608
|
-
if (error) {
|
|
609
|
-
return (
|
|
610
|
-
<div className="bg-red-50 border border-red-200 rounded p-4">
|
|
611
|
-
<p className="text-red-700">
|
|
612
|
-
{error.data?.message || 'An error occurred'}
|
|
613
|
-
</p>
|
|
614
|
-
</div>
|
|
615
|
-
);
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
if (isLoading) {
|
|
619
|
-
return <div className="animate-pulse">Loading...</div>;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
return <div>{/* Normal content */}</div>;
|
|
623
|
-
};
|
|
624
|
-
```
|
|
625
|
-
|
|
626
|
-
## Performance
|
|
627
|
-
|
|
628
|
-
### Loading States
|
|
629
|
-
|
|
630
|
-
```tsx
|
|
631
|
-
const LoadingSkeleton = () => (
|
|
632
|
-
<div className="animate-pulse space-y-4">
|
|
633
|
-
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
|
|
634
|
-
<div className="h-4 bg-gray-200 rounded w-1/2"></div>
|
|
635
|
-
<div className="h-32 bg-gray-200 rounded"></div>
|
|
636
|
-
</div>
|
|
637
|
-
);
|
|
638
|
-
```
|
|
639
|
-
|
|
640
|
-
### Lazy Loading
|
|
641
|
-
|
|
642
|
-
```tsx
|
|
643
|
-
import { LazyComponent } from '@akinon/next/components/lazy-component';
|
|
644
|
-
|
|
645
|
-
const LazyOrderHistory = LazyComponent(() => import('./OrderHistory'));
|
|
646
|
-
```
|
|
647
|
-
|
|
648
|
-
## Best Practices
|
|
649
|
-
|
|
650
|
-
### TypeScript
|
|
651
|
-
|
|
652
|
-
```tsx
|
|
653
|
-
interface User {
|
|
654
|
-
id: number;
|
|
655
|
-
first_name: string;
|
|
656
|
-
last_name: string;
|
|
657
|
-
email: string;
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
interface Order {
|
|
661
|
-
id: number;
|
|
662
|
-
number: string;
|
|
663
|
-
status: string;
|
|
664
|
-
total: number;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
interface Address {
|
|
668
|
-
id: number;
|
|
669
|
-
title: string;
|
|
670
|
-
address: string;
|
|
671
|
-
city: string;
|
|
672
|
-
district: string;
|
|
673
|
-
}
|
|
674
|
-
```
|
|
675
|
-
|
|
676
|
-
### File Organization
|
|
677
|
-
|
|
678
|
-
```
|
|
679
|
-
account/
|
|
680
|
-
├── page.tsx # Main account page
|
|
681
|
-
├── layout.tsx # Layout (optional)
|
|
682
|
-
├── profile/
|
|
683
|
-
│ └── page.tsx # Profile page
|
|
684
|
-
├── orders/
|
|
685
|
-
│ ├── page.tsx # Orders list
|
|
686
|
-
│ └── [id]/
|
|
687
|
-
│ └── page.tsx # Order detail
|
|
688
|
-
└── addresses/
|
|
689
|
-
├── page.tsx # Address list
|
|
690
|
-
└── new/
|
|
691
|
-
└── page.tsx # New address
|
|
692
|
-
```
|
|
693
|
-
|
|
694
|
-
---
|
|
695
|
-
|
|
696
|
-
**Note:** These instructions are based on Project Zero's existing API and hook structures. Follow existing patterns when adding new features.
|
|
697
|
-
|
|
698
|
-
## Important Notes
|
|
699
|
-
|
|
700
|
-
### Hook Usage Rules
|
|
701
|
-
|
|
702
|
-
1. **Query hooks** run automatically, no manual triggering required
|
|
703
|
-
2. **Mutation hooks** return array destructuring with `[mutationFn, { isLoading, error }]`
|
|
704
|
-
3. Use **unwrap()** for promise-based error handling
|
|
705
|
-
4. **isLoading** state should be used in UI for loading states
|
|
706
|
-
|
|
707
|
-
### Error Handling Best Practices
|
|
708
|
-
|
|
709
|
-
```tsx
|
|
710
|
-
// RTK Query error handling
|
|
711
|
-
const { data, error, isLoading } = useGetOrdersQuery();
|
|
712
|
-
|
|
713
|
-
if (error) {
|
|
714
|
-
// RTK Query error structure
|
|
715
|
-
const errorMessage =
|
|
716
|
-
error.data?.message || error.error || 'An error occurred';
|
|
717
|
-
return <div className="text-red-500">{errorMessage}</div>;
|
|
718
|
-
}
|
|
719
|
-
```
|
|
720
|
-
|
|
721
|
-
### Conditional Rendering
|
|
722
|
-
|
|
723
|
-
```tsx
|
|
724
|
-
// Show alternative content when no data
|
|
725
|
-
const OrdersList = () => {
|
|
726
|
-
const { data: orders, isLoading } = useGetOrdersQuery();
|
|
727
|
-
|
|
728
|
-
if (isLoading) return <LoadingSkeleton />;
|
|
729
|
-
|
|
730
|
-
if (!orders?.results?.length) {
|
|
731
|
-
return (
|
|
732
|
-
<div className="text-center py-8">
|
|
733
|
-
<p>You don't have any orders yet.</p>
|
|
734
|
-
<Link href="/products" className="text-blue-500">
|
|
735
|
-
Start shopping
|
|
736
|
-
</Link>
|
|
737
|
-
</div>
|
|
738
|
-
);
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
return (
|
|
742
|
-
<div>
|
|
743
|
-
{orders.results.map((order) => (
|
|
744
|
-
<OrderCard key={order.id} order={order} />
|
|
745
|
-
))}
|
|
746
|
-
</div>
|
|
747
|
-
);
|
|
748
|
-
};
|
|
749
|
-
```
|