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,147 @@
1
+ import { createRouter, createWebHistory } from 'vue-router'
2
+ import { loadingBar } from '@/lib/loading-bar'
3
+
4
+ // ─── Route Guard (Auth Protection) ────────────────────────────────────────────
5
+ // Uncomment the block below to enable authentication-based route protection.
6
+ // Make sure your auth store has an `isAuthenticated` boolean state.
7
+ //
8
+ // import { useAuthStore } from '@/stores/auth'
9
+ //
10
+ // const PUBLIC_ROUTES: string[] = [
11
+ // 'login',
12
+ // 'register',
13
+ // 'forgot-password',
14
+ // 'reset-password',
15
+ // 'verify-otp',
16
+ // 'confirm-email',
17
+ // ]
18
+ //
19
+ // router.beforeEach((to, _from, next) => {
20
+ // const authStore = useAuthStore()
21
+ // const isPublic = PUBLIC_ROUTES.includes(to.name as string)
22
+ //
23
+ // if (!isPublic && !authStore.isAuthenticated) {
24
+ // // Not authenticated → redirect to login, preserve intended destination
25
+ // next({ name: 'login', query: { redirect: to.fullPath } })
26
+ // } else if (isPublic && authStore.isAuthenticated) {
27
+ // // Already authenticated → skip auth pages, go straight to dashboard
28
+ // next({ name: 'dashboard' })
29
+ // } else {
30
+ // next()
31
+ // }
32
+ // })
33
+ // ──────────────────────────────────────────────────────────────────────────────
34
+
35
+ const router = createRouter({
36
+ history: createWebHistory(import.meta.env.BASE_URL),
37
+ routes: [
38
+ {
39
+ path: '/',
40
+ component: () => import('@/layouts/AdminLayout.vue'),
41
+ children: [
42
+ {
43
+ path: '',
44
+ name: 'dashboard',
45
+ component: () => import('@/views/dashboard/IndexView.vue'),
46
+ },
47
+ {
48
+ path: '_blank',
49
+ name: 'starter-blank',
50
+ component: () => import('@/views/_starter/BlankView.vue'),
51
+ },
52
+ {
53
+ path: 'errors/404',
54
+ name: 'not-found',
55
+ component: () => import('@/views/errors/NotFoundView.vue'),
56
+ },
57
+ {
58
+ path: 'errors/500',
59
+ name: 'server-error',
60
+ component: () => import('@/views/errors/ServerErrorView.vue'),
61
+ },
62
+ {
63
+ path: 'errors/403',
64
+ name: 'unauthorized',
65
+ component: () => import('@/views/errors/UnauthorizedView.vue'),
66
+ },
67
+ {
68
+ path: 'errors/maintenance',
69
+ name: 'maintenance',
70
+ component: () => import('@/views/errors/MaintenanceView.vue'),
71
+ },
72
+ {
73
+ path: 'errors/coming-soon',
74
+ name: 'coming-soon',
75
+ component: () => import('@/views/errors/ComingSoonView.vue'),
76
+ },
77
+ ],
78
+ },
79
+ {
80
+ path: '/auth',
81
+ component: () => import('@/layouts/AuthLayout.vue'),
82
+ children: [
83
+ {
84
+ path: 'login',
85
+ name: 'login',
86
+ component: () => import('@/views/auth/LoginView.vue'),
87
+ },
88
+ {
89
+ path: 'register',
90
+ name: 'register',
91
+ component: () => import('@/views/auth/RegisterView.vue'),
92
+ },
93
+ {
94
+ path: 'forgot-password',
95
+ name: 'forgot-password',
96
+ component: () => import('@/views/auth/ForgotPasswordView.vue'),
97
+ },
98
+ {
99
+ path: 'reset-password',
100
+ name: 'reset-password',
101
+ component: () => import('@/views/auth/ResetPasswordView.vue'),
102
+ },
103
+ {
104
+ path: 'confirm-email',
105
+ name: 'confirm-email',
106
+ component: () => import('@/views/auth/ConfirmEmailView.vue'),
107
+ },
108
+ {
109
+ path: 'verify-otp',
110
+ name: 'verify-otp',
111
+ component: () => import('@/views/auth/VerifyOtpView.vue'),
112
+ },
113
+ {
114
+ path: 'lock-screen',
115
+ name: 'lock-screen',
116
+ component: () => import('@/views/auth/LockScreenView.vue'),
117
+ },
118
+ ],
119
+ },
120
+ {
121
+ path: '/:pathMatch(.*)*',
122
+ component: () => import('@/layouts/AdminLayout.vue'),
123
+ children: [
124
+ {
125
+ path: '',
126
+ name: 'catch-all-not-found',
127
+ component: () => import('@/views/errors/NotFoundView.vue'),
128
+ },
129
+ ],
130
+ },
131
+ ],
132
+ })
133
+
134
+ // ─── Global Route Transition Loading Indicator ──────────────────────────────
135
+ router.beforeEach(() => {
136
+ loadingBar.start()
137
+ })
138
+
139
+ router.afterEach(() => {
140
+ loadingBar.finish()
141
+ })
142
+
143
+ router.onError(() => {
144
+ loadingBar.fail()
145
+ })
146
+
147
+ export default router
@@ -0,0 +1,93 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest'
2
+ import { authService, type LoginCredentials } from './auth.service'
3
+ import { apiClient, tokenStorage } from '@/lib/axios'
4
+
5
+ vi.mock('@/lib/axios', () => ({
6
+ apiClient: {
7
+ get: vi.fn(),
8
+ post: vi.fn(),
9
+ delete: vi.fn(),
10
+ },
11
+ tokenStorage: {
12
+ getAccessToken: vi.fn(),
13
+ setAccessToken: vi.fn(),
14
+ getRefreshToken: vi.fn(),
15
+ setRefreshToken: vi.fn(),
16
+ clearTokens: vi.fn(),
17
+ },
18
+ }))
19
+
20
+ describe('authService', () => {
21
+ beforeEach(() => {
22
+ vi.clearAllMocks()
23
+ })
24
+
25
+ it('stores tokens and returns data when login succeeds', async () => {
26
+ const mockApiResponse = {
27
+ user: {
28
+ id: 'u1',
29
+ name: 'Super Admin',
30
+ email: 'super@nala.dev',
31
+ avatar: '',
32
+ role: 'Admin',
33
+ },
34
+ accessToken: 'real_access_token_123',
35
+ refreshToken: 'real_refresh_token_123',
36
+ }
37
+
38
+ vi.mocked(apiClient.post).mockResolvedValueOnce({ data: mockApiResponse })
39
+
40
+ const credentials: LoginCredentials = {
41
+ email: 'super@nala.dev',
42
+ password: 'Password123!',
43
+ }
44
+
45
+ const result = await authService.login(credentials)
46
+
47
+ expect(apiClient.post).toHaveBeenCalledWith('/auth/login', credentials)
48
+ expect(tokenStorage.setAccessToken).toHaveBeenCalledWith('real_access_token_123')
49
+ expect(tokenStorage.setRefreshToken).toHaveBeenCalledWith('real_refresh_token_123')
50
+ expect(result.user.email).toBe('super@nala.dev')
51
+ })
52
+
53
+ it('provides fallback mock auth data when backend API is offline', async () => {
54
+ vi.mocked(apiClient.post).mockRejectedValueOnce(new Error('Network Error'))
55
+
56
+ const credentials: LoginCredentials = {
57
+ email: 'john@example.com',
58
+ password: 'password',
59
+ }
60
+
61
+ const result = await authService.login(credentials)
62
+
63
+ expect(tokenStorage.setAccessToken).toHaveBeenCalled()
64
+ expect(result.user.email).toBe('john@example.com')
65
+ expect(result.user.name).toBe('john')
66
+ expect(result.accessToken).toContain('mock_jwt_access_')
67
+ })
68
+
69
+ it('calls clearTokens on logout even if backend API fails', async () => {
70
+ vi.mocked(apiClient.post).mockRejectedValueOnce(new Error('500 Internal Server Error'))
71
+
72
+ await authService.logout()
73
+
74
+ expect(apiClient.post).toHaveBeenCalledWith('/auth/logout', {}, { skipToast: true })
75
+ expect(tokenStorage.clearTokens).toHaveBeenCalledTimes(1)
76
+ })
77
+
78
+ it('fetches user profile via getProfile', async () => {
79
+ const mockProfile = {
80
+ id: 'usr_1',
81
+ name: 'Admin User',
82
+ email: 'admin@example.com',
83
+ avatar: '/img/avatar.webp',
84
+ role: 'Administrator',
85
+ }
86
+
87
+ vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockProfile })
88
+
89
+ const result = await authService.getProfile()
90
+ expect(apiClient.get).toHaveBeenCalledWith('/auth/me')
91
+ expect(result.name).toBe('Admin User')
92
+ })
93
+ })
@@ -0,0 +1,243 @@
1
+ import { apiClient, tokenStorage } from '@/lib/axios'
2
+ import type { ApiResponse } from './types'
3
+
4
+ export interface LoginCredentials {
5
+ email: string
6
+ password?: string
7
+ rememberMe?: boolean
8
+ }
9
+
10
+ export interface RegisterPayload {
11
+ name: string
12
+ email: string
13
+ password?: string
14
+ agreeTerms?: boolean
15
+ }
16
+
17
+ export interface ResetPasswordPayload {
18
+ token?: string
19
+ email?: string
20
+ newPassword?: string
21
+ }
22
+
23
+ export interface UserProfile {
24
+ id: string
25
+ name: string
26
+ email: string
27
+ avatar: string
28
+ role: string
29
+ username?: string
30
+ bio?: string
31
+ timezone?: string
32
+ company?: string
33
+ phone?: string
34
+ jobTitle?: string
35
+ location?: string
36
+ website?: string
37
+ github?: string
38
+ twitter?: string
39
+ linkedin?: string
40
+ createdAt?: string
41
+ }
42
+
43
+ export interface UpdateProfilePayload {
44
+ name: string
45
+ email: string
46
+ username?: string
47
+ avatar?: string
48
+ bio?: string
49
+ timezone?: string
50
+ company?: string
51
+ phone?: string
52
+ jobTitle?: string
53
+ location?: string
54
+ website?: string
55
+ github?: string
56
+ twitter?: string
57
+ linkedin?: string
58
+ }
59
+
60
+ export interface AuthResponse {
61
+ user: UserProfile
62
+ accessToken: string
63
+ refreshToken?: string
64
+ expiresIn?: number
65
+ }
66
+
67
+ /**
68
+ * Authentication API Service
69
+ */
70
+ export const authService = {
71
+ /**
72
+ * Login user with credentials
73
+ */
74
+ async login(credentials: LoginCredentials): Promise<AuthResponse> {
75
+ try {
76
+ const res = await apiClient.post<ApiResponse<AuthResponse> | AuthResponse>(
77
+ '/auth/login',
78
+ credentials,
79
+ )
80
+ const authData = 'data' in res && res.data ? res.data : (res as AuthResponse)
81
+
82
+ if (authData.accessToken) {
83
+ tokenStorage.setAccessToken(authData.accessToken)
84
+ }
85
+ if (authData.refreshToken) {
86
+ tokenStorage.setRefreshToken(authData.refreshToken)
87
+ }
88
+
89
+ return authData
90
+ } catch {
91
+ // Fallback mock login for template preview when backend is not connected
92
+ const mockAuth: AuthResponse = {
93
+ user: {
94
+ id: 'usr_admin_1',
95
+ name: credentials.email.split('@')[0] || 'Admin User',
96
+ email: credentials.email,
97
+ avatar: '/img/avatar.webp',
98
+ role: 'Administrator',
99
+ username: 'admin',
100
+ timezone: 'utc-7',
101
+ company: 'Nala Corp',
102
+ },
103
+ accessToken: `mock_jwt_access_${Date.now()}`,
104
+ refreshToken: `mock_jwt_refresh_${Date.now()}`,
105
+ }
106
+
107
+ tokenStorage.setAccessToken(mockAuth.accessToken)
108
+ if (mockAuth.refreshToken) {
109
+ tokenStorage.setRefreshToken(mockAuth.refreshToken)
110
+ }
111
+
112
+ return mockAuth
113
+ }
114
+ },
115
+
116
+ /**
117
+ * Register new account
118
+ */
119
+ async register(payload: RegisterPayload): Promise<AuthResponse> {
120
+ try {
121
+ const res = await apiClient.post<ApiResponse<AuthResponse> | AuthResponse>(
122
+ '/auth/register',
123
+ payload,
124
+ )
125
+ return 'data' in res && res.data ? res.data : (res as AuthResponse)
126
+ } catch {
127
+ // Fallback mock register response
128
+ return {
129
+ user: {
130
+ id: `usr_${Date.now()}`,
131
+ name: payload.name,
132
+ email: payload.email,
133
+ avatar: '',
134
+ role: 'User',
135
+ },
136
+ accessToken: `mock_jwt_access_${Date.now()}`,
137
+ refreshToken: `mock_jwt_refresh_${Date.now()}`,
138
+ }
139
+ }
140
+ },
141
+
142
+ /**
143
+ * Request password reset link/email
144
+ */
145
+ async forgotPassword(email: string): Promise<{ success: boolean; message: string }> {
146
+ try {
147
+ const res = await apiClient.post<{ success: boolean; message: string }>(
148
+ '/auth/forgot-password',
149
+ { email },
150
+ )
151
+ return res
152
+ } catch {
153
+ return { success: true, message: 'Password reset link sent to your email.' }
154
+ }
155
+ },
156
+
157
+ /**
158
+ * Reset password with token
159
+ */
160
+ async resetPassword(payload: ResetPasswordPayload): Promise<{ success: boolean }> {
161
+ try {
162
+ return await apiClient.post<{ success: boolean }>('/auth/reset-password', payload)
163
+ } catch {
164
+ return { success: true }
165
+ }
166
+ },
167
+
168
+ /**
169
+ * Fetch current authenticated user profile
170
+ */
171
+ async getProfile(): Promise<UserProfile> {
172
+ const res = await apiClient.get<ApiResponse<UserProfile> | UserProfile>('/auth/me')
173
+ return 'data' in res && res.data ? res.data : (res as UserProfile)
174
+ },
175
+
176
+ /**
177
+ * Update authenticated user profile
178
+ */
179
+ async updateProfile(payload: UpdateProfilePayload): Promise<UserProfile> {
180
+ try {
181
+ const res = await apiClient.patch<ApiResponse<UserProfile> | UserProfile>(
182
+ '/auth/profile',
183
+ payload,
184
+ )
185
+ return 'data' in res && res.data ? res.data : (res as UserProfile)
186
+ } catch (error) {
187
+ if (import.meta.env.DEV) {
188
+ return {
189
+ id: 'usr_admin_1',
190
+ role: 'Administrator',
191
+ avatar: payload.avatar || '/img/avatar.webp',
192
+ name: payload.name,
193
+ email: payload.email,
194
+ username: payload.username || 'admin',
195
+ bio: payload.bio,
196
+ timezone: payload.timezone || 'utc-7',
197
+ company: payload.company || 'Nala Corp',
198
+ phone: payload.phone,
199
+ jobTitle: payload.jobTitle,
200
+ location: payload.location,
201
+ website: payload.website,
202
+ github: payload.github,
203
+ twitter: payload.twitter,
204
+ linkedin: payload.linkedin,
205
+ createdAt: '2025-01-15T00:00:00.000Z',
206
+ }
207
+ }
208
+ throw error
209
+ }
210
+ },
211
+
212
+ /**
213
+ * Delete account with password verification safeguard
214
+ */
215
+ async deleteAccount(password: string): Promise<{ success: boolean; message: string }> {
216
+ try {
217
+ const res = await apiClient.post<{ success: boolean; message: string }>('/auth/delete-account', { password })
218
+ return res
219
+ } catch (error) {
220
+ if (import.meta.env.DEV) {
221
+ tokenStorage.clearTokens()
222
+ return {
223
+ success: true,
224
+ message: 'Account successfully deleted and all sessions purged.',
225
+ }
226
+ }
227
+ throw error
228
+ }
229
+ },
230
+
231
+ /**
232
+ * Logout user and clear tokens
233
+ */
234
+ async logout(): Promise<void> {
235
+ try {
236
+ await apiClient.post('/auth/logout', {}, { skipToast: true })
237
+ } catch {
238
+ // Ignore errors during logout
239
+ } finally {
240
+ tokenStorage.clearTokens()
241
+ }
242
+ },
243
+ }
@@ -0,0 +1,3 @@
1
+ export * from './types'
2
+ export * from './auth.service'
3
+ export * from './user.service'
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Common API Response & Request Types
3
+ */
4
+
5
+ export interface ApiResponse<T = unknown> {
6
+ success: boolean
7
+ message?: string
8
+ data: T
9
+ }
10
+
11
+ export interface PaginationMeta {
12
+ page: number
13
+ perPage: number
14
+ total: number
15
+ totalPages: number
16
+ }
17
+
18
+ export interface PaginatedResponse<T = unknown> {
19
+ data: T[]
20
+ meta: PaginationMeta
21
+ }
22
+
23
+ export interface ApiError {
24
+ statusCode: number
25
+ message: string
26
+ error?: string
27
+ details?: Record<string, string[]>
28
+ }
@@ -0,0 +1,91 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest'
2
+ import { userService, type CreateUserPayload, type UpdateUserPayload } from './user.service'
3
+ import { apiClient } from '@/lib/axios'
4
+
5
+ vi.mock('@/lib/axios', () => ({
6
+ apiClient: {
7
+ get: vi.fn(),
8
+ post: vi.fn(),
9
+ put: vi.fn(),
10
+ patch: vi.fn(),
11
+ delete: vi.fn(),
12
+ },
13
+ }))
14
+
15
+ describe('userService', () => {
16
+ beforeEach(() => {
17
+ vi.clearAllMocks()
18
+ })
19
+
20
+ it('fetches paginated users with query params', async () => {
21
+ const mockData = [
22
+ {
23
+ id: 'usr-1',
24
+ name: 'Test User',
25
+ email: 'test@example.com',
26
+ role: 'Admin' as const,
27
+ status: 'Active' as const,
28
+ twoFactor: true,
29
+ avatar: '',
30
+ initials: 'TU',
31
+ lastActive: 'Just now',
32
+ createdAt: 'Jan 1, 2026',
33
+ },
34
+ ]
35
+
36
+ vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockData })
37
+
38
+ const result = await userService.getUsers({ page: 1, perPage: 10, search: 'Test' })
39
+ expect(apiClient.get).toHaveBeenCalledWith('/users', {
40
+ params: { page: 1, perPage: 10, search: 'Test' },
41
+ skipToast: true,
42
+ })
43
+ expect(result.length).toBe(1)
44
+ expect(result[0].name).toBe('Test User')
45
+ })
46
+
47
+ it('uses mock fallback when getUsers API fails', async () => {
48
+ vi.mocked(apiClient.get).mockRejectedValueOnce(new Error('Network Error'))
49
+
50
+ const result = await userService.getUsers({ search: 'Olivia' })
51
+ expect(result.length).toBeGreaterThan(0)
52
+ expect(result[0].name).toContain('Olivia')
53
+ })
54
+
55
+ it('creates user with initials generated when avatar is not provided', async () => {
56
+ const payload: CreateUserPayload = {
57
+ name: 'Sarah Connor',
58
+ email: 'sarah@example.com',
59
+ role: 'Developer',
60
+ status: 'Active',
61
+ twoFactor: false,
62
+ }
63
+
64
+ vi.mocked(apiClient.post).mockRejectedValueOnce(new Error('Dev Mode Offline'))
65
+
66
+ const created = await userService.createUser(payload)
67
+ expect(created.name).toBe('Sarah Connor')
68
+ expect(created.initials).toBe('SC')
69
+ expect(created.id).toBeDefined()
70
+ })
71
+
72
+ it('updates existing user attributes correctly', async () => {
73
+ const updatePayload: UpdateUserPayload = {
74
+ role: 'Admin',
75
+ status: 'Suspended',
76
+ }
77
+
78
+ vi.mocked(apiClient.patch).mockRejectedValueOnce(new Error('Dev Mode Offline'))
79
+
80
+ const updated = await userService.updateUser('usr-1', updatePayload)
81
+ expect(updated.role).toBe('Admin')
82
+ expect(updated.status).toBe('Suspended')
83
+ })
84
+
85
+ it('deletes user by id', async () => {
86
+ vi.mocked(apiClient.delete).mockResolvedValueOnce({ data: { success: true } })
87
+
88
+ await expect(userService.deleteUser('usr-1')).resolves.not.toThrow()
89
+ expect(apiClient.delete).toHaveBeenCalledWith('/users/usr-1')
90
+ })
91
+ })