Skip to content

Commit f5a8b12

Browse files
Copilothotlong
andcommitted
feat: upgrade @objectstack packages to ^3.0.4 and add preview mode support
- Upgrade all @objectstack/* dependencies from ^3.0.2 to ^3.0.4 - Add PreviewModeOptions type and previewMode prop to AuthProvider - Add isPreviewMode and previewMode to AuthContextValue - Create PreviewBanner component for preview mode UI indication - Update ConditionalAuthWrapper to detect preview mode from discovery - Update console App to render PreviewBanner - Add tests for preview mode in AuthProvider and PreviewBanner Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 144572f commit f5a8b12

25 files changed

Lines changed: 563 additions & 232 deletions

File tree

apps/console/package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,13 @@
5858
"@object-ui/plugin-view": "workspace:*",
5959
"@object-ui/react": "workspace:*",
6060
"@object-ui/types": "workspace:*",
61-
"@objectstack/cli": "^3.0.2",
62-
"@objectstack/client": "^3.0.2",
63-
"@objectstack/driver-memory": "^3.0.2",
64-
"@objectstack/objectql": "^3.0.2",
65-
"@objectstack/plugin-msw": "^3.0.2",
66-
"@objectstack/runtime": "^3.0.2",
67-
"@objectstack/spec": "^3.0.2",
61+
"@objectstack/cli": "^3.0.4",
62+
"@objectstack/client": "^3.0.4",
63+
"@objectstack/driver-memory": "^3.0.4",
64+
"@objectstack/objectql": "^3.0.4",
65+
"@objectstack/plugin-msw": "^3.0.4",
66+
"@objectstack/runtime": "^3.0.4",
67+
"@objectstack/spec": "^3.0.4",
6868
"@tailwindcss/postcss": "^4.1.18",
6969
"@testing-library/jest-dom": "^6.9.1",
7070
"@testing-library/react": "^16.3.2",

apps/console/src/App.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { SchemaRendererProvider } from '@object-ui/react';
77
import { ObjectStackAdapter } from './dataSource';
88
import type { ConnectionState } from './dataSource';
99
import appConfig from '../objectstack.shared';
10-
import { AuthGuard, useAuth } from '@object-ui/auth';
10+
import { AuthGuard, useAuth, PreviewBanner } from '@object-ui/auth';
1111

1212
// Components (eagerly loaded — always needed)
1313
import { ConsoleLayout } from './components/ConsoleLayout';
@@ -387,6 +387,7 @@ export function App() {
387387
<ThemeProvider defaultTheme="system" storageKey="object-ui-theme">
388388
<ConsoleToaster position="bottom-right" />
389389
<ConditionalAuthWrapper authUrl="/api/auth">
390+
<PreviewBanner />
390391
<BrowserRouter basename={import.meta.env.BASE_URL?.replace(/\/$/, '') || '/'}>
391392
<Suspense fallback={<LoadingScreen />}>
392393
<Routes>

apps/console/src/components/ConditionalAuthWrapper.tsx

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
*
44
* This component fetches discovery information from the server and conditionally
55
* enables/disables authentication based on the server's auth service status.
6+
* Also detects preview mode from the server and configures the auth provider accordingly.
67
*/
78

89
import { useState, useEffect, ReactNode } from 'react';
910
import { ObjectStackAdapter } from '../dataSource';
1011
import { AuthProvider } from '@object-ui/auth';
12+
import type { PreviewModeOptions } from '@object-ui/auth';
1113
import { LoadingScreen } from './LoadingScreen';
1214
import type { DiscoveryInfo } from '@object-ui/react';
1315

