@liedsonc/core-auth-kit 0.1.0 → 0.2.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 (35) hide show
  1. package/README.md +802 -759
  2. package/auth-ui/bin/init.js +126 -0
  3. package/auth-ui/components/auth-card.tsx +49 -49
  4. package/auth-ui/components/auth-form.tsx +53 -53
  5. package/auth-ui/components/error-message.tsx +24 -24
  6. package/auth-ui/components/form-field.tsx +39 -39
  7. package/auth-ui/components/loading-spinner.tsx +19 -19
  8. package/auth-ui/components/oauth-buttons.tsx +49 -49
  9. package/auth-ui/components/password-input.tsx +93 -93
  10. package/auth-ui/components/success-message.tsx +24 -24
  11. package/auth-ui/package.json +61 -55
  12. package/auth-ui/pages/forgot-password/index.tsx +83 -83
  13. package/auth-ui/pages/login/index.tsx +119 -119
  14. package/auth-ui/pages/register/index.tsx +149 -149
  15. package/auth-ui/pages/reset-password/index.tsx +133 -133
  16. package/auth-ui/pages/verify-email/index.tsx +143 -143
  17. package/auth-ui/templates/app/(auth)/forgot-password/page.tsx +5 -0
  18. package/auth-ui/templates/app/(auth)/layout.tsx +12 -0
  19. package/auth-ui/templates/app/(auth)/login/page.tsx +5 -0
  20. package/auth-ui/templates/app/(auth)/register/page.tsx +5 -0
  21. package/auth-ui/templates/app/(auth)/reset-password/page.tsx +5 -0
  22. package/auth-ui/templates/app/(auth)/verify-email/page.tsx +5 -0
  23. package/auth-ui/templates/app/api/auth/forgot-password/route.ts +16 -0
  24. package/auth-ui/templates/app/api/auth/login/route.ts +16 -0
  25. package/auth-ui/templates/app/api/auth/logout/route.ts +8 -0
  26. package/auth-ui/templates/app/api/auth/register/route.ts +16 -0
  27. package/auth-ui/templates/app/api/auth/reset-password/route.ts +16 -0
  28. package/auth-ui/templates/app/api/auth/session/route.ts +8 -0
  29. package/auth-ui/templates/app/api/auth/verify-email/route.ts +16 -0
  30. package/auth-ui/templates/env.example +25 -0
  31. package/auth-ui/templates/lib/auth-client.ts +20 -0
  32. package/auth-ui/templates/lib/auth-config.ts +43 -0
  33. package/auth-ui/tsconfig.json +4 -15
  34. package/package.json +17 -6
  35. package/tailwind.config.ts +41 -41
