@authdog/react-elements 0.0.29 → 0.0.31
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 +11 -11
- package/CHANGELOG.md +12 -0
- package/dist/index.d.mts +6 -1
- package/dist/index.d.ts +6 -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/styles.css +109 -16
- package/package.json +6 -5
- package/src/components/core/user-profile.tsx +30 -32
- package/src/components/flow/totp-validator.tsx +231 -0
- package/src/index.ts +2 -1
- package/src/stories/TotpValidator.stories.tsx +14 -0
- /package/src/stories/{Button.stories.tsx → Button._stories.tsx} +0 -0
- /package/src/stories/{Navbar.stories.tsx → Navbar._stories.tsx} +0 -0
- /package/src/stories/{PlaceholderAlert.stories.tsx → PlaceholderAlert._stories.tsx} +0 -0
package/dist/styles.css
CHANGED
|
@@ -630,6 +630,10 @@ video {
|
|
|
630
630
|
margin-left: 0.5rem;
|
|
631
631
|
margin-right: 0.5rem;
|
|
632
632
|
}
|
|
633
|
+
.mx-auto {
|
|
634
|
+
margin-left: auto;
|
|
635
|
+
margin-right: auto;
|
|
636
|
+
}
|
|
633
637
|
.my-1 {
|
|
634
638
|
margin-top: 0.25rem;
|
|
635
639
|
margin-bottom: 0.25rem;
|
|
@@ -637,12 +641,12 @@ video {
|
|
|
637
641
|
.mb-2 {
|
|
638
642
|
margin-bottom: 0.5rem;
|
|
639
643
|
}
|
|
644
|
+
.mb-3 {
|
|
645
|
+
margin-bottom: 0.75rem;
|
|
646
|
+
}
|
|
640
647
|
.mb-4 {
|
|
641
648
|
margin-bottom: 1rem;
|
|
642
649
|
}
|
|
643
|
-
.mb-6 {
|
|
644
|
-
margin-bottom: 1.5rem;
|
|
645
|
-
}
|
|
646
650
|
.ml-1 {
|
|
647
651
|
margin-left: 0.25rem;
|
|
648
652
|
}
|
|
@@ -658,6 +662,9 @@ video {
|
|
|
658
662
|
.mr-4 {
|
|
659
663
|
margin-right: 1rem;
|
|
660
664
|
}
|
|
665
|
+
.mt-1 {
|
|
666
|
+
margin-top: 0.25rem;
|
|
667
|
+
}
|
|
661
668
|
.mt-auto {
|
|
662
669
|
margin-top: auto;
|
|
663
670
|
}
|
|
@@ -711,6 +718,9 @@ video {
|
|
|
711
718
|
.h-12 {
|
|
712
719
|
height: 3rem;
|
|
713
720
|
}
|
|
721
|
+
.h-14 {
|
|
722
|
+
height: 3.5rem;
|
|
723
|
+
}
|
|
714
724
|
.h-16 {
|
|
715
725
|
height: 4rem;
|
|
716
726
|
}
|
|
@@ -720,6 +730,9 @@ video {
|
|
|
720
730
|
.h-4 {
|
|
721
731
|
height: 1rem;
|
|
722
732
|
}
|
|
733
|
+
.h-6 {
|
|
734
|
+
height: 1.5rem;
|
|
735
|
+
}
|
|
723
736
|
.h-8 {
|
|
724
737
|
height: 2rem;
|
|
725
738
|
}
|
|
@@ -735,9 +748,6 @@ video {
|
|
|
735
748
|
.h-px {
|
|
736
749
|
height: 1px;
|
|
737
750
|
}
|
|
738
|
-
.h-screen {
|
|
739
|
-
height: 100vh;
|
|
740
|
-
}
|
|
741
751
|
.min-h-4 {
|
|
742
752
|
min-height: 1rem;
|
|
743
753
|
}
|
|
@@ -753,6 +763,9 @@ video {
|
|
|
753
763
|
.w-12 {
|
|
754
764
|
width: 3rem;
|
|
755
765
|
}
|
|
766
|
+
.w-16 {
|
|
767
|
+
width: 4rem;
|
|
768
|
+
}
|
|
756
769
|
.w-3\/4 {
|
|
757
770
|
width: 75%;
|
|
758
771
|
}
|
|
@@ -762,6 +775,9 @@ video {
|
|
|
762
775
|
.w-56 {
|
|
763
776
|
width: 14rem;
|
|
764
777
|
}
|
|
778
|
+
.w-6 {
|
|
779
|
+
width: 1.5rem;
|
|
780
|
+
}
|
|
765
781
|
.w-8 {
|
|
766
782
|
width: 2rem;
|
|
767
783
|
}
|
|
@@ -819,8 +835,8 @@ video {
|
|
|
819
835
|
.grid-cols-\[0_1fr\] {
|
|
820
836
|
grid-template-columns: 0 1fr;
|
|
821
837
|
}
|
|
822
|
-
.grid-cols-\[
|
|
823
|
-
grid-template-columns:
|
|
838
|
+
.grid-cols-\[14rem\2c 1fr\] {
|
|
839
|
+
grid-template-columns: 14rem 1fr;
|
|
824
840
|
}
|
|
825
841
|
.grid-rows-\[auto_auto\] {
|
|
826
842
|
grid-template-rows: auto auto;
|
|
@@ -871,6 +887,11 @@ video {
|
|
|
871
887
|
margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
|
|
872
888
|
margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
|
|
873
889
|
}
|
|
890
|
+
.space-y-2\.5 > :not([hidden]) ~ :not([hidden]) {
|
|
891
|
+
--tw-space-y-reverse: 0;
|
|
892
|
+
margin-top: calc(0.625rem * calc(1 - var(--tw-space-y-reverse)));
|
|
893
|
+
margin-bottom: calc(0.625rem * var(--tw-space-y-reverse));
|
|
894
|
+
}
|
|
874
895
|
.space-y-3 > :not([hidden]) ~ :not([hidden]) {
|
|
875
896
|
--tw-space-y-reverse: 0;
|
|
876
897
|
margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse)));
|
|
@@ -881,10 +902,15 @@ video {
|
|
|
881
902
|
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
|
|
882
903
|
margin-bottom: calc(1rem * var(--tw-space-y-reverse));
|
|
883
904
|
}
|
|
884
|
-
.space-y-
|
|
905
|
+
.space-y-5 > :not([hidden]) ~ :not([hidden]) {
|
|
906
|
+
--tw-space-y-reverse: 0;
|
|
907
|
+
margin-top: calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));
|
|
908
|
+
margin-bottom: calc(1.25rem * var(--tw-space-y-reverse));
|
|
909
|
+
}
|
|
910
|
+
.space-y-6 > :not([hidden]) ~ :not([hidden]) {
|
|
885
911
|
--tw-space-y-reverse: 0;
|
|
886
|
-
margin-top: calc(
|
|
887
|
-
margin-bottom: calc(
|
|
912
|
+
margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
|
|
913
|
+
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
|
|
888
914
|
}
|
|
889
915
|
.self-start {
|
|
890
916
|
align-self: flex-start;
|
|
@@ -925,6 +951,9 @@ video {
|
|
|
925
951
|
.border {
|
|
926
952
|
border-width: 1px;
|
|
927
953
|
}
|
|
954
|
+
.border-2 {
|
|
955
|
+
border-width: 2px;
|
|
956
|
+
}
|
|
928
957
|
.border-b {
|
|
929
958
|
border-bottom-width: 1px;
|
|
930
959
|
}
|
|
@@ -937,6 +966,13 @@ video {
|
|
|
937
966
|
.border-t {
|
|
938
967
|
border-top-width: 1px;
|
|
939
968
|
}
|
|
969
|
+
.border-none {
|
|
970
|
+
border-style: none;
|
|
971
|
+
}
|
|
972
|
+
.border-green-200 {
|
|
973
|
+
--tw-border-opacity: 1;
|
|
974
|
+
border-color: rgb(187 247 208 / var(--tw-border-opacity, 1));
|
|
975
|
+
}
|
|
940
976
|
.border-input {
|
|
941
977
|
border-color: hsl(var(--input));
|
|
942
978
|
}
|
|
@@ -949,6 +985,10 @@ video {
|
|
|
949
985
|
.bg-black\/50 {
|
|
950
986
|
background-color: rgb(0 0 0 / 0.5);
|
|
951
987
|
}
|
|
988
|
+
.bg-blue-100 {
|
|
989
|
+
--tw-bg-opacity: 1;
|
|
990
|
+
background-color: rgb(219 234 254 / var(--tw-bg-opacity, 1));
|
|
991
|
+
}
|
|
952
992
|
.bg-border {
|
|
953
993
|
background-color: hsl(var(--border));
|
|
954
994
|
}
|
|
@@ -966,6 +1006,14 @@ video {
|
|
|
966
1006
|
--tw-bg-opacity: 1;
|
|
967
1007
|
background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1));
|
|
968
1008
|
}
|
|
1009
|
+
.bg-green-100 {
|
|
1010
|
+
--tw-bg-opacity: 1;
|
|
1011
|
+
background-color: rgb(220 252 231 / var(--tw-bg-opacity, 1));
|
|
1012
|
+
}
|
|
1013
|
+
.bg-green-50 {
|
|
1014
|
+
--tw-bg-opacity: 1;
|
|
1015
|
+
background-color: rgb(240 253 244 / var(--tw-bg-opacity, 1));
|
|
1016
|
+
}
|
|
969
1017
|
.bg-muted {
|
|
970
1018
|
background-color: hsl(var(--muted));
|
|
971
1019
|
}
|
|
@@ -999,18 +1047,18 @@ video {
|
|
|
999
1047
|
.fill-current {
|
|
1000
1048
|
fill: currentColor;
|
|
1001
1049
|
}
|
|
1050
|
+
.p-0 {
|
|
1051
|
+
padding: 0px;
|
|
1052
|
+
}
|
|
1002
1053
|
.p-1 {
|
|
1003
1054
|
padding: 0.25rem;
|
|
1004
1055
|
}
|
|
1005
|
-
.p-
|
|
1006
|
-
padding:
|
|
1056
|
+
.p-3 {
|
|
1057
|
+
padding: 0.75rem;
|
|
1007
1058
|
}
|
|
1008
1059
|
.p-4 {
|
|
1009
1060
|
padding: 1rem;
|
|
1010
1061
|
}
|
|
1011
|
-
.p-6 {
|
|
1012
|
-
padding: 1.5rem;
|
|
1013
|
-
}
|
|
1014
1062
|
.px-2 {
|
|
1015
1063
|
padding-left: 0.5rem;
|
|
1016
1064
|
padding-right: 0.5rem;
|
|
@@ -1068,6 +1116,9 @@ video {
|
|
|
1068
1116
|
.pr-2 {
|
|
1069
1117
|
padding-right: 0.5rem;
|
|
1070
1118
|
}
|
|
1119
|
+
.pt-6 {
|
|
1120
|
+
padding-top: 1.5rem;
|
|
1121
|
+
}
|
|
1071
1122
|
.text-center {
|
|
1072
1123
|
text-align: center;
|
|
1073
1124
|
}
|
|
@@ -1079,6 +1130,10 @@ video {
|
|
|
1079
1130
|
font-size: 1rem;
|
|
1080
1131
|
line-height: 1.5rem;
|
|
1081
1132
|
}
|
|
1133
|
+
.text-lg {
|
|
1134
|
+
font-size: 1.125rem;
|
|
1135
|
+
line-height: 1.75rem;
|
|
1136
|
+
}
|
|
1082
1137
|
.text-sm {
|
|
1083
1138
|
font-size: 0.875rem;
|
|
1084
1139
|
line-height: 1.25rem;
|
|
@@ -1112,6 +1167,10 @@ video {
|
|
|
1112
1167
|
.tracking-widest {
|
|
1113
1168
|
letter-spacing: 0.1em;
|
|
1114
1169
|
}
|
|
1170
|
+
.text-blue-600 {
|
|
1171
|
+
--tw-text-opacity: 1;
|
|
1172
|
+
color: rgb(37 99 235 / var(--tw-text-opacity, 1));
|
|
1173
|
+
}
|
|
1115
1174
|
.text-card-foreground {
|
|
1116
1175
|
color: hsl(var(--card-foreground));
|
|
1117
1176
|
}
|
|
@@ -1136,6 +1195,18 @@ video {
|
|
|
1136
1195
|
--tw-text-opacity: 1;
|
|
1137
1196
|
color: rgb(17 24 39 / var(--tw-text-opacity, 1));
|
|
1138
1197
|
}
|
|
1198
|
+
.text-green-600 {
|
|
1199
|
+
--tw-text-opacity: 1;
|
|
1200
|
+
color: rgb(22 163 74 / var(--tw-text-opacity, 1));
|
|
1201
|
+
}
|
|
1202
|
+
.text-green-700 {
|
|
1203
|
+
--tw-text-opacity: 1;
|
|
1204
|
+
color: rgb(21 128 61 / var(--tw-text-opacity, 1));
|
|
1205
|
+
}
|
|
1206
|
+
.text-green-900 {
|
|
1207
|
+
--tw-text-opacity: 1;
|
|
1208
|
+
color: rgb(20 83 45 / var(--tw-text-opacity, 1));
|
|
1209
|
+
}
|
|
1139
1210
|
.text-muted-foreground {
|
|
1140
1211
|
color: hsl(var(--muted-foreground));
|
|
1141
1212
|
}
|
|
@@ -1283,6 +1354,10 @@ video {
|
|
|
1283
1354
|
.placeholder\:text-muted-foreground::placeholder {
|
|
1284
1355
|
color: hsl(var(--muted-foreground));
|
|
1285
1356
|
}
|
|
1357
|
+
.focus-within\:border-blue-500:focus-within {
|
|
1358
|
+
--tw-border-opacity: 1;
|
|
1359
|
+
border-color: rgb(59 130 246 / var(--tw-border-opacity, 1));
|
|
1360
|
+
}
|
|
1286
1361
|
.hover\:bg-accent:hover {
|
|
1287
1362
|
background-color: hsl(var(--accent));
|
|
1288
1363
|
}
|
|
@@ -1538,6 +1613,10 @@ video {
|
|
|
1538
1613
|
}
|
|
1539
1614
|
@media (min-width: 768px) {
|
|
1540
1615
|
|
|
1616
|
+
.md\:mb-4 {
|
|
1617
|
+
margin-bottom: 1rem;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1541
1620
|
.md\:flex {
|
|
1542
1621
|
display: flex;
|
|
1543
1622
|
}
|
|
@@ -1546,6 +1625,20 @@ video {
|
|
|
1546
1625
|
display: none;
|
|
1547
1626
|
}
|
|
1548
1627
|
|
|
1628
|
+
.md\:space-y-6 > :not([hidden]) ~ :not([hidden]) {
|
|
1629
|
+
--tw-space-y-reverse: 0;
|
|
1630
|
+
margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
|
|
1631
|
+
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
.md\:p-4 {
|
|
1635
|
+
padding: 1rem;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
.md\:p-5 {
|
|
1639
|
+
padding: 1.25rem;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1549
1642
|
.md\:px-6 {
|
|
1550
1643
|
padding-left: 1.5rem;
|
|
1551
1644
|
padding-right: 1.5rem;
|
package/package.json
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@authdog/react-elements",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.31",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.mts",
|
|
7
7
|
"peerDependencies": {
|
|
8
|
-
"react": "^
|
|
9
|
-
"react-dom": "^
|
|
8
|
+
"react": "^19.1.0",
|
|
9
|
+
"react-dom": "^19.1.0"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@ladle/react": "^2.0.0",
|
|
13
13
|
"@types/node": "^20",
|
|
14
|
-
"@types/react": "^
|
|
14
|
+
"@types/react": "^19.1.0",
|
|
15
|
+
"@types/react-dom": "^19.1.0",
|
|
15
16
|
"@vitejs/plugin-react": "^4.4.1",
|
|
16
17
|
"autoprefixer": "^10",
|
|
17
18
|
"css-loader": "^6.8.1",
|
|
@@ -33,7 +34,7 @@
|
|
|
33
34
|
"@radix-ui/react-dropdown-menu": "^2.1.14",
|
|
34
35
|
"@radix-ui/react-label": "^2.1.6",
|
|
35
36
|
"@radix-ui/react-separator": "^1.1.6",
|
|
36
|
-
"@radix-ui/react-slot": "^1.2.
|
|
37
|
+
"@radix-ui/react-slot": "^1.2.3",
|
|
37
38
|
"class-variance-authority": "^0.7.0",
|
|
38
39
|
"clsx": "^2.1.1",
|
|
39
40
|
"lucide-react": "^0.451.0",
|
|
@@ -50,9 +50,9 @@ export const UserProfile = ({
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
return (
|
|
53
|
-
<div className="grid grid-cols-[
|
|
54
|
-
<div className="h-full border-r p-
|
|
55
|
-
<div className="mb-
|
|
53
|
+
<div className="grid grid-cols-[14rem,1fr] w-full bg-transparent">
|
|
54
|
+
<div className="h-full border-r p-3 md:p-4 bg-transparent flex flex-col min-w-0">
|
|
55
|
+
<div className="mb-3 md:mb-4">
|
|
56
56
|
<h1 className="text-xl font-bold">Account</h1>
|
|
57
57
|
<p className="text-sm text-gray-500">Manage your account info.</p>
|
|
58
58
|
</div>
|
|
@@ -79,8 +79,8 @@ export const UserProfile = ({
|
|
|
79
79
|
</nav>
|
|
80
80
|
</div>
|
|
81
81
|
|
|
82
|
-
<div className="h-full p-
|
|
83
|
-
<div className="flex justify-between items-center mb-
|
|
82
|
+
<div className="h-full p-3 md:p-5 min-w-0 bg-transparent">
|
|
83
|
+
<div className="flex justify-between items-center mb-3 md:mb-4">
|
|
84
84
|
<h2 className="text-xl font-semibold">
|
|
85
85
|
{activeTab === "profile" ? "Profile details" : "Security settings"}
|
|
86
86
|
</h2>
|
|
@@ -90,10 +90,10 @@ export const UserProfile = ({
|
|
|
90
90
|
</div>
|
|
91
91
|
|
|
92
92
|
{activeTab === "profile" ? (
|
|
93
|
-
<div className="space-y-
|
|
93
|
+
<div className="space-y-5 md:space-y-6">
|
|
94
94
|
{/* Profile Section */}
|
|
95
95
|
<div>
|
|
96
|
-
<h3 className="text-sm font-medium mb-
|
|
96
|
+
<h3 className="text-sm font-medium mb-3">Profile</h3>
|
|
97
97
|
<div className="flex items-center justify-between">
|
|
98
98
|
<div className="flex items-center">
|
|
99
99
|
<Avatar className="h-12 w-12 mr-4 border">
|
|
@@ -110,8 +110,8 @@ export const UserProfile = ({
|
|
|
110
110
|
|
|
111
111
|
{/* Email Addresses Section */}
|
|
112
112
|
<div>
|
|
113
|
-
<h3 className="text-sm font-medium mb-
|
|
114
|
-
<div className="space-y-
|
|
113
|
+
<h3 className="text-sm font-medium mb-3">Email addresses</h3>
|
|
114
|
+
<div className="space-y-2.5">
|
|
115
115
|
|
|
116
116
|
{/* {JSON.stringify(user)} */}
|
|
117
117
|
|
|
@@ -126,18 +126,16 @@ export const UserProfile = ({
|
|
|
126
126
|
</div>
|
|
127
127
|
))} */}
|
|
128
128
|
|
|
129
|
-
{
|
|
130
|
-
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
))
|
|
140
|
-
}
|
|
129
|
+
{user.emails.map((email: any, idx: number) => (
|
|
130
|
+
<div className="flex items-center justify-between" key={email.value}>
|
|
131
|
+
<span>{email.value}</span>
|
|
132
|
+
{idx === 0 && (
|
|
133
|
+
<Badge variant="outline" className="text-xs bg-gray-100 text-gray-700 hover:bg-gray-100">
|
|
134
|
+
Primary
|
|
135
|
+
</Badge>
|
|
136
|
+
)}
|
|
137
|
+
</div>
|
|
138
|
+
))}
|
|
141
139
|
{/* <Button variant="ghost" size="sm" className="flex items-center text-gray-700">
|
|
142
140
|
{renderIcon(PlusCircle)}
|
|
143
141
|
Add email address
|
|
@@ -147,8 +145,8 @@ export const UserProfile = ({
|
|
|
147
145
|
|
|
148
146
|
{/* Phone Number Section */}
|
|
149
147
|
{/* <div>
|
|
150
|
-
<h3 className="text-sm font-medium mb-
|
|
151
|
-
<div className="space-y-
|
|
148
|
+
<h3 className="text-sm font-medium mb-3">Phone number</h3>
|
|
149
|
+
<div className="space-y-2.5">
|
|
152
150
|
<div className="flex items-center justify-between">
|
|
153
151
|
<span>+1 (555) 123-4567</span>
|
|
154
152
|
<Badge variant="outline" className="text-xs bg-gray-100 text-gray-700 hover:bg-gray-100">
|
|
@@ -164,8 +162,8 @@ export const UserProfile = ({
|
|
|
164
162
|
|
|
165
163
|
{/* Connected Accounts Section */}
|
|
166
164
|
<div>
|
|
167
|
-
<h3 className="text-sm font-medium mb-
|
|
168
|
-
<div className="space-y-
|
|
165
|
+
<h3 className="text-sm font-medium mb-3">Connected accounts</h3>
|
|
166
|
+
<div className="space-y-2.5">
|
|
169
167
|
<div className="flex items-center justify-between" key={user.provider}>
|
|
170
168
|
<div className="flex items-center">
|
|
171
169
|
<div className="mr-2">
|
|
@@ -178,11 +176,11 @@ export const UserProfile = ({
|
|
|
178
176
|
</div>
|
|
179
177
|
</div>
|
|
180
178
|
) : (
|
|
181
|
-
<div className="space-y-
|
|
179
|
+
<div className="space-y-5 md:space-y-6">
|
|
182
180
|
{/* Security Settings */}
|
|
183
181
|
<div key="two-factor">
|
|
184
|
-
<h3 className="text-sm font-medium mb-
|
|
185
|
-
<div className="space-y-
|
|
182
|
+
<h3 className="text-sm font-medium mb-3">Two-factor authentication</h3>
|
|
183
|
+
<div className="space-y-2.5">
|
|
186
184
|
<div className="flex items-center justify-between">
|
|
187
185
|
<div>
|
|
188
186
|
<p className="font-medium">Two-factor authentication</p>
|
|
@@ -196,8 +194,8 @@ export const UserProfile = ({
|
|
|
196
194
|
</div>
|
|
197
195
|
|
|
198
196
|
<div key="password">
|
|
199
|
-
<h3 className="text-sm font-medium mb-
|
|
200
|
-
<div className="space-y-
|
|
197
|
+
<h3 className="text-sm font-medium mb-3">Password</h3>
|
|
198
|
+
<div className="space-y-2.5">
|
|
201
199
|
<div className="flex items-center justify-between">
|
|
202
200
|
<div>
|
|
203
201
|
<p className="font-medium">Change password</p>
|
|
@@ -211,8 +209,8 @@ export const UserProfile = ({
|
|
|
211
209
|
</div>
|
|
212
210
|
|
|
213
211
|
<div key="sessions">
|
|
214
|
-
<h3 className="text-sm font-medium mb-
|
|
215
|
-
<div className="space-y-
|
|
212
|
+
<h3 className="text-sm font-medium mb-3">Active sessions</h3>
|
|
213
|
+
<div className="space-y-2.5">
|
|
216
214
|
<div className="flex items-center justify-between">
|
|
217
215
|
<div>
|
|
218
216
|
<p className="font-medium">Current session</p>
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import type React from "react"
|
|
4
|
+
|
|
5
|
+
import { useState, useRef } from "react"
|
|
6
|
+
import { Button } from "../../components/ui/button"
|
|
7
|
+
import { Card, CardContent } from "../../components/ui/card"
|
|
8
|
+
import { Alert, AlertDescription } from "../../components/ui/alert"
|
|
9
|
+
import { Shield, CheckCircle, AlertCircle } from "lucide-react"
|
|
10
|
+
|
|
11
|
+
interface TOTPValidatorProps {
|
|
12
|
+
onValidate: (code: string) => Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const TOTPValidator = (
|
|
16
|
+
{
|
|
17
|
+
onValidate
|
|
18
|
+
}: TOTPValidatorProps
|
|
19
|
+
) => {
|
|
20
|
+
const [code, setCode] = useState(["", "", "", "", "", ""])
|
|
21
|
+
const [loading, setLoading] = useState(false)
|
|
22
|
+
const [error, setError] = useState("")
|
|
23
|
+
const [success, setSuccess] = useState(false)
|
|
24
|
+
const inputRefs = useRef<(HTMLInputElement | null)[]>([])
|
|
25
|
+
|
|
26
|
+
const handleInputChange = (index: number, value: string) => {
|
|
27
|
+
// Only allow digits
|
|
28
|
+
if (!/^\d*$/.test(value)) return
|
|
29
|
+
|
|
30
|
+
const newCode = [...code]
|
|
31
|
+
newCode[index] = value.slice(-1) // Only take the last character
|
|
32
|
+
|
|
33
|
+
setCode(newCode)
|
|
34
|
+
setError("")
|
|
35
|
+
setSuccess(false)
|
|
36
|
+
|
|
37
|
+
// Auto-focus next input
|
|
38
|
+
if (value && index < 5) {
|
|
39
|
+
inputRefs.current[index + 1]?.focus()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const handleKeyDown = (index: number, e: React.KeyboardEvent) => {
|
|
44
|
+
// Handle backspace
|
|
45
|
+
if (e.key === "Backspace" && !code[index] && index > 0) {
|
|
46
|
+
inputRefs.current[index - 1]?.focus()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Handle paste
|
|
50
|
+
if (e.key === "v" && (e.ctrlKey || e.metaKey)) {
|
|
51
|
+
e.preventDefault()
|
|
52
|
+
navigator.clipboard.readText().then((text) => {
|
|
53
|
+
const digits = text.replace(/\D/g, "").slice(0, 6).split("")
|
|
54
|
+
const newCode = [...code]
|
|
55
|
+
digits.forEach((digit, i) => {
|
|
56
|
+
if (i < 6) newCode[i] = digit
|
|
57
|
+
})
|
|
58
|
+
setCode(newCode)
|
|
59
|
+
|
|
60
|
+
// Focus the next empty input or the last one
|
|
61
|
+
const nextIndex = Math.min(digits.length, 5)
|
|
62
|
+
inputRefs.current[nextIndex]?.focus()
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const validateTOTP = async () => {
|
|
68
|
+
const totpCode = code.join("")
|
|
69
|
+
|
|
70
|
+
if (totpCode.length !== 6) {
|
|
71
|
+
setError("Please enter all 6 digits")
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
setLoading(true)
|
|
76
|
+
setError("")
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
await onValidate(totpCode)
|
|
80
|
+
setSuccess(true)
|
|
81
|
+
} catch (error) {
|
|
82
|
+
setError("Invalid TOTP code. Please try again.")
|
|
83
|
+
} finally {
|
|
84
|
+
setLoading(false)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
// try {
|
|
90
|
+
// Call your TOTP validation endpoint
|
|
91
|
+
// const response = await fetch("/api/validate-totp", {
|
|
92
|
+
// method: "POST",
|
|
93
|
+
// headers: {
|
|
94
|
+
// "Content-Type": "application/json",
|
|
95
|
+
// },
|
|
96
|
+
// body: JSON.stringify({ code: totpCode }),
|
|
97
|
+
// })
|
|
98
|
+
|
|
99
|
+
// // Check if response is JSON
|
|
100
|
+
// const contentType = response.headers.get("content-type")
|
|
101
|
+
// if (!contentType || !contentType.includes("application/json")) {
|
|
102
|
+
// throw new Error("Server returned non-JSON response")
|
|
103
|
+
// }
|
|
104
|
+
|
|
105
|
+
// const result = await response.json()
|
|
106
|
+
|
|
107
|
+
// if (response.ok && result.valid) {
|
|
108
|
+
// setSuccess(true)
|
|
109
|
+
// setError("")
|
|
110
|
+
// } else {
|
|
111
|
+
// setError(result.message || "Invalid TOTP code. Please try again.")
|
|
112
|
+
// // Clear the code on error
|
|
113
|
+
// setCode(["", "", "", "", "", ""])
|
|
114
|
+
// inputRefs.current[0]?.focus()
|
|
115
|
+
// }
|
|
116
|
+
// } catch (err) {
|
|
117
|
+
// console.error("TOTP validation error:", err)
|
|
118
|
+
// if (err instanceof Error && err.message.includes("non-JSON")) {
|
|
119
|
+
// setError("Server error. Please try again later.")
|
|
120
|
+
// } else {
|
|
121
|
+
// setError("Network error. Please try again.")
|
|
122
|
+
// }
|
|
123
|
+
// // Clear the code on error
|
|
124
|
+
// setCode(["", "", "", "", "", ""])
|
|
125
|
+
// inputRefs.current[0]?.focus()
|
|
126
|
+
// } finally {
|
|
127
|
+
// setLoading(false)
|
|
128
|
+
// }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
132
|
+
e.preventDefault()
|
|
133
|
+
validateTOTP()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const clearCode = () => {
|
|
137
|
+
setCode(["", "", "", "", "", ""])
|
|
138
|
+
setError("")
|
|
139
|
+
setSuccess(false)
|
|
140
|
+
setLoading(false)
|
|
141
|
+
inputRefs.current[0]?.focus()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (success) {
|
|
145
|
+
return (
|
|
146
|
+
<div className="max-w-md mx-auto">
|
|
147
|
+
<Card className="border-green-200 bg-green-50">
|
|
148
|
+
<CardContent className="pt-6">
|
|
149
|
+
<div className="text-center space-y-4">
|
|
150
|
+
<div className="mx-auto w-16 h-16 bg-green-100 rounded-full flex items-center justify-center">
|
|
151
|
+
<CheckCircle className="w-8 h-8 text-green-600" />
|
|
152
|
+
</div>
|
|
153
|
+
<div>
|
|
154
|
+
<h3 className="text-lg font-semibold text-green-900">Verification Successful!</h3>
|
|
155
|
+
<p className="text-sm text-green-700 mt-1">Your TOTP code has been validated.</p>
|
|
156
|
+
</div>
|
|
157
|
+
<Button onClick={clearCode} variant="outline" className="bg-white">
|
|
158
|
+
Verify Another Code
|
|
159
|
+
</Button>
|
|
160
|
+
</div>
|
|
161
|
+
</CardContent>
|
|
162
|
+
</Card>
|
|
163
|
+
</div>
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<div className="max-w-md mx-auto">
|
|
169
|
+
<Card>
|
|
170
|
+
<CardContent className="pt-6">
|
|
171
|
+
<div className="text-center space-y-6">
|
|
172
|
+
<div>
|
|
173
|
+
<div className="mx-auto w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center mb-4">
|
|
174
|
+
<Shield className="w-6 h-6 text-blue-600" />
|
|
175
|
+
</div>
|
|
176
|
+
<h3 className="text-lg font-semibold">Enter Verification Code</h3>
|
|
177
|
+
<p className="text-sm text-muted-foreground mt-1">Enter the 6-digit code from your authenticator app</p>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<form onSubmit={handleSubmit} className="space-y-6">
|
|
181
|
+
<div className="flex justify-center gap-2">
|
|
182
|
+
{code.map((digit, index) => (
|
|
183
|
+
<Card key={index} className="w-12 h-14 border-2 focus-within:border-blue-500 transition-colors">
|
|
184
|
+
<CardContent className="p-0 h-full flex items-center justify-center">
|
|
185
|
+
<input
|
|
186
|
+
ref={(el) => {
|
|
187
|
+
inputRefs.current[index] = el
|
|
188
|
+
}}
|
|
189
|
+
type="tel"
|
|
190
|
+
inputMode="numeric"
|
|
191
|
+
maxLength={1}
|
|
192
|
+
value={digit}
|
|
193
|
+
onChange={(e) => handleInputChange(index, e.target.value)}
|
|
194
|
+
onKeyDown={(e) => handleKeyDown(index, e)}
|
|
195
|
+
className="w-full h-full text-center text-2xl font-bold border-none outline-none bg-transparent"
|
|
196
|
+
autoComplete="one-time-code"
|
|
197
|
+
disabled={loading}
|
|
198
|
+
style={{
|
|
199
|
+
height: 'auto'
|
|
200
|
+
}}
|
|
201
|
+
/>
|
|
202
|
+
</CardContent>
|
|
203
|
+
</Card>
|
|
204
|
+
))}
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
{error && (
|
|
208
|
+
<Alert variant="destructive">
|
|
209
|
+
<AlertCircle className="h-4 w-4" />
|
|
210
|
+
<AlertDescription>{error}</AlertDescription>
|
|
211
|
+
</Alert>
|
|
212
|
+
)}
|
|
213
|
+
|
|
214
|
+
<div className="space-y-3">
|
|
215
|
+
<Button type="submit" className="w-full" disabled={loading || code.some((digit) => digit === "")}>
|
|
216
|
+
{loading ? "Verifying..." : "Verify Code"}
|
|
217
|
+
</Button>
|
|
218
|
+
|
|
219
|
+
<Button type="button" variant="ghost" size="sm" onClick={clearCode} className="w-full text-xs">
|
|
220
|
+
Clear Code
|
|
221
|
+
</Button>
|
|
222
|
+
</div>
|
|
223
|
+
</form>
|
|
224
|
+
|
|
225
|
+
<p className="text-xs text-muted-foreground">Codes refresh every 30 seconds</p>
|
|
226
|
+
</div>
|
|
227
|
+
</CardContent>
|
|
228
|
+
</Card>
|
|
229
|
+
</div>
|
|
230
|
+
)
|
|
231
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,4 +2,5 @@ export {Button} from "./components/ui/button";
|
|
|
2
2
|
export { ClientOnly } from "./components/core/client-only";
|
|
3
3
|
export {Navbar} from "./components/core/navbar";
|
|
4
4
|
export {UserProfile} from "./components/core/user-profile";
|
|
5
|
-
export {PlaceholderAlert} from "./components/core/placeholder-alert";
|
|
5
|
+
export {PlaceholderAlert} from "./components/core/placeholder-alert";
|
|
6
|
+
export {TOTPValidator} from "./components/flow/totp-validator";
|