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

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>