Skip to content
Draft
Show file tree
Hide file tree
Changes from 14 commits
Commits
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: '[email protected]',
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: '[email protected]',
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",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you dont use this script? it can be done within .storybook configurations

"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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this script

/**
* 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