@akinon/projectzero 1.100.0-rc.72 → 1.100.0
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 -234
- package/app-template/.env.example +0 -1
- package/app-template/CHANGELOG.md +333 -4991
- package/app-template/README.md +1 -25
- package/app-template/package.json +19 -21
- package/app-template/public/locales/en/common.json +1 -48
- package/app-template/public/locales/tr/common.json +1 -48
- 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/app/api/form/[...id]/route.ts +7 -1
- 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/generate-form-fields.tsx +4 -43
- 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/redux/store.ts +21 -1
- package/app-template/src/settings.js +2 -8
- package/app-template/src/types/index.ts +0 -17
- 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/product/product-info.tsx +263 -62
- package/app-template/src/views/product/slider.tsx +73 -86
- 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/routing.instructions.md +0 -603
- 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,603 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
applyTo: 'src/app/**'
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# App Router & Routing Guidelines
|
|
6
|
-
|
|
7
|
-
## Route Structure
|
|
8
|
-
|
|
9
|
-
Project Zero uses App Router with dynamic segments:
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
src/app/[commerce]/[locale]/[currency]/
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
**Dynamic Parameters:**
|
|
16
|
-
|
|
17
|
-
- `[commerce]` - Commerce instance (configured in settings.js)
|
|
18
|
-
- `[locale]` - Language code (e.g., 'tr-TR', 'en-US')
|
|
19
|
-
- `[currency]` - Currency code (e.g., 'TRY', 'USD', 'EUR')
|
|
20
|
-
|
|
21
|
-
## File Structure
|
|
22
|
-
|
|
23
|
-
### Required Files
|
|
24
|
-
|
|
25
|
-
- `page.tsx` - Page component (required for each route)
|
|
26
|
-
- `layout.tsx` - Layout wrapper (optional, inherits from parent)
|
|
27
|
-
- `loading.tsx` - Loading UI (optional)
|
|
28
|
-
- `error.tsx` - Error boundary (optional)
|
|
29
|
-
- `not-found.tsx` - 404 page (optional, custom implementation in `pz-not-found/`)
|
|
30
|
-
|
|
31
|
-
### Route Organization
|
|
32
|
-
|
|
33
|
-
```
|
|
34
|
-
app/[commerce]/[locale]/[currency]/
|
|
35
|
-
├── layout.tsx # Root layout with withSegmentDefaults
|
|
36
|
-
├── template.tsx # Root template with client-side logic
|
|
37
|
-
├── client-root.tsx # Client root component
|
|
38
|
-
├── page.tsx # Homepage with widget system
|
|
39
|
-
├── error.tsx # Error boundary with Sentry integration
|
|
40
|
-
├── [...prettyurl]/ # Pretty URL handler for legacy routes
|
|
41
|
-
│ └── page.tsx # Dynamic route resolver
|
|
42
|
-
├── category/ # Category routes
|
|
43
|
-
│ ├── [pk]/ # Category by ID
|
|
44
|
-
│ │ ├── page.tsx # Category page
|
|
45
|
-
│ │ └── loading.tsx # Category loading state
|
|
46
|
-
│ └── [...slug]/ # Category by slug (empty directory)
|
|
47
|
-
├── product/ # Product routes
|
|
48
|
-
│ ├── [pk]/ # Product by ID
|
|
49
|
-
│ │ └── page.tsx # Product page with metadata
|
|
50
|
-
│ └── [slug]/ # Product by slug (empty directory)
|
|
51
|
-
├── group-product/ # Group product routes
|
|
52
|
-
│ └── [pk]/
|
|
53
|
-
│ ├── page.tsx # Group product page
|
|
54
|
-
│ └── loading.tsx # Group product loading state
|
|
55
|
-
├── special-page/ # Special page routes
|
|
56
|
-
│ └── [pk]/
|
|
57
|
-
│ ├── page.tsx # Special page with widgets
|
|
58
|
-
│ └── loading.tsx # Special page loading state
|
|
59
|
-
├── flat-page/ # Flat/CMS page routes
|
|
60
|
-
│ └── [pk]/
|
|
61
|
-
│ ├── page.tsx # Flat page with HTML content
|
|
62
|
-
│ └── loading.tsx # Flat page loading state
|
|
63
|
-
├── landing-page/ # Landing page routes
|
|
64
|
-
│ └── [pk]/
|
|
65
|
-
│ ├── page.tsx # Landing page
|
|
66
|
-
│ └── loading.tsx # Landing page loading state
|
|
67
|
-
├── account/ # Account section
|
|
68
|
-
│ ├── layout.tsx # Account layout with authentication
|
|
69
|
-
│ ├── page.tsx # Account dashboard
|
|
70
|
-
│ ├── orders/ # Order management
|
|
71
|
-
│ │ └── [id]/ # Order detail
|
|
72
|
-
│ │ ├── layout.tsx # Order layout
|
|
73
|
-
│ │ ├── page.tsx # Order details
|
|
74
|
-
│ │ └── cancellation/ # Order cancellation
|
|
75
|
-
│ ├── profile/page.tsx # User profile
|
|
76
|
-
│ ├── address/page.tsx # Address management
|
|
77
|
-
│ ├── change-email/page.tsx # Email change
|
|
78
|
-
│ ├── change-password/page.tsx # Password change
|
|
79
|
-
│ ├── contact/page.tsx # Contact form
|
|
80
|
-
│ ├── coupons/page.tsx # Coupon management
|
|
81
|
-
│ ├── email-verification/page.tsx # Email verification
|
|
82
|
-
│ ├── faq/page.tsx # FAQ page
|
|
83
|
-
│ ├── favourite-products/page.tsx # Wishlist
|
|
84
|
-
│ └── my-quotations/page.tsx # Quotations
|
|
85
|
-
├── basket/ # Shopping basket
|
|
86
|
-
│ └── page.tsx # Basket page
|
|
87
|
-
├── basket-b2b/ # B2B basket
|
|
88
|
-
│ └── page.tsx # B2B basket page
|
|
89
|
-
├── orders/ # Order flow
|
|
90
|
-
│ ├── checkout/page.tsx # Checkout process
|
|
91
|
-
│ └── completed/[token]/ # Order completion
|
|
92
|
-
│ ├── layout.tsx # Completion layout
|
|
93
|
-
│ └── page.tsx # Completion page
|
|
94
|
-
├── auth/ # Authentication
|
|
95
|
-
│ ├── page.tsx # Login page
|
|
96
|
-
│ └── oauth-login/page.tsx # OAuth login
|
|
97
|
-
├── users/ # User management
|
|
98
|
-
│ ├── password/reset/page.tsx # Password reset
|
|
99
|
-
│ ├── email-set-primary/[[...id]]/page.tsx # Primary email
|
|
100
|
-
│ ├── registration/account-confirm-email/[[...id]]/page.tsx # Email confirmation
|
|
101
|
-
│ └── reset/[[...id]]/page.tsx # Account reset
|
|
102
|
-
├── list/page.tsx # Product listing/search
|
|
103
|
-
├── contact-us/page.tsx # Contact page
|
|
104
|
-
├── anonymous-tracking/page.tsx # Anonymous order tracking
|
|
105
|
-
├── address/stores/page.tsx # Store locator
|
|
106
|
-
├── forms/[pk]/generate/page.tsx # Form generation
|
|
107
|
-
├── pz-not-found/page.tsx # Custom 404 page
|
|
108
|
-
└── xml-sitemap/ # Sitemap generation
|
|
109
|
-
├── route.ts # Main sitemap
|
|
110
|
-
└── [node]/route.ts # Node-specific sitemap
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## Route Parameters
|
|
114
|
-
|
|
115
|
-
### Server Components
|
|
116
|
-
|
|
117
|
-
```tsx
|
|
118
|
-
import { PageProps } from '@akinon/next/types';
|
|
119
|
-
|
|
120
|
-
// For pages with additional parameters (e.g., pk for product/category)
|
|
121
|
-
export default function Page({
|
|
122
|
-
params,
|
|
123
|
-
searchParams
|
|
124
|
-
}: PageProps<{ pk: number }>) {
|
|
125
|
-
const { commerce, locale, currency, pk } = params;
|
|
126
|
-
// Component logic
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// For pages with slug parameters
|
|
130
|
-
export default function Page({
|
|
131
|
-
params,
|
|
132
|
-
searchParams
|
|
133
|
-
}: PageProps<{ slug: string }>) {
|
|
134
|
-
const { commerce, locale, currency, slug } = params;
|
|
135
|
-
// Component logic
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// For pages with prettyurl parameters
|
|
139
|
-
export default function Page({ params }: PageProps) {
|
|
140
|
-
const { commerce, locale, currency, prettyurl } = params;
|
|
141
|
-
// Component logic
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
**Note:** The `PageProps` interface from `@akinon/next/types` automatically includes:
|
|
146
|
-
|
|
147
|
-
- `params: { locale: string; currency: string } & T` (where T is your custom params)
|
|
148
|
-
- `searchParams: URLSearchParams`
|
|
149
|
-
|
|
150
|
-
### Client Components
|
|
151
|
-
|
|
152
|
-
```tsx
|
|
153
|
-
'use client';
|
|
154
|
-
import { useParams, useSearchParams } from 'next/navigation';
|
|
155
|
-
|
|
156
|
-
export default function ClientComponent() {
|
|
157
|
-
const params = useParams();
|
|
158
|
-
const searchParams = useSearchParams();
|
|
159
|
-
|
|
160
|
-
const { commerce, locale, currency } = params;
|
|
161
|
-
// Component logic
|
|
162
|
-
}
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
## Route Constants
|
|
166
|
-
|
|
167
|
-
Route patterns are defined in `src/routes/index.ts`. Use these constants instead of hardcoding URLs:
|
|
168
|
-
|
|
169
|
-
```tsx
|
|
170
|
-
import { ROUTES } from '@theme/routes';
|
|
171
|
-
|
|
172
|
-
// General routes
|
|
173
|
-
ROUTES.HOME; // '/'
|
|
174
|
-
ROUTES.BASKET; // '/baskets/basket'
|
|
175
|
-
ROUTES.LIST; // '/list'
|
|
176
|
-
|
|
177
|
-
// Authentication routes
|
|
178
|
-
ROUTES.AUTH; // '/users/auth'
|
|
179
|
-
ROUTES.FORGOT_PASSWORD; // '/users/password/reset'
|
|
180
|
-
ROUTES.EMAIL_SET_PRIMARY; // '/users/email-set-primary/.+'
|
|
181
|
-
ROUTES.CONFIRM_EMAIL; // '/users/registration/account-confirm-email/.+'
|
|
182
|
-
|
|
183
|
-
// Account routes
|
|
184
|
-
ROUTES.ACCOUNT; // '/account'
|
|
185
|
-
ROUTES.ACCOUNT_ADDRESS; // '/account/address'
|
|
186
|
-
ROUTES.ACCOUNT_CHANGE_EMAIL; // '/account/change-email'
|
|
187
|
-
ROUTES.ACCOUNT_CHANGE_PASSWORD; // '/account/change-password'
|
|
188
|
-
ROUTES.ACCOUNT_CONTACT; // '/account/contact'
|
|
189
|
-
ROUTES.ACCOUNT_COUPONS; // '/account/coupons'
|
|
190
|
-
ROUTES.ACCOUNT_FAQ; // '/account/faq'
|
|
191
|
-
ROUTES.ACCOUNT_ORDERS; // '/users/orders'
|
|
192
|
-
ROUTES.ACCOUNT_PROFILE; // '/account/profile'
|
|
193
|
-
ROUTES.ACCOUNT_WISHLIST; // '/account/favourite-products/'
|
|
194
|
-
ROUTES.ANONYMOUS_TRACKING; // '/anonymous-tracking'
|
|
195
|
-
|
|
196
|
-
// Order routes
|
|
197
|
-
ROUTES.CHECKOUT; // '/orders/checkout'
|
|
198
|
-
ROUTES.CHECKOUT_COMPLETED; // '/orders/completed'
|
|
199
|
-
|
|
200
|
-
// Flat page routes
|
|
201
|
-
ROUTES.CONTACT_US; // '/contact-us'
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
## Middleware Integration
|
|
205
|
-
|
|
206
|
-
The `middleware.ts` file with `withPzDefault` wrapper handles:
|
|
207
|
-
|
|
208
|
-
- Parameter validation
|
|
209
|
-
- Commerce/locale/currency resolution
|
|
210
|
-
- Authentication checks
|
|
211
|
-
- Redirects and rewrites
|
|
212
|
-
|
|
213
|
-
**Critical:** Never remove `withPzDefault` - it's essential for routing functionality.
|
|
214
|
-
|
|
215
|
-
```tsx
|
|
216
|
-
// middleware.ts
|
|
217
|
-
import { withPzDefault } from '@akinon/next/middlewares';
|
|
218
|
-
import { NextMiddleware, NextResponse } from 'next/server';
|
|
219
|
-
|
|
220
|
-
export const config = {
|
|
221
|
-
matcher: [
|
|
222
|
-
'/((?!api|_next|[\\w-\\/*]+\\.\\w+).*)',
|
|
223
|
-
'/(.*sitemap\\.xml)',
|
|
224
|
-
'/(.+\\.)(html|htm|aspx|asp|php)',
|
|
225
|
-
'/(.*orders\\/checkout-with-token.*)'
|
|
226
|
-
]
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
const middleware: NextMiddleware = () => {
|
|
230
|
-
return NextResponse.next();
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
export default withPzDefault(middleware);
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
## Pretty URL System
|
|
237
|
-
|
|
238
|
-
The `[...prettyurl]` route handles legacy URL patterns and dynamic routing:
|
|
239
|
-
|
|
240
|
-
```tsx
|
|
241
|
-
// [...prettyurl]/page.tsx
|
|
242
|
-
export default async function Page({ params }: PageProps) {
|
|
243
|
-
const { prettyurl } = params;
|
|
244
|
-
const pageSlug = prettyurl
|
|
245
|
-
.filter((x) => !x.startsWith('searchparams'))
|
|
246
|
-
.join('/');
|
|
247
|
-
|
|
248
|
-
// Resolves legacy URLs to current route structure
|
|
249
|
-
// Handles: /urun/product-name -> /product/[pk]
|
|
250
|
-
// Handles: /kategori/category-name -> /category/[pk]
|
|
251
|
-
// etc.
|
|
252
|
-
}
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
## Dynamic Routes
|
|
256
|
-
|
|
257
|
-
### Product Pages by ID
|
|
258
|
-
|
|
259
|
-
```tsx
|
|
260
|
-
// product/[pk]/page.tsx
|
|
261
|
-
export async function generateMetadata({
|
|
262
|
-
params,
|
|
263
|
-
searchParams
|
|
264
|
-
}: PageProps<{ pk: number }>) {
|
|
265
|
-
// Generate dynamic metadata for SEO
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
export default function ProductPage({
|
|
269
|
-
params,
|
|
270
|
-
searchParams
|
|
271
|
-
}: PageProps<{ pk: number }>) {
|
|
272
|
-
const { pk } = params;
|
|
273
|
-
// Product logic
|
|
274
|
-
}
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
### Category Pages by ID
|
|
278
|
-
|
|
279
|
-
```tsx
|
|
280
|
-
// category/[pk]/page.tsx
|
|
281
|
-
export default function CategoryPage({
|
|
282
|
-
params,
|
|
283
|
-
searchParams
|
|
284
|
-
}: PageProps<{ pk: number }>) {
|
|
285
|
-
const { pk } = params;
|
|
286
|
-
// Category logic
|
|
287
|
-
}
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
### Special Pages
|
|
291
|
-
|
|
292
|
-
```tsx
|
|
293
|
-
// special-page/[pk]/page.tsx
|
|
294
|
-
export default function SpecialPage({
|
|
295
|
-
params,
|
|
296
|
-
searchParams
|
|
297
|
-
}: PageProps<{ pk: number }>) {
|
|
298
|
-
const { pk } = params;
|
|
299
|
-
// Special page with widgets
|
|
300
|
-
}
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
### Flat/CMS Pages
|
|
304
|
-
|
|
305
|
-
```tsx
|
|
306
|
-
// flat-page/[pk]/page.tsx
|
|
307
|
-
export default function FlatPage({ params }: PageProps<{ pk: number }>) {
|
|
308
|
-
const { pk } = params;
|
|
309
|
-
// CMS content rendering
|
|
310
|
-
}
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
## Navigation
|
|
314
|
-
|
|
315
|
-
### Link Components
|
|
316
|
-
|
|
317
|
-
```tsx
|
|
318
|
-
import Link from 'next/link';
|
|
319
|
-
|
|
320
|
-
export default function Navigation() {
|
|
321
|
-
return <Link href="/account">Account</Link>;
|
|
322
|
-
}
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
### Programmatic Navigation
|
|
326
|
-
|
|
327
|
-
```tsx
|
|
328
|
-
'use client';
|
|
329
|
-
import { useRouter } from 'next/navigation';
|
|
330
|
-
|
|
331
|
-
export default function Component() {
|
|
332
|
-
const router = useRouter();
|
|
333
|
-
|
|
334
|
-
const handleNavigate = () => {
|
|
335
|
-
router.push('/account');
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
-
return <button onClick={handleNavigate}>Go to Account</button>;
|
|
339
|
-
}
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
## Error Handling
|
|
343
|
-
|
|
344
|
-
### Error Boundaries
|
|
345
|
-
|
|
346
|
-
```tsx
|
|
347
|
-
// error.tsx
|
|
348
|
-
'use client';
|
|
349
|
-
|
|
350
|
-
import { useSentryUncaughtErrors } from '@akinon/next/hooks';
|
|
351
|
-
import PzErrorPage from '@akinon/next/views/error-page';
|
|
352
|
-
|
|
353
|
-
export default function ErrorPage({
|
|
354
|
-
error,
|
|
355
|
-
reset
|
|
356
|
-
}: {
|
|
357
|
-
error: Error & { digest?: string; isServerError?: boolean };
|
|
358
|
-
reset: () => void;
|
|
359
|
-
}) {
|
|
360
|
-
// DO NOT REMOVE THIS LINE TO REPORT UNCAUGHT ERRORS TO SENTRY
|
|
361
|
-
useSentryUncaughtErrors(error);
|
|
362
|
-
|
|
363
|
-
return <PzErrorPage error={error} reset={reset} />;
|
|
364
|
-
}
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
### Not Found Pages
|
|
368
|
-
|
|
369
|
-
```tsx
|
|
370
|
-
// pz-not-found/page.tsx
|
|
371
|
-
'use client';
|
|
372
|
-
|
|
373
|
-
import React from 'react';
|
|
374
|
-
import { Button, Link } from '@theme/components';
|
|
375
|
-
import { useLocalization } from '@akinon/next/hooks';
|
|
376
|
-
|
|
377
|
-
const NotFound = () => {
|
|
378
|
-
const { t } = useLocalization();
|
|
379
|
-
|
|
380
|
-
return (
|
|
381
|
-
<div className="py-6 flex flex-col items-center justify-center">
|
|
382
|
-
<div className="text-8xl font-bold">404</div>
|
|
383
|
-
<h1 className="text-4xl font-bold mb-4">{t('not_found.title')}</h1>
|
|
384
|
-
<p className="text-lg mb-6">{t('not_found.sub_title')}</p>
|
|
385
|
-
<Link href={'/'}>
|
|
386
|
-
<Button className="h-auto mt-4 text-base py-3 px-6">
|
|
387
|
-
{t('not_found.button')}
|
|
388
|
-
</Button>
|
|
389
|
-
</Link>
|
|
390
|
-
</div>
|
|
391
|
-
);
|
|
392
|
-
};
|
|
393
|
-
|
|
394
|
-
export default NotFound;
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
## Loading States
|
|
398
|
-
|
|
399
|
-
```tsx
|
|
400
|
-
// loading.tsx
|
|
401
|
-
import { Skeleton, SkeletonWrapper } from 'components';
|
|
402
|
-
|
|
403
|
-
export default function Loading() {
|
|
404
|
-
return (
|
|
405
|
-
<div className="container p-4 mx-auto lg:px-0 lg:my-4">
|
|
406
|
-
<SkeletonWrapper className="md:mb-7">
|
|
407
|
-
<Skeleton className="w-[17.25rem] h-4 lg:w-64" />
|
|
408
|
-
</SkeletonWrapper>
|
|
409
|
-
|
|
410
|
-
<div className="w-full flex gap-8">
|
|
411
|
-
<div className="hidden lg:block">
|
|
412
|
-
<SkeletonWrapper className="w-[17.25rem] h-[650px] shrink-0">
|
|
413
|
-
<Skeleton className="w-full h-full" />
|
|
414
|
-
</SkeletonWrapper>
|
|
415
|
-
</div>
|
|
416
|
-
|
|
417
|
-
<div className="flex-1">
|
|
418
|
-
<div className="grid gap-x-4 gap-y-7 grid-cols-2 md:grid-cols-3 lg:grid-cols-3">
|
|
419
|
-
{Array(6)
|
|
420
|
-
.fill(null)
|
|
421
|
-
.map((_, index) => (
|
|
422
|
-
<Skeleton
|
|
423
|
-
key={index}
|
|
424
|
-
className="w-full h-80 md:h-[26.813rem] lg:h-[35.875rem]"
|
|
425
|
-
/>
|
|
426
|
-
))}
|
|
427
|
-
</div>
|
|
428
|
-
</div>
|
|
429
|
-
</div>
|
|
430
|
-
</div>
|
|
431
|
-
);
|
|
432
|
-
}
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
## Metadata Generation
|
|
436
|
-
|
|
437
|
-
### Static Metadata
|
|
438
|
-
|
|
439
|
-
```tsx
|
|
440
|
-
import type { Metadata } from 'next';
|
|
441
|
-
|
|
442
|
-
export const metadata: Metadata = {
|
|
443
|
-
title: 'Page Title',
|
|
444
|
-
description: 'Page description'
|
|
445
|
-
};
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
### Dynamic Metadata
|
|
449
|
-
|
|
450
|
-
```tsx
|
|
451
|
-
import { PageProps, Metadata } from '@akinon/next/types';
|
|
452
|
-
|
|
453
|
-
export async function generateMetadata({
|
|
454
|
-
params,
|
|
455
|
-
searchParams
|
|
456
|
-
}: PageProps<{ pk: number }>): Promise<Metadata> {
|
|
457
|
-
const { pk } = params;
|
|
458
|
-
|
|
459
|
-
try {
|
|
460
|
-
const data = await getProductData({ pk, searchParams });
|
|
461
|
-
|
|
462
|
-
return {
|
|
463
|
-
title: data.product.name,
|
|
464
|
-
description: String(data.product.attributes.description),
|
|
465
|
-
twitter: {
|
|
466
|
-
title: data.product.name,
|
|
467
|
-
description: String(data.product.attributes.description)
|
|
468
|
-
},
|
|
469
|
-
openGraph: {
|
|
470
|
-
title: data.product.name,
|
|
471
|
-
description: String(data.product.attributes.description),
|
|
472
|
-
images: data.product.productimage_set?.map((item) => ({
|
|
473
|
-
url: item.image
|
|
474
|
-
}))
|
|
475
|
-
}
|
|
476
|
-
};
|
|
477
|
-
} catch (error) {
|
|
478
|
-
return {};
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
```
|
|
482
|
-
|
|
483
|
-
## Component Patterns
|
|
484
|
-
|
|
485
|
-
### withSegmentDefaults Wrapper
|
|
486
|
-
|
|
487
|
-
All page components should use the `withSegmentDefaults` HOC:
|
|
488
|
-
|
|
489
|
-
```tsx
|
|
490
|
-
import { withSegmentDefaults } from '@akinon/next/hocs/server';
|
|
491
|
-
|
|
492
|
-
async function Page({ params, searchParams }: PageProps<{ pk: number }>) {
|
|
493
|
-
// Page logic
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
export default withSegmentDefaults(Page, { segmentType: 'page' });
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
### Layout Components
|
|
500
|
-
|
|
501
|
-
```tsx
|
|
502
|
-
import { withSegmentDefaults } from '@akinon/next/hocs/server';
|
|
503
|
-
import { RootLayoutProps } from '@akinon/next/types';
|
|
504
|
-
|
|
505
|
-
async function RootLayout({
|
|
506
|
-
params,
|
|
507
|
-
locale,
|
|
508
|
-
translations,
|
|
509
|
-
children
|
|
510
|
-
}: RootLayoutProps) {
|
|
511
|
-
return (
|
|
512
|
-
<html lang={locale.isoCode}>
|
|
513
|
-
<head />
|
|
514
|
-
<body>{children}</body>
|
|
515
|
-
</html>
|
|
516
|
-
);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
export default withSegmentDefaults(RootLayout, {
|
|
520
|
-
segmentType: 'root-layout'
|
|
521
|
-
});
|
|
522
|
-
```
|
|
523
|
-
|
|
524
|
-
## API Routes
|
|
525
|
-
|
|
526
|
-
The application includes several API routes in the `app/api/` directory:
|
|
527
|
-
|
|
528
|
-
```tsx
|
|
529
|
-
// API route structure
|
|
530
|
-
app/api/
|
|
531
|
-
├── cache/route.ts # Cache management
|
|
532
|
-
├── client/[...slug]/route.ts # Client API proxy
|
|
533
|
-
├── form/[...id]/route.ts # Form handling
|
|
534
|
-
├── logout/route.ts # Logout endpoint
|
|
535
|
-
├── sentry/route.ts # Sentry integration
|
|
536
|
-
├── web-vitals/route.ts # Web vitals tracking
|
|
537
|
-
└── widgets/[slug]/ # Widget API
|
|
538
|
-
```
|
|
539
|
-
|
|
540
|
-
### API Route Example
|
|
541
|
-
|
|
542
|
-
```tsx
|
|
543
|
-
// app/api/logout/route.ts
|
|
544
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
545
|
-
|
|
546
|
-
export async function POST(request: NextRequest) {
|
|
547
|
-
// Logout logic
|
|
548
|
-
return NextResponse.json({ success: true });
|
|
549
|
-
}
|
|
550
|
-
```
|
|
551
|
-
|
|
552
|
-
## Route Testing
|
|
553
|
-
|
|
554
|
-
### Test Route Parameters
|
|
555
|
-
|
|
556
|
-
```tsx
|
|
557
|
-
import { render } from '@testing-library/react';
|
|
558
|
-
import ProductPage from '@/app/[commerce]/[locale]/[currency]/product/[pk]/page';
|
|
559
|
-
|
|
560
|
-
describe('Product Page', () => {
|
|
561
|
-
it('handles route parameters correctly', () => {
|
|
562
|
-
const mockParams = {
|
|
563
|
-
commerce: 'tr',
|
|
564
|
-
locale: 'tr-TR',
|
|
565
|
-
currency: 'TRY',
|
|
566
|
-
pk: 123
|
|
567
|
-
};
|
|
568
|
-
|
|
569
|
-
const mockSearchParams = new URLSearchParams();
|
|
570
|
-
|
|
571
|
-
render(<ProductPage params={mockParams} searchParams={mockSearchParams} />);
|
|
572
|
-
// Test assertions
|
|
573
|
-
});
|
|
574
|
-
});
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
### Test Middleware
|
|
578
|
-
|
|
579
|
-
```tsx
|
|
580
|
-
import { withPzDefault } from '@akinon/next/middlewares';
|
|
581
|
-
|
|
582
|
-
describe('Middleware', () => {
|
|
583
|
-
it('should wrap middleware with withPzDefault', () => {
|
|
584
|
-
const middleware = () => NextResponse.next();
|
|
585
|
-
const wrappedMiddleware = withPzDefault(middleware);
|
|
586
|
-
|
|
587
|
-
expect(wrappedMiddleware).toBeDefined();
|
|
588
|
-
});
|
|
589
|
-
});
|
|
590
|
-
```
|
|
591
|
-
|
|
592
|
-
## Best Practices
|
|
593
|
-
|
|
594
|
-
1. **Always use `withSegmentDefaults`** for page and layout components
|
|
595
|
-
2. **Use `PageProps<T>` interface** from `@akinon/next/types` for type safety
|
|
596
|
-
3. **Implement proper error boundaries** with Sentry integration
|
|
597
|
-
4. **Use route constants** from `@theme/routes` instead of hardcoded URLs
|
|
598
|
-
5. **Generate dynamic metadata** for SEO optimization
|
|
599
|
-
6. **Implement loading states** with skeleton components
|
|
600
|
-
7. **Use the pretty URL system** for legacy URL support
|
|
601
|
-
8. **Follow the middleware pattern** with `withPzDefault` wrapper
|
|
602
|
-
|
|
603
|
-
This routing structure supports the multi-commerce, internationalized architecture of Project Zero applications with comprehensive error handling, SEO optimization, and legacy URL support.
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { Skeleton, SkeletonWrapper } from 'components';
|
|
2
|
-
|
|
3
|
-
export default function Loading() {
|
|
4
|
-
return (
|
|
5
|
-
<div className="container mx-auto">
|
|
6
|
-
<div className="max-w-5xl mx-auto my-5 px-7">
|
|
7
|
-
<SkeletonWrapper className="md:mb-7">
|
|
8
|
-
<Skeleton className="w-[17.25rem] h-4 lg:w-64" />
|
|
9
|
-
</SkeletonWrapper>
|
|
10
|
-
</div>
|
|
11
|
-
<div className="grid max-w-5xl grid-cols-2 lg:gap-8 mx-auto px-7">
|
|
12
|
-
<div className="col-span-2 mb-7 md:mb-0 lg:col-span-1">
|
|
13
|
-
<div className="flex gap-1">
|
|
14
|
-
<SkeletonWrapper className="hidden md:block md:mb-7">
|
|
15
|
-
{Array(5)
|
|
16
|
-
.fill(null)
|
|
17
|
-
.map((_, index) => (
|
|
18
|
-
<Skeleton key={index} className="w-20 h-24 mb-2" />
|
|
19
|
-
))}
|
|
20
|
-
</SkeletonWrapper>
|
|
21
|
-
|
|
22
|
-
<div className="flex-1">
|
|
23
|
-
<SkeletonWrapper className="md:mb-7">
|
|
24
|
-
<Skeleton className="w-full h-[30.375rem] md:h-[36.375rem]" />
|
|
25
|
-
</SkeletonWrapper>
|
|
26
|
-
</div>
|
|
27
|
-
</div>
|
|
28
|
-
</div>
|
|
29
|
-
<div className="flex flex-col items-center col-span-2 lg:col-span-1">
|
|
30
|
-
<div className="w-full">
|
|
31
|
-
<SkeletonWrapper className="w-full md:mb-7 flex justify-center items-center">
|
|
32
|
-
<Skeleton className="w-96 h-16 mb-9" />
|
|
33
|
-
<Skeleton className="hidden w-36 h-14 mb-9 md:block" />
|
|
34
|
-
|
|
35
|
-
<div className="flex flex-col justify-center items-center mb-9">
|
|
36
|
-
<Skeleton className="w-36 h-4 mb-2" />
|
|
37
|
-
<div className="flex items-center gap-2">
|
|
38
|
-
{Array(3)
|
|
39
|
-
.fill(null)
|
|
40
|
-
.map((_, index) => (
|
|
41
|
-
<Skeleton key={index} className="w-24 h-10" />
|
|
42
|
-
))}
|
|
43
|
-
</div>
|
|
44
|
-
</div>
|
|
45
|
-
|
|
46
|
-
<div className="flex flex-col justify-center items-center mb-4">
|
|
47
|
-
<Skeleton className="w-36 h-4 mb-2" />
|
|
48
|
-
<div className="flex items-center gap-2">
|
|
49
|
-
{Array(5)
|
|
50
|
-
.fill(null)
|
|
51
|
-
.map((_, index) => (
|
|
52
|
-
<Skeleton key={index} className="w-11 h-11" />
|
|
53
|
-
))}
|
|
54
|
-
</div>
|
|
55
|
-
</div>
|
|
56
|
-
|
|
57
|
-
<Skeleton className="hidden w-full h-14 mb-6 md:block" />
|
|
58
|
-
<Skeleton className="w-40 h-10 mb-9 md:w-72" />
|
|
59
|
-
<Skeleton className="w-24 h-10 mb-7" />
|
|
60
|
-
<Skeleton className="w-full h-36" />
|
|
61
|
-
</SkeletonWrapper>
|
|
62
|
-
</div>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
|
-
);
|
|
67
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from '@akinon/next/api/image-proxy';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from '@akinon/next/api/similar-product-list';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from '@akinon/next/api/similar-products';
|