@promakeai/cli 0.5.8 → 0.6.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.
Files changed (135) hide show
  1. package/README.md +71 -71
  2. package/dist/index.js +259 -254
  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 +3 -3
  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 +1 -1
  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 +2 -2
  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/video-hero.json +4 -4
  105. package/dist/registry/youtube-embed.json +4 -4
  106. package/package.json +59 -56
  107. package/template/.env +6 -6
  108. package/template/README.md +54 -54
  109. package/template/eslint.config.js +41 -41
  110. package/template/package.json +95 -95
  111. package/template/public/_redirects +1 -1
  112. package/template/public/robots.txt +14 -14
  113. package/template/scripts/init-db.ts +18 -18
  114. package/template/src/App.tsx +19 -19
  115. package/template/src/components/FormField.tsx +48 -48
  116. package/template/src/components/FormFileInput.tsx +75 -75
  117. package/template/src/components/GoogleAnalytics.tsx +34 -34
  118. package/template/src/components/LanguageSwitcher.tsx +53 -53
  119. package/template/src/components/PasswordInput.tsx +60 -60
  120. package/template/src/components/ScriptInjector.tsx +62 -62
  121. package/template/src/components/Stack.tsx +39 -39
  122. package/template/src/constants/constants.json +67 -67
  123. package/template/src/db/index.ts +20 -20
  124. package/template/src/db/provider.tsx +78 -78
  125. package/template/src/db/schema.json +258 -258
  126. package/template/src/db/types.ts +195 -195
  127. package/template/src/hooks/use-debounced-value.ts +12 -12
  128. package/template/src/lang/index.ts +90 -90
  129. package/template/src/lib/api.ts +345 -345
  130. package/template/src/lib/env.ts +19 -19
  131. package/template/src/router.tsx +14 -14
  132. package/template/src/vite-env.d.ts +1 -1
  133. package/template/vite.config.ts +68 -64
  134. package/template/public/data/database.db-shm +0 -0
  135. package/template/public/data/database.db-wal +0 -0
