robodev 0.47.2 → 0.48.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 (172) hide show
  1. package/package.json +3 -3
  2. package/src/emit-starter.test.ts +6 -3
  3. package/templates/auth-chat/ROBODEV.md +7 -1
  4. package/templates/auth-chat/package.json +2 -2
  5. package/templates/backend/ROBODEV.md +7 -1
  6. package/templates/backend/package.json +2 -2
  7. package/templates/catalog.json +5 -0
  8. package/templates/empty/ROBODEV.md +7 -1
  9. package/templates/empty/package.json +2 -2
  10. package/templates/marketplace/ROBODEV.md +7 -1
  11. package/templates/marketplace/package.json +2 -2
  12. package/templates/povio/.config/local.spa.template.yml +27 -0
  13. package/templates/povio/.oxlint-base.json +29 -0
  14. package/templates/povio/.oxlintrc.json +78 -0
  15. package/templates/povio/.rulesync/rules/frontend-api-boundary.md +36 -0
  16. package/templates/povio/.rulesync/rules/frontend-component-structure.md +40 -0
  17. package/templates/povio/.rulesync/rules/frontend-forms.md +27 -0
  18. package/templates/povio/.rulesync/rules/frontend-notifications.md +24 -0
  19. package/templates/povio/.rulesync/rules/frontend-povio-components.md +42 -0
  20. package/templates/povio/.rulesync/rules/frontend-povio-ui.md +45 -0
  21. package/templates/povio/.rulesync/rules/frontend-query-autocomplete.md +24 -0
  22. package/templates/povio/.rulesync/rules/frontend-tables-and-lists.md +26 -0
  23. package/templates/povio/.rulesync/rules/frontend-translations.md +25 -0
  24. package/templates/povio/.rulesync/rules/project-overview.md +32 -0
  25. package/templates/povio/.rulesync/rules/robodev-api.md +30 -0
  26. package/templates/povio/.rulesync/rules/robodev-auth.md +40 -0
  27. package/templates/povio/.rulesync/rules/robodev-database.md +22 -0
  28. package/templates/povio/.rulesync/rules/role-based-app-structure.md +36 -0
  29. package/templates/povio/.rulesync/skills/media-feature/SKILL.md +21 -0
  30. package/templates/povio/.rulesync/skills/povio-ui-styling/SKILL.md +211 -0
  31. package/templates/povio/.rulesync/skills/povio-ui-styling/agents/openai.yaml +4 -0
  32. package/templates/povio/.rulesync/skills/robodev-api-route/SKILL.md +13 -0
  33. package/templates/povio/.rulesync/skills/robodev-table/SKILL.md +12 -0
  34. package/templates/povio/README.md +40 -0
  35. package/templates/povio/ROBODEV.md +84 -0
  36. package/templates/povio/apps/fe/index.html +24 -0
  37. package/templates/povio/apps/fe/openapi-codegen.config.ts +72 -0
  38. package/templates/povio/apps/fe/package.json +71 -0
  39. package/templates/povio/apps/fe/public/apple-touch-icon.png +0 -0
  40. package/templates/povio/apps/fe/public/favicon-96x96.png +0 -0
  41. package/templates/povio/apps/fe/public/favicon.ico +0 -0
  42. package/templates/povio/apps/fe/public/favicon.svg +3 -0
  43. package/templates/povio/apps/fe/public/site.webmanifest +21 -0
  44. package/templates/povio/apps/fe/public/web-app-manifest-192x192.png +0 -0
  45. package/templates/povio/apps/fe/public/web-app-manifest-512x512.png +0 -0
  46. package/templates/povio/apps/fe/src/assets/locales/en/translation.json +166 -0
  47. package/templates/povio/apps/fe/src/clients/app-rest-client.ts +15 -0
  48. package/templates/povio/apps/fe/src/clients/auth-token-store.ts +101 -0
  49. package/templates/povio/apps/fe/src/clients/rest/app-error-handler.ts +31 -0
  50. package/templates/povio/apps/fe/src/clients/rest/interceptors/authorization-header.interceptor.ts +15 -0
  51. package/templates/povio/apps/fe/src/clients/rest/interceptors/refresh-token.interceptor.ts +45 -0
  52. package/templates/povio/apps/fe/src/clients/rest/interceptors/response.interceptor.ts +17 -0
  53. package/templates/povio/apps/fe/src/components/404.tsx +18 -0
  54. package/templates/povio/apps/fe/src/components/features/auth/AuthBrandPanel.tsx +60 -0
  55. package/templates/povio/apps/fe/src/components/features/auth/AuthLayout.tsx +23 -0
  56. package/templates/povio/apps/fe/src/components/features/auth/LoginPage.tsx +111 -0
  57. package/templates/povio/apps/fe/src/components/features/auth/RegisterPage.tsx +143 -0
  58. package/templates/povio/apps/fe/src/components/features/home/HomePage.tsx +21 -0
  59. package/templates/povio/apps/fe/src/components/features/profile/ProfilePage.tsx +96 -0
  60. package/templates/povio/apps/fe/src/components/layout/AppLayout.tsx +19 -0
  61. package/templates/povio/apps/fe/src/components/layout/app-header/AppHeader.tsx +91 -0
  62. package/templates/povio/apps/fe/src/components/layout/app-header/MobileNavigation.tsx +51 -0
  63. package/templates/povio/apps/fe/src/components/layout/app-header/NavLink.tsx +29 -0
  64. package/templates/povio/apps/fe/src/components/shared/branding/BrandLogo.tsx +13 -0
  65. package/templates/povio/apps/fe/src/components/shared/error/ErrorFallback.tsx +43 -0
  66. package/templates/povio/apps/fe/src/components/shared/error/ErrorText.tsx +20 -0
  67. package/templates/povio/apps/fe/src/components/shared/error/NotFound.tsx +36 -0
  68. package/templates/povio/apps/fe/src/components/shared/forms/RequiredLabel.tsx +21 -0
  69. package/templates/povio/apps/fe/src/components/shared/forms/RowInputWrapper.tsx +50 -0
  70. package/templates/povio/apps/fe/src/components/shared/head/AppHead.tsx +32 -0
  71. package/templates/povio/apps/fe/src/components/shared/head/DefaultAppHead.tsx +34 -0
  72. package/templates/povio/apps/fe/src/components/shared/layout/LoadingState.tsx +9 -0
  73. package/templates/povio/apps/fe/src/components/shared/layout/ThinPageWrapper.tsx +5 -0
  74. package/templates/povio/apps/fe/src/components/shared/page/PageHeader.tsx +69 -0
  75. package/templates/povio/apps/fe/src/components/shared/ui/Card.tsx +60 -0
  76. package/templates/povio/apps/fe/src/components/shared/ui/GoogleLoginButton.tsx +19 -0
  77. package/templates/povio/apps/fe/src/components/shared/ui/RequiredLabel.tsx +22 -0
  78. package/templates/povio/apps/fe/src/components/shared/ui/TableActions.tsx +22 -0
  79. package/templates/povio/apps/fe/src/config/app.config.ts +23 -0
  80. package/templates/povio/apps/fe/src/config/i18n.ts +37 -0
  81. package/templates/povio/apps/fe/src/config/inits/logger.ts +7 -0
  82. package/templates/povio/apps/fe/src/config/jwt.config.ts +2 -0
  83. package/templates/povio/apps/fe/src/config/query.config.ts +20 -0
  84. package/templates/povio/apps/fe/src/hooks/useAuth.ts +5 -0
  85. package/templates/povio/apps/fe/src/main.tsx +52 -0
  86. package/templates/povio/apps/fe/src/pages/(guest)/login.tsx +23 -0
  87. package/templates/povio/apps/fe/src/pages/(guest)/register.tsx +23 -0
  88. package/templates/povio/apps/fe/src/pages/(guest)/route.tsx +18 -0
  89. package/templates/povio/apps/fe/src/pages/(private)/index.tsx +23 -0
  90. package/templates/povio/apps/fe/src/pages/(private)/profile.tsx +23 -0
  91. package/templates/povio/apps/fe/src/pages/(private)/route.tsx +18 -0
  92. package/templates/povio/apps/fe/src/pages/(public)/auth.tsx +37 -0
  93. package/templates/povio/apps/fe/src/pages/(public)/route.tsx +9 -0
  94. package/templates/povio/apps/fe/src/pages/__root.tsx +133 -0
  95. package/templates/povio/apps/fe/src/providers/AppErrorBoundary.tsx +44 -0
  96. package/templates/povio/apps/fe/src/providers/OpenApiRuntimeProvider.tsx +31 -0
  97. package/templates/povio/apps/fe/src/providers/index.tsx +44 -0
  98. package/templates/povio/apps/fe/src/providers/jwt.provider.tsx +95 -0
  99. package/templates/povio/apps/fe/src/routeTree.gen.ts +225 -0
  100. package/templates/povio/apps/fe/src/styles/base.css +84 -0
  101. package/templates/povio/apps/fe/src/styles/fonts/fonts.tsx +10 -0
  102. package/templates/povio/apps/fe/src/styles/globals.css +26 -0
  103. package/templates/povio/apps/fe/src/styles/overrides/defaults/button.override.ts +536 -0
  104. package/templates/povio/apps/fe/src/styles/overrides/defaults/checkbox.override.ts +71 -0
  105. package/templates/povio/apps/fe/src/styles/overrides/defaults/input.override.ts +252 -0
  106. package/templates/povio/apps/fe/src/styles/overrides/defaults/label.override.ts +91 -0
  107. package/templates/povio/apps/fe/src/styles/overrides/defaults/modal.override.ts +62 -0
  108. package/templates/povio/apps/fe/src/styles/overrides/defaults/radio.override.ts +46 -0
  109. package/templates/povio/apps/fe/src/styles/overrides/defaults/table.override.ts +104 -0
  110. package/templates/povio/apps/fe/src/styles/overrides/defaults/tag.override.ts +66 -0
  111. package/templates/povio/apps/fe/src/styles/overrides/defaults/typography.override.ts +115 -0
  112. package/templates/povio/apps/fe/src/styles/overrides/outline.clsx.ts +10 -0
  113. package/templates/povio/apps/fe/src/styles/overrides/uiOverrides.override.ts +60 -0
  114. package/templates/povio/apps/fe/src/styles/theme.css +2177 -0
  115. package/templates/povio/apps/fe/src/types/i18next.d.ts +12 -0
  116. package/templates/povio/apps/fe/src/types/table.d.ts +17 -0
  117. package/templates/povio/apps/fe/src/types/ui.d.ts +26 -0
  118. package/templates/povio/apps/fe/src/types/vite-env.d.ts +13 -0
  119. package/templates/povio/apps/fe/src/utils/date.utils.ts +17 -0
  120. package/templates/povio/apps/fe/src/utils/image-fallback.ts +9 -0
  121. package/templates/povio/apps/fe/src/utils/number.utils.ts +12 -0
  122. package/templates/povio/apps/fe/src/utils/string.utils.ts +5 -0
  123. package/templates/povio/apps/fe/src/vite-env.d.ts +1 -0
  124. package/templates/povio/apps/fe/tsconfig.app.json +32 -0
  125. package/templates/povio/apps/fe/tsconfig.json +9 -0
  126. package/templates/povio/apps/fe/tsconfig.node.json +31 -0
  127. package/templates/povio/apps/fe/vite.config.ts +150 -0
  128. package/templates/povio/openapi.json +911 -0
  129. package/templates/povio/oxfmt.config.js +23 -0
  130. package/templates/povio/package.json +19 -0
  131. package/templates/povio/robodev/api/_lib.ts +99 -0
  132. package/templates/povio/robodev/api/health.ts +7 -0
  133. package/templates/povio/robodev/database.ts +28 -0
  134. package/templates/povio/robodev/package.json +4 -0
  135. package/templates/povio/rulesync.jsonc +18 -0
  136. package/templates/povio/tsconfig.json +11 -0
  137. package/templates/shadcn/.rulesync/rules/frontend-api-boundary.md +29 -0
  138. package/templates/shadcn/.rulesync/rules/frontend-component-structure.md +19 -0
  139. package/templates/shadcn/.rulesync/rules/frontend-hosted-ui.md +38 -0
  140. package/templates/shadcn/.rulesync/rules/project-overview.md +29 -0
  141. package/templates/shadcn/.rulesync/rules/robodev-api.md +22 -0
  142. package/templates/shadcn/.rulesync/rules/robodev-auth.md +47 -0
  143. package/templates/shadcn/.rulesync/rules/robodev-database.md +21 -0
  144. package/templates/shadcn/.rulesync/skills/robodev-crud-page/SKILL.md +181 -0
  145. package/templates/shadcn/README.md +9 -4
  146. package/templates/shadcn/ROBODEV.md +14 -6
  147. package/templates/shadcn/package.json +4 -6
  148. package/templates/shadcn/rulesync.jsonc +18 -0
  149. package/templates/shadcn/src/App.tsx +12 -3
  150. package/templates/shadcn/src/app.css +359 -0
  151. package/templates/shadcn/src/components/auth/AuthCard.tsx +29 -0
  152. package/templates/shadcn/src/components/ui/badge.tsx +17 -0
  153. package/templates/shadcn/src/components/ui/card.tsx +28 -0
  154. package/templates/shadcn/src/components/ui/dialog.tsx +38 -0
  155. package/templates/shadcn/src/components/ui/empty-state.tsx +17 -0
  156. package/templates/shadcn/src/components/ui/input.tsx +8 -0
  157. package/templates/shadcn/src/components/ui/label.tsx +8 -0
  158. package/templates/shadcn/src/components/ui/skeleton.tsx +6 -0
  159. package/templates/shadcn/src/components/ui/spinner.tsx +3 -0
  160. package/templates/shadcn/src/components/ui/table.tsx +30 -0
  161. package/templates/shadcn/src/components/ui/textarea.tsx +8 -0
  162. package/templates/shadcn/src/lib/auth.tsx +72 -0
  163. package/templates/shadcn/src/lib/client.ts +8 -0
  164. package/templates/shadcn/src/pages/Home.tsx +15 -0
  165. package/templates/shadcn/src/pages/Login.tsx +70 -0
  166. package/templates/shadcn/src/pages/NotFound.tsx +14 -0
  167. package/templates/shadcn/src/pages/Register.tsx +82 -0
  168. package/templates/space/ROBODEV.md +7 -1
  169. package/templates/space/package.json +2 -2
  170. package/templates/shadcn/components.json +0 -21
  171. package/templates/shadcn/postcss.config.js +0 -6
  172. package/templates/shadcn/tailwind.config.ts +0 -42
