Skip to content
Draft
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
769739c
Initial plan
Copilot Jul 17, 2025
9927d3f
Implement comprehensive Storybook stories for authentication components
Copilot Jul 17, 2025
e1e3bde
format: Apply prettier --fix changes
Copilot Jul 19, 2025
8a85ba8
Remove isolated components and use real auth components with mocked s…
Copilot Jul 20, 2025
b173cba
Fix CICD error: Remove direct Firebase hook mocking from auth stories
Copilot Jul 23, 2025
6c0ff0b
Fix build error: Replace @storybook/test with vitest mocking for Stor…
Copilot Aug 12, 2025
34cd28e
fix: Resolve Vercel build errors and improve Storybook 9 compatibility
snomiao Aug 21, 2025
2fb0990
format: Apply prettier --fix changes
snomiao Oct 2, 2025
556b247
Merge remote-tracking branch 'origin/main' into copilot/fix-167
snomiao Oct 25, 2025
b0e441f
refactor: Relocate auth stories to components directory following new…
snomiao Oct 25, 2025
1b1cd5a
format: Apply prettier --fix changes
snomiao Oct 25, 2025
835e438
Merge branch 'main' into copilot/fix-167
snomiao Oct 25, 2025
4eb7839
fix: Ensure Storybook uses /_storybook/ prefix for all asset paths
snomiao Oct 26, 2025
4357eb7
format: Apply prettier --fix changes
snomiao Oct 26, 2025
986b630
refactor: Remove fix-storybook-paths.ts and use Vite base config
snomiao Oct 26, 2025
f66df2f
Merge branch 'main' into copilot/fix-167
snomiao May 15, 2026
0012090
format: Apply prettier --fix changes
snomiao May 15, 2026
7dc4360
fix(storybook): remove react-firebase-hooks/auth mock breaking existi…
snomiao May 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 4 additions & 20 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,19 @@ export default defineConfig({
],
framework: '@storybook/nextjs-vite',
staticDirs: ['../src/assets'],
// Inject base tag for manager (navigation/toolbar) when deploying to /_storybook/
managerHead:
process.env.CHROMATIC !== 'true'
? (head) => `
${head}
<base href="/_storybook/" />
`
: undefined,
// Inject base tag for preview (iframe where components render) when deploying to /_storybook/
previewHead:
process.env.CHROMATIC !== 'true'
? (head) => `
${head}
<base href="/_storybook/" />
`
: undefined,
viteFinal: async (c, { configType }) => {
// Dynamically import the plugin to avoid build issues
if (!createMockResolverPlugin) {
const mockPlugin = await import('./mockResolverPlugin.js')
createMockResolverPlugin = mockPlugin.createMockResolverPlugin
}

const isProduction = configType === 'PRODUCTION'
const isChromatic = process.env.CHROMATIC === 'true'

return mergeConfig(c, {
// Only set custom base path for production builds (not for Chromatic)
base:
configType === 'PRODUCTION' && process.env.CHROMATIC !== 'true'
? '/_storybook/'
: c.base,
base: isProduction && !isChromatic ? '/_storybook/' : c.base,
server: {
allowedHosts: true,
hmr: { clientPort: 443 },
Expand Down
91 changes: 91 additions & 0 deletions components/AuthUI/AuthUI.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Meta, StoryObj } from '@storybook/nextjs-vite'
import { HttpResponse, http } from 'msw'
import AuthUI from '@/components/AuthUI/AuthUI'
import { User } from '@/src/api/generated'
import {
mockFirebaseUser,
useFirebaseUser,
} from '@/src/hooks/useFirebaseUser.mock'
import { CAPI } from '@/src/mocks/apibase'
import { handlers } from '@/src/mocks/handlers'

const meta = {
title: 'Components/AuthUI/SignIn',
component: AuthUI,
parameters: {
layout: 'fullscreen',
backgrounds: { default: 'dark' },
msw: {
handlers: handlers,
},
},
decorators: [
(Story) => (
<div className="bg-gray-900 min-h-screen">
<Story />
</div>
),
],
} satisfies Meta<typeof AuthUI>

export default meta

type Story = StoryObj<typeof meta>

// Mock user data
const mockUser: User = {
id: 'user-123',
name: 'John Doe',
email: 'john.doe@example.com',
isAdmin: false,
isApproved: true,
}

export const Default: Story = {
parameters: {
msw: {
handlers: [
http.get(CAPI('/users'), () =>
HttpResponse.json(null, { status: 401 })
),
...handlers,
],
},
},
async beforeEach() {
// Mock Firebase user as logged out
useFirebaseUser.mockReturnValue([null, false, undefined])
},
}

export const Loading: Story = {
parameters: {
msw: {
handlers: [
http.get(CAPI('/users'), () =>
HttpResponse.json(null, { status: 401 })
),
...handlers,
],
},
},
async beforeEach() {
// Mock Firebase user as loading
useFirebaseUser.mockReturnValue([null, true, undefined])
},
}

export const AlreadyLoggedIn: Story = {
parameters: {
msw: {
handlers: [
http.get(CAPI('/users'), () => HttpResponse.json(mockUser)),
...handlers,
],
},
},
async beforeEach() {
// Mock Firebase user as logged in
useFirebaseUser.mockReturnValue([mockFirebaseUser, false, undefined])
},
}
74 changes: 74 additions & 0 deletions components/AuthUI/Logout.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Meta, StoryObj } from '@storybook/nextjs-vite'
import { HttpResponse, http } from 'msw'
import Logout from '@/components/AuthUI/Logout'
import { User } from '@/src/api/generated'
import {
mockFirebaseUser,
useFirebaseUser,
} from '@/src/hooks/useFirebaseUser.mock'
import { CAPI } from '@/src/mocks/apibase'
import { handlers } from '@/src/mocks/handlers'

const meta = {
title: 'Components/AuthUI/Logout',
component: Logout,
parameters: {
layout: 'fullscreen',
backgrounds: { default: 'dark' },
msw: {
handlers: handlers,
},
},
decorators: [
(Story) => (
<div className="bg-gray-900 min-h-screen">
<Story />
</div>
),
],
} satisfies Meta<typeof Logout>

export default meta

type Story = StoryObj<typeof meta>

// Mock user data
const mockUser: User = {
id: 'user-123',
name: 'John Doe',
email: 'john.doe@example.com',
isAdmin: false,
isApproved: true,
}

export const Default: Story = {
parameters: {
msw: {
handlers: [
http.get(CAPI('/users'), () => HttpResponse.json(mockUser)),
...handlers,
],
},
},
async beforeEach() {
// Mock Firebase user as logged in
useFirebaseUser.mockReturnValue([mockFirebaseUser, false, undefined])
},
}

export const LoggedOut: Story = {
parameters: {
msw: {
handlers: [
http.get(CAPI('/users'), () =>
HttpResponse.json(null, { status: 401 })
),
...handlers,
],
},
},
async beforeEach() {
// Mock Firebase user as logged out
useFirebaseUser.mockReturnValue([null, false, undefined])
},
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"build": "concurrently \"next build\" \"bun run build-storybook\"",
"build-storybook": "storybook build -o public/_storybook",
"build-storybook": "storybook build -o public/_storybook && bun scripts/fix-storybook-paths.ts",
Comment thread
snomiao marked this conversation as resolved.
Outdated
"chromatic": "npx chromatic --project-token=$CHROMATIC_PROJECT_TOKEN",
"dev": "next dev",
"fix": "next lint --fix .",
Expand Down
20 changes: 20 additions & 0 deletions react-firebase-hooks/auth.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { vi } from 'vitest'

// Mock the Firebase auth hooks for Storybook
export const useSignInWithGoogle = vi.fn().mockName('useSignInWithGoogle')
export const useSignInWithGithub = vi.fn().mockName('useSignInWithGithub')
export const useSignOut = vi.fn().mockName('useSignOut')
export const useAuthState = vi.fn().mockName('useAuthState')

// Set default return values
useSignInWithGoogle.mockReturnValue([vi.fn(), undefined, false, undefined])
useSignInWithGithub.mockReturnValue([vi.fn(), undefined, false, undefined])
useSignOut.mockReturnValue([vi.fn(), false, undefined])
useAuthState.mockReturnValue([null, false, undefined])

console.log('mocking react-firebase-hooks/auth', {
useSignInWithGoogle,
useSignInWithGithub,
useSignOut,
useAuthState,
})
68 changes: 68 additions & 0 deletions scripts/fix-storybook-paths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env bun
Comment thread
snomiao marked this conversation as resolved.
Outdated
/**
* Fix Storybook paths to use /_storybook/ prefix
* This script updates index.html and iframe.html to use absolute paths
* so Storybook works correctly when deployed under /_storybook/ subdirectory
*/

import { readFileSync, writeFileSync } from 'fs'
import { join } from 'path'

const STORYBOOK_DIR = 'public/_storybook'
const BASE_PATH = '/_storybook/'

function fixPaths(filePath: string) {
console.log(`Fixing paths in ${filePath}...`)

let content = readFileSync(filePath, 'utf-8')

// Replace all relative paths with absolute paths using /_storybook/ prefix
// Match patterns like: href="./something" or src="./something" or import './something'
content = content.replace(
/(['"])\.\/([^'"]+)(['"])/g,
(match, quote1, path, quote2) => {
// Don't modify if it's already absolute
if (path.startsWith('/') || path.startsWith('http')) {
return match
}
return `${quote1}${BASE_PATH}${path}${quote2}`
}
)

// Also fix absolute paths that should be under /_storybook/
// Match patterns like: src="/vite-inject-mocker-entry.js"
content = content.replace(
/(['"])\/([^'"\/][^'"]*\.(?:js|css|json|svg|png|jpg|jpeg|gif|woff|woff2))(['"])/g,
(match, quote1, path, quote2) => {
// Skip if it's already under /_storybook/ or is an external URL
if (
path.startsWith('_storybook/') ||
path.startsWith('http') ||
path.startsWith('//')
) {
return match
}
return `${quote1}${BASE_PATH}${path}${quote2}`
}
)

writeFileSync(filePath, content, 'utf-8')
console.log(`βœ“ Fixed ${filePath}`)
}

// Fix both index.html (manager UI) and iframe.html (preview frame)
const filesToFix = [
join(process.cwd(), STORYBOOK_DIR, 'index.html'),
join(process.cwd(), STORYBOOK_DIR, 'iframe.html'),
]

for (const file of filesToFix) {
try {
fixPaths(file)
} catch (error) {
console.error(`Error fixing ${file}:`, error)
process.exit(1)
}
}

console.log('\nβœ… All Storybook paths fixed successfully!')
8 changes: 4 additions & 4 deletions src/hooks/useFirebaseUser.mock.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { fn } from '@storybook/test'
import { User as FirebaseUser } from 'firebase/auth'
import { vi } from 'vitest'
import * as actual from './useFirebaseUser'

export * from './useFirebaseUser'
Expand All @@ -24,8 +25,7 @@ export const mockFirebaseUser = {
providerId: 'google',
} satisfies FirebaseUser // Using 'as any' to avoid having to mock the entire Firebase User interface

export const useFirebaseUser = fn(actual.useFirebaseUser)
export const useFirebaseUser = vi
.fn(actual.useFirebaseUser)
.mockName('useFirebaseUser')
.mockReturnValue([mockFirebaseUser, false, undefined])

import { User as FirebaseUser } from 'firebase/auth'
Loading