@@ -1,119 +1,119 @@
1
- "use client";
2
-
3
- import Link from "next/link";
4
- import { useAuth } from "../../hooks/use-auth";
5
- import { useOAuth } from "../../hooks/use-oauth";
6
- import { useAuthUIConfig } from "../../context";
7
- import { AuthCard } from "../../components/auth-card";
8
- import { AuthForm } from "../../components/auth-form";
9
- import { FormField } from "../../components/form-field";
10
- import { OAuthButtons } from "../../components/oauth-buttons";
11
- import { PasswordInput } from "../../components/password-input";
12
- import { Button } from "../../components/ui/button";
13
- import { Input } from "../../components/ui/input";
14
- import { useCallback, useState } from "react";
15
-
16
- const genericError = "Invalid email or password. Please try again.";
17
-
18
- export function LoginPage() {
19
- const config = useAuthUIConfig();
20
- const { login, loading, error, clearError } = useAuth();
21
- const oauthProviders = config.oauthProviders ?? [];
22
- const { signIn, loadingProvider } = useOAuth(
23
- useCallback(
24
- (provider) => {
25
- const baseUrl = typeof window !== "undefined" ? window.location.origin : "";
26
- return Promise.resolve(`${baseUrl}/api/auth/${provider}`);
27
- },
28
- []
29
- )
30
- );
31
- const [email, setEmail] = useState("");
32
- const [password, setPassword] = useState("");
33
- const [fieldErrors, setFieldErrors] = useState<{ email?: string; password?: string }>({});
34
-
35
- const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
36
- e.preventDefault();
37
- clearError();
38
- setFieldErrors({});
39
- const err: { email?: string; password?: string } = {};
40
- if (!email.trim()) err.email = "Email is required.";
41
- if (!password) err.password = "Password is required.";
42
- if (Object.keys(err).length) {
43
- setFieldErrors(err);
44
- return;
45
- }
46
- const result = await login(email.trim(), password);
47
- if (!result.success) setFieldErrors({ password: genericError });
48
- };
49
-
50
- return (
51
- <div className="flex min-h-screen flex-col items-center justify-center p-4">
52
- <AuthCard
53
- title="Sign in"
54
- subtitle="Enter your credentials to continue"
55
- footer={
56
- <div className="flex w-full flex-col items-center gap-2 text-center text-sm text-muted-foreground">
57
- <Link
58
- href="/register"
59
- className="underline underline-offset-4 hover:text-foreground"
60
- >
61
- Create an account
62
- </Link>
63
- <Link
64
- href="/forgot-password"
65
- className="underline underline-offset-4 hover:text-foreground"
66
- >
67
- Forgot password?
68
- </Link>
69
- </div>
70
- }
71
- >
72
- <AuthForm onSubmit={handleSubmit} loading={loading} error={error ? genericError : undefined}>
73
- <OAuthButtons
74
- providers={oauthProviders}
75
- loadingProvider={loadingProvider}
76
- onSignIn={signIn}
77
- />
78
- <div className="relative">
79
- <div className="absolute inset-0 flex items-center">
80
- <span className="w-full border-t border-border" />
81
- </div>
82
- <div className="relative flex justify-center text-xs uppercase">
83
- <span className="bg-card px-2 text-muted-foreground">Or continue with email</span>
84
- </div>
85
- </div>
86
- <FormField label="Email" htmlFor="login-email" error={fieldErrors.email} required>
87
- <Input
88
- id="login-email"
89
- type="email"
90
- autoComplete="email"
91
- placeholder="you@example.com"
92
- value={email}
93
- onChange={(e) => setEmail(e.target.value)}
94
- disabled={loading}
95
- aria-invalid={!!fieldErrors.email}
96
- />
97
- </FormField>
98
- <FormField label="Password" htmlFor="login-password" error={fieldErrors.password} required>
99
- <PasswordInput
100
- id="login-password"
101
- placeholder="••••••••"
102
- value={password}
103
- onChange={(e) => setPassword(e.target.value)}
104
- disabled={loading}
105
- aria-invalid={!!fieldErrors.password}
106
- />
107
- </FormField>
108
- <Button type="submit" className="w-full" disabled={loading}>
109
- {loading ? (
110
- <span className="inline-block size-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
111
- ) : (
112
- "Sign in"
113
- )}
114
- </Button>
115
- </AuthForm>
116
- </AuthCard>
117
- </div>
118
- );
119
- }
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { useAuth } from "../../hooks/use-auth";
5
+ import { useOAuth } from "../../hooks/use-oauth";
6
+ import { useAuthUIConfig } from "../../context";
7
+ import { AuthCard } from "../../components/auth-card";
8
+ import { AuthForm } from "../../components/auth-form";
9
+ import { FormField } from "../../components/form-field";
10
+ import { OAuthButtons } from "../../components/oauth-buttons";
11
+ import { PasswordInput } from "../../components/password-input";
12
+ import { Button } from "../../components/ui/button";
13
+ import { Input } from "../../components/ui/input";
14
+ import { useCallback, useState } from "react";
15
+
16
+ const genericError = "Invalid email or password. Please try again.";
17
+
18
+ export function LoginPage() {
19
+ const config = useAuthUIConfig();
20
+ const { login, loading, error, clearError } = useAuth();
21
+ const oauthProviders = config.oauthProviders ?? [];
22
+ const { signIn, loadingProvider } = useOAuth(
23
+ useCallback(
24
+ (provider) => {
25
+ const baseUrl = typeof window !== "undefined" ? window.location.origin : "";
26
+ return Promise.resolve(`${baseUrl}/api/auth/${provider}`);
27
+ },
28
+ []
29
+ )
30
+ );
31
+ const [email, setEmail] = useState("");
32
+ const [password, setPassword] = useState("");
33
+ const [fieldErrors, setFieldErrors] = useState<{ email?: string; password?: string }>({});
34
+
35
+ const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
36
+ e.preventDefault();
37
+ clearError();
38
+ setFieldErrors({});
39
+ const err: { email?: string; password?: string } = {};
40
+ if (!email.trim()) err.email = "Email is required.";
41
+ if (!password) err.password = "Password is required.";
42
+ if (Object.keys(err).length) {
43
+ setFieldErrors(err);
44
+ return;
45
+ }
46
+ const result = await login(email.trim(), password);
47
+ if (!result.success) setFieldErrors({ password: genericError });
48
+ };
49
+
50
+ return (
51
+ <div className="flex min-h-screen flex-col items-center justify-center p-4">
52
+ <AuthCard
53
+ title="Sign in"
54
+ subtitle="Enter your credentials to continue"
55
+ footer={
56
+ <div className="flex w-full flex-col items-center gap-2 text-center text-sm text-muted-foreground">
57
+ <Link
58
+ href="/register"
59
+ className="underline underline-offset-4 hover:text-foreground"
60
+ >
61
+ Create an account
62
+ </Link>
63
+ <Link
64
+ href="/forgot-password"
65
+ className="underline underline-offset-4 hover:text-foreground"
66
+ >
67
+ Forgot password?
68
+ </Link>
69
+ </div>
70
+ }
71
+ >
72
+ <AuthForm onSubmit={handleSubmit} loading={loading} error={error ? genericError : undefined}>
73
+ <OAuthButtons
74
+ providers={oauthProviders}
75
+ loadingProvider={loadingProvider}
76
+ onSignIn={signIn}
77
+ />
78
+ <div className="relative">
79
+ <div className="absolute inset-0 flex items-center">
80
+ <span className="w-full border-t border-border" />
81
+ </div>
82
+ <div className="relative flex justify-center text-xs uppercase">
83
+ <span className="bg-card px-2 text-muted-foreground">Or continue with email</span>
84
+ </div>
85
+ </div>
86
+ <FormField label="Email" htmlFor="login-email" error={fieldErrors.email} required>
87
+ <Input
88
+ id="login-email"
89
+ type="email"
90
+ autoComplete="email"
91
+ placeholder="you@example.com"
92
+ value={email}
93
+ onChange={(e) => setEmail(e.target.value)}
94
+ disabled={loading}
95
+ aria-invalid={!!fieldErrors.email}
96
+ />
97
+ </FormField>
98
+ <FormField label="Password" htmlFor="login-password" error={fieldErrors.password} required>
99
+ <PasswordInput
100
+ id="login-password"
101
+ placeholder="••••••••"
102
+ value={password}
103
+ onChange={(e) => setPassword(e.target.value)}
104
+ disabled={loading}
105
+ aria-invalid={!!fieldErrors.password}
106
+ />
107
+ </FormField>
108
+ <Button type="submit" className="w-full" disabled={loading}>
109
+ {loading ? (
110
+ <span className="inline-block size-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
111
+ ) : (
112
+ "Sign in"
113
+ )}
114
+ </Button>
115
+ </AuthForm>
116
+ </AuthCard>
117
+ </div>
118
+ );
119
+ }
@@ -1,149 +1,149 @@
1
- "use client";
2
-
3
- import Link from "next/link";
4
- import { useAuth } from "../../hooks/use-auth";
5
- import { useOAuth } from "../../hooks/use-oauth";
6
- import { useAuthUIConfig } from "../../context";
7
- import { AuthCard } from "../../components/auth-card";
8
- import { AuthForm } from "../../components/auth-form";
9
- import { FormField } from "../../components/form-field";
10
- import { OAuthButtons } from "../../components/oauth-buttons";
11
- import { PasswordInput } from "../../components/password-input";
12
- import { SuccessMessage } from "../../components/success-message";
13
- import { Button } from "../../components/ui/button";
14
- import { Input } from "../../components/ui/input";
15
- import { useCallback, useState } from "react";
16
-
17
- const genericError = "Something went wrong. Please try again.";
18
- const duplicateError = "An account with this email already exists.";
19
-
20
- export function RegisterPage() {
21
- const config = useAuthUIConfig();
22
- const { register, loading, error, clearError } = useAuth();
23
- const oauthProviders = config.oauthProviders ?? [];
24
- const { signIn, loadingProvider } = useOAuth(
25
- useCallback((provider) => {
26
- const baseUrl = typeof window !== "undefined" ? window.location.origin : "";
27
- return Promise.resolve(`${baseUrl}/api/auth/${provider}`);
28
- }, [])
29
- );
30
- const [email, setEmail] = useState("");
31
- const [password, setPassword] = useState("");
32
- const [confirmPassword, setConfirmPassword] = useState("");
33
- const [fieldErrors, setFieldErrors] = useState<{
34
- email?: string;
35
- password?: string;
36
- confirm?: string;
37
- }>({});
38
- const [showVerificationMessage, setShowVerificationMessage] = useState(false);
39
-
40
- const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
41
- e.preventDefault();
42
- clearError();
43
- setFieldErrors({});
44
- setShowVerificationMessage(false);
45
- const err: { email?: string; password?: string; confirm?: string } = {};
46
- if (!email.trim()) err.email = "Email is required.";
47
- if (!password) err.password = "Password is required.";
48
- if (password.length < 8) err.password = "Password must be at least 8 characters.";
49
- if (password !== confirmPassword) err.confirm = "Passwords do not match.";
50
- if (Object.keys(err).length) {
51
- setFieldErrors(err);
52
- return;
53
- }
54
- const result = await register(email.trim(), password);
55
- if (result.success) {
56
- setShowVerificationMessage(true);
57
- } else {
58
- const isDuplicate = result.error.code === "DUPLICATE_EMAIL" || result.error.code === "EMAIL_TAKEN";
59
- setFieldErrors({ email: isDuplicate ? duplicateError : genericError });
60
- }
61
- };
62
-
63
- return (
64
- <div className="flex min-h-screen flex-col items-center justify-center p-4">
65
- <AuthCard
66
- title="Create an account"
67
- subtitle="Enter your details to get started"
68
- footer={
69
- <div className="flex w-full justify-center text-sm text-muted-foreground">
70
- <Link
71
- href="/login"
72
- className="underline underline-offset-4 hover:text-foreground"
73
- >
74
- Already have an account? Sign in
75
- </Link>
76
- </div>
77
- }
78
- >
79
- {showVerificationMessage ? (
80
- <SuccessMessage>
81
- Check your email to verify your account. You can sign in after verification.
82
- </SuccessMessage>
83
- ) : (
84
- <AuthForm onSubmit={handleSubmit} loading={loading} error={error ? genericError : undefined}>
85
- <OAuthButtons
86
- providers={oauthProviders}
87
- loadingProvider={loadingProvider}
88
- onSignIn={signIn}
89
- />
90
- <div className="relative">
91
- <div className="absolute inset-0 flex items-center">
92
- <span className="w-full border-t border-border" />
93
- </div>
94
- <div className="relative flex justify-center text-xs uppercase">
95
- <span className="bg-card px-2 text-muted-foreground">Or continue with email</span>
96
- </div>
97
- </div>
98
- <FormField label="Email" htmlFor="register-email" error={fieldErrors.email} required>
99
- <Input
100
- id="register-email"
101
- type="email"
102
- autoComplete="email"
103
- placeholder="you@example.com"
104
- value={email}
105
- onChange={(e) => setEmail(e.target.value)}
106
- disabled={loading}
107
- aria-invalid={!!fieldErrors.email}
108
- />
109
- </FormField>
110
- <FormField label="Password" htmlFor="register-password" error={fieldErrors.password} required>
111
- <PasswordInput
112
- id="register-password"
113
- placeholder="••••••••"
114
- value={password}
115
- onChange={(e) => setPassword(e.target.value)}
116
- disabled={loading}
117
- showStrength
118
- aria-invalid={!!fieldErrors.password}
119
- />
120
- </FormField>
121
- <FormField
122
- label="Confirm password"
123
- htmlFor="register-confirm"
124
- error={fieldErrors.confirm}
125
- required
126
- >
127
- <PasswordInput
128
- id="register-confirm"
129
- placeholder="••••••••"
130
- autoComplete="new-password"
131
- value={confirmPassword}
132
- onChange={(e) => setConfirmPassword(e.target.value)}
133
- disabled={loading}
134
- aria-invalid={!!fieldErrors.confirm}
135
- />
136
- </FormField>
137
- <Button type="submit" className="w-full" disabled={loading}>
138
- {loading ? (
139
- <span className="inline-block size-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
140
- ) : (
141
- "Create account"
142
- )}
143
- </Button>
144
- </AuthForm>
145
- )}
146
- </AuthCard>
147
- </div>
148
- );
149
- }
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { useAuth } from "../../hooks/use-auth";
5
+ import { useOAuth } from "../../hooks/use-oauth";
6
+ import { useAuthUIConfig } from "../../context";
7
+ import { AuthCard } from "../../components/auth-card";
8
+ import { AuthForm } from "../../components/auth-form";
9
+ import { FormField } from "../../components/form-field";
10
+ import { OAuthButtons } from "../../components/oauth-buttons";
11
+ import { PasswordInput } from "../../components/password-input";
12
+ import { SuccessMessage } from "../../components/success-message";
13
+ import { Button } from "../../components/ui/button";
14
+ import { Input } from "../../components/ui/input";
15
+ import { useCallback, useState } from "react";
16
+
17
+ const genericError = "Something went wrong. Please try again.";
18
+ const duplicateError = "An account with this email already exists.";
19
+
20
+ export function RegisterPage() {
21
+ const config = useAuthUIConfig();
22
+ const { register, loading, error, clearError } = useAuth();
23
+ const oauthProviders = config.oauthProviders ?? [];
24
+ const { signIn, loadingProvider } = useOAuth(
25
+ useCallback((provider) => {
26
+ const baseUrl = typeof window !== "undefined" ? window.location.origin : "";
27
+ return Promise.resolve(`${baseUrl}/api/auth/${provider}`);
28
+ }, [])
29
+ );
30
+ const [email, setEmail] = useState("");
31
+ const [password, setPassword] = useState("");
32
+ const [confirmPassword, setConfirmPassword] = useState("");
33
+ const [fieldErrors, setFieldErrors] = useState<{
34
+ email?: string;
35
+ password?: string;
36
+ confirm?: string;
37
+ }>({});
38
+ const [showVerificationMessage, setShowVerificationMessage] = useState(false);
39
+
40
+ const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
41
+ e.preventDefault();
42
+ clearError();
43
+ setFieldErrors({});
44
+ setShowVerificationMessage(false);
45
+ const err: { email?: string; password?: string; confirm?: string } = {};
46
+ if (!email.trim()) err.email = "Email is required.";
47
+ if (!password) err.password = "Password is required.";
48
+ if (password.length < 8) err.password = "Password must be at least 8 characters.";
49
+ if (password !== confirmPassword) err.confirm = "Passwords do not match.";
50
+ if (Object.keys(err).length) {
51
+ setFieldErrors(err);
52
+ return;
53
+ }
54
+ const result = await register(email.trim(), password);
55
+ if (result.success) {
56
+ setShowVerificationMessage(true);
57
+ } else {
58
+ const isDuplicate = result.error.code === "DUPLICATE_EMAIL" || result.error.code === "EMAIL_TAKEN";
59
+ setFieldErrors({ email: isDuplicate ? duplicateError : genericError });
60
+ }
61
+ };
62
+
63
+ return (
64
+ <div className="flex min-h-screen flex-col items-center justify-center p-4">
65
+ <AuthCard
66
+ title="Create an account"
67
+ subtitle="Enter your details to get started"
68
+ footer={
69
+ <div className="flex w-full justify-center text-sm text-muted-foreground">
70
+ <Link
71
+ href="/login"
72
+ className="underline underline-offset-4 hover:text-foreground"
73
+ >
74
+ Already have an account? Sign in
75
+ </Link>
76
+ </div>
77
+ }
78
+ >
79
+ {showVerificationMessage ? (
80
+ <SuccessMessage>
81
+ Check your email to verify your account. You can sign in after verification.
82
+ </SuccessMessage>
83
+ ) : (
84
+ <AuthForm onSubmit={handleSubmit} loading={loading} error={error ? genericError : undefined}>
85
+ <OAuthButtons
86
+ providers={oauthProviders}
87
+ loadingProvider={loadingProvider}
88
+ onSignIn={signIn}
89
+ />
90
+ <div className="relative">
91
+ <div className="absolute inset-0 flex items-center">
92
+ <span className="w-full border-t border-border" />
93
+ </div>
94
+ <div className="relative flex justify-center text-xs uppercase">
95
+ <span className="bg-card px-2 text-muted-foreground">Or continue with email</span>
96
+ </div>
97
+ </div>
98
+ <FormField label="Email" htmlFor="register-email" error={fieldErrors.email} required>
99
+ <Input
100
+ id="register-email"
101
+ type="email"
102
+ autoComplete="email"
103
+ placeholder="you@example.com"
104
+ value={email}
105
+ onChange={(e) => setEmail(e.target.value)}
106
+ disabled={loading}
107
+ aria-invalid={!!fieldErrors.email}
108
+ />
109
+ </FormField>
110
+ <FormField label="Password" htmlFor="register-password" error={fieldErrors.password} required>
111
+ <PasswordInput
112
+ id="register-password"
113
+ placeholder="••••••••"
114
+ value={password}
115
+ onChange={(e) => setPassword(e.target.value)}
116
+ disabled={loading}
117
+ showStrength
118
+ aria-invalid={!!fieldErrors.password}
119
+ />
120
+ </FormField>
121
+ <FormField
122
+ label="Confirm password"
123
+ htmlFor="register-confirm"
124
+ error={fieldErrors.confirm}
125
+ required
126
+ >
127
+ <PasswordInput
128
+ id="register-confirm"
129
+ placeholder="••••••••"
130
+ autoComplete="new-password"
131
+ value={confirmPassword}
132
+ onChange={(e) => setConfirmPassword(e.target.value)}
133
+ disabled={loading}
134
+ aria-invalid={!!fieldErrors.confirm}
135
+ />
136
+ </FormField>
137
+ <Button type="submit" className="w-full" disabled={loading}>
138
+ {loading ? (
139
+ <span className="inline-block size-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
140
+ ) : (
141
+ "Create account"
142
+ )}
143
+ </Button>
144
+ </AuthForm>
145
+ )}
146
+ </AuthCard>
147
+ </div>
148
+ );
149
+ }