kaze 1.3.1 → 2.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 (263) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +72 -1
  3. data/README.md +1 -1
  4. data/lib/kaze/commands/app/hotwire_stack.rb +2 -0
  5. data/lib/kaze/commands/app/inertia_react_stack.rb +5 -1
  6. data/lib/kaze/commands/app/inertia_vue_stack.rb +5 -1
  7. data/lib/kaze/commands/app.rb +9 -2
  8. data/lib/kaze/version.rb +1 -1
  9. data/stubs/default/config/routes.rb +11 -4
  10. data/stubs/default/test/integration/appearance_test.rb +11 -0
  11. data/stubs/default/test/integration/password_update_test.rb +9 -1
  12. data/stubs/hotwire/app/assets/tailwind/application.css +123 -0
  13. data/stubs/hotwire/app/components/breadcrumbs_component.rb +32 -0
  14. data/stubs/hotwire/app/components/danger_button_component.rb +1 -1
  15. data/stubs/hotwire/app/components/dropdown_component.html.erb +2 -2
  16. data/stubs/hotwire/app/components/dropdown_component.rb +5 -1
  17. data/stubs/hotwire/app/components/dropdown_link_component.rb +1 -1
  18. data/stubs/hotwire/app/components/heading_component.rb +19 -0
  19. data/stubs/hotwire/app/components/icon_component.rb +30 -0
  20. data/stubs/hotwire/app/components/input_error_component.rb +1 -1
  21. data/stubs/hotwire/app/components/input_label_component.rb +3 -3
  22. data/stubs/hotwire/app/components/modal_component.html.erb +2 -2
  23. data/stubs/hotwire/app/components/primary_button_component.rb +1 -1
  24. data/stubs/hotwire/app/components/secondary_button_component.rb +1 -1
  25. data/stubs/hotwire/app/components/sidebar_menu_button_component.rb +25 -0
  26. data/stubs/hotwire/app/components/text_input_component.rb +1 -1
  27. data/stubs/hotwire/app/components/user_avatar_component.rb +27 -0
  28. data/stubs/hotwire/app/controllers/application_controller.rb +1 -0
  29. data/stubs/hotwire/app/controllers/concerns/sidebar_state.rb +14 -0
  30. data/stubs/hotwire/app/controllers/settings/appearance_controller.rb +7 -0
  31. data/stubs/hotwire/app/controllers/settings/password_controller.rb +19 -0
  32. data/stubs/hotwire/app/controllers/{profile_controller.rb → settings/profile_controller.rb} +3 -4
  33. data/stubs/hotwire/app/views/auth/login.html.erb +1 -1
  34. data/stubs/hotwire/app/views/dashboard/index.html.erb +6 -11
  35. data/stubs/hotwire/app/views/layouts/_appearance.html.erb +18 -0
  36. data/stubs/hotwire/app/views/layouts/_settings.html.erb +26 -0
  37. data/stubs/hotwire/app/views/layouts/_sidebar.html.erb +56 -0
  38. data/stubs/hotwire/app/views/layouts/_sidebar_header.html.erb +11 -0
  39. data/stubs/hotwire/app/views/layouts/_sidebar_user.html.erb +34 -0
  40. data/stubs/hotwire/app/views/layouts/application.html.erb +25 -15
  41. data/stubs/hotwire/app/views/layouts/guest.html.erb +1 -0
  42. data/stubs/hotwire/app/views/settings/appearance/_update_appearance_form.html.erb +28 -0
  43. data/stubs/hotwire/app/views/settings/appearance/edit.html.erb +10 -0
  44. data/stubs/hotwire/app/views/{profile/partials → settings/password}/_update_password_form.html.erb +2 -10
  45. data/stubs/hotwire/app/views/settings/password/edit.html.erb +10 -0
  46. data/stubs/hotwire/app/views/{profile/partials → settings/profile}/_confirm_user_deletion_modal.html.erb +2 -2
  47. data/stubs/hotwire/app/views/settings/profile/_delete_user_form.html.erb +6 -0
  48. data/stubs/hotwire/app/views/{profile/partials → settings/profile}/_update_profile_information_form.html.erb +4 -12
  49. data/stubs/hotwire/app/views/{profile → settings/profile}/destroy.turbo_stream.erb +1 -1
  50. data/stubs/hotwire/app/views/settings/profile/edit.html.erb +14 -0
  51. data/stubs/hotwire/app/views/welcome/index.html.erb +1 -0
  52. data/stubs/inertia-common/app/controllers/concerns/handle_inertia_requests.rb +4 -1
  53. data/stubs/inertia-common/app/controllers/settings/appearance_controller.rb +7 -0
  54. data/stubs/inertia-common/app/controllers/settings/password_controller.rb +17 -0
  55. data/stubs/inertia-common/app/controllers/{profile_controller.rb → settings/profile_controller.rb} +2 -2
  56. data/stubs/inertia-common/test/integration/appearance_test.rb +11 -0
  57. data/stubs/inertia-common/test/integration/password_update_test.rb +10 -2
  58. data/stubs/inertia-react-ts/app/assets/tailwind/application.css +124 -0
  59. data/stubs/inertia-react-ts/app/javascript/Components/AppLogo.tsx +17 -0
  60. data/stubs/inertia-react-ts/app/javascript/Components/AppShell.tsx +9 -0
  61. data/stubs/inertia-react-ts/app/javascript/Components/AppSidebar.tsx +65 -0
  62. data/stubs/inertia-react-ts/app/javascript/Components/AppSidebarHeader.tsx +14 -0
  63. data/stubs/inertia-react-ts/app/javascript/Components/Breadcrumbs.tsx +43 -0
  64. data/stubs/inertia-react-ts/app/javascript/Components/Heading.tsx +19 -0
  65. data/stubs/inertia-react-ts/app/javascript/Components/InputError.tsx +1 -1
  66. data/stubs/inertia-react-ts/app/javascript/Components/NavFooter.tsx +32 -0
  67. data/stubs/inertia-react-ts/app/javascript/Components/NavMain.tsx +32 -0
  68. data/stubs/inertia-react-ts/app/javascript/Components/NavUser.tsx +39 -0
  69. data/stubs/inertia-react-ts/app/javascript/Components/UserInfo.tsx +23 -0
  70. data/stubs/inertia-react-ts/app/javascript/Components/UserMenuContent.tsx +57 -0
  71. data/stubs/inertia-react-ts/app/javascript/Components/ui/avatar.tsx +51 -0
  72. data/stubs/inertia-react-ts/app/javascript/Components/ui/breadcrumb.tsx +109 -0
  73. data/stubs/inertia-react-ts/app/javascript/Components/ui/button.tsx +49 -0
  74. data/stubs/inertia-react-ts/app/javascript/Components/ui/checkbox.tsx +27 -0
  75. data/stubs/inertia-react-ts/app/javascript/Components/ui/dialog.tsx +109 -0
  76. data/stubs/inertia-react-ts/app/javascript/Components/ui/dropdown-menu.tsx +217 -0
  77. data/stubs/inertia-react-ts/app/javascript/Components/ui/input.tsx +21 -0
  78. data/stubs/inertia-react-ts/app/javascript/Components/ui/label.tsx +19 -0
  79. data/stubs/inertia-react-ts/app/javascript/Components/ui/separator.tsx +26 -0
  80. data/stubs/inertia-react-ts/app/javascript/Components/ui/sheet.tsx +137 -0
  81. data/stubs/inertia-react-ts/app/javascript/Components/ui/sidebar.tsx +719 -0
  82. data/stubs/inertia-react-ts/app/javascript/Components/ui/skeleton.tsx +13 -0
  83. data/stubs/inertia-react-ts/app/javascript/Components/ui/tooltip.tsx +55 -0
  84. data/stubs/inertia-react-ts/app/javascript/Layouts/AuthenticatedLayout.tsx +19 -127
  85. data/stubs/inertia-react-ts/app/javascript/Layouts/GuestLayout.tsx +2 -2
  86. data/stubs/inertia-react-ts/app/javascript/Layouts/SettingsLayout.tsx +57 -0
  87. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/ForgotPassword.tsx +6 -6
  88. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/Login.tsx +17 -12
  89. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/Register.tsx +14 -14
  90. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/ResetPassword.tsx +10 -10
  91. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/VerifyEmail.tsx +2 -2
  92. data/stubs/inertia-react-ts/app/javascript/Pages/Dashboard.tsx +13 -8
  93. data/stubs/inertia-react-ts/app/javascript/Pages/Settings/Appearance.tsx +33 -0
  94. data/stubs/inertia-react-ts/app/javascript/Pages/Settings/Partials/DeleteUserForm.tsx +107 -0
  95. data/stubs/inertia-react-ts/app/javascript/Pages/Settings/Partials/UpdateAppearanceForm.tsx +41 -0
  96. data/stubs/inertia-react-ts/app/javascript/Pages/{Profile → Settings}/Partials/UpdatePasswordForm.tsx +12 -20
  97. data/stubs/inertia-react-ts/app/javascript/Pages/{Profile → Settings}/Partials/UpdateProfileInformationForm.tsx +13 -21
  98. data/stubs/inertia-react-ts/app/javascript/Pages/Settings/Password.tsx +33 -0
  99. data/stubs/inertia-react-ts/app/javascript/Pages/Settings/Profile.tsx +35 -0
  100. data/stubs/inertia-react-ts/app/javascript/entrypoints/application.tsx +6 -1
  101. data/stubs/inertia-react-ts/app/javascript/hooks/use-initials.tsx +23 -0
  102. data/stubs/inertia-react-ts/app/javascript/hooks/use-mobile-navigation.tsx +11 -0
  103. data/stubs/inertia-react-ts/app/javascript/hooks/use-mobile.tsx +36 -0
  104. data/stubs/inertia-react-ts/app/javascript/lib/appearance.ts +22 -0
  105. data/stubs/inertia-react-ts/app/javascript/lib/utils.ts +6 -0
  106. data/stubs/inertia-react-ts/app/javascript/types/global.d.ts +2 -2
  107. data/stubs/inertia-react-ts/app/javascript/types/index.d.ts +14 -0
  108. data/stubs/inertia-react-ts/app/views/layouts/application.html.erb +19 -1
  109. data/stubs/inertia-react-ts/components.json +21 -0
  110. data/stubs/inertia-react-ts/package.json +13 -0
  111. data/stubs/inertia-vue-ts/app/assets/tailwind/application.css +124 -0
  112. data/stubs/inertia-vue-ts/app/javascript/Components/AppLogo.vue +17 -0
  113. data/stubs/inertia-vue-ts/app/javascript/Components/AppShell.vue +12 -0
  114. data/stubs/inertia-vue-ts/app/javascript/Components/AppSidebar.vue +65 -0
  115. data/stubs/inertia-vue-ts/app/javascript/Components/AppSidebarHeader.vue +20 -0
  116. data/stubs/inertia-vue-ts/app/javascript/Components/Breadcrumbs.vue +32 -0
  117. data/stubs/inertia-vue-ts/app/javascript/Components/Heading.vue +15 -0
  118. data/stubs/inertia-vue-ts/app/javascript/Components/InputError.vue +1 -1
  119. data/stubs/inertia-vue-ts/app/javascript/Components/NavFooter.vue +33 -0
  120. data/stubs/inertia-vue-ts/app/javascript/Components/NavMain.vue +32 -0
  121. data/stubs/inertia-vue-ts/app/javascript/Components/NavUser.vue +40 -0
  122. data/stubs/inertia-vue-ts/app/javascript/Components/UserInfo.vue +25 -0
  123. data/stubs/inertia-vue-ts/app/javascript/Components/UserMenuContent.vue +47 -0
  124. data/stubs/inertia-vue-ts/app/javascript/Components/ui/avatar/Avatar.vue +18 -0
  125. data/stubs/inertia-vue-ts/app/javascript/Components/ui/avatar/AvatarFallback.vue +21 -0
  126. data/stubs/inertia-vue-ts/app/javascript/Components/ui/avatar/AvatarImage.vue +16 -0
  127. data/stubs/inertia-vue-ts/app/javascript/Components/ui/avatar/index.ts +3 -0
  128. data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/Breadcrumb.vue +17 -0
  129. data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/BreadcrumbEllipsis.vue +23 -0
  130. data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/BreadcrumbItem.vue +17 -0
  131. data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/BreadcrumbLink.vue +21 -0
  132. data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/BreadcrumbList.vue +17 -0
  133. data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/BreadcrumbPage.vue +20 -0
  134. data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/BreadcrumbSeparator.vue +22 -0
  135. data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/index.ts +7 -0
  136. data/stubs/inertia-vue-ts/app/javascript/Components/ui/button/Button.vue +31 -0
  137. data/stubs/inertia-vue-ts/app/javascript/Components/ui/button/index.ts +34 -0
  138. data/stubs/inertia-vue-ts/app/javascript/Components/ui/checkbox/Checkbox.vue +35 -0
  139. data/stubs/inertia-vue-ts/app/javascript/Components/ui/checkbox/index.ts +1 -0
  140. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/Dialog.vue +15 -0
  141. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogClose.vue +12 -0
  142. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogContent.vue +52 -0
  143. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogDescription.vue +23 -0
  144. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogFooter.vue +25 -0
  145. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogHeader.vue +14 -0
  146. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogOverlay.vue +26 -0
  147. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogTitle.vue +23 -0
  148. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogTrigger.vue +12 -0
  149. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/index.ts +9 -0
  150. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/DropdownMenu.vue +15 -0
  151. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/DropdownMenuContent.vue +37 -0
  152. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/DropdownMenuGroup.vue +12 -0
  153. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/DropdownMenuItem.vue +41 -0
  154. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/DropdownMenuLabel.vue +23 -0
  155. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/DropdownMenuSeparator.vue +23 -0
  156. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/DropdownMenuTrigger.vue +14 -0
  157. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/index.ts +7 -0
  158. data/stubs/inertia-vue-ts/app/javascript/Components/ui/input/Input.vue +50 -0
  159. data/stubs/inertia-vue-ts/app/javascript/Components/ui/input/index.ts +1 -0
  160. data/stubs/inertia-vue-ts/app/javascript/Components/ui/label/Label.vue +26 -0
  161. data/stubs/inertia-vue-ts/app/javascript/Components/ui/label/index.ts +1 -0
  162. data/stubs/inertia-vue-ts/app/javascript/Components/ui/separator/Separator.vue +29 -0
  163. data/stubs/inertia-vue-ts/app/javascript/Components/ui/separator/index.ts +1 -0
  164. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/Sheet.vue +19 -0
  165. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetClose.vue +15 -0
  166. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetContent.vue +62 -0
  167. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetDescription.vue +21 -0
  168. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetFooter.vue +16 -0
  169. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetHeader.vue +15 -0
  170. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetOverlay.vue +21 -0
  171. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetTitle.vue +21 -0
  172. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetTrigger.vue +15 -0
  173. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/index.ts +8 -0
  174. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/Sidebar.vue +96 -0
  175. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarContent.vue +18 -0
  176. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarFooter.vue +18 -0
  177. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarGroup.vue +18 -0
  178. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarGroupAction.vue +27 -0
  179. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarGroupContent.vue +18 -0
  180. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarGroupLabel.vue +25 -0
  181. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarHeader.vue +18 -0
  182. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarInput.vue +22 -0
  183. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarInset.vue +21 -0
  184. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenu.vue +18 -0
  185. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuAction.vue +35 -0
  186. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuBadge.vue +26 -0
  187. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuButton.vue +48 -0
  188. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuButtonChild.vue +36 -0
  189. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuItem.vue +18 -0
  190. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuSkeleton.vue +35 -0
  191. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuSub.vue +22 -0
  192. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuSubButton.vue +36 -0
  193. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuSubItem.vue +18 -0
  194. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarProvider.vue +82 -0
  195. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarRail.vue +33 -0
  196. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarSeparator.vue +19 -0
  197. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarTrigger.vue +28 -0
  198. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/index.ts +60 -0
  199. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/utils.ts +19 -0
  200. data/stubs/inertia-vue-ts/app/javascript/Components/ui/skeleton/Skeleton.vue +17 -0
  201. data/stubs/inertia-vue-ts/app/javascript/Components/ui/skeleton/index.ts +1 -0
  202. data/stubs/inertia-vue-ts/app/javascript/Components/ui/tooltip/Tooltip.vue +19 -0
  203. data/stubs/inertia-vue-ts/app/javascript/Components/ui/tooltip/TooltipContent.vue +34 -0
  204. data/stubs/inertia-vue-ts/app/javascript/Components/ui/tooltip/TooltipProvider.vue +14 -0
  205. data/stubs/inertia-vue-ts/app/javascript/Components/ui/tooltip/TooltipTrigger.vue +15 -0
  206. data/stubs/inertia-vue-ts/app/javascript/Components/ui/tooltip/index.ts +4 -0
  207. data/stubs/inertia-vue-ts/app/javascript/Layouts/AuthenticatedLayout.vue +17 -147
  208. data/stubs/inertia-vue-ts/app/javascript/Layouts/SettingsLayout.vue +57 -0
  209. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/ForgotPassword.vue +7 -7
  210. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/Login.vue +10 -12
  211. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/Register.vue +12 -14
  212. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/ResetPassword.vue +8 -10
  213. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/VerifyEmail.vue +3 -3
  214. data/stubs/inertia-vue-ts/app/javascript/Pages/Dashboard.vue +15 -10
  215. data/stubs/inertia-vue-ts/app/javascript/Pages/Settings/Appearance.vue +33 -0
  216. data/stubs/inertia-vue-ts/app/javascript/Pages/Settings/Partials/DeleteUserForm.vue +96 -0
  217. data/stubs/inertia-vue-ts/app/javascript/Pages/Settings/Partials/UpdateAppearanceForm.vue +42 -0
  218. data/stubs/inertia-vue-ts/app/javascript/Pages/{Profile → Settings}/Partials/UpdatePasswordForm.vue +12 -20
  219. data/stubs/inertia-vue-ts/app/javascript/Pages/{Profile → Settings}/Partials/UpdateProfileInformationForm.vue +12 -20
  220. data/stubs/inertia-vue-ts/app/javascript/Pages/Settings/Password.vue +33 -0
  221. data/stubs/inertia-vue-ts/app/javascript/Pages/Settings/Profile.vue +40 -0
  222. data/stubs/inertia-vue-ts/app/javascript/composables/useInitials.ts +29 -0
  223. data/stubs/inertia-vue-ts/app/javascript/lib/appearance.ts +22 -0
  224. data/stubs/inertia-vue-ts/app/javascript/lib/utils.ts +6 -0
  225. data/stubs/inertia-vue-ts/app/javascript/types/index.d.ts +14 -0
  226. data/stubs/inertia-vue-ts/app/javascript/types/vue-shims.d.ts +10 -0
  227. data/stubs/inertia-vue-ts/app/views/layouts/application.html.erb +19 -1
  228. data/stubs/inertia-vue-ts/components.json +20 -0
  229. data/stubs/inertia-vue-ts/package.json +8 -1
  230. data/stubs/inertia-vue-ts/vite.config.ts +25 -1
  231. metadata +174 -43
  232. data/stubs/hotwire/app/components/nav_link_component.rb +0 -15
  233. data/stubs/hotwire/app/components/responsive_nav_link_component.rb +0 -15
  234. data/stubs/hotwire/app/controllers/password_controller.rb +0 -13
  235. data/stubs/hotwire/app/views/layouts/_navigation.html.erb +0 -82
  236. data/stubs/hotwire/app/views/profile/edit.html.erb +0 -24
  237. data/stubs/hotwire/app/views/profile/partials/_delete_user_form.html.erb +0 -14
  238. data/stubs/inertia-common/app/controllers/password_controller.rb +0 -13
  239. data/stubs/inertia-react-ts/app/javascript/Components/Checkbox.tsx +0 -11
  240. data/stubs/inertia-react-ts/app/javascript/Components/DangerButton.tsx +0 -20
  241. data/stubs/inertia-react-ts/app/javascript/Components/Dropdown.tsx +0 -100
  242. data/stubs/inertia-react-ts/app/javascript/Components/InputLabel.tsx +0 -14
  243. data/stubs/inertia-react-ts/app/javascript/Components/Modal.tsx +0 -66
  244. data/stubs/inertia-react-ts/app/javascript/Components/NavLink.tsx +0 -21
  245. data/stubs/inertia-react-ts/app/javascript/Components/PrimaryButton.tsx +0 -20
  246. data/stubs/inertia-react-ts/app/javascript/Components/ResponsiveNavLink.tsx +0 -21
  247. data/stubs/inertia-react-ts/app/javascript/Components/SecondaryButton.tsx +0 -22
  248. data/stubs/inertia-react-ts/app/javascript/Components/TextInput.tsx +0 -32
  249. data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Edit.tsx +0 -32
  250. data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/DeleteUserForm.tsx +0 -102
  251. data/stubs/inertia-vue-ts/app/javascript/Components/Checkbox.vue +0 -29
  252. data/stubs/inertia-vue-ts/app/javascript/Components/DangerButton.vue +0 -7
  253. data/stubs/inertia-vue-ts/app/javascript/Components/Dropdown.vue +0 -75
  254. data/stubs/inertia-vue-ts/app/javascript/Components/DropdownLink.vue +0 -16
  255. data/stubs/inertia-vue-ts/app/javascript/Components/InputLabel.vue +0 -12
  256. data/stubs/inertia-vue-ts/app/javascript/Components/Modal.vue +0 -109
  257. data/stubs/inertia-vue-ts/app/javascript/Components/NavLink.vue +0 -21
  258. data/stubs/inertia-vue-ts/app/javascript/Components/PrimaryButton.vue +0 -7
  259. data/stubs/inertia-vue-ts/app/javascript/Components/ResponsiveNavLink.vue +0 -21
  260. data/stubs/inertia-vue-ts/app/javascript/Components/SecondaryButton.vue +0 -19
  261. data/stubs/inertia-vue-ts/app/javascript/Components/TextInput.vue +0 -23
  262. data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Edit.vue +0 -38
  263. data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/DeleteUserForm.vue +0 -99
