@promakeai/cli 0.5.4 → 0.5.5

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.
@@ -23,7 +23,7 @@
23
23
  "path": "reset-password-page-split/reset-password-page-split.tsx",
24
24
  "type": "registry:page",
25
25
  "target": "$modules$/reset-password-page-split/reset-password-page-split.tsx",
26
- "content": "import { useState } from \"react\";\r\nimport { Link, useNavigate, useSearchParams } from \"react-router\";\r\nimport { toast } from \"sonner\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { Logo } from \"@/components/Logo\";\r\nimport { KeyRound, ArrowLeft, CheckCircle } from \"lucide-react\";\r\nimport { useAuth } from \"@/modules/auth-core\";\r\nimport { getErrorMessage } from \"@/modules/api\";\r\n\r\ninterface ResetPasswordPageSplitProps {\r\n image?: string;\r\n}\r\n\r\nexport function ResetPasswordPageSplit({\r\n image = \"/images/placeholder.png\",\r\n}: ResetPasswordPageSplitProps) {\r\n const { t } = useTranslation(\"reset-password-page-split\");\r\n usePageTitle({ title: t(\"title\", \"Reset Password\") });\r\n const navigate = useNavigate();\r\n const [searchParams] = useSearchParams();\r\n const { resetPassword } = useAuth();\r\n\r\n // Get code and username from URL params\r\n const code = searchParams.get(\"code\") || \"\";\r\n const username = searchParams.get(\"username\") || searchParams.get(\"email\") || \"\";\r\n\r\n const [password, setPassword] = useState(\"\");\r\n const [confirmPassword, setConfirmPassword] = useState(\"\");\r\n const [isLoading, setIsLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const [isSuccess, setIsSuccess] = useState(false);\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setError(null);\r\n\r\n // Validate passwords match\r\n if (password !== confirmPassword) {\r\n setError(t(\"passwordMismatch\", \"Passwords do not match\"));\r\n return;\r\n }\r\n\r\n // Validate we have required params\r\n if (!code || !username) {\r\n setError(t(\"invalidLink\", \"Invalid or expired reset link. Please request a new one.\"));\r\n return;\r\n }\r\n\r\n setIsLoading(true);\r\n\r\n try {\r\n await resetPassword(username, code, password);\r\n\r\n setIsSuccess(true);\r\n toast.success(t(\"passwordResetSuccess\", \"Password reset successfully!\"));\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"resetPasswordError\", \"Failed to reset password. Please try again.\")\r\n );\r\n setError(errorMessage);\r\n } finally {\r\n setIsLoading(false);\r\n }\r\n };\r\n\r\n // Success state\r\n if (isSuccess) {\r\n return (\r\n <section className=\"w-full md:grid md:min-h-screen md:grid-cols-2\">\r\n <div className=\"flex items-center justify-center px-4 py-12\">\r\n <div className=\"mx-auto grid w-full max-w-sm gap-6 text-center\">\r\n <Logo />\r\n <hr />\r\n\r\n <div className=\"flex justify-center\">\r\n <div className=\"w-16 h-16 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center\">\r\n <CheckCircle className=\"w-8 h-8 text-green-600 dark:text-green-400\" />\r\n </div>\r\n </div>\r\n\r\n <div>\r\n <h1 className=\"text-xl font-bold tracking-tight\">\r\n {t(\"passwordReset\", \"Password Reset!\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground mt-2\">\r\n {t(\"passwordResetDescription\", \"Your password has been reset successfully. You can now log in with your new password.\")}\r\n </p>\r\n </div>\r\n\r\n <Button onClick={() => navigate(\"/login\")} className=\"w-full\">\r\n {t(\"goToLogin\", \"Go to Login\")}\r\n </Button>\r\n\r\n <hr />\r\n <p className=\"text-sm text-muted-foreground\">\r\n © {new Date().getFullYear()} {t(\"copyright\", \"All rights reserved.\")}\r\n </p>\r\n </div>\r\n </div>\r\n <div className=\"hidden p-4 md:block\">\r\n <img\r\n loading=\"lazy\"\r\n decoding=\"async\"\r\n width=\"1920\"\r\n height=\"1080\"\r\n alt={t(\"imageAlt\", \"Reset password background\")}\r\n src={image}\r\n className=\"size-full rounded-lg border bg-muted object-cover object-center\"\r\n />\r\n </div>\r\n </section>\r\n );\r\n }\r\n\r\n // Invalid link state\r\n if (!code || !username) {\r\n return (\r\n <section className=\"w-full md:grid md:min-h-screen md:grid-cols-2\">\r\n <div className=\"flex items-center justify-center px-4 py-12\">\r\n <div className=\"mx-auto grid w-full max-w-sm gap-6 text-center\">\r\n <Logo />\r\n <hr />\r\n\r\n <div>\r\n <h1 className=\"text-xl font-bold tracking-tight text-red-600\">\r\n {t(\"invalidLinkTitle\", \"Invalid Reset Link\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground mt-2\">\r\n {t(\"invalidLinkDescription\", \"This password reset link is invalid or has expired. Please request a new one.\")}\r\n </p>\r\n </div>\r\n\r\n <Button onClick={() => navigate(\"/forgot-password\")} className=\"w-full\">\r\n {t(\"requestNewLink\", \"Request New Link\")}\r\n </Button>\r\n\r\n <Link\r\n to=\"/login\"\r\n className=\"inline-flex items-center justify-center gap-2 text-sm text-muted-foreground hover:text-foreground\"\r\n >\r\n <ArrowLeft className=\"w-4 h-4\" />\r\n {t(\"backToLogin\", \"Back to login\")}\r\n </Link>\r\n\r\n <hr />\r\n <p className=\"text-sm text-muted-foreground\">\r\n © {new Date().getFullYear()} {t(\"copyright\", \"All rights reserved.\")}\r\n </p>\r\n </div>\r\n </div>\r\n <div className=\"hidden p-4 md:block\">\r\n <img\r\n loading=\"lazy\"\r\n decoding=\"async\"\r\n width=\"1920\"\r\n height=\"1080\"\r\n alt={t(\"imageAlt\", \"Reset password background\")}\r\n src={image}\r\n className=\"size-full rounded-lg border bg-muted object-cover object-center\"\r\n />\r\n </div>\r\n </section>\r\n );\r\n }\r\n\r\n return (\r\n <section className=\"w-full md:grid md:min-h-screen md:grid-cols-2\">\r\n <div className=\"flex items-center justify-center px-4 py-12\">\r\n <div className=\"mx-auto grid w-full max-w-sm gap-6\">\r\n <Logo />\r\n <hr />\r\n\r\n <div className=\"flex justify-center\">\r\n <div className=\"w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center\">\r\n <KeyRound className=\"w-6 h-6 text-primary\" />\r\n </div>\r\n </div>\r\n\r\n <div className=\"text-center\">\r\n <h1 className=\"text-xl font-bold tracking-tight\">\r\n {t(\"title\", \"Reset Password\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground mt-1\">\r\n {t(\"subtitle\", \"Enter your new password below\")}\r\n </p>\r\n </div>\r\n\r\n {error && (\r\n <div className=\"p-3 text-sm text-red-600 bg-red-50 dark:bg-red-950 dark:text-red-400 rounded-md\">\r\n {error}\r\n </div>\r\n )}\r\n\r\n <form onSubmit={handleSubmit} className=\"grid gap-4\">\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"password\">{t(\"newPassword\", \"New Password\")}</Label>\r\n <Input\r\n required\r\n id=\"password\"\r\n type=\"password\"\r\n autoComplete=\"new-password\"\r\n placeholder=\"••••••••\"\r\n value={password}\r\n onChange={(e) => setPassword(e.target.value)}\r\n disabled={isLoading}\r\n minLength={8}\r\n />\r\n </div>\r\n\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"confirm-password\">{t(\"confirmPassword\", \"Confirm Password\")}</Label>\r\n <Input\r\n required\r\n id=\"confirm-password\"\r\n type=\"password\"\r\n autoComplete=\"new-password\"\r\n placeholder=\"••••••••\"\r\n value={confirmPassword}\r\n onChange={(e) => setConfirmPassword(e.target.value)}\r\n disabled={isLoading}\r\n minLength={8}\r\n />\r\n </div>\r\n\r\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\r\n {isLoading ? (\r\n <>\r\n <div className=\"w-4 h-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"resetting\", \"Resetting...\")}\r\n </>\r\n ) : (\r\n t(\"resetPassword\", \"Reset Password\")\r\n )}\r\n </Button>\r\n </form>\r\n\r\n <Link\r\n to=\"/login\"\r\n className=\"inline-flex items-center justify-center gap-2 text-sm text-muted-foreground hover:text-foreground\"\r\n >\r\n <ArrowLeft className=\"w-4 h-4\" />\r\n {t(\"backToLogin\", \"Back to login\")}\r\n </Link>\r\n\r\n <hr />\r\n <p className=\"text-sm text-muted-foreground\">\r\n © {new Date().getFullYear()} {t(\"copyright\", \"All rights reserved.\")}\r\n </p>\r\n </div>\r\n </div>\r\n <div className=\"hidden p-4 md:block\">\r\n <img\r\n loading=\"lazy\"\r\n decoding=\"async\"\r\n width=\"1920\"\r\n height=\"1080\"\r\n alt={t(\"imageAlt\", \"Reset password background\")}\r\n src={image}\r\n className=\"size-full rounded-lg border bg-muted object-cover object-center\"\r\n />\r\n </div>\r\n </section>\r\n );\r\n}\r\n\r\nexport default ResetPasswordPageSplit;\r\n"
26
+ "content": "import { useState } from \"react\";\r\nimport { Link, useNavigate, useSearchParams } from \"react-router\";\r\nimport { toast } from \"sonner\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Logo } from \"@/components/Logo\";\r\nimport { KeyRound, ArrowLeft, CheckCircle } from \"lucide-react\";\r\nimport { useAuth } from \"@/modules/auth-core\";\r\nimport { getErrorMessage } from \"@/modules/api\";\r\nimport { FormField } from \"@/components/FormField\";\r\nimport { PasswordInput } from \"@/components/PasswordInput\";\r\n\r\ninterface ResetPasswordPageSplitProps {\r\n image?: string;\r\n}\r\n\r\nexport function ResetPasswordPageSplit({\r\n image = \"/images/placeholder.png\",\r\n}: ResetPasswordPageSplitProps) {\r\n const { t } = useTranslation(\"reset-password-page-split\");\r\n usePageTitle({ title: t(\"title\", \"Reset Password\") });\r\n const navigate = useNavigate();\r\n const [searchParams] = useSearchParams();\r\n const { resetPassword } = useAuth();\r\n\r\n // Get code and username from URL params\r\n const code = searchParams.get(\"code\") || \"\";\r\n const username = searchParams.get(\"username\") || searchParams.get(\"email\") || \"\";\r\n\r\n const [password, setPassword] = useState(\"\");\r\n const [confirmPassword, setConfirmPassword] = useState(\"\");\r\n const [isLoading, setIsLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const [isSuccess, setIsSuccess] = useState(false);\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setError(null);\r\n\r\n // Validate passwords match\r\n if (password !== confirmPassword) {\r\n setError(t(\"passwordMismatch\", \"Passwords do not match\"));\r\n return;\r\n }\r\n\r\n // Validate we have required params\r\n if (!code || !username) {\r\n setError(t(\"invalidLink\", \"Invalid or expired reset link. Please request a new one.\"));\r\n return;\r\n }\r\n\r\n setIsLoading(true);\r\n\r\n try {\r\n await resetPassword(username, code, password);\r\n\r\n setIsSuccess(true);\r\n toast.success(t(\"passwordResetSuccess\", \"Password reset successfully!\"));\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"resetPasswordError\", \"Failed to reset password. Please try again.\")\r\n );\r\n setError(errorMessage);\r\n } finally {\r\n setIsLoading(false);\r\n }\r\n };\r\n\r\n // Success state\r\n if (isSuccess) {\r\n return (\r\n <section className=\"w-full md:grid md:min-h-screen md:grid-cols-2\">\r\n <div className=\"flex items-center justify-center px-4 py-12\">\r\n <div className=\"mx-auto grid w-full max-w-sm gap-6 text-center\">\r\n <Logo />\r\n <hr />\r\n\r\n <div className=\"flex justify-center\">\r\n <div className=\"w-16 h-16 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center\">\r\n <CheckCircle className=\"w-8 h-8 text-green-600 dark:text-green-400\" />\r\n </div>\r\n </div>\r\n\r\n <div>\r\n <h1 className=\"text-xl font-bold tracking-tight\">\r\n {t(\"passwordReset\", \"Password Reset!\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground mt-2\">\r\n {t(\"passwordResetDescription\", \"Your password has been reset successfully. You can now log in with your new password.\")}\r\n </p>\r\n </div>\r\n\r\n <Button onClick={() => navigate(\"/login\")} className=\"w-full\">\r\n {t(\"goToLogin\", \"Go to Login\")}\r\n </Button>\r\n\r\n <hr />\r\n <p className=\"text-sm text-muted-foreground\">\r\n © {new Date().getFullYear()} {t(\"copyright\", \"All rights reserved.\")}\r\n </p>\r\n </div>\r\n </div>\r\n <div className=\"hidden p-4 md:block\">\r\n <img\r\n loading=\"lazy\"\r\n decoding=\"async\"\r\n width=\"1920\"\r\n height=\"1080\"\r\n alt={t(\"imageAlt\", \"Reset password background\")}\r\n src={image}\r\n className=\"size-full rounded-lg border bg-muted object-cover object-center\"\r\n />\r\n </div>\r\n </section>\r\n );\r\n }\r\n\r\n // Invalid link state\r\n if (!code || !username) {\r\n return (\r\n <section className=\"w-full md:grid md:min-h-screen md:grid-cols-2\">\r\n <div className=\"flex items-center justify-center px-4 py-12\">\r\n <div className=\"mx-auto grid w-full max-w-sm gap-6 text-center\">\r\n <Logo />\r\n <hr />\r\n\r\n <div>\r\n <h1 className=\"text-xl font-bold tracking-tight text-red-600\">\r\n {t(\"invalidLinkTitle\", \"Invalid Reset Link\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground mt-2\">\r\n {t(\"invalidLinkDescription\", \"This password reset link is invalid or has expired. Please request a new one.\")}\r\n </p>\r\n </div>\r\n\r\n <Button onClick={() => navigate(\"/forgot-password\")} className=\"w-full\">\r\n {t(\"requestNewLink\", \"Request New Link\")}\r\n </Button>\r\n\r\n <Link\r\n to=\"/login\"\r\n className=\"inline-flex items-center justify-center gap-2 text-sm text-muted-foreground hover:text-foreground\"\r\n >\r\n <ArrowLeft className=\"w-4 h-4\" />\r\n {t(\"backToLogin\", \"Back to login\")}\r\n </Link>\r\n\r\n <hr />\r\n <p className=\"text-sm text-muted-foreground\">\r\n © {new Date().getFullYear()} {t(\"copyright\", \"All rights reserved.\")}\r\n </p>\r\n </div>\r\n </div>\r\n <div className=\"hidden p-4 md:block\">\r\n <img\r\n loading=\"lazy\"\r\n decoding=\"async\"\r\n width=\"1920\"\r\n height=\"1080\"\r\n alt={t(\"imageAlt\", \"Reset password background\")}\r\n src={image}\r\n className=\"size-full rounded-lg border bg-muted object-cover object-center\"\r\n />\r\n </div>\r\n </section>\r\n );\r\n } \r\n\r\n return (\r\n <section className=\"w-full md:grid md:min-h-screen md:grid-cols-2\">\r\n <div className=\"flex items-center justify-center px-4 py-12\">\r\n <div className=\"mx-auto grid w-full max-w-sm gap-6\">\r\n <Logo />\r\n <hr />\r\n\r\n <div className=\"flex justify-center\">\r\n <div className=\"w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center\">\r\n <KeyRound className=\"w-6 h-6 text-primary\" />\r\n </div>\r\n </div>\r\n\r\n <div className=\"text-center\">\r\n <h1 className=\"text-xl font-bold tracking-tight\">\r\n {t(\"title\", \"Reset Password\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground mt-1\">\r\n {t(\"subtitle\", \"Enter your new password below\")}\r\n </p>\r\n </div>\r\n\r\n {error && (\r\n <div className=\"p-3 text-sm text-red-600 bg-red-50 dark:bg-red-950 dark:text-red-400 rounded-md\">\r\n {error}\r\n </div>\r\n )}\r\n\r\n <form onSubmit={handleSubmit} className=\"grid gap-4\">\r\n <div className=\"grid gap-2\">\r\n <FormField label={t(\"newPassword\", \"New Password\")} htmlFor=\"password\" required>\r\n <PasswordInput\r\n required\r\n id=\"password\"\r\n name=\"password\"\r\n autoComplete=\"new-password\"\r\n placeholder=\"••••••••\"\r\n value={password}\r\n onChange={(e) => setPassword(e.target.value)}\r\n disabled={isLoading}\r\n minLength={8}\r\n />\r\n </FormField>\r\n </div>\r\n\r\n <div className=\"grid gap-2\">\r\n <FormField label={t(\"confirmPassword\", \"Confirm Password\")} htmlFor=\"confirm-password\" required>\r\n <PasswordInput\r\n required\r\n id=\"confirm-password\"\r\n name=\"confirm-password\"\r\n autoComplete=\"confirm-password\"\r\n placeholder=\"••••••••\"\r\n value={confirmPassword}\r\n onChange={(e) => setConfirmPassword(e.target.value)}\r\n disabled={isLoading}\r\n minLength={8}\r\n />\r\n </FormField>\r\n </div>\r\n\r\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\r\n {isLoading ? (\r\n <>\r\n <div className=\"w-4 h-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"resetting\", \"Resetting...\")}\r\n </>\r\n ) : (\r\n t(\"resetPassword\", \"Reset Password\")\r\n )}\r\n </Button>\r\n </form>\r\n\r\n <Link\r\n to=\"/login\"\r\n className=\"inline-flex items-center justify-center gap-2 text-sm text-muted-foreground hover:text-foreground\"\r\n >\r\n <ArrowLeft className=\"w-4 h-4\" />\r\n {t(\"backToLogin\", \"Back to login\")}\r\n </Link>\r\n\r\n <hr />\r\n <p className=\"text-sm text-muted-foreground\">\r\n © {new Date().getFullYear()} {t(\"copyright\", \"All rights reserved.\")}\r\n </p>\r\n </div>\r\n </div>\r\n <div className=\"hidden p-4 md:block\">\r\n <img\r\n loading=\"lazy\"\r\n decoding=\"async\"\r\n width=\"1920\"\r\n height=\"1080\"\r\n alt={t(\"imageAlt\", \"Reset password background\")}\r\n src={image}\r\n className=\"size-full rounded-lg border bg-muted object-cover object-center\"\r\n />\r\n </div>\r\n </section>\r\n );\r\n}\r\n\r\nexport default ResetPasswordPageSplit;\r\n"
27
27
  },
