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,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>