diff --git a/packages/venia-concept/package.json b/packages/venia-concept/package.json index 0512fc5fb6..41a8b7429d 100644 --- a/packages/venia-concept/package.json +++ b/packages/venia-concept/package.json @@ -58,9 +58,7 @@ "@magento/pagebuilder": "9.3.4-alpha9", "@magento/peregrine": "15.5.1-alpha9", "@magento/pwa-theme-venia": "~2.4.0", - "@magento/recommendations-js-sdk": "~2.0.7", - "@magento/upward-security-headers": "~1.0.14", - "@magento/venia-data-collector": "~1.0.7", + "@magento/upward-security-headers": "1.1.18-alpha9", "@magento/venia-ui": "11.7.0-alpha9", "@pmmmwh/react-refresh-webpack-plugin": "0.4.1", "@storybook/react": "~6.3.7", diff --git a/packages/venia-concept/src/.storybook/config.js b/packages/venia-concept/src/.storybook/config.js deleted file mode 100644 index 95b22f22d2..0000000000 --- a/packages/venia-concept/src/.storybook/config.js +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import { configure, addDecorator } from '@storybook/react'; -import Adapter from '@magento/venia-ui/lib/components/Adapter'; -import store from '../store'; -import '@magento/venia-ui/lib/index.module.css'; -import 'tailwindcss/tailwind.css'; - -const loadStories = () => { - // Load all stories from venia-ui - const veniaContext = require.context( - '../../node_modules/@magento/venia-ui/lib', - true, - /__stories__\/.+\.js$/ - ); - veniaContext.keys().forEach(veniaContext); - - // Load all custom defined stories in src - const customContext = require.context('..', true, /__stories__\/.+\.js$/); - customContext.keys().forEach(customContext); -}; - -const origin = process.env.MAGENTO_BACKEND_URL; - -addDecorator(storyFn => ( - - {storyFn()} - -)); - -configure(loadStories, module); diff --git a/packages/venia-concept/src/.storybook/main.js b/packages/venia-concept/src/.storybook/main.js new file mode 100644 index 0000000000..d110c14d4c --- /dev/null +++ b/packages/venia-concept/src/.storybook/main.js @@ -0,0 +1,115 @@ +module.exports = { + stories: [ + '../../node_modules/@magento/venia-ui/lib/**/__stories__/*.js', + '../**/__stories__/*.js' + ], + addons: [], + + // Fix for Manager webpack build (Storybook UI) + managerWebpack: async (config, options) => { + // Add babel rule for react-draggable in manager build + config.module.rules.push({ + test: /\.js$/, + include: /node_modules\/react-draggable/, + use: { + loader: 'babel-loader', + options: { + presets: [ + [ + '@babel/preset-env', + { + targets: { + browsers: ['last 2 versions', 'ie >= 11'] + } + } + ] + ] + } + } + }); + + return config; + }, + + // Fix for Preview webpack build (Story iframe) + webpackFinal: async (config, { configType }) => { + // Import the existing webpack config logic + const path = require('path'); + const { + graphQL: { + getPossibleTypes, + getStoreConfigData, + getAvailableStoresConfigData + }, + Utilities: { loadEnvironment } + } = require('@magento/pwa-buildpack'); + const baseWebpackConfig = require('../../webpack.config'); + const { DefinePlugin, EnvironmentPlugin } = require('webpack'); + const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); + + const projectConfig = await loadEnvironment( + path.resolve(__dirname, '../..') + ); + + if (projectConfig.error) { + throw projectConfig.error; + } + + const possibleTypes = await getPossibleTypes(); + const storeConfigData = await getStoreConfigData(); + const { availableStores } = await getAvailableStoresConfigData(); + global.LOCALE = storeConfigData.locale.replace('_', '-'); + + const [webpackConfig] = await baseWebpackConfig(configType); + + config.module = webpackConfig.module; + config.resolve = webpackConfig.resolve; + + // Add babel rule for react-draggable in preview build too + config.module.rules.push({ + test: /\.js$/, + include: /node_modules\/react-draggable/, + use: { + loader: 'babel-loader', + options: { + presets: [ + [ + '@babel/preset-env', + { + targets: { + browsers: ['last 2 versions', 'ie >= 11'] + } + } + ] + ] + } + } + }); + + + // Make sure to provide any plugins that UI code may depend on. + config.plugins = [ + ...config.plugins, + new DefinePlugin({ + __fetchLocaleData__: async () => { + // no-op in storybook + }, + POSSIBLE_TYPES: JSON.stringify(possibleTypes), + STORE_NAME: JSON.stringify('Storybook'), + STORE_VIEW_LOCALE: JSON.stringify(global.LOCALE), + STORE_VIEW_CODE: process.env.STORE_VIEW_CODE + ? JSON.stringify(process.env.STORE_VIEW_CODE) + : JSON.stringify(storeConfigData.code), + AVAILABLE_STORE_VIEWS: JSON.stringify(availableStores), + DEFAULT_LOCALE: JSON.stringify(global.LOCALE), + DEFAULT_COUNTRY_CODE: JSON.stringify( + process.env.DEFAULT_COUNTRY_CODE || 'US' + ) + }), + new EnvironmentPlugin(projectConfig.env), + new ReactRefreshWebpackPlugin() + ]; + + return config; + } +}; diff --git a/packages/venia-concept/src/.storybook/preview.js b/packages/venia-concept/src/.storybook/preview.js new file mode 100644 index 0000000000..7f7a7febef --- /dev/null +++ b/packages/venia-concept/src/.storybook/preview.js @@ -0,0 +1,168 @@ +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import Adapter from '@magento/venia-ui/lib/components/Adapter'; +import { Form } from 'informed'; +import store from '../store'; +import '@magento/venia-ui/lib/index.module.css'; +import 'tailwindcss/tailwind.css'; + +// Mock browser APIs that components might expect +if (typeof window !== 'undefined') { + // Ensure URLSearchParams is available with comprehensive methods + if (!window.URLSearchParams) { + window.URLSearchParams = class URLSearchParams { + constructor(search = '') { + this.params = new Map(); + if (typeof search === 'string') { + if (search.startsWith('?')) search = search.slice(1); + if (search) { + search.split('&').forEach(param => { + const [key, value] = param.split('='); + if (key) + this.params.set( + decodeURIComponent(key), + decodeURIComponent(value || '') + ); + }); + } + } + } + get(key) { + return this.params.get(key); + } + set(key, value) { + this.params.set(key, value); + } + delete(key) { + this.params.delete(key); + } + has(key) { + return this.params.has(key); + } + append(key, value) { + this.params.set(key, value); + } + toString() { + const entries = Array.from(this.params.entries()); + return entries + .map( + ([k, v]) => + `${encodeURIComponent(k)}=${encodeURIComponent(v)}` + ) + .join('&'); + } + forEach(callback) { + this.params.forEach(callback); + } + keys() { + return this.params.keys(); + } + values() { + return this.params.values(); + } + entries() { + return this.params.entries(); + } + }; + } + + // Create comprehensive URL object mock + if (!window.URL) { + window.URL = class URL { + constructor(url, base) { + this.href = url || 'http://localhost:9001/?page=1'; + this.origin = 'http://localhost:9001'; + this.protocol = 'http:'; + this.host = 'localhost:9001'; + this.hostname = 'localhost'; + this.port = '9001'; + this.pathname = '/'; + this.search = '?page=1'; + this.hash = ''; + } + toString() { + return this.href; + } + }; + } + + // Mock location.search and other properties that might be undefined + try { + if (!window.location.search || window.location.search === '') { + Object.defineProperty(window.location, 'search', { + value: '?page=1', + writable: true, + configurable: true + }); + } + if (!window.location.pathname || window.location.pathname === '') { + Object.defineProperty(window.location, 'pathname', { + value: '/', + writable: true, + configurable: true + }); + } + } catch (e) { + // Some browsers don't allow modifying location + console.warn('Could not mock location properties:', e); + } + + // Global string replacement protection + const originalStringReplace = String.prototype.replace; + String.prototype.replace = function(...args) { + if (this == null || this === undefined) { + console.warn('Attempted to call replace on undefined/null value'); + return ''; + } + return originalStringReplace.apply(this, args); + }; +} + +const origin = + process.env.MAGENTO_BACKEND_URL || + 'https://master-7rqtwti-c5v7sxvquxwl4.eu-4.magentosite.cloud/'; + +// Form wrapper for components that need form context +const FormWrapper = ({ children }) => { + return
{children}
; +}; + +// Router wrapper for components that need routing context +const RouterWrapper = ({ children }) => { + // Provide initial location with comprehensive data that components might expect + const initialEntries = [ + { + pathname: '/', + search: '?page=1', + hash: '', + state: null, + key: 'default' + } + ]; + + return ( + {children} + ); +}; + +export const decorators = [ + Story => ( + + + + + + + + ) +]; + +export const parameters = { + actions: { argTypesRegex: '^on[A-Z].*' }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/ + } + } +}; diff --git a/packages/venia-concept/src/.storybook/webpack.config.js b/packages/venia-concept/src/.storybook/webpack.config.js deleted file mode 100644 index 0981879c70..0000000000 --- a/packages/venia-concept/src/.storybook/webpack.config.js +++ /dev/null @@ -1,61 +0,0 @@ -const path = require('path'); -const { - graphQL: { - getPossibleTypes, - getStoreConfigData, - getAvailableStoresConfigData - }, - Utilities: { loadEnvironment } -} = require('@magento/pwa-buildpack'); -const baseWebpackConfig = require('../../webpack.config'); -const { DefinePlugin, EnvironmentPlugin } = require('webpack'); -const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); - -// Storybook 5.2.8 uses a different signature for webpack config than webpack -// defines in the docs. -// See https://storybook.js.org/docs/configurations/custom-webpack-config/#full-control-mode -module.exports = async ({ config: storybookBaseConfig, mode }) => { - const projectConfig = await loadEnvironment( - // Load .env from root - path.resolve(__dirname, '../..') - ); - - if (projectConfig.error) { - throw projectConfig.error; - } - - const possibleTypes = await getPossibleTypes(); - const storeConfigData = await getStoreConfigData(); - const { availableStores } = await getAvailableStoresConfigData(); - global.LOCALE = storeConfigData.locale.replace('_', '-'); - - const [webpackConfig] = await baseWebpackConfig(mode); - - storybookBaseConfig.module = webpackConfig.module; - storybookBaseConfig.resolve = webpackConfig.resolve; - - // Make sure to provide any plugins that UI code may depend on. - storybookBaseConfig.plugins = [ - ...storybookBaseConfig.plugins, - new DefinePlugin({ - __fetchLocaleData__: async () => { - // no-op in storybook - }, - POSSIBLE_TYPES: JSON.stringify(possibleTypes), - STORE_NAME: JSON.stringify('Storybook'), - STORE_VIEW_LOCALE: JSON.stringify(global.LOCALE), - STORE_VIEW_CODE: process.env.STORE_VIEW_CODE - ? JSON.stringify(process.env.STORE_VIEW_CODE) - : JSON.stringify(storeConfigData.code), - AVAILABLE_STORE_VIEWS: JSON.stringify(availableStores), - DEFAULT_LOCALE: JSON.stringify(global.LOCALE), - DEFAULT_COUNTRY_CODE: JSON.stringify( - process.env.DEFAULT_COUNTRY_CODE || 'US' - ) - }), - new EnvironmentPlugin(projectConfig.env), - new ReactRefreshWebpackPlugin() - ]; - - return storybookBaseConfig; -};