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.
Files changed (316) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -0
  3. package/dist/index.js +227 -0
  4. package/package.json +48 -0
  5. package/template/.env.example +45 -0
  6. package/template/_gitignore +33 -0
  7. package/template/components.json +24 -0
  8. package/template/index.html +30 -0
  9. package/template/package.json +51 -0
  10. package/template/public/favicon.svg +15 -0
  11. package/template/public/icons.svg +24 -0
  12. package/template/public/img/avatar.webp +0 -0
  13. package/template/public/screenshot.png +0 -0
  14. package/template/src/App.vue +15 -0
  15. package/template/src/auto-imports.d.ts +691 -0
  16. package/template/src/components/AppLogo.vue +159 -0
  17. package/template/src/components/EmptyState.vue +60 -0
  18. package/template/src/components/PageHeader.vue +60 -0
  19. package/template/src/components/layout/AppNavbar.vue +159 -0
  20. package/template/src/components/layout/AppSidebar.vue +74 -0
  21. package/template/src/components/layout/CommandSearchDialog.vue +120 -0
  22. package/template/src/components/ui/accordion/Accordion.vue +15 -0
  23. package/template/src/components/ui/accordion/AccordionContent.vue +20 -0
  24. package/template/src/components/ui/accordion/AccordionItem.vue +20 -0
  25. package/template/src/components/ui/accordion/AccordionTrigger.vue +34 -0
  26. package/template/src/components/ui/accordion/index.ts +4 -0
  27. package/template/src/components/ui/alert/Alert.vue +21 -0
  28. package/template/src/components/ui/alert/AlertDescription.vue +17 -0
  29. package/template/src/components/ui/alert/AlertTitle.vue +17 -0
  30. package/template/src/components/ui/alert/index.ts +30 -0
  31. package/template/src/components/ui/alert-dialog/AlertDialog.vue +15 -0
  32. package/template/src/components/ui/alert-dialog/AlertDialogAction.vue +18 -0
  33. package/template/src/components/ui/alert-dialog/AlertDialogCancel.vue +25 -0
  34. package/template/src/components/ui/alert-dialog/AlertDialogContent.vue +44 -0
  35. package/template/src/components/ui/alert-dialog/AlertDialogDescription.vue +23 -0
  36. package/template/src/components/ui/alert-dialog/AlertDialogFooter.vue +22 -0
  37. package/template/src/components/ui/alert-dialog/AlertDialogHeader.vue +17 -0
  38. package/template/src/components/ui/alert-dialog/AlertDialogTitle.vue +21 -0
  39. package/template/src/components/ui/alert-dialog/AlertDialogTrigger.vue +12 -0
  40. package/template/src/components/ui/alert-dialog/index.ts +9 -0
  41. package/template/src/components/ui/avatar/Avatar.vue +50 -0
  42. package/template/src/components/ui/avatar/AvatarFallback.vue +21 -0
  43. package/template/src/components/ui/avatar/AvatarGroup.vue +69 -0
  44. package/template/src/components/ui/avatar/AvatarImage.vue +16 -0
  45. package/template/src/components/ui/avatar/index.ts +4 -0
  46. package/template/src/components/ui/badge/Badge.vue +40 -0
  47. package/template/src/components/ui/badge/index.ts +45 -0
  48. package/template/src/components/ui/breadcrumb/Breadcrumb.vue +17 -0
  49. package/template/src/components/ui/breadcrumb/BreadcrumbEllipsis.vue +23 -0
  50. package/template/src/components/ui/breadcrumb/BreadcrumbItem.vue +17 -0
  51. package/template/src/components/ui/breadcrumb/BreadcrumbLink.vue +21 -0
  52. package/template/src/components/ui/breadcrumb/BreadcrumbList.vue +17 -0
  53. package/template/src/components/ui/breadcrumb/BreadcrumbPage.vue +20 -0
  54. package/template/src/components/ui/breadcrumb/BreadcrumbSeparator.vue +22 -0
  55. package/template/src/components/ui/breadcrumb/index.ts +7 -0
  56. package/template/src/components/ui/button/Button.spec.ts +46 -0
  57. package/template/src/components/ui/button/Button.vue +31 -0
  58. package/template/src/components/ui/button/index.ts +41 -0
  59. package/template/src/components/ui/calendar/Calendar.vue +106 -0
  60. package/template/src/components/ui/calendar/index.ts +1 -0
  61. package/template/src/components/ui/card/Card.vue +29 -0
  62. package/template/src/components/ui/card/CardAction.vue +17 -0
  63. package/template/src/components/ui/card/CardContent.vue +17 -0
  64. package/template/src/components/ui/card/CardDescription.vue +17 -0
  65. package/template/src/components/ui/card/CardFooter.vue +17 -0
  66. package/template/src/components/ui/card/CardHeader.vue +29 -0
  67. package/template/src/components/ui/card/CardTitle.vue +17 -0
  68. package/template/src/components/ui/card/index.ts +7 -0
  69. package/template/src/components/ui/chart/AreaChart.vue +143 -0
  70. package/template/src/components/ui/chart/BarChart.vue +142 -0
  71. package/template/src/components/ui/chart/DonutChart.vue +75 -0
  72. package/template/src/components/ui/chart/LineChart.vue +132 -0
  73. package/template/src/components/ui/chart/index.ts +4 -0
  74. package/template/src/components/ui/checkbox/Checkbox.vue +57 -0
  75. package/template/src/components/ui/checkbox/index.ts +1 -0
  76. package/template/src/components/ui/collapsible/Collapsible.vue +15 -0
  77. package/template/src/components/ui/collapsible/CollapsibleContent.vue +18 -0
  78. package/template/src/components/ui/collapsible/CollapsibleTrigger.vue +12 -0
  79. package/template/src/components/ui/collapsible/index.ts +3 -0
  80. package/template/src/components/ui/command/Command.vue +88 -0
  81. package/template/src/components/ui/command/CommandDialog.vue +31 -0
  82. package/template/src/components/ui/command/CommandEmpty.vue +27 -0
  83. package/template/src/components/ui/command/CommandGroup.vue +45 -0
  84. package/template/src/components/ui/command/CommandInput.vue +39 -0
  85. package/template/src/components/ui/command/CommandItem.vue +76 -0
  86. package/template/src/components/ui/command/CommandList.vue +25 -0
  87. package/template/src/components/ui/command/CommandSeparator.vue +21 -0
  88. package/template/src/components/ui/command/CommandShortcut.vue +17 -0
  89. package/template/src/components/ui/command/index.ts +25 -0
  90. package/template/src/components/ui/context-menu/ContextMenu.vue +15 -0
  91. package/template/src/components/ui/context-menu/ContextMenuCheckboxItem.vue +36 -0
  92. package/template/src/components/ui/context-menu/ContextMenuContent.vue +33 -0
  93. package/template/src/components/ui/context-menu/ContextMenuGroup.vue +12 -0
  94. package/template/src/components/ui/context-menu/ContextMenuItem.vue +36 -0
  95. package/template/src/components/ui/context-menu/ContextMenuLabel.vue +25 -0
  96. package/template/src/components/ui/context-menu/ContextMenuRadioGroup.vue +18 -0
  97. package/template/src/components/ui/context-menu/ContextMenuRadioItem.vue +36 -0
  98. package/template/src/components/ui/context-menu/ContextMenuSeparator.vue +18 -0
  99. package/template/src/components/ui/context-menu/ContextMenuShortcut.vue +17 -0
  100. package/template/src/components/ui/context-menu/ContextMenuSub.vue +15 -0
  101. package/template/src/components/ui/context-menu/ContextMenuSubContent.vue +36 -0
  102. package/template/src/components/ui/context-menu/ContextMenuSubTrigger.vue +32 -0
  103. package/template/src/components/ui/context-menu/ContextMenuTrigger.vue +13 -0
  104. package/template/src/components/ui/context-menu/index.ts +14 -0
  105. package/template/src/components/ui/date-picker/DatePicker.vue +233 -0
  106. package/template/src/components/ui/date-picker/index.ts +1 -0
  107. package/template/src/components/ui/dialog/Dialog.vue +19 -0
  108. package/template/src/components/ui/dialog/DialogClose.vue +15 -0
  109. package/template/src/components/ui/dialog/DialogContent.vue +53 -0
  110. package/template/src/components/ui/dialog/DialogDescription.vue +23 -0
  111. package/template/src/components/ui/dialog/DialogFooter.vue +27 -0
  112. package/template/src/components/ui/dialog/DialogHeader.vue +17 -0
  113. package/template/src/components/ui/dialog/DialogOverlay.vue +21 -0
  114. package/template/src/components/ui/dialog/DialogScrollContent.vue +59 -0
  115. package/template/src/components/ui/dialog/DialogTitle.vue +23 -0
  116. package/template/src/components/ui/dialog/DialogTrigger.vue +15 -0
  117. package/template/src/components/ui/dialog/index.ts +10 -0
  118. package/template/src/components/ui/dropdown-menu/DropdownMenu.vue +19 -0
  119. package/template/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +39 -0
  120. package/template/src/components/ui/dropdown-menu/DropdownMenuContent.vue +41 -0
  121. package/template/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +15 -0
  122. package/template/src/components/ui/dropdown-menu/DropdownMenuItem.vue +31 -0
  123. package/template/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +23 -0
  124. package/template/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +21 -0
  125. package/template/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +40 -0
  126. package/template/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +23 -0
  127. package/template/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +17 -0
  128. package/template/src/components/ui/dropdown-menu/DropdownMenuSub.vue +18 -0
  129. package/template/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +27 -0
  130. package/template/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +31 -0
  131. package/template/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +17 -0
  132. package/template/src/components/ui/dropdown-menu/index.ts +16 -0
  133. package/template/src/components/ui/form/FormControl.vue +17 -0
  134. package/template/src/components/ui/form/FormDescription.vue +21 -0
  135. package/template/src/components/ui/form/FormItem.vue +23 -0
  136. package/template/src/components/ui/form/FormLabel.vue +25 -0
  137. package/template/src/components/ui/form/FormMessage.vue +23 -0
  138. package/template/src/components/ui/form/index.ts +7 -0
  139. package/template/src/components/ui/form/injectionKeys.ts +4 -0
  140. package/template/src/components/ui/form/useFormField.ts +30 -0
  141. package/template/src/components/ui/hover-card/HoverCard.vue +19 -0
  142. package/template/src/components/ui/hover-card/HoverCardContent.vue +43 -0
  143. package/template/src/components/ui/hover-card/HoverCardTrigger.vue +15 -0
  144. package/template/src/components/ui/hover-card/index.ts +3 -0
  145. package/template/src/components/ui/index.ts +47 -0
  146. package/template/src/components/ui/input/Input.vue +33 -0
  147. package/template/src/components/ui/input/InputAddon.vue +29 -0
  148. package/template/src/components/ui/input/InputGroup.vue +17 -0
  149. package/template/src/components/ui/input/InputIcon.vue +29 -0
  150. package/template/src/components/ui/input/index.ts +4 -0
  151. package/template/src/components/ui/kbd/Kbd.vue +30 -0
  152. package/template/src/components/ui/kbd/index.ts +1 -0
  153. package/template/src/components/ui/label/Label.vue +26 -0
  154. package/template/src/components/ui/label/index.ts +1 -0
  155. package/template/src/components/ui/loading-bar/LoadingBar.vue +41 -0
  156. package/template/src/components/ui/loading-bar/index.ts +2 -0
  157. package/template/src/components/ui/pagination/Pagination.vue +26 -0
  158. package/template/src/components/ui/pagination/PaginationContent.vue +22 -0
  159. package/template/src/components/ui/pagination/PaginationEllipsis.vue +25 -0
  160. package/template/src/components/ui/pagination/PaginationFirst.vue +33 -0
  161. package/template/src/components/ui/pagination/PaginationItem.vue +34 -0
  162. package/template/src/components/ui/pagination/PaginationLast.vue +33 -0
  163. package/template/src/components/ui/pagination/PaginationNext.vue +33 -0
  164. package/template/src/components/ui/pagination/PaginationPrevious.vue +33 -0
  165. package/template/src/components/ui/pagination/index.ts +8 -0
  166. package/template/src/components/ui/pin-input/PinInput.vue +28 -0
  167. package/template/src/components/ui/pin-input/PinInputGroup.vue +18 -0
  168. package/template/src/components/ui/pin-input/PinInputInput.vue +24 -0
  169. package/template/src/components/ui/pin-input/PinInputSeparator.vue +21 -0
  170. package/template/src/components/ui/pin-input/index.ts +4 -0
  171. package/template/src/components/ui/popover/Popover.vue +19 -0
  172. package/template/src/components/ui/popover/PopoverAnchor.vue +15 -0
  173. package/template/src/components/ui/popover/PopoverContent.vue +45 -0
  174. package/template/src/components/ui/popover/PopoverTrigger.vue +15 -0
  175. package/template/src/components/ui/popover/index.ts +4 -0
  176. package/template/src/components/ui/progress/Progress.vue +35 -0
  177. package/template/src/components/ui/progress/index.ts +1 -0
  178. package/template/src/components/ui/radio-group/RadioGroup.vue +23 -0
  179. package/template/src/components/ui/radio-group/RadioGroupItem.vue +30 -0
  180. package/template/src/components/ui/radio-group/index.ts +2 -0
  181. package/template/src/components/ui/scroll-area/ScrollArea.vue +40 -0
  182. package/template/src/components/ui/scroll-area/ScrollBar.vue +44 -0
  183. package/template/src/components/ui/scroll-area/index.ts +2 -0
  184. package/template/src/components/ui/select/Select.vue +19 -0
  185. package/template/src/components/ui/select/SelectContent.vue +51 -0
  186. package/template/src/components/ui/select/SelectGroup.vue +15 -0
  187. package/template/src/components/ui/select/SelectItem.vue +44 -0
  188. package/template/src/components/ui/select/SelectItemText.vue +15 -0
  189. package/template/src/components/ui/select/SelectLabel.vue +17 -0
  190. package/template/src/components/ui/select/SelectScrollDownButton.vue +26 -0
  191. package/template/src/components/ui/select/SelectScrollUpButton.vue +26 -0
  192. package/template/src/components/ui/select/SelectSeparator.vue +19 -0
  193. package/template/src/components/ui/select/SelectTrigger.vue +33 -0
  194. package/template/src/components/ui/select/SelectValue.vue +15 -0
  195. package/template/src/components/ui/select/index.ts +11 -0
  196. package/template/src/components/ui/separator/Separator.vue +29 -0
  197. package/template/src/components/ui/separator/index.ts +1 -0
  198. package/template/src/components/ui/sheet/Sheet.vue +19 -0
  199. package/template/src/components/ui/sheet/SheetClose.vue +15 -0
  200. package/template/src/components/ui/sheet/SheetContent.vue +62 -0
  201. package/template/src/components/ui/sheet/SheetDescription.vue +21 -0
  202. package/template/src/components/ui/sheet/SheetFooter.vue +16 -0
  203. package/template/src/components/ui/sheet/SheetHeader.vue +15 -0
  204. package/template/src/components/ui/sheet/SheetOverlay.vue +21 -0
  205. package/template/src/components/ui/sheet/SheetTitle.vue +21 -0
  206. package/template/src/components/ui/sheet/SheetTrigger.vue +15 -0
  207. package/template/src/components/ui/sheet/index.ts +8 -0
  208. package/template/src/components/ui/sidebar/Sidebar.vue +113 -0
  209. package/template/src/components/ui/sidebar/SidebarContent.vue +18 -0
  210. package/template/src/components/ui/sidebar/SidebarControl.vue +53 -0
  211. package/template/src/components/ui/sidebar/SidebarFooter.vue +18 -0
  212. package/template/src/components/ui/sidebar/SidebarGroup.vue +18 -0
  213. package/template/src/components/ui/sidebar/SidebarGroupAction.vue +27 -0
  214. package/template/src/components/ui/sidebar/SidebarGroupContent.vue +18 -0
  215. package/template/src/components/ui/sidebar/SidebarGroupLabel.vue +25 -0
  216. package/template/src/components/ui/sidebar/SidebarHeader.vue +18 -0
  217. package/template/src/components/ui/sidebar/SidebarInput.vue +22 -0
  218. package/template/src/components/ui/sidebar/SidebarInset.vue +21 -0
  219. package/template/src/components/ui/sidebar/SidebarMenu.vue +18 -0
  220. package/template/src/components/ui/sidebar/SidebarMenuAction.vue +35 -0
  221. package/template/src/components/ui/sidebar/SidebarMenuBadge.vue +26 -0
  222. package/template/src/components/ui/sidebar/SidebarMenuButton.vue +48 -0
  223. package/template/src/components/ui/sidebar/SidebarMenuButtonChild.vue +36 -0
  224. package/template/src/components/ui/sidebar/SidebarMenuItem.vue +18 -0
  225. package/template/src/components/ui/sidebar/SidebarMenuSkeleton.vue +35 -0
  226. package/template/src/components/ui/sidebar/SidebarMenuSub.vue +22 -0
  227. package/template/src/components/ui/sidebar/SidebarMenuSubButton.vue +36 -0
  228. package/template/src/components/ui/sidebar/SidebarMenuSubItem.vue +18 -0
  229. package/template/src/components/ui/sidebar/SidebarProvider.vue +136 -0
  230. package/template/src/components/ui/sidebar/SidebarRail.vue +33 -0
  231. package/template/src/components/ui/sidebar/SidebarSeparator.vue +19 -0
  232. package/template/src/components/ui/sidebar/SidebarTrigger.vue +27 -0
  233. package/template/src/components/ui/sidebar/index.ts +61 -0
  234. package/template/src/components/ui/sidebar/utils.ts +27 -0
  235. package/template/src/components/ui/skeleton/Skeleton.vue +17 -0
  236. package/template/src/components/ui/skeleton/index.ts +1 -0
  237. package/template/src/components/ui/slider/Slider.vue +42 -0
  238. package/template/src/components/ui/slider/index.ts +1 -0
  239. package/template/src/components/ui/sonner/Sonner.vue +42 -0
  240. package/template/src/components/ui/sonner/index.ts +2 -0
  241. package/template/src/components/ui/stepper/Stepper.vue +102 -0
  242. package/template/src/components/ui/stepper/index.ts +1 -0
  243. package/template/src/components/ui/switch/Switch.vue +38 -0
  244. package/template/src/components/ui/switch/index.ts +1 -0
  245. package/template/src/components/ui/table/Table.vue +16 -0
  246. package/template/src/components/ui/table/TableBody.vue +17 -0
  247. package/template/src/components/ui/table/TableCaption.vue +17 -0
  248. package/template/src/components/ui/table/TableCell.vue +22 -0
  249. package/template/src/components/ui/table/TableEmpty.vue +34 -0
  250. package/template/src/components/ui/table/TableFooter.vue +17 -0
  251. package/template/src/components/ui/table/TableHead.vue +17 -0
  252. package/template/src/components/ui/table/TableHeader.vue +17 -0
  253. package/template/src/components/ui/table/TableRow.vue +17 -0
  254. package/template/src/components/ui/table/index.ts +9 -0
  255. package/template/src/components/ui/table/utils.ts +10 -0
  256. package/template/src/components/ui/tabs/Tabs.vue +24 -0
  257. package/template/src/components/ui/tabs/TabsContent.vue +21 -0
  258. package/template/src/components/ui/tabs/TabsList.vue +24 -0
  259. package/template/src/components/ui/tabs/TabsTrigger.vue +26 -0
  260. package/template/src/components/ui/tabs/index.ts +4 -0
  261. package/template/src/components/ui/textarea/Textarea.vue +28 -0
  262. package/template/src/components/ui/textarea/index.ts +1 -0
  263. package/template/src/components/ui/timeline/Timeline.vue +73 -0
  264. package/template/src/components/ui/timeline/index.ts +1 -0
  265. package/template/src/components/ui/toggle/Toggle.vue +35 -0
  266. package/template/src/components/ui/toggle/index.ts +28 -0
  267. package/template/src/components/ui/toggle-group/ToggleGroup.vue +41 -0
  268. package/template/src/components/ui/toggle-group/ToggleGroupItem.vue +43 -0
  269. package/template/src/components/ui/toggle-group/index.ts +12 -0
  270. package/template/src/components/ui/tooltip/Tooltip.vue +19 -0
  271. package/template/src/components/ui/tooltip/TooltipContent.vue +34 -0
  272. package/template/src/components/ui/tooltip/TooltipProvider.vue +14 -0
  273. package/template/src/components/ui/tooltip/TooltipTrigger.vue +15 -0
  274. package/template/src/components/ui/tooltip/index.ts +4 -0
  275. package/template/src/components.d.ts +228 -0
  276. package/template/src/composables/useFormatter.ts +105 -0
  277. package/template/src/composables/useThemeConfig.ts +252 -0
  278. package/template/src/layouts/AdminLayout.vue +55 -0
  279. package/template/src/layouts/AuthLayout.vue +42 -0
  280. package/template/src/lib/axios.spec.ts +46 -0
  281. package/template/src/lib/axios.ts +243 -0
  282. package/template/src/lib/formatters.spec.ts +137 -0
  283. package/template/src/lib/formatters.ts +332 -0
  284. package/template/src/lib/loading-bar.ts +103 -0
  285. package/template/src/lib/utils.spec.ts +26 -0
  286. package/template/src/lib/utils.ts +7 -0
  287. package/template/src/lib/validation.spec.ts +78 -0
  288. package/template/src/main.ts +13 -0
  289. package/template/src/router/index.ts +147 -0
  290. package/template/src/services/auth.service.spec.ts +93 -0
  291. package/template/src/services/auth.service.ts +243 -0
  292. package/template/src/services/index.ts +3 -0
  293. package/template/src/services/types.ts +28 -0
  294. package/template/src/services/user.service.spec.ts +91 -0
  295. package/template/src/services/user.service.ts +283 -0
  296. package/template/src/stores/auth.spec.ts +52 -0
  297. package/template/src/stores/auth.ts +130 -0
  298. package/template/src/style.css +587 -0
  299. package/template/src/views/_starter/BlankView.vue +47 -0
  300. package/template/src/views/auth/ConfirmEmailView.vue +209 -0
  301. package/template/src/views/auth/ForgotPasswordView.vue +151 -0
  302. package/template/src/views/auth/LockScreenView.vue +175 -0
  303. package/template/src/views/auth/LoginView.vue +54 -0
  304. package/template/src/views/auth/RegisterView.vue +265 -0
  305. package/template/src/views/auth/ResetPasswordView.vue +260 -0
  306. package/template/src/views/auth/VerifyOtpView.vue +261 -0
  307. package/template/src/views/dashboard/IndexView.vue +159 -0
  308. package/template/src/views/errors/ComingSoonView.vue +241 -0
  309. package/template/src/views/errors/MaintenanceView.vue +252 -0
  310. package/template/src/views/errors/NotFoundView.vue +81 -0
  311. package/template/src/views/errors/ServerErrorView.vue +101 -0
  312. package/template/src/views/errors/UnauthorizedView.vue +64 -0
  313. package/template/tsconfig.app.json +27 -0
  314. package/template/tsconfig.json +19 -0
  315. package/template/tsconfig.node.json +23 -0
  316. 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>