28
28
  {
29
29
  "path": "reset-password-page-split/lang/en.json",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promakeai/cli",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "promake": "dist/index.js"
@@ -1,41 +1,41 @@
1
- import js from '@eslint/js';
2
- import globals from 'globals';
3
- import reactHooks from 'eslint-plugin-react-hooks';
4
- import reactRefresh from 'eslint-plugin-react-refresh';
5
- import tseslint from 'typescript-eslint';
6
- import { defineConfig, globalIgnores } from 'eslint/config';
7
- import eslintConfigPrettier from 'eslint-config-prettier/flat';
8
-
9
- export default defineConfig([
10
- globalIgnores(['dist']),
11
- {
12
- files: ['**/*.{ts,tsx}'],
13
- extends: [
14
- js.configs.recommended,
15
- tseslint.configs.recommended,
16
- reactHooks.configs.flat.recommended,
17
- reactRefresh.configs.vite,
18
- eslintConfigPrettier,
19
- ],
20
- languageOptions: {
21
- ecmaVersion: 2020,
22
- globals: globals.browser,
23
- },
24
- rules: {
25
- '@typescript-eslint/consistent-type-imports': ['warn', {
26
- prefer: 'type-imports',
27
- fixStyle: 'inline-type-imports',
28
- }],
29
- '@typescript-eslint/no-explicit-any': 'off',
30
- '@typescript-eslint/no-unused-vars': 'off',
31
- '@typescript-eslint/no-unused-expressions': 'off',
32
- 'react-refresh/only-export-components': 'off',
33
- 'react-hooks/purity': 'off',
34
- 'react-hooks/exhaustive-deps': 'off',
35
- 'react-hooks/set-state-in-effect': 'off',
36
- 'no-unused-vars': 'off',
37
- 'prefer-const': 'warn',
38
- '@typescript-eslint/no-empty-object-type': 'off',
39
- },
40
- },
41
- ]);
1
+ import js from '@eslint/js';
2
+ import globals from 'globals';
3
+ import reactHooks from 'eslint-plugin-react-hooks';
4
+ import reactRefresh from 'eslint-plugin-react-refresh';
5
+ import tseslint from 'typescript-eslint';
6
+ import { defineConfig, globalIgnores } from 'eslint/config';
7
+ import eslintConfigPrettier from 'eslint-config-prettier/flat';
8
+
9
+ export default defineConfig([
10
+ globalIgnores(['dist']),
11
+ {
12
+ files: ['**/*.{ts,tsx}'],
13
+ extends: [
14
+ js.configs.recommended,
15
+ tseslint.configs.recommended,
16
+ reactHooks.configs.flat.recommended,
17
+ reactRefresh.configs.vite,
18
+ eslintConfigPrettier,
19
+ ],
20
+ languageOptions: {
21
+ ecmaVersion: 2020,
22
+ globals: globals.browser,
23
+ },
24
+ rules: {
25
+ '@typescript-eslint/consistent-type-imports': ['warn', {
26
+ prefer: 'type-imports',
27
+ fixStyle: 'inline-type-imports',
28
+ }],
29
+ '@typescript-eslint/no-explicit-any': 'off',
30
+ '@typescript-eslint/no-unused-vars': 'off',
31
+ '@typescript-eslint/no-unused-expressions': 'off',
32
+ 'react-refresh/only-export-components': 'off',
33
+ 'react-hooks/purity': 'off',
34
+ 'react-hooks/exhaustive-deps': 'off',
35
+ 'react-hooks/set-state-in-effect': 'off',
36
+ 'no-unused-vars': 'off',
37
+ 'prefer-const': 'warn',
38
+ '@typescript-eslint/no-empty-object-type': 'off',
39
+ },
40
+ },
41
+ ]);
@@ -0,0 +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
+ );
61
+ }
@@ -1,15 +1,17 @@
1
1
  import { Stack } from "./Stack";
