robodev 0.12.0 → 0.14.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 (157) hide show
  1. package/package.json +1 -1
  2. package/src/emit-starter.test.ts +14 -0
  3. package/src/index.ts +54 -45
  4. package/src/parse-create-args.test.ts +26 -0
  5. package/src/parse-create-args.ts +63 -0
  6. package/templates/auth-chat/.config/local.spa.template.yml +27 -0
  7. package/templates/auth-chat/.oxlint-base.json +29 -0
  8. package/templates/auth-chat/.oxlintrc.json +78 -0
  9. package/templates/auth-chat/.rulesync/rules/frontend-api-boundary.md +36 -0
  10. package/templates/auth-chat/.rulesync/rules/frontend-component-structure.md +38 -0
  11. package/templates/auth-chat/.rulesync/rules/frontend-forms.md +32 -0
  12. package/templates/auth-chat/.rulesync/rules/frontend-notifications.md +24 -0
  13. package/templates/auth-chat/.rulesync/rules/frontend-povio-components.md +42 -0
  14. package/templates/auth-chat/.rulesync/rules/frontend-povio-ui.md +45 -0
  15. package/templates/auth-chat/.rulesync/rules/frontend-query-autocomplete.md +29 -0
  16. package/templates/auth-chat/.rulesync/rules/frontend-tables-and-lists.md +34 -0
  17. package/templates/auth-chat/.rulesync/rules/frontend-translations.md +26 -0
  18. package/templates/auth-chat/.rulesync/rules/project-overview.md +32 -0
  19. package/templates/auth-chat/.rulesync/rules/robodev-api.md +30 -0
  20. package/templates/auth-chat/.rulesync/rules/robodev-auth.md +40 -0
  21. package/templates/auth-chat/.rulesync/rules/robodev-database.md +22 -0
  22. package/templates/auth-chat/.rulesync/rules/role-based-app-structure.md +36 -0
  23. package/templates/auth-chat/.rulesync/skills/media-feature/SKILL.md +21 -0
  24. package/templates/auth-chat/.rulesync/skills/povio-ui-styling/SKILL.md +208 -0
  25. package/templates/auth-chat/.rulesync/skills/povio-ui-styling/agents/openai.yaml +4 -0
  26. package/templates/auth-chat/.rulesync/skills/robodev-api-route/SKILL.md +13 -0
  27. package/templates/auth-chat/.rulesync/skills/robodev-table/SKILL.md +12 -0
  28. package/templates/auth-chat/README.md +26 -7
  29. package/templates/auth-chat/api/invite.ts +43 -0
  30. package/templates/auth-chat/apps/fe/index.html +24 -0
  31. package/templates/auth-chat/apps/fe/openapi-codegen.config.ts +37 -0
  32. package/templates/auth-chat/apps/fe/package.json +82 -0
  33. package/templates/auth-chat/apps/fe/public/apple-touch-icon.png +0 -0
  34. package/templates/auth-chat/apps/fe/public/favicon-96x96.png +0 -0
  35. package/templates/auth-chat/apps/fe/public/favicon.ico +0 -0
  36. package/templates/auth-chat/apps/fe/public/favicon.svg +3 -0
  37. package/templates/auth-chat/apps/fe/public/site.webmanifest +21 -0
  38. package/templates/auth-chat/apps/fe/public/web-app-manifest-192x192.png +0 -0
  39. package/templates/auth-chat/apps/fe/public/web-app-manifest-512x512.png +0 -0
  40. package/templates/auth-chat/apps/fe/src/assets/fonts/GeneralSans-Bold.otf +0 -0
  41. package/templates/auth-chat/apps/fe/src/assets/fonts/GeneralSans-Medium.otf +0 -0
  42. package/templates/auth-chat/apps/fe/src/assets/fonts/GeneralSans-Regular.otf +0 -0
  43. package/templates/auth-chat/apps/fe/src/assets/fonts/GeneralSans-Semibold.otf +0 -0
  44. package/templates/auth-chat/apps/fe/src/assets/locales/en/translation.json +202 -0
  45. package/templates/auth-chat/apps/fe/src/assets/locales/sl/translation.json +202 -0
  46. package/templates/auth-chat/apps/fe/src/clients/app-rest-client.ts +15 -0
  47. package/templates/auth-chat/apps/fe/src/clients/auth-token-store.ts +101 -0
  48. package/templates/auth-chat/apps/fe/src/clients/rest/app-error-handler.ts +31 -0
  49. package/templates/auth-chat/apps/fe/src/clients/rest/interceptors/authorization-header.interceptor.ts +15 -0
  50. package/templates/auth-chat/apps/fe/src/clients/rest/interceptors/refresh-token.interceptor.ts +37 -0
  51. package/templates/auth-chat/apps/fe/src/clients/rest/interceptors/response.interceptor.ts +17 -0
  52. package/templates/auth-chat/apps/fe/src/components/404.tsx +18 -0
  53. package/templates/auth-chat/apps/fe/src/components/features/auth/AuthBrandPanel.tsx +60 -0
  54. package/templates/auth-chat/apps/fe/src/components/features/auth/AuthLayout.tsx +23 -0
  55. package/templates/auth-chat/apps/fe/src/components/features/auth/LoginPage.tsx +111 -0
  56. package/templates/auth-chat/apps/fe/src/components/features/auth/RegisterPage.tsx +143 -0
  57. package/templates/auth-chat/apps/fe/src/components/features/chat/ChatPage.tsx +105 -0
  58. package/templates/auth-chat/apps/fe/src/components/features/chat/InviteForm.tsx +84 -0
  59. package/templates/auth-chat/apps/fe/src/components/features/profile/ProfilePage.tsx +96 -0
  60. package/templates/auth-chat/apps/fe/src/components/googleAnalytics/GoogleAnalytics.tsx +35 -0
  61. package/templates/auth-chat/apps/fe/src/components/layout/AppLayout.tsx +19 -0
  62. package/templates/auth-chat/apps/fe/src/components/layout/app-header/AppHeader.tsx +91 -0
  63. package/templates/auth-chat/apps/fe/src/components/layout/app-header/MobileNavigation.tsx +51 -0
  64. package/templates/auth-chat/apps/fe/src/components/layout/app-header/NavLink.tsx +29 -0
  65. package/templates/auth-chat/apps/fe/src/components/shared/branding/BrandLogo.tsx +13 -0
  66. package/templates/auth-chat/apps/fe/src/components/shared/error/ErrorFallback.tsx +43 -0
  67. package/templates/auth-chat/apps/fe/src/components/shared/error/ErrorText.tsx +20 -0
  68. package/templates/auth-chat/apps/fe/src/components/shared/error/NotFound.tsx +36 -0
  69. package/templates/auth-chat/apps/fe/src/components/shared/forms/RequiredLabel.tsx +21 -0
  70. package/templates/auth-chat/apps/fe/src/components/shared/forms/RowInputWrapper.tsx +50 -0
  71. package/templates/auth-chat/apps/fe/src/components/shared/head/AppHead.tsx +32 -0
  72. package/templates/auth-chat/apps/fe/src/components/shared/head/DefaultAppHead.tsx +34 -0
  73. package/templates/auth-chat/apps/fe/src/components/shared/layout/LoadingState.tsx +9 -0
  74. package/templates/auth-chat/apps/fe/src/components/shared/layout/ThinPageWrapper.tsx +5 -0
  75. package/templates/auth-chat/apps/fe/src/components/shared/page/PageHeader.tsx +69 -0
  76. package/templates/auth-chat/apps/fe/src/components/shared/ui/Card.tsx +60 -0
  77. package/templates/auth-chat/apps/fe/src/components/shared/ui/GoogleLoginButton.tsx +19 -0
  78. package/templates/auth-chat/apps/fe/src/components/shared/ui/RequiredLabel.tsx +22 -0
  79. package/templates/auth-chat/apps/fe/src/components/shared/ui/TableActions.tsx +22 -0
  80. package/templates/auth-chat/apps/fe/src/config/app.config.ts +35 -0
  81. package/templates/auth-chat/apps/fe/src/config/i18n.ts +43 -0
  82. package/templates/auth-chat/apps/fe/src/config/inits/a11y.ts +14 -0
  83. package/templates/auth-chat/apps/fe/src/config/inits/logger.ts +7 -0
  84. package/templates/auth-chat/apps/fe/src/config/inits/sentry.ts +21 -0
  85. package/templates/auth-chat/apps/fe/src/config/jwt.config.ts +2 -0
  86. package/templates/auth-chat/apps/fe/src/config/query.config.ts +20 -0
  87. package/templates/auth-chat/apps/fe/src/hooks/useAuth.ts +5 -0
  88. package/templates/auth-chat/apps/fe/src/main.tsx +52 -0
  89. package/templates/auth-chat/apps/fe/src/pages/(guest)/login.tsx +23 -0
  90. package/templates/auth-chat/apps/fe/src/pages/(guest)/register.tsx +23 -0
  91. package/templates/auth-chat/apps/fe/src/pages/(guest)/route.tsx +18 -0
  92. package/templates/auth-chat/apps/fe/src/pages/(private)/index.tsx +23 -0
  93. package/templates/auth-chat/apps/fe/src/pages/(private)/profile.tsx +23 -0
  94. package/templates/auth-chat/apps/fe/src/pages/(private)/route.tsx +18 -0
  95. package/templates/auth-chat/apps/fe/src/pages/(public)/auth.tsx +37 -0
  96. package/templates/auth-chat/apps/fe/src/pages/(public)/route.tsx +9 -0
  97. package/templates/auth-chat/apps/fe/src/pages/__root.tsx +164 -0
  98. package/templates/auth-chat/apps/fe/src/providers/AppErrorBoundary.tsx +10 -0
  99. package/templates/auth-chat/apps/fe/src/providers/OpenApiRuntimeProvider.tsx +31 -0
  100. package/templates/auth-chat/apps/fe/src/providers/index.tsx +44 -0
  101. package/templates/auth-chat/apps/fe/src/providers/jwt.provider.tsx +95 -0
  102. package/templates/auth-chat/apps/fe/src/routeTree.gen.ts +225 -0
  103. package/templates/auth-chat/apps/fe/src/styles/base.css +103 -0
  104. package/templates/auth-chat/apps/fe/src/styles/fonts/fonts.tsx +10 -0
  105. package/templates/auth-chat/apps/fe/src/styles/fonts/general-sans.css +31 -0
  106. package/templates/auth-chat/apps/fe/src/styles/globals.css +28 -0
  107. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/button.override.ts +536 -0
  108. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/checkbox.override.ts +71 -0
  109. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/input.override.ts +252 -0
  110. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/label.override.ts +91 -0
  111. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/modal.override.ts +57 -0
  112. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/radio.override.ts +46 -0
  113. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/table.override.ts +104 -0
  114. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/tag.override.ts +66 -0
  115. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/typography.override.ts +115 -0
  116. package/templates/auth-chat/apps/fe/src/styles/overrides/outline.clsx.ts +10 -0
  117. package/templates/auth-chat/apps/fe/src/styles/overrides/uiOverrides.override.ts +60 -0
  118. package/templates/auth-chat/apps/fe/src/styles/theme.css +2177 -0
  119. package/templates/auth-chat/apps/fe/src/types/i18next.d.ts +12 -0
  120. package/templates/auth-chat/apps/fe/src/types/table.d.ts +17 -0
  121. package/templates/auth-chat/apps/fe/src/types/ui.d.ts +26 -0
  122. package/templates/auth-chat/apps/fe/src/types/vite-env.d.ts +20 -0
  123. package/templates/auth-chat/apps/fe/src/utils/date.utils.ts +17 -0
  124. package/templates/auth-chat/apps/fe/src/utils/number.utils.ts +12 -0
  125. package/templates/auth-chat/apps/fe/src/utils/string.utils.ts +5 -0
  126. package/templates/auth-chat/apps/fe/src/vite-env.d.ts +1 -0
  127. package/templates/auth-chat/apps/fe/tsconfig.app.json +32 -0
  128. package/templates/auth-chat/apps/fe/tsconfig.json +9 -0
  129. package/templates/auth-chat/apps/fe/tsconfig.node.json +31 -0
  130. package/templates/auth-chat/apps/fe/vite.config.ts +94 -0
  131. package/templates/auth-chat/openapi.json +1016 -0
  132. package/templates/auth-chat/oxfmt.config.js +23 -0
  133. package/templates/auth-chat/package.json +6 -13
  134. package/templates/auth-chat/rulesync.jsonc +18 -0
  135. package/templates/auth-chat/tsconfig.json +2 -4
  136. package/templates/backend/README.md +2 -2
  137. package/templates/backend/api/_lib.ts +359 -0
  138. package/templates/backend/api/aliens/labels.ts +19 -0
  139. package/templates/backend/api/files/presigned-url.ts +48 -0
  140. package/templates/backend/api/files/upload.ts +29 -0
  141. package/templates/backend/api/planets/[id]/like.ts +36 -0
  142. package/templates/backend/api/planets/[id].ts +56 -13
  143. package/templates/backend/api/planets/paginate.ts +36 -0
  144. package/templates/backend/api/planets.ts +52 -44
  145. package/templates/backend/database.ts +33 -13
  146. package/templates/backend/package.json +1 -1
  147. package/templates/catalog.json +2 -2
  148. package/templates/empty/package.json +1 -1
  149. package/templates/space/package.json +1 -1
  150. package/templates/auth-chat/index.html +0 -201
  151. package/templates/auth-chat/src/main.tsx +0 -282
  152. package/templates/auth-chat/vite.config.ts +0 -7
  153. package/templates/backend/api/health.ts +0 -7
  154. package/templates/backend/api/launch.ts +0 -29
  155. package/templates/backend/api/me.ts +0 -14
  156. package/templates/backend/api/motto.ts +0 -9
  157. package/templates/backend/api/rockets.ts +0 -43
