create-ej-app 4.0.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/LICENSE +1 -1
  2. package/package.json +4 -4
  3. package/template/new/.env.example +13 -0
  4. package/template/new/components.json +22 -0
  5. package/template/new/drizzle.config.ts +10 -0
  6. package/template/new/eslint.config.mjs +18 -0
  7. package/template/new/next.config.ts +14 -0
  8. package/template/new/package.json +81 -0
  9. package/template/new/postcss.config.mjs +5 -0
  10. package/template/new/prettier.config.mjs +11 -0
  11. package/template/new/public/placeholder.svg +1 -0
  12. package/template/new/src/app/(auth)/login/email-login-form.tsx +97 -0
  13. package/template/new/src/app/(auth)/login/page.tsx +72 -0
  14. package/template/new/src/app/(auth)/login/social-login-buttons.tsx +69 -0
  15. package/template/new/src/app/api/auth/[...all]/route.ts +4 -0
  16. package/template/new/src/app/favicon.ico +0 -0
  17. package/template/new/src/app/globals.css +128 -0
  18. package/template/new/src/app/layout.tsx +50 -0
  19. package/template/new/src/app/page.tsx +3 -0
  20. package/template/new/src/components/loader.tsx +25 -0
  21. package/template/new/src/components/react-query-provider.tsx +12 -0
  22. package/template/new/src/components/screen-size.tsx +40 -0
  23. package/template/new/src/components/theme-toggle.tsx +22 -0
  24. package/template/new/src/components/ui/button.tsx +60 -0
  25. package/template/new/src/components/ui/card.tsx +92 -0
  26. package/template/new/src/components/ui/form.tsx +167 -0
  27. package/template/new/src/components/ui/input.tsx +21 -0
  28. package/template/new/src/components/ui/label.tsx +24 -0
  29. package/template/new/src/components/ui/separator.tsx +28 -0
  30. package/template/new/src/components/ui/sonner.tsx +40 -0
  31. package/template/new/src/db/index.ts +7 -0
  32. package/template/new/src/db/schema.ts +74 -0
  33. package/template/new/src/env.ts +42 -0
  34. package/template/new/src/lib/auth-client.ts +18 -0
  35. package/template/new/src/lib/auth.ts +67 -0
  36. package/template/new/src/lib/cn.ts +4 -0
  37. package/template/new/src/lib/delay.ts +2 -0
  38. package/template/new/src/lib/safe-action.ts +15 -0
  39. package/template/new/src/lib/siteConfig.ts +4 -0
  40. package/template/new/src/lib/toast-message.ts +15 -0
  41. package/template/new/tsconfig.json +49 -0
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 Ekansh Jain
3
+ Copyright (c) 2025 Ekansh Jain
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ej-app",
3
- "version": "4.0.0",
3
+ "version": "5.0.0",
4
4
  "description": "Bootstrap a next.js app with my preferred setup",
5
5
  "main": "cli.js",
6
6
  "scripts": {
@@ -30,12 +30,12 @@
30
30
  },
31
31
  "homepage": "https://github.com/ejekanshjain/create-ej-app#readme",
32
32
  "dependencies": {
33
- "commander": "^12.1.0",
33
+ "commander": "^14.0.2",
34
34
  "copy-dir": "^1.3.0",
35
35
  "enquirer": "^2.4.1"
36
36
  },
37
37
  "devDependencies": {
38
- "@types/node": "^20.14.9",
39
- "typescript": "^5.5.2"
38
+ "@types/node": "^24.10.0",
39
+ "typescript": "^5.9.3"
40
40
  }
41
41
  }