@@ -1,6 +1,14 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class PasswordUpdateTest < ActionDispatch::IntegrationTest
4
+ test 'password page is displayed' do
5
+ user = FactoryBot.create(:user)
6
+
7
+ acting_as(user).get password_edit_path
8
+
9
+ assert_response :success
10
+ end
11
+
4
12
  test 'password can be updated' do
5
13
  user = FactoryBot.create(:user)
6
14
 
@@ -10,7 +18,7 @@ class PasswordUpdateTest < ActionDispatch::IntegrationTest
10
18
  password_confirmation: 'new-password'
11
19
  }
12
20
 
13
- assert_redirected_to profile_edit_path
21
+ assert_redirected_to password_edit_path
14
22
  assert BCrypt::Password.new(user.reload.password_digest).is_password?('new-password')
15
23
  end
16
24
 
@@ -23,6 +31,6 @@ class PasswordUpdateTest < ActionDispatch::IntegrationTest
23
31
  password_confirmation: 'new-password'
24
32
  }
25
33
 
26
- assert_redirected_to profile_edit_path
34
+ assert_redirected_to password_edit_path
27
35
  end
28
36
  end
@@ -1,4 +1,128 @@
1
1
  @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+
4
+ /* Drive `dark:` from a `dark` class rather than prefers-color-scheme, so the
5
+ appearance preference can override the operating system. The layouts apply
6
+ the class before first paint. */
7
+ @custom-variant dark (&:is(.dark *));
8
+
9
+ @theme inline {
10
+ --color-background: var(--background);
11
+ --color-foreground: var(--foreground);
12
+
13
+ --color-card: var(--card);
14
+ --color-card-foreground: var(--card-foreground);
15
+
16
+ --color-popover: var(--popover);
17
+ --color-popover-foreground: var(--popover-foreground);
18
+
19
+ --color-primary: var(--primary);
20
+ --color-primary-foreground: var(--primary-foreground);
21
+
22
+ --color-secondary: var(--secondary);
23
+ --color-secondary-foreground: var(--secondary-foreground);
24
+
25
+ --color-muted: var(--muted);
26
+ --color-muted-foreground: var(--muted-foreground);
27
+
28
+ --color-accent: var(--accent);
29
+ --color-accent-foreground: var(--accent-foreground);
30
+
31
+ --color-destructive: var(--destructive);
32
+ --color-destructive-foreground: var(--destructive-foreground);
33
+
34
+ --color-border: var(--border);
35
+ --color-input: var(--input);
36
+ --color-ring: var(--ring);
37
+
38
+ --color-sidebar: var(--sidebar);
39
+ --color-sidebar-foreground: var(--sidebar-foreground);
40
+ --color-sidebar-primary: var(--sidebar-primary);
41
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
42
+ --color-sidebar-accent: var(--sidebar-accent);
43
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
44
+ --color-sidebar-border: var(--sidebar-border);
45
+ --color-sidebar-ring: var(--sidebar-ring);
46
+
47
+ --radius-sm: calc(var(--radius) - 4px);
48
+ --radius-md: calc(var(--radius) - 2px);
49
+ --radius-lg: var(--radius);
50
+ --radius-xl: calc(var(--radius) + 4px);
51
+ }
52
+
53
+ :root {
54
+ --radius: 0.625rem;
55
+
56
+ --background: oklch(1 0 0);
57
+ --foreground: oklch(0.145 0 0);
58
+ --card: oklch(1 0 0);
59
+ --card-foreground: oklch(0.145 0 0);
60
+ --popover: oklch(1 0 0);
61
+ --popover-foreground: oklch(0.145 0 0);
62
+ --primary: oklch(0.205 0 0);
63
+ --primary-foreground: oklch(0.985 0 0);
64
+ --secondary: oklch(0.97 0 0);
65
+ --secondary-foreground: oklch(0.205 0 0);
66
+ --muted: oklch(0.97 0 0);
67
+ --muted-foreground: oklch(0.556 0 0);
68
+ --accent: oklch(0.97 0 0);
69
+ --accent-foreground: oklch(0.205 0 0);
70
+ --destructive: oklch(0.577 0.245 27.325);
71
+ --destructive-foreground: oklch(0.985 0 0);
72
+ --border: oklch(0.922 0 0);
73
+ --input: oklch(0.922 0 0);
74
+ --ring: oklch(0.87 0 0);
75
+
76
+ --sidebar: oklch(0.985 0 0);
77
+ --sidebar-foreground: oklch(0.145 0 0);
78
+ --sidebar-primary: oklch(0.205 0 0);
79
+ --sidebar-primary-foreground: oklch(0.985 0 0);
80
+ --sidebar-accent: oklch(0.97 0 0);
81
+ --sidebar-accent-foreground: oklch(0.205 0 0);
82
+ --sidebar-border: oklch(0.922 0 0);
83
+ --sidebar-ring: oklch(0.87 0 0);
84
+ }
85
+
86
+ .dark {
87
+ --background: oklch(0.145 0 0);
88
+ --foreground: oklch(0.985 0 0);
89
+ --card: oklch(0.145 0 0);
90
+ --card-foreground: oklch(0.985 0 0);
91
+ --popover: oklch(0.145 0 0);
92
+ --popover-foreground: oklch(0.985 0 0);
93
+ --primary: oklch(0.985 0 0);
94
+ --primary-foreground: oklch(0.205 0 0);
95
+ --secondary: oklch(0.269 0 0);
96
+ --secondary-foreground: oklch(0.985 0 0);
97
+ --muted: oklch(0.269 0 0);
98
+ --muted-foreground: oklch(0.708 0 0);
99
+ --accent: oklch(0.269 0 0);
100
+ --accent-foreground: oklch(0.985 0 0);
101
+ --destructive: oklch(0.396 0.141 25.723);
102
+ --destructive-foreground: oklch(0.985 0 0);
103
+ --border: oklch(0.269 0 0);
104
+ --input: oklch(0.269 0 0);
105
+ --ring: oklch(0.439 0 0);
106
+
107
+ --sidebar: oklch(0.205 0 0);
108
+ --sidebar-foreground: oklch(0.985 0 0);
109
+ --sidebar-primary: oklch(0.985 0 0);
110
+ --sidebar-primary-foreground: oklch(0.985 0 0);
111
+ --sidebar-accent: oklch(0.269 0 0);
112
+ --sidebar-accent-foreground: oklch(0.985 0 0);
113
+ --sidebar-border: oklch(0.269 0 0);
114
+ --sidebar-ring: oklch(0.439 0 0);
115
+ }
116
+
117
+ @layer base {
118
+ * {
119
+ @apply border-border outline-ring/50;
120
+ }
121
+
122
+ body {
123
+ @apply bg-background text-foreground;
124
+ }
125
+ }
2
126
 
