@authdog/react-elements 0.0.12 → 0.0.14
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/.turbo/turbo-build.log +47 -23
- package/CHANGELOG.md +12 -0
- package/dist/components/ui/alert.js +3 -0
- package/dist/components/ui/alert.js.map +1 -0
- package/dist/components/ui/alert.mjs +3 -0
- package/dist/components/ui/alert.mjs.map +1 -0
- package/dist/components/ui/badge.js +3 -0
- package/dist/components/ui/badge.js.map +1 -0
- package/dist/components/ui/badge.mjs +3 -0
- package/dist/components/ui/badge.mjs.map +1 -0
- package/dist/components/ui/card.js +3 -0
- package/dist/components/ui/card.js.map +1 -0
- package/dist/components/ui/card.mjs +3 -0
- package/dist/components/ui/card.mjs.map +1 -0
- package/dist/components/ui/input.js +3 -0
- package/dist/components/ui/input.js.map +1 -0
- package/dist/components/ui/input.mjs +3 -0
- package/dist/components/ui/input.mjs.map +1 -0
- package/dist/components/ui/label.js +3 -0
- package/dist/components/ui/label.js.map +1 -0
- package/dist/components/ui/label.mjs +3 -0
- package/dist/components/ui/label.mjs.map +1 -0
- package/dist/components/ui/separator.js +3 -0
- package/dist/components/ui/separator.js.map +1 -0
- package/dist/components/ui/separator.mjs +3 -0
- package/dist/components/ui/separator.mjs.map +1 -0
- package/dist/index.d.mts +18 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/postcss.config.mjs +2 -0
- package/dist/styles.css +406 -6
- package/dist/tailwind.config.ts +5 -5
- package/ladle.config.mjs +21 -0
- package/package.json +16 -4
- package/postcss.config.mjs +2 -0
- package/src/components/core/navbar.tsx +44 -39
- package/src/components/core/user-profile.tsx +245 -0
- package/src/components/flow/login.tsx +158 -0
- package/src/components/ui/alert.tsx +66 -0
- package/src/components/ui/badge.tsx +46 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/label.tsx +24 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/index.ts +3 -2
- package/src/main.tsx +11 -0
- package/src/preview.tsx +10 -0
- package/src/stories/Button.stories.tsx +24 -0
- package/src/stories/LoginForm.stories.tsx +29 -0
- package/src/stories/Navbar.stories.tsx +22 -0
- package/src/stories/UserProfile.stories.tsx +56 -0
- package/tailwind.config.ts +5 -5
package/package.json
CHANGED
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@authdog/react-elements",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.mts",
|
|
7
7
|
"peerDependencies": {
|
|
8
|
-
"react": "^
|
|
8
|
+
"react": "^19.1.0",
|
|
9
|
+
"react-dom": "^19.1.0"
|
|
9
10
|
},
|
|
10
11
|
"devDependencies": {
|
|
12
|
+
"@ladle/react": "^2.0.0",
|
|
11
13
|
"@types/node": "^20",
|
|
12
|
-
"@types/react": "^
|
|
14
|
+
"@types/react": "^19.1.0",
|
|
15
|
+
"@vitejs/plugin-react": "^4.4.1",
|
|
13
16
|
"autoprefixer": "^10",
|
|
17
|
+
"css-loader": "^6.8.1",
|
|
14
18
|
"postcss": "^8",
|
|
15
19
|
"postcss-cli": "^11.0.1",
|
|
20
|
+
"postcss-import": "^16.1.0",
|
|
16
21
|
"postcss-load-config": "^6",
|
|
22
|
+
"style-loader": "^3.3.3",
|
|
17
23
|
"tailwindcss": "3.4.17",
|
|
24
|
+
"ts-loader": "^9.5.1",
|
|
18
25
|
"typescript": "^5.6.3",
|
|
26
|
+
"webpack": "^5.89.0",
|
|
19
27
|
"@authdog/eslint-config": "0.0.0",
|
|
20
28
|
"@authdog/typescript-config": "0.0.0"
|
|
21
29
|
},
|
|
@@ -23,6 +31,8 @@
|
|
|
23
31
|
"@radix-ui/react-avatar": "^1.1.9",
|
|
24
32
|
"@radix-ui/react-dialog": "^1.1.13",
|
|
25
33
|
"@radix-ui/react-dropdown-menu": "^2.1.14",
|
|
34
|
+
"@radix-ui/react-label": "^2.1.6",
|
|
35
|
+
"@radix-ui/react-separator": "^1.1.6",
|
|
26
36
|
"@radix-ui/react-slot": "^1.2.2",
|
|
27
37
|
"class-variance-authority": "^0.7.0",
|
|
28
38
|
"clsx": "^2.1.1",
|
|
@@ -60,6 +70,8 @@
|
|
|
60
70
|
"ui": "pnpm dlx shadcn@latest",
|
|
61
71
|
"lint": "eslint .",
|
|
62
72
|
"build:styles": "postcss src/global.css -o dist/styles.css",
|
|
63
|
-
"build": "pnpm tsup && pnpm build:styles"
|
|
73
|
+
"build": "pnpm tsup && pnpm build:styles",
|
|
74
|
+
"ladle": "ladle serve",
|
|
75
|
+
"ladle:build": "ladle build"
|
|
64
76
|
}
|
|
65
77
|
}
|
package/postcss.config.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import type React from "react"
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
4
|
+
import { useState, useEffect } from "react"
|
|
5
|
+
import { User, Settings, LogOut, Menu } from "lucide-react"
|
|
6
|
+
import type { LucideProps } from "lucide-react"
|
|
7
7
|
|
|
8
8
|
import { cn } from "../../lib/utils"
|
|
9
9
|
import { Avatar, AvatarFallback, AvatarImage } from "../../components/ui/avatar"
|
|
@@ -56,33 +56,39 @@ export function Navbar({
|
|
|
56
56
|
onLogout = () => console.log("Logout clicked"),
|
|
57
57
|
}: NavbarProps) {
|
|
58
58
|
const [open, setOpen] = useState(false)
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
const [isMounted, setIsMounted] = useState(false)
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
setIsMounted(true)
|
|
63
|
+
}, [])
|
|
64
|
+
|
|
65
|
+
const iconProps: LucideProps = {
|
|
66
|
+
className: "mr-2 h-4 w-4",
|
|
67
|
+
"aria-hidden": "true"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const renderIcon = (Icon: any) => {
|
|
71
|
+
if (!isMounted) return null
|
|
72
|
+
return <Icon {...iconProps} />
|
|
73
|
+
}
|
|
63
74
|
|
|
64
75
|
return (
|
|
65
76
|
<header className={cn("border-b bg-background", className)}>
|
|
66
77
|
<div className="container flex h-16 items-center justify-between px-4 md:px-6">
|
|
67
78
|
<div className="flex items-center gap-4">
|
|
68
|
-
|
|
69
|
-
<span className="text-xl font-bold">{logoText}</span>
|
|
70
|
-
</Link> */}
|
|
79
|
+
<span className="text-xl font-bold">{logoText}</span>
|
|
71
80
|
<nav className="hidden md:flex gap-6">
|
|
72
81
|
{items?.map((item, index) => (
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<a href={item?.href}>
|
|
84
|
-
{item.title}
|
|
85
|
-
</a>
|
|
82
|
+
<a
|
|
83
|
+
key={index}
|
|
84
|
+
href={item.href}
|
|
85
|
+
className={cn(
|
|
86
|
+
"text-sm font-medium transition-colors hover:text-primary",
|
|
87
|
+
item.disabled && "cursor-not-allowed opacity-80"
|
|
88
|
+
)}
|
|
89
|
+
>
|
|
90
|
+
{item.title}
|
|
91
|
+
</a>
|
|
86
92
|
))}
|
|
87
93
|
</nav>
|
|
88
94
|
</div>
|
|
@@ -107,17 +113,17 @@ export function Navbar({
|
|
|
107
113
|
<DropdownMenuSeparator />
|
|
108
114
|
<DropdownMenuGroup>
|
|
109
115
|
<DropdownMenuItem>
|
|
110
|
-
|
|
116
|
+
{renderIcon(User)}
|
|
111
117
|
<span>Profile</span>
|
|
112
118
|
</DropdownMenuItem>
|
|
113
119
|
<DropdownMenuItem>
|
|
114
|
-
|
|
120
|
+
{renderIcon(Settings)}
|
|
115
121
|
<span>Settings</span>
|
|
116
122
|
</DropdownMenuItem>
|
|
117
123
|
</DropdownMenuGroup>
|
|
118
124
|
<DropdownMenuSeparator />
|
|
119
125
|
<DropdownMenuItem onClick={onLogout}>
|
|
120
|
-
|
|
126
|
+
{renderIcon(LogOut)}
|
|
121
127
|
<span>Log out</span>
|
|
122
128
|
</DropdownMenuItem>
|
|
123
129
|
</DropdownMenuContent>
|
|
@@ -125,24 +131,23 @@ export function Navbar({
|
|
|
125
131
|
<Sheet open={open} onOpenChange={setOpen}>
|
|
126
132
|
<SheetTrigger asChild>
|
|
127
133
|
<Button variant="ghost" size="icon" className="md:hidden" aria-label="Open Menu">
|
|
128
|
-
|
|
134
|
+
{renderIcon(Menu)}
|
|
129
135
|
</Button>
|
|
130
136
|
</SheetTrigger>
|
|
131
137
|
<SheetContent side="left" className="pr-0">
|
|
132
138
|
<nav className="grid gap-2 py-6">
|
|
133
139
|
{items?.map((item, index) => (
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
<a href="https://www.goo.bar" />
|
|
140
|
+
<a
|
|
141
|
+
key={index}
|
|
142
|
+
href={item.href}
|
|
143
|
+
className={cn(
|
|
144
|
+
"flex w-full items-center rounded-md px-3 py-2 text-sm font-medium hover:bg-accent",
|
|
145
|
+
item.disabled && "cursor-not-allowed opacity-80"
|
|
146
|
+
)}
|
|
147
|
+
onClick={() => setOpen(false)}
|
|
148
|
+
>
|
|
149
|
+
{item.title}
|
|
150
|
+
</a>
|
|
146
151
|
))}
|
|
147
152
|
</nav>
|
|
148
153
|
</SheetContent>
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react"
|
|
4
|
+
import { Avatar, AvatarFallback, AvatarImage } from "../../components/ui/avatar"
|
|
5
|
+
import { Button } from "../../components/ui/button"
|
|
6
|
+
import { Card } from "../../components/ui/card"
|
|
7
|
+
import { Badge } from "../../components/ui/badge"
|
|
8
|
+
import { PlusCircle, User, Shield, X, LucideProps } from "lucide-react"
|
|
9
|
+
|
|
10
|
+
export interface UserProfileProps {
|
|
11
|
+
user: {
|
|
12
|
+
name: string;
|
|
13
|
+
email: string;
|
|
14
|
+
image: string;
|
|
15
|
+
};
|
|
16
|
+
emails?: { address: string; isPrimary?: boolean }[];
|
|
17
|
+
connectedAccounts?: { provider: string; email: string }[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const UserProfile = ({
|
|
21
|
+
user,
|
|
22
|
+
emails = [],
|
|
23
|
+
connectedAccounts = [],
|
|
24
|
+
}: UserProfileProps) => {
|
|
25
|
+
const [isMounted, setIsMounted] = useState(false)
|
|
26
|
+
const [activeTab, setActiveTab] = useState<"profile" | "security">("profile");
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
setIsMounted(true)
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
const iconProps: LucideProps = {
|
|
33
|
+
className: "mr-2 h-4 w-4",
|
|
34
|
+
"aria-hidden": "true"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const renderIcon = (Icon: any) => {
|
|
38
|
+
if (!isMounted) return null
|
|
39
|
+
return <Icon {...iconProps} />
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div className="grid grid-cols-[16rem,1fr] h-screen bg-gray-100">
|
|
44
|
+
<div className="h-full border-r p-6 bg-white flex flex-col min-w-0">
|
|
45
|
+
<div className="mb-6">
|
|
46
|
+
<h1 className="text-xl font-bold">Account</h1>
|
|
47
|
+
<p className="text-sm text-gray-500">Manage your account info.</p>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<nav className="space-y-1 flex-1">
|
|
51
|
+
<button
|
|
52
|
+
onClick={() => setActiveTab("profile")}
|
|
53
|
+
className={`flex items-center w-full px-3 py-2 text-sm rounded-md ${
|
|
54
|
+
activeTab === "profile" ? "bg-gray-100 text-gray-900" : "text-gray-700 hover:bg-gray-50"
|
|
55
|
+
}`}
|
|
56
|
+
>
|
|
57
|
+
{renderIcon(User)}
|
|
58
|
+
Profile
|
|
59
|
+
</button>
|
|
60
|
+
<button
|
|
61
|
+
onClick={() => setActiveTab("security")}
|
|
62
|
+
className={`flex items-center w-full px-3 py-2 text-sm rounded-md ${
|
|
63
|
+
activeTab === "security" ? "bg-gray-100 text-gray-900" : "text-gray-700 hover:bg-gray-50"
|
|
64
|
+
}`}
|
|
65
|
+
>
|
|
66
|
+
{renderIcon(Shield)}
|
|
67
|
+
Security
|
|
68
|
+
</button>
|
|
69
|
+
</nav>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div className="h-full p-10 overflow-y-auto min-w-0 bg-white">
|
|
73
|
+
<div className="flex justify-between items-center mb-6">
|
|
74
|
+
<h2 className="text-xl font-semibold">
|
|
75
|
+
{activeTab === "profile" ? "Profile details" : "Security settings"}
|
|
76
|
+
</h2>
|
|
77
|
+
<button className="text-gray-500 hover:text-gray-700">
|
|
78
|
+
{renderIcon(X)}
|
|
79
|
+
</button>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
{activeTab === "profile" ? (
|
|
83
|
+
<div className="space-y-8">
|
|
84
|
+
{/* Profile Section */}
|
|
85
|
+
<div>
|
|
86
|
+
<h3 className="text-sm font-medium mb-4">Profile</h3>
|
|
87
|
+
<div className="flex items-center justify-between">
|
|
88
|
+
<div className="flex items-center">
|
|
89
|
+
<Avatar className="h-12 w-12 mr-4 border">
|
|
90
|
+
<AvatarImage src={user.image} alt="Profile picture" />
|
|
91
|
+
<AvatarFallback>{user.name.split(" ").map(n => n[0]).join("")}</AvatarFallback>
|
|
92
|
+
</Avatar>
|
|
93
|
+
<span className="font-medium">{user.name}</span>
|
|
94
|
+
</div>
|
|
95
|
+
<Button variant="outline" size="sm">
|
|
96
|
+
Edit profile
|
|
97
|
+
</Button>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
{/* Email Addresses Section */}
|
|
102
|
+
<div>
|
|
103
|
+
<h3 className="text-sm font-medium mb-4">Email addresses</h3>
|
|
104
|
+
<div className="space-y-3">
|
|
105
|
+
{(emails.length > 0 ? emails : [{ address: user.email, isPrimary: true }]).map((email, i) => (
|
|
106
|
+
<div className="flex items-center justify-between" key={email.address}>
|
|
107
|
+
<span>{email.address}</span>
|
|
108
|
+
{email.isPrimary && (
|
|
109
|
+
<Badge variant="outline" className="text-xs bg-gray-100 text-gray-700 hover:bg-gray-100">
|
|
110
|
+
Primary
|
|
111
|
+
</Badge>
|
|
112
|
+
)}
|
|
113
|
+
</div>
|
|
114
|
+
))}
|
|
115
|
+
<Button variant="ghost" size="sm" className="flex items-center text-gray-700">
|
|
116
|
+
{renderIcon(PlusCircle)}
|
|
117
|
+
Add email address
|
|
118
|
+
</Button>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
{/* Phone Number Section */}
|
|
123
|
+
<div>
|
|
124
|
+
<h3 className="text-sm font-medium mb-4">Phone number</h3>
|
|
125
|
+
<div className="space-y-3">
|
|
126
|
+
<div className="flex items-center justify-between">
|
|
127
|
+
<span>+1 (555) 123-4567</span>
|
|
128
|
+
<Badge variant="outline" className="text-xs bg-gray-100 text-gray-700 hover:bg-gray-100">
|
|
129
|
+
Primary
|
|
130
|
+
</Badge>
|
|
131
|
+
</div>
|
|
132
|
+
<Button variant="ghost" size="sm" className="flex items-center text-gray-700">
|
|
133
|
+
{renderIcon(PlusCircle)}
|
|
134
|
+
Add phone number
|
|
135
|
+
</Button>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
{/* Connected Accounts Section */}
|
|
140
|
+
<div>
|
|
141
|
+
<h3 className="text-sm font-medium mb-4">Connected accounts</h3>
|
|
142
|
+
<div className="space-y-3">
|
|
143
|
+
{connectedAccounts.length > 0 ? connectedAccounts.map((acc, i) => (
|
|
144
|
+
<div className="flex items-center justify-between" key={acc.provider + acc.email}>
|
|
145
|
+
<div className="flex items-center">
|
|
146
|
+
<div className="mr-2">
|
|
147
|
+
<span>{acc.provider}</span>
|
|
148
|
+
</div>
|
|
149
|
+
<span>{acc.provider}</span>
|
|
150
|
+
</div>
|
|
151
|
+
<span className="text-sm text-gray-500">{acc.email}</span>
|
|
152
|
+
</div>
|
|
153
|
+
)) : (
|
|
154
|
+
<div className="flex items-center justify-between">
|
|
155
|
+
<div className="flex items-center">
|
|
156
|
+
<div className="mr-2">
|
|
157
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20">
|
|
158
|
+
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" />
|
|
159
|
+
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" />
|
|
160
|
+
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" />
|
|
161
|
+
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" />
|
|
162
|
+
</svg>
|
|
163
|
+
</div>
|
|
164
|
+
<span>Google</span>
|
|
165
|
+
</div>
|
|
166
|
+
<span className="text-sm text-gray-500">{user.email}</span>
|
|
167
|
+
</div>
|
|
168
|
+
)}
|
|
169
|
+
<Button variant="ghost" size="sm" className="flex items-center text-gray-700">
|
|
170
|
+
{renderIcon(PlusCircle)}
|
|
171
|
+
Connect account
|
|
172
|
+
</Button>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
) : (
|
|
177
|
+
<div className="space-y-8">
|
|
178
|
+
{/* Security Settings */}
|
|
179
|
+
<div>
|
|
180
|
+
<h3 className="text-sm font-medium mb-4">Two-factor authentication</h3>
|
|
181
|
+
<div className="space-y-3">
|
|
182
|
+
<div className="flex items-center justify-between">
|
|
183
|
+
<div>
|
|
184
|
+
<p className="font-medium">Two-factor authentication</p>
|
|
185
|
+
<p className="text-sm text-gray-500">Add an extra layer of security to your account</p>
|
|
186
|
+
</div>
|
|
187
|
+
<Button variant="outline" size="sm">
|
|
188
|
+
Enable
|
|
189
|
+
</Button>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<div>
|
|
195
|
+
<h3 className="text-sm font-medium mb-4">Password</h3>
|
|
196
|
+
<div className="space-y-3">
|
|
197
|
+
<div className="flex items-center justify-between">
|
|
198
|
+
<div>
|
|
199
|
+
<p className="font-medium">Change password</p>
|
|
200
|
+
<p className="text-sm text-gray-500">Last changed 3 months ago</p>
|
|
201
|
+
</div>
|
|
202
|
+
<Button variant="outline" size="sm">
|
|
203
|
+
Change
|
|
204
|
+
</Button>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<div>
|
|
210
|
+
<h3 className="text-sm font-medium mb-4">Active sessions</h3>
|
|
211
|
+
<div className="space-y-3">
|
|
212
|
+
<div className="flex items-center justify-between">
|
|
213
|
+
<div>
|
|
214
|
+
<p className="font-medium">Current session</p>
|
|
215
|
+
<p className="text-sm text-gray-500">Chrome on Windows • Active now</p>
|
|
216
|
+
</div>
|
|
217
|
+
<Button variant="outline" size="sm">
|
|
218
|
+
Sign out
|
|
219
|
+
</Button>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
)}
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
<div className="absolute bottom-4 text-xs text-gray-500 flex items-center">
|
|
228
|
+
Secured by
|
|
229
|
+
<span className="ml-1 font-medium flex items-center">
|
|
230
|
+
<svg
|
|
231
|
+
width="14"
|
|
232
|
+
height="14"
|
|
233
|
+
viewBox="0 0 16 16"
|
|
234
|
+
fill="none"
|
|
235
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
236
|
+
className="mr-1"
|
|
237
|
+
>
|
|
238
|
+
<path d="M8 0L14.9282 4V12L8 16L1.07179 12V4L8 0Z" fill="#6C47FF" />
|
|
239
|
+
</svg>
|
|
240
|
+
Authdog
|
|
241
|
+
</span>
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
)
|
|
245
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import type React from "react"
|
|
4
|
+
|
|
5
|
+
import { useState } from "react"
|
|
6
|
+
import { Button } from "../../components/ui/button"
|
|
7
|
+
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "../../components/ui/card"
|
|
8
|
+
import { Input } from "../../components/ui/input"
|
|
9
|
+
import { Label } from "../../components/ui/label"
|
|
10
|
+
import { Separator } from "../../components/ui/separator"
|
|
11
|
+
import { Github, AlertCircle } from "lucide-react"
|
|
12
|
+
import { Alert, AlertDescription } from "../../components/ui/alert"
|
|
13
|
+
|
|
14
|
+
export const LoginForm = () => {
|
|
15
|
+
const [email, setEmail] = useState("")
|
|
16
|
+
const [password, setPassword] = useState("")
|
|
17
|
+
const [error, setError] = useState("")
|
|
18
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
19
|
+
|
|
20
|
+
const handleEmailLogin = async (e: React.FormEvent) => {
|
|
21
|
+
e.preventDefault()
|
|
22
|
+
setIsLoading(true)
|
|
23
|
+
setError("")
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
// Simulate API call
|
|
27
|
+
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
28
|
+
|
|
29
|
+
// Redirect after successful login
|
|
30
|
+
// router.push('/dashboard')
|
|
31
|
+
} catch (err) {
|
|
32
|
+
setError("Invalid email or password")
|
|
33
|
+
} finally {
|
|
34
|
+
setIsLoading(false)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const handleOAuthLogin = async (provider: string) => {
|
|
39
|
+
setIsLoading(true)
|
|
40
|
+
setError("")
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
|
|
44
|
+
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
45
|
+
} catch (err) {
|
|
46
|
+
setError(`Failed to login with ${provider}`)
|
|
47
|
+
} finally {
|
|
48
|
+
setIsLoading(false)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div className="flex min-h-screen items-center justify-center bg-gray-50 px-4 py-12 sm:px-6 lg:px-8">
|
|
54
|
+
<Card className="w-full max-w-md">
|
|
55
|
+
<CardHeader className="space-y-1">
|
|
56
|
+
<CardTitle className="text-2xl font-bold text-center">Login</CardTitle>
|
|
57
|
+
<CardDescription className="text-center">Choose your preferred login method</CardDescription>
|
|
58
|
+
</CardHeader>
|
|
59
|
+
<CardContent className="space-y-4">
|
|
60
|
+
{error && (
|
|
61
|
+
<Alert variant="destructive">
|
|
62
|
+
<AlertCircle className="h-4 w-4" />
|
|
63
|
+
<AlertDescription>{error}</AlertDescription>
|
|
64
|
+
</Alert>
|
|
65
|
+
)}
|
|
66
|
+
|
|
67
|
+
<div className="space-y-2">
|
|
68
|
+
<Button
|
|
69
|
+
variant="outline"
|
|
70
|
+
className="w-full"
|
|
71
|
+
onClick={() => handleOAuthLogin("google")}
|
|
72
|
+
disabled={isLoading}
|
|
73
|
+
>
|
|
74
|
+
<svg className="mr-2 h-4 w-4" viewBox="0 0 24 24">
|
|
75
|
+
<path
|
|
76
|
+
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
|
|
77
|
+
fill="#4285F4"
|
|
78
|
+
/>
|
|
79
|
+
<path
|
|
80
|
+
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
|
|
81
|
+
fill="#34A853"
|
|
82
|
+
/>
|
|
83
|
+
<path
|
|
84
|
+
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
|
|
85
|
+
fill="#FBBC05"
|
|
86
|
+
/>
|
|
87
|
+
<path
|
|
88
|
+
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
|
|
89
|
+
fill="#EA4335"
|
|
90
|
+
/>
|
|
91
|
+
<path d="M1 1h22v22H1z" fill="none" />
|
|
92
|
+
</svg>
|
|
93
|
+
Continue with Google
|
|
94
|
+
</Button>
|
|
95
|
+
|
|
96
|
+
<Button
|
|
97
|
+
variant="outline"
|
|
98
|
+
className="w-full"
|
|
99
|
+
onClick={() => handleOAuthLogin("github")}
|
|
100
|
+
disabled={isLoading}
|
|
101
|
+
>
|
|
102
|
+
<Github className="mr-2 h-4 w-4" />
|
|
103
|
+
Continue with GitHub
|
|
104
|
+
</Button>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div className="flex items-center">
|
|
108
|
+
<Separator className="flex-1" />
|
|
109
|
+
<span className="mx-2 text-xs text-muted-foreground">OR</span>
|
|
110
|
+
<Separator className="flex-1" />
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<form onSubmit={handleEmailLogin} className="space-y-4">
|
|
114
|
+
<div className="space-y-2">
|
|
115
|
+
<Label htmlFor="email">Email</Label>
|
|
116
|
+
<Input
|
|
117
|
+
id="email"
|
|
118
|
+
type="email"
|
|
119
|
+
placeholder="m@example.com"
|
|
120
|
+
value={email}
|
|
121
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
122
|
+
required
|
|
123
|
+
disabled={isLoading}
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
126
|
+
<div className="space-y-2">
|
|
127
|
+
<div className="flex items-center justify-between">
|
|
128
|
+
<Label htmlFor="password">Password</Label>
|
|
129
|
+
<a href="/forgot-password" className="text-xs text-primary hover:underline">
|
|
130
|
+
Forgot password?
|
|
131
|
+
</a>
|
|
132
|
+
</div>
|
|
133
|
+
<Input
|
|
134
|
+
id="password"
|
|
135
|
+
type="password"
|
|
136
|
+
value={password}
|
|
137
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
138
|
+
required
|
|
139
|
+
disabled={isLoading}
|
|
140
|
+
/>
|
|
141
|
+
</div>
|
|
142
|
+
<Button type="submit" className="w-full" disabled={isLoading}>
|
|
143
|
+
{isLoading ? "Loading..." : "Sign in with Email"}
|
|
144
|
+
</Button>
|
|
145
|
+
</form>
|
|
146
|
+
</CardContent>
|
|
147
|
+
<CardFooter className="flex justify-center">
|
|
148
|
+
<p className="text-xs text-muted-foreground">
|
|
149
|
+
Don't have an account?{" "}
|
|
150
|
+
<a href="/signup" className="text-primary hover:underline">
|
|
151
|
+
Sign up
|
|
152
|
+
</a>
|
|
153
|
+
</p>
|
|
154
|
+
</CardFooter>
|
|
155
|
+
</Card>
|
|
156
|
+
</div>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
|
|
4
|
+
import { cn } from "@authdog/react-elements/lib/utils"
|
|
5
|
+
|
|
6
|
+
const alertVariants = cva(
|
|
7
|
+
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: "bg-card text-card-foreground",
|
|
12
|
+
destructive:
|
|
13
|
+
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
variant: "default",
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
function Alert({
|
|
23
|
+
className,
|
|
24
|
+
variant,
|
|
25
|
+
...props
|
|
26
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
data-slot="alert"
|
|
30
|
+
role="alert"
|
|
31
|
+
className={cn(alertVariants({ variant }), className)}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
38
|
+
return (
|
|
39
|
+
<div
|
|
40
|
+
data-slot="alert-title"
|
|
41
|
+
className={cn(
|
|
42
|
+
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
|
|
43
|
+
className
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function AlertDescription({
|
|
51
|
+
className,
|
|
52
|
+
...props
|
|
53
|
+
}: React.ComponentProps<"div">) {
|
|
54
|
+
return (
|
|
55
|
+
<div
|
|
56
|
+
data-slot="alert-description"
|
|
57
|
+
className={cn(
|
|
58
|
+
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
|
|
59
|
+
className
|
|
60
|
+
)}
|
|
61
|
+
{...props}
|
|
62
|
+
/>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { Alert, AlertTitle, AlertDescription }
|