@@ -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
+ }
@@ -1,53 +1,53 @@
1
- import { useTranslation } from "react-i18next";
2
- import { Button } from "@/components/ui/button";
3
- import {
4
- DropdownMenu,
5
- DropdownMenuContent,
6
- DropdownMenuItem,
7
- DropdownMenuTrigger,
8
- } from "@/components/ui/dropdown-menu";
9
- import { changeLanguage } from "@/lang";
10
- import { cn } from "@/lib/utils";
11
- import constants from "@/constants/constants.json";
12
-
13
- interface LanguageSwitcherProps {
14
- className?: string;
15
- style?: React.CSSProperties;
16
- }
17
-
18
- const languages: Record<string, string> =
19
- constants?.site?.availableLanguages || {};
20
-
21
- export function LanguageSwitcher({ className, style }: LanguageSwitcherProps) {
22
- const { i18n } = useTranslation();
23
- const currentLang = i18n.language;
24
-
25
- return (
26
- <DropdownMenu>
27
- <DropdownMenuTrigger asChild>
28
- <Button
29
- variant="ghost"
30
- size="sm"
31
- className={cn("h-9 px-2 text-sm font-medium", className)}
32
- style={style}
33
- >
34
- {languages?.[currentLang] || currentLang.toUpperCase()}
35
- </Button>
36
- </DropdownMenuTrigger>
37
- <DropdownMenuContent align="end">
38
- {Object.entries(languages).map(([lang, label]) => (
39
- <DropdownMenuItem
40
- key={lang}
41
- onClick={() => changeLanguage(lang)}
42
- className={cn(
43
- currentLang === lang ? "bg-accent" : "",
44
- "hover:text-primary focus:text-primary",
45
- )}
46
- >
47
- {label || lang.toUpperCase()}
48
- </DropdownMenuItem>
49
- ))}
50
- </DropdownMenuContent>
51
- </DropdownMenu>
52
- );
53
- }
1
+ import { useTranslation } from "react-i18next";
2
+ import { Button } from "@/components/ui/button";
3
+ import {
4
+ DropdownMenu,
5
+ DropdownMenuContent,
6
+ DropdownMenuItem,
7
+ DropdownMenuTrigger,
8
+ } from "@/components/ui/dropdown-menu";
9
+ import { changeLanguage } from "@/lang";
10
+ import { cn } from "@/lib/utils";
11
+ import constants from "@/constants/constants.json";
12
+
13
+ interface LanguageSwitcherProps {
14
+ className?: string;
15
+ style?: React.CSSProperties;
16
+ }
17
+
18
+ const languages: Record<string, string> =
19
+ constants?.site?.availableLanguages || {};
20
+
21
+ export function LanguageSwitcher({ className, style }: LanguageSwitcherProps) {
22
+ const { i18n } = useTranslation();
23
+ const currentLang = i18n.language;
24
+
25
+ return (
26
+ <DropdownMenu>
27
+ <DropdownMenuTrigger asChild>
28
+ <Button
29
+ variant="ghost"
30
+ size="sm"
31
+ className={cn("h-9 px-2 text-sm font-medium", className)}
32
+ style={style}
33
+ >
34
+ {languages?.[currentLang] || currentLang.toUpperCase()}
35
+ </Button>
36
+ </DropdownMenuTrigger>
37
+ <DropdownMenuContent align="end">
38
+ {Object.entries(languages).map(([lang, label]) => (
39
+ <DropdownMenuItem
40
+ key={lang}
41
+ onClick={() => changeLanguage(lang)}
42
+ className={cn(
43
+ currentLang === lang ? "bg-accent" : "",
44
+ "hover:text-primary focus:text-primary",
45
+ )}
46
+ >
47
+ {label || lang.toUpperCase()}
48
+ </DropdownMenuItem>
49
+ ))}
50
+ </DropdownMenuContent>
51
+ </DropdownMenu>
52
+ );
53
+ }
@@ -1,61 +1,61 @@
1
- import { useState } from "react";
2
- import { Input } from "@/components/ui/input";
3
- import { Eye, EyeOff } from "lucide-react";
4
-
5
- interface PasswordInputProps {
6
- id?: string;
7
- name?: string;
8
- value: string;
9
- onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
10
- placeholder?: string;
11
- required?: boolean;
12
- autoComplete?: string;
13
- className?: string;
14
- disabled?: boolean;
15
- minLength?: number;
16
- }
17
-
18
- export function PasswordInput({
19
- id = "password",
20
- name = "password",
21
- value,
22
- onChange,
23
- placeholder = "Enter password",
24
- required = false,
25
- autoComplete = "current-password",
26
- disabled = false,
27
- minLength = undefined,
28
- className = "",
29
- }: PasswordInputProps) {
30
- const [showPassword, setShowPassword] = useState(false);
31
-
32
- return (
33
- <div className="relative">
34
- <Input
35
- id={id}
36
- name={name}
37
- type={showPassword ? "text" : "password"}
38
- value={value}
39
- onChange={onChange}
40
- placeholder={placeholder}
41
- required={required}
42
- className={`mt-1 pr-10 ${className}`}
43
- autoComplete={autoComplete}
44
- disabled={disabled}
45
- minLength={minLength}
46
- />
47
- <button
48
- type="button"
49
- onClick={() => setShowPassword(!showPassword)}
50
- className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
51
- aria-label={showPassword ? "Hide password" : "Show password"}
52
- >
53
- {showPassword ? (
54
- <EyeOff className="w-4 h-4" />
55
- ) : (
56
- <Eye className="w-4 h-4" />
57
- )}
58
- </button>
59
- </div>
60
- );
1
+ import { useState } from "react";
2
+ import { Input } from "@/components/ui/input";
3
+ import { Eye, EyeOff } from "lucide-react";
4
+
5
+ interface PasswordInputProps {
6
+ id?: string;
7
+ name?: string;
8
+ value: string;
9
+ onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
10
+ placeholder?: string;
11
+ required?: boolean;
12
+ autoComplete?: string;
13
+ className?: string;
14
+ disabled?: boolean;
15
+ minLength?: number;
16
+ }
17
+
18
+ export function PasswordInput({
19
+ id = "password",
20
+ name = "password",
21
+ value,
22
+ onChange,
23
+ placeholder = "Enter password",
24
+ required = false,
25
+ autoComplete = "current-password",
26
+ disabled = false,
27
+ minLength = undefined,
28
+ className = "",
29
+ }: PasswordInputProps) {
30
+ const [showPassword, setShowPassword] = useState(false);
31
+
32
+ return (
33
+ <div className="relative">
34
+ <Input
35
+ id={id}
36
+ name={name}
37
+ type={showPassword ? "text" : "password"}
38
+ value={value}
39
+ onChange={onChange}
40
+ placeholder={placeholder}
41
+ required={required}
42
+ className={`mt-1 pr-10 ${className}`}
43
+ autoComplete={autoComplete}
44
+ disabled={disabled}
45
+ minLength={minLength}
46
+ />
47
+ <button
48
+ type="button"
49
+ onClick={() => setShowPassword(!showPassword)}
50
+ className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
51
+ aria-label={showPassword ? "Hide password" : "Show password"}
52
+ >
53
+ {showPassword ? (
54
+ <EyeOff className="w-4 h-4" />
55
+ ) : (
56
+ <Eye className="w-4 h-4" />
57
+ )}
58
+ </button>
59
+ </div>
60
+ );
61
61
  }
@@ -1,62 +1,62 @@
1
- import { useEffect, useRef } from "react";
2
- import constants from "@/constants/constants.json";
3
-
4
- function injectScript(html: string, target: "head" | "body", position: "start" | "end", marker: string) {
5
- if (!html) return;
6
-
7
- // Check if already injected
8
- if (document.querySelector(`[data-injected="${marker}"]`)) return;
9
-
10
- const container = document.createElement("div");
11
- container.innerHTML = html;
12
-
13
- const targetElement = target === "head" ? document.head : document.body;
14
-
15
- // Create wrapper with marker
16
- const wrapper = document.createDocumentFragment();
17
-
18
- Array.from(container.childNodes).forEach((node) => {
19
- if (node.nodeName === "SCRIPT") {
20
- // Recreate script for execution
21
- const script = node as HTMLScriptElement;
22
- const newScript = document.createElement("script");
23
- newScript.setAttribute("data-injected", marker);
24
- Array.from(script.attributes).forEach((attr) => {
25
- newScript.setAttribute(attr.name, attr.value);
26
- });
27
- if (script.innerHTML) {
28
- newScript.innerHTML = script.innerHTML;
29
- }
30
- wrapper.appendChild(newScript);
31
- } else {
32
- const clone = node.cloneNode(true) as HTMLElement;
33
- if (clone.setAttribute) {
34
- clone.setAttribute("data-injected", marker);
35
- }
36
- wrapper.appendChild(clone);
37
- }
38
- });
39
-
40
- if (position === "start") {
41
- targetElement.insertBefore(wrapper, targetElement.firstChild);
42
- } else {
43
- targetElement.appendChild(wrapper);
44
- }
45
- }
46
-
47
- export function ScriptInjector() {
48
- const injected = useRef(false);
49
- const { headStart, headEnd, bodyStart, bodyEnd } = constants.scripts;
50
-
51
- useEffect(() => {
52
- if (injected.current) return;
53
- injected.current = true;
54
-
55
- injectScript(headStart, "head", "start", "head-start");
56
- injectScript(headEnd, "head", "end", "head-end");
57
- injectScript(bodyStart, "body", "start", "body-start");
58
- injectScript(bodyEnd, "body", "end", "body-end");
59
- }, []);
60
-
61
- return null;
62
- }
1
+ import { useEffect, useRef } from "react";
2
+ import constants from "@/constants/constants.json";
3
+
4
+ function injectScript(html: string, target: "head" | "body", position: "start" | "end", marker: string) {
5
+ if (!html) return;
6
+
7
+ // Check if already injected
8
+ if (document.querySelector(`[data-injected="${marker}"]`)) return;
9
+
10
+ const container = document.createElement("div");
11
+ container.innerHTML = html;
12
+
13
+ const targetElement = target === "head" ? document.head : document.body;
14
+
15
+ // Create wrapper with marker
16
+ const wrapper = document.createDocumentFragment();
17
+
18
+ Array.from(container.childNodes).forEach((node) => {
19
+ if (node.nodeName === "SCRIPT") {
20
+ // Recreate script for execution
21
+ const script = node as HTMLScriptElement;
22
+ const newScript = document.createElement("script");
23
+ newScript.setAttribute("data-injected", marker);
24
+ Array.from(script.attributes).forEach((attr) => {
25
+ newScript.setAttribute(attr.name, attr.value);
26
+ });
27
+ if (script.innerHTML) {
28
+ newScript.innerHTML = script.innerHTML;
29
+ }
30
+ wrapper.appendChild(newScript);
31
+ } else {
32
+ const clone = node.cloneNode(true) as HTMLElement;
33
+ if (clone.setAttribute) {
34
+ clone.setAttribute("data-injected", marker);
35
+ }
36
+ wrapper.appendChild(clone);
37
+ }
38
+ });
39
+
40
+ if (position === "start") {
41
+ targetElement.insertBefore(wrapper, targetElement.firstChild);
42
+ } else {
43
+ targetElement.appendChild(wrapper);
44
+ }
45
+ }
46
+
47
+ export function ScriptInjector() {
48
+ const injected = useRef(false);
49
+ const { headStart, headEnd, bodyStart, bodyEnd } = constants.scripts;
50
+
51
+ useEffect(() => {
52
+ if (injected.current) return;
53
+ injected.current = true;
54
+
55
+ injectScript(headStart, "head", "start", "head-start");
56
+ injectScript(headEnd, "head", "end", "head-end");
57
+ injectScript(bodyStart, "body", "start", "body-start");
58
+ injectScript(bodyEnd, "body", "end", "body-end");
59
+ }, []);
60
+
61
+ return null;
62
+ }