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,209 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
3
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from '@/components/ui/card'
|
|
4
|
+
import { Button } from '@/components/ui/button'
|
|
5
|
+
import { MailCheck, CheckCircle2, ArrowRight, ArrowLeft, RotateCw } from '@lucide/vue'
|
|
6
|
+
|
|
7
|
+
const digits = ref<string[]>(['', '', '', '', '', ''])
|
|
8
|
+
const inputRefs = ref<HTMLInputElement[]>([])
|
|
9
|
+
const isVerifying = ref(false)
|
|
10
|
+
const isVerified = ref(false)
|
|
11
|
+
const errorMessage = ref('')
|
|
12
|
+
const countdown = ref(45)
|
|
13
|
+
const isResending = ref(false)
|
|
14
|
+
let timer: ReturnType<typeof setInterval> | null = null
|
|
15
|
+
|
|
16
|
+
const email = 'alex.smith@example.com'
|
|
17
|
+
|
|
18
|
+
const isCodeComplete = computed(() => digits.value.every((d) => d.length === 1))
|
|
19
|
+
|
|
20
|
+
const startCountdown = () => {
|
|
21
|
+
countdown.value = 45
|
|
22
|
+
if (timer) clearInterval(timer)
|
|
23
|
+
timer = setInterval(() => {
|
|
24
|
+
if (countdown.value > 0) {
|
|
25
|
+
countdown.value--
|
|
26
|
+
} else {
|
|
27
|
+
if (timer) clearInterval(timer)
|
|
28
|
+
}
|
|
29
|
+
}, 1000)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
onMounted(() => {
|
|
33
|
+
startCountdown()
|
|
34
|
+
// Focus first input on mount
|
|
35
|
+
setTimeout(() => {
|
|
36
|
+
inputRefs.value[0]?.focus()
|
|
37
|
+
}, 100)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
onUnmounted(() => {
|
|
41
|
+
if (timer) clearInterval(timer)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const handleInput = (index: number, event: Event) => {
|
|
45
|
+
errorMessage.value = ''
|
|
46
|
+
const target = event.target as HTMLInputElement
|
|
47
|
+
const value = target.value.replace(/\D/g, '')
|
|
48
|
+
|
|
49
|
+
// Only take the last entered character if multiple typed
|
|
50
|
+
digits.value[index] = value ? value.slice(-1) : ''
|
|
51
|
+
|
|
52
|
+
if (value && index < 5) {
|
|
53
|
+
inputRefs.value[index + 1]?.focus()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const handleKeyDown = (index: number, event: KeyboardEvent) => {
|
|
58
|
+
if (event.key === 'Backspace' && !digits.value[index] && index > 0) {
|
|
59
|
+
inputRefs.value[index - 1]?.focus()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const handlePaste = (event: ClipboardEvent) => {
|
|
64
|
+
event.preventDefault()
|
|
65
|
+
const pastedData = event.clipboardData?.getData('text') || ''
|
|
66
|
+
const numbers = pastedData.replace(/\D/g, '').slice(0, 6)
|
|
67
|
+
|
|
68
|
+
if (numbers.length > 0) {
|
|
69
|
+
for (let i = 0; i < 6; i++) {
|
|
70
|
+
digits.value[i] = numbers[i] || ''
|
|
71
|
+
}
|
|
72
|
+
const nextIndex = Math.min(numbers.length, 5)
|
|
73
|
+
inputRefs.value[nextIndex]?.focus()
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const handleVerify = () => {
|
|
78
|
+
if (!isCodeComplete.value) return
|
|
79
|
+
isVerifying.value = true
|
|
80
|
+
errorMessage.value = ''
|
|
81
|
+
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
isVerifying.value = false
|
|
84
|
+
const enteredCode = digits.value.join('')
|
|
85
|
+
// Simulate verification
|
|
86
|
+
if (enteredCode === '000000') {
|
|
87
|
+
errorMessage.value = 'Invalid verification code. Please try again.'
|
|
88
|
+
} else {
|
|
89
|
+
isVerified.value = true
|
|
90
|
+
}
|
|
91
|
+
}, 1000)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const handleResend = () => {
|
|
95
|
+
if (countdown.value > 0 || isResending.value) return
|
|
96
|
+
isResending.value = true
|
|
97
|
+
errorMessage.value = ''
|
|
98
|
+
|
|
99
|
+
setTimeout(() => {
|
|
100
|
+
isResending.value = false
|
|
101
|
+
digits.value = ['', '', '', '', '', '']
|
|
102
|
+
inputRefs.value[0]?.focus()
|
|
103
|
+
startCountdown()
|
|
104
|
+
}, 800)
|
|
105
|
+
}
|
|
106
|
+
</script>
|
|
107
|
+
|
|
108
|
+
<template>
|
|
109
|
+
<Card flush class="w-full max-w-md shadow-xl border-border/80 bg-card">
|
|
110
|
+
<!-- Success State -->
|
|
111
|
+
<template v-if="isVerified">
|
|
112
|
+
<CardHeader class="pt-8 pb-6 px-6 sm:px-8 space-y-2 text-center">
|
|
113
|
+
<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">
|
|
114
|
+
<CheckCircle2 class="h-6 w-6" />
|
|
115
|
+
</div>
|
|
116
|
+
<CardTitle class="text-2xl font-bold tracking-tight">Email verified!</CardTitle>
|
|
117
|
+
<CardDescription class="text-sm">
|
|
118
|
+
Your email address has been successfully verified. Welcome to Nala Admin.
|
|
119
|
+
</CardDescription>
|
|
120
|
+
</CardHeader>
|
|
121
|
+
|
|
122
|
+
<CardContent class="px-6 sm:px-8 pb-8">
|
|
123
|
+
<Button as-child class="w-full font-medium cursor-pointer">
|
|
124
|
+
<router-link to="/" class="flex items-center justify-center gap-2">
|
|
125
|
+
Go to Dashboard
|
|
126
|
+
<ArrowRight class="h-4 w-4" />
|
|
127
|
+
</router-link>
|
|
128
|
+
</Button>
|
|
129
|
+
</CardContent>
|
|
130
|
+
</template>
|
|
131
|
+
|
|
132
|
+
<!-- Verification Code Form State -->
|
|
133
|
+
<template v-else>
|
|
134
|
+
<CardHeader class="pt-8 pb-6 px-6 sm:px-8 space-y-1.5 text-center">
|
|
135
|
+
<div class="mx-auto mb-2 flex h-12 w-12 items-center justify-center rounded-xl bg-primary/10 text-primary">
|
|
136
|
+
<MailCheck class="h-6 w-6" />
|
|
137
|
+
</div>
|
|
138
|
+
<CardTitle class="text-2xl font-bold tracking-tight">Verify your email</CardTitle>
|
|
139
|
+
<CardDescription class="text-sm">
|
|
140
|
+
We've sent a 6-digit confirmation code to
|
|
141
|
+
<span class="font-medium text-foreground block mt-0.5">{{ email }}</span>
|
|
142
|
+
</CardDescription>
|
|
143
|
+
</CardHeader>
|
|
144
|
+
|
|
145
|
+
<CardContent class="px-6 sm:px-8 pb-6 space-y-5">
|
|
146
|
+
<!-- 6-digit OTP Inputs -->
|
|
147
|
+
<div class="flex items-center justify-center gap-2 sm:gap-3" @paste="handlePaste">
|
|
148
|
+
<input
|
|
149
|
+
v-for="(_, index) in digits"
|
|
150
|
+
:key="index"
|
|
151
|
+
ref="inputRefs"
|
|
152
|
+
type="text"
|
|
153
|
+
inputmode="numeric"
|
|
154
|
+
pattern="[0-9]*"
|
|
155
|
+
maxlength="1"
|
|
156
|
+
:value="digits[index]"
|
|
157
|
+
class="h-12 w-11 sm:h-13 sm:w-12 rounded-lg border border-input bg-background text-center text-lg font-semibold shadow-xs outline-none transition-all focus:border-primary focus:ring-2 focus:ring-primary/20 disabled:cursor-not-allowed disabled:opacity-50"
|
|
158
|
+
@input="handleInput(index, $event)"
|
|
159
|
+
@keydown="handleKeyDown(index, $event)"
|
|
160
|
+
/>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<!-- Error Message -->
|
|
164
|
+
<p v-if="errorMessage" class="text-xs font-medium text-destructive text-center">
|
|
165
|
+
{{ errorMessage }}
|
|
166
|
+
</p>
|
|
167
|
+
|
|
168
|
+
<!-- Submit Button -->
|
|
169
|
+
<Button
|
|
170
|
+
type="button"
|
|
171
|
+
class="w-full font-medium cursor-pointer"
|
|
172
|
+
:disabled="!isCodeComplete || isVerifying"
|
|
173
|
+
@click="handleVerify"
|
|
174
|
+
>
|
|
175
|
+
<span v-if="isVerifying">Verifying code...</span>
|
|
176
|
+
<span v-else class="flex items-center justify-center gap-2">
|
|
177
|
+
Confirm Email
|
|
178
|
+
<ArrowRight class="h-4 w-4" />
|
|
179
|
+
</span>
|
|
180
|
+
</Button>
|
|
181
|
+
|
|
182
|
+
<!-- Resend Code section -->
|
|
183
|
+
<div class="text-center text-xs text-muted-foreground">
|
|
184
|
+
<span>Didn't receive the code? </span>
|
|
185
|
+
<button
|
|
186
|
+
type="button"
|
|
187
|
+
class="font-medium text-primary hover:underline transition-colors cursor-pointer disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:no-underline inline-flex items-center gap-1"
|
|
188
|
+
:disabled="countdown > 0 || isResending"
|
|
189
|
+
@click="handleResend"
|
|
190
|
+
>
|
|
191
|
+
<RotateCw v-if="isResending" class="h-3 w-3 animate-spin" />
|
|
192
|
+
<span v-if="countdown > 0">Resend code in {{ countdown }}s</span>
|
|
193
|
+
<span v-else>Resend code</span>
|
|
194
|
+
</button>
|
|
195
|
+
</div>
|
|
196
|
+
</CardContent>
|
|
197
|
+
|
|
198
|
+
<CardFooter class="border-t border-border/60 bg-muted/30 px-6 py-4 flex flex-col items-center justify-center text-center">
|
|
199
|
+
<router-link
|
|
200
|
+
to="/auth/login"
|
|
201
|
+
class="inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors"
|
|
202
|
+
>
|
|
203
|
+
<ArrowLeft class="h-3.5 w-3.5" />
|
|
204
|
+
Back to sign in
|
|
205
|
+
</router-link>
|
|
206
|
+
</CardFooter>
|
|
207
|
+
</template>
|
|
208
|
+
</Card>
|
|
209
|
+
</template>
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } 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 { Mail, KeyRound, ArrowLeft, CheckCircle2, RotateCw } from '@lucide/vue'
|
|
17
|
+
|
|
18
|
+
const isSubmitting = ref(false)
|
|
19
|
+
const isSubmitted = ref(false)
|
|
20
|
+
const submittedEmail = ref('')
|
|
21
|
+
|
|
22
|
+
const forgotPasswordSchema = z.object({
|
|
23
|
+
email: z
|
|
24
|
+
.string()
|
|
25
|
+
.min(1, 'Email address is required')
|
|
26
|
+
.email('Please enter a valid email address'),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const form = useForm({
|
|
30
|
+
validationSchema: toTypedSchema(forgotPasswordSchema),
|
|
31
|
+
initialValues: {
|
|
32
|
+
email: '',
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const onSubmit = form.handleSubmit((values) => {
|
|
37
|
+
isSubmitting.value = true
|
|
38
|
+
submittedEmail.value = values.email
|
|
39
|
+
|
|
40
|
+
// Simulate API request to send reset link
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
isSubmitting.value = false
|
|
43
|
+
isSubmitted.value = true
|
|
44
|
+
}, 1000)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const handleResend = () => {
|
|
48
|
+
isSubmitting.value = true
|
|
49
|
+
setTimeout(() => {
|
|
50
|
+
isSubmitting.value = false
|
|
51
|
+
}, 800)
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<template>
|
|
56
|
+
<Card flush class="w-full max-w-md shadow-xl border-border/80 bg-card">
|
|
57
|
+
<!-- Success State -->
|
|
58
|
+
<template v-if="isSubmitted">
|
|
59
|
+
<CardHeader class="pt-8 pb-6 px-6 sm:px-8 space-y-2 text-center">
|
|
60
|
+
<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">
|
|
61
|
+
<CheckCircle2 class="h-6 w-6" />
|
|
62
|
+
</div>
|
|
63
|
+
<CardTitle class="text-2xl font-bold tracking-tight">Check your email</CardTitle>
|
|
64
|
+
<CardDescription class="text-sm">
|
|
65
|
+
We have sent a password reset link to
|
|
66
|
+
<span class="font-medium text-foreground block mt-1">{{ submittedEmail }}</span>
|
|
67
|
+
</CardDescription>
|
|
68
|
+
</CardHeader>
|
|
69
|
+
|
|
70
|
+
<CardContent class="px-6 sm:px-8 pb-6 space-y-4">
|
|
71
|
+
<p class="text-xs text-center text-muted-foreground leading-relaxed">
|
|
72
|
+
Please check your inbox and spam folder. If you don't receive an email within a few minutes, you can request a new link.
|
|
73
|
+
</p>
|
|
74
|
+
|
|
75
|
+
<Button
|
|
76
|
+
type="button"
|
|
77
|
+
variant="outline"
|
|
78
|
+
class="w-full gap-2 cursor-pointer"
|
|
79
|
+
:disabled="isSubmitting"
|
|
80
|
+
@click="handleResend"
|
|
81
|
+
>
|
|
82
|
+
<RotateCw class="h-4 w-4" :class="{ 'animate-spin': isSubmitting }" />
|
|
83
|
+
{{ isSubmitting ? 'Resending...' : 'Click to resend' }}
|
|
84
|
+
</Button>
|
|
85
|
+
</CardContent>
|
|
86
|
+
|
|
87
|
+
<CardFooter class="border-t border-border/60 bg-muted/30 px-6 py-4 flex flex-col items-center justify-center text-center">
|
|
88
|
+
<router-link
|
|
89
|
+
to="/auth/login"
|
|
90
|
+
class="inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors"
|
|
91
|
+
>
|
|
92
|
+
<ArrowLeft class="h-3.5 w-3.5" />
|
|
93
|
+
Back to sign in
|
|
94
|
+
</router-link>
|
|
95
|
+
</CardFooter>
|
|
96
|
+
</template>
|
|
97
|
+
|
|
98
|
+
<!-- Request Form State -->
|
|
99
|
+
<template v-else>
|
|
100
|
+
<CardHeader class="pt-8 pb-6 px-6 sm:px-8 space-y-1.5 text-center">
|
|
101
|
+
<div class="mx-auto mb-2 flex h-12 w-12 items-center justify-center rounded-xl bg-primary/10 text-primary">
|
|
102
|
+
<KeyRound class="h-6 w-6" />
|
|
103
|
+
</div>
|
|
104
|
+
<CardTitle class="text-2xl font-bold tracking-tight">Forgot password?</CardTitle>
|
|
105
|
+
<CardDescription class="text-sm">
|
|
106
|
+
No worries, enter your registered email and we'll send you reset instructions.
|
|
107
|
+
</CardDescription>
|
|
108
|
+
</CardHeader>
|
|
109
|
+
|
|
110
|
+
<CardContent class="px-6 sm:px-8 pb-6">
|
|
111
|
+
<form @submit.prevent="onSubmit" class="space-y-4" novalidate>
|
|
112
|
+
<FormField v-slot="{ componentField }" name="email">
|
|
113
|
+
<FormItem class="space-y-1.5">
|
|
114
|
+
<FormLabel class="text-xs font-semibold">Email address</FormLabel>
|
|
115
|
+
<FormControl>
|
|
116
|
+
<InputGroup>
|
|
117
|
+
<InputIcon>
|
|
118
|
+
<Mail />
|
|
119
|
+
</InputIcon>
|
|
120
|
+
<Input
|
|
121
|
+
type="email"
|
|
122
|
+
placeholder="name@example.com"
|
|
123
|
+
autocomplete="email"
|
|
124
|
+
class="pl-9"
|
|
125
|
+
v-bind="componentField"
|
|
126
|
+
/>
|
|
127
|
+
</InputGroup>
|
|
128
|
+
</FormControl>
|
|
129
|
+
<FormMessage class="text-xs" />
|
|
130
|
+
</FormItem>
|
|
131
|
+
</FormField>
|
|
132
|
+
|
|
133
|
+
<Button type="submit" class="w-full mt-2 font-medium cursor-pointer" :disabled="isSubmitting">
|
|
134
|
+
<span v-if="isSubmitting">Sending reset link...</span>
|
|
135
|
+
<span v-else>Send Reset Link</span>
|
|
136
|
+
</Button>
|
|
137
|
+
</form>
|
|
138
|
+
</CardContent>
|
|
139
|
+
|
|
140
|
+
<CardFooter class="border-t border-border/60 bg-muted/30 px-6 py-4 flex flex-col items-center justify-center text-center">
|
|
141
|
+
<router-link
|
|
142
|
+
to="/auth/login"
|
|
143
|
+
class="inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors"
|
|
144
|
+
>
|
|
145
|
+
<ArrowLeft class="h-3.5 w-3.5" />
|
|
146
|
+
Back to sign in
|
|
147
|
+
</router-link>
|
|
148
|
+
</CardFooter>
|
|
149
|
+
</template>
|
|
150
|
+
</Card>
|
|
151
|
+
</template>
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, onMounted } from 'vue'
|
|
3
|
+
import { useRouter } from 'vue-router'
|
|
4
|
+
import {
|
|
5
|
+
Lock,
|
|
6
|
+
Unlock,
|
|
7
|
+
Eye,
|
|
8
|
+
EyeOff,
|
|
9
|
+
ArrowRight,
|
|
10
|
+
UserCheck,
|
|
11
|
+
Sparkles,
|
|
12
|
+
} from '@lucide/vue'
|
|
13
|
+
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
|
14
|
+
import { Button } from '@/components/ui/button'
|
|
15
|
+
import { Input } from '@/components/ui/input'
|
|
16
|
+
import { Label } from '@/components/ui/label'
|
|
17
|
+
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
|
18
|
+
import { Badge } from '@/components/ui/badge'
|
|
19
|
+
import AppLogo from '@/components/AppLogo.vue'
|
|
20
|
+
import { toast } from '@/components/ui/sonner'
|
|
21
|
+
|
|
22
|
+
const router = useRouter()
|
|
23
|
+
|
|
24
|
+
const password = ref('')
|
|
25
|
+
const showPassword = ref(false)
|
|
26
|
+
const isUnlocking = ref(false)
|
|
27
|
+
const lockedAt = ref('')
|
|
28
|
+
|
|
29
|
+
// Mock Locked User Profile
|
|
30
|
+
const userProfile = {
|
|
31
|
+
name: 'Alex Morgan',
|
|
32
|
+
role: 'Lead Administrator',
|
|
33
|
+
email: 'alex.morgan@nala.dev',
|
|
34
|
+
avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=150&auto=format&fit=crop&q=80',
|
|
35
|
+
initials: 'AM',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
onMounted(() => {
|
|
39
|
+
const now = new Date()
|
|
40
|
+
lockedAt.value = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
async function handleUnlock() {
|
|
44
|
+
if (!password.value) {
|
|
45
|
+
toast.error('Please enter your password to unlock the session.')
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
isUnlocking.value = true
|
|
50
|
+
|
|
51
|
+
// Simulate verification delay
|
|
52
|
+
setTimeout(() => {
|
|
53
|
+
isUnlocking.value = false
|
|
54
|
+
toast.success(`Welcome back, ${userProfile.name}! Session unlocked.`)
|
|
55
|
+
router.push('/')
|
|
56
|
+
}, 650)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function fillDemoPassword() {
|
|
60
|
+
password.value = 'password123'
|
|
61
|
+
toast.info('Demo password autofilled. Click Unlock to proceed.')
|
|
62
|
+
}
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<template>
|
|
66
|
+
<div class="w-full max-w-md space-y-6">
|
|
67
|
+
<!-- Brand Logo Header -->
|
|
68
|
+
<div class="flex flex-col items-center justify-center text-center pb-1">
|
|
69
|
+
<router-link
|
|
70
|
+
to="/"
|
|
71
|
+
class="inline-flex items-center gap-2.5 hover:opacity-90 transition-opacity"
|
|
72
|
+
aria-label="Nala Home"
|
|
73
|
+
>
|
|
74
|
+
<AppLogo class="h-10 w-10" withBackground withText textClass="text-2xl font-bold tracking-tight" />
|
|
75
|
+
</router-link>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<!-- Lock Screen Card -->
|
|
79
|
+
<Card class="border-border/80 shadow-lg bg-card/95 backdrop-blur-xs relative overflow-hidden">
|
|
80
|
+
<!-- Glow Accent Line -->
|
|
81
|
+
<div class="absolute top-0 left-0 right-0 h-1 bg-linear-to-r from-primary/50 via-primary to-primary/50" />
|
|
82
|
+
|
|
83
|
+
<CardHeader class="pb-4 text-center">
|
|
84
|
+
<!-- Avatar with Lock Pip -->
|
|
85
|
+
<div class="relative mx-auto mb-2">
|
|
86
|
+
<Avatar class="h-20 w-20 border-2 border-primary/40 ring-4 ring-primary/10 shadow-md">
|
|
87
|
+
<AvatarImage :src="userProfile.avatar" :alt="userProfile.name" />
|
|
88
|
+
<AvatarFallback class="bg-primary/10 text-primary text-lg font-bold">
|
|
89
|
+
{{ userProfile.initials }}
|
|
90
|
+
</AvatarFallback>
|
|
91
|
+
</Avatar>
|
|
92
|
+
<div
|
|
93
|
+
class="absolute -bottom-1 -right-1 flex h-7 w-7 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-sm ring-2 ring-background"
|
|
94
|
+
title="Session Locked"
|
|
95
|
+
>
|
|
96
|
+
<Lock class="h-3.5 w-3.5" />
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<CardTitle class="text-xl font-bold tracking-tight text-foreground">
|
|
101
|
+
{{ userProfile.name }}
|
|
102
|
+
</CardTitle>
|
|
103
|
+
<CardDescription class="text-xs text-muted-foreground flex items-center justify-center gap-1.5 pt-1">
|
|
104
|
+
<Badge variant="secondary" shape="pill" class="text-[10px] py-0 px-2 font-medium">
|
|
105
|
+
{{ userProfile.role }}
|
|
106
|
+
</Badge>
|
|
107
|
+
<span>•</span>
|
|
108
|
+
<span>Locked at {{ lockedAt || 'just now' }}</span>
|
|
109
|
+
</CardDescription>
|
|
110
|
+
</CardHeader>
|
|
111
|
+
|
|
112
|
+
<CardContent class="space-y-4">
|
|
113
|
+
<form @submit.prevent="handleUnlock" class="space-y-4">
|
|
114
|
+
<div class="space-y-2">
|
|
115
|
+
<div class="flex items-center justify-between">
|
|
116
|
+
<Label for="unlock-password" class="text-xs font-medium text-foreground">
|
|
117
|
+
Password
|
|
118
|
+
</Label>
|
|
119
|
+
<button
|
|
120
|
+
type="button"
|
|
121
|
+
class="text-[11px] text-primary hover:underline cursor-pointer flex items-center gap-1"
|
|
122
|
+
@click="fillDemoPassword"
|
|
123
|
+
>
|
|
124
|
+
<Sparkles class="h-3 w-3" />
|
|
125
|
+
<span>Auto-fill Demo</span>
|
|
126
|
+
</button>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<div class="relative">
|
|
130
|
+
<Input
|
|
131
|
+
id="unlock-password"
|
|
132
|
+
v-model="password"
|
|
133
|
+
:type="showPassword ? 'text' : 'password'"
|
|
134
|
+
placeholder="Enter password to unlock"
|
|
135
|
+
class="h-10 text-sm pr-10 bg-background"
|
|
136
|
+
autofocus
|
|
137
|
+
/>
|
|
138
|
+
<button
|
|
139
|
+
type="button"
|
|
140
|
+
class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground cursor-pointer"
|
|
141
|
+
@click="showPassword = !showPassword"
|
|
142
|
+
:aria-label="showPassword ? 'Hide password' : 'Show password'"
|
|
143
|
+
>
|
|
144
|
+
<component :is="showPassword ? EyeOff : Eye" class="h-4 w-4" />
|
|
145
|
+
</button>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<Button
|
|
150
|
+
type="submit"
|
|
151
|
+
class="w-full h-10 gap-2 font-medium cursor-pointer shadow-xs"
|
|
152
|
+
:disabled="isUnlocking"
|
|
153
|
+
>
|
|
154
|
+
<component :is="isUnlocking ? Unlock : Lock" class="h-4 w-4" :class="{ 'animate-bounce': isUnlocking }" />
|
|
155
|
+
<span>{{ isUnlocking ? 'Unlocking Session...' : 'Unlock Dashboard' }}</span>
|
|
156
|
+
<ArrowRight v-if="!isUnlocking" class="h-3.5 w-3.5" />
|
|
157
|
+
</Button>
|
|
158
|
+
</form>
|
|
159
|
+
</CardContent>
|
|
160
|
+
|
|
161
|
+
<CardFooter class="flex flex-col items-center justify-center border-t border-border/50 py-4 bg-muted/20 text-xs text-muted-foreground">
|
|
162
|
+
<div class="flex items-center gap-1.5">
|
|
163
|
+
<span>Not {{ userProfile.name }}?</span>
|
|
164
|
+
<router-link
|
|
165
|
+
to="/auth/login"
|
|
166
|
+
class="font-semibold text-primary hover:underline transition-colors cursor-pointer inline-flex items-center gap-1"
|
|
167
|
+
>
|
|
168
|
+
<UserCheck class="h-3.5 w-3.5" />
|
|
169
|
+
<span>Switch Account</span>
|
|
170
|
+
</router-link>
|
|
171
|
+
</div>
|
|
172
|
+
</CardFooter>
|
|
173
|
+
</Card>
|
|
174
|
+
</div>
|
|
175
|
+
</template>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from '@/components/ui/card'
|
|
3
|
+
import { Button } from '@/components/ui/button'
|
|
4
|
+
import { Input } from '@/components/ui/input'
|
|
5
|
+
import { Label } from '@/components/ui/label'
|
|
6
|
+
import { useRouter } from 'vue-router'
|
|
7
|
+
|
|
8
|
+
const router = useRouter()
|
|
9
|
+
|
|
10
|
+
function handleLogin() {
|
|
11
|
+
router.push('/')
|
|
12
|
+
}
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<Card flush class="w-full max-w-md shadow-xl border-border/80 bg-card">
|
|
17
|
+
<CardHeader class="pt-8 pb-6 px-6 sm:px-8 space-y-1.5 text-center">
|
|
18
|
+
<CardTitle class="text-2xl font-bold tracking-tight">Sign in</CardTitle>
|
|
19
|
+
<CardDescription class="text-sm">Enter your credentials to access Nala Admin panel</CardDescription>
|
|
20
|
+
</CardHeader>
|
|
21
|
+
<CardContent class="px-6 sm:px-8 pb-6">
|
|
22
|
+
<form @submit.prevent="handleLogin" class="space-y-4">
|
|
23
|
+
<div class="space-y-2">
|
|
24
|
+
<Label for="email">Email</Label>
|
|
25
|
+
<Input id="email" type="email" placeholder="admin@example.com" required />
|
|
26
|
+
</div>
|
|
27
|
+
<div class="space-y-2">
|
|
28
|
+
<div class="flex items-center justify-between">
|
|
29
|
+
<Label for="password">Password</Label>
|
|
30
|
+
<router-link
|
|
31
|
+
to="/auth/forgot-password"
|
|
32
|
+
class="text-xs font-medium text-primary hover:underline transition-colors"
|
|
33
|
+
>
|
|
34
|
+
Forgot password?
|
|
35
|
+
</router-link>
|
|
36
|
+
</div>
|
|
37
|
+
<Input id="password" type="password" required />
|
|
38
|
+
</div>
|
|
39
|
+
<Button type="submit" class="w-full font-medium cursor-pointer">Sign In</Button>
|
|
40
|
+
</form>
|
|
41
|
+
</CardContent>
|
|
42
|
+
<CardFooter class="border-t border-border/60 bg-muted/30 px-6 py-4 flex flex-col items-center justify-center text-center">
|
|
43
|
+
<p class="text-xs text-muted-foreground">
|
|
44
|
+
Don't have an account?
|
|
45
|
+
<router-link
|
|
46
|
+
to="/auth/register"
|
|
47
|
+
class="font-medium text-primary hover:underline transition-colors ml-1"
|
|
48
|
+
>
|
|
49
|
+
Sign up
|
|
50
|
+
</router-link>
|
|
51
|
+
</p>
|
|
52
|
+
</CardFooter>
|
|
53
|
+
</Card>
|
|
54
|
+
</template>
|