@umituz/web-dashboard 2.0.8 → 2.1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/web-dashboard",
3
- "version": "2.0.8",
3
+ "version": "2.1.0",
4
4
  "description": "Dashboard Layout System - Customizable, themeable dashboard layouts and settings",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -23,6 +23,11 @@
23
23
  "./onboarding/hooks": "./src/domains/onboarding/hooks/index.ts",
24
24
  "./onboarding/utils": "./src/domains/onboarding/utils/index.ts",
25
25
  "./onboarding/types": "./src/domains/onboarding/types/index.ts",
26
+ "./auth": "./src/domains/auth/index.ts",
27
+ "./auth/components": "./src/domains/auth/components/index.ts",
28
+ "./auth/hooks": "./src/domains/auth/hooks/index.ts",
29
+ "./auth/utils": "./src/domains/auth/utils/index.ts",
30
+ "./auth/types": "./src/domains/auth/types/index.ts",
26
31
  "./package.json": "./package.json"
27
32
  },
28
33
  "files": [
@@ -64,6 +69,10 @@
64
69
  "settings",
65
70
  "onboarding",
66
71
  "wizard",
72
+ "auth",
73
+ "authentication",
74
+ "login",
75
+ "register",
67
76
  "react",
68
77
  "typescript",
69
78
  "components",
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Auth Layout Component
3
+ *
4
+ * Configurable layout wrapper for authentication pages
5
+ */
6
+
7
+ import { BrandLogo } from "../../layouts/components";
8
+ import type { AuthLayoutProps } from "../types/auth";
9
+
10
+ export const AuthLayout = ({
11
+ config,
12
+ authState,
13
+ authActions,
14
+ children,
15
+ }: AuthLayoutProps) => {
16
+ return (
17
+ <div className="min-h-screen bg-background flex flex-col">
18
+ {/* Header */}
19
+ <header className="border-b border-border px-6 py-4 flex items-center justify-between">
20
+ <div className="flex items-center gap-2">
21
+ <BrandLogo size={32} />
22
+ <span className="font-bold text-xl text-foreground">{config.brandName}</span>
23
+ </div>
24
+
25
+ {/* Additional Header Content */}
26
+ {config.afterLoginRoute && (
27
+ <a
28
+ href={config.afterLoginRoute}
29
+ className="text-sm text-muted-foreground hover:text-foreground transition-colors"
30
+ >
31
+ Back to home
32
+ </a>
33
+ )}
34
+ </header>
35
+
36
+ {/* Main Content */}
37
+ <main className="flex-1 flex items-center justify-center px-4 py-12 bg-secondary/30">
38
+ <div className="w-full max-w-md">
39
+ {/* Card Container */}
40
+ <div className="bg-background border border-border rounded-2xl shadow-sm p-8">
41
+ {/* Tagline */}
42
+ {config.brandTagline && (
43
+ <p className="text-center text-sm text-muted-foreground mb-6">
44
+ {config.brandTagline}
45
+ </p>
46
+ )}
47
+
48
+ {/* Children Content (Forms) */}
49
+ {children}
50
+ </div>
51
+
52
+ {/* Social Login */}
53
+ {config.showSocialLogin && config.socialProviders && config.socialProviders.length > 0 && (
54
+ <div className="mt-6 space-y-4">
55
+ {/* Divider */}
56
+ <div className="relative">
57
+ <div className="absolute inset-0 flex items-center">
58
+ <div className="w-full border-t border-border" />
59
+ </div>
60
+ <div className="relative flex justify-center text-sm">
61
+ <span className="px-2 bg-secondary/30 text-muted-foreground">
62
+ Or continue with
63
+ </span>
64
+ </div>
65
+ </div>
66
+
67
+ {/* Social Buttons */}
68
+ <div className="grid grid-cols-2 gap-3">
69
+ {config.socialProviders.map((provider) => (
70
+ <button
71
+ key={provider.id}
72
+ type="button"
73
+ className={cn(
74
+ "flex items-center justify-center gap-2 px-4 py-3 rounded-lg border",
75
+ "bg-background hover:bg-muted transition-colors",
76
+ "text-sm font-medium text-foreground"
77
+ )}
78
+ onClick={() => {
79
+ // Handle social login
80
+ console.log(`Social login with ${provider.id}`);
81
+ }}
82
+ >
83
+ <span className="text-lg">{provider.icon}</span>
84
+ <span>{provider.name}</span>
85
+ </button>
86
+ ))}
87
+ </div>
88
+ </div>
89
+ )}
90
+ </div>
91
+ </main>
92
+
93
+ {/* Footer */}
94
+ <footer className="border-t border-border px-6 py-4">
95
+ <div className="max-w-4xl mx-auto flex items-center justify-between text-xs text-muted-foreground">
96
+ <p>© {new Date().getFullYear()} {config.brandName}. All rights reserved.</p>
97
+ <div className="flex items-center gap-4">
98
+ <a href="/terms" className="hover:text-foreground transition-colors">
99
+ Terms
100
+ </a>
101
+ <a href="/privacy" className="hover:text-foreground transition-colors">
102
+ Privacy
103
+ </a>
104
+ <a href="/contact" className="hover:text-foreground transition-colors">
105
+ Contact
106
+ </a>
107
+ </div>
108
+ </div>
109
+ </footer>
110
+ </div>
111
+ );
112
+ };
113
+
114
+ export default AuthLayout;
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Forgot Password Form Component
3
+ *
4
+ * Password reset request form
5
+ */
6
+
7
+ import { useState } from "react";
8
+ import { useNavigate } from "react-router-dom";
9
+ import { ArrowLeft, Loader2, CheckCircle } from "lucide-react";
10
+ import { cn } from "@umituz/web-design-system/utils";
11
+ import { Button } from "@umituz/web-design-system/atoms";
12
+ import type { ForgotPasswordFormProps } from "../types/auth";
13
+ import { validateForgotPassword } from "../utils/auth";
14
+
15
+ export const ForgotPasswordForm = ({
16
+ config,
17
+ onSuccess,
18
+ onError,
19
+ showBackLink = true,
20
+ }: ForgotPasswordFormProps) => {
21
+ const navigate = useNavigate();
22
+ const [email, setEmail] = useState("");
23
+ const [isLoading, setIsLoading] = useState(false);
24
+ const [error, setError] = useState<string | null>(null);
25
+ const [success, setSuccess] = useState(false);
26
+
27
+ const handleSubmit = async (e: React.FormEvent) => {
28
+ e.preventDefault();
29
+ setError(null);
30
+
31
+ // Validate
32
+ const validation = validateForgotPassword({ email });
33
+ if (!validation.valid) {
34
+ setError(validation.error || "Invalid email");
35
+ onError?.(validation.error || "Invalid email");
36
+ return;
37
+ }
38
+
39
+ setIsLoading(true);
40
+
41
+ try {
42
+ // In production, call your auth API
43
+ // await forgotPassword({ email });
44
+
45
+ // Simulate API call
46
+ await new Promise((resolve) => setTimeout(resolve, 1000));
47
+
48
+ setSuccess(true);
49
+ await onSuccess?.();
50
+ } catch (err) {
51
+ const errorMessage = err instanceof Error ? err.message : "Failed to send reset email";
52
+ setError(errorMessage);
53
+ onError?.(errorMessage);
54
+ } finally {
55
+ setIsLoading(false);
56
+ }
57
+ };
58
+
59
+ if (success) {
60
+ return (
61
+ <div className="w-full max-w-md space-y-6">
62
+ {/* Success Message */}
63
+ <div className="text-center space-y-4">
64
+ <div className="flex justify-center">
65
+ <div className="w-16 h-16 bg-green-100 dark:bg-green-900/20 rounded-full flex items-center justify-center">
66
+ <CheckCircle className="h-8 w-8 text-green-600 dark:text-green-500" />
67
+ </div>
68
+ </div>
69
+ <div>
70
+ <h1 className="text-2xl font-extrabold text-foreground mb-2">
71
+ Check your email
72
+ </h1>
73
+ <p className="text-muted-foreground">
74
+ We sent a password reset link to{" "}
75
+ <span className="font-medium text-foreground">{email}</span>
76
+ </p>
77
+ </div>
78
+ </div>
79
+
80
+ {/* Instructions */}
81
+ <div className="bg-muted/50 border border-border rounded-lg p-4 space-y-2">
82
+ <p className="text-sm text-muted-foreground">
83
+ ✓ Click the link in your email
84
+ </p>
85
+ <p className="text-sm text-muted-foreground">
86
+ ✓ Create a new password
87
+ </p>
88
+ <p className="text-sm text-muted-foreground">
89
+ ✓ Sign in with your new password
90
+ </p>
91
+ </div>
92
+
93
+ {/* Back to Login */}
94
+ {showBackLink && config.loginRoute && (
95
+ <Button
96
+ type="button"
97
+ variant="ghost"
98
+ onClick={() => navigate(config.loginRoute!)}
99
+ className="w-full"
100
+ >
101
+ <ArrowLeft className="h-4 w-4 mr-2" />
102
+ Back to sign in
103
+ </Button>
104
+ )}
105
+ </div>
106
+ );
107
+ }
108
+
109
+ return (
110
+ <form onSubmit={handleSubmit} className="w-full max-w-md space-y-6">
111
+ {/* Header */}
112
+ <div className="text-center">
113
+ <h1 className="text-3xl font-extrabold text-foreground mb-2">
114
+ Forgot password?
115
+ </h1>
116
+ <p className="text-muted-foreground">
117
+ No worries, we'll send you reset instructions
118
+ </p>
119
+ </div>
120
+
121
+ {/* Error Message */}
122
+ {error && (
123
+ <div className="bg-destructive/10 border border-destructive/20 text-destructive px-4 py-3 rounded-lg text-sm">
124
+ {error}
125
+ </div>
126
+ )}
127
+
128
+ {/* Email Field */}
129
+ <div className="space-y-2">
130
+ <label htmlFor="email" className="text-sm font-medium text-foreground">
131
+ Email
132
+ </label>
133
+ <input
134
+ id="email"
135
+ type="email"
136
+ value={email}
137
+ onChange={(e) => setEmail(e.target.value)}
138
+ placeholder="you@example.com"
139
+ className={cn(
140
+ "w-full px-4 py-3 rounded-lg border bg-background",
141
+ "focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent",
142
+ "placeholder:text-muted-foreground",
143
+ error && "border-destructive"
144
+ )}
145
+ disabled={isLoading}
146
+ autoComplete="email"
147
+ />
148
+ </div>
149
+
150
+ {/* Submit Button */}
151
+ <Button
152
+ type="submit"
153
+ className="w-full h-12 text-base font-bold rounded-full"
154
+ disabled={isLoading}
155
+ >
156
+ {isLoading ? (
157
+ <>
158
+ <Loader2 className="h-4 w-4 mr-2 animate-spin" />
159
+ Sending...
160
+ </>
161
+ ) : (
162
+ "Send reset link"
163
+ )}
164
+ </Button>
165
+
166
+ {/* Back to Login */}
167
+ {showBackLink && config.loginRoute && (
168
+ <button
169
+ type="button"
170
+ onClick={() => navigate(config.loginRoute!)}
171
+ className="w-full flex items-center justify-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors"
172
+ >
173
+ <ArrowLeft className="h-4 w-4" />
174
+ Back to sign in
175
+ </button>
176
+ )}
177
+ </form>
178
+ );
179
+ };
180
+
181
+ export default ForgotPasswordForm;
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Login Form Component
3
+ *
4
+ * Configurable login form with validation
5
+ */
6
+
7
+ import { useState } from "react";
8
+ import { useNavigate } from "react-router-dom";
9
+ import { Eye, EyeOff, Loader2 } from "lucide-react";
10
+ import { cn } from "@umituz/web-design-system/utils";
11
+ import { Button } from "@umituz/web-design-system/atoms";
12
+ import type { LoginFormProps } from "../types/auth";
13
+ import { validateLogin, calculatePasswordStrength } from "../utils/auth";
14
+
15
+ export const LoginForm = ({
16
+ config,
17
+ defaultCredentials = {},
18
+ showRememberMe = true,
19
+ showForgotPassword = true,
20
+ showRegisterLink = true,
21
+ onLoginSuccess,
22
+ onLoginError,
23
+ }: LoginFormProps) => {
24
+ const navigate = useNavigate();
25
+ const [email, setEmail] = useState(defaultCredentials.email || "");
26
+ const [password, setPassword] = useState(defaultCredentials.password || "");
27
+ const [rememberMe, setRememberMe] = useState(false);
28
+ const [showPassword, setShowPassword] = useState(false);
29
+ const [isLoading, setIsLoading] = useState(false);
30
+ const [error, setError] = useState<string | null>(null);
31
+
32
+ const handleSubmit = async (e: React.FormEvent) => {
33
+ e.preventDefault();
34
+ setError(null);
35
+
36
+ // Validate
37
+ const validation = validateLogin({ email, password });
38
+ if (!validation.valid) {
39
+ setError(validation.error || "Invalid credentials");
40
+ onLoginError?.(validation.error || "Invalid credentials");
41
+ return;
42
+ }
43
+
44
+ setIsLoading(true);
45
+
46
+ try {
47
+ // In production, call your auth API
48
+ // const user = await login({ email, password });
49
+
50
+ // Simulate API call
51
+ await new Promise((resolve) => setTimeout(resolve, 1000));
52
+
53
+ // Success
54
+ const mockUser = {
55
+ id: "1",
56
+ email,
57
+ name: email.split("@")[0],
58
+ };
59
+
60
+ await onLoginSuccess?.(mockUser);
61
+ navigate(config.afterLoginRoute);
62
+ } catch (err) {
63
+ const errorMessage = err instanceof Error ? err.message : "Login failed";
64
+ setError(errorMessage);
65
+ onLoginError?.(errorMessage);
66
+ } finally {
67
+ setIsLoading(false);
68
+ }
69
+ };
70
+
71
+ const passwordStrength = calculatePasswordStrength(password);
72
+
73
+ return (
74
+ <form onSubmit={handleSubmit} className="w-full max-w-md space-y-6">
75
+ {/* Header */}
76
+ <div className="text-center">
77
+ <h1 className="text-3xl font-extrabold text-foreground mb-2">
78
+ Welcome back
79
+ </h1>
80
+ <p className="text-muted-foreground">
81
+ Sign in to your {config.brandName} account
82
+ </p>
83
+ </div>
84
+
85
+ {/* Error Message */}
86
+ {error && (
87
+ <div className="bg-destructive/10 border border-destructive/20 text-destructive px-4 py-3 rounded-lg text-sm">
88
+ {error}
89
+ </div>
90
+ )}
91
+
92
+ {/* Email Field */}
93
+ <div className="space-y-2">
94
+ <label htmlFor="email" className="text-sm font-medium text-foreground">
95
+ Email
96
+ </label>
97
+ <input
98
+ id="email"
99
+ type="email"
100
+ value={email}
101
+ onChange={(e) => setEmail(e.target.value)}
102
+ placeholder="you@example.com"
103
+ className={cn(
104
+ "w-full px-4 py-3 rounded-lg border bg-background",
105
+ "focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent",
106
+ "placeholder:text-muted-foreground",
107
+ error && "border-destructive"
108
+ )}
109
+ disabled={isLoading}
110
+ autoComplete="email"
111
+ />
112
+ </div>
113
+
114
+ {/* Password Field */}
115
+ <div className="space-y-2">
116
+ <label htmlFor="password" className="text-sm font-medium text-foreground">
117
+ Password
118
+ </label>
119
+ <div className="relative">
120
+ <input
121
+ id="password"
122
+ type={showPassword ? "text" : "password"}
123
+ value={password}
124
+ onChange={(e) => setPassword(e.target.value)}
125
+ placeholder="••••••••"
126
+ className={cn(
127
+ "w-full px-4 py-3 rounded-lg border bg-background pr-12",
128
+ "focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent",
129
+ "placeholder:text-muted-foreground",
130
+ error && "border-destructive"
131
+ )}
132
+ disabled={isLoading}
133
+ autoComplete="current-password"
134
+ />
135
+ <button
136
+ type="button"
137
+ onClick={() => setShowPassword(!showPassword)}
138
+ className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors"
139
+ tabIndex={-1}
140
+ >
141
+ {showPassword ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
142
+ </button>
143
+ </div>
144
+ {/* Password Strength Indicator */}
145
+ {password && (
146
+ <div className="flex items-center gap-2">
147
+ <div className="flex-1 h-1 bg-muted rounded-full overflow-hidden">
148
+ <div
149
+ className={cn(
150
+ "h-full transition-all duration-300",
151
+ passwordStrength < 25 && "bg-destructive",
152
+ passwordStrength >= 25 && passwordStrength < 50 && "bg-orange-500",
153
+ passwordStrength >= 50 && passwordStrength < 75 && "bg-yellow-500",
154
+ passwordStrength >= 75 && "bg-green-500"
155
+ )}
156
+ style={{ width: `${passwordStrength}%` }}
157
+ />
158
+ </div>
159
+ <span className="text-xs text-muted-foreground">
160
+ {passwordStrength < 25 && "Weak"}
161
+ {passwordStrength >= 25 && passwordStrength < 50 && "Fair"}
162
+ {passwordStrength >= 50 && passwordStrength < 75 && "Good"}
163
+ {passwordStrength >= 75 && "Strong"}
164
+ </span>
165
+ </div>
166
+ )}
167
+ </div>
168
+
169
+ {/* Remember Me & Forgot Password */}
170
+ {(showRememberMe || showForgotPassword) && (
171
+ <div className="flex items-center justify-between">
172
+ {showRememberMe && (
173
+ <label className="flex items-center gap-2 cursor-pointer">
174
+ <input
175
+ type="checkbox"
176
+ checked={rememberMe}
177
+ onChange={(e) => setRememberMe(e.target.checked)}
178
+ className="w-4 h-4 rounded border-border text-primary focus:ring-2 focus:ring-primary"
179
+ />
180
+ <span className="text-sm text-muted-foreground">Remember me</span>
181
+ </label>
182
+ )}
183
+ {showForgotPassword && config.forgotPasswordRoute && (
184
+ <button
185
+ type="button"
186
+ onClick={() => navigate(config.forgotPasswordRoute!)}
187
+ className="text-sm text-primary hover:underline font-medium"
188
+ >
189
+ Forgot password?
190
+ </button>
191
+ )}
192
+ </div>
193
+ )}
194
+
195
+ {/* Submit Button */}
196
+ <Button
197
+ type="submit"
198
+ className="w-full h-12 text-base font-bold rounded-full"
199
+ disabled={isLoading}
200
+ >
201
+ {isLoading ? (
202
+ <>
203
+ <Loader2 className="h-4 w-4 mr-2 animate-spin" />
204
+ Signing in...
205
+ </>
206
+ ) : (
207
+ "Sign in"
208
+ )}
209
+ </Button>
210
+
211
+ {/* Register Link */}
212
+ {showRegisterLink && config.registerRoute && (
213
+ <p className="text-center text-sm text-muted-foreground">
214
+ Don't have an account?{" "}
215
+ <button
216
+ type="button"
217
+ onClick={() => navigate(config.registerRoute)}
218
+ className="text-primary hover:underline font-medium"
219
+ >
220
+ Sign up
221
+ </button>
222
+ </p>
223
+ )}
224
+ </form>
225
+ );
226
+ };
227
+
228
+ export default LoginForm;