create-template-project 1.1.2 → 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.
Files changed (29) hide show
  1. package/README.md +11 -0
  2. package/dist/config/dependencies.json +15 -15
  3. package/dist/index.js +677 -483
  4. package/dist/templates/base/files/.release-it.json +1 -1
  5. package/dist/templates/base/files/_oxc.config.ts +212 -87
  6. package/dist/templates/cli/files/src/index.ts +1 -2
  7. package/dist/templates/cli/files/src/lib.ts +1 -3
  8. package/dist/templates/web-app/files/playwright.config.ts +7 -4
  9. package/dist/templates/web-app/files/src/App.tsx +11 -12
  10. package/dist/templates/web-app/files/src/index.tsx +1 -1
  11. package/dist/templates/web-app/files/tests/e2e/basic.e2e-test.ts +2 -2
  12. package/dist/templates/web-fullstack/files/client/src/App.tsx +8 -5
  13. package/dist/templates/web-fullstack/files/client/src/components/ProtectedRoute.tsx +5 -3
  14. package/dist/templates/web-fullstack/files/client/src/contexts/AuthContext.tsx +22 -18
  15. package/dist/templates/web-fullstack/files/client/src/main.tsx +8 -5
  16. package/dist/templates/web-fullstack/files/client/src/pages/Dashboard.tsx +12 -7
  17. package/dist/templates/web-fullstack/files/client/src/pages/Login.tsx +11 -7
  18. package/dist/templates/web-fullstack/files/client/src/trpc.ts +2 -1
  19. package/dist/templates/web-fullstack/files/playwright.config.ts +8 -5
  20. package/dist/templates/web-fullstack/files/server/src/context.ts +7 -5
  21. package/dist/templates/web-fullstack/files/server/src/index.ts +2 -3
  22. package/dist/templates/web-fullstack/files/server/src/routers/_app.ts +1 -0
  23. package/dist/templates/web-fullstack/files/server/src/routers/auth.ts +4 -4
  24. package/dist/templates/web-fullstack/files/server/src/trpc.ts +3 -4
  25. package/dist/templates/web-vanilla/files/playwright.config.ts +7 -4
  26. package/dist/templates/web-vanilla/files/src/index.test.ts +1 -1
  27. package/dist/templates/web-vanilla/files/src/index.ts +1 -1
  28. package/dist/templates/web-vanilla/files/src/lib.ts +1 -3
  29. package/package.json +13 -7
