From 042452ef06ed08f7104600fb5577b67e5e818ebf Mon Sep 17 00:00:00 2001 From: kelvin Date: Fri, 16 May 2025 23:57:29 +0300 Subject: [PATCH 1/4] feat: added products route --- components/Navigation/Navigation.tsx | 2 ++ package.json | 2 +- pnpm-lock.yaml | 41 ++++++++++++++-------------- routes/index.ts | 3 ++ 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/components/Navigation/Navigation.tsx b/components/Navigation/Navigation.tsx index 676bac9..f913267 100644 --- a/components/Navigation/Navigation.tsx +++ b/components/Navigation/Navigation.tsx @@ -19,6 +19,7 @@ import { IconListDetails, IconLogin2, IconMessages, + IconPackages, IconReceipt2, IconRotateRectangle, IconUserCircle, @@ -85,6 +86,7 @@ const mockdata = [ icon: IconFiles, link: PATH_APPS.fileManager.root, }, + { label: 'Products', icon: IconPackages, link: PATH_APPS.orders }, ], }, { diff --git a/package.json b/package.json index 2839e1b..497a377 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@mantine/nprogress": "^7.14.3", "@mantine/spotlight": "^7.14.3", "@mantine/tiptap": "^7.14.3", - "@tabler/icons-react": "^2.40.0", + "@tabler/icons-react": "^3.33.0", "@tiptap/extension-highlight": "^2.1.12", "@tiptap/extension-link": "^2.10.3", "@tiptap/extension-placeholder": "^2.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 173a26e..3a25c0b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -87,8 +87,8 @@ importers: specifier: ^7.14.3 version: 7.17.7(@mantine/core@7.17.7(@mantine/hooks@7.17.7(react@18.3.1))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@7.17.7(react@18.3.1))(@tiptap/extension-link@2.11.9(@tiptap/core@2.11.9(@tiptap/pm@2.11.9))(@tiptap/pm@2.11.9))(@tiptap/react@2.11.9(@tiptap/core@2.11.9(@tiptap/pm@2.11.9))(@tiptap/pm@2.11.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tabler/icons-react': - specifier: ^2.40.0 - version: 2.47.0(react@18.3.1) + specifier: ^3.33.0 + version: 3.33.0(react@18.3.1) '@tiptap/extension-highlight': specifier: ^2.1.12 version: 2.11.9(@tiptap/core@2.11.9(@tiptap/pm@2.11.9)) @@ -242,7 +242,7 @@ importers: version: 9.1.0 eslint-plugin-import: specifier: ^2.31.0 - version: 2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + version: 2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) husky: specifier: ^8.0.0 version: 8.0.3 @@ -2619,13 +2619,13 @@ packages: '@swc/types@0.1.14': resolution: {integrity: sha512-PbSmTiYCN+GMrvfjrMo9bdY+f2COnwbdnoMw7rqU/PI5jXpKjxOGZ0qqZCImxnT81NkNsKnmEpvu+hRXLBeCJg==} - '@tabler/icons-react@2.47.0': - resolution: {integrity: sha512-iqly2FvCF/qUbgmvS8E40rVeYY7laltc5GUjRxQj59DuX0x/6CpKHTXt86YlI2whg4czvd/c8Ce8YR08uEku0g==} + '@tabler/icons-react@3.33.0': + resolution: {integrity: sha512-ay+HDecCjmFl25Lg14hcl59ffSjnOcgfrlV14shu8Qjbz+Xh4LRus93DuoyLQte8YSxE7Pe5gnEz6OF0GtwNtw==} peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 + react: '>= 16' - '@tabler/icons@2.47.0': - resolution: {integrity: sha512-4w5evLh+7FUUiA1GucvGj2ReX2TvOjEr4ejXdwL/bsjoSkof6r1gQmzqI+VHrE2CpJpB3al7bCTulOkFa/RcyA==} + '@tabler/icons@3.33.0': + resolution: {integrity: sha512-NZeFfzcYe7xcBHR3zKoCSrw/cFWvfj6LjenPQ48yVMTGdX854HH9nH44ZfMH8rrDzHBllfjwl4CIX6Vh2tyN0Q==} '@testing-library/dom@9.3.4': resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==} @@ -11312,13 +11312,12 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@tabler/icons-react@2.47.0(react@18.3.1)': + '@tabler/icons-react@3.33.0(react@18.3.1)': dependencies: - '@tabler/icons': 2.47.0 - prop-types: 15.8.1 + '@tabler/icons': 3.33.0 react: 18.3.1 - '@tabler/icons@2.47.0': {} + '@tabler/icons@3.33.0': {} '@testing-library/dom@9.3.4': dependencies: @@ -13411,8 +13410,8 @@ snapshots: '@typescript-eslint/parser': 8.13.0(eslint@8.57.1)(typescript@5.1.6) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.2(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1) @@ -13431,37 +13430,37 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.0 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.13.0(eslint@8.57.1)(typescript@5.1.6) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -13472,7 +13471,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.1.6))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 diff --git a/routes/index.ts b/routes/index.ts index f4efb9f..128e9a9 100644 --- a/routes/index.ts +++ b/routes/index.ts @@ -41,6 +41,9 @@ export const PATH_APPS = { fileManager: { root: path(ROOT_APPS, '/file-manager'), }, + products: { + root: path(ROOT_APPS, '/products'), + }, }; export const PATH_PAGES = { From e4822d548b819221d0d8eca61caca40cd58c4c5a Mon Sep 17 00:00:00 2001 From: kelvin Date: Sat, 17 May 2025 00:36:53 +0300 Subject: [PATCH 2/4] feat: added product page and minimal create product components --- app/api/products/route.ts | 76 ++++++++ .../products/components/NewProductDrawer.tsx | 149 +++++++++++++++ .../ProductCard/ProductsCard.module.css | 3 + .../ProductCard/ProductsCard.stories.tsx | 36 ++++ .../components/ProductCard/ProductsCard.tsx | 178 ++++++++++++++++++ app/apps/products/layout.tsx | 15 ++ app/apps/products/page.tsx | 169 +++++++++++++++++ components/Navigation/Navigation.tsx | 2 +- types/products.ts | 35 ++++ 9 files changed, 662 insertions(+), 1 deletion(-) create mode 100644 app/api/products/route.ts create mode 100644 app/apps/products/components/NewProductDrawer.tsx create mode 100644 app/apps/products/components/ProductCard/ProductsCard.module.css create mode 100644 app/apps/products/components/ProductCard/ProductsCard.stories.tsx create mode 100644 app/apps/products/components/ProductCard/ProductsCard.tsx create mode 100644 app/apps/products/layout.tsx create mode 100644 app/apps/products/page.tsx create mode 100644 types/products.ts diff --git a/app/api/products/route.ts b/app/api/products/route.ts new file mode 100644 index 0000000..3f0e1ce --- /dev/null +++ b/app/api/products/route.ts @@ -0,0 +1,76 @@ +import { NextResponse } from 'next/server'; + +export async function GET(request: Request) { + try { + // Get the authorization header from the incoming request + const authHeader = request.headers.get('authorization'); + + const headers: HeadersInit = { + 'Content-Type': 'application/json', + }; + + // Add the authorization header if it exists + if (authHeader) { + headers['Authorization'] = authHeader; + } + + const response = await fetch( + process.env.NEXT_PUBLIC_API_URL + '/api/products', + { + method: 'GET', + headers, + }, + ); + + const data = await response.json(); + return NextResponse.json(data); + } catch (error) { + return NextResponse.json( + { error: 'Failed to fetch products' }, + { status: 500 }, + ); + } +} + +export async function POST(request: Request) { + try { + const body = await request.json(); + + // Get the authorization header from the incoming request + const authHeader = request.headers.get('authorization'); + + const headers: HeadersInit = { + 'Content-Type': 'application/json', + }; + + // Add the authorization header if it exists + if (authHeader) { + headers['Authorization'] = authHeader; + } + + const response = await fetch( + process.env.NEXT_PUBLIC_API_URL + '/api/products', + { + method: 'POST', + headers, + body: JSON.stringify(body), + }, + ); + + const data = await response.json(); + + if (!response.ok) { + return NextResponse.json( + { error: 'Failed to create product', details: data }, + { status: response.status }, + ); + } + + return NextResponse.json(data); + } catch (error) { + return NextResponse.json( + { error: 'Failed to create product' }, + { status: 500 }, + ); + } +} diff --git a/app/apps/products/components/NewProductDrawer.tsx b/app/apps/products/components/NewProductDrawer.tsx new file mode 100644 index 0000000..847d6f3 --- /dev/null +++ b/app/apps/products/components/NewProductDrawer.tsx @@ -0,0 +1,149 @@ +'use client'; + +import { useState } from 'react'; + +import { + Button, + Drawer, + DrawerProps, + LoadingOverlay, + Stack, + TextInput, + Textarea, +} from '@mantine/core'; +import { DateInput } from '@mantine/dates'; +import { isNotEmpty, useForm } from '@mantine/form'; +import { notifications } from '@mantine/notifications'; + +import { useAuth } from '@/hooks/useAuth'; + +type NewProjectDrawerProps = Omit & { + onProjectCreated?: () => void; +}; + +export const NewProductDrawer = ({ + onProjectCreated, + ...drawerProps +}: NewProjectDrawerProps) => { + const { user, accessToken } = useAuth(); + const [loading, setLoading] = useState(false); + + const form = useForm({ + mode: 'controlled', + initialValues: { + title: '', + description: '', + status: 1, + startDate: null, + dueDate: null, + }, + validate: { + title: isNotEmpty('Project title cannot be empty'), + description: isNotEmpty('Project description cannot be empty'), + startDate: isNotEmpty('Start date cannot be empty'), + }, + }); + + const handleSubmit = async (values: typeof form.values) => { + setLoading(true); + try { + // Format dates for API + const payload = { + ...values, + startDate: values.startDate + ? new Date(values.startDate).toISOString() + : null, + dueDate: values.dueDate ? new Date(values.dueDate).toISOString() : null, + ownerId: user?.id, + }; + + const response = await fetch('/api/projects', { + method: 'POST', + headers: { + Authorization: 'Bearer ' + accessToken, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || 'Failed to create project'); + } + + // Show success notification + notifications.show({ + title: 'Success', + message: 'Project created successfully', + color: 'green', + }); + + // Reset form + form.reset(); + + // Close drawer + if (drawerProps.onClose) { + drawerProps.onClose(); + } + + // Trigger refresh of projects list + if (onProjectCreated) { + onProjectCreated(); + } + } catch (error) { + // Show error notification + notifications.show({ + title: 'Error', + message: + error instanceof Error ? error.message : 'Failed to create project', + color: 'red', + }); + } finally { + setLoading(false); + } + }; + + return ( + + +
+ + +