@idevconn/create-icore 0.5.2 → 0.6.1
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/dist/cli.js +109 -45
- package/dist/index.cjs +109 -45
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +109 -45
- package/package.json +1 -1
- package/templates/apps/api/.env.example +20 -0
- package/templates/apps/api/package.json +1 -0
- package/templates/apps/api/tsconfig.json +6 -1
- package/templates/apps/api/webpack.config.js +20 -0
- package/templates/apps/microservices/auth/.env.example +5 -0
- package/templates/apps/microservices/auth/tsconfig.json +6 -1
- package/templates/apps/microservices/auth/webpack.config.js +30 -0
- package/templates/apps/microservices/jobs/tsconfig.json +6 -1
- package/templates/apps/microservices/jobs/webpack.config.js +30 -0
- package/templates/apps/microservices/notes/.env.example +5 -0
- package/templates/apps/microservices/notes/tsconfig.json +6 -1
- package/templates/apps/microservices/notes/webpack.config.js +30 -0
- package/templates/apps/microservices/notes-e2e/src/support/global.d.ts +6 -0
- package/templates/apps/microservices/payment/.env.example +5 -0
- package/templates/apps/microservices/payment/tsconfig.json +6 -1
- package/templates/apps/microservices/payment/webpack.config.js +30 -0
- package/templates/apps/microservices/upload/.env.example +5 -0
- package/templates/apps/microservices/upload/tsconfig.json +6 -1
- package/templates/apps/microservices/upload/webpack.config.js +30 -0
- package/templates/apps/templates/client-antd/src/components/AccessDeniedPage.tsx +1 -1
- package/templates/apps/templates/client-antd/src/components/layout/LayoutHeader.tsx +1 -1
- package/templates/apps/templates/client-antd/src/components/layout/LayoutSider.tsx +3 -3
- package/templates/apps/templates/client-antd/src/routes/_dashboard/dashboard.tsx +2 -2
- package/templates/apps/templates/client-antd/src/routes/_dashboard/notes.tsx +2 -2
- package/templates/apps/templates/client-antd/src/routes/_dashboard/profile.tsx +2 -2
- package/templates/apps/templates/client-antd/src/routes/auth.callback.tsx +1 -1
- package/templates/apps/templates/client-antd/src/routes/auth.oauth.callback.tsx +1 -1
- package/templates/apps/templates/client-antd/src/routes/login.tsx +1 -1
- package/templates/apps/templates/client-antd/tsconfig.json +6 -1
- package/templates/apps/templates/client-antd-e2e/src/icore.spec.ts +2 -2
- package/templates/apps/templates/client-mui/src/components/AccessDeniedPage.tsx +1 -1
- package/templates/apps/templates/client-mui/src/components/layout/LayoutHeader.tsx +1 -1
- package/templates/apps/templates/client-mui/src/components/layout/LayoutSider.tsx +3 -15
- package/templates/apps/templates/client-mui/src/routes/_dashboard/dashboard.tsx +2 -6
- package/templates/apps/templates/client-mui/src/routes/_dashboard/notes.tsx +2 -2
- package/templates/apps/templates/client-mui/src/routes/_dashboard/profile.tsx +3 -3
- package/templates/apps/templates/client-mui/src/routes/auth.callback.tsx +1 -1
- package/templates/apps/templates/client-mui/src/routes/auth.oauth.callback.tsx +1 -1
- package/templates/apps/templates/client-mui/src/routes/login.tsx +3 -3
- package/templates/apps/templates/client-mui/tsconfig.json +6 -1
- package/templates/apps/templates/client-mui-e2e/src/icore.spec.ts +2 -2
- package/templates/apps/templates/client-shadcn/src/components/AccessDeniedPage.tsx +1 -1
- package/templates/apps/templates/client-shadcn/src/components/layout/LayoutSider.tsx +3 -3
- package/templates/apps/templates/client-shadcn/src/components/notes/DeleteNoteConfirm.tsx +1 -1
- package/templates/apps/templates/client-shadcn/src/components/notes/NoteDialog.tsx +3 -3
- package/templates/apps/templates/client-shadcn/src/components/notes/NotesTable.tsx +1 -1
- package/templates/apps/templates/client-shadcn/src/components/ui/button.tsx +1 -1
- package/templates/apps/templates/client-shadcn/src/components/ui/card.tsx +1 -1
- package/templates/apps/templates/client-shadcn/src/components/ui/input.tsx +1 -1
- package/templates/apps/templates/client-shadcn/src/components/ui/label.tsx +1 -1
- package/templates/apps/templates/client-shadcn/src/routes/_dashboard/dashboard.tsx +3 -9
- package/templates/apps/templates/client-shadcn/src/routes/_dashboard/notes.tsx +6 -6
- package/templates/apps/templates/client-shadcn/src/routes/_dashboard/profile.tsx +7 -7
- package/templates/apps/templates/client-shadcn/src/routes/auth.callback.tsx +1 -1
- package/templates/apps/templates/client-shadcn/src/routes/auth.oauth.callback.tsx +1 -1
- package/templates/apps/templates/client-shadcn/src/routes/login.tsx +4 -4
- package/templates/apps/templates/client-shadcn/tsconfig.json +6 -1
- package/templates/libs/auth-client/package.json +1 -1
- package/templates/libs/auth-strategies/firebase/package.json +1 -1
- package/templates/libs/auth-strategies/firebase/src/lib/__tests__/firebase-auth.contract.unit.test.ts +6 -3
- package/templates/libs/auth-strategies/supabase/package.json +1 -1
- package/templates/libs/auth-strategies/supabase/src/lib/__tests__/supabase-auth.contract.unit.test.ts +5 -2
- package/templates/libs/db-strategies/firestore/package.json +1 -1
- package/templates/libs/db-strategies/firestore/src/lib/__tests__/firestore-db.contract.unit.test.ts +1 -2
- package/templates/libs/db-strategies/supabase/package.json +1 -1
- package/templates/libs/db-strategies/supabase/src/lib/__tests__/supabase-db.contract.unit.test.ts +1 -2
- package/templates/libs/firebase-admin/package.json +1 -1
- package/templates/libs/jobs-client/package.json +1 -1
- package/templates/libs/notes-client/package.json +1 -1
- package/templates/libs/payment-client/package.json +1 -1
- package/templates/libs/shared/package.json +3 -3
- package/templates/libs/shared/src/__tests__/cross-boundary.unit.test.ts +2 -1
- package/templates/libs/shared/src/__tests__/transport.unit.test.ts +47 -8
- package/templates/libs/shared/src/abilities/subjects.ts +12 -1
- package/templates/libs/shared/src/strategies/__tests__/fake-auth.contract.unit.test.ts +2 -2
- package/templates/libs/shared/src/strategies/__tests__/fake-db.contract.unit.test.ts +2 -2
- package/templates/libs/shared/src/strategies/__tests__/fake-storage.contract.unit.test.ts +2 -2
- package/templates/libs/shared/src/transport.ts +41 -0
- package/templates/libs/storage-strategies/cloudinary/package.json +1 -1
- package/templates/libs/storage-strategies/cloudinary/src/lib/__tests__/cloudinary-storage.contract.unit.test.ts +1 -2
- package/templates/libs/storage-strategies/firebase/package.json +1 -1
- package/templates/libs/storage-strategies/firebase/src/lib/__tests__/firebase-storage.contract.unit.test.ts +1 -2
- package/templates/libs/storage-strategies/supabase/package.json +1 -1
- package/templates/libs/storage-strategies/supabase/src/lib/__tests__/supabase-storage.contract.unit.test.ts +1 -2
- package/templates/libs/template-shared/package.json +1 -1
- package/templates/libs/upload-client/package.json +1 -1
- package/templates/libs/vite-plugins/src/index.d.mts +5 -7
- package/templates/libs/vite-plugins/src/index.mjs +1 -1
- package/templates/libs/vite-plugins/tsconfig.json +2 -1
- package/templates/package.json +2 -1
- package/templates/tools/create-icore/_template-shell/package.json +2 -1
- package/templates/.yarn/releases/yarn-4.5.0.cjs +0 -925
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
|
|
2
|
+
const { TsconfigPathsPlugin } = require('tsconfig-paths-webpack-plugin');
|
|
2
3
|
const { join } = require('path');
|
|
3
4
|
|
|
4
5
|
module.exports = {
|
|
@@ -9,6 +10,31 @@ module.exports = {
|
|
|
9
10
|
devtoolModuleFilenameTemplate: '[absolute-resource-path]',
|
|
10
11
|
}),
|
|
11
12
|
},
|
|
13
|
+
resolve: {
|
|
14
|
+
// Resolve @icore/* via tsconfig paths so webpack can bundle them inline.
|
|
15
|
+
// nx skips its own tsconfig-paths plugin on "TS solution" workspaces, so we
|
|
16
|
+
// wire the standalone plugin explicitly (it follows extends → tsconfig.base).
|
|
17
|
+
plugins: [new TsconfigPathsPlugin({ configFile: join(__dirname, 'tsconfig.app.json') })],
|
|
18
|
+
},
|
|
19
|
+
// Keep every npm package external EXCEPT @icore/* workspace packages, which
|
|
20
|
+
// are bundled inline. @icore/* are workspace-internal: at runtime the package
|
|
21
|
+
// manager symlinks them to their TS source dir (not the compiled dist), so an
|
|
22
|
+
// external require('@icore/shared') fails ("Cannot find module './env'").
|
|
23
|
+
// Bundling removes runtime workspace resolution entirely — works identically
|
|
24
|
+
// on yarn / npm / pnpm.
|
|
25
|
+
externals: [
|
|
26
|
+
function ({ request }, callback) {
|
|
27
|
+
if (
|
|
28
|
+
!request ||
|
|
29
|
+
request.startsWith('.') ||
|
|
30
|
+
request.startsWith('/') ||
|
|
31
|
+
request.startsWith('@icore/')
|
|
32
|
+
) {
|
|
33
|
+
return callback(); // bundle inline
|
|
34
|
+
}
|
|
35
|
+
return callback(null, 'commonjs ' + request); // keep external
|
|
36
|
+
},
|
|
37
|
+
],
|
|
12
38
|
plugins: [
|
|
13
39
|
new NxAppWebpackPlugin({
|
|
14
40
|
target: 'node',
|
|
@@ -19,6 +45,10 @@ module.exports = {
|
|
|
19
45
|
optimization: false,
|
|
20
46
|
outputHashing: 'none',
|
|
21
47
|
generatePackageJson: true,
|
|
48
|
+
// Keep our externals (above) authoritative — do not let the plugin add
|
|
49
|
+
// its own nodeExternals that would re-externalize @icore/*.
|
|
50
|
+
mergeExternals: true,
|
|
51
|
+
externalDependencies: [],
|
|
22
52
|
sourceMap: true,
|
|
23
53
|
}),
|
|
24
54
|
],
|
|
@@ -4,6 +4,11 @@ UPLOAD_HOST=127.0.0.1
|
|
|
4
4
|
UPLOAD_PORT=4002
|
|
5
5
|
# UPLOAD_REDIS_URL=redis://localhost:6379
|
|
6
6
|
# UPLOAD_NATS_URL=nats://localhost:4222
|
|
7
|
+
# UPLOAD_MQTT_URL=mqtt://localhost:1883
|
|
8
|
+
# UPLOAD_RMQ_URL=amqp://localhost:5672
|
|
9
|
+
# UPLOAD_RMQ_QUEUE=upload_queue
|
|
10
|
+
# UPLOAD_KAFKA_BROKERS=localhost:9092
|
|
11
|
+
# UPLOAD_KAFKA_CLIENT_ID=upload
|
|
7
12
|
|
|
8
13
|
# Which concrete StorageStrategy to instantiate
|
|
9
14
|
STORAGE_PROVIDER=supabase
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
|
|
2
|
+
const { TsconfigPathsPlugin } = require('tsconfig-paths-webpack-plugin');
|
|
2
3
|
const { join } = require('path');
|
|
3
4
|
|
|
4
5
|
module.exports = {
|
|
@@ -9,6 +10,31 @@ module.exports = {
|
|
|
9
10
|
devtoolModuleFilenameTemplate: '[absolute-resource-path]',
|
|
10
11
|
}),
|
|
11
12
|
},
|
|
13
|
+
resolve: {
|
|
14
|
+
// Resolve @icore/* via tsconfig paths so webpack can bundle them inline.
|
|
15
|
+
// nx skips its own tsconfig-paths plugin on "TS solution" workspaces, so we
|
|
16
|
+
// wire the standalone plugin explicitly (it follows extends → tsconfig.base).
|
|
17
|
+
plugins: [new TsconfigPathsPlugin({ configFile: join(__dirname, 'tsconfig.app.json') })],
|
|
18
|
+
},
|
|
19
|
+
// Keep every npm package external EXCEPT @icore/* workspace packages, which
|
|
20
|
+
// are bundled inline. @icore/* are workspace-internal: at runtime the package
|
|
21
|
+
// manager symlinks them to their TS source dir (not the compiled dist), so an
|
|
22
|
+
// external require('@icore/shared') fails ("Cannot find module './env'").
|
|
23
|
+
// Bundling removes runtime workspace resolution entirely — works identically
|
|
24
|
+
// on yarn / npm / pnpm.
|
|
25
|
+
externals: [
|
|
26
|
+
function ({ request }, callback) {
|
|
27
|
+
if (
|
|
28
|
+
!request ||
|
|
29
|
+
request.startsWith('.') ||
|
|
30
|
+
request.startsWith('/') ||
|
|
31
|
+
request.startsWith('@icore/')
|
|
32
|
+
) {
|
|
33
|
+
return callback(); // bundle inline
|
|
34
|
+
}
|
|
35
|
+
return callback(null, 'commonjs ' + request); // keep external
|
|
36
|
+
},
|
|
37
|
+
],
|
|
12
38
|
plugins: [
|
|
13
39
|
new NxAppWebpackPlugin({
|
|
14
40
|
target: 'node',
|
|
@@ -19,6 +45,10 @@ module.exports = {
|
|
|
19
45
|
optimization: false,
|
|
20
46
|
outputHashing: 'none',
|
|
21
47
|
generatePackageJson: true,
|
|
48
|
+
// Keep our externals (above) authoritative — do not let the plugin add
|
|
49
|
+
// its own nodeExternals that would re-externalize @icore/*.
|
|
50
|
+
mergeExternals: true,
|
|
51
|
+
externalDependencies: [],
|
|
22
52
|
sourceMap: true,
|
|
23
53
|
}),
|
|
24
54
|
],
|
|
@@ -19,17 +19,17 @@ export function LayoutSider() {
|
|
|
19
19
|
{
|
|
20
20
|
key: 'dashboard',
|
|
21
21
|
icon: <DashboardOutlined />,
|
|
22
|
-
label: <Link to="/
|
|
22
|
+
label: <Link to="/dashboard">{t('nav.dashboard')}</Link>,
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
25
|
key: 'notes',
|
|
26
26
|
icon: <FileTextOutlined />,
|
|
27
|
-
label: <Link to="/
|
|
27
|
+
label: <Link to="/notes">{t('notes.title')}</Link>,
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
key: 'profile',
|
|
31
31
|
icon: <UserOutlined />,
|
|
32
|
-
label: <Link to="/
|
|
32
|
+
label: <Link to="/profile">{t('nav.profile')}</Link>,
|
|
33
33
|
},
|
|
34
34
|
];
|
|
35
35
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createFileRoute, Link } from '@tanstack/react-router';
|
|
2
2
|
import { Button, Card } from 'antd';
|
|
3
3
|
import { useAuthStore } from '@icore/template-shared';
|
|
4
|
-
import { PageLayout } from '
|
|
4
|
+
import { PageLayout } from '@/components/PageLayout';
|
|
5
5
|
|
|
6
6
|
function DashboardHome() {
|
|
7
7
|
const user = useAuthStore((s) => s.user);
|
|
@@ -11,7 +11,7 @@ function DashboardHome() {
|
|
|
11
11
|
title="Hello, world"
|
|
12
12
|
style={{ maxWidth: 600 }}
|
|
13
13
|
extra={
|
|
14
|
-
<Link to="/
|
|
14
|
+
<Link to="/profile">
|
|
15
15
|
<Button type="link">Go to profile →</Button>
|
|
16
16
|
</Link>
|
|
17
17
|
}
|
|
@@ -3,14 +3,14 @@ import { useState } from 'react';
|
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { Button, Form, Input, Modal, Popconfirm, Space, Table, Typography } from 'antd';
|
|
5
5
|
import type { ColumnsType } from 'antd/es/table';
|
|
6
|
-
import { PageLayout } from '
|
|
6
|
+
import { PageLayout } from '@/components/PageLayout';
|
|
7
7
|
import {
|
|
8
8
|
useCreateNote,
|
|
9
9
|
useDeleteNote,
|
|
10
10
|
useNotesList,
|
|
11
11
|
useUpdateNote,
|
|
12
12
|
type Note,
|
|
13
|
-
} from '
|
|
13
|
+
} from '@/queries/notes';
|
|
14
14
|
|
|
15
15
|
const PAGE_SIZE = 20;
|
|
16
16
|
|
|
@@ -3,8 +3,8 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
|
3
3
|
import { useEffect, useState } from 'react';
|
|
4
4
|
import { Button, Form, Input } from 'antd';
|
|
5
5
|
import { useDraft, useNotify, useAuthStore } from '@icore/template-shared';
|
|
6
|
-
import { PageLayout } from '
|
|
7
|
-
import { api } from '
|
|
6
|
+
import { PageLayout } from '@/components/PageLayout';
|
|
7
|
+
import { api } from '@/main';
|
|
8
8
|
|
|
9
9
|
interface ProfilePayload {
|
|
10
10
|
uid: string;
|
|
@@ -41,7 +41,7 @@ function LoginPage() {
|
|
|
41
41
|
});
|
|
42
42
|
setAuth(session);
|
|
43
43
|
notify.success(t('auth.login'));
|
|
44
|
-
await navigate({ to: '/
|
|
44
|
+
await navigate({ to: '/dashboard' });
|
|
45
45
|
} catch (err) {
|
|
46
46
|
notify.error(err instanceof Error ? err.message : t('error.unknown'));
|
|
47
47
|
}
|
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
"esModuleInterop": false,
|
|
9
9
|
"allowSyntheticDefaultImports": true,
|
|
10
10
|
"strict": true,
|
|
11
|
-
"types": ["vite/client", "vitest"]
|
|
11
|
+
"types": ["vite/client", "vitest"],
|
|
12
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
13
|
+
"paths": {
|
|
14
|
+
"@/*": ["./src/*"]
|
|
15
|
+
},
|
|
16
|
+
"baseUrl": "."
|
|
12
17
|
},
|
|
13
18
|
"files": [],
|
|
14
19
|
"include": [],
|
|
@@ -14,12 +14,12 @@ test.describe('icore client-antd smoke', () => {
|
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
test('protected route redirects to login when unauthenticated', async ({ page }) => {
|
|
17
|
-
await page.goto('/
|
|
17
|
+
await page.goto('/dashboard');
|
|
18
18
|
await expect(page).toHaveURL(/\/login$/);
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
test('profile route redirects to login when unauthenticated', async ({ page }) => {
|
|
22
|
-
await page.goto('/
|
|
22
|
+
await page.goto('/profile');
|
|
23
23
|
await expect(page).toHaveURL(/\/login$/);
|
|
24
24
|
});
|
|
25
25
|
});
|
|
@@ -16,7 +16,7 @@ export function AccessDeniedPage() {
|
|
|
16
16
|
{t('error.unknown')}
|
|
17
17
|
</Typography>
|
|
18
18
|
<Box>
|
|
19
|
-
<Button component={Link} to="/
|
|
19
|
+
<Button component={Link} to="/dashboard" variant="contained">
|
|
20
20
|
Dashboard
|
|
21
21
|
</Button>
|
|
22
22
|
</Box>
|
|
@@ -91,7 +91,7 @@ export function LayoutHeader() {
|
|
|
91
91
|
{user?.email ?? ''}
|
|
92
92
|
</Typography>
|
|
93
93
|
</MenuItem>
|
|
94
|
-
<MenuItem component={Link} to="/
|
|
94
|
+
<MenuItem component={Link} to="/profile" onClick={handleMenuClose}>
|
|
95
95
|
{t('nav.profile')}
|
|
96
96
|
</MenuItem>
|
|
97
97
|
<MenuItem onClick={handleLogout} sx={{ color: 'error.main' }}>
|
|
@@ -25,33 +25,21 @@ export function LayoutSider() {
|
|
|
25
25
|
}}
|
|
26
26
|
>
|
|
27
27
|
<List>
|
|
28
|
-
<ListItemButton
|
|
29
|
-
component={Link}
|
|
30
|
-
to="/_dashboard/dashboard"
|
|
31
|
-
selected={pathname === '/dashboard'}
|
|
32
|
-
>
|
|
28
|
+
<ListItemButton component={Link} to="/dashboard" selected={pathname === '/dashboard'}>
|
|
33
29
|
<ListItemIcon>
|
|
34
30
|
<DashboardOutlinedIcon />
|
|
35
31
|
</ListItemIcon>
|
|
36
32
|
<ListItemText primary={t('nav.dashboard')} />
|
|
37
33
|
</ListItemButton>
|
|
38
34
|
|
|
39
|
-
<ListItemButton
|
|
40
|
-
component={Link}
|
|
41
|
-
to="/_dashboard/notes"
|
|
42
|
-
selected={pathname.includes('/notes')}
|
|
43
|
-
>
|
|
35
|
+
<ListItemButton component={Link} to="/notes" selected={pathname.includes('/notes')}>
|
|
44
36
|
<ListItemIcon>
|
|
45
37
|
<NoteOutlinedIcon />
|
|
46
38
|
</ListItemIcon>
|
|
47
39
|
<ListItemText primary={t('notes.title')} />
|
|
48
40
|
</ListItemButton>
|
|
49
41
|
|
|
50
|
-
<ListItemButton
|
|
51
|
-
component={Link}
|
|
52
|
-
to="/_dashboard/profile"
|
|
53
|
-
selected={pathname === '/profile'}
|
|
54
|
-
>
|
|
42
|
+
<ListItemButton component={Link} to="/profile" selected={pathname === '/profile'}>
|
|
55
43
|
<ListItemIcon>
|
|
56
44
|
<PersonOutlineIcon />
|
|
57
45
|
</ListItemIcon>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createFileRoute, Link } from '@tanstack/react-router';
|
|
2
2
|
import { Card, CardContent, CardHeader, Typography } from '@mui/material';
|
|
3
3
|
import { useAuthStore } from '@icore/template-shared';
|
|
4
|
-
import { PageLayout } from '
|
|
4
|
+
import { PageLayout } from '@/components/PageLayout';
|
|
5
5
|
|
|
6
6
|
function DashboardHome() {
|
|
7
7
|
const user = useAuthStore((s) => s.user);
|
|
@@ -13,11 +13,7 @@ function DashboardHome() {
|
|
|
13
13
|
subheader="Edit this page in src/routes/_dashboard/dashboard.tsx"
|
|
14
14
|
/>
|
|
15
15
|
<CardContent>
|
|
16
|
-
<Typography
|
|
17
|
-
component={Link}
|
|
18
|
-
to="/_dashboard/profile"
|
|
19
|
-
sx={{ textDecoration: 'underline' }}
|
|
20
|
-
>
|
|
16
|
+
<Typography component={Link} to="/profile" sx={{ textDecoration: 'underline' }}>
|
|
21
17
|
Go to profile →
|
|
22
18
|
</Typography>
|
|
23
19
|
</CardContent>
|
|
@@ -22,14 +22,14 @@ import {
|
|
|
22
22
|
} from '@mui/material';
|
|
23
23
|
import EditIcon from '@mui/icons-material/Edit';
|
|
24
24
|
import DeleteIcon from '@mui/icons-material/Delete';
|
|
25
|
-
import { PageLayout } from '
|
|
25
|
+
import { PageLayout } from '@/components/PageLayout';
|
|
26
26
|
import {
|
|
27
27
|
useCreateNote,
|
|
28
28
|
useDeleteNote,
|
|
29
29
|
useNotesList,
|
|
30
30
|
useUpdateNote,
|
|
31
31
|
type Note,
|
|
32
|
-
} from '
|
|
32
|
+
} from '@/queries/notes';
|
|
33
33
|
|
|
34
34
|
const PAGE_SIZE = 20;
|
|
35
35
|
|
|
@@ -3,8 +3,8 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
|
3
3
|
import { useEffect, useState } from 'react';
|
|
4
4
|
import { Box, Button, TextField } from '@mui/material';
|
|
5
5
|
import { useDraft, useNotify, useAuthStore } from '@icore/template-shared';
|
|
6
|
-
import { PageLayout } from '
|
|
7
|
-
import { api } from '
|
|
6
|
+
import { PageLayout } from '@/components/PageLayout';
|
|
7
|
+
import { api } from '@/main';
|
|
8
8
|
|
|
9
9
|
interface ProfilePayload {
|
|
10
10
|
uid: string;
|
|
@@ -45,7 +45,7 @@ function ProfilePage() {
|
|
|
45
45
|
onError: (err) => notify.error(err instanceof Error ? err.message : 'save_failed'),
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
function handleSubmit(e: React.
|
|
48
|
+
function handleSubmit(e: React.SyntheticEvent<HTMLFormElement>) {
|
|
49
49
|
e.preventDefault();
|
|
50
50
|
save.mutate(name);
|
|
51
51
|
}
|
|
@@ -32,7 +32,7 @@ function LoginPage() {
|
|
|
32
32
|
const [sentEmail, setSentEmail] = useState('');
|
|
33
33
|
const [submitting, setSubmitting] = useState(false);
|
|
34
34
|
|
|
35
|
-
async function handlePasswordSubmit(e: React.
|
|
35
|
+
async function handlePasswordSubmit(e: React.SyntheticEvent<HTMLFormElement>) {
|
|
36
36
|
e.preventDefault();
|
|
37
37
|
setSubmitting(true);
|
|
38
38
|
try {
|
|
@@ -47,7 +47,7 @@ function LoginPage() {
|
|
|
47
47
|
});
|
|
48
48
|
setAuth(session);
|
|
49
49
|
notify.success(t('auth.login'));
|
|
50
|
-
await navigate({ to: '/
|
|
50
|
+
await navigate({ to: '/dashboard' });
|
|
51
51
|
} catch (err) {
|
|
52
52
|
notify.error(err instanceof Error ? err.message : t('error.unknown'));
|
|
53
53
|
} finally {
|
|
@@ -55,7 +55,7 @@ function LoginPage() {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
async function handleMagicLinkSubmit(e: React.
|
|
58
|
+
async function handleMagicLinkSubmit(e: React.SyntheticEvent<HTMLFormElement>) {
|
|
59
59
|
e.preventDefault();
|
|
60
60
|
setSubmitting(true);
|
|
61
61
|
try {
|
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
"esModuleInterop": false,
|
|
9
9
|
"allowSyntheticDefaultImports": true,
|
|
10
10
|
"strict": true,
|
|
11
|
-
"types": ["vite/client", "vitest"]
|
|
11
|
+
"types": ["vite/client", "vitest"],
|
|
12
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
13
|
+
"paths": {
|
|
14
|
+
"@/*": ["./src/*"]
|
|
15
|
+
},
|
|
16
|
+
"baseUrl": "."
|
|
12
17
|
},
|
|
13
18
|
"files": [],
|
|
14
19
|
"include": [],
|
|
@@ -14,12 +14,12 @@ test.describe('icore client-mui smoke', () => {
|
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
test('protected route redirects to login when unauthenticated', async ({ page }) => {
|
|
17
|
-
await page.goto('/
|
|
17
|
+
await page.goto('/dashboard');
|
|
18
18
|
await expect(page).toHaveURL(/\/login$/);
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
test('profile route redirects to login when unauthenticated', async ({ page }) => {
|
|
22
|
-
await page.goto('/
|
|
22
|
+
await page.goto('/profile');
|
|
23
23
|
await expect(page).toHaveURL(/\/login$/);
|
|
24
24
|
});
|
|
25
25
|
});
|
|
@@ -7,7 +7,7 @@ export function AccessDeniedPage() {
|
|
|
7
7
|
<div className="min-h-[60vh] flex flex-col items-center justify-center gap-2 text-center px-6">
|
|
8
8
|
<h1 className="text-2xl font-semibold">{t('error.accessDenied')}</h1>
|
|
9
9
|
<p className="text-muted-foreground">{t('error.unknown')}</p>
|
|
10
|
-
<Link to="/
|
|
10
|
+
<Link to="/dashboard" className="mt-4 underline">
|
|
11
11
|
← Dashboard
|
|
12
12
|
</Link>
|
|
13
13
|
</div>
|
|
@@ -15,7 +15,7 @@ export function LayoutSider() {
|
|
|
15
15
|
>
|
|
16
16
|
<nav className="flex flex-col gap-1 p-2 flex-1">
|
|
17
17
|
<Link
|
|
18
|
-
to="/
|
|
18
|
+
to="/dashboard"
|
|
19
19
|
activeOptions={{ exact: true }}
|
|
20
20
|
className="flex items-center gap-2 px-2 py-2 rounded hover:bg-muted text-sm text-foreground transition-colors [&.active]:bg-muted [&.active]:font-medium"
|
|
21
21
|
>
|
|
@@ -23,14 +23,14 @@ export function LayoutSider() {
|
|
|
23
23
|
{!collapsed && <span>{t('nav.dashboard')}</span>}
|
|
24
24
|
</Link>
|
|
25
25
|
<Link
|
|
26
|
-
to="/
|
|
26
|
+
to="/notes"
|
|
27
27
|
className="flex items-center gap-2 px-2 py-2 rounded hover:bg-muted text-sm text-foreground transition-colors [&.active]:bg-muted [&.active]:font-medium"
|
|
28
28
|
>
|
|
29
29
|
<StickyNote size={16} className="shrink-0" />
|
|
30
30
|
{!collapsed && <span>{t('notes.title')}</span>}
|
|
31
31
|
</Link>
|
|
32
32
|
<Link
|
|
33
|
-
to="/
|
|
33
|
+
to="/profile"
|
|
34
34
|
className="flex items-center gap-2 px-2 py-2 rounded hover:bg-muted text-sm text-foreground transition-colors [&.active]:bg-muted [&.active]:font-medium"
|
|
35
35
|
>
|
|
36
36
|
<User size={16} className="shrink-0" />
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SyntheticEvent, useEffect, useState } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { Button } from '../ui/button';
|
|
4
4
|
import { Input } from '../ui/input';
|
|
5
5
|
import { Label } from '../ui/label';
|
|
6
|
-
import type { Note } from '
|
|
6
|
+
import type { Note } from '@/queries/notes';
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
9
|
open: boolean;
|
|
@@ -27,7 +27,7 @@ export function NoteDialog({ open, initial, saving, onClose, onSubmit }: Props)
|
|
|
27
27
|
|
|
28
28
|
if (!open) return null;
|
|
29
29
|
|
|
30
|
-
function handleSubmit(e:
|
|
30
|
+
function handleSubmit(e: SyntheticEvent<HTMLFormElement>) {
|
|
31
31
|
e.preventDefault();
|
|
32
32
|
onSubmit({ title, body });
|
|
33
33
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { Slot } from '@radix-ui/react-slot';
|
|
3
3
|
import { cva, type VariantProps } from 'class-variance-authority';
|
|
4
|
-
import { cn } from '
|
|
4
|
+
import { cn } from '@/lib/utils';
|
|
5
5
|
|
|
6
6
|
const buttonVariants = cva(
|
|
7
7
|
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// vendored design-system primitives that always travel together are kept
|
|
5
5
|
// in a single file matching the upstream shadcn source structure.
|
|
6
6
|
import * as React from 'react';
|
|
7
|
-
import { cn } from '
|
|
7
|
+
import { cn } from '@/lib/utils';
|
|
8
8
|
|
|
9
9
|
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
10
10
|
({ className, ...props }, ref) => (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import * as LabelPrimitive from '@radix-ui/react-label';
|
|
3
3
|
import { cva, type VariantProps } from 'class-variance-authority';
|
|
4
|
-
import { cn } from '
|
|
4
|
+
import { cn } from '@/lib/utils';
|
|
5
5
|
|
|
6
6
|
const labelVariants = cva(
|
|
7
7
|
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import { createFileRoute, Link } from '@tanstack/react-router';
|
|
2
2
|
import { useAuthStore } from '@icore/template-shared';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
CardContent,
|
|
6
|
-
CardDescription,
|
|
7
|
-
CardHeader,
|
|
8
|
-
CardTitle,
|
|
9
|
-
} from '../../components/ui/card';
|
|
10
|
-
import { PageLayout } from '../../components/PageLayout';
|
|
3
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
4
|
+
import { PageLayout } from '@/components/PageLayout';
|
|
11
5
|
|
|
12
6
|
function DashboardHome() {
|
|
13
7
|
const user = useAuthStore((s) => s.user);
|
|
@@ -19,7 +13,7 @@ function DashboardHome() {
|
|
|
19
13
|
<CardDescription>Edit this page in src/routes/_dashboard/dashboard.tsx</CardDescription>
|
|
20
14
|
</CardHeader>
|
|
21
15
|
<CardContent>
|
|
22
|
-
<Link to="/
|
|
16
|
+
<Link to="/profile" className="underline">
|
|
23
17
|
Go to profile →
|
|
24
18
|
</Link>
|
|
25
19
|
</CardContent>
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { createFileRoute } from '@tanstack/react-router';
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import { PageLayout } from '
|
|
5
|
-
import { Button } from '
|
|
6
|
-
import { NotesTable } from '
|
|
7
|
-
import { NoteDialog } from '
|
|
8
|
-
import { DeleteNoteConfirm } from '
|
|
4
|
+
import { PageLayout } from '@/components/PageLayout';
|
|
5
|
+
import { Button } from '@/components/ui/button';
|
|
6
|
+
import { NotesTable } from '@/components/notes/NotesTable';
|
|
7
|
+
import { NoteDialog } from '@/components/notes/NoteDialog';
|
|
8
|
+
import { DeleteNoteConfirm } from '@/components/notes/DeleteNoteConfirm';
|
|
9
9
|
import {
|
|
10
10
|
useCreateNote,
|
|
11
11
|
useDeleteNote,
|
|
12
12
|
useNotesList,
|
|
13
13
|
useUpdateNote,
|
|
14
14
|
type Note,
|
|
15
|
-
} from '
|
|
15
|
+
} from '@/queries/notes';
|
|
16
16
|
|
|
17
17
|
const PAGE_SIZE = 20;
|
|
18
18
|
|