From 7b1dfb23bfb8bb65487427a6b44cd275b041061c Mon Sep 17 00:00:00 2001 From: stvncode Date: Fri, 30 May 2025 11:49:10 +0200 Subject: [PATCH 1/2] Delete unused code and create a common layout --- modules/web/pnpm-lock.yaml | 150 ++++++++++++------ .../NavigationBar/NavigationBar.tsx | 15 -- .../components/Footer/Footer.styled.ts | 23 --- .../components/Footer/Footer.tsx | 68 -------- .../NavigationBar/components/Footer/index.ts | 1 - .../components/Header/Header.styled.ts | 3 - .../components/Header/Header.tsx | 22 --- .../NavigationBar/components/Header/index.ts | 1 - .../components/Items/Items.data.tsx | 41 ----- .../components/Items/Items.styled.ts | 20 --- .../NavigationBar/components/Items/Items.tsx | 20 --- .../Items/components/Item/Item.hooks.ts | 8 - .../Items/components/Item/Item.styled.ts | 45 ------ .../components/Items/components/Item/Item.tsx | 24 --- .../Items/components/Item/Item.types.ts | 9 -- .../components/Items/components/Item/index.ts | 1 - .../NavigationBar/components/Items/index.ts | 1 - .../customers/cards/address/AddressCard.tsx | 71 --------- .../cards/address/EditAddressModal.tsx | 122 -------------- .../customers/cards/address/schema.ts | 18 --- .../customers/cards/balance/BalanceCard.tsx | 36 ----- .../cards/balance/EditBalanceModal.tsx | 73 --------- .../customers/cards/balance/schema.ts | 9 -- .../customers/cards/customer/CustomerCard.tsx | 49 ------ .../cards/customer/EditCustomerModal.tsx | 102 ------------ .../customers/cards/customer/schema.ts | 9 -- modules/web/web-app/package.json | 1 + modules/web/web-app/vite.config.ts | 10 +- 28 files changed, 111 insertions(+), 841 deletions(-) delete mode 100644 modules/web/web-app/components/layouts/TenantLayout/NavigationBar/NavigationBar.tsx delete mode 100644 modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Footer/Footer.styled.ts delete mode 100644 modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Footer/Footer.tsx delete mode 100644 modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Footer/index.ts delete mode 100644 modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Header/Header.styled.ts delete mode 100644 modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Header/Header.tsx delete mode 100644 modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Header/index.ts delete mode 100644 modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/Items.data.tsx delete mode 100644 modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/Items.styled.ts delete mode 100644 modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/Items.tsx delete mode 100644 modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/components/Item/Item.hooks.ts delete mode 100644 modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/components/Item/Item.styled.ts delete mode 100644 modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/components/Item/Item.tsx delete mode 100644 modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/components/Item/Item.types.ts delete mode 100644 modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/components/Item/index.ts delete mode 100644 modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/index.ts delete mode 100644 modules/web/web-app/features/customers/cards/address/AddressCard.tsx delete mode 100644 modules/web/web-app/features/customers/cards/address/EditAddressModal.tsx delete mode 100644 modules/web/web-app/features/customers/cards/address/schema.ts delete mode 100644 modules/web/web-app/features/customers/cards/balance/BalanceCard.tsx delete mode 100644 modules/web/web-app/features/customers/cards/balance/EditBalanceModal.tsx delete mode 100644 modules/web/web-app/features/customers/cards/balance/schema.ts delete mode 100644 modules/web/web-app/features/customers/cards/customer/CustomerCard.tsx delete mode 100644 modules/web/web-app/features/customers/cards/customer/EditCustomerModal.tsx delete mode 100644 modules/web/web-app/features/customers/cards/customer/schema.ts diff --git a/modules/web/pnpm-lock.yaml b/modules/web/pnpm-lock.yaml index ff99a4a2c..16d2e6f5a 100644 --- a/modules/web/pnpm-lock.yaml +++ b/modules/web/pnpm-lock.yaml @@ -600,6 +600,9 @@ importers: ts-pattern: specifier: ^5.6.2 version: 5.6.2 + vite-plugin-checker: + specifier: ^0.9.3 + version: 0.9.3(eslint@8.57.1)(typescript@5.8.2)(vite@5.4.14) zod: specifier: ^3.24.2 version: 3.24.2 @@ -717,6 +720,15 @@ packages: js-tokens: 4.0.0 picocolors: 1.1.1 + /@babel/code-frame@7.27.1: + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + dev: false + /@babel/compat-data@7.26.8: resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} engines: {node: '>=6.9.0'} @@ -903,6 +915,11 @@ packages: resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} engines: {node: '>=6.9.0'} + /@babel/helper-validator-identifier@7.27.1: + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + dev: false + /@babel/helper-validator-option@7.25.9: resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} engines: {node: '>=6.9.0'} @@ -2406,7 +2423,6 @@ packages: cpu: [ppc64] os: [aix] requiresBuild: true - dev: true optional: true /@esbuild/aix-ppc64@0.25.1: @@ -2433,7 +2449,6 @@ packages: cpu: [arm64] os: [android] requiresBuild: true - dev: true optional: true /@esbuild/android-arm64@0.25.1: @@ -2460,7 +2475,6 @@ packages: cpu: [arm] os: [android] requiresBuild: true - dev: true optional: true /@esbuild/android-arm@0.25.1: @@ -2487,7 +2501,6 @@ packages: cpu: [x64] os: [android] requiresBuild: true - dev: true optional: true /@esbuild/android-x64@0.25.1: @@ -2514,7 +2527,6 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true - dev: true optional: true /@esbuild/darwin-arm64@0.25.1: @@ -2541,7 +2553,6 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true - dev: true optional: true /@esbuild/darwin-x64@0.25.1: @@ -2568,7 +2579,6 @@ packages: cpu: [arm64] os: [freebsd] requiresBuild: true - dev: true optional: true /@esbuild/freebsd-arm64@0.25.1: @@ -2595,7 +2605,6 @@ packages: cpu: [x64] os: [freebsd] requiresBuild: true - dev: true optional: true /@esbuild/freebsd-x64@0.25.1: @@ -2622,7 +2631,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-arm64@0.25.1: @@ -2649,7 +2657,6 @@ packages: cpu: [arm] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-arm@0.25.1: @@ -2676,7 +2683,6 @@ packages: cpu: [ia32] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-ia32@0.25.1: @@ -2703,7 +2709,6 @@ packages: cpu: [loong64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-loong64@0.25.1: @@ -2730,7 +2735,6 @@ packages: cpu: [mips64el] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-mips64el@0.25.1: @@ -2757,7 +2761,6 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-ppc64@0.25.1: @@ -2784,7 +2787,6 @@ packages: cpu: [riscv64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-riscv64@0.25.1: @@ -2811,7 +2813,6 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-s390x@0.25.1: @@ -2838,7 +2839,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-x64@0.25.1: @@ -2874,7 +2874,6 @@ packages: cpu: [x64] os: [netbsd] requiresBuild: true - dev: true optional: true /@esbuild/netbsd-x64@0.25.1: @@ -2910,7 +2909,6 @@ packages: cpu: [x64] os: [openbsd] requiresBuild: true - dev: true optional: true /@esbuild/openbsd-x64@0.25.1: @@ -2937,7 +2935,6 @@ packages: cpu: [x64] os: [sunos] requiresBuild: true - dev: true optional: true /@esbuild/sunos-x64@0.25.1: @@ -2964,7 +2961,6 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true - dev: true optional: true /@esbuild/win32-arm64@0.25.1: @@ -2991,7 +2987,6 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true - dev: true optional: true /@esbuild/win32-ia32@0.25.1: @@ -3018,7 +3013,6 @@ packages: cpu: [x64] os: [win32] requiresBuild: true - dev: true optional: true /@esbuild/win32-x64@0.25.1: @@ -5660,7 +5654,6 @@ packages: cpu: [arm] os: [android] requiresBuild: true - dev: true optional: true /@rollup/rollup-android-arm64@4.36.0: @@ -5668,7 +5661,6 @@ packages: cpu: [arm64] os: [android] requiresBuild: true - dev: true optional: true /@rollup/rollup-darwin-arm64@4.36.0: @@ -5676,7 +5668,6 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true - dev: true optional: true /@rollup/rollup-darwin-x64@4.36.0: @@ -5684,7 +5675,6 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true - dev: true optional: true /@rollup/rollup-freebsd-arm64@4.36.0: @@ -5692,7 +5682,6 @@ packages: cpu: [arm64] os: [freebsd] requiresBuild: true - dev: true optional: true /@rollup/rollup-freebsd-x64@4.36.0: @@ -5700,7 +5689,6 @@ packages: cpu: [x64] os: [freebsd] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-arm-gnueabihf@4.36.0: @@ -5708,7 +5696,6 @@ packages: cpu: [arm] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-arm-musleabihf@4.36.0: @@ -5716,7 +5703,6 @@ packages: cpu: [arm] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-arm64-gnu@4.36.0: @@ -5724,7 +5710,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-arm64-musl@4.36.0: @@ -5732,7 +5717,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-loongarch64-gnu@4.36.0: @@ -5740,7 +5724,6 @@ packages: cpu: [loong64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-powerpc64le-gnu@4.36.0: @@ -5748,7 +5731,6 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-riscv64-gnu@4.36.0: @@ -5756,7 +5738,6 @@ packages: cpu: [riscv64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-s390x-gnu@4.36.0: @@ -5764,7 +5745,6 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-x64-gnu@4.36.0: @@ -5772,7 +5752,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-x64-musl@4.36.0: @@ -5780,7 +5759,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-win32-arm64-msvc@4.36.0: @@ -5788,7 +5766,6 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true - dev: true optional: true /@rollup/rollup-win32-ia32-msvc@4.36.0: @@ -5796,7 +5773,6 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true - dev: true optional: true /@rollup/rollup-win32-x64-msvc@4.36.0: @@ -5804,7 +5780,6 @@ packages: cpu: [x64] os: [win32] requiresBuild: true - dev: true optional: true /@rtsao/scc@1.1.0: @@ -7076,7 +7051,6 @@ packages: /@types/estree@1.0.6: resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - dev: true /@types/express-serve-static-core@4.19.6: resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} @@ -7197,7 +7171,6 @@ packages: resolution: {integrity: sha512-iEUCUJoU0i3VnrCmgoWCXttklWcvoCIx4jzcP22fioIVSdTmjgoEvmAO/QPw6TcS9k5FrNgn4w7q5lGOd1CT5g==} dependencies: undici-types: 6.20.0 - dev: true /@types/normalize-package-data@2.4.4: resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -9913,7 +9886,6 @@ packages: '@esbuild/win32-arm64': 0.21.5 '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 - dev: true /esbuild@0.25.1: resolution: {integrity: sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==} @@ -10456,6 +10428,17 @@ packages: dependencies: picomatch: 4.0.2 + /fdir@6.4.5(picomatch@4.0.2): + resolution: {integrity: sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + dependencies: + picomatch: 4.0.2 + dev: false + /fetch-retry@5.0.6: resolution: {integrity: sha512-3yurQZ2hD9VISAhJJP9bpYFNQrHHBXE2JxxjY5aLEcDi46RmAzJE2OC9FAde0yis5ElW0jTTzs0zfg/Cca4XqQ==} dev: true @@ -12623,6 +12606,14 @@ packages: path-key: 4.0.0 dev: true + /npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + dev: false + /npmlog@5.0.1: resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} deprecated: This package is no longer supported. @@ -12957,7 +12948,6 @@ packages: /path-key@4.0.0: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} engines: {node: '>=12'} - dev: true /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -14107,7 +14097,6 @@ packages: '@rollup/rollup-win32-ia32-msvc': 4.36.0 '@rollup/rollup-win32-x64-msvc': 4.36.0 fsevents: 2.3.3 - dev: true /rspack-resolver@1.1.2: resolution: {integrity: sha512-eHhz+9JWHFdbl/CVVqEP6kviLFZqw1s0MWxLdsGMtUKUspSO3SERptPohmrUIC9jT1bGV9Bd3+r8AmWbdfNAzQ==} @@ -15123,6 +15112,14 @@ packages: fdir: 6.4.3(picomatch@4.0.2) picomatch: 4.0.2 + /tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + dependencies: + fdir: 6.4.5(picomatch@4.0.2) + picomatch: 4.0.2 + dev: false + /tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} dev: true @@ -15489,7 +15486,6 @@ packages: /undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} - dev: true /unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} @@ -15514,6 +15510,11 @@ packages: engines: {node: '>=4'} dev: true + /unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + dev: false + /unique-string@2.0.0: resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} engines: {node: '>=8'} @@ -15695,6 +15696,54 @@ packages: engines: {node: '>= 0.8'} dev: true + /vite-plugin-checker@0.9.3(eslint@8.57.1)(typescript@5.8.2)(vite@5.4.14): + resolution: {integrity: sha512-Tf7QBjeBtG7q11zG0lvoF38/2AVUzzhMNu+Wk+mcsJ00Rk/FpJ4rmUviVJpzWkagbU13cGXvKpt7CMiqtxVTbQ==} + engines: {node: '>=14.16'} + peerDependencies: + '@biomejs/biome': '>=1.7' + eslint: '>=7' + meow: ^13.2.0 + optionator: ^0.9.4 + stylelint: '>=16' + typescript: '*' + vite: '>=2.0.0' + vls: '*' + vti: '*' + vue-tsc: ~2.2.10 + peerDependenciesMeta: + '@biomejs/biome': + optional: true + eslint: + optional: true + meow: + optional: true + optionator: + optional: true + stylelint: + optional: true + typescript: + optional: true + vls: + optional: true + vti: + optional: true + vue-tsc: + optional: true + dependencies: + '@babel/code-frame': 7.27.1 + chokidar: 4.0.3 + eslint: 8.57.1 + npm-run-path: 6.0.0 + picocolors: 1.1.1 + picomatch: 4.0.2 + strip-ansi: 7.1.0 + tiny-invariant: 1.3.3 + tinyglobby: 0.2.14 + typescript: 5.8.2 + vite: 5.4.14(@types/node@22.13.11)(sass@1.86.0) + vscode-uri: 3.1.0 + dev: false + /vite-plugin-dts@3.9.1(@types/node@22.13.11)(typescript@5.8.2)(vite@5.4.14): resolution: {integrity: sha512-rVp2KM9Ue22NGWB8dNtWEr+KekN3rIgz1tWD050QnRGlriUCmaDwa7qA5zDEjbXg5lAXhYMSBJtx3q3hQIJZSg==} engines: {node: ^14.18.0 || >=16.0.0} @@ -15790,7 +15839,10 @@ packages: sass: 1.86.0 optionalDependencies: fsevents: 2.3.3 - dev: true + + /vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + dev: false /vue-template-compiler@2.7.16: resolution: {integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==} diff --git a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/NavigationBar.tsx b/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/NavigationBar.tsx deleted file mode 100644 index 5e609ee57..000000000 --- a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/NavigationBar.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { FunctionComponent } from 'react' - -import Footer from './components/Footer' -import Header from './components/Header' -import Items from './components/Items' - -export const NavigationBar: FunctionComponent = () => { - return ( - - ) -} diff --git a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Footer/Footer.styled.ts b/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Footer/Footer.styled.ts deleted file mode 100644 index 99b39aa21..000000000 --- a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Footer/Footer.styled.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { radius, spaces } from '@md/foundation' -import { styled } from '@stitches/react' - -export const StyledFooter = styled('footer', { - width: '100%', -}) - -export const Avatar = styled('img', { - borderRadius: radius.round, -}) - -export const AvatarTrigger = styled('li', { - width: `calc(100% - ${spaces.space5} * 2)`, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - position: 'relative', - margin: `0 ${spaces.space5}`, - padding: `${spaces.space4} 0`, - borderRadius: radius.radius3, - backgroundColor: 'transparent', - transition: 'background-color 0.2s ease-in-out', -}) diff --git a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Footer/Footer.tsx b/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Footer/Footer.tsx deleted file mode 100644 index a0d5e5251..000000000 --- a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Footer/Footer.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { SettingsIcon } from '@md/icons' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuGroup, - DropdownMenuItem, - DropdownMenuTrigger, - Tooltip, - TooltipContent, - TooltipTrigger, -} from '@md/ui' -import { LogOutIcon, TerminalIcon, UserCircle2Icon } from 'lucide-react' -import { Link } from 'react-router-dom' - -import { StyledItems as Items } from '../Items/Items.styled' -import Item from '../Items/components/Item/Item' - -import { AvatarTrigger, StyledFooter } from './Footer.styled' - -import type { FunctionComponent, ReactNode } from 'react' - -const Footer: FunctionComponent = () => { - return ( - - - } /> - } /> - - - - ) -} - -const UserPreferenceTooltip = ({ children }: { children: ReactNode }) => { - return ( - - {children} - Account - - ) -} - -export const FooterAccountDropdown: FunctionComponent = () => { - return ( -
  • - - - - - - - - - - - - - Logout - - - - - -
  • - ) -} - -export default Footer diff --git a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Footer/index.ts b/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Footer/index.ts deleted file mode 100644 index 5d06e9b71..000000000 --- a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Footer/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Footer' diff --git a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Header/Header.styled.ts b/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Header/Header.styled.ts deleted file mode 100644 index 187339d0c..000000000 --- a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Header/Header.styled.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { styled } from '@stitches/react' - -export const StyledHeader = styled('header', {}) diff --git a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Header/Header.tsx b/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Header/Header.tsx deleted file mode 100644 index b174e47b2..000000000 --- a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Header/Header.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { LogoSymbol } from '@md/foundation' -import { Link } from 'react-router-dom' - -import { useTheme } from 'providers/ThemeProvider' - -import { StyledHeader } from './Header.styled' - -import type { FunctionComponent } from 'react' - -const Header: FunctionComponent = () => { - const { isDarkMode } = useTheme() - - return ( - - - - - - ) -} - -export default Header diff --git a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Header/index.ts b/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Header/index.ts deleted file mode 100644 index 6f8c57905..000000000 --- a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Header/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Header' diff --git a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/Items.data.tsx b/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/Items.data.tsx deleted file mode 100644 index 4e611cde5..000000000 --- a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/Items.data.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { BillingIcon, Catalog2Icon, CustomersIcon, HomeIcon, ReportsIcon } from '@md/icons' -import { LightbulbIcon } from 'lucide-react' - -import { NavigationItemType } from './components/Item/Item.types' - -export const NAVIGATION_ITEMS: NavigationItemType[] = [ - { - label: 'Home', - to: '.', - end: true, - icon: , - divider: true, - }, - - { - label: 'Product catalog', // metrics - to: 'plans', - icon: , - }, - { - label: 'Billing', - to: 'subscriptions', - icon: , - }, - { - label: 'Customers', - to: 'customers', - icon: , - divider: true, - }, - { - label: 'Growth', - to: 'growth', - icon: , - }, - { - label: 'Reports', - to: 'reports', - icon: , - }, -] diff --git a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/Items.styled.ts b/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/Items.styled.ts deleted file mode 100644 index 56e911431..000000000 --- a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/Items.styled.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { colors, spaces } from '@md/foundation' -import { styled } from '@stitches/react' - -const NAVIGATION_BAR_WIDTH = 55 - -export const StyledItems = styled('ul', { - maxWidth: NAVIGATION_BAR_WIDTH, - width: '100%', - display: 'flex', - flexDirection: 'column', - gap: spaces.space4, -}) - -export const ItemDivider = styled('hr', { - width: `calc(100% - ${spaces.space5} * 2)`, - height: 1, - border: 'none', - background: `linear-gradient(81deg, rgba(246,202,220,0) 0%, ${colors.neutral6} 49%, rgba(255,255,255,0) 100%)`, - marginLeft: spaces.space5, -}) diff --git a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/Items.tsx b/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/Items.tsx deleted file mode 100644 index 717ce61b8..000000000 --- a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/Items.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Fragment, FunctionComponent } from 'react' - -import { NAVIGATION_ITEMS } from './Items.data' -import { ItemDivider, StyledItems } from './Items.styled' -import Item from './components/Item' - -const Items: FunctionComponent = () => { - return ( - - {NAVIGATION_ITEMS.map(item => ( - - - {item.divider && } - - ))} - - ) -} - -export default Items diff --git a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/components/Item/Item.hooks.ts b/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/components/Item/Item.hooks.ts deleted file mode 100644 index 71046e156..000000000 --- a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/components/Item/Item.hooks.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const onClick = () => { - const activeLink = document.querySelector('aside ul li a.active') - activeLink?.setAttribute('data-exit', 'true') - - setTimeout(() => { - activeLink?.removeAttribute('data-exit') - }, 300) -} diff --git a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/components/Item/Item.styled.ts b/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/components/Item/Item.styled.ts deleted file mode 100644 index 4733b5910..000000000 --- a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/components/Item/Item.styled.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { radius, spaces } from '@md/foundation' -import { keyframes, styled } from '@stitches/react' -import { NavLink } from 'react-router-dom' - -const ActiveStateKeyframe = keyframes({ - '0%': { - transform: 'translateX(-4px)', - opacity: 0, - }, - '100%': { - transform: 'translateX(0)', - opacity: 1, - }, -}) - -export const ItemLink = styled(NavLink, { - position: 'relative', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - margin: `0 ${spaces.space5}`, - padding: `${spaces.space4} 0`, - borderRadius: radius.radius3, - backgroundColor: 'transparent', - transition: 'background-color 0.2s ease-in-out', - - '&.active::before, &[data-exit="true"]::before': { - content: '', - position: 'absolute', - right: `calc(${spaces.space5} * -1)`, - top: 0, - width: 3, - height: '100%', - borderTopLeftRadius: 1.5, - borderBottomLeftRadius: 1.5, - background: 'linear-gradient(180deg, #4F46FF -37.5%, #817AFF 100%), #7B74FF', - animation: `${ActiveStateKeyframe} 0.3s ease-in`, - transition: 'transform 0.3s ease-out, opacity 0.3s ease-out', - }, - - '&[data-exit="true"]::before': { - transform: 'translateX(-4px)', - opacity: 0, - }, -}) diff --git a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/components/Item/Item.tsx b/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/components/Item/Item.tsx deleted file mode 100644 index d0e5d53a2..000000000 --- a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/components/Item/Item.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Tooltip, TooltipContent, TooltipTrigger } from '@md/ui' - -import { onClick } from './Item.hooks' -import { ItemLink } from './Item.styled' -import { NavigationItemType } from './Item.types' - -import type { FunctionComponent } from 'react' - -const Item: FunctionComponent = ({ to, end, icon, label }) => { - return ( -
  • - - - - {icon} - - - {label} - -
  • - ) -} - -export default Item diff --git a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/components/Item/Item.types.ts b/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/components/Item/Item.types.ts deleted file mode 100644 index 83e11ba38..000000000 --- a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/components/Item/Item.types.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { To } from 'react-router-dom' - -export type NavigationItemType = { - label: string - to: To - end?: boolean - icon: React.ReactNode - divider?: boolean -} diff --git a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/components/Item/index.ts b/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/components/Item/index.ts deleted file mode 100644 index 748b336b1..000000000 --- a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/components/Item/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Item' diff --git a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/index.ts b/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/index.ts deleted file mode 100644 index 65af26b77..000000000 --- a/modules/web/web-app/components/layouts/TenantLayout/NavigationBar/components/Items/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Items' diff --git a/modules/web/web-app/features/customers/cards/address/AddressCard.tsx b/modules/web/web-app/features/customers/cards/address/AddressCard.tsx deleted file mode 100644 index e1f6337bc..000000000 --- a/modules/web/web-app/features/customers/cards/address/AddressCard.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { ComponentProps, useState } from 'react' - -import { PageSection } from '@/components/layouts/shared/PageSection' -import { CardAction } from '@/features/customers/cards/CardAction' -import { EditAddressModal } from '@/features/customers/cards/address/EditAddressModal' -import { Address, Customer } from '@/rpc/api/customers/v1/models_pb' - -type Props = Pick, 'className'> & { - customer: Customer -} - -export const AddressLines = ({ address }: { address: Partial
    }) => { - return ( -
    - {address.line1} - {address.line2} - {address.city} - {address.state} - {address.country} - {address.zipCode} -
    - ) -} - -export const AddressLinesCompact = ({ address }: { address: Partial
    }) => { - return ( -
    - {address.line1} - {address.line2} - - {address.city}, {address.state} {address.zipCode} - - {address.country} -
    - ) -} - -export const AddressCard = ({ customer, className }: Props) => { - const [editModalVisible, setEditModalVisible] = useState(false) - - return ( - setEditModalVisible(true)} />, - }} - > -
    -
    - Billing address - {customer.billingAddress && } -
    -
    - Shipping address - {customer.shippingAddress?.sameAsBilling ? ( - Same as billing address - ) : ( - - )} -
    -
    - - setEditModalVisible(false)} - /> -
    - ) -} diff --git a/modules/web/web-app/features/customers/cards/address/EditAddressModal.tsx b/modules/web/web-app/features/customers/cards/address/EditAddressModal.tsx deleted file mode 100644 index 38c6c5180..000000000 --- a/modules/web/web-app/features/customers/cards/address/EditAddressModal.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { createConnectQueryKey, useMutation } from '@connectrpc/connect-query' -import { CheckboxFormField, Form, InputFormField, Modal } from '@md/ui' -import { useQueryClient } from '@tanstack/react-query' -import { ComponentProps } from 'react' -import { toast } from 'sonner' -import { z } from 'zod' - -import { addressesSchema } from '@/features/customers/cards/address/schema' -import { useZodForm } from '@/hooks/useZodForm' -import { - getCustomerById, - patchCustomer, -} from '@/rpc/api/customers/v1/customers-CustomersService_connectquery' -import { Customer } from '@/rpc/api/customers/v1/models_pb' - -type Props = Pick, 'visible' | 'onCancel'> & { - customer: Customer -} - -export const EditAddressModal = ({ customer, ...props }: Props) => { - const queryClient = useQueryClient() - const patchCustomerMutation = useMutation(patchCustomer, { - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: createConnectQueryKey(getCustomerById, { id: customer.id }), - }) - }, - }) - - const methods = useZodForm({ - mode: 'onChange', - schema: addressesSchema, - defaultValues: { - billing_address: customer.billingAddress, - shipping_address: { - ...customer.shippingAddress, - sameAsBilling: customer.shippingAddress?.sameAsBilling !== false, - }, - }, - }) - const sameShippingAddress = methods.watch('shipping_address.sameAsBilling') - - const onSubmit = async (data: z.infer) => { - await patchCustomerMutation.mutateAsync({ - customer: { - id: customer.id, - billingAddress: data.billing_address, - shippingAddress: data.shipping_address, - }, - }) - toast.success('Address updated') - props.onCancel?.() - methods.reset() - } - const inputProps = { - control: methods.control, - direction: 'horizontal' as const, - } - - return ( - Edit address} {...props} onConfirm={() => methods.handleSubmit(onSubmit)()}> - -
    - -
    -

    Billing address

    - - - - - - - -

    Shipping address

    - - - {sameShippingAddress || ( - <> - - - - - - - - )} -
    -
    - -
    -
    - ) -} diff --git a/modules/web/web-app/features/customers/cards/address/schema.ts b/modules/web/web-app/features/customers/cards/address/schema.ts deleted file mode 100644 index f2badec5e..000000000 --- a/modules/web/web-app/features/customers/cards/address/schema.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { z } from 'zod' - -const addressSchema = z.object({ - line1: z.string().optional(), - line2: z.string().optional(), - city: z.string().optional(), - country: z.string().optional(), - state: z.string().optional(), - zipcode: z.string().optional(), -}) -const shippingAddressSchema = z.object({ - address: addressSchema.optional(), - sameAsBilling: z.boolean(), -}) -export const addressesSchema = z.object({ - billing_address: addressSchema.optional(), - shipping_address: shippingAddressSchema.optional(), -}) diff --git a/modules/web/web-app/features/customers/cards/balance/BalanceCard.tsx b/modules/web/web-app/features/customers/cards/balance/BalanceCard.tsx deleted file mode 100644 index e0d356e0b..000000000 --- a/modules/web/web-app/features/customers/cards/balance/BalanceCard.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { ComponentProps, useState } from 'react' - -import { Property } from '@/components/Property' -import { PageSection } from '@/components/layouts/shared/PageSection' -import { CardAction } from '@/features/customers/cards/CardAction' -import { EditBalanceModal } from '@/features/customers/cards/balance/EditBalanceModal' -import { Customer } from '@/rpc/api/customers/v1/models_pb' - -type Props = Pick, 'className'> & { - customer: Customer -} -export const BalanceCard = ({ customer, className }: Props) => { - const [editModalVisible, setEditModalVisible] = useState(false) - - return ( - setEditModalVisible(true)} />, - }} - > -
    -
    - -
    -
    - - setEditModalVisible(false)} - /> -
    - ) -} diff --git a/modules/web/web-app/features/customers/cards/balance/EditBalanceModal.tsx b/modules/web/web-app/features/customers/cards/balance/EditBalanceModal.tsx deleted file mode 100644 index 633d9474c..000000000 --- a/modules/web/web-app/features/customers/cards/balance/EditBalanceModal.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { createConnectQueryKey, useMutation } from '@connectrpc/connect-query' -import { Form, InputFormField, Modal } from '@md/ui' -import { useQueryClient } from '@tanstack/react-query' -import { ComponentProps } from 'react' -import { toast } from 'sonner' -import { z } from 'zod' - -import { balanceSchema } from '@/features/customers/cards/balance/schema' -import { useZodForm } from '@/hooks/useZodForm' -import { - getCustomerById, - patchCustomer, -} from '@/rpc/api/customers/v1/customers-CustomersService_connectquery' -import { Customer } from '@/rpc/api/customers/v1/models_pb' - -type Props = Pick, 'visible' | 'onCancel'> & { - customer: Customer -} - -export const EditBalanceModal = ({ customer, ...props }: Props) => { - const queryClient = useQueryClient() - const patchCustomerMutation = useMutation(patchCustomer, { - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: createConnectQueryKey(getCustomerById, { id: customer.id }), - }) - }, - }) - - const methods = useZodForm({ - mode: 'onChange', - schema: balanceSchema, - defaultValues: { - balanceValueCents: Number(customer.balanceValueCents), - }, - }) - - const onSubmit = async (data: z.infer) => { - console.log('data', data) - await patchCustomerMutation.mutateAsync({ - customer: { - id: customer.id, - balanceValueCents: BigInt(data.balanceValueCents), - }, - }) - toast.success('Balance updated') - props.onCancel?.() - } - - return ( - Edit balance} - {...props} - onConfirm={() => methods.handleSubmit(onSubmit)()} - > - -
    - -
    - -
    -
    - -
    -
    - ) -} diff --git a/modules/web/web-app/features/customers/cards/balance/schema.ts b/modules/web/web-app/features/customers/cards/balance/schema.ts deleted file mode 100644 index e22b228a3..000000000 --- a/modules/web/web-app/features/customers/cards/balance/schema.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { z } from 'zod' - -const CURRENCIES = ['EUR', 'USD'] as const -export const balanceSchema = z.object({ - balanceValueCents: z.number().min(0, 'Must be positive'), - balanceCurrency: z.enum(CURRENCIES, { - errorMap: () => ({ message: "Expecting 'EUR' or 'USD'" }), - }), -}) diff --git a/modules/web/web-app/features/customers/cards/customer/CustomerCard.tsx b/modules/web/web-app/features/customers/cards/customer/CustomerCard.tsx deleted file mode 100644 index 635a7f69a..000000000 --- a/modules/web/web-app/features/customers/cards/customer/CustomerCard.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import dayjs from 'dayjs' -import { ComponentProps, useState } from 'react' - -import { Property } from '@/components/Property' -import { PageSection } from '@/components/layouts/shared/PageSection' -import { CardAction } from '@/features/customers/cards/CardAction' -import { EditCustomerModal } from '@/features/customers/cards/customer/EditCustomerModal' -import { Customer } from '@/rpc/api/customers/v1/models_pb' - -type Props = Pick, 'className'> & { - customer: Customer -} - -export const CustomerCard = ({ customer, className }: Props) => { - const [editModalVisible, setEditModalVisible] = useState(false) - - return ( - setEditModalVisible(true)} />, - }} - > -
    -
    - - - -
    -
    - - {/* TODO */} - - -
    -
    - - setEditModalVisible(false)} - /> -
    - ) -} diff --git a/modules/web/web-app/features/customers/cards/customer/EditCustomerModal.tsx b/modules/web/web-app/features/customers/cards/customer/EditCustomerModal.tsx deleted file mode 100644 index a867c0d41..000000000 --- a/modules/web/web-app/features/customers/cards/customer/EditCustomerModal.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { createConnectQueryKey, useMutation } from '@connectrpc/connect-query' -import { Form, InputFormField, Modal } from '@md/ui' -import { useQueryClient } from '@tanstack/react-query' -import { ComponentProps } from 'react' -import { toast } from 'sonner' -import { z } from 'zod' - -import { customerSchema } from '@/features/customers/cards/customer/schema' -import { useZodForm } from '@/hooks/useZodForm' -import { - getCustomerById, - patchCustomer, -} from '@/rpc/api/customers/v1/customers-CustomersService_connectquery' -import { Customer } from '@/rpc/api/customers/v1/models_pb' - -type Props = Pick, 'visible' | 'onCancel'> & { - customer: Customer -} - -export const EditCustomerModal = ({ customer, ...props }: Props) => { - const queryClient = useQueryClient() - const patchCustomerMutation = useMutation(patchCustomer, { - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: createConnectQueryKey(getCustomerById, { id: customer.id }), - }) - }, - }) - - const methods = useZodForm({ - mode: 'onChange', - schema: customerSchema, - defaultValues: customer, - }) - - const onSubmit = async (data: z.infer) => { - await patchCustomerMutation.mutateAsync({ - customer: { - id: customer.id, - name: data.name, - alias: data.alias, - billingEmail: data.email, - // TODO allow multiple - invoicingEmails: data.invoicingEmail ? { emails: [data.invoicingEmail] } : undefined, - phone: data.phone, - }, - }) - toast.success('Address updated') - props.onCancel?.() - } - - return ( - Edit customer} - {...props} - onConfirm={() => methods.handleSubmit(onSubmit)()} - > - -
    - -
    -

    Customer details

    - - - - - -
    -
    - -
    -
    - ) -} diff --git a/modules/web/web-app/features/customers/cards/customer/schema.ts b/modules/web/web-app/features/customers/cards/customer/schema.ts deleted file mode 100644 index 552ace5ac..000000000 --- a/modules/web/web-app/features/customers/cards/customer/schema.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { z } from 'zod' - -export const customerSchema = z.object({ - name: z.string().min(3, 'Required'), - alias: z.string().optional(), - email: z.string().optional(), - invoicingEmail: z.string().optional(), - phone: z.string().optional(), -}) diff --git a/modules/web/web-app/package.json b/modules/web/web-app/package.json index e6bc9bd24..090609ce2 100644 --- a/modules/web/web-app/package.json +++ b/modules/web/web-app/package.json @@ -58,6 +58,7 @@ "styled-components": "^6.1.16", "superjson": "2.2.1", "ts-pattern": "^5.6.2", + "vite-plugin-checker": "^0.9.3", "zod": "^3.24.2", "zustand": "^4.5.6" }, diff --git a/modules/web/web-app/vite.config.ts b/modules/web/web-app/vite.config.ts index 4af7dbc76..4405ce5c4 100644 --- a/modules/web/web-app/vite.config.ts +++ b/modules/web/web-app/vite.config.ts @@ -1,6 +1,7 @@ import basicSsl from '@vitejs/plugin-basic-ssl' import react from '@vitejs/plugin-react' import { UserConfigExport, defineConfig } from 'vite' +import checker from 'vite-plugin-checker' import svgr from 'vite-plugin-svgr' import tsconfigPaths from 'vite-tsconfig-paths' @@ -22,7 +23,14 @@ export default defineConfig(({ mode }) => { }, }, envDir: '../../../', - plugins: [react(), tsconfigPaths(), svgr()], + plugins: [ + react(), + tsconfigPaths(), + svgr(), + checker({ + typescript: true, + }), + ], } return localSsl ? { From 0e0436cc53d60d518494bbc919e6ce4791d1893e Mon Sep 17 00:00:00 2001 From: stvncode Date: Fri, 30 May 2025 19:58:28 +0200 Subject: [PATCH 2/2] Refacto subscription, invoices and customers --- modules/web/pnpm-lock.yaml | 177 +++++++++---- modules/web/web-app/components/ButtonTabs.tsx | 25 ++ .../PageHeading/PageHeading.styled.ts | 17 -- .../components/PageHeading/PageHeading.tsx | 12 +- .../web-app/components/layouts/PageLayout.tsx | 76 ++++++ .../Pagination/Pagination.styled.ts | 15 -- .../web-app/features/auth/shared.styled.ts | 8 - .../customers/cards/address/AddressCard.tsx | 71 ++++++ .../cards/address/EditAddressModal.tsx | 122 +++++++++ .../customers/cards/address/schema.ts | 18 ++ .../customers/headers/CustomerHeader.tsx | 147 ----------- .../customers/headers/CustomersHeader.tsx | 115 --------- .../features/customers/headers/index.ts | 2 - .../web/web-app/features/customers/index.tsx | 3 +- .../customers/panels/CustomerDetailsPanel.tsx | 49 ++++ .../panels/CustomerOverviewPanel.tsx | 55 ++++ .../features/customers/panels/index.ts | 3 + .../features/invoices/InvoicesHeader.tsx | 60 ----- .../web/web-app/features/invoices/index.tsx | 4 +- .../subscriptions/SubscriptionsHeader.tsx | 47 ---- .../web-app/features/subscriptions/index.tsx | 4 +- modules/web/web-app/package.json | 4 +- .../web-app/pages/tenants/billing/index.tsx | 10 +- .../pages/tenants/customer/customer.tsx | 236 ++++++++++-------- .../pages/tenants/customer/customers.tsx | 96 ++++--- .../pages/tenants/invoice/Invoices.tsx | 55 ++-- .../tenants/subscription/subscriptions.tsx | 43 +++- .../header/{customer.svg => customers.svg} | 0 .../web/web-app/public/header/invoices.svg | 39 +++ .../web-app/public/header/subscriptions.svg | 51 ++++ modules/web/web-app/router/tenant/billing.tsx | 6 +- 31 files changed, 934 insertions(+), 636 deletions(-) create mode 100644 modules/web/web-app/components/ButtonTabs.tsx delete mode 100644 modules/web/web-app/components/PageHeading/PageHeading.styled.ts create mode 100644 modules/web/web-app/components/layouts/PageLayout.tsx delete mode 100644 modules/web/web-app/components/table/CustomTable/components/Pagination/Pagination.styled.ts delete mode 100644 modules/web/web-app/features/auth/shared.styled.ts create mode 100644 modules/web/web-app/features/customers/cards/address/AddressCard.tsx create mode 100644 modules/web/web-app/features/customers/cards/address/EditAddressModal.tsx create mode 100644 modules/web/web-app/features/customers/cards/address/schema.ts delete mode 100644 modules/web/web-app/features/customers/headers/CustomerHeader.tsx delete mode 100644 modules/web/web-app/features/customers/headers/CustomersHeader.tsx delete mode 100644 modules/web/web-app/features/customers/headers/index.ts create mode 100644 modules/web/web-app/features/customers/panels/CustomerDetailsPanel.tsx create mode 100644 modules/web/web-app/features/customers/panels/CustomerOverviewPanel.tsx create mode 100644 modules/web/web-app/features/customers/panels/index.ts delete mode 100644 modules/web/web-app/features/invoices/InvoicesHeader.tsx delete mode 100644 modules/web/web-app/features/subscriptions/SubscriptionsHeader.tsx rename modules/web/web-app/public/header/{customer.svg => customers.svg} (100%) create mode 100644 modules/web/web-app/public/header/invoices.svg create mode 100644 modules/web/web-app/public/header/subscriptions.svg diff --git a/modules/web/pnpm-lock.yaml b/modules/web/pnpm-lock.yaml index 16d2e6f5a..847fb89c8 100644 --- a/modules/web/pnpm-lock.yaml +++ b/modules/web/pnpm-lock.yaml @@ -600,9 +600,6 @@ importers: ts-pattern: specifier: ^5.6.2 version: 5.6.2 - vite-plugin-checker: - specifier: ^0.9.3 - version: 0.9.3(eslint@8.57.1)(typescript@5.8.2)(vite@5.4.14) zod: specifier: ^3.24.2 version: 3.24.2 @@ -685,6 +682,9 @@ importers: vite: specifier: ^5.4.14 version: 5.4.14(@types/node@22.13.11)(sass@1.86.0) + vite-plugin-checker: + specifier: ^0.8.0 + version: 0.8.0(eslint@8.57.1)(typescript@5.8.2)(vite@5.4.14) vite-plugin-svgr: specifier: ^4.3.0 version: 4.3.0(typescript@5.8.2)(vite@5.4.14) @@ -727,7 +727,7 @@ packages: '@babel/helper-validator-identifier': 7.27.1 js-tokens: 4.0.0 picocolors: 1.1.1 - dev: false + dev: true /@babel/compat-data@7.26.8: resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} @@ -918,7 +918,7 @@ packages: /@babel/helper-validator-identifier@7.27.1: resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} - dev: false + dev: true /@babel/helper-validator-option@7.25.9: resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} @@ -2423,6 +2423,7 @@ packages: cpu: [ppc64] os: [aix] requiresBuild: true + dev: true optional: true /@esbuild/aix-ppc64@0.25.1: @@ -2449,6 +2450,7 @@ packages: cpu: [arm64] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-arm64@0.25.1: @@ -2475,6 +2477,7 @@ packages: cpu: [arm] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-arm@0.25.1: @@ -2501,6 +2504,7 @@ packages: cpu: [x64] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-x64@0.25.1: @@ -2527,6 +2531,7 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true + dev: true optional: true /@esbuild/darwin-arm64@0.25.1: @@ -2553,6 +2558,7 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true + dev: true optional: true /@esbuild/darwin-x64@0.25.1: @@ -2579,6 +2585,7 @@ packages: cpu: [arm64] os: [freebsd] requiresBuild: true + dev: true optional: true /@esbuild/freebsd-arm64@0.25.1: @@ -2605,6 +2612,7 @@ packages: cpu: [x64] os: [freebsd] requiresBuild: true + dev: true optional: true /@esbuild/freebsd-x64@0.25.1: @@ -2631,6 +2639,7 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-arm64@0.25.1: @@ -2657,6 +2666,7 @@ packages: cpu: [arm] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-arm@0.25.1: @@ -2683,6 +2693,7 @@ packages: cpu: [ia32] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-ia32@0.25.1: @@ -2709,6 +2720,7 @@ packages: cpu: [loong64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-loong64@0.25.1: @@ -2735,6 +2747,7 @@ packages: cpu: [mips64el] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-mips64el@0.25.1: @@ -2761,6 +2774,7 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-ppc64@0.25.1: @@ -2787,6 +2801,7 @@ packages: cpu: [riscv64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-riscv64@0.25.1: @@ -2813,6 +2828,7 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-s390x@0.25.1: @@ -2839,6 +2855,7 @@ packages: cpu: [x64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-x64@0.25.1: @@ -2874,6 +2891,7 @@ packages: cpu: [x64] os: [netbsd] requiresBuild: true + dev: true optional: true /@esbuild/netbsd-x64@0.25.1: @@ -2909,6 +2927,7 @@ packages: cpu: [x64] os: [openbsd] requiresBuild: true + dev: true optional: true /@esbuild/openbsd-x64@0.25.1: @@ -2935,6 +2954,7 @@ packages: cpu: [x64] os: [sunos] requiresBuild: true + dev: true optional: true /@esbuild/sunos-x64@0.25.1: @@ -2961,6 +2981,7 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-arm64@0.25.1: @@ -2987,6 +3008,7 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-ia32@0.25.1: @@ -3013,6 +3035,7 @@ packages: cpu: [x64] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-x64@0.25.1: @@ -5654,6 +5677,7 @@ packages: cpu: [arm] os: [android] requiresBuild: true + dev: true optional: true /@rollup/rollup-android-arm64@4.36.0: @@ -5661,6 +5685,7 @@ packages: cpu: [arm64] os: [android] requiresBuild: true + dev: true optional: true /@rollup/rollup-darwin-arm64@4.36.0: @@ -5668,6 +5693,7 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true + dev: true optional: true /@rollup/rollup-darwin-x64@4.36.0: @@ -5675,6 +5701,7 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true + dev: true optional: true /@rollup/rollup-freebsd-arm64@4.36.0: @@ -5682,6 +5709,7 @@ packages: cpu: [arm64] os: [freebsd] requiresBuild: true + dev: true optional: true /@rollup/rollup-freebsd-x64@4.36.0: @@ -5689,6 +5717,7 @@ packages: cpu: [x64] os: [freebsd] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-arm-gnueabihf@4.36.0: @@ -5696,6 +5725,7 @@ packages: cpu: [arm] os: [linux] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-arm-musleabihf@4.36.0: @@ -5703,6 +5733,7 @@ packages: cpu: [arm] os: [linux] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-arm64-gnu@4.36.0: @@ -5710,6 +5741,7 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-arm64-musl@4.36.0: @@ -5717,6 +5749,7 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-loongarch64-gnu@4.36.0: @@ -5724,6 +5757,7 @@ packages: cpu: [loong64] os: [linux] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-powerpc64le-gnu@4.36.0: @@ -5731,6 +5765,7 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-riscv64-gnu@4.36.0: @@ -5738,6 +5773,7 @@ packages: cpu: [riscv64] os: [linux] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-s390x-gnu@4.36.0: @@ -5745,6 +5781,7 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-x64-gnu@4.36.0: @@ -5752,6 +5789,7 @@ packages: cpu: [x64] os: [linux] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-x64-musl@4.36.0: @@ -5759,6 +5797,7 @@ packages: cpu: [x64] os: [linux] requiresBuild: true + dev: true optional: true /@rollup/rollup-win32-arm64-msvc@4.36.0: @@ -5766,6 +5805,7 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true + dev: true optional: true /@rollup/rollup-win32-ia32-msvc@4.36.0: @@ -5773,6 +5813,7 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true + dev: true optional: true /@rollup/rollup-win32-x64-msvc@4.36.0: @@ -5780,6 +5821,7 @@ packages: cpu: [x64] os: [win32] requiresBuild: true + dev: true optional: true /@rtsao/scc@1.1.0: @@ -7051,6 +7093,7 @@ packages: /@types/estree@1.0.6: resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + dev: true /@types/express-serve-static-core@4.19.6: resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} @@ -7171,6 +7214,7 @@ packages: resolution: {integrity: sha512-iEUCUJoU0i3VnrCmgoWCXttklWcvoCIx4jzcP22fioIVSdTmjgoEvmAO/QPw6TcS9k5FrNgn4w7q5lGOd1CT5g==} dependencies: undici-types: 6.20.0 + dev: true /@types/normalize-package-data@2.4.4: resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -7946,6 +7990,13 @@ packages: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + /ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.21.3 + dev: true + /ansi-escapes@7.0.0: resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==} engines: {node: '>=18'} @@ -8773,6 +8824,11 @@ packages: engines: {node: '>= 10'} dev: true + /commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + dev: true + /commander@9.5.0: resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} engines: {node: ^12.20.0 || >=14} @@ -9886,6 +9942,7 @@ packages: '@esbuild/win32-arm64': 0.21.5 '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 + dev: true /esbuild@0.25.1: resolution: {integrity: sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==} @@ -10428,17 +10485,6 @@ packages: dependencies: picomatch: 4.0.2 - /fdir@6.4.5(picomatch@4.0.2): - resolution: {integrity: sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - dependencies: - picomatch: 4.0.2 - dev: false - /fetch-retry@5.0.6: resolution: {integrity: sha512-3yurQZ2hD9VISAhJJP9bpYFNQrHHBXE2JxxjY5aLEcDi46RmAzJE2OC9FAde0yis5ElW0jTTzs0zfg/Cca4XqQ==} dev: true @@ -12606,14 +12652,6 @@ packages: path-key: 4.0.0 dev: true - /npm-run-path@6.0.0: - resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} - engines: {node: '>=18'} - dependencies: - path-key: 4.0.0 - unicorn-magic: 0.3.0 - dev: false - /npmlog@5.0.1: resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} deprecated: This package is no longer supported. @@ -12948,6 +12986,7 @@ packages: /path-key@4.0.0: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} engines: {node: '>=12'} + dev: true /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -14097,6 +14136,7 @@ packages: '@rollup/rollup-win32-ia32-msvc': 4.36.0 '@rollup/rollup-win32-x64-msvc': 4.36.0 fsevents: 2.3.3 + dev: true /rspack-resolver@1.1.2: resolution: {integrity: sha512-eHhz+9JWHFdbl/CVVqEP6kviLFZqw1s0MWxLdsGMtUKUspSO3SERptPohmrUIC9jT1bGV9Bd3+r8AmWbdfNAzQ==} @@ -15112,14 +15152,6 @@ packages: fdir: 6.4.3(picomatch@4.0.2) picomatch: 4.0.2 - /tinyglobby@0.2.14: - resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} - engines: {node: '>=12.0.0'} - dependencies: - fdir: 6.4.5(picomatch@4.0.2) - picomatch: 4.0.2 - dev: false - /tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} dev: true @@ -15374,6 +15406,11 @@ packages: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} + /type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + dev: true + /type-fest@0.6.0: resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} engines: {node: '>=8'} @@ -15486,6 +15523,7 @@ packages: /undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + dev: true /unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} @@ -15510,11 +15548,6 @@ packages: engines: {node: '>=4'} dev: true - /unicorn-magic@0.3.0: - resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} - engines: {node: '>=18'} - dev: false - /unique-string@2.0.0: resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} engines: {node: '>=8'} @@ -15696,20 +15729,20 @@ packages: engines: {node: '>= 0.8'} dev: true - /vite-plugin-checker@0.9.3(eslint@8.57.1)(typescript@5.8.2)(vite@5.4.14): - resolution: {integrity: sha512-Tf7QBjeBtG7q11zG0lvoF38/2AVUzzhMNu+Wk+mcsJ00Rk/FpJ4rmUviVJpzWkagbU13cGXvKpt7CMiqtxVTbQ==} + /vite-plugin-checker@0.8.0(eslint@8.57.1)(typescript@5.8.2)(vite@5.4.14): + resolution: {integrity: sha512-UA5uzOGm97UvZRTdZHiQVYFnd86AVn8EVaD4L3PoVzxH+IZSfaAw14WGFwX9QS23UW3lV/5bVKZn6l0w+q9P0g==} engines: {node: '>=14.16'} peerDependencies: '@biomejs/biome': '>=1.7' eslint: '>=7' - meow: ^13.2.0 - optionator: ^0.9.4 - stylelint: '>=16' + meow: ^9.0.0 + optionator: ^0.9.1 + stylelint: '>=13' typescript: '*' vite: '>=2.0.0' vls: '*' vti: '*' - vue-tsc: ~2.2.10 + vue-tsc: ~2.1.6 peerDependenciesMeta: '@biomejs/biome': optional: true @@ -15731,18 +15764,23 @@ packages: optional: true dependencies: '@babel/code-frame': 7.27.1 - chokidar: 4.0.3 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + chokidar: 3.6.0 + commander: 8.3.0 eslint: 8.57.1 - npm-run-path: 6.0.0 - picocolors: 1.1.1 - picomatch: 4.0.2 - strip-ansi: 7.1.0 + fast-glob: 3.3.3 + fs-extra: 11.3.0 + npm-run-path: 4.0.1 + strip-ansi: 6.0.1 tiny-invariant: 1.3.3 - tinyglobby: 0.2.14 typescript: 5.8.2 vite: 5.4.14(@types/node@22.13.11)(sass@1.86.0) + vscode-languageclient: 7.0.0 + vscode-languageserver: 7.0.0 + vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.1.0 - dev: false + dev: true /vite-plugin-dts@3.9.1(@types/node@22.13.11)(typescript@5.8.2)(vite@5.4.14): resolution: {integrity: sha512-rVp2KM9Ue22NGWB8dNtWEr+KekN3rIgz1tWD050QnRGlriUCmaDwa7qA5zDEjbXg5lAXhYMSBJtx3q3hQIJZSg==} @@ -15839,10 +15877,47 @@ packages: sass: 1.86.0 optionalDependencies: fsevents: 2.3.3 + dev: true + + /vscode-jsonrpc@6.0.0: + resolution: {integrity: sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==} + engines: {node: '>=8.0.0 || >=10.0.0'} + dev: true + + /vscode-languageclient@7.0.0: + resolution: {integrity: sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg==} + engines: {vscode: ^1.52.0} + dependencies: + minimatch: 3.1.2 + semver: 7.7.1 + vscode-languageserver-protocol: 3.16.0 + dev: true + + /vscode-languageserver-protocol@3.16.0: + resolution: {integrity: sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==} + dependencies: + vscode-jsonrpc: 6.0.0 + vscode-languageserver-types: 3.16.0 + dev: true + + /vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + dev: true + + /vscode-languageserver-types@3.16.0: + resolution: {integrity: sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==} + dev: true + + /vscode-languageserver@7.0.0: + resolution: {integrity: sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==} + hasBin: true + dependencies: + vscode-languageserver-protocol: 3.16.0 + dev: true /vscode-uri@3.1.0: resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} - dev: false + dev: true /vue-template-compiler@2.7.16: resolution: {integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==} diff --git a/modules/web/web-app/components/ButtonTabs.tsx b/modules/web/web-app/components/ButtonTabs.tsx new file mode 100644 index 000000000..749a88643 --- /dev/null +++ b/modules/web/web-app/components/ButtonTabs.tsx @@ -0,0 +1,25 @@ +import { Button, ButtonProps, cn } from "@ui/index" +import { PropsWithChildren } from "react" + +interface ButtonTabsProps extends Omit, PropsWithChildren { + active?: boolean +} + +export const ButtonTabs = ({ children, active = false, ...props }: ButtonTabsProps) => { + const { className, ...rest } = props + + return ( + + ) +} \ No newline at end of file diff --git a/modules/web/web-app/components/PageHeading/PageHeading.styled.ts b/modules/web/web-app/components/PageHeading/PageHeading.styled.ts deleted file mode 100644 index 32790c67a..000000000 --- a/modules/web/web-app/components/PageHeading/PageHeading.styled.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { colors, fontSizes, fontWeights, spaces } from '@md/foundation' -import { styled } from '@stitches/react' - -export const StyledPageHeading = styled('h1', { - fontSize: fontSizes.fontSize6, - fontWeight: fontWeights.bold, - lineHeight: 1, -}) - -export const Count = styled('span', { - display: 'inline-block', - fontSize: fontSizes.fontSize4, - fontWeight: fontWeights.medium, - lineHeight: 1, - color: colors.neutral9, - marginLeft: spaces.space3, -}) diff --git a/modules/web/web-app/components/PageHeading/PageHeading.tsx b/modules/web/web-app/components/PageHeading/PageHeading.tsx index 0f556b102..92ff9277c 100644 --- a/modules/web/web-app/components/PageHeading/PageHeading.tsx +++ b/modules/web/web-app/components/PageHeading/PageHeading.tsx @@ -1,5 +1,3 @@ -import { Count, StyledPageHeading } from '@/components/PageHeading/PageHeading.styled' - import type { FunctionComponent, ReactNode } from 'react' interface PageHeadingProps { @@ -9,10 +7,14 @@ interface PageHeadingProps { const PageHeading: FunctionComponent = ({ children, count }) => { return ( - +

    {children} - {count !== undefined && count >= 0 && ({count})} - + {count !== undefined && count >= 0 && ( + + ({count}) + + )} +

    ) } diff --git a/modules/web/web-app/components/layouts/PageLayout.tsx b/modules/web/web-app/components/layouts/PageLayout.tsx new file mode 100644 index 000000000..bc8268f2e --- /dev/null +++ b/modules/web/web-app/components/layouts/PageLayout.tsx @@ -0,0 +1,76 @@ +import { Flex } from '@ui/index' +import { PropsWithChildren, ReactNode } from 'react' +import { useSearchParams } from 'react-router-dom' + +import { ButtonTabs } from '@/components/ButtonTabs' + +interface TabConfig { + key: string + label: string +} + +interface PageLayoutProps extends PropsWithChildren { + imgLink: 'customers' | 'invoices' | 'subscriptions' + title: string + tabs?: TabConfig[] + customTabs?: ReactNode + actions?: ReactNode +} + +export const PageLayout = ({ imgLink, title, children, tabs, customTabs, actions }: PageLayoutProps) => { + const [searchParams, setSearchParams] = useSearchParams() + const currentTab = searchParams.get('tab') || (tabs?.[0]?.key ?? 'all') + + const updateTab = (tab: string) => { + const newSearchParams = new URLSearchParams(searchParams) + + if (tab === (tabs?.[0]?.key ?? 'all')) { + newSearchParams.delete('tab') + } else { + newSearchParams.set('tab', tab.toLowerCase()) + } + setSearchParams(newSearchParams) + } + + const renderTabs = () => { + if (customTabs) { + return customTabs + } + + if (tabs && tabs.length > 0) { + return ( + + {tabs.map((tab) => ( + updateTab(tab.key)} + > + {tab.label} + + ))} + + ) + } + + return null + } + + return
    +
    + + + + {title} +
    {title}
    + {renderTabs()} +
    + {actions && + {actions} + } +
    + {children} +
    +
    +
    +} \ No newline at end of file diff --git a/modules/web/web-app/components/table/CustomTable/components/Pagination/Pagination.styled.ts b/modules/web/web-app/components/table/CustomTable/components/Pagination/Pagination.styled.ts deleted file mode 100644 index 1952437bf..000000000 --- a/modules/web/web-app/components/table/CustomTable/components/Pagination/Pagination.styled.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { colors, fontSizes, fontWeights, spaces } from '@md/foundation' -import { styled } from '@stitches/react' - -export const CountInfo = styled('div', { - fontSize: fontSizes.fontSize2, - color: colors.neutral11, - display: 'flex', - alignItems: 'center', - gap: spaces.space2, - lineHeight: 1, - - '& > span': { - fontWeight: fontWeights.medium, - }, -}) diff --git a/modules/web/web-app/features/auth/shared.styled.ts b/modules/web/web-app/features/auth/shared.styled.ts deleted file mode 100644 index acf3b2768..000000000 --- a/modules/web/web-app/features/auth/shared.styled.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { styled } from '@stitches/react' - -export const StyledContainer = styled('div', { - height: '100%', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', -}) diff --git a/modules/web/web-app/features/customers/cards/address/AddressCard.tsx b/modules/web/web-app/features/customers/cards/address/AddressCard.tsx new file mode 100644 index 000000000..92b6c32dc --- /dev/null +++ b/modules/web/web-app/features/customers/cards/address/AddressCard.tsx @@ -0,0 +1,71 @@ +import { ComponentProps, useState } from 'react' + +import { PageSection } from '@/components/layouts/shared/PageSection' +import { CardAction } from '@/features/customers/cards/CardAction' +import { EditAddressModal } from '@/features/customers/cards/address/EditAddressModal' +import { Address, Customer } from '@/rpc/api/customers/v1/models_pb' + +type Props = Pick, 'className'> & { + customer: Customer +} + +export const AddressLines = ({ address }: { address: Partial
    }) => { + return ( +
    + {address.line1} + {address.line2} + {address.city} + {address.state} + {address.country} + {address.zipCode} +
    + ) +} + +export const AddressLinesCompact = ({ address }: { address: Partial
    }) => { + return ( +
    + {address.line1} + {address.line2} + + {address.city}, {address.state} {address.zipCode} + + {address.country} +
    + ) +} + +export const AddressCard = ({ customer, className }: Props) => { + const [editModalVisible, setEditModalVisible] = useState(false) + + return ( + setEditModalVisible(true)} />, + }} + > +
    +
    + Billing address + {customer.billingAddress && } +
    +
    + Shipping address + {customer.shippingAddress?.sameAsBilling ? ( + Same as billing address + ) : ( + + )} +
    +
    + + setEditModalVisible(false)} + /> +
    + ) +} \ No newline at end of file diff --git a/modules/web/web-app/features/customers/cards/address/EditAddressModal.tsx b/modules/web/web-app/features/customers/cards/address/EditAddressModal.tsx new file mode 100644 index 000000000..13585b4c9 --- /dev/null +++ b/modules/web/web-app/features/customers/cards/address/EditAddressModal.tsx @@ -0,0 +1,122 @@ +import { createConnectQueryKey, useMutation } from '@connectrpc/connect-query' +import { CheckboxFormField, Form, InputFormField, Modal } from '@md/ui' +import { useQueryClient } from '@tanstack/react-query' +import { ComponentProps } from 'react' +import { toast } from 'sonner' +import { z } from 'zod' + +import { addressesSchema } from '@/features/customers/cards/address/schema' +import { useZodForm } from '@/hooks/useZodForm' +import { + getCustomerById, + patchCustomer, +} from '@/rpc/api/customers/v1/customers-CustomersService_connectquery' +import { Customer } from '@/rpc/api/customers/v1/models_pb' + +type Props = Pick, 'visible' | 'onCancel'> & { + customer: Customer +} + +export const EditAddressModal = ({ customer, ...props }: Props) => { + const queryClient = useQueryClient() + const patchCustomerMutation = useMutation(patchCustomer, { + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: createConnectQueryKey(getCustomerById, { id: customer.id }), + }) + }, + }) + + const methods = useZodForm({ + mode: 'onChange', + schema: addressesSchema, + defaultValues: { + billing_address: customer.billingAddress, + shipping_address: { + ...customer.shippingAddress, + sameAsBilling: customer.shippingAddress?.sameAsBilling !== false, + }, + }, + }) + const sameShippingAddress = methods.watch('shipping_address.sameAsBilling') + + const onSubmit = async (data: z.infer) => { + await patchCustomerMutation.mutateAsync({ + customer: { + id: customer.id, + billingAddress: data.billing_address, + shippingAddress: data.shipping_address, + }, + }) + toast.success('Address updated') + props.onCancel?.() + methods.reset() + } + const inputProps = { + control: methods.control, + direction: 'horizontal' as const, + } + + return ( + Edit address} {...props} onConfirm={() => methods.handleSubmit(onSubmit)()}> + +
    + +
    +

    Billing address

    + + + + + + + +

    Shipping address

    + + + {sameShippingAddress || ( + <> + + + + + + + + )} +
    +
    + +
    +
    + ) +} \ No newline at end of file diff --git a/modules/web/web-app/features/customers/cards/address/schema.ts b/modules/web/web-app/features/customers/cards/address/schema.ts new file mode 100644 index 000000000..4fe142d2c --- /dev/null +++ b/modules/web/web-app/features/customers/cards/address/schema.ts @@ -0,0 +1,18 @@ +import { z } from 'zod' + +const addressSchema = z.object({ + line1: z.string().optional(), + line2: z.string().optional(), + city: z.string().optional(), + country: z.string().optional(), + state: z.string().optional(), + zipcode: z.string().optional(), +}) +const shippingAddressSchema = z.object({ + address: addressSchema.optional(), + sameAsBilling: z.boolean(), +}) +export const addressesSchema = z.object({ + billing_address: addressSchema.optional(), + shipping_address: shippingAddressSchema.optional(), +}) \ No newline at end of file diff --git a/modules/web/web-app/features/customers/headers/CustomerHeader.tsx b/modules/web/web-app/features/customers/headers/CustomerHeader.tsx deleted file mode 100644 index 284ecf888..000000000 --- a/modules/web/web-app/features/customers/headers/CustomerHeader.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { spaces } from '@md/foundation' -import { - Button, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, - Input, - Flex as NewFlex, - Separator, - cn, -} from '@md/ui' -import { Flex } from '@ui/components/legacy' -import { Check, ChevronDown, ChevronRight } from 'lucide-react' -import { FunctionComponent, useState } from 'react' -import { useNavigate } from 'react-router-dom' - -import { Loader } from '@/features/auth/components/Loader' -import { useBasePath } from '@/hooks/useBasePath' -import { useDebounceValue } from '@/hooks/useDebounce' -import { useQuery } from '@/lib/connectrpc' -import { listCustomers } from '@/rpc/api/customers/v1/customers-CustomersService_connectquery' -import { ListCustomerRequest_SortBy } from '@/rpc/api/customers/v1/customers_pb' - -interface CustomerHeaderProps { - name?: string - setEditPanelVisible: (visible: boolean) => void - setShowIncoice: () => void -} - -export const CustomerHeader: FunctionComponent = ({ - name, - setEditPanelVisible, - setShowIncoice, -}) => { - const basePath = useBasePath() - const navigate = useNavigate() - - const [search, setSearch] = useState('') - - const debouncedSearch = useDebounceValue(search, 400) - - const pageIndex = 0 - const pageSize = 20 - - const customersQuery = useQuery( - listCustomers, - { - pagination: { - limit: pageSize, - offset: pageIndex * pageSize, - }, - search: debouncedSearch.length > 0 ? debouncedSearch : undefined, - sortBy: ListCustomerRequest_SortBy.NAME_ASC, - }, - {} - ) - - const data = customersQuery.data?.customers ?? [] - const isLoading = customersQuery.isLoading - - return ( - <> - - - - customer logo -
    navigate('..')} - > - Customers -
    - -
    {name}
    - - - - - - - - setSearch(e.target.value)} - autoFocus - placeholder="Search..." - className="h-7 w-full bg-transparent focus-visible:shadow-none outline-none border-none" - /> - - {isLoading ? ( - - ) : ( - data.map(customer => ( - navigate(`${basePath}/customers/${customer.id}`)} - key={customer.id} - className={cn(customer.name === name && 'bg-accent', 'mt-1 cursor-pointer')} - > - - {customer.name} - {customer.name === name && } - - - )) - )} - - -
    - - - - - - - - Assign subscription - Charge one time payment - Create Invoice - Create quote - - Add balance - setEditPanelVisible(true)}> - Edit customer details - - - Archive customer - - - -
    -
    - -
    -
    - - ) -} diff --git a/modules/web/web-app/features/customers/headers/CustomersHeader.tsx b/modules/web/web-app/features/customers/headers/CustomersHeader.tsx deleted file mode 100644 index bb219174f..000000000 --- a/modules/web/web-app/features/customers/headers/CustomersHeader.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { spaces } from '@md/foundation' -import { SearchIcon } from '@md/icons' -import { Button, ButtonProps, InputWithIcon, Flex as NewFlex, Separator, cn } from '@md/ui' -import { Flex } from '@ui/components/legacy' -import { ListFilter } from 'lucide-react' -import { FunctionComponent, PropsWithChildren, useState } from 'react' -import { useSearchParams } from 'react-router-dom' - -import { CustomersExportModal } from '@/features/customers/modals/CustomersExportModal' - -interface CustomersHeaderProps { - setEditPanelVisible: (visible: boolean) => void - setSearch: (search: string) => void - search: string -} - -export const CustomersHeader: FunctionComponent = ({ - setEditPanelVisible, - setSearch, - search, -}) => { - const [searchParams, setSearchParams] = useSearchParams() - const currentTab = searchParams.get('tab') || 'all' - - const [visible, setVisible] = useState(false) - - const updateTab = (tab: string) => { - const newSearchParams = new URLSearchParams(searchParams) - - if (tab === 'all') newSearchParams.delete('tab') - else { - newSearchParams.set('tab', tab.toLowerCase()) - } - setSearchParams(newSearchParams) - } - - return ( - <> - - - - customer logo -
    Customers
    - - updateTab('all')}> - All - - updateTab('active')}> - Active - - updateTab('inactive')}> - Inactive - - updateTab('archived')}> - Archived - - -
    - - - - -
    -
    - -
    - - } - width="fit-content" - value={search} - onChange={e => setSearch(e.target.value)} - /> - - -
    - - - ) -} - -interface ButtonTabsProps extends Omit, PropsWithChildren { - active?: boolean -} - -const ButtonTabs = ({ children, active = false, ...props }: ButtonTabsProps) => { - const { className, ...rest } = props - - return ( - - ) -} diff --git a/modules/web/web-app/features/customers/headers/index.ts b/modules/web/web-app/features/customers/headers/index.ts deleted file mode 100644 index 0fd719ec3..000000000 --- a/modules/web/web-app/features/customers/headers/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { CustomerHeader } from './CustomerHeader' -export { CustomersHeader } from './CustomersHeader' diff --git a/modules/web/web-app/features/customers/index.tsx b/modules/web/web-app/features/customers/index.tsx index 50f1363ba..d0875b3be 100644 --- a/modules/web/web-app/features/customers/index.tsx +++ b/modules/web/web-app/features/customers/index.tsx @@ -1,3 +1,4 @@ export { CustomersEditPanel } from './CustomersEditPanel' -export { CustomerHeader, CustomersHeader } from './headers' +export { CustomerDetailsPanel, CustomerOverviewPanel } from './panels' export { CustomersTable } from './table/CustomersTable' + diff --git a/modules/web/web-app/features/customers/panels/CustomerDetailsPanel.tsx b/modules/web/web-app/features/customers/panels/CustomerDetailsPanel.tsx new file mode 100644 index 000000000..9063ff899 --- /dev/null +++ b/modules/web/web-app/features/customers/panels/CustomerDetailsPanel.tsx @@ -0,0 +1,49 @@ +import { Flex, Separator } from '@md/ui' + +import { Customer } from '@/rpc/api/customers/v1/models_pb' + +interface CustomerDetailsPanelProps { + customer: Customer +} + +export const CustomerDetailsPanel = ({ customer }: CustomerDetailsPanelProps) => { + return ( + + +
    {customer.name}
    +
    {customer.alias}
    + + + + + +
    Address
    +
    {customer.billingAddress?.city}
    +
    + + +
    + + +
    Integrations
    + + {/* TODO */} + +
    + + +
    Payment
    + + + +
    +
    + ) +} + +const FlexDetails = ({ title, value }: { title: string; value?: string }) => ( + +
    {title}
    +
    {value ?? 'N/A'}
    +
    +) \ No newline at end of file diff --git a/modules/web/web-app/features/customers/panels/CustomerOverviewPanel.tsx b/modules/web/web-app/features/customers/panels/CustomerOverviewPanel.tsx new file mode 100644 index 000000000..bcf6dfeee --- /dev/null +++ b/modules/web/web-app/features/customers/panels/CustomerOverviewPanel.tsx @@ -0,0 +1,55 @@ +import { Card, Flex } from '@md/ui' +import { ChevronDown, Plus } from 'lucide-react' + +import { InvoicesCard } from '@/features/customers/cards/InvoicesCard' +import { SubscriptionsCard } from '@/features/customers/cards/SubscriptionsCard' +import { Customer } from '@/rpc/api/customers/v1/models_pb' + +interface CustomerOverviewPanelProps { + customer: Customer + onCreateInvoice: () => void +} + +export const CustomerOverviewPanel = ({ customer, onCreateInvoice }: CustomerOverviewPanelProps) => { + return ( + +
    Overview
    +
    + + +
    + +
    Subscriptions
    + + Assign subscription + +
    +
    + +
    + +
    Invoices
    + + Create invoice + +
    +
    + +
    +
    + ) +} + +const OverviewCard = ({ title, value }: { title: string; value?: number }) => ( + + +
    {title}
    + +
    +
    € {value}
    +
    +) \ No newline at end of file diff --git a/modules/web/web-app/features/customers/panels/index.ts b/modules/web/web-app/features/customers/panels/index.ts new file mode 100644 index 000000000..53e4d5ccd --- /dev/null +++ b/modules/web/web-app/features/customers/panels/index.ts @@ -0,0 +1,3 @@ +export { CustomerDetailsPanel } from './CustomerDetailsPanel' +export { CustomerOverviewPanel } from './CustomerOverviewPanel' + diff --git a/modules/web/web-app/features/invoices/InvoicesHeader.tsx b/modules/web/web-app/features/invoices/InvoicesHeader.tsx deleted file mode 100644 index f88116b89..000000000 --- a/modules/web/web-app/features/invoices/InvoicesHeader.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { spaces } from '@md/foundation' -import { PlusIcon, SearchIcon } from '@md/icons' -import { Button, InputWithIcon } from '@md/ui' -import { Flex } from '@ui/components/legacy' -import { RefreshCwIcon } from 'lucide-react' -import { FunctionComponent } from 'react' - -import PageHeading from '@/components/PageHeading/PageHeading' -import { FilterDropdown } from '@/features/invoices/FilterDropdown' -import { InvoicesSearch } from '@/features/invoices/types' - -type InvoicesProps = { - count: number - isLoading: boolean - refetch: () => void - setEditPanelVisible: (visible: boolean) => void - setSearch: (search: InvoicesSearch) => void - search: InvoicesSearch -} - -export const InvoicesHeader: FunctionComponent = ({ - count, - isLoading, - refetch, - setEditPanelVisible, - setSearch, - search, -}) => { - return ( - - - Invoices - - - - - - - } - width="fit-content" - value={search.text} - onChange={e => setSearch({ ...search, text: e.target.value })} - /> - - setSearch({ ...search, status: value })} - /> - - - ) -} diff --git a/modules/web/web-app/features/invoices/index.tsx b/modules/web/web-app/features/invoices/index.tsx index c2b82037f..2db40b97b 100644 --- a/modules/web/web-app/features/invoices/index.tsx +++ b/modules/web/web-app/features/invoices/index.tsx @@ -1,2 +1,2 @@ -export { InvoicesHeader } from './InvoicesHeader' -export { InvoicesTable } from './InvoicesTable' +export { InvoicesTable } from './InvoicesTable'; + diff --git a/modules/web/web-app/features/subscriptions/SubscriptionsHeader.tsx b/modules/web/web-app/features/subscriptions/SubscriptionsHeader.tsx deleted file mode 100644 index 27117f75d..000000000 --- a/modules/web/web-app/features/subscriptions/SubscriptionsHeader.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { colors, spaces } from '@md/foundation' -import { PlusIcon, SearchIcon } from '@md/icons' -import { Button, InputWithIcon } from '@ui/components' -import { Flex } from '@ui/components/legacy' -import { RefreshCwIcon } from 'lucide-react' -import { FunctionComponent } from 'react' -import { Link } from 'react-router-dom' - -import PageHeading from '@/components/PageHeading/PageHeading' - -interface SubscriptionsProps { - count: number - isLoading: boolean - refetch: () => void -} - -export const SubscriptionsHeader: FunctionComponent = ({ - count, - isLoading, - refetch, -}) => { - return ( - - - Subscriptions - - - - - - - - } - width="fit-content" - disabled - /> - - - - ) -} diff --git a/modules/web/web-app/features/subscriptions/index.tsx b/modules/web/web-app/features/subscriptions/index.tsx index 7dba11cab..247ac46b2 100644 --- a/modules/web/web-app/features/subscriptions/index.tsx +++ b/modules/web/web-app/features/subscriptions/index.tsx @@ -1,2 +1,2 @@ -export { SubscriptionsHeader } from './SubscriptionsHeader' -export { SubscriptionsTable } from './SubscriptionsTable' +export { SubscriptionsTable } from './SubscriptionsTable'; + diff --git a/modules/web/web-app/package.json b/modules/web/web-app/package.json index 090609ce2..7b110cb57 100644 --- a/modules/web/web-app/package.json +++ b/modules/web/web-app/package.json @@ -58,7 +58,6 @@ "styled-components": "^6.1.16", "superjson": "2.2.1", "ts-pattern": "^5.6.2", - "vite-plugin-checker": "^0.9.3", "zod": "^3.24.2", "zustand": "^4.5.6" }, @@ -88,6 +87,7 @@ "tailwindcss-radix": "^2.9.0", "typescript": "^5.8.2", "vite": "^5.4.14", + "vite-plugin-checker": "^0.8.0", "vite-plugin-svgr": "^4.3.0", "vite-tsconfig-paths": "^4.3.2" }, @@ -102,4 +102,4 @@ "generate:proto": "buf generate --template=buf.gen.yaml ../../..", "postinstall": "pnpm run generate:proto" } -} +} \ No newline at end of file diff --git a/modules/web/web-app/pages/tenants/billing/index.tsx b/modules/web/web-app/pages/tenants/billing/index.tsx index 467b3c6a3..ab5430fc8 100644 --- a/modules/web/web-app/pages/tenants/billing/index.tsx +++ b/modules/web/web-app/pages/tenants/billing/index.tsx @@ -1,7 +1,7 @@ import { createConnectQueryKey, useMutation } from '@connectrpc/connect-query' import { useQueryClient } from '@tanstack/react-query' import { FunctionComponent } from 'react' -import { Navigate, Outlet } from 'react-router-dom' +import { Navigate } from 'react-router-dom' import { TenantPageLayout } from '@/components/layouts' import ProductEmptyState from '@/features/productCatalog/ProductEmptyState' @@ -14,14 +14,6 @@ export const Billing: FunctionComponent = () => { return } -export const BillingOutlet: FunctionComponent = () => { - return ( - - - - ) -} - export const FamilyCreationModalPage = () => { const queryClient = useQueryClient() diff --git a/modules/web/web-app/pages/tenants/customer/customer.tsx b/modules/web/web-app/pages/tenants/customer/customer.tsx index 0339d7f0f..ea1c077bf 100644 --- a/modules/web/web-app/pages/tenants/customer/customer.tsx +++ b/modules/web/web-app/pages/tenants/customer/customer.tsx @@ -1,21 +1,42 @@ -import { Card, Flex, Separator, Skeleton } from '@md/ui' -import { ChevronDown, Plus } from 'lucide-react' +import { + Button, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, + Flex, + Input, + Separator, + Skeleton, + cn, +} from '@md/ui' +import { Check, ChevronDown, ChevronRight } from 'lucide-react' import { Fragment, useState } from 'react' +import { useNavigate } from 'react-router-dom' -import { TenantPageLayout } from '@/components/layouts' -import { CustomerHeader, CustomersEditPanel } from '@/features/customers' -import { InvoicesCard } from '@/features/customers/cards/InvoicesCard' -import { SubscriptionsCard } from '@/features/customers/cards/SubscriptionsCard' +import { PageLayout } from '@/components/layouts/PageLayout' +import { Loader } from '@/features/auth/components/Loader' +import { CustomersEditPanel } from '@/features/customers' import { CustomerInvoiceModal } from '@/features/customers/modals/CustomerInvoiceModal' +import { CustomerDetailsPanel, CustomerOverviewPanel } from '@/features/customers/panels' +import { useBasePath } from '@/hooks/useBasePath' +import { useDebounceValue } from '@/hooks/useDebounce' import { useQuery } from '@/lib/connectrpc' -import { getCustomerById } from '@/rpc/api/customers/v1/customers-CustomersService_connectquery' +import { getCustomerById, listCustomers } from '@/rpc/api/customers/v1/customers-CustomersService_connectquery' +import { ListCustomerRequest_SortBy } from '@/rpc/api/customers/v1/customers_pb' import { useTypedParams } from '@/utils/params' export const Customer = () => { const { customerId } = useTypedParams<{ customerId: string }>() + const basePath = useBasePath() + const navigate = useNavigate() const [editPanelVisible, setEditPanelVisible] = useState(false) const [createInvoiceVisible, setCreateInvoiceVisible] = useState(false) + const [search, setSearch] = useState('') + + const debouncedSearch = useDebounceValue(search, 400) const customerQuery = useQuery( getCustomerById, @@ -25,88 +46,120 @@ export const Customer = () => { { enabled: Boolean(customerId) } ) + const customersQuery = useQuery( + listCustomers, + { + pagination: { + limit: 20, + offset: 0, + }, + search: debouncedSearch.length > 0 ? debouncedSearch : undefined, + sortBy: ListCustomerRequest_SortBy.NAME_ASC, + }, + {} + ) + const data = customerQuery.data?.customer const isLoading = customerQuery.isLoading + const customersList = customersQuery.data?.customers ?? [] + const isCustomersLoading = customersQuery.isLoading return ( - - - setCreateInvoiceVisible(true)} - /> - {isLoading || !data ? ( - <> - - - - ) : ( - - -
    Overview
    -
    - - -
    - -
    Subscriptions
    - - Assign subscription - -
    -
    - -
    - -
    Invoices
    - setCreateInvoiceVisible(true)} - > - Create invoice - -
    -
    - -
    -
    - - -
    {data.name}
    -
    {data.alias}
    - - - - - -
    Address
    -
    {data.billingAddress?.city}
    -
    - - -
    - - -
    Integrations
    - - {/* TODO */} - -
    - - -
    Payment
    - - - -
    + +
    navigate('..')} + > + Customers +
    + +
    {data?.name || data?.alias}
    + + + + -
    - )} -
    -
    + + + setSearch(e.target.value)} + autoFocus + placeholder="Search..." + className="h-7 w-full bg-transparent focus-visible:shadow-none outline-none border-none" + /> + + {isCustomersLoading ? ( + + ) : ( + customersList.map(customer => ( + navigate(`${basePath}/customers/${customer.id}`)} + key={customer.id} + className={cn(customer.name === data?.name && 'bg-accent', 'mt-1 cursor-pointer')} + > + + {customer.name} + {customer.name === data?.name && } + + + )) + )} + + + } + actions={<> + + + + + + + Assign subscription + Charge one time payment + setCreateInvoiceVisible(true)}>Create Invoice + Create quote + + Add balance + setEditPanelVisible(true)}> + Edit customer details + + + Archive customer + + + } + > +
    + +
    + {isLoading || !data ? ( + <> + + + + ) : ( +
    + setCreateInvoiceVisible(true)} + /> + +
    + )} + setEditPanelVisible(false)} @@ -115,20 +168,3 @@ export const Customer = () => {
    ) } - -const OverviewCard = ({ title, value }: { title: string; value?: number }) => ( - - -
    {title}
    - -
    -
    € {value}
    -
    -) - -const FlexDetails = ({ title, value }: { title: string; value?: string }) => ( - -
    {title}
    -
    {value ?? 'N/A'}
    -
    -) diff --git a/modules/web/web-app/pages/tenants/customer/customers.tsx b/modules/web/web-app/pages/tenants/customer/customers.tsx index c416543da..eedb36e4e 100644 --- a/modules/web/web-app/pages/tenants/customer/customers.tsx +++ b/modules/web/web-app/pages/tenants/customer/customers.tsx @@ -1,9 +1,13 @@ -import { Button, Flex } from '@ui/index' +import { SearchIcon } from '@md/icons' +import { Button, InputWithIcon, Separator } from '@md/ui' +import { Flex } from '@ui/index' +import { ListFilter } from 'lucide-react' import { Fragment, FunctionComponent, useState } from 'react' import { EmptyState } from '@/components/empty-state/EmptyState' -import { TenantPageLayout } from '@/components/layouts' -import { CustomersEditPanel, CustomersHeader, CustomersTable } from '@/features/customers' +import { PageLayout } from '@/components/layouts/PageLayout' +import { CustomersEditPanel, CustomersTable } from '@/features/customers' +import { CustomersExportModal } from '@/features/customers/modals/CustomersExportModal' import { useDebounceValue } from '@/hooks/useDebounce' import { useQuery } from '@/lib/connectrpc' import { listCustomers } from '@/rpc/api/customers/v1/customers-CustomersService_connectquery' @@ -14,6 +18,7 @@ import type { PaginationState } from '@tanstack/react-table' export const Customers: FunctionComponent = () => { const [createPanelVisible, setCreatePanelVisible] = useState(false) const [search, setSearch] = useState('') + const [exportModalVisible, setExportModalVisible] = useState(false) const debouncedSearch = useDebounceValue(search, 400) @@ -43,39 +48,70 @@ export const Customers: FunctionComponent = () => { return ( - - - + + + } + > +
    + +
    + + } + width="fit-content" + value={search} + onChange={e => setSearch(e.target.value)} /> - {isEmpty ? ( - setCreatePanelVisible(true)}> - New customer - - } - /> - ) : ( - - )} + -
    + {isEmpty ? ( + setCreatePanelVisible(true)}> + New customer + + } + /> + ) : ( + + )} + setCreatePanelVisible(false)} /> +
    ) } diff --git a/modules/web/web-app/pages/tenants/invoice/Invoices.tsx b/modules/web/web-app/pages/tenants/invoice/Invoices.tsx index 916cc7401..c110238a7 100644 --- a/modules/web/web-app/pages/tenants/invoice/Invoices.tsx +++ b/modules/web/web-app/pages/tenants/invoice/Invoices.tsx @@ -1,8 +1,12 @@ -import { spaces } from '@md/foundation' -import { Flex } from '@ui/components/legacy' +import { SearchIcon } from '@md/icons' +import { Button, InputWithIcon } from '@md/ui' +import { Flex } from '@ui/index' +import { RefreshCwIcon } from 'lucide-react' import { Fragment, useState } from 'react' -import { InvoicesHeader, InvoicesTable } from '@/features/invoices' +import { PageLayout } from '@/components/layouts/PageLayout' +import { InvoicesTable } from '@/features/invoices' +import { FilterDropdown } from '@/features/invoices/FilterDropdown' import { InvoicesSearch } from '@/features/invoices/types' import { useDebounceValue } from '@/hooks/useDebounce' import { useQuery } from '@/lib/connectrpc' @@ -12,7 +16,7 @@ import { ListInvoicesRequest_SortBy } from '@/rpc/api/invoices/v1/invoices_pb' import type { PaginationState } from '@tanstack/react-table' export const Invoices = () => { - const [, setEditPanelVisible] = useState(false) + const [_, setEditPanelVisible] = useState(false) const [search, setSearch] = useState({}) const debouncedSearch = useDebounceValue(search, 400) @@ -44,17 +48,40 @@ export const Invoices = () => { invoicesQuery.refetch() } + const tabs = [ + { key: 'active', label: 'Active' }, + { key: 'expired', label: 'Expired' }, + { key: 'cancelled', label: 'Cancelled' } + ] + return ( - - + setEditPanelVisible(true)} size="sm"> + New invoice + + } + > + + } + width="fit-content" + value={search.text} + onChange={e => setSearch({ ...search, text: e.target.value })} + /> + + setSearch({ ...search, status: value })} + /> + { setPagination={setPagination} isLoading={isLoading} /> - + ) } diff --git a/modules/web/web-app/pages/tenants/subscription/subscriptions.tsx b/modules/web/web-app/pages/tenants/subscription/subscriptions.tsx index d9c74b9a7..4ab9fba99 100644 --- a/modules/web/web-app/pages/tenants/subscription/subscriptions.tsx +++ b/modules/web/web-app/pages/tenants/subscription/subscriptions.tsx @@ -1,8 +1,12 @@ -import { spaces } from '@md/foundation' -import { Flex } from '@ui/components/legacy' +import { SearchIcon } from '@md/icons' +import { Button, InputWithIcon } from '@ui/components' +import { Flex } from '@ui/index' +import { RefreshCwIcon } from 'lucide-react' import { useState } from 'react' +import { Link } from 'react-router-dom' -import { SubscriptionsHeader, SubscriptionsTable } from '@/features/subscriptions' +import { PageLayout } from '@/components/layouts/PageLayout' +import { SubscriptionsTable } from '@/features/subscriptions' import { useQuery } from '@/lib/connectrpc' import { listSubscriptions } from '@/rpc/api/subscriptions/v1/subscriptions-SubscriptionsService_connectquery' @@ -33,9 +37,36 @@ export const Subscriptions = () => { subscriptionsQuery.refetch() } + const tabs = [ + { key: 'active', label: 'Active' }, + { key: 'expired', label: 'Expired' }, + { key: 'cancelled', label: 'Cancelled' } + ] + return ( - - + + + + } + > + + } + width="fit-content" + disabled + /> + + { setPagination={setPagination} isLoading={isLoading} /> - + ) } diff --git a/modules/web/web-app/public/header/customer.svg b/modules/web/web-app/public/header/customers.svg similarity index 100% rename from modules/web/web-app/public/header/customer.svg rename to modules/web/web-app/public/header/customers.svg diff --git a/modules/web/web-app/public/header/invoices.svg b/modules/web/web-app/public/header/invoices.svg new file mode 100644 index 000000000..c77a5aba2 --- /dev/null +++ b/modules/web/web-app/public/header/invoices.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/web/web-app/public/header/subscriptions.svg b/modules/web/web-app/public/header/subscriptions.svg new file mode 100644 index 000000000..42d4d72bb --- /dev/null +++ b/modules/web/web-app/public/header/subscriptions.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/web/web-app/router/tenant/billing.tsx b/modules/web/web-app/router/tenant/billing.tsx index ba19f62ca..1315beeb7 100644 --- a/modules/web/web-app/router/tenant/billing.tsx +++ b/modules/web/web-app/router/tenant/billing.tsx @@ -1,14 +1,14 @@ -import { RouteObject } from 'react-router-dom' +import { Outlet, RouteObject } from 'react-router-dom' import { NotImplemented } from '@/features/NotImplemented' -import { Billing, BillingOutlet } from '@/pages/tenants/billing' +import { Billing } from '@/pages/tenants/billing' import { Invoice, Invoices } from '@/pages/tenants/invoice' import { Subscriptions } from '@/pages/tenants/subscription' import { Subscription } from '@/pages/tenants/subscription/subscription' import { SubscriptionCreate } from '@/pages/tenants/subscription/subscriptionCreate' export const billingRoutes: RouteObject = { - element: , + element: , children: [ { index: true,