import '@fontsource/dm-mono/400.css';
import '@fontsource/dm-mono/500.css';
import '@fontsource/inter/400.css';
import '@fontsource/inter/500.css';
import '@fontsource/inter/600.css';
import '@fontsource/inter/700.css';
import { i18n } from '@lingui/core';
import { I18nProvider } from '@lingui/react';
import { type Preview } from '@storybook/react-vite';
import { http, HttpResponse } from 'msw';
import { initialize, mswLoader } from 'msw-storybook-addon';
import { SOURCE_LOCALE } from 'twenty-shared/translations';
// oxlint-disable-next-line no-restricted-imports
import { DateFormat } from '../src/modules/localization/constants/DateFormat';
// oxlint-disable-next-line no-restricted-imports
import { TimeFormat } from '../src/modules/localization/constants/TimeFormat';
// oxlint-disable-next-line no-restricted-imports
import { FileUploadProvider } from '../src/modules/file-upload/components/FileUploadProvider';
// oxlint-disable-next-line no-restricted-imports
import { RootDecorator } from '../src/testing/decorators/RootDecorator';
// oxlint-disable-next-line no-restricted-imports
import { resetJotaiStore } from '../src/modules/ui/utilities/state/jotai/jotaiStore';
// oxlint-disable-next-line no-restricted-imports
import { UserContext } from '../src/modules/users/contexts/UserContext';
// Stories rendering CodeEditor / GraphiQL need Monaco's worker factory, which
// the app normally sets up in src/index.tsx.
// oxlint-disable-next-line no-restricted-imports
import '../src/modules/app/utils/setupMonacoEnvironment';
import 'react-loading-skeleton/dist/skeleton.css';
import 'twenty-ui/style.css';
import 'twenty-ui/theme-light.css';
import 'twenty-ui/theme-dark.css';
import { ThemeProvider } from 'twenty-ui/theme-constants';
// oxlint-disable-next-line no-restricted-imports
import { messages as enMessages } from '../src/locales/generated/en';
i18n.load({ [SOURCE_LOCALE]: enMessages });
i18n.activate(SOURCE_LOCALE);
// oxlint-disable-next-line no-restricted-imports
import { ClickOutsideListenerContext } from '../src/modules/ui/utilities/pointer-event/contexts/ClickOutsideListenerContext';
const MOCK_IMAGE_SVG =
'';
const respondWithMockImage = () =>
new HttpResponse(MOCK_IMAGE_SVG, {
headers: { 'Content-Type': 'image/svg+xml' },
});
const remoteImageMockHandlers = [
http.get('https://picsum.photos/*', respondWithMockImage),
http.get('https://twenty-icons.com/*', respondWithMockImage),
http.get('https://twentyhq.github.io/*', respondWithMockImage),
http.get('https://via.placeholder.com/*', respondWithMockImage),
http.get(
'https://twenty-front-screenshots.s3.eu-west-3.amazonaws.com/*',
respondWithMockImage,
),
];
initialize(
{
onUnhandledRequest: async (request: Request) => {
const fileExtensionsToIgnore =
/\.(ts|tsx|js|jsx|svg|css|png|woff2)(\?v=[a-zA-Z0-9]+)?/;
if (fileExtensionsToIgnore.test(request.url)) {
return;
}
if (request.url.startsWith('http://localhost:3000/files/')) {
return;
}
try {
const requestBody = await request.json();
// oxlint-disable-next-line no-console
console.warn(`Unhandled ${request.method} request to ${request.url}
with payload ${JSON.stringify(requestBody)}\n
This request should be mocked with MSW`);
} catch (error) {
// oxlint-disable-next-line no-console
console.error(`Cannot parse msw request body : ${error}`);
}
// oxlint-disable-next-line no-console
console.warn(
`Unhandled ${request.method} request to ${request.url} \n This request should be mocked with MSW`,
);
},
quiet: true,
},
remoteImageMockHandlers,
);
// Mirrors production's MinimalMetadataGater so any story rendering a
// date-aware component (DateTimeDisplay, etc.) sees a real IANA timeZone
// instead of UserContext's default `{}`. Stories needing a specific timezone
// can still override by nesting their own UserContext.Provider.
const STORYBOOK_DEFAULT_USER_CONTEXT = {
dateFormat: DateFormat.DAY_FIRST,
timeFormat: TimeFormat.HOUR_24,
timeZone: 'UTC',
};
const INTER_FONT_WEIGHTS = ['400', '500', '600'];
const waitForInterFontLoadedBeforeScreenshot = async () => {
await Promise.all(
INTER_FONT_WEIGHTS.map((weight) =>
document.fonts.load(`${weight} 1em Inter`),
),
);
await document.fonts.ready;
};
const preview: Preview = {
decorators: [
(Story) => {
return (
);
},
RootDecorator,
],
beforeEach: () => {
resetJotaiStore();
},
loaders: [mswLoader, waitForInterFontLoadedBeforeScreenshot],
parameters: {
argos: {
// CSS zoom combined with viewport-sized stories makes the iframe grow during capture.
fitToContent: { zoom: 1 },
},
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
mockingDate: new Date('2024-03-12T09:30:00.000Z'),
options: {
storySort: {
order: ['UI', 'Modules', 'Pages'],
},
},
},
};
export default preview;