mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
feat: add clerk auth support with convex (#548)
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
export default {
|
||||
providers: [
|
||||
{
|
||||
// Replace with your own Clerk Issuer URL from your "convex" JWT template
|
||||
// or with `process.env.CLERK_JWT_ISSUER_DOMAIN`
|
||||
// and configure CLERK_JWT_ISSUER_DOMAIN on the Convex Dashboard
|
||||
// See https://docs.convex.dev/auth/clerk#configuring-dev-and-prod-instances
|
||||
domain: process.env.CLERK_JWT_ISSUER_DOMAIN,
|
||||
applicationID: "convex",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import { query } from "./_generated/server";
|
||||
|
||||
export const get = query({
|
||||
args: {},
|
||||
handler: async (ctx) => {
|
||||
const identity = await ctx.auth.getUserIdentity();
|
||||
if (identity === null) {
|
||||
return {
|
||||
message: "Not authenticated",
|
||||
};
|
||||
}
|
||||
return {
|
||||
message: "This is private",
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Redirect, Stack } from "expo-router";
|
||||
import { useAuth } from "@clerk/clerk-expo";
|
||||
|
||||
export default function AuthRoutesLayout() {
|
||||
const { isSignedIn } = useAuth();
|
||||
|
||||
if (isSignedIn) {
|
||||
return <Redirect href={"/"} />;
|
||||
}
|
||||
|
||||
return <Stack />;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { useSignIn } from "@clerk/clerk-expo";
|
||||
import { Link, useRouter } from "expo-router";
|
||||
import { Text, TextInput, TouchableOpacity, View } from "react-native";
|
||||
import React from "react";
|
||||
|
||||
export default function Page() {
|
||||
const { signIn, setActive, isLoaded } = useSignIn();
|
||||
const router = useRouter();
|
||||
|
||||
const [emailAddress, setEmailAddress] = React.useState("");
|
||||
const [password, setPassword] = React.useState("");
|
||||
|
||||
// Handle the submission of the sign-in form
|
||||
const onSignInPress = async () => {
|
||||
if (!isLoaded) return;
|
||||
|
||||
// Start the sign-in process using the email and password provided
|
||||
try {
|
||||
const signInAttempt = await signIn.create({
|
||||
identifier: emailAddress,
|
||||
password,
|
||||
});
|
||||
|
||||
// If sign-in process is complete, set the created session as active
|
||||
// and redirect the user
|
||||
if (signInAttempt.status === "complete") {
|
||||
await setActive({ session: signInAttempt.createdSessionId });
|
||||
router.replace("/");
|
||||
} else {
|
||||
// If the status isn't complete, check why. User might need to
|
||||
// complete further steps.
|
||||
console.error(JSON.stringify(signInAttempt, null, 2));
|
||||
}
|
||||
} catch (err) {
|
||||
// See https://clerk.com/docs/custom-flows/error-handling
|
||||
// for more info on error handling
|
||||
console.error(JSON.stringify(err, null, 2));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Text>Sign in</Text>
|
||||
<TextInput
|
||||
autoCapitalize="none"
|
||||
value={emailAddress}
|
||||
placeholder="Enter email"
|
||||
onChangeText={(emailAddress) => setEmailAddress(emailAddress)}
|
||||
/>
|
||||
<TextInput
|
||||
value={password}
|
||||
placeholder="Enter password"
|
||||
secureTextEntry={true}
|
||||
onChangeText={(password) => setPassword(password)}
|
||||
/>
|
||||
<TouchableOpacity onPress={onSignInPress}>
|
||||
<Text>Continue</Text>
|
||||
</TouchableOpacity>
|
||||
<View style=\{{ display: "flex", flexDirection: "row", gap: 3 }}>
|
||||
<Text>Don't have an account?</Text>
|
||||
<Link href="/sign-up">
|
||||
<Text>Sign up</Text>
|
||||
</Link>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import * as React from "react";
|
||||
import { Text, TextInput, TouchableOpacity, View } from "react-native";
|
||||
import { useSignUp } from "@clerk/clerk-expo";
|
||||
import { Link, useRouter } from "expo-router";
|
||||
|
||||
export default function SignUpScreen() {
|
||||
const { isLoaded, signUp, setActive } = useSignUp();
|
||||
const router = useRouter();
|
||||
|
||||
const [emailAddress, setEmailAddress] = React.useState("");
|
||||
const [password, setPassword] = React.useState("");
|
||||
const [pendingVerification, setPendingVerification] = React.useState(false);
|
||||
const [code, setCode] = React.useState("");
|
||||
|
||||
// Handle submission of sign-up form
|
||||
const onSignUpPress = async () => {
|
||||
if (!isLoaded) return;
|
||||
|
||||
console.log(emailAddress, password);
|
||||
|
||||
// Start sign-up process using email and password provided
|
||||
try {
|
||||
await signUp.create({
|
||||
emailAddress,
|
||||
password,
|
||||
});
|
||||
|
||||
// Send user an email with verification code
|
||||
await signUp.prepareEmailAddressVerification({ strategy: "email_code" });
|
||||
|
||||
// Set 'pendingVerification' to true to display second form
|
||||
// and capture OTP code
|
||||
setPendingVerification(true);
|
||||
} catch (err) {
|
||||
// See https://clerk.com/docs/custom-flows/error-handling
|
||||
// for more info on error handling
|
||||
console.error(JSON.stringify(err, null, 2));
|
||||
}
|
||||
};
|
||||
|
||||
// Handle submission of verification form
|
||||
const onVerifyPress = async () => {
|
||||
if (!isLoaded) return;
|
||||
|
||||
try {
|
||||
// Use the code the user provided to attempt verification
|
||||
const signUpAttempt = await signUp.attemptEmailAddressVerification({
|
||||
code,
|
||||
});
|
||||
|
||||
// If verification was completed, set the session to active
|
||||
// and redirect the user
|
||||
if (signUpAttempt.status === "complete") {
|
||||
await setActive({ session: signUpAttempt.createdSessionId });
|
||||
router.replace("/");
|
||||
} else {
|
||||
// If the status is not complete, check why. User may need to
|
||||
// complete further steps.
|
||||
console.error(JSON.stringify(signUpAttempt, null, 2));
|
||||
}
|
||||
} catch (err) {
|
||||
// See https://clerk.com/docs/custom-flows/error-handling
|
||||
// for more info on error handling
|
||||
console.error(JSON.stringify(err, null, 2));
|
||||
}
|
||||
};
|
||||
|
||||
if (pendingVerification) {
|
||||
return (
|
||||
<>
|
||||
<Text>Verify your email</Text>
|
||||
<TextInput
|
||||
value={code}
|
||||
placeholder="Enter your verification code"
|
||||
onChangeText={(code) => setCode(code)}
|
||||
/>
|
||||
<TouchableOpacity onPress={onVerifyPress}>
|
||||
<Text>Verify</Text>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Text>Sign up</Text>
|
||||
<TextInput
|
||||
autoCapitalize="none"
|
||||
value={emailAddress}
|
||||
placeholder="Enter email"
|
||||
onChangeText={(email) => setEmailAddress(email)}
|
||||
/>
|
||||
<TextInput
|
||||
value={password}
|
||||
placeholder="Enter password"
|
||||
secureTextEntry={true}
|
||||
onChangeText={(password) => setPassword(password)}
|
||||
/>
|
||||
<TouchableOpacity onPress={onSignUpPress}>
|
||||
<Text>Continue</Text>
|
||||
</TouchableOpacity>
|
||||
<View style=\{{ display: "flex", flexDirection: "row", gap: 3 }}>
|
||||
<Text>Already have an account?</Text>
|
||||
<Link href="/sign-in">
|
||||
<Text>Sign in</Text>
|
||||
</Link>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { useClerk } from "@clerk/clerk-expo";
|
||||
import { useRouter } from "expo-router";
|
||||
import { Text, TouchableOpacity } from "react-native";
|
||||
|
||||
export const SignOutButton = () => {
|
||||
// Use `useClerk()` to access the `signOut()` function
|
||||
const { signOut } = useClerk();
|
||||
const router = useRouter();
|
||||
|
||||
const handleSignOut = async () => {
|
||||
try {
|
||||
await signOut();
|
||||
// Redirect to your desired page
|
||||
router.replace("/");
|
||||
} catch (err) {
|
||||
// See https://clerk.com/docs/custom-flows/error-handling
|
||||
// for more info on error handling
|
||||
console.error(JSON.stringify(err, null, 2));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={handleSignOut}>
|
||||
<Text>Sign out</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
"use client";
|
||||
|
||||
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
||||
import { SignInButton, UserButton, useUser } from "@clerk/nextjs";
|
||||
import { Authenticated, AuthLoading, Unauthenticated, useQuery } from "convex/react";
|
||||
|
||||
export default function Dashboard() {
|
||||
const user = useUser();
|
||||
const privateData = useQuery(api.privateData.get);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Authenticated>
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
<p>Welcome {user.user?.fullName}</p>
|
||||
<p>privateData: {privateData?.message}</p>
|
||||
<UserButton />
|
||||
</div>
|
||||
</Authenticated>
|
||||
<Unauthenticated>
|
||||
<SignInButton />
|
||||
</Unauthenticated>
|
||||
<AuthLoading>
|
||||
<div>Loading...</div>
|
||||
</AuthLoading>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { clerkMiddleware } from "@clerk/nextjs/server";
|
||||
|
||||
export default clerkMiddleware();
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
// Skip Next.js internals and all static files, unless found in search params
|
||||
"/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)",
|
||||
// Always run for API routes
|
||||
"/(api|trpc)(.*)",
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { SignInButton, UserButton, useUser } from "@clerk/clerk-react";
|
||||
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
||||
import {
|
||||
Authenticated,
|
||||
AuthLoading,
|
||||
Unauthenticated,
|
||||
useQuery,
|
||||
} from "convex/react";
|
||||
|
||||
export default function Dashboard() {
|
||||
const privateData = useQuery(api.privateData.get);
|
||||
const user = useUser();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Authenticated>
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
<p>Welcome {user.user?.fullName}</p>
|
||||
<p>privateData: {privateData?.message}</p>
|
||||
<UserButton />
|
||||
</div>
|
||||
</Authenticated>
|
||||
<Unauthenticated>
|
||||
<SignInButton />
|
||||
</Unauthenticated>
|
||||
<AuthLoading>
|
||||
<div>Loading...</div>
|
||||
</AuthLoading>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { SignInButton, UserButton, useUser } from "@clerk/clerk-react";
|
||||
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import {
|
||||
Authenticated,
|
||||
AuthLoading,
|
||||
Unauthenticated,
|
||||
useQuery,
|
||||
} from "convex/react";
|
||||
|
||||
export const Route = createFileRoute("/dashboard")({
|
||||
component: RouteComponent,
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const privateData = useQuery(api.privateData.get);
|
||||
const user = useUser()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Authenticated>
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
<p>Welcome {user.user?.fullName}</p>
|
||||
<p>privateData: {privateData?.message}</p>
|
||||
<UserButton />
|
||||
</div>
|
||||
</Authenticated>
|
||||
<Unauthenticated>
|
||||
<SignInButton />
|
||||
</Unauthenticated>
|
||||
<AuthLoading>
|
||||
<div>Loading...</div>
|
||||
</AuthLoading>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { SignInButton, UserButton, useUser } from "@clerk/tanstack-react-start";
|
||||
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import {
|
||||
Authenticated,
|
||||
AuthLoading,
|
||||
Unauthenticated,
|
||||
useQuery,
|
||||
} from "convex/react";
|
||||
|
||||
export const Route = createFileRoute("/dashboard")({
|
||||
component: RouteComponent,
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const privateData = useQuery(api.privateData.get);
|
||||
const user = useUser();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Authenticated>
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
<p>Welcome {user.user?.fullName}</p>
|
||||
<p>privateData: {privateData?.message}</p>
|
||||
<UserButton />
|
||||
</div>
|
||||
</Authenticated>
|
||||
<Unauthenticated>
|
||||
<SignInButton />
|
||||
</Unauthenticated>
|
||||
<AuthLoading>
|
||||
<div>Loading...</div>
|
||||
</AuthLoading>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { createClerkHandler } from "@clerk/tanstack-react-start/server";
|
||||
import {
|
||||
createStartHandler,
|
||||
defaultStreamHandler,
|
||||
defineHandlerCallback,
|
||||
} from "@tanstack/react-start/server";
|
||||
import { createRouter } from "./router";
|
||||
|
||||
const handlerFactory = createClerkHandler(
|
||||
createStartHandler({
|
||||
createRouter,
|
||||
}),
|
||||
);
|
||||
|
||||
export default defineHandlerCallback(async (event) => {
|
||||
const startHandler = await handlerFactory(defaultStreamHandler);
|
||||
return startHandler(event);
|
||||
});
|
||||
Reference in New Issue
Block a user