robodev 0.11.0 → 0.13.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 (329) hide show
  1. package/package.json +1 -1
  2. package/src/collect-files.test.ts +19 -0
  3. package/src/collect-files.ts +1 -0
  4. package/src/config.test.ts +26 -0
  5. package/src/config.ts +34 -5
  6. package/src/emit-starter.test.ts +68 -0
  7. package/src/emit-starter.ts +47 -13
  8. package/src/index.ts +45 -4
  9. package/templates/auth-chat/.config/local.spa.template.yml +27 -0
  10. package/templates/auth-chat/.oxlint-base.json +29 -0
  11. package/templates/auth-chat/.oxlintrc.json +78 -0
  12. package/templates/auth-chat/.rulesync/rules/frontend-api-boundary.md +36 -0
  13. package/templates/auth-chat/.rulesync/rules/frontend-component-structure.md +38 -0
  14. package/templates/auth-chat/.rulesync/rules/frontend-forms.md +32 -0
  15. package/templates/auth-chat/.rulesync/rules/frontend-notifications.md +24 -0
  16. package/templates/auth-chat/.rulesync/rules/frontend-povio-components.md +42 -0
  17. package/templates/auth-chat/.rulesync/rules/frontend-povio-ui.md +45 -0
  18. package/templates/auth-chat/.rulesync/rules/frontend-query-autocomplete.md +29 -0
  19. package/templates/auth-chat/.rulesync/rules/frontend-tables-and-lists.md +34 -0
  20. package/templates/auth-chat/.rulesync/rules/frontend-translations.md +26 -0
  21. package/templates/auth-chat/.rulesync/rules/project-overview.md +32 -0
  22. package/templates/auth-chat/.rulesync/rules/robodev-api.md +30 -0
  23. package/templates/auth-chat/.rulesync/rules/robodev-auth.md +40 -0
  24. package/templates/auth-chat/.rulesync/rules/robodev-database.md +22 -0
  25. package/templates/auth-chat/.rulesync/rules/role-based-app-structure.md +36 -0
  26. package/templates/auth-chat/.rulesync/skills/media-feature/SKILL.md +21 -0
  27. package/templates/auth-chat/.rulesync/skills/povio-ui-styling/SKILL.md +208 -0
  28. package/templates/auth-chat/.rulesync/skills/povio-ui-styling/agents/openai.yaml +4 -0
  29. package/templates/auth-chat/.rulesync/skills/robodev-api-route/SKILL.md +13 -0
  30. package/templates/auth-chat/.rulesync/skills/robodev-table/SKILL.md +12 -0
  31. package/templates/auth-chat/README.md +26 -7
  32. package/templates/auth-chat/api/invite.ts +43 -0
  33. package/templates/auth-chat/apps/fe/index.html +24 -0
  34. package/templates/auth-chat/apps/fe/openapi-codegen.config.ts +37 -0
  35. package/templates/auth-chat/apps/fe/package.json +82 -0
  36. package/templates/auth-chat/apps/fe/public/apple-touch-icon.png +0 -0
  37. package/templates/auth-chat/apps/fe/public/favicon-96x96.png +0 -0
  38. package/templates/auth-chat/apps/fe/public/favicon.ico +0 -0
  39. package/templates/auth-chat/apps/fe/public/favicon.svg +3 -0
  40. package/templates/auth-chat/apps/fe/public/site.webmanifest +21 -0
  41. package/templates/auth-chat/apps/fe/public/web-app-manifest-192x192.png +0 -0
  42. package/templates/auth-chat/apps/fe/public/web-app-manifest-512x512.png +0 -0
  43. package/templates/auth-chat/apps/fe/src/assets/fonts/GeneralSans-Bold.otf +0 -0
  44. package/templates/auth-chat/apps/fe/src/assets/fonts/GeneralSans-Medium.otf +0 -0
  45. package/templates/auth-chat/apps/fe/src/assets/fonts/GeneralSans-Regular.otf +0 -0
  46. package/templates/auth-chat/apps/fe/src/assets/fonts/GeneralSans-Semibold.otf +0 -0
  47. package/templates/auth-chat/apps/fe/src/assets/locales/en/translation.json +202 -0
  48. package/templates/auth-chat/apps/fe/src/assets/locales/sl/translation.json +202 -0
  49. package/templates/auth-chat/apps/fe/src/clients/app-rest-client.ts +15 -0
  50. package/templates/auth-chat/apps/fe/src/clients/auth-token-store.ts +101 -0
  51. package/templates/auth-chat/apps/fe/src/clients/rest/app-error-handler.ts +31 -0
  52. package/templates/auth-chat/apps/fe/src/clients/rest/interceptors/authorization-header.interceptor.ts +15 -0
  53. package/templates/auth-chat/apps/fe/src/clients/rest/interceptors/refresh-token.interceptor.ts +37 -0
  54. package/templates/auth-chat/apps/fe/src/clients/rest/interceptors/response.interceptor.ts +17 -0
  55. package/templates/auth-chat/apps/fe/src/components/404.tsx +18 -0
  56. package/templates/auth-chat/apps/fe/src/components/features/auth/AuthBrandPanel.tsx +60 -0
  57. package/templates/auth-chat/apps/fe/src/components/features/auth/AuthLayout.tsx +23 -0
  58. package/templates/auth-chat/apps/fe/src/components/features/auth/LoginPage.tsx +111 -0
  59. package/templates/auth-chat/apps/fe/src/components/features/auth/RegisterPage.tsx +143 -0
  60. package/templates/auth-chat/apps/fe/src/components/features/chat/ChatPage.tsx +105 -0
  61. package/templates/auth-chat/apps/fe/src/components/features/chat/InviteForm.tsx +84 -0
  62. package/templates/auth-chat/apps/fe/src/components/features/profile/ProfilePage.tsx +96 -0
  63. package/templates/auth-chat/apps/fe/src/components/googleAnalytics/GoogleAnalytics.tsx +35 -0
  64. package/templates/auth-chat/apps/fe/src/components/layout/AppLayout.tsx +19 -0
  65. package/templates/auth-chat/apps/fe/src/components/layout/app-header/AppHeader.tsx +91 -0
  66. package/templates/auth-chat/apps/fe/src/components/layout/app-header/MobileNavigation.tsx +51 -0
  67. package/templates/auth-chat/apps/fe/src/components/layout/app-header/NavLink.tsx +29 -0
  68. package/templates/auth-chat/apps/fe/src/components/shared/branding/BrandLogo.tsx +13 -0
  69. package/templates/auth-chat/apps/fe/src/components/shared/error/ErrorFallback.tsx +43 -0
  70. package/templates/auth-chat/apps/fe/src/components/shared/error/ErrorText.tsx +20 -0
  71. package/templates/auth-chat/apps/fe/src/components/shared/error/NotFound.tsx +36 -0
  72. package/templates/auth-chat/apps/fe/src/components/shared/forms/RequiredLabel.tsx +21 -0
  73. package/templates/auth-chat/apps/fe/src/components/shared/forms/RowInputWrapper.tsx +50 -0
  74. package/templates/auth-chat/apps/fe/src/components/shared/head/AppHead.tsx +32 -0
  75. package/templates/auth-chat/apps/fe/src/components/shared/head/DefaultAppHead.tsx +34 -0
  76. package/templates/auth-chat/apps/fe/src/components/shared/layout/LoadingState.tsx +9 -0
  77. package/templates/auth-chat/apps/fe/src/components/shared/layout/ThinPageWrapper.tsx +5 -0
  78. package/templates/auth-chat/apps/fe/src/components/shared/page/PageHeader.tsx +69 -0
  79. package/templates/auth-chat/apps/fe/src/components/shared/ui/Card.tsx +60 -0
  80. package/templates/auth-chat/apps/fe/src/components/shared/ui/GoogleLoginButton.tsx +19 -0
  81. package/templates/auth-chat/apps/fe/src/components/shared/ui/RequiredLabel.tsx +22 -0
  82. package/templates/auth-chat/apps/fe/src/components/shared/ui/TableActions.tsx +22 -0
  83. package/templates/auth-chat/apps/fe/src/config/app.config.ts +35 -0
  84. package/templates/auth-chat/apps/fe/src/config/i18n.ts +43 -0
  85. package/templates/auth-chat/apps/fe/src/config/inits/a11y.ts +14 -0
  86. package/templates/auth-chat/apps/fe/src/config/inits/logger.ts +7 -0
  87. package/templates/auth-chat/apps/fe/src/config/inits/sentry.ts +21 -0
  88. package/templates/auth-chat/apps/fe/src/config/jwt.config.ts +2 -0
  89. package/templates/auth-chat/apps/fe/src/config/query.config.ts +20 -0
  90. package/templates/auth-chat/apps/fe/src/hooks/useAuth.ts +5 -0
  91. package/templates/auth-chat/apps/fe/src/main.tsx +52 -0
  92. package/templates/auth-chat/apps/fe/src/pages/(guest)/login.tsx +23 -0
  93. package/templates/auth-chat/apps/fe/src/pages/(guest)/register.tsx +23 -0
  94. package/templates/auth-chat/apps/fe/src/pages/(guest)/route.tsx +18 -0
  95. package/templates/auth-chat/apps/fe/src/pages/(private)/index.tsx +23 -0
  96. package/templates/auth-chat/apps/fe/src/pages/(private)/profile.tsx +23 -0
  97. package/templates/auth-chat/apps/fe/src/pages/(private)/route.tsx +18 -0
  98. package/templates/auth-chat/apps/fe/src/pages/(public)/auth.tsx +37 -0
  99. package/templates/auth-chat/apps/fe/src/pages/(public)/route.tsx +9 -0
  100. package/templates/auth-chat/apps/fe/src/pages/__root.tsx +164 -0
  101. package/templates/auth-chat/apps/fe/src/providers/AppErrorBoundary.tsx +10 -0
  102. package/templates/auth-chat/apps/fe/src/providers/OpenApiRuntimeProvider.tsx +31 -0
  103. package/templates/auth-chat/apps/fe/src/providers/index.tsx +44 -0
  104. package/templates/auth-chat/apps/fe/src/providers/jwt.provider.tsx +95 -0
  105. package/templates/auth-chat/apps/fe/src/routeTree.gen.ts +225 -0
  106. package/templates/auth-chat/apps/fe/src/styles/base.css +103 -0
  107. package/templates/auth-chat/apps/fe/src/styles/fonts/fonts.tsx +10 -0
  108. package/templates/auth-chat/apps/fe/src/styles/fonts/general-sans.css +31 -0
  109. package/templates/auth-chat/apps/fe/src/styles/globals.css +28 -0
  110. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/button.override.ts +536 -0
  111. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/checkbox.override.ts +71 -0
  112. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/input.override.ts +252 -0
  113. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/label.override.ts +91 -0
  114. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/modal.override.ts +57 -0
  115. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/radio.override.ts +46 -0
  116. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/table.override.ts +104 -0
  117. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/tag.override.ts +66 -0
  118. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/typography.override.ts +115 -0
  119. package/templates/auth-chat/apps/fe/src/styles/overrides/outline.clsx.ts +10 -0
  120. package/templates/auth-chat/apps/fe/src/styles/overrides/uiOverrides.override.ts +60 -0
  121. package/templates/auth-chat/apps/fe/src/styles/theme.css +2177 -0
  122. package/templates/auth-chat/apps/fe/src/types/i18next.d.ts +12 -0
  123. package/templates/auth-chat/apps/fe/src/types/table.d.ts +17 -0
  124. package/templates/auth-chat/apps/fe/src/types/ui.d.ts +26 -0
  125. package/templates/auth-chat/apps/fe/src/types/vite-env.d.ts +20 -0
  126. package/templates/auth-chat/apps/fe/src/utils/date.utils.ts +17 -0
  127. package/templates/auth-chat/apps/fe/src/utils/number.utils.ts +12 -0
  128. package/templates/auth-chat/apps/fe/src/utils/string.utils.ts +5 -0
  129. package/templates/auth-chat/apps/fe/src/vite-env.d.ts +1 -0
  130. package/templates/auth-chat/apps/fe/tsconfig.app.json +32 -0
  131. package/templates/auth-chat/apps/fe/tsconfig.json +9 -0
  132. package/templates/auth-chat/apps/fe/tsconfig.node.json +31 -0
  133. package/templates/auth-chat/apps/fe/vite.config.ts +94 -0
  134. package/templates/auth-chat/openapi.json +1016 -0
  135. package/templates/auth-chat/oxfmt.config.js +23 -0
  136. package/templates/auth-chat/package.json +6 -13
  137. package/templates/auth-chat/rulesync.jsonc +18 -0
  138. package/templates/auth-chat/tsconfig.json +2 -4
  139. package/templates/backend/README.md +2 -2
  140. package/templates/backend/api/_lib.ts +359 -0
  141. package/templates/backend/api/aliens/labels.ts +19 -0
  142. package/templates/backend/api/files/presigned-url.ts +48 -0
  143. package/templates/backend/api/files/upload.ts +29 -0
  144. package/templates/backend/api/planets/[id]/like.ts +36 -0
  145. package/templates/backend/api/planets/[id].ts +56 -13
  146. package/templates/backend/api/planets/paginate.ts +36 -0
  147. package/templates/backend/api/planets.ts +52 -44
  148. package/templates/backend/database.ts +33 -13
  149. package/templates/backend/package.json +1 -1
  150. package/templates/catalog.json +3 -3
  151. package/templates/empty/package.json +1 -1
  152. package/templates/space/.config/local.spa.template.yml +27 -0
  153. package/templates/space/.oxlint-base.json +29 -0
  154. package/templates/space/.oxlintrc.json +78 -0
  155. package/templates/space/.rulesync/rules/frontend-api-boundary.md +36 -0
  156. package/templates/space/.rulesync/rules/frontend-component-structure.md +38 -0
  157. package/templates/space/.rulesync/rules/frontend-forms.md +32 -0
  158. package/templates/space/.rulesync/rules/frontend-notifications.md +24 -0
  159. package/templates/space/.rulesync/rules/frontend-povio-components.md +42 -0
  160. package/templates/space/.rulesync/rules/frontend-povio-ui.md +45 -0
  161. package/templates/space/.rulesync/rules/frontend-query-autocomplete.md +29 -0
  162. package/templates/space/.rulesync/rules/frontend-tables-and-lists.md +34 -0
  163. package/templates/space/.rulesync/rules/frontend-translations.md +26 -0
  164. package/templates/space/.rulesync/rules/project-overview.md +32 -0
  165. package/templates/space/.rulesync/rules/robodev-api.md +30 -0
  166. package/templates/space/.rulesync/rules/robodev-auth.md +40 -0
  167. package/templates/space/.rulesync/rules/robodev-database.md +22 -0
  168. package/templates/space/.rulesync/rules/role-based-app-structure.md +36 -0
  169. package/templates/space/.rulesync/skills/media-feature/SKILL.md +21 -0
  170. package/templates/space/.rulesync/skills/povio-ui-styling/SKILL.md +208 -0
  171. package/templates/space/.rulesync/skills/povio-ui-styling/agents/openai.yaml +4 -0
  172. package/templates/space/.rulesync/skills/robodev-api-route/SKILL.md +13 -0
  173. package/templates/space/.rulesync/skills/robodev-table/SKILL.md +12 -0
  174. package/templates/space/README.md +26 -7
  175. package/templates/space/api/_lib.ts +359 -0
  176. package/templates/space/api/aliens/labels.ts +19 -0
  177. package/templates/space/api/files/presigned-url.ts +48 -0
  178. package/templates/space/api/files/upload.ts +29 -0
  179. package/templates/space/api/planets/[id]/like.ts +36 -0
  180. package/templates/space/api/planets/[id].ts +56 -13
  181. package/templates/space/api/planets/paginate.ts +36 -0
  182. package/templates/space/api/planets.ts +52 -44
  183. package/templates/space/apps/fe/index.html +24 -0
  184. package/templates/space/apps/fe/openapi-codegen.config.ts +37 -0
  185. package/templates/space/apps/fe/package.json +82 -0
  186. package/templates/space/apps/fe/public/apple-touch-icon.png +0 -0
  187. package/templates/space/apps/fe/public/favicon-96x96.png +0 -0
  188. package/templates/space/apps/fe/public/favicon.ico +0 -0
  189. package/templates/space/apps/fe/public/favicon.svg +3 -0
  190. package/templates/space/apps/fe/public/site.webmanifest +21 -0
  191. package/templates/space/apps/fe/public/web-app-manifest-192x192.png +0 -0
  192. package/templates/space/apps/fe/public/web-app-manifest-512x512.png +0 -0
  193. package/templates/space/apps/fe/src/assets/fonts/GeneralSans-Bold.otf +0 -0
  194. package/templates/space/apps/fe/src/assets/fonts/GeneralSans-Medium.otf +0 -0
  195. package/templates/space/apps/fe/src/assets/fonts/GeneralSans-Regular.otf +0 -0
  196. package/templates/space/apps/fe/src/assets/fonts/GeneralSans-Semibold.otf +0 -0
  197. package/templates/space/apps/fe/src/assets/locales/en/translation.json +301 -0
  198. package/templates/space/apps/fe/src/assets/locales/sl/translation.json +301 -0
  199. package/templates/space/apps/fe/src/clients/app-rest-client.ts +15 -0
  200. package/templates/space/apps/fe/src/clients/auth-token-store.ts +101 -0
  201. package/templates/space/apps/fe/src/clients/rest/app-error-handler.ts +31 -0
  202. package/templates/space/apps/fe/src/clients/rest/interceptors/authorization-header.interceptor.ts +15 -0
  203. package/templates/space/apps/fe/src/clients/rest/interceptors/refresh-token.interceptor.ts +37 -0
  204. package/templates/space/apps/fe/src/clients/rest/interceptors/response.interceptor.ts +17 -0
  205. package/templates/space/apps/fe/src/components/404.tsx +18 -0
  206. package/templates/space/apps/fe/src/components/features/auth/AuthBrandPanel.tsx +60 -0
  207. package/templates/space/apps/fe/src/components/features/auth/AuthLayout.tsx +23 -0
  208. package/templates/space/apps/fe/src/components/features/auth/LoginPage.tsx +111 -0
  209. package/templates/space/apps/fe/src/components/features/auth/RegisterPage.tsx +143 -0
  210. package/templates/space/apps/fe/src/components/features/home/HomePage.tsx +21 -0
  211. package/templates/space/apps/fe/src/components/features/planets/details/PlanetDetailsPage.tsx +139 -0
  212. package/templates/space/apps/fe/src/components/features/planets/details/PlanetEditPage.tsx +266 -0
  213. package/templates/space/apps/fe/src/components/features/planets/feed/PlanetFeedCard.tsx +131 -0
  214. package/templates/space/apps/fe/src/components/features/planets/feed/PlanetsFeedPage.tsx +119 -0
  215. package/templates/space/apps/fe/src/components/features/planets/list/PlanetCard.tsx +89 -0
  216. package/templates/space/apps/fe/src/components/features/planets/list/PlanetCreateModal.tsx +179 -0
  217. package/templates/space/apps/fe/src/components/features/planets/list/PlanetsFilters.css +3 -0
  218. package/templates/space/apps/fe/src/components/features/planets/list/PlanetsFilters.tsx +73 -0
  219. package/templates/space/apps/fe/src/components/features/planets/list/PlanetsGridPage.tsx +51 -0
  220. package/templates/space/apps/fe/src/components/features/planets/list/PlanetsInfiniteTablePage.tsx +79 -0
  221. package/templates/space/apps/fe/src/components/features/planets/list/PlanetsPage.tsx +65 -0
  222. package/templates/space/apps/fe/src/components/features/planets/list/PlanetsTablePage.tsx +72 -0
  223. package/templates/space/apps/fe/src/components/features/planets/list/PlanetsTableSummary.tsx +29 -0
  224. package/templates/space/apps/fe/src/components/features/planets/list/PlanetsViewTabs.tsx +79 -0
  225. package/templates/space/apps/fe/src/components/features/planets/list/table/PlanetsTable.tsx +30 -0
  226. package/templates/space/apps/fe/src/components/features/planets/list/table/PlanetsTableActions.tsx +64 -0
  227. package/templates/space/apps/fe/src/components/features/planets/list/table/PlanetsTableInfinite.tsx +36 -0
  228. package/templates/space/apps/fe/src/components/features/planets/list/table/planetsTableColumns.tsx +72 -0
  229. package/templates/space/apps/fe/src/components/features/planets/list/table/usePlanetTableFilters.tsx +102 -0
  230. package/templates/space/apps/fe/src/components/features/profile/ProfilePage.tsx +96 -0
  231. package/templates/space/apps/fe/src/components/googleAnalytics/GoogleAnalytics.tsx +35 -0
  232. package/templates/space/apps/fe/src/components/layout/AppLayout.tsx +19 -0
  233. package/templates/space/apps/fe/src/components/layout/app-header/AppHeader.tsx +93 -0
  234. package/templates/space/apps/fe/src/components/layout/app-header/MobileNavigation.tsx +53 -0
  235. package/templates/space/apps/fe/src/components/layout/app-header/NavLink.tsx +29 -0
  236. package/templates/space/apps/fe/src/components/shared/branding/BrandLogo.tsx +13 -0
  237. package/templates/space/apps/fe/src/components/shared/error/ErrorFallback.tsx +43 -0
  238. package/templates/space/apps/fe/src/components/shared/error/ErrorText.tsx +20 -0
  239. package/templates/space/apps/fe/src/components/shared/error/NotFound.tsx +36 -0
  240. package/templates/space/apps/fe/src/components/shared/forms/RequiredLabel.tsx +21 -0
  241. package/templates/space/apps/fe/src/components/shared/forms/RowInputWrapper.tsx +50 -0
  242. package/templates/space/apps/fe/src/components/shared/head/AppHead.tsx +32 -0
  243. package/templates/space/apps/fe/src/components/shared/head/DefaultAppHead.tsx +34 -0
  244. package/templates/space/apps/fe/src/components/shared/layout/LoadingState.tsx +9 -0
  245. package/templates/space/apps/fe/src/components/shared/layout/ThinPageWrapper.tsx +5 -0
  246. package/templates/space/apps/fe/src/components/shared/page/PageHeader.tsx +69 -0
  247. package/templates/space/apps/fe/src/components/shared/ui/Card.tsx +60 -0
  248. package/templates/space/apps/fe/src/components/shared/ui/GoogleLoginButton.tsx +19 -0
  249. package/templates/space/apps/fe/src/components/shared/ui/RequiredLabel.tsx +22 -0
  250. package/templates/space/apps/fe/src/components/shared/ui/TableActions.tsx +22 -0
  251. package/templates/space/apps/fe/src/config/app.config.ts +35 -0
  252. package/templates/space/apps/fe/src/config/i18n.ts +43 -0
  253. package/templates/space/apps/fe/src/config/inits/a11y.ts +14 -0
  254. package/templates/space/apps/fe/src/config/inits/logger.ts +7 -0
  255. package/templates/space/apps/fe/src/config/inits/sentry.ts +21 -0
  256. package/templates/space/apps/fe/src/config/jwt.config.ts +2 -0
  257. package/templates/space/apps/fe/src/config/query.config.ts +20 -0
  258. package/templates/space/apps/fe/src/hooks/useAuth.ts +5 -0
  259. package/templates/space/apps/fe/src/main.tsx +52 -0
  260. package/templates/space/apps/fe/src/pages/(guest)/login.tsx +23 -0
  261. package/templates/space/apps/fe/src/pages/(guest)/register.tsx +23 -0
  262. package/templates/space/apps/fe/src/pages/(guest)/route.tsx +18 -0
  263. package/templates/space/apps/fe/src/pages/(private)/index.tsx +23 -0
  264. package/templates/space/apps/fe/src/pages/(private)/planets/$id/edit.tsx +53 -0
  265. package/templates/space/apps/fe/src/pages/(private)/planets/$id/index.tsx +41 -0
  266. package/templates/space/apps/fe/src/pages/(private)/planets/index.tsx +24 -0
  267. package/templates/space/apps/fe/src/pages/(private)/planets-feed.tsx +23 -0
  268. package/templates/space/apps/fe/src/pages/(private)/profile.tsx +23 -0
  269. package/templates/space/apps/fe/src/pages/(private)/route.tsx +18 -0
  270. package/templates/space/apps/fe/src/pages/(public)/auth.tsx +37 -0
  271. package/templates/space/apps/fe/src/pages/(public)/route.tsx +9 -0
  272. package/templates/space/apps/fe/src/pages/__root.tsx +164 -0
  273. package/templates/space/apps/fe/src/providers/AppErrorBoundary.tsx +10 -0
  274. package/templates/space/apps/fe/src/providers/OpenApiRuntimeProvider.tsx +31 -0
  275. package/templates/space/apps/fe/src/providers/index.tsx +44 -0
  276. package/templates/space/apps/fe/src/providers/jwt.provider.tsx +95 -0
  277. package/templates/space/apps/fe/src/routeTree.gen.ts +309 -0
  278. package/templates/space/apps/fe/src/styles/base.css +103 -0
  279. package/templates/space/apps/fe/src/styles/fonts/fonts.tsx +10 -0
  280. package/templates/space/apps/fe/src/styles/fonts/general-sans.css +31 -0
  281. package/templates/space/apps/fe/src/styles/globals.css +28 -0
  282. package/templates/space/apps/fe/src/styles/overrides/defaults/button.override.ts +536 -0
  283. package/templates/space/apps/fe/src/styles/overrides/defaults/checkbox.override.ts +71 -0
  284. package/templates/space/apps/fe/src/styles/overrides/defaults/input.override.ts +252 -0
  285. package/templates/space/apps/fe/src/styles/overrides/defaults/label.override.ts +91 -0
  286. package/templates/space/apps/fe/src/styles/overrides/defaults/modal.override.ts +57 -0
  287. package/templates/space/apps/fe/src/styles/overrides/defaults/radio.override.ts +46 -0
  288. package/templates/space/apps/fe/src/styles/overrides/defaults/table.override.ts +104 -0
  289. package/templates/space/apps/fe/src/styles/overrides/defaults/tag.override.ts +66 -0
  290. package/templates/space/apps/fe/src/styles/overrides/defaults/typography.override.ts +115 -0
  291. package/templates/space/apps/fe/src/styles/overrides/outline.clsx.ts +10 -0
  292. package/templates/space/apps/fe/src/styles/overrides/uiOverrides.override.ts +60 -0
  293. package/templates/space/apps/fe/src/styles/theme.css +2177 -0
  294. package/templates/space/apps/fe/src/types/i18next.d.ts +12 -0
  295. package/templates/space/apps/fe/src/types/table.d.ts +17 -0
  296. package/templates/space/apps/fe/src/types/ui.d.ts +26 -0
  297. package/templates/space/apps/fe/src/types/vite-env.d.ts +20 -0
  298. package/templates/space/apps/fe/src/utils/date.utils.ts +17 -0
  299. package/templates/space/apps/fe/src/utils/image-fallback.ts +9 -0
  300. package/templates/space/apps/fe/src/utils/media-upload.ts +63 -0
  301. package/templates/space/apps/fe/src/utils/number.utils.ts +12 -0
  302. package/templates/space/apps/fe/src/utils/string.utils.ts +5 -0
  303. package/templates/space/apps/fe/src/vite-env.d.ts +1 -0
  304. package/templates/space/apps/fe/tsconfig.app.json +32 -0
  305. package/templates/space/apps/fe/tsconfig.json +9 -0
  306. package/templates/space/apps/fe/tsconfig.node.json +31 -0
  307. package/templates/space/apps/fe/vite.config.ts +94 -0
  308. package/templates/space/database.ts +33 -13
  309. package/templates/space/openapi.json +1819 -0
  310. package/templates/space/oxfmt.config.js +23 -0
  311. package/templates/space/package.json +6 -13
  312. package/templates/space/rulesync.jsonc +18 -0
  313. package/templates/space/tsconfig.json +2 -4
  314. package/templates/auth-chat/index.html +0 -201
  315. package/templates/auth-chat/src/main.tsx +0 -282
  316. package/templates/auth-chat/vite.config.ts +0 -7
  317. package/templates/backend/api/health.ts +0 -7
  318. package/templates/backend/api/launch.ts +0 -29
  319. package/templates/backend/api/me.ts +0 -14
  320. package/templates/backend/api/motto.ts +0 -9
  321. package/templates/backend/api/rockets.ts +0 -43
  322. package/templates/space/api/health.ts +0 -7
  323. package/templates/space/api/launch.ts +0 -29
  324. package/templates/space/api/me.ts +0 -14
  325. package/templates/space/api/motto.ts +0 -9
  326. package/templates/space/api/rockets.ts +0 -43
  327. package/templates/space/index.html +0 -138
  328. package/templates/space/src/main.tsx +0 -229
  329. package/templates/space/vite.config.ts +0 -7
