a5a79f01a7
Next.js (App Router) + React 19 + TypeScript + Tailwind v4, with shadcn-style UI primitives (Button, Input, Card, Label via cva/tailwind-merge). A typed API client is generated from the backend OpenAPI spec with openapi-typescript + openapi-fetch (npm run gen:api); the committed openapi.json/schema.d.ts are the snapshot. Views: landing, login, register, tree list + create, and tree detail with person list + create. Auth rides the same-origin HttpOnly session cookie the backend sets (Caddy proxies /api/*), so no token handling in JS. Built as a standalone container. Mobile-first; next build is clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Justin Paul <justin@jpaul.me>
786 lines
21 KiB
TypeScript
786 lines
21 KiB
TypeScript
/**
|
|
* This file was auto-generated by openapi-typescript.
|
|
* Do not make direct changes to the file.
|
|
*/
|
|
|
|
export interface paths {
|
|
"/health": {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
/** Health */
|
|
get: operations["health_health_get"];
|
|
put?: never;
|
|
post?: never;
|
|
delete?: never;
|
|
options?: never;
|
|
head?: never;
|
|
patch?: never;
|
|
trace?: never;
|
|
};
|
|
"/health/ready": {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
/** Ready */
|
|
get: operations["ready_health_ready_get"];
|
|
put?: never;
|
|
post?: never;
|
|
delete?: never;
|
|
options?: never;
|
|
head?: never;
|
|
patch?: never;
|
|
trace?: never;
|
|
};
|
|
"/api/v1/auth/register": {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
get?: never;
|
|
put?: never;
|
|
/** Register */
|
|
post: operations["register_api_v1_auth_register_post"];
|
|
delete?: never;
|
|
options?: never;
|
|
head?: never;
|
|
patch?: never;
|
|
trace?: never;
|
|
};
|
|
"/api/v1/auth/login": {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
get?: never;
|
|
put?: never;
|
|
/** Login */
|
|
post: operations["login_api_v1_auth_login_post"];
|
|
delete?: never;
|
|
options?: never;
|
|
head?: never;
|
|
patch?: never;
|
|
trace?: never;
|
|
};
|
|
"/api/v1/auth/logout": {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
get?: never;
|
|
put?: never;
|
|
/** Logout */
|
|
post: operations["logout_api_v1_auth_logout_post"];
|
|
delete?: never;
|
|
options?: never;
|
|
head?: never;
|
|
patch?: never;
|
|
trace?: never;
|
|
};
|
|
"/api/v1/auth/verify-email": {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
get?: never;
|
|
put?: never;
|
|
/** Verify Email */
|
|
post: operations["verify_email_api_v1_auth_verify_email_post"];
|
|
delete?: never;
|
|
options?: never;
|
|
head?: never;
|
|
patch?: never;
|
|
trace?: never;
|
|
};
|
|
"/api/v1/auth/request-password-reset": {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
get?: never;
|
|
put?: never;
|
|
/** Request Password Reset */
|
|
post: operations["request_password_reset_api_v1_auth_request_password_reset_post"];
|
|
delete?: never;
|
|
options?: never;
|
|
head?: never;
|
|
patch?: never;
|
|
trace?: never;
|
|
};
|
|
"/api/v1/auth/reset-password": {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
get?: never;
|
|
put?: never;
|
|
/** Reset Password */
|
|
post: operations["reset_password_api_v1_auth_reset_password_post"];
|
|
delete?: never;
|
|
options?: never;
|
|
head?: never;
|
|
patch?: never;
|
|
trace?: never;
|
|
};
|
|
"/api/v1/users/me": {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
/** Read Me */
|
|
get: operations["read_me_api_v1_users_me_get"];
|
|
put?: never;
|
|
post?: never;
|
|
delete?: never;
|
|
options?: never;
|
|
head?: never;
|
|
patch?: never;
|
|
trace?: never;
|
|
};
|
|
"/api/v1/trees": {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
/** List My Trees */
|
|
get: operations["list_my_trees_api_v1_trees_get"];
|
|
put?: never;
|
|
/** Create Tree */
|
|
post: operations["create_tree_api_v1_trees_post"];
|
|
delete?: never;
|
|
options?: never;
|
|
head?: never;
|
|
patch?: never;
|
|
trace?: never;
|
|
};
|
|
"/api/v1/trees/{tree_id}": {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
/** Get Tree */
|
|
get: operations["get_tree_api_v1_trees__tree_id__get"];
|
|
put?: never;
|
|
post?: never;
|
|
delete?: never;
|
|
options?: never;
|
|
head?: never;
|
|
patch?: never;
|
|
trace?: never;
|
|
};
|
|
"/api/v1/trees/{tree_id}/persons": {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
/** List Persons */
|
|
get: operations["list_persons_api_v1_trees__tree_id__persons_get"];
|
|
put?: never;
|
|
/** Create Person */
|
|
post: operations["create_person_api_v1_trees__tree_id__persons_post"];
|
|
delete?: never;
|
|
options?: never;
|
|
head?: never;
|
|
patch?: never;
|
|
trace?: never;
|
|
};
|
|
}
|
|
export type webhooks = Record<string, never>;
|
|
export interface components {
|
|
schemas: {
|
|
/** HTTPValidationError */
|
|
HTTPValidationError: {
|
|
/** Detail */
|
|
detail?: components["schemas"]["ValidationError"][];
|
|
};
|
|
/** LoginRequest */
|
|
LoginRequest: {
|
|
/** Email */
|
|
email: string;
|
|
/** Password */
|
|
password: string;
|
|
};
|
|
/** PasswordResetConfirm */
|
|
PasswordResetConfirm: {
|
|
/** Token */
|
|
token: string;
|
|
/** New Password */
|
|
new_password: string;
|
|
};
|
|
/** PasswordResetRequest */
|
|
PasswordResetRequest: {
|
|
/** Email */
|
|
email: string;
|
|
};
|
|
/** PersonCreate */
|
|
PersonCreate: {
|
|
/** Given */
|
|
given?: string | null;
|
|
/** Surname */
|
|
surname?: string | null;
|
|
/** Gender */
|
|
gender?: string | null;
|
|
/** Is Living */
|
|
is_living?: boolean | null;
|
|
/** @default inherit */
|
|
privacy?: components["schemas"]["PersonPrivacy"];
|
|
/** Notes */
|
|
notes?: string | null;
|
|
};
|
|
/**
|
|
* PersonPrivacy
|
|
* @description Per-person override of the tree's visibility (PRD US-041).
|
|
* @enum {string}
|
|
*/
|
|
PersonPrivacy: "inherit" | "private" | "public";
|
|
/** PersonRead */
|
|
PersonRead: {
|
|
/**
|
|
* Id
|
|
* Format: uuid
|
|
*/
|
|
id: string;
|
|
/**
|
|
* Tree Id
|
|
* Format: uuid
|
|
*/
|
|
tree_id: string;
|
|
/** Primary Name */
|
|
primary_name?: string | null;
|
|
/** Gender */
|
|
gender: string | null;
|
|
/** Is Living */
|
|
is_living: boolean | null;
|
|
privacy: components["schemas"]["PersonPrivacy"];
|
|
/**
|
|
* Created At
|
|
* Format: date-time
|
|
*/
|
|
created_at: string;
|
|
};
|
|
/** RegisterRequest */
|
|
RegisterRequest: {
|
|
/** Email */
|
|
email: string;
|
|
/** Password */
|
|
password: string;
|
|
/** Display Name */
|
|
display_name?: string | null;
|
|
};
|
|
/** SessionRead */
|
|
SessionRead: {
|
|
user: components["schemas"]["UserRead"];
|
|
/** Token */
|
|
token: string;
|
|
/**
|
|
* Expires At
|
|
* Format: date-time
|
|
*/
|
|
expires_at: string;
|
|
};
|
|
/** TokenRequest */
|
|
TokenRequest: {
|
|
/** Token */
|
|
token: string;
|
|
};
|
|
/** TreeCreate */
|
|
TreeCreate: {
|
|
/** Name */
|
|
name: string;
|
|
/** Description */
|
|
description?: string | null;
|
|
/** @default private */
|
|
visibility?: components["schemas"]["TreeVisibility"];
|
|
};
|
|
/** TreeRead */
|
|
TreeRead: {
|
|
/**
|
|
* Id
|
|
* Format: uuid
|
|
*/
|
|
id: string;
|
|
/** Name */
|
|
name: string;
|
|
/** Description */
|
|
description: string | null;
|
|
visibility: components["schemas"]["TreeVisibility"];
|
|
/**
|
|
* Owner Id
|
|
* Format: uuid
|
|
*/
|
|
owner_id: string;
|
|
/**
|
|
* Created At
|
|
* Format: date-time
|
|
*/
|
|
created_at: string;
|
|
};
|
|
/**
|
|
* TreeVisibility
|
|
* @enum {string}
|
|
*/
|
|
TreeVisibility: "public" | "unlisted" | "private";
|
|
/** UserRead */
|
|
UserRead: {
|
|
/**
|
|
* Id
|
|
* Format: uuid
|
|
*/
|
|
id: string;
|
|
/** Email */
|
|
email: string;
|
|
/** Display Name */
|
|
display_name: string | null;
|
|
/** Email Verified At */
|
|
email_verified_at: string | null;
|
|
/**
|
|
* Created At
|
|
* Format: date-time
|
|
*/
|
|
created_at: string;
|
|
};
|
|
/** ValidationError */
|
|
ValidationError: {
|
|
/** Location */
|
|
loc: (string | number)[];
|
|
/** Message */
|
|
msg: string;
|
|
/** Error Type */
|
|
type: string;
|
|
/** Input */
|
|
input?: unknown;
|
|
/** Context */
|
|
ctx?: Record<string, never>;
|
|
};
|
|
};
|
|
responses: never;
|
|
parameters: never;
|
|
requestBodies: never;
|
|
headers: never;
|
|
pathItems: never;
|
|
}
|
|
export type $defs = Record<string, never>;
|
|
export interface operations {
|
|
health_health_get: {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
requestBody?: never;
|
|
responses: {
|
|
/** @description Successful Response */
|
|
200: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content: {
|
|
"application/json": {
|
|
[key: string]: unknown;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
ready_health_ready_get: {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
requestBody?: never;
|
|
responses: {
|
|
/** @description Successful Response */
|
|
200: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content: {
|
|
"application/json": {
|
|
[key: string]: unknown;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
register_api_v1_auth_register_post: {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
requestBody: {
|
|
content: {
|
|
"application/json": components["schemas"]["RegisterRequest"];
|
|
};
|
|
};
|
|
responses: {
|
|
/** @description Successful Response */
|
|
201: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content: {
|
|
"application/json": components["schemas"]["SessionRead"];
|
|
};
|
|
};
|
|
/** @description Validation Error */
|
|
422: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content: {
|
|
"application/json": components["schemas"]["HTTPValidationError"];
|
|
};
|
|
};
|
|
};
|
|
};
|
|
login_api_v1_auth_login_post: {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
requestBody: {
|
|
content: {
|
|
"application/json": components["schemas"]["LoginRequest"];
|
|
};
|
|
};
|
|
responses: {
|
|
/** @description Successful Response */
|
|
200: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content: {
|
|
"application/json": components["schemas"]["SessionRead"];
|
|
};
|
|
};
|
|
/** @description Validation Error */
|
|
422: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content: {
|
|
"application/json": components["schemas"]["HTTPValidationError"];
|
|
};
|
|
};
|
|
};
|
|
};
|
|
logout_api_v1_auth_logout_post: {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
requestBody?: never;
|
|
responses: {
|
|
/** @description Successful Response */
|
|
204: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content?: never;
|
|
};
|
|
};
|
|
};
|
|
verify_email_api_v1_auth_verify_email_post: {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
requestBody: {
|
|
content: {
|
|
"application/json": components["schemas"]["TokenRequest"];
|
|
};
|
|
};
|
|
responses: {
|
|
/** @description Successful Response */
|
|
204: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content?: never;
|
|
};
|
|
/** @description Validation Error */
|
|
422: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content: {
|
|
"application/json": components["schemas"]["HTTPValidationError"];
|
|
};
|
|
};
|
|
};
|
|
};
|
|
request_password_reset_api_v1_auth_request_password_reset_post: {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
requestBody: {
|
|
content: {
|
|
"application/json": components["schemas"]["PasswordResetRequest"];
|
|
};
|
|
};
|
|
responses: {
|
|
/** @description Successful Response */
|
|
202: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content: {
|
|
"application/json": {
|
|
[key: string]: unknown;
|
|
};
|
|
};
|
|
};
|
|
/** @description Validation Error */
|
|
422: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content: {
|
|
"application/json": components["schemas"]["HTTPValidationError"];
|
|
};
|
|
};
|
|
};
|
|
};
|
|
reset_password_api_v1_auth_reset_password_post: {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
requestBody: {
|
|
content: {
|
|
"application/json": components["schemas"]["PasswordResetConfirm"];
|
|
};
|
|
};
|
|
responses: {
|
|
/** @description Successful Response */
|
|
204: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content?: never;
|
|
};
|
|
/** @description Validation Error */
|
|
422: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content: {
|
|
"application/json": components["schemas"]["HTTPValidationError"];
|
|
};
|
|
};
|
|
};
|
|
};
|
|
read_me_api_v1_users_me_get: {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
requestBody?: never;
|
|
responses: {
|
|
/** @description Successful Response */
|
|
200: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content: {
|
|
"application/json": components["schemas"]["UserRead"];
|
|
};
|
|
};
|
|
};
|
|
};
|
|
list_my_trees_api_v1_trees_get: {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
requestBody?: never;
|
|
responses: {
|
|
/** @description Successful Response */
|
|
200: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content: {
|
|
"application/json": components["schemas"]["TreeRead"][];
|
|
};
|
|
};
|
|
};
|
|
};
|
|
create_tree_api_v1_trees_post: {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path?: never;
|
|
cookie?: never;
|
|
};
|
|
requestBody: {
|
|
content: {
|
|
"application/json": components["schemas"]["TreeCreate"];
|
|
};
|
|
};
|
|
responses: {
|
|
/** @description Successful Response */
|
|
201: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content: {
|
|
"application/json": components["schemas"]["TreeRead"];
|
|
};
|
|
};
|
|
/** @description Validation Error */
|
|
422: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content: {
|
|
"application/json": components["schemas"]["HTTPValidationError"];
|
|
};
|
|
};
|
|
};
|
|
};
|
|
get_tree_api_v1_trees__tree_id__get: {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path: {
|
|
tree_id: string;
|
|
};
|
|
cookie?: never;
|
|
};
|
|
requestBody?: never;
|
|
responses: {
|
|
/** @description Successful Response */
|
|
200: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content: {
|
|
"application/json": components["schemas"]["TreeRead"];
|
|
};
|
|
};
|
|
/** @description Validation Error */
|
|
422: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content: {
|
|
"application/json": components["schemas"]["HTTPValidationError"];
|
|
};
|
|
};
|
|
};
|
|
};
|
|
list_persons_api_v1_trees__tree_id__persons_get: {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path: {
|
|
tree_id: string;
|
|
};
|
|
cookie?: never;
|
|
};
|
|
requestBody?: never;
|
|
responses: {
|
|
/** @description Successful Response */
|
|
200: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content: {
|
|
"application/json": components["schemas"]["PersonRead"][];
|
|
};
|
|
};
|
|
/** @description Validation Error */
|
|
422: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content: {
|
|
"application/json": components["schemas"]["HTTPValidationError"];
|
|
};
|
|
};
|
|
};
|
|
};
|
|
create_person_api_v1_trees__tree_id__persons_post: {
|
|
parameters: {
|
|
query?: never;
|
|
header?: never;
|
|
path: {
|
|
tree_id: string;
|
|
};
|
|
cookie?: never;
|
|
};
|
|
requestBody: {
|
|
content: {
|
|
"application/json": components["schemas"]["PersonCreate"];
|
|
};
|
|
};
|
|
responses: {
|
|
/** @description Successful Response */
|
|
201: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content: {
|
|
"application/json": components["schemas"]["PersonRead"];
|
|
};
|
|
};
|
|
/** @description Validation Error */
|
|
422: {
|
|
headers: {
|
|
[name: string]: unknown;
|
|
};
|
|
content: {
|
|
"application/json": components["schemas"]["HTTPValidationError"];
|
|
};
|
|
};
|
|
};
|
|
};
|
|
}
|