From ee1b1ae5e6e2c67f8d782d54e800c6cfebbb9d83 Mon Sep 17 00:00:00 2001 From: lonelyhentxi Date: Sat, 26 Apr 2025 01:43:23 +0800 Subject: [PATCH] refactor: refactor webui --- apps/webui/components.json | 10 +- apps/webui/package.json | 61 ++++----- apps/webui/src/app/auth/context.ts | 46 +++++-- apps/webui/src/app/auth/guard.ts | 12 +- apps/webui/src/app/auth/hooks.ts | 2 +- apps/webui/src/domains/auth/auth.service.ts | 54 ++------ apps/webui/src/infra/auth/auth.provider.ts | 27 ++++ .../infra/auth/basic/basic-auth.provider.ts | 22 ++++ apps/webui/src/infra/auth/basic/index.ts | 1 + apps/webui/src/infra/auth/defs.ts | 12 ++ .../{app/auth => infra/auth/oidc}/config.ts | 10 -- apps/webui/src/infra/auth/oidc/index.ts | 2 + .../src/infra/auth/oidc/oidc-auth.provider.ts | 41 ++++++ apps/webui/src/infra/graphql/context.ts | 16 +++ .../src/infra/graphql/graphql.service.ts | 30 +++++ apps/webui/src/infra/graphql/index.ts | 2 + apps/webui/src/infra/hooks/use-mobile.ts | 21 --- apps/webui/src/infra/http/http.service.ts | 7 - apps/webui/src/infra/routes/traits.ts | 2 +- apps/webui/src/infra/routes/utils.ts | 2 +- apps/webui/src/main.tsx | 24 ++-- .../components/layout/app-icon.tsx | 4 +- .../components/layout/app-layout.tsx | 14 +- .../components/layout/app-not-found.tsx | 0 .../components/layout/app-sidebar.tsx | 4 +- .../components/layout/app-skeleton.tsx | 0 .../components/layout/nav-main.tsx | 4 +- .../components/layout/nav-projects.tsx | 4 +- .../components/layout/nav-user.tsx | 10 +- .../{ => views}/components/ui/accordion.tsx | 18 +-- .../components/ui/alert-dialog.tsx | 34 ++--- .../src/{ => views}/components/ui/alert.tsx | 16 +-- .../components/ui/aspect-ratio.tsx | 0 .../src/{ => views}/components/ui/avatar.tsx | 16 +-- .../src/{ => views}/components/ui/badge.tsx | 16 +-- .../{ => views}/components/ui/breadcrumb.tsx | 28 ++-- .../src/{ => views}/components/ui/button.tsx | 18 +-- .../{ => views}/components/ui/calendar.tsx | 14 +- .../src/{ => views}/components/ui/card.tsx | 20 +-- .../{ => views}/components/ui/carousel.tsx | 124 +++++++++--------- .../src/{ => views}/components/ui/chart.tsx | 2 +- .../{ => views}/components/ui/checkbox.tsx | 14 +- .../{ => views}/components/ui/collapsible.tsx | 0 .../src/{ => views}/components/ui/command.tsx | 36 ++--- .../components/ui/context-menu.tsx | 50 +++---- .../src/{ => views}/components/ui/dialog.tsx | 30 ++--- .../src/{ => views}/components/ui/drawer.tsx | 28 ++-- .../components/ui/dropdown-menu.tsx | 50 +++---- .../src/{ => views}/components/ui/form.tsx | 4 +- .../{ => views}/components/ui/hover-card.tsx | 14 +- .../src/{ => views}/components/ui/image.tsx | 0 .../{ => views}/components/ui/input-otp.tsx | 28 ++-- .../src/{ => views}/components/ui/input.tsx | 8 +- .../src/{ => views}/components/ui/label.tsx | 12 +- .../src/{ => views}/components/ui/menubar.tsx | 50 +++---- .../components/ui/navigation-menu.tsx | 32 ++--- .../{ => views}/components/ui/pagination.tsx | 28 ++-- .../src/{ => views}/components/ui/popover.tsx | 18 +-- .../{ => views}/components/ui/pro-link.tsx | 0 .../components/ui/progress-circle.tsx | 18 +-- .../{ => views}/components/ui/progress.tsx | 10 +- .../{ => views}/components/ui/radio-group.tsx | 16 +-- .../{ => views}/components/ui/resizable.tsx | 18 +-- .../{ => views}/components/ui/scroll-area.tsx | 14 +- .../src/{ => views}/components/ui/select.tsx | 32 ++--- .../{ => views}/components/ui/separator.tsx | 12 +- .../src/{ => views}/components/ui/sheet.tsx | 32 ++--- .../src/{ => views}/components/ui/sidebar.tsx | 18 +-- .../{ => views}/components/ui/skeleton.tsx | 6 +- .../src/{ => views}/components/ui/slider.tsx | 14 +- .../src/{ => views}/components/ui/sonner.tsx | 0 .../src/{ => views}/components/ui/spinner.tsx | 52 ++++---- .../src/{ => views}/components/ui/switch.tsx | 12 +- .../src/{ => views}/components/ui/table.tsx | 22 ++-- .../src/{ => views}/components/ui/tabs.tsx | 18 +-- .../{ => views}/components/ui/textarea.tsx | 8 +- .../components/ui/toggle-group.tsx | 22 ++-- .../src/{ => views}/components/ui/toggle.tsx | 14 +- .../src/{ => views}/components/ui/tooltip.tsx | 16 +-- apps/webui/src/views/hooks/use-mobile.ts | 19 +++ apps/webui/src/{ => views}/routeTree.gen.ts | 0 apps/webui/src/{ => views}/routes/404.tsx | 2 +- apps/webui/src/{ => views}/routes/__root.tsx | 0 .../routes/_app/_explore/explore.tsx | 0 .../{ => views}/routes/_app/_explore/feed.tsx | 0 .../_app/playground/graphql-api.lazy.tsx | 14 +- .../routes/_app/playground/graphql-api.tsx | 2 +- .../routes/_app/playground/route.tsx | 0 .../src/{ => views}/routes/_app/route.tsx | 2 +- .../routes/_app/settings/downloader.tsx | 0 .../routes/_app/settings/route.tsx | 0 .../routes/_app/subscriptions/create.tsx | 53 ++++---- .../subscriptions/edit.$subscription-id.tsx | 0 .../routes/_app/subscriptions/manage.tsx | 0 .../routes/_app/subscriptions/route.tsx | 0 apps/webui/src/{ => views}/routes/about.tsx | 0 .../{ => views}/routes/auth/oidc/callback.tsx | 57 ++++---- .../src/{ => views}/routes/auth/sign-in.tsx | 0 .../src/{ => views}/routes/auth/sign-up.tsx | 0 apps/webui/src/{ => views}/routes/index.tsx | 4 +- .../styles/utils.ts => views/utils/index.ts} | 0 apps/webui/tsr.config.json | 4 +- biome.json | 3 +- pnpm-lock.yaml | 95 ++++++-------- 104 files changed, 934 insertions(+), 827 deletions(-) create mode 100644 apps/webui/src/infra/auth/auth.provider.ts create mode 100644 apps/webui/src/infra/auth/basic/basic-auth.provider.ts create mode 100644 apps/webui/src/infra/auth/basic/index.ts create mode 100644 apps/webui/src/infra/auth/defs.ts rename apps/webui/src/{app/auth => infra/auth/oidc}/config.ts (80%) create mode 100644 apps/webui/src/infra/auth/oidc/index.ts create mode 100644 apps/webui/src/infra/auth/oidc/oidc-auth.provider.ts create mode 100644 apps/webui/src/infra/graphql/context.ts create mode 100644 apps/webui/src/infra/graphql/graphql.service.ts create mode 100644 apps/webui/src/infra/graphql/index.ts delete mode 100644 apps/webui/src/infra/hooks/use-mobile.ts delete mode 100644 apps/webui/src/infra/http/http.service.ts rename apps/webui/src/{ => views}/components/layout/app-icon.tsx (91%) rename apps/webui/src/{ => views}/components/layout/app-layout.tsx (94%) rename apps/webui/src/{ => views}/components/layout/app-not-found.tsx (100%) rename apps/webui/src/{ => views}/components/layout/app-sidebar.tsx (95%) rename apps/webui/src/{ => views}/components/layout/app-skeleton.tsx (100%) rename apps/webui/src/{ => views}/components/layout/nav-main.tsx (97%) rename apps/webui/src/{ => views}/components/layout/nav-projects.tsx (96%) rename apps/webui/src/{ => views}/components/layout/nav-user.tsx (94%) rename apps/webui/src/{ => views}/components/ui/accordion.tsx (89%) rename apps/webui/src/{ => views}/components/ui/alert-dialog.tsx (94%) rename apps/webui/src/{ => views}/components/ui/alert.tsx (87%) rename apps/webui/src/{ => views}/components/ui/aspect-ratio.tsx (100%) rename apps/webui/src/{ => views}/components/ui/avatar.tsx (81%) rename apps/webui/src/{ => views}/components/ui/badge.tsx (84%) rename apps/webui/src/{ => views}/components/ui/breadcrumb.tsx (88%) rename apps/webui/src/{ => views}/components/ui/button.tsx (86%) rename apps/webui/src/{ => views}/components/ui/calendar.tsx (91%) rename apps/webui/src/{ => views}/components/ui/card.tsx (94%) rename apps/webui/src/{ => views}/components/ui/carousel.tsx (73%) rename apps/webui/src/{ => views}/components/ui/chart.tsx (99%) rename apps/webui/src/{ => views}/components/ui/checkbox.tsx (82%) rename apps/webui/src/{ => views}/components/ui/collapsible.tsx (100%) rename apps/webui/src/{ => views}/components/ui/command.tsx (93%) rename apps/webui/src/{ => views}/components/ui/context-menu.tsx (96%) rename apps/webui/src/{ => views}/components/ui/dialog.tsx (92%) rename apps/webui/src/{ => views}/components/ui/drawer.tsx (93%) rename apps/webui/src/{ => views}/components/ui/dropdown-menu.tsx (96%) rename apps/webui/src/{ => views}/components/ui/form.tsx (97%) rename apps/webui/src/{ => views}/components/ui/hover-card.tsx (90%) rename apps/webui/src/{ => views}/components/ui/image.tsx (100%) rename apps/webui/src/{ => views}/components/ui/input-otp.tsx (85%) rename apps/webui/src/{ => views}/components/ui/input.tsx (90%) rename apps/webui/src/{ => views}/components/ui/label.tsx (75%) rename apps/webui/src/{ => views}/components/ui/menubar.tsx (96%) rename apps/webui/src/{ => views}/components/ui/navigation-menu.tsx (96%) rename apps/webui/src/{ => views}/components/ui/pagination.tsx (88%) rename apps/webui/src/{ => views}/components/ui/popover.tsx (89%) rename apps/webui/src/{ => views}/components/ui/pro-link.tsx (100%) rename apps/webui/src/{ => views}/components/ui/progress-circle.tsx (81%) rename apps/webui/src/{ => views}/components/ui/progress.tsx (78%) rename apps/webui/src/{ => views}/components/ui/radio-group.tsx (87%) rename apps/webui/src/{ => views}/components/ui/resizable.tsx (85%) rename apps/webui/src/{ => views}/components/ui/scroll-area.tsx (92%) rename apps/webui/src/{ => views}/components/ui/select.tsx (95%) rename apps/webui/src/{ => views}/components/ui/separator.tsx (75%) rename apps/webui/src/{ => views}/components/ui/sheet.tsx (91%) rename apps/webui/src/{ => views}/components/ui/sidebar.tsx (98%) rename apps/webui/src/{ => views}/components/ui/skeleton.tsx (53%) rename apps/webui/src/{ => views}/components/ui/slider.tsx (91%) rename apps/webui/src/{ => views}/components/ui/sonner.tsx (100%) rename apps/webui/src/{ => views}/components/ui/spinner.tsx (88%) rename apps/webui/src/{ => views}/components/ui/switch.tsx (86%) rename apps/webui/src/{ => views}/components/ui/table.tsx (95%) rename apps/webui/src/{ => views}/components/ui/tabs.tsx (89%) rename apps/webui/src/{ => views}/components/ui/textarea.tsx (87%) rename apps/webui/src/{ => views}/components/ui/toggle-group.tsx (83%) rename apps/webui/src/{ => views}/components/ui/toggle.tsx (84%) rename apps/webui/src/{ => views}/components/ui/tooltip.tsx (91%) create mode 100644 apps/webui/src/views/hooks/use-mobile.ts rename apps/webui/src/{ => views}/routeTree.gen.ts (100%) rename apps/webui/src/{ => views}/routes/404.tsx (64%) rename apps/webui/src/{ => views}/routes/__root.tsx (100%) rename apps/webui/src/{ => views}/routes/_app/_explore/explore.tsx (100%) rename apps/webui/src/{ => views}/routes/_app/_explore/feed.tsx (100%) rename apps/webui/src/{ => views}/routes/_app/playground/graphql-api.lazy.tsx (72%) rename apps/webui/src/{ => views}/routes/_app/playground/graphql-api.tsx (80%) rename apps/webui/src/{ => views}/routes/_app/playground/route.tsx (100%) rename apps/webui/src/{ => views}/routes/_app/route.tsx (84%) rename apps/webui/src/{ => views}/routes/_app/settings/downloader.tsx (100%) rename apps/webui/src/{ => views}/routes/_app/settings/route.tsx (100%) rename apps/webui/src/{ => views}/routes/_app/subscriptions/create.tsx (87%) rename apps/webui/src/{ => views}/routes/_app/subscriptions/edit.$subscription-id.tsx (100%) rename apps/webui/src/{ => views}/routes/_app/subscriptions/manage.tsx (100%) rename apps/webui/src/{ => views}/routes/_app/subscriptions/route.tsx (100%) rename apps/webui/src/{ => views}/routes/about.tsx (100%) rename apps/webui/src/{ => views}/routes/auth/oidc/callback.tsx (61%) rename apps/webui/src/{ => views}/routes/auth/sign-in.tsx (100%) rename apps/webui/src/{ => views}/routes/auth/sign-up.tsx (100%) rename apps/webui/src/{ => views}/routes/index.tsx (66%) rename apps/webui/src/{infra/styles/utils.ts => views/utils/index.ts} (100%) diff --git a/apps/webui/components.json b/apps/webui/components.json index 6a6ec45..834db9e 100644 --- a/apps/webui/components.json +++ b/apps/webui/components.json @@ -10,11 +10,11 @@ "cssVariables": true }, "aliases": { - "components": "@/components", - "utils": "@/infra/styles/utils", - "ui": "@/components/ui", - "lib": "@/lib", - "hooks": "@/infra/hooks" + "components": "@/views/components", + "utils": "@/views/utils", + "ui": "@/views/components/ui", + "lib": "@/views/lib", + "hooks": "@/views/hooks" }, "iconLibrary": "lucide" } diff --git a/apps/webui/package.json b/apps/webui/package.json index 66f8cd9..eee895f 100644 --- a/apps/webui/package.json +++ b/apps/webui/package.json @@ -18,32 +18,32 @@ "@graphiql/toolkit": "^0.11.1", "@hookform/resolvers": "^5.0.1", "@outposts/injection-js": "^2.5.1", - "@radix-ui/react-accordion": "^1.2.7", - "@radix-ui/react-alert-dialog": "^1.1.10", - "@radix-ui/react-aspect-ratio": "^1.1.4", - "@radix-ui/react-avatar": "^1.1.6", - "@radix-ui/react-checkbox": "^1.2.2", - "@radix-ui/react-collapsible": "^1.1.7", - "@radix-ui/react-context-menu": "^2.2.11", - "@radix-ui/react-dialog": "^1.1.10", - "@radix-ui/react-dropdown-menu": "^2.1.11", - "@radix-ui/react-hover-card": "^1.1.10", - "@radix-ui/react-label": "^2.1.4", - "@radix-ui/react-menubar": "^1.1.11", - "@radix-ui/react-navigation-menu": "^1.2.9", - "@radix-ui/react-popover": "^1.1.10", - "@radix-ui/react-progress": "^1.1.4", - "@radix-ui/react-radio-group": "^1.3.3", - "@radix-ui/react-scroll-area": "^1.2.5", - "@radix-ui/react-select": "^2.2.2", - "@radix-ui/react-separator": "^1.1.4", - "@radix-ui/react-slider": "^1.3.2", - "@radix-ui/react-slot": "^1.2.0", - "@radix-ui/react-switch": "^1.2.2", - "@radix-ui/react-tabs": "^1.1.8", - "@radix-ui/react-toggle": "^1.1.6", - "@radix-ui/react-toggle-group": "^1.1.7", - "@radix-ui/react-tooltip": "^1.2.3", + "@radix-ui/react-accordion": "^1.2.10", + "@radix-ui/react-alert-dialog": "^1.1.13", + "@radix-ui/react-aspect-ratio": "^1.1.6", + "@radix-ui/react-avatar": "^1.1.9", + "@radix-ui/react-checkbox": "^1.3.1", + "@radix-ui/react-collapsible": "^1.1.10", + "@radix-ui/react-context-menu": "^2.2.14", + "@radix-ui/react-dialog": "^1.1.13", + "@radix-ui/react-dropdown-menu": "^2.1.14", + "@radix-ui/react-hover-card": "^1.1.13", + "@radix-ui/react-label": "^2.1.6", + "@radix-ui/react-menubar": "^1.1.14", + "@radix-ui/react-navigation-menu": "^1.2.12", + "@radix-ui/react-popover": "^1.1.13", + "@radix-ui/react-progress": "^1.1.6", + "@radix-ui/react-radio-group": "^1.3.6", + "@radix-ui/react-scroll-area": "^1.2.8", + "@radix-ui/react-select": "^2.2.4", + "@radix-ui/react-separator": "^1.1.6", + "@radix-ui/react-slider": "^1.3.4", + "@radix-ui/react-slot": "^1.2.2", + "@radix-ui/react-switch": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.11", + "@radix-ui/react-toggle": "^1.1.8", + "@radix-ui/react-toggle-group": "^1.1.9", + "@radix-ui/react-tooltip": "^1.2.6", "@rsbuild/plugin-react": "^1.2.0", "@tanstack/react-router": "^1.112.13", "@tanstack/router-devtools": "^1.112.13", @@ -63,10 +63,10 @@ "next-themes": "^0.4.6", "oidc-client-rx": "0.1.0-alpha.9", "react": "^19.1.0", - "react-day-picker": "9.6.7", + "react-day-picker": "8.10.1", "react-dom": "^19.1.0", - "react-hook-form": "^7.56.0", - "react-resizable-panels": "^3.0.0", + "react-hook-form": "^7.56.3", + "react-resizable-panels": "^3.0.1", "recharts": "^2.15.3", "rxjs": "^7.8.2", "sonner": "^2.0.3", @@ -74,7 +74,8 @@ "tailwindcss": "^4.0.6", "tw-animate-css": "^1.2.7", "type-fest": "^4.40.0", - "vaul": "^1.1.2" + "vaul": "^1.1.2", + "zod": "^3.24.4" }, "devDependencies": { "@rsbuild/core": "^1.2.15", diff --git a/apps/webui/src/app/auth/context.ts b/apps/webui/src/app/auth/context.ts index fc895e5..5115fd7 100644 --- a/apps/webui/src/app/auth/context.ts +++ b/apps/webui/src/app/auth/context.ts @@ -1,4 +1,13 @@ import { AuthService } from '@/domains/auth/auth.service'; +import { AUTH_PROVIDER, type AuthProvider } from '@/infra/auth/auth.provider'; +import { BasicAuthProvider } from '@/infra/auth/basic'; +import { + AUTH_METHOD, + type AuthMethodType, + getAppAuthMethod, +} from '@/infra/auth/defs'; +import { OidcAuthProvider, buildOidcConfig } from '@/infra/auth/oidc'; +import { UnreachableError } from '@/infra/errors/common'; import type { Injector, Provider } from '@outposts/injection-js'; import type { AnyRouter } from '@tanstack/react-router'; import { @@ -9,16 +18,11 @@ import { } from 'oidc-client-rx'; import { withTanstackRouter } from 'oidc-client-rx/adapters/@tanstack/react-router'; import type { Observable } from 'rxjs'; -import { - AppAuthMethod, - AuthMethodEnum, - type AuthMethodType, - buildOidcConfig, -} from './config'; export function provideAuth(router: AnyRouter): Provider[] { const providers: Provider[] = [AuthService]; - if (AppAuthMethod === AuthMethodEnum.OIDC) { + const appAuthMethod = getAppAuthMethod(); + if (appAuthMethod === AUTH_METHOD.OIDC) { providers.push( ...provideOidcAuth( { @@ -32,18 +36,25 @@ export function provideAuth(router: AnyRouter): Provider[] { withCheckAuthResultEvent() ) ); + providers.push({ + provide: AUTH_PROVIDER, + useClass: OidcAuthProvider, + }); + } else if (appAuthMethod === AUTH_METHOD.BASIC) { + providers.push({ + provide: AUTH_PROVIDER, + useClass: BasicAuthProvider, + }); + } else { + throw new UnreachableError(`Unsupported auth method: ${appAuthMethod}`); } return providers; } -export function setupAuthContext(injector: Injector) { - const { authService } = authContextFromInjector(injector); - authService.setup(); -} - export interface AuthContext { type: AuthMethodType; authService: AuthService; + authProvider: AuthProvider; isAuthenticated$: Observable; userData$: Observable<{}>; checkAuthResultEvent$: Observable; @@ -51,11 +62,18 @@ export interface AuthContext { export function authContextFromInjector(injector: Injector): AuthContext { const authService = injector.get(AuthService); + const authProvider = injector.get(AUTH_PROVIDER); return { - type: AppAuthMethod, - authService, + type: authProvider.authMethod, isAuthenticated$: authService.isAuthenticated$, userData$: authService.userData$, checkAuthResultEvent$: authService.checkAuthResultEvent$, + authService, + authProvider, }; } + +export function setupAuthContext(injector: Injector) { + const { authService } = authContextFromInjector(injector); + authService.setup(); +} diff --git a/apps/webui/src/app/auth/guard.ts b/apps/webui/src/app/auth/guard.ts index 56da264..3bf7b5a 100644 --- a/apps/webui/src/app/auth/guard.ts +++ b/apps/webui/src/app/auth/guard.ts @@ -1,19 +1,17 @@ import type { RouterContext } from '@/infra/routes/traits'; -import { runInInjectionContext } from '@outposts/injection-js'; -import { autoLoginPartialRoutesGuard } from 'oidc-client-rx'; import { firstValueFrom } from 'rxjs'; import { authContextFromInjector } from './context'; export const beforeLoadGuard = async ({ context, }: { context: RouterContext }) => { - const { isAuthenticated$ } = authContextFromInjector(context.injector); + const { isAuthenticated$, authProvider } = authContextFromInjector( + context.injector + ); if (!(await firstValueFrom(isAuthenticated$))) { - const guard$ = runInInjectionContext(context.injector, () => - autoLoginPartialRoutesGuard() + const isAuthenticated = await firstValueFrom( + authProvider.autoLoginPartialRoutesGuard() ); - - const isAuthenticated = await firstValueFrom(guard$); if (!isAuthenticated) { throw !isAuthenticated; } diff --git a/apps/webui/src/app/auth/hooks.ts b/apps/webui/src/app/auth/hooks.ts index 394a5fa..2b50f14 100644 --- a/apps/webui/src/app/auth/hooks.ts +++ b/apps/webui/src/app/auth/hooks.ts @@ -26,9 +26,9 @@ export function useAuth() { ); return { + ...authContext, userData, injector, isAuthenticated, - authContext, }; } diff --git a/apps/webui/src/domains/auth/auth.service.ts b/apps/webui/src/domains/auth/auth.service.ts index a089134..5e22166 100644 --- a/apps/webui/src/domains/auth/auth.service.ts +++ b/apps/webui/src/domains/auth/auth.service.ts @@ -1,53 +1,15 @@ -import { AppAuthMethod, AuthMethodEnum } from '@/app/auth/config'; -import { injectInjector } from '@/infra/di/inject'; -import { Injectable, type Injector } from '@outposts/injection-js'; -import { - CHECK_AUTH_RESULT_EVENT, - type CheckAuthResultEventType, - OidcSecurityService, -} from 'oidc-client-rx'; -import { NEVER, type Observable, map, of } from 'rxjs'; - -const BASIC_AUTH_IS_AUTHENTICATED$ = of(true) as Observable; - -const BASIC_AUTH_USER_DATA$ = of({}); +import { AUTH_PROVIDER } from '@/infra/auth/auth.provider'; +import { Injectable, inject } from '@outposts/injection-js'; @Injectable() export class AuthService { - private injector: Injector = injectInjector(); - oidcSecurityService: OidcSecurityService | undefined; - checkAuthResultEvent$: Observable; - constructor() { - if (AppAuthMethod === 'oidc') { - this.oidcSecurityService = this.injector.get(OidcSecurityService); - this.checkAuthResultEvent$ = this.injector.get(CHECK_AUTH_RESULT_EVENT); - } else { - this.checkAuthResultEvent$ = NEVER; - } - } + private authProvider = inject(AUTH_PROVIDER); + + isAuthenticated$ = this.authProvider.isAuthenticated$; + userData$ = this.authProvider.userData$; + checkAuthResultEvent$ = this.authProvider.checkAuthResultEvent$; setup() { - if (AppAuthMethod === AuthMethodEnum.OIDC) { - this.oidcSecurityService!.checkAuth().subscribe(); - } - } - - get isAuthenticated$() { - return ( - this.oidcSecurityService?.isAuthenticated$.pipe( - map((s) => s.isAuthenticated) - ) ?? BASIC_AUTH_IS_AUTHENTICATED$ - ); - } - - get userData$() { - return ( - this.oidcSecurityService?.userData$?.pipe(map((s) => s.userData)) ?? - BASIC_AUTH_USER_DATA$ - ); - } - - getAccessToken(): Observable { - return this.oidcSecurityService?.getAccessToken() ?? of(undefined); + this.authProvider.setup(); } } diff --git a/apps/webui/src/infra/auth/auth.provider.ts b/apps/webui/src/infra/auth/auth.provider.ts new file mode 100644 index 0000000..6e9f5d2 --- /dev/null +++ b/apps/webui/src/infra/auth/auth.provider.ts @@ -0,0 +1,27 @@ +import { InjectionToken } from '@outposts/injection-js'; +import type { CheckAuthResultEventType } from 'oidc-client-rx'; +import { type Observable, map } from 'rxjs'; +import type { AuthMethodType } from './defs'; + +export abstract class AuthProvider { + abstract authMethod: AuthMethodType; + abstract checkAuthResultEvent$: Observable; + abstract isAuthenticated$: Observable; + abstract userData$: Observable; + abstract getAccessToken(): Observable; + abstract setup(): void; + abstract autoLoginPartialRoutesGuard(): Observable; + getAuthHeaders(): Observable> { + return this.getAccessToken().pipe( + map((accessToken) => + accessToken + ? { + Authorization: `Bearer ${accessToken}`, + } + : ({} as Record) + ) + ); + } +} + +export const AUTH_PROVIDER = new InjectionToken('AUTH_PROVIDER'); diff --git a/apps/webui/src/infra/auth/basic/basic-auth.provider.ts b/apps/webui/src/infra/auth/basic/basic-auth.provider.ts new file mode 100644 index 0000000..b653d99 --- /dev/null +++ b/apps/webui/src/infra/auth/basic/basic-auth.provider.ts @@ -0,0 +1,22 @@ +import { UnreachableError } from '@/infra/errors/common'; +import type { CheckAuthResultEventType } from 'oidc-client-rx'; +import { NEVER, type Observable, of } from 'rxjs'; +import { AuthProvider } from '../auth.provider'; +import { AUTH_METHOD } from '../defs'; + +export class BasicAuthProvider extends AuthProvider { + authMethod = AUTH_METHOD.BASIC; + isAuthenticated$ = of(true); + userData$ = of({}); + checkAuthResultEvent$: Observable = NEVER; + + getAccessToken(): Observable { + return of(undefined); + } + + setup(): void {} + + autoLoginPartialRoutesGuard(): Observable { + throw new UnreachableError('Basic auth should always be authenticated'); + } +} diff --git a/apps/webui/src/infra/auth/basic/index.ts b/apps/webui/src/infra/auth/basic/index.ts new file mode 100644 index 0000000..ea9b5da --- /dev/null +++ b/apps/webui/src/infra/auth/basic/index.ts @@ -0,0 +1 @@ +export { BasicAuthProvider } from './basic-auth.provider'; diff --git a/apps/webui/src/infra/auth/defs.ts b/apps/webui/src/infra/auth/defs.ts new file mode 100644 index 0000000..eb85dfd --- /dev/null +++ b/apps/webui/src/infra/auth/defs.ts @@ -0,0 +1,12 @@ +import type { ValueOf } from 'type-fest'; + +export const AUTH_METHOD = { + BASIC: 'basic', + OIDC: 'oidc', +} as const; + +export type AuthMethodType = ValueOf; + +export function getAppAuthMethod(): AuthMethodType { + return process.env.AUTH_TYPE as AuthMethodType; +} diff --git a/apps/webui/src/app/auth/config.ts b/apps/webui/src/infra/auth/oidc/config.ts similarity index 80% rename from apps/webui/src/app/auth/config.ts rename to apps/webui/src/infra/auth/oidc/config.ts index 216835a..3402ba5 100644 --- a/apps/webui/src/app/auth/config.ts +++ b/apps/webui/src/infra/auth/oidc/config.ts @@ -1,14 +1,4 @@ import { LogLevel, type OpenIdConfiguration } from 'oidc-client-rx'; -import type { ValueOf } from 'type-fest'; - -export const AuthMethodEnum = { - BASIC: 'basic', - OIDC: 'oidc', -} as const; - -export type AuthMethodType = ValueOf; - -export const AppAuthMethod = process.env.AUTH_TYPE as AuthMethodType; export function buildOidcConfig(): OpenIdConfiguration { const origin = window.location.origin; diff --git a/apps/webui/src/infra/auth/oidc/index.ts b/apps/webui/src/infra/auth/oidc/index.ts new file mode 100644 index 0000000..1df6761 --- /dev/null +++ b/apps/webui/src/infra/auth/oidc/index.ts @@ -0,0 +1,2 @@ +export { buildOidcConfig } from './config'; +export { OidcAuthProvider } from './oidc-auth.provider'; diff --git a/apps/webui/src/infra/auth/oidc/oidc-auth.provider.ts b/apps/webui/src/infra/auth/oidc/oidc-auth.provider.ts new file mode 100644 index 0000000..283e696 --- /dev/null +++ b/apps/webui/src/infra/auth/oidc/oidc-auth.provider.ts @@ -0,0 +1,41 @@ +import { injectInjector } from '@/infra/di/inject'; +import { inject, runInInjectionContext } from '@outposts/injection-js'; +import { + CHECK_AUTH_RESULT_EVENT, + OidcSecurityService, + autoLoginPartialRoutesGuard, +} from 'oidc-client-rx'; +import { type Observable, map } from 'rxjs'; +import { AuthProvider } from '../auth.provider'; +import { AUTH_METHOD } from '../defs'; + +export class OidcAuthProvider extends AuthProvider { + authMethod = AUTH_METHOD.OIDC; + oidcSecurityService = inject(OidcSecurityService); + checkAuthResultEvent$ = inject(CHECK_AUTH_RESULT_EVENT); + injector = injectInjector(); + + setup() { + this.oidcSecurityService.checkAuth().subscribe(); + } + + get isAuthenticated$() { + return this.oidcSecurityService.isAuthenticated$.pipe( + map((s) => s.isAuthenticated) + ); + } + + get userData$() { + return this.oidcSecurityService.userData$.pipe(map((s) => s.userData)); + } + + getAccessToken(): Observable { + return this.oidcSecurityService.getAccessToken(); + } + + autoLoginPartialRoutesGuard() { + return runInInjectionContext(this.injector, () => + autoLoginPartialRoutesGuard() + ); + } +} diff --git a/apps/webui/src/infra/graphql/context.ts b/apps/webui/src/infra/graphql/context.ts new file mode 100644 index 0000000..a29ddc2 --- /dev/null +++ b/apps/webui/src/infra/graphql/context.ts @@ -0,0 +1,16 @@ +import type { Injector, Provider } from '@outposts/injection-js'; +import { GraphQLService } from './graphql.service'; + +export function provideGraphql(): Provider[] { + return [GraphQLService]; +} + +export interface GraphQLContext { + graphqlService: GraphQLService; +} + +export function graphqlContextFromInjector(injector: Injector): GraphQLContext { + return { + graphqlService: injector.get(GraphQLService), + }; +} diff --git a/apps/webui/src/infra/graphql/graphql.service.ts b/apps/webui/src/infra/graphql/graphql.service.ts new file mode 100644 index 0000000..db933cf --- /dev/null +++ b/apps/webui/src/infra/graphql/graphql.service.ts @@ -0,0 +1,30 @@ +import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client'; +import { setContext } from '@apollo/client/link/context'; +import { Injectable, inject } from '@outposts/injection-js'; +import { firstValueFrom } from 'rxjs'; +import { AUTH_PROVIDER } from '../auth/auth.provider.ts'; + +@Injectable() +export class GraphQLService { + private authProvider = inject(AUTH_PROVIDER); + + private apiLink = createHttpLink({ + uri: '/api/graphql', + }); + + private authLink = setContext(async (_, { headers }) => { + const authHeaders = await firstValueFrom( + this.authProvider.getAuthHeaders() + ); + return { headers: { ...headers, ...authHeaders } }; + }); + + _apollo = new ApolloClient({ + link: this.authLink.concat(this.apiLink), + cache: new InMemoryCache(), + }); + + query = this._apollo.query; + mutate = this._apollo.mutate; + watchQuery = this._apollo.watchQuery; +} diff --git a/apps/webui/src/infra/graphql/index.ts b/apps/webui/src/infra/graphql/index.ts new file mode 100644 index 0000000..8321271 --- /dev/null +++ b/apps/webui/src/infra/graphql/index.ts @@ -0,0 +1,2 @@ +export { GraphQLService } from './graphql.service'; +export { provideGraphql } from './context'; diff --git a/apps/webui/src/infra/hooks/use-mobile.ts b/apps/webui/src/infra/hooks/use-mobile.ts deleted file mode 100644 index 462d7a7..0000000 --- a/apps/webui/src/infra/hooks/use-mobile.ts +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; - -const MOBILE_BREAKPOINT = 768; - -export function useIsMobile() { - const [isMobile, setIsMobile] = React.useState( - undefined - ); - - React.useEffect(() => { - const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); - const onChange = () => { - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); - }; - mql.addEventListener('change', onChange); - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); - return () => mql.removeEventListener('change', onChange); - }, []); - - return !!isMobile; -} diff --git a/apps/webui/src/infra/http/http.service.ts b/apps/webui/src/infra/http/http.service.ts deleted file mode 100644 index a6080cc..0000000 --- a/apps/webui/src/infra/http/http.service.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Injectable, inject } from '@outposts/injection-js'; -import { OidcSecurityService } from 'oidc-client-rx'; - -@Injectable() -export class HttpService { - authService = inject(OidcSecurityService); -} diff --git a/apps/webui/src/infra/routes/traits.ts b/apps/webui/src/infra/routes/traits.ts index a927de6..ff4da3d 100644 --- a/apps/webui/src/infra/routes/traits.ts +++ b/apps/webui/src/infra/routes/traits.ts @@ -1,4 +1,4 @@ -import type { ProLinkProps } from '@/components/ui/pro-link'; +import type { ProLinkProps } from '@/views/components/ui/pro-link'; import type { Injector } from '@outposts/injection-js'; import type { LucideIcon } from 'lucide-react'; diff --git a/apps/webui/src/infra/routes/utils.ts b/apps/webui/src/infra/routes/utils.ts index af6a758..d30e613 100644 --- a/apps/webui/src/infra/routes/utils.ts +++ b/apps/webui/src/infra/routes/utils.ts @@ -1,5 +1,5 @@ -import { guardRouteIndexAsNotFound } from '@/components/layout/app-not-found'; import type { RouteStateDataOption } from '@/infra/routes/traits'; +import { guardRouteIndexAsNotFound } from '@/views/components/layout/app-not-found'; import { Outlet } from '@tanstack/react-router'; export interface BuildVirtualBranchRouteOptions { diff --git a/apps/webui/src/main.tsx b/apps/webui/src/main.tsx index aaf8606..565f97b 100644 --- a/apps/webui/src/main.tsx +++ b/apps/webui/src/main.tsx @@ -3,6 +3,8 @@ import { provideAuth, setupAuthContext } from '@/app/auth/context'; import { providePlatform } from '@/infra/platform/context'; import { provideStorages } from '@/infra/storage/context'; import { provideStyles } from '@/infra/styles/context'; +import { AppNotFoundComponent } from '@/views/components/layout/app-not-found'; +import { routeTree } from '@/views/routeTree.gen'; import { type Injector, ReflectiveInjector } from '@outposts/injection-js'; import { RouterProvider, createRouter } from '@tanstack/react-router'; import { @@ -11,9 +13,10 @@ import { } from 'oidc-client-rx/adapters/react'; import { Suspense } from 'react'; import { createRoot } from 'react-dom/client'; -import { AppNotFoundComponent } from './components/layout/app-not-found'; -import { routeTree } from './routeTree.gen'; import './app.css'; +import { ApolloProvider } from '@apollo/client'; +import { provideGraphql } from './infra/graphql'; +import { graphqlContextFromInjector } from './infra/graphql/context'; // Create a new router instance const router = createRouter({ @@ -40,22 +43,27 @@ const injector: Injector = ReflectiveInjector.resolveAndCreate([ ...provideStorages(), ...provideAuth(router), ...provideStyles(), + ...provideGraphql(), ]); setupAuthContext(injector); const rootElement = document.getElementById('root'); +const { graphqlService } = graphqlContextFromInjector(injector); + const App = () => { return ( - + + + ); diff --git a/apps/webui/src/components/layout/app-icon.tsx b/apps/webui/src/views/components/layout/app-icon.tsx similarity index 91% rename from apps/webui/src/components/layout/app-icon.tsx rename to apps/webui/src/views/components/layout/app-icon.tsx index 9526392..1412256 100644 --- a/apps/webui/src/components/layout/app-icon.tsx +++ b/apps/webui/src/views/components/layout/app-icon.tsx @@ -2,9 +2,9 @@ import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, -} from '@/components/ui/sidebar'; +} from '@/views/components/ui/sidebar'; -import { Image } from '@/components/ui/image'; +import { Image } from '@/views/components/ui/image'; export function AppIcon() { return ( diff --git a/apps/webui/src/components/layout/app-layout.tsx b/apps/webui/src/views/components/layout/app-layout.tsx similarity index 94% rename from apps/webui/src/components/layout/app-layout.tsx rename to apps/webui/src/views/components/layout/app-layout.tsx index a67db47..c16bdb7 100644 --- a/apps/webui/src/components/layout/app-layout.tsx +++ b/apps/webui/src/views/components/layout/app-layout.tsx @@ -1,4 +1,6 @@ -import { AppSidebar } from '@/components/layout/app-sidebar'; +import type { RouteStateDataOption } from '@/infra/routes/traits'; +import type { RouteBreadcrumbItem } from '@/infra/routes/traits'; +import { AppSidebar } from '@/views/components/layout/app-sidebar'; import { Breadcrumb, BreadcrumbItem, @@ -6,16 +8,14 @@ import { BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, -} from '@/components/ui/breadcrumb'; -import { Separator } from '@/components/ui/separator'; +} from '@/views/components/ui/breadcrumb'; +import { Separator } from '@/views/components/ui/separator'; import { SidebarInset, SidebarProvider, SidebarTrigger, -} from '@/components/ui/sidebar'; -import type { RouteStateDataOption } from '@/infra/routes/traits'; -import type { RouteBreadcrumbItem } from '@/infra/routes/traits'; -import { cn } from '@/infra/styles/utils'; +} from '@/views/components/ui/sidebar'; +import { cn } from '@/views/utils'; import { useMatches } from '@tanstack/react-router'; import { type DetailedHTMLProps, diff --git a/apps/webui/src/components/layout/app-not-found.tsx b/apps/webui/src/views/components/layout/app-not-found.tsx similarity index 100% rename from apps/webui/src/components/layout/app-not-found.tsx rename to apps/webui/src/views/components/layout/app-not-found.tsx diff --git a/apps/webui/src/components/layout/app-sidebar.tsx b/apps/webui/src/views/components/layout/app-sidebar.tsx similarity index 95% rename from apps/webui/src/components/layout/app-sidebar.tsx rename to apps/webui/src/views/components/layout/app-sidebar.tsx index 4d23a44..f303f10 100644 --- a/apps/webui/src/components/layout/app-sidebar.tsx +++ b/apps/webui/src/views/components/layout/app-sidebar.tsx @@ -1,11 +1,11 @@ +import { AppNavMainData } from '@/infra/routes/nav'; import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarRail, -} from '@/components/ui/sidebar'; -import { AppNavMainData } from '@/infra/routes/nav'; +} from '@/views/components/ui/sidebar'; import type { ComponentPropsWithoutRef } from 'react'; import { AppIcon } from './app-icon'; import { NavMain } from './nav-main'; diff --git a/apps/webui/src/components/layout/app-skeleton.tsx b/apps/webui/src/views/components/layout/app-skeleton.tsx similarity index 100% rename from apps/webui/src/components/layout/app-skeleton.tsx rename to apps/webui/src/views/components/layout/app-skeleton.tsx diff --git a/apps/webui/src/components/layout/nav-main.tsx b/apps/webui/src/views/components/layout/nav-main.tsx similarity index 97% rename from apps/webui/src/components/layout/nav-main.tsx rename to apps/webui/src/views/components/layout/nav-main.tsx index 45209b0..b6bdb53 100644 --- a/apps/webui/src/components/layout/nav-main.tsx +++ b/apps/webui/src/views/components/layout/nav-main.tsx @@ -6,7 +6,7 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger, -} from '@/components/ui/collapsible'; +} from '@/views/components/ui/collapsible'; import { SidebarGroup, SidebarGroupLabel, @@ -16,7 +16,7 @@ import { SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, -} from '@/components/ui/sidebar'; +} from '@/views/components/ui/sidebar'; import { useMatches } from '@tanstack/react-router'; import { ProLink, type ProLinkProps } from '../ui/pro-link'; diff --git a/apps/webui/src/components/layout/nav-projects.tsx b/apps/webui/src/views/components/layout/nav-projects.tsx similarity index 96% rename from apps/webui/src/components/layout/nav-projects.tsx rename to apps/webui/src/views/components/layout/nav-projects.tsx index 0d7d745..2aff406 100644 --- a/apps/webui/src/components/layout/nav-projects.tsx +++ b/apps/webui/src/views/components/layout/nav-projects.tsx @@ -16,7 +16,7 @@ import { DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; +} from '@/views/components/ui/dropdown-menu'; import { SidebarGroup, SidebarGroupLabel, @@ -25,7 +25,7 @@ import { SidebarMenuButton, SidebarMenuItem, useSidebar, -} from '@/components/ui/sidebar'; +} from '@/views/components/ui/sidebar'; import type { ComponentProps } from 'react'; export function NavProjects({ diff --git a/apps/webui/src/components/layout/nav-user.tsx b/apps/webui/src/views/components/layout/nav-user.tsx similarity index 94% rename from apps/webui/src/components/layout/nav-user.tsx rename to apps/webui/src/views/components/layout/nav-user.tsx index f138ae2..5c2c452 100644 --- a/apps/webui/src/components/layout/nav-user.tsx +++ b/apps/webui/src/views/components/layout/nav-user.tsx @@ -9,7 +9,11 @@ import { Sparkles, } from 'lucide-react'; -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { + Avatar, + AvatarFallback, + AvatarImage, +} from '@/views/components/ui/avatar'; import { DropdownMenu, DropdownMenuContent, @@ -18,13 +22,13 @@ import { DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; +} from '@/views/components/ui/dropdown-menu'; import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar, -} from '@/components/ui/sidebar'; +} from '@/views/components/ui/sidebar'; export function NavUser({ user, diff --git a/apps/webui/src/components/ui/accordion.tsx b/apps/webui/src/views/components/ui/accordion.tsx similarity index 89% rename from apps/webui/src/components/ui/accordion.tsx rename to apps/webui/src/views/components/ui/accordion.tsx index b262424..e228043 100644 --- a/apps/webui/src/components/ui/accordion.tsx +++ b/apps/webui/src/views/components/ui/accordion.tsx @@ -1,13 +1,13 @@ -import * as React from "react" -import * as AccordionPrimitive from "@radix-ui/react-accordion" -import { ChevronDownIcon } from "lucide-react" +import * as AccordionPrimitive from "@radix-ui/react-accordion"; +import { ChevronDownIcon } from "lucide-react"; +import * as React from "react"; -import { cn } from "@/infra/styles/utils" +import { cn } from "@/views/utils"; function Accordion({ ...props }: React.ComponentProps) { - return + return ; } function AccordionItem({ @@ -20,7 +20,7 @@ function AccordionItem({ className={cn("border-b last:border-b-0", className)} {...props} /> - ) + ); } function AccordionTrigger({ @@ -42,7 +42,7 @@ function AccordionTrigger({ - ) + ); } function AccordionContent({ @@ -58,7 +58,7 @@ function AccordionContent({ >
{children}
- ) + ); } -export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; diff --git a/apps/webui/src/components/ui/alert-dialog.tsx b/apps/webui/src/views/components/ui/alert-dialog.tsx similarity index 94% rename from apps/webui/src/components/ui/alert-dialog.tsx rename to apps/webui/src/views/components/ui/alert-dialog.tsx index f387492..03fddb0 100644 --- a/apps/webui/src/components/ui/alert-dialog.tsx +++ b/apps/webui/src/views/components/ui/alert-dialog.tsx @@ -1,15 +1,15 @@ -"use client" +"use client"; -import * as React from "react" -import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; +import * as React from "react"; -import { cn } from "@/infra/styles/utils" -import { buttonVariants } from "@/components/ui/button" +import { buttonVariants } from "@/views/components/ui/button"; +import { cn } from "@/views/utils"; function AlertDialog({ ...props }: React.ComponentProps) { - return + return ; } function AlertDialogTrigger({ @@ -17,7 +17,7 @@ function AlertDialogTrigger({ }: React.ComponentProps) { return ( - ) + ); } function AlertDialogPortal({ @@ -25,7 +25,7 @@ function AlertDialogPortal({ }: React.ComponentProps) { return ( - ) + ); } function AlertDialogOverlay({ @@ -41,7 +41,7 @@ function AlertDialogOverlay({ )} {...props} /> - ) + ); } function AlertDialogContent({ @@ -60,7 +60,7 @@ function AlertDialogContent({ {...props} /> - ) + ); } function AlertDialogHeader({ @@ -73,7 +73,7 @@ function AlertDialogHeader({ className={cn("flex flex-col gap-2 text-center sm:text-left", className)} {...props} /> - ) + ); } function AlertDialogFooter({ @@ -89,7 +89,7 @@ function AlertDialogFooter({ )} {...props} /> - ) + ); } function AlertDialogTitle({ @@ -102,7 +102,7 @@ function AlertDialogTitle({ className={cn("text-lg font-semibold", className)} {...props} /> - ) + ); } function AlertDialogDescription({ @@ -115,7 +115,7 @@ function AlertDialogDescription({ className={cn("text-muted-foreground text-sm", className)} {...props} /> - ) + ); } function AlertDialogAction({ @@ -127,7 +127,7 @@ function AlertDialogAction({ className={cn(buttonVariants(), className)} {...props} /> - ) + ); } function AlertDialogCancel({ @@ -139,7 +139,7 @@ function AlertDialogCancel({ className={cn(buttonVariants({ variant: "outline" }), className)} {...props} /> - ) + ); } export { @@ -154,4 +154,4 @@ export { AlertDialogDescription, AlertDialogAction, AlertDialogCancel, -} +}; diff --git a/apps/webui/src/components/ui/alert.tsx b/apps/webui/src/views/components/ui/alert.tsx similarity index 87% rename from apps/webui/src/components/ui/alert.tsx rename to apps/webui/src/views/components/ui/alert.tsx index a65136c..6bc97f7 100644 --- a/apps/webui/src/components/ui/alert.tsx +++ b/apps/webui/src/views/components/ui/alert.tsx @@ -1,7 +1,7 @@ -import * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" +import { type VariantProps, cva } from "class-variance-authority"; +import * as React from "react"; -import { cn } from "@/infra/styles/utils" +import { cn } from "@/views/utils"; const alertVariants = cva( "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", @@ -17,7 +17,7 @@ const alertVariants = cva( variant: "default", }, } -) +); function Alert({ className, @@ -31,7 +31,7 @@ function Alert({ className={cn(alertVariants({ variant }), className)} {...props} /> - ) + ); } function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { @@ -44,7 +44,7 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { )} {...props} /> - ) + ); } function AlertDescription({ @@ -60,7 +60,7 @@ function AlertDescription({ )} {...props} /> - ) + ); } -export { Alert, AlertTitle, AlertDescription } +export { Alert, AlertTitle, AlertDescription }; diff --git a/apps/webui/src/components/ui/aspect-ratio.tsx b/apps/webui/src/views/components/ui/aspect-ratio.tsx similarity index 100% rename from apps/webui/src/components/ui/aspect-ratio.tsx rename to apps/webui/src/views/components/ui/aspect-ratio.tsx diff --git a/apps/webui/src/components/ui/avatar.tsx b/apps/webui/src/views/components/ui/avatar.tsx similarity index 81% rename from apps/webui/src/components/ui/avatar.tsx rename to apps/webui/src/views/components/ui/avatar.tsx index 795e165..3fab008 100644 --- a/apps/webui/src/components/ui/avatar.tsx +++ b/apps/webui/src/views/components/ui/avatar.tsx @@ -1,9 +1,9 @@ -"use client" +"use client"; -import * as React from "react" -import * as AvatarPrimitive from "@radix-ui/react-avatar" +import * as AvatarPrimitive from "@radix-ui/react-avatar"; +import * as React from "react"; -import { cn } from "@/infra/styles/utils" +import { cn } from "@/views/utils"; function Avatar({ className, @@ -18,7 +18,7 @@ function Avatar({ )} {...props} /> - ) + ); } function AvatarImage({ @@ -31,7 +31,7 @@ function AvatarImage({ className={cn("aspect-square size-full", className)} {...props} /> - ) + ); } function AvatarFallback({ @@ -47,7 +47,7 @@ function AvatarFallback({ )} {...props} /> - ) + ); } -export { Avatar, AvatarImage, AvatarFallback } +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/apps/webui/src/components/ui/badge.tsx b/apps/webui/src/views/components/ui/badge.tsx similarity index 84% rename from apps/webui/src/components/ui/badge.tsx rename to apps/webui/src/views/components/ui/badge.tsx index 2a6d599..68fc6bf 100644 --- a/apps/webui/src/components/ui/badge.tsx +++ b/apps/webui/src/views/components/ui/badge.tsx @@ -1,8 +1,8 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "@radix-ui/react-slot"; +import { type VariantProps, cva } from "class-variance-authority"; +import * as React from "react"; -import { cn } from "@/infra/styles/utils" +import { cn } from "@/views/utils"; const badgeVariants = cva( "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", @@ -23,7 +23,7 @@ const badgeVariants = cva( variant: "default", }, } -) +); function Badge({ className, @@ -32,7 +32,7 @@ function Badge({ ...props }: React.ComponentProps<"span"> & VariantProps & { asChild?: boolean }) { - const Comp = asChild ? Slot : "span" + const Comp = asChild ? Slot : "span"; return ( - ) + ); } -export { Badge, badgeVariants } +export { Badge, badgeVariants }; diff --git a/apps/webui/src/components/ui/breadcrumb.tsx b/apps/webui/src/views/components/ui/breadcrumb.tsx similarity index 88% rename from apps/webui/src/components/ui/breadcrumb.tsx rename to apps/webui/src/views/components/ui/breadcrumb.tsx index 4ded77b..5d6339d 100644 --- a/apps/webui/src/components/ui/breadcrumb.tsx +++ b/apps/webui/src/views/components/ui/breadcrumb.tsx @@ -1,11 +1,11 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { ChevronRight, MoreHorizontal } from "lucide-react" +import { Slot } from "@radix-ui/react-slot"; +import { ChevronRight, MoreHorizontal } from "lucide-react"; +import * as React from "react"; -import { cn } from "@/infra/styles/utils" +import { cn } from "@/views/utils"; function Breadcrumb({ ...props }: React.ComponentProps<"nav">) { - return