@@ -0,0 +1,69 @@
1
+ import { InlineIconButton, Typography } from "@povio/ui";
2
+ import { type LinkProps, useRouter } from "@tanstack/react-router";
3
+ import { ChevronLeft } from "lucide-react";
4
+ import type { ReactNode } from "react";
5
+
6
+ type PageHeaderBackProps =
7
+ | {
8
+ backLink: LinkProps;
9
+ onBack?: never;
10
+ }
11
+ | {
12
+ backLink?: never;
13
+ onBack: () => void;
14
+ };
15
+
16
+ interface PageHeaderProps {
17
+ title: ReactNode;
18
+ actions?: ReactNode;
19
+ enableBack?: boolean;
20
+ backProps?: PageHeaderBackProps;
21
+ }
22
+
23
+ export function PageHeader({ title, actions, enableBack = false, backProps }: PageHeaderProps) {
24
+ const router = useRouter();
25
+
26
+ const handleBack = () => {
27
+ if (backProps?.onBack) {
28
+ backProps.onBack();
29
+ return;
30
+ }
31
+
32
+ if (window.history.length > 1) {
33
+ router.history.back();
34
+ return;
35
+ }
36
+
37
+ if (backProps?.backLink) {
38
+ router.navigate(backProps.backLink);
39
+ }
40
+ };
41
+
42
+ return (
43
+ <div className="flex flex-col gap-4">
44
+ <div className="flex items-center justify-between gap-4">
45
+ <div className="flex min-w-0 items-center gap-2">
46
+ {enableBack && (
47
+ <InlineIconButton
48
+ label="Back"
49
+ icon={ChevronLeft}
50
+ color="secondary"
51
+ onPress={handleBack}
52
+ />
53
+ )}
54
+
55
+ <Typography
56
+ as="h1"
57
+ size="title-4"
58
+ variant="prominent-1"
59
+ className="min-w-0 text-text-default-1"
60
+ >
61
+ {title}
62
+ </Typography>
63
+ </div>
64
+
65
+ {actions && <div className="flex items-center gap-2">{actions}</div>}
66
+ </div>
67
+ </div>
68
+ );
69
+ }
@@ -0,0 +1,60 @@
1
+ import { Typography, type TypographyProps } from "@povio/ui";
2
+ import { type VariantProps, cva } from "class-variance-authority";
3
+
4
+ const cardVariants = cva(
5
+ "flex flex-1 flex-col gap-5 rounded-sm border-elevation-outline-default-1 border-b bg-elevation-fill-default-1 shadow-1",
6
+ {
7
+ variants: {
8
+ padding: {
9
+ sm: "p-3",
10
+ md: "p-4",
11
+ lg: "p-5",
12
+ },
13
+ },
14
+ defaultVariants: {
15
+ padding: "md",
16
+ },
17
+ },
18
+ );
19
+
20
+ interface CardVariantProps extends VariantProps<typeof cardVariants>, React.HTMLAttributes<HTMLDivElement> {}
21
+
22
+ export interface CardProps extends CardVariantProps {
23
+ children: React.ReactNode;
24
+ title?: string;
25
+ titleSize?: TypographyProps["size"];
26
+ titleVariant?: TypographyProps["variant"];
27
+ isDisabled?: boolean;
28
+ }
29
+
30
+ export function Card({
31
+ children,
32
+ className,
33
+ padding,
34
+ title,
35
+ titleSize = "title-5",
36
+ titleVariant = "prominent-1",
37
+ isDisabled = false,
38
+ ...props
39
+ }: CardProps) {
40
+ return (
41
+ <div
42
+ className={cardVariants({
43
+ padding,
44
+ className: [className, isDisabled && "pointer-events-none opacity-50"],
45
+ })}
46
+ >
47
+ {title && (
48
+ <Typography
49
+ size={titleSize}
50
+ variant={titleVariant}
51
+ {...props}
52
+ >
53
+ {title}
54
+ </Typography>
55
+ )}
56
+
57
+ {children}
58
+ </div>
59
+ );
60
+ }
@@ -0,0 +1,19 @@
1
+ import { Button } from "@povio/ui";
2
+
3
+ import { AppConfig } from "@/config/app.config";
4
+
5
+ export function GoogleLoginButton() {
6
+ const handleGoogleLogin = () => {
7
+ window.location.href = AppConfig.auth.googleRedirectUrl;
8
+ };
9
+
10
+ return (
11
+ <Button
12
+ size="s"
13
+ width="hug"
14
+ onPress={handleGoogleLogin}
15
+ >
16
+ Continue with Google
17
+ </Button>
18
+ );
19
+ }
@@ -0,0 +1,22 @@
1
+ import { Typography, type TypographyProps } from "@povio/ui";
2
+ import type { ComponentProps } from "react";
3
+
4
+ interface RequiredLabelProps extends Omit<TypographyProps, "variant" | "size"> {
5
+ variant?: ComponentProps<typeof Typography>["variant"];
6
+ size?: ComponentProps<typeof Typography>["size"];
7
+ }
8
+
9
+ export function RequiredLabel({ variant = "prominent-1", size = "label-1", children, ...props }: RequiredLabelProps) {
10
+ return (
11
+ <div className="flex items-center">
12
+ <Typography
13
+ variant={variant}
14
+ size={size}
15
+ {...props}
16
+ >
17
+ {children}
18
+ <span className="text-text-error-3">*</span>
19
+ </Typography>
20
+ </div>
21
+ );
22
+ }
@@ -0,0 +1,22 @@
1
+ import clsx from "clsx";
2
+ import type { HTMLAttributes } from "react";
3
+
4
+ export function TableActions({ children, className, onClick, onKeyDown, ...props }: HTMLAttributes<HTMLDivElement>) {
5
+ return (
6
+ <div
7
+ {...props}
8
+ role="presentation"
9
+ className={clsx("flex items-center justify-end gap-1", className)}
10
+ onClick={(event) => {
11
+ event.stopPropagation();
12
+ onClick?.(event);
13
+ }}
14
+ onKeyDown={(event) => {
15
+ event.stopPropagation();
16
+ onKeyDown?.(event);
17
+ }}
18
+ >
19
+ {children}
20
+ </div>
21
+ );
22
+ }
@@ -0,0 +1,35 @@
1
+ const authCustomJWT = {
2
+ enableMagicLink: import.meta.env.VITE_PUBLIC_AUTH_CUSTOM_JWT_ENABLE_MAGIC_LINK === "true",
3
+ };
4
+
5
+ const apiUrl = import.meta.env.VITE_PUBLIC_API_URL ?? "http://localhost:4000";
6
+ const apiOrigin = apiUrl.replace(/\/+$/, "") || "/";
7
+ const apiMode = import.meta.env.VITE_PUBLIC_API_MODE ?? "real";
8
+ export const AppConfig = {
9
+ isProduction: import.meta.env.NODE_ENV === "production",
10
+ enableWebVitals: import.meta.env.VITE_PUBLIC_ENABLE_WEB_VITALS === "true",
11
+ release: import.meta.env.VITE_PUBLIC_RELEASE,
12
+ stage: import.meta.env.VITE_PUBLIC_STAGE,
13
+ api: {
14
+ url: apiOrigin,
15
+ mode: apiMode,
16
+ },
17
+ log: {
18
+ level: import.meta.env.VITE_PUBLIC_LOG_LEVEL ?? "error",
19
+ },
20
+ sentry: {
21
+ enabled: import.meta.env.VITE_PUBLIC_SENTRY_DSN ? true : false,
22
+ dsn: import.meta.env.VITE_PUBLIC_SENTRY_DSN,
23
+ environment: import.meta.env.VITE_PUBLIC_SENTRY_ENVIRONMENT,
24
+ tracesSampleRate: parseFloat(import.meta.env.VITE_PUBLIC_SENTRY_TRACES_SAMPLE_RATE ?? "0"),
25
+ replaysSessionSampleRate: parseFloat(import.meta.env.VITE_PUBLIC_SENTRY_REPLAYS_SESSION_SAMPLE_RATE ?? "0"),
26
+ replaysOnErrorSampleRate: parseFloat(import.meta.env.VITE_PUBLIC_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE ?? "0"),
27
+ },
28
+ auth: {
29
+ customJWT: authCustomJWT,
30
+ googleRedirectUrl: `${apiOrigin}/api/user/auth/google`,
31
+ },
32
+ googleAnalytics: {
33
+ measurementId: import.meta.env.VITE_PUBLIC_GOOGLE_ANALYTICS_MEASUREMENT_ID,
34
+ },
35
+ };
@@ -0,0 +1,43 @@
1
+ import { resources as uiResources } from "@povio/ui";
2
+ import {
3
+ configureTranslations as configureOpenApiTranslations,
4
+ resources as openApiResources,
5
+ } from "@povio/openapi-codegen-cli";
6
+ import i18n from "i18next";
7
+ import { initReactI18next } from "react-i18next";
8
+
9
+ import translationEN from "@/assets/locales/en/translation.json";
10
+ import translationSL from "@/assets/locales/sl/translation.json";
11
+
12
+ export const defaultLanguage = "en";
13
+ export const defaultNS = "main";
14
+ export const resources = {
15
+ en: {
16
+ ...openApiResources.en,
17
+ ...uiResources.en,
18
+ [defaultNS]: translationEN,
19
+ },
20
+ sl: {
21
+ ...openApiResources.sl,
22
+ ...uiResources.sl,
23
+ [defaultNS]: translationSL,
24
+ },
25
+ } as const;
26
+
27
+ i18n.use(initReactI18next).init({
28
+ compatibilityJSON: "v4",
29
+ fallbackLng: defaultLanguage,
30
+ resources,
31
+ ns: Object.keys(resources.en),
32
+ defaultNS,
33
+ lng: defaultLanguage,
34
+ returnNull: false,
35
+ interpolation: {
36
+ escapeValue: false,
37
+ },
38
+ react: {
39
+ useSuspense: false,
40
+ },
41
+ });
42
+
43
+ configureOpenApiTranslations(() => i18n.t);
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+
3
+ import { AppConfig } from "@/config/app.config";
4
+
5
+ export async function initA11y() {
6
+ if (typeof window !== "undefined" && !AppConfig.isProduction) {
7
+ const ReactDOM = await import("react-dom");
8
+
9
+ // oxlint-disable-next-line import/no-extraneous-dependencies
10
+ const axe = await import("@axe-core/react");
11
+
12
+ axe.default(React, ReactDOM, 1000);
13
+ }
14
+ }
@@ -0,0 +1,7 @@
1
+ import log from "loglevel";
2
+
3
+ import { AppConfig } from "@/config/app.config";
4
+
5
+ export function initLogger() {
6
+ log.setLevel(AppConfig.log.level as log.LogLevelDesc);
7
+ }
@@ -0,0 +1,21 @@
1
+ // oxlint-disable-next-line import/no-namespace
2
+ import * as Sentry from "@sentry/react";
3
+
4
+ import { AppConfig } from "@/config/app.config";
5
+
6
+ export function initSentry() {
7
+ Sentry.init({
8
+ enabled: AppConfig.sentry.enabled,
9
+ dsn: AppConfig.sentry.dsn,
10
+ release: AppConfig.release,
11
+ integrations: [
12
+ ...(AppConfig.sentry.tracesSampleRate > 0 ? [] : []),
13
+ ...(AppConfig.sentry.replaysOnErrorSampleRate > 0 ? [Sentry.replayIntegration()] : []),
14
+ ],
15
+
16
+ tracesSampleRate: AppConfig.sentry.tracesSampleRate,
17
+
18
+ replaysSessionSampleRate: AppConfig.sentry.replaysSessionSampleRate,
19
+ replaysOnErrorSampleRate: AppConfig.sentry.replaysOnErrorSampleRate,
20
+ });
21
+ }
@@ -0,0 +1,2 @@
1
+ export const ACCESS_TOKEN_KEY = "auth-custom-jwt:access-token";
2
+ export const REFRESH_TOKEN_KEY = "auth-custom-jwt:refresh-token";
@@ -0,0 +1,20 @@
1
+ import type { ApplicationException, GeneralErrorCodes } from "@povio/openapi-codegen-cli";
2
+ import { QueryClient, type QueryClientConfig } from "@tanstack/react-query";
3
+
4
+ export const QueryConfig: QueryClientConfig = {
5
+ defaultOptions: {
6
+ queries: {
7
+ retry: false,
8
+ refetchOnWindowFocus: false,
9
+ staleTime: 2 * 60 * 1000,
10
+ },
11
+ },
12
+ };
13
+
14
+ export const queryClient = new QueryClient(QueryConfig);
15
+
16
+ declare module "@tanstack/react-query" {
17
+ interface Register {
18
+ defaultError: ApplicationException<GeneralErrorCodes> | null;
19
+ }
20
+ }
@@ -0,0 +1,5 @@
1
+ import { AuthContext } from "@povio/openapi-codegen-cli";
2
+
3
+ import type { UserModels } from "@/openapi/user/user.models";
4
+
5
+ export const useAuth = () => AuthContext.useAuth<UserModels.UserMeResponse>();
@@ -0,0 +1,52 @@
1
+ import { AbilityContext } from "@povio/openapi-codegen-cli/acl";
2
+ import { LinkContext, type LinkNavigationProps } from "@povio/ui";
3
+ import { QueryClientProvider } from "@tanstack/react-query";
4
+ import { RouterProvider, createLink, createRouter } from "@tanstack/react-router";
5
+ import { type ComponentType, StrictMode } from "react";
6
+ import { Link as AriaLink } from "react-aria-components";
7
+ import { createRoot } from "react-dom/client";
8
+
9
+ import { queryClient } from "@/config/query.config";
10
+ import Providers from "@/providers";
11
+ import { JWTProvider } from "@/providers/jwt.provider";
12
+ import "@/styles/globals.css";
13
+
14
+ import { routeTree } from "./routeTree.gen";
15
+
16
+ const LinkComponent = createLink(AriaLink) as unknown as ComponentType<LinkNavigationProps>;
17
+
18
+ const router = createRouter({
19
+ routeTree,
20
+ defaultPreload: "intent",
21
+ scrollRestoration: true,
22
+ defaultStructuralSharing: true,
23
+ defaultPreloadStaleTime: 0,
24
+ // basepath: import.meta.env.BASE_URL, // Uncomment in monorepo
25
+ });
26
+
27
+ declare module "@tanstack/react-router" {
28
+ interface Register {
29
+ router: typeof router;
30
+ }
31
+ }
32
+
33
+ const rootElement = document.getElementById("app");
34
+
35
+ if (rootElement && !rootElement.innerHTML) {
36
+ const root = createRoot(rootElement);
37
+
38
+ root.render(
39
+ <StrictMode>
40
+ <Providers
41
+ providers={[
42
+ { provider: LinkContext.LinkContextProvider, props: { LinkComponent } },
43
+ { provider: QueryClientProvider, props: { client: queryClient } },
44
+ { provider: JWTProvider },
45
+ { provider: AbilityContext.Provider },
46
+ ]}
47
+ >
48
+ <RouterProvider router={router} />
49
+ </Providers>
50
+ </StrictMode>,
51
+ );
52
+ }
@@ -0,0 +1,23 @@
1
+ import { createFileRoute } from "@tanstack/react-router";
2
+ import { useTranslation } from "react-i18next";
3
+
4
+ import { LoginPage } from "@/components/features/auth/LoginPage";
5
+ import { AppHead } from "@/components/shared/head/AppHead";
6
+
7
+ function PageComponent() {
8
+ const { t } = useTranslation();
9
+
10
+ return (
11
+ <>
12
+ <AppHead
13
+ title={t(($) => $.auth.login.head.title)}
14
+ description={t(($) => $.auth.login.head.description)}
15
+ />
16
+ <LoginPage />
17
+ </>
18
+ );
19
+ }
20
+
21
+ export const Route = createFileRoute("/(guest)/login")({
22
+ component: PageComponent,
23
+ });
@@ -0,0 +1,23 @@
1
+ import { createFileRoute } from "@tanstack/react-router";
2
+ import { useTranslation } from "react-i18next";
3
+
4
+ import { RegisterPage } from "@/components/features/auth/RegisterPage";
5
+ import { AppHead } from "@/components/shared/head/AppHead";
6
+
7
+ function PageComponent() {
8
+ const { t } = useTranslation();
9
+
10
+ return (
11
+ <>
12
+ <AppHead
13
+ title={t(($) => $.auth.register.head.title)}
14
+ description={t(($) => $.auth.register.head.description)}
15
+ />
16
+ <RegisterPage />
17
+ </>
18
+ );
19
+ }
20
+
21
+ export const Route = createFileRoute("/(guest)/register")({
22
+ component: PageComponent,
23
+ });
@@ -0,0 +1,18 @@
1
+ import { AuthGuard } from "@povio/openapi-codegen-cli";
2
+ import { Outlet, createFileRoute } from "@tanstack/react-router";
3
+
4
+ import { AuthLayout } from "@/components/features/auth/AuthLayout";
5
+
6
+ function GuestLayout() {
7
+ return (
8
+ <AuthGuard type="public-only">
9
+ <AuthLayout>
10
+ <Outlet />
11
+ </AuthLayout>
12
+ </AuthGuard>
13
+ );
14
+ }
15
+
16
+ export const Route = createFileRoute("/(guest)")({
17
+ component: GuestLayout,
18
+ });
@@ -0,0 +1,23 @@
1
+ import { createFileRoute } from "@tanstack/react-router";
2
+ import { useTranslation } from "react-i18next";
3
+
4
+ import { ChatPage } from "@/components/features/chat/ChatPage";
5
+ import { AppHead } from "@/components/shared/head/AppHead";
6
+
7
+ function PageComponent() {
8
+ const { t } = useTranslation();
9
+
10
+ return (
11
+ <>
12
+ <AppHead
13
+ title={t(($) => $.chat.head.title)}
14
+ description={t(($) => $.chat.head.description)}
15
+ />
16
+ <ChatPage />
17
+ </>
18
+ );
19
+ }
20
+
21
+ export const Route = createFileRoute("/(private)/")({
22
+ component: PageComponent,
23
+ });
@@ -0,0 +1,23 @@
1
+ import { createFileRoute } from "@tanstack/react-router";
2
+ import { useTranslation } from "react-i18next";
3
+
4
+ import { ProfilePage } from "@/components/features/profile/ProfilePage";
5
+ import { AppHead } from "@/components/shared/head/AppHead";
6
+
7
+ function PageComponent() {
8
+ const { t } = useTranslation();
9
+
10
+ return (
11
+ <>
12
+ <AppHead
13
+ title={t(($) => $.profile.head.title)}
14
+ description={t(($) => $.profile.head.description)}
15
+ />
16
+ <ProfilePage />
17
+ </>
18
+ );
19
+ }
20
+
21
+ export const Route = createFileRoute("/(private)/profile")({
22
+ component: PageComponent,
23
+ });
@@ -0,0 +1,18 @@
1
+ import { AuthGuard } from "@povio/openapi-codegen-cli";
2
+ import { Outlet, createFileRoute } from "@tanstack/react-router";
3
+
4
+ import { AppLayout } from "@/components/layout/AppLayout";
5
+
6
+ function PrivateLayout() {
7
+ return (
8
+ <AuthGuard type="private">
9
+ <AppLayout>
10
+ <Outlet />
11
+ </AppLayout>
12
+ </AuthGuard>
13
+ );
14
+ }
15
+
16
+ export const Route = createFileRoute("/(private)")({
17
+ component: PrivateLayout,
18
+ });
@@ -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
+ });