gladvn 0.2.33 → 0.2.35
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/bin/cli.js +2 -1
- package/package.json +1 -1
- package/src/blocks/auth-form.tsx +105 -0
- package/src/blocks/auth-recovery.tsx +5 -5
- package/src/blocks/auth-split.tsx +8 -8
- package/src/blocks/dashboard.tsx +162 -54
- package/src/blocks/settings.tsx +197 -68
- package/src/components/macro/field-preset.tsx +14 -17
- package/src/components/macro/input-preset.tsx +1 -1
- package/src/components/macro/textarea-preset.tsx +1 -1
- package/src/dev/App.tsx +192 -72
- package/src/dev/components/BlockViewer.tsx +136 -0
- package/src/dev/components/code-highlighter.tsx +17 -5
- package/src/dev/data.ts +40 -0
- package/src/dev/showcase/auth-form-block.tsx +13 -0
- package/src/dev/showcase/auth-recovery-block.tsx +13 -0
- package/src/dev/showcase/auth-split-block.tsx +13 -0
- package/src/dev/showcase/dashboard-block.tsx +77 -0
- package/src/dev/showcase/overview.tsx +53 -30
- package/src/dev/showcase/settings-block.tsx +13 -0
- package/src/blocks/auth-card.tsx +0 -104
- package/src/blocks/auth-dialog.tsx +0 -116
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { BlockViewer } from "../components/BlockViewer";
|
|
2
|
+
import codeString from "../../blocks/dashboard.tsx?raw";
|
|
3
|
+
import { InfoIcon } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
export default function DashboardBlockShowcase() {
|
|
6
|
+
return (
|
|
7
|
+
<div className="space-y-8">
|
|
8
|
+
<BlockViewer
|
|
9
|
+
blockId="dashboard"
|
|
10
|
+
title="Dashboard"
|
|
11
|
+
description="A full-featured dashboard layout with a responsive sidebar, header, and data visualizations."
|
|
12
|
+
codeString={codeString}
|
|
13
|
+
/>
|
|
14
|
+
|
|
15
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
16
|
+
<div className="rounded-2xl border border-info/30 bg-info/5 p-6 md:p-8 space-y-4 relative overflow-hidden">
|
|
17
|
+
<div className="absolute top-0 right-0 p-8 opacity-5 pointer-events-none">
|
|
18
|
+
<InfoIcon className="w-32 h-32 text-info" />
|
|
19
|
+
</div>
|
|
20
|
+
<h3 className="text-xl font-bold text-foreground flex items-center gap-2 relative z-10">
|
|
21
|
+
<InfoIcon className="size-5 text-info" />
|
|
22
|
+
Triết lý lắp ghép (Composition)
|
|
23
|
+
</h3>
|
|
24
|
+
<div className="text-muted-foreground leading-relaxed space-y-4 relative z-10">
|
|
25
|
+
<p>
|
|
26
|
+
Nhìn vào mã nguồn của Dashboard này, bạn sẽ thấy sự xuất hiện dày đặc của cả <strong>Micro</strong> (Avatar, Button, DropdownMenu...) và <strong>Macro</strong> (DatePicker).
|
|
27
|
+
</p>
|
|
28
|
+
<p>
|
|
29
|
+
gladvn khuyến khích bạn <strong>tự do kết hợp</strong> cả hai tầng kiến trúc:
|
|
30
|
+
</p>
|
|
31
|
+
<ul className="list-disc list-inside space-y-2 ml-2">
|
|
32
|
+
<li><strong>Dùng Macro</strong> khi bạn cần một khối chức năng hoàn chỉnh, hoạt động ngay lập tức (như DatePicker).</li>
|
|
33
|
+
<li><strong>Dùng Micro</strong> khi bạn cần tuỳ chỉnh sâu bố cục, hoặc xây dựng các phần tử đặc thù (như Sidebar, Header).</li>
|
|
34
|
+
</ul>
|
|
35
|
+
<p>
|
|
36
|
+
Không có ranh giới cứng nhắc. Hãy dùng kết hợp để tối ưu tốc độ phát triển mà không đánh mất sự linh hoạt!
|
|
37
|
+
</p>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div className="rounded-2xl border border-warning/30 bg-warning/5 p-6 md:p-8 space-y-4 relative overflow-hidden">
|
|
42
|
+
<div className="absolute top-0 right-0 p-8 opacity-5 pointer-events-none">
|
|
43
|
+
<InfoIcon className="w-32 h-32 text-warning" />
|
|
44
|
+
</div>
|
|
45
|
+
<h3 className="text-xl font-bold text-foreground flex items-center gap-2 relative z-10">
|
|
46
|
+
<InfoIcon className="size-5 text-warning" />
|
|
47
|
+
Cạm bẫy "Trừu tượng hoá sớm"
|
|
48
|
+
</h3>
|
|
49
|
+
<div className="text-muted-foreground leading-relaxed space-y-4 relative z-10">
|
|
50
|
+
<p>
|
|
51
|
+
Nhiều người khi nhìn vào mã nguồn file này sẽ thắc mắc: <em>"Tại sao không tách các thẻ Card kia thành một component nhỏ hơn (ví dụ DashboardCard) cho code ngắn lại?"</em>
|
|
52
|
+
</p>
|
|
53
|
+
<p>
|
|
54
|
+
Đừng rơi vào bẫy <strong>Premature Abstraction (Trừu tượng hoá quá sớm)</strong>. Hãy tự hỏi:
|
|
55
|
+
</p>
|
|
56
|
+
<ul className="list-disc list-inside space-y-2 ml-2 font-medium text-foreground/80">
|
|
57
|
+
<li>Các Card này có thực sự được tái sử dụng ở trang khác không?</li>
|
|
58
|
+
<li>Tách ra có làm mất đi ngữ cảnh (context) khi đọc code không?</li>
|
|
59
|
+
<li>Người tiếp theo vào maintain có thể nhìn một phát hiểu ngay overview trang web, hay phải nhảy qua lại giữa 10 file nhỏ xíu chỉ để biết một cái Card chứa gì?</li>
|
|
60
|
+
</ul>
|
|
61
|
+
<p>
|
|
62
|
+
Viết code dài trong một file không xấu, nếu nó <strong>minh bạch và dễ theo dõi (transparent)</strong>. Chỉ tách component khi bạn thực sự cần tái sử dụng nó ở nhiều nơi.
|
|
63
|
+
</p>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div className="text-center pb-8 pt-4">
|
|
69
|
+
<p className="text-sm text-muted-foreground italic max-w-2xl mx-auto">
|
|
70
|
+
*P/S: Tại sao những điều này lại được viết ở Dashboard block mà không phải nơi khác?
|
|
71
|
+
Thực ra thì chúng áp dụng cho <strong>tất cả mọi Block</strong>, tôi chỉ tình cờ chọn Dashboard làm chỗ để tâm sự mỏng thôi.
|
|
72
|
+
Đời mà, đâu cần cái gì cũng phải phân chia hoàn hảo, thong thả đi! ☕️
|
|
73
|
+
</p>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -133,7 +133,7 @@ export default function OverviewSection() {
|
|
|
133
133
|
Tinh tế. Đẳng cấp.
|
|
134
134
|
</span>
|
|
135
135
|
<span className="block mt-2 bg-gradient-to-r leading-[1.2] from-primary via-info to-primary bg-[length:200%_auto] bg-clip-text text-transparent animate-[spin_8s_linear_infinite] [animation-name:gradient] [animation-duration:8s] [animation-timing-function:linear] [animation-iteration-count:infinite]">
|
|
136
|
-
|
|
136
|
+
Không giới hạn.
|
|
137
137
|
</span>
|
|
138
138
|
</h1>
|
|
139
139
|
|
|
@@ -151,6 +151,13 @@ export default function OverviewSection() {
|
|
|
151
151
|
Đọc được, sửa được, xóa được.
|
|
152
152
|
</strong>
|
|
153
153
|
</p>
|
|
154
|
+
|
|
155
|
+
<div
|
|
156
|
+
className="max-w-2xl text-lg md:text-xl font-medium text-foreground/90 italic mt-2 p-5 border-l-4 border-primary bg-primary/5 rounded-r-xl shadow-sm animate-fade-up"
|
|
157
|
+
style={{ animationDelay: "300ms", animationFillMode: "both" }}
|
|
158
|
+
>
|
|
159
|
+
"Thư viện này sinh ra để giúp bạn code nhanh hơn, sống nhàn hơn, đừng tự làm khó mình."
|
|
160
|
+
</div>
|
|
154
161
|
</div>
|
|
155
162
|
|
|
156
163
|
{/* 3D Floating Showcase Dashboard */}
|
|
@@ -308,13 +315,12 @@ export default function OverviewSection() {
|
|
|
308
315
|
<section className="container max-w-6xl mx-auto space-y-12 px-4">
|
|
309
316
|
<div className="text-center max-w-2xl mx-auto space-y-4">
|
|
310
317
|
<h2 className="text-3xl md:text-4xl font-extrabold tracking-tight">
|
|
311
|
-
Kiến trúc
|
|
318
|
+
Kiến trúc rành mạch. Sửa code không "đổ mồ hôi".
|
|
312
319
|
</h2>
|
|
313
320
|
<p className="text-muted-foreground text-lg">
|
|
314
|
-
Nhiều dự án bắt đầu
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
{", "}nên bạn luôn biết mình đang sửa ở đâu và ảnh hưởng đến đâu.
|
|
321
|
+
Nhiều dự án thường bắt đầu "bốc mùi" sau một thời gian — đụng vào đâu cũng sợ gãy, muốn sửa mà rén. gladvn chia code thành{" "}
|
|
322
|
+
<strong className="text-foreground">các tầng rạch ròi</strong>
|
|
323
|
+
{", "}giúp bạn luôn biết chính xác mình đang sửa cái gì, ảnh hưởng tới đâu. Tâm an, code mới nhàn!
|
|
318
324
|
</p>
|
|
319
325
|
</div>
|
|
320
326
|
|
|
@@ -413,7 +419,7 @@ export default function OverviewSection() {
|
|
|
413
419
|
<p className="text-sm font-bold uppercase tracking-wider text-primary mb-5">
|
|
414
420
|
Tuỳ chỉnh theo từng tầng kiến trúc
|
|
415
421
|
</p>
|
|
416
|
-
<div className="grid sm:grid-cols-
|
|
422
|
+
<div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
417
423
|
{/* MACRO — RECOMMENDED */}
|
|
418
424
|
<div className="p-5 rounded-2xl border-2 border-primary/40 bg-primary/5 space-y-3 relative">
|
|
419
425
|
<div className="absolute top-3 right-3 text-[10px] font-bold uppercase tracking-wider text-primary bg-primary/10 px-2 py-0.5 rounded-full">
|
|
@@ -423,16 +429,14 @@ export default function OverviewSection() {
|
|
|
423
429
|
src/components/macro/
|
|
424
430
|
</div>
|
|
425
431
|
<h4 className="font-bold text-foreground text-base">
|
|
426
|
-
Macro —
|
|
432
|
+
Macro — Khu vực an toàn
|
|
427
433
|
</h4>
|
|
428
434
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
|
429
|
-
Bắt đầu
|
|
430
|
-
cho DatePicker, bỏ nút đóng của Dialog, chỉnh placeholder
|
|
431
|
-
Combobox.{" "}
|
|
435
|
+
Muốn tuỳ chỉnh? Bắt đầu từ đây nhé. Mỗi Macro là một khối độc lập — thêm footer cho DatePicker, giấu nút đóng của Dialog, v.v.{" "}
|
|
432
436
|
<strong className="text-foreground">
|
|
433
|
-
|
|
437
|
+
Cứ thoải mái vọc vạch
|
|
434
438
|
</strong>
|
|
435
|
-
,
|
|
439
|
+
, sửa cái nào thì chỉ ảnh hưởng cái đó thôi, chả lo "cháy nhà" hàng xóm.
|
|
436
440
|
</p>
|
|
437
441
|
</div>
|
|
438
442
|
|
|
@@ -445,15 +449,14 @@ export default function OverviewSection() {
|
|
|
445
449
|
src/components/micro/
|
|
446
450
|
</div>
|
|
447
451
|
<h4 className="font-bold text-foreground text-base">
|
|
448
|
-
Micro —
|
|
452
|
+
Micro — Lãnh địa cốt lõi
|
|
449
453
|
</h4>
|
|
450
454
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
|
451
|
-
|
|
452
|
-
Button hay animation của Tooltip thì{" "}
|
|
455
|
+
Mọi thứ đều xây từ đây. Sửa cái padding của Button hay animation của Tooltip thì{" "}
|
|
453
456
|
<strong className="text-foreground">
|
|
454
|
-
|
|
457
|
+
nguyên dàn app sẽ ăn theo
|
|
455
458
|
</strong>
|
|
456
|
-
.
|
|
459
|
+
. Cân nhắc kỹ chút trước khi gõ phím nhé, "sai một ly đi một dặm" đấy!
|
|
457
460
|
</p>
|
|
458
461
|
</div>
|
|
459
462
|
|
|
@@ -466,15 +469,33 @@ export default function OverviewSection() {
|
|
|
466
469
|
src/index.css
|
|
467
470
|
</div>
|
|
468
471
|
<h4 className="font-bold text-foreground text-base">
|
|
469
|
-
CSS Token —
|
|
472
|
+
CSS Token — Nút bấm hạt nhân
|
|
470
473
|
</h4>
|
|
471
474
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
|
472
|
-
|
|
473
|
-
một token ở đây thì{" "}
|
|
475
|
+
Nơi nắm trùm về màu sắc, font, độ bo góc. Đổi một biến ở đây là{" "}
|
|
474
476
|
<strong className="text-foreground">
|
|
475
|
-
|
|
477
|
+
giao diện lột xác toàn tập
|
|
476
478
|
</strong>
|
|
477
|
-
.
|
|
479
|
+
. Rất đã khi cần thay áo mới (rebrand), nhưng tuyệt đối đừng dùng để vá lỗi vặt.
|
|
480
|
+
</p>
|
|
481
|
+
</div>
|
|
482
|
+
|
|
483
|
+
{/* BLOCKS — FLEXIBLE COMPOSITION */}
|
|
484
|
+
<div className="p-5 rounded-2xl border border-info/40 bg-info/5 space-y-3 relative">
|
|
485
|
+
<div className="absolute top-3 right-3 text-[10px] font-bold uppercase tracking-wider text-info bg-info/10 px-2 py-0.5 rounded-full">
|
|
486
|
+
Lắp ghép linh hoạt
|
|
487
|
+
</div>
|
|
488
|
+
<div className="font-mono text-xs text-info/70">
|
|
489
|
+
src/blocks/
|
|
490
|
+
</div>
|
|
491
|
+
<h4 className="font-bold text-foreground text-base">
|
|
492
|
+
Blocks — Sân chơi tự do
|
|
493
|
+
</h4>
|
|
494
|
+
<p className="text-sm text-muted-foreground leading-relaxed">
|
|
495
|
+
Chẳng có quy tắc nào ép bạn phải dùng 100% Micro hay Macro.
|
|
496
|
+
Layout quen thuộc thì <strong className="text-foreground">lấy Macro ra xài cho lẹ</strong>.
|
|
497
|
+
Gặp thiết kế dị biệt thì <strong className="text-foreground">mở Micro ra tự xếp hình</strong>.
|
|
498
|
+
Linh hoạt lên, code sao cho bản thân thấy sướng là được!
|
|
478
499
|
</p>
|
|
479
500
|
</div>
|
|
480
501
|
</div>
|
|
@@ -489,13 +510,12 @@ export default function OverviewSection() {
|
|
|
489
510
|
</div>
|
|
490
511
|
<div className="relative z-10 space-y-4 mb-10">
|
|
491
512
|
<h3 className="text-2xl md:text-4xl font-extrabold tracking-tight">
|
|
492
|
-
|
|
513
|
+
Luật chơi Micro/Macro
|
|
493
514
|
</h3>
|
|
494
515
|
<p className="text-muted-foreground max-w-lg text-lg leading-relaxed">
|
|
495
|
-
Micro là
|
|
496
|
-
là preset — lắp ghép Micro thành giao diện hoàn chỉnh.
|
|
516
|
+
Micro chỉ là những mảnh lego thuần túy — đẹp nhưng vô tri. Macro mới là người chỉ huy — sắp xếp các mảnh lego thành giao diện hoàn chỉnh.
|
|
497
517
|
<strong className="text-foreground block mt-2">
|
|
498
|
-
Micro không bao giờ tự
|
|
518
|
+
Micro không bao giờ được phép tự tiện lo chuyện layout (margin, width). Việc đó là của Macro!
|
|
499
519
|
</strong>
|
|
500
520
|
</p>
|
|
501
521
|
</div>
|
|
@@ -990,6 +1010,7 @@ export default function OverviewSection() {
|
|
|
990
1010
|
<div className="relative z-10 mt-10">
|
|
991
1011
|
<Button
|
|
992
1012
|
render={<a href="/dialog" />}
|
|
1013
|
+
nativeButton={false}
|
|
993
1014
|
variant="outline"
|
|
994
1015
|
color="warning"
|
|
995
1016
|
className="gap-2 font-bold px-6 py-5 rounded-xl border-warning/30 bg-warning/10 hover:bg-warning/20 shadow-sm"
|
|
@@ -1087,6 +1108,7 @@ export default function OverviewSection() {
|
|
|
1087
1108
|
rel="noreferrer"
|
|
1088
1109
|
/>
|
|
1089
1110
|
}
|
|
1111
|
+
nativeButton={false}
|
|
1090
1112
|
>
|
|
1091
1113
|
Đóng góp cùng chúng tôi
|
|
1092
1114
|
</Button>
|
|
@@ -1099,14 +1121,14 @@ export default function OverviewSection() {
|
|
|
1099
1121
|
<div className="absolute inset-0 bg-[radial-gradient(ellipse_60%_60%_at_50%_0%,rgba(var(--primary-rgb),0.15),transparent)] pointer-events-none" />
|
|
1100
1122
|
<div className="relative z-10 space-y-6 max-w-2xl mx-auto">
|
|
1101
1123
|
<h2 className="text-3xl md:text-5xl font-extrabold tracking-tight">
|
|
1102
|
-
30 giây
|
|
1124
|
+
30 giây là có đồ chơi.
|
|
1103
1125
|
</h2>
|
|
1104
1126
|
<p className="text-muted-foreground text-lg leading-relaxed">
|
|
1105
|
-
|
|
1127
|
+
Quẳng hết code vào{" "}
|
|
1106
1128
|
<code className="text-sm bg-primary/20 text-primary px-1.5 py-0.5 rounded">
|
|
1107
1129
|
src/
|
|
1108
1130
|
</code>{" "}
|
|
1109
|
-
|
|
1131
|
+
rồi xài thôi. Không config rườm rà nhức đầu làm gì.
|
|
1110
1132
|
</p>
|
|
1111
1133
|
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 pt-2">
|
|
1112
1134
|
<button
|
|
@@ -1131,6 +1153,7 @@ export default function OverviewSection() {
|
|
|
1131
1153
|
rel="noreferrer"
|
|
1132
1154
|
/>
|
|
1133
1155
|
}
|
|
1156
|
+
nativeButton={false}
|
|
1134
1157
|
variant="ghost"
|
|
1135
1158
|
className="text-muted-foreground hover:text-foreground"
|
|
1136
1159
|
>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BlockViewer } from "../components/BlockViewer";
|
|
2
|
+
import codeString from "../../blocks/settings.tsx?raw";
|
|
3
|
+
|
|
4
|
+
export default function SettingsBlockShowcase() {
|
|
5
|
+
return (
|
|
6
|
+
<BlockViewer
|
|
7
|
+
blockId="settings"
|
|
8
|
+
title="Settings Layout"
|
|
9
|
+
description="A vertical tabs-based settings page with various input controls and danger zones."
|
|
10
|
+
codeString={codeString}
|
|
11
|
+
/>
|
|
12
|
+
);
|
|
13
|
+
}
|
package/src/blocks/auth-card.tsx
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { useState } from "react";
|
|
3
|
-
import { Button } from "../components/micro/button";
|
|
4
|
-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/micro/card";
|
|
5
|
-
import { Checkbox } from "../components/micro/checkbox";
|
|
6
|
-
import { Input } from "../components/micro/input";
|
|
7
|
-
import { Label } from "../components/micro/label";
|
|
8
|
-
import { Separator } from "../components/micro/separator";
|
|
9
|
-
|
|
10
|
-
export default function AuthCardBlock() {
|
|
11
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
12
|
-
const [mode, setMode] = useState<"login" | "register">("login");
|
|
13
|
-
|
|
14
|
-
const handleSubmit = (e: React.FormEvent) => {
|
|
15
|
-
e.preventDefault();
|
|
16
|
-
setIsLoading(true);
|
|
17
|
-
setTimeout(() => setIsLoading(false), 1000);
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<div className="flex min-h-screen w-full items-center justify-center p-4 bg-muted/20">
|
|
22
|
-
<Card className="w-full max-w-sm">
|
|
23
|
-
<CardHeader className="text-center space-y-2">
|
|
24
|
-
<CardTitle className="text-2xl font-bold">
|
|
25
|
-
{mode === "login" ? "Welcome back" : "Create an account"}
|
|
26
|
-
</CardTitle>
|
|
27
|
-
<CardDescription>
|
|
28
|
-
{mode === "login"
|
|
29
|
-
? "Enter your email below to login to your account"
|
|
30
|
-
: "Enter your details below to create your account"}
|
|
31
|
-
</CardDescription>
|
|
32
|
-
</CardHeader>
|
|
33
|
-
<CardContent>
|
|
34
|
-
<form onSubmit={handleSubmit} className="space-y-4">
|
|
35
|
-
{mode === "register" && (
|
|
36
|
-
<div className="space-y-2">
|
|
37
|
-
<Label htmlFor="name">Full Name</Label>
|
|
38
|
-
<Input id="name" placeholder="John Doe" required />
|
|
39
|
-
</div>
|
|
40
|
-
)}
|
|
41
|
-
<div className="space-y-2">
|
|
42
|
-
<Label htmlFor="email">Email</Label>
|
|
43
|
-
<Input id="email" type="email" placeholder="m@example.com" required />
|
|
44
|
-
</div>
|
|
45
|
-
<div className="space-y-2">
|
|
46
|
-
<div className="flex items-center justify-between">
|
|
47
|
-
<Label htmlFor="password">Password</Label>
|
|
48
|
-
{mode === "login" && (
|
|
49
|
-
<Button variant="link" color="primary" className="h-auto p-0 text-xs">
|
|
50
|
-
Forgot password?
|
|
51
|
-
</Button>
|
|
52
|
-
)}
|
|
53
|
-
</div>
|
|
54
|
-
<Input id="password" type="password" required />
|
|
55
|
-
</div>
|
|
56
|
-
|
|
57
|
-
{mode === "register" && (
|
|
58
|
-
<div className="flex items-center gap-2">
|
|
59
|
-
<Checkbox id="terms" required />
|
|
60
|
-
<Label htmlFor="terms" className="text-sm font-normal text-muted-foreground">
|
|
61
|
-
I agree to the <a href="#" className="text-primary hover:underline">terms and conditions</a>
|
|
62
|
-
</Label>
|
|
63
|
-
</div>
|
|
64
|
-
)}
|
|
65
|
-
|
|
66
|
-
<Button type="submit" className="w-full" disabled={isLoading}>
|
|
67
|
-
{isLoading ? "Loading..." : mode === "login" ? "Sign In" : "Create Account"}
|
|
68
|
-
</Button>
|
|
69
|
-
</form>
|
|
70
|
-
|
|
71
|
-
<div className="my-6 flex items-center gap-4">
|
|
72
|
-
<Separator className="flex-1" />
|
|
73
|
-
<span className="text-xs text-muted-foreground">OR</span>
|
|
74
|
-
<Separator className="flex-1" />
|
|
75
|
-
</div>
|
|
76
|
-
|
|
77
|
-
<Button variant="outline" className="w-full">
|
|
78
|
-
<svg viewBox="0 0 24 24" className="mr-2 size-4" fill="currentColor">
|
|
79
|
-
<path 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" fill="#4285F4" />
|
|
80
|
-
<path 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" fill="#34A853" />
|
|
81
|
-
<path 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" fill="#FBBC05" />
|
|
82
|
-
<path 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" fill="#EA4335" />
|
|
83
|
-
</svg>
|
|
84
|
-
Continue with Google
|
|
85
|
-
</Button>
|
|
86
|
-
|
|
87
|
-
<div className="mt-4 text-center text-sm">
|
|
88
|
-
<span className="text-muted-foreground">
|
|
89
|
-
{mode === "login" ? "Don't have an account? " : "Already have an account? "}
|
|
90
|
-
</span>
|
|
91
|
-
<Button
|
|
92
|
-
variant="link"
|
|
93
|
-
color="primary"
|
|
94
|
-
className="h-auto p-0"
|
|
95
|
-
onClick={() => setMode(mode === "login" ? "register" : "login")}
|
|
96
|
-
>
|
|
97
|
-
{mode === "login" ? "Sign up" : "Sign in"}
|
|
98
|
-
</Button>
|
|
99
|
-
</div>
|
|
100
|
-
</CardContent>
|
|
101
|
-
</Card>
|
|
102
|
-
</div>
|
|
103
|
-
);
|
|
104
|
-
}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { useState } from "react";
|
|
3
|
-
import { Button } from "../components/micro/button";
|
|
4
|
-
import { Checkbox } from "../components/micro/checkbox";
|
|
5
|
-
import {
|
|
6
|
-
Dialog,
|
|
7
|
-
DialogContent,
|
|
8
|
-
DialogDescription,
|
|
9
|
-
DialogHeader,
|
|
10
|
-
DialogTitle,
|
|
11
|
-
DialogTrigger,
|
|
12
|
-
} from "../components/micro/dialog";
|
|
13
|
-
import { Input } from "../components/micro/input";
|
|
14
|
-
import { Label } from "../components/micro/label";
|
|
15
|
-
import { Separator } from "../components/micro/separator";
|
|
16
|
-
|
|
17
|
-
export default function AuthDialogBlock() {
|
|
18
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
19
|
-
const [mode, setMode] = useState<"login" | "register">("login");
|
|
20
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
21
|
-
|
|
22
|
-
const handleSubmit = (e: React.FormEvent) => {
|
|
23
|
-
e.preventDefault();
|
|
24
|
-
setIsLoading(true);
|
|
25
|
-
setTimeout(() => {
|
|
26
|
-
setIsLoading(false);
|
|
27
|
-
setIsOpen(false);
|
|
28
|
-
}, 1000);
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<div className="flex min-h-[50vh] w-full items-center justify-center p-4 bg-muted/10 border border-dashed rounded-lg">
|
|
33
|
-
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
|
34
|
-
<DialogTrigger render={<Button>Open Authentication</Button>} />
|
|
35
|
-
<DialogContent className="sm:max-w-md">
|
|
36
|
-
<DialogHeader className="text-center space-y-2">
|
|
37
|
-
<DialogTitle className="text-2xl font-bold">
|
|
38
|
-
{mode === "login" ? "Welcome back" : "Create an account"}
|
|
39
|
-
</DialogTitle>
|
|
40
|
-
<DialogDescription>
|
|
41
|
-
{mode === "login"
|
|
42
|
-
? "Enter your email below to login to your account"
|
|
43
|
-
: "Enter your details below to create your account"}
|
|
44
|
-
</DialogDescription>
|
|
45
|
-
</DialogHeader>
|
|
46
|
-
|
|
47
|
-
<form onSubmit={handleSubmit} className="space-y-4 py-4">
|
|
48
|
-
{mode === "register" && (
|
|
49
|
-
<div className="space-y-2">
|
|
50
|
-
<Label htmlFor="dialog-name">Full Name</Label>
|
|
51
|
-
<Input id="dialog-name" placeholder="John Doe" required />
|
|
52
|
-
</div>
|
|
53
|
-
)}
|
|
54
|
-
<div className="space-y-2">
|
|
55
|
-
<Label htmlFor="dialog-email">Email</Label>
|
|
56
|
-
<Input id="dialog-email" type="email" placeholder="m@example.com" required />
|
|
57
|
-
</div>
|
|
58
|
-
<div className="space-y-2">
|
|
59
|
-
<div className="flex items-center justify-between">
|
|
60
|
-
<Label htmlFor="dialog-password">Password</Label>
|
|
61
|
-
{mode === "login" && (
|
|
62
|
-
<Button type="button" variant="link" color="primary" className="h-auto p-0 text-xs">
|
|
63
|
-
Forgot password?
|
|
64
|
-
</Button>
|
|
65
|
-
)}
|
|
66
|
-
</div>
|
|
67
|
-
<Input id="dialog-password" type="password" required />
|
|
68
|
-
</div>
|
|
69
|
-
|
|
70
|
-
{mode === "register" && (
|
|
71
|
-
<div className="flex items-center gap-2">
|
|
72
|
-
<Checkbox id="dialog-terms" required />
|
|
73
|
-
<Label htmlFor="dialog-terms" className="text-sm font-normal text-muted-foreground">
|
|
74
|
-
I agree to the <a href="#" className="text-primary hover:underline">terms</a>
|
|
75
|
-
</Label>
|
|
76
|
-
</div>
|
|
77
|
-
)}
|
|
78
|
-
|
|
79
|
-
<Button type="submit" className="w-full" disabled={isLoading}>
|
|
80
|
-
{isLoading ? "Loading..." : mode === "login" ? "Sign In" : "Create Account"}
|
|
81
|
-
</Button>
|
|
82
|
-
</form>
|
|
83
|
-
|
|
84
|
-
<div className="flex items-center gap-4">
|
|
85
|
-
<Separator className="flex-1" />
|
|
86
|
-
<span className="text-xs text-muted-foreground uppercase">Or</span>
|
|
87
|
-
<Separator className="flex-1" />
|
|
88
|
-
</div>
|
|
89
|
-
|
|
90
|
-
<Button type="button" variant="outline" className="w-full mt-4">
|
|
91
|
-
<svg viewBox="0 0 24 24" className="mr-2 size-4" fill="currentColor">
|
|
92
|
-
<path 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" fill="#4285F4" />
|
|
93
|
-
<path 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" fill="#34A853" />
|
|
94
|
-
<path 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" fill="#FBBC05" />
|
|
95
|
-
<path 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" fill="#EA4335" />
|
|
96
|
-
</svg>
|
|
97
|
-
Google
|
|
98
|
-
</Button>
|
|
99
|
-
|
|
100
|
-
<p className="mt-4 text-center text-sm text-muted-foreground">
|
|
101
|
-
{mode === "login" ? "Don't have an account? " : "Already have an account? "}
|
|
102
|
-
<Button
|
|
103
|
-
type="button"
|
|
104
|
-
variant="link"
|
|
105
|
-
color="primary"
|
|
106
|
-
className="h-auto p-0 font-semibold"
|
|
107
|
-
onClick={() => setMode(mode === "login" ? "register" : "login")}
|
|
108
|
-
>
|
|
109
|
-
{mode === "login" ? "Sign up" : "Sign in"}
|
|
110
|
-
</Button>
|
|
111
|
-
</p>
|
|
112
|
-
</DialogContent>
|
|
113
|
-
</Dialog>
|
|
114
|
-
</div>
|
|
115
|
-
);
|
|
116
|
-
}
|