robodev 0.11.0 → 0.12.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.
- package/package.json +1 -1
- package/src/collect-files.test.ts +19 -0
- package/src/collect-files.ts +1 -0
- package/src/config.test.ts +26 -0
- package/src/config.ts +34 -5
- package/src/emit-starter.test.ts +54 -0
- package/src/emit-starter.ts +47 -13
- package/src/index.ts +37 -4
- package/templates/auth-chat/package.json +1 -1
- package/templates/auth-chat/src/main.tsx +1 -1
- package/templates/backend/package.json +1 -1
- package/templates/catalog.json +1 -1
- package/templates/empty/package.json +1 -1
- package/templates/space/.config/local.spa.template.yml +27 -0
- package/templates/space/.oxlint-base.json +29 -0
- package/templates/space/.oxlintrc.json +78 -0
- package/templates/space/.rulesync/rules/frontend-api-boundary.md +36 -0
- package/templates/space/.rulesync/rules/frontend-component-structure.md +38 -0
- package/templates/space/.rulesync/rules/frontend-forms.md +32 -0
- package/templates/space/.rulesync/rules/frontend-notifications.md +24 -0
- package/templates/space/.rulesync/rules/frontend-povio-components.md +42 -0
- package/templates/space/.rulesync/rules/frontend-povio-ui.md +45 -0
- package/templates/space/.rulesync/rules/frontend-query-autocomplete.md +29 -0
- package/templates/space/.rulesync/rules/frontend-tables-and-lists.md +34 -0
- package/templates/space/.rulesync/rules/frontend-translations.md +26 -0
- package/templates/space/.rulesync/rules/project-overview.md +32 -0
- package/templates/space/.rulesync/rules/robodev-api.md +30 -0
- package/templates/space/.rulesync/rules/robodev-auth.md +40 -0
- package/templates/space/.rulesync/rules/robodev-database.md +22 -0
- package/templates/space/.rulesync/rules/role-based-app-structure.md +36 -0
- package/templates/space/.rulesync/skills/media-feature/SKILL.md +21 -0
- package/templates/space/.rulesync/skills/povio-ui-styling/SKILL.md +208 -0
- package/templates/space/.rulesync/skills/povio-ui-styling/agents/openai.yaml +4 -0
- package/templates/space/.rulesync/skills/robodev-api-route/SKILL.md +13 -0
- package/templates/space/.rulesync/skills/robodev-table/SKILL.md +12 -0
- package/templates/space/README.md +26 -7
- package/templates/space/api/_lib.ts +359 -0
- package/templates/space/api/aliens/labels.ts +19 -0
- package/templates/space/api/files/presigned-url.ts +48 -0
- package/templates/space/api/files/upload.ts +29 -0
- package/templates/space/api/planets/[id]/like.ts +36 -0
- package/templates/space/api/planets/[id].ts +56 -13
- package/templates/space/api/planets/paginate.ts +36 -0
- package/templates/space/api/planets.ts +52 -44
- package/templates/space/apps/fe/index.html +24 -0
- package/templates/space/apps/fe/openapi-codegen.config.ts +37 -0
- package/templates/space/apps/fe/package.json +82 -0
- package/templates/space/apps/fe/public/apple-touch-icon.png +0 -0
- package/templates/space/apps/fe/public/favicon-96x96.png +0 -0
- package/templates/space/apps/fe/public/favicon.ico +0 -0
- package/templates/space/apps/fe/public/favicon.svg +3 -0
- package/templates/space/apps/fe/public/site.webmanifest +21 -0
- package/templates/space/apps/fe/public/web-app-manifest-192x192.png +0 -0
- package/templates/space/apps/fe/public/web-app-manifest-512x512.png +0 -0
- package/templates/space/apps/fe/src/assets/fonts/GeneralSans-Bold.otf +0 -0
- package/templates/space/apps/fe/src/assets/fonts/GeneralSans-Medium.otf +0 -0
- package/templates/space/apps/fe/src/assets/fonts/GeneralSans-Regular.otf +0 -0
- package/templates/space/apps/fe/src/assets/fonts/GeneralSans-Semibold.otf +0 -0
- package/templates/space/apps/fe/src/assets/locales/en/translation.json +301 -0
- package/templates/space/apps/fe/src/assets/locales/sl/translation.json +301 -0
- package/templates/space/apps/fe/src/clients/app-rest-client.ts +15 -0
- package/templates/space/apps/fe/src/clients/auth-token-store.ts +101 -0
- package/templates/space/apps/fe/src/clients/rest/app-error-handler.ts +31 -0
- package/templates/space/apps/fe/src/clients/rest/interceptors/authorization-header.interceptor.ts +15 -0
- package/templates/space/apps/fe/src/clients/rest/interceptors/refresh-token.interceptor.ts +37 -0
- package/templates/space/apps/fe/src/clients/rest/interceptors/response.interceptor.ts +17 -0
- package/templates/space/apps/fe/src/components/404.tsx +18 -0
- package/templates/space/apps/fe/src/components/features/auth/AuthBrandPanel.tsx +60 -0
- package/templates/space/apps/fe/src/components/features/auth/AuthLayout.tsx +23 -0
- package/templates/space/apps/fe/src/components/features/auth/LoginPage.tsx +111 -0
- package/templates/space/apps/fe/src/components/features/auth/RegisterPage.tsx +143 -0
- package/templates/space/apps/fe/src/components/features/home/HomePage.tsx +21 -0
- package/templates/space/apps/fe/src/components/features/planets/details/PlanetDetailsPage.tsx +139 -0
- package/templates/space/apps/fe/src/components/features/planets/details/PlanetEditPage.tsx +266 -0
- package/templates/space/apps/fe/src/components/features/planets/feed/PlanetFeedCard.tsx +131 -0
- package/templates/space/apps/fe/src/components/features/planets/feed/PlanetsFeedPage.tsx +119 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetCard.tsx +89 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetCreateModal.tsx +179 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsFilters.css +3 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsFilters.tsx +73 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsGridPage.tsx +51 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsInfiniteTablePage.tsx +79 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsPage.tsx +65 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsTablePage.tsx +72 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsTableSummary.tsx +29 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsViewTabs.tsx +79 -0
- package/templates/space/apps/fe/src/components/features/planets/list/table/PlanetsTable.tsx +30 -0
- package/templates/space/apps/fe/src/components/features/planets/list/table/PlanetsTableActions.tsx +64 -0
- package/templates/space/apps/fe/src/components/features/planets/list/table/PlanetsTableInfinite.tsx +36 -0
- package/templates/space/apps/fe/src/components/features/planets/list/table/planetsTableColumns.tsx +72 -0
- package/templates/space/apps/fe/src/components/features/planets/list/table/usePlanetTableFilters.tsx +102 -0
- package/templates/space/apps/fe/src/components/features/profile/ProfilePage.tsx +96 -0
- package/templates/space/apps/fe/src/components/googleAnalytics/GoogleAnalytics.tsx +35 -0
- package/templates/space/apps/fe/src/components/layout/AppLayout.tsx +19 -0
- package/templates/space/apps/fe/src/components/layout/app-header/AppHeader.tsx +93 -0
- package/templates/space/apps/fe/src/components/layout/app-header/MobileNavigation.tsx +53 -0
- package/templates/space/apps/fe/src/components/layout/app-header/NavLink.tsx +29 -0
- package/templates/space/apps/fe/src/components/shared/branding/BrandLogo.tsx +13 -0
- package/templates/space/apps/fe/src/components/shared/error/ErrorFallback.tsx +43 -0
- package/templates/space/apps/fe/src/components/shared/error/ErrorText.tsx +20 -0
- package/templates/space/apps/fe/src/components/shared/error/NotFound.tsx +36 -0
- package/templates/space/apps/fe/src/components/shared/forms/RequiredLabel.tsx +21 -0
- package/templates/space/apps/fe/src/components/shared/forms/RowInputWrapper.tsx +50 -0
- package/templates/space/apps/fe/src/components/shared/head/AppHead.tsx +32 -0
- package/templates/space/apps/fe/src/components/shared/head/DefaultAppHead.tsx +34 -0
- package/templates/space/apps/fe/src/components/shared/layout/LoadingState.tsx +9 -0
- package/templates/space/apps/fe/src/components/shared/layout/ThinPageWrapper.tsx +5 -0
- package/templates/space/apps/fe/src/components/shared/page/PageHeader.tsx +69 -0
- package/templates/space/apps/fe/src/components/shared/ui/Card.tsx +60 -0
- package/templates/space/apps/fe/src/components/shared/ui/GoogleLoginButton.tsx +19 -0
- package/templates/space/apps/fe/src/components/shared/ui/RequiredLabel.tsx +22 -0
- package/templates/space/apps/fe/src/components/shared/ui/TableActions.tsx +22 -0
- package/templates/space/apps/fe/src/config/app.config.ts +35 -0
- package/templates/space/apps/fe/src/config/i18n.ts +43 -0
- package/templates/space/apps/fe/src/config/inits/a11y.ts +14 -0
- package/templates/space/apps/fe/src/config/inits/logger.ts +7 -0
- package/templates/space/apps/fe/src/config/inits/sentry.ts +21 -0
- package/templates/space/apps/fe/src/config/jwt.config.ts +2 -0
- package/templates/space/apps/fe/src/config/query.config.ts +20 -0
- package/templates/space/apps/fe/src/hooks/useAuth.ts +5 -0
- package/templates/space/apps/fe/src/main.tsx +52 -0
- package/templates/space/apps/fe/src/pages/(guest)/login.tsx +23 -0
- package/templates/space/apps/fe/src/pages/(guest)/register.tsx +23 -0
- package/templates/space/apps/fe/src/pages/(guest)/route.tsx +18 -0
- package/templates/space/apps/fe/src/pages/(private)/index.tsx +23 -0
- package/templates/space/apps/fe/src/pages/(private)/planets/$id/edit.tsx +53 -0
- package/templates/space/apps/fe/src/pages/(private)/planets/$id/index.tsx +41 -0
- package/templates/space/apps/fe/src/pages/(private)/planets/index.tsx +24 -0
- package/templates/space/apps/fe/src/pages/(private)/planets-feed.tsx +23 -0
- package/templates/space/apps/fe/src/pages/(private)/profile.tsx +23 -0
- package/templates/space/apps/fe/src/pages/(private)/route.tsx +18 -0
- package/templates/space/apps/fe/src/pages/(public)/auth.tsx +37 -0
- package/templates/space/apps/fe/src/pages/(public)/route.tsx +9 -0
- package/templates/space/apps/fe/src/pages/__root.tsx +164 -0
- package/templates/space/apps/fe/src/providers/AppErrorBoundary.tsx +10 -0
- package/templates/space/apps/fe/src/providers/OpenApiRuntimeProvider.tsx +31 -0
- package/templates/space/apps/fe/src/providers/index.tsx +44 -0
- package/templates/space/apps/fe/src/providers/jwt.provider.tsx +95 -0
- package/templates/space/apps/fe/src/routeTree.gen.ts +309 -0
- package/templates/space/apps/fe/src/styles/base.css +103 -0
- package/templates/space/apps/fe/src/styles/fonts/fonts.tsx +10 -0
- package/templates/space/apps/fe/src/styles/fonts/general-sans.css +31 -0
- package/templates/space/apps/fe/src/styles/globals.css +28 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/button.override.ts +536 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/checkbox.override.ts +71 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/input.override.ts +252 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/label.override.ts +91 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/modal.override.ts +57 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/radio.override.ts +46 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/table.override.ts +104 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/tag.override.ts +66 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/typography.override.ts +115 -0
- package/templates/space/apps/fe/src/styles/overrides/outline.clsx.ts +10 -0
- package/templates/space/apps/fe/src/styles/overrides/uiOverrides.override.ts +60 -0
- package/templates/space/apps/fe/src/styles/theme.css +2177 -0
- package/templates/space/apps/fe/src/types/i18next.d.ts +12 -0
- package/templates/space/apps/fe/src/types/table.d.ts +17 -0
- package/templates/space/apps/fe/src/types/ui.d.ts +26 -0
- package/templates/space/apps/fe/src/types/vite-env.d.ts +20 -0
- package/templates/space/apps/fe/src/utils/date.utils.ts +17 -0
- package/templates/space/apps/fe/src/utils/image-fallback.ts +9 -0
- package/templates/space/apps/fe/src/utils/media-upload.ts +63 -0
- package/templates/space/apps/fe/src/utils/number.utils.ts +12 -0
- package/templates/space/apps/fe/src/utils/string.utils.ts +5 -0
- package/templates/space/apps/fe/src/vite-env.d.ts +1 -0
- package/templates/space/apps/fe/tsconfig.app.json +32 -0
- package/templates/space/apps/fe/tsconfig.json +9 -0
- package/templates/space/apps/fe/tsconfig.node.json +31 -0
- package/templates/space/apps/fe/vite.config.ts +94 -0
- package/templates/space/database.ts +33 -13
- package/templates/space/openapi.json +1819 -0
- package/templates/space/oxfmt.config.js +23 -0
- package/templates/space/package.json +6 -13
- package/templates/space/rulesync.jsonc +18 -0
- package/templates/space/tsconfig.json +2 -4
- package/templates/space/api/health.ts +0 -7
- package/templates/space/api/launch.ts +0 -29
- package/templates/space/api/me.ts +0 -14
- package/templates/space/api/motto.ts +0 -9
- package/templates/space/api/rockets.ts +0 -43
- package/templates/space/index.html +0 -138
- package/templates/space/src/main.tsx +0 -229
- package/templates/space/vite.config.ts +0 -7
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
{
|
|
2
|
+
"appName": "Tiny Template",
|
|
3
|
+
"appDescription": "A template for building web applications",
|
|
4
|
+
"shared": {
|
|
5
|
+
"notFound": {
|
|
6
|
+
"head": {
|
|
7
|
+
"title": "Stran ni bila najdena",
|
|
8
|
+
"description": "Stran, ki jo iščete, ne obstaja."
|
|
9
|
+
},
|
|
10
|
+
"heading": "Stran ni bila najdena",
|
|
11
|
+
"subheading": "Stran, ki jo iščete, ne obstaja.",
|
|
12
|
+
"homeBtn": "Na domačo stran"
|
|
13
|
+
},
|
|
14
|
+
"errorFallback": {
|
|
15
|
+
"heading": "Nekaj je šlo narobe",
|
|
16
|
+
"subheading": "Napaka: {{error}}",
|
|
17
|
+
"retryBtn": "Poskusi znova"
|
|
18
|
+
},
|
|
19
|
+
"apiErrors": {
|
|
20
|
+
"requestFailed": "Zahteva ni uspela: {{error}}",
|
|
21
|
+
"unknown": "Prišlo je do neznane napake."
|
|
22
|
+
},
|
|
23
|
+
"seedData": {
|
|
24
|
+
"actionButton": "Ponastavi začetne podatke",
|
|
25
|
+
"confirmation": {
|
|
26
|
+
"heading": "Ponastaviti začetne podatke?",
|
|
27
|
+
"description": "Vsi lokalni podatki bodo ponastavljeni na začetno stanje.",
|
|
28
|
+
"confirm": "Ponastavi",
|
|
29
|
+
"cancel": "Prekliči"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"actions": {
|
|
33
|
+
"back": "Nazaj"
|
|
34
|
+
},
|
|
35
|
+
"fakeMailbox": {
|
|
36
|
+
"title": "Lažni poštni predal",
|
|
37
|
+
"open": "Odpri lažni poštni predal",
|
|
38
|
+
"close": "Zapri lažni poštni predal",
|
|
39
|
+
"empty": "Ni prejetih e-poštnih sporočil.",
|
|
40
|
+
"from": "Od",
|
|
41
|
+
"to": "Za",
|
|
42
|
+
"cc": "Kp",
|
|
43
|
+
"bcc": "Skp",
|
|
44
|
+
"replyTo": "Odgovori na",
|
|
45
|
+
"openPopup": "Odpri pojavno okno",
|
|
46
|
+
"popupBlocked": {
|
|
47
|
+
"heading": "Pojavno okno e-pošte je blokirano",
|
|
48
|
+
"description": "Brskalnik je blokiral samodejno okno e-pošte. Dovolite pojavna okna za to stran ali sporočilo odprite zdaj.",
|
|
49
|
+
"open": "Odpri e-pošto",
|
|
50
|
+
"cancel": "Pusti neprebrano"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"auth": {
|
|
55
|
+
"wrapper": {
|
|
56
|
+
"title": "Administrativni portal",
|
|
57
|
+
"description": "Tiny",
|
|
58
|
+
"watermark": "© {{year}} Tiny • Razvija in vzdržuje Povio"
|
|
59
|
+
},
|
|
60
|
+
"shared": {
|
|
61
|
+
"errors": {
|
|
62
|
+
"invalidCredentials": "Neveljaven e-poštni naslov ali geslo.",
|
|
63
|
+
"userAlreadyExists": "Račun s tem e-poštnim naslovom že obstaja.",
|
|
64
|
+
"identityNotFound": "Tega računa ni bilo mogoče najti.",
|
|
65
|
+
"notConfirmed": "Ta račun še ni potrjen."
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"login": {
|
|
69
|
+
"head": {
|
|
70
|
+
"title": "Prijava",
|
|
71
|
+
"description": "Prijavite se v svoj račun"
|
|
72
|
+
},
|
|
73
|
+
"form": {
|
|
74
|
+
"title": "Prijava",
|
|
75
|
+
"email": "E-pošta",
|
|
76
|
+
"emailPlaceholder": "Vnesite svoj e-poštni naslov",
|
|
77
|
+
"password": "Geslo",
|
|
78
|
+
"passwordPlaceholder": "Vnesite svoje geslo",
|
|
79
|
+
"submit": "Prijava",
|
|
80
|
+
"submitLoading": "Prijavljanje...",
|
|
81
|
+
"demoLogin": "Prijava kot demo uporabnik",
|
|
82
|
+
"noAccount": "Nimate računa?",
|
|
83
|
+
"registerLink": "Registracija",
|
|
84
|
+
"loginFailed": "Prijava ni uspela",
|
|
85
|
+
"resetSeedData": "Ponastavi začetne podatke"
|
|
86
|
+
},
|
|
87
|
+
"confirmations": {
|
|
88
|
+
"resetSeed": {
|
|
89
|
+
"heading": "Ponastaviti začetne podatke?",
|
|
90
|
+
"description": "Vsi lokalni podatki bodo ponastavljeni na začetno stanje.",
|
|
91
|
+
"confirm": "Ponastavi",
|
|
92
|
+
"cancel": "Prekliči"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"register": {
|
|
97
|
+
"head": {
|
|
98
|
+
"title": "Registracija",
|
|
99
|
+
"description": "Ustvarite nov račun"
|
|
100
|
+
},
|
|
101
|
+
"form": {
|
|
102
|
+
"title": "Registracija",
|
|
103
|
+
"description": "Ustvarite nov račun",
|
|
104
|
+
"email": "E-pošta",
|
|
105
|
+
"emailPlaceholder": "Vnesite svoj e-poštni naslov",
|
|
106
|
+
"name": "Ime",
|
|
107
|
+
"namePlaceholder": "Vnesite svoje ime",
|
|
108
|
+
"password": "Geslo",
|
|
109
|
+
"passwordPlaceholder": "Vnesite svoje geslo",
|
|
110
|
+
"confirmPassword": "Potrdite geslo",
|
|
111
|
+
"confirmPasswordPlaceholder": "Potrdite svoje geslo",
|
|
112
|
+
"confirmPasswordRequired": "Potrditev gesla je obvezna",
|
|
113
|
+
"passwordsDoNotMatch": "Gesli se ne ujemata",
|
|
114
|
+
"passwordRequirements": "Geslo mora vsebovati vsaj 12 znakov.",
|
|
115
|
+
"submit": "Registriraj se",
|
|
116
|
+
"registrationFailed": "Registracija ni uspela",
|
|
117
|
+
"hasAccount": "Že imate račun?",
|
|
118
|
+
"loginLink": "Prijava"
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
"setPassword": {
|
|
122
|
+
"form": {
|
|
123
|
+
"title": "Nastavite svoje geslo",
|
|
124
|
+
"welcome": "Dobrodošli, {{firstName}} {{lastName}}. Nastavite geslo, da dokončate nastavitev računa.",
|
|
125
|
+
"tokenNote": "Vnesite povabilni žeton, ki ste ga prejeli po e-pošti.",
|
|
126
|
+
"token": "Povabilni žeton",
|
|
127
|
+
"tokenPlaceholder": "Vnesite svoj povabilni žeton",
|
|
128
|
+
"tokenChecking": "Preverjanje žetona...",
|
|
129
|
+
"tokenError": "{{error}}",
|
|
130
|
+
"invalidToken": "Neveljaven ali potekel žeton",
|
|
131
|
+
"email": "E-pošta",
|
|
132
|
+
"password": "Geslo",
|
|
133
|
+
"passwordPlaceholder": "Vnesite svoje geslo",
|
|
134
|
+
"confirmPassword": "Potrdite geslo",
|
|
135
|
+
"confirmPasswordPlaceholder": "Potrdite svoje geslo",
|
|
136
|
+
"passwordRequirements": "Geslo mora vsebovati vsaj 8 znakov.",
|
|
137
|
+
"submit": "Nastavi geslo",
|
|
138
|
+
"setPasswordFailed": "Nastavitev gesla ni uspela",
|
|
139
|
+
"hasPassword": "Že imate geslo?",
|
|
140
|
+
"loginLink": "Prijava"
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
"layout": {
|
|
145
|
+
"header": {
|
|
146
|
+
"logo": "Tiny",
|
|
147
|
+
"openMenu": "Odpri meni",
|
|
148
|
+
"closeMenu": "Zapri meni",
|
|
149
|
+
"nav": {
|
|
150
|
+
"home": "Domov",
|
|
151
|
+
"planets": "Planeti",
|
|
152
|
+
"planetsFeed": "Tok planetov"
|
|
153
|
+
},
|
|
154
|
+
"profile": "Profil",
|
|
155
|
+
"logout": "Odjava"
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"home": {
|
|
159
|
+
"head": {
|
|
160
|
+
"title": "Domov",
|
|
161
|
+
"description": "Dobrodošli v aplikaciji!"
|
|
162
|
+
},
|
|
163
|
+
"title": "Domov",
|
|
164
|
+
"message": "Dobrodošli v aplikaciji!"
|
|
165
|
+
},
|
|
166
|
+
"planets": {
|
|
167
|
+
"detail": {
|
|
168
|
+
"back": "Nazaj na planete",
|
|
169
|
+
"notFound": "Planet ni bil najden.",
|
|
170
|
+
"description": "Opis",
|
|
171
|
+
"details": "Podrobnosti",
|
|
172
|
+
"created": "Ustvarjeno",
|
|
173
|
+
"updated": "Zadnja posodobitev",
|
|
174
|
+
"discoveryDate": "Datum odkritja",
|
|
175
|
+
"createdBy": "Ustvaril"
|
|
176
|
+
},
|
|
177
|
+
"page": {
|
|
178
|
+
"title": "Planeti",
|
|
179
|
+
"headTitle": "Planeti",
|
|
180
|
+
"headDescription": "Upravljanje planetov",
|
|
181
|
+
"addPlanet": "Dodaj planet",
|
|
182
|
+
"empty": "Planetov še ni. Dodajte prvega."
|
|
183
|
+
},
|
|
184
|
+
"views": {
|
|
185
|
+
"label": "Prikaz planetov",
|
|
186
|
+
"grid": "Mreža",
|
|
187
|
+
"table": "Tabela",
|
|
188
|
+
"infinite": "Neskončna tabela"
|
|
189
|
+
},
|
|
190
|
+
"filters": {
|
|
191
|
+
"search": "Iskanje",
|
|
192
|
+
"alien": "Tujec",
|
|
193
|
+
"alienPlaceholder": "Vsi tujci",
|
|
194
|
+
"discoveryDate": "Datum odkritja",
|
|
195
|
+
"discoveryDatePlaceholder": "Kateri koli datum",
|
|
196
|
+
"clearAll": "Počisti vse"
|
|
197
|
+
},
|
|
198
|
+
"create": {
|
|
199
|
+
"title": "Ustvari planet",
|
|
200
|
+
"name": "Ime",
|
|
201
|
+
"namePlaceholder": "Ime planeta",
|
|
202
|
+
"alien": "Tujec",
|
|
203
|
+
"alienPlaceholder": "Izberite tujca",
|
|
204
|
+
"discoveryDate": "Datum odkritja",
|
|
205
|
+
"discoveryDatePlaceholder": "Izberite datum odkritja",
|
|
206
|
+
"description": "Opis",
|
|
207
|
+
"descriptionPlaceholder": "Opcijski opis",
|
|
208
|
+
"image": "Slika",
|
|
209
|
+
"imageEmptyText": "Brez slike",
|
|
210
|
+
"imageUploadText": "Naloži",
|
|
211
|
+
"imageBrowseText": "Brskaj",
|
|
212
|
+
"submit": "Ustvari",
|
|
213
|
+
"cancel": "Prekliči",
|
|
214
|
+
"success": "Planet ustvarjen",
|
|
215
|
+
"error": "Ustvarjanje planeta ni uspelo"
|
|
216
|
+
},
|
|
217
|
+
"edit": {
|
|
218
|
+
"title": "Uredi planet",
|
|
219
|
+
"name": "Ime",
|
|
220
|
+
"namePlaceholder": "Ime planeta",
|
|
221
|
+
"alien": "Tujec",
|
|
222
|
+
"alienPlaceholder": "Izberite tujca",
|
|
223
|
+
"discoveryDate": "Datum odkritja",
|
|
224
|
+
"discoveryDatePlaceholder": "Izberite datum odkritja",
|
|
225
|
+
"description": "Opis",
|
|
226
|
+
"descriptionPlaceholder": "Opcijski opis",
|
|
227
|
+
"image": "Slika",
|
|
228
|
+
"imageEmptyText": "Brez slike",
|
|
229
|
+
"imageUploadText": "Naloži",
|
|
230
|
+
"imageBrowseText": "Brskaj",
|
|
231
|
+
"submit": "Shrani",
|
|
232
|
+
"cancel": "Prekliči",
|
|
233
|
+
"success": "Planet posodobljen",
|
|
234
|
+
"error": "Posodobitev planeta ni uspela"
|
|
235
|
+
},
|
|
236
|
+
"card": {
|
|
237
|
+
"createdBy": "Ustvaril {{name}} dne {{date}}",
|
|
238
|
+
"alien": "Tujec: {{name}}",
|
|
239
|
+
"unknownCreator": "Neznano"
|
|
240
|
+
},
|
|
241
|
+
"table": {
|
|
242
|
+
"allLoaded": "Prikazani so vsi zadetki",
|
|
243
|
+
"showing": "Prikaz {{displayed}} od {{total}}",
|
|
244
|
+
"name": "Ime",
|
|
245
|
+
"description": "Opis",
|
|
246
|
+
"alien": "Tujec",
|
|
247
|
+
"discoveryDate": "Datum odkritja",
|
|
248
|
+
"creator": "Ustvaril",
|
|
249
|
+
"actions": "Dejanja"
|
|
250
|
+
},
|
|
251
|
+
"actions": {
|
|
252
|
+
"edit": "Uredi",
|
|
253
|
+
"remove": "Odstrani"
|
|
254
|
+
},
|
|
255
|
+
"removeConfirm": {
|
|
256
|
+
"heading": "Odstraniti planet?",
|
|
257
|
+
"description": "Ali ste prepričani, da želite odstraniti \"{{name}}\"? Tega ni mogoče razveljaviti.",
|
|
258
|
+
"confirm": "Odstrani",
|
|
259
|
+
"cancel": "Prekliči",
|
|
260
|
+
"success": "Planet odstranjen",
|
|
261
|
+
"error": "Odstranitev planeta ni uspela"
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
"planetsFeed": {
|
|
265
|
+
"page": {
|
|
266
|
+
"title": "Tok planetov",
|
|
267
|
+
"headTitle": "Tok planetov",
|
|
268
|
+
"headDescription": "Pomikajte se po planetih in njihovih tujcih",
|
|
269
|
+
"empty": "Ni planetov za prikaz.",
|
|
270
|
+
"end": "Dosegli ste konec."
|
|
271
|
+
},
|
|
272
|
+
"card": {
|
|
273
|
+
"like": "Vsec mi je ({{count}})",
|
|
274
|
+
"liked": "Vsec mi je ({{count}})",
|
|
275
|
+
"discovered": "Odkrito {{date}}",
|
|
276
|
+
"alienLabel": "Tujec na tem planetu",
|
|
277
|
+
"creatorLabel": "Ustvaril",
|
|
278
|
+
"noAlien": "Tujec ni dodeljen"
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
"profile": {
|
|
282
|
+
"head": {
|
|
283
|
+
"title": "Profil",
|
|
284
|
+
"description": "Vaš profil"
|
|
285
|
+
},
|
|
286
|
+
"form": {
|
|
287
|
+
"title": "Profil",
|
|
288
|
+
"description": "Posodobite podatke profila",
|
|
289
|
+
"email": "E-pošta",
|
|
290
|
+
"emailPlaceholder": "Vnesite svoj e-poštni naslov",
|
|
291
|
+
"name": "Ime",
|
|
292
|
+
"namePlaceholder": "Vnesite svoje ime",
|
|
293
|
+
"submit": "Shrani",
|
|
294
|
+
"submitLoading": "Shranjevanje...",
|
|
295
|
+
"notifications": {
|
|
296
|
+
"success": "Profil posodobljen",
|
|
297
|
+
"error": "Posodobitev profila ni uspela"
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { RestClient } from "@povio/openapi-codegen-cli";
|
|
2
|
+
|
|
3
|
+
import { AuthorizationHeaderInterceptor } from "@/clients/rest/interceptors/authorization-header.interceptor";
|
|
4
|
+
import { RefreshTokenInterceptor } from "@/clients/rest/interceptors/refresh-token.interceptor";
|
|
5
|
+
import { ResponseInterceptor } from "@/clients/rest/interceptors/response.interceptor";
|
|
6
|
+
import { AppConfig } from "@/config/app.config";
|
|
7
|
+
import { AppErrorHandler } from "@/clients/rest/app-error-handler";
|
|
8
|
+
|
|
9
|
+
export const AppRestClient = new RestClient({
|
|
10
|
+
config: {
|
|
11
|
+
baseURL: AppConfig.api.url,
|
|
12
|
+
},
|
|
13
|
+
errorHandler: AppErrorHandler,
|
|
14
|
+
interceptors: [AuthorizationHeaderInterceptor, RefreshTokenInterceptor, ResponseInterceptor],
|
|
15
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY } from "@/config/jwt.config";
|
|
2
|
+
|
|
3
|
+
type TokenListener = () => void;
|
|
4
|
+
|
|
5
|
+
const listeners = new Set<TokenListener>();
|
|
6
|
+
let storageListenerCount = 0;
|
|
7
|
+
|
|
8
|
+
const isBrowser = () => typeof window !== "undefined";
|
|
9
|
+
|
|
10
|
+
const readToken = (key: string) => {
|
|
11
|
+
if (!isBrowser()) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
return window.localStorage.getItem(key);
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const writeToken = (key: string, token: string | null) => {
|
|
23
|
+
if (!isBrowser()) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (token) {
|
|
29
|
+
window.localStorage.setItem(key, token);
|
|
30
|
+
} else {
|
|
31
|
+
window.localStorage.removeItem(key);
|
|
32
|
+
}
|
|
33
|
+
} catch {
|
|
34
|
+
// Keep in-memory auth subscribers in sync even if storage is unavailable.
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const emitChange = () => {
|
|
39
|
+
for (const listener of listeners) {
|
|
40
|
+
listener();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const onStorageChange = (event: StorageEvent) => {
|
|
45
|
+
if (event.storageArea !== window.localStorage) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (event.key === ACCESS_TOKEN_KEY || event.key === REFRESH_TOKEN_KEY || event.key === null) {
|
|
50
|
+
emitChange();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const authTokenStore = {
|
|
55
|
+
getAccessToken: () => readToken(ACCESS_TOKEN_KEY),
|
|
56
|
+
getRefreshToken: () => readToken(REFRESH_TOKEN_KEY),
|
|
57
|
+
getServerSnapshot: () => null,
|
|
58
|
+
subscribe: (listener: TokenListener) => {
|
|
59
|
+
listeners.add(listener);
|
|
60
|
+
|
|
61
|
+
if (isBrowser()) {
|
|
62
|
+
if (storageListenerCount === 0) {
|
|
63
|
+
window.addEventListener("storage", onStorageChange);
|
|
64
|
+
}
|
|
65
|
+
storageListenerCount += 1;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return () => {
|
|
69
|
+
listeners.delete(listener);
|
|
70
|
+
|
|
71
|
+
if (isBrowser()) {
|
|
72
|
+
storageListenerCount -= 1;
|
|
73
|
+
if (storageListenerCount === 0) {
|
|
74
|
+
window.removeEventListener("storage", onStorageChange);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
setAccessToken: (token: string | null) => {
|
|
80
|
+
writeToken(ACCESS_TOKEN_KEY, token);
|
|
81
|
+
emitChange();
|
|
82
|
+
},
|
|
83
|
+
setRefreshToken: (token: string | null) => {
|
|
84
|
+
writeToken(REFRESH_TOKEN_KEY, token);
|
|
85
|
+
emitChange();
|
|
86
|
+
},
|
|
87
|
+
setTokens: (accessToken: string | null, refreshToken?: string | null) => {
|
|
88
|
+
writeToken(ACCESS_TOKEN_KEY, accessToken);
|
|
89
|
+
|
|
90
|
+
if (refreshToken !== undefined) {
|
|
91
|
+
writeToken(REFRESH_TOKEN_KEY, refreshToken);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
emitChange();
|
|
95
|
+
},
|
|
96
|
+
clear: () => {
|
|
97
|
+
writeToken(ACCESS_TOKEN_KEY, null);
|
|
98
|
+
writeToken(REFRESH_TOKEN_KEY, null);
|
|
99
|
+
emitChange();
|
|
100
|
+
},
|
|
101
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type ErrorEntry, ErrorHandler } from "@povio/openapi-codegen-cli";
|
|
2
|
+
|
|
3
|
+
export enum AuthErrorCodes {
|
|
4
|
+
InvalidCredentials = "invalid-credentials",
|
|
5
|
+
LocalAuthnIdentityExists = "identity-already-exists",
|
|
6
|
+
IdentityNotFound = "identity-not-found",
|
|
7
|
+
NotConfirmed = "identity-not-confirmed",
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const authErrors: ErrorEntry<AuthErrorCodes>[] = [
|
|
11
|
+
{
|
|
12
|
+
code: AuthErrorCodes.InvalidCredentials,
|
|
13
|
+
getMessage: (t) => t("auth.shared.errors.invalidCredentials"),
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
code: AuthErrorCodes.LocalAuthnIdentityExists,
|
|
17
|
+
getMessage: (t) => t("auth.shared.errors.userAlreadyExists"),
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
code: AuthErrorCodes.IdentityNotFound,
|
|
21
|
+
getMessage: (t) => t("auth.shared.errors.identityNotFound"),
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
code: AuthErrorCodes.NotConfirmed,
|
|
25
|
+
getMessage: (t) => t("auth.shared.errors.notConfirmed"),
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const entries = [...authErrors];
|
|
30
|
+
|
|
31
|
+
export const AppErrorHandler = new ErrorHandler({ entries });
|
package/templates/space/apps/fe/src/clients/rest/interceptors/authorization-header.interceptor.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { RestInterceptor } from "@povio/openapi-codegen-cli";
|
|
2
|
+
|
|
3
|
+
import { authTokenStore } from "@/clients/auth-token-store";
|
|
4
|
+
|
|
5
|
+
export const AuthorizationHeaderInterceptor = new RestInterceptor((client) => {
|
|
6
|
+
return client.interceptors.request.use(async (config) => {
|
|
7
|
+
const accessToken = authTokenStore.getAccessToken();
|
|
8
|
+
if (accessToken != null) {
|
|
9
|
+
// oxlint-disable-next-line no-param-reassign
|
|
10
|
+
config.headers.Authorization = `Bearer ${accessToken}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return config;
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { RestInterceptor } from "@povio/openapi-codegen-cli";
|
|
2
|
+
|
|
3
|
+
import { AuthErrorCodes } from "@/clients/rest/app-error-handler";
|
|
4
|
+
import { UserAuthApi } from "@/openapi/userAuth/userAuth.api";
|
|
5
|
+
import { authTokenStore } from "@/clients/auth-token-store";
|
|
6
|
+
|
|
7
|
+
export const RefreshTokenInterceptor = new RestInterceptor((client) => {
|
|
8
|
+
return client.interceptors.response.use(
|
|
9
|
+
(response) => response,
|
|
10
|
+
// oxlint-disable-next-line promise/prefer-await-to-callbacks
|
|
11
|
+
async (error) => {
|
|
12
|
+
const originalRequest = error.config;
|
|
13
|
+
|
|
14
|
+
if (error.response?.data?.code === AuthErrorCodes.InvalidCredentials) {
|
|
15
|
+
return Promise.reject(error);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (error.response?.status === 401 && !originalRequest._retry) {
|
|
19
|
+
originalRequest._retry = true;
|
|
20
|
+
try {
|
|
21
|
+
const refreshToken = authTokenStore.getRefreshToken();
|
|
22
|
+
const { accessToken, refreshToken: newRefreshToken } = await UserAuthApi.accessToken({
|
|
23
|
+
refreshToken: refreshToken || "",
|
|
24
|
+
});
|
|
25
|
+
authTokenStore.setTokens(accessToken, newRefreshToken || null);
|
|
26
|
+
|
|
27
|
+
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
|
|
28
|
+
|
|
29
|
+
return client(originalRequest);
|
|
30
|
+
} catch {
|
|
31
|
+
authTokenStore.clear();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return Promise.reject(error);
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { RestInterceptor } from "@povio/openapi-codegen-cli";
|
|
2
|
+
import type { AxiosError, AxiosResponse } from "axios";
|
|
3
|
+
|
|
4
|
+
export const ResponseInterceptor = new RestInterceptor((client) => {
|
|
5
|
+
return client.interceptors.response.use(
|
|
6
|
+
async (response: AxiosResponse) => {
|
|
7
|
+
return {
|
|
8
|
+
...response,
|
|
9
|
+
data: response.data === "" ? undefined : response.data,
|
|
10
|
+
};
|
|
11
|
+
},
|
|
12
|
+
// oxlint-disable-next-line promise/prefer-await-to-callbacks
|
|
13
|
+
async (error: AxiosError) => {
|
|
14
|
+
throw error;
|
|
15
|
+
},
|
|
16
|
+
);
|
|
17
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useTranslation } from "react-i18next";
|
|
2
|
+
|
|
3
|
+
import { NotFound } from "@/components/shared/error/NotFound";
|
|
4
|
+
import { AppHead } from "@/components/shared/head/AppHead";
|
|
5
|
+
|
|
6
|
+
export const NotFoundPage = () => {
|
|
7
|
+
const { t } = useTranslation();
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<>
|
|
11
|
+
<AppHead
|
|
12
|
+
title={t(($) => $.shared.notFound.head.title)}
|
|
13
|
+
description={t(($) => $.shared.notFound.head.description)}
|
|
14
|
+
/>
|
|
15
|
+
<NotFound />
|
|
16
|
+
</>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Typography } from "@povio/ui";
|
|
2
|
+
import { useTranslation } from "react-i18next";
|
|
3
|
+
|
|
4
|
+
import { BrandLogo } from "@/components/shared/branding/BrandLogo";
|
|
5
|
+
|
|
6
|
+
export function AuthBrandPanel() {
|
|
7
|
+
const { t } = useTranslation();
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<section className="relative hidden flex-1 overflow-hidden bg-elevation-fill-default-2 lg:flex lg:flex-col lg:justify-between">
|
|
11
|
+
<div className="relative z-raised flex flex-col gap-1 px-16 pt-20 xl:px-24 xl:pt-24">
|
|
12
|
+
<Typography
|
|
13
|
+
as="p"
|
|
14
|
+
className="text-text-default-2"
|
|
15
|
+
size="title-3"
|
|
16
|
+
>
|
|
17
|
+
{t(($) => $.auth.wrapper.title)}
|
|
18
|
+
</Typography>
|
|
19
|
+
<Typography
|
|
20
|
+
as="h1"
|
|
21
|
+
className="text-text-default-2"
|
|
22
|
+
size="title-1"
|
|
23
|
+
variant="prominent-1"
|
|
24
|
+
>
|
|
25
|
+
{t(($) => $.auth.wrapper.description)}
|
|
26
|
+
</Typography>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<BrandLogo className="absolute -right-12 top-10 text-[10rem] leading-none text-[#e1e9ee]" />
|
|
30
|
+
|
|
31
|
+
<svg
|
|
32
|
+
aria-hidden="true"
|
|
33
|
+
className="absolute bottom-0 left-0 h-auto w-full opacity-20"
|
|
34
|
+
viewBox="0 0 760 390"
|
|
35
|
+
>
|
|
36
|
+
<path
|
|
37
|
+
d="M0 245 80 202l62 22 98-85 92 77 68-45 76 53 84-113 74 95 126-50v234H0Z"
|
|
38
|
+
fill="#006ab6"
|
|
39
|
+
/>
|
|
40
|
+
<path
|
|
41
|
+
d="m52 390 132-120 78 61 106-130 91 108 69-59 128 140Z"
|
|
42
|
+
fill="#006ab6"
|
|
43
|
+
opacity=".4"
|
|
44
|
+
/>
|
|
45
|
+
</svg>
|
|
46
|
+
|
|
47
|
+
<div className="relative z-raised flex flex-col items-center gap-3 pb-8">
|
|
48
|
+
<BrandLogo className="text-interactive-contained-primary-idle" />
|
|
49
|
+
<Typography
|
|
50
|
+
as="span"
|
|
51
|
+
className="text-text-default-2"
|
|
52
|
+
size="body-4"
|
|
53
|
+
variant="prominent-1"
|
|
54
|
+
>
|
|
55
|
+
{t(($) => $.auth.wrapper.watermark, { year: new Date().getFullYear() })}
|
|
56
|
+
</Typography>
|
|
57
|
+
</div>
|
|
58
|
+
</section>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PropsWithChildren } from "react";
|
|
2
|
+
|
|
3
|
+
import { BrandLogo } from "@/components/shared/branding/BrandLogo";
|
|
4
|
+
|
|
5
|
+
import { AuthBrandPanel } from "./AuthBrandPanel";
|
|
6
|
+
|
|
7
|
+
export function AuthLayout({ children }: PropsWithChildren) {
|
|
8
|
+
return (
|
|
9
|
+
<div className="flex min-h-dvh w-full overflow-hidden bg-elevation-fill-default-2">
|
|
10
|
+
<AuthBrandPanel />
|
|
11
|
+
|
|
12
|
+
<section className="m-4 flex w-full flex-1 flex-col rounded-sm bg-elevation-fill-default-1 lg:m-0 lg:rounded-none">
|
|
13
|
+
<div className="flex flex-1 items-center justify-center px-4 py-10 sm:px-8">
|
|
14
|
+
<div className="w-full max-w-[440px]">{children}</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div className="flex justify-center pb-6 lg:hidden">
|
|
18
|
+
<BrandLogo className="text-interactive-contained-primary-idle" />
|
|
19
|
+
</div>
|
|
20
|
+
</section>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|