create-nala 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +58 -0
- package/dist/index.js +227 -0
- package/package.json +48 -0
- package/template/.env.example +45 -0
- package/template/_gitignore +33 -0
- package/template/components.json +24 -0
- package/template/index.html +30 -0
- package/template/package.json +51 -0
- package/template/public/favicon.svg +15 -0
- package/template/public/icons.svg +24 -0
- package/template/public/img/avatar.webp +0 -0
- package/template/public/screenshot.png +0 -0
- package/template/src/App.vue +15 -0
- package/template/src/auto-imports.d.ts +691 -0
- package/template/src/components/AppLogo.vue +159 -0
- package/template/src/components/EmptyState.vue +60 -0
- package/template/src/components/PageHeader.vue +60 -0
- package/template/src/components/layout/AppNavbar.vue +159 -0
- package/template/src/components/layout/AppSidebar.vue +74 -0
- package/template/src/components/layout/CommandSearchDialog.vue +120 -0
- package/template/src/components/ui/accordion/Accordion.vue +15 -0
- package/template/src/components/ui/accordion/AccordionContent.vue +20 -0
- package/template/src/components/ui/accordion/AccordionItem.vue +20 -0
- package/template/src/components/ui/accordion/AccordionTrigger.vue +34 -0
- package/template/src/components/ui/accordion/index.ts +4 -0
- package/template/src/components/ui/alert/Alert.vue +21 -0
- package/template/src/components/ui/alert/AlertDescription.vue +17 -0
- package/template/src/components/ui/alert/AlertTitle.vue +17 -0
- package/template/src/components/ui/alert/index.ts +30 -0
- package/template/src/components/ui/alert-dialog/AlertDialog.vue +15 -0
- package/template/src/components/ui/alert-dialog/AlertDialogAction.vue +18 -0
- package/template/src/components/ui/alert-dialog/AlertDialogCancel.vue +25 -0
- package/template/src/components/ui/alert-dialog/AlertDialogContent.vue +44 -0
- package/template/src/components/ui/alert-dialog/AlertDialogDescription.vue +23 -0
- package/template/src/components/ui/alert-dialog/AlertDialogFooter.vue +22 -0
- package/template/src/components/ui/alert-dialog/AlertDialogHeader.vue +17 -0
- package/template/src/components/ui/alert-dialog/AlertDialogTitle.vue +21 -0
- package/template/src/components/ui/alert-dialog/AlertDialogTrigger.vue +12 -0
- package/template/src/components/ui/alert-dialog/index.ts +9 -0
- package/template/src/components/ui/avatar/Avatar.vue +50 -0
- package/template/src/components/ui/avatar/AvatarFallback.vue +21 -0
- package/template/src/components/ui/avatar/AvatarGroup.vue +69 -0
- package/template/src/components/ui/avatar/AvatarImage.vue +16 -0
- package/template/src/components/ui/avatar/index.ts +4 -0
- package/template/src/components/ui/badge/Badge.vue +40 -0
- package/template/src/components/ui/badge/index.ts +45 -0
- package/template/src/components/ui/breadcrumb/Breadcrumb.vue +17 -0
- package/template/src/components/ui/breadcrumb/BreadcrumbEllipsis.vue +23 -0
- package/template/src/components/ui/breadcrumb/BreadcrumbItem.vue +17 -0
- package/template/src/components/ui/breadcrumb/BreadcrumbLink.vue +21 -0
- package/template/src/components/ui/breadcrumb/BreadcrumbList.vue +17 -0
- package/template/src/components/ui/breadcrumb/BreadcrumbPage.vue +20 -0
- package/template/src/components/ui/breadcrumb/BreadcrumbSeparator.vue +22 -0
- package/template/src/components/ui/breadcrumb/index.ts +7 -0
- package/template/src/components/ui/button/Button.spec.ts +46 -0
- package/template/src/components/ui/button/Button.vue +31 -0
- package/template/src/components/ui/button/index.ts +41 -0
- package/template/src/components/ui/calendar/Calendar.vue +106 -0
- package/template/src/components/ui/calendar/index.ts +1 -0
- package/template/src/components/ui/card/Card.vue +29 -0
- package/template/src/components/ui/card/CardAction.vue +17 -0
- package/template/src/components/ui/card/CardContent.vue +17 -0
- package/template/src/components/ui/card/CardDescription.vue +17 -0
- package/template/src/components/ui/card/CardFooter.vue +17 -0
- package/template/src/components/ui/card/CardHeader.vue +29 -0
- package/template/src/components/ui/card/CardTitle.vue +17 -0
- package/template/src/components/ui/card/index.ts +7 -0
- package/template/src/components/ui/chart/AreaChart.vue +143 -0
- package/template/src/components/ui/chart/BarChart.vue +142 -0
- package/template/src/components/ui/chart/DonutChart.vue +75 -0
- package/template/src/components/ui/chart/LineChart.vue +132 -0
- package/template/src/components/ui/chart/index.ts +4 -0
- package/template/src/components/ui/checkbox/Checkbox.vue +57 -0
- package/template/src/components/ui/checkbox/index.ts +1 -0
- package/template/src/components/ui/collapsible/Collapsible.vue +15 -0
- package/template/src/components/ui/collapsible/CollapsibleContent.vue +18 -0
- package/template/src/components/ui/collapsible/CollapsibleTrigger.vue +12 -0
- package/template/src/components/ui/collapsible/index.ts +3 -0
- package/template/src/components/ui/command/Command.vue +88 -0
- package/template/src/components/ui/command/CommandDialog.vue +31 -0
- package/template/src/components/ui/command/CommandEmpty.vue +27 -0
- package/template/src/components/ui/command/CommandGroup.vue +45 -0
- package/template/src/components/ui/command/CommandInput.vue +39 -0
- package/template/src/components/ui/command/CommandItem.vue +76 -0
- package/template/src/components/ui/command/CommandList.vue +25 -0
- package/template/src/components/ui/command/CommandSeparator.vue +21 -0
- package/template/src/components/ui/command/CommandShortcut.vue +17 -0
- package/template/src/components/ui/command/index.ts +25 -0
- package/template/src/components/ui/context-menu/ContextMenu.vue +15 -0
- package/template/src/components/ui/context-menu/ContextMenuCheckboxItem.vue +36 -0
- package/template/src/components/ui/context-menu/ContextMenuContent.vue +33 -0
- package/template/src/components/ui/context-menu/ContextMenuGroup.vue +12 -0
- package/template/src/components/ui/context-menu/ContextMenuItem.vue +36 -0
- package/template/src/components/ui/context-menu/ContextMenuLabel.vue +25 -0
- package/template/src/components/ui/context-menu/ContextMenuRadioGroup.vue +18 -0
- package/template/src/components/ui/context-menu/ContextMenuRadioItem.vue +36 -0
- package/template/src/components/ui/context-menu/ContextMenuSeparator.vue +18 -0
- package/template/src/components/ui/context-menu/ContextMenuShortcut.vue +17 -0
- package/template/src/components/ui/context-menu/ContextMenuSub.vue +15 -0
- package/template/src/components/ui/context-menu/ContextMenuSubContent.vue +36 -0
- package/template/src/components/ui/context-menu/ContextMenuSubTrigger.vue +32 -0
- package/template/src/components/ui/context-menu/ContextMenuTrigger.vue +13 -0
- package/template/src/components/ui/context-menu/index.ts +14 -0
- package/template/src/components/ui/date-picker/DatePicker.vue +233 -0
- package/template/src/components/ui/date-picker/index.ts +1 -0
- package/template/src/components/ui/dialog/Dialog.vue +19 -0
- package/template/src/components/ui/dialog/DialogClose.vue +15 -0
- package/template/src/components/ui/dialog/DialogContent.vue +53 -0
- package/template/src/components/ui/dialog/DialogDescription.vue +23 -0
- package/template/src/components/ui/dialog/DialogFooter.vue +27 -0
- package/template/src/components/ui/dialog/DialogHeader.vue +17 -0
- package/template/src/components/ui/dialog/DialogOverlay.vue +21 -0
- package/template/src/components/ui/dialog/DialogScrollContent.vue +59 -0
- package/template/src/components/ui/dialog/DialogTitle.vue +23 -0
- package/template/src/components/ui/dialog/DialogTrigger.vue +15 -0
- package/template/src/components/ui/dialog/index.ts +10 -0
- package/template/src/components/ui/dropdown-menu/DropdownMenu.vue +19 -0
- package/template/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +39 -0
- package/template/src/components/ui/dropdown-menu/DropdownMenuContent.vue +41 -0
- package/template/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +15 -0
- package/template/src/components/ui/dropdown-menu/DropdownMenuItem.vue +31 -0
- package/template/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +23 -0
- package/template/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +21 -0
- package/template/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +40 -0
- package/template/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +23 -0
- package/template/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +17 -0
- package/template/src/components/ui/dropdown-menu/DropdownMenuSub.vue +18 -0
- package/template/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +27 -0
- package/template/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +31 -0
- package/template/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +17 -0
- package/template/src/components/ui/dropdown-menu/index.ts +16 -0
- package/template/src/components/ui/form/FormControl.vue +17 -0
- package/template/src/components/ui/form/FormDescription.vue +21 -0
- package/template/src/components/ui/form/FormItem.vue +23 -0
- package/template/src/components/ui/form/FormLabel.vue +25 -0
- package/template/src/components/ui/form/FormMessage.vue +23 -0
- package/template/src/components/ui/form/index.ts +7 -0
- package/template/src/components/ui/form/injectionKeys.ts +4 -0
- package/template/src/components/ui/form/useFormField.ts +30 -0
- package/template/src/components/ui/hover-card/HoverCard.vue +19 -0
- package/template/src/components/ui/hover-card/HoverCardContent.vue +43 -0
- package/template/src/components/ui/hover-card/HoverCardTrigger.vue +15 -0
- package/template/src/components/ui/hover-card/index.ts +3 -0
- package/template/src/components/ui/index.ts +47 -0
- package/template/src/components/ui/input/Input.vue +33 -0
- package/template/src/components/ui/input/InputAddon.vue +29 -0
- package/template/src/components/ui/input/InputGroup.vue +17 -0
- package/template/src/components/ui/input/InputIcon.vue +29 -0
- package/template/src/components/ui/input/index.ts +4 -0
- package/template/src/components/ui/kbd/Kbd.vue +30 -0
- package/template/src/components/ui/kbd/index.ts +1 -0
- package/template/src/components/ui/label/Label.vue +26 -0
- package/template/src/components/ui/label/index.ts +1 -0
- package/template/src/components/ui/loading-bar/LoadingBar.vue +41 -0
- package/template/src/components/ui/loading-bar/index.ts +2 -0
- package/template/src/components/ui/pagination/Pagination.vue +26 -0
- package/template/src/components/ui/pagination/PaginationContent.vue +22 -0
- package/template/src/components/ui/pagination/PaginationEllipsis.vue +25 -0
- package/template/src/components/ui/pagination/PaginationFirst.vue +33 -0
- package/template/src/components/ui/pagination/PaginationItem.vue +34 -0
- package/template/src/components/ui/pagination/PaginationLast.vue +33 -0
- package/template/src/components/ui/pagination/PaginationNext.vue +33 -0
- package/template/src/components/ui/pagination/PaginationPrevious.vue +33 -0
- package/template/src/components/ui/pagination/index.ts +8 -0
- package/template/src/components/ui/pin-input/PinInput.vue +28 -0
- package/template/src/components/ui/pin-input/PinInputGroup.vue +18 -0
- package/template/src/components/ui/pin-input/PinInputInput.vue +24 -0
- package/template/src/components/ui/pin-input/PinInputSeparator.vue +21 -0
- package/template/src/components/ui/pin-input/index.ts +4 -0
- package/template/src/components/ui/popover/Popover.vue +19 -0
- package/template/src/components/ui/popover/PopoverAnchor.vue +15 -0
- package/template/src/components/ui/popover/PopoverContent.vue +45 -0
- package/template/src/components/ui/popover/PopoverTrigger.vue +15 -0
- package/template/src/components/ui/popover/index.ts +4 -0
- package/template/src/components/ui/progress/Progress.vue +35 -0
- package/template/src/components/ui/progress/index.ts +1 -0
- package/template/src/components/ui/radio-group/RadioGroup.vue +23 -0
- package/template/src/components/ui/radio-group/RadioGroupItem.vue +30 -0
- package/template/src/components/ui/radio-group/index.ts +2 -0
- package/template/src/components/ui/scroll-area/ScrollArea.vue +40 -0
- package/template/src/components/ui/scroll-area/ScrollBar.vue +44 -0
- package/template/src/components/ui/scroll-area/index.ts +2 -0
- package/template/src/components/ui/select/Select.vue +19 -0
- package/template/src/components/ui/select/SelectContent.vue +51 -0
- package/template/src/components/ui/select/SelectGroup.vue +15 -0
- package/template/src/components/ui/select/SelectItem.vue +44 -0
- package/template/src/components/ui/select/SelectItemText.vue +15 -0
- package/template/src/components/ui/select/SelectLabel.vue +17 -0
- package/template/src/components/ui/select/SelectScrollDownButton.vue +26 -0
- package/template/src/components/ui/select/SelectScrollUpButton.vue +26 -0
- package/template/src/components/ui/select/SelectSeparator.vue +19 -0
- package/template/src/components/ui/select/SelectTrigger.vue +33 -0
- package/template/src/components/ui/select/SelectValue.vue +15 -0
- package/template/src/components/ui/select/index.ts +11 -0
- package/template/src/components/ui/separator/Separator.vue +29 -0
- package/template/src/components/ui/separator/index.ts +1 -0
- package/template/src/components/ui/sheet/Sheet.vue +19 -0
- package/template/src/components/ui/sheet/SheetClose.vue +15 -0
- package/template/src/components/ui/sheet/SheetContent.vue +62 -0
- package/template/src/components/ui/sheet/SheetDescription.vue +21 -0
- package/template/src/components/ui/sheet/SheetFooter.vue +16 -0
- package/template/src/components/ui/sheet/SheetHeader.vue +15 -0
- package/template/src/components/ui/sheet/SheetOverlay.vue +21 -0
- package/template/src/components/ui/sheet/SheetTitle.vue +21 -0
- package/template/src/components/ui/sheet/SheetTrigger.vue +15 -0
- package/template/src/components/ui/sheet/index.ts +8 -0
- package/template/src/components/ui/sidebar/Sidebar.vue +113 -0
- package/template/src/components/ui/sidebar/SidebarContent.vue +18 -0
- package/template/src/components/ui/sidebar/SidebarControl.vue +53 -0
- package/template/src/components/ui/sidebar/SidebarFooter.vue +18 -0
- package/template/src/components/ui/sidebar/SidebarGroup.vue +18 -0
- package/template/src/components/ui/sidebar/SidebarGroupAction.vue +27 -0
- package/template/src/components/ui/sidebar/SidebarGroupContent.vue +18 -0
- package/template/src/components/ui/sidebar/SidebarGroupLabel.vue +25 -0
- package/template/src/components/ui/sidebar/SidebarHeader.vue +18 -0
- package/template/src/components/ui/sidebar/SidebarInput.vue +22 -0
- package/template/src/components/ui/sidebar/SidebarInset.vue +21 -0
- package/template/src/components/ui/sidebar/SidebarMenu.vue +18 -0
- package/template/src/components/ui/sidebar/SidebarMenuAction.vue +35 -0
- package/template/src/components/ui/sidebar/SidebarMenuBadge.vue +26 -0
- package/template/src/components/ui/sidebar/SidebarMenuButton.vue +48 -0
- package/template/src/components/ui/sidebar/SidebarMenuButtonChild.vue +36 -0
- package/template/src/components/ui/sidebar/SidebarMenuItem.vue +18 -0
- package/template/src/components/ui/sidebar/SidebarMenuSkeleton.vue +35 -0
- package/template/src/components/ui/sidebar/SidebarMenuSub.vue +22 -0
- package/template/src/components/ui/sidebar/SidebarMenuSubButton.vue +36 -0
- package/template/src/components/ui/sidebar/SidebarMenuSubItem.vue +18 -0
- package/template/src/components/ui/sidebar/SidebarProvider.vue +136 -0
- package/template/src/components/ui/sidebar/SidebarRail.vue +33 -0
- package/template/src/components/ui/sidebar/SidebarSeparator.vue +19 -0
- package/template/src/components/ui/sidebar/SidebarTrigger.vue +27 -0
- package/template/src/components/ui/sidebar/index.ts +61 -0
- package/template/src/components/ui/sidebar/utils.ts +27 -0
- package/template/src/components/ui/skeleton/Skeleton.vue +17 -0
- package/template/src/components/ui/skeleton/index.ts +1 -0
- package/template/src/components/ui/slider/Slider.vue +42 -0
- package/template/src/components/ui/slider/index.ts +1 -0
- package/template/src/components/ui/sonner/Sonner.vue +42 -0
- package/template/src/components/ui/sonner/index.ts +2 -0
- package/template/src/components/ui/stepper/Stepper.vue +102 -0
- package/template/src/components/ui/stepper/index.ts +1 -0
- package/template/src/components/ui/switch/Switch.vue +38 -0
- package/template/src/components/ui/switch/index.ts +1 -0
- package/template/src/components/ui/table/Table.vue +16 -0
- package/template/src/components/ui/table/TableBody.vue +17 -0
- package/template/src/components/ui/table/TableCaption.vue +17 -0
- package/template/src/components/ui/table/TableCell.vue +22 -0
- package/template/src/components/ui/table/TableEmpty.vue +34 -0
- package/template/src/components/ui/table/TableFooter.vue +17 -0
- package/template/src/components/ui/table/TableHead.vue +17 -0
- package/template/src/components/ui/table/TableHeader.vue +17 -0
- package/template/src/components/ui/table/TableRow.vue +17 -0
- package/template/src/components/ui/table/index.ts +9 -0
- package/template/src/components/ui/table/utils.ts +10 -0
- package/template/src/components/ui/tabs/Tabs.vue +24 -0
- package/template/src/components/ui/tabs/TabsContent.vue +21 -0
- package/template/src/components/ui/tabs/TabsList.vue +24 -0
- package/template/src/components/ui/tabs/TabsTrigger.vue +26 -0
- package/template/src/components/ui/tabs/index.ts +4 -0
- package/template/src/components/ui/textarea/Textarea.vue +28 -0
- package/template/src/components/ui/textarea/index.ts +1 -0
- package/template/src/components/ui/timeline/Timeline.vue +73 -0
- package/template/src/components/ui/timeline/index.ts +1 -0
- package/template/src/components/ui/toggle/Toggle.vue +35 -0
- package/template/src/components/ui/toggle/index.ts +28 -0
- package/template/src/components/ui/toggle-group/ToggleGroup.vue +41 -0
- package/template/src/components/ui/toggle-group/ToggleGroupItem.vue +43 -0
- package/template/src/components/ui/toggle-group/index.ts +12 -0
- package/template/src/components/ui/tooltip/Tooltip.vue +19 -0
- package/template/src/components/ui/tooltip/TooltipContent.vue +34 -0
- package/template/src/components/ui/tooltip/TooltipProvider.vue +14 -0
- package/template/src/components/ui/tooltip/TooltipTrigger.vue +15 -0
- package/template/src/components/ui/tooltip/index.ts +4 -0
- package/template/src/components.d.ts +228 -0
- package/template/src/composables/useFormatter.ts +105 -0
- package/template/src/composables/useThemeConfig.ts +252 -0
- package/template/src/layouts/AdminLayout.vue +55 -0
- package/template/src/layouts/AuthLayout.vue +42 -0
- package/template/src/lib/axios.spec.ts +46 -0
- package/template/src/lib/axios.ts +243 -0
- package/template/src/lib/formatters.spec.ts +137 -0
- package/template/src/lib/formatters.ts +332 -0
- package/template/src/lib/loading-bar.ts +103 -0
- package/template/src/lib/utils.spec.ts +26 -0
- package/template/src/lib/utils.ts +7 -0
- package/template/src/lib/validation.spec.ts +78 -0
- package/template/src/main.ts +13 -0
- package/template/src/router/index.ts +147 -0
- package/template/src/services/auth.service.spec.ts +93 -0
- package/template/src/services/auth.service.ts +243 -0
- package/template/src/services/index.ts +3 -0
- package/template/src/services/types.ts +28 -0
- package/template/src/services/user.service.spec.ts +91 -0
- package/template/src/services/user.service.ts +283 -0
- package/template/src/stores/auth.spec.ts +52 -0
- package/template/src/stores/auth.ts +130 -0
- package/template/src/style.css +587 -0
- package/template/src/views/_starter/BlankView.vue +47 -0
- package/template/src/views/auth/ConfirmEmailView.vue +209 -0
- package/template/src/views/auth/ForgotPasswordView.vue +151 -0
- package/template/src/views/auth/LockScreenView.vue +175 -0
- package/template/src/views/auth/LoginView.vue +54 -0
- package/template/src/views/auth/RegisterView.vue +265 -0
- package/template/src/views/auth/ResetPasswordView.vue +260 -0
- package/template/src/views/auth/VerifyOtpView.vue +261 -0
- package/template/src/views/dashboard/IndexView.vue +159 -0
- package/template/src/views/errors/ComingSoonView.vue +241 -0
- package/template/src/views/errors/MaintenanceView.vue +252 -0
- package/template/src/views/errors/NotFoundView.vue +81 -0
- package/template/src/views/errors/ServerErrorView.vue +101 -0
- package/template/src/views/errors/UnauthorizedView.vue +64 -0
- package/template/tsconfig.app.json +27 -0
- package/template/tsconfig.json +19 -0
- package/template/tsconfig.node.json +23 -0
- package/template/vite.config.ts +46 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, computed } from 'vue'
|
|
3
|
+
import { useRouter } from 'vue-router'
|
|
4
|
+
import { useForm } from 'vee-validate'
|
|
5
|
+
import { toTypedSchema } from '@vee-validate/zod'
|
|
6
|
+
import * as z from 'zod'
|
|
7
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from '@/components/ui/card'
|
|
8
|
+
import { Button } from '@/components/ui/button'
|
|
9
|
+
import { Input } from '@/components/ui/input'
|
|
10
|
+
import {
|
|
11
|
+
FormControl,
|
|
12
|
+
FormField,
|
|
13
|
+
FormItem,
|
|
14
|
+
FormLabel,
|
|
15
|
+
FormMessage,
|
|
16
|
+
} from '@/components/ui/form'
|
|
17
|
+
import { Eye, EyeOff, Check, X, Lock, Mail, ShieldCheck, ArrowRight } from '@lucide/vue'
|
|
18
|
+
|
|
19
|
+
const router = useRouter()
|
|
20
|
+
const showPassword = ref(false)
|
|
21
|
+
const showConfirmPassword = ref(false)
|
|
22
|
+
const isSubmitting = ref(false)
|
|
23
|
+
|
|
24
|
+
// Zod Schema with modern password security requirements
|
|
25
|
+
const registerSchema = z
|
|
26
|
+
.object({
|
|
27
|
+
email: z
|
|
28
|
+
.string()
|
|
29
|
+
.min(1, 'Email address is required')
|
|
30
|
+
.email('Please enter a valid email address'),
|
|
31
|
+
password: z
|
|
32
|
+
.string()
|
|
33
|
+
.min(8, 'Password must be at least 8 characters')
|
|
34
|
+
.regex(/[a-z]/, 'Must contain at least one lowercase letter')
|
|
35
|
+
.regex(/[A-Z]/, 'Must contain at least one uppercase letter')
|
|
36
|
+
.regex(/[0-9]/, 'Must contain at least one number')
|
|
37
|
+
.regex(/[^A-Za-z0-9]/, 'Must contain at least one special character'),
|
|
38
|
+
confirmPassword: z
|
|
39
|
+
.string()
|
|
40
|
+
.min(1, 'Please confirm your password'),
|
|
41
|
+
})
|
|
42
|
+
.refine((data) => data.password === data.confirmPassword, {
|
|
43
|
+
message: 'Passwords do not match',
|
|
44
|
+
path: ['confirmPassword'],
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const form = useForm({
|
|
48
|
+
validationSchema: toTypedSchema(registerSchema),
|
|
49
|
+
initialValues: {
|
|
50
|
+
email: '',
|
|
51
|
+
password: '',
|
|
52
|
+
confirmPassword: '',
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// Password criteria checks
|
|
57
|
+
const passwordValue = computed(() => form.values.password || '')
|
|
58
|
+
|
|
59
|
+
const criteria = computed(() => [
|
|
60
|
+
{
|
|
61
|
+
id: 'length',
|
|
62
|
+
label: 'At least 8 characters',
|
|
63
|
+
met: passwordValue.value.length >= 8,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'lowercase',
|
|
67
|
+
label: 'One lowercase letter',
|
|
68
|
+
met: /[a-z]/.test(passwordValue.value),
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 'uppercase',
|
|
72
|
+
label: 'One uppercase letter',
|
|
73
|
+
met: /[A-Z]/.test(passwordValue.value),
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'number',
|
|
77
|
+
label: 'One number (0-9)',
|
|
78
|
+
met: /[0-9]/.test(passwordValue.value),
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'special',
|
|
82
|
+
label: 'One special character (!@#$%...)',
|
|
83
|
+
met: /[^A-Za-z0-9]/.test(passwordValue.value),
|
|
84
|
+
},
|
|
85
|
+
])
|
|
86
|
+
|
|
87
|
+
// Password strength calculation
|
|
88
|
+
const strengthScore = computed(() => {
|
|
89
|
+
if (!passwordValue.value) return 0
|
|
90
|
+
return criteria.value.filter((c) => c.met).length
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const strengthConfig = computed(() => {
|
|
94
|
+
const score = strengthScore.value
|
|
95
|
+
if (score === 0) return { label: 'Empty', color: 'bg-muted', textClass: 'text-muted-foreground', percent: 0 }
|
|
96
|
+
if (score <= 2) return { label: 'Weak', color: 'bg-destructive', textClass: 'text-destructive', percent: 25 }
|
|
97
|
+
if (score === 3) return { label: 'Fair', color: 'bg-amber-500', textClass: 'text-amber-500', percent: 50 }
|
|
98
|
+
if (score === 4) return { label: 'Good', color: 'bg-blue-500', textClass: 'text-blue-500', percent: 75 }
|
|
99
|
+
return { label: 'Strong', color: 'bg-emerald-500', textClass: 'text-emerald-500', percent: 100 }
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const onSubmit = form.handleSubmit(() => {
|
|
103
|
+
isSubmitting.value = true
|
|
104
|
+
// Simulating registration action
|
|
105
|
+
setTimeout(() => {
|
|
106
|
+
isSubmitting.value = false
|
|
107
|
+
router.push('/auth/login')
|
|
108
|
+
}, 1000)
|
|
109
|
+
})
|
|
110
|
+
</script>
|
|
111
|
+
|
|
112
|
+
<template>
|
|
113
|
+
<Card flush class="w-full max-w-md shadow-xl border-border/80 bg-card">
|
|
114
|
+
<CardHeader class="pt-8 pb-6 px-6 sm:px-8 space-y-1.5 text-center">
|
|
115
|
+
<div class="mx-auto mb-2 flex h-12 w-12 items-center justify-center rounded-xl bg-primary/10 text-primary">
|
|
116
|
+
<ShieldCheck class="h-6 w-6" />
|
|
117
|
+
</div>
|
|
118
|
+
<CardTitle class="text-2xl font-bold tracking-tight">Create an account</CardTitle>
|
|
119
|
+
<CardDescription class="text-sm">
|
|
120
|
+
Enter your email and create a secure password to register
|
|
121
|
+
</CardDescription>
|
|
122
|
+
</CardHeader>
|
|
123
|
+
|
|
124
|
+
<CardContent class="px-6 sm:px-8 pb-6">
|
|
125
|
+
<form @submit.prevent="onSubmit" class="space-y-4" novalidate>
|
|
126
|
+
<!-- Email Field -->
|
|
127
|
+
<FormField v-slot="{ componentField }" name="email">
|
|
128
|
+
<FormItem class="space-y-1.5">
|
|
129
|
+
<FormLabel class="text-xs font-semibold">Email address</FormLabel>
|
|
130
|
+
<FormControl>
|
|
131
|
+
<InputGroup>
|
|
132
|
+
<InputIcon>
|
|
133
|
+
<Mail />
|
|
134
|
+
</InputIcon>
|
|
135
|
+
<Input
|
|
136
|
+
type="email"
|
|
137
|
+
placeholder="name@example.com"
|
|
138
|
+
autocomplete="email"
|
|
139
|
+
class="pl-9"
|
|
140
|
+
v-bind="componentField"
|
|
141
|
+
/>
|
|
142
|
+
</InputGroup>
|
|
143
|
+
</FormControl>
|
|
144
|
+
<FormMessage class="text-xs" />
|
|
145
|
+
</FormItem>
|
|
146
|
+
</FormField>
|
|
147
|
+
|
|
148
|
+
<!-- Password Field -->
|
|
149
|
+
<FormField v-slot="{ componentField }" name="password">
|
|
150
|
+
<FormItem class="space-y-1.5">
|
|
151
|
+
<FormLabel class="text-xs font-semibold">Password</FormLabel>
|
|
152
|
+
<FormControl>
|
|
153
|
+
<InputGroup>
|
|
154
|
+
<InputIcon>
|
|
155
|
+
<Lock />
|
|
156
|
+
</InputIcon>
|
|
157
|
+
<Input
|
|
158
|
+
:type="showPassword ? 'text' : 'password'"
|
|
159
|
+
placeholder="Create a strong password"
|
|
160
|
+
autocomplete="new-password"
|
|
161
|
+
class="pl-9 pr-10"
|
|
162
|
+
v-bind="componentField"
|
|
163
|
+
/>
|
|
164
|
+
<button
|
|
165
|
+
type="button"
|
|
166
|
+
@click="showPassword = !showPassword"
|
|
167
|
+
class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors cursor-pointer"
|
|
168
|
+
:aria-label="showPassword ? 'Hide password' : 'Show password'"
|
|
169
|
+
>
|
|
170
|
+
<EyeOff v-if="showPassword" class="h-4 w-4" />
|
|
171
|
+
<Eye v-else class="h-4 w-4" />
|
|
172
|
+
</button>
|
|
173
|
+
</InputGroup>
|
|
174
|
+
</FormControl>
|
|
175
|
+
|
|
176
|
+
<!-- Password Strength Bar & Label -->
|
|
177
|
+
<div v-if="passwordValue.length > 0" class="pt-1.5 space-y-2">
|
|
178
|
+
<div class="flex items-center justify-between text-xs">
|
|
179
|
+
<span class="text-muted-foreground">Password strength:</span>
|
|
180
|
+
<span :class="['font-semibold transition-colors', strengthConfig.textClass]">
|
|
181
|
+
{{ strengthConfig.label }}
|
|
182
|
+
</span>
|
|
183
|
+
</div>
|
|
184
|
+
<div class="h-1.5 w-full overflow-hidden rounded-full bg-muted">
|
|
185
|
+
<div
|
|
186
|
+
class="h-full transition-all duration-300 rounded-full"
|
|
187
|
+
:class="strengthConfig.color"
|
|
188
|
+
:style="{ width: `${strengthConfig.percent}%` }"
|
|
189
|
+
></div>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<!-- Security Criteria Checklist -->
|
|
193
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-1.5 pt-1 text-[11px]">
|
|
194
|
+
<div
|
|
195
|
+
v-for="item in criteria"
|
|
196
|
+
:key="item.id"
|
|
197
|
+
class="flex items-center gap-1.5 transition-colors"
|
|
198
|
+
:class="item.met ? 'text-emerald-600 dark:text-emerald-400 font-medium' : 'text-muted-foreground'"
|
|
199
|
+
>
|
|
200
|
+
<Check v-if="item.met" class="h-3.5 w-3.5 shrink-0 text-emerald-500" />
|
|
201
|
+
<X v-else class="h-3.5 w-3.5 shrink-0 text-muted-foreground/60" />
|
|
202
|
+
<span>{{ item.label }}</span>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<FormMessage class="text-xs" />
|
|
208
|
+
</FormItem>
|
|
209
|
+
</FormField>
|
|
210
|
+
|
|
211
|
+
<!-- Confirm Password Field -->
|
|
212
|
+
<FormField v-slot="{ componentField }" name="confirmPassword">
|
|
213
|
+
<FormItem class="space-y-1.5">
|
|
214
|
+
<FormLabel class="text-xs font-semibold">Confirm Password</FormLabel>
|
|
215
|
+
<FormControl>
|
|
216
|
+
<InputGroup>
|
|
217
|
+
<InputIcon>
|
|
218
|
+
<Lock />
|
|
219
|
+
</InputIcon>
|
|
220
|
+
<Input
|
|
221
|
+
:type="showConfirmPassword ? 'text' : 'password'"
|
|
222
|
+
placeholder="Repeat your password"
|
|
223
|
+
autocomplete="new-password"
|
|
224
|
+
class="pl-9 pr-10"
|
|
225
|
+
v-bind="componentField"
|
|
226
|
+
/>
|
|
227
|
+
<button
|
|
228
|
+
type="button"
|
|
229
|
+
@click="showConfirmPassword = !showConfirmPassword"
|
|
230
|
+
class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors cursor-pointer"
|
|
231
|
+
:aria-label="showConfirmPassword ? 'Hide password' : 'Show password'"
|
|
232
|
+
>
|
|
233
|
+
<EyeOff v-if="showConfirmPassword" class="h-4 w-4" />
|
|
234
|
+
<Eye v-else class="h-4 w-4" />
|
|
235
|
+
</button>
|
|
236
|
+
</InputGroup>
|
|
237
|
+
</FormControl>
|
|
238
|
+
<FormMessage class="text-xs" />
|
|
239
|
+
</FormItem>
|
|
240
|
+
</FormField>
|
|
241
|
+
|
|
242
|
+
<!-- Submit Button -->
|
|
243
|
+
<Button type="submit" class="w-full mt-2 font-medium cursor-pointer" :disabled="isSubmitting">
|
|
244
|
+
<span v-if="isSubmitting">Creating account...</span>
|
|
245
|
+
<span v-else class="flex items-center justify-center gap-2">
|
|
246
|
+
Create Account
|
|
247
|
+
<ArrowRight class="h-4 w-4" />
|
|
248
|
+
</span>
|
|
249
|
+
</Button>
|
|
250
|
+
</form>
|
|
251
|
+
</CardContent>
|
|
252
|
+
|
|
253
|
+
<CardFooter class="border-t border-border/60 bg-muted/30 px-6 py-4 flex flex-col items-center justify-center text-center">
|
|
254
|
+
<p class="text-xs text-muted-foreground">
|
|
255
|
+
Already have an account?
|
|
256
|
+
<router-link
|
|
257
|
+
to="/auth/login"
|
|
258
|
+
class="font-medium text-primary hover:underline transition-colors ml-1"
|
|
259
|
+
>
|
|
260
|
+
Sign in
|
|
261
|
+
</router-link>
|
|
262
|
+
</p>
|
|
263
|
+
</CardFooter>
|
|
264
|
+
</Card>
|
|
265
|
+
</template>
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, computed } from 'vue'
|
|
3
|
+
import { useForm } from 'vee-validate'
|
|
4
|
+
import { toTypedSchema } from '@vee-validate/zod'
|
|
5
|
+
import * as z from 'zod'
|
|
6
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from '@/components/ui/card'
|
|
7
|
+
import { Button } from '@/components/ui/button'
|
|
8
|
+
import { Input } from '@/components/ui/input'
|
|
9
|
+
import {
|
|
10
|
+
FormControl,
|
|
11
|
+
FormField,
|
|
12
|
+
FormItem,
|
|
13
|
+
FormLabel,
|
|
14
|
+
FormMessage,
|
|
15
|
+
} from '@/components/ui/form'
|
|
16
|
+
import { Eye, EyeOff, Check, X, Lock, KeyRound, CheckCircle2, ArrowRight, ArrowLeft } from '@lucide/vue'
|
|
17
|
+
|
|
18
|
+
const showPassword = ref(false)
|
|
19
|
+
const showConfirmPassword = ref(false)
|
|
20
|
+
const isSubmitting = ref(false)
|
|
21
|
+
const isResetComplete = ref(false)
|
|
22
|
+
|
|
23
|
+
// Zod Schema with modern password security requirements
|
|
24
|
+
const resetPasswordSchema = z
|
|
25
|
+
.object({
|
|
26
|
+
password: z
|
|
27
|
+
.string()
|
|
28
|
+
.min(8, 'Password must be at least 8 characters')
|
|
29
|
+
.regex(/[a-z]/, 'Must contain at least one lowercase letter')
|
|
30
|
+
.regex(/[A-Z]/, 'Must contain at least one uppercase letter')
|
|
31
|
+
.regex(/[0-9]/, 'Must contain at least one number')
|
|
32
|
+
.regex(/[^A-Za-z0-9]/, 'Must contain at least one special character'),
|
|
33
|
+
confirmPassword: z
|
|
34
|
+
.string()
|
|
35
|
+
.min(1, 'Please confirm your new password'),
|
|
36
|
+
})
|
|
37
|
+
.refine((data) => data.password === data.confirmPassword, {
|
|
38
|
+
message: 'Passwords do not match',
|
|
39
|
+
path: ['confirmPassword'],
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const form = useForm({
|
|
43
|
+
validationSchema: toTypedSchema(resetPasswordSchema),
|
|
44
|
+
initialValues: {
|
|
45
|
+
password: '',
|
|
46
|
+
confirmPassword: '',
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// Password criteria checks
|
|
51
|
+
const passwordValue = computed(() => form.values.password || '')
|
|
52
|
+
|
|
53
|
+
const criteria = computed(() => [
|
|
54
|
+
{
|
|
55
|
+
id: 'length',
|
|
56
|
+
label: 'At least 8 characters',
|
|
57
|
+
met: passwordValue.value.length >= 8,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'lowercase',
|
|
61
|
+
label: 'One lowercase letter',
|
|
62
|
+
met: /[a-z]/.test(passwordValue.value),
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'uppercase',
|
|
66
|
+
label: 'One uppercase letter',
|
|
67
|
+
met: /[A-Z]/.test(passwordValue.value),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 'number',
|
|
71
|
+
label: 'One number (0-9)',
|
|
72
|
+
met: /[0-9]/.test(passwordValue.value),
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'special',
|
|
76
|
+
label: 'One special character (!@#$%...)',
|
|
77
|
+
met: /[^A-Za-z0-9]/.test(passwordValue.value),
|
|
78
|
+
},
|
|
79
|
+
])
|
|
80
|
+
|
|
81
|
+
// Password strength calculation
|
|
82
|
+
const strengthScore = computed(() => {
|
|
83
|
+
if (!passwordValue.value) return 0
|
|
84
|
+
return criteria.value.filter((c) => c.met).length
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const strengthConfig = computed(() => {
|
|
88
|
+
const score = strengthScore.value
|
|
89
|
+
if (score === 0) return { label: 'Empty', color: 'bg-muted', textClass: 'text-muted-foreground', percent: 0 }
|
|
90
|
+
if (score <= 2) return { label: 'Weak', color: 'bg-destructive', textClass: 'text-destructive', percent: 25 }
|
|
91
|
+
if (score === 3) return { label: 'Fair', color: 'bg-amber-500', textClass: 'text-amber-500', percent: 50 }
|
|
92
|
+
if (score === 4) return { label: 'Good', color: 'bg-blue-500', textClass: 'text-blue-500', percent: 75 }
|
|
93
|
+
return { label: 'Strong', color: 'bg-emerald-500', textClass: 'text-emerald-500', percent: 100 }
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const onSubmit = form.handleSubmit(() => {
|
|
97
|
+
isSubmitting.value = true
|
|
98
|
+
// Simulating reset password request
|
|
99
|
+
setTimeout(() => {
|
|
100
|
+
isSubmitting.value = false
|
|
101
|
+
isResetComplete.value = true
|
|
102
|
+
}, 1000)
|
|
103
|
+
})
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<template>
|
|
107
|
+
<Card flush class="w-full max-w-md shadow-xl border-border/80 bg-card">
|
|
108
|
+
<!-- Success State -->
|
|
109
|
+
<template v-if="isResetComplete">
|
|
110
|
+
<CardHeader class="pt-8 pb-6 px-6 sm:px-8 space-y-2 text-center">
|
|
111
|
+
<div class="mx-auto mb-2 flex h-12 w-12 items-center justify-center rounded-xl bg-emerald-500/10 text-emerald-600 dark:text-emerald-400">
|
|
112
|
+
<CheckCircle2 class="h-6 w-6" />
|
|
113
|
+
</div>
|
|
114
|
+
<CardTitle class="text-2xl font-bold tracking-tight">Password reset successful</CardTitle>
|
|
115
|
+
<CardDescription class="text-sm">
|
|
116
|
+
Your password has been updated. You can now sign in with your new password.
|
|
117
|
+
</CardDescription>
|
|
118
|
+
</CardHeader>
|
|
119
|
+
|
|
120
|
+
<CardContent class="px-6 sm:px-8 pb-8">
|
|
121
|
+
<Button as-child class="w-full font-medium cursor-pointer">
|
|
122
|
+
<router-link to="/auth/login" class="flex items-center justify-center gap-2">
|
|
123
|
+
Continue to Sign in
|
|
124
|
+
<ArrowRight class="h-4 w-4" />
|
|
125
|
+
</router-link>
|
|
126
|
+
</Button>
|
|
127
|
+
</CardContent>
|
|
128
|
+
</template>
|
|
129
|
+
|
|
130
|
+
<!-- Reset Form State -->
|
|
131
|
+
<template v-else>
|
|
132
|
+
<CardHeader class="pt-8 pb-6 px-6 sm:px-8 space-y-1.5 text-center">
|
|
133
|
+
<div class="mx-auto mb-2 flex h-12 w-12 items-center justify-center rounded-xl bg-primary/10 text-primary">
|
|
134
|
+
<KeyRound class="h-6 w-6" />
|
|
135
|
+
</div>
|
|
136
|
+
<CardTitle class="text-2xl font-bold tracking-tight">Reset password</CardTitle>
|
|
137
|
+
<CardDescription class="text-sm">
|
|
138
|
+
Please enter and confirm your new secure password.
|
|
139
|
+
</CardDescription>
|
|
140
|
+
</CardHeader>
|
|
141
|
+
|
|
142
|
+
<CardContent class="px-6 sm:px-8 pb-6">
|
|
143
|
+
<form @submit.prevent="onSubmit" class="space-y-4" novalidate>
|
|
144
|
+
<!-- New Password Field -->
|
|
145
|
+
<FormField v-slot="{ componentField }" name="password">
|
|
146
|
+
<FormItem class="space-y-1.5">
|
|
147
|
+
<FormLabel class="text-xs font-semibold">New Password</FormLabel>
|
|
148
|
+
<FormControl>
|
|
149
|
+
<InputGroup>
|
|
150
|
+
<InputIcon>
|
|
151
|
+
<Lock class="h-4 w-4" />
|
|
152
|
+
</InputIcon>
|
|
153
|
+
<Input
|
|
154
|
+
:type="showPassword ? 'text' : 'password'"
|
|
155
|
+
placeholder="Enter new password"
|
|
156
|
+
autocomplete="new-password"
|
|
157
|
+
class="pl-9 pr-10"
|
|
158
|
+
v-bind="componentField"
|
|
159
|
+
/>
|
|
160
|
+
<button
|
|
161
|
+
type="button"
|
|
162
|
+
@click="showPassword = !showPassword"
|
|
163
|
+
class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors cursor-pointer"
|
|
164
|
+
:aria-label="showPassword ? 'Hide password' : 'Show password'"
|
|
165
|
+
>
|
|
166
|
+
<EyeOff v-if="showPassword" class="h-4 w-4" />
|
|
167
|
+
<Eye v-else class="h-4 w-4" />
|
|
168
|
+
</button>
|
|
169
|
+
</InputGroup>
|
|
170
|
+
</FormControl>
|
|
171
|
+
|
|
172
|
+
<!-- Password Strength Bar & Label -->
|
|
173
|
+
<div v-if="passwordValue.length > 0" class="pt-1.5 space-y-2">
|
|
174
|
+
<div class="flex items-center justify-between text-xs">
|
|
175
|
+
<span class="text-muted-foreground">Password strength:</span>
|
|
176
|
+
<span :class="['font-semibold transition-colors', strengthConfig.textClass]">
|
|
177
|
+
{{ strengthConfig.label }}
|
|
178
|
+
</span>
|
|
179
|
+
</div>
|
|
180
|
+
<div class="h-1.5 w-full overflow-hidden rounded-full bg-muted">
|
|
181
|
+
<div
|
|
182
|
+
class="h-full transition-all duration-300 rounded-full"
|
|
183
|
+
:class="strengthConfig.color"
|
|
184
|
+
:style="{ width: `${strengthConfig.percent}%` }"
|
|
185
|
+
></div>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<!-- Security Criteria Checklist -->
|
|
189
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-1.5 pt-1 text-[11px]">
|
|
190
|
+
<div
|
|
191
|
+
v-for="item in criteria"
|
|
192
|
+
:key="item.id"
|
|
193
|
+
class="flex items-center gap-1.5 transition-colors"
|
|
194
|
+
:class="item.met ? 'text-emerald-600 dark:text-emerald-400 font-medium' : 'text-muted-foreground'"
|
|
195
|
+
>
|
|
196
|
+
<Check v-if="item.met" class="h-3.5 w-3.5 shrink-0 text-emerald-500" />
|
|
197
|
+
<X v-else class="h-3.5 w-3.5 shrink-0 text-muted-foreground/60" />
|
|
198
|
+
<span>{{ item.label }}</span>
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
|
|
203
|
+
<FormMessage class="text-xs" />
|
|
204
|
+
</FormItem>
|
|
205
|
+
</FormField>
|
|
206
|
+
|
|
207
|
+
<!-- Confirm Password Field -->
|
|
208
|
+
<FormField v-slot="{ componentField }" name="confirmPassword">
|
|
209
|
+
<FormItem class="space-y-1.5">
|
|
210
|
+
<FormLabel class="text-xs font-semibold">Confirm New Password</FormLabel>
|
|
211
|
+
<FormControl>
|
|
212
|
+
<InputGroup>
|
|
213
|
+
<InputIcon>
|
|
214
|
+
<Lock class="h-4 w-4" />
|
|
215
|
+
</InputIcon>
|
|
216
|
+
<Input
|
|
217
|
+
:type="showConfirmPassword ? 'text' : 'password'"
|
|
218
|
+
placeholder="Confirm new password"
|
|
219
|
+
autocomplete="new-password"
|
|
220
|
+
class="pl-9 pr-10"
|
|
221
|
+
v-bind="componentField"
|
|
222
|
+
/>
|
|
223
|
+
<button
|
|
224
|
+
type="button"
|
|
225
|
+
@click="showConfirmPassword = !showConfirmPassword"
|
|
226
|
+
class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors cursor-pointer"
|
|
227
|
+
:aria-label="showConfirmPassword ? 'Hide password' : 'Show password'"
|
|
228
|
+
>
|
|
229
|
+
<EyeOff v-if="showConfirmPassword" class="h-4 w-4" />
|
|
230
|
+
<Eye v-else class="h-4 w-4" />
|
|
231
|
+
</button>
|
|
232
|
+
</InputGroup>
|
|
233
|
+
</FormControl>
|
|
234
|
+
<FormMessage class="text-xs" />
|
|
235
|
+
</FormItem>
|
|
236
|
+
</FormField>
|
|
237
|
+
|
|
238
|
+
<!-- Submit Button -->
|
|
239
|
+
<Button type="submit" class="w-full mt-2 font-medium cursor-pointer" :disabled="isSubmitting">
|
|
240
|
+
<span v-if="isSubmitting">Updating password...</span>
|
|
241
|
+
<span v-else class="flex items-center justify-center gap-2">
|
|
242
|
+
Reset Password
|
|
243
|
+
<ArrowRight class="h-4 w-4" />
|
|
244
|
+
</span>
|
|
245
|
+
</Button>
|
|
246
|
+
</form>
|
|
247
|
+
</CardContent>
|
|
248
|
+
|
|
249
|
+
<CardFooter class="border-t border-border/60 bg-muted/30 px-6 py-4 flex flex-col items-center justify-center text-center">
|
|
250
|
+
<router-link
|
|
251
|
+
to="/auth/login"
|
|
252
|
+
class="inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors"
|
|
253
|
+
>
|
|
254
|
+
<ArrowLeft class="h-3.5 w-3.5" />
|
|
255
|
+
Back to sign in
|
|
256
|
+
</router-link>
|
|
257
|
+
</CardFooter>
|
|
258
|
+
</template>
|
|
259
|
+
</Card>
|
|
260
|
+
</template>
|