@promakeai/cli 0.9.8 → 0.9.10

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 (137) hide show
  1. package/README.md +111 -111
  2. package/dist/index.js +142 -142
  3. package/dist/registry/about-page.json +3 -3
  4. package/dist/registry/about-section.json +4 -4
  5. package/dist/registry/animations.json +2 -2
  6. package/dist/registry/announcement-bar.json +4 -4
  7. package/dist/registry/api.json +1 -1
  8. package/dist/registry/auth-core.json +2 -2
  9. package/dist/registry/bento-grid-section.json +4 -4
  10. package/dist/registry/blog-core.json +5 -5
  11. package/dist/registry/blog-list-page.json +4 -4
  12. package/dist/registry/blog-section.json +4 -4
  13. package/dist/registry/cards-carousel-section.json +4 -4
  14. package/dist/registry/cart-drawer.json +4 -4
  15. package/dist/registry/cart-page.json +4 -4
  16. package/dist/registry/case-study-page.json +3 -3
  17. package/dist/registry/category-section.json +4 -4
  18. package/dist/registry/checkout-page.json +4 -4
  19. package/dist/registry/coming-soon-page-minimal.json +4 -4
  20. package/dist/registry/coming-soon-page.json +4 -4
  21. package/dist/registry/contact-info-grid.json +4 -4
  22. package/dist/registry/contact-page-centered.json +4 -4
  23. package/dist/registry/contact-page-map-overlay.json +3 -3
  24. package/dist/registry/contact-page-map-split.json +3 -3
  25. package/dist/registry/contact-page-split.json +4 -4
  26. package/dist/registry/contact-page.json +4 -4
  27. package/dist/registry/content-section.json +4 -4
  28. package/dist/registry/cookie-consent.json +4 -4
  29. package/dist/registry/cookies-page.json +3 -3
  30. package/dist/registry/cta-section.json +3 -3
  31. package/dist/registry/ecommerce-core.json +8 -8
  32. package/dist/registry/empty-page.json +3 -3
  33. package/dist/registry/faq-categorized.json +4 -4
  34. package/dist/registry/faq-simple.json +4 -4
  35. package/dist/registry/favorites-blog-block.json +1 -1
  36. package/dist/registry/favorites-blog-page.json +4 -4
  37. package/dist/registry/favorites-ecommerce-block.json +1 -1
  38. package/dist/registry/favorites-ecommerce-page.json +4 -4
  39. package/dist/registry/feature-section.json +3 -3
  40. package/dist/registry/featured-products.json +4 -4
  41. package/dist/registry/footer-detailed.json +4 -4
  42. package/dist/registry/footer-minimal.json +3 -3
  43. package/dist/registry/footer.json +3 -3
  44. package/dist/registry/forgot-password-page-split.json +4 -4
  45. package/dist/registry/forgot-password-page.json +4 -4
  46. package/dist/registry/google-adsense.json +4 -4
  47. package/dist/registry/google-map.json +2 -2
  48. package/dist/registry/header-centered-pill.json +4 -4
  49. package/dist/registry/header-ecommerce.json +4 -4
  50. package/dist/registry/header-mega.json +4 -4
  51. package/dist/registry/header-minimal.json +4 -4
  52. package/dist/registry/header-simple.json +3 -3
  53. package/dist/registry/hero-carousel.json +3 -3
  54. package/dist/registry/hero-cta.json +4 -4
  55. package/dist/registry/hero-gradient.json +4 -4
  56. package/dist/registry/hero-grid.json +4 -4
  57. package/dist/registry/hero-profile.json +3 -3
  58. package/dist/registry/hero.json +3 -3
  59. package/dist/registry/index.json +103 -103
  60. package/dist/registry/landing-page-app.json +3 -3
  61. package/dist/registry/landing-page-saas.json +3 -3
  62. package/dist/registry/login-page-split.json +4 -4
  63. package/dist/registry/login-page.json +4 -4
  64. package/dist/registry/logo-cloud.json +4 -4
  65. package/dist/registry/masonry-grid.json +3 -3
  66. package/dist/registry/my-orders-page.json +4 -4
  67. package/dist/registry/newsletter-section.json +4 -4
  68. package/dist/registry/order-card-compact.json +3 -3
  69. package/dist/registry/order-confirmation-page.json +4 -4
  70. package/dist/registry/order-detail-block.json +1 -1
  71. package/dist/registry/orders-list-block.json +1 -1
  72. package/dist/registry/payment-success-block.json +2 -2
  73. package/dist/registry/portfolio-page.json +4 -4
  74. package/dist/registry/post-card.json +4 -4
  75. package/dist/registry/post-detail-block.json +4 -4
  76. package/dist/registry/post-detail-page.json +4 -4
  77. package/dist/registry/pricing-card.json +3 -3
  78. package/dist/registry/pricing-page.json +4 -4
  79. package/dist/registry/pricing-section.json +4 -4
  80. package/dist/registry/privacy-page.json +3 -3
  81. package/dist/registry/product-card-detailed.json +4 -4
  82. package/dist/registry/product-card-hover.json +4 -4
  83. package/dist/registry/product-card.json +4 -4
  84. package/dist/registry/product-detail-block.json +2 -2
  85. package/dist/registry/product-detail-page.json +4 -4
  86. package/dist/registry/product-detail-section.json +4 -4
  87. package/dist/registry/product-quick-view.json +4 -4
  88. package/dist/registry/products-page.json +4 -4
  89. package/dist/registry/reading-progress.json +4 -4
  90. package/dist/registry/register-page-split.json +4 -4
  91. package/dist/registry/register-page.json +4 -4
  92. package/dist/registry/related-posts-block.json +1 -1
  93. package/dist/registry/related-products-block.json +2 -2
  94. package/dist/registry/reset-password-page-split.json +4 -4
  95. package/dist/registry/reset-password-page.json +4 -4
  96. package/dist/registry/service-card.json +1 -1
  97. package/dist/registry/share-buttons.json +4 -4
  98. package/dist/registry/skill-card.json +1 -1
  99. package/dist/registry/team-page.json +4 -4
  100. package/dist/registry/terms-page.json +3 -3
  101. package/dist/registry/testimonials-carousel.json +4 -4
  102. package/dist/registry/testimonials-grid.json +4 -4
  103. package/dist/registry/timeline-section.json +4 -4
  104. package/dist/registry/verify-email-page.json +4 -4
  105. package/dist/registry/video-hero.json +4 -4
  106. package/dist/registry/youtube-embed.json +4 -4
  107. package/package.json +1 -1
  108. package/template/.env +5 -5
  109. package/template/README.md +54 -54
  110. package/template/eslint.config.js +41 -41
  111. package/template/index.html +237 -237
  112. package/template/package.json +96 -96
  113. package/template/public/_redirects +1 -1
  114. package/template/public/robots.txt +14 -14
  115. package/template/scripts/init-db.ts +18 -18
  116. package/template/src/App.tsx +21 -21
  117. package/template/src/components/FormField.tsx +48 -48
  118. package/template/src/components/FormFileInput.tsx +75 -75
  119. package/template/src/components/GoogleAnalytics.tsx +34 -34
  120. package/template/src/components/LanguageSwitcher.tsx +53 -53
  121. package/template/src/components/MetriaAnalytics.tsx +68 -68
  122. package/template/src/components/PasswordInput.tsx +60 -60
  123. package/template/src/components/ScriptInjector.tsx +62 -62
  124. package/template/src/components/Stack.tsx +39 -39
  125. package/template/src/constants/constants.json +71 -71
  126. package/template/src/db/index.ts +21 -21
  127. package/template/src/db/provider.tsx +106 -106
  128. package/template/src/db/schema.json +278 -278
  129. package/template/src/db/types.ts +195 -195
  130. package/template/src/hooks/use-debounced-value.ts +12 -12
  131. package/template/src/hooks/use-page-title.ts +55 -55
  132. package/template/src/lang/index.ts +90 -90
  133. package/template/src/lib/api.ts +345 -345
  134. package/template/src/lib/env.ts +19 -19
  135. package/template/src/router.tsx +14 -14
  136. package/template/src/vite-env.d.ts +1 -1
  137. package/template/vite.config.ts +194 -194
