diff --git a/deployment/docker/Dockerfile b/deployment/docker/Dockerfile index 22296b3..8496cd1 100644 --- a/deployment/docker/Dockerfile +++ b/deployment/docker/Dockerfile @@ -16,7 +16,10 @@ RUN pip3 install --upgrade pip && pip install --upgrade pip ARG CPUCOUNT=1 RUN pip3 install -r /requirements.txt -RUN apt-get update -y && apt-get install -y nodejs npm +RUN apt-get update -y && apt-get install -y curl ca-certificates gnupg \ + && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ + && apt-get install -y nodejs \ + && node -v && npm -v ADD django_project /home/web/django_project diff --git a/django_project/frontend/.gitignore b/django_project/frontend/.gitignore index 4d29575..14e54fc 100644 --- a/django_project/frontend/.gitignore +++ b/django_project/frontend/.gitignore @@ -21,3 +21,6 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +*storybook.log +storybook-static diff --git a/django_project/frontend/.storybook/main.ts b/django_project/frontend/.storybook/main.ts new file mode 100644 index 0000000..aae05da --- /dev/null +++ b/django_project/frontend/.storybook/main.ts @@ -0,0 +1,58 @@ +import type { StorybookConfig } from '@storybook/react-webpack5'; +import path from 'path'; + +const config: StorybookConfig = { + "stories": [ + "../src/**/*.mdx", + "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)" + ], + "addons": [ + "@storybook/addon-webpack5-compiler-babel", + "@storybook/addon-docs", + "@storybook/addon-onboarding" + ], + "framework": { + "name": "@storybook/react-webpack5", + "options": {} + }, + staticDirs: ['../public'], + webpackFinal: async (cfg) => { + cfg.resolve = cfg.resolve || {}; + cfg.resolve.alias = { + ...(cfg.resolve.alias || {}), + '@': path.resolve(__dirname, '../src'), + }; + + // --- SCSS support for Storybook --- + cfg.module = cfg.module || { rules: [] }; + cfg.module.rules = cfg.module.rules || []; + cfg.module.rules.push({ + test: /\.s[ac]ss$/i, + oneOf: [ + // If you use CSS Modules like *.module.scss, enable this branch + { + test: /\.module\.s[ac]ss$/i, + use: [ + require.resolve('style-loader'), + { + loader: require.resolve('css-loader'), + options: { modules: { auto: true, localIdentName: '[name]__[local]--[hash:base64:5]' } }, + }, + require.resolve('sass-loader'), + ], + }, + // Global SCSS + { + use: [ + require.resolve('style-loader'), + require.resolve('css-loader'), + require.resolve('sass-loader'), + ], + }, + ], + }); + + return cfg; + }, +}; +export default config; \ No newline at end of file diff --git a/django_project/frontend/.storybook/preview.tsx b/django_project/frontend/.storybook/preview.tsx new file mode 100644 index 0000000..a6a09f8 --- /dev/null +++ b/django_project/frontend/.storybook/preview.tsx @@ -0,0 +1,22 @@ +import type { Preview } from '@storybook/react'; +import { ChakraProvider, defaultSystem } from '@chakra-ui/react'; +import { MemoryRouter } from 'react-router-dom'; +import React from "react"; + +const preview: Preview = { + decorators: [ + (Story) => ( + + + + + + ), + ], + parameters: { + actions: { argTypesRegex: '^on[A-Z].*' }, + controls: { expanded: true, matchers: { color: /(background|color)$/i, date: /Date$/i } }, + }, +}; + +export default preview; diff --git a/django_project/frontend/.storybook/tsconfig.json b/django_project/frontend/.storybook/tsconfig.json new file mode 100644 index 0000000..7145247 --- /dev/null +++ b/django_project/frontend/.storybook/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "include": [ + "../src/**/*.stories.@(ts|tsx|js|jsx)", + "../src/**/*.mdx", + "./**/*.ts", + "./**/*.tsx" + ] +} \ No newline at end of file diff --git a/django_project/frontend/babel.config.js b/django_project/frontend/babel.config.js index 7556049..9096ea2 100644 --- a/django_project/frontend/babel.config.js +++ b/django_project/frontend/babel.config.js @@ -1,7 +1,7 @@ module.exports = { presets: [ - "@babel/preset-env", - ["@babel/preset-react", { runtime: "automatic" }], - "@babel/preset-typescript" + ['@babel/preset-env', { targets: 'defaults' }], + ['@babel/preset-react', { runtime: 'automatic' }], + ['@babel/preset-typescript', { allowDeclareFields: true }], ], -}; +}; \ No newline at end of file diff --git a/django_project/frontend/package.json b/django_project/frontend/package.json index ca2afda..743fc37 100644 --- a/django_project/frontend/package.json +++ b/django_project/frontend/package.json @@ -25,12 +25,15 @@ "scripts": { "serve": "webpack serve --mode development", "build": "webpack --mode production", - "test": "jest" + "test": "jest", + "storybook": "storybook dev -p 6006 --host 0.0.0.0 --no-open", + "build-storybook": "storybook build" }, "eslintConfig": { "extends": [ "react-app", - "react-app/jest" + "react-app/jest", + "plugin:storybook/recommended" ] }, "browserslist": { @@ -46,10 +49,18 @@ ] }, "devDependencies": { + "@babel/core": "^7.28.4", "@babel/preset-env": "^7.28.3", "@babel/preset-react": "^7.27.1", "@babel/preset-typescript": "^7.27.1", + "@mdx-js/react": "^3.1.1", "@pmmmwh/react-refresh-webpack-plugin": "^0.6.1", + "@storybook/addon-docs": "^9.1.7", + "@storybook/addon-onboarding": "^9.1.7", + "@storybook/addon-webpack5-compiler-babel": "^3.0.6", + "@storybook/addon-webpack5-compiler-swc": "^4.0.1", + "@storybook/react": "^9.1.7", + "@storybook/react-webpack5": "^9.1.7", "@testing-library/jest-dom": "^6.8.0", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", @@ -58,14 +69,18 @@ "@types/react": "^19.1.12", "@types/react-dom": "^19.1.9", "babel-jest": "^30.1.2", + "babel-loader": "^10.0.0", "clean-webpack-plugin": "^4.0.0", "css-loader": "^7.1.2", + "eslint-plugin-storybook": "^9.1.7", "identity-obj-proxy": "^3.0.0", "jest": "^30.1.2", "jest-environment-jsdom": "^30.1.2", "mini-css-extract-plugin": "^2.9.4", "sass": "^1.91.0", "sass-loader": "^16.0.5", + "storybook": "^9.1.7", + "style-loader": "^4.0.0", "ts-jest": "^29.4.1", "ts-loader": "^9.5.4", "typescript": "^5.9.2" diff --git a/django_project/frontend/src/components/Hello/Hello.stories.tsx b/django_project/frontend/src/components/Hello/Hello.stories.tsx new file mode 100644 index 0000000..fe70731 --- /dev/null +++ b/django_project/frontend/src/components/Hello/Hello.stories.tsx @@ -0,0 +1,8 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Hello } from './Hello'; + +const meta: Meta = { title: 'Example/Hello', component: Hello }; +export default meta; + +type Story = StoryObj; +export const Basic: Story = { args: { name: 'Dimas' } }; diff --git a/django_project/frontend/src/components/Hello/Hello.tsx b/django_project/frontend/src/components/Hello/Hello.tsx new file mode 100644 index 0000000..6d2e54b --- /dev/null +++ b/django_project/frontend/src/components/Hello/Hello.tsx @@ -0,0 +1,5 @@ +import React from "react"; + +export function Hello({ name = 'world' }: { name?: string }) { + return Hello, {name}!; +} diff --git a/django_project/frontend/src/components/NavBar/Navbar.stories.tsx b/django_project/frontend/src/components/NavBar/Navbar.stories.tsx new file mode 100644 index 0000000..44e6303 --- /dev/null +++ b/django_project/frontend/src/components/NavBar/Navbar.stories.tsx @@ -0,0 +1,47 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Box } from '@chakra-ui/react'; +import Navbar from './index'; +import React from "react"; + +const meta = { + title: 'Layout/Navbar', + component: Navbar, + parameters: { + layout: 'fullscreen', // navbar usually spans full width + }, + decorators: [ + // Give the story a little vertical room if needed + (Story) => ( + + + + ), + ], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; + +export const OnMapRoute: Story = { + name: 'On /map route', + render: () => , + parameters: { + // If your preview wraps with MemoryRouter, you can set initial route there. + // If not, add a decorator here that wraps with MemoryRouter({ initialEntries: ['/map'] }). + }, +}; + +export const LongPage: Story = { + render: () => ( + <> + + + + + + ), +}; \ No newline at end of file diff --git a/django_project/frontend/src/storybook/main.ts b/django_project/frontend/src/storybook/main.ts new file mode 100644 index 0000000..f2aab0f --- /dev/null +++ b/django_project/frontend/src/storybook/main.ts @@ -0,0 +1,26 @@ +import type { StorybookConfig } from '@storybook/react-webpack5'; +import path from 'path'; + +const config: StorybookConfig = { + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], + addons: [ + '@storybook/addon-essentials', + '@storybook/addon-interactions', + // optional: + // '@chakra-ui/storybook-addon', + ], + framework: { + name: '@storybook/react-webpack5', + options: {}, + }, + staticDirs: ['../public'], + webpackFinal: async (cfg) => { + cfg.resolve = cfg.resolve || {}; + cfg.resolve.alias = { + ...(cfg.resolve.alias || {}), + '@': path.resolve(__dirname, '../src'), + }; + return cfg; + }, +}; +export default config; diff --git a/django_project/frontend/src/storybook/preview.tsx b/django_project/frontend/src/storybook/preview.tsx new file mode 100644 index 0000000..a6a09f8 --- /dev/null +++ b/django_project/frontend/src/storybook/preview.tsx @@ -0,0 +1,22 @@ +import type { Preview } from '@storybook/react'; +import { ChakraProvider, defaultSystem } from '@chakra-ui/react'; +import { MemoryRouter } from 'react-router-dom'; +import React from "react"; + +const preview: Preview = { + decorators: [ + (Story) => ( + + + + + + ), + ], + parameters: { + actions: { argTypesRegex: '^on[A-Z].*' }, + controls: { expanded: true, matchers: { color: /(background|color)$/i, date: /Date$/i } }, + }, +}; + +export default preview; diff --git a/django_project/frontend/tsconfig.app.json b/django_project/frontend/tsconfig.app.json new file mode 100644 index 0000000..4c873e3 --- /dev/null +++ b/django_project/frontend/tsconfig.app.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["**/*.stories.*", "**/*.test.*", ".storybook"] +} \ No newline at end of file diff --git a/django_project/frontend/webpack.config.js b/django_project/frontend/webpack.config.js index 953b9ce..895f832 100644 --- a/django_project/frontend/webpack.config.js +++ b/django_project/frontend/webpack.config.js @@ -25,7 +25,10 @@ let conf = { { test: /\.tsx?$/, exclude: /node_modules/, - use: [{ loader: 'ts-loader' }], + use: { + loader: 'ts-loader', + options: { configFile: 'tsconfig.app.json' } + } }, { test: /\.s[ac]ss$/i,