201 lines
5.9 KiB
TypeScript
201 lines
5.9 KiB
TypeScript
import type { Meta, StoryObj } from '@storybook/nextjs'
|
|
import { useEffect, useMemo, useState } from 'react'
|
|
import { CredentialTypeEnum } from '@/app/components/plugins/plugin-auth/types'
|
|
import { NotionPageSelector } from '.'
|
|
import type { DataSourceCredential } from '@/app/components/header/account-setting/data-source-page-new/types'
|
|
import type { NotionPage } from '@/models/common'
|
|
|
|
const DATASET_ID = 'dataset-demo'
|
|
const CREDENTIALS: DataSourceCredential[] = [
|
|
{
|
|
id: 'cred-1',
|
|
name: 'Marketing Workspace',
|
|
type: CredentialTypeEnum.OAUTH2,
|
|
is_default: true,
|
|
avatar_url: '',
|
|
credential: {
|
|
workspace_name: 'Marketing Workspace',
|
|
workspace_icon: null,
|
|
workspace_id: 'workspace-1',
|
|
},
|
|
},
|
|
{
|
|
id: 'cred-2',
|
|
name: 'Product Workspace',
|
|
type: CredentialTypeEnum.OAUTH2,
|
|
is_default: false,
|
|
avatar_url: '',
|
|
credential: {
|
|
workspace_name: 'Product Workspace',
|
|
workspace_icon: null,
|
|
workspace_id: 'workspace-2',
|
|
},
|
|
},
|
|
]
|
|
|
|
const marketingPages = {
|
|
notion_info: [
|
|
{
|
|
workspace_name: 'Marketing Workspace',
|
|
workspace_id: 'workspace-1',
|
|
workspace_icon: null,
|
|
pages: [
|
|
{
|
|
page_icon: { type: 'emoji', emoji: '\u{1F4CB}', url: null },
|
|
page_id: 'briefs',
|
|
page_name: 'Campaign Briefs',
|
|
parent_id: 'root',
|
|
type: 'page',
|
|
is_bound: false,
|
|
},
|
|
{
|
|
page_icon: { type: 'emoji', emoji: '\u{1F4DD}', url: null },
|
|
page_id: 'notes',
|
|
page_name: 'Meeting Notes',
|
|
parent_id: 'root',
|
|
type: 'page',
|
|
is_bound: true,
|
|
},
|
|
{
|
|
page_icon: { type: 'emoji', emoji: '\u{1F30D}', url: null },
|
|
page_id: 'localizations',
|
|
page_name: 'Localization Pipeline',
|
|
parent_id: 'briefs',
|
|
type: 'page',
|
|
is_bound: false,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
|
|
const productPages = {
|
|
notion_info: [
|
|
{
|
|
workspace_name: 'Product Workspace',
|
|
workspace_id: 'workspace-2',
|
|
workspace_icon: null,
|
|
pages: [
|
|
{
|
|
page_icon: { type: 'emoji', emoji: '\u{1F4A1}', url: null },
|
|
page_id: 'ideas',
|
|
page_name: 'Idea Backlog',
|
|
parent_id: 'root',
|
|
type: 'page',
|
|
is_bound: false,
|
|
},
|
|
{
|
|
page_icon: { type: 'emoji', emoji: '\u{1F9EA}', url: null },
|
|
page_id: 'experiments',
|
|
page_name: 'Experiments',
|
|
parent_id: 'ideas',
|
|
type: 'page',
|
|
is_bound: false,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
|
|
type NotionApiResponse = typeof marketingPages
|
|
const emptyNotionResponse: NotionApiResponse = { notion_info: [] }
|
|
|
|
const useMockNotionApi = () => {
|
|
const responseMap = useMemo(() => ({
|
|
[`${DATASET_ID}:cred-1`]: marketingPages,
|
|
[`${DATASET_ID}:cred-2`]: productPages,
|
|
}) satisfies Record<`${typeof DATASET_ID}:${typeof CREDENTIALS[number]['id']}`, NotionApiResponse>, [])
|
|
|
|
useEffect(() => {
|
|
const originalFetch = globalThis.fetch?.bind(globalThis)
|
|
|
|
const handler = async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
const url = typeof input === 'string'
|
|
? input
|
|
: input instanceof URL
|
|
? input.toString()
|
|
: input.url
|
|
|
|
if (url.includes('/notion/pre-import/pages')) {
|
|
const parsed = new URL(url, globalThis.location.origin)
|
|
const datasetId = parsed.searchParams.get('dataset_id') || ''
|
|
const credentialId = parsed.searchParams.get('credential_id') || ''
|
|
let payload: NotionApiResponse = emptyNotionResponse
|
|
|
|
if (datasetId === DATASET_ID) {
|
|
const credential = CREDENTIALS.find(item => item.id === credentialId)
|
|
if (credential) {
|
|
const mapKey = `${DATASET_ID}:${credential.id}` as keyof typeof responseMap
|
|
payload = responseMap[mapKey]
|
|
}
|
|
}
|
|
|
|
return new Response(
|
|
JSON.stringify(payload),
|
|
{ headers: { 'Content-Type': 'application/json' }, status: 200 },
|
|
)
|
|
}
|
|
|
|
if (originalFetch)
|
|
return originalFetch(input, init)
|
|
|
|
throw new Error(`Unmocked fetch call for ${url}`)
|
|
}
|
|
|
|
globalThis.fetch = handler as typeof globalThis.fetch
|
|
|
|
return () => {
|
|
if (originalFetch)
|
|
globalThis.fetch = originalFetch
|
|
}
|
|
}, [responseMap])
|
|
}
|
|
|
|
const NotionSelectorPreview = () => {
|
|
const [selectedPages, setSelectedPages] = useState<NotionPage[]>([])
|
|
const [credentialId, setCredentialId] = useState<string>()
|
|
|
|
useMockNotionApi()
|
|
|
|
return (
|
|
<div className="flex w-full max-w-3xl flex-col gap-4 rounded-2xl border border-divider-subtle bg-components-panel-bg p-6">
|
|
<NotionPageSelector
|
|
datasetId={DATASET_ID}
|
|
credentialList={CREDENTIALS}
|
|
value={selectedPages.map(page => page.page_id)}
|
|
onSelect={setSelectedPages}
|
|
onSelectCredential={setCredentialId}
|
|
canPreview
|
|
/>
|
|
<div className="rounded-xl border border-divider-subtle bg-background-default-subtle p-4 text-xs text-text-secondary">
|
|
<div className="mb-2 font-semibold uppercase tracking-[0.18em] text-text-tertiary">
|
|
Debug state
|
|
</div>
|
|
<p className="mb-1">Active credential: <span className="font-mono">{credentialId || 'None'}</span></p>
|
|
<pre className="max-h-40 overflow-auto rounded-lg bg-background-default p-3 font-mono text-[11px] leading-relaxed text-text-tertiary">
|
|
{JSON.stringify(selectedPages, null, 2)}
|
|
</pre>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const meta = {
|
|
title: 'Base/Other/NotionPageSelector',
|
|
component: NotionSelectorPreview,
|
|
parameters: {
|
|
layout: 'centered',
|
|
docs: {
|
|
description: {
|
|
component: 'Credential-aware selector that fetches Notion pages and lets users choose which ones to sync.',
|
|
},
|
|
},
|
|
},
|
|
tags: ['autodocs'],
|
|
} satisfies Meta<typeof NotionSelectorPreview>
|
|
|
|
export default meta
|
|
type Story = StoryObj<typeof meta>
|
|
|
|
export const Playground: Story = {}
|