From 396721fbf286c19d4ace7137330736fc9cb6a199 Mon Sep 17 00:00:00 2001 From: Ardeman Date: Sun, 23 Feb 2025 17:41:54 +0800 Subject: [PATCH] feat: add multiple admin dashboard layout components and update sidebar and navbar --- app/components/icons/chevron-down.tsx | 21 +++ app/contexts/admin.tsx | 2 +- app/layouts/admin/dashboard.tsx | 2 +- app/layouts/admin/navbar.tsx | 13 +- app/layouts/admin/sidebar.tsx | 14 +- app/routes/_layout.admin.dashboard._index.tsx | 2 +- app/routes/_layout.admin.dashboard.admins.tsx | 4 + ..._layout.admin.dashboard.advertisements.tsx | 4 + .../_layout.admin.dashboard.contents.tsx | 4 + .../_layout.admin.dashboard.settings.tsx | 4 + .../_layout.admin.dashboard.site-data.tsx | 4 + .../_layout.admin.dashboard.subsciptions.tsx | 4 + app/routes/_layout.admin.dashboard.users.tsx | 4 + eslint.config.mjs | 12 ++ knip.json | 1 + package.json | 1 + pnpm-lock.yaml | 140 ++++++++++++++++++ 17 files changed, 224 insertions(+), 12 deletions(-) create mode 100644 app/components/icons/chevron-down.tsx create mode 100644 app/routes/_layout.admin.dashboard.admins.tsx create mode 100644 app/routes/_layout.admin.dashboard.advertisements.tsx create mode 100644 app/routes/_layout.admin.dashboard.contents.tsx create mode 100644 app/routes/_layout.admin.dashboard.settings.tsx create mode 100644 app/routes/_layout.admin.dashboard.site-data.tsx create mode 100644 app/routes/_layout.admin.dashboard.subsciptions.tsx create mode 100644 app/routes/_layout.admin.dashboard.users.tsx diff --git a/app/components/icons/chevron-down.tsx b/app/components/icons/chevron-down.tsx new file mode 100644 index 0000000..c242f04 --- /dev/null +++ b/app/components/icons/chevron-down.tsx @@ -0,0 +1,21 @@ +import type { JSX, SVGProps } from 'react' + +export const ChevronDownIcon = ( + properties: JSX.IntrinsicAttributes & SVGProps, +) => { + return ( + + + + ) +} diff --git a/app/contexts/admin.tsx b/app/contexts/admin.tsx index 364248f..8b5384e 100644 --- a/app/contexts/admin.tsx +++ b/app/contexts/admin.tsx @@ -22,7 +22,7 @@ const AdminContext = createContext( export const AdminProvider = ({ children }: PropsWithChildren) => { const [adminProfile, setAdminProfile] = useState({ - name: '', + name: 'Admin', }) return ( diff --git a/app/layouts/admin/dashboard.tsx b/app/layouts/admin/dashboard.tsx index 512f776..a57480e 100644 --- a/app/layouts/admin/dashboard.tsx +++ b/app/layouts/admin/dashboard.tsx @@ -10,7 +10,7 @@ export const AdminDashboardLayout = (properties: PropsWithChildren) => {
-
{children}
+
{children}
) diff --git a/app/layouts/admin/navbar.tsx b/app/layouts/admin/navbar.tsx index 568554b..1e81d0c 100644 --- a/app/layouts/admin/navbar.tsx +++ b/app/layouts/admin/navbar.tsx @@ -1,9 +1,13 @@ import { Link } from 'react-router' +import { ChevronDownIcon } from '~/components/icons/chevron-down' import { NotificationIcon } from '~/components/icons/notification' +import { useAdminContext } from '~/contexts/admin' import { APP } from '~/data/meta' export const Navbar = () => { + const { adminProfile } = useAdminContext() + return (
{ className="h-3/4 w-auto sm:h-full" /> -
+
+
+
+
+ {adminProfile.name} +
+ +
{ return (
{MENU.map(({ group, items }) => ( -
-
- {group} -
+
+
{group}
{items.map(({ title, url, icon: Icon }) => ( { {title} diff --git a/app/routes/_layout.admin.dashboard._index.tsx b/app/routes/_layout.admin.dashboard._index.tsx index c7a9a58..2140281 100644 --- a/app/routes/_layout.admin.dashboard._index.tsx +++ b/app/routes/_layout.admin.dashboard._index.tsx @@ -1,4 +1,4 @@ const DashboardIndexLayout = () => { - return
Dashboard Page
+ return
Dashboard Page
} export default DashboardIndexLayout diff --git a/app/routes/_layout.admin.dashboard.admins.tsx b/app/routes/_layout.admin.dashboard.admins.tsx new file mode 100644 index 0000000..9173750 --- /dev/null +++ b/app/routes/_layout.admin.dashboard.admins.tsx @@ -0,0 +1,4 @@ +const DashboardAdminsLayout = () => { + return
Admins Page
+} +export default DashboardAdminsLayout diff --git a/app/routes/_layout.admin.dashboard.advertisements.tsx b/app/routes/_layout.admin.dashboard.advertisements.tsx new file mode 100644 index 0000000..8288de7 --- /dev/null +++ b/app/routes/_layout.admin.dashboard.advertisements.tsx @@ -0,0 +1,4 @@ +const DashboardAdvertisementsLayout = () => { + return
Advertisements Page
+} +export default DashboardAdvertisementsLayout diff --git a/app/routes/_layout.admin.dashboard.contents.tsx b/app/routes/_layout.admin.dashboard.contents.tsx new file mode 100644 index 0000000..477b9a4 --- /dev/null +++ b/app/routes/_layout.admin.dashboard.contents.tsx @@ -0,0 +1,4 @@ +const DashboardContentsLayout = () => { + return
Contents Page
+} +export default DashboardContentsLayout diff --git a/app/routes/_layout.admin.dashboard.settings.tsx b/app/routes/_layout.admin.dashboard.settings.tsx new file mode 100644 index 0000000..52fbb11 --- /dev/null +++ b/app/routes/_layout.admin.dashboard.settings.tsx @@ -0,0 +1,4 @@ +const DashboardSettingsLayout = () => { + return
Settings Page
+} +export default DashboardSettingsLayout diff --git a/app/routes/_layout.admin.dashboard.site-data.tsx b/app/routes/_layout.admin.dashboard.site-data.tsx new file mode 100644 index 0000000..78a0838 --- /dev/null +++ b/app/routes/_layout.admin.dashboard.site-data.tsx @@ -0,0 +1,4 @@ +const DashboardSiteDataLayout = () => { + return
Site Data Page
+} +export default DashboardSiteDataLayout diff --git a/app/routes/_layout.admin.dashboard.subsciptions.tsx b/app/routes/_layout.admin.dashboard.subsciptions.tsx new file mode 100644 index 0000000..bb2f896 --- /dev/null +++ b/app/routes/_layout.admin.dashboard.subsciptions.tsx @@ -0,0 +1,4 @@ +const DashboardSubscriptionsLayout = () => { + return
Subscriptions Page
+} +export default DashboardSubscriptionsLayout diff --git a/app/routes/_layout.admin.dashboard.users.tsx b/app/routes/_layout.admin.dashboard.users.tsx new file mode 100644 index 0000000..87ac591 --- /dev/null +++ b/app/routes/_layout.admin.dashboard.users.tsx @@ -0,0 +1,4 @@ +const DashboardUsersLayout = () => { + return
Users Page
+} +export default DashboardUsersLayout diff --git a/eslint.config.mjs b/eslint.config.mjs index 8403701..c4e8f2b 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -6,6 +6,7 @@ import importPlugin from 'eslint-plugin-import' import tseslint from 'typescript-eslint' import tsEslintParser from '@typescript-eslint/parser' import eslintPluginUnicorn from 'eslint-plugin-unicorn' +import reactPlugin from 'eslint-plugin-react' export default tseslint.config( { ignores: ['dist', 'node_modules', '.react-router'] }, @@ -24,6 +25,7 @@ export default tseslint.config( }, plugins: { 'react-hooks': reactHooks, + react: reactPlugin, }, languageOptions: { parser: tsEslintParser, @@ -42,6 +44,7 @@ export default tseslint.config( rules: { ...reactHooks.configs.recommended.rules, ...tseslint.configs.recommended.rules, + ...reactPlugin.configs.recommended.rules, '@typescript-eslint/no-unused-vars': [ 'error', { @@ -120,6 +123,15 @@ export default tseslint.config( ], }, ], + 'react/react-in-jsx-scope': 'off', + 'react/jsx-key': [ + 'error', + { + checkFragmentShorthand: true, + checkKeyMustBeforeSpread: true, + warnOnDuplicates: true, + }, + ], }, }, ) diff --git a/knip.json b/knip.json index 7e29617..3f5075a 100644 --- a/knip.json +++ b/knip.json @@ -12,6 +12,7 @@ "ignore": ["app/components/icons/*.tsx"], "ignoreDependencies": [ "tailwindcss", + "@tailwindcss/typography", "react-router-devtools", "eslint-import-resolver-typescript" ] diff --git a/package.json b/package.json index 03b6b02..11d67d2 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "eslint-import-resolver-typescript": "^3.7.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-react": "^7.37.4", "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-unicorn": "^56.0.1", "globals": "^15.14.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f3d32a1..71a0982 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -90,6 +90,9 @@ importers: eslint-plugin-jsx-a11y: specifier: ^6.10.2 version: 6.10.2(eslint@8.57.1) + eslint-plugin-react: + specifier: ^7.37.4 + version: 7.37.4(eslint@8.57.1) eslint-plugin-react-hooks: specifier: ^5.1.0 version: 5.1.0(eslint@8.57.1) @@ -1485,6 +1488,10 @@ packages: resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} engines: {node: '>= 0.4'} + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + array.prototype.findlastindex@1.2.5: resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} engines: {node: '>= 0.4'} @@ -1497,6 +1504,10 @@ packages: resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} engines: {node: '>= 0.4'} + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + arraybuffer.prototype.slice@1.0.4: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} @@ -2025,6 +2036,10 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-iterator-helpers@1.2.1: + resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} + engines: {node: '>= 0.4'} + es-module-lexer@1.6.0: resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} @@ -2122,6 +2137,12 @@ packages: peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + eslint-plugin-react@7.37.4: + resolution: {integrity: sha512-BGP0jRmfYyvOyvMoRX/uoUeW+GqNj9y16bPQzqAHf3AYII/tDs+jMN0dBVkl88/OZwNGwrVFxE7riHsXVfy/LQ==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + eslint-plugin-unicorn@56.0.1: resolution: {integrity: sha512-FwVV0Uwf8XPfVnKSGpMg7NtlZh0G0gBarCaFcMUOoqPxXryxdYxTRRv4kH6B9TFCVIrjRXG+emcxIk2ayZilog==} engines: {node: '>=18.18'} @@ -2655,6 +2676,10 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -3059,6 +3084,10 @@ packages: resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} + object.entries@1.1.8: + resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==} + engines: {node: '>= 0.4'} + object.fromentries@2.0.8: resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} engines: {node: '>= 0.4'} @@ -3508,6 +3537,10 @@ packages: engines: {node: '>= 0.4'} hasBin: true + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true + restore-cursor@5.1.0: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} @@ -3703,6 +3736,13 @@ packages: resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} engines: {node: '>= 0.4'} + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + string.prototype.trim@1.2.10: resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} engines: {node: '>= 0.4'} @@ -5521,6 +5561,15 @@ snapshots: get-intrinsic: 1.2.7 is-string: 1.1.1 + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.0.2 + array.prototype.findlastindex@1.2.5: dependencies: call-bind: 1.0.8 @@ -5544,6 +5593,14 @@ snapshots: es-abstract: 1.23.9 es-shim-unscopables: 1.0.2 + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-shim-unscopables: 1.0.2 + arraybuffer.prototype.slice@1.0.4: dependencies: array-buffer-byte-length: 1.0.2 @@ -6106,6 +6163,25 @@ snapshots: es-errors@1.3.0: {} + es-iterator-helpers@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.3 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.7 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + safe-array-concat: 1.1.3 + es-module-lexer@1.6.0: {} es-object-atoms@1.1.1: @@ -6250,6 +6326,28 @@ snapshots: dependencies: eslint: 8.57.1 + eslint-plugin-react@7.37.4(eslint@8.57.1): + dependencies: + array-includes: 3.1.8 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.2.1 + eslint: 8.57.1 + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.8 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + eslint-plugin-unicorn@56.0.1(eslint@8.57.1): dependencies: '@babel/helper-validator-identifier': 7.25.9 @@ -6861,6 +6959,15 @@ snapshots: isexe@2.0.0: {} + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.1 + get-intrinsic: 1.2.7 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -7227,6 +7334,12 @@ snapshots: has-symbols: 1.1.0 object-keys: 1.1.1 + object.entries@1.1.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + object.fromentries@2.0.8: dependencies: call-bind: 1.0.8 @@ -7640,6 +7753,12 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + resolve@2.0.0-next.5: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + restore-cursor@5.1.0: dependencies: onetime: 7.0.0 @@ -7879,6 +7998,27 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.23.9 + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.3 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.2.7 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.23.9 + string.prototype.trim@1.2.10: dependencies: call-bind: 1.0.8