feat: add multiple admin dashboard layout components and update sidebar and navbar

This commit is contained in:
Ardeman 2025-02-23 17:41:54 +08:00
parent 661baca101
commit 396721fbf2
17 changed files with 224 additions and 12 deletions

View File

@ -0,0 +1,21 @@
import type { JSX, SVGProps } from 'react'
export const ChevronDownIcon = (
properties: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) => {
return (
<svg
width={21}
height={21}
viewBox="0 0 21 21"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...properties}
>
<path
d="M10.197 13.623l5.008-5.008-1.177-1.18-3.83 3.834-3.831-3.833-1.178 1.178 5.008 5.009z"
fill="currentColor"
/>
</svg>
)
}

View File

@ -22,7 +22,7 @@ const AdminContext = createContext<AdminContextProperties | undefined>(
export const AdminProvider = ({ children }: PropsWithChildren) => {
const [adminProfile, setAdminProfile] = useState<AdminProfile>({
name: '',
name: 'Admin',
})
return (

View File

@ -10,7 +10,7 @@ export const AdminDashboardLayout = (properties: PropsWithChildren) => {
<Navbar />
<div className="flex">
<Sidebar />
<div className="min-h-[calc(100dvh-80px)] flex-1">{children}</div>
<div className="min-h-[calc(100dvh-80px)] flex-1 p-8">{children}</div>
</div>
</div>
)

View File

@ -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 (
<div className="flex h-20 items-center justify-between border-b border-[#ECECEC] bg-white px-10 py-5">
<Link
@ -16,7 +20,14 @@ export const Navbar = () => {
className="h-3/4 w-auto sm:h-full"
/>
</Link>
<div>
<div className="flex items-center gap-x-8">
<div className="flex w-3xs items-center justify-between">
<div className="flex items-center">
<div className="mr-3 h-8 w-8 rounded-full bg-[#C4C4C4]" />
<span className="text-xs">{adminProfile.name}</span>
</div>
<ChevronDownIcon className="opacity-10" />
</div>
<NotificationIcon
className="text-[#B0C3CC]"
showBadge={true}

View File

@ -7,13 +7,11 @@ export const Sidebar = () => {
return (
<div className="flex min-h-[calc(100dvh-80px)] flex-col gap-y-10 overflow-y-auto bg-white p-5">
{MENU.map(({ group, items }) => (
<div className="flex flex-col">
<div
key={group}
className="px-5 pb-4 text-[#4C5CA0]/50 uppercase"
>
{group}
</div>
<div
className="flex flex-col"
key={group}
>
<div className="px-5 pb-4 text-[#4C5CA0]/50 uppercase">{group}</div>
{items.map(({ title, url, icon: Icon }) => (
<NavLink
to={url}
@ -36,7 +34,7 @@ export const Sidebar = () => {
<span
className={twMerge(
isActive ? 'text-[#5363AB]' : 'text-[#273240]',
'transition-all group-hover/menu:text-[#5363AB]',
'text-base transition-all group-hover/menu:text-[#5363AB]',
)}
>
{title}

View File

@ -1,4 +1,4 @@
const DashboardIndexLayout = () => {
return <div className="flex items-center justify-center">Dashboard Page</div>
return <div>Dashboard Page</div>
}
export default DashboardIndexLayout

View File

@ -0,0 +1,4 @@
const DashboardAdminsLayout = () => {
return <div>Admins Page</div>
}
export default DashboardAdminsLayout

View File

@ -0,0 +1,4 @@
const DashboardAdvertisementsLayout = () => {
return <div>Advertisements Page</div>
}
export default DashboardAdvertisementsLayout

View File

@ -0,0 +1,4 @@
const DashboardContentsLayout = () => {
return <div>Contents Page</div>
}
export default DashboardContentsLayout

View File

@ -0,0 +1,4 @@
const DashboardSettingsLayout = () => {
return <div>Settings Page</div>
}
export default DashboardSettingsLayout

View File

@ -0,0 +1,4 @@
const DashboardSiteDataLayout = () => {
return <div>Site Data Page</div>
}
export default DashboardSiteDataLayout

View File

@ -0,0 +1,4 @@
const DashboardSubscriptionsLayout = () => {
return <div>Subscriptions Page</div>
}
export default DashboardSubscriptionsLayout

View File

@ -0,0 +1,4 @@
const DashboardUsersLayout = () => {
return <div>Users Page</div>
}
export default DashboardUsersLayout

View File

@ -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,
},
],
},
},
)

View File

@ -12,6 +12,7 @@
"ignore": ["app/components/icons/*.tsx"],
"ignoreDependencies": [
"tailwindcss",
"@tailwindcss/typography",
"react-router-devtools",
"eslint-import-resolver-typescript"
]

View File

@ -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",

140
pnpm-lock.yaml generated
View File

@ -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