@@ -0,0 +1,13 @@
1
+ NODE_ENV="production"
2
+ DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"
3
+ BETTER_AUTH_URL="http://localhost:3000"
4
+ BETTER_AUTH_SECRET="secret"
5
+ BETTER_AUTH_GITHUB_ID="github_id"
6
+ BETTER_AUTH_GITHUB_SECRET="github_secret"
7
+ BETTER_AUTH_GOOGLE_ID="google_id"
8
+ BETTER_AUTH_GOOGLE_SECRET="google_secret"
9
+ EMAIL_SERVER_USER="username"
10
+ EMAIL_SERVER_PASSWORD="password"
11
+ EMAIL_SERVER_HOST="smtp.example.com"
12
+ EMAIL_SERVER_PORT="465"
13
+ EMAIL_FROM="no-reply@example.com"
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/app/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "iconLibrary": "lucide",
14
+ "aliases": {
15
+ "components": "~/components",
16
+ "utils": "~/lib/cn",
17
+ "ui": "~/components/ui",
18
+ "lib": "~/lib",
19
+ "hooks": "~/hooks"
20
+ },
21
+ "registries": {}
22
+ }
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'drizzle-kit'
2
+ import { env } from '~/env'
3
+
4
+ export default defineConfig({
5
+ schema: './src/db/schema.ts',
6
+ dialect: 'postgresql',
7
+ dbCredentials: {
8
+ url: env.DATABASE_URL
9
+ }
10
+ })
@@ -0,0 +1,18 @@
1
+ // @ts-ignore
2
+ import nextVitals from 'eslint-config-next/core-web-vitals'
3
+ // import nextTs from 'eslint-config-next/typescript'
4
+ import { defineConfig, globalIgnores } from 'eslint/config'
5
+
6
+ const eslintConfig = defineConfig([
7
+ ...nextVitals,
8
+ // ...nextTs,
9
+ globalIgnores([
10
+ 'node_modules/**',
11
+ '.next/**',
12
+ 'out/**',
13
+ 'build/**',
14
+ 'next-env.d.ts'
15
+ ])
16
+ ])
17
+
18
+ export default eslintConfig
@@ -0,0 +1,14 @@
1
+ import bundleAnalyzer from '@next/bundle-analyzer'
2
+ import type { NextConfig } from 'next'
3
+ import { withWorkflow } from 'workflow/next'
4
+ import { env } from '~/env'
5
+
6
+ const nextConfig: NextConfig = {
7
+ reactCompiler: true
8
+ }
9
+
10
+ const withBundleAnalyzer = bundleAnalyzer({
11
+ enabled: env.ANALYZE === 'true'
12
+ })
13
+
14
+ export default withWorkflow(withBundleAnalyzer(nextConfig))
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "my-app",
3
+ "description": "A Legendary Next.js App",
4
+ "version": "1.0.0",
5
+ "private": true,
6
+ "scripts": {
7
+ "dev": "next dev",
8
+ "build": "next build",
9
+ "analyze": "ANALYZE=true next build --webpack",
10
+ "start": "next start",
11
+ "lint": "eslint",
12
+ "format": "prettier --write \"./src/**/*.{js,cjs,mjs,jsx,ts,tsx}\"",
13
+ "db:push": "drizzle-kit push",
14
+ "db:studio": "drizzle-kit studio",
15
+ "gen:auth": "bunx @better-auth/cli generate"
16
+ },
17
+ "dependencies": {
18
+ "@hookform/resolvers": "5.2.2",
19
+ "@next/third-parties": "16.0.1",
20
+ "@paralleldrive/cuid2": "3.0.4",
21
+ "@radix-ui/react-avatar": "1.1.11",
22
+ "@radix-ui/react-checkbox": "1.3.3",
23
+ "@radix-ui/react-collapsible": "1.1.12",
24
+ "@radix-ui/react-dialog": "1.1.15",
25
+ "@radix-ui/react-dropdown-menu": "2.1.16",
26
+ "@radix-ui/react-label": "2.1.8",
27
+ "@radix-ui/react-popover": "1.1.15",
28
+ "@radix-ui/react-radio-group": "1.3.8",
29
+ "@radix-ui/react-select": "2.2.6",
30
+ "@radix-ui/react-separator": "1.1.8",
31
+ "@radix-ui/react-slider": "1.3.6",
32
+ "@radix-ui/react-slot": "1.2.4",
33
+ "@t3-oss/env-nextjs": "0.13.8",
34
+ "@tanstack/react-query": "5.90.7",
35
+ "better-auth": "1.3.34",
36
+ "class-variance-authority": "0.7.1",
37
+ "clsx": "2.1.1",
38
+ "date-fns": "4.1.0",
39
+ "decimal.js": "10.6.0",
40
+ "drizzle-orm": "0.44.7",
41
+ "lucide-react": "0.552.0",
42
+ "next": "16.0.1",
43
+ "next-safe-action": "8.0.11",
44
+ "next-themes": "0.4.6",
45
+ "nextjs-toploader": "3.9.17",
46
+ "nodemailer": "7.0.10",
47
+ "postgres": "3.4.7",
48
+ "react": "19.2.0",
49
+ "react-day-picker": "9.11.1",
50
+ "react-dom": "19.2.0",
51
+ "react-hook-form": "7.66.0",
52
+ "sonner": "2.0.7",
53
+ "tailwind-merge": "3.3.1",
54
+ "tw-animate-css": "1.4.0",
55
+ "workflow": "4.0.1-beta.11",
56
+ "zod": "4.1.12"
57
+ },
58
+ "devDependencies": {
59
+ "@next/bundle-analyzer": "16.0.1",
60
+ "@tailwindcss/postcss": "4.1.16",
61
+ "@types/bun": "1.3.1",
62
+ "@types/react": "19.2.2",
63
+ "@types/react-dom": "19.2.2",
64
+ "babel-plugin-react-compiler": "1.0.0",
65
+ "drizzle-kit": "0.31.6",
66
+ "eslint": "9.39.1",
67
+ "eslint-config-next": "16.0.1",
68
+ "prettier": "3.6.2",
69
+ "prettier-plugin-organize-imports": "4.3.0",
70
+ "prettier-plugin-tailwindcss": "0.7.1",
71
+ "tailwindcss": "4.1.16",
72
+ "typescript": "5.9.3"
73
+ },
74
+ "trustedDependencies": [
75
+ "@swc/core",
76
+ "@tailwindcss/oxide",
77
+ "esbuild",
78
+ "sharp",
79
+ "unrs-resolver"
80
+ ]
81
+ }
@@ -0,0 +1,5 @@
1
+ const config = {
2
+ plugins: ['@tailwindcss/postcss']
3
+ }
4
+
5
+ export default config
@@ -0,0 +1,11 @@
1
+ /** @type {import('prettier').Config & import('prettier-plugin-tailwindcss').PluginOptions} */
2
+ const config = {
3
+ plugins: ['prettier-plugin-organize-imports', 'prettier-plugin-tailwindcss'],
4
+ arrowParens: 'avoid',
5
+ trailingComma: 'none',
6
+ semi: false,
7
+ singleQuote: true,
8
+ tabWidth: 2
9
+ }
10
+
11
+ export default config
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>
@@ -0,0 +1,97 @@
1
+ 'use client'
2
+
3
+ import { zodResolver } from '@hookform/resolvers/zod'
4
+ import { Mail } from 'lucide-react'
5
+ import { useState } from 'react'
6
+ import { useForm } from 'react-hook-form'
7
+ import { z } from 'zod'
8
+ import { Button } from '~/components/ui/button'
9
+ import {
10
+ Form,
11
+ FormControl,
12
+ FormField,
13
+ FormItem,
14
+ FormLabel,
15
+ FormMessage
16
+ } from '~/components/ui/form'
17
+ import { Input } from '~/components/ui/input'
18
+ import { signIn } from '~/lib/auth-client'
19
+ import { toastSuccessMessage } from '~/lib/toast-message'
20
+
21
+ const emailLoginFormSchema = z.object({
22
+ email: z.email()
23
+ })
24
+
25
+ export const EmailLoginForm = () => {
26
+ const form = useForm<z.infer<typeof emailLoginFormSchema>>({
27
+ resolver: zodResolver(emailLoginFormSchema),
28
+ defaultValues: {
29
+ email: ''
30
+ }
31
+ })
32
+
33
+ const [isLoading, setIsLoading] = useState(false)
34
+ const [isSent, setIsSent] = useState('')
35
+
36
+ async function onSubmit(values: z.infer<typeof emailLoginFormSchema>) {
37
+ const email = values.email
38
+
39
+ if (!email) return
40
+
41
+ setIsLoading(true)
42
+ try {
43
+ const res = await signIn.magicLink({
44
+ email,
45
+ name: ''
46
+ })
47
+ if (res?.error) throw res.error
48
+ setIsSent(email)
49
+ toastSuccessMessage('Magic link sent! Check your email.')
50
+ } catch (err) {
51
+ console.error('Error sending magic link:', err)
52
+ } finally {
53
+ setIsLoading(false)
54
+ }
55
+ }
56
+
57
+ if (isSent) {
58
+ return (
59
+ <div className="rounded-lg border p-4 text-center">
60
+ <Mail className="text-primary mx-auto mb-2 size-6" />
61
+ <h3 className="mb-1 text-sm font-medium">Check your email</h3>
62
+ <p className="text-muted-foreground text-xs">
63
+ We&apos;ve sent a magic link to <strong>{isSent}</strong>
64
+ </p>
65
+ </div>
66
+ )
67
+ }
68
+
69
+ return (
70
+ <Form {...form}>
71
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
72
+ <FormField
73
+ control={form.control}
74
+ name="email"
75
+ render={({ field }) => (
76
+ <FormItem>
77
+ <FormLabel>Email</FormLabel>
78
+ <FormControl>
79
+ <Input
80
+ placeholder="name@example.com"
81
+ autoCapitalize="off"
82
+ autoComplete="email"
83
+ autoFocus
84
+ {...field}
85
+ />
86
+ </FormControl>
87
+ <FormMessage />
88
+ </FormItem>
89
+ )}
90
+ />
91
+ <Button type="submit" className="w-full" disabled={isLoading}>
92
+ {isLoading ? 'Sending...' : 'Send Magic Link'}
93
+ </Button>
94
+ </form>
95
+ </Form>
96
+ )
97
+ }
@@ -0,0 +1,72 @@
1
+ import { ShoppingBag } from 'lucide-react'
2
+ import Link from 'next/link'
3
+ import { redirect } from 'next/navigation'
4
+ import {
5
+ Card,
6
+ CardContent,
7
+ CardDescription,
8
+ CardFooter,
9
+ CardHeader,
10
+ CardTitle
11
+ } from '~/components/ui/card'
12
+ import { Separator } from '~/components/ui/separator'
13
+ import { getAuthSession } from '~/lib/auth'
14
+ import { EmailLoginForm } from './email-login-form'
15
+ import { SocialLoginButtons } from './social-login-buttons'
16
+
17
+ export default async function LoginPage() {
18
+ const session = await getAuthSession()
19
+
20
+ if (session?.user) redirect('/')
21
+
22
+ return (
23
+ <div className="flex min-h-screen items-center justify-center px-4 py-12 sm:px-6 lg:px-8">
24
+ <Card className="w-full max-w-md">
25
+ <CardHeader className="space-y-1 text-center">
26
+ <div className="bg-primary/10 mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full">
27
+ <ShoppingBag className="text-primary size-6" />
28
+ </div>
29
+ <CardTitle className="text-2xl font-bold">Welcome back</CardTitle>
30
+ <CardDescription className="text-muted-foreground text-sm">
31
+ Sign in to your account to continue
32
+ </CardDescription>
33
+ </CardHeader>
34
+ <CardContent className="space-y-4">
35
+ <div className="border-muted-foreground/50 bg-muted/50 rounded-lg border border-dashed p-4">
36
+ <p className="text-muted-foreground text-sm">
37
+ <strong>New to our site?</strong> No need to create a separate
38
+ account. Simply use one of the options below to both sign up and
39
+ log in.
40
+ </p>
41
+ </div>
42
+
43
+ <EmailLoginForm />
44
+
45
+ <div className="relative">
46
+ <div className="absolute inset-0 flex items-center">
47
+ <span className="w-full border-t" />
48
+ </div>
49
+ <div className="relative flex justify-center text-xs uppercase">
50
+ <span className="bg-background text-muted-foreground px-2">
51
+ Or continue with
52
+ </span>
53
+ </div>
54
+ </div>
55
+
56
+ <SocialLoginButtons />
57
+ </CardContent>
58
+ <CardFooter className="flex flex-col space-y-4">
59
+ <Separator className="my-2" />
60
+ <div className="text-muted-foreground text-center text-sm">
61
+ <Link
62
+ href="/"
63
+ className="text-primary underline-offset-4 hover:underline"
64
+ >
65
+ Return to home page
66
+ </Link>
67
+ </div>
68
+ </CardFooter>
69
+ </Card>
70
+ </div>
71
+ )
72
+ }
@@ -0,0 +1,69 @@
1
+ 'use client'
2
+
3
+ import { Button } from '~/components/ui/button'
4
+ import { getLastUsedLoginMethod, signIn } from '~/lib/auth-client'
5
+
6
+ export const SocialLoginButtons = () => {
7
+ const lastUsedLoginMethod = getLastUsedLoginMethod()
8
+
9
+ return (
10
+ <div className="flex flex-col space-y-2">
11
+ <Button
12
+ variant="outline"
13
+ onClick={async () => {
14
+ await signIn.social({
15
+ provider: 'google'
16
+ })
17
+ }}
18
+ >
19
+ <svg
20
+ viewBox="0 0 24 24"
21
+ width="16"
22
+ height="16"
23
+ xmlns="http://www.w3.org/2000/svg"
24
+ >
25
+ <g transform="matrix(1, 0, 0, 1, 27.009001, -39.238998)">
26
+ <path
27
+ fill="#4285F4"
28
+ d="M -3.264 51.509 C -3.264 50.719 -3.334 49.969 -3.454 49.239 L -14.754 49.239 L -14.754 53.749 L -8.284 53.749 C -8.574 55.229 -9.424 56.479 -10.684 57.329 L -10.684 60.329 L -6.824 60.329 C -4.564 58.239 -3.264 55.159 -3.264 51.509 Z"
29
+ />
30
+ <path
31
+ fill="#34A853"
32
+ d="M -14.754 63.239 C -11.514 63.239 -8.804 62.159 -6.824 60.329 L -10.684 57.329 C -11.764 58.049 -13.134 58.489 -14.754 58.489 C -17.884 58.489 -20.534 56.379 -21.484 53.529 L -25.464 53.529 L -25.464 56.619 C -23.494 60.539 -19.444 63.239 -14.754 63.239 Z"
33
+ />
34
+ <path
35
+ fill="#FBBC05"
36
+ d="M -21.484 53.529 C -21.734 52.809 -21.864 52.039 -21.864 51.239 C -21.864 50.439 -21.724 49.669 -21.484 48.949 L -21.484 45.859 L -25.464 45.859 C -26.284 47.479 -26.754 49.299 -26.754 51.239 C -26.754 53.179 -26.284 54.999 -25.464 56.619 L -21.484 53.529 Z"
37
+ />
38
+ <path
39
+ fill="#EA4335"
40
+ d="M -14.754 43.989 C -12.984 43.989 -11.404 44.599 -10.154 45.789 L -6.734 42.369 C -8.804 40.429 -11.514 39.239 -14.754 39.239 C -19.444 39.239 -23.494 41.939 -25.464 45.859 L -21.484 48.949 C -20.534 46.099 -17.884 43.989 -14.754 43.989 Z"
41
+ />
42
+ </g>
43
+ </svg>
44
+ Continue with Google
45
+ {lastUsedLoginMethod === 'google' ? ' (last used)' : ''}
46
+ </Button>
47
+
48
+ <Button
49
+ variant="outline"
50
+ onClick={async () => {
51
+ await signIn.social({
52
+ provider: 'github'
53
+ })
54
+ }}
55
+ >
56
+ <svg
57
+ viewBox="0 0 24 24"
58
+ width="16"
59
+ height="16"
60
+ xmlns="http://www.w3.org/2000/svg"
61
+ >
62
+ <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
63
+ </svg>
64
+ Continue with GitHub
65
+ {lastUsedLoginMethod === 'github' ? ' (last used)' : ''}
66
+ </Button>
67
+ </div>
68
+ )
69
+ }
@@ -0,0 +1,4 @@
1
+ import { toNextJsHandler } from 'better-auth/next-js'
2
+ import { auth } from '~/lib/auth'
3
+
4
+ export const { GET, POST } = toNextJsHandler(auth.handler)
Binary file
@@ -0,0 +1,128 @@
1
+ @import 'tailwindcss';
2
+ @import 'tw-animate-css';
3
+
4
+ @custom-variant dark (&:is(.dark *));
5
+
6
+ @theme inline {
7
+ --radius-sm: calc(var(--radius) - 4px);
8
+ --radius-md: calc(var(--radius) - 2px);
9
+ --radius-lg: var(--radius);
10
+ --radius-xl: calc(var(--radius) + 4px);
11
+ --color-background: var(--background);
12
+ --color-foreground: var(--foreground);
13
+ --color-card: var(--card);
14
+ --color-card-foreground: var(--card-foreground);
15
+ --color-popover: var(--popover);
16
+ --color-popover-foreground: var(--popover-foreground);
17
+ --color-primary: var(--primary);
18
+ --color-primary-foreground: var(--primary-foreground);
19
+ --color-secondary: var(--secondary);
20
+ --color-secondary-foreground: var(--secondary-foreground);
21
+ --color-muted: var(--muted);
22
+ --color-muted-foreground: var(--muted-foreground);
23
+ --color-accent: var(--accent);
24
+ --color-accent-foreground: var(--accent-foreground);
25
+ --color-destructive: var(--destructive);
26
+ --color-border: var(--border);
27
+ --color-input: var(--input);
28
+ --color-ring: var(--ring);
29
+ --color-chart-1: var(--chart-1);
30
+ --color-chart-2: var(--chart-2);
31
+ --color-chart-3: var(--chart-3);
32
+ --color-chart-4: var(--chart-4);
33
+ --color-chart-5: var(--chart-5);
34
+ --color-sidebar: var(--sidebar);
35
+ --color-sidebar-foreground: var(--sidebar-foreground);
36
+ --color-sidebar-primary: var(--sidebar-primary);
37
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
38
+ --color-sidebar-accent: var(--sidebar-accent);
39
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
40
+ --color-sidebar-border: var(--sidebar-border);
41
+ --color-sidebar-ring: var(--sidebar-ring);
42
+ }
43
+
44
+ :root {
45
+ --radius: 0.625rem;
46
+ --background: oklch(1 0 0);
47
+ --foreground: oklch(0.145 0 0);
48
+ --card: oklch(1 0 0);
49
+ --card-foreground: oklch(0.145 0 0);
50
+ --popover: oklch(1 0 0);
51
+ --popover-foreground: oklch(0.145 0 0);
52
+ --primary: oklch(0.205 0 0);
53
+ --primary-foreground: oklch(0.985 0 0);
54
+ --secondary: oklch(0.97 0 0);
55
+ --secondary-foreground: oklch(0.205 0 0);
56
+ --muted: oklch(0.97 0 0);
57
+ --muted-foreground: oklch(0.556 0 0);
58
+ --accent: oklch(0.97 0 0);
59
+ --accent-foreground: oklch(0.205 0 0);
60
+ --destructive: oklch(0.577 0.245 27.325);
61
+ --border: oklch(0.922 0 0);
62
+ --input: oklch(0.922 0 0);
63
+ --ring: oklch(0.708 0 0);
64
+ --chart-1: oklch(0.646 0.222 41.116);
65
+ --chart-2: oklch(0.6 0.118 184.704);
66
+ --chart-3: oklch(0.398 0.07 227.392);
67
+ --chart-4: oklch(0.828 0.189 84.429);
68
+ --chart-5: oklch(0.769 0.188 70.08);
69
+ --sidebar: oklch(0.985 0 0);
70
+ --sidebar-foreground: oklch(0.145 0 0);
71
+ --sidebar-primary: oklch(0.205 0 0);
72
+ --sidebar-primary-foreground: oklch(0.985 0 0);
73
+ --sidebar-accent: oklch(0.97 0 0);
74
+ --sidebar-accent-foreground: oklch(0.205 0 0);
75
+ --sidebar-border: oklch(0.922 0 0);
76
+ --sidebar-ring: oklch(0.708 0 0);
77
+ }
78
+
79
+ .dark {
80
+ --background: oklch(0.145 0 0);
81
+ --foreground: oklch(0.985 0 0);
82
+ --card: oklch(0.205 0 0);
83
+ --card-foreground: oklch(0.985 0 0);
84
+ --popover: oklch(0.205 0 0);
85
+ --popover-foreground: oklch(0.985 0 0);
86
+ --primary: oklch(0.922 0 0);
87
+ --primary-foreground: oklch(0.205 0 0);
88
+ --secondary: oklch(0.269 0 0);
89
+ --secondary-foreground: oklch(0.985 0 0);
90
+ --muted: oklch(0.269 0 0);
91
+ --muted-foreground: oklch(0.708 0 0);
92
+ --accent: oklch(0.269 0 0);
93
+ --accent-foreground: oklch(0.985 0 0);
94
+ --destructive: oklch(0.704 0.191 22.216);
95
+ --border: oklch(1 0 0 / 10%);
96
+ --input: oklch(1 0 0 / 15%);
97
+ --ring: oklch(0.556 0 0);
98
+ --chart-1: oklch(0.488 0.243 264.376);
99
+ --chart-2: oklch(0.696 0.17 162.48);
100
+ --chart-3: oklch(0.769 0.188 70.08);
101
+ --chart-4: oklch(0.627 0.265 303.9);
102
+ --chart-5: oklch(0.645 0.246 16.439);
103
+ --sidebar: oklch(0.205 0 0);
104
+ --sidebar-foreground: oklch(0.985 0 0);
105
+ --sidebar-primary: oklch(0.488 0.243 264.376);
106
+ --sidebar-primary-foreground: oklch(0.985 0 0);
107
+ --sidebar-accent: oklch(0.269 0 0);
108
+ --sidebar-accent-foreground: oklch(0.985 0 0);
109
+ --sidebar-border: oklch(1 0 0 / 10%);
110
+ --sidebar-ring: oklch(0.556 0 0);
111
+ }
112
+
113
+ @layer base {
114
+ * {
115
+ @apply border-border outline-ring/50;
116
+ }
117
+ body {
118
+ @apply text-foreground bg-background min-h-screen font-sans antialiased;
119
+ }
120
+ button:not([disabled]),
121
+ [role='button']:not([disabled]) {
122
+ cursor: pointer;
123
+ }
124
+ }
125
+
126
+ @utility container {
127
+ @apply mx-auto p-4;
128
+ }
@@ -0,0 +1,50 @@
1
+ import type { Metadata } from 'next'
2
+ import { ThemeProvider } from 'next-themes'
3
+ import { Geist, Geist_Mono } from 'next/font/google'
4
+ import { Loader } from '~/components/loader'
5
+ import { ReactQueryProvider } from '~/components/react-query-provider'
6
+ import { ScreenSize } from '~/components/screen-size'
7
+ import { Toaster } from '~/components/ui/sonner'
8
+ import { env } from '~/env'
9
+ import { siteConfig } from '~/lib/siteConfig'
10
+ import './globals.css'
11
+
12
+ const geistSans = Geist({
13
+ variable: '--font-geist-sans',
14
+ subsets: ['latin']
15
+ })
16
+
17
+ const geistMono = Geist_Mono({
18
+ variable: '--font-geist-mono',
19
+ subsets: ['latin']
20
+ })
21
+
22
+ export const metadata: Metadata = {
23
+ title: siteConfig.name,
24
+ description: siteConfig.description
25
+ }
26
+
27
+ export default function RootLayout({
28
+ children
29
+ }: Readonly<{
30
+ children: React.ReactNode
31
+ }>) {
32
+ return (
33
+ <html lang="en" suppressHydrationWarning>
34
+ <body className={`${geistSans.variable} ${geistMono.variable}`}>
35
+ <ReactQueryProvider>
36
+ <ThemeProvider
37
+ attribute="class"
38
+ defaultTheme="light"
39
+ forcedTheme="light"
40
+ >
41
+ <Loader />
42
+ {children}
43
+ {env.NODE_ENV === 'development' ? <ScreenSize /> : null}
44
+ <Toaster />
45
+ </ThemeProvider>
46
+ </ReactQueryProvider>
47
+ </body>
48
+ </html>
49
+ )
50
+ }
@@ -0,0 +1,3 @@
1
+ export default async function HomePage() {
2
+ return <div>Hello, World!</div>
3
+ }