@promakeai/cli 0.6.3 → 0.7.1

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 (133) hide show
  1. package/README.md +71 -71
  2. package/dist/index.js +317 -429
  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 -9
  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/video-hero.json +4 -4
  105. package/dist/registry/youtube-embed.json +4 -4
  106. package/package.json +56 -59
  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 +94 -94
  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 -68
@@ -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,19 +1,19 @@
1
- import { TooltipProvider } from "@/components/ui/tooltip";
2
- import { GoogleAnalytics } from "@/components/GoogleAnalytics";
3
- import { ScriptInjector } from "@/components/ScriptInjector";
4
- import { AppDbProvider } from "@/db";
5
- import { Router } from "./router";
6
-
7
- const App = () => {
8
- return (
9
- <AppDbProvider>
10
- <TooltipProvider>
11
- <GoogleAnalytics />
12
- <ScriptInjector />
13
- <Router />
14
- </TooltipProvider>
15
- </AppDbProvider>
16
- );
17
- };
18
-
19
- export default App;
1
+ import { TooltipProvider } from "@/components/ui/tooltip";
2
+ import { GoogleAnalytics } from "@/components/GoogleAnalytics";
3
+ import { ScriptInjector } from "@/components/ScriptInjector";
4
+ import { AppDbProvider } from "@/db";
5
+ import { Router } from "./router";
6
+
7
+ const App = () => {
8
+ return (
9
+ <AppDbProvider>
10
+ <TooltipProvider>
11
+ <GoogleAnalytics />
12
+ <ScriptInjector />
13
+ <Router />
14
+ </TooltipProvider>
15
+ </AppDbProvider>
16
+ );
17
+ };
18
+
19
+ 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
+ }
@@ -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
  }