Skip to content

Commit 3768510

Browse files
committed
feat(registry): add clerk auth blocks
1 parent 9a106c4 commit 3768510

File tree

7 files changed

+803
-0
lines changed

7 files changed

+803
-0
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { Button } from '@/registry/ui/button';
2+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/registry/ui/card';
3+
import { Input } from '@/registry/ui/input';
4+
import { Label } from '@/registry/ui/label';
5+
import { Text } from '@/registry/ui/text';
6+
import { useSignIn } from '@clerk/clerk-expo';
7+
import * as React from 'react';
8+
import { View } from 'react-native';
9+
10+
export function ForgotPasswordForm() {
11+
const [email, setEmail] = React.useState('');
12+
const { signIn, isLoaded } = useSignIn();
13+
const [error, setError] = React.useState<{ email?: string; password?: string }>({});
14+
15+
const onSubmit = async () => {
16+
if (!email) {
17+
setError({ email: 'Email is required' });
18+
return;
19+
}
20+
if (!isLoaded) {
21+
return;
22+
}
23+
24+
try {
25+
await signIn.create({
26+
strategy: 'reset_password_email_code',
27+
identifier: email,
28+
});
29+
30+
// TODO: Navigate to reset password screen
31+
} catch (err) {
32+
// See https://go.clerk.com/mRUDrIe for more info on error handling
33+
if (err instanceof Error) {
34+
setError({ email: err.message });
35+
return;
36+
}
37+
console.error(JSON.stringify(err, null, 2));
38+
}
39+
};
40+
41+
return (
42+
<View className="gap-6">
43+
<Card className="border-border/0 sm:border-border shadow-none sm:shadow-sm sm:shadow-black/5">
44+
<CardHeader>
45+
<CardTitle className="text-center text-xl sm:text-left">Forgot password?</CardTitle>
46+
<CardDescription className="text-center sm:text-left">
47+
Enter your email to reset your password
48+
</CardDescription>
49+
</CardHeader>
50+
<CardContent className="gap-6">
51+
<View className="gap-6">
52+
<View className="gap-1.5">
53+
<Label htmlFor="email">Email</Label>
54+
<Input
55+
id="email"
56+
defaultValue={email}
57+
placeholder="[email protected]"
58+
keyboardType="email-address"
59+
autoComplete="email"
60+
autoCapitalize="none"
61+
onChangeText={setEmail}
62+
onSubmitEditing={onSubmit}
63+
returnKeyType="send"
64+
/>
65+
{error.email ? (
66+
<Text className="text-destructive text-sm font-medium">{error.email}</Text>
67+
) : null}
68+
</View>
69+
<Button className="w-full" onPress={onSubmit}>
70+
<Text>Reset your password</Text>
71+
</Button>
72+
</View>
73+
</CardContent>
74+
</Card>
75+
</View>
76+
);
77+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { Button } from '@/registry/ui/button';
2+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/registry/ui/card';
3+
import { Input } from '@/registry/ui/input';
4+
import { Label } from '@/registry/ui/label';
5+
import { Text } from '@/registry/ui/text';
6+
import { useSignIn } from '@clerk/clerk-expo';
7+
import * as React from 'react';
8+
import { TextInput, View } from 'react-native';
9+
10+
export function ResetPasswordForm() {
11+
const { signIn, setActive, isLoaded } = useSignIn();
12+
const [password, setPassword] = React.useState('');
13+
const [code, setCode] = React.useState('');
14+
const codeInputRef = React.useRef<TextInput>(null);
15+
const [error, setError] = React.useState({ code: '', password: '' });
16+
17+
async function onSubmit() {
18+
if (!isLoaded) {
19+
return;
20+
}
21+
try {
22+
const result = await signIn?.attemptFirstFactor({
23+
strategy: 'reset_password_email_code',
24+
code,
25+
password,
26+
});
27+
28+
if (result.status === 'complete') {
29+
// Set the active session to
30+
// the newly created session (user is now signed in)
31+
setActive({ session: result.createdSessionId });
32+
// TODO: If your app does not use `Stack.Protected`, navigate to your protected screen
33+
return;
34+
}
35+
// TODO: Handle other statuses
36+
} catch (err) {
37+
// See https://go.clerk.com/mRUDrIe for more info on error handling
38+
if (err instanceof Error) {
39+
const isPasswordMessage = err.message.toLowerCase().includes('password');
40+
setError({ code: '', password: isPasswordMessage ? err.message : '' });
41+
return;
42+
}
43+
console.error(JSON.stringify(err, null, 2));
44+
}
45+
}
46+
47+
function onPasswordSubmitEditing() {
48+
codeInputRef.current?.focus();
49+
}
50+
51+
return (
52+
<View className="gap-6">
53+
<Card className="border-border/0 sm:border-border shadow-none sm:shadow-sm sm:shadow-black/5">
54+
<CardHeader>
55+
<CardTitle className="text-center text-xl sm:text-left">Reset password</CardTitle>
56+
<CardDescription className="text-center sm:text-left">
57+
Enter the code sent to your email and set a new password
58+
</CardDescription>
59+
</CardHeader>
60+
<CardContent className="gap-6">
61+
<View className="gap-6">
62+
<View className="gap-1.5">
63+
<View className="flex-row items-center">
64+
<Label htmlFor="password">New password</Label>
65+
</View>
66+
<Input
67+
id="password"
68+
secureTextEntry
69+
onChangeText={setPassword}
70+
returnKeyType="next"
71+
submitBehavior="submit"
72+
onSubmitEditing={onPasswordSubmitEditing}
73+
/>
74+
{error.password ? (
75+
<Text className="text-destructive text-sm font-medium">{error.password}</Text>
76+
) : null}
77+
</View>
78+
<View className="gap-1.5">
79+
<Label htmlFor="code">Verification code</Label>
80+
<Input
81+
id="code"
82+
autoCapitalize="none"
83+
onChangeText={setCode}
84+
returnKeyType="send"
85+
keyboardType="numeric"
86+
autoComplete="sms-otp"
87+
textContentType="oneTimeCode"
88+
onSubmitEditing={onSubmit}
89+
/>
90+
{error.code ? (
91+
<Text className="text-destructive text-sm font-medium">{error.code}</Text>
92+
) : null}
93+
</View>
94+
<Button className="w-full" onPress={onSubmit}>
95+
<Text>Reset Password</Text>
96+
</Button>
97+
</View>
98+
</CardContent>
99+
</Card>
100+
</View>
101+
);
102+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { SocialConnections } from '@/registry/blocks/social-connections';
2+
import { Button } from '@/registry/ui/button';
3+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/registry/ui/card';
4+
import { Input } from '@/registry/ui/input';
5+
import { Label } from '@/registry/ui/label';
6+
import { Separator } from '@/registry/ui/separator';
7+
import { Text } from '@/registry/ui/text';
8+
import { useSignIn } from '@clerk/clerk-expo';
9+
import * as React from 'react';
10+
import { Pressable, type TextInput, View } from 'react-native';
11+
12+
export function SignInForm() {
13+
const { signIn, setActive, isLoaded } = useSignIn();
14+
const [email, setEmail] = React.useState('');
15+
const [password, setPassword] = React.useState('');
16+
const passwordInputRef = React.useRef<TextInput>(null);
17+
const [error, setError] = React.useState<{ email?: string; password?: string }>({});
18+
19+
async function onSubmit() {
20+
if (!isLoaded) {
21+
return;
22+
}
23+
24+
// Start the sign-in process using the email and password provided
25+
try {
26+
const signInAttempt = await signIn.create({
27+
identifier: email,
28+
password,
29+
});
30+
31+
// If sign-in process is complete, set the created session as active
32+
// and redirect the user
33+
if (signInAttempt.status === 'complete') {
34+
setError({ email: '', password: '' });
35+
await setActive({ session: signInAttempt.createdSessionId });
36+
// TODO: If your app does not use `Stack.Protected`, navigate to your protected screen
37+
return;
38+
}
39+
// TODO: Handle other statuses
40+
console.error(JSON.stringify(signInAttempt, null, 2));
41+
} catch (err) {
42+
// See https://go.clerk.com/mRUDrIe for more info on error handling
43+
if (err instanceof Error) {
44+
const isEmailMessage =
45+
err.message.toLowerCase().includes('identifier') ||
46+
err.message.toLowerCase().includes('email');
47+
setError(isEmailMessage ? { email: err.message } : { password: err.message });
48+
return;
49+
}
50+
console.error(JSON.stringify(err, null, 2));
51+
}
52+
}
53+
54+
function onEmailSubmitEditing() {
55+
passwordInputRef.current?.focus();
56+
}
57+
58+
return (
59+
<View className="gap-6">
60+
<Card className="border-border/0 sm:border-border shadow-none sm:shadow-sm sm:shadow-black/5">
61+
<CardHeader>
62+
<CardTitle className="text-center text-xl sm:text-left">Sign in to your app</CardTitle>
63+
<CardDescription className="text-center sm:text-left">
64+
Welcome back! Please sign in to continue
65+
</CardDescription>
66+
</CardHeader>
67+
<CardContent className="gap-6">
68+
<View className="gap-6">
69+
<View className="gap-1.5">
70+
<Label htmlFor="email">Email</Label>
71+
<Input
72+
id="email"
73+
placeholder="[email protected]"
74+
keyboardType="email-address"
75+
autoComplete="email"
76+
autoCapitalize="none"
77+
onChangeText={setEmail}
78+
onSubmitEditing={onEmailSubmitEditing}
79+
returnKeyType="next"
80+
submitBehavior="submit"
81+
/>
82+
{error.email ? (
83+
<Text className="text-destructive text-sm font-medium">{error.email}</Text>
84+
) : null}
85+
</View>
86+
<View className="gap-1.5">
87+
<View className="flex-row items-center">
88+
<Label htmlFor="password">Password</Label>
89+
<Button
90+
variant="link"
91+
size="sm"
92+
className="web:h-fit ml-auto h-4 px-1 py-0 sm:h-4"
93+
onPress={() => {
94+
// TODO: Navigate to forgot password screen
95+
}}>
96+
<Text className="font-normal leading-4">Forgot your password?</Text>
97+
</Button>
98+
</View>
99+
<Input
100+
ref={passwordInputRef}
101+
id="password"
102+
secureTextEntry
103+
onChangeText={setPassword}
104+
returnKeyType="send"
105+
onSubmitEditing={onSubmit}
106+
/>
107+
{error.password ? (
108+
<Text className="text-destructive text-sm font-medium">{error.password}</Text>
109+
) : null}
110+
</View>
111+
<Button className="w-full" onPress={onSubmit}>
112+
<Text>Continue</Text>
113+
</Button>
114+
</View>
115+
<Text className="text-center text-sm">
116+
Don&apos;t have an account?{' '}
117+
<Pressable
118+
onPress={() => {
119+
// TODO: Navigate to sign up screen
120+
}}>
121+
<Text className="text-sm underline underline-offset-4">Sign up</Text>
122+
</Pressable>
123+
</Text>
124+
<View className="flex-row items-center">
125+
<Separator className="flex-1" />
126+
<Text className="text-muted-foreground px-4 text-sm">or</Text>
127+
<Separator className="flex-1" />
128+
</View>
129+
<SocialConnections />
130+
</CardContent>
131+
</Card>
132+
</View>
133+
);
134+
}

0 commit comments

Comments
 (0)