@fluid-app/fluid-cli-portal 0.1.6 → 0.1.8

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluid-app/fluid-cli-portal",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Fluid CLI plugin for building and deploying portal applications",
5
5
  "files": [
6
6
  "dist",
@@ -28,7 +28,7 @@
28
28
  "ora": "^8.0.0",
29
29
  "prompts": "^2.4.2",
30
30
  "tsx": "^4.21.0",
31
- "@fluid-app/fluid-cli": "0.1.2"
31
+ "@fluid-app/fluid-cli": "0.1.1"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@swc/core": "^1.15.18",
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "plugins": ["typescript", "react"],
4
+ "rules": {
5
+ "react/react-in-jsx-scope": "off",
6
+ "react/prop-types": "off"
7
+ },
8
+ "ignorePatterns": ["dist/**"]
9
+ }
@@ -1,159 +1,4 @@
1
1
  @import "tailwindcss";
2
2
  @import "@fluid-app/portal-sdk/globals.css";
3
- @source "../node_modules/@fluid-app/portal-sdk/dist/**/*.mjs";
4
3
 
5
- /*
6
- * Fallback design tokens (OKLCH format).
7
- *
8
- * The theme engine injects higher-specificity CSS via [data-theme="..."]
9
- * which overrides these at runtime for both light and dark modes.
10
- *
11
- * Canonical variables use the --color- prefix to match the css-generator
12
- * output and the widgets tailwind.config.ts color palette registration.
13
- * Short-name aliases (--background, --foreground, etc.) are included for
14
- * backward compatibility with shadcn/Radix components.
15
- */
16
-
17
- /*
18
- * Register CSS variables as Tailwind theme values.
19
- * `inline` means Tailwind won't emit its own --color-* declarations —
20
- * we define them ourselves in :root below (and the theme engine overrides
21
- * them at runtime via [data-theme="..."]).
22
- */
23
- @theme inline {
24
- --color-background: var(--background);
25
- --color-foreground: var(--foreground);
26
- --color-primary: var(--primary);
27
- --color-primary-foreground: var(--primary-foreground);
28
- --color-secondary: var(--secondary);
29
- --color-secondary-foreground: var(--secondary-foreground);
30
- --color-muted: var(--muted);
31
- --color-muted-foreground: var(--muted-foreground);
32
- --color-accent: var(--accent);
33
- --color-accent-foreground: var(--accent-foreground);
34
- --color-destructive: var(--destructive);
35
- --color-destructive-foreground: var(--destructive-foreground);
36
- --color-border: var(--border);
37
- --color-input: var(--input);
38
- --color-ring: var(--ring);
39
- --color-sidebar: var(--sidebar);
40
- --color-sidebar-foreground: var(--sidebar-foreground);
41
- --color-sidebar-primary: var(--sidebar-primary);
42
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
43
- --color-sidebar-accent: var(--sidebar-accent);
44
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
45
- --color-sidebar-border: var(--sidebar-border);
46
- --color-sidebar-ring: var(--sidebar-ring);
47
- --radius: var(--radius);
48
- }
49
-
50
- :root {
51
- --color-background: oklch(1 0 0);
52
- --color-foreground: oklch(0.145 0 0);
53
- --color-primary: oklch(0.205 0 0);
54
- --color-primary-foreground: oklch(0.985 0 0);
55
- --color-secondary: oklch(0.97 0 0);
56
- --color-secondary-foreground: oklch(0.205 0 0);
57
- --color-muted: oklch(0.97 0 0);
58
- --color-muted-foreground: oklch(0.556 0 0);
59
- --color-accent: oklch(0.97 0 0);
60
- --color-accent-foreground: oklch(0.205 0 0);
61
- --color-destructive: oklch(0.577 0.245 27.325);
62
- --color-destructive-foreground: oklch(0.985 0 0);
63
- --color-border: oklch(0.922 0 0);
64
-
65
- /* Short-name aliases (match css-generator globalCSSOverride) */
66
- --background: var(--color-background);
67
- --foreground: var(--color-foreground);
68
- --primary: var(--color-primary);
69
- --primary-foreground: var(--color-primary-foreground);
70
- --secondary: var(--color-secondary);
71
- --secondary-foreground: var(--color-secondary-foreground);
72
- --muted: var(--color-muted);
73
- --muted-foreground: var(--color-muted-foreground);
74
- --accent: var(--color-accent);
75
- --accent-foreground: var(--color-accent-foreground);
76
- --destructive: var(--color-destructive);
77
- --destructive-foreground: var(--color-destructive-foreground);
78
- --border: var(--color-border);
79
- --input: var(--color-border);
80
- --ring: var(--color-primary);
81
- --radius: 0.625rem;
82
-
83
- /* Sidebar aliases */
84
- --sidebar: var(--color-muted);
85
- --sidebar-foreground: var(--color-muted-foreground);
86
- --sidebar-primary: var(--color-primary);
87
- --sidebar-primary-foreground: var(--color-primary-foreground);
88
- --sidebar-accent: var(--color-accent);
89
- --sidebar-accent-foreground: var(--color-accent-foreground);
90
- --sidebar-border: var(--color-border);
91
- --sidebar-ring: var(--color-primary);
92
- }
93
-
94
- /*
95
- * Dark-mode fallback tokens.
96
- * Applied when the user toggles dark mode but no API theme is loaded.
97
- * The theme engine will override these with richer palette-derived values
98
- * once a theme is fetched from the API.
99
- */
100
- [data-theme-mode="dark"] {
101
- --color-background: oklch(0.145 0 0);
102
- --color-foreground: oklch(0.985 0 0);
103
- --color-primary: oklch(0.985 0 0);
104
- --color-primary-foreground: oklch(0.205 0 0);
105
- --color-secondary: oklch(0.215 0 0);
106
- --color-secondary-foreground: oklch(0.985 0 0);
107
- --color-muted: oklch(0.215 0 0);
108
- --color-muted-foreground: oklch(0.708 0 0);
109
- --color-accent: oklch(0.215 0 0);
110
- --color-accent-foreground: oklch(0.985 0 0);
111
- --color-destructive: oklch(0.577 0.245 27.325);
112
- --color-destructive-foreground: oklch(0.985 0 0);
113
- --color-border: oklch(0.3 0 0);
114
-
115
- --background: var(--color-background);
116
- --foreground: var(--color-foreground);
117
- --primary: var(--color-primary);
118
- --primary-foreground: var(--color-primary-foreground);
119
- --secondary: var(--color-secondary);
120
- --secondary-foreground: var(--color-secondary-foreground);
121
- --muted: var(--color-muted);
122
- --muted-foreground: var(--color-muted-foreground);
123
- --accent: var(--color-accent);
124
- --accent-foreground: var(--color-accent-foreground);
125
- --destructive: var(--color-destructive);
126
- --destructive-foreground: var(--color-destructive-foreground);
127
- --border: var(--color-border);
128
- --input: var(--color-border);
129
- --ring: var(--color-primary);
130
-
131
- --sidebar: var(--color-muted);
132
- --sidebar-foreground: var(--color-muted-foreground);
133
- --sidebar-primary: var(--color-primary);
134
- --sidebar-primary-foreground: var(--color-primary-foreground);
135
- --sidebar-accent: var(--color-accent);
136
- --sidebar-accent-foreground: var(--color-accent-foreground);
137
- --sidebar-border: var(--color-border);
138
- --sidebar-ring: var(--color-primary);
139
- }
140
-
141
- @layer base {
142
- * {
143
- @apply border-border;
144
- }
145
- body {
146
- @apply bg-background text-foreground;
147
- }
148
- }
149
-
150
- /* Hide scrollbars while preserving scroll functionality */
151
- @layer utilities {
152
- .scrollbar-none {
153
- -ms-overflow-style: none;
154
- scrollbar-width: none;
155
- }
156
- .scrollbar-none::-webkit-scrollbar {
157
- display: none;
158
- }
159
- }
4
+ /* Add your custom styles below */
@@ -1,42 +1,5 @@
1
- import { StrictMode } from "react";
2
- import { createRoot } from "react-dom/client";
3
- import {
4
- FluidAuthProvider,
5
- FluidProvider,
6
- RequireAuth,
7
- } from "@fluid-app/portal-sdk";
8
- import { authConfig, fluidConfig } from "./fluid.config";
9
- import App from "./App";
1
+ import { createPortal } from "@fluid-app/portal-sdk";
2
+ import { customPages } from "./portal.config";
10
3
  import "./index.css";