2
2
  import { Label } from "./ui/label";
3
3
 
4
+ // FormField.tsx
4
5
  export interface FormFieldProps {
5
- label: string;
6
+ label: React.ReactNode;
6
7
  htmlFor?: string;
7
8
  error?: string;
8
9
  required?: boolean;
9
10
  description?: string;
10
11
  children: React.ReactNode;
11
- gap?: 0 | 1 | 2 | 3 | 4 | 6 | 8 | 10 | 12; // Stack gap override
12
+ gap?: 0 | 1 | 2 | 3 | 4 | 6 | 8 | 10 | 12;
12
13
  className?: string;
14
+ labelAction?: React.ReactNode; // YENİ
13
15
  }
14
16
 
15
17
  export function FormField({
@@ -21,12 +23,16 @@ export function FormField({
21
23
  description,
22
24
  gap = 2,
23
25
  className,
26
+ labelAction, // YENİ
24
27
  }: FormFieldProps) {
25
28
  return (
26
29
  <Stack gap={gap} className={className}>
27
- <Label htmlFor={htmlFor}>
28
- {label} {required && <span className="text-destructive">*</span>}
29
- </Label>
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>
30
36
  {children}
31
37
  {description && (
32
38
  <p className="text-sm text-muted-foreground">
@@ -1,90 +1,90 @@
1
- import i18n from "i18next";
2
- import { initReactI18next } from "react-i18next";
3
- import LanguageDetector from "i18next-browser-languagedetector";
4
-
5
- import constants from "@/constants/constants.json";
6
-
7
- // Auto-import translations from two locations:
8
- // 1. Core: @/lang/{lang}/{namespace}.json (e.g., lang/en/header.json)
9
- // 2. Modules: @/modules/{namespace}/lang/{lang}.json (e.g., modules/about-page/lang/en.json)
10
- const coreLangs = import.meta.glob("@/lang/*/*.json", { eager: true });
11
- const moduleLangs = import.meta.glob("@/modules/*/lang/*.json", { eager: true });
12
-
13
- // Get available languages from config
14
- const availableLanguages =
15
- Object.keys(constants?.site?.availableLanguages || {}) || [];
16
-
17
- // Build resources object: { en: { header: {...}, about-page: {...} }, tr: {...} }
18
- const resources: Record<string, Record<string, any>> = {};
19
-
20
- // Process core translations: /lang/{lang}/{namespace}.json
21
- Object.entries(coreLangs).forEach(([path, module]) => {
22
- const match = path.match(/\/lang\/([^/]+)\/([^/]+)\.json$/);
23
- if (match) {
24
- const [, lang, namespace] = match;
25
- if (!availableLanguages.includes(lang)) return;
26
- if (!resources[lang]) resources[lang] = {};
27
- resources[lang][namespace] = (module as any).default || module;
28
- }
29
- });
30
-
31
- // Process module translations: /modules/{namespace}/lang/{lang}.json
32
- Object.entries(moduleLangs).forEach(([path, module]) => {
33
- const match = path.match(/\/modules\/([^/]+)\/lang\/([^/]+)\.json$/);
34
- if (match) {
35
- const [, namespace, lang] = match;
36
- if (!availableLanguages.includes(lang)) return;
37
- if (!resources[lang]) resources[lang] = {};
38
- const moduleData = (module as any).default || module;
39
- resources[lang][namespace] = {
40
- ...moduleData,
41
- ...resources[lang][namespace],
42
- };
43
- }
44
- });
45
-
46
- // Custom detector for cached settings
47
- const settingsDetector = {
48
- name: "settingsDetector",
49
- lookup() {
50
- if (constants.site.overrideBrowserLanguage) {
51
- return constants?.site?.defaultLanguage;
52
- }
53
- return undefined;
54
- },
55
- cacheUserLanguage() {
56
- // Don't cache - settings detector is read-only
57
- },
58
- };
59
-
60
- // Create detector instance and add custom detector
61
- const languageDetector = new LanguageDetector();
62
- languageDetector.addDetector(settingsDetector);
63
-
64
- i18n
65
- .use(languageDetector)
66
- .use(initReactI18next)
67
- .init({
68
- resources,
69
- fallbackLng: constants?.site?.defaultLanguage || "en",
70
- supportedLngs: availableLanguages,
71
- detection: {
72
- // Priority: localStorage > settings > browser
73
- order: ["localStorage", "settingsDetector", "navigator"],
74
- lookupLocalStorage: "i18nextLng",
75
- caches: ["localStorage"],
76
- },
77
- interpolation: {
78
- escapeValue: false,
79
- },
80
- });
81
-
82
- // Helper to change language and persist to localStorage
83
- export const changeLanguage = (lang: string) => {
84
- if (availableLanguages.includes(lang)) {
85
- i18n.changeLanguage(lang);
86
- }
87
- };
88
-
89
- export { availableLanguages };
90
- export default i18n;
1
+ import i18n from "i18next";
2
+ import { initReactI18next } from "react-i18next";
3
+ import LanguageDetector from "i18next-browser-languagedetector";
4
+
5
+ import constants from "@/constants/constants.json";
6
+
7
+ // Auto-import translations from two locations:
8
+ // 1. Core: @/lang/{lang}/{namespace}.json (e.g., lang/en/header.json)
9
+ // 2. Modules: @/modules/{namespace}/lang/{lang}.json (e.g., modules/about-page/lang/en.json)
10
+ const coreLangs = import.meta.glob("@/lang/*/*.json", { eager: true });
11
+ const moduleLangs = import.meta.glob("@/modules/*/lang/*.json", { eager: true });
12
+
13
+ // Get available languages from config
14
+ const availableLanguages =
15
+ Object.keys(constants?.site?.availableLanguages || {}) || [];
16
+
17
+ // Build resources object: { en: { header: {...}, about-page: {...} }, tr: {...} }
18
+ const resources: Record<string, Record<string, any>> = {};
19
+
20
+ // Process core translations: /lang/{lang}/{namespace}.json
21
+ Object.entries(coreLangs).forEach(([path, module]) => {
22
+ const match = path.match(/\/lang\/([^/]+)\/([^/]+)\.json$/);
23
+ if (match) {
24
+ const [, lang, namespace] = match;
25
+ if (!availableLanguages.includes(lang)) return;
26
+ if (!resources[lang]) resources[lang] = {};
27
+ resources[lang][namespace] = (module as any).default || module;
28
+ }
29
+ });
30
+
31
+ // Process module translations: /modules/{namespace}/lang/{lang}.json
32
+ Object.entries(moduleLangs).forEach(([path, module]) => {
33
+ const match = path.match(/\/modules\/([^/]+)\/lang\/([^/]+)\.json$/);
34
+ if (match) {
35
+ const [, namespace, lang] = match;
36
+ if (!availableLanguages.includes(lang)) return;
37
+ if (!resources[lang]) resources[lang] = {};
38
+ const moduleData = (module as any).default || module;
39
+ resources[lang][namespace] = {
40
+ ...moduleData,
41
+ ...resources[lang][namespace],
42
+ };
43
+ }
44
+ });
45
+
46
+ // Custom detector for cached settings
47
+ const settingsDetector = {
48
+ name: "settingsDetector",
49
+ lookup() {
50
+ if (constants.site.overrideBrowserLanguage) {
51
+ return constants?.site?.defaultLanguage;
52
+ }
53
+ return undefined;
54
+ },
55
+ cacheUserLanguage() {
56
+ // Don't cache - settings detector is read-only
57
+ },
58
+ };
59
+
60
+ // Create detector instance and add custom detector
61
+ const languageDetector = new LanguageDetector();
62
+ languageDetector.addDetector(settingsDetector);
63
+
64
+ i18n
65
+ .use(languageDetector)
66
+ .use(initReactI18next)
67
+ .init({
68
+ resources,
69
+ fallbackLng: constants?.site?.defaultLanguage || "en",
70
+ supportedLngs: availableLanguages,
71
+ detection: {
72
+ // Priority: localStorage > settings > browser
73
+ order: ["localStorage", "settingsDetector", "navigator"],
74
+ lookupLocalStorage: "i18nextLng",
75
+ caches: ["localStorage"],
76
+ },
77
+ interpolation: {
78
+ escapeValue: false,
79
+ },
80
+ });
81
+
82
+ // Helper to change language and persist to localStorage
83
+ export const changeLanguage = (lang: string) => {
84
+ if (availableLanguages.includes(lang)) {
85
+ i18n.changeLanguage(lang);
86
+ }
87
+ };
88
+
89
+ export { availableLanguages };
90
+ export default i18n;