@authdog/react-elements 0.0.34 → 0.0.36
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 +17 -17
- package/CHANGELOG.md +13 -0
- package/dist/index.d.mts +5 -1
- package/dist/index.d.ts +5 -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 +83 -0
- package/package.json +1 -1
- package/src/components/core/user-dropdown.tsx +2 -2
- package/src/components/core/user-profile.tsx +84 -6
- package/src/stories/UserProfile.stories.tsx +42 -63
package/dist/styles.css
CHANGED
|
@@ -665,6 +665,9 @@ video {
|
|
|
665
665
|
.mt-1 {
|
|
666
666
|
margin-top: 0.25rem;
|
|
667
667
|
}
|
|
668
|
+
.mt-2 {
|
|
669
|
+
margin-top: 0.5rem;
|
|
670
|
+
}
|
|
668
671
|
.mt-auto {
|
|
669
672
|
margin-top: auto;
|
|
670
673
|
}
|
|
@@ -871,6 +874,9 @@ video {
|
|
|
871
874
|
.items-center {
|
|
872
875
|
align-items: center;
|
|
873
876
|
}
|
|
877
|
+
.justify-end {
|
|
878
|
+
justify-content: flex-end;
|
|
879
|
+
}
|
|
874
880
|
.justify-center {
|
|
875
881
|
justify-content: center;
|
|
876
882
|
}
|
|
@@ -998,6 +1004,10 @@ video {
|
|
|
998
1004
|
.border-none {
|
|
999
1005
|
border-style: none;
|
|
1000
1006
|
}
|
|
1007
|
+
.border-amber-300 {
|
|
1008
|
+
--tw-border-opacity: 1;
|
|
1009
|
+
border-color: rgb(252 211 77 / var(--tw-border-opacity, 1));
|
|
1010
|
+
}
|
|
1001
1011
|
.border-border {
|
|
1002
1012
|
border-color: hsl(var(--border));
|
|
1003
1013
|
}
|
|
@@ -1005,12 +1015,20 @@ video {
|
|
|
1005
1015
|
--tw-border-opacity: 1;
|
|
1006
1016
|
border-color: rgb(187 247 208 / var(--tw-border-opacity, 1));
|
|
1007
1017
|
}
|
|
1018
|
+
.border-green-300 {
|
|
1019
|
+
--tw-border-opacity: 1;
|
|
1020
|
+
border-color: rgb(134 239 172 / var(--tw-border-opacity, 1));
|
|
1021
|
+
}
|
|
1008
1022
|
.border-input {
|
|
1009
1023
|
border-color: hsl(var(--input));
|
|
1010
1024
|
}
|
|
1011
1025
|
.border-transparent {
|
|
1012
1026
|
border-color: transparent;
|
|
1013
1027
|
}
|
|
1028
|
+
.bg-amber-100 {
|
|
1029
|
+
--tw-bg-opacity: 1;
|
|
1030
|
+
background-color: rgb(254 243 199 / var(--tw-bg-opacity, 1));
|
|
1031
|
+
}
|
|
1014
1032
|
.bg-background {
|
|
1015
1033
|
background-color: hsl(var(--background));
|
|
1016
1034
|
}
|
|
@@ -1101,10 +1119,17 @@ video {
|
|
|
1101
1119
|
.p-4 {
|
|
1102
1120
|
padding: 1rem;
|
|
1103
1121
|
}
|
|
1122
|
+
.p-6 {
|
|
1123
|
+
padding: 1.5rem;
|
|
1124
|
+
}
|
|
1104
1125
|
.px-2 {
|
|
1105
1126
|
padding-left: 0.5rem;
|
|
1106
1127
|
padding-right: 0.5rem;
|
|
1107
1128
|
}
|
|
1129
|
+
.px-2\.5 {
|
|
1130
|
+
padding-left: 0.625rem;
|
|
1131
|
+
padding-right: 0.625rem;
|
|
1132
|
+
}
|
|
1108
1133
|
.px-3 {
|
|
1109
1134
|
padding-left: 0.75rem;
|
|
1110
1135
|
padding-right: 0.75rem;
|
|
@@ -1215,6 +1240,10 @@ video {
|
|
|
1215
1240
|
.tracking-widest {
|
|
1216
1241
|
letter-spacing: 0.1em;
|
|
1217
1242
|
}
|
|
1243
|
+
.text-amber-800 {
|
|
1244
|
+
--tw-text-opacity: 1;
|
|
1245
|
+
color: rgb(146 64 14 / var(--tw-text-opacity, 1));
|
|
1246
|
+
}
|
|
1218
1247
|
.text-blue-600 {
|
|
1219
1248
|
--tw-text-opacity: 1;
|
|
1220
1249
|
color: rgb(37 99 235 / var(--tw-text-opacity, 1));
|
|
@@ -1255,6 +1284,10 @@ video {
|
|
|
1255
1284
|
--tw-text-opacity: 1;
|
|
1256
1285
|
color: rgb(21 128 61 / var(--tw-text-opacity, 1));
|
|
1257
1286
|
}
|
|
1287
|
+
.text-green-800 {
|
|
1288
|
+
--tw-text-opacity: 1;
|
|
1289
|
+
color: rgb(22 101 52 / var(--tw-text-opacity, 1));
|
|
1290
|
+
}
|
|
1258
1291
|
.text-green-900 {
|
|
1259
1292
|
--tw-text-opacity: 1;
|
|
1260
1293
|
color: rgb(20 83 45 / var(--tw-text-opacity, 1));
|
|
@@ -1442,6 +1475,10 @@ video {
|
|
|
1442
1475
|
.hover\:bg-primary\/90:hover {
|
|
1443
1476
|
background-color: hsl(var(--primary) / 0.9);
|
|
1444
1477
|
}
|
|
1478
|
+
.hover\:bg-red-50:hover {
|
|
1479
|
+
--tw-bg-opacity: 1;
|
|
1480
|
+
background-color: rgb(254 242 242 / var(--tw-bg-opacity, 1));
|
|
1481
|
+
}
|
|
1445
1482
|
.hover\:bg-secondary\/80:hover {
|
|
1446
1483
|
background-color: hsl(var(--secondary) / 0.8);
|
|
1447
1484
|
}
|
|
@@ -1455,6 +1492,10 @@ video {
|
|
|
1455
1492
|
.hover\:text-primary:hover {
|
|
1456
1493
|
color: hsl(var(--primary));
|
|
1457
1494
|
}
|
|
1495
|
+
.hover\:text-red-700:hover {
|
|
1496
|
+
--tw-text-opacity: 1;
|
|
1497
|
+
color: rgb(185 28 28 / var(--tw-text-opacity, 1));
|
|
1498
|
+
}
|
|
1458
1499
|
.hover\:underline:hover {
|
|
1459
1500
|
text-decoration-line: underline;
|
|
1460
1501
|
}
|
|
@@ -1464,6 +1505,10 @@ video {
|
|
|
1464
1505
|
.focus\:bg-accent:focus {
|
|
1465
1506
|
background-color: hsl(var(--accent));
|
|
1466
1507
|
}
|
|
1508
|
+
.focus\:bg-red-50:focus {
|
|
1509
|
+
--tw-bg-opacity: 1;
|
|
1510
|
+
background-color: rgb(254 242 242 / var(--tw-bg-opacity, 1));
|
|
1511
|
+
}
|
|
1467
1512
|
.focus\:text-accent-foreground:focus {
|
|
1468
1513
|
color: hsl(var(--accent-foreground));
|
|
1469
1514
|
}
|
|
@@ -1663,9 +1708,21 @@ video {
|
|
|
1663
1708
|
.group[data-disabled="true"] .group-data-\[disabled\=true\]\:opacity-50 {
|
|
1664
1709
|
opacity: 0.5;
|
|
1665
1710
|
}
|
|
1711
|
+
.dark\:border-amber-400\/40:is(.dark *) {
|
|
1712
|
+
border-color: rgb(251 191 36 / 0.4);
|
|
1713
|
+
}
|
|
1714
|
+
.dark\:border-green-400\/40:is(.dark *) {
|
|
1715
|
+
border-color: rgb(74 222 128 / 0.4);
|
|
1716
|
+
}
|
|
1717
|
+
.dark\:bg-amber-500\/20:is(.dark *) {
|
|
1718
|
+
background-color: rgb(245 158 11 / 0.2);
|
|
1719
|
+
}
|
|
1666
1720
|
.dark\:bg-destructive\/60:is(.dark *) {
|
|
1667
1721
|
background-color: hsl(var(--destructive) / 0.6);
|
|
1668
1722
|
}
|
|
1723
|
+
.dark\:bg-green-500\/20:is(.dark *) {
|
|
1724
|
+
background-color: rgb(34 197 94 / 0.2);
|
|
1725
|
+
}
|
|
1669
1726
|
.dark\:bg-input\/30:is(.dark *) {
|
|
1670
1727
|
background-color: hsl(var(--input) / 0.3);
|
|
1671
1728
|
}
|
|
@@ -1673,6 +1730,10 @@ video {
|
|
|
1673
1730
|
--tw-bg-opacity: 1;
|
|
1674
1731
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
|
|
1675
1732
|
}
|
|
1733
|
+
.dark\:text-amber-200:is(.dark *) {
|
|
1734
|
+
--tw-text-opacity: 1;
|
|
1735
|
+
color: rgb(253 230 138 / var(--tw-text-opacity, 1));
|
|
1736
|
+
}
|
|
1676
1737
|
.dark\:text-gray-100:is(.dark *) {
|
|
1677
1738
|
--tw-text-opacity: 1;
|
|
1678
1739
|
color: rgb(243 244 246 / var(--tw-text-opacity, 1));
|
|
@@ -1685,6 +1746,28 @@ video {
|
|
|
1685
1746
|
--tw-text-opacity: 1;
|
|
1686
1747
|
color: rgb(156 163 175 / var(--tw-text-opacity, 1));
|
|
1687
1748
|
}
|
|
1749
|
+
.dark\:text-green-200:is(.dark *) {
|
|
1750
|
+
--tw-text-opacity: 1;
|
|
1751
|
+
color: rgb(187 247 208 / var(--tw-text-opacity, 1));
|
|
1752
|
+
}
|
|
1753
|
+
.dark\:text-red-400:is(.dark *) {
|
|
1754
|
+
--tw-text-opacity: 1;
|
|
1755
|
+
color: rgb(248 113 113 / var(--tw-text-opacity, 1));
|
|
1756
|
+
}
|
|
1757
|
+
.dark\:hover\:bg-red-950\/40:hover:is(.dark *) {
|
|
1758
|
+
background-color: rgb(69 10 10 / 0.4);
|
|
1759
|
+
}
|
|
1760
|
+
.dark\:hover\:text-red-300:hover:is(.dark *) {
|
|
1761
|
+
--tw-text-opacity: 1;
|
|
1762
|
+
color: rgb(252 165 165 / var(--tw-text-opacity, 1));
|
|
1763
|
+
}
|
|
1764
|
+
.dark\:focus\:bg-red-950\/40:focus:is(.dark *) {
|
|
1765
|
+
background-color: rgb(69 10 10 / 0.4);
|
|
1766
|
+
}
|
|
1767
|
+
.dark\:focus\:text-red-300:focus:is(.dark *) {
|
|
1768
|
+
--tw-text-opacity: 1;
|
|
1769
|
+
color: rgb(252 165 165 / var(--tw-text-opacity, 1));
|
|
1770
|
+
}
|
|
1688
1771
|
.dark\:focus-visible\:ring-destructive\/40:focus-visible:is(.dark *) {
|
|
1689
1772
|
--tw-ring-color: hsl(var(--destructive) / 0.4);
|
|
1690
1773
|
}
|
package/package.json
CHANGED
|
@@ -87,8 +87,8 @@ export const UserDropdown = ({ trigger, user, className, onManageAccount, onSign
|
|
|
87
87
|
)
|
|
88
88
|
})}
|
|
89
89
|
<DropdownMenuSeparator />
|
|
90
|
-
<DropdownMenuItem className="cursor-pointer text-red-600 focus:text-red-700
|
|
91
|
-
<LogOut className="mr-2 h-4 w-4" />
|
|
90
|
+
<DropdownMenuItem className="cursor-pointer py-2 rounded-md font-semibold text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-950/40 hover:text-red-700 dark:hover:text-red-300 focus:bg-red-50 dark:focus:bg-red-950/40 focus:text-red-700 dark:focus:text-red-300" onClick={() => onSignout?.()}>
|
|
91
|
+
<LogOut className="mr-2 h-4 w-4 text-red-600 dark:text-red-400" />
|
|
92
92
|
<span>Sign out</span>
|
|
93
93
|
</DropdownMenuItem>
|
|
94
94
|
</DropdownMenuContent>
|
|
@@ -4,6 +4,16 @@ import { useEffect, useState } from "react"
|
|
|
4
4
|
import { Avatar, AvatarFallback, AvatarImage } from "../../components/ui/avatar"
|
|
5
5
|
import { Button } from "../../components/ui/button"
|
|
6
6
|
import { Badge } from "../../components/ui/badge"
|
|
7
|
+
import {
|
|
8
|
+
Card,
|
|
9
|
+
CardHeader,
|
|
10
|
+
CardTitle,
|
|
11
|
+
CardDescription,
|
|
12
|
+
CardContent,
|
|
13
|
+
CardFooter,
|
|
14
|
+
} from "../../components/ui/card"
|
|
15
|
+
import { Input } from "../../components/ui/input"
|
|
16
|
+
import { Label } from "../../components/ui/label"
|
|
7
17
|
import { User, Shield, SlidersHorizontal, LucideProps } from "lucide-react"
|
|
8
18
|
|
|
9
19
|
export interface UserProfileProps {
|
|
@@ -13,6 +23,7 @@ export interface UserProfileProps {
|
|
|
13
23
|
handleAuthenticated?: () => void;
|
|
14
24
|
onRequestEmailVerification?: (email: string) => Promise<{ success: boolean; message?: string } | void>;
|
|
15
25
|
onVerifyEmail?: (email: string, code: string) => Promise<{ success: boolean; message?: string } | void>;
|
|
26
|
+
onAddEmail?: (email: string) => Promise<{ success: boolean; message?: string } | void>;
|
|
16
27
|
}
|
|
17
28
|
|
|
18
29
|
export const UserProfile = ({
|
|
@@ -21,11 +32,14 @@ export const UserProfile = ({
|
|
|
21
32
|
handleAuthenticated,
|
|
22
33
|
onRequestEmailVerification,
|
|
23
34
|
onVerifyEmail,
|
|
35
|
+
onAddEmail,
|
|
24
36
|
}: UserProfileProps) => {
|
|
25
37
|
const [isMounted, setIsMounted] = useState(false)
|
|
26
38
|
const [activeTab, setActiveTab] = useState<"profile" | "security" | "preferences">("profile");
|
|
27
39
|
const [verifyingEmail, setVerifyingEmail] = useState<string | null>(null)
|
|
28
40
|
const [codeByEmail, setCodeByEmail] = useState<Record<string, string>>({})
|
|
41
|
+
const [addingEmail, setAddingEmail] = useState<boolean>(false)
|
|
42
|
+
const [newEmail, setNewEmail] = useState<string>("")
|
|
29
43
|
|
|
30
44
|
useEffect(() => {
|
|
31
45
|
setIsMounted(true);
|
|
@@ -159,8 +173,16 @@ export const UserProfile = ({
|
|
|
159
173
|
<div className="flex items-start justify-between gap-2" key={email.value}>
|
|
160
174
|
<div className="flex flex-col">
|
|
161
175
|
<span className="text-foreground">{email.value}</span>
|
|
162
|
-
<div className="mt-1
|
|
163
|
-
{isVerified ?
|
|
176
|
+
<div className="mt-1">
|
|
177
|
+
{isVerified ? (
|
|
178
|
+
<Badge className="text-xs rounded-full px-2.5 py-0.5 bg-green-100 text-green-800 border border-green-300 dark:bg-green-500/20 dark:text-green-200 dark:border-green-400/40">
|
|
179
|
+
Verified
|
|
180
|
+
</Badge>
|
|
181
|
+
) : (
|
|
182
|
+
<Badge className="text-xs rounded-full px-2.5 py-0.5 bg-amber-100 text-amber-800 border border-amber-300 dark:bg-amber-500/20 dark:text-amber-200 dark:border-amber-400/40">
|
|
183
|
+
Not verified
|
|
184
|
+
</Badge>
|
|
185
|
+
)}
|
|
164
186
|
</div>
|
|
165
187
|
</div>
|
|
166
188
|
<div className="flex items-center gap-2">
|
|
@@ -217,10 +239,66 @@ export const UserProfile = ({
|
|
|
217
239
|
</div>
|
|
218
240
|
)
|
|
219
241
|
})}
|
|
220
|
-
|
|
221
|
-
{
|
|
222
|
-
|
|
223
|
-
|
|
242
|
+
<div className="mt-2">
|
|
243
|
+
{addingEmail ? (
|
|
244
|
+
<div className="max-w-md">
|
|
245
|
+
<Card>
|
|
246
|
+
<CardHeader>
|
|
247
|
+
<CardTitle>Add email address</CardTitle>
|
|
248
|
+
<CardDescription>
|
|
249
|
+
You'll need to verify this email address before it can be added to your account.
|
|
250
|
+
</CardDescription>
|
|
251
|
+
</CardHeader>
|
|
252
|
+
<CardContent>
|
|
253
|
+
<div className="space-y-2">
|
|
254
|
+
<Label htmlFor="new-email">Email address</Label>
|
|
255
|
+
<Input
|
|
256
|
+
id="new-email"
|
|
257
|
+
placeholder="Enter your email address"
|
|
258
|
+
value={newEmail}
|
|
259
|
+
onChange={(e) => setNewEmail(e.target.value)}
|
|
260
|
+
onKeyDown={async (e) => {
|
|
261
|
+
if (e.key === "Enter") {
|
|
262
|
+
const v = String(newEmail || "").trim().toLowerCase()
|
|
263
|
+
if (!v) return
|
|
264
|
+
if (onAddEmail) await onAddEmail(v)
|
|
265
|
+
setAddingEmail(false)
|
|
266
|
+
setNewEmail("")
|
|
267
|
+
}
|
|
268
|
+
}}
|
|
269
|
+
/>
|
|
270
|
+
</div>
|
|
271
|
+
</CardContent>
|
|
272
|
+
<CardFooter className="justify-end gap-2">
|
|
273
|
+
<Button
|
|
274
|
+
variant="ghost"
|
|
275
|
+
onClick={() => {
|
|
276
|
+
setAddingEmail(false)
|
|
277
|
+
setNewEmail("")
|
|
278
|
+
}}
|
|
279
|
+
>
|
|
280
|
+
Cancel
|
|
281
|
+
</Button>
|
|
282
|
+
<Button
|
|
283
|
+
onClick={async () => {
|
|
284
|
+
const v = String(newEmail || "").trim().toLowerCase()
|
|
285
|
+
if (!v) return
|
|
286
|
+
if (onAddEmail) await onAddEmail(v)
|
|
287
|
+
setAddingEmail(false)
|
|
288
|
+
setNewEmail("")
|
|
289
|
+
}}
|
|
290
|
+
>
|
|
291
|
+
Add
|
|
292
|
+
</Button>
|
|
293
|
+
</CardFooter>
|
|
294
|
+
</Card>
|
|
295
|
+
</div>
|
|
296
|
+
) : (
|
|
297
|
+
<Button variant="outline" size="sm" className="text-xs" onClick={() => setAddingEmail(true)}>
|
|
298
|
+
Add email
|
|
299
|
+
</Button>
|
|
300
|
+
)}
|
|
301
|
+
</div>
|
|
224
302
|
</div>
|
|
225
303
|
</div>
|
|
226
304
|
|
|
@@ -1,67 +1,46 @@
|
|
|
1
|
-
import type { Story } from
|
|
2
|
-
import { UserProfile
|
|
3
|
-
import "../global.css"
|
|
1
|
+
import type { StoryDefault, Story } from "@ladle/react"
|
|
2
|
+
import { UserProfile } from "../components/core/user-profile"
|
|
3
|
+
import "../global.css"
|
|
4
4
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
id: "123",
|
|
9
|
-
displayName: "Jaylon Dias",
|
|
10
|
-
emails: [{ value: "example@authdog.xyz" }],
|
|
11
|
-
photos: [{ value: "https://i.pravatar.cc/150?u=a042581f4e29026704d" }],
|
|
12
|
-
provider: "authdog",
|
|
13
|
-
}}
|
|
14
|
-
loading={false}
|
|
15
|
-
handleAuthenticated={() => {}}
|
|
16
|
-
emails={[{ address: "example@authdog.xyz", isPrimary: true }]}
|
|
17
|
-
/>
|
|
18
|
-
);
|
|
19
|
-
Default.storyName = 'Default User Profile';
|
|
5
|
+
export default {
|
|
6
|
+
title: "Core/UserProfile",
|
|
7
|
+
} satisfies StoryDefault
|
|
20
8
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}}
|
|
30
|
-
loading={false}
|
|
31
|
-
handleAuthenticated={() => {}}
|
|
32
|
-
emails={[{ address: "example@authdog.xyz", isPrimary: true }]}
|
|
33
|
-
/>
|
|
34
|
-
);
|
|
35
|
-
WithCustomUser.storyName = 'User Profile with Custom User';
|
|
9
|
+
const baseUser: any = {
|
|
10
|
+
id: "user_123",
|
|
11
|
+
displayName: "Jane Doe",
|
|
12
|
+
provider: "google-oauth20",
|
|
13
|
+
emails: [{ id: "e1", value: "jane.primary@example.com" }],
|
|
14
|
+
verifications: [],
|
|
15
|
+
photos: [],
|
|
16
|
+
}
|
|
36
17
|
|
|
37
|
-
export const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}}
|
|
46
|
-
loading={false}
|
|
47
|
-
handleAuthenticated={() => {}}
|
|
48
|
-
emails={[{ address: "example@authdog.xyz", isPrimary: true }, { address: "example@gmail.com", isPrimary: false }]}
|
|
49
|
-
/>
|
|
50
|
-
);
|
|
51
|
-
WithMultipleEmails.storyName = 'User Profile with Multiple Emails';
|
|
18
|
+
export const EmailNotVerified: Story = () => {
|
|
19
|
+
return (
|
|
20
|
+
<div className="p-6 bg-background text-foreground">
|
|
21
|
+
<UserProfile loading={false} user={{ ...baseUser }} />
|
|
22
|
+
</div>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
EmailNotVerified.storyName = "Email not verified"
|
|
52
26
|
|
|
53
|
-
export const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
27
|
+
export const EmailVerified: Story = () => {
|
|
28
|
+
const verifiedUser = {
|
|
29
|
+
...baseUser,
|
|
30
|
+
verifications: [
|
|
31
|
+
{
|
|
32
|
+
id: "v1",
|
|
33
|
+
email: "jane.primary@example.com",
|
|
34
|
+
verified: true,
|
|
35
|
+
createdAt: new Date().toISOString(),
|
|
36
|
+
updatedAt: new Date().toISOString(),
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
}
|
|
40
|
+
return (
|
|
41
|
+
<div className="p-6 bg-background text-foreground">
|
|
42
|
+
<UserProfile loading={false} user={verifiedUser} />
|
|
43
|
+
</div>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
EmailVerified.storyName = "Email verified"
|