11
4
 
12
- // Find the root element
13
- const rootElement = document.getElementById("root");
14
- if (!rootElement) {
15
- throw new Error("Root element not found");
16
- }
17
-
18
- /**
19
- * App Wrapper with Authentication
20
- *
21
- * The provider hierarchy is:
22
- * 1. FluidAuthProvider - Handles token extraction, validation, storage
23
- * 2. FluidProvider - Fluid SDK context (API client, theme, QueryClientProvider)
24
- * 3. RequireAuth - Only renders children when authenticated
25
- *
26
- * Authentication flow:
27
- * 1. Parent Fluid app opens this portal with ?fluidUserToken=... in URL
28
- * 2. FluidAuthProvider extracts token, validates it, stores in cookie/localStorage
29
- * 3. Token is removed from URL (security)
30
- * 4. RequireAuth shows loading while checking, then renders app or error
31
- */
32
- createRoot(rootElement).render(
33
- <StrictMode>
34
- <FluidAuthProvider config={authConfig}>
35
- <FluidProvider config={fluidConfig}>
36
- <RequireAuth>
37
- <App />
38
- </RequireAuth>
39
- </FluidProvider>
40
- </FluidAuthProvider>
41
- </StrictMode>,
42
- );
5
+ createPortal({ customPages });
@@ -18,4 +18,9 @@ export const navigation = [
18
18
  slug: "dashboard",
19
19
  icon: "home",
20
20
  },
