create-ely 0.1.2 → 0.1.4
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 +36 -27
- package/package.json +14 -4
- package/scripts/postinstall.ts +43 -0
- package/src/constants.ts +20 -0
- package/src/git.ts +95 -0
- package/src/index.ts +123 -251
- package/src/template.ts +106 -0
- package/src/utils.ts +64 -0
- package/templates.zip +0 -0
- package/templates/monorepo/README.md +0 -75
- package/templates/monorepo/apps/backend/.cursor/mcp.json +0 -8
- package/templates/monorepo/apps/backend/.dockerignore +0 -60
- package/templates/monorepo/apps/backend/.env.example +0 -19
- package/templates/monorepo/apps/backend/.github/workflows/lint.yml +0 -31
- package/templates/monorepo/apps/backend/.github/workflows/tests.yml +0 -36
- package/templates/monorepo/apps/backend/AGENTS.md +0 -79
- package/templates/monorepo/apps/backend/CHANGELOG.md +0 -190
- package/templates/monorepo/apps/backend/CLAUDE.md +0 -149
- package/templates/monorepo/apps/backend/Dockerfile +0 -35
- package/templates/monorepo/apps/backend/LICENSE +0 -21
- package/templates/monorepo/apps/backend/README.md +0 -274
- package/templates/monorepo/apps/backend/biome.json +0 -58
- package/templates/monorepo/apps/backend/bun.lock +0 -303
- package/templates/monorepo/apps/backend/bunfig.toml +0 -4
- package/templates/monorepo/apps/backend/docker-compose.yml +0 -37
- package/templates/monorepo/apps/backend/drizzle.config.ts +0 -14
- package/templates/monorepo/apps/backend/package.json +0 -42
- package/templates/monorepo/apps/backend/src/common/config.ts +0 -29
- package/templates/monorepo/apps/backend/src/common/logger.ts +0 -18
- package/templates/monorepo/apps/backend/src/db/index.ts +0 -31
- package/templates/monorepo/apps/backend/src/db/migrations/20251111132328_curly_spectrum.sql +0 -8
- package/templates/monorepo/apps/backend/src/db/migrations/meta/20251111132328_snapshot.json +0 -70
- package/templates/monorepo/apps/backend/src/db/migrations/meta/_journal.json +0 -13
- package/templates/monorepo/apps/backend/src/db/schema/users.ts +0 -39
- package/templates/monorepo/apps/backend/src/main.ts +0 -67
- package/templates/monorepo/apps/backend/src/middleware/error-handler.ts +0 -36
- package/templates/monorepo/apps/backend/src/modules/users/index.ts +0 -61
- package/templates/monorepo/apps/backend/src/modules/users/model.ts +0 -48
- package/templates/monorepo/apps/backend/src/modules/users/service.ts +0 -46
- package/templates/monorepo/apps/backend/src/tests/users.test.ts +0 -102
- package/templates/monorepo/apps/backend/src/util/graceful-shutdown.ts +0 -37
- package/templates/monorepo/apps/backend/tsconfig.json +0 -35
- package/templates/monorepo/apps/backend-biome.json.template +0 -14
- package/templates/monorepo/apps/frontend/README.md +0 -59
- package/templates/monorepo/apps/frontend/biome.json +0 -16
- package/templates/monorepo/apps/frontend/components.json +0 -21
- package/templates/monorepo/apps/frontend/index.html +0 -15
- package/templates/monorepo/apps/frontend/package.json +0 -48
- package/templates/monorepo/apps/frontend/public/favicon.ico +0 -0
- package/templates/monorepo/apps/frontend/src/assets/fonts/.gitkeep +0 -0
- package/templates/monorepo/apps/frontend/src/assets/images/.gitkeep +0 -0
- package/templates/monorepo/apps/frontend/src/features/layout/Header.tsx +0 -73
- package/templates/monorepo/apps/frontend/src/main.tsx +0 -36
- package/templates/monorepo/apps/frontend/src/routeTree.gen.ts +0 -95
- package/templates/monorepo/apps/frontend/src/routes/__root.tsx +0 -25
- package/templates/monorepo/apps/frontend/src/routes/index.tsx +0 -34
- package/templates/monorepo/apps/frontend/src/routes/users/index.tsx +0 -79
- package/templates/monorepo/apps/frontend/src/routes/users/new.tsx +0 -148
- package/templates/monorepo/apps/frontend/src/shared/api/client.ts +0 -6
- package/templates/monorepo/apps/frontend/src/shared/components/.gitkeep +0 -0
- package/templates/monorepo/apps/frontend/src/shared/constants/.gitkeep +0 -0
- package/templates/monorepo/apps/frontend/src/shared/hooks/.gitkeep +0 -0
- package/templates/monorepo/apps/frontend/src/shared/types/.gitkeep +0 -0
- package/templates/monorepo/apps/frontend/src/shared/utils/utils.ts +0 -6
- package/templates/monorepo/apps/frontend/src/styles.css +0 -138
- package/templates/monorepo/apps/frontend/src/vite-env.d.ts +0 -13
- package/templates/monorepo/apps/frontend/tsconfig.json +0 -27
- package/templates/monorepo/apps/frontend/vite.config.ts +0 -27
- package/templates/monorepo/biome.json +0 -65
- package/templates/monorepo/bun.lock +0 -1044
- package/templates/monorepo/package.json +0 -13
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { Link } from '@tanstack/react-router';
|
|
2
|
-
import { Home, Menu, Users, X } from 'lucide-react';
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
|
|
5
|
-
export default function Header() {
|
|
6
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
7
|
-
|
|
8
|
-
return (
|
|
9
|
-
<>
|
|
10
|
-
<header className="p-4 flex items-center bg-gray-800 text-white shadow-lg">
|
|
11
|
-
<button
|
|
12
|
-
onClick={() => setIsOpen(true)}
|
|
13
|
-
className="p-2 hover:bg-gray-700 rounded-lg transition-colors"
|
|
14
|
-
aria-label="Open menu"
|
|
15
|
-
type="button"
|
|
16
|
-
>
|
|
17
|
-
<Menu size={24} />
|
|
18
|
-
</button>
|
|
19
|
-
<h1 className="ml-4 text-xl font-semibold">
|
|
20
|
-
<Link to="/" className="hover:text-cyan-400 transition-colors">
|
|
21
|
-
App
|
|
22
|
-
</Link>
|
|
23
|
-
</h1>
|
|
24
|
-
</header>
|
|
25
|
-
|
|
26
|
-
<aside
|
|
27
|
-
className={`fixed top-0 left-0 h-full w-80 bg-gray-900 text-white shadow-2xl z-50 transform transition-transform duration-300 ease-in-out flex flex-col ${
|
|
28
|
-
isOpen ? 'translate-x-0' : '-translate-x-full'
|
|
29
|
-
}`}
|
|
30
|
-
>
|
|
31
|
-
<div className="flex items-center justify-between p-4 border-b border-gray-700">
|
|
32
|
-
<h2 className="text-xl font-bold">Navigation</h2>
|
|
33
|
-
<button
|
|
34
|
-
onClick={() => setIsOpen(false)}
|
|
35
|
-
className="p-2 hover:bg-gray-800 rounded-lg transition-colors"
|
|
36
|
-
aria-label="Close menu"
|
|
37
|
-
type="button"
|
|
38
|
-
>
|
|
39
|
-
<X size={24} />
|
|
40
|
-
</button>
|
|
41
|
-
</div>
|
|
42
|
-
|
|
43
|
-
<nav className="flex-1 p-4 overflow-y-auto">
|
|
44
|
-
<Link
|
|
45
|
-
to="/"
|
|
46
|
-
onClick={() => setIsOpen(false)}
|
|
47
|
-
className="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-800 transition-colors mb-2"
|
|
48
|
-
activeProps={{
|
|
49
|
-
className:
|
|
50
|
-
'flex items-center gap-3 p-3 rounded-lg bg-cyan-600 hover:bg-cyan-700 transition-colors mb-2',
|
|
51
|
-
}}
|
|
52
|
-
>
|
|
53
|
-
<Home size={20} />
|
|
54
|
-
<span className="font-medium">Home</span>
|
|
55
|
-
</Link>
|
|
56
|
-
|
|
57
|
-
<Link
|
|
58
|
-
to="/users"
|
|
59
|
-
onClick={() => setIsOpen(false)}
|
|
60
|
-
className="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-800 transition-colors mb-2"
|
|
61
|
-
activeProps={{
|
|
62
|
-
className:
|
|
63
|
-
'flex items-center gap-3 p-3 rounded-lg bg-cyan-600 hover:bg-cyan-700 transition-colors mb-2',
|
|
64
|
-
}}
|
|
65
|
-
>
|
|
66
|
-
<Users size={20} />
|
|
67
|
-
<span className="font-medium">Users</span>
|
|
68
|
-
</Link>
|
|
69
|
-
</nav>
|
|
70
|
-
</aside>
|
|
71
|
-
</>
|
|
72
|
-
);
|
|
73
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { createRouter, RouterProvider } from '@tanstack/react-router';
|
|
2
|
-
import { StrictMode } from 'react';
|
|
3
|
-
import ReactDOM from 'react-dom/client';
|
|
4
|
-
|
|
5
|
-
// Import the generated route tree
|
|
6
|
-
import { routeTree } from './routeTree.gen';
|
|
7
|
-
|
|
8
|
-
import './styles.css';
|
|
9
|
-
|
|
10
|
-
// Create a new router instance
|
|
11
|
-
const router = createRouter({
|
|
12
|
-
routeTree,
|
|
13
|
-
context: {},
|
|
14
|
-
defaultPreload: 'intent',
|
|
15
|
-
scrollRestoration: true,
|
|
16
|
-
defaultStructuralSharing: true,
|
|
17
|
-
defaultPreloadStaleTime: 0,
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
// Register the router instance for type safety
|
|
21
|
-
declare module '@tanstack/react-router' {
|
|
22
|
-
interface Register {
|
|
23
|
-
router: typeof router;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Render the app
|
|
28
|
-
const rootElement = document.getElementById('app');
|
|
29
|
-
if (rootElement && !rootElement.innerHTML) {
|
|
30
|
-
const root = ReactDOM.createRoot(rootElement);
|
|
31
|
-
root.render(
|
|
32
|
-
<StrictMode>
|
|
33
|
-
<RouterProvider router={router} />
|
|
34
|
-
</StrictMode>,
|
|
35
|
-
);
|
|
36
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
/* eslint-disable */
|
|
2
|
-
|
|
3
|
-
// @ts-nocheck
|
|
4
|
-
|
|
5
|
-
// noinspection JSUnusedGlobalSymbols
|
|
6
|
-
|
|
7
|
-
// This file was automatically generated by TanStack Router.
|
|
8
|
-
// You should NOT make any changes in this file as it will be overwritten.
|
|
9
|
-
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
|
10
|
-
|
|
11
|
-
import { Route as rootRouteImport } from './routes/__root'
|
|
12
|
-
import { Route as IndexRouteImport } from './routes/index'
|
|
13
|
-
import { Route as UsersIndexRouteImport } from './routes/users/index'
|
|
14
|
-
import { Route as UsersNewRouteImport } from './routes/users/new'
|
|
15
|
-
|
|
16
|
-
const IndexRoute = IndexRouteImport.update({
|
|
17
|
-
id: '/',
|
|
18
|
-
path: '/',
|
|
19
|
-
getParentRoute: () => rootRouteImport,
|
|
20
|
-
} as any)
|
|
21
|
-
const UsersIndexRoute = UsersIndexRouteImport.update({
|
|
22
|
-
id: '/users/',
|
|
23
|
-
path: '/users/',
|
|
24
|
-
getParentRoute: () => rootRouteImport,
|
|
25
|
-
} as any)
|
|
26
|
-
const UsersNewRoute = UsersNewRouteImport.update({
|
|
27
|
-
id: '/users/new',
|
|
28
|
-
path: '/users/new',
|
|
29
|
-
getParentRoute: () => rootRouteImport,
|
|
30
|
-
} as any)
|
|
31
|
-
|
|
32
|
-
export interface FileRoutesByFullPath {
|
|
33
|
-
'/': typeof IndexRoute
|
|
34
|
-
'/users/new': typeof UsersNewRoute
|
|
35
|
-
'/users': typeof UsersIndexRoute
|
|
36
|
-
}
|
|
37
|
-
export interface FileRoutesByTo {
|
|
38
|
-
'/': typeof IndexRoute
|
|
39
|
-
'/users/new': typeof UsersNewRoute
|
|
40
|
-
'/users': typeof UsersIndexRoute
|
|
41
|
-
}
|
|
42
|
-
export interface FileRoutesById {
|
|
43
|
-
__root__: typeof rootRouteImport
|
|
44
|
-
'/': typeof IndexRoute
|
|
45
|
-
'/users/new': typeof UsersNewRoute
|
|
46
|
-
'/users/': typeof UsersIndexRoute
|
|
47
|
-
}
|
|
48
|
-
export interface FileRouteTypes {
|
|
49
|
-
fileRoutesByFullPath: FileRoutesByFullPath
|
|
50
|
-
fullPaths: '/' | '/users/new' | '/users'
|
|
51
|
-
fileRoutesByTo: FileRoutesByTo
|
|
52
|
-
to: '/' | '/users/new' | '/users'
|
|
53
|
-
id: '__root__' | '/' | '/users/new' | '/users/'
|
|
54
|
-
fileRoutesById: FileRoutesById
|
|
55
|
-
}
|
|
56
|
-
export interface RootRouteChildren {
|
|
57
|
-
IndexRoute: typeof IndexRoute
|
|
58
|
-
UsersNewRoute: typeof UsersNewRoute
|
|
59
|
-
UsersIndexRoute: typeof UsersIndexRoute
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
declare module '@tanstack/react-router' {
|
|
63
|
-
interface FileRoutesByPath {
|
|
64
|
-
'/': {
|
|
65
|
-
id: '/'
|
|
66
|
-
path: '/'
|
|
67
|
-
fullPath: '/'
|
|
68
|
-
preLoaderRoute: typeof IndexRouteImport
|
|
69
|
-
parentRoute: typeof rootRouteImport
|
|
70
|
-
}
|
|
71
|
-
'/users/': {
|
|
72
|
-
id: '/users/'
|
|
73
|
-
path: '/users'
|
|
74
|
-
fullPath: '/users'
|
|
75
|
-
preLoaderRoute: typeof UsersIndexRouteImport
|
|
76
|
-
parentRoute: typeof rootRouteImport
|
|
77
|
-
}
|
|
78
|
-
'/users/new': {
|
|
79
|
-
id: '/users/new'
|
|
80
|
-
path: '/users/new'
|
|
81
|
-
fullPath: '/users/new'
|
|
82
|
-
preLoaderRoute: typeof UsersNewRouteImport
|
|
83
|
-
parentRoute: typeof rootRouteImport
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const rootRouteChildren: RootRouteChildren = {
|
|
89
|
-
IndexRoute: IndexRoute,
|
|
90
|
-
UsersNewRoute: UsersNewRoute,
|
|
91
|
-
UsersIndexRoute: UsersIndexRoute,
|
|
92
|
-
}
|
|
93
|
-
export const routeTree = rootRouteImport
|
|
94
|
-
._addFileChildren(rootRouteChildren)
|
|
95
|
-
._addFileTypes<FileRouteTypes>()
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { TanStackDevtools } from '@tanstack/react-devtools';
|
|
2
|
-
import { createRootRoute, Outlet } from '@tanstack/react-router';
|
|
3
|
-
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools';
|
|
4
|
-
|
|
5
|
-
import Header from '@/features/layout/Header';
|
|
6
|
-
|
|
7
|
-
export const Route = createRootRoute({
|
|
8
|
-
component: () => (
|
|
9
|
-
<>
|
|
10
|
-
<Header />
|
|
11
|
-
<Outlet />
|
|
12
|
-
<TanStackDevtools
|
|
13
|
-
config={{
|
|
14
|
-
position: 'bottom-right',
|
|
15
|
-
}}
|
|
16
|
-
plugins={[
|
|
17
|
-
{
|
|
18
|
-
name: 'Tanstack Router',
|
|
19
|
-
render: <TanStackRouterDevtoolsPanel />,
|
|
20
|
-
},
|
|
21
|
-
]}
|
|
22
|
-
/>
|
|
23
|
-
</>
|
|
24
|
-
),
|
|
25
|
-
});
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { createFileRoute } from '@tanstack/react-router';
|
|
2
|
-
|
|
3
|
-
export const Route = createFileRoute('/')({
|
|
4
|
-
component: App,
|
|
5
|
-
});
|
|
6
|
-
|
|
7
|
-
function App() {
|
|
8
|
-
return (
|
|
9
|
-
<div className="text-center">
|
|
10
|
-
<header className="min-h-screen flex flex-col items-center justify-center bg-[#282c34] text-white text-[calc(10px+2vmin)]">
|
|
11
|
-
<h1 className="text-5xl font-bold mb-4">Welcome</h1>
|
|
12
|
-
<p>
|
|
13
|
-
Edit <code>src/routes/index.tsx</code> and save to reload.
|
|
14
|
-
</p>
|
|
15
|
-
<a
|
|
16
|
-
className="text-[#61dafb] hover:underline"
|
|
17
|
-
href="https://reactjs.org"
|
|
18
|
-
target="_blank"
|
|
19
|
-
rel="noopener noreferrer"
|
|
20
|
-
>
|
|
21
|
-
Learn React
|
|
22
|
-
</a>
|
|
23
|
-
<a
|
|
24
|
-
className="text-[#61dafb] hover:underline"
|
|
25
|
-
href="https://tanstack.com"
|
|
26
|
-
target="_blank"
|
|
27
|
-
rel="noopener noreferrer"
|
|
28
|
-
>
|
|
29
|
-
Learn TanStack
|
|
30
|
-
</a>
|
|
31
|
-
</header>
|
|
32
|
-
</div>
|
|
33
|
-
);
|
|
34
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { createFileRoute, Link } from '@tanstack/react-router';
|
|
2
|
-
import { UserPlus } from 'lucide-react';
|
|
3
|
-
import { api } from '@/shared/api/client';
|
|
4
|
-
|
|
5
|
-
export const Route = createFileRoute('/users/')({
|
|
6
|
-
component: Users,
|
|
7
|
-
loader: async () => {
|
|
8
|
-
const { data, error } = await api.users.get({
|
|
9
|
-
query: {
|
|
10
|
-
limit: 100,
|
|
11
|
-
offset: 0,
|
|
12
|
-
},
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
if (error) {
|
|
16
|
-
throw new Error(error.value as string);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return { users: data?.users ?? [], total: data?.total ?? 0 };
|
|
20
|
-
},
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
function Users() {
|
|
24
|
-
const { users, total } = Route.useLoaderData();
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<div className="min-h-screen bg-gray-50 py-8 px-4 sm:px-6 lg:px-8">
|
|
28
|
-
<div className="max-w-4xl mx-auto">
|
|
29
|
-
<div className="flex items-center justify-between mb-6">
|
|
30
|
-
<h1 className="text-3xl font-bold text-gray-900">Users</h1>
|
|
31
|
-
<Link
|
|
32
|
-
to="/users/new"
|
|
33
|
-
className="inline-flex items-center gap-2 px-4 py-2 bg-cyan-600 text-white rounded-md hover:bg-cyan-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-cyan-500 transition-colors"
|
|
34
|
-
>
|
|
35
|
-
<UserPlus size={20} />
|
|
36
|
-
Create New User
|
|
37
|
-
</Link>
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
<div className="bg-white rounded-lg shadow-md overflow-hidden">
|
|
41
|
-
{users.length === 0 ? (
|
|
42
|
-
<div className="p-8 text-center text-gray-500">
|
|
43
|
-
<p className="text-lg mb-2">No users found</p>
|
|
44
|
-
<p className="text-sm">Create your first user to get started.</p>
|
|
45
|
-
</div>
|
|
46
|
-
) : (
|
|
47
|
-
<>
|
|
48
|
-
<div className="px-6 py-4 bg-gray-50 border-b border-gray-200">
|
|
49
|
-
<p className="text-sm text-gray-600">
|
|
50
|
-
Showing {users.length} of {total} users
|
|
51
|
-
</p>
|
|
52
|
-
</div>
|
|
53
|
-
<div className="divide-y divide-gray-200">
|
|
54
|
-
{users.map((user) => (
|
|
55
|
-
<div
|
|
56
|
-
key={user.id}
|
|
57
|
-
className="px-6 py-4 hover:bg-gray-50 transition-colors"
|
|
58
|
-
>
|
|
59
|
-
<div className="flex items-center justify-between">
|
|
60
|
-
<div>
|
|
61
|
-
<h3 className="text-lg font-semibold text-gray-900">
|
|
62
|
-
{user.name} {user.surname}
|
|
63
|
-
</h3>
|
|
64
|
-
<p className="text-sm text-gray-600">{user.email}</p>
|
|
65
|
-
</div>
|
|
66
|
-
<div className="text-xs text-gray-400">
|
|
67
|
-
ID: {user.id.slice(0, 8)}...
|
|
68
|
-
</div>
|
|
69
|
-
</div>
|
|
70
|
-
</div>
|
|
71
|
-
))}
|
|
72
|
-
</div>
|
|
73
|
-
</>
|
|
74
|
-
)}
|
|
75
|
-
</div>
|
|
76
|
-
</div>
|
|
77
|
-
</div>
|
|
78
|
-
);
|
|
79
|
-
}
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
|
2
|
-
import { useState } from 'react';
|
|
3
|
-
import { api } from '@/shared/api/client';
|
|
4
|
-
|
|
5
|
-
export const Route = createFileRoute('/users/new')({
|
|
6
|
-
component: NewUser,
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
function NewUser() {
|
|
10
|
-
const navigate = useNavigate();
|
|
11
|
-
const [formData, setFormData] = useState({
|
|
12
|
-
name: '',
|
|
13
|
-
surname: '',
|
|
14
|
-
email: '',
|
|
15
|
-
});
|
|
16
|
-
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
17
|
-
const [error, setError] = useState<string | null>(null);
|
|
18
|
-
|
|
19
|
-
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
|
20
|
-
e.preventDefault();
|
|
21
|
-
setIsSubmitting(true);
|
|
22
|
-
setError(null);
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
const { data, error: apiError } = await api.users.post(formData);
|
|
26
|
-
|
|
27
|
-
if (apiError) {
|
|
28
|
-
setError(apiError.value);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (data) {
|
|
33
|
-
navigate({ to: '/users' });
|
|
34
|
-
}
|
|
35
|
-
} catch (err) {
|
|
36
|
-
setError(err instanceof Error ? err.message : 'Failed to create user');
|
|
37
|
-
} finally {
|
|
38
|
-
setIsSubmitting(false);
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
43
|
-
const { name, value } = e.target;
|
|
44
|
-
setFormData((prev) => ({
|
|
45
|
-
...prev,
|
|
46
|
-
[name]: value,
|
|
47
|
-
}));
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<div className="min-h-screen bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
|
52
|
-
<div className="max-w-md mx-auto bg-white rounded-lg shadow-md p-6">
|
|
53
|
-
<h1 className="text-2xl font-bold text-gray-900 mb-6">
|
|
54
|
-
Create New User
|
|
55
|
-
</h1>
|
|
56
|
-
|
|
57
|
-
{error && (
|
|
58
|
-
<div className="mb-4 p-3 bg-red-50 border border-red-200 rounded-md">
|
|
59
|
-
<p className="text-sm text-red-800">{error}</p>
|
|
60
|
-
</div>
|
|
61
|
-
)}
|
|
62
|
-
|
|
63
|
-
<form onSubmit={handleSubmit} className="space-y-4">
|
|
64
|
-
<div>
|
|
65
|
-
<label
|
|
66
|
-
htmlFor="name"
|
|
67
|
-
className="block text-sm font-medium text-gray-700 mb-1"
|
|
68
|
-
>
|
|
69
|
-
Name
|
|
70
|
-
</label>
|
|
71
|
-
<input
|
|
72
|
-
type="text"
|
|
73
|
-
id="name"
|
|
74
|
-
name="name"
|
|
75
|
-
value={formData.name}
|
|
76
|
-
onChange={handleChange}
|
|
77
|
-
required
|
|
78
|
-
minLength={1}
|
|
79
|
-
maxLength={255}
|
|
80
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-cyan-500 focus:border-cyan-500"
|
|
81
|
-
disabled={isSubmitting}
|
|
82
|
-
/>
|
|
83
|
-
</div>
|
|
84
|
-
|
|
85
|
-
<div>
|
|
86
|
-
<label
|
|
87
|
-
htmlFor="surname"
|
|
88
|
-
className="block text-sm font-medium text-gray-700 mb-1"
|
|
89
|
-
>
|
|
90
|
-
Surname
|
|
91
|
-
</label>
|
|
92
|
-
<input
|
|
93
|
-
type="text"
|
|
94
|
-
id="surname"
|
|
95
|
-
name="surname"
|
|
96
|
-
value={formData.surname}
|
|
97
|
-
onChange={handleChange}
|
|
98
|
-
required
|
|
99
|
-
minLength={1}
|
|
100
|
-
maxLength={255}
|
|
101
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-cyan-500 focus:border-cyan-500"
|
|
102
|
-
disabled={isSubmitting}
|
|
103
|
-
/>
|
|
104
|
-
</div>
|
|
105
|
-
|
|
106
|
-
<div>
|
|
107
|
-
<label
|
|
108
|
-
htmlFor="email"
|
|
109
|
-
className="block text-sm font-medium text-gray-700 mb-1"
|
|
110
|
-
>
|
|
111
|
-
Email
|
|
112
|
-
</label>
|
|
113
|
-
<input
|
|
114
|
-
type="email"
|
|
115
|
-
id="email"
|
|
116
|
-
name="email"
|
|
117
|
-
value={formData.email}
|
|
118
|
-
onChange={handleChange}
|
|
119
|
-
required
|
|
120
|
-
minLength={1}
|
|
121
|
-
maxLength={255}
|
|
122
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-cyan-500 focus:border-cyan-500"
|
|
123
|
-
disabled={isSubmitting}
|
|
124
|
-
/>
|
|
125
|
-
</div>
|
|
126
|
-
|
|
127
|
-
<div className="flex gap-3 pt-4">
|
|
128
|
-
<button
|
|
129
|
-
type="button"
|
|
130
|
-
onClick={() => navigate({ to: '/users' })}
|
|
131
|
-
className="flex-1 px-4 py-2 border border-gray-300 rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-cyan-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
132
|
-
disabled={isSubmitting}
|
|
133
|
-
>
|
|
134
|
-
Cancel
|
|
135
|
-
</button>
|
|
136
|
-
<button
|
|
137
|
-
type="submit"
|
|
138
|
-
className="flex-1 px-4 py-2 border border-transparent rounded-md shadow-sm text-white bg-cyan-600 hover:bg-cyan-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-cyan-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
139
|
-
disabled={isSubmitting}
|
|
140
|
-
>
|
|
141
|
-
{isSubmitting ? 'Creating...' : 'Create User'}
|
|
142
|
-
</button>
|
|
143
|
-
</div>
|
|
144
|
-
</form>
|
|
145
|
-
</div>
|
|
146
|
-
</div>
|
|
147
|
-
);
|
|
148
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
@import 'tailwindcss';
|
|
2
|
-
|
|
3
|
-
@import 'tw-animate-css';
|
|
4
|
-
|
|
5
|
-
@custom-variant dark (&:is(.dark *));
|
|
6
|
-
|
|
7
|
-
body {
|
|
8
|
-
@apply m-0;
|
|
9
|
-
font-family:
|
|
10
|
-
-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
|
|
11
|
-
'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
|
12
|
-
-webkit-font-smoothing: antialiased;
|
|
13
|
-
-moz-osx-font-smoothing: grayscale;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
code {
|
|
17
|
-
font-family:
|
|
18
|
-
source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
:root {
|
|
22
|
-
--background: oklch(1 0 0);
|
|
23
|
-
--foreground: oklch(0.141 0.005 285.823);
|
|
24
|
-
--card: oklch(1 0 0);
|
|
25
|
-
--card-foreground: oklch(0.141 0.005 285.823);
|
|
26
|
-
--popover: oklch(1 0 0);
|
|
27
|
-
--popover-foreground: oklch(0.141 0.005 285.823);
|
|
28
|
-
--primary: oklch(0.21 0.006 285.885);
|
|
29
|
-
--primary-foreground: oklch(0.985 0 0);
|
|
30
|
-
--secondary: oklch(0.967 0.001 286.375);
|
|
31
|
-
--secondary-foreground: oklch(0.21 0.006 285.885);
|
|
32
|
-
--muted: oklch(0.967 0.001 286.375);
|
|
33
|
-
--muted-foreground: oklch(0.552 0.016 285.938);
|
|
34
|
-
--accent: oklch(0.967 0.001 286.375);
|
|
35
|
-
--accent-foreground: oklch(0.21 0.006 285.885);
|
|
36
|
-
--destructive: oklch(0.577 0.245 27.325);
|
|
37
|
-
--destructive-foreground: oklch(0.577 0.245 27.325);
|
|
38
|
-
--border: oklch(0.92 0.004 286.32);
|
|
39
|
-
--input: oklch(0.92 0.004 286.32);
|
|
40
|
-
--ring: oklch(0.871 0.006 286.286);
|
|
41
|
-
--chart-1: oklch(0.646 0.222 41.116);
|
|
42
|
-
--chart-2: oklch(0.6 0.118 184.704);
|
|
43
|
-
--chart-3: oklch(0.398 0.07 227.392);
|
|
44
|
-
--chart-4: oklch(0.828 0.189 84.429);
|
|
45
|
-
--chart-5: oklch(0.769 0.188 70.08);
|
|
46
|
-
--radius: 0.625rem;
|
|
47
|
-
--sidebar: oklch(0.985 0 0);
|
|
48
|
-
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
|
49
|
-
--sidebar-primary: oklch(0.21 0.006 285.885);
|
|
50
|
-
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
51
|
-
--sidebar-accent: oklch(0.967 0.001 286.375);
|
|
52
|
-
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
|
53
|
-
--sidebar-border: oklch(0.92 0.004 286.32);
|
|
54
|
-
--sidebar-ring: oklch(0.871 0.006 286.286);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.dark {
|
|
58
|
-
--background: oklch(0.141 0.005 285.823);
|
|
59
|
-
--foreground: oklch(0.985 0 0);
|
|
60
|
-
--card: oklch(0.141 0.005 285.823);
|
|
61
|
-
--card-foreground: oklch(0.985 0 0);
|
|
62
|
-
--popover: oklch(0.141 0.005 285.823);
|
|
63
|
-
--popover-foreground: oklch(0.985 0 0);
|
|
64
|
-
--primary: oklch(0.985 0 0);
|
|
65
|
-
--primary-foreground: oklch(0.21 0.006 285.885);
|
|
66
|
-
--secondary: oklch(0.274 0.006 286.033);
|
|
67
|
-
--secondary-foreground: oklch(0.985 0 0);
|
|
68
|
-
--muted: oklch(0.274 0.006 286.033);
|
|
69
|
-
--muted-foreground: oklch(0.705 0.015 286.067);
|
|
70
|
-
--accent: oklch(0.274 0.006 286.033);
|
|
71
|
-
--accent-foreground: oklch(0.985 0 0);
|
|
72
|
-
--destructive: oklch(0.396 0.141 25.723);
|
|
73
|
-
--destructive-foreground: oklch(0.637 0.237 25.331);
|
|
74
|
-
--border: oklch(0.274 0.006 286.033);
|
|
75
|
-
--input: oklch(0.274 0.006 286.033);
|
|
76
|
-
--ring: oklch(0.442 0.017 285.786);
|
|
77
|
-
--chart-1: oklch(0.488 0.243 264.376);
|
|
78
|
-
--chart-2: oklch(0.696 0.17 162.48);
|
|
79
|
-
--chart-3: oklch(0.769 0.188 70.08);
|
|
80
|
-
--chart-4: oklch(0.627 0.265 303.9);
|
|
81
|
-
--chart-5: oklch(0.645 0.246 16.439);
|
|
82
|
-
--sidebar: oklch(0.21 0.006 285.885);
|
|
83
|
-
--sidebar-foreground: oklch(0.985 0 0);
|
|
84
|
-
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
85
|
-
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
86
|
-
--sidebar-accent: oklch(0.274 0.006 286.033);
|
|
87
|
-
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
88
|
-
--sidebar-border: oklch(0.274 0.006 286.033);
|
|
89
|
-
--sidebar-ring: oklch(0.442 0.017 285.786);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
@theme inline {
|
|
93
|
-
--color-background: var(--background);
|
|
94
|
-
--color-foreground: var(--foreground);
|
|
95
|
-
--color-card: var(--card);
|
|
96
|
-
--color-card-foreground: var(--card-foreground);
|
|
97
|
-
--color-popover: var(--popover);
|
|
98
|
-
--color-popover-foreground: var(--popover-foreground);
|
|
99
|
-
--color-primary: var(--primary);
|
|
100
|
-
--color-primary-foreground: var(--primary-foreground);
|
|
101
|
-
--color-secondary: var(--secondary);
|
|
102
|
-
--color-secondary-foreground: var(--secondary-foreground);
|
|
103
|
-
--color-muted: var(--muted);
|
|
104
|
-
--color-muted-foreground: var(--muted-foreground);
|
|
105
|
-
--color-accent: var(--accent);
|
|
106
|
-
--color-accent-foreground: var(--accent-foreground);
|
|
107
|
-
--color-destructive: var(--destructive);
|
|
108
|
-
--color-destructive-foreground: var(--destructive-foreground);
|
|
109
|
-
--color-border: var(--border);
|
|
110
|
-
--color-input: var(--input);
|
|
111
|
-
--color-ring: var(--ring);
|
|
112
|
-
--color-chart-1: var(--chart-1);
|
|
113
|
-
--color-chart-2: var(--chart-2);
|
|
114
|
-
--color-chart-3: var(--chart-3);
|
|
115
|
-
--color-chart-4: var(--chart-4);
|
|
116
|
-
--color-chart-5: var(--chart-5);
|
|
117
|
-
--radius-sm: calc(var(--radius) - 4px);
|
|
118
|
-
--radius-md: calc(var(--radius) - 2px);
|
|
119
|
-
--radius-lg: var(--radius);
|
|
120
|
-
--radius-xl: calc(var(--radius) + 4px);
|
|
121
|
-
--color-sidebar: var(--sidebar);
|
|
122
|
-
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
123
|
-
--color-sidebar-primary: var(--sidebar-primary);
|
|
124
|
-
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
125
|
-
--color-sidebar-accent: var(--sidebar-accent);
|
|
126
|
-
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
127
|
-
--color-sidebar-border: var(--sidebar-border);
|
|
128
|
-
--color-sidebar-ring: var(--sidebar-ring);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
@layer base {
|
|
132
|
-
* {
|
|
133
|
-
@apply border-border outline-ring/50;
|
|
134
|
-
}
|
|
135
|
-
body {
|
|
136
|
-
@apply bg-background text-foreground;
|
|
137
|
-
}
|
|
138
|
-
}
|