Update prettier

This commit is contained in:
2026-01-14 00:33:38 -06:00
parent 4b5c12d868
commit ce2264ef6d
58 changed files with 12945 additions and 568 deletions

View File

@@ -1 +1 @@
[["1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22"],{"key":"23","value":"24"},{"key":"25","value":"26"},{"key":"27","value":"28"},{"key":"29","value":"30"},{"key":"31","value":"32"},{"key":"33","value":"34"},{"key":"35","value":"36"},{"key":"37","value":"38"},{"key":"39","value":"40"},{"key":"41","value":"42"},{"key":"43","value":"44"},{"key":"45","value":"46"},{"key":"47","value":"48"},{"key":"49","value":"50"},{"key":"51","value":"52"},{"key":"53","value":"54"},{"key":"55","value":"56"},{"key":"57","value":"58"},{"key":"59","value":"60"},{"key":"61","value":"62"},{"key":"63","value":"64"},{"key":"65","value":"66"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/package.json",{"size":2249,"mtime":1766222924000,"hash":"67","data":"68"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/index.ts",{"size":28,"mtime":1768155639000,"hash":"69","data":"70"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/metro.config.js",{"size":511,"mtime":1768155639000,"hash":"71","data":"72"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/app/index.tsx",{"size":5019,"mtime":1768155639000,"hash":"73","data":"74"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/turbo.json",{"size":163,"mtime":1766222924000,"hash":"75","data":"76"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/postcss.config.js",{"size":66,"mtime":1768155639000,"hash":"77","data":"78"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/.cache/.prettiercache",{"size":4787,"mtime":1768171236840,"hash":"79"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/eas.json",{"size":567,"mtime":1766222924000,"hash":"80","data":"81"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/.expo-shared/assets.json",{"size":155,"mtime":1766222924000,"hash":"82","data":"83"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/app/_layout.tsx",{"size":927,"mtime":1768155639000,"hash":"84","data":"85"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/nativewind-env.d.ts",{"size":246,"mtime":1766222924000,"hash":"86","data":"87"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/utils/auth.ts",{"size":398,"mtime":1768155639000,"hash":"88","data":"89"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/app/post/[id].tsx",{"size":757,"mtime":1768155639000,"hash":"90","data":"91"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/styles.css",{"size":90,"mtime":1768155639000,"hash":"92","data":"93"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/utils/session-store.ts",{"size":272,"mtime":1768155639000,"hash":"94","data":"95"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/assets/icon-dark.png",{"size":19633,"mtime":1766222924000,"hash":"96"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/eslint.config.mts",{"size":275,"mtime":1768155639000,"hash":"97","data":"98"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/utils/api.tsx",{"size":1326,"mtime":1768155639000,"hash":"99","data":"100"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/utils/base-url.ts",{"size":880,"mtime":1768155639000,"hash":"101","data":"102"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/app.config.ts",{"size":1333,"mtime":1768155639000,"hash":"103","data":"104"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/assets/icon-light.png",{"size":19133,"mtime":1766222924000,"hash":"105"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/tsconfig.json",{"size":387,"mtime":1766228480000,"hash":"106","data":"107"},"d8763702c14cdc382dcfb84f6f9a068f",{"hashOfOptions":"108"},"11cdbef6afa001cd39bc187041ca6865",{"hashOfOptions":"109"},"dbe97bcde588a81538bbcd6a9befdddd",{"hashOfOptions":"110"},"1c10eb388cf5dcbc87d2d63770a227f1",{"hashOfOptions":"111"},"c7d4dcf839dfeaa02e0407adfd5e47a6",{"hashOfOptions":"112"},"b7edffce093c4c84092cc93f3dc208ef",{"hashOfOptions":"113"},"75e4e9158c89e7a7bc04e94fd2366743","a3c1487f8318513ae7c156acc857fde2",{"hashOfOptions":"114"},"0f7f54c7161b8403d3bc42d91f59cd91",{"hashOfOptions":"115"},"8e407b4b1b0c0bd9c862a00243344be3",{"hashOfOptions":"116"},"d4d589c153ac8b5e7bf0fb130a5b5a7d",{"hashOfOptions":"117"},"cecbed1604a530a7cc099fecddddd76c",{"hashOfOptions":"118"},"4fcefde979d34a7339f7a266d3ec931b",{"hashOfOptions":"119"},"52a1d72379b952dd802f47e1865bd0da",{"hashOfOptions":"120"},"1bc3e15a40c117eecc51294886ea9b38",{"hashOfOptions":"121"},"1e8ac0d261e95efb19d290ffcf70ce36","1c1710ce3de3ce02e8054cc3787c8579",{"hashOfOptions":"122"},"5ff899a601102659dcbd2900e415ce8b",{"hashOfOptions":"123"},"dd2007a211e323deabb3f7fa7d16313f",{"hashOfOptions":"124"},"4f49c6df7733f874fbe72b4e20b3092b",{"hashOfOptions":"125"},"863da15dbd856008b7c24077ca746d91","6937fb7370f1e17491df649888d6ecc9",{"hashOfOptions":"126"},"2742538293","3367041120","3963086909","3173464737","3850474653","3346849767","2045418596","3048291867","3525769784","4000169781","471581913","1427279653","2305419495","818733105","2587128410","1262173017","408469422","2836740315","2315460658"]
[["1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21"],{"key":"22","value":"23"},{"key":"24","value":"25"},{"key":"26","value":"27"},{"key":"28","value":"29"},{"key":"30","value":"31"},{"key":"32","value":"33"},{"key":"34","value":"35"},{"key":"36","value":"37"},{"key":"38","value":"39"},{"key":"40","value":"41"},{"key":"42","value":"43"},{"key":"44","value":"45"},{"key":"46","value":"47"},{"key":"48","value":"49"},{"key":"50","value":"51"},{"key":"52","value":"53"},{"key":"54","value":"55"},{"key":"56","value":"57"},{"key":"58","value":"59"},{"key":"60","value":"61"},{"key":"62","value":"63"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/package.json",{"size":2249,"mtime":1766222924000,"hash":"64","data":"65"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/index.ts",{"size":28,"mtime":1768155639000,"hash":"66","data":"67"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/metro.config.js",{"size":511,"mtime":1768155639000,"hash":"68","data":"69"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/app/index.tsx",{"size":5019,"mtime":1768372346938,"hash":"70","data":"71"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/turbo.json",{"size":163,"mtime":1766222924000,"hash":"72","data":"73"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/postcss.config.js",{"size":66,"mtime":1768155639000,"hash":"74","data":"75"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/eas.json",{"size":567,"mtime":1766222924000,"hash":"76","data":"77"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/.expo-shared/assets.json",{"size":155,"mtime":1766222924000,"hash":"78","data":"79"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/app/_layout.tsx",{"size":927,"mtime":1768155639000,"hash":"80","data":"81"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/nativewind-env.d.ts",{"size":246,"mtime":1766222924000,"hash":"82","data":"83"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/utils/auth.ts",{"size":398,"mtime":1768155639000,"hash":"84","data":"85"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/app/post/[id].tsx",{"size":757,"mtime":1768372346967,"hash":"86","data":"87"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/styles.css",{"size":90,"mtime":1768155639000,"hash":"88","data":"89"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/utils/session-store.ts",{"size":272,"mtime":1768155639000,"hash":"90","data":"91"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/assets/icon-dark.png",{"size":19633,"mtime":1766222924000,"hash":"92"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/eslint.config.mts",{"size":275,"mtime":1768155639000,"hash":"93","data":"94"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/utils/api.tsx",{"size":1326,"mtime":1768155639000,"hash":"95","data":"96"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/utils/base-url.ts",{"size":880,"mtime":1768155639000,"hash":"97","data":"98"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/app.config.ts",{"size":1333,"mtime":1768155639000,"hash":"99","data":"100"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/assets/icon-light.png",{"size":19133,"mtime":1766222924000,"hash":"101"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/tsconfig.json",{"size":387,"mtime":1766228480000,"hash":"102","data":"103"},"d8763702c14cdc382dcfb84f6f9a068f",{"hashOfOptions":"104"},"11cdbef6afa001cd39bc187041ca6865",{"hashOfOptions":"105"},"dbe97bcde588a81538bbcd6a9befdddd",{"hashOfOptions":"106"},"73c235a66242df70b69394cce29d1ed3",{"hashOfOptions":"107"},"c7d4dcf839dfeaa02e0407adfd5e47a6",{"hashOfOptions":"108"},"b7edffce093c4c84092cc93f3dc208ef",{"hashOfOptions":"109"},"a3c1487f8318513ae7c156acc857fde2",{"hashOfOptions":"110"},"0f7f54c7161b8403d3bc42d91f59cd91",{"hashOfOptions":"111"},"8e407b4b1b0c0bd9c862a00243344be3",{"hashOfOptions":"112"},"d4d589c153ac8b5e7bf0fb130a5b5a7d",{"hashOfOptions":"113"},"cecbed1604a530a7cc099fecddddd76c",{"hashOfOptions":"114"},"ead19d73283f9d8e08b55c896c9fd570",{"hashOfOptions":"115"},"52a1d72379b952dd802f47e1865bd0da",{"hashOfOptions":"116"},"1bc3e15a40c117eecc51294886ea9b38",{"hashOfOptions":"117"},"1e8ac0d261e95efb19d290ffcf70ce36","1c1710ce3de3ce02e8054cc3787c8579",{"hashOfOptions":"118"},"5ff899a601102659dcbd2900e415ce8b",{"hashOfOptions":"119"},"dd2007a211e323deabb3f7fa7d16313f",{"hashOfOptions":"120"},"4f49c6df7733f874fbe72b4e20b3092b",{"hashOfOptions":"121"},"863da15dbd856008b7c24077ca746d91","6937fb7370f1e17491df649888d6ecc9",{"hashOfOptions":"122"},"1820601142","1684748001","3531839294","2748941218","956511134","132171752","3925902565","1550174236","2506462393","526883382","4111358426","2953691686","2383171816","2740949298","3787272667","3740930138","4230803759","3315245788","3164486579"]

View File

@@ -14,8 +14,8 @@ function PostCard(props: {
onDelete: () => void;
}) {
return (
<View className="bg-muted flex flex-row rounded-lg p-4">
<View className="grow">
<View className='bg-muted flex flex-row rounded-lg p-4'>
<View className='grow'>
<Link
asChild
href={{
@@ -23,16 +23,16 @@ function PostCard(props: {
params: { id: props.post.id },
}}
>
<Pressable className="">
<Text className="text-primary text-xl font-semibold">
<Pressable className=''>
<Text className='text-primary text-xl font-semibold'>
{props.post.title}
</Text>
<Text className="text-foreground mt-2">{props.post.content}</Text>
<Text className='text-foreground mt-2'>{props.post.content}</Text>
</Pressable>
</Link>
</View>
<Pressable onPress={props.onDelete}>
<Text className="text-primary font-bold uppercase">Delete</Text>
<Text className='text-primary font-bold uppercase'>Delete</Text>
</Pressable>
</View>
);
@@ -55,31 +55,31 @@ function CreatePost() {
);
return (
<View className="mt-4 flex gap-2">
<View className='mt-4 flex gap-2'>
<TextInput
className="border-input bg-background text-foreground items-center rounded-md border px-3 text-lg leading-tight"
className='border-input bg-background text-foreground items-center rounded-md border px-3 text-lg leading-tight'
value={title}
onChangeText={setTitle}
placeholder="Title"
placeholder='Title'
/>
{error?.data?.zodError?.fieldErrors.title && (
<Text className="text-destructive mb-2">
<Text className='text-destructive mb-2'>
{error.data.zodError.fieldErrors.title}
</Text>
)}
<TextInput
className="border-input bg-background text-foreground items-center rounded-md border px-3 text-lg leading-tight"
className='border-input bg-background text-foreground items-center rounded-md border px-3 text-lg leading-tight'
value={content}
onChangeText={setContent}
placeholder="Content"
placeholder='Content'
/>
{error?.data?.zodError?.fieldErrors.content && (
<Text className="text-destructive mb-2">
<Text className='text-destructive mb-2'>
{error.data.zodError.fieldErrors.content}
</Text>
)}
<Pressable
className="bg-primary flex items-center rounded-sm p-2"
className='bg-primary flex items-center rounded-sm p-2'
onPress={() => {
mutate({
title,
@@ -87,10 +87,10 @@ function CreatePost() {
});
}}
>
<Text className="text-foreground">Create</Text>
<Text className='text-foreground'>Create</Text>
</Pressable>
{error?.data?.code === 'UNAUTHORIZED' && (
<Text className="text-destructive mt-2">
<Text className='text-destructive mt-2'>
You need to be logged in to create a post
</Text>
)}
@@ -103,7 +103,7 @@ function MobileAuth() {
return (
<>
<Text className="text-foreground pb-2 text-center text-xl font-semibold">
<Text className='text-foreground pb-2 text-center text-xl font-semibold'>
{session?.user.name ? `Hello, ${session.user.name}` : 'Not logged in'}
</Text>
<Pressable
@@ -115,7 +115,7 @@ function MobileAuth() {
callbackURL: '/',
})
}
className="bg-primary flex items-center rounded-sm p-2"
className='bg-primary flex items-center rounded-sm p-2'
>
<Text>{session ? 'Sign Out' : 'Sign In With Discord'}</Text>
</Pressable>
@@ -136,18 +136,18 @@ export default function Index() {
);
return (
<SafeAreaView className="bg-background">
<SafeAreaView className='bg-background'>
{/* Changes page title visible on the header */}
<Stack.Screen options={{ title: 'Home Page' }} />
<View className="bg-background h-full w-full p-4">
<Text className="text-foreground pb-2 text-center text-5xl font-bold">
Create <Text className="text-primary">T3</Text> Turbo
<View className='bg-background h-full w-full p-4'>
<Text className='text-foreground pb-2 text-center text-5xl font-bold'>
Create <Text className='text-primary'>T3</Text> Turbo
</Text>
<MobileAuth />
<View className="py-2">
<Text className="text-primary font-semibold italic">
<View className='py-2'>
<Text className='text-primary font-semibold italic'>
Press on a post
</Text>
</View>
@@ -156,7 +156,7 @@ export default function Index() {
data={postQuery.data ?? []}
estimatedItemSize={20}
keyExtractor={(item) => item.id}
ItemSeparatorComponent={() => <View className="h-2" />}
ItemSeparatorComponent={() => <View className='h-2' />}
renderItem={(p) => (
<PostCard
post={p.item}

View File

@@ -11,13 +11,13 @@ export default function Post() {
if (!data) return null;
return (
<SafeAreaView className="bg-background">
<SafeAreaView className='bg-background'>
<Stack.Screen options={{ title: data.title }} />
<View className="h-full w-full p-4">
<Text className="text-primary py-2 text-3xl font-bold">
<View className='h-full w-full p-4'>
<Text className='text-primary py-2 text-3xl font-bold'>
{data.title}
</Text>
<Text className="text-foreground py-4">{data.content}</Text>
<Text className='text-foreground py-4'>{data.content}</Text>
</View>
</SafeAreaView>
);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -132,12 +132,12 @@ const ForgotPassword = () => {
};
return (
<div className="flex flex-col items-center">
<Card className="bg-card/25 min-h-[400px] w-sm p-4 lg:w-md">
<CardHeader className="flex flex-col items-center gap-4">
<div className='flex flex-col items-center'>
<Card className='bg-card/25 min-h-[400px] w-sm p-4 lg:w-md'>
<CardHeader className='flex flex-col items-center gap-4'>
{flow === 'reset' ? (
<>
<CardTitle className="text-2xl font-bold">
<CardTitle className='text-2xl font-bold'>
Forgot Password
</CardTitle>
<CardDescription>
@@ -147,7 +147,7 @@ const ForgotPassword = () => {
</>
) : (
<>
<CardTitle className="text-2xl font-bold">
<CardTitle className='text-2xl font-bold'>
Reset Password
</CardTitle>
<CardDescription>
@@ -158,7 +158,7 @@ const ForgotPassword = () => {
)}
</CardHeader>
<CardContent>
<Card className="bg-card/50">
<Card className='bg-card/50'>
<CardContent>
{flow === 'reset' ? (
<Form {...forgotPasswordForm}>
@@ -166,31 +166,31 @@ const ForgotPassword = () => {
onSubmit={forgotPasswordForm.handleSubmit(
handleForgotPasswordSubmit,
)}
className="flex flex-col space-y-4"
className='flex flex-col space-y-4'
>
<FormField
control={forgotPasswordForm.control}
name="email"
name='email'
render={({ field }) => (
<FormItem>
<FormLabel className="text-xl">Email</FormLabel>
<FormLabel className='text-xl'>Email</FormLabel>
<FormControl>
<Input
type="email"
placeholder="you@example.com"
type='email'
placeholder='you@example.com'
{...field}
/>
</FormControl>
<div className="flex w-full flex-col items-center">
<FormMessage className="w-5/6 text-center" />
<div className='flex w-full flex-col items-center'>
<FormMessage className='w-5/6 text-center' />
</div>
</FormItem>
)}
/>
<SubmitButton
disabled={loading}
pendingText="Sending Email..."
className="mx-auto w-2/3 text-xl font-semibold"
pendingText='Sending Email...'
className='mx-auto w-2/3 text-xl font-semibold'
>
Send Email
</SubmitButton>
@@ -202,14 +202,14 @@ const ForgotPassword = () => {
onSubmit={resetVerificationForm.handleSubmit(
handleResetVerificationSubmit,
)}
className="flex flex-col space-y-4"
className='flex flex-col space-y-4'
>
<FormField
control={resetVerificationForm.control}
name="code"
name='code'
render={({ field }) => (
<FormItem>
<FormLabel className="text-xl">Code</FormLabel>
<FormLabel className='text-xl'>Code</FormLabel>
<FormControl>
<InputOTP
maxLength={6}
@@ -232,58 +232,58 @@ const ForgotPassword = () => {
Please enter the one-time password sent to your
phone.
</FormDescription>
<div className="flex w-full flex-col items-center">
<FormMessage className="w-5/6 text-center" />
<div className='flex w-full flex-col items-center'>
<FormMessage className='w-5/6 text-center' />
</div>
</FormItem>
)}
/>
<FormField
control={resetVerificationForm.control}
name="newPassword"
name='newPassword'
render={({ field }) => (
<FormItem>
<FormLabel className="text-xl">
<FormLabel className='text-xl'>
New Password
</FormLabel>
<FormControl>
<Input
type="password"
placeholder="Your password"
type='password'
placeholder='Your password'
{...field}
/>
</FormControl>
<div className="flex w-full flex-col items-center">
<FormMessage className="w-5/6 text-center" />
<div className='flex w-full flex-col items-center'>
<FormMessage className='w-5/6 text-center' />
</div>
</FormItem>
)}
/>
<FormField
control={resetVerificationForm.control}
name="confirmPassword"
name='confirmPassword'
render={({ field }) => (
<FormItem>
<FormLabel className="text-xl">
<FormLabel className='text-xl'>
Confirm Passsword
</FormLabel>
<FormControl>
<Input
type="password"
placeholder="Confirm your password"
type='password'
placeholder='Confirm your password'
{...field}
/>
</FormControl>
<div className="flex w-full flex-col items-center">
<FormMessage className="w-5/6 text-center" />
<div className='flex w-full flex-col items-center'>
<FormMessage className='w-5/6 text-center' />
</div>
</FormItem>
)}
/>
<SubmitButton
disabled={loading}
pendingText="Resetting Password..."
className="mx-auto w-2/3 text-xl font-semibold"
pendingText='Resetting Password...'
className='mx-auto w-2/3 text-xl font-semibold'
>
Reset Password
</SubmitButton>

View File

@@ -14,28 +14,34 @@ import { Card, Separator } from '@gib/ui';
const Profile = async () => {
const preloadedUser = await preloadQuery(api.auth.getUser, {});
const preloadedUserProvider = await preloadQuery(api.auth.getUserProvider, {});
const preloadedUserProvider = await preloadQuery(
api.auth.getUserProvider,
{},
);
return (
<main className="container mx-auto px-4 py-12 md:py-16">
<div className="mx-auto max-w-3xl">
<main className='container mx-auto px-4 py-12 md:py-16'>
<div className='mx-auto max-w-3xl'>
{/* Page Header */}
<div className="mb-8 text-center">
<h1 className="mb-2 text-3xl font-bold tracking-tight sm:text-4xl">
<div className='mb-8 text-center'>
<h1 className='mb-2 text-3xl font-bold tracking-tight sm:text-4xl'>
Your Profile
</h1>
<p className="text-muted-foreground">
<p className='text-muted-foreground'>
Manage your personal information and preferences
</p>
</div>
{/* Profile Card */}
<Card className="border-border/40">
<Card className='border-border/40'>
<ProfileHeader preloadedUser={preloadedUser} />
<AvatarUpload preloadedUser={preloadedUser} />
<Separator className="my-6" />
<UserInfoForm preloadedUser={preloadedUser} preloadedProvider={preloadedUserProvider} />
<Separator className='my-6' />
<UserInfoForm
preloadedUser={preloadedUser}
preloadedProvider={preloadedUserProvider}
/>
<ResetPasswordForm preloadedProvider={preloadedUserProvider} />
<Separator className="my-6" />
<Separator className='my-6' />
<SignOutForm />
</Card>
</div>

View File

@@ -179,24 +179,24 @@ const SignIn = () => {
if (flow === 'email-verification') {
return (
<div className="flex flex-col items-center">
<Card className="bg-card/25 min-h-[720px] w-md p-4">
<div className='flex flex-col items-center'>
<Card className='bg-card/25 min-h-[720px] w-md p-4'>
<CardContent>
<div className="mb-6 text-center">
<h2 className="text-2xl font-bold">Verify Your Email</h2>
<p className="text-muted-foreground">We sent a code to {email}</p>
<div className='mb-6 text-center'>
<h2 className='text-2xl font-bold'>Verify Your Email</h2>
<p className='text-muted-foreground'>We sent a code to {email}</p>
</div>
<Form {...verifyEmailForm}>
<form
onSubmit={verifyEmailForm.handleSubmit(handleVerifyEmail)}
className="flex flex-col space-y-8"
className='flex flex-col space-y-8'
>
<FormField
control={verifyEmailForm.control}
name="code"
name='code'
render={({ field }) => (
<FormItem>
<FormLabel className="text-xl">Code</FormLabel>
<FormLabel className='text-xl'>Code</FormLabel>
<FormControl>
<InputOTP
maxLength={6}
@@ -217,25 +217,25 @@ const SignIn = () => {
<FormDescription>
Please enter the one-time password sent to your email.
</FormDescription>
<div className="flex w-full flex-col items-center">
<FormMessage className="w-5/6 text-center" />
<div className='flex w-full flex-col items-center'>
<FormMessage className='w-5/6 text-center' />
</div>
</FormItem>
)}
/>
<SubmitButton
disabled={loading}
pendingText="Signing Up..."
className="mx-auto w-2/3 text-xl font-semibold"
pendingText='Signing Up...'
className='mx-auto w-2/3 text-xl font-semibold'
>
Verify Email
</SubmitButton>
</form>
</Form>
<div className="mt-4 text-center">
<div className='mt-4 text-center'>
<button
onClick={() => setFlow('signUp')}
className="text-muted-foreground text-sm hover:underline"
className='text-muted-foreground text-sm hover:underline'
>
Back to Sign Up
</button>
@@ -247,204 +247,204 @@ const SignIn = () => {
}
return (
<div className="flex flex-col items-center">
<Card className="bg-card/25 min-h-[720px] w-md p-4">
<div className='flex flex-col items-center'>
<Card className='bg-card/25 min-h-[720px] w-md p-4'>
<Tabs
defaultValue={flow}
onValueChange={(value) => setFlow(value as 'signIn' | 'signUp')}
className="items-center"
className='items-center'
>
<TabsList className="py-6">
<TabsList className='py-6'>
<TabsTrigger
value="signIn"
className="cursor-pointer p-6 text-2xl font-bold"
value='signIn'
className='cursor-pointer p-6 text-2xl font-bold'
>
Sign In
</TabsTrigger>
<TabsTrigger
value="signUp"
className="cursor-pointer p-6 text-2xl font-bold"
value='signUp'
className='cursor-pointer p-6 text-2xl font-bold'
>
Sign Up
</TabsTrigger>
</TabsList>
<TabsContent value="signIn">
<Card className="bg-card/50 min-w-xs sm:min-w-sm">
<TabsContent value='signIn'>
<Card className='bg-card/50 min-w-xs sm:min-w-sm'>
<CardContent>
<Form {...signInForm}>
<form
onSubmit={signInForm.handleSubmit(handleSignIn)}
className="flex flex-col space-y-8"
className='flex flex-col space-y-8'
>
<FormField
control={signInForm.control}
name="email"
name='email'
render={({ field }) => (
<FormItem>
<FormLabel className="text-xl">Email</FormLabel>
<FormLabel className='text-xl'>Email</FormLabel>
<FormControl>
<Input
type="email"
placeholder="you@example.com"
type='email'
placeholder='you@example.com'
{...field}
/>
</FormControl>
<div className="flex w-full flex-col items-center">
<FormMessage className="w-5/6 text-center" />
<div className='flex w-full flex-col items-center'>
<FormMessage className='w-5/6 text-center' />
</div>
</FormItem>
)}
/>
<FormField
control={signInForm.control}
name="password"
name='password'
render={({ field }) => (
<FormItem>
<div className="flex justify-between">
<FormLabel className="text-xl">Password</FormLabel>
<Link href="/forgot-password">
<div className='flex justify-between'>
<FormLabel className='text-xl'>Password</FormLabel>
<Link href='/forgot-password'>
Forgot Password?
</Link>
</div>
<FormControl>
<Input
type="password"
placeholder="Your password"
type='password'
placeholder='Your password'
{...field}
/>
</FormControl>
<div className="flex w-full flex-col items-center">
<FormMessage className="w-5/6 text-center" />
<div className='flex w-full flex-col items-center'>
<FormMessage className='w-5/6 text-center' />
</div>
</FormItem>
)}
/>
<SubmitButton
disabled={loading}
pendingText="Signing in..."
className="mx-auto w-2/3 text-xl font-semibold"
pendingText='Signing in...'
className='mx-auto w-2/3 text-xl font-semibold'
>
Sign In
</SubmitButton>
</form>
</Form>
<div className="flex justify-center">
<div className="mx-auto my-2.5 flex w-1/4 flex-row items-center justify-center">
<Separator className="mr-3 py-0.5" />
<span className="text-lg font-semibold">or</span>
<Separator className="ml-3 py-0.5" />
<div className='flex justify-center'>
<div className='mx-auto my-2.5 flex w-1/4 flex-row items-center justify-center'>
<Separator className='mr-3 py-0.5' />
<span className='text-lg font-semibold'>or</span>
<Separator className='ml-3 py-0.5' />
</div>
</div>
<div className="mt-3 flex justify-center">
<div className='mt-3 flex justify-center'>
<GibsAuthSignInButton />
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="signUp">
<Card className="bg-card/50 min-w-xs sm:min-w-sm">
<TabsContent value='signUp'>
<Card className='bg-card/50 min-w-xs sm:min-w-sm'>
<CardContent>
<Form {...signUpForm}>
<form
onSubmit={signUpForm.handleSubmit(handleSignUp)}
className="flex flex-col space-y-8"
className='flex flex-col space-y-8'
>
<FormField
control={signUpForm.control}
name="name"
name='name'
render={({ field }) => (
<FormItem>
<FormLabel className="text-xl">Name</FormLabel>
<FormLabel className='text-xl'>Name</FormLabel>
<FormControl>
<Input
type="text"
placeholder="Full Name"
type='text'
placeholder='Full Name'
{...field}
/>
</FormControl>
<div className="flex w-full flex-col items-center">
<FormMessage className="w-5/6 text-center" />
<div className='flex w-full flex-col items-center'>
<FormMessage className='w-5/6 text-center' />
</div>
</FormItem>
)}
/>
<FormField
control={signUpForm.control}
name="email"
name='email'
render={({ field }) => (
<FormItem>
<FormLabel className="text-xl">Email</FormLabel>
<FormLabel className='text-xl'>Email</FormLabel>
<FormControl>
<Input
type="email"
placeholder="you@example.com"
type='email'
placeholder='you@example.com'
{...field}
/>
</FormControl>
<div className="flex w-full flex-col items-center">
<FormMessage className="w-5/6 text-center" />
<div className='flex w-full flex-col items-center'>
<FormMessage className='w-5/6 text-center' />
</div>
</FormItem>
)}
/>
<FormField
control={signUpForm.control}
name="password"
name='password'
render={({ field }) => (
<FormItem>
<FormLabel className="text-xl">Password</FormLabel>
<FormLabel className='text-xl'>Password</FormLabel>
<FormControl>
<Input
type="password"
placeholder="Your password"
type='password'
placeholder='Your password'
{...field}
/>
</FormControl>
<div className="flex w-full flex-col items-center">
<FormMessage className="w-5/6 text-center" />
<div className='flex w-full flex-col items-center'>
<FormMessage className='w-5/6 text-center' />
</div>
</FormItem>
)}
/>
<FormField
control={signUpForm.control}
name="confirmPassword"
name='confirmPassword'
render={({ field }) => (
<FormItem>
<FormLabel className="text-xl">
<FormLabel className='text-xl'>
Confirm Passsword
</FormLabel>
<FormControl>
<Input
type="password"
placeholder="Confirm your password"
type='password'
placeholder='Confirm your password'
{...field}
/>
</FormControl>
<div className="flex w-full flex-col items-center">
<FormMessage className="w-5/6 text-center" />
<div className='flex w-full flex-col items-center'>
<FormMessage className='w-5/6 text-center' />
</div>
</FormItem>
)}
/>
<SubmitButton
disabled={loading}
pendingText="Signing Up..."
className="mx-auto w-2/3 text-xl font-semibold"
pendingText='Signing Up...'
className='mx-auto w-2/3 text-xl font-semibold'
>
Sign Up
</SubmitButton>
</form>
</Form>
<div className="my-auto flex w-2/3 justify-center">
<div className="my-2.5 flex w-1/3 flex-row items-center">
<Separator className="mr-3 py-0.5" />
<span className="text-lg font-semibold">or</span>
<Separator className="ml-3 py-0.5" />
<div className='my-auto flex w-2/3 justify-center'>
<div className='my-2.5 flex w-1/3 flex-row items-center'>
<Separator className='mr-3 py-0.5' />
<span className='text-lg font-semibold'>or</span>
<Separator className='ml-3 py-0.5' />
</div>
</div>
<div className="mt-3 flex justify-center">
<GibsAuthSignInButton type="signUp" />
<div className='mt-3 flex justify-center'>
<GibsAuthSignInButton type='signUp' />
</div>
</CardContent>
</Card>

View File

@@ -3,13 +3,15 @@
import type { Metadata, Viewport } from 'next';
import NextError from 'next/error';
import { Geist, Geist_Mono } from 'next/font/google';
import '@/app/styles.css';
import { useEffect } from 'react';
import Footer from '@/components/layout/footer';
import Header from '@/components/layout/header';
import * as Sentry from '@sentry/nextjs';
import { ConvexClientProvider } from '@/components/providers';
import { generateMetadata } from '@/lib/metadata';
import * as Sentry from '@sentry/nextjs';
import PlausibleProvider from 'next-plausible';
import { Button, ThemeProvider, Toaster } from '@gib/ui';
@@ -35,7 +37,7 @@ const geistMono = Geist_Mono({
interface GlobalErrorProps {
error: Error & { digest?: string };
reset?: () => void;
};
}
const GlobalError = ({ error, reset = undefined }: GlobalErrorProps) => {
useEffect(() => {
@@ -43,21 +45,21 @@ const GlobalError = ({ error, reset = undefined }: GlobalErrorProps) => {
}, [error]);
return (
<PlausibleProvider
domain="convexmonorepo.gbrown.org"
customDomain="https://plausible.gbrown.org"
domain='convexmonorepo.gbrown.org'
customDomain='https://plausible.gbrown.org'
>
<html lang="en" suppressHydrationWarning>
<html lang='en' suppressHydrationWarning>
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<ThemeProvider
attribute="class"
defaultTheme="system"
attribute='class'
defaultTheme='system'
enableSystem
disableTransitionOnChange
>
<ConvexClientProvider>
<main className="flex min-h-screen flex-col items-center">
<main className='flex min-h-screen flex-col items-center'>
<Header />
<NextError statusCode={0} />
{reset !== undefined && (
@@ -66,7 +68,7 @@ const GlobalError = ({ error, reset = undefined }: GlobalErrorProps) => {
<Toaster />
<Footer />
</main>
<main className="flex min-h-[90vh] flex-col items-center">
<main className='flex min-h-[90vh] flex-col items-center'>
<Toaster />
</main>
</ConvexClientProvider>

View File

@@ -1,5 +1,6 @@
import type { Metadata, Viewport } from 'next';
import { Geist, Geist_Mono } from 'next/font/google';
import { env } from '@/env';
import '@/app/styles.css';
@@ -38,23 +39,23 @@ const RootLayout = ({
return (
<ConvexAuthNextjsServerProvider>
<PlausibleProvider
domain="convexmonorepo.gbrown.org"
customDomain="https://plausible.gbrown.org"
domain={env.NEXT_PUBLIC_SITE_URL}
customDomain={env.NEXT_PUBLIC_PLAUSIBLE_URL}
>
<html lang="en">
<html lang='en'>
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<ThemeProvider
attribute="class"
defaultTheme="system"
attribute='class'
defaultTheme='system'
enableSystem
disableTransitionOnChange
>
<ConvexClientProvider>
<div className="flex min-h-screen flex-col">
<div className='flex min-h-screen flex-col'>
<Header />
<div className="flex-1">{children}</div>
{children}
<Footer />
</div>
<Toaster />

View File

@@ -1,14 +1,12 @@
import { CTA, Features, Hero, TechStack } from '@/components/landing';
const Home = async () => {
export default function Home() {
return (
<main className="flex min-h-screen flex-col">
<main className='flex min-h-screen flex-col'>
<Hero />
<Features />
<TechStack />
<CTA />
</main>
);
};
export default Home;
}

View File

@@ -5,24 +5,24 @@ import { Button } from '@gib/ui/button';
export function CTA() {
return (
<section className="container mx-auto px-4 py-24">
<div className="mx-auto max-w-4xl">
<div className="border-border/40 from-muted/50 to-muted/30 rounded-2xl border bg-gradient-to-br p-8 text-center md:p-12">
<h2 className="mb-4 text-3xl font-bold tracking-tight sm:text-4xl">
<section className='container mx-auto px-4 py-24'>
<div className='mx-auto max-w-4xl'>
<div className='border-border/40 from-muted/50 to-muted/30 rounded-2xl border bg-gradient-to-br p-8 text-center md:p-12'>
<h2 className='mb-4 text-3xl font-bold tracking-tight sm:text-4xl'>
Ready to Build Something Amazing?
</h2>
<p className="text-muted-foreground mb-8 text-lg">
<p className='text-muted-foreground mb-8 text-lg'>
Clone the repository and start building your next project with
everything pre-configured.
</p>
{/* Quick Start Command */}
<div className="mt-12">
<p className="text-muted-foreground mb-3 text-sm font-medium">
<div className='mt-12'>
<p className='text-muted-foreground mb-3 text-sm font-medium'>
Quick Start
</p>
<div className="border-border/40 bg-background mx-auto max-w-2xl rounded-lg border p-4">
<code className="text-sm">
<div className='border-border/40 bg-background mx-auto max-w-2xl rounded-lg border p-4'>
<code className='text-sm'>
git clone https://git.gbrown.org/gib/convex-monorepo.git
<br />
cd convex-monorepo

View File

@@ -59,29 +59,29 @@ const features = [
export function Features() {
return (
<section id="features" className="container mx-auto px-4 py-24">
<div className="mx-auto max-w-6xl">
<section id='features' className='container mx-auto px-4 py-24'>
<div className='mx-auto max-w-6xl'>
{/* Section Header */}
<div className="mb-16 text-center">
<h2 className="mb-4 text-3xl font-bold tracking-tight sm:text-4xl md:text-5xl">
<div className='mb-16 text-center'>
<h2 className='mb-4 text-3xl font-bold tracking-tight sm:text-4xl md:text-5xl'>
Everything You Need to Ship Fast
</h2>
<p className="text-muted-foreground mx-auto max-w-2xl text-lg">
<p className='text-muted-foreground mx-auto max-w-2xl text-lg'>
A complete monorepo template with all the tools and patterns you
need for production-ready applications.
</p>
</div>
{/* Features Grid */}
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<div className='grid gap-6 md:grid-cols-2 lg:grid-cols-3'>
{features.map((feature) => (
<Card key={feature.title} className="border-border/40">
<Card key={feature.title} className='border-border/40'>
<CardHeader className='flex items-center gap-2'>
<div className="mb-2 text-3xl">{feature.icon}</div>
<CardTitle className="text-xl">{feature.title}</CardTitle>
<div className='mb-2 text-3xl'>{feature.icon}</div>
<CardTitle className='text-xl'>{feature.title}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">{feature.description}</p>
<p className='text-muted-foreground'>{feature.description}</p>
</CardContent>
</Card>
))}

View File

@@ -1,7 +1,8 @@
import Link from 'next/link';
import Image from 'next/image';
import { Button } from '@gib/ui/button';
import { Kanit } from 'next/font/google';
import Image from 'next/image';
import Link from 'next/link';
import { Button } from '@gib/ui/button';
const kanitSans = Kanit({
subsets: ['latin'],
@@ -10,36 +11,38 @@ const kanitSans = Kanit({
export function Hero() {
return (
<section className="container mx-auto px-4 py-24 md:py-32 lg:py-40">
<div className="mx-auto flex max-w-5xl flex-col items-center gap-8 text-center">
<section className='container mx-auto px-4 py-24 md:py-32 lg:py-40'>
<div className='mx-auto flex max-w-5xl flex-col items-center gap-8 text-center'>
{/* Badge */}
<div className="border-border/40 bg-muted/50 inline-flex items-center rounded-full border px-3 py-1 text-sm font-medium">
<span className="mr-2">🚀</span>
<div className='border-border/40 bg-muted/50 inline-flex items-center rounded-full border px-3 py-1 text-sm font-medium'>
<span className='mr-2'>🚀</span>
<span>Production-ready monorepo template</span>
</div>
{/* Heading */}
<h1 className="from-foreground to-foreground/70 bg-linear-to-br bg-clip-text text-4xl font-bold tracking-tight text-transparent sm:text-5xl md:text-6xl lg:text-7xl">
<h1 className='from-foreground to-foreground/70 bg-linear-to-br bg-clip-text text-4xl font-bold tracking-tight text-transparent sm:text-5xl md:text-6xl lg:text-7xl'>
Build Full-Stack Apps with{' '}
<span className={`${kanitSans.className} to-accent-foreground bg-linear-to-r from-[#281A65] via-[#363354] bg-clip-text text-transparent dark:from-[#bec8e6] dark:via-[#F0EEE4] dark:to-[#FFF8E7] sm:text-6xl md:text-7xl lg:text-8xl`}>
<span
className={`${kanitSans.className} to-accent-foreground bg-linear-to-r from-[#281A65] via-[#363354] bg-clip-text text-transparent sm:text-6xl lg:text-7xl xl:text-8xl dark:from-[#bec8e6] dark:via-[#F0EEE4] dark:to-[#FFF8E7]`}
>
convex monorepo
</span>
</h1>
{/* Description */}
<p className="text-muted-foreground max-w-2xl text-lg md:text-xl">
<p className='text-muted-foreground max-w-2xl text-lg md:text-xl'>
A Turborepo starter with Next.js, Expo, and self-hosted Convex. Ship
web and mobile apps faster with shared code, type-safe backend, and
complete control over your infrastructure.
</p>
{/* CTA Buttons */}
<div className="flex flex-col gap-3 sm:flex-row">
<Button size="lg" variant="outline" asChild>
<div className='flex flex-col gap-3 sm:flex-row'>
<Button size='lg' variant='outline' asChild>
<Link
href="https://git.gbrown.org/gib/convex-monorepo"
target="_blank"
rel="noopener noreferrer"
href='https://git.gbrown.org/gib/convex-monorepo'
target='_blank'
rel='noopener noreferrer'
>
<Image
src='/misc/gitea/gitea.svg'
@@ -53,67 +56,67 @@ export function Hero() {
</div>
{/* Features Quick List */}
<div className="text-muted-foreground mt-8 flex flex-wrap items-center justify-center gap-6 text-sm">
<div className="flex items-center gap-2">
<div className='text-muted-foreground mt-8 flex flex-wrap items-center justify-center gap-6 text-sm'>
<div className='flex items-center gap-2'>
<svg
className="h-5 w-5 text-green-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
className='h-5 w-5 text-green-500'
fill='none'
viewBox='0 0 24 24'
stroke='currentColor'
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth={2}
d="M5 13l4 4L19 7"
d='M5 13l4 4L19 7'
/>
</svg>
<span>TypeScript</span>
</div>
<div className="flex items-center gap-2">
<div className='flex items-center gap-2'>
<svg
className="h-5 w-5 text-green-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
className='h-5 w-5 text-green-500'
fill='none'
viewBox='0 0 24 24'
stroke='currentColor'
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth={2}
d="M5 13l4 4L19 7"
d='M5 13l4 4L19 7'
/>
</svg>
<span>Self-Hosted</span>
</div>
<div className="flex items-center gap-2">
<div className='flex items-center gap-2'>
<svg
className="h-5 w-5 text-green-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
className='h-5 w-5 text-green-500'
fill='none'
viewBox='0 0 24 24'
stroke='currentColor'
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth={2}
d="M5 13l4 4L19 7"
d='M5 13l4 4L19 7'
/>
</svg>
<span>Real-time</span>
</div>
<div className="flex items-center gap-2">
<div className='flex items-center gap-2'>
<svg
className="h-5 w-5 text-green-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
className='h-5 w-5 text-green-500'
fill='none'
viewBox='0 0 24 24'
stroke='currentColor'
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth={2}
d="M5 13l4 4L19 7"
d='M5 13l4 4L19 7'
/>
</svg>
<span>Auth Included</span>

View File

@@ -38,32 +38,32 @@ const techStack = [
export function TechStack() {
return (
<section id="tech-stack" className="border-border/40 bg-muted/30 border-t">
<div className="container mx-auto px-4 py-24">
<div className="mx-auto max-w-6xl">
<section id='tech-stack' className='border-border/40 bg-muted/30 border-t'>
<div className='container mx-auto px-4 py-24'>
<div className='mx-auto max-w-6xl'>
{/* Section Header */}
<div className="mb-16 text-center">
<h2 className="mb-4 text-3xl font-bold tracking-tight sm:text-4xl md:text-5xl">
<div className='mb-16 text-center'>
<h2 className='mb-4 text-3xl font-bold tracking-tight sm:text-4xl md:text-5xl'>
Modern Tech Stack
</h2>
<p className="text-muted-foreground mx-auto max-w-2xl text-lg">
<p className='text-muted-foreground mx-auto max-w-2xl text-lg'>
Built with the latest and greatest tools for maximum productivity
and performance.
</p>
</div>
{/* Tech Stack Grid */}
<div className="grid gap-12 md:grid-cols-3">
<div className='grid gap-12 md:grid-cols-3'>
{techStack.map((stack) => (
<div key={stack.category}>
<h3 className="mb-6 text-xl font-semibold">{stack.category}</h3>
<ul className="space-y-4">
<h3 className='mb-6 text-xl font-semibold'>{stack.category}</h3>
<ul className='space-y-4'>
{stack.technologies.map((tech) => (
<li key={tech.name}>
<div className="text-foreground font-medium">
<div className='text-foreground font-medium'>
{tech.name}
</div>
<div className="text-muted-foreground text-sm">
<div className='text-muted-foreground text-sm'>
{tech.description}
</div>
</li>

View File

@@ -21,15 +21,15 @@ export const GibsAuthSignInButton = ({
const { signIn } = useAuthActions();
return (
<Button
size="lg"
size='lg'
onClick={() => signIn('authentik')}
className="text-lg font-semibold"
className='text-lg font-semibold'
{...buttonProps}
>
<div className="my-auto flex flex-row space-x-1">
<div className='my-auto flex flex-row space-x-1'>
<Image
src={'/misc/auth/gibs-auth-logo.png'}
className=""
className=''
alt="Gib's Auth"
width={30}
height={30}

View File

@@ -50,7 +50,7 @@ export const AvatarUpload = ({ preloadedUser }: AvatarUploadProps) => {
const currentImageUrl = useQuery(
api.files.getImageUrl,
user?.image ? { storageId: user.image } : 'skip',
user?.image ? { storageId: user.image as Id<'_storage'> } : 'skip',
);
const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {
@@ -109,28 +109,28 @@ export const AvatarUpload = ({ preloadedUser }: AvatarUploadProps) => {
return (
<CardContent>
<div className="flex flex-col items-center gap-4">
<div className='flex flex-col items-center gap-4'>
{/* Current avatar + trigger (hidden when cropping) */}
{!selectedFile && (
<div
className="group relative cursor-pointer"
className='group relative cursor-pointer'
onClick={() => inputRef.current?.click()}
>
<BasedAvatar
src={currentImageUrl ?? undefined}
fullName={user?.name}
className="h-42 w-42 text-6xl font-semibold"
className='h-42 w-42 text-6xl font-semibold'
userIconProps={{ size: 100 }}
/>
<div className="absolute inset-0 flex items-center justify-center rounded-full bg-black/0 transition-all group-hover:bg-black/50">
<div className='absolute inset-0 flex items-center justify-center rounded-full bg-black/0 transition-all group-hover:bg-black/50'>
<Upload
className="text-white opacity-0 transition-opacity group-hover:opacity-100"
className='text-white opacity-0 transition-opacity group-hover:opacity-100'
size={24}
/>
</div>
<div className="absolute inset-1 flex items-end justify-end transition-all">
<div className='absolute inset-1 flex items-end justify-end transition-all'>
<Pencil
className="text-white opacity-100 transition-opacity group-hover:opacity-0"
className='text-white opacity-100 transition-opacity group-hover:opacity-0'
size={24}
/>
</div>
@@ -140,17 +140,17 @@ export const AvatarUpload = ({ preloadedUser }: AvatarUploadProps) => {
{/* File input (hidden) */}
<Input
ref={inputRef}
id="avatar-upload"
type="file"
accept="image/*"
className="hidden"
id='avatar-upload'
type='file'
accept='image/*'
className='hidden'
onChange={handleFileChange}
disabled={isUploading}
/>
{/* Crop UI */}
{selectedFile && !croppedImage && (
<div className="flex flex-col items-center gap-3">
<div className='flex flex-col items-center gap-3'>
<ImageCrop
aspect={1}
circularCrop
@@ -158,16 +158,13 @@ export const AvatarUpload = ({ preloadedUser }: AvatarUploadProps) => {
maxImageSize={3 * 1024 * 1024} // 3MB guard
onCrop={setCroppedImage}
>
<ImageCropContent className="max-w-sm" />
<div className="flex items-center gap-2">
<ImageCropApply />
<Button
onClick={handleReset}
size="icon"
type="button"
variant="ghost"
>
<XIcon className="size-4" />
<ImageCropContent className='max-w-sm' />
<div className='flex items-center gap-2'>
<Button size='icon' variant='outline'>
<ImageCropApply className='h-full w-full scale-150' />
</Button>
<Button onClick={handleReset} size='icon' variant='destructive'>
<XIcon className='scale-150' />
</Button>
</div>
</ImageCrop>
@@ -176,19 +173,20 @@ export const AvatarUpload = ({ preloadedUser }: AvatarUploadProps) => {
{/* Cropped preview + actions */}
{croppedImage && (
<div className="flex flex-col items-center gap-3">
<Avatar className="h-42 w-42">
<AvatarImage alt="Cropped preview" src={croppedImage} />
<div className='flex flex-col items-center gap-3'>
<Avatar className='h-42 w-42'>
<AvatarImage alt='Cropped preview' src={croppedImage} />
</Avatar>
<div className="flex items-center gap-1">
<div className='flex items-center gap-1'>
<Button
onClick={handleSave}
disabled={isUploading}
className="px-4"
variant='secondary'
className='px-4'
>
{isUploading ? (
<span className="inline-flex items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin" />
<span className='inline-flex items-center gap-2'>
<Loader2 className='h-4 w-4 animate-spin' />
Saving...
</span>
) : (
@@ -197,12 +195,11 @@ export const AvatarUpload = ({ preloadedUser }: AvatarUploadProps) => {
</Button>
<Button
onClick={handleReset}
size="icon"
type="button"
className="hover:dark:bg-accent bg-red-400/80 hover:text-red-800/80 dark:bg-red-500/30 hover:dark:text-red-300/60"
variant="secondary"
size='icon'
type='button'
variant='destructive'
>
<XIcon className="size-4" />
<XIcon className='size-4' />
</Button>
</div>
</div>
@@ -210,8 +207,8 @@ export const AvatarUpload = ({ preloadedUser }: AvatarUploadProps) => {
{/* Uploading indicator */}
{isUploading && !croppedImage && (
<div className="mt-2 flex items-center text-sm text-gray-500">
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<div className='mt-2 flex items-center text-sm text-gray-500'>
<Loader2 className='mr-2 h-4 w-4 animate-spin' />
Uploading...
</div>
)}

View File

@@ -14,9 +14,7 @@ const ProfileHeader = ({ preloadedUser }: ProfileCardProps) => {
const user = usePreloadedQuery(preloadedUser);
return (
<CardHeader>
<CardTitle className="text-xl">
Account Settings
</CardTitle>
<CardTitle className='text-xl'>Account Settings</CardTitle>
<CardDescription>
Update your profile information and manage your account preferences
</CardDescription>

View File

@@ -68,7 +68,7 @@ const formSchema = z
interface ResetFormProps {
preloadedProvider: Preloaded<typeof api.auth.getUserProvider>;
};
}
export const ResetPasswordForm = ({ preloadedProvider }: ResetFormProps) => {
const userProvider = usePreloadedQuery(preloadedProvider);
@@ -121,19 +121,19 @@ export const ResetPasswordForm = ({ preloadedProvider }: ResetFormProps) => {
<Form {...form}>
<form
onSubmit={form.handleSubmit(handleSubmit)}
className="space-y-4"
className='space-y-4'
>
<FormField
control={form.control}
name="currentPassword"
name='currentPassword'
render={({ field }) => (
<FormItem>
<FormLabel>Current Password</FormLabel>
<FormControl>
<Input
type="password"
type='password'
{...field}
placeholder="Enter current password"
placeholder='Enter current password'
/>
</FormControl>
<FormMessage />
@@ -142,15 +142,15 @@ export const ResetPasswordForm = ({ preloadedProvider }: ResetFormProps) => {
/>
<FormField
control={form.control}
name="newPassword"
name='newPassword'
render={({ field }) => (
<FormItem>
<FormLabel>New Password</FormLabel>
<FormControl>
<Input
type="password"
type='password'
{...field}
placeholder="Enter new password"
placeholder='Enter new password'
/>
</FormControl>
<FormDescription>
@@ -163,23 +163,23 @@ export const ResetPasswordForm = ({ preloadedProvider }: ResetFormProps) => {
/>
<FormField
control={form.control}
name="confirmPassword"
name='confirmPassword'
render={({ field }) => (
<FormItem>
<FormLabel>Confirm New Password</FormLabel>
<FormControl>
<Input
type="password"
type='password'
{...field}
placeholder="Confirm new password"
placeholder='Confirm new password'
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end pt-2">
<SubmitButton disabled={loading} pendingText="Updating...">
<div className='flex justify-end pt-2'>
<SubmitButton disabled={loading} pendingText='Updating...'>
Update Password
</SubmitButton>
</div>

View File

@@ -39,12 +39,12 @@ export const SignOutForm = () => {
</CardHeader>
<CardContent>
<Button
variant="destructive"
className="w-full"
variant='destructive'
className='w-full'
onClick={handleSignOut}
disabled={isSigningOut}
>
<LogOut className="mr-2 h-4 w-4" />
<LogOut className='mr-2 h-4 w-4' />
{isSigningOut ? 'Signing Out...' : 'Sign Out'}
</Button>
</CardContent>

View File

@@ -43,9 +43,12 @@ const formSchema = z.object({
interface UserInfoFormProps {
preloadedUser: Preloaded<typeof api.auth.getUser>;
preloadedProvider: Preloaded<typeof api.auth.getUserProvider>;
};
}
export const UserInfoForm = ({ preloadedUser, preloadedProvider }: UserInfoFormProps) => {
export const UserInfoForm = ({
preloadedUser,
preloadedProvider,
}: UserInfoFormProps) => {
const user = usePreloadedQuery(preloadedUser);
const userProvider = usePreloadedQuery(preloadedProvider);
const [loading, setLoading] = useState(false);
@@ -106,16 +109,16 @@ export const UserInfoForm = ({ preloadedUser, preloadedProvider }: UserInfoFormP
<Form {...form}>
<form
onSubmit={form.handleSubmit(handleSubmit)}
className="space-y-4"
className='space-y-4'
>
<FormField
control={form.control}
name="name"
name='name'
render={({ field }) => (
<FormItem>
<FormLabel>Full Name</FormLabel>
<FormControl>
<Input {...field} placeholder="John Doe" />
<Input {...field} placeholder='John Doe' />
</FormControl>
<FormDescription>Your public display name</FormDescription>
<FormMessage />
@@ -125,27 +128,33 @@ export const UserInfoForm = ({ preloadedUser, preloadedProvider }: UserInfoFormP
<FormField
control={form.control}
name="email"
name='email'
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input
{...field}
type="email"
placeholder="john@example.com"
type='email'
placeholder='john@example.com'
disabled={userProvider !== 'email'}
/>
</FormControl>
<FormDescription>
Your email address for account notifications
</FormDescription>
{userProvider === 'email' ? (
<FormDescription>
Your email address for account notifications
</FormDescription>
) : (
<FormDescription>
Email is managed through your {userProvider} account
</FormDescription>
)}
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end pt-2">
<SubmitButton disabled={loading} pendingText="Saving...">
<div className='flex justify-end pt-2'>
<SubmitButton disabled={loading} pendingText='Saving...'>
Save Changes
</SubmitButton>
</div>

View File

@@ -1,5 +1,5 @@
import Link from 'next/link';
import { Kanit } from 'next/font/google';
import Link from 'next/link';
const kanitSans = Kanit({
subsets: ['latin'],
@@ -8,51 +8,53 @@ const kanitSans = Kanit({
export default function Footer() {
return (
<footer className="border-border/40 bg-muted/30 border-t">
<div className="container mx-auto px-4 py-12">
<div className="grid gap-8 md:grid-cols-4">
<footer className='border-border/40 bg-muted/30 border-t'>
<div className='container mx-auto px-4 py-12'>
<div className='grid gap-8 md:grid-cols-4'>
{/* Brand */}
<div className="md:col-span-2">
<h3 className={`mb-2 text-3xl font-bold ${kanitSans.className}`}>convex monorepo</h3>
<p className="text-muted-foreground text-sm">
A production-ready Turborepo starter with Next.js, Expo, and
a self-hosted Convex backend, including Convex Auth with a
custom useSend email provider to ensure everything can be
self-hosted. Built for developers who want complete control
without sacrificing ease of use.
<div className='md:col-span-2'>
<h3 className={`mb-2 text-3xl font-bold ${kanitSans.className}`}>
convex monorepo
</h3>
<p className='text-muted-foreground text-sm'>
A production-ready Turborepo starter with Next.js, Expo, and a
self-hosted Convex backend, including Convex Auth with a custom
useSend email provider to ensure everything can be self-hosted.
Built for developers who want complete control without sacrificing
ease of use.
</p>
</div>
{/* Links */}
<div>
<h4 className="mb-4 text-sm font-semibold">Resources</h4>
<ul className="space-y-2 text-sm">
<h4 className='mb-4 text-sm font-semibold'>Resources</h4>
<ul className='space-y-2 text-sm'>
<li>
<Link
href="https://git.gbrown.org/gib/convex-monorepo"
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground transition-colors"
href='https://git.gbrown.org/gib/convex-monorepo'
target='_blank'
rel='noopener noreferrer'
className='text-muted-foreground hover:text-foreground transition-colors'
>
Gitea Repository
</Link>
</li>
<li>
<Link
href="https://docs.convex.dev"
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground transition-colors"
href='https://docs.convex.dev'
target='_blank'
rel='noopener noreferrer'
className='text-muted-foreground hover:text-foreground transition-colors'
>
Convex Documentation
</Link>
</li>
<li>
<Link
href="https://turbo.build"
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground transition-colors"
href='https://turbo.build'
target='_blank'
rel='noopener noreferrer'
className='text-muted-foreground hover:text-foreground transition-colors'
>
Turborepo
</Link>
@@ -62,34 +64,34 @@ export default function Footer() {
{/* Tech */}
<div>
<h4 className="mb-4 text-sm font-semibold">Built With</h4>
<ul className="space-y-2 text-sm">
<h4 className='mb-4 text-sm font-semibold'>Built With</h4>
<ul className='space-y-2 text-sm'>
<li>
<Link
href="https://nextjs.org"
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground transition-colors"
href='https://nextjs.org'
target='_blank'
rel='noopener noreferrer'
className='text-muted-foreground hover:text-foreground transition-colors'
>
Next.js
</Link>
</li>
<li>
<Link
href="https://expo.dev"
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground transition-colors"
href='https://expo.dev'
target='_blank'
rel='noopener noreferrer'
className='text-muted-foreground hover:text-foreground transition-colors'
>
Expo
</Link>
</li>
<li>
<Link
href="https://ui.shadcn.com"
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground transition-colors"
href='https://ui.shadcn.com'
target='_blank'
rel='noopener noreferrer'
className='text-muted-foreground hover:text-foreground transition-colors'
>
shadcn/ui
</Link>
@@ -99,14 +101,14 @@ export default function Footer() {
</div>
{/* Bottom */}
<div className="border-border/40 text-muted-foreground mt-12 border-t pt-8 text-center text-sm">
<div className='border-border/40 text-muted-foreground mt-12 border-t pt-8 text-center text-sm'>
<p>
Built by{' '}
<Link
href="https://gbrown.org"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground font-medium transition-colors"
href='https://gbrown.org'
target='_blank'
rel='noopener noreferrer'
className='hover:text-foreground font-medium transition-colors'
>
Gib.
</Link>

View File

@@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation';
import { useAuthActions } from '@convex-dev/auth/react';
import { useConvexAuth, useQuery } from 'convex/react';
import type { Id } from '@gib/backend/convex/_generated/dataModel.js';
import { api } from '@gib/backend/convex/_generated/api.js';
import {
BasedAvatar,
@@ -24,26 +25,23 @@ export const AvatarDropdown = () => {
const user = useQuery(api.auth.getUser, {});
const currentImageUrl = useQuery(
api.files.getImageUrl,
user?.image ? { storageId: user.image as any } : 'skip',
user?.image ? { storageId: user.image as Id<'_storage'> } : 'skip',
);
if (isLoading) {
return (
<div className="flex items-center gap-2">
<div className="bg-muted h-8 w-16 animate-pulse rounded-md" />
<div className="bg-muted h-9 w-9 animate-pulse rounded-full" />
<div className='flex items-center gap-2'>
<div className='bg-muted h-8 w-16 animate-pulse rounded-md' />
<div className='bg-muted h-9 w-9 animate-pulse rounded-full' />
</div>
);
}
if (!isAuthenticated) {
return (
<div className="flex items-center gap-2">
<Button variant="ghost" size="sm" asChild>
<Link href="/sign-in">Sign In</Link>
</Button>
<Button size="sm" asChild>
<Link href="/sign-in">Get Started</Link>
<div className='flex items-center gap-2'>
<Button size='sm' asChild>
<Link href='/sign-in'>Sign In</Link>
</Button>
</div>
);
@@ -55,22 +53,22 @@ export const AvatarDropdown = () => {
<BasedAvatar
src={currentImageUrl}
fullName={user?.name}
className="h-9 w-9"
className='h-9 w-9'
fallbackProps={{ className: 'text-sm font-semibold' }}
userIconProps={{ size: 20 }}
/>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuContent align='end'>
{(user?.name ?? user?.email) && (
<>
<DropdownMenuLabel className="text-center font-bold">
<DropdownMenuLabel className='text-center font-bold'>
{user.name?.trim() ?? user.email?.trim()}
</DropdownMenuLabel>
<DropdownMenuSeparator />
</>
)}
<DropdownMenuItem asChild>
<Link href="/profile" className="w-full cursor-pointer">
<Link href='/profile' className='w-full cursor-pointer'>
Edit Profile
</Link>
</DropdownMenuItem>
@@ -82,7 +80,7 @@ export const AvatarDropdown = () => {
router.push('/');
})
}
className="w-full cursor-pointer"
className='w-full cursor-pointer'
>
Sign Out
</button>

View File

@@ -7,7 +7,7 @@ import { AvatarDropdown } from './AvatarDropdown';
export const Controls = (themeToggleProps?: ThemeToggleProps) => {
return (
<div className="flex items-center gap-3">
<div className='flex items-center gap-3'>
<ThemeToggle
size={1.1}
buttonProps={{

View File

@@ -1,65 +1,64 @@
'use client';
import type { ComponentProps } from 'react';
import { Kanit } from 'next/font/google';
import Image from 'next/image';
import Link from 'next/link';
import { Kanit } from 'next/font/google';
import { Coffee, Server, Wrench } from 'lucide-react';
import { Controls } from './controls';
const kanitSans = Kanit({
subsets: ['latin'],
weight: ['400', '500', '600', '700'],
});
import { Controls } from './controls';
export default function Header(headerProps: ComponentProps<'header'>) {
return (
<header
className="border-border/40 bg-background/95 supports-backdrop-filter:bg-background/60 sticky top-0 z-50 w-full border-b backdrop-blur"
className='border-border/40 bg-background/95 supports-backdrop-filter:bg-background/60 sticky top-0 z-50 w-full border-b backdrop-blur'
{...headerProps}
>
<div className="container mx-auto flex h-16 items-center justify-between px-4 md:px-6">
<div className='container mx-auto flex h-16 items-center justify-between px-4 md:px-6'>
{/* Logo */}
<Link
href="/"
className="flex items-center gap-2 transition-opacity hover:opacity-80 to-accent-foreground bg-linear-to-r from-[#281A65] via-[#363354] bg-clip-text text-transparent dark:from-[#bec8e6] dark:via-[#F0EEE4] dark:to-[#FFF8E7]"
href='/'
className='to-accent-foreground flex items-center gap-2 bg-linear-to-r from-[#281A65] via-[#363354] bg-clip-text text-transparent transition-opacity hover:opacity-80 dark:from-[#bec8e6] dark:via-[#F0EEE4] dark:to-[#FFF8E7]'
>
<Image
src="/misc/convex/convex-symbol-white.svg"
alt="Convex Monorepo"
src='/misc/convex/convex-symbol-white.svg'
alt='Convex Monorepo'
width={50}
height={50}
className='invert dark:invert-0'
/>
<span className={`hidden lg:text-5xl lg:inline mb-3 font-extrabold ${kanitSans.className}`}>
<span
className={`mb-3 hidden font-extrabold lg:inline lg:text-5xl ${kanitSans.className}`}
>
convex monorepo
</span>
</Link>
{/* Navigation */}
<nav className="hidden items-center gap-6 text-base font-medium md:flex">
<nav className='hidden items-center gap-6 text-base font-medium md:flex'>
<Link
href="/#features"
className="text-foreground/60 hover:text-foreground transition-colors flex gap-2 items-center"
href='/#features'
className='text-foreground/60 hover:text-foreground flex items-center gap-2 transition-colors'
>
<Wrench width={18} height={18} />
Features
</Link>
<Link
href="/#tech-stack"
className="text-foreground/60 hover:text-foreground transition-colors flex gap-2 items-center"
href='/#tech-stack'
className='text-foreground/60 hover:text-foreground flex items-center gap-2 transition-colors'
>
<Server width={18} height={18} />
Stack
</Link>
<Link
href="https://git.gbrown.org/gib/convex-monorepo"
target="_blank"
rel="noopener noreferrer"
className="text-foreground/60 hover:text-foreground transition-colors flex gap-2 items-center"
href='https://git.gbrown.org/gib/convex-monorepo'
target='_blank'
rel='noopener noreferrer'
className='text-foreground/60 hover:text-foreground flex items-center gap-2 transition-colors'
>
<Coffee width={20} height={20} />
Repository
</Link>