21
+ {
22
+ label: "My Site",
23
+ slug: "my-site",
24
+ icon: "globe",
25
+ },
21
26
  ];
@@ -1,22 +1,18 @@
1
1
  /**
2
2
  * Portal Configuration
3
3
  *
4
- * Register custom page components here. These are rendered by AppShell
5
- * when a navigation item's slug matches the key in this map.
4
+ * Register custom page components and widget plugins here.
6
5
  *
7
- * Navigation structure is defined in navigation.config.ts and synced to
8
- * the Fluid OS API on `fluid deploy`.
6
+ * - customPages: rendered by AppShell when a navigation slug matches
7
+ * - customWidgets: registered as plugin widgets in the builder and SDK
9
8
  *
10
- * QUICK START:
11
- * 1. Create a new screen component in ./screens/
12
- * 2. Import it below and add it to customPages
13
- * 3. Add a matching entry to src/navigation.config.ts
9
+ * Navigation structure is defined in navigation.config.ts and synced
10
+ * to the Fluid OS API on `fluid deploy`.
14
11
  */
15
12
 
16
13
  import type { ComponentType } from "react";
14
+ import type { WidgetManifest } from "@fluid-app/portal-core/registries";
17
15
  import { DashboardScreen } from "./screens/Dashboard";
18
- // Uncomment after installing form deps (pnpm add react-hook-form zod @hookform/resolvers):
19
- // import { ExampleFormScreen } from "./screens/ExampleForm";
20
16
 
21
17
  /**
22
18
  * Custom page components keyed by navigation slug.
@@ -30,3 +26,15 @@ export const customPages: Record<string, ComponentType> = {
30
26
  // Add more custom screens here:
31
27
  // "example-form": ExampleFormScreen,
32
28
  };
29
+
30
+ /**
31
+ * Custom widget plugins.
32
+ *
33
+ * Each manifest registers a widget in both the builder palette
34
+ * and the SDK runtime. Add manifests here after scaffolding
35
+ * with `fluid widget create <name>`.
36
+ */
37
+ export const customWidgets: WidgetManifest[] = [
38
+ // Import and add widget manifests here:
39
+ // stockTicker,
40
+ ];
@@ -1,16 +1,10 @@
1
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2
- // @ts-nocheck — This file requires optional dependencies (react-hook-form, zod, @hookform/resolvers).
3
- // Install them before removing this directive: pnpm add react-hook-form zod @hookform/resolvers
4
1
  /**
5
2
  * Example Form Screen
6
3
  *
7
4
  * Demonstrates how to build forms in a portal using
8
5
  * React Hook Form + Zod for validation.
9
6
  *
10
- * SETUP: Install form dependencies before using this screen:
11
- * pnpm add react-hook-form zod @hookform/resolvers
12
- *
13
- * Then uncomment the nav entry and import in portal.config.ts.
7
+ * Uncomment the nav entry and import in portal.config.ts to enable.
14
8
  */
