@akinon/projectzero 1.99.0-rc.70 → 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.
Files changed (64) hide show
  1. package/CHANGELOG.md +5 -240
  2. package/app-template/.env.example +0 -1
  3. package/app-template/CHANGELOG.md +300 -5065
  4. package/app-template/README.md +1 -25
  5. package/app-template/package.json +19 -21
  6. package/app-template/public/locales/en/common.json +1 -48
  7. package/app-template/public/locales/tr/common.json +1 -48
  8. package/app-template/src/app/[commerce]/[locale]/[currency]/basket/page.tsx +82 -9
  9. package/app-template/src/app/[commerce]/[locale]/[currency]/category/[pk]/page.tsx +4 -17
  10. package/app-template/src/app/[commerce]/[locale]/[currency]/flat-page/[pk]/page.tsx +1 -12
  11. package/app-template/src/app/[commerce]/[locale]/[currency]/group-product/[pk]/page.tsx +11 -29
  12. package/app-template/src/app/[commerce]/[locale]/[currency]/landing-page/[pk]/page.tsx +1 -12
  13. package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/page.tsx +10 -28
  14. package/app-template/src/app/[commerce]/[locale]/[currency]/special-page/[pk]/page.tsx +1 -12
  15. package/app-template/src/app/api/form/[...id]/route.ts +7 -1
  16. package/app-template/src/assets/fonts/pz-icon.css +0 -3
  17. package/app-template/src/components/__tests__/link.test.tsx +0 -2
  18. package/app-template/src/components/accordion.tsx +19 -22
  19. package/app-template/src/components/currency-select.tsx +0 -1
  20. package/app-template/src/components/file-input.tsx +7 -27
  21. package/app-template/src/components/generate-form-fields.tsx +4 -43
  22. package/app-template/src/components/input.tsx +2 -9
  23. package/app-template/src/components/modal.tsx +16 -32
  24. package/app-template/src/components/pagination.tsx +0 -1
  25. package/app-template/src/components/select.tsx +26 -38
  26. package/app-template/src/components/types/index.ts +1 -25
  27. package/app-template/src/hooks/index.ts +0 -2
  28. package/app-template/src/plugins.js +1 -3
  29. package/app-template/src/settings.js +2 -8
  30. package/app-template/src/types/index.ts +3 -74
  31. package/app-template/src/views/account/address-form.tsx +4 -8
  32. package/app-template/src/views/account/contact-form.tsx +1 -1
  33. package/app-template/src/views/account/content-header.tsx +2 -2
  34. package/app-template/src/views/account/faq/faq-tabs.tsx +2 -8
  35. package/app-template/src/views/basket/basket-item.tsx +14 -22
  36. package/app-template/src/views/basket/summary.tsx +7 -10
  37. package/app-template/src/views/breadcrumb.tsx +2 -2
  38. package/app-template/src/views/category/category-info.tsx +0 -1
  39. package/app-template/src/views/category/filters/index.tsx +1 -1
  40. package/app-template/src/views/guest-login/index.tsx +1 -6
  41. package/app-template/src/views/header/action-menu.tsx +1 -1
  42. package/app-template/src/views/header/search/index.tsx +5 -17
  43. package/app-template/src/views/login/index.tsx +10 -11
  44. package/app-template/src/views/otp-login/index.tsx +6 -11
  45. package/app-template/src/views/product/product-info.tsx +263 -62
  46. package/app-template/src/views/product/slider.tsx +73 -86
  47. package/app-template/src/views/register/index.tsx +11 -15
  48. package/app-template/src/widgets/footer-menu.tsx +2 -6
  49. package/commands/plugins.ts +16 -63
  50. package/dist/commands/plugins.js +16 -57
  51. package/package.json +1 -1
  52. package/app-template/.github/instructions/account.instructions.md +0 -749
  53. package/app-template/.github/instructions/edge-cases.instructions.md +0 -73
  54. package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/loading.tsx +0 -67
  55. package/app-template/src/app/api/image-proxy/route.ts +0 -1
  56. package/app-template/src/app/api/similar-product-list/route.ts +0 -1
  57. package/app-template/src/app/api/similar-products/route.ts +0 -1
  58. package/app-template/src/hooks/use-product-cart.ts +0 -77
  59. package/app-template/src/hooks/use-stock-alert.ts +0 -74
  60. package/app-template/src/utils/variant-validation.ts +0 -41
  61. package/app-template/src/views/basket/basket-content.tsx +0 -106
  62. package/app-template/src/views/product/product-actions.tsx +0 -165
  63. package/app-template/src/views/product/product-share.tsx +0 -56
  64. 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
- ```