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,