@opensaas/stack-auth 0.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.
Files changed (56) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/INTEGRATION_SUMMARY.md +425 -0
  3. package/README.md +445 -0
  4. package/dist/client/index.d.ts +38 -0
  5. package/dist/client/index.d.ts.map +1 -0
  6. package/dist/client/index.js +23 -0
  7. package/dist/client/index.js.map +1 -0
  8. package/dist/config/index.d.ts +50 -0
  9. package/dist/config/index.d.ts.map +1 -0
  10. package/dist/config/index.js +115 -0
  11. package/dist/config/index.js.map +1 -0
  12. package/dist/config/types.d.ts +160 -0
  13. package/dist/config/types.d.ts.map +1 -0
  14. package/dist/config/types.js +2 -0
  15. package/dist/config/types.js.map +1 -0
  16. package/dist/index.d.ts +35 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +34 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/lists/index.d.ts +46 -0
  21. package/dist/lists/index.d.ts.map +1 -0
  22. package/dist/lists/index.js +227 -0
  23. package/dist/lists/index.js.map +1 -0
  24. package/dist/server/index.d.ts +27 -0
  25. package/dist/server/index.d.ts.map +1 -0
  26. package/dist/server/index.js +90 -0
  27. package/dist/server/index.js.map +1 -0
  28. package/dist/ui/components/ForgotPasswordForm.d.ts +36 -0
  29. package/dist/ui/components/ForgotPasswordForm.d.ts.map +1 -0
  30. package/dist/ui/components/ForgotPasswordForm.js +50 -0
  31. package/dist/ui/components/ForgotPasswordForm.js.map +1 -0
  32. package/dist/ui/components/SignInForm.d.ts +52 -0
  33. package/dist/ui/components/SignInForm.d.ts.map +1 -0
  34. package/dist/ui/components/SignInForm.js +66 -0
  35. package/dist/ui/components/SignInForm.js.map +1 -0
  36. package/dist/ui/components/SignUpForm.d.ts +56 -0
  37. package/dist/ui/components/SignUpForm.d.ts.map +1 -0
  38. package/dist/ui/components/SignUpForm.js +74 -0
  39. package/dist/ui/components/SignUpForm.js.map +1 -0
  40. package/dist/ui/index.d.ts +7 -0
  41. package/dist/ui/index.d.ts.map +1 -0
  42. package/dist/ui/index.js +4 -0
  43. package/dist/ui/index.js.map +1 -0
  44. package/package.json +55 -0
  45. package/src/client/index.ts +44 -0
  46. package/src/config/index.ts +140 -0
  47. package/src/config/types.ts +166 -0
  48. package/src/index.ts +44 -0
  49. package/src/lists/index.ts +245 -0
  50. package/src/server/index.ts +120 -0
  51. package/src/ui/components/ForgotPasswordForm.tsx +120 -0
  52. package/src/ui/components/SignInForm.tsx +191 -0
  53. package/src/ui/components/SignUpForm.tsx +238 -0
  54. package/src/ui/index.ts +7 -0
  55. package/tsconfig.json +14 -0
  56. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,191 @@
