nexu-app 2.0.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 (106) hide show
  1. package/README.md +149 -0
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +1192 -0
  4. package/package.json +43 -0
  5. package/templates/default/.changeset/config.json +11 -0
  6. package/templates/default/.eslintignore +16 -0
  7. package/templates/default/.eslintrc.js +67 -0
  8. package/templates/default/.github/actions/build/action.yml +35 -0
  9. package/templates/default/.github/actions/quality/action.yml +53 -0
  10. package/templates/default/.github/dependabot.yml +51 -0
  11. package/templates/default/.github/workflows/deploy-dev.yml +83 -0
  12. package/templates/default/.github/workflows/deploy-prod.yml +83 -0
  13. package/templates/default/.github/workflows/deploy-rec.yml +83 -0
  14. package/templates/default/.husky/commit-msg +1 -0
  15. package/templates/default/.husky/pre-commit +1 -0
  16. package/templates/default/.nexu-version +1 -0
  17. package/templates/default/.prettierignore +7 -0
  18. package/templates/default/.prettierrc +19 -0
  19. package/templates/default/.vscode/extensions.json +14 -0
  20. package/templates/default/.vscode/settings.json +36 -0
  21. package/templates/default/apps/gitkeep +0 -0
  22. package/templates/default/commitlint.config.js +26 -0
  23. package/templates/default/docker/docker-compose.dev.yml +49 -0
  24. package/templates/default/docker/docker-compose.prod.yml +64 -0
  25. package/templates/default/docker/docker-compose.yml +6 -0
  26. package/templates/default/docs/architecture.md +452 -0
  27. package/templates/default/docs/cli.md +330 -0
  28. package/templates/default/docs/contributing.md +462 -0
  29. package/templates/default/docs/scripts.md +460 -0
  30. package/templates/default/gitignore +44 -0
  31. package/templates/default/lintstagedrc.cjs +4 -0
  32. package/templates/default/package.json +51 -0
  33. package/templates/default/packages/auth/package.json +61 -0
  34. package/templates/default/packages/auth/src/components/ProtectedRoute.tsx +75 -0
  35. package/templates/default/packages/auth/src/components/SignInForm.tsx +153 -0
  36. package/templates/default/packages/auth/src/components/SignUpForm.tsx +179 -0
  37. package/templates/default/packages/auth/src/components/SocialButtons.tsx +147 -0
  38. package/templates/default/packages/auth/src/components/index.ts +4 -0
  39. package/templates/default/packages/auth/src/hooks/index.ts +4 -0
  40. package/templates/default/packages/auth/src/hooks/useAuth.ts +51 -0
  41. package/templates/default/packages/auth/src/hooks/useRequireAuth.ts +54 -0
  42. package/templates/default/packages/auth/src/hooks/useSession.ts +48 -0
  43. package/templates/default/packages/auth/src/hooks/useUser.ts +48 -0
  44. package/templates/default/packages/auth/src/index.ts +45 -0
  45. package/templates/default/packages/auth/src/next/index.ts +18 -0
  46. package/templates/default/packages/auth/src/next/middleware.ts +183 -0
  47. package/templates/default/packages/auth/src/next/server.ts +219 -0
  48. package/templates/default/packages/auth/src/providers/AuthContext.tsx +435 -0
  49. package/templates/default/packages/auth/src/providers/index.ts +1 -0
  50. package/templates/default/packages/auth/src/types/index.ts +284 -0
  51. package/templates/default/packages/auth/src/utils/api.ts +228 -0
  52. package/templates/default/packages/auth/src/utils/index.ts +3 -0
  53. package/templates/default/packages/auth/src/utils/oauth.ts +230 -0
  54. package/templates/default/packages/auth/src/utils/token.ts +204 -0
  55. package/templates/default/packages/auth/tsconfig.json +14 -0
  56. package/templates/default/packages/auth/tsup.config.ts +18 -0
  57. package/templates/default/packages/cache/package.json +26 -0
  58. package/templates/default/packages/cache/src/index.ts +137 -0
  59. package/templates/default/packages/cache/tsconfig.json +9 -0
  60. package/templates/default/packages/cache/tsup.config.ts +9 -0
  61. package/templates/default/packages/config/eslint/index.js +20 -0
  62. package/templates/default/packages/config/package.json +9 -0
  63. package/templates/default/packages/config/typescript/base.json +26 -0
  64. package/templates/default/packages/constants/package.json +26 -0
  65. package/templates/default/packages/constants/src/index.ts +121 -0
  66. package/templates/default/packages/constants/tsconfig.json +9 -0
  67. package/templates/default/packages/constants/tsup.config.ts +9 -0
  68. package/templates/default/packages/logger/package.json +27 -0
  69. package/templates/default/packages/logger/src/index.ts +197 -0
  70. package/templates/default/packages/logger/tsconfig.json +11 -0
  71. package/templates/default/packages/logger/tsup.config.ts +9 -0
  72. package/templates/default/packages/result/package.json +26 -0
  73. package/templates/default/packages/result/src/index.ts +142 -0
  74. package/templates/default/packages/result/tsconfig.json +9 -0
  75. package/templates/default/packages/result/tsup.config.ts +9 -0
  76. package/templates/default/packages/types/package.json +26 -0
  77. package/templates/default/packages/types/src/index.ts +78 -0
  78. package/templates/default/packages/types/tsconfig.json +9 -0
  79. package/templates/default/packages/types/tsup.config.ts +10 -0
  80. package/templates/default/packages/ui/package.json +38 -0
  81. package/templates/default/packages/ui/src/components/Button.tsx +58 -0
  82. package/templates/default/packages/ui/src/components/Card.tsx +85 -0
  83. package/templates/default/packages/ui/src/components/Input.tsx +45 -0
  84. package/templates/default/packages/ui/src/index.ts +15 -0
  85. package/templates/default/packages/ui/tsconfig.json +11 -0
  86. package/templates/default/packages/ui/tsup.config.ts +11 -0
  87. package/templates/default/packages/utils/package.json +30 -0
  88. package/templates/default/packages/utils/src/index.test.ts +130 -0
  89. package/templates/default/packages/utils/src/index.ts +154 -0
  90. package/templates/default/packages/utils/tsconfig.json +10 -0
  91. package/templates/default/packages/utils/tsup.config.ts +10 -0
  92. package/templates/default/pnpm-workspace.yaml +3 -0
  93. package/templates/default/scripts/audit.mjs +700 -0
  94. package/templates/default/scripts/deploy.mjs +40 -0
  95. package/templates/default/scripts/generate-app.mjs +808 -0
  96. package/templates/default/scripts/lib/package-manager.mjs +186 -0
  97. package/templates/default/scripts/setup.mjs +102 -0
  98. package/templates/default/services/.env.example +16 -0
  99. package/templates/default/services/docker-compose.yml +207 -0
  100. package/templates/default/services/grafana/provisioning/dashboards/dashboards.yml +11 -0
  101. package/templates/default/services/grafana/provisioning/datasources/datasources.yml +9 -0
  102. package/templates/default/services/postgres/init/gitkeep +2 -0
  103. package/templates/default/services/prometheus/prometheus.yml +13 -0
  104. package/templates/default/tsconfig.json +27 -0
  105. package/templates/default/turbo.json +40 -0
  106. package/templates/default/vitest.config.ts +15 -0