@@ -1,96 +1,96 @@
1
- {
2
- "name": "@promakeai/template",
3
- "private": true,
4
- "version": "0.3.3",
5
- "type": "module",
6
- "scripts": {
7
- "dev": "vite",
8
- "prebuild": "tsc -b || cd .",
9
- "build": "vite build",
10
- "typecheck": "tsc -b",
11
- "lint": "eslint .",
12
- "preview": "vite preview",
13
- "check": "tsc --noEmit",
14
- "format": "prettier --write ."
15
- },
16
- "dependencies": {
17
- "@hookform/resolvers": "^5.2.2",
18
- "@promakeai/dbreact": "1.1.3",
19
- "@promakeai/orm": "1.3.2",
20
- "@promakeai/customer-backend-client": "1.2.0",
21
- "@promakeai/inspector": "^1.7.4",
22
- "@radix-ui/react-accordion": "^1.2.12",
23
- "@radix-ui/react-alert-dialog": "^1.1.15",
24
- "@radix-ui/react-aspect-ratio": "^1.1.8",
25
- "@radix-ui/react-avatar": "^1.1.11",
26
- "@radix-ui/react-checkbox": "^1.3.3",
27
- "@radix-ui/react-collapsible": "^1.1.12",
28
- "@radix-ui/react-context-menu": "^2.2.16",
29
- "@radix-ui/react-dialog": "^1.1.15",
30
- "@radix-ui/react-dropdown-menu": "^2.1.16",
31
- "@radix-ui/react-hover-card": "^1.1.15",
32
- "@radix-ui/react-label": "^2.1.8",
33
- "@radix-ui/react-menubar": "^1.1.16",
34
- "@radix-ui/react-navigation-menu": "^1.2.14",
35
- "@radix-ui/react-popover": "^1.1.15",
36
- "@radix-ui/react-progress": "^1.1.8",
37
- "@radix-ui/react-radio-group": "^1.3.8",
38
- "@radix-ui/react-scroll-area": "^1.2.10",
39
- "@radix-ui/react-select": "^2.2.6",
40
- "@radix-ui/react-separator": "^1.1.8",
41
- "@radix-ui/react-slider": "^1.3.6",
42
- "@radix-ui/react-slot": "^1.2.4",
43
- "@radix-ui/react-switch": "^1.2.6",
44
- "@radix-ui/react-tabs": "^1.1.13",
45
- "@radix-ui/react-toggle": "^1.1.10",
46
- "@radix-ui/react-toggle-group": "^1.1.11",
47
- "@radix-ui/react-tooltip": "^1.2.8",
48
- "@tailwindcss/vite": "^4.1.18",
49
- "@tanstack/react-query": "^5.90.17",
50
- "axios": "^1.13.2",
51
- "class-variance-authority": "^0.7.1",
52
- "clsx": "^2.1.1",
53
- "cmdk": "^1.1.1",
54
- "date-fns": "^4.1.0",
55
- "embla-carousel-react": "^8.6.0",
56
- "i18next": "^25.7.4",
57
- "i18next-browser-languagedetector": "^8.2.0",
58
- "input-otp": "^1.4.2",
59
- "lucide-react": "^0.562.0",
60
- "motion": "^12.26.2",
61
- "next-themes": "^0.4.6",
62
- "react": "^19.2.0",
63
- "react-day-picker": "^9.13.0",
64
- "react-dom": "^19.2.0",
65
- "react-hook-form": "^7.71.1",
66
- "react-i18next": "^16.5.3",
67
- "react-resizable-panels": "^4.4.1",
68
- "react-router": "^7.12.0",
69
- "recharts": "2.15.4",
70
- "sonner": "^2.0.7",
71
- "sql.js": "^1.13.0",
72
- "tailwind-merge": "^3.4.0",
73
- "tailwindcss": "^4.1.18",
74
- "vaul": "^1.1.2",
75
- "zod": "^4.3.5",
76
- "zustand": "^5.0.10"
77
- },
78
- "devDependencies": {
79
- "@eslint/js": "^9.39.1",
80
- "@types/node": "^25.0.8",
81
- "@types/react": "^19.2.5",
82
- "@types/react-dom": "^19.2.3",
83
- "@types/sql.js": "^1.4.9",
84
- "@vitejs/plugin-react": "^5.1.1",
85
- "eslint": "^9.39.1",
86
- "eslint-config-prettier": "^10.1.8",
87
- "eslint-plugin-react-hooks": "^7.0.1",
88
- "eslint-plugin-react-refresh": "^0.4.24",
89
- "globals": "^16.5.0",
90
- "prettier": "3.8.0",
91
- "tw-animate-css": "^1.4.0",
92
- "typescript": "~5.9.3",
93
- "typescript-eslint": "^8.46.4",
94
- "vite": "^7.2.4"
95
- }
96
- }
1
+ {
2
+ "name": "@promakeai/template",
3
+ "private": true,
4
+ "version": "0.3.5",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "prebuild": "tsc -b || cd .",
9
+ "build": "vite build",
10
+ "typecheck": "tsc -b",
11
+ "lint": "eslint .",
12
+ "preview": "vite preview",
13
+ "check": "tsc --noEmit",
14
+ "format": "prettier --write ."
15
+ },
16
+ "dependencies": {
17
+ "@hookform/resolvers": "^5.2.2",
18
+ "@promakeai/dbreact": "1.1.3",
19
+ "@promakeai/orm": "1.3.2",
20
+ "@promakeai/customer-backend-client": "1.2.0",
21
+ "@promakeai/inspector": "^1.7.4",
22
+ "@radix-ui/react-accordion": "^1.2.12",
23
+ "@radix-ui/react-alert-dialog": "^1.1.15",
24
+ "@radix-ui/react-aspect-ratio": "^1.1.8",
25
+ "@radix-ui/react-avatar": "^1.1.11",
26
+ "@radix-ui/react-checkbox": "^1.3.3",
27
+ "@radix-ui/react-collapsible": "^1.1.12",
28
+ "@radix-ui/react-context-menu": "^2.2.16",
29
+ "@radix-ui/react-dialog": "^1.1.15",
30
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
31
+ "@radix-ui/react-hover-card": "^1.1.15",
32
+ "@radix-ui/react-label": "^2.1.8",
33
+ "@radix-ui/react-menubar": "^1.1.16",
34
+ "@radix-ui/react-navigation-menu": "^1.2.14",
35
+ "@radix-ui/react-popover": "^1.1.15",
36
+ "@radix-ui/react-progress": "^1.1.8",
37
+ "@radix-ui/react-radio-group": "^1.3.8",
38
+ "@radix-ui/react-scroll-area": "^1.2.10",
39
+ "@radix-ui/react-select": "^2.2.6",
40
+ "@radix-ui/react-separator": "^1.1.8",
41
+ "@radix-ui/react-slider": "^1.3.6",
42
+ "@radix-ui/react-slot": "^1.2.4",
43
+ "@radix-ui/react-switch": "^1.2.6",
44
+ "@radix-ui/react-tabs": "^1.1.13",
45
+ "@radix-ui/react-toggle": "^1.1.10",
46
+ "@radix-ui/react-toggle-group": "^1.1.11",
47
+ "@radix-ui/react-tooltip": "^1.2.8",
48
+ "@tailwindcss/vite": "^4.1.18",
49
+ "@tanstack/react-query": "^5.90.17",
50
+ "axios": "^1.13.2",
51
+ "class-variance-authority": "^0.7.1",
52
+ "clsx": "^2.1.1",
53
+ "cmdk": "^1.1.1",
54
+ "date-fns": "^4.1.0",
55
+ "embla-carousel-react": "^8.6.0",
56
+ "i18next": "^25.7.4",
57
+ "i18next-browser-languagedetector": "^8.2.0",
58
+ "input-otp": "^1.4.2",
59
+ "lucide-react": "^0.562.0",
60
+ "motion": "^12.26.2",
61
+ "next-themes": "^0.4.6",
62
+ "react": "^19.2.0",
63
+ "react-day-picker": "^9.13.0",
64
+ "react-dom": "^19.2.0",
65
+ "react-hook-form": "^7.71.1",
66
+ "react-i18next": "^16.5.3",
67
+ "react-resizable-panels": "^4.4.1",
68
+ "react-router": "^7.12.0",
69
+ "recharts": "2.15.4",
70
+ "sonner": "^2.0.7",
71
+ "sql.js": "^1.13.0",
72
+ "tailwind-merge": "^3.4.0",
73
+ "tailwindcss": "^4.1.18",
74
+ "vaul": "^1.1.2",
75
+ "zod": "^4.3.5",
76
+ "zustand": "^5.0.10"
77
+ },
78
+ "devDependencies": {
79
+ "@eslint/js": "^9.39.1",
80
+ "@types/node": "^25.0.8",
81
+ "@types/react": "^19.2.5",
82
+ "@types/react-dom": "^19.2.3",
83
+ "@types/sql.js": "^1.4.9",
84
+ "@vitejs/plugin-react": "^5.1.1",
85
+ "eslint": "^9.39.1",
86
+ "eslint-config-prettier": "^10.1.8",
87
+ "eslint-plugin-react-hooks": "^7.0.1",
88
+ "eslint-plugin-react-refresh": "^0.4.24",
89
+ "globals": "^16.5.0",
90
+ "prettier": "3.8.0",
91
+ "tw-animate-css": "^1.4.0",
92
+ "typescript": "~5.9.3",
93
+ "typescript-eslint": "^8.46.4",
94
+ "vite": "^7.2.4"
95
+ }
96
+ }
@@ -1 +1 @@
1
- /* /index.html 200
1
+ /* /index.html 200
@@ -1,14 +1,14 @@
1
- User-agent: Googlebot
2
- Allow: /
3
-
4
- User-agent: Bingbot
5
- Allow: /
6
-
7
- User-agent: Twitterbot
8
- Allow: /
9
-
10
- User-agent: facebookexternalhit
11
- Allow: /
12
-
13
- User-agent: *
14
- Allow: /
1
+ User-agent: Googlebot
2
+ Allow: /
3
+
4
+ User-agent: Bingbot
5
+ Allow: /
6
+
7
+ User-agent: Twitterbot
8
+ Allow: /
9
+
10
+ User-agent: facebookexternalhit
11
+ Allow: /
12
+
13
+ User-agent: *
14
+ Allow: /
@@ -1,18 +1,18 @@
1
- import { execSync } from "child_process";
2
- import path from "path";
3
-
4
- const cwd = process.cwd();
5
- const schemaPath = path.join(cwd, "src", "db", "schema.json");
6
- const outputDir = path.join(cwd, "src", "db");
7
- const dbPath = path.join(cwd, "public", "data", "database.db");
8
-
9
- const cmd = `dbcli generate --schema "${schemaPath}" --database "${dbPath}" --output "${outputDir}"`;
10
-
11
- try {
12
- console.log("Generating database with dbcli...");
13
- execSync(cmd, { stdio: "inherit" });
14
- console.log(`Database created: ${dbPath}`);
15
- } catch (err) {
16
- console.error("Failed to generate database. Ensure dbcli is installed globally.");
17
- throw err;
18
- }
1
+ import { execSync } from "child_process";
2
+ import path from "path";
3
+
4
+ const cwd = process.cwd();
5
+ const schemaPath = path.join(cwd, "src", "db", "schema.json");
6
+ const outputDir = path.join(cwd, "src", "db");
7
+ const dbPath = path.join(cwd, "public", "data", "database.db");
8
+
9
+ const cmd = `dbcli generate --schema "${schemaPath}" --database "${dbPath}" --output "${outputDir}"`;
10
+
11
+ try {
12
+ console.log("Generating database with dbcli...");
13
+ execSync(cmd, { stdio: "inherit" });
14
+ console.log(`Database created: ${dbPath}`);
15
+ } catch (err) {
16
+ console.error("Failed to generate database. Ensure dbcli is installed globally.");
17
+ throw err;
18
+ }
@@ -1,21 +1,21 @@
1
- import { TooltipProvider } from "@/components/ui/tooltip";
2
- import { GoogleAnalytics } from "@/components/GoogleAnalytics";
3
- import { MetriaAnalytics } from "@/components/MetriaAnalytics";
4
- import { ScriptInjector } from "@/components/ScriptInjector";
5
- import { AppDbProvider } from "@/db";
6
- import { Router } from "./router";
7
-
8
- const App = () => {
9
- return (
10
- <AppDbProvider>
11
- <TooltipProvider>
12
- <GoogleAnalytics />
13
- <MetriaAnalytics />
14
- <ScriptInjector />
15
- <Router />
16
- </TooltipProvider>
17
- </AppDbProvider>
18
- );
19
- };
20
-
21
- export default App;
1
+ import { TooltipProvider } from "@/components/ui/tooltip";
2
+ import { GoogleAnalytics } from "@/components/GoogleAnalytics";
3
+ import { MetriaAnalytics } from "@/components/MetriaAnalytics";
4
+ import { ScriptInjector } from "@/components/ScriptInjector";
5
+ import { AppDbProvider } from "@/db";
6
+ import { Router } from "./router";
7
+
8
+ const App = () => {
9
+ return (
10
+ <AppDbProvider>
11
+ <TooltipProvider>
12
+ <GoogleAnalytics />
13
+ <MetriaAnalytics />
14
+ <ScriptInjector />
15
+ <Router />
16
+ </TooltipProvider>
17
+ </AppDbProvider>
18
+ );
19
+ };
20
+
21
+ export default App;
@@ -1,49 +1,49 @@
1
- import { Stack } from "./Stack";
2
- import { Label } from "./ui/label";
3
-
4
- // FormField.tsx
5
- export interface FormFieldProps {
6
- label: React.ReactNode;
7
- htmlFor?: string;
8
- error?: string;
9
- required?: boolean;
10
- description?: string;
11
- children: React.ReactNode;
12
- gap?: 0 | 1 | 2 | 3 | 4 | 6 | 8 | 10 | 12;
13
- className?: string;
14
- labelAction?: React.ReactNode; // YENİ
15
- }
16
-
17
- export function FormField({
18
- label,
19
- htmlFor,
20
- error,
21
- required,
22
- children,
23
- description,
24
- gap = 2,
25
- className,
26
- labelAction, // YENİ
27
- }: FormFieldProps) {
28
- return (
29
- <Stack gap={gap} className={className}>
30
- <div className="flex items-center justify-between">
31
- <Label htmlFor={htmlFor}>
32
- {label} {required && <span className="text-destructive">*</span>}
33
- </Label>
34
- {labelAction}
35
- </div>
36
- {children}
37
- {description && (
38
- <p className="text-sm text-muted-foreground">
39
- {description}
40
- </p>
41
- )}
42
- {error && (
43
- <p className="text-sm text-destructive" role="alert">
44
- {error}
45
- </p>
46
- )}
47
- </Stack>
48
- );
1
+ import { Stack } from "./Stack";
2
+ import { Label } from "./ui/label";
3
+
4
+ // FormField.tsx
5
+ export interface FormFieldProps {
6
+ label: React.ReactNode;
7
+ htmlFor?: string;
8
+ error?: string;
9
+ required?: boolean;
10
+ description?: string;
11
+ children: React.ReactNode;
12
+ gap?: 0 | 1 | 2 | 3 | 4 | 6 | 8 | 10 | 12;
13
+ className?: string;
14
+ labelAction?: React.ReactNode; // YENİ
15
+ }
16
+
17
+ export function FormField({
18
+ label,
19
+ htmlFor,
20
+ error,
21
+ required,
22
+ children,
23
+ description,
24
+ gap = 2,
25
+ className,
26
+ labelAction, // YENİ
27
+ }: FormFieldProps) {
28
+ return (
29
+ <Stack gap={gap} className={className}>
30
+ <div className="flex items-center justify-between">
31
+ <Label htmlFor={htmlFor}>
32
+ {label} {required && <span className="text-destructive">*</span>}
33
+ </Label>
34
+ {labelAction}
35
+ </div>
36
+ {children}
37
+ {description && (
38
+ <p className="text-sm text-muted-foreground">
39
+ {description}
40
+ </p>
41
+ )}
42
+ {error && (
43
+ <p className="text-sm text-destructive" role="alert">
44
+ {error}
45
+ </p>
46
+ )}
47
+ </Stack>
48
+ );
49
49
  }
@@ -1,76 +1,76 @@
1
- import { useRef } from "react";
2
- import { Input } from "@/components/ui/input";
3
- import { Button } from "@/components/ui/button";
4
- import { Stack } from "./Stack";
5
- import { Upload } from "lucide-react";
6
-
7
- interface FormFileInputProps {
8
- files: File[];
9
- onFilesChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
10
- maxFiles?: number;
11
- accept?: string;
12
- disabled?: boolean;
13
- uploadButtonText?: string;
14
- maxFilesReachedText?: string;
15
- className?: string;
16
- handleRemoveFile: (index: number) => void;
17
- }
18
-
19
- export const FormFileInput: React.FC<FormFileInputProps> = ({
20
- files,
21
- onFilesChange,
22
- maxFiles = 5,
23
- accept = ".pdf,.doc,.docx,.jpg,.jpeg,.png",
24
- disabled = false,
25
- uploadButtonText = "Add Files",
26
- maxFilesReachedText = "Max files reached",
27
- className,
28
- handleRemoveFile,
29
- }) => {
30
- const fileInputRef = useRef<HTMLInputElement>(null);
31
-
32
-
33
-
34
- const isMaxReached = files.length >= maxFiles;
35
-
36
- return (
37
- <Stack gap={2} className={className}>
38
- <Input
39
- ref={fileInputRef}
40
- type="file"
41
- className="hidden"
42
- multiple
43
- disabled={isMaxReached || disabled}
44
- onChange={onFilesChange}
45
- accept={accept}
46
- />
47
- <Button
48
- type="button"
49
- variant="outline"
50
- className="w-full"
51
- disabled={isMaxReached || disabled}
52
- onClick={() => fileInputRef.current?.click()}
53
- >
54
- <Upload className="w-4 h-4 mr-2" />
55
- {isMaxReached ? maxFilesReachedText : uploadButtonText}
56
- </Button>
57
-
58
- {files.length > 0 && (
59
- <div className="flex flex-wrap gap-2 mt-2">
60
- {files.map((file, index) => (
61
- <div key={index} className="flex items-center gap-2 px-3 py-1 bg-muted rounded-md text-sm">
62
- <span className="text-muted-foreground">{file.name}</span>
63
- <button
64
- type="button"
65
- onClick={() => handleRemoveFile(index)}
66
- className="text-destructive hover:text-destructive/80"
67
- >
68
- ×
69
- </button>
70
- </div>
71
- ))}
72
- </div>
73
- )}
74
- </Stack>
75
- );
1
+ import { useRef } from "react";
2
+ import { Input } from "@/components/ui/input";
3
+ import { Button } from "@/components/ui/button";
4
+ import { Stack } from "./Stack";
5
+ import { Upload } from "lucide-react";
6
+
7
+ interface FormFileInputProps {
8
+ files: File[];
9
+ onFilesChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
10
+ maxFiles?: number;
11
+ accept?: string;
12
+ disabled?: boolean;
13
+ uploadButtonText?: string;
14
+ maxFilesReachedText?: string;
15
+ className?: string;
16
+ handleRemoveFile: (index: number) => void;
17
+ }
18
+
19
+ export const FormFileInput: React.FC<FormFileInputProps> = ({
20
+ files,
21
+ onFilesChange,
22
+ maxFiles = 5,
23
+ accept = ".pdf,.doc,.docx,.jpg,.jpeg,.png",
24
+ disabled = false,
25
+ uploadButtonText = "Add Files",
26
+ maxFilesReachedText = "Max files reached",
27
+ className,
28
+ handleRemoveFile,
29
+ }) => {
30
+ const fileInputRef = useRef<HTMLInputElement>(null);
31
+
32
+
33
+
34
+ const isMaxReached = files.length >= maxFiles;
35
+
36
+ return (
37
+ <Stack gap={2} className={className}>
38
+ <Input
39
+ ref={fileInputRef}
40
+ type="file"
41
+ className="hidden"
42
+ multiple
43
+ disabled={isMaxReached || disabled}
44
+ onChange={onFilesChange}
45
+ accept={accept}
46
+ />
47
+ <Button
48
+ type="button"
49
+ variant="outline"
50
+ className="w-full"
51
+ disabled={isMaxReached || disabled}
52
+ onClick={() => fileInputRef.current?.click()}
53
+ >
54
+ <Upload className="w-4 h-4 mr-2" />
55
+ {isMaxReached ? maxFilesReachedText : uploadButtonText}
56
+ </Button>
57
+
58
+ {files.length > 0 && (
59
+ <div className="flex flex-wrap gap-2 mt-2">
60
+ {files.map((file, index) => (
61
+ <div key={index} className="flex items-center gap-2 px-3 py-1 bg-muted rounded-md text-sm">
62
+ <span className="text-muted-foreground">{file.name}</span>
63
+ <button
64
+ type="button"
65
+ onClick={() => handleRemoveFile(index)}
66
+ className="text-destructive hover:text-destructive/80"
67
+ >
68
+ ×
69
+ </button>
70
+ </div>
71
+ ))}
72
+ </div>
73
+ )}
74
+ </Stack>
75
+ );
76
76
  };
@@ -1,34 +1,34 @@
1
- import { useEffect, useRef } from "react";
2
- import constants from "@/constants/constants.json";
3
-
4
- export function GoogleAnalytics() {
5
- const injected = useRef(false);
6
- const gaId = constants.scripts.gaId;
7
-
8
- useEffect(() => {
9
- if (!gaId || injected.current) return;
10
- if (document.querySelector(`[data-injected="gtag"]`)) return;
11
-
12
- injected.current = true;
13
-
14
- // Inject gtag.js script
15
- const script = document.createElement("script");
16
- script.async = true;
17
- script.src = `https://www.googletagmanager.com/gtag/js?id=${gaId}`;
18
- script.setAttribute("data-injected", "gtag");
19
- document.head.appendChild(script);
20
-
21
- // Inject inline config script
22
- const inlineScript = document.createElement("script");
23
- inlineScript.setAttribute("data-injected", "gtag-config");
24
- inlineScript.innerHTML = `
25
- window.dataLayer = window.dataLayer || [];
26
- function gtag(){dataLayer.push(arguments);}
27
- gtag('js', new Date());
28
- gtag('config', '${gaId}');
29
- `;
30
- document.head.appendChild(inlineScript);
31
- }, []);
32
-
33
- return null;
34
- }
1
+ import { useEffect, useRef } from "react";
2
+ import constants from "@/constants/constants.json";
3
+
4
+ export function GoogleAnalytics() {
5
+ const injected = useRef(false);
6
+ const gaId = constants.scripts.gaId;
7
+
8
+ useEffect(() => {
9
+ if (!gaId || injected.current) return;
10
+ if (document.querySelector(`[data-injected="gtag"]`)) return;
11
+
12
+ injected.current = true;
13
+
14
+ // Inject gtag.js script
15
+ const script = document.createElement("script");
16
+ script.async = true;
17
+ script.src = `https://www.googletagmanager.com/gtag/js?id=${gaId}`;
18
+ script.setAttribute("data-injected", "gtag");
19
+ document.head.appendChild(script);
20
+
21
+ // Inject inline config script
22
+ const inlineScript = document.createElement("script");
23
+ inlineScript.setAttribute("data-injected", "gtag-config");
24
+ inlineScript.innerHTML = `
25
+ window.dataLayer = window.dataLayer || [];
26
+ function gtag(){dataLayer.push(arguments);}
27
+ gtag('js', new Date());
28
+ gtag('config', '${gaId}');
29
+ `;
30
+ document.head.appendChild(inlineScript);
31
+ }, []);
32
+
33
+ return null;
34
+ }