@@ -0,0 +1,37 @@
1
+ import { createFileRoute, useNavigate } from "@tanstack/react-router";
2
+ import { useEffect } from "react";
3
+ import z from "zod";
4
+
5
+ import { LoadingState } from "@/components/shared/layout/LoadingState";
6
+ import { useAuth } from "@/hooks/useAuth";
7
+
8
+ const authSearchSchema = z.object({
9
+ type: z.string().optional(),
10
+ accessToken: z.string().optional(),
11
+ refreshToken: z.string().optional(),
12
+ });
13
+
14
+ function AuthCallbackPage() {
15
+ const navigate = useNavigate();
16
+ const { accessToken, refreshToken } = Route.useSearch();
17
+ const { updateTokens } = useAuth();
18
+
19
+ useEffect(() => {
20
+ if (accessToken) {
21
+ const decodedAccessToken = decodeURIComponent(accessToken);
22
+ const decodedRefreshToken = refreshToken ? decodeURIComponent(refreshToken) : undefined;
23
+ updateTokens?.(decodedAccessToken, decodedRefreshToken);
24
+
25
+ navigate({ to: "/" });
26
+ } else {
27
+ navigate({ to: "/login" });
28
+ }
29
+ }, [accessToken, refreshToken, navigate, updateTokens]);
30
+
31
+ return <LoadingState />;
32
+ }
33
+
34
+ export const Route = createFileRoute("/(public)/auth")({
35
+ component: AuthCallbackPage,
36
+ validateSearch: authSearchSchema,
37
+ });
@@ -0,0 +1,9 @@
1
+ import { Outlet, createFileRoute } from "@tanstack/react-router";
2
+
3
+ function PublicLayout() {
4
+ return <Outlet />;
5
+ }
6
+
7
+ export const Route = createFileRoute("/(public)")({
8
+ component: PublicLayout,
9
+ });
@@ -0,0 +1,133 @@
1
+ import { OpenApiRouter } from "@povio/openapi-codegen-cli";
2
+ import { Confirmation, ToastContainer, UIConfig, UIOverrides, UIRouter } from "@povio/ui";
3
+ import { Outlet, createRootRoute, useNavigate, useRouterState } from "@tanstack/react-router";
4
+ import { useCallback, useEffect } from "react";
5
+ import { useTranslation } from "react-i18next";
6
+
7
+ import { NotFoundPage } from "@/components/404";
8
+ import { DefaultAppHead } from "@/components/shared/head/DefaultAppHead";
9
+ import { initLogger } from "@/config/inits/logger";
10
+ import Providers from "@/providers";
11
+ import { AppErrorBoundary } from "@/providers/AppErrorBoundary";
12
+ import { OpenApiRuntimeProvider } from "@/providers/OpenApiRuntimeProvider";
13
+ import { Fonts } from "@/styles/fonts/fonts";
14
+ import { uiOverrides } from "@/styles/overrides/uiOverrides.override";
15
+
16
+ import "@/config/i18n";
17
+ initLogger();
18
+
19
+ const Root = () => {
20
+ const { i18n } = useTranslation();
21
+
22
+ const navigate = useNavigate();
23
+ const searchString = useRouterState({
24
+ select: (state) => state.location.searchStr,
25
+ });
26
+
27
+ const push = useCallback(
28
+ async (to: string, params?: Record<string, unknown>) => {
29
+ await navigate({ to, search: params });
30
+ return true;
31
+ },
32
+ [navigate],
33
+ );
34
+
35
+ const replace = useCallback(
36
+ async (to: string, params?: Record<string, unknown>) => {
37
+ await navigate({ to, replace: true, search: params });
38
+ return true;
39
+ },
40
+ [navigate],
41
+ );
42
+
43
+ useEffect(() => {
44
+ const handler = (lng: string) => {
45
+ if (typeof document !== "undefined") {
46
+ document.documentElement.setAttribute("lang", lng);
47
+ }
48
+ };
49
+ i18n.on("languageChanged", handler);
50
+ return () => {
51
+ i18n.off("languageChanged", handler);
52
+ };
53
+ }, [i18n]);
54
+
55
+ return (
56
+ <>
57
+ <AppErrorBoundary>
58
+ <Providers
59
+ providers={[
60
+ // { provider: ThemeContext.ThemeContextProvider }, // Uncomment for dark theme support
61
+ { provider: UIRouter.UIRouterProvider, props: { push, replace, searchString } },
62
+ { provider: OpenApiRouter.Provider, props: { replace } },
63
+ {
64
+ provider: UIConfig.Provider,
65
+ props: {
66
+ config: {
67
+ input: {
68
+ size: "small",
69
+ },
70
+ tableCellText: {
71
+ size: "label-2",
72
+ variant: "default",
73
+ },
74
+ tableHeaderText: {
75
+ size: "label-2",
76
+ variant: "prominent-1",
77
+ },
78
+ tag: {
79
+ textSize: "label-3",
80
+ },
81
+ },
82
+ },
83
+ },
84
+ {
85
+ provider: UIOverrides.Provider,
86
+ props: {
87
+ config: uiOverrides,
88
+ },
89
+ },
90
+ { provider: OpenApiRuntimeProvider },
91
+ { provider: Confirmation.Provider },
92
+ ]}
93
+ >
94
+ <Fonts />
95
+ <DefaultAppHead />
96
+
97
+ <RootLayout />
98
+
99
+ <ToastContainer />
100
+ </Providers>
101
+ </AppErrorBoundary>
102
+ </>
103
+ );
104
+ };
105
+
106
+ function RootLayout() {
107
+ return (
108
+ <main className="font-primary">
109
+ <Outlet />
110
+ </main>
111
+ );
112
+ }
113
+
114
+ export const Route = createRootRoute({
115
+ head: () => ({
116
+ meta: [
117
+ { charSet: "utf8" },
118
+ { name: "viewport", content: "width=device-width, initial-scale=1" },
119
+ { name: "theme-color", content: "#ffffff" },
120
+ { title: "Tiny Template" },
121
+ { name: "msapplication-TileColor", content: "#da532c" },
122
+ ],
123
+ links: [
124
+ { rel: "apple-touch-icon", sizes: "180x180", href: "/apple-touch-icon.png" },
125
+ { rel: "icon", type: "image/png", sizes: "96x96", href: "/favicon-96x96.png" },
126
+ { rel: "icon", type: "image/svg+xml", href: "/favicon.svg" },
127
+ { rel: "shortcut icon", href: "/favicon.ico" },
128
+ { rel: "manifest", href: "/site.webmanifest" },
129
+ ],
130
+ }),
131
+ component: Root,
132
+ notFoundComponent: NotFoundPage,
133
+ });
@@ -0,0 +1,44 @@
1
+ import { Component, type ErrorInfo, type PropsWithChildren } from "react";
2
+
3
+ import { ErrorFallback } from "@/components/shared/error/ErrorFallback";
4
+
5
+ interface AppErrorBoundaryState {
6
+ error: unknown;
7
+ }
8
+
9
+ /**
10
+ * Catches render errors below it and shows `ErrorFallback` with a retry that remounts the tree.
11
+ * React only exposes error boundaries as class components, so this is the one class in the seed.
12
+ * No error-reporting SDK is wired in.
13
+ */
14
+ export class AppErrorBoundary extends Component<PropsWithChildren, AppErrorBoundaryState> {
15
+ constructor(props: PropsWithChildren) {
16
+ super(props);
17
+ this.state = { error: null };
18
+ }
19
+
20
+ static getDerivedStateFromError(error: unknown): AppErrorBoundaryState {
21
+ return { error };
22
+ }
23
+
24
+ componentDidCatch(error: unknown, info: ErrorInfo) {
25
+ console.error(error, info.componentStack);
26
+ }
27
+
28
+ resetError = () => {
29
+ // oxlint-disable-next-line react/no-set-state -- the only way to clear a boundary's error
30
+ this.setState({ error: null });
31
+ };
32
+
33
+ render() {
34
+ if (this.state.error !== null) {
35
+ return (
36
+ <ErrorFallback
37
+ error={this.state.error}
38
+ resetError={this.resetError}
39
+ />
40
+ );
41
+ }
42
+ return this.props.children;
43
+ }
44
+ }
@@ -0,0 +1,31 @@
1
+ import { ErrorHandler, OpenApiQueryConfig } from "@povio/openapi-codegen-cli";
2
+ import { useToast } from "@povio/ui";
3
+ import type { PropsWithChildren } from "react";
4
+ import { useCallback } from "react";
5
+ import { useTranslation } from "react-i18next";
6
+
7
+ export function OpenApiRuntimeProvider({ children }: PropsWithChildren) {
8
+ const { t } = useTranslation();
9
+ const { errorToast } = useToast();
10
+
11
+ const handleError = useCallback(
12
+ (error: unknown) => {
13
+ console.error(error);
14
+ errorToast({
15
+ text: t(($) => $.shared.apiErrors.requestFailed, {
16
+ error: ErrorHandler.getErrorMessage(error) ?? t(($) => $.shared.apiErrors.unknown),
17
+ }),
18
+ });
19
+ },
20
+ [errorToast, t],
21
+ );
22
+
23
+ return (
24
+ <OpenApiQueryConfig.Provider
25
+ allowInvalidResponseData={import.meta.env.DEV}
26
+ onError={handleError}
27
+ >
28
+ {children}
29
+ </OpenApiQueryConfig.Provider>
30
+ );
31
+ }
@@ -0,0 +1,44 @@
1
+ import type { ComponentProps, JSX, ReactNode } from "react";
2
+
3
+ type HasRequiredProperty<T> = {
4
+ [K in keyof T]-?: Record<string, never> extends Pick<T, K> ? never : K;
5
+ }[keyof T];
6
+
7
+ interface ProviderBase<T> {
8
+ provider: T;
9
+ }
10
+
11
+ interface ProviderProps<T extends keyof JSX.IntrinsicElements> {
12
+ props: Omit<ComponentProps<T>, "children">;
13
+ }
14
+
15
+ // oxlint-disable-next-line no-explicit-any
16
+ type ProvidersArray<T extends any[]> = [
17
+ ...{
18
+ [K in keyof T]: HasRequiredProperty<Omit<ComponentProps<T[K]>, "children">> extends never
19
+ ? ProviderBase<T[K]> & Partial<ProviderProps<T[K]>>
20
+ : ProviderBase<T[K]> & ProviderProps<T[K]>;
21
+ },
22
+ ];
23
+
24
+ // oxlint-disable-next-line no-explicit-any
25
+ interface Props<T extends any[]> {
26
+ providers: ProvidersArray<T>;
27
+ children: ReactNode;
28
+ }
29
+
30
+ // oxlint-disable-next-line no-explicit-any
31
+ function Providers<T extends any[]>({ providers, children }: Props<T>) {
32
+ return (
33
+ <>
34
+ {providers.reduceRight(
35
+ (child, { provider: Provider, props }) => (
36
+ <Provider {...props}>{child}</Provider>
37
+ ),
38
+ children,
39
+ )}
40
+ </>
41
+ );
42
+ }
43
+
44
+ export default Providers;
@@ -0,0 +1,95 @@
1
+ import { AuthContext } from "@povio/openapi-codegen-cli";
2
+ import { useQueryClient } from "@tanstack/react-query";
3
+ import { type PropsWithChildren, useCallback, useEffect, useMemo, useRef, useSyncExternalStore } from "react";
4
+
5
+ import { authTokenStore } from "@/clients/auth-token-store";
6
+ import { LoadingState } from "@/components/shared/layout/LoadingState";
7
+ import { UserApi } from "@/openapi/user/user.api";
8
+ import type { UserModels } from "@/openapi/user/user.models";
9
+ import { UserQueries } from "@/openapi/user/user.queries";
10
+
11
+ const routes: AuthContext.Routes = {
12
+ authenticated: "/",
13
+ unauthenticated: "/login",
14
+ };
15
+
16
+ export const JWTProvider = ({ children }: PropsWithChildren) => {
17
+ const queryClient = useQueryClient();
18
+
19
+ const previousAccessToken = useRef<string | null>(null);
20
+ const accessToken = useSyncExternalStore(
21
+ authTokenStore.subscribe,
22
+ authTokenStore.getAccessToken,
23
+ authTokenStore.getServerSnapshot,
24
+ );
25
+
26
+ const authenticated = !!accessToken;
27
+ const isInitializing = false;
28
+
29
+ const { data: user } = UserQueries.useGet({
30
+ enabled: authenticated,
31
+ staleTime: 10 * 60 * 1000,
32
+ });
33
+
34
+ const onAccessTokenChange = useCallback((token: string | null) => {
35
+ authTokenStore.setAccessToken(token);
36
+ }, []);
37
+
38
+ const onRefreshTokenChange = useCallback((token: string | null) => {
39
+ authTokenStore.setRefreshToken(token);
40
+ }, []);
41
+
42
+ const logout = useCallback(() => {
43
+ authTokenStore.clear();
44
+ queryClient.clear();
45
+ }, [queryClient]);
46
+
47
+ useEffect(() => {
48
+ if (previousAccessToken.current && !accessToken) {
49
+ queryClient.clear();
50
+ }
51
+
52
+ previousAccessToken.current = accessToken;
53
+ }, [accessToken, queryClient]);
54
+
55
+ const userPromise = useCallback(async () => {
56
+ try {
57
+ const user = await UserApi.get();
58
+ queryClient.setQueryData<UserModels.UserMeResponse>(UserQueries.keys.get(), user);
59
+ return user;
60
+ } catch {
61
+ logout();
62
+ return null;
63
+ }
64
+ }, [logout, queryClient]);
65
+
66
+ const updateTokens = useCallback(
67
+ (accessToken: string | null, refreshToken?: string | null) => {
68
+ if (accessToken !== null) {
69
+ onAccessTokenChange(accessToken);
70
+ }
71
+ if (refreshToken !== null && refreshToken !== undefined) {
72
+ onRefreshTokenChange(refreshToken);
73
+ }
74
+ },
75
+ [onAccessTokenChange, onRefreshTokenChange],
76
+ );
77
+
78
+ const loadingState = useMemo(() => <LoadingState />, []);
79
+
80
+ return (
81
+ <AuthContext.Provider
82
+ isAuthenticated={authenticated}
83
+ isInitializing={isInitializing}
84
+ logout={logout}
85
+ updateTokens={updateTokens}
86
+ accessToken={accessToken}
87
+ user={user}
88
+ userPromise={userPromise}
89
+ routes={routes}
90
+ loadingState={loadingState}
91
+ >
92
+ {children}
93
+ </AuthContext.Provider>
94
+ );
95
+ };
@@ -0,0 +1,225 @@
1
+ /* eslint-disable */
2
+
3
+ // @ts-nocheck
4
+
5
+ // noinspection JSUnusedGlobalSymbols
6
+
7
+ // This file was automatically generated by TanStack Router.
8
+ // You should NOT make any changes in this file as it will be overwritten.
9
+ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
10
+
11
+ import { Route as rootRouteImport } from './pages/__root'
12
+ import { Route as publicRouteRouteImport } from './pages/(public)/route'
13
+ import { Route as privateRouteRouteImport } from './pages/(private)/route'
14
+ import { Route as guestRouteRouteImport } from './pages/(guest)/route'
15
+ import { Route as privateIndexRouteImport } from './pages/(private)/index'
16
+ import { Route as publicAuthRouteImport } from './pages/(public)/auth'
17
+ import { Route as privateProfileRouteImport } from './pages/(private)/profile'
18
+ import { Route as guestRegisterRouteImport } from './pages/(guest)/register'
19
+ import { Route as guestLoginRouteImport } from './pages/(guest)/login'
20
+
21
+ const publicRouteRoute = publicRouteRouteImport.update({
22
+ id: '/(public)',
23
+ getParentRoute: () => rootRouteImport,
24
+ } as any)
25
+ const privateRouteRoute = privateRouteRouteImport.update({
26
+ id: '/(private)',
27
+ getParentRoute: () => rootRouteImport,
28
+ } as any)
29
+ const guestRouteRoute = guestRouteRouteImport.update({
30
+ id: '/(guest)',
31
+ getParentRoute: () => rootRouteImport,
32
+ } as any)
33
+ const privateIndexRoute = privateIndexRouteImport.update({
34
+ id: '/',
35
+ path: '/',
36
+ getParentRoute: () => privateRouteRoute,
37
+ } as any)
38
+ const publicAuthRoute = publicAuthRouteImport.update({
39
+ id: '/auth',
40
+ path: '/auth',
41
+ getParentRoute: () => publicRouteRoute,
42
+ } as any)
43
+ const privateProfileRoute = privateProfileRouteImport.update({
44
+ id: '/profile',
45
+ path: '/profile',
46
+ getParentRoute: () => privateRouteRoute,
47
+ } as any)
48
+ const guestRegisterRoute = guestRegisterRouteImport.update({
49
+ id: '/register',
50
+ path: '/register',
51
+ getParentRoute: () => guestRouteRoute,
52
+ } as any)
53
+ const guestLoginRoute = guestLoginRouteImport.update({
54
+ id: '/login',
55
+ path: '/login',
56
+ getParentRoute: () => guestRouteRoute,
57
+ } as any)
58
+
59
+ export interface FileRoutesByFullPath {
60
+ '/login': typeof guestLoginRoute
61
+ '/register': typeof guestRegisterRoute
62
+ '/profile': typeof privateProfileRoute
63
+ '/auth': typeof publicAuthRoute
64
+ '/': typeof privateIndexRoute
65
+ }
66
+ export interface FileRoutesByTo {
67
+ '/login': typeof guestLoginRoute
68
+ '/register': typeof guestRegisterRoute
69
+ '/profile': typeof privateProfileRoute
70
+ '/auth': typeof publicAuthRoute
71
+ '/': typeof privateIndexRoute
72
+ }
73
+ export interface FileRoutesById {
74
+ __root__: typeof rootRouteImport
75
+ '/(guest)': typeof guestRouteRouteWithChildren
76
+ '/(private)': typeof privateRouteRouteWithChildren
77
+ '/(public)': typeof publicRouteRouteWithChildren
78
+ '/(guest)/login': typeof guestLoginRoute
79
+ '/(guest)/register': typeof guestRegisterRoute
80
+ '/(private)/profile': typeof privateProfileRoute
81
+ '/(public)/auth': typeof publicAuthRoute
82
+ '/(private)/': typeof privateIndexRoute
83
+ }
84
+ export interface FileRouteTypes {
85
+ fileRoutesByFullPath: FileRoutesByFullPath
86
+ fullPaths:
87
+ | '/login'
88
+ | '/register'
89
+ | '/profile'
90
+ | '/auth'
91
+ | '/'
92
+ fileRoutesByTo: FileRoutesByTo
93
+ to:
94
+ | '/login'
95
+ | '/register'
96
+ | '/profile'
97
+ | '/auth'
98
+ | '/'
99
+ id:
100
+ | '__root__'
101
+ | '/(guest)'
102
+ | '/(private)'
103
+ | '/(public)'
104
+ | '/(guest)/login'
105
+ | '/(guest)/register'
106
+ | '/(private)/profile'
107
+ | '/(public)/auth'
108
+ | '/(private)/'
109
+ fileRoutesById: FileRoutesById
110
+ }
111
+ export interface RootRouteChildren {
112
+ guestRouteRoute: typeof guestRouteRouteWithChildren
113
+ privateRouteRoute: typeof privateRouteRouteWithChildren
114
+ publicRouteRoute: typeof publicRouteRouteWithChildren
115
+ }
116
+
117
+ declare module '@tanstack/react-router' {
118
+ interface FileRoutesByPath {
119
+ '/(public)': {
120
+ id: '/(public)'
121
+ path: ''
122
+ fullPath: ''
123
+ preLoaderRoute: typeof publicRouteRouteImport
124
+ parentRoute: typeof rootRouteImport
125
+ }
126
+ '/(private)': {
127
+ id: '/(private)'
128
+ path: ''
129
+ fullPath: ''
130
+ preLoaderRoute: typeof privateRouteRouteImport
131
+ parentRoute: typeof rootRouteImport
132
+ }
133
+ '/(guest)': {
134
+ id: '/(guest)'
135
+ path: ''
136
+ fullPath: ''
137
+ preLoaderRoute: typeof guestRouteRouteImport
138
+ parentRoute: typeof rootRouteImport
139
+ }
140
+ '/(private)/': {
141
+ id: '/(private)/'
142
+ path: '/'
143
+ fullPath: '/'
144
+ preLoaderRoute: typeof privateIndexRouteImport
145
+ parentRoute: typeof privateRouteRoute
146
+ }
147
+ '/(public)/auth': {
148
+ id: '/(public)/auth'
149
+ path: '/auth'
150
+ fullPath: '/auth'
151
+ preLoaderRoute: typeof publicAuthRouteImport
152
+ parentRoute: typeof publicRouteRoute
153
+ }
154
+ '/(private)/profile': {
155
+ id: '/(private)/profile'
156
+ path: '/profile'
157
+ fullPath: '/profile'
158
+ preLoaderRoute: typeof privateProfileRouteImport
159
+ parentRoute: typeof privateRouteRoute
160
+ }
161
+ '/(guest)/register': {
162
+ id: '/(guest)/register'
163
+ path: '/register'
164
+ fullPath: '/register'
165
+ preLoaderRoute: typeof guestRegisterRouteImport
166
+ parentRoute: typeof guestRouteRoute
167
+ }
168
+ '/(guest)/login': {
169
+ id: '/(guest)/login'
170
+ path: '/login'
171
+ fullPath: '/login'
172
+ preLoaderRoute: typeof guestLoginRouteImport
173
+ parentRoute: typeof guestRouteRoute
174
+ }
175
+ }
176
+ }
177
+
178
+ interface guestRouteRouteChildren {
179
+ guestLoginRoute: typeof guestLoginRoute
180
+ guestRegisterRoute: typeof guestRegisterRoute
181
+ }
182
+
183
+ const guestRouteRouteChildren: guestRouteRouteChildren = {
184
+ guestLoginRoute: guestLoginRoute,
185
+ guestRegisterRoute: guestRegisterRoute,
186
+ }
187
+
188
+ const guestRouteRouteWithChildren = guestRouteRoute._addFileChildren(
189
+ guestRouteRouteChildren,
190
+ )
191
+
192
+ interface privateRouteRouteChildren {
193
+ privateProfileRoute: typeof privateProfileRoute
194
+ privateIndexRoute: typeof privateIndexRoute
195
+ }
196
+
197
+ const privateRouteRouteChildren: privateRouteRouteChildren = {
198
+ privateProfileRoute: privateProfileRoute,
199
+ privateIndexRoute: privateIndexRoute,
200
+ }
201
+
202
+ const privateRouteRouteWithChildren = privateRouteRoute._addFileChildren(
203
+ privateRouteRouteChildren,
204
+ )
205
+
206
+ interface publicRouteRouteChildren {
207
+ publicAuthRoute: typeof publicAuthRoute
208
+ }
209
+
210
+ const publicRouteRouteChildren: publicRouteRouteChildren = {
211
+ publicAuthRoute: publicAuthRoute,
212
+ }
213
+
214
+ const publicRouteRouteWithChildren = publicRouteRoute._addFileChildren(
215
+ publicRouteRouteChildren,
216
+ )
217
+
218
+ const rootRouteChildren: RootRouteChildren = {
219
+ guestRouteRoute: guestRouteRouteWithChildren,
220
+ privateRouteRoute: privateRouteRouteWithChildren,
221
+ publicRouteRoute: publicRouteRouteWithChildren,
222
+ }
223
+ export const routeTree = rootRouteImport
224
+ ._addFileChildren(rootRouteChildren)
225
+ ._addFileTypes<FileRouteTypes>()