@@ -0,0 +1,153 @@
1
+ 'use client';
2
+
3
+ import React, { useState } from 'react';
4
+
5
+ import { useAuth } from '../hooks/useAuth';
6
+ import type { AuthError, SignInFormProps } from '../types';
7
+
8
+ import { SocialButtons } from './SocialButtons';
9
+
10
+ /**
11
+ * Sign in form component
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * <SignInForm
16
+ * providers={['google', 'github']}
17
+ * onSuccess={(response) => router.push('/dashboard')}
18
+ * showRememberMe
19
+ * showForgotPassword
20
+ * forgotPasswordUrl="/forgot-password"
21
+ * signUpUrl="/signup"
22
+ * />
23
+ * ```
24
+ */
25
+ export function SignInForm({
26
+ onSuccess,
27
+ onError,
28
+ providers = [],
29
+ showRememberMe = true,
30
+ showForgotPassword = true,
31
+ forgotPasswordUrl = '/forgot-password',
32
+ signUpUrl = '/signup',
33
+ redirectUrl,
34
+ className,
35
+ }: SignInFormProps) {
36
+ const { signIn, isLoading, error } = useAuth();
37
+
38
+ const [email, setEmail] = useState('');
39
+ const [password, setPassword] = useState('');
40
+ const [remember, setRemember] = useState(false);
41
+ const [formError, setFormError] = useState<string | null>(null);
42
+
43
+ const handleSubmit = async (e: React.FormEvent) => {
44
+ e.preventDefault();
45
+ setFormError(null);
46
+
47
+ if (!email || !password) {
48
+ setFormError('Please fill in all fields');
49
+ return;
50
+ }
51
+
52
+ try {
53
+ const response = await signIn({ email, password, remember });
54
+ onSuccess?.(response);
55
+
56
+ if (redirectUrl && typeof window !== 'undefined') {
57
+ window.location.href = redirectUrl;
58
+ }
59
+ } catch (err) {
60
+ const authError = err as AuthError;
61
+ setFormError(authError.message);
62
+ onError?.(authError);
63
+ }
64
+ };
65
+
66
+ const displayError = formError || error?.message;
67
+
68
+ return (
69
+ <div className={className}>
70
+ <form onSubmit={e => void handleSubmit(e)}>
71
+ {displayError && (
72
+ <div role="alert" aria-live="polite">
73
+ {displayError}
74
+ </div>
75
+ )}
76
+
77
+ <div>
78
+ <label htmlFor="email">Email</label>
79
+ <input
80
+ id="email"
81
+ name="email"
82
+ type="email"
83
+ autoComplete="email"
84
+ required
85
+ value={email}
86
+ onChange={e => setEmail(e.target.value)}
87
+ disabled={isLoading}
88
+ placeholder="you@example.com"
89
+ />
90
+ </div>
91
+
92
+ <div>
93
+ <label htmlFor="password">Password</label>
94
+ <input
95
+ id="password"
96
+ name="password"
97
+ type="password"
98
+ autoComplete="current-password"
99
+ required
100
+ value={password}
101
+ onChange={e => setPassword(e.target.value)}
102
+ disabled={isLoading}
103
+ placeholder="••••••••"
104
+ />
105
+ </div>
106
+
107
+ <div>
108
+ {showRememberMe && (
109
+ <label>
110
+ <input
111
+ type="checkbox"
112
+ checked={remember}
113
+ onChange={e => setRemember(e.target.checked)}
114
+ disabled={isLoading}
115
+ />
116
+ <span>Remember me</span>
117
+ </label>
118
+ )}
119
+
120
+ {showForgotPassword && <a href={forgotPasswordUrl}>Forgot password?</a>}
121
+ </div>
122
+
123
+ <button type="submit" disabled={isLoading}>
124
+ {isLoading ? 'Signing in...' : 'Sign in'}
125
+ </button>
126
+ </form>
127
+
128
+ {providers.length > 0 && (
129
+ <>
130
+ <div>
131
+ <span>Or continue with</span>
132
+ </div>
133
+ <SocialButtons
134
+ providers={providers}
135
+ mode="signin"
136
+ onSuccess={() => {
137
+ if (redirectUrl && typeof window !== 'undefined') {
138
+ window.location.href = redirectUrl;
139
+ }
140
+ }}
141
+ onError={onError}
142
+ />
143
+ </>
144
+ )}
145
+
146
+ {signUpUrl && (
147
+ <p>
148
+ Don&apos;t have an account? <a href={signUpUrl}>Sign up</a>
149
+ </p>
150
+ )}
151
+ </div>
152
+ );
153
+ }
@@ -0,0 +1,179 @@
1
+ 'use client';
2
+
3
+ import React, { useState } from 'react';
4
+
5
+ import { useAuth } from '../hooks/useAuth';
6
+ import type { AuthError, SignUpFormProps } from '../types';
7
+
8
+ import { SocialButtons } from './SocialButtons';
9
+
10
+ /**
11
+ * Sign up form component
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * <SignUpForm
16
+ * providers={['google', 'github']}
17
+ * onSuccess={(response) => router.push('/onboarding')}
18
+ * showName
19
+ * signInUrl="/signin"
20
+ * />
21
+ * ```
22
+ */
23
+ export function SignUpForm({
24
+ onSuccess,
25
+ onError,
26
+ providers = [],
27
+ showName = true,
28
+ signInUrl = '/signin',
29
+ redirectUrl,
30
+ className,
31
+ }: SignUpFormProps) {
32
+ const { signUp, isLoading, error } = useAuth();
33
+
34
+ const [email, setEmail] = useState('');
35
+ const [password, setPassword] = useState('');
36
+ const [confirmPassword, setConfirmPassword] = useState('');
37
+ const [name, setName] = useState('');
38
+ const [formError, setFormError] = useState<string | null>(null);
39
+
40
+ const handleSubmit = async (e: React.FormEvent) => {
41
+ e.preventDefault();
42
+ setFormError(null);
43
+
44
+ if (!email || !password) {
45
+ setFormError('Please fill in all required fields');
46
+ return;
47
+ }
48
+
49
+ if (password !== confirmPassword) {
50
+ setFormError('Passwords do not match');
51
+ return;
52
+ }
53
+
54
+ if (password.length < 8) {
55
+ setFormError('Password must be at least 8 characters');
56
+ return;
57
+ }
58
+
59
+ try {
60
+ const response = await signUp({
61
+ email,
62
+ password,
63
+ name: showName ? name : undefined,
64
+ });
65
+ onSuccess?.(response);
66
+
67
+ if (redirectUrl && typeof window !== 'undefined') {
68
+ window.location.href = redirectUrl;
69
+ }
70
+ } catch (err) {
71
+ const authError = err as AuthError;
72
+ setFormError(authError.message);
73
+ onError?.(authError);
74
+ }
75
+ };
76
+
77
+ const displayError = formError || error?.message;
78
+
79
+ return (
80
+ <div className={className}>
81
+ <form onSubmit={e => void handleSubmit(e)}>
82
+ {displayError && (
83
+ <div role="alert" aria-live="polite">
84
+ {displayError}
85
+ </div>
86
+ )}
87
+
88
+ {showName && (
89
+ <div>
90
+ <label htmlFor="name">Name</label>
91
+ <input
92
+ id="name"
93
+ name="name"
94
+ type="text"
95
+ autoComplete="name"
96
+ value={name}
97
+ onChange={e => setName(e.target.value)}
98
+ disabled={isLoading}
99
+ placeholder="John Doe"
100
+ />
101
+ </div>
102
+ )}
103
+
104
+ <div>
105
+ <label htmlFor="email">Email</label>
106
+ <input
107
+ id="email"
108
+ name="email"
109
+ type="email"
110
+ autoComplete="email"
111
+ required
112
+ value={email}
113
+ onChange={e => setEmail(e.target.value)}
114
+ disabled={isLoading}
115
+ placeholder="you@example.com"
116
+ />
117
+ </div>
118
+
119
+ <div>
120
+ <label htmlFor="password">Password</label>
121
+ <input
122
+ id="password"
123
+ name="password"
124
+ type="password"
125
+ autoComplete="new-password"
126
+ required
127
+ value={password}
128
+ onChange={e => setPassword(e.target.value)}
129
+ disabled={isLoading}
130
+ placeholder="••••••••"
131
+ />
132
+ </div>
133
+
134
+ <div>
135
+ <label htmlFor="confirmPassword">Confirm Password</label>
136
+ <input
137
+ id="confirmPassword"
138
+ name="confirmPassword"
139
+ type="password"
140
+ autoComplete="new-password"
141
+ required
142
+ value={confirmPassword}
143
+ onChange={e => setConfirmPassword(e.target.value)}
144
+ disabled={isLoading}
145
+ placeholder="••••••••"
146
+ />
147
+ </div>
148
+
149
+ <button type="submit" disabled={isLoading}>
150
+ {isLoading ? 'Creating account...' : 'Create account'}
151
+ </button>
152
+ </form>
153
+
154
+ {providers.length > 0 && (
155
+ <>
156
+ <div>
157
+ <span>Or continue with</span>
158
+ </div>
159
+ <SocialButtons
160
+ providers={providers}
161
+ mode="signup"
162
+ onSuccess={() => {
163
+ if (redirectUrl && typeof window !== 'undefined') {
164
+ window.location.href = redirectUrl;
165
+ }
166
+ }}
167
+ onError={onError}
168
+ />
169
+ </>
170
+ )}
171
+
172
+ {signInUrl && (
173
+ <p>
174
+ Already have an account? <a href={signInUrl}>Sign in</a>
175
+ </p>
176
+ )}
177
+ </div>
178
+ );
179
+ }
@@ -0,0 +1,147 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ import { useAuth } from '../hooks/useAuth';
6
+ import type { AuthError, AuthProvider, SocialButtonsProps } from '../types';
7
+
8
+ /**
9
+ * Social provider icons (SVG)
10
+ */
11
+ const ProviderIcons: Record<string, React.ReactNode> = {
12
+ google: (
13
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
14
+ <path
15
+ d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
16
+ fill="#4285F4"
17
+ />
18
+ <path
19
+ d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
20
+ fill="#34A853"
21
+ />
22
+ <path
23
+ d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
24
+ fill="#FBBC05"
25
+ />
26
+ <path
27
+ d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
28
+ fill="#EA4335"
29
+ />
30
+ </svg>
31
+ ),
32
+ github: (
33
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
34
+ <path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
35
+ </svg>
36
+ ),
37
+ facebook: (
38
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="#1877F2">
39
+ <path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z" />
40
+ </svg>
41
+ ),
42
+ apple: (
43
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
44
+ <path d="M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z" />
45
+ </svg>
46
+ ),
47
+ twitter: (
48
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
49
+ <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
50
+ </svg>
51
+ ),
52
+ };
53
+
54
+ /**
55
+ * Provider display names
56
+ */
57
+ const ProviderNames: Record<string, string> = {
58
+ google: 'Google',
59
+ github: 'GitHub',
60
+ facebook: 'Facebook',
61
+ apple: 'Apple',
62
+ twitter: 'X (Twitter)',
63
+ };
64
+
65
+ interface SocialButtonProps {
66
+ provider: AuthProvider;
67
+ onClick: () => void;
68
+ disabled?: boolean;
69
+ mode?: 'signin' | 'signup';
70
+ className?: string;
71
+ }
72
+
73
+ /**
74
+ * Individual social login button
75
+ */
76
+ export function SocialButton({
77
+ provider,
78
+ onClick,
79
+ disabled,
80
+ mode = 'signin',
81
+ className,
82
+ }: SocialButtonProps) {
83
+ const actionText = mode === 'signin' ? 'Sign in' : 'Sign up';
84
+ const providerName = ProviderNames[provider] || provider;
85
+
86
+ return (
87
+ <button
88
+ type="button"
89
+ onClick={onClick}
90
+ disabled={disabled}
91
+ className={className}
92
+ aria-label={`${actionText} with ${providerName}`}
93
+ >
94
+ {ProviderIcons[provider]}
95
+ <span>
96
+ {actionText} with {providerName}
97
+ </span>
98
+ </button>
99
+ );
100
+ }
101
+
102
+ /**
103
+ * Social login buttons component
104
+ *
105
+ * @example
106
+ * ```tsx
107
+ * <SocialButtons
108
+ * providers={['google', 'github']}
109
+ * mode="signin"
110
+ * onSuccess={(provider) => console.log(`Signed in with ${provider}`)}
111
+ * />
112
+ * ```
113
+ */
114
+ export function SocialButtons({
115
+ providers,
116
+ onSuccess,
117
+ onError,
118
+ mode = 'signin',
119
+ className,
120
+ }: SocialButtonsProps) {
121
+ const { signInWithProvider, isLoading } = useAuth();
122
+
123
+ const handleProviderClick = async (provider: AuthProvider) => {
124
+ try {
125
+ await signInWithProvider(provider);
126
+ onSuccess?.(provider);
127
+ } catch (error) {
128
+ onError?.(error as AuthError);
129
+ }
130
+ };
131
+
132
+ if (providers.length === 0) return null;
133
+
134
+ return (
135
+ <div className={className}>
136
+ {providers.map(provider => (
137
+ <SocialButton
138
+ key={provider}
139
+ provider={provider}
140
+ onClick={() => void handleProviderClick(provider)}
141
+ disabled={isLoading}
142
+ mode={mode}
143
+ />
144
+ ))}
145
+ </div>
146
+ );
147
+ }
@@ -0,0 +1,4 @@
1
+ export { SignInForm } from './SignInForm';
2
+ export { SignUpForm } from './SignUpForm';
3
+ export { SocialButtons, SocialButton } from './SocialButtons';
4
+ export { ProtectedRoute } from './ProtectedRoute';
@@ -0,0 +1,4 @@
1
+ export { useAuth } from './useAuth';
2
+ export { useUser } from './useUser';
3
+ export { useSession } from './useSession';
4
+ export { useRequireAuth } from './useRequireAuth';
@@ -0,0 +1,51 @@
1
+ 'use client';
2
+
3
+ import { useAuthContext } from '../providers/AuthContext';
4
+ import type { UseAuthReturn } from '../types';
5
+
6
+ /**
7
+ * Hook to access authentication state and methods
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * function LoginButton() {
12
+ * const { isAuthenticated, user, signIn, signOut } = useAuth();
13
+ *
14
+ * if (isAuthenticated) {
15
+ * return (
16
+ * <div>
17
+ * <span>Welcome, {user?.name}</span>
18
+ * <button onClick={signOut}>Sign Out</button>
19
+ * </div>
20
+ * );
21
+ * }
22
+ *
23
+ * return (
24
+ * <button onClick={() => signIn({ email: 'user@example.com', password: 'password' })}>
25
+ * Sign In
26
+ * </button>
27
+ * );
28
+ * }
29
+ * ```
30
+ */
31
+ export function useAuth(): UseAuthReturn {
32
+ const context = useAuthContext();
33
+
34
+ return {
35
+ user: context.user,
36
+ session: context.session,
37
+ isAuthenticated: context.isAuthenticated,
38
+ isLoading: context.isLoading,
39
+ error: context.error,
40
+ signIn: context.signIn,
41
+ signUp: context.signUp,
42
+ signOut: context.signOut,
43
+ signInWithProvider: context.signInWithProvider,
44
+ refreshSession: context.refreshSession,
45
+ resetPassword: context.resetPassword,
46
+ updatePassword: context.updatePassword,
47
+ updateUser: context.updateUser,
48
+ verifyEmail: context.verifyEmail,
49
+ resendVerification: context.resendVerification,
50
+ };
51
+ }
@@ -0,0 +1,54 @@
1
+ 'use client';
2
+
3
+ import { useEffect } from 'react';
4
+
5
+ import { useAuth } from './useAuth';
6
+
7
+ interface UseRequireAuthOptions {
8
+ /**
9
+ * Redirect to this URL if not authenticated
10
+ */
11
+ redirectTo?: string;
12
+
13
+ /**
14
+ * Callback when not authenticated
15
+ */
16
+ onUnauthenticated?: () => void;
17
+ }
18
+
19
+ /**
20
+ * Hook to require authentication for a page/component
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * function ProtectedPage() {
25
+ * const { user, isLoading } = useRequireAuth({
26
+ * redirectTo: '/login',
27
+ * });
28
+ *
29
+ * if (isLoading) return <div>Loading...</div>;
30
+ *
31
+ * return <div>Welcome, {user?.name}!</div>;
32
+ * }
33
+ * ```
34
+ */
35
+ export function useRequireAuth(options: UseRequireAuthOptions = {}) {
36
+ const auth = useAuth();
37
+
38
+ useEffect(() => {
39
+ if (auth.isLoading) return;
40
+
41
+ if (!auth.isAuthenticated) {
42
+ if (options.redirectTo && typeof window !== 'undefined') {
43
+ const currentPath = window.location.pathname + window.location.search;
44
+ const redirectUrl = new URL(options.redirectTo, window.location.origin);
45
+ redirectUrl.searchParams.set('returnTo', currentPath);
46
+ window.location.href = redirectUrl.toString();
47
+ }
48
+
49
+ options.onUnauthenticated?.();
50
+ }
51
+ }, [auth.isAuthenticated, auth.isLoading, options]);
52
+
53
+ return auth;
54
+ }
@@ -0,0 +1,48 @@
1
+ 'use client';
2
+
3
+ import { useCallback } from 'react';
4
+
5
+ import { useAuthContext } from '../providers/AuthContext';
6
+ import type { UseSessionReturn } from '../types';
7
+
8
+ /**
9
+ * Hook to access and manage the current session
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * function SessionInfo() {
14
+ * const { session, isLoading, isValid, getToken, refresh } = useSession();
15
+ *
16
+ * if (isLoading) return <div>Loading...</div>;
17
+ * if (!session) return <div>No session</div>;
18
+ *
19
+ * return (
20
+ * <div>
21
+ * <p>Session valid: {isValid() ? 'Yes' : 'No'}</p>
22
+ * <p>Expires at: {new Date(session.expiresAt).toLocaleString()}</p>
23
+ * <button onClick={refresh}>Refresh Session</button>
24
+ * </div>
25
+ * );
26
+ * }
27
+ * ```
28
+ */
29
+ export function useSession(): UseSessionReturn {
30
+ const context = useAuthContext();
31
+
32
+ const isValid = useCallback((): boolean => {
33
+ if (!context.session) return false;
34
+ return Date.now() < context.session.expiresAt;
35
+ }, [context.session]);
36
+
37
+ const getToken = useCallback((): string | null => {
38
+ return context.tokenManager.getAccessToken();
39
+ }, [context.tokenManager]);
40
+
41
+ return {
42
+ session: context.session,
43
+ isLoading: context.isLoading,
44
+ refresh: context.refreshSession,
45
+ isValid,
46
+ getToken,
47
+ };
48
+ }
@@ -0,0 +1,48 @@
1
+ 'use client';
2
+
3
+ import { useCallback } from 'react';
4
+
5
+ import { useAuthContext } from '../providers/AuthContext';
6
+ import type { AuthUser, UseUserReturn } from '../types';
7
+
8
+ /**
9
+ * Hook to access and manage the current user
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * function UserProfile() {
14
+ * const { user, isLoading, update } = useUser();
15
+ *
16
+ * if (isLoading) return <div>Loading...</div>;
17
+ * if (!user) return <div>Not authenticated</div>;
18
+ *
19
+ * return (
20
+ * <div>
21
+ * <h1>{user.name}</h1>
22
+ * <button onClick={() => update({ name: 'New Name' })}>
23
+ * Update Name
24
+ * </button>
25
+ * </div>
26
+ * );
27
+ * }
28
+ * ```
29
+ */
30
+ export function useUser(): UseUserReturn {
31
+ const context = useAuthContext();
32
+
33
+ const refresh = useCallback(async (): Promise<AuthUser | null> => {
34
+ try {
35
+ const user = await context.apiClient.getUser();
36
+ return user;
37
+ } catch {
38
+ return null;
39
+ }
40
+ }, [context.apiClient]);
41
+
42
+ return {
43
+ user: context.user,
44
+ isLoading: context.isLoading,
45
+ update: context.updateUser,
46
+ refresh,
47
+ };
48
+ }