@@ -0,0 +1,131 @@
1
+ import { Button, Link, Typography } from "@povio/ui";
2
+ import { Heart, Satellite, UserRound } from "lucide-react";
3
+ import { useTranslation } from "react-i18next";
4
+
5
+ import type { PlanetModels } from "@/openapi/planet/planet.models";
6
+ import { DateUtils } from "@/utils/date.utils";
7
+ import { getFallbackImageUrl } from "@/utils/image-fallback";
8
+
9
+ type PlanetFeedItem = PlanetModels.PlanetsGetResponseDto;
10
+
11
+ interface PlanetFeedCardProps {
12
+ planet: PlanetFeedItem;
13
+ isLikePending: boolean;
14
+ onToggleLike: (planet: PlanetFeedItem) => void;
15
+ }
16
+
17
+ export function PlanetFeedCard({ planet, isLikePending, onToggleLike }: PlanetFeedCardProps) {
18
+ const { t } = useTranslation();
19
+ const imageSrc = planet.image?.url ?? getFallbackImageUrl({ width: 960, height: 540 });
20
+ const discoveryDate = planet.discoveryDate ? DateUtils.formatDate(planet.discoveryDate) : null;
21
+ const alienName = planet.alienName ?? t(($) => $.planetsFeed.card.noAlien);
22
+ const isLiked = planet.likedByMe;
23
+ const likesCount = typeof planet.likesCount === "number" ? planet.likesCount : 0;
24
+
25
+ return (
26
+ <article className="overflow-hidden rounded-m border border-elevation-outline-default-1 bg-elevation-fill-default-1 shadow-sm">
27
+ <Link
28
+ to="/planets/$id"
29
+ params={{ id: planet.id }}
30
+ className="block no-underline! outline-none focus-visible:ring-2 focus-visible:ring-interactive-outline-primary-idle"
31
+ >
32
+ <div className="aspect-video w-full bg-elevation-fill-default-2">
33
+ <img
34
+ src={imageSrc}
35
+ alt=""
36
+ className="h-full w-full object-cover"
37
+ />
38
+ </div>
39
+ </Link>
40
+
41
+ <div className="flex flex-col gap-4 p-4 sm:p-5">
42
+ <div className="flex items-start justify-between gap-4">
43
+ <div className="min-w-0">
44
+ <Typography
45
+ as="h2"
46
+ size="title-4"
47
+ variant="prominent-1"
48
+ className="text-text-default-1"
49
+ >
50
+ {planet.name}
51
+ </Typography>
52
+ {discoveryDate ? (
53
+ <Typography
54
+ size="label-3"
55
+ className="mt-1 text-text-default-2"
56
+ >
57
+ {t(($) => $.planetsFeed.card.discovered, { date: discoveryDate })}
58
+ </Typography>
59
+ ) : null}
60
+ </div>
61
+
62
+ <Button
63
+ variant={isLiked ? "contained" : "outlined"}
64
+ color={isLiked ? "primary" : "secondary"}
65
+ size="s"
66
+ icon={Heart}
67
+ isLoading={isLikePending}
68
+ onPress={() => onToggleLike(planet)}
69
+ >
70
+ {isLiked
71
+ ? t(($) => $.planetsFeed.card.liked, { count: likesCount })
72
+ : t(($) => $.planetsFeed.card.like, { count: likesCount })}
73
+ </Button>
74
+ </div>
75
+
76
+ {planet.description ? (
77
+ <Typography
78
+ size="body-3"
79
+ className="text-text-default-2"
80
+ >
81
+ {planet.description}
82
+ </Typography>
83
+ ) : null}
84
+
85
+ <div className="grid gap-3 sm:grid-cols-2">
86
+ <div className="flex min-w-0 items-center gap-3 rounded-s bg-elevation-fill-default-2 px-3 py-2">
87
+ <Satellite
88
+ className="size-5 shrink-0 text-text-default-2"
89
+ aria-hidden
90
+ />
91
+ <div className="min-w-0">
92
+ <Typography
93
+ size="label-3"
94
+ className="text-text-default-2"
95
+ >
96
+ {t(($) => $.planetsFeed.card.alienLabel)}
97
+ </Typography>
98
+ <Typography
99
+ size="body-3"
100
+ className="truncate text-text-default-1"
101
+ >
102
+ {alienName}
103
+ </Typography>
104
+ </div>
105
+ </div>
106
+
107
+ <div className="flex min-w-0 items-center gap-3 rounded-s bg-elevation-fill-default-2 px-3 py-2">
108
+ <UserRound
109
+ className="size-5 shrink-0 text-text-default-2"
110
+ aria-hidden
111
+ />
112
+ <div className="min-w-0">
113
+ <Typography
114
+ size="label-3"
115
+ className="text-text-default-2"
116
+ >
117
+ {t(($) => $.planetsFeed.card.creatorLabel)}
118
+ </Typography>
119
+ <Typography
120
+ size="body-3"
121
+ className="truncate text-text-default-1"
122
+ >
123
+ {planet.creatorName ?? t(($) => $.planets.card.unknownCreator)}
124
+ </Typography>
125
+ </div>
126
+ </div>
127
+ </div>
128
+ </div>
129
+ </article>
130
+ );
131
+ }
@@ -0,0 +1,119 @@
1
+ import { useQueryClient } from "@tanstack/react-query";
2
+ import { Typography } from "@povio/ui";
3
+ import { useCallback, useEffect, useRef } from "react";
4
+ import { useTranslation } from "react-i18next";
5
+
6
+ import { LoadingState } from "@/components/shared/layout/LoadingState";
7
+ import { PageHeader } from "@/components/shared/page/PageHeader";
8
+ import type { PlanetModels } from "@/openapi/planet/planet.models";
9
+ import { PlanetQueries } from "@/openapi/planet/planet.queries";
10
+
11
+ import { PlanetFeedCard } from "./PlanetFeedCard";
12
+
13
+ const PAGE_SIZE = 8;
14
+
15
+ type PlanetFeedItem = PlanetModels.PlanetsGetResponseDto;
16
+
17
+ export function PlanetsFeedPage() {
18
+ const { t } = useTranslation();
19
+ const queryClient = useQueryClient();
20
+ const loaderRef = useRef<HTMLDivElement | null>(null);
21
+
22
+ const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = PlanetQueries.usePaginateInfinite({
23
+ cursor: undefined,
24
+ limit: PAGE_SIZE,
25
+ order: "-discoveryDate",
26
+ });
27
+
28
+ const likeMutation = PlanetQueries.useLike({
29
+ onSuccess: async () => {
30
+ await queryClient.invalidateQueries({ queryKey: PlanetQueries.keys.all });
31
+ },
32
+ });
33
+
34
+ const unlikeMutation = PlanetQueries.useUnlike({
35
+ onSuccess: async () => {
36
+ await queryClient.invalidateQueries({ queryKey: PlanetQueries.keys.all });
37
+ },
38
+ });
39
+
40
+ const planets = data?.pages.flatMap((page) => page.items ?? []) ?? [];
41
+
42
+ useEffect(() => {
43
+ const loader = loaderRef.current;
44
+ if (!loader || !hasNextPage) {
45
+ return;
46
+ }
47
+
48
+ const observer = new IntersectionObserver(
49
+ ([entry]) => {
50
+ if (entry?.isIntersecting && !isFetchingNextPage) {
51
+ void fetchNextPage();
52
+ }
53
+ },
54
+ { rootMargin: "360px 0px" },
55
+ );
56
+
57
+ observer.observe(loader);
58
+ return () => observer.disconnect();
59
+ }, [fetchNextPage, hasNextPage, isFetchingNextPage]);
60
+
61
+ const toggleLike = useCallback(
62
+ (planet: PlanetFeedItem) => {
63
+ if (planet.likedByMe) {
64
+ unlikeMutation.mutate({ id: planet.id });
65
+ } else {
66
+ likeMutation.mutate({ id: planet.id });
67
+ }
68
+ },
69
+ [likeMutation.mutate, unlikeMutation.mutate],
70
+ );
71
+
72
+ return (
73
+ <div className="mx-auto flex w-full max-w-3xl flex-col gap-6">
74
+ <PageHeader title={t(($) => $.planetsFeed.page.title)} />
75
+
76
+ {isLoading ? (
77
+ <LoadingState />
78
+ ) : (
79
+ <div className="flex flex-col gap-5">
80
+ {planets.map((planet) => (
81
+ <PlanetFeedCard
82
+ key={planet.id}
83
+ planet={planet}
84
+ isLikePending={
85
+ (likeMutation.isPending && likeMutation.variables?.id === planet.id) ||
86
+ (unlikeMutation.isPending && unlikeMutation.variables?.id === planet.id)
87
+ }
88
+ onToggleLike={toggleLike}
89
+ />
90
+ ))}
91
+ </div>
92
+ )}
93
+
94
+ {!isLoading && planets.length === 0 && (
95
+ <Typography
96
+ size="body-3"
97
+ className="text-text-default-2"
98
+ >
99
+ {t(($) => $.planetsFeed.page.empty)}
100
+ </Typography>
101
+ )}
102
+
103
+ <div
104
+ ref={loaderRef}
105
+ className="flex min-h-16 items-center justify-center"
106
+ >
107
+ {isFetchingNextPage && <LoadingState />}
108
+ {!hasNextPage && planets.length > 0 && (
109
+ <Typography
110
+ size="label-3"
111
+ className="text-text-default-2"
112
+ >
113
+ {t(($) => $.planetsFeed.page.end)}
114
+ </Typography>
115
+ )}
116
+ </div>
117
+ </div>
118
+ );
119
+ }
@@ -0,0 +1,89 @@
1
+ import { IconButton, Link, Typography } from "@povio/ui";
2
+ import { Pencil } from "lucide-react";
3
+ import { useTranslation } from "react-i18next";
4
+
5
+ import { useAuth } from "@/hooks/useAuth";
6
+ import type { PlanetModels } from "@/openapi/planet/planet.models";
7
+ import { DateUtils } from "@/utils/date.utils";
8
+ import { getFallbackImageUrl } from "@/utils/image-fallback";
9
+
10
+ interface PlanetCardProps {
11
+ planet: PlanetModels.PlanetsGetResponseDto;
12
+ }
13
+
14
+ export function PlanetCard({ planet }: PlanetCardProps) {
15
+ const { t } = useTranslation();
16
+ const { user } = useAuth();
17
+
18
+ const imageSrc = planet.image?.url ?? getFallbackImageUrl({ width: 400, height: 300 });
19
+ const createdDate = DateUtils.formatDate(new Date(planet.createdAt));
20
+ const creatorName = planet.creatorName ?? t(($) => $.planets.card.unknownCreator);
21
+ // Match the details/edit pages: show owner-only edit affordances, but keep backend authorization authoritative.
22
+ const canEdit = user?.id === planet.userId;
23
+
24
+ return (
25
+ <div className="relative overflow-hidden rounded-m border border-elevation-outline-default-1 bg-elevation-fill-default-1 shadow-sm transition-all hover:border-elevation-outline-default-2 hover:bg-elevation-fill-default-2">
26
+ {canEdit && (
27
+ <div className="absolute right-2 top-2 z-10">
28
+ <IconButton
29
+ size="xs"
30
+ variant="subtle"
31
+ label={t(($) => $.planets.actions.edit)}
32
+ icon={Pencil}
33
+ color="secondary"
34
+ link={{ to: "/planets/$id/edit", params: { id: planet.id } }}
35
+ />
36
+ </div>
37
+ )}
38
+
39
+ <Link
40
+ to="/planets/$id"
41
+ params={{ id: planet.id }}
42
+ className="flex flex-col no-underline! outline-none focus-visible:ring-2 focus-visible:ring-interactive-outline-primary-idle"
43
+ >
44
+ <div className="aspect-video w-full bg-elevation-fill-default-2">
45
+ <img
46
+ src={imageSrc}
47
+ alt=""
48
+ className="h-full w-full object-cover"
49
+ />
50
+ </div>
51
+
52
+ <div className="flex flex-col gap-2 p-4">
53
+ <Typography
54
+ size="title-5"
55
+ variant="prominent-1"
56
+ as="h3"
57
+ className="min-w-0 flex-1 text-text-default-1"
58
+ >
59
+ {planet.name}
60
+ </Typography>
61
+ {planet.description && (
62
+ <Typography
63
+ size="body-3"
64
+ className="line-clamp-2 w-full text-text-default-2"
65
+ >
66
+ {planet.description}
67
+ </Typography>
68
+ )}
69
+
70
+ <Typography
71
+ size="label-3"
72
+ className="pt-2 text-text-default-2"
73
+ >
74
+ {t(($) => $.planets.card.createdBy, { name: creatorName, date: createdDate })}
75
+ </Typography>
76
+
77
+ {planet.alienName && (
78
+ <Typography
79
+ size="label-3"
80
+ className="text-text-default-2"
81
+ >
82
+ {t(($) => $.planets.card.alien, { name: planet.alienName })}
83
+ </Typography>
84
+ )}
85
+ </div>
86
+ </Link>
87
+ </div>
88
+ );
89
+ }
@@ -0,0 +1,179 @@
1
+ import {
2
+ Button,
3
+ DatePicker,
4
+ FileUpload,
5
+ Modal,
6
+ QueryAutocomplete,
7
+ TextArea,
8
+ TextInput,
9
+ Typography,
10
+ useForm,
11
+ useToast,
12
+ } from "@povio/ui/tanstack";
13
+ import { useTranslation } from "react-i18next";
14
+
15
+ import { RowInputWrapper } from "@/components/shared/forms/RowInputWrapper";
16
+ import { AlienQueries } from "@/openapi/alien/alien.queries";
17
+ import { MediaModels } from "@/openapi/media/media.models";
18
+ import { PlanetModels } from "@/openapi/planet/planet.models";
19
+ import { PlanetQueries } from "@/openapi/planet/planet.queries";
20
+ import { useMediaUploadHandler } from "@/utils/media-upload";
21
+
22
+ interface PlanetCreateModalProps {
23
+ isOpen: boolean;
24
+ onClose: () => void;
25
+ }
26
+
27
+ const useAlienLabelsQuery = AlienQueries.useGetLabels as never;
28
+
29
+ export function PlanetCreateModal({ isOpen, onClose }: PlanetCreateModalProps) {
30
+ const { t } = useTranslation();
31
+ const createPlanet = PlanetQueries.useCreate();
32
+ const { successToast, errorToast } = useToast();
33
+ // Generated schemas drive form typing and validation; do not import backend types in frontend code.
34
+ const form = useForm({
35
+ zodSchema: PlanetModels.PlanetsCreateRequestDtoSchema,
36
+ defaultValues: {
37
+ name: "",
38
+ alienId: undefined,
39
+ discoveryDate: undefined,
40
+ description: "",
41
+ image: undefined,
42
+ },
43
+ });
44
+
45
+ const handleImageUpload = useMediaUploadHandler({
46
+ resourceName: MediaModels.MediaResourceName["planet-image"],
47
+ onUploaded: (media) => form.setFieldValue("image", { id: media.id }),
48
+ });
49
+
50
+ const handleImageChange = (file: File | null) => {
51
+ if (!file) {
52
+ form.setFieldValue("image", undefined);
53
+ }
54
+ };
55
+
56
+ const onSubmit = async (data: PlanetModels.PlanetsCreateRequestDto) => {
57
+ try {
58
+ await createPlanet.mutateAsync({ data });
59
+ successToast({ text: t(($) => $.planets.create.success) });
60
+ form.reset();
61
+ onClose();
62
+ } catch {
63
+ errorToast({ text: t(($) => $.planets.create.error) });
64
+ }
65
+ };
66
+
67
+ const handleClose = () => {
68
+ form.reset();
69
+ onClose();
70
+ };
71
+
72
+ return (
73
+ <Modal
74
+ isOpen={isOpen}
75
+ onClose={handleClose}
76
+ aside="right"
77
+ showCloseIcon
78
+ modalClassName="h-full min-w-0 overflow-y-auto"
79
+ >
80
+ <form
81
+ onSubmit={form.handleSubmit(onSubmit)}
82
+ className="flex w-full min-w-0 flex-col gap-4"
83
+ >
84
+ <Typography
85
+ size="title-5"
86
+ variant="prominent-1"
87
+ as="h2"
88
+ className="mb-2 text-text-default-1"
89
+ >
90
+ {t(($) => $.planets.create.title)}
91
+ </Typography>
92
+
93
+ <RowInputWrapper
94
+ label={t(($) => $.planets.create.name)}
95
+ isRequired
96
+ >
97
+ <TextInput
98
+ variant="filled"
99
+ size="extra-small"
100
+ field={{ form, name: "name" }}
101
+ label={t(($) => $.planets.create.name)}
102
+ placeholder={t(($) => $.planets.create.namePlaceholder)}
103
+ hideLabel
104
+ isRequired
105
+ />
106
+ </RowInputWrapper>
107
+
108
+ <RowInputWrapper label={t(($) => $.planets.create.alien)}>
109
+ <QueryAutocomplete
110
+ variant="filled"
111
+ size="extra-small"
112
+ field={{ form, name: "alienId" }}
113
+ query={useAlienLabelsQuery}
114
+ label={t(($) => $.planets.create.alien)}
115
+ placeholder={t(($) => $.planets.create.alienPlaceholder)}
116
+ hideLabel
117
+ />
118
+ </RowInputWrapper>
119
+
120
+ <RowInputWrapper label={t(($) => $.planets.create.discoveryDate)}>
121
+ <DatePicker
122
+ variant="filled"
123
+ size="extra-small"
124
+ field={{ form, name: "discoveryDate" }}
125
+ label={t(($) => $.planets.create.discoveryDate)}
126
+ placeholder={t(($) => $.planets.create.discoveryDatePlaceholder)}
127
+ hideLabel
128
+ />
129
+ </RowInputWrapper>
130
+
131
+ <RowInputWrapper label={t(($) => $.planets.create.description)}>
132
+ <TextArea
133
+ variant="filled"
134
+ size="extra-small"
135
+ field={{ form, name: "description" }}
136
+ label={t(($) => $.planets.create.description)}
137
+ placeholder={t(($) => $.planets.create.descriptionPlaceholder)}
138
+ hideLabel
139
+ />
140
+ </RowInputWrapper>
141
+
142
+ <FileUpload
143
+ label={t(($) => $.planets.create.image)}
144
+ emptyText={t(($) => $.planets.create.imageEmptyText)}
145
+ uploadText={t(($) => $.planets.create.imageUploadText)}
146
+ browseText={t(($) => $.planets.create.imageBrowseText)}
147
+ acceptedFileTypes={["image/jpeg", "image/png", "image/webp"]}
148
+ onChange={handleImageChange}
149
+ fileUpload={handleImageUpload}
150
+ />
151
+
152
+ <div className="mt-6 flex items-center justify-end gap-2 self-end">
153
+ <Button
154
+ type="button"
155
+ variant="outlined"
156
+ size="xs"
157
+ color="secondary"
158
+ width="hug"
159
+ onPress={handleClose}
160
+ >
161
+ {t(($) => $.planets.create.cancel)}
162
+ </Button>
163
+
164
+ <Button
165
+ type="submit"
166
+ variant="contained"
167
+ size="xs"
168
+ color="primary"
169
+ width="hug"
170
+ isDisabled={createPlanet.isPending}
171
+ isLoading={createPlanet.isPending}
172
+ >
173
+ {t(($) => $.planets.create.submit)}
174
+ </Button>
175
+ </div>
176
+ </form>
177
+ </Modal>
178
+ );
179
+ }
@@ -0,0 +1,3 @@
1
+ .planets-filters__control-without-label label {
2
+ display: none !important;
3
+ }
@@ -0,0 +1,73 @@
1
+ import { Button, DateRangePicker, QueryAutocomplete } from "@povio/ui/tanstack";
2
+ import type { FilterStore } from "@povio/ui/tanstack";
3
+ import { useTranslation } from "react-i18next";
4
+
5
+ import { AlienQueries } from "@/openapi/alien/alien.queries";
6
+ import type { PlanetModels } from "@/openapi/planet/planet.models";
7
+
8
+ import "./PlanetsFilters.css";
9
+
10
+ export type PlanetsFiltersValue = NonNullable<PlanetModels.PlanetPaginateFilterParam>;
11
+
12
+ interface PlanetsFiltersProps {
13
+ filters: FilterStore<PlanetsFiltersValue>;
14
+ }
15
+
16
+ function getDateRangeValue(value: PlanetsFiltersValue["discoveryDate"]) {
17
+ if (!value) {
18
+ return null;
19
+ }
20
+
21
+ return {
22
+ start: value.start ?? null,
23
+ end: value.end ?? null,
24
+ };
25
+ }
26
+
27
+ export function PlanetsFilters({ filters }: PlanetsFiltersProps) {
28
+ const { t } = useTranslation();
29
+ const handleClearAllFilters = () => filters.clearAllFilters();
30
+
31
+ return (
32
+ <div className="flex flex-wrap items-end gap-1 *:w-fit!">
33
+ {/* QueryAutocomplete works well with lightweight label endpoints instead of loading full related records. */}
34
+ <div className="planets-filters__control-without-label">
35
+ <QueryAutocomplete
36
+ value={filters.filterData.alienId ?? null}
37
+ onChange={(alienId) => filters.setFilterValue({ alienId: alienId ? String(alienId) : undefined })}
38
+ query={AlienQueries.useGetLabels}
39
+ label={t(($) => $.planets.filters.alien)}
40
+ placeholder={t(($) => $.planets.filters.alienPlaceholder)}
41
+ isClearable
42
+ as="filter"
43
+ size="extra-small"
44
+ variant="filled"
45
+ />
46
+ </div>
47
+
48
+ <div className="planets-filters__control-without-label">
49
+ <DateRangePicker
50
+ value={getDateRangeValue(filters.filterData.discoveryDate)}
51
+ onChange={(discoveryDate) => filters.setFilterValue({ discoveryDate: discoveryDate ?? undefined })}
52
+ label={t(($) => $.planets.filters.discoveryDate)}
53
+ placeholder={t(($) => $.planets.filters.discoveryDatePlaceholder)}
54
+ isClearable
55
+ // fullIso matches the backend z.iso.datetime schema and avoids local date parsing ambiguity.
56
+ fullIso
57
+ as="filter"
58
+ size="extra-small"
59
+ variant="filled"
60
+ />
61
+ </div>
62
+
63
+ <Button
64
+ variant="subtle"
65
+ color="secondary"
66
+ size="xs"
67
+ onPress={handleClearAllFilters}
68
+ >
69
+ {t(($) => $.planets.filters.clearAll)}
70
+ </Button>
71
+ </div>
72
+ );
73
+ }
@@ -0,0 +1,51 @@
1
+ import { Typography, useFilters } from "@povio/ui";
2
+ import { useTranslation } from "react-i18next";
3
+
4
+ import { LoadingState } from "@/components/shared/layout/LoadingState";
5
+ import { PlanetModels } from "@/openapi/planet/planet.models";
6
+ import { PlanetQueries } from "@/openapi/planet/planet.queries";
7
+
8
+ import { PlanetCard } from "./PlanetCard";
9
+ import { PlanetsFilters, type PlanetsFiltersValue } from "./PlanetsFilters";
10
+
11
+ export function PlanetsGridPage() {
12
+ const { t } = useTranslation();
13
+ const filters = useFilters<PlanetsFiltersValue>({}, "", PlanetModels.PlanetPaginateFilterParamSchema);
14
+
15
+ const { data, isLoading } = PlanetQueries.usePaginate({
16
+ page: 1,
17
+ cursor: undefined,
18
+ limit: 20,
19
+ filter: filters.filterData,
20
+ order: "-discoveryDate",
21
+ });
22
+ const planets = data?.items ?? [];
23
+
24
+ return (
25
+ <div className="flex flex-col gap-4">
26
+ <PlanetsFilters filters={filters} />
27
+
28
+ {isLoading ? (
29
+ <LoadingState />
30
+ ) : (
31
+ <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
32
+ {planets.map((planet) => (
33
+ <PlanetCard
34
+ key={planet.id}
35
+ planet={planet}
36
+ />
37
+ ))}
38
+ </div>
39
+ )}
40
+
41
+ {!isLoading && planets.length === 0 && (
42
+ <Typography
43
+ size="body-3"
44
+ className="text-text-default-2"
45
+ >
46
+ {t(($) => $.planets.page.empty)}
47
+ </Typography>
48
+ )}
49
+ </div>
50
+ );
51
+ }