@@ -23,11 +25,12 @@ interface ConditionalAuthWrapperProps {
2325
* 1. Creates a temporary data source connection
2426
* 2. Fetches discovery information from the server
2527
* 3. Checks if auth.enabled is true in the discovery response
26-
* 4. Conditionally wraps children with AuthProvider if auth is enabled
27-
* 5. Bypasses auth if discovery indicates auth is disabled (development/demo mode)
28+
* 4. Detects preview mode from discovery (mode === 'preview')
29+
* 5. Conditionally wraps children with AuthProvider with the appropriate config
2830
*/
2931
export function ConditionalAuthWrapper({ children, authUrl }: ConditionalAuthWrapperProps) {
3032
const [authEnabled, setAuthEnabled] = useState<boolean | null>(null);
33+
const [previewMode, setPreviewMode] = useState<PreviewModeOptions | undefined>(undefined);
3134
const [isLoading, setIsLoading] = useState(true);
3235

3336
useEffect(() => {
@@ -47,10 +50,24 @@ export function ConditionalAuthWrapper({ children, authUrl }: ConditionalAuthWra
4750
const discovery = await adapter.getDiscovery() as DiscoveryInfo | null;
4851

4952
if (!cancelled) {
50-
// Check if auth is enabled in discovery
51-
// Default to true if discovery doesn't provide this information
52-
const isAuthEnabled = discovery?.services?.auth?.enabled ?? true;
53-
setAuthEnabled(isAuthEnabled);
53+
// Detect preview mode from discovery
54+
if (discovery?.mode === 'preview') {
55+
setPreviewMode({
56+
autoLogin: discovery.previewMode?.autoLogin ?? true,
57+
simulatedRole: discovery.previewMode?.simulatedRole ?? 'admin',
58+
simulatedUserName: discovery.previewMode?.simulatedUserName ?? 'Preview User',
59+
readOnly: discovery.previewMode?.readOnly ?? false,
60+
expiresInSeconds: discovery.previewMode?.expiresInSeconds ?? 0,
61+
bannerMessage: discovery.previewMode?.bannerMessage,
62+
});
63+
// In preview mode, auth is effectively bypassed
64+
setAuthEnabled(false);
65+
} else {
66+
// Check if auth is enabled in discovery
67+
// Default to true if discovery doesn't provide this information
68+
const isAuthEnabled = discovery?.services?.auth?.enabled ?? true;
69+
setAuthEnabled(isAuthEnabled);
70+
}
5471
}
5572
} catch (error) {
5673
if (!cancelled) {
@@ -76,6 +93,15 @@ export function ConditionalAuthWrapper({ children, authUrl }: ConditionalAuthWra
7693
return <LoadingScreen />;
7794
}
7895

96+
// If in preview mode, wrap with a preview-configured AuthProvider
97+
if (previewMode) {
98+
return (
99+
<AuthProvider authUrl={authUrl} previewMode={previewMode}>
100+
{children}
101+
</AuthProvider>
102+
);
103+
}
104+
79105
// If auth is enabled, wrap with AuthProvider
80106
if (authEnabled) {
81107
return (

examples/crm/package.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,19 @@
2020
"dependencies": {
2121
"@hono/node-server": "^1.19.9",
2222
"@object-ui/console": "workspace:*",
23-
"@objectstack/core": "^3.0.2",
24-
"@objectstack/driver-memory": "^3.0.2",
25-
"@objectstack/objectql": "^3.0.2",
26-
"@objectstack/plugin-auth": "^3.0.2",
27-
"@objectstack/plugin-hono-server": "^3.0.2",
28-
"@objectstack/runtime": "^3.0.2",
29-
"@objectstack/spec": "^3.0.2",
23+
"@objectstack/core": "^3.0.4",
24+
"@objectstack/driver-memory": "^3.0.4",
25+
"@objectstack/objectql": "^3.0.4",
26+
"@objectstack/plugin-auth": "^3.0.4",
27+
"@objectstack/plugin-hono-server": "^3.0.4",
28+
"@objectstack/runtime": "^3.0.4",
29+
"@objectstack/spec": "^3.0.4",
3030
"hono": "^4.11.9",
3131
"pino": "^8.21.0",
3232
"pino-pretty": "^13.1.3"
3333
},
3434
"devDependencies": {
35-
"@objectstack/cli": "^3.0.2",
35+
"@objectstack/cli": "^3.0.4",
3636
"tsx": "^4.21.0",
3737
"typescript": "^5.9.3"
3838
}

examples/kitchen-sink/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
"build": "objectstack compile objectstack.config.ts"
1515
},
1616
"dependencies": {
17-
"@objectstack/spec": "^3.0.2"
17+
"@objectstack/spec": "^3.0.4"
1818
},
1919
"devDependencies": {
20-
"@objectstack/cli": "^3.0.2",
20+
"@objectstack/cli": "^3.0.4",
2121
"typescript": "^5.9.3"
2222
}
2323
}

examples/msw-todo/package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@
1111
},
1212
"dependencies": {
1313
"@object-ui/example-todo": "workspace:*",
14-
"@objectstack/client": "^3.0.2",
15-
"@objectstack/driver-memory": "^3.0.2",
16-
"@objectstack/objectql": "^3.0.2",
17-
"@objectstack/plugin-msw": "^3.0.2",
18-
"@objectstack/runtime": "^3.0.2",
19-
"@objectstack/spec": "^3.0.2",
14+
"@objectstack/client": "^3.0.4",
15+
"@objectstack/driver-memory": "^3.0.4",
16+
"@objectstack/objectql": "^3.0.4",
17+
"@objectstack/plugin-msw": "^3.0.4",
18+
"@objectstack/runtime": "^3.0.4",
19+
"@objectstack/spec": "^3.0.4",
2020
"react": "19.2.4",
2121
"react-dom": "19.2.4"
2222
},

examples/todo/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
"build": "objectstack compile objectstack.config.ts"
1313
},
1414
"dependencies": {
15-
"@objectstack/client": "^3.0.2",
16-
"@objectstack/spec": "^3.0.2"
15+
"@objectstack/client": "^3.0.4",
16+
"@objectstack/spec": "^3.0.4"
1717
},
1818
"devDependencies": {
19-
"@objectstack/cli": "^3.0.2",
19+
"@objectstack/cli": "^3.0.4",
2020
"typescript": "^5.9.3"
2121
}
2222
}

package.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,13 @@
6666
"devDependencies": {
6767
"@changesets/cli": "^2.29.8",
6868
"@eslint/js": "^9.39.2",
69-
"@objectstack/cli": "^3.0.2",
70-
"@objectstack/core": "^3.0.2",
71-
"@objectstack/driver-memory": "^3.0.2",
72-
"@objectstack/objectql": "^3.0.2",
73-
"@objectstack/plugin-msw": "^3.0.2",
74-
"@objectstack/runtime": "^3.0.2",
75-
"@objectstack/spec": "^3.0.2",
69+
"@objectstack/cli": "^3.0.4",
70+
"@objectstack/core": "^3.0.4",
71+
"@objectstack/driver-memory": "^3.0.4",
72+
"@objectstack/objectql": "^3.0.4",
73+
"@objectstack/plugin-msw": "^3.0.4",
74+
"@objectstack/runtime": "^3.0.4",
75+
"@objectstack/spec": "^3.0.4",
7676
"@playwright/test": "^1.58.2",
7777
"@storybook/addon-essentials": "^8.6.14",
7878
"@storybook/addon-interactions": "^8.6.14",
@@ -132,8 +132,8 @@
132132
},
133133
"dependencies": {
134134
"@hono/node-server": "^1.19.9",
135-
"@objectstack/plugin-hono-server": "^3.0.2",
136-
"@objectstack/studio": "^3.0.2",
135+
"@objectstack/plugin-hono-server": "^3.0.4",
136+
"@objectstack/studio": "^3.0.4",
137137
"coverage-v8": "0.0.1-security",
138138
"hono": "^4.11.9",
139139
"pino": "^8.21.0",

packages/auth/src/AuthContext.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import { createContext } from 'react';
10-
import type { AuthUser, AuthSession } from './types';
10+
import type { AuthUser, AuthSession, PreviewModeOptions } from './types';
1111

1212
export interface AuthContextValue {
1313
/** Current authenticated user */
@@ -20,6 +20,10 @@ export interface AuthContextValue {
2020
isLoading: boolean;
2121
/** Authentication error */
2222
error: Error | null;
23+
/** Whether the app is running in preview mode */
24+
isPreviewMode: boolean;
25+
/** Preview mode configuration (only set when isPreviewMode is true) */
26+
previewMode: PreviewModeOptions | null;
2327
/** Sign in with email and password */
2428
signIn: (email: string, password: string) => Promise<void>;
2529
/** Sign up with name, email, and password */

packages/auth/src/AuthProvider.tsx

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import React, { useState, useEffect, useCallback, useMemo } from 'react';
10-
import type { AuthUser, AuthClient, AuthProviderConfig } from './types';
10+
import type { AuthUser, AuthClient, AuthProviderConfig, PreviewModeOptions } from './types';
1111
import { AuthCtx, type AuthContextValue } from './AuthContext';
1212
import { createAuthClient } from './createAuthClient';
1313

@@ -20,6 +20,12 @@ export interface AuthProviderProps extends AuthProviderConfig {
2020
* @default true
2121
*/
2222
enabled?: boolean;
23+
/**
24+
* Preview mode configuration.
25+
* When provided, the auth provider auto-logs in a simulated user and bypasses
26+
* login/registration screens. Useful for marketplace demos and app showcases.
27+
*/
28+
previewMode?: PreviewModeOptions;
2329
}
2430

2531
/**
@@ -41,12 +47,19 @@ export interface AuthProviderProps extends AuthProviderConfig {
4147
* <App />
4248
* </AuthProvider>
4349
* ```
50+
* @example With preview mode (marketplace demo)
51+
* ```tsx
52+
* <AuthProvider authUrl="/api/auth" previewMode={{ simulatedRole: 'admin', bannerMessage: 'Demo mode' }}>
53+
* <App />
54+
* </AuthProvider>
55+
* ```
4456
*/
4557
export function AuthProvider({
4658
authUrl,
4759
client: externalClient,
4860
onAuthStateChange,
4961
enabled = true,
62+
previewMode,
5063
children,
5164
}: AuthProviderProps) {
5265
const client = useMemo<AuthClient>(
@@ -59,13 +72,38 @@ export function AuthProvider({
5972
const [isLoading, setIsLoading] = useState(true);
6073
const [error, setError] = useState<Error | null>(null);
6174

62-
// If auth is disabled, automatically set as authenticated with a guest user
63-
const isAuthenticated = enabled
75+
// Determine if we're in preview mode
76+
const isPreviewMode = previewMode != null;
77+
78+
// If auth is disabled or in preview mode, automatically set as authenticated
79+
const isAuthenticated = (enabled && !isPreviewMode)
6480
? user !== null && session !== null
6581
: true;
6682

67-
// Load session on mount (only if auth is enabled)
83+
// Load session on mount (only if auth is enabled and not in preview mode)
6884
useEffect(() => {
85+
if (isPreviewMode) {
86+
// Preview mode: simulate a user based on previewMode config
87+
const role = previewMode.simulatedRole ?? 'admin';
88+
const name = previewMode.simulatedUserName ?? 'Preview User';
89+
const expiresInSeconds = previewMode.expiresInSeconds ?? 0;
90+
setUser({
91+
id: 'preview-user',
92+
email: 'preview@preview.local',
93+
name,
94+
role,
95+
roles: [role],
96+
});
97+
setSession({
98+
token: 'preview-token',
99+
expiresAt: expiresInSeconds > 0
100+
? new Date(Date.now() + expiresInSeconds * 1000)
101+
: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000),
102+
});
103+
setIsLoading(false);
104+
return;
105+
}
106+
69107
if (!enabled) {
70108
// When auth is disabled, set a guest user and mark as loaded
71109
setUser({
@@ -103,7 +141,7 @@ export function AuthProvider({
103141

104142
loadSession();
105143
return () => { cancelled = true; };
106-
}, [client, enabled]);
144+
}, [client, enabled, isPreviewMode, previewMode]);
107145

108146
// Notify on auth state changes
109147
useEffect(() => {
@@ -218,14 +256,16 @@ export function AuthProvider({
218256
isAuthenticated,
219257
isLoading,
220258
error,
259+
isPreviewMode,
260+
previewMode: isPreviewMode ? previewMode : null,
221261
signIn,
222262
signUp,
223263
signOut,
224264
updateUser,
225265
forgotPassword,
226266
resetPassword,
227267
}),
228-
[user, session, isAuthenticated, isLoading, error, signIn, signUp, signOut, updateUser, forgotPassword, resetPassword],
268+
[user, session, isAuthenticated, isLoading, error, isPreviewMode, previewMode, signIn, signUp, signOut, updateUser, forgotPassword, resetPassword],
229269
);
230270

231271
return <AuthCtx.Provider value={value}>{children}</AuthCtx.Provider>;

0 commit comments

Comments
 (0)