15
9
 
16
10
  import { useForm } from "react-hook-form";
@@ -51,10 +45,9 @@ export function ExampleFormScreen() {
51
45
  resolver: zodResolver(contactFormSchema),
52
46
  });
53
47
 
54
- const onSubmit = async (data: ContactFormData) => {
48
+ const onSubmit = async (_data: ContactFormData) => {
55
49
  // Replace with your API call, e.g.:
56
- // const result = await apiClient.post("/contacts", data);
57
- console.log("Form submitted:", data);
50
+ // const result = await apiClient.post("/contacts", _data);
58
51
 
59
52
  // Simulate API delay
60
53
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -77,7 +70,7 @@ export function ExampleFormScreen() {
77
70
  <div className="border-border bg-background rounded-lg border p-6 shadow-sm">
78
71
  {isSubmitSuccessful ? (
79
72
  <div className="border-accent bg-accent text-accent-foreground rounded-lg border p-4 text-sm">
80
- Form submitted successfully! Check the console for the data.
73
+ Form submitted successfully!
81
74
  </div>
82
75
  ) : (
83
76
  <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
@@ -9,7 +9,7 @@
9
9
  "start": "node dist/server/entry.js",
10
10
  "preview": "vite preview",
11
11
  "typecheck": "tsc --noEmit",
12
- "lint": "eslint src/",
12
+ "lint": "oxlint",
13
13
  "db:generate": "drizzle-kit generate",
14
14
  "db:migrate": "drizzle-kit migrate",
15
15
  "db:push": "drizzle-kit push",
@@ -33,7 +33,6 @@
33
33
  },
34
34
  "devDependencies": {
35
35
  "@fluid-app/fluid-cli-portal": "{{sdkVersion}}",
36
- "@eslint/js": "^9.0.0",
37
36
  "@hono/vite-dev-server": "^0.25.0",
38
37
  "@tailwindcss/vite": "^4.0.0",
39
38
  "esbuild": "^0.25.0",
@@ -42,11 +41,9 @@
42
41
  "@types/react-dom": "^19.0.0",
43
42
  "@vitejs/plugin-react": "^4.3.0",
44
43
  "drizzle-kit": "^0.31.0",
45
- "eslint": "^9.0.0",
46
- "eslint-plugin-react-hooks": "^5.0.0",
44
+ "oxlint": "^1.51.0",
47
45
  "tailwindcss": "^4.0.0",
48
46
  "typescript": "^5.6.0",
49
- "typescript-eslint": "^8.0.0",
50
47
  "vite": "^6.0.0",
51
48
  "vitest": "^4.0.0"
52
49
  },
@@ -2,11 +2,13 @@ import { defineConfig } from "vite";
2
2
  import react from "@vitejs/plugin-react";
3
3
  import tailwindcss from "@tailwindcss/vite";
4
4
  import devServer from "@hono/vite-dev-server";
5
+ import { fluidManifestPlugin } from "@fluid-app/portal-sdk/vite";
5
6
 
6
7
  export default defineConfig({
7
8
  plugins: [
8
9
  react(),
9
10
  tailwindcss(),
11
+ fluidManifestPlugin(),
10
12
  devServer({
11
13
  entry: "src/server/index.ts",
12
14
  // Only send /api/* requests to Hono — let Vite handle everything else
@@ -7,7 +7,8 @@
7
7
  "dev": "vite",
8
8
  "build": "tsc -b && vite build",
9
9
  "preview": "vite preview",
10
- "typecheck": "tsc --noEmit"
10
+ "typecheck": "tsc --noEmit",
11
+ "lint": "oxlint"
11
12
  },
12
13
  "dependencies": {
13
14
  "@fluid-app/portal-sdk": "{{sdkVersion}}",
@@ -24,6 +25,7 @@
24
25
  "@types/react-dom": "^19.0.0",
25
26
  "@vitejs/plugin-react": "^4.3.0",
26
27
  "tailwindcss": "^4.0.0",
28
+ "oxlint": "^1.51.0",
27
29
  "typescript": "^5.6.0",
28
30
  "vite": "^6.0.0"
29
31
  },
@@ -1,12 +1,28 @@
1
1
  import { defineConfig } from "vite";
2
2
  import react from "@vitejs/plugin-react";
3
3
  import tailwindcss from "@tailwindcss/vite";
4
+ import { fluidManifestPlugin } from "@fluid-app/portal-sdk/vite";
4
5
 
5
- // https://vite.dev/config/
6
6
  export default defineConfig({
7
- plugins: [react(), tailwindcss()],
7
+ plugins: [react(), tailwindcss(), fluidManifestPlugin()],
8
8
  build: {
9
- // Target modern browsers for smaller bundles
10
9
  target: "esnext",
10
+ chunkSizeWarningLimit: 600,
11
+ rollupOptions: {
12
+ output: {
13
+ manualChunks(id) {
14
+ if (
15
+ id.includes("node_modules/react-dom") ||
16
+ id.includes("node_modules/react/") ||
17
+ id.includes("node_modules/scheduler")
18
+ ) {
19
+ return "vendor";
20
+ }
21
+ if (id.includes("node_modules/@tanstack/react-query")) {
22
+ return "query";
23
+ }
24
+ },
25
+ },
26
+ },
11
27
  },
12
28
  });
@@ -1,18 +0,0 @@
1
- import { AppShell } from "@fluid-app/portal-sdk";
2
- import { customPages } from "./portal.config";
3
-
4
- /**
5
- * Main App component
6
- *
7
- * Uses the AppShell from @fluid-app/portal-sdk which provides:
8
- * - Collapsible sidebar with navigation from your Fluid profile
9
- * - Header with sub-tabs and theme mode toggle
10
- * - Polished content area with rounded corners and shadow
11
- * - Path-based routing for SPA navigation
12
- *
13
- * Custom pages are registered in portal.config.ts.
14
- * Navigation and themes are fetched from the Fluid API.
15
- */
16
- export default function App() {
17
- return <AppShell customPages={customPages} />;
18
- }
@@ -1,13 +0,0 @@
1
- import js from "@eslint/js";
2
- import tseslint from "typescript-eslint";
3
- import reactHooks from "eslint-plugin-react-hooks";
4
-
5
- export default tseslint.config(
6
- js.configs.recommended,
7
- ...tseslint.configs.recommended,
8
- {
9
- plugins: { "react-hooks": reactHooks },
10
- rules: reactHooks.configs.recommended.rules,
11
- },
12
- { ignores: ["dist/", "*.config.*"] },
13
- );
@@ -1,62 +0,0 @@
1
- import type { FluidSDKConfig, FluidAuthConfig } from "@fluid-app/portal-sdk";
2
- import { getStoredToken } from "@fluid-app/portal-sdk";
3
-
4
- /**
5
- * Fluid SDK Configuration
6
- *
7
- * This file contains the configuration for connecting to your Fluid Commerce account.
8
- * Authentication is handled by FluidAuthProvider, which automatically:
9
- * - Extracts tokens from URL parameters (passed by parent Fluid app)
10
- * - Stores tokens in cookies and localStorage
11
- * - Validates token expiration
12
- */
13
-
14
- /**
15
- * Authentication configuration
16
- *
17
- * Pattern: safety-satisfies-over-type-annotation
18
- * Using `satisfies FluidAuthConfig` validates the object shape against the type
19
- * WITHOUT widening the inferred types. This means:
20
- * - TypeScript checks that all required properties are present
21
- * - TypeScript checks that property types are compatible
22
- * - The actual inferred types remain as specific as possible
23
- *
24
- * Compare to type annotation (`const authConfig: FluidAuthConfig = {...}`):
25
- * - Type annotation widens types to match the interface exactly
26
- * - `satisfies` preserves literal types while validating structure
27
- */
28
- export const authConfig = {
29
- // Auth failure defaults: SDK automatically redirects to https://auth.fluid.app
30
- // with a redirect_url back to the current page. To customize:
31
- // onAuthFailure: () => { window.location.href = "/custom-login"; },
32
- // authUrl: "https://custom-auth.example.com",
33
- } satisfies FluidAuthConfig;
34
-
35
- /**
36
- * API client configuration
37
- *
38
- * Pattern: safety-satisfies-over-type-annotation
39
- * See authConfig above for explanation of the `satisfies` pattern.
40
- *
41
- * This config is passed to FluidProvider to configure the API client.
42
- * The callbacks (getAuthToken, onAuthError) are called by the SDK
43
- * when making API requests.
44
- */
45
- export const fluidConfig = {
46
- /**
47
- * Base URL for the Fluid API
48
- * Change this to your Fluid instance URL if self-hosted
49
- */
50
- baseUrl: import.meta.env.VITE_API_URL ?? "https://api.fluid.app",
51
-
52
- /**
53
- * Returns the authentication token for API requests
54
- * This reads from the token stored by FluidAuthProvider
55
- */
56
- getAuthToken: () => {
57
- return getStoredToken() ?? null;
58
- },
59
-
60
- // 401 errors default: SDK automatically redirects to https://auth.fluid.app
61
- // To customize: onAuthError: () => { clearTokens(); window.location.reload(); },
62
- } satisfies FluidSDKConfig;
@@ -1,62 +0,0 @@
1
- import type { FluidSDKConfig, FluidAuthConfig } from "@fluid-app/portal-sdk";
2
- import { getStoredToken } from "@fluid-app/portal-sdk";
3
-
4
- /**
5
- * Fluid SDK Configuration
6
- *
7
- * This file contains the configuration for connecting to your Fluid Commerce account.
8
- * Authentication is handled by FluidAuthProvider, which automatically:
9
- * - Extracts tokens from URL parameters (passed by parent Fluid app)
10
- * - Stores tokens in cookies and localStorage
11
- * - Validates token expiration
12
- */
13
-
14
- /**
15
- * Authentication configuration
16
- *
17
- * Pattern: safety-satisfies-over-type-annotation
18
- * Using `satisfies FluidAuthConfig` validates the object shape against the type
19
- * WITHOUT widening the inferred types. This means:
20
- * - TypeScript checks that all required properties are present
21
- * - TypeScript checks that property types are compatible
22
- * - The actual inferred types remain as specific as possible
23
- *
24
- * Compare to type annotation (`const authConfig: FluidAuthConfig = {...}`):
25
- * - Type annotation widens types to match the interface exactly
26
- * - `satisfies` preserves literal types while validating structure
27
- */
28
- export const authConfig = {
29
- // Auth failure defaults: SDK automatically redirects to https://auth.fluid.app
30
- // with a redirect_url back to the current page. To customize:
31
- // onAuthFailure: () => { window.location.href = "/custom-login"; },
32
- // authUrl: "https://custom-auth.example.com",
33
- } satisfies FluidAuthConfig;
34
-
35
- /**
36
- * API client configuration
37
- *
38
- * Pattern: safety-satisfies-over-type-annotation
39
- * See authConfig above for explanation of the `satisfies` pattern.
40
- *
41
- * This config is passed to FluidProvider to configure the API client.
42
- * The callbacks (getAuthToken, onAuthError) are called by the SDK
43
- * when making API requests.
44
- */
45
- export const fluidConfig = {
46
- /**
47
- * Base URL for the Fluid API
48
- * Change this to your Fluid instance URL if self-hosted
49
- */
50
- baseUrl: import.meta.env.VITE_API_URL ?? "https://api.fluid.app",
51
-
52
- /**
53
- * Returns the authentication token for API requests
54
- * This reads from the token stored by FluidAuthProvider
55
- */
56
- getAuthToken: () => {
57
- return getStoredToken() ?? null;
58
- },
59
-
60
- // 401 errors default: SDK automatically redirects to https://auth.fluid.app
61
- // To customize: onAuthError: () => { clearTokens(); window.location.reload(); },
62
- } satisfies FluidSDKConfig;