3
127
  @plugin "@tailwindcss/forms";
4
128
 
@@ -0,0 +1,17 @@
1
+ import ApplicationLogo from '@/Components/ApplicationLogo'
2
+
3
+ export default function AppLogo() {
4
+ const appName = import.meta.env.VITE_APP_NAME || 'Rails'
5
+
6
+ return (
7
+ <>
8
+ <div className="flex aspect-square size-8 items-center justify-center rounded-md bg-sidebar-primary text-sidebar-primary-foreground">
9
+ <ApplicationLogo className="size-5 fill-current text-white dark:text-black" />
10
+ </div>
11
+
12
+ <div className="ml-1 grid flex-1 text-left text-sm">
13
+ <span className="mb-0.5 truncate leading-tight font-semibold">{appName}</span>
14
+ </div>
15
+ </>
16
+ )
17
+ }
@@ -0,0 +1,9 @@
1
+ import { PropsWithChildren } from 'react'
2
+ import { usePage } from '@inertiajs/react'
3
+ import { SidebarProvider } from '@/Components/ui/sidebar'
4
+
5
+ export default function AppShell({ children }: PropsWithChildren) {
6
+ const sidebarOpen = usePage().props.sidebar_open
7
+
8
+ return <SidebarProvider defaultOpen={sidebarOpen}>{children}</SidebarProvider>
9
+ }
@@ -0,0 +1,65 @@
1
+ import { Link } from '@inertiajs/react'
2
+ import { BookOpen, FolderGit2, LayoutGrid } from 'lucide-react'
3
+ import AppLogo from '@/Components/AppLogo'
4
+ import NavFooter from '@/Components/NavFooter'
5
+ import NavMain from '@/Components/NavMain'
6
+ import NavUser from '@/Components/NavUser'
7
+ import {
8
+ Sidebar,
9
+ SidebarContent,
10
+ SidebarFooter,
11
+ SidebarHeader,
12
+ SidebarMenu,
13
+ SidebarMenuButton,
14
+ SidebarMenuItem,
15
+ } from '@/Components/ui/sidebar'
16
+ import { dashboard_path } from '@/routes'
17
+ import { NavItem } from '@/types'
18
+
19
+ const mainNavItems: NavItem[] = [
20
+ {
21
+ title: 'Dashboard',
22
+ href: dashboard_path(),
23
+ icon: LayoutGrid,
24
+ },
25
+ ]
26
+
27
+ const footerNavItems: NavItem[] = [
28
+ {
29
+ title: 'Repository',
30
+ href: 'https://github.com/cuonggt/kaze',
31
+ icon: FolderGit2,
32
+ },
33
+ {
34
+ title: 'Documentation',
35
+ href: 'https://github.com/cuonggt/kaze#readme',
36
+ icon: BookOpen,
37
+ },
38
+ ]
39
+
40
+ export default function AppSidebar() {
41
+ return (
42
+ <Sidebar collapsible="icon" variant="inset">
43
+ <SidebarHeader>
44
+ <SidebarMenu>
45
+ <SidebarMenuItem>
46
+ <SidebarMenuButton size="lg" asChild>
47
+ <Link href={dashboard_path()} prefetch>
48
+ <AppLogo />
49
+ </Link>
50
+ </SidebarMenuButton>
51
+ </SidebarMenuItem>
52
+ </SidebarMenu>
53
+ </SidebarHeader>
54
+
55
+ <SidebarContent>
56
+ <NavMain items={mainNavItems} />
57
+ </SidebarContent>
58
+
59
+ <SidebarFooter>
60
+ <NavFooter items={footerNavItems} className="mt-auto" />
61
+ <NavUser />
62
+ </SidebarFooter>
63
+ </Sidebar>
64
+ )
65
+ }
@@ -0,0 +1,14 @@
1
+ import Breadcrumbs from '@/Components/Breadcrumbs'
2
+ import { SidebarTrigger } from '@/Components/ui/sidebar'
3
+ import { BreadcrumbItem } from '@/types'
4
+
5
+ export default function AppSidebarHeader({ breadcrumbs = [] }: { breadcrumbs?: BreadcrumbItem[] }) {
6
+ return (
7
+ <header className="flex h-16 shrink-0 items-center gap-2 border-b border-sidebar-border/50 px-6 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12 md:px-4">
8
+ <div className="flex items-center gap-2">
9
+ <SidebarTrigger className="-ml-1" />
10
+ <Breadcrumbs breadcrumbs={breadcrumbs} />
11
+ </div>
12
+ </header>
13
+ )
14
+ }
@@ -0,0 +1,43 @@
1
+ import { Fragment } from 'react'
2
+ import { Link } from '@inertiajs/react'
3
+ import {
4
+ Breadcrumb,
5
+ BreadcrumbItem,
6
+ BreadcrumbLink,
7
+ BreadcrumbList,
8
+ BreadcrumbPage,
9
+ BreadcrumbSeparator,
10
+ } from '@/Components/ui/breadcrumb'
11
+ import { BreadcrumbItem as BreadcrumbItemType } from '@/types'
12
+
13
+ export default function Breadcrumbs({ breadcrumbs }: { breadcrumbs: BreadcrumbItemType[] }) {
14
+ if (breadcrumbs.length === 0) {
15
+ return null
16
+ }
17
+
18
+ return (
19
+ <Breadcrumb>
20
+ <BreadcrumbList>
21
+ {breadcrumbs.map((item, index) => {
22
+ const isLast = index === breadcrumbs.length - 1
23
+
24
+ return (
25
+ <Fragment key={item.href}>
26
+ <BreadcrumbItem>
27
+ {isLast ? (
28
+ <BreadcrumbPage>{item.title}</BreadcrumbPage>
29
+ ) : (
30
+ <BreadcrumbLink asChild>
31
+ <Link href={item.href}>{item.title}</Link>
32
+ </BreadcrumbLink>
33
+ )}
34
+ </BreadcrumbItem>
35
+
36
+ {!isLast && <BreadcrumbSeparator />}
37
+ </Fragment>
38
+ )
39
+ })}
40
+ </BreadcrumbList>
41
+ </Breadcrumb>
42
+ )
43
+ }
@@ -0,0 +1,19 @@
1
+ export default function Heading({
2
+ title,
3
+ description,
4
+ variant = 'default',
5
+ }: {
6
+ title: string
7
+ description?: string
8
+ variant?: 'default' | 'small'
9
+ }) {
10
+ return (
11
+ <header className={variant === 'small' ? '' : 'mb-8 space-y-0.5'}>
12
+ <h2 className={variant === 'small' ? 'mb-0.5 text-base font-medium' : 'text-xl font-semibold tracking-tight'}>
13
+ {title}
14
+ </h2>
15
+
16
+ {description && <p className="text-sm text-muted-foreground">{description}</p>}
17
+ </header>
18
+ )
19
+ }
@@ -6,7 +6,7 @@ export default function InputError({
6
6
  ...props
7
7
  }: HTMLAttributes<HTMLParagraphElement> & { message?: string }) {
8
8
  return message ? (
9
- <p {...props} className={`text-sm text-red-600 dark:text-red-400 ${className}`}>
9
+ <p {...props} className={`text-sm text-destructive ${className}`}>
10
10
  {message}
11
11
  </p>
12
12
  ) : null
@@ -0,0 +1,32 @@
1
+ import { ComponentPropsWithoutRef } from 'react'
2
+ import { SidebarGroup, SidebarGroupContent, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/Components/ui/sidebar'
3
+ import { cn } from '@/lib/utils'
4
+ import { NavItem } from '@/types'
5
+
6
+ export default function NavFooter({
7
+ items,
8
+ className,
9
+ ...props
10
+ }: ComponentPropsWithoutRef<typeof SidebarGroup> & { items: NavItem[] }) {
11
+ return (
12
+ <SidebarGroup {...props} className={cn('group-data-[collapsible=icon]:p-0', className)}>
13
+ <SidebarGroupContent>
14
+ <SidebarMenu>
15
+ {items.map((item) => (
16
+ <SidebarMenuItem key={item.title}>
17
+ <SidebarMenuButton
18
+ asChild
19
+ className="text-neutral-600 hover:text-neutral-800 dark:text-neutral-300 dark:hover:text-neutral-100"
20
+ >
21
+ <a href={item.href} target="_blank" rel="noopener noreferrer">
22
+ {item.icon && <item.icon className="h-5 w-5" />}
23
+ <span>{item.title}</span>
24
+ </a>
25
+ </SidebarMenuButton>
26
+ </SidebarMenuItem>
27
+ ))}
28
+ </SidebarMenu>
29
+ </SidebarGroupContent>
30
+ </SidebarGroup>
31
+ )
32
+ }
@@ -0,0 +1,32 @@
1
+ import { Link, usePage } from '@inertiajs/react'
2
+ import {
3
+ SidebarGroup,
4
+ SidebarGroupLabel,
5
+ SidebarMenu,
6
+ SidebarMenuButton,
7
+ SidebarMenuItem,
8
+ } from '@/Components/ui/sidebar'
9
+ import { NavItem } from '@/types'
10
+
11
+ export default function NavMain({ items }: { items: NavItem[] }) {
12
+ const { url } = usePage()
13
+
14
+ return (
15
+ <SidebarGroup className="px-2 py-0">
16
+ <SidebarGroupLabel>Platform</SidebarGroupLabel>
17
+
18
+ <SidebarMenu>
19
+ {items.map((item) => (
20
+ <SidebarMenuItem key={item.title}>
21
+ <SidebarMenuButton asChild isActive={url.startsWith(item.href)} tooltip={{ children: item.title }}>
22
+ <Link href={item.href} prefetch>
23
+ {item.icon && <item.icon />}
24
+ <span>{item.title}</span>
25
+ </Link>
26
+ </SidebarMenuButton>
27
+ </SidebarMenuItem>
28
+ ))}
29
+ </SidebarMenu>
30
+ </SidebarGroup>
31
+ )
32
+ }
@@ -0,0 +1,39 @@
1
+ import { usePage } from '@inertiajs/react'
2
+ import { ChevronsUpDown } from 'lucide-react'
3
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '@/Components/ui/dropdown-menu'
4
+ import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar } from '@/Components/ui/sidebar'
5
+ import UserInfo from '@/Components/UserInfo'
6
+ import UserMenuContent from '@/Components/UserMenuContent'
7
+ import { useIsMobile } from '@/hooks/use-mobile'
8
+
9
+ export default function NavUser() {
10
+ const user = usePage().props.auth.user
11
+ const { state } = useSidebar()
12
+ const isMobile = useIsMobile()
13
+
14
+ return (
15
+ <SidebarMenu>
16
+ <SidebarMenuItem>
17
+ <DropdownMenu>
18
+ <DropdownMenuTrigger asChild>
19
+ <SidebarMenuButton
20
+ size="lg"
21
+ className="group text-sidebar-accent-foreground data-[state=open]:bg-sidebar-accent"
22
+ >
23
+ <UserInfo user={user} />
24
+ <ChevronsUpDown className="ml-auto size-4" />
25
+ </SidebarMenuButton>
26
+ </DropdownMenuTrigger>
27
+
28
+ <DropdownMenuContent
29
+ className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
30
+ align="end"
31
+ side={isMobile ? 'bottom' : state === 'collapsed' ? 'left' : 'bottom'}
32
+ >
33
+ <UserMenuContent user={user} />
34
+ </DropdownMenuContent>
35
+ </DropdownMenu>
36
+ </SidebarMenuItem>
37
+ </SidebarMenu>
38
+ )
39
+ }
@@ -0,0 +1,23 @@
1
+ import { Avatar, AvatarFallback } from '@/Components/ui/avatar'
2
+ import { useInitials } from '@/hooks/use-initials'
3
+ import { User } from '@/types'
4
+
5
+ export default function UserInfo({ user, showEmail = false }: { user: User; showEmail?: boolean }) {
6
+ const getInitials = useInitials()
7
+
8
+ return (
9
+ <>
10
+ <Avatar className="h-8 w-8 overflow-hidden rounded-full">
11
+ <AvatarFallback className="rounded-lg bg-neutral-200 text-black dark:bg-neutral-700 dark:text-white">
12
+ {getInitials(user.name)}
13
+ </AvatarFallback>
14
+ </Avatar>
15
+
16
+ <div className="grid flex-1 text-left text-sm leading-tight">
17
+ <span className="truncate font-medium">{user.name}</span>
18
+
19
+ {showEmail && <span className="truncate text-xs text-muted-foreground">{user.email}</span>}
20
+ </div>
21
+ </>
22
+ )
23
+ }
@@ -0,0 +1,57 @@
1
+ import { Link, router } from '@inertiajs/react'
2
+ import { LogOut, Settings } from 'lucide-react'
3
+ import {
4
+ DropdownMenuGroup,
5
+ DropdownMenuItem,
6
+ DropdownMenuLabel,
7
+ DropdownMenuSeparator,
8
+ } from '@/Components/ui/dropdown-menu'
9
+ import UserInfo from '@/Components/UserInfo'
10
+ import { useMobileNavigation } from '@/hooks/use-mobile-navigation'
11
+ import { logout_path, profile_edit_path } from '@/routes'
12
+ import { User } from '@/types'
13
+
14
+ export default function UserMenuContent({ user }: { user: User }) {
15
+ const cleanup = useMobileNavigation()
16
+
17
+ const handleLogout = () => {
18
+ cleanup()
19
+ router.flushAll()
20
+ }
21
+
22
+ return (
23
+ <>
24
+ <DropdownMenuLabel className="p-0 font-normal">
25
+ <div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
26
+ <UserInfo user={user} showEmail />
27
+ </div>
28
+ </DropdownMenuLabel>
29
+
30
+ <DropdownMenuSeparator />
31
+
32
+ <DropdownMenuGroup>
33
+ <DropdownMenuItem asChild>
34
+ <Link className="block w-full cursor-pointer" href={profile_edit_path()} prefetch onClick={cleanup}>
35
+ <Settings className="mr-2" />
36
+ Settings
37
+ </Link>
38
+ </DropdownMenuItem>
39
+ </DropdownMenuGroup>
40
+
41
+ <DropdownMenuSeparator />
42
+
43
+ <DropdownMenuItem asChild>
44
+ <Link
45
+ className="block w-full cursor-pointer"
46
+ href={logout_path()}
47
+ method="post"
48
+ as="button"
49
+ onClick={handleLogout}
50
+ >
51
+ <LogOut className="mr-2" />
52
+ Log out
53
+ </Link>
54
+ </DropdownMenuItem>
55
+ </>
56
+ )
57
+ }
@@ -0,0 +1,51 @@
1
+ import * as AvatarPrimitive from "@radix-ui/react-avatar"
2
+ import * as React from "react"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ function Avatar({
7
+ className,
8
+ ...props
9
+ }: React.ComponentProps<typeof AvatarPrimitive.Root>) {
10
+ return (
11
+ <AvatarPrimitive.Root
12
+ data-slot="avatar"
13
+ className={cn(
14
+ "relative flex size-8 shrink-0 overflow-hidden rounded-full",
15
+ className
16
+ )}
17
+ {...props}
18
+ />
19
+ )
20
+ }
21
+
22
+ function AvatarImage({
23
+ className,
24
+ ...props
25
+ }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
26
+ return (
27
+ <AvatarPrimitive.Image
28
+ data-slot="avatar-image"
29
+ className={cn("aspect-square size-full", className)}
30
+ {...props}
31
+ />
32
+ )
33
+ }
34
+
35
+ function AvatarFallback({
36
+ className,
37
+ ...props
38
+ }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
39
+ return (
40
+ <AvatarPrimitive.Fallback
41
+ data-slot="avatar-fallback"
42
+ className={cn(
43
+ "bg-muted flex size-full items-center justify-center rounded-full",
44
+ className
45
+ )}
46
+ {...props}
47
+ />
48
+ )
49
+ }
50
+
51
+ export { Avatar, AvatarImage, AvatarFallback }