1
+ 'use client'
2
+
3
+ import React, { useState } from 'react'
4
+ import type { createAuthClient } from 'better-auth/react'
5
+
6
+ export type SignInFormProps = {
7
+ /**
8
+ * Better-auth client instance
9
+ * Created with createAuthClient from better-auth/react
10
+ * Pass your client from lib/auth-client.ts
11
+ */
12
+ authClient: ReturnType<typeof createAuthClient>
13
+ /**
14
+ * URL to redirect to after successful sign in
15
+ * @default '/'
16
+ */
17
+ redirectTo?: string
18
+ /**
19
+ * Show OAuth provider buttons
20
+ * @default true
21
+ */
22
+ showSocialProviders?: boolean
23
+ /**
24
+ * Which OAuth providers to show
25
+ * @default ['github', 'google']
26
+ */
27
+ socialProviders?: string[]
28
+ /**
29
+ * Custom CSS class for the form container
30
+ */
31
+ className?: string
32
+ /**
33
+ * Callback when sign in is successful
34
+ */
35
+ onSuccess?: () => void
36
+ /**
37
+ * Callback when sign in fails
38
+ */
39
+ onError?: (error: Error) => void
40
+ }
41
+
42
+ /**
43
+ * Sign in form component
44
+ * Provides email/password sign in and OAuth provider buttons
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * import { SignInForm } from '@opensaas/stack-auth/ui'
49
+ * import { authClient } from '@/lib/auth-client'
50
+ *
51
+ * export default function SignInPage() {
52
+ * return <SignInForm authClient={authClient} redirectTo="/admin" />
53
+ * }
54
+ * ```
55
+ */
56
+ export function SignInForm({
57
+ authClient,
58
+ redirectTo = '/',
59
+ showSocialProviders = true,
60
+ socialProviders = ['github', 'google'],
61
+ className = '',
62
+ onSuccess,
63
+ onError,
64
+ }: SignInFormProps) {
65
+ const [email, setEmail] = useState('')
66
+ const [password, setPassword] = useState('')
67
+ const [error, setError] = useState('')
68
+ const [loading, setLoading] = useState(false)
69
+
70
+ const handleSubmit = async (e: React.FormEvent) => {
71
+ e.preventDefault()
72
+ setError('')
73
+ setLoading(true)
74
+
75
+ try {
76
+ const result = await authClient.signIn.email({
77
+ email,
78
+ password,
79
+ callbackURL: redirectTo,
80
+ })
81
+
82
+ if (result.error) {
83
+ throw new Error(result.error.message)
84
+ }
85
+
86
+ onSuccess?.()
87
+ } catch (err) {
88
+ const message = err instanceof Error ? err.message : 'Sign in failed'
89
+ setError(message)
90
+ onError?.(err instanceof Error ? err : new Error(message))
91
+ } finally {
92
+ setLoading(false)
93
+ }
94
+ }
95
+
96
+ const handleSocialSignIn = async (provider: string) => {
97
+ setError('')
98
+ setLoading(true)
99
+
100
+ try {
101
+ await authClient.signIn.social({
102
+ provider,
103
+ callbackURL: redirectTo,
104
+ })
105
+ onSuccess?.()
106
+ } catch (err) {
107
+ const message = err instanceof Error ? err.message : 'Sign in failed'
108
+ setError(message)
109
+ onError?.(err instanceof Error ? err : new Error(message))
110
+ setLoading(false)
111
+ }
112
+ }
113
+
114
+ return (
115
+ <div className={`w-full max-w-md mx-auto p-6 ${className}`}>
116
+ <h2 className="text-2xl font-bold mb-6">Sign In</h2>
117
+
118
+ {error && (
119
+ <div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded mb-4">
120
+ {error}
121
+ </div>
122
+ )}
123
+
124
+ <form onSubmit={handleSubmit} className="space-y-4">
125
+ <div>
126
+ <label htmlFor="email" className="block text-sm font-medium mb-2">
127
+ Email
128
+ </label>
129
+ <input
130
+ id="email"
131
+ type="email"
132
+ value={email}
133
+ onChange={(e) => setEmail((e.target as HTMLInputElement).value)}
134
+ required
135
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
136
+ disabled={loading}
137
+ />
138
+ </div>
139
+
140
+ <div>
141
+ <label htmlFor="password" className="block text-sm font-medium mb-2">
142
+ Password
143
+ </label>
144
+ <input
145
+ id="password"
146
+ type="password"
147
+ value={password}
148
+ onChange={(e) => setPassword((e.target as HTMLInputElement).value)}
149
+ required
150
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
151
+ disabled={loading}
152
+ />
153
+ </div>
154
+
155
+ <button
156
+ type="submit"
157
+ disabled={loading}
158
+ className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
159
+ >
160
+ {loading ? 'Signing in...' : 'Sign In'}
161
+ </button>
162
+ </form>
163
+
164
+ {showSocialProviders && socialProviders.length > 0 && (
165
+ <>
166
+ <div className="relative my-6">
167
+ <div className="absolute inset-0 flex items-center">
168
+ <div className="w-full border-t border-gray-300"></div>
169
+ </div>
170
+ <div className="relative flex justify-center text-sm">
171
+ <span className="px-2 bg-white text-gray-500">Or continue with</span>
172
+ </div>
173
+ </div>
174
+
175
+ <div className="space-y-2">
176
+ {socialProviders.map((provider) => (
177
+ <button
178
+ key={provider}
179
+ onClick={() => handleSocialSignIn(provider)}
180
+ disabled={loading}
181
+ className="w-full bg-white border border-gray-300 text-gray-700 py-2 px-4 rounded-md hover:bg-gray-50 disabled:bg-gray-100 disabled:cursor-not-allowed"
182
+ >
183
+ Sign in with {provider.charAt(0).toUpperCase() + provider.slice(1)}
184
+ </button>
185
+ ))}
186
+ </div>
187
+ </>
188
+ )}
189
+ </div>
190
+ )
191
+ }
@@ -0,0 +1,238 @@
1
+ 'use client'
2
+
3
+ import React, { useState } from 'react'
4
+ import type { createAuthClient } from 'better-auth/react'
5
+
6
+ export type SignUpFormProps = {
7
+ /**
8
+ * Better-auth client instance
9
+ * Created with createAuthClient from better-auth/react
10
+ */
11
+ authClient: ReturnType<typeof createAuthClient>
12
+ /**
13
+ * URL to redirect to after successful sign up
14
+ * @default '/'
15
+ */
16
+ redirectTo?: string
17
+ /**
18
+ * Show OAuth provider buttons
19
+ * @default true
20
+ */
21
+ showSocialProviders?: boolean
22
+ /**
23
+ * Which OAuth providers to show
24
+ * @default ['github', 'google']
25
+ */
26
+ socialProviders?: string[]
27
+ /**
28
+ * Require password confirmation
29
+ * @default true
30
+ */
31
+ requirePasswordConfirmation?: boolean
32
+ /**
33
+ * Custom CSS class for the form container
34
+ */
35
+ className?: string
36
+ /**
37
+ * Callback when sign up is successful
38
+ */
39
+ onSuccess?: () => void
40
+ /**
41
+ * Callback when sign up fails
42
+ */
43
+ onError?: (error: Error) => void
44
+ }
45
+
46
+ /**
47
+ * Sign up form component
48
+ * Provides email/password registration and OAuth provider buttons
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * import { SignUpForm } from '@opensaas/stack-auth/ui'
53
+ * import { authClient } from '@/lib/auth-client'
54
+ *
55
+ * export default function SignUpPage() {
56
+ * return <SignUpForm authClient={authClient} redirectTo="/admin" />
57
+ * }
58
+ * ```
59
+ */
60
+ export function SignUpForm({
61
+ authClient,
62
+ redirectTo = '/',
63
+ showSocialProviders = true,
64
+ socialProviders = ['github', 'google'],
65
+ requirePasswordConfirmation = true,
66
+ className = '',
67
+ onSuccess,
68
+ onError,
69
+ }: SignUpFormProps) {
70
+ const [name, setName] = useState('')
71
+ const [email, setEmail] = useState('')
72
+ const [password, setPassword] = useState('')
73
+ const [confirmPassword, setConfirmPassword] = useState('')
74
+ const [error, setError] = useState('')
75
+ const [loading, setLoading] = useState(false)
76
+
77
+ const handleSubmit = async (e: React.FormEvent) => {
78
+ e.preventDefault()
79
+ setError('')
80
+
81
+ // Validate password confirmation
82
+ if (requirePasswordConfirmation && password !== confirmPassword) {
83
+ setError('Passwords do not match')
84
+ return
85
+ }
86
+
87
+ setLoading(true)
88
+
89
+ try {
90
+ const result = await authClient.signUp.email({
91
+ email,
92
+ password,
93
+ name,
94
+ callbackURL: redirectTo,
95
+ })
96
+
97
+ if (result.error) {
98
+ throw new Error(result.error.message)
99
+ }
100
+
101
+ onSuccess?.()
102
+ } catch (err) {
103
+ const message = err instanceof Error ? err.message : 'Sign up failed'
104
+ setError(message)
105
+ onError?.(err instanceof Error ? err : new Error(message))
106
+ } finally {
107
+ setLoading(false)
108
+ }
109
+ }
110
+
111
+ const handleSocialSignUp = async (provider: string) => {
112
+ setError('')
113
+ setLoading(true)
114
+
115
+ try {
116
+ await authClient.signIn.social({
117
+ provider,
118
+ callbackURL: redirectTo,
119
+ })
120
+ onSuccess?.()
121
+ } catch (err) {
122
+ const message = err instanceof Error ? err.message : 'Sign up failed'
123
+ setError(message)
124
+ onError?.(err instanceof Error ? err : new Error(message))
125
+ setLoading(false)
126
+ }
127
+ }
128
+
129
+ return (
130
+ <div className={`w-full max-w-md mx-auto p-6 ${className}`}>
131
+ <h2 className="text-2xl font-bold mb-6">Sign Up</h2>
132
+
133
+ {error && (
134
+ <div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded mb-4">
135
+ {error}
136
+ </div>
137
+ )}
138
+
139
+ <form onSubmit={handleSubmit} className="space-y-4">
140
+ <div>
141
+ <label htmlFor="name" className="block text-sm font-medium mb-2">
142
+ Name
143
+ </label>
144
+ <input
145
+ id="name"
146
+ type="text"
147
+ value={name}
148
+ onChange={(e) => setName((e.target as HTMLInputElement).value)}
149
+ required
150
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
151
+ disabled={loading}
152
+ />
153
+ </div>
154
+
155
+ <div>
156
+ <label htmlFor="email" className="block text-sm font-medium mb-2">
157
+ Email
158
+ </label>
159
+ <input
160
+ id="email"
161
+ type="email"
162
+ value={email}
163
+ onChange={(e) => setEmail((e.target as HTMLInputElement).value)}
164
+ required
165
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
166
+ disabled={loading}
167
+ />
168
+ </div>
169
+
170
+ <div>
171
+ <label htmlFor="password" className="block text-sm font-medium mb-2">
172
+ Password
173
+ </label>
174
+ <input
175
+ id="password"
176
+ type="password"
177
+ value={password}
178
+ onChange={(e) => setPassword((e.target as HTMLInputElement).value)}
179
+ required
180
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
181
+ disabled={loading}
182
+ />
183
+ </div>
184
+
185
+ {requirePasswordConfirmation && (
186
+ <div>
187
+ <label htmlFor="confirmPassword" className="block text-sm font-medium mb-2">
188
+ Confirm Password
189
+ </label>
190
+ <input
191
+ id="confirmPassword"
192
+ type="password"
193
+ value={confirmPassword}
194
+ onChange={(e) => setConfirmPassword((e.target as HTMLInputElement).value)}
195
+ required
196
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
197
+ disabled={loading}
198
+ />
199
+ </div>
200
+ )}
201
+
202
+ <button
203
+ type="submit"
204
+ disabled={loading}
205
+ className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
206
+ >
207
+ {loading ? 'Creating account...' : 'Sign Up'}
208
+ </button>
209
+ </form>
210
+
211
+ {showSocialProviders && socialProviders.length > 0 && (
212
+ <>
213
+ <div className="relative my-6">
214
+ <div className="absolute inset-0 flex items-center">
215
+ <div className="w-full border-t border-gray-300"></div>
216
+ </div>
217
+ <div className="relative flex justify-center text-sm">
218
+ <span className="px-2 bg-white text-gray-500">Or continue with</span>
219
+ </div>
220
+ </div>
221
+
222
+ <div className="space-y-2">
223
+ {socialProviders.map((provider) => (
224
+ <button
225
+ key={provider}
226
+ onClick={() => handleSocialSignUp(provider)}
227
+ disabled={loading}
228
+ className="w-full bg-white border border-gray-300 text-gray-700 py-2 px-4 rounded-md hover:bg-gray-50 disabled:bg-gray-100 disabled:cursor-not-allowed"
229
+ >
230
+ Sign up with {provider.charAt(0).toUpperCase() + provider.slice(1)}
231
+ </button>
232
+ ))}
233
+ </div>
234
+ </>
235
+ )}
236
+ </div>
237
+ )
238
+ }
@@ -0,0 +1,7 @@
1
+ export { SignInForm } from './components/SignInForm.js'
2
+ export { SignUpForm } from './components/SignUpForm.js'
3
+ export { ForgotPasswordForm } from './components/ForgotPasswordForm.js'
4
+
5
+ export type { SignInFormProps } from './components/SignInForm.js'
6
+ export type { SignUpFormProps } from './components/SignUpForm.js'
7
+ export type { ForgotPasswordFormProps } from './components/ForgotPasswordForm.js'
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "composite": true,
7
+ "declaration": true,
8
+ "declarationMap": true,
9
+ "jsx": "react-jsx",
10
+ "lib": ["ES2022", "DOM", "DOM.Iterable"]
11
+ },
12
+ "include": ["src/**/*"],
13
+ "exclude": ["node_modules", "dist"]
14
+ }