metabinaries 1.3.3
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/package.json +32 -0
- package/src/constants.js +94 -0
- package/src/index.js +219 -0
- package/src/templates/app-clean.js +1066 -0
- package/src/templates/configs.js +391 -0
- package/src/templates/core-clean.js +269 -0
- package/src/templates/feature-ai-chat.js +1786 -0
- package/src/templates/folder-structure.js +29 -0
- package/src/templates/layout-clean.js +788 -0
- package/src/templates/misc.js +275 -0
- package/src/templates/packages.js +74 -0
- package/src/templates/ui-2.js +585 -0
- package/src/templates/ui-3.js +606 -0
- package/src/templates/ui-4.js +1025 -0
- package/src/templates/ui.js +777 -0
- package/src/utils.js +38 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
export const configTemplates = {
|
|
2
|
+
'.gitattributes': `# Auto detect text files and perform LF normalization
|
|
3
|
+
* text=auto
|
|
4
|
+
|
|
5
|
+
# Force Unix line endings for shell scripts
|
|
6
|
+
*.sh text eol=lf
|
|
7
|
+
.husky/* text eol=lf
|
|
8
|
+
|
|
9
|
+
# Force Windows line endings for batch files
|
|
10
|
+
*.bat text eol=crlf
|
|
11
|
+
|
|
12
|
+
# Binary files
|
|
13
|
+
*.png binary
|
|
14
|
+
*.jpg binary
|
|
15
|
+
*.ico binary
|
|
16
|
+
*.gif binary
|
|
17
|
+
*.woff binary
|
|
18
|
+
*.woff2 binary
|
|
19
|
+
`,
|
|
20
|
+
|
|
21
|
+
'.gitignore': `# dependencies
|
|
22
|
+
/node_modules
|
|
23
|
+
/.pnp
|
|
24
|
+
.pnp.js
|
|
25
|
+
|
|
26
|
+
# testing
|
|
27
|
+
/coverage
|
|
28
|
+
|
|
29
|
+
# next.js
|
|
30
|
+
/.next/
|
|
31
|
+
/out/
|
|
32
|
+
|
|
33
|
+
# production
|
|
34
|
+
/build
|
|
35
|
+
|
|
36
|
+
# debug
|
|
37
|
+
npm-debug.log*
|
|
38
|
+
yarn-debug.log*
|
|
39
|
+
yarn-error.log*
|
|
40
|
+
|
|
41
|
+
# local env files
|
|
42
|
+
.env
|
|
43
|
+
.env.local
|
|
44
|
+
.env.development.local
|
|
45
|
+
.env.test.local
|
|
46
|
+
.env.production.local
|
|
47
|
+
|
|
48
|
+
# vercel
|
|
49
|
+
.vercel
|
|
50
|
+
|
|
51
|
+
# typescript
|
|
52
|
+
*.tsbuildinfo
|
|
53
|
+
next-env.d.ts`,
|
|
54
|
+
|
|
55
|
+
'.env': `NEXT_PUBLIC_APP_URL=http://localhost:3000
|
|
56
|
+
NEXT_PUBLIC_SITE_NAME=Next.js Starter Template
|
|
57
|
+
NEXT_PUBLIC_DEFAULT_LOCALE=en
|
|
58
|
+
NEXT_PUBLIC_BACKEND_URL=http://localhost:5000`,
|
|
59
|
+
|
|
60
|
+
'.env.local': `NEXT_PUBLIC_APP_URL=http://localhost:3000
|
|
61
|
+
NEXT_PUBLIC_SITE_NAME=Next.js Starter Template
|
|
62
|
+
NEXT_PUBLIC_DEFAULT_LOCALE=en
|
|
63
|
+
NEXT_PUBLIC_BACKEND_URL=http://localhost:5000`,
|
|
64
|
+
|
|
65
|
+
'.env.example': `NEXT_PUBLIC_APP_URL=http://localhost:3000
|
|
66
|
+
NEXT_PUBLIC_SITE_NAME=Next.js Starter Template
|
|
67
|
+
NEXT_PUBLIC_DEFAULT_LOCALE=en
|
|
68
|
+
NEXT_PUBLIC_BACKEND_URL=http://localhost:5000`,
|
|
69
|
+
|
|
70
|
+
'api.yml': projectName => `openapi: 3.0.0
|
|
71
|
+
info:
|
|
72
|
+
title: ${projectName} API
|
|
73
|
+
version: 1.0.0
|
|
74
|
+
paths: {}`,
|
|
75
|
+
|
|
76
|
+
'.versionrc.json': `{
|
|
77
|
+
"types": [
|
|
78
|
+
{"type": "feat", "section": "Features"},
|
|
79
|
+
{"type": "fix", "section": "Bug Fixes"}
|
|
80
|
+
]
|
|
81
|
+
}`,
|
|
82
|
+
|
|
83
|
+
'components.json': `{
|
|
84
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
85
|
+
"style": "default",
|
|
86
|
+
"rsc": true,
|
|
87
|
+
"tsx": true,
|
|
88
|
+
"tailwind": {
|
|
89
|
+
"config": "tailwind.config.ts",
|
|
90
|
+
"css": "app/[locale]/globals.css",
|
|
91
|
+
"baseColor": "slate",
|
|
92
|
+
"cssVariables": true,
|
|
93
|
+
"prefix": ""
|
|
94
|
+
},
|
|
95
|
+
"aliases": {
|
|
96
|
+
"components": "@/components",
|
|
97
|
+
"utils": "@/lib/utils"
|
|
98
|
+
}
|
|
99
|
+
}`,
|
|
100
|
+
|
|
101
|
+
'eslint.config.mjs': `export default [];`,
|
|
102
|
+
|
|
103
|
+
'next.config.ts': `import type { NextConfig } from 'next';
|
|
104
|
+
import createNextIntlPlugin from 'next-intl/plugin';
|
|
105
|
+
|
|
106
|
+
const nextConfig: NextConfig = {
|
|
107
|
+
// Only use standalone output in production builds, not for dev server
|
|
108
|
+
...(process.env.NODE_ENV === 'production' &&
|
|
109
|
+
!process.env.NEXT_PUBLIC_SKIP_STANDALONE
|
|
110
|
+
? { output: 'standalone' }
|
|
111
|
+
: {}),
|
|
112
|
+
// Silence workspace root warning (useful for nested projects)
|
|
113
|
+
outputFileTracingRoot: process.cwd(),
|
|
114
|
+
/* config options here */
|
|
115
|
+
// Ignore ESLint errors during production builds
|
|
116
|
+
eslint: {
|
|
117
|
+
ignoreDuringBuilds: true,
|
|
118
|
+
},
|
|
119
|
+
// Ignore TypeScript errors during production builds
|
|
120
|
+
typescript: {
|
|
121
|
+
ignoreBuildErrors: false,
|
|
122
|
+
},
|
|
123
|
+
images: {
|
|
124
|
+
// Disable image optimization for external URLs to fix cloud deployment issues
|
|
125
|
+
unoptimized: true,
|
|
126
|
+
// Keep domains for backward compatibility
|
|
127
|
+
domains: [
|
|
128
|
+
'cdn.sportmonks.com',
|
|
129
|
+
'images.unsplash.com',
|
|
130
|
+
'images.pexels.com',
|
|
131
|
+
'images.pixabay.com',
|
|
132
|
+
'imgur.com',
|
|
133
|
+
'localhost',
|
|
134
|
+
'127.0.0.1',
|
|
135
|
+
],
|
|
136
|
+
remotePatterns: [
|
|
137
|
+
{
|
|
138
|
+
protocol: 'https',
|
|
139
|
+
hostname: '**',
|
|
140
|
+
port: '',
|
|
141
|
+
pathname: '/**',
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
dangerouslyAllowSVG: true,
|
|
145
|
+
contentDispositionType: 'attachment',
|
|
146
|
+
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
|
|
147
|
+
// Add image optimization settings
|
|
148
|
+
formats: ['image/webp', 'image/avif'],
|
|
149
|
+
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
|
|
150
|
+
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
|
|
151
|
+
},
|
|
152
|
+
// Environment variables
|
|
153
|
+
env: {
|
|
154
|
+
NEXT_PUBLIC_BACKEND_URL:
|
|
155
|
+
process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:5000',
|
|
156
|
+
},
|
|
157
|
+
// Disable static optimization for pages that use Redux
|
|
158
|
+
experimental: {
|
|
159
|
+
// This will ensure the page is rendered on the server
|
|
160
|
+
serverActions: {
|
|
161
|
+
bodySizeLimit: '2mb',
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
// Configure which pages should be statically optimized
|
|
165
|
+
pageExtensions: ['tsx', 'ts', 'jsx', 'js'],
|
|
166
|
+
// Ensure proper handling of client-side navigation
|
|
167
|
+
reactStrictMode: true,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const withNextIntl = createNextIntlPlugin();
|
|
171
|
+
export default withNextIntl(nextConfig);`,
|
|
172
|
+
|
|
173
|
+
'postcss.config.mjs': `const config = {
|
|
174
|
+
plugins: {
|
|
175
|
+
"@tailwindcss/postcss": {},
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export default config;`,
|
|
180
|
+
|
|
181
|
+
'tailwind.config.ts': `import type { Config } from "tailwindcss";
|
|
182
|
+
|
|
183
|
+
const config: Config = {
|
|
184
|
+
darkMode: "class",
|
|
185
|
+
content: [
|
|
186
|
+
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
|
|
187
|
+
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
|
188
|
+
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
|
189
|
+
],
|
|
190
|
+
theme: {
|
|
191
|
+
extend: {
|
|
192
|
+
colors: {
|
|
193
|
+
background: 'hsl(var(--background))',
|
|
194
|
+
foreground: 'hsl(var(--foreground))',
|
|
195
|
+
card: {
|
|
196
|
+
DEFAULT: 'hsl(var(--card))',
|
|
197
|
+
foreground: 'hsl(var(--card-foreground))'
|
|
198
|
+
},
|
|
199
|
+
popover: {
|
|
200
|
+
DEFAULT: 'hsl(var(--popover))',
|
|
201
|
+
foreground: 'hsl(var(--popover-foreground))'
|
|
202
|
+
},
|
|
203
|
+
primary: {
|
|
204
|
+
DEFAULT: 'hsl(var(--primary))',
|
|
205
|
+
foreground: 'hsl(var(--primary-foreground))'
|
|
206
|
+
},
|
|
207
|
+
secondary: {
|
|
208
|
+
DEFAULT: 'hsl(var(--secondary))',
|
|
209
|
+
foreground: 'hsl(var(--secondary-foreground))'
|
|
210
|
+
},
|
|
211
|
+
muted: {
|
|
212
|
+
DEFAULT: 'hsl(var(--muted))',
|
|
213
|
+
foreground: 'hsl(var(--muted-foreground))'
|
|
214
|
+
},
|
|
215
|
+
accent: {
|
|
216
|
+
DEFAULT: 'hsl(var(--accent))',
|
|
217
|
+
foreground: 'hsl(var(--accent-foreground))'
|
|
218
|
+
},
|
|
219
|
+
destructive: {
|
|
220
|
+
DEFAULT: 'hsl(var(--destructive))',
|
|
221
|
+
foreground: 'hsl(var(--destructive-foreground))'
|
|
222
|
+
},
|
|
223
|
+
border: 'hsl(var(--border))',
|
|
224
|
+
input: 'hsl(var(--input))',
|
|
225
|
+
ring: 'hsl(var(--ring))',
|
|
226
|
+
chart: {
|
|
227
|
+
'1': 'hsl(var(--chart-1))',
|
|
228
|
+
'2': 'hsl(var(--chart-2))',
|
|
229
|
+
'3': 'hsl(var(--chart-3))',
|
|
230
|
+
'4': 'hsl(var(--chart-4))',
|
|
231
|
+
'5': 'hsl(var(--chart-5))'
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
borderRadius: {
|
|
235
|
+
lg: 'var(--radius)',
|
|
236
|
+
md: 'calc(var(--radius) - 2px)',
|
|
237
|
+
sm: 'calc(var(--radius) - 4px)'
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
plugins: [require("tailwindcss-animate")],
|
|
242
|
+
};
|
|
243
|
+
export default config;`,
|
|
244
|
+
|
|
245
|
+
'tsconfig.json': `{
|
|
246
|
+
"compilerOptions": {
|
|
247
|
+
"target": "ES2017",
|
|
248
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
249
|
+
"allowJs": true,
|
|
250
|
+
"skipLibCheck": true,
|
|
251
|
+
"strict": true,
|
|
252
|
+
"noEmit": true,
|
|
253
|
+
"esModuleInterop": true,
|
|
254
|
+
"module": "esnext",
|
|
255
|
+
"moduleResolution": "bundler",
|
|
256
|
+
"resolveJsonModule": true,
|
|
257
|
+
"isolatedModules": true,
|
|
258
|
+
"jsx": "preserve",
|
|
259
|
+
"incremental": true,
|
|
260
|
+
"plugins": [
|
|
261
|
+
{
|
|
262
|
+
"name": "next"
|
|
263
|
+
}
|
|
264
|
+
],
|
|
265
|
+
"paths": {
|
|
266
|
+
"@/*": ["./*"]
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
"include": [
|
|
270
|
+
"next-env.d.ts",
|
|
271
|
+
"**/*.ts",
|
|
272
|
+
"**/*.tsx",
|
|
273
|
+
".next/types/**/*.ts",
|
|
274
|
+
".next/dev/types/**/*.ts",
|
|
275
|
+
"**/*.mts"
|
|
276
|
+
],
|
|
277
|
+
"exclude": ["node_modules"]
|
|
278
|
+
}`,
|
|
279
|
+
|
|
280
|
+
'next-intl.config.ts': `import { getRequestConfig } from 'next-intl/server';
|
|
281
|
+
import { notFound } from 'next/navigation';
|
|
282
|
+
import { routing } from './i18n/routing';
|
|
283
|
+
|
|
284
|
+
export default getRequestConfig(async ({ locale }) => {
|
|
285
|
+
if (!locale || !routing.locales.includes(locale as any)) {
|
|
286
|
+
notFound();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
locale,
|
|
291
|
+
messages: (await import(\`./messages/\${locale}.json\`)).default,
|
|
292
|
+
};
|
|
293
|
+
});`,
|
|
294
|
+
'messages/en.json': `{
|
|
295
|
+
"Metadata": {
|
|
296
|
+
"title": "MetaBinaries",
|
|
297
|
+
"description": "We build innovative software solutions that transform your ideas into reality. Web development, mobile apps, and AI-powered applications.",
|
|
298
|
+
"keywords": "software development, web development, mobile apps, AI, MetaBinaries, digital solutions",
|
|
299
|
+
"siteName": "MetaBinaries"
|
|
300
|
+
},
|
|
301
|
+
"HomePage": {
|
|
302
|
+
"title": "Welcome to MetaBinaries",
|
|
303
|
+
"description": "Your Websites-style workspace is ready."
|
|
304
|
+
},
|
|
305
|
+
"aiChat": {
|
|
306
|
+
"offline": "Offline",
|
|
307
|
+
"online": "Online",
|
|
308
|
+
"confirmClear": "Clear conversation history?",
|
|
309
|
+
"confirmClearTitle": "Clear History",
|
|
310
|
+
"clear": "Clear History",
|
|
311
|
+
"cancel": "Cancel",
|
|
312
|
+
"micErrorTitle": "Microphone Error",
|
|
313
|
+
"micErrorDesc": "Microphone access denied or not supported.",
|
|
314
|
+
"ok": "OK"
|
|
315
|
+
},
|
|
316
|
+
"aiQuestions": {
|
|
317
|
+
"title": "AI Assistant",
|
|
318
|
+
"subtitle": "Ask me anything!",
|
|
319
|
+
"placeholder": "Ask me anything...",
|
|
320
|
+
"disclaimer": "AI may produce inaccurate information.",
|
|
321
|
+
"emptyStateTitle": "How can I help you today?",
|
|
322
|
+
"emptyStateSubtitle": "I can answer questions, summarize text, and more.",
|
|
323
|
+
"clickToAsk": "Click to ask",
|
|
324
|
+
"description": "Your AI questions are ready.",
|
|
325
|
+
"questions": {
|
|
326
|
+
"meta": "What is Meta?",
|
|
327
|
+
"initials": "What are Initials?",
|
|
328
|
+
"hottorun": "What is Hot to Run?"
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
"Questions": {
|
|
332
|
+
"title": "AI Assistant",
|
|
333
|
+
"subtitle": "Ask me anything!",
|
|
334
|
+
"placeholder": "Ask me anything...",
|
|
335
|
+
"disclaimer": "AI may produce inaccurate information.",
|
|
336
|
+
"emptyStateTitle": "How can I help you today?",
|
|
337
|
+
"emptyStateSubtitle": "I can answer questions, summarize text, and more.",
|
|
338
|
+
"clickToAsk": "Click to ask",
|
|
339
|
+
"description": "Your AI questions are ready."
|
|
340
|
+
}
|
|
341
|
+
}`,
|
|
342
|
+
'messages/ar.json': `{
|
|
343
|
+
"Metadata": {
|
|
344
|
+
"title": "ميتابيناريز",
|
|
345
|
+
"description": "نبني حلول برمجية مبتكرة تحول أفكارك إلى واقع. تطوير المواقع، تطبيقات الموبايل، وتطبيقات الذكاء الاصطناعي.",
|
|
346
|
+
"keywords": "تطوير البرمجيات, تطوير المواقع, تطبيقات الموبايل, الذكاء الاصطناعي, ميتابيناريز, الحلول الرقمية",
|
|
347
|
+
"siteName": "ميتابيناريز"
|
|
348
|
+
},
|
|
349
|
+
"HomePage": {
|
|
350
|
+
"title": "مرحباً بك في ميتابيناريز",
|
|
351
|
+
"description": "بيئة عمل المواقع الخاصة بك جاهزة."
|
|
352
|
+
},
|
|
353
|
+
"aiChat": {
|
|
354
|
+
"offline": "غير متصل",
|
|
355
|
+
"online": "متصل",
|
|
356
|
+
"confirmClear": "هل أنت متأكد من مسح سجل المحادثة؟",
|
|
357
|
+
"confirmClearTitle": "مسح السجل",
|
|
358
|
+
"clear": "مسح السجل",
|
|
359
|
+
"cancel": "إلغاء",
|
|
360
|
+
"micErrorTitle": "خطأ في الميكروفون",
|
|
361
|
+
"micErrorDesc": "تم رفض الوصول إلى الميكروفون أو أنه غير مدعوم.",
|
|
362
|
+
"ok": "تم"
|
|
363
|
+
},
|
|
364
|
+
"aiQuestions": {
|
|
365
|
+
"title": "المساعد الذكي",
|
|
366
|
+
"subtitle": "اسألني أي شيء!",
|
|
367
|
+
"placeholder": "اسألني أي شيء...",
|
|
368
|
+
"disclaimer": "قد ينتج الذكاء الاصطناعي معلومات غير دقيقة.",
|
|
369
|
+
"emptyStateTitle": "كيف يمكنني مساعدتك اليوم؟",
|
|
370
|
+
"emptyStateSubtitle": "يمكنني الإجابة على الأسئلة وتلخيص النصوص والمزيد.",
|
|
371
|
+
"clickToAsk": "انقر للسؤال",
|
|
372
|
+
"description": "الأسئلة",
|
|
373
|
+
"questions": {
|
|
374
|
+
"meta": "ما هو ما تعرفه ب Meta؟",
|
|
375
|
+
"initials": "ما هي Initials؟",
|
|
376
|
+
"hottorun": "كيف يتم البداية"
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
"Questions": {
|
|
380
|
+
"title": "المساعد الذكي",
|
|
381
|
+
"subtitle": "اسألني أي شيء!",
|
|
382
|
+
"placeholder": "اسألني أي شيء...",
|
|
383
|
+
"disclaimer": "قد ينتج الذكاء الاصطناعي معلومات غير دقيقة.",
|
|
384
|
+
"emptyStateTitle": "كيف يمكنني مساعدتك اليوم؟",
|
|
385
|
+
"emptyStateSubtitle": "يمكنني الإجابة على الأسئلة وتلخيص النصوص والمزيد.",
|
|
386
|
+
"clickToAsk": "انقر للسؤال",
|
|
387
|
+
"description": "الأسئلة"
|
|
388
|
+
}
|
|
389
|
+
}`,
|
|
390
|
+
};
|
|
391
|
+
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
// Core templates (lib, utils, hooks, i18n, middleware)
|
|
2
|
+
// Conditionally exports i18n files based on options
|
|
3
|
+
export const coreTemplates = (projectName, options = {}) => {
|
|
4
|
+
const { i18n = true } = options;
|
|
5
|
+
|
|
6
|
+
const baseTemplates = {
|
|
7
|
+
'lib/utils.ts': `import { clsx, type ClassValue } from 'clsx';
|
|
8
|
+
import { twMerge } from 'tailwind-merge';
|
|
9
|
+
|
|
10
|
+
export function cn(...inputs: ClassValue[]) {
|
|
11
|
+
return twMerge(clsx(inputs));
|
|
12
|
+
}`,
|
|
13
|
+
|
|
14
|
+
'hooks/use-mobile.tsx': `import * as React from "react"
|
|
15
|
+
|
|
16
|
+
const MOBILE_BREAKPOINT = 768
|
|
17
|
+
|
|
18
|
+
export function useIsMobile() {
|
|
19
|
+
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
|
20
|
+
|
|
21
|
+
React.useEffect(() => {
|
|
22
|
+
const mql = window.matchMedia(\`(max-width: \${MOBILE_BREAKPOINT - 1}px)\`)
|
|
23
|
+
const onChange = () => {
|
|
24
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
25
|
+
}
|
|
26
|
+
mql.addEventListener("change", onChange)
|
|
27
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
28
|
+
return () => mql.removeEventListener("change", onChange)
|
|
29
|
+
}, [])
|
|
30
|
+
|
|
31
|
+
return !!isMobile
|
|
32
|
+
}`,
|
|
33
|
+
|
|
34
|
+
'hooks/use-mobile.ts': `import * as React from "react"
|
|
35
|
+
|
|
36
|
+
const MOBILE_BREAKPOINT = 768
|
|
37
|
+
|
|
38
|
+
export function useIsMobile() {
|
|
39
|
+
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
|
40
|
+
|
|
41
|
+
React.useEffect(() => {
|
|
42
|
+
const mql = window.matchMedia(\`(max-width: \${MOBILE_BREAKPOINT - 1}px)\`)
|
|
43
|
+
const onChange = () => {
|
|
44
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
45
|
+
}
|
|
46
|
+
mql.addEventListener("change", onChange)
|
|
47
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
48
|
+
return () => mql.removeEventListener("change", onChange)
|
|
49
|
+
}, [])
|
|
50
|
+
|
|
51
|
+
return !!isMobile
|
|
52
|
+
}`,
|
|
53
|
+
|
|
54
|
+
'types/menu-item.d.ts': `export type MenuItem = {
|
|
55
|
+
id?: number;
|
|
56
|
+
name: string;
|
|
57
|
+
url: string;
|
|
58
|
+
};`,
|
|
59
|
+
|
|
60
|
+
'types/menu-item.ts': `export type MenuLink = {
|
|
61
|
+
name: string;
|
|
62
|
+
arabicName: string;
|
|
63
|
+
url: string;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export type SubCategory = {
|
|
67
|
+
_id: string;
|
|
68
|
+
name: string;
|
|
69
|
+
arabicName: string;
|
|
70
|
+
categoryId: string;
|
|
71
|
+
links: MenuLink[];
|
|
72
|
+
order: number;
|
|
73
|
+
createdAt?: string;
|
|
74
|
+
updatedAt?: string;
|
|
75
|
+
__v?: number;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export type Category = {
|
|
79
|
+
_id: string;
|
|
80
|
+
name: string;
|
|
81
|
+
arabicName: string;
|
|
82
|
+
order: number;
|
|
83
|
+
createdAt?: string;
|
|
84
|
+
updatedAt?: string;
|
|
85
|
+
__v?: number;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// For backward compatibility and admin interface
|
|
89
|
+
export type MenuItem = Category | SubCategory;`,
|
|
90
|
+
|
|
91
|
+
'services/subcategory.ts': `// This service is currently not used as the application is in frontend-only mode.
|
|
92
|
+
export const subcategoryService = {
|
|
93
|
+
getMenuStructure: async () => [],
|
|
94
|
+
};`,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Only include i18n files if i18n is enabled
|
|
98
|
+
if (i18n) {
|
|
99
|
+
baseTemplates['i18n/routing.ts'] = `import { defineRouting } from 'next-intl/routing';
|
|
100
|
+
import { createNavigation } from 'next-intl/navigation';
|
|
101
|
+
|
|
102
|
+
export const routing = defineRouting({
|
|
103
|
+
locales: ['en', 'ar'],
|
|
104
|
+
defaultLocale: 'en',
|
|
105
|
+
localePrefix: 'always',
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
export type Locale = (typeof routing.locales)[number];`;
|
|
109
|
+
|
|
110
|
+
baseTemplates['i18n/navigation.ts'] = `import { createNavigation } from 'next-intl/navigation';
|
|
111
|
+
import { routing } from './routing';
|
|
112
|
+
|
|
113
|
+
export const { Link, redirect, usePathname, useRouter } =
|
|
114
|
+
createNavigation(routing);`;
|
|
115
|
+
|
|
116
|
+
baseTemplates['i18n/request.ts'] = `import { getRequestConfig } from 'next-intl/server';
|
|
117
|
+
import { routing } from './routing';
|
|
118
|
+
|
|
119
|
+
export default getRequestConfig(async ({ requestLocale }) => {
|
|
120
|
+
let locale = await requestLocale;
|
|
121
|
+
|
|
122
|
+
if (!locale || !routing.locales.includes(locale as any)) {
|
|
123
|
+
locale = routing.defaultLocale;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
locale,
|
|
128
|
+
messages: (await import(\`../messages/\${locale}.json\`)).default,
|
|
129
|
+
};
|
|
130
|
+
});`;
|
|
131
|
+
|
|
132
|
+
baseTemplates['middleware.ts'] = `import { NextRequest, NextResponse } from 'next/server';
|
|
133
|
+
import createMiddleware from 'next-intl/middleware';
|
|
134
|
+
import { routing } from './i18n/routing';
|
|
135
|
+
|
|
136
|
+
const intlMiddleware = createMiddleware(routing);
|
|
137
|
+
|
|
138
|
+
// Supported locales
|
|
139
|
+
const locales = routing.locales; // ['en', 'ar']
|
|
140
|
+
const defaultLocale = routing.defaultLocale; // 'en'
|
|
141
|
+
|
|
142
|
+
// Helper function to check if a segment is a valid locale
|
|
143
|
+
function isLocale(segment: string): segment is 'en' | 'ar' {
|
|
144
|
+
return locales.includes(segment as 'en' | 'ar');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export default function middleware(request: NextRequest) {
|
|
148
|
+
const { pathname } = request.nextUrl;
|
|
149
|
+
const pathSegments = pathname.split('/').filter(Boolean);
|
|
150
|
+
|
|
151
|
+
// Check if the path is a static file (has file extension)
|
|
152
|
+
const lastSegment = pathSegments[pathSegments.length - 1] || '';
|
|
153
|
+
const isStaticFile =
|
|
154
|
+
lastSegment.includes('.') &&
|
|
155
|
+
/\\.(jpg|jpeg|png|gif|svg|ico|webp|pdf|css|js|json|xml|txt|woff|woff2|ttf|eot|mp4|mp3|wav)$/i.test(
|
|
156
|
+
lastSegment
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// Skip processing for excluded paths
|
|
160
|
+
if (
|
|
161
|
+
pathname.startsWith('/api') ||
|
|
162
|
+
pathname.startsWith('/_next') ||
|
|
163
|
+
pathname.startsWith('/_vercel') ||
|
|
164
|
+
pathname.startsWith('/_image') ||
|
|
165
|
+
pathname.startsWith('/_static') ||
|
|
166
|
+
isStaticFile
|
|
167
|
+
) {
|
|
168
|
+
return intlMiddleware(request);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Case: Root path or no locale in path - add default locale
|
|
172
|
+
if (pathSegments.length === 0 || !isLocale(pathSegments[0])) {
|
|
173
|
+
const restOfPath = pathSegments.length === 0 ? '' : pathSegments.join('/');
|
|
174
|
+
const newUrl = request.nextUrl.clone();
|
|
175
|
+
newUrl.pathname = \`/\${defaultLocale}\${restOfPath ? \`/\${restOfPath}\` : ''}\`;
|
|
176
|
+
return NextResponse.redirect(newUrl);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Case: Multiple locales detected - remove duplicates
|
|
180
|
+
const localeSegments = pathSegments.filter(segment => isLocale(segment));
|
|
181
|
+
if (localeSegments.length > 1) {
|
|
182
|
+
const firstLocale = localeSegments[0];
|
|
183
|
+
const nonLocaleSegments = pathSegments.filter(
|
|
184
|
+
segment => !isLocale(segment)
|
|
185
|
+
);
|
|
186
|
+
const restOfPath = nonLocaleSegments.join('/');
|
|
187
|
+
const newUrl = request.nextUrl.clone();
|
|
188
|
+
newUrl.pathname = \`/\${firstLocale}\${restOfPath ? \`/\${restOfPath}\` : ''}\`;
|
|
189
|
+
return NextResponse.redirect(newUrl);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return intlMiddleware(request);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export const config = {
|
|
196
|
+
matcher: ['/((?!api|_next|_vercel|.*\\..*).*)'],
|
|
197
|
+
};`;
|
|
198
|
+
|
|
199
|
+
baseTemplates['lib/axios.ts'] = `import axios from 'axios';
|
|
200
|
+
|
|
201
|
+
const getCurrentLocale = (): string => {
|
|
202
|
+
if (typeof window !== 'undefined') {
|
|
203
|
+
const pathname = window.location.pathname;
|
|
204
|
+
const localeMatch = pathname.match(/(en|ar)/);
|
|
205
|
+
return localeMatch ? localeMatch[1] : 'en';
|
|
206
|
+
}
|
|
207
|
+
return 'en';
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export const api = axios.create({
|
|
211
|
+
baseURL:
|
|
212
|
+
process.env.NEXT_PUBLIC_API_URL ||
|
|
213
|
+
process.env.NEXT_PUBLIC_BACKEND_URL ||
|
|
214
|
+
'http://localhost:5000',
|
|
215
|
+
headers: {
|
|
216
|
+
'Content-Type': 'application/json',
|
|
217
|
+
},
|
|
218
|
+
withCredentials: true,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
api.interceptors.request.use(config => {
|
|
222
|
+
const currentLocale = getCurrentLocale();
|
|
223
|
+
config.headers['Accept-Language'] = currentLocale;
|
|
224
|
+
return config;
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
export default api;`;
|
|
228
|
+
|
|
229
|
+
baseTemplates['lib/locale.ts'] = `/**
|
|
230
|
+
* Get current locale from the URL path
|
|
231
|
+
* Extracts locale from /[locale]/... pattern
|
|
232
|
+
* @returns 'en' or 'ar'
|
|
233
|
+
*/
|
|
234
|
+
export const getCurrentLocale = (): string => {
|
|
235
|
+
if (typeof window !== 'undefined') {
|
|
236
|
+
const pathname = window.location.pathname;
|
|
237
|
+
const localeMatch = pathname.match(/(en|ar)/);
|
|
238
|
+
return localeMatch ? localeMatch[1] : 'en';
|
|
239
|
+
}
|
|
240
|
+
return 'en';
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Check if current locale is RTL (Arabic)
|
|
245
|
+
* @returns true if Arabic, false if English
|
|
246
|
+
*/
|
|
247
|
+
export const isRTL = (): boolean => {
|
|
248
|
+
return getCurrentLocale() === 'ar';
|
|
249
|
+
};`;
|
|
250
|
+
} else {
|
|
251
|
+
// Non-i18n version of axios (simpler, without locale)
|
|
252
|
+
baseTemplates['lib/axios.ts'] = `import axios from 'axios';
|
|
253
|
+
|
|
254
|
+
export const api = axios.create({
|
|
255
|
+
baseURL:
|
|
256
|
+
process.env.NEXT_PUBLIC_API_URL ||
|
|
257
|
+
process.env.NEXT_PUBLIC_BACKEND_URL ||
|
|
258
|
+
'http://localhost:5000',
|
|
259
|
+
headers: {
|
|
260
|
+
'Content-Type': 'application/json',
|
|
261
|
+
},
|
|
262
|
+
withCredentials: true,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
export default api;`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return baseTemplates;
|
|
269
|
+
};
|