shortcut-next 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +223 -13
- package/package.json +1 -1
- package/templates/base/.eslintrc.json +75 -2
- package/templates/base/.prettierignore +8 -0
- package/templates/base/app/(dashboard)/layout.tsx +15 -89
- package/templates/base/app/home/page.tsx +2 -2
- package/templates/base/app/icon.svg +4 -0
- package/templates/base/app/layout.tsx +16 -6
- package/templates/base/app/login/page.tsx +31 -31
- package/templates/base/app/page.tsx +2 -2
- package/templates/base/{@core/components → components/ability}/Can.tsx +1 -1
- package/templates/base/components/auth/LoginForm.tsx +2 -2
- package/templates/base/components/auth/SignupForm.tsx +1 -1
- package/templates/base/{@core/components → components/common}/LanguageDropdown.tsx +2 -2
- package/templates/base/components/icon/Icon.tsx +8 -0
- package/templates/base/components/ui/GradientText.tsx +0 -1
- package/templates/base/core/configs/themeConfig.ts +40 -0
- package/templates/base/{@core → core}/context/AuthContext.tsx +2 -2
- package/templates/base/{@core → core}/context/SettingsContext.tsx +1 -0
- package/templates/base/{@core → core}/hooks/useAbility.ts +2 -6
- package/templates/base/{@core → core}/hooks/useLanguage.ts +1 -3
- package/templates/base/core/icons/customIcons.ts +12 -0
- package/templates/base/core/layouts/SidebarLayout/ActiveRouteContext.tsx +25 -0
- package/templates/base/core/layouts/SidebarLayout/FavoritesContext.tsx +63 -0
- package/templates/base/core/layouts/SidebarLayout/Sidebar.tsx +142 -0
- package/templates/base/core/layouts/SidebarLayout/SidebarContext.tsx +66 -0
- package/templates/base/core/layouts/SidebarLayout/components/CommandPalette.tsx +143 -0
- package/templates/base/core/layouts/SidebarLayout/components/FavoritesSection.tsx +113 -0
- package/templates/base/core/layouts/SidebarLayout/components/NavItemContextMenu.tsx +64 -0
- package/templates/base/core/layouts/SidebarLayout/components/NavItems.tsx +66 -0
- package/templates/base/core/layouts/SidebarLayout/components/SidebarAnimatedLabel.tsx +40 -0
- package/templates/base/core/layouts/SidebarLayout/components/SidebarFooter.tsx +76 -0
- package/templates/base/core/layouts/SidebarLayout/components/SidebarLogo.tsx +91 -0
- package/templates/base/core/layouts/SidebarLayout/components/SidebarNavGroup.tsx +115 -0
- package/templates/base/core/layouts/SidebarLayout/components/SidebarNavLink.tsx +116 -0
- package/templates/base/core/layouts/SidebarLayout/components/SidebarNavMore.tsx +74 -0
- package/templates/base/core/layouts/SidebarLayout/components/SidebarSection.tsx +115 -0
- package/templates/base/core/layouts/SidebarLayout/index.tsx +115 -0
- package/templates/base/core/layouts/SidebarLayout/ui/SidebarStyledComponents.tsx +82 -0
- package/templates/base/core/layouts/SidebarLayout/utils/SidebarUtils.ts +67 -0
- package/templates/base/core/layouts/types.ts +58 -0
- package/templates/base/{@core → core}/theme/ThemeComponent.tsx +1 -1
- package/templates/base/{@core → core}/theme/overrides/accordion.ts +2 -3
- package/templates/base/{@core → core}/theme/overrides/alerts.ts +14 -9
- package/templates/base/{@core → core}/theme/overrides/autocomplete.ts +0 -1
- package/templates/base/{@core → core}/theme/overrides/avatars.ts +7 -4
- package/templates/base/{@core → core}/theme/overrides/backdrop.ts +3 -6
- package/templates/base/{@core → core}/theme/overrides/breadcrumbs.ts +1 -2
- package/templates/base/{@core → core}/theme/overrides/button.ts +44 -59
- package/templates/base/core/theme/overrides/buttonGroup.ts +11 -0
- package/templates/base/{@core → core}/theme/overrides/card.ts +5 -4
- package/templates/base/{@core → core}/theme/overrides/chip.ts +11 -12
- package/templates/base/{@core → core}/theme/overrides/dataGrid.ts +18 -2
- package/templates/base/{@core → core}/theme/overrides/dialog.ts +25 -20
- package/templates/base/{@core → core}/theme/overrides/divider.ts +1 -2
- package/templates/base/{@core → core}/theme/overrides/fab.ts +1 -2
- package/templates/base/core/theme/overrides/iconButton.ts +36 -0
- package/templates/base/{@core → core}/theme/overrides/index.ts +8 -6
- package/templates/base/{@core → core}/theme/overrides/input.ts +3 -3
- package/templates/base/core/theme/overrides/link.ts +44 -0
- package/templates/base/{@core → core}/theme/overrides/list.ts +1 -2
- package/templates/base/core/theme/overrides/menu.ts +43 -0
- package/templates/base/core/theme/overrides/pagination.ts +36 -0
- package/templates/base/core/theme/overrides/paper.ts +12 -0
- package/templates/base/{@core → core}/theme/overrides/popover.ts +3 -3
- package/templates/base/{@core → core}/theme/overrides/progress.ts +2 -5
- package/templates/base/{@core → core}/theme/overrides/rating.ts +1 -2
- package/templates/base/{@core → core}/theme/overrides/select.ts +6 -3
- package/templates/base/{@core → core}/theme/overrides/snackbar.ts +3 -3
- package/templates/base/core/theme/overrides/switches.ts +62 -0
- package/templates/base/{@core → core}/theme/overrides/table.ts +3 -21
- package/templates/base/core/theme/overrides/tabs.ts +67 -0
- package/templates/base/core/theme/overrides/textFields.ts +96 -0
- package/templates/base/{@core → core}/theme/overrides/timeline.ts +2 -5
- package/templates/base/{@core → core}/theme/overrides/toggleButton.ts +4 -2
- package/templates/base/{@core → core}/theme/overrides/tooltip.ts +8 -7
- package/templates/base/{@core → core}/theme/overrides/typography.ts +1 -2
- package/templates/base/{@core → core}/theme/typography/index.ts +3 -2
- package/templates/base/docs/SidebarDocumentation.md +426 -0
- package/templates/base/navigation/dynamicRoutes.ts +32 -0
- package/templates/base/navigation/sidebarRoutes.ts +61 -0
- package/templates/base/next.config.ts +1 -1
- package/templates/base/package-lock.json +1314 -487
- package/templates/base/package.json +15 -11
- package/templates/base/providers/AppProviders.tsx +10 -8
- package/templates/base/providers/I18nProvider.tsx +1 -1
- package/templates/base/{middleware.ts → proxy.ts} +2 -2
- package/templates/base/public/fonts/Montserrat-Arabic-Black.ttf +0 -0
- package/templates/base/public/fonts/Montserrat-Arabic-Bold.ttf +0 -0
- package/templates/base/public/fonts/Montserrat-Arabic-ExtraBold.ttf +0 -0
- package/templates/base/public/fonts/Montserrat-Arabic-ExtraLight.ttf +0 -0
- package/templates/base/public/fonts/Montserrat-Arabic-Light.ttf +0 -0
- package/templates/base/public/fonts/Montserrat-Arabic-Medium.ttf +0 -0
- package/templates/base/public/fonts/Montserrat-Arabic-Regular.ttf +0 -0
- package/templates/base/public/fonts/Montserrat-Arabic-SemiBold.ttf +0 -0
- package/templates/base/public/fonts/Montserrat-Arabic-Thin.ttf +0 -0
- package/templates/base/public/images/shortcut-next.png +0 -0
- package/templates/base/tsconfig.json +21 -5
- package/templates/base/@core/configs/themeConfig.ts +0 -51
- package/templates/base/@core/layouts/types.ts +0 -41
- package/templates/base/@core/theme/overrides/buttonGroup.ts +0 -9
- package/templates/base/@core/theme/overrides/link.ts +0 -9
- package/templates/base/@core/theme/overrides/menu.ts +0 -25
- package/templates/base/@core/theme/overrides/pagination.ts +0 -59
- package/templates/base/@core/theme/overrides/paper.ts +0 -9
- package/templates/base/@core/theme/overrides/switches.ts +0 -29
- package/templates/base/@core/theme/overrides/tabs.ts +0 -33
- package/templates/base/components/loaders/spinner.css +0 -136
- /package/templates/base/{@core/components → components/common}/Direction.tsx +0 -0
- /package/templates/base/{@core/components → components/common}/Translations.tsx +0 -0
- /package/templates/base/components/{HydrationGate.tsx → wrappers/HydrationGate.tsx} +0 -0
- /package/templates/base/{@core → core}/clients/apiClient.ts +0 -0
- /package/templates/base/{@core → core}/configs/authConfig.ts +0 -0
- /package/templates/base/{@core → core}/configs/clientConfig.ts +0 -0
- /package/templates/base/{@core → core}/configs/i18n.ts +0 -0
- /package/templates/base/{@core → core}/hooks/useSettings.ts +0 -0
- /package/templates/base/{@core → core}/hooks/useToggleMode.ts +0 -0
- /package/templates/base/{@core → core}/theme/ThemeOptions.ts +0 -0
- /package/templates/base/{@core → core}/theme/breakpoints/index.ts +0 -0
- /package/templates/base/{@core → core}/theme/globalStyles.tsx +0 -0
- /package/templates/base/{@core → core}/theme/palette/index.ts +0 -0
- /package/templates/base/{@core → core}/theme/shadows/index.ts +0 -0
- /package/templates/base/{@core → core}/theme/spacing/index.ts +0 -0
- /package/templates/base/{@core → core}/theme/types.ts +0 -0
- /package/templates/base/{@core → core}/utils/hex-to-rgba.ts +0 -0
- /package/templates/base/{components → providers}/MSWProvider.tsx +0 -0
package/README.md
CHANGED
|
@@ -1,26 +1,236 @@
|
|
|
1
|
-
# Shortcut Next
|
|
1
|
+
# Shortcut Next
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
> Optionally add **Tailwind CSS v4** with a single command.
|
|
3
|
+
Stop starting from scratch. Scaffold a production-ready **Next.js 15+** project with **MUI**, **React Hook Form**, **TanStack Query**, role-based authorization, i18n, and dark mode — all wired up and ready to go.
|
|
5
4
|
|
|
6
5
|
---
|
|
7
6
|
|
|
8
|
-
##
|
|
7
|
+
## What You Get
|
|
9
8
|
|
|
10
|
-
- **Next.js 15
|
|
11
|
-
- **MUI
|
|
12
|
-
- **React Hook Form**
|
|
13
|
-
- **TanStack Query
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
9
|
+
- **Next.js 15** with App Router and TypeScript (strict mode)
|
|
10
|
+
- **MUI v7** — fully themed with 30+ customized components, dark mode, and RTL support
|
|
11
|
+
- **React Hook Form** + Yup validation
|
|
12
|
+
- **TanStack Query** for data fetching and caching
|
|
13
|
+
- **CASL authorization** — role-based access control out of the box
|
|
14
|
+
- **i18n** — English and Arabic with auto-detection
|
|
15
|
+
- **Tailwind CSS v4** (optional preset)
|
|
16
|
+
- **MSW** — mock API responses during development
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
20
|
-
##
|
|
20
|
+
## Installation
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Run this single command and follow the prompts:
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
25
|
npx shortcut-next@latest
|
|
26
|
+
```
|
|
26
27
|
|
|
28
|
+
You'll be asked three things:
|
|
29
|
+
|
|
30
|
+
1. **Project name** — the folder that will be created
|
|
31
|
+
2. **Preset** — `base` (MUI stack) or `tailwind` (MUI + Tailwind v4)
|
|
32
|
+
3. **Package manager** — pnpm, npm, yarn, or bun
|
|
33
|
+
|
|
34
|
+
That's it. The CLI creates your project, initializes git, and installs dependencies.
|
|
35
|
+
|
|
36
|
+
### Skip the Prompts (Optional)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx shortcut-next@latest my-app --preset tailwind --pm pnpm
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
| Flag | Options |
|
|
43
|
+
| --------------- | ------------------------------ |
|
|
44
|
+
| `--preset` | `base` or `tailwind` |
|
|
45
|
+
| `--pm` | `npm`, `pnpm`, `yarn`, `bun` |
|
|
46
|
+
| `--no-git` | Skip git initialization |
|
|
47
|
+
| `--no-install` | Skip dependency installation |
|
|
48
|
+
|
|
49
|
+
### Start Developing
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cd my-app
|
|
53
|
+
npm run dev
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Other available scripts:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm run build # Production build
|
|
60
|
+
npm run lint # ESLint
|
|
61
|
+
npm run typecheck # TypeScript validation
|
|
62
|
+
npm run format # Prettier
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Project Structure
|
|
68
|
+
|
|
69
|
+
```text
|
|
70
|
+
app/
|
|
71
|
+
├── layout.tsx # Root layout (fonts, providers)
|
|
72
|
+
├── page.tsx # Landing page (public)
|
|
73
|
+
├── login/page.tsx # Login / Signup page
|
|
74
|
+
├── home/page.tsx # Authenticated home page
|
|
75
|
+
├── unauthorized/page.tsx # Access denied page
|
|
76
|
+
└── (dashboard)/
|
|
77
|
+
└── dashboard/page.tsx # Dashboard with role-aware cards
|
|
78
|
+
|
|
79
|
+
core/
|
|
80
|
+
├── clients/apiClient.ts # Axios instance (token refresh, auto-logout)
|
|
81
|
+
├── context/AuthContext.tsx # Auth state (login, signup, logout)
|
|
82
|
+
├── context/SettingsContext.tsx # Theme mode, direction, language
|
|
83
|
+
├── theme/ # MUI theme system (see below)
|
|
84
|
+
├── configs/ # Auth endpoints, i18n, theme defaults
|
|
85
|
+
└── hooks/ # useAbility, useCan, useLanguage
|
|
86
|
+
|
|
87
|
+
lib/abilities/ # CASL authorization (see below)
|
|
88
|
+
providers/AppProviders.tsx # Composes all context providers
|
|
89
|
+
proxy.ts # Middleware (auth + route protection)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Authorization
|
|
95
|
+
|
|
96
|
+
The template includes a ready-to-use role-based access system powered by [CASL](https://casl.js.org/).
|
|
97
|
+
|
|
98
|
+
### Roles
|
|
99
|
+
|
|
100
|
+
| Role | What they can do |
|
|
101
|
+
| --------- | -------------------------------------------------------- |
|
|
102
|
+
| `admin` | Full access to everything |
|
|
103
|
+
| `manager` | Read all, manage Users/Tickets/Reports, no Settings |
|
|
104
|
+
| `agent` | Read Dashboard/Tickets/Reports, manage Tickets |
|
|
105
|
+
| `viewer` | Read Dashboard and Reports only |
|
|
106
|
+
|
|
107
|
+
Your backend JWT must include a `role` claim. The token is read from an `accessToken` cookie.
|
|
108
|
+
|
|
109
|
+
### Protect a New Route
|
|
110
|
+
|
|
111
|
+
Add one entry to `lib/abilities/routeMap.ts`:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
export const routePermissions: RoutePermission[] = [
|
|
115
|
+
// ... existing routes
|
|
116
|
+
|
|
117
|
+
// Exact route
|
|
118
|
+
{ pattern: '/dashboard/reports', action: 'read', subject: 'Reports' },
|
|
119
|
+
|
|
120
|
+
// Dynamic param
|
|
121
|
+
{ pattern: '/dashboard/users/[id]', action: 'manage', subject: 'Users' },
|
|
122
|
+
|
|
123
|
+
// Wildcard (all sub-routes)
|
|
124
|
+
{ pattern: '/settings/*', action: 'manage', subject: 'Settings' },
|
|
125
|
+
]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
That's it — the middleware handles enforcement automatically.
|
|
129
|
+
|
|
130
|
+
### Check Permissions in Components
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
import { useAbility, useCan } from '@/core/hooks/useAbility'
|
|
134
|
+
|
|
135
|
+
// Option 1: Full ability object
|
|
136
|
+
const ability = useAbility()
|
|
137
|
+
if (ability.can('read', 'Users')) {
|
|
138
|
+
// show users link
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Option 2: Simple boolean
|
|
142
|
+
const canManageSettings = useCan('manage', 'Settings')
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Add a New Role
|
|
146
|
+
|
|
147
|
+
Edit `lib/abilities/roles.ts`:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
export const roleAbilities: Record<UserRole, (can: Can, cannot: Cannot) => void> = {
|
|
151
|
+
// ... existing roles
|
|
152
|
+
|
|
153
|
+
support: (can) => {
|
|
154
|
+
can('read', ['Dashboard', 'Tickets'])
|
|
155
|
+
can('manage', 'Tickets')
|
|
156
|
+
},
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Then add `'support'` to the `UserRole` type in `lib/abilities/types.ts`.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## MUI Theme Customization
|
|
165
|
+
|
|
166
|
+
The theme lives in `core/theme/` and is fully modular.
|
|
167
|
+
|
|
168
|
+
### Change the Default Theme
|
|
169
|
+
|
|
170
|
+
Edit `core/configs/themeConfig.ts`:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
const themeConfig = {
|
|
174
|
+
mode: 'dark', // 'light' or 'dark'
|
|
175
|
+
direction: 'ltr', // 'ltr' or 'rtl'
|
|
176
|
+
responsiveFontSizes: true,
|
|
177
|
+
disableRipple: true,
|
|
178
|
+
borderRadius: 10,
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Customize a Component
|
|
183
|
+
|
|
184
|
+
Each MUI component has its own override file in `core/theme/overrides/`. For example, to change button styles, edit `core/theme/overrides/button.ts`:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
const Button = (settings: Settings) => ({
|
|
188
|
+
MuiButton: {
|
|
189
|
+
styleOverrides: {
|
|
190
|
+
root: ({ theme }) => ({
|
|
191
|
+
borderRadius: 8,
|
|
192
|
+
textTransform: 'none',
|
|
193
|
+
fontWeight: 600,
|
|
194
|
+
}),
|
|
195
|
+
contained: ({ theme }) => ({
|
|
196
|
+
boxShadow: theme.shadows[3],
|
|
197
|
+
}),
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
})
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Components with Overrides
|
|
204
|
+
|
|
205
|
+
Accordion, Alerts, Autocomplete, Avatars, Backdrop, Breadcrumbs, Button, ButtonGroup, Card, Chip, DataGrid, Dialog, Divider, FAB, IconButton, Input, Link, List, Menu, Pagination, Paper, Popover, Progress, Rating, Select, Snackbar, Switches, Table, Tabs, TextField, Timeline, ToggleButton, Tooltip, Typography.
|
|
206
|
+
|
|
207
|
+
### Change the Color Palette
|
|
208
|
+
|
|
209
|
+
Edit `core/theme/palette/index.ts`:
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
primary: {
|
|
213
|
+
light: '#8B9BFF',
|
|
214
|
+
main: '#5B74FF', // your brand color
|
|
215
|
+
dark: '#3B54DF',
|
|
216
|
+
},
|
|
217
|
+
secondary: {
|
|
218
|
+
main: '#00D0FF',
|
|
219
|
+
},
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Environment Variables
|
|
225
|
+
|
|
226
|
+
Create a `.env` file in your project root:
|
|
227
|
+
|
|
228
|
+
```env
|
|
229
|
+
NEXT_PUBLIC_URL=https://your-backend-url.com
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## License
|
|
235
|
+
|
|
236
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,3 +1,76 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
"extends": [
|
|
3
|
+
"next/core-web-vitals",
|
|
4
|
+
"plugin:@typescript-eslint/recommended",
|
|
5
|
+
"prettier"
|
|
6
|
+
],
|
|
7
|
+
"rules": {
|
|
8
|
+
"react/display-name": "off",
|
|
9
|
+
"@next/next/no-img-element": "off",
|
|
10
|
+
"react/no-unescaped-entities": "off",
|
|
11
|
+
"import/no-anonymous-default-export": "off",
|
|
12
|
+
"@typescript-eslint/no-unused-vars": "error",
|
|
13
|
+
"@typescript-eslint/ban-ts-comment": "off",
|
|
14
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
15
|
+
"@typescript-eslint/no-non-null-assertion": "off",
|
|
16
|
+
// add new line above comment
|
|
17
|
+
"lines-around-comment": [
|
|
18
|
+
"error",
|
|
19
|
+
{
|
|
20
|
+
"beforeLineComment": true,
|
|
21
|
+
"beforeBlockComment": true,
|
|
22
|
+
"allowBlockStart": true,
|
|
23
|
+
"allowClassStart": true,
|
|
24
|
+
"allowObjectStart": true,
|
|
25
|
+
"allowArrayStart": true
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
// add new line above return
|
|
29
|
+
"newline-before-return": "error",
|
|
30
|
+
// add new line below import
|
|
31
|
+
"import/newline-after-import": [
|
|
32
|
+
"error",
|
|
33
|
+
{
|
|
34
|
+
"count": 1
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"@typescript-eslint/ban-types": [
|
|
38
|
+
"error",
|
|
39
|
+
{
|
|
40
|
+
"extendDefaults": true,
|
|
41
|
+
"types": {
|
|
42
|
+
"{}": false
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
"plugins": [
|
|
48
|
+
"import"
|
|
49
|
+
],
|
|
50
|
+
"settings": {
|
|
51
|
+
"import/parsers": {
|
|
52
|
+
"@typescript-eslint/parser": [
|
|
53
|
+
".ts",
|
|
54
|
+
".tsx"
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
"import/resolver": {
|
|
58
|
+
"typescript": {
|
|
59
|
+
"alwaysTryTypes": true,
|
|
60
|
+
"project": [
|
|
61
|
+
"./tsconfig.json"
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"overrides": [
|
|
67
|
+
{
|
|
68
|
+
"files": [
|
|
69
|
+
"src/iconify-bundle/*"
|
|
70
|
+
],
|
|
71
|
+
"rules": {
|
|
72
|
+
"@typescript-eslint/no-var-requires": "off"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
@@ -1,97 +1,23 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* JWT payload structure
|
|
8
|
-
*/
|
|
9
|
-
interface JWTPayload {
|
|
10
|
-
sub: string
|
|
11
|
-
email?: string
|
|
12
|
-
role?: string
|
|
13
|
-
name?: string
|
|
14
|
-
exp?: number
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Session data extracted from JWT
|
|
19
|
-
*/
|
|
20
|
-
interface Session {
|
|
21
|
-
userId: string
|
|
22
|
-
role: UserRole
|
|
23
|
-
name?: string
|
|
24
|
-
email?: string
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Extract session from cookies (server-side)
|
|
29
|
-
*
|
|
30
|
-
* This is the defense-in-depth check that runs in addition to middleware.
|
|
31
|
-
* It ensures no protected content is ever rendered without a valid session.
|
|
32
|
-
*/
|
|
33
|
-
async function getServerSession(): Promise<Session | null> {
|
|
34
|
-
const cookieStore = await cookies()
|
|
35
|
-
const token = cookieStore.get('accessToken')?.value
|
|
36
|
-
|
|
37
|
-
if (!token) {
|
|
38
|
-
return null
|
|
39
|
-
}
|
|
1
|
+
import SidebarLayout from '@/core/layouts/SidebarLayout'
|
|
2
|
+
import navigation from '@/navigation/sidebarRoutes'
|
|
3
|
+
import { fetchDynamicRoutes } from '@/navigation/dynamicRoutes'
|
|
4
|
+
import themeConfig from '@/core/configs/themeConfig'
|
|
40
5
|
|
|
6
|
+
export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
|
|
7
|
+
let dynamicNavItems: Awaited<ReturnType<typeof fetchDynamicRoutes>> = []
|
|
41
8
|
try {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (payload.exp && payload.exp * 1000 < Date.now()) {
|
|
46
|
-
return null
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const validRoles: UserRole[] = ['admin', 'manager', 'agent', 'viewer']
|
|
50
|
-
const role = validRoles.includes(payload.role as UserRole)
|
|
51
|
-
? (payload.role as UserRole)
|
|
52
|
-
: 'viewer'
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
userId: payload.sub,
|
|
56
|
-
role,
|
|
57
|
-
name: payload.name,
|
|
58
|
-
email: payload.email,
|
|
59
|
-
}
|
|
60
|
-
} catch {
|
|
61
|
-
return null
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Dashboard Layout
|
|
67
|
-
*
|
|
68
|
-
* This is a Server Component that provides defense-in-depth for all dashboard routes.
|
|
69
|
-
* Even if middleware is bypassed, this layout ensures no protected content renders
|
|
70
|
-
* without a valid session.
|
|
71
|
-
*
|
|
72
|
-
* The layout wraps all routes in the (dashboard) route group.
|
|
73
|
-
*/
|
|
74
|
-
export default async function DashboardLayout({
|
|
75
|
-
children,
|
|
76
|
-
}: {
|
|
77
|
-
children: React.ReactNode
|
|
78
|
-
}) {
|
|
79
|
-
const session = await getServerSession()
|
|
80
|
-
|
|
81
|
-
// Defense-in-depth: redirect if no session
|
|
82
|
-
// This should rarely trigger since middleware handles it first
|
|
83
|
-
if (!session) {
|
|
84
|
-
redirect('/login?returnUrl=/dashboard')
|
|
9
|
+
dynamicNavItems = await fetchDynamicRoutes()
|
|
10
|
+
} catch (error) {
|
|
11
|
+
console.error('Failed to fetch dynamic nav routes:', error)
|
|
85
12
|
}
|
|
86
13
|
|
|
87
14
|
return (
|
|
88
|
-
<div className=
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
<main className="dashboard-content">{children}</main>
|
|
15
|
+
<div className='dashboard-layout'>
|
|
16
|
+
<main className='dashboard-content'>
|
|
17
|
+
<SidebarLayout navItems={navigation()} dynamicNavItems={dynamicNavItems} appName={themeConfig.templateName}>
|
|
18
|
+
{children}
|
|
19
|
+
</SidebarLayout>
|
|
20
|
+
</main>
|
|
95
21
|
</div>
|
|
96
22
|
)
|
|
97
23
|
}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { Container, Typography, Paper, Box, Chip, Button } from '@mui/material'
|
|
4
4
|
import { LogOut } from 'lucide-react'
|
|
5
|
-
import { useAuth } from '
|
|
6
|
-
import { useCan } from '
|
|
5
|
+
import { useAuth } from '@/core/context/AuthContext'
|
|
6
|
+
import { useCan } from '@/core/hooks/useAbility'
|
|
7
7
|
|
|
8
8
|
// Role-specific home components
|
|
9
9
|
function AdminHome() {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
|
2
|
+
<path fill-rule="evenodd" clip-rule="evenodd" fill="#5B74FF" d="M 530.5,258.5 C 531.5,258.5 532.5,258.5 533.5,258.5C 553.598,259.825 573.598,259.825 593.5,258.5C 596.5,258.5 599.5,258.5 602.5,258.5C 605.273,259.657 608.273,260.324 611.5,260.5C 616.302,262.127 620.468,264.794 624,268.5C 630.942,279.405 638.109,290.071 645.5,300.5C 655.003,316.844 665.003,332.844 675.5,348.5C 675.427,350.027 676.094,351.027 677.5,351.5C 683.754,363.016 690.754,374.016 698.5,384.5C 703.155,392.472 707.822,400.472 712.5,408.5C 712.427,410.027 713.094,411.027 714.5,411.5C 719.474,419.087 717.807,424.921 709.5,429C 662.833,429.667 616.167,429.667 569.5,429C 567.975,427.991 566.308,427.491 564.5,427.5C 560.603,425.114 557.103,422.114 554,418.5C 549.364,410.056 544.198,402.056 538.5,394.5C 537,390.827 535,387.494 532.5,384.5C 530.281,379.291 527.281,374.624 523.5,370.5C 515.491,356.377 507.158,343.043 498.5,330.5C 496.316,326.119 493.65,322.119 490.5,318.5C 490.011,317.005 489.345,315.671 488.5,314.5C 487.649,312.118 486.316,310.118 484.5,308.5C 484.672,307.508 484.338,306.842 483.5,306.5C 483.5,305.833 483.167,305.5 482.5,305.5C 476.33,297.555 468.33,295.055 458.5,298C 456.273,298.941 454.273,300.108 452.5,301.5C 451.833,301.5 451.5,301.833 451.5,302.5C 445.554,308.427 444.054,315.427 447,323.5C 459.358,344.235 472.191,364.569 485.5,384.5C 498.336,406.178 511.669,427.511 525.5,448.5C 527.194,455.947 524.527,461.113 517.5,464C 468.167,464.667 418.833,464.667 369.5,464C 363.047,462.611 357.713,459.444 353.5,454.5C 343.82,439.797 334.153,425.13 324.5,410.5C 323.66,408.147 322.326,406.147 320.5,404.5C 315.997,396.156 310.997,388.156 305.5,380.5C 305.5,379.5 305.5,378.5 305.5,377.5C 305.5,374.167 305.5,370.833 305.5,367.5C 307.269,362.052 310.603,357.719 315.5,354.5C 319.566,351.437 322.899,347.771 325.5,343.5C 326.5,342.167 327.5,340.833 328.5,339.5C 331.5,337.167 334.167,334.5 336.5,331.5C 339.5,329.167 342.167,326.5 344.5,323.5C 356.112,311.553 367.778,299.553 379.5,287.5C 382.5,286.5 384.5,284.5 385.5,281.5C 389.167,277.5 392.833,273.5 396.5,269.5C 400.516,265.492 405.182,262.492 410.5,260.5C 450.805,259.802 490.805,259.135 530.5,258.5 Z"/>
|
|
3
|
+
<path fill-rule="evenodd" clip-rule="evenodd" fill="#5B74FF" d="M 657.5,475.5 C 664.789,477.595 670.956,481.595 676,487.5C 692,511.5 708,535.5 724,559.5C 725.874,563.52 725.874,567.52 724,571.5C 721.283,574.71 718.783,578.044 716.5,581.5C 691.195,608.474 665.861,635.474 640.5,662.5C 634.73,668.771 628.73,674.938 622.5,681C 618.547,683.81 614.213,685.81 609.5,687C 546.833,687.667 484.167,687.667 421.5,687C 419.277,686.434 417.277,685.6 415.5,684.5C 415.027,683.094 414.027,682.427 412.5,682.5C 411.833,682.5 411.167,682.5 410.5,682.5C 405.379,677.722 401.046,672.388 397.5,666.5C 396.331,663.489 394.664,660.822 392.5,658.5C 386.069,647.96 379.402,637.627 372.5,627.5C 371.241,623.975 369.241,620.975 366.5,618.5C 355.912,600.316 344.579,582.649 332.5,565.5C 332.068,564.29 331.401,563.29 330.5,562.5C 327.996,557.485 324.996,552.819 321.5,548.5C 321.672,547.508 321.338,546.842 320.5,546.5C 319.525,544.205 318.192,542.205 316.5,540.5C 313.555,534.896 310.221,529.562 306.5,524.5C 304.65,520.355 304.15,516.021 305,511.5C 306.728,508.047 309.562,506.214 313.5,506C 361.156,506.344 408.49,506.344 455.5,506C 465.15,507.51 472.483,512.343 477.5,520.5C 490.587,541.677 503.92,562.677 517.5,583.5C 522.42,593.014 528.086,602.014 534.5,610.5C 539.119,620.228 545.119,629.061 552.5,637C 559.208,640.671 566.208,641.338 573.5,639C 576.106,638.238 578.106,636.708 579.5,634.5C 580.167,634.5 580.5,634.167 580.5,633.5C 584.921,628.719 586.421,623.053 585,616.5C 582.908,611.303 580.074,606.636 576.5,602.5C 572.33,594.82 567.663,587.487 562.5,580.5C 562.573,578.973 561.906,577.973 560.5,577.5C 559.66,575.147 558.326,573.147 556.5,571.5C 553.664,565.822 550.331,560.489 546.5,555.5C 533.273,533.389 519.773,511.389 506,489.5C 504.566,482.794 507.066,478.294 513.5,476C 561.499,475.5 609.499,475.333 657.5,475.5 Z"/>
|
|
4
|
+
</svg>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Metadata } from 'next'
|
|
2
|
-
import { Poppins
|
|
2
|
+
import { Poppins } from 'next/font/google'
|
|
3
|
+
import localFont from 'next/font/local'
|
|
3
4
|
import './globals.css'
|
|
4
5
|
import AppProviders from '@/providers/AppProviders'
|
|
5
6
|
|
|
@@ -9,10 +10,19 @@ const poppins = Poppins({
|
|
|
9
10
|
weight: ['300', '400', '500', '600', '700']
|
|
10
11
|
})
|
|
11
12
|
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
const montserratArabic = localFont({
|
|
14
|
+
src: [
|
|
15
|
+
{ path: '../public/fonts/Montserrat-Arabic-Thin.ttf', weight: '100' },
|
|
16
|
+
{ path: '../public/fonts/Montserrat-Arabic-ExtraLight.ttf', weight: '200' },
|
|
17
|
+
{ path: '../public/fonts/Montserrat-Arabic-Light.ttf', weight: '300' },
|
|
18
|
+
{ path: '../public/fonts/Montserrat-Arabic-Regular.ttf', weight: '400' },
|
|
19
|
+
{ path: '../public/fonts/Montserrat-Arabic-Medium.ttf', weight: '500' },
|
|
20
|
+
{ path: '../public/fonts/Montserrat-Arabic-SemiBold.ttf', weight: '600' },
|
|
21
|
+
{ path: '../public/fonts/Montserrat-Arabic-Bold.ttf', weight: '700' },
|
|
22
|
+
{ path: '../public/fonts/Montserrat-Arabic-ExtraBold.ttf', weight: '800' },
|
|
23
|
+
{ path: '../public/fonts/Montserrat-Arabic-Black.ttf', weight: '900' }
|
|
24
|
+
],
|
|
25
|
+
variable: '--font-montserrat-arabic'
|
|
16
26
|
})
|
|
17
27
|
export const metadata: Metadata = {
|
|
18
28
|
title: 'Shortcut Nextjs Template',
|
|
@@ -26,7 +36,7 @@ export default async function RootLayout({
|
|
|
26
36
|
}>) {
|
|
27
37
|
return (
|
|
28
38
|
<html lang='en' dir='ltr'>
|
|
29
|
-
<body className={`${poppins.variable} ${
|
|
39
|
+
<body className={`${poppins.variable} ${montserratArabic.variable} antialiased`}>
|
|
30
40
|
<AppProviders>{children}</AppProviders>
|
|
31
41
|
</body>
|
|
32
42
|
</html>
|