create-ej-app 4.0.0 → 6.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.
- package/LICENSE +1 -1
- package/cli.js +4 -0
- package/cli.ts +5 -0
- package/package.json +4 -4
- package/template/new/.env.example +13 -0
- package/template/new/components.json +22 -0
- package/template/new/drizzle.config.ts +10 -0
- package/template/new/eslint.config.mjs +18 -0
- package/template/new/next.config.ts +14 -0
- package/template/new/package.json +81 -0
- package/template/new/postcss.config.mjs +5 -0
- package/template/new/prettier.config.mjs +11 -0
- package/template/new/public/placeholder.svg +1 -0
- package/template/new/src/app/(auth)/login/email-login-form.tsx +97 -0
- package/template/new/src/app/(auth)/login/page.tsx +72 -0
- package/template/new/src/app/(auth)/login/social-login-buttons.tsx +69 -0
- package/template/new/src/app/api/auth/[...all]/route.ts +4 -0
- package/template/new/src/app/favicon.ico +0 -0
- package/template/new/src/app/globals.css +128 -0
- package/template/new/src/app/layout.tsx +50 -0
- package/template/new/src/app/page.tsx +3 -0
- package/template/new/src/components/loader.tsx +25 -0
- package/template/new/src/components/react-query-provider.tsx +12 -0
- package/template/new/src/components/screen-size.tsx +40 -0
- package/template/new/src/components/theme-toggle.tsx +22 -0
- package/template/new/src/components/ui/button.tsx +60 -0
- package/template/new/src/components/ui/card.tsx +92 -0
- package/template/new/src/components/ui/form.tsx +167 -0
- package/template/new/src/components/ui/input.tsx +21 -0
- package/template/new/src/components/ui/label.tsx +24 -0
- package/template/new/src/components/ui/separator.tsx +28 -0
- package/template/new/src/components/ui/sonner.tsx +40 -0
- package/template/new/src/db/index.ts +7 -0
- package/template/new/src/db/schema.ts +74 -0
- package/template/new/src/env.ts +42 -0
- package/template/new/src/lib/auth-client.ts +18 -0
- package/template/new/src/lib/auth.ts +67 -0
- package/template/new/src/lib/cn.ts +4 -0
- package/template/new/src/lib/delay.ts +2 -0
- package/template/new/src/lib/safe-action.ts +15 -0
- package/template/new/src/lib/siteConfig.ts +4 -0
- package/template/new/src/lib/toast-message.ts +15 -0
- package/template/new/tsconfig.json +49 -0
package/LICENSE
CHANGED
package/cli.js
CHANGED
|
@@ -49,6 +49,10 @@ commander_1.program.action(() => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
49
49
|
{
|
|
50
50
|
name: 'prisma-advanced',
|
|
51
51
|
message: 'Advanced setup with User Types and ACLs (roles & permissions)'
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'new',
|
|
55
|
+
message: 'Advanced new setup with next 16, Better Auth and Drizzle ORM'
|
|
52
56
|
}
|
|
53
57
|
]
|
|
54
58
|
}
|
package/cli.ts
CHANGED
|
@@ -45,6 +45,11 @@ program.action(async () => {
|
|
|
45
45
|
name: 'prisma-advanced',
|
|
46
46
|
message:
|
|
47
47
|
'Advanced setup with User Types and ACLs (roles & permissions)'
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'new',
|
|
51
|
+
message:
|
|
52
|
+
'Advanced new setup with next 16, Better Auth and Drizzle ORM'
|
|
48
53
|
}
|
|
49
54
|
]
|
|
50
55
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-ej-app",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.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": "^
|
|
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": "^
|
|
39
|
-
"typescript": "^5.
|
|
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,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,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'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
|
+
}
|
|
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
|
+
}
|