robodev 0.47.3 → 0.49.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/package.json +3 -3
- package/src/dev-inspect.test.ts +15 -0
- package/src/dev-inspect.ts +10 -3
- package/src/dev-server.ts +6 -0
- package/src/emit-starter.test.ts +6 -3
- package/templates/auth-chat/ROBODEV.md +7 -1
- package/templates/auth-chat/package.json +2 -2
- package/templates/backend/ROBODEV.md +7 -1
- package/templates/backend/package.json +2 -2
- package/templates/catalog.json +5 -0
- package/templates/empty/ROBODEV.md +7 -1
- package/templates/empty/package.json +2 -2
- package/templates/marketplace/ROBODEV.md +7 -1
- package/templates/marketplace/package.json +2 -2
- package/templates/povio/.config/local.spa.template.yml +27 -0
- package/templates/povio/.oxlint-base.json +29 -0
- package/templates/povio/.oxlintrc.json +78 -0
- package/templates/povio/.rulesync/rules/frontend-api-boundary.md +36 -0
- package/templates/povio/.rulesync/rules/frontend-component-structure.md +40 -0
- package/templates/povio/.rulesync/rules/frontend-forms.md +27 -0
- package/templates/povio/.rulesync/rules/frontend-notifications.md +24 -0
- package/templates/povio/.rulesync/rules/frontend-povio-components.md +42 -0
- package/templates/povio/.rulesync/rules/frontend-povio-ui.md +45 -0
- package/templates/povio/.rulesync/rules/frontend-query-autocomplete.md +24 -0
- package/templates/povio/.rulesync/rules/frontend-tables-and-lists.md +26 -0
- package/templates/povio/.rulesync/rules/frontend-translations.md +25 -0
- package/templates/povio/.rulesync/rules/project-overview.md +32 -0
- package/templates/povio/.rulesync/rules/robodev-api.md +30 -0
- package/templates/povio/.rulesync/rules/robodev-auth.md +40 -0
- package/templates/povio/.rulesync/rules/robodev-database.md +22 -0
- package/templates/povio/.rulesync/rules/role-based-app-structure.md +36 -0
- package/templates/povio/.rulesync/skills/media-feature/SKILL.md +21 -0
- package/templates/povio/.rulesync/skills/povio-ui-styling/SKILL.md +211 -0
- package/templates/povio/.rulesync/skills/povio-ui-styling/agents/openai.yaml +4 -0
- package/templates/povio/.rulesync/skills/robodev-api-route/SKILL.md +13 -0
- package/templates/povio/.rulesync/skills/robodev-table/SKILL.md +12 -0
- package/templates/povio/README.md +40 -0
- package/templates/povio/ROBODEV.md +84 -0
- package/templates/povio/apps/fe/index.html +24 -0
- package/templates/povio/apps/fe/openapi-codegen.config.ts +72 -0
- package/templates/povio/apps/fe/package.json +71 -0
- package/templates/povio/apps/fe/public/apple-touch-icon.png +0 -0
- package/templates/povio/apps/fe/public/favicon-96x96.png +0 -0
- package/templates/povio/apps/fe/public/favicon.ico +0 -0
- package/templates/povio/apps/fe/public/favicon.svg +3 -0
- package/templates/povio/apps/fe/public/site.webmanifest +21 -0
- package/templates/povio/apps/fe/public/web-app-manifest-192x192.png +0 -0
- package/templates/povio/apps/fe/public/web-app-manifest-512x512.png +0 -0
- package/templates/povio/apps/fe/src/assets/locales/en/translation.json +166 -0
- package/templates/povio/apps/fe/src/clients/app-rest-client.ts +15 -0
- package/templates/povio/apps/fe/src/clients/auth-token-store.ts +101 -0
- package/templates/povio/apps/fe/src/clients/rest/app-error-handler.ts +31 -0
- package/templates/povio/apps/fe/src/clients/rest/interceptors/authorization-header.interceptor.ts +15 -0
- package/templates/povio/apps/fe/src/clients/rest/interceptors/refresh-token.interceptor.ts +45 -0
- package/templates/povio/apps/fe/src/clients/rest/interceptors/response.interceptor.ts +17 -0
- package/templates/povio/apps/fe/src/components/404.tsx +18 -0
- package/templates/povio/apps/fe/src/components/features/auth/AuthBrandPanel.tsx +60 -0
- package/templates/povio/apps/fe/src/components/features/auth/AuthLayout.tsx +23 -0
- package/templates/povio/apps/fe/src/components/features/auth/LoginPage.tsx +111 -0
- package/templates/povio/apps/fe/src/components/features/auth/RegisterPage.tsx +143 -0
- package/templates/povio/apps/fe/src/components/features/home/HomePage.tsx +21 -0
- package/templates/povio/apps/fe/src/components/features/profile/ProfilePage.tsx +96 -0
- package/templates/povio/apps/fe/src/components/layout/AppLayout.tsx +19 -0
- package/templates/povio/apps/fe/src/components/layout/app-header/AppHeader.tsx +91 -0
- package/templates/povio/apps/fe/src/components/layout/app-header/MobileNavigation.tsx +51 -0
- package/templates/povio/apps/fe/src/components/layout/app-header/NavLink.tsx +29 -0
- package/templates/povio/apps/fe/src/components/shared/branding/BrandLogo.tsx +13 -0
- package/templates/povio/apps/fe/src/components/shared/error/ErrorFallback.tsx +43 -0
- package/templates/povio/apps/fe/src/components/shared/error/ErrorText.tsx +20 -0
- package/templates/povio/apps/fe/src/components/shared/error/NotFound.tsx +36 -0
- package/templates/povio/apps/fe/src/components/shared/forms/RequiredLabel.tsx +21 -0
- package/templates/povio/apps/fe/src/components/shared/forms/RowInputWrapper.tsx +50 -0
- package/templates/povio/apps/fe/src/components/shared/head/AppHead.tsx +32 -0
- package/templates/povio/apps/fe/src/components/shared/head/DefaultAppHead.tsx +34 -0
- package/templates/povio/apps/fe/src/components/shared/layout/LoadingState.tsx +9 -0
- package/templates/povio/apps/fe/src/components/shared/layout/ThinPageWrapper.tsx +5 -0
- package/templates/povio/apps/fe/src/components/shared/page/PageHeader.tsx +69 -0
- package/templates/povio/apps/fe/src/components/shared/ui/Card.tsx +60 -0
- package/templates/povio/apps/fe/src/components/shared/ui/GoogleLoginButton.tsx +19 -0
- package/templates/povio/apps/fe/src/components/shared/ui/RequiredLabel.tsx +22 -0
- package/templates/povio/apps/fe/src/components/shared/ui/TableActions.tsx +22 -0
- package/templates/povio/apps/fe/src/config/app.config.ts +23 -0
- package/templates/povio/apps/fe/src/config/i18n.ts +37 -0
- package/templates/povio/apps/fe/src/config/inits/logger.ts +7 -0
- package/templates/povio/apps/fe/src/config/jwt.config.ts +2 -0
- package/templates/povio/apps/fe/src/config/query.config.ts +20 -0
- package/templates/povio/apps/fe/src/hooks/useAuth.ts +5 -0
- package/templates/povio/apps/fe/src/main.tsx +52 -0
- package/templates/povio/apps/fe/src/pages/(guest)/login.tsx +23 -0
- package/templates/povio/apps/fe/src/pages/(guest)/register.tsx +23 -0
- package/templates/povio/apps/fe/src/pages/(guest)/route.tsx +18 -0
- package/templates/povio/apps/fe/src/pages/(private)/index.tsx +23 -0
- package/templates/povio/apps/fe/src/pages/(private)/profile.tsx +23 -0
- package/templates/povio/apps/fe/src/pages/(private)/route.tsx +18 -0
- package/templates/povio/apps/fe/src/pages/(public)/auth.tsx +37 -0
- package/templates/povio/apps/fe/src/pages/(public)/route.tsx +9 -0
- package/templates/povio/apps/fe/src/pages/__root.tsx +133 -0
- package/templates/povio/apps/fe/src/providers/AppErrorBoundary.tsx +44 -0
- package/templates/povio/apps/fe/src/providers/OpenApiRuntimeProvider.tsx +31 -0
- package/templates/povio/apps/fe/src/providers/index.tsx +44 -0
- package/templates/povio/apps/fe/src/providers/jwt.provider.tsx +95 -0
- package/templates/povio/apps/fe/src/routeTree.gen.ts +225 -0
- package/templates/povio/apps/fe/src/styles/base.css +84 -0
- package/templates/povio/apps/fe/src/styles/fonts/fonts.tsx +10 -0
- package/templates/povio/apps/fe/src/styles/globals.css +26 -0
- package/templates/povio/apps/fe/src/styles/overrides/defaults/button.override.ts +536 -0
- package/templates/povio/apps/fe/src/styles/overrides/defaults/checkbox.override.ts +71 -0
- package/templates/povio/apps/fe/src/styles/overrides/defaults/input.override.ts +252 -0
- package/templates/povio/apps/fe/src/styles/overrides/defaults/label.override.ts +91 -0
- package/templates/povio/apps/fe/src/styles/overrides/defaults/modal.override.ts +62 -0
- package/templates/povio/apps/fe/src/styles/overrides/defaults/radio.override.ts +46 -0
- package/templates/povio/apps/fe/src/styles/overrides/defaults/table.override.ts +104 -0
- package/templates/povio/apps/fe/src/styles/overrides/defaults/tag.override.ts +66 -0
- package/templates/povio/apps/fe/src/styles/overrides/defaults/typography.override.ts +115 -0
- package/templates/povio/apps/fe/src/styles/overrides/outline.clsx.ts +10 -0
- package/templates/povio/apps/fe/src/styles/overrides/uiOverrides.override.ts +60 -0
- package/templates/povio/apps/fe/src/styles/theme.css +2177 -0
- package/templates/povio/apps/fe/src/types/i18next.d.ts +12 -0
- package/templates/povio/apps/fe/src/types/table.d.ts +17 -0
- package/templates/povio/apps/fe/src/types/ui.d.ts +26 -0
- package/templates/povio/apps/fe/src/types/vite-env.d.ts +13 -0
- package/templates/povio/apps/fe/src/utils/date.utils.ts +17 -0
- package/templates/povio/apps/fe/src/utils/image-fallback.ts +9 -0
- package/templates/povio/apps/fe/src/utils/number.utils.ts +12 -0
- package/templates/povio/apps/fe/src/utils/string.utils.ts +5 -0
- package/templates/povio/apps/fe/src/vite-env.d.ts +1 -0
- package/templates/povio/apps/fe/tsconfig.app.json +32 -0
- package/templates/povio/apps/fe/tsconfig.json +9 -0
- package/templates/povio/apps/fe/tsconfig.node.json +31 -0
- package/templates/povio/apps/fe/vite.config.ts +150 -0
- package/templates/povio/openapi.json +911 -0
- package/templates/povio/oxfmt.config.js +23 -0
- package/templates/povio/package.json +19 -0
- package/templates/povio/robodev/api/_lib.ts +99 -0
- package/templates/povio/robodev/api/health.ts +7 -0
- package/templates/povio/robodev/database.ts +28 -0
- package/templates/povio/robodev/package.json +4 -0
- package/templates/povio/rulesync.jsonc +18 -0
- package/templates/povio/tsconfig.json +11 -0
- package/templates/shadcn/.rulesync/rules/frontend-api-boundary.md +29 -0
- package/templates/shadcn/.rulesync/rules/frontend-component-structure.md +19 -0
- package/templates/shadcn/.rulesync/rules/frontend-hosted-ui.md +38 -0
- package/templates/shadcn/.rulesync/rules/project-overview.md +29 -0
- package/templates/shadcn/.rulesync/rules/robodev-api.md +22 -0
- package/templates/shadcn/.rulesync/rules/robodev-auth.md +47 -0
- package/templates/shadcn/.rulesync/rules/robodev-database.md +21 -0
- package/templates/shadcn/.rulesync/skills/robodev-crud-page/SKILL.md +181 -0
- package/templates/shadcn/README.md +9 -4
- package/templates/shadcn/ROBODEV.md +14 -6
- package/templates/shadcn/package.json +4 -6
- package/templates/shadcn/rulesync.jsonc +18 -0
- package/templates/shadcn/src/App.tsx +12 -3
- package/templates/shadcn/src/app.css +359 -0
- package/templates/shadcn/src/components/auth/AuthCard.tsx +29 -0
- package/templates/shadcn/src/components/ui/badge.tsx +17 -0
- package/templates/shadcn/src/components/ui/card.tsx +28 -0
- package/templates/shadcn/src/components/ui/dialog.tsx +38 -0
- package/templates/shadcn/src/components/ui/empty-state.tsx +17 -0
- package/templates/shadcn/src/components/ui/input.tsx +8 -0
- package/templates/shadcn/src/components/ui/label.tsx +8 -0
- package/templates/shadcn/src/components/ui/skeleton.tsx +6 -0
- package/templates/shadcn/src/components/ui/spinner.tsx +3 -0
- package/templates/shadcn/src/components/ui/table.tsx +30 -0
- package/templates/shadcn/src/components/ui/textarea.tsx +8 -0
- package/templates/shadcn/src/lib/auth.tsx +72 -0
- package/templates/shadcn/src/lib/client.ts +8 -0
- package/templates/shadcn/src/pages/Home.tsx +15 -0
- package/templates/shadcn/src/pages/Login.tsx +70 -0
- package/templates/shadcn/src/pages/NotFound.tsx +14 -0
- package/templates/shadcn/src/pages/Register.tsx +82 -0
- package/templates/space/ROBODEV.md +7 -1
- package/templates/space/package.json +2 -2
- package/templates/shadcn/components.json +0 -21
- package/templates/shadcn/postcss.config.js +0 -6
- package/templates/shadcn/tailwind.config.ts +0 -42
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { HTMLAttributes, TableHTMLAttributes } from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
export function Table({ className, ...props }: TableHTMLAttributes<HTMLTableElement>) {
|
|
5
|
+
return (
|
|
6
|
+
<div className="table-wrapper">
|
|
7
|
+
<table className={cn("table", className)} {...props} />
|
|
8
|
+
</div>
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function TableHeader(props: HTMLAttributes<HTMLTableSectionElement>) {
|
|
13
|
+
return <thead {...props} />;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function TableBody(props: HTMLAttributes<HTMLTableSectionElement>) {
|
|
17
|
+
return <tbody {...props} />;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function TableRow(props: HTMLAttributes<HTMLTableRowElement>) {
|
|
21
|
+
return <tr {...props} />;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function TableHead(props: HTMLAttributes<HTMLTableCellElement>) {
|
|
25
|
+
return <th {...props} />;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function TableCell(props: HTMLAttributes<HTMLTableCellElement>) {
|
|
29
|
+
return <td {...props} />;
|
|
30
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TextareaHTMLAttributes } from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
export type TextareaProps = TextareaHTMLAttributes<HTMLTextAreaElement>;
|
|
5
|
+
|
|
6
|
+
export function Textarea({ className, ...props }: TextareaProps) {
|
|
7
|
+
return <textarea className={cn("input", className)} {...props} />;
|
|
8
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
useCallback,
|
|
4
|
+
useContext,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useState,
|
|
8
|
+
type ReactNode,
|
|
9
|
+
} from "react";
|
|
10
|
+
import { Navigate, useLocation } from "react-router-dom";
|
|
11
|
+
import type { AuthUser } from "@robodev-ai/client";
|
|
12
|
+
import { client } from "@/lib/client";
|
|
13
|
+
import { Spinner } from "@/components/ui/spinner";
|
|
14
|
+
|
|
15
|
+
type AuthContextValue = {
|
|
16
|
+
user: AuthUser | null;
|
|
17
|
+
loading: boolean;
|
|
18
|
+
refresh: () => Promise<void>;
|
|
19
|
+
signOut: () => Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const AuthContext = createContext<AuthContextValue | null>(null);
|
|
23
|
+
|
|
24
|
+
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
25
|
+
const [user, setUser] = useState<AuthUser | null>(null);
|
|
26
|
+
const [loading, setLoading] = useState(true);
|
|
27
|
+
|
|
28
|
+
const refresh = useCallback(async () => {
|
|
29
|
+
const result = await client.auth.getUser();
|
|
30
|
+
setUser(result.user);
|
|
31
|
+
setLoading(false);
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
const signOut = useCallback(async () => {
|
|
35
|
+
await client.auth.signOut();
|
|
36
|
+
setUser(null);
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
void refresh();
|
|
41
|
+
}, [refresh]);
|
|
42
|
+
|
|
43
|
+
const value = useMemo(
|
|
44
|
+
() => ({ user, loading, refresh, signOut }),
|
|
45
|
+
[user, loading, refresh, signOut],
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function useAuth(): AuthContextValue {
|
|
52
|
+
const context = useContext(AuthContext);
|
|
53
|
+
if (!context) throw new Error("useAuth must be used inside <AuthProvider>");
|
|
54
|
+
return context;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Wrap protected routes: <Route element={<AuthGuard><Page /></AuthGuard>} /> */
|
|
58
|
+
export function AuthGuard({ children }: { children: ReactNode }) {
|
|
59
|
+
const { user, loading } = useAuth();
|
|
60
|
+
const location = useLocation();
|
|
61
|
+
if (loading) {
|
|
62
|
+
return (
|
|
63
|
+
<div className="auth-page">
|
|
64
|
+
<Spinner />
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
if (!user) {
|
|
69
|
+
return <Navigate to="/login" replace state={{ from: location.pathname + location.search }} />;
|
|
70
|
+
}
|
|
71
|
+
return <>{children}</>;
|
|
72
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createClient } from "@robodev-ai/client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Same-origin Robodev client. Works on the hosted app URL and through the
|
|
5
|
+
* local Vite proxy. Adds the Authorization header and refreshes tokens on 401.
|
|
6
|
+
* Call app APIs with client.get/post/put/patch/del("/api/...").
|
|
7
|
+
*/
|
|
8
|
+
export const client = createClient({ url: "" });
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
|
+
import { Link } from "react-router-dom";
|
|
2
3
|
import { getHealth } from "@/api/generated/endpoints";
|
|
4
|
+
import { useAuth } from "@/lib/auth";
|
|
3
5
|
import { Button } from "@/components/ui/button";
|
|
4
6
|
|
|
5
7
|
export function Home() {
|
|
8
|
+
const { user, loading, signOut } = useAuth();
|
|
6
9
|
const [status, setStatus] = useState("Checking health…");
|
|
7
10
|
|
|
8
11
|
async function loadHealth() {
|
|
@@ -26,6 +29,18 @@ export function Home() {
|
|
|
26
29
|
<main>
|
|
27
30
|
<h1>Welcome</h1>
|
|
28
31
|
<p>Your app is ready.</p>
|
|
32
|
+
{loading ? null : user ? (
|
|
33
|
+
<p className="muted">
|
|
34
|
+
Signed in as {user.email}.{" "}
|
|
35
|
+
<Button variant="link" size="sm" onClick={() => void signOut()}>
|
|
36
|
+
Sign out
|
|
37
|
+
</Button>
|
|
38
|
+
</p>
|
|
39
|
+
) : (
|
|
40
|
+
<p className="muted">
|
|
41
|
+
<Link to="/login">Sign in</Link> or <Link to="/register">create an account</Link>.
|
|
42
|
+
</p>
|
|
43
|
+
)}
|
|
29
44
|
<Button type="button" onClick={() => void loadHealth()}>
|
|
30
45
|
Check health
|
|
31
46
|
</Button>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { useState, type FormEvent } from "react";
|
|
2
|
+
import { Link, useLocation, useNavigate } from "react-router-dom";
|
|
3
|
+
import { client } from "@/lib/client";
|
|
4
|
+
import { useAuth } from "@/lib/auth";
|
|
5
|
+
import { AuthCard } from "@/components/auth/AuthCard";
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import { Input } from "@/components/ui/input";
|
|
8
|
+
import { Label } from "@/components/ui/label";
|
|
9
|
+
|
|
10
|
+
export function Login() {
|
|
11
|
+
const navigate = useNavigate();
|
|
12
|
+
const location = useLocation();
|
|
13
|
+
const { refresh } = useAuth();
|
|
14
|
+
const [email, setEmail] = useState("");
|
|
15
|
+
const [password, setPassword] = useState("");
|
|
16
|
+
const [error, setError] = useState<string | null>(null);
|
|
17
|
+
const [submitting, setSubmitting] = useState(false);
|
|
18
|
+
|
|
19
|
+
async function onSubmit(event: FormEvent) {
|
|
20
|
+
event.preventDefault();
|
|
21
|
+
setError(null);
|
|
22
|
+
setSubmitting(true);
|
|
23
|
+
try {
|
|
24
|
+
await client.auth.signIn.email({ email, password });
|
|
25
|
+
await refresh();
|
|
26
|
+
const from = (location.state as { from?: string } | null)?.from;
|
|
27
|
+
navigate(from ?? "/", { replace: true });
|
|
28
|
+
} catch (cause) {
|
|
29
|
+
setError(cause instanceof Error ? cause.message : "Sign in failed");
|
|
30
|
+
} finally {
|
|
31
|
+
setSubmitting(false);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<AuthCard title="Sign in" description="Enter your email and password.">
|
|
37
|
+
<form className="stack" onSubmit={(event) => void onSubmit(event)}>
|
|
38
|
+
<div className="field">
|
|
39
|
+
<Label htmlFor="email">Email</Label>
|
|
40
|
+
<Input
|
|
41
|
+
id="email"
|
|
42
|
+
type="email"
|
|
43
|
+
autoComplete="email"
|
|
44
|
+
required
|
|
45
|
+
value={email}
|
|
46
|
+
onChange={(event) => setEmail(event.target.value)}
|
|
47
|
+
/>
|
|
48
|
+
</div>
|
|
49
|
+
<div className="field">
|
|
50
|
+
<Label htmlFor="password">Password</Label>
|
|
51
|
+
<Input
|
|
52
|
+
id="password"
|
|
53
|
+
type="password"
|
|
54
|
+
autoComplete="current-password"
|
|
55
|
+
required
|
|
56
|
+
value={password}
|
|
57
|
+
onChange={(event) => setPassword(event.target.value)}
|
|
58
|
+
/>
|
|
59
|
+
{error ? <p className="field-error">{error}</p> : null}
|
|
60
|
+
</div>
|
|
61
|
+
<Button type="submit" disabled={submitting}>
|
|
62
|
+
{submitting ? "Signing in…" : "Sign in"}
|
|
63
|
+
</Button>
|
|
64
|
+
<p className="muted">
|
|
65
|
+
No account? <Link to="/register" state={location.state}>Create one</Link>
|
|
66
|
+
</p>
|
|
67
|
+
</form>
|
|
68
|
+
</AuthCard>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Link } from "react-router-dom";
|
|
2
|
+
import { EmptyState } from "@/components/ui/empty-state";
|
|
3
|
+
|
|
4
|
+
export function NotFound() {
|
|
5
|
+
return (
|
|
6
|
+
<div className="page">
|
|
7
|
+
<EmptyState
|
|
8
|
+
title="Page not found"
|
|
9
|
+
description="The page you are looking for does not exist."
|
|
10
|
+
action={<Link to="/">Go home</Link>}
|
|
11
|
+
/>
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useState, type FormEvent } from "react";
|
|
2
|
+
import { Link, useLocation, useNavigate } from "react-router-dom";
|
|
3
|
+
import { client } from "@/lib/client";
|
|
4
|
+
import { useAuth } from "@/lib/auth";
|
|
5
|
+
import { AuthCard } from "@/components/auth/AuthCard";
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import { Input } from "@/components/ui/input";
|
|
8
|
+
import { Label } from "@/components/ui/label";
|
|
9
|
+
|
|
10
|
+
export function Register() {
|
|
11
|
+
const navigate = useNavigate();
|
|
12
|
+
const location = useLocation();
|
|
13
|
+
const { refresh } = useAuth();
|
|
14
|
+
const [name, setName] = useState("");
|
|
15
|
+
const [email, setEmail] = useState("");
|
|
16
|
+
const [password, setPassword] = useState("");
|
|
17
|
+
const [error, setError] = useState<string | null>(null);
|
|
18
|
+
const [submitting, setSubmitting] = useState(false);
|
|
19
|
+
|
|
20
|
+
async function onSubmit(event: FormEvent) {
|
|
21
|
+
event.preventDefault();
|
|
22
|
+
setError(null);
|
|
23
|
+
setSubmitting(true);
|
|
24
|
+
try {
|
|
25
|
+
await client.auth.signUp({ name, email, password });
|
|
26
|
+
await refresh();
|
|
27
|
+
const from = (location.state as { from?: string } | null)?.from;
|
|
28
|
+
navigate(from ?? "/", { replace: true });
|
|
29
|
+
} catch (cause) {
|
|
30
|
+
setError(cause instanceof Error ? cause.message : "Registration failed");
|
|
31
|
+
} finally {
|
|
32
|
+
setSubmitting(false);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<AuthCard title="Create account" description="Passwords need at least 12 characters.">
|
|
38
|
+
<form className="stack" onSubmit={(event) => void onSubmit(event)}>
|
|
39
|
+
<div className="field">
|
|
40
|
+
<Label htmlFor="name">Name</Label>
|
|
41
|
+
<Input
|
|
42
|
+
id="name"
|
|
43
|
+
autoComplete="name"
|
|
44
|
+
required
|
|
45
|
+
value={name}
|
|
46
|
+
onChange={(event) => setName(event.target.value)}
|
|
47
|
+
/>
|
|
48
|
+
</div>
|
|
49
|
+
<div className="field">
|
|
50
|
+
<Label htmlFor="email">Email</Label>
|
|
51
|
+
<Input
|
|
52
|
+
id="email"
|
|
53
|
+
type="email"
|
|
54
|
+
autoComplete="email"
|
|
55
|
+
required
|
|
56
|
+
value={email}
|
|
57
|
+
onChange={(event) => setEmail(event.target.value)}
|
|
58
|
+
/>
|
|
59
|
+
</div>
|
|
60
|
+
<div className="field">
|
|
61
|
+
<Label htmlFor="password">Password</Label>
|
|
62
|
+
<Input
|
|
63
|
+
id="password"
|
|
64
|
+
type="password"
|
|
65
|
+
autoComplete="new-password"
|
|
66
|
+
required
|
|
67
|
+
minLength={12}
|
|
68
|
+
value={password}
|
|
69
|
+
onChange={(event) => setPassword(event.target.value)}
|
|
70
|
+
/>
|
|
71
|
+
{error ? <p className="field-error">{error}</p> : null}
|
|
72
|
+
</div>
|
|
73
|
+
<Button type="submit" disabled={submitting}>
|
|
74
|
+
{submitting ? "Creating account…" : "Create account"}
|
|
75
|
+
</Button>
|
|
76
|
+
<p className="muted">
|
|
77
|
+
Already have an account? <Link to="/login" state={location.state}>Sign in</Link>
|
|
78
|
+
</p>
|
|
79
|
+
</form>
|
|
80
|
+
</AuthCard>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -22,6 +22,12 @@ export const get = defineApi({
|
|
|
22
22
|
});
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
Method exports: one export per method — `get` / `post` / `put` / `patch` / `delete`. `delete` is a reserved word — write `export const del = ...` or `export { remove as delete }`; both serve DELETE.
|
|
26
|
+
|
|
27
|
+
`ctx.user.id` is a string like `usr_abc123`, not a UUID. Type user id columns as `text`, never `uuid`.
|
|
28
|
+
|
|
29
|
+
Throw an Error with an integer `statusCode` (400-599) to answer with that status: `throw Object.assign(new Error("Not found"), { statusCode: 404 })`. Any other throw is a generic 500.
|
|
30
|
+
|
|
25
31
|
## Hard rules
|
|
26
32
|
|
|
27
33
|
- Do not run npm, pnpm, or yarn install.
|
|
@@ -34,7 +40,7 @@ export const get = defineApi({
|
|
|
34
40
|
|
|
35
41
|
## Auth and product data
|
|
36
42
|
|
|
37
|
-
Persist primary product data with `defineApi` and `robodev-publish be`. Do not use localStorage as the product store unless the user asked for a public, static, or no-account page. Auth is on by default: add login and register and protect routes unless the product is public or unauthenticated. Use `auth: "required"` and `ctx.user.id`. Call reserved `/api/user/auth/*` — do not implement `api/user/**`. Tiny: reuse seed login/register and AuthGuard.
|
|
43
|
+
Persist primary product data with `defineApi` and `robodev-publish be`. Do not use localStorage as the product store unless the user asked for a public, static, or no-account page. Auth is on by default: add login and register and protect routes unless the product is public or unauthenticated. Use `auth: "required"` and `ctx.user.id`. Call reserved `/api/user/auth/*` — do not implement `api/user/**`. Tiny and Shadcn: reuse seed login/register and AuthGuard. Empty and legacy: create login/register with `@robodev-ai/client` tokens. A missing `.rulesync` skill is not a reason to skip APIs. Do not require `robodev-auth set` every run.
|
|
38
44
|
|
|
39
45
|
## Backend
|
|
40
46
|
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
"rules:gen": "rulesync generate"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@robodev-ai/sdk": "^0.
|
|
12
|
+
"@robodev-ai/sdk": "^0.15.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"robodev": "^0.
|
|
15
|
+
"robodev": "^0.49.0",
|
|
16
16
|
"rulesync": "^8.18.0",
|
|
17
17
|
"typescript": "^5.9.2"
|
|
18
18
|
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
-
"style": "new-york",
|
|
4
|
-
"rsc": false,
|
|
5
|
-
"tsx": true,
|
|
6
|
-
"tailwind": {
|
|
7
|
-
"config": "tailwind.config.ts",
|
|
8
|
-
"css": "src/app.css",
|
|
9
|
-
"baseColor": "neutral",
|
|
10
|
-
"cssVariables": true,
|
|
11
|
-
"prefix": ""
|
|
12
|
-
},
|
|
13
|
-
"aliases": {
|
|
14
|
-
"components": "@/components",
|
|
15
|
-
"utils": "@/lib/utils",
|
|
16
|
-
"ui": "@/components/ui",
|
|
17
|
-
"lib": "@/lib",
|
|
18
|
-
"hooks": "@/hooks"
|
|
19
|
-
},
|
|
20
|
-
"iconLibrary": "lucide"
|
|
21
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import type { Config } from "tailwindcss";
|
|
2
|
-
|
|
3
|
-
export default {
|
|
4
|
-
darkMode: ["class"],
|
|
5
|
-
content: ["./index.html", "./src/**/*.{ts,tsx}"],
|
|
6
|
-
theme: {
|
|
7
|
-
extend: {
|
|
8
|
-
borderRadius: {
|
|
9
|
-
lg: "var(--radius)",
|
|
10
|
-
md: "calc(var(--radius) - 2px)",
|
|
11
|
-
sm: "calc(var(--radius) - 4px)",
|
|
12
|
-
},
|
|
13
|
-
colors: {
|
|
14
|
-
background: "hsl(var(--background))",
|
|
15
|
-
foreground: "hsl(var(--foreground))",
|
|
16
|
-
primary: {
|
|
17
|
-
DEFAULT: "hsl(var(--primary))",
|
|
18
|
-
foreground: "hsl(var(--primary-foreground))",
|
|
19
|
-
},
|
|
20
|
-
secondary: {
|
|
21
|
-
DEFAULT: "hsl(var(--secondary))",
|
|
22
|
-
foreground: "hsl(var(--secondary-foreground))",
|
|
23
|
-
},
|
|
24
|
-
muted: {
|
|
25
|
-
DEFAULT: "hsl(var(--muted))",
|
|
26
|
-
foreground: "hsl(var(--muted-foreground))",
|
|
27
|
-
},
|
|
28
|
-
accent: {
|
|
29
|
-
DEFAULT: "hsl(var(--accent))",
|
|
30
|
-
foreground: "hsl(var(--accent-foreground))",
|
|
31
|
-
},
|
|
32
|
-
destructive: {
|
|
33
|
-
DEFAULT: "hsl(var(--destructive))",
|
|
34
|
-
foreground: "hsl(var(--destructive-foreground))",
|
|
35
|
-
},
|
|
36
|
-
border: "hsl(var(--border))",
|
|
37
|
-
ring: "hsl(var(--ring))",
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
plugins: [],
|
|
42
|
-
} satisfies Config;
|