@@ -5,7 +5,7 @@
5
5
  "git": {
6
6
  "requireCleanWorkingDir": true,
7
7
  "commit": true,
8
- "commitMessage": "chore(release): ${version}",
8
+ "commitMessage": "chore: release ${version}",
9
9
  "tag": true,
10
10
  "tagName": "v${version}",
11
11
  "push": true,
@@ -1,98 +1,223 @@
1
- import { defineConfig } from "oxlint";
2
- import pluginRegexp from "eslint-plugin-regexp";
1
+ import {defineConfig} from 'oxlint';
2
+ import {configs as regexpConfigs} from 'eslint-plugin-regexp';
3
3
 
4
4
  /** Filter out core ESLint rules bundled into eslint-plugin-regexp recommended config */
5
- const regexpPluginRules = Object.fromEntries(Object.entries(pluginRegexp.configs.recommended.rules ?? {}).filter(([key]) => key.startsWith("regexp/")));
5
+ const regexpPluginRules = Object.fromEntries(Object.entries(regexpConfigs.recommended.rules).filter(([key]) => key.startsWith('regexp/')));
6
6
 
7
7
  const commonIgnore = [
8
- "**/.*",
9
- "node_modules/**",
10
- "dist/**",
11
- "build/**",
12
- "coverage/**",
13
- "temp/**",
14
- "public/**",
15
- "**/*.md",
8
+ '**/.*',
9
+ 'node_modules/**',
10
+ 'dist/**',
11
+ 'build/**',
12
+ 'coverage/**',
13
+ 'temp/**',
14
+ 'public/**',
15
+ '**/*.md',
16
16
  ];
17
17
 
18
18
  export const linter = defineConfig({
19
- options: {
20
- typeAware: true,
21
- typeCheck: true,
22
- },
23
- plugins: [
24
- "unicorn",
25
- "typescript",
26
- "oxc",
27
- "import",
28
- "react",
29
- "jsdoc",
30
- "promise",
31
- "vitest",
32
- ],
33
- jsPlugins: ["eslint-plugin-regexp"],
34
- categories: {
35
- correctness: "error",
36
- },
37
- rules: {
38
- ...regexpPluginRules,
39
- curly: ["error", "all"],
40
- "typescript/no-unused-vars": [
41
- "error",
42
- {
43
- caughtErrors: "none",
44
- argsIgnorePattern: "^_",
45
- },
46
- ],
47
- },
48
- settings: {
49
- "jsx-a11y": {
50
- polymorphicPropName: undefined,
51
- components: {},
52
- attributes: {},
53
- },
54
- next: {
55
- rootDir: [],
56
- },
57
- react: {
58
- formComponents: [],
59
- linkComponents: [],
60
- version: undefined,
61
- },
62
- jsdoc: {
63
- ignorePrivate: false,
64
- ignoreInternal: false,
65
- ignoreReplacesDocs: true,
66
- overrideReplacesDocs: true,
67
- augmentsExtendsReplacesDocs: false,
68
- implementsReplacesDocs: false,
69
- exemptDestructuredRootsFromChecks: false,
70
- tagNamePreference: {},
71
- },
72
- vitest: {
73
- typecheck: false,
74
- },
75
- },
76
- env: {
77
- builtin: true,
78
- },
79
- globals: {},
80
- ignorePatterns: commonIgnore,
19
+ options: {
20
+ typeAware: true,
21
+ typeCheck: true,
22
+ },
23
+ plugins: [
24
+ 'unicorn',
25
+ 'typescript',
26
+ 'oxc',
27
+ 'import',
28
+ 'react',
29
+ 'jsdoc',
30
+ 'promise',
31
+ 'vitest',
32
+ ],
33
+ jsPlugins: ['eslint-plugin-regexp'],
34
+ categories: {
35
+ correctness: 'error',
36
+ nursery: 'error',
37
+ pedantic: 'error',
38
+ perf: 'error',
39
+ restriction: 'error',
40
+ style: 'error',
41
+ suspicious: 'error',
42
+ },
43
+ rules: {
44
+ ...regexpPluginRules,
45
+ 'eslint/complexity': 'off', // TODO: consider enabling
46
+ 'eslint/curly': ['error', 'all'],
47
+ 'eslint/id-length': 'off',
48
+ 'eslint/init-declarations': 'off', // TODO: consider enabling
49
+ 'eslint/max-depth': 'off', // TODO: consider enabling
50
+ 'eslint/max-lines': 'off', // TODO: consider enabling
51
+ 'eslint/max-lines-per-function': 'off', // TODO: consider enabling
52
+ 'eslint/max-params': 'off', // TODO: consider enabling
53
+ 'eslint/max-statements': 'off', // TODO: consider enabling
54
+ 'eslint/capitalized-comments': 'off', // TODO: consider enabling
55
+ 'eslint/no-await-in-loop': 'warn',
56
+ 'eslint/no-console': 'off',
57
+ 'eslint/no-continue': 'off',
58
+ 'eslint/no-inline-comments': 'off',
59
+ 'eslint/no-magic-numbers': 'off',
60
+ 'eslint/no-negated-condition': 'off', // TODO: consider enabling
61
+ 'eslint/no-nested-ternary': 'off',
62
+ 'eslint/no-warning-comments': 'off',
63
+ 'eslint/no-undefined': 'off', // TODO: consider enabling
64
+ 'eslint/no-plusplus': 'off',
65
+ 'eslint/sort-imports': 'off',
66
+ 'eslint/sort-keys': 'off',
67
+ 'eslint/no-ternary': 'off',
68
+ 'typescript/no-unused-vars': [
69
+ 'error',
70
+ {
71
+ caughtErrors: 'none',
72
+ argsIgnorePattern: '^_',
73
+ },
74
+ ],
75
+ 'typescript/consistent-type-definitions': ['error', 'type'],
76
+ 'typescript/no-import-type-side-effects': 'off',
77
+ 'typescript/prefer-readonly-parameter-types': 'off',
78
+ 'import/consistent-type-specifier-style': ['error', 'prefer-inline'],
79
+ 'import/exports-last': 'off',
80
+ 'import/group-exports': 'off',
81
+ 'import/max-dependencies': 'off',
82
+ 'import/no-named-export': 'off',
83
+ 'import/no-namespace': 'off', // TODO: consider enabling
84
+ 'import/no-nodejs-modules': 'off',
85
+ 'import/prefer-default-export': 'off',
86
+ 'import/no-default-export': 'off',
87
+ // FIXME: remove all jest rules
88
+ 'jest/consistent-test-it': 'off',
89
+ 'jest/expect-expect': 'off',
90
+ 'jest/max-expects': 'off',
91
+ 'jest/max-nested-describe': 'off',
92
+ 'jest/no-alias-methods': 'off',
93
+ 'jest/no-commented-out-tests': 'off',
94
+ 'jest/no-conditional-expect': 'off',
95
+ 'jest/no-conditional-in-test': 'off',
96
+ 'jest/no-confusing-set-timeout': 'off',
97
+ 'jest/no-deprecated-functions': 'off',
98
+ 'jest/no-disabled-tests': 'off',
99
+ 'jest/no-done-callback': 'off',
100
+ 'jest/no-duplicate-hooks': 'off',
101
+ 'jest/no-export': 'off',
102
+ 'jest/no-focused-tests': 'off',
103
+ 'jest/no-hooks': 'off',
104
+ 'jest/no-identical-title': 'off',
105
+ 'jest/no-interpolation-in-snapshots': 'off',
106
+ 'jest/no-jasmine-globals': 'off',
107
+ 'jest/no-large-snapshots': 'off',
108
+ 'jest/no-mocks-import': 'off',
109
+ 'jest/no-restricted-jest-methods': 'off',
110
+ 'jest/no-restricted-matchers': 'off',
111
+ 'jest/no-standalone-expect': 'off',
112
+ 'jest/no-test-prefixes': 'off',
113
+ 'jest/no-test-return-statement': 'off',
114
+ 'jest/no-unneeded-async-expect-function': 'off',
115
+ 'jest/no-untyped-mock-factory': 'off',
116
+ //'jest/padding-around-after-all-blocks': 'off',
117
+ 'jest/padding-around-test-blocks': 'off',
118
+ 'jest/prefer-called-with': 'off',
119
+ 'jest/prefer-comparison-matcher': 'off',
120
+ 'jest/prefer-each': 'off',
121
+ 'jest/prefer-equality-matcher': 'off',
122
+ 'jest/prefer-expect-resolves': 'off',
123
+ 'jest/prefer-hooks-in-order': 'off',
124
+ 'jest/prefer-hooks-on-top': 'off',
125
+ 'jest/prefer-jest-mocked': 'off',
126
+ 'jest/prefer-lowercase-title': 'off',
127
+ 'jest/prefer-mock-promise-shorthand': 'off',
128
+ 'jest/prefer-mock-return-shorthand': 'off',
129
+ //'jest/prefer-snapshot-hint': 'off',
130
+ 'jest/prefer-spy-on': 'off',
131
+ 'jest/prefer-strict-equal': 'off',
132
+ 'jest/prefer-to-be': 'off',
133
+ 'jest/prefer-to-contain': 'off',
134
+ 'jest/prefer-to-have-been-called': 'off',
135
+ 'jest/prefer-to-have-been-called-times': 'off',
136
+ 'jest/prefer-to-have-length': 'off',
137
+ 'jest/prefer-todo': 'off',
138
+ 'jest/require-hook': 'off',
139
+ 'jest/require-to-throw-message': 'off',
140
+ 'jest/require-top-level-describe': 'off',
141
+ 'jest/valid-describe-callback': 'off',
142
+ 'jest/valid-expect': 'off',
143
+ 'jest/valid-title': 'off',
144
+ 'oxc/no-async-await': 'off',
145
+ 'oxc/no-map-spread': 'off', // TODO: consider enabling
146
+ 'oxc/no-rest-spread-properties': 'off',
147
+ 'unicorn/escape-case': 'off',
148
+ 'unicorn/filename-case': 'off', // TODO: consider enabling
149
+ 'unicorn/no-array-reduce': 'off', // TODO: consider enabling
150
+ 'unicorn/no-array-sort': 'off', // TODO: consider enabling
151
+ 'unicorn/no-hex-escape': 'off',
152
+ 'unicorn/no-immediate-mutation': 'off',
153
+ 'unicorn/no-nested-ternary': 'off',
154
+ 'unicorn/no-null': 'off', // TODO: consider enabling
155
+ 'unicorn/no-process-exit': 'off', // TODO: consider enabling
156
+ 'unicorn/no-typeof-undefined': 'off', // TODO: consider enabling
157
+ 'unicorn/prefer-module': 'off', // TODO: consider enabling
158
+ 'react/jsx-filename-extension': 'off',
159
+ 'react/react-in-jsx-scope': 'off',
160
+ 'vitest/no-importing-vitest-globals': 'off',
161
+ 'vitest/prefer-describe-function-title': 'off',
162
+ 'vitest/prefer-to-be-truthy': 'off', // FIXME: Conflict Detected: prefer-strict-boolean-matchers enforces toBe(true), but prefer-to-be-truthy enforces toBeTruthy().
163
+ 'vitest/require-test-timeout': 'off',
164
+ },
165
+ overrides: [
166
+ {
167
+ files: ['tests/e2e/**/*.e2e-test.ts', '**/*.e2e-test.ts'],
168
+ rules: {
169
+ 'vitest/prefer-importing-vitest-globals': 'off',
170
+ },
171
+ },
172
+ ],
173
+ settings: {
174
+ 'jsx-a11y': {
175
+ polymorphicPropName: undefined,
176
+ components: {},
177
+ attributes: {},
178
+ },
179
+ next: {
180
+ rootDir: [],
181
+ },
182
+ react: {
183
+ formComponents: [],
184
+ linkComponents: [],
185
+ version: undefined,
186
+ },
187
+ jsdoc: {
188
+ ignorePrivate: false,
189
+ ignoreInternal: false,
190
+ ignoreReplacesDocs: true,
191
+ overrideReplacesDocs: true,
192
+ augmentsExtendsReplacesDocs: false,
193
+ implementsReplacesDocs: false,
194
+ exemptDestructuredRootsFromChecks: false,
195
+ tagNamePreference: {},
196
+ },
197
+ vitest: {
198
+ typecheck: false,
199
+ },
200
+ },
201
+ env: {
202
+ builtin: true,
203
+ },
204
+ globals: {},
205
+ ignorePatterns: commonIgnore,
81
206
  });
82
207
 
83
208
  export const formatter = {
84
- printWidth: 160,
85
- embeddedLanguageFormatting: "off",
86
- useTabs: true,
87
- singleQuote: true,
88
- bracketSpacing: false,
89
- ignorePatterns: commonIgnore,
90
- overrides: [
91
- {
92
- files: ["src/**/*.{scss,css}"],
93
- options: {
94
- singleQuote: false,
95
- },
96
- },
97
- ],
209
+ printWidth: 160,
210
+ embeddedLanguageFormatting: 'off',
211
+ useTabs: true,
212
+ singleQuote: true,
213
+ bracketSpacing: false,
214
+ ignorePatterns: commonIgnore,
215
+ overrides: [
216
+ {
217
+ files: ['src/**/*.{scss,css}'],
218
+ options: {
219
+ singleQuote: false,
220
+ },
221
+ },
222
+ ],
98
223
  };
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {Command} from 'commander';
3
3
  import {SingleBar, Presets} from 'cli-progress';
4
+ import {greet} from './lib.js';
4
5
 
5
6
  const program = new Command();
6
7
  program.name('my-cli').description('A sample CLI');
@@ -11,6 +12,4 @@ bar.start(100, 0);
11
12
  bar.update(50);
12
13
  bar.stop();
13
14
 
14
- import {greet} from './lib.js';
15
-
16
15
  console.log(greet('Developer'));
@@ -1,6 +1,4 @@
1
- export const greet = (name: string): string => {
2
- return `Hello, ${name}! Welcome to your new CLI.`;
3
- };
1
+ export const greet = (name: string): string => `Hello, ${name}! Welcome to your new CLI.`;
4
2
 
5
3
  export const calculateProgress = (current: number, total: number): number => {
6
4
  if (total === 0) {
@@ -1,12 +1,15 @@
1
1
  import {defineConfig, devices} from '@playwright/test';
2
+ import {env} from 'node:process';
3
+
4
+ const isCi = env.CI !== undefined;
2
5
 
3
6
  export default defineConfig({
4
7
  testDir: './tests/e2e',
5
8
  testMatch: '**/*.e2e-test.ts',
6
9
  fullyParallel: true,
7
- forbidOnly: !!process.env.CI,
8
- retries: process.env.CI ? 2 : 0,
9
- workers: process.env.CI ? 1 : undefined,
10
+ forbidOnly: isCi,
11
+ retries: isCi ? 2 : 0,
12
+ workers: isCi ? 1 : undefined,
10
13
  reporter: 'html',
11
14
  use: {
12
15
  baseURL: 'http://localhost:3000',
@@ -21,6 +24,6 @@ export default defineConfig({
21
24
  webServer: {
22
25
  command: 'npm run build && npm run start',
23
26
  url: 'http://localhost:3000',
24
- reuseExistingServer: !process.env.CI,
27
+ reuseExistingServer: !isCi,
25
28
  },
26
29
  });
@@ -1,14 +1,13 @@
1
1
  import {Typography, Container, Box} from '@mui/material';
2
+ import {type ReactElement} from 'react';
2
3
 
3
- export const App = () => {
4
- return (
5
- <Container maxWidth="sm">
6
- <Box sx={{my: 4, textAlign: 'center'}}>
7
- <Typography variant="h4" component="h1" gutterBottom>
8
- Hello from React!
9
- </Typography>
10
- <Typography variant="body1">This project was scaffolded with the web-app template.</Typography>
11
- </Box>
12
- </Container>
13
- );
14
- };
4
+ export const App = (): ReactElement => (
5
+ <Container maxWidth="sm">
6
+ <Box sx={{my: 4, textAlign: 'center'}}>
7
+ <Typography variant="h4" component="h1" gutterBottom>
8
+ Hello from React!
9
+ </Typography>
10
+ <Typography variant="body1">This project was scaffolded with the web-app template.</Typography>
11
+ </Box>
12
+ </Container>
13
+ );
@@ -1,7 +1,7 @@
1
1
  import {createRoot} from 'react-dom/client';
2
2
  import {App} from './App.js';
3
3
 
4
- const container = document.getElementById('app');
4
+ const container = document.querySelector<HTMLElement>('#app');
5
5
  if (container) {
6
6
  const root = createRoot(container);
7
7
  root.render(<App />);
@@ -8,6 +8,6 @@ test('has title', async ({page}) => {
8
8
  test('api is reachable', async ({page}) => {
9
9
  const response = await page.request.get('/api/hello');
10
10
  expect(response.ok()).toBeTruthy();
11
- const data = await response.json();
12
- expect(data.message).toBe('Hello from Express!');
11
+ const data: unknown = await response.json();
12
+ expect(data).toMatchObject({message: 'Hello from Express!'});
13
13
  });
@@ -1,4 +1,5 @@
1
- import {useState} from 'react';
1
+ /* eslint-disable react/jsx-max-depth */
2
+ import {useMemo, type ReactNode} from 'react';
2
3
  import {BrowserRouter, Routes, Route, Navigate} from 'react-router-dom';
3
4
  import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
4
5
  import {httpBatchLink} from '@trpc/client';
@@ -11,20 +12,22 @@ import {CssBaseline, ThemeProvider, createTheme} from '@mui/material';
11
12
 
12
13
  const theme = createTheme();
13
14
 
14
- export const App = () => {
15
- const [queryClient] = useState(() => new QueryClient());
16
- const [trpcClient] = useState(() =>
15
+ export const App = (): ReactNode => {
16
+ const queryClient = useMemo(() => new QueryClient(), []);
17
+ const trpcClient = useMemo(
18
+ () =>
17
19
  trpc.createClient({
18
20
  links: [
19
21
  httpBatchLink({
20
22
  url: 'http://localhost:3001/trpc',
21
23
  headers: () => {
22
24
  const token = localStorage.getItem('token');
23
- return token ? {Authorization: `Bearer ${token}`} : {};
25
+ return token !== null && token !== '' ? {Authorization: `Bearer ${token}`} : {};
24
26
  },
25
27
  }),
26
28
  ],
27
29
  }),
30
+ [],
28
31
  );
29
32
 
30
33
  return (
@@ -1,19 +1,21 @@
1
+ /* eslint-disable import/no-relative-parent-imports */
1
2
  import {Navigate, Outlet} from 'react-router-dom';
3
+ import {type ReactNode} from 'react';
2
4
  import {useAuth} from '../contexts/AuthContext.js';
3
5
  import {CircularProgress, Box} from '@mui/material';
4
6
 
5
- export const ProtectedRoute = () => {
7
+ export const ProtectedRoute = (): ReactNode => {
6
8
  const {token, isLoading} = useAuth();
7
9
 
8
10
  if (isLoading) {
9
11
  return (
10
- <Box display="flex" justifyContent="center" alignItems="center" minHeight="100vh">
12
+ <Box sx={{display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '100vh'}}>
11
13
  <CircularProgress />
12
14
  </Box>
13
15
  );
14
16
  }
15
17
 
16
- if (!token) {
18
+ if (token === null || token === '') {
17
19
  return <Navigate to="/login" replace />;
18
20
  }
19
21
 
@@ -1,56 +1,60 @@
1
- import {createContext, useContext, useState, useEffect, ReactNode} from 'react';
1
+ /* eslint-disable import/no-relative-parent-imports */
2
+ /* eslint-disable react/only-export-components */
3
+ /* eslint-disable react/jsx-no-constructed-context-values */
4
+ /* eslint-disable typescript/no-unnecessary-condition */
5
+ import {createContext, useContext, useState, useEffect, type ReactNode} from 'react';
2
6
  import {trpc} from '../trpc.js';
3
7
 
4
- interface User {
8
+ type User = {
5
9
  id: string;
6
10
  name: string;
7
11
  email: string;
8
- }
12
+ };
9
13
 
10
- interface AuthContextType {
14
+ type AuthContextType = {
11
15
  user: User | null;
12
16
  token: string | null;
13
17
  isLoading: boolean;
14
18
  login: (token: string, user: User) => void;
15
19
  logout: () => void;
16
- }
20
+ };
17
21
 
18
22
  const AuthContext = createContext<AuthContextType | undefined>(undefined);
19
23
 
20
- export const AuthProvider = ({children}: {children: ReactNode}) => {
21
- const [token, setToken] = useState<string | null>(localStorage.getItem('token'));
24
+ export const AuthProvider = ({children}: {children: ReactNode}): ReactNode => {
25
+ const [token, setToken] = useState(localStorage.getItem('token'));
22
26
  const [user, setUser] = useState<User | null>(null);
23
27
 
24
28
  const {data: me, isLoading} = trpc.auth.me.useQuery(undefined, {
25
- enabled: !!token,
29
+ enabled: Boolean(token),
26
30
  retry: false,
27
31
  });
28
32
 
33
+ const logout = (): void => {
34
+ localStorage.removeItem('token');
35
+ setToken(null);
36
+ setUser(null);
37
+ };
38
+
29
39
  useEffect(() => {
30
- if (me) {
40
+ if (me !== undefined && me !== null) {
31
41
  setUser(me);
32
- } else if (!isLoading && token) {
42
+ } else if (!isLoading && token !== null && token !== '') {
33
43
  // Token might be invalid
34
44
  logout();
35
45
  }
36
46
  }, [me, isLoading, token]);
37
47
 
38
- const login = (newToken: string, newUser: User) => {
48
+ const login = (newToken: string, newUser: User): void => {
39
49
  localStorage.setItem('token', newToken);
40
50
  setToken(newToken);
41
51
  setUser(newUser);
42
52
  };
43
53
 
44
- const logout = () => {
45
- localStorage.removeItem('token');
46
- setToken(null);
47
- setUser(null);
48
- };
49
-
50
54
  return <AuthContext.Provider value={{user, token, isLoading, login, logout}}>{children}</AuthContext.Provider>;
51
55
  };
52
56
 
53
- export const useAuth = () => {
57
+ export const useAuth = (): AuthContextType => {
54
58
  const context = useContext(AuthContext);
55
59
  if (!context) {
56
60
  throw new Error('useAuth must be used within an AuthProvider');
@@ -2,8 +2,11 @@ import React from 'react';
2
2
  import ReactDOM from 'react-dom/client';
3
3
  import {App} from './App.js';
4
4
 
5
- ReactDOM.createRoot(document.getElementById('root')!).render(
6
- <React.StrictMode>
7
- <App />
8
- </React.StrictMode>,
9
- );
5
+ const rootElement = document.querySelector('#root');
6
+ if (rootElement !== null) {
7
+ ReactDOM.createRoot(rootElement).render(
8
+ <React.StrictMode>
9
+ <App />
10
+ </React.StrictMode>,
11
+ );
12
+ }
@@ -1,29 +1,34 @@
1
+ /* eslint-disable react/jsx-max-depth */
2
+ /* eslint-disable import/no-relative-parent-imports */
3
+ /* eslint-disable eslint/no-void */
4
+ /* eslint-disable oxc/no-optional-chaining */
1
5
  import {Container, Typography, Button, Paper, Box} from '@mui/material';
6
+ import {type ReactNode} from 'react';
2
7
  import {useAuth} from '../contexts/AuthContext.js';
3
8
  import {useNavigate} from 'react-router-dom';
4
9
 
5
- export const Dashboard = () => {
10
+ export const Dashboard = (): ReactNode => {
6
11
  const {user, logout} = useAuth();
7
12
  const navigate = useNavigate();
8
13
 
9
- const handleLogout = () => {
14
+ const handleLogout = (): void => {
10
15
  logout();
11
16
  void navigate('/login');
12
17
  };
13
18
 
14
19
  return (
15
20
  <Container maxWidth="md">
16
- <Box mt={4}>
21
+ <Box sx={{mt: 4}}>
17
22
  <Paper elevation={3}>
18
- <Box p={4}>
23
+ <Box sx={{p: 4}}>
19
24
  <Typography variant="h4" gutterBottom>
20
25
  Dashboard
21
26
  </Typography>
22
- <Typography variant="h6">Welcome, {user?.name}!</Typography>
27
+ <Typography variant="h6">Welcome, {user?.name ?? ''}!</Typography>
23
28
  <Typography variant="body1" color="textSecondary" gutterBottom>
24
- Email: {user?.email}
29
+ Email: {user?.email ?? ''}
25
30
  </Typography>
26
- <Box mt={4}>
31
+ <Box sx={{mt: 4}}>
27
32
  <Typography variant="body2" gutterBottom>
28
33
  This is a protected page. You can only see this because you are logged in.
29
34
  </Typography>