create-fate 0.0.0 → 1.0.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/LICENSE +21 -0
- package/README.md +24 -0
- package/bin/create-fate.mjs +412 -0
- package/package.json +26 -3
- package/templates/fate/README.md +8 -0
- package/templates/fate/drizzle/.github/workflows/push.yml +27 -0
- package/templates/fate/drizzle/.oxfmtrc.jsonc +23 -0
- package/templates/fate/drizzle/.vite-hooks/pre-commit +1 -0
- package/templates/fate/drizzle/.vscode/extensions.json +12 -0
- package/templates/fate/drizzle/.vscode/settings.json +25 -0
- package/templates/fate/drizzle/AGENTS.md +55 -0
- package/templates/fate/drizzle/README.md +112 -0
- package/templates/fate/drizzle/_gitignore +20 -0
- package/templates/fate/drizzle/client/package.json +58 -0
- package/templates/fate/drizzle/client/pages/index.tsx +96 -0
- package/templates/fate/drizzle/client/pages/layout.tsx +104 -0
- package/templates/fate/drizzle/client/pages/login.tsx +107 -0
- package/templates/fate/drizzle/client/pages/post/[id].tsx +22 -0
- package/templates/fate/drizzle/client/pages/search.tsx +95 -0
- package/templates/fate/drizzle/client/public/icon.svg +1 -0
- package/templates/fate/drizzle/client/src/App.css +153 -0
- package/templates/fate/drizzle/client/src/lib/AvailableLanguages.tsx +5 -0
- package/templates/fate/drizzle/client/src/lib/cx.tsx +6 -0
- package/templates/fate/drizzle/client/src/lib/env.tsx +5 -0
- package/templates/fate/drizzle/client/src/ui/Badge.tsx +31 -0
- package/templates/fate/drizzle/client/src/ui/Button.tsx +108 -0
- package/templates/fate/drizzle/client/src/ui/Card.tsx +32 -0
- package/templates/fate/drizzle/client/src/ui/CommentCard.tsx +75 -0
- package/templates/fate/drizzle/client/src/ui/Error.tsx +19 -0
- package/templates/fate/drizzle/client/src/ui/H2.tsx +15 -0
- package/templates/fate/drizzle/client/src/ui/H3.tsx +20 -0
- package/templates/fate/drizzle/client/src/ui/Header.tsx +96 -0
- package/templates/fate/drizzle/client/src/ui/Input.tsx +17 -0
- package/templates/fate/drizzle/client/src/ui/Link.tsx +11 -0
- package/templates/fate/drizzle/client/src/ui/LocaleSwitcher.tsx +52 -0
- package/templates/fate/drizzle/client/src/ui/PostCard.tsx +284 -0
- package/templates/fate/drizzle/client/src/ui/Section.tsx +23 -0
- package/templates/fate/drizzle/client/src/ui/UserCard.tsx +131 -0
- package/templates/fate/drizzle/client/src/user/AuthClient.tsx +6 -0
- package/templates/fate/drizzle/client/translations/de_DE.json +689 -0
- package/templates/fate/drizzle/client/translations/ja_JP.json +689 -0
- package/templates/fate/drizzle/client/vite.config.ts +39 -0
- package/templates/fate/drizzle/client/void.json +29 -0
- package/templates/fate/drizzle/docker-compose.yml +11 -0
- package/templates/fate/drizzle/package.json +46 -0
- package/templates/fate/drizzle/pnpm-workspace.yaml +36 -0
- package/templates/fate/drizzle/public/fate-logo-dark.svg +4 -0
- package/templates/fate/drizzle/public/fate-logo.svg +4 -0
- package/templates/fate/drizzle/server/.env.example +5 -0
- package/templates/fate/drizzle/server/drizzle.config.ts +11 -0
- package/templates/fate/drizzle/server/package.json +48 -0
- package/templates/fate/drizzle/server/src/__tests__/index.test.tsx +5 -0
- package/templates/fate/drizzle/server/src/app.tsx +44 -0
- package/templates/fate/drizzle/server/src/drizzle/db.ts +25 -0
- package/templates/fate/drizzle/server/src/drizzle/queries.ts +89 -0
- package/templates/fate/drizzle/server/src/drizzle/schema.ts +174 -0
- package/templates/fate/drizzle/server/src/drizzle/seed.tsx +77 -0
- package/templates/fate/drizzle/server/src/drizzle/seedData.ts +205 -0
- package/templates/fate/drizzle/server/src/index.tsx +24 -0
- package/templates/fate/drizzle/server/src/lib/auth.ts +33 -0
- package/templates/fate/drizzle/server/src/lib/env.ts +3 -0
- package/templates/fate/drizzle/server/src/lib/origins.ts +18 -0
- package/templates/fate/drizzle/server/src/router.ts +15 -0
- package/templates/fate/drizzle/server/src/trpc/connection.ts +4 -0
- package/templates/fate/drizzle/server/src/trpc/context.ts +22 -0
- package/templates/fate/drizzle/server/src/trpc/init.ts +51 -0
- package/templates/fate/drizzle/server/src/trpc/routers/comment.ts +146 -0
- package/templates/fate/drizzle/server/src/trpc/routers/post.ts +99 -0
- package/templates/fate/drizzle/server/src/trpc/routers/user.ts +68 -0
- package/templates/fate/drizzle/server/src/trpc/views.ts +75 -0
- package/templates/fate/drizzle/server/src/user/SessionUser.tsx +17 -0
- package/templates/fate/drizzle/server/vite.config.ts +8 -0
- package/templates/fate/drizzle/tsconfig.json +33 -0
- package/templates/fate/drizzle/vite.config.ts +71 -0
- package/templates/fate/http/.github/workflows/push.yml +27 -0
- package/templates/fate/http/.oxfmtrc.jsonc +23 -0
- package/templates/fate/http/.vite-hooks/pre-commit +1 -0
- package/templates/fate/http/.vscode/extensions.json +12 -0
- package/templates/fate/http/.vscode/settings.json +25 -0
- package/templates/fate/http/AGENTS.md +55 -0
- package/templates/fate/http/README.md +112 -0
- package/templates/fate/http/_gitignore +20 -0
- package/templates/fate/http/client/package.json +56 -0
- package/templates/fate/http/client/pages/index.tsx +96 -0
- package/templates/fate/http/client/pages/layout.tsx +89 -0
- package/templates/fate/http/client/pages/login.tsx +107 -0
- package/templates/fate/http/client/pages/post/[id].tsx +22 -0
- package/templates/fate/http/client/pages/search.tsx +95 -0
- package/templates/fate/http/client/public/icon.svg +1 -0
- package/templates/fate/http/client/src/App.css +153 -0
- package/templates/fate/http/client/src/lib/AvailableLanguages.tsx +5 -0
- package/templates/fate/http/client/src/lib/cx.tsx +6 -0
- package/templates/fate/http/client/src/lib/env.tsx +5 -0
- package/templates/fate/http/client/src/ui/Badge.tsx +31 -0
- package/templates/fate/http/client/src/ui/Button.tsx +108 -0
- package/templates/fate/http/client/src/ui/Card.tsx +32 -0
- package/templates/fate/http/client/src/ui/CommentCard.tsx +75 -0
- package/templates/fate/http/client/src/ui/Error.tsx +19 -0
- package/templates/fate/http/client/src/ui/H2.tsx +15 -0
- package/templates/fate/http/client/src/ui/H3.tsx +20 -0
- package/templates/fate/http/client/src/ui/Header.tsx +96 -0
- package/templates/fate/http/client/src/ui/Input.tsx +17 -0
- package/templates/fate/http/client/src/ui/Link.tsx +11 -0
- package/templates/fate/http/client/src/ui/LocaleSwitcher.tsx +52 -0
- package/templates/fate/http/client/src/ui/PostCard.tsx +281 -0
- package/templates/fate/http/client/src/ui/Section.tsx +23 -0
- package/templates/fate/http/client/src/ui/UserCard.tsx +131 -0
- package/templates/fate/http/client/src/user/AuthClient.tsx +6 -0
- package/templates/fate/http/client/translations/de_DE.json +689 -0
- package/templates/fate/http/client/translations/ja_JP.json +689 -0
- package/templates/fate/http/client/vite.config.ts +40 -0
- package/templates/fate/http/client/void.json +29 -0
- package/templates/fate/http/docker-compose.yml +11 -0
- package/templates/fate/http/package.json +46 -0
- package/templates/fate/http/pnpm-workspace.yaml +36 -0
- package/templates/fate/http/public/fate-logo-dark.svg +4 -0
- package/templates/fate/http/public/fate-logo.svg +4 -0
- package/templates/fate/http/server/.env.example +5 -0
- package/templates/fate/http/server/drizzle.config.ts +11 -0
- package/templates/fate/http/server/package.json +46 -0
- package/templates/fate/http/server/src/__tests__/index.test.tsx +5 -0
- package/templates/fate/http/server/src/app.tsx +36 -0
- package/templates/fate/http/server/src/drizzle/db.ts +25 -0
- package/templates/fate/http/server/src/drizzle/queries.ts +89 -0
- package/templates/fate/http/server/src/drizzle/schema.ts +174 -0
- package/templates/fate/http/server/src/drizzle/seed.tsx +77 -0
- package/templates/fate/http/server/src/drizzle/seedData.ts +205 -0
- package/templates/fate/http/server/src/fate/context.ts +20 -0
- package/templates/fate/http/server/src/fate/server.ts +285 -0
- package/templates/fate/http/server/src/fate/views.ts +75 -0
- package/templates/fate/http/server/src/index.tsx +24 -0
- package/templates/fate/http/server/src/lib/auth.ts +33 -0
- package/templates/fate/http/server/src/lib/env.ts +3 -0
- package/templates/fate/http/server/src/lib/origins.ts +18 -0
- package/templates/fate/http/server/src/router.ts +2 -0
- package/templates/fate/http/server/src/user/SessionUser.tsx +17 -0
- package/templates/fate/http/server/vite.config.ts +8 -0
- package/templates/fate/http/tsconfig.json +33 -0
- package/templates/fate/http/vite.config.ts +71 -0
- package/templates/fate/prisma/.github/workflows/push.yml +27 -0
- package/templates/fate/prisma/.oxfmtrc.jsonc +23 -0
- package/templates/fate/prisma/.vite-hooks/pre-commit +1 -0
- package/templates/fate/prisma/.vscode/extensions.json +12 -0
- package/templates/fate/prisma/.vscode/settings.json +25 -0
- package/templates/fate/prisma/AGENTS.md +55 -0
- package/templates/fate/prisma/README.md +126 -0
- package/templates/fate/prisma/_gitignore +20 -0
- package/templates/fate/prisma/client/package.json +58 -0
- package/templates/fate/prisma/client/pages/index.tsx +96 -0
- package/templates/fate/prisma/client/pages/layout.tsx +94 -0
- package/templates/fate/prisma/client/pages/login.tsx +107 -0
- package/templates/fate/prisma/client/pages/post/[id].tsx +22 -0
- package/templates/fate/prisma/client/pages/search.tsx +95 -0
- package/templates/fate/prisma/client/public/icon.svg +1 -0
- package/templates/fate/prisma/client/src/App.css +153 -0
- package/templates/fate/prisma/client/src/lib/AvailableLanguages.tsx +5 -0
- package/templates/fate/prisma/client/src/lib/cx.tsx +6 -0
- package/templates/fate/prisma/client/src/lib/env.tsx +5 -0
- package/templates/fate/prisma/client/src/ui/Badge.tsx +31 -0
- package/templates/fate/prisma/client/src/ui/Button.tsx +108 -0
- package/templates/fate/prisma/client/src/ui/Card.tsx +32 -0
- package/templates/fate/prisma/client/src/ui/CommentCard.tsx +75 -0
- package/templates/fate/prisma/client/src/ui/Error.tsx +19 -0
- package/templates/fate/prisma/client/src/ui/H2.tsx +15 -0
- package/templates/fate/prisma/client/src/ui/H3.tsx +20 -0
- package/templates/fate/prisma/client/src/ui/Header.tsx +96 -0
- package/templates/fate/prisma/client/src/ui/Input.tsx +17 -0
- package/templates/fate/prisma/client/src/ui/Link.tsx +11 -0
- package/templates/fate/prisma/client/src/ui/LocaleSwitcher.tsx +52 -0
- package/templates/fate/prisma/client/src/ui/PostCard.tsx +281 -0
- package/templates/fate/prisma/client/src/ui/Section.tsx +23 -0
- package/templates/fate/prisma/client/src/ui/UserCard.tsx +131 -0
- package/templates/fate/prisma/client/src/user/AuthClient.tsx +6 -0
- package/templates/fate/prisma/client/translations/de_DE.json +689 -0
- package/templates/fate/prisma/client/translations/ja_JP.json +689 -0
- package/templates/fate/prisma/client/vite.config.ts +39 -0
- package/templates/fate/prisma/client/void.json +29 -0
- package/templates/fate/prisma/docker-compose.yml +11 -0
- package/templates/fate/prisma/package.json +47 -0
- package/templates/fate/prisma/pnpm-workspace.yaml +39 -0
- package/templates/fate/prisma/public/fate-logo-dark.svg +4 -0
- package/templates/fate/prisma/public/fate-logo.svg +4 -0
- package/templates/fate/prisma/server/.env.example +5 -0
- package/templates/fate/prisma/server/package.json +42 -0
- package/templates/fate/prisma/server/prisma.config.ts +21 -0
- package/templates/fate/prisma/server/src/__tests__/index.test.tsx +5 -0
- package/templates/fate/prisma/server/src/app.tsx +40 -0
- package/templates/fate/prisma/server/src/index.tsx +24 -0
- package/templates/fate/prisma/server/src/lib/auth.ts +31 -0
- package/templates/fate/prisma/server/src/lib/env.ts +3 -0
- package/templates/fate/prisma/server/src/lib/origins.ts +18 -0
- package/templates/fate/prisma/server/src/prisma/prisma.ts +9 -0
- package/templates/fate/prisma/server/src/prisma/schema.prisma +98 -0
- package/templates/fate/prisma/server/src/prisma/seed.tsx +71 -0
- package/templates/fate/prisma/server/src/prisma/seedData.ts +205 -0
- package/templates/fate/prisma/server/src/router.ts +14 -0
- package/templates/fate/prisma/server/src/trpc/connection.ts +4 -0
- package/templates/fate/prisma/server/src/trpc/context.ts +22 -0
- package/templates/fate/prisma/server/src/trpc/init.ts +8 -0
- package/templates/fate/prisma/server/src/trpc/routers/comment.ts +176 -0
- package/templates/fate/prisma/server/src/trpc/routers/post.ts +154 -0
- package/templates/fate/prisma/server/src/trpc/routers/user.ts +71 -0
- package/templates/fate/prisma/server/src/trpc/views.ts +77 -0
- package/templates/fate/prisma/server/src/user/SessionUser.tsx +17 -0
- package/templates/fate/prisma/server/vite.config.ts +8 -0
- package/templates/fate/prisma/tsconfig.json +33 -0
- package/templates/fate/prisma/vite.config.ts +72 -0
- package/templates/fate/void/.oxfmtrc.jsonc +14 -0
- package/templates/fate/void/README.md +55 -0
- package/templates/fate/void/auth.ts +22 -0
- package/templates/fate/void/db/migrations/20260508073357_initial_schema.sql +133 -0
- package/templates/fate/void/db/migrations/20260508120500_seed_void_demo.sql +170 -0
- package/templates/fate/void/db/migrations/meta/20260508073357_snapshot.json +868 -0
- package/templates/fate/void/db/migrations/meta/_journal.json +20 -0
- package/templates/fate/void/db/queries.ts +211 -0
- package/templates/fate/void/db/schema.ts +325 -0
- package/templates/fate/void/db/seed-auth.ts +31 -0
- package/templates/fate/void/db/seed.ts +163 -0
- package/templates/fate/void/middleware/01.shared.ts +29 -0
- package/templates/fate/void/package.json +65 -0
- package/templates/fate/void/pages/category/[id].tsx +23 -0
- package/templates/fate/void/pages/index.tsx +153 -0
- package/templates/fate/void/pages/layout.tsx +46 -0
- package/templates/fate/void/pages/login.tsx +103 -0
- package/templates/fate/void/pages/post/[id].tsx +20 -0
- package/templates/fate/void/pages/search.tsx +81 -0
- package/templates/fate/void/pnpm-workspace.yaml +31 -0
- package/templates/fate/void/public/.assetsignore +1 -0
- package/templates/fate/void/public/icon.svg +1 -0
- package/templates/fate/void/routes/fate-live.ts +8 -0
- package/templates/fate/void/routes/fate.ts +8 -0
- package/templates/fate/void/seedData.ts +535 -0
- package/templates/fate/void/src/App.css +130 -0
- package/templates/fate/void/src/fate/__tests__/commentPermissions.test.ts +31 -0
- package/templates/fate/void/src/fate/commentPermissions.ts +17 -0
- package/templates/fate/void/src/fate/context.ts +15 -0
- package/templates/fate/void/src/fate/live.ts +6 -0
- package/templates/fate/void/src/fate/server.ts +401 -0
- package/templates/fate/void/src/fate/views.ts +164 -0
- package/templates/fate/void/src/lib/cx.tsx +6 -0
- package/templates/fate/void/src/lib/formatLabel.tsx +6 -0
- package/templates/fate/void/src/lib/shared.ts +10 -0
- package/templates/fate/void/src/ui/Badge.tsx +31 -0
- package/templates/fate/void/src/ui/Button.tsx +92 -0
- package/templates/fate/void/src/ui/Card.tsx +29 -0
- package/templates/fate/void/src/ui/CategoryCard.tsx +105 -0
- package/templates/fate/void/src/ui/CommentCard.tsx +80 -0
- package/templates/fate/void/src/ui/CreatePost.tsx +111 -0
- package/templates/fate/void/src/ui/Error.tsx +19 -0
- package/templates/fate/void/src/ui/EventCard.tsx +152 -0
- package/templates/fate/void/src/ui/H2.tsx +15 -0
- package/templates/fate/void/src/ui/H3.tsx +20 -0
- package/templates/fate/void/src/ui/Header.tsx +96 -0
- package/templates/fate/void/src/ui/Input.tsx +33 -0
- package/templates/fate/void/src/ui/Link.tsx +11 -0
- package/templates/fate/void/src/ui/Number.tsx +5 -0
- package/templates/fate/void/src/ui/PostCard.tsx +294 -0
- package/templates/fate/void/src/ui/Section.tsx +23 -0
- package/templates/fate/void/src/ui/TagBadge.tsx +23 -0
- package/templates/fate/void/src/ui/UserCard.tsx +129 -0
- package/templates/fate/void/src/user/AuthClient.tsx +1 -0
- package/templates/fate/void/src/user/SessionUser.tsx +17 -0
- package/templates/fate/void/tsconfig.json +34 -0
- package/templates/fate/void/vite.config.ts +83 -0
- package/templates/fate/void/void.json +32 -0
- package/templates/fate/void/wrangler.jsonc +16 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { computed, count, dataView, field, list, type Entity } from '@nkzw/fate/server';
|
|
2
|
+
import type {
|
|
3
|
+
CategoryItem,
|
|
4
|
+
CommentItem,
|
|
5
|
+
EventAttendeeItem,
|
|
6
|
+
EventItem,
|
|
7
|
+
PostItem,
|
|
8
|
+
} from '../../db/queries.ts';
|
|
9
|
+
import type { CategoryRow, TagRow, UserRow } from '../../db/schema.ts';
|
|
10
|
+
import type { AppContext } from './context.ts';
|
|
11
|
+
|
|
12
|
+
export const userDataView = dataView<UserRow>('User')({
|
|
13
|
+
email: computed<UserRow, string | null, AppContext>({
|
|
14
|
+
authorize: ({ id }, context) => context?.sessionUser?.id === id,
|
|
15
|
+
resolve: (_item, deps) => (deps.email as string | null) ?? null,
|
|
16
|
+
select: {
|
|
17
|
+
email: field('email'),
|
|
18
|
+
},
|
|
19
|
+
}),
|
|
20
|
+
id: true,
|
|
21
|
+
name: true,
|
|
22
|
+
username: true,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const tagDataView = dataView<TagRow>('Tag')({
|
|
26
|
+
description: true,
|
|
27
|
+
id: true,
|
|
28
|
+
name: true,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const categorySummaryDataView = dataView<CategoryRow>('Category')({
|
|
32
|
+
id: true,
|
|
33
|
+
name: true,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const basePost = {
|
|
37
|
+
author: userDataView,
|
|
38
|
+
category: categorySummaryDataView,
|
|
39
|
+
commentCount: computed<PostItem, number>({
|
|
40
|
+
resolve: (_item, deps) => (deps.count as number) ?? 0,
|
|
41
|
+
select: {
|
|
42
|
+
count: count('comments'),
|
|
43
|
+
},
|
|
44
|
+
}),
|
|
45
|
+
content: true,
|
|
46
|
+
id: true,
|
|
47
|
+
likes: true,
|
|
48
|
+
title: true,
|
|
49
|
+
} as const;
|
|
50
|
+
|
|
51
|
+
export const postSummaryDataView = dataView<PostItem>('Post')({
|
|
52
|
+
...basePost,
|
|
53
|
+
tags: list(tagDataView),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
export const commentDataView = dataView<CommentItem>('Comment')({
|
|
57
|
+
author: userDataView,
|
|
58
|
+
content: true,
|
|
59
|
+
id: true,
|
|
60
|
+
post: postSummaryDataView,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export const postDataView = dataView<PostItem>('Post')({
|
|
64
|
+
...basePost,
|
|
65
|
+
comments: list(commentDataView, { orderBy: { createdAt: 'asc', id: 'asc' } }),
|
|
66
|
+
tags: list(tagDataView),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export const categoryDataView = dataView<CategoryItem>('Category')({
|
|
70
|
+
description: true,
|
|
71
|
+
id: true,
|
|
72
|
+
name: true,
|
|
73
|
+
postCount: computed<CategoryItem, number>({
|
|
74
|
+
resolve: (_item, deps) => (deps.count as number) ?? 0,
|
|
75
|
+
select: {
|
|
76
|
+
count: count('posts'),
|
|
77
|
+
},
|
|
78
|
+
}),
|
|
79
|
+
posts: list(postDataView),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export const eventAttendeeDataView = dataView<EventAttendeeItem>('EventAttendee')({
|
|
83
|
+
id: true,
|
|
84
|
+
notes: true,
|
|
85
|
+
status: true,
|
|
86
|
+
user: userDataView,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
export const eventDataView = dataView<EventItem>('Event')({
|
|
90
|
+
attendees: list(eventAttendeeDataView, { orderBy: { createdAt: 'asc', id: 'asc' } }),
|
|
91
|
+
attendingCount: computed<EventItem, number>({
|
|
92
|
+
resolve: (_item, deps) => (deps.count as number) ?? 0,
|
|
93
|
+
select: {
|
|
94
|
+
count: count('attendees', {
|
|
95
|
+
where: { status: 'GOING' },
|
|
96
|
+
}),
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
99
|
+
capacity: true,
|
|
100
|
+
description: true,
|
|
101
|
+
endAt: true,
|
|
102
|
+
host: userDataView,
|
|
103
|
+
id: true,
|
|
104
|
+
livestreamUrl: true,
|
|
105
|
+
location: true,
|
|
106
|
+
name: true,
|
|
107
|
+
startAt: true,
|
|
108
|
+
topics: true,
|
|
109
|
+
type: true,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
export type User = Entity<typeof userDataView, 'User'>;
|
|
113
|
+
export type Tag = Entity<typeof tagDataView, 'Tag'>;
|
|
114
|
+
export type Comment = Entity<
|
|
115
|
+
typeof commentDataView,
|
|
116
|
+
'Comment',
|
|
117
|
+
{
|
|
118
|
+
author: User;
|
|
119
|
+
post: Post;
|
|
120
|
+
}
|
|
121
|
+
>;
|
|
122
|
+
export type EventAttendee = Entity<
|
|
123
|
+
typeof eventAttendeeDataView,
|
|
124
|
+
'EventAttendee',
|
|
125
|
+
{
|
|
126
|
+
user: User;
|
|
127
|
+
}
|
|
128
|
+
>;
|
|
129
|
+
export type Post = Entity<
|
|
130
|
+
typeof postDataView,
|
|
131
|
+
'Post',
|
|
132
|
+
{
|
|
133
|
+
author: User;
|
|
134
|
+
category: Category | null;
|
|
135
|
+
comments: Array<Comment>;
|
|
136
|
+
tags: Array<Tag>;
|
|
137
|
+
}
|
|
138
|
+
>;
|
|
139
|
+
export type Category = Entity<
|
|
140
|
+
typeof categoryDataView,
|
|
141
|
+
'Category',
|
|
142
|
+
{
|
|
143
|
+
posts: Array<Post>;
|
|
144
|
+
}
|
|
145
|
+
>;
|
|
146
|
+
export type Event = Entity<
|
|
147
|
+
typeof eventDataView,
|
|
148
|
+
'Event',
|
|
149
|
+
{
|
|
150
|
+
attendees: Array<EventAttendee>;
|
|
151
|
+
host: User;
|
|
152
|
+
}
|
|
153
|
+
>;
|
|
154
|
+
|
|
155
|
+
export const Root = {
|
|
156
|
+
categories: list(categoryDataView, { orderBy: { createdAt: 'asc', id: 'asc' } }),
|
|
157
|
+
commentSearch: {
|
|
158
|
+
procedure: 'search',
|
|
159
|
+
view: list(commentDataView, { orderBy: { createdAt: 'desc', id: 'desc' } }),
|
|
160
|
+
},
|
|
161
|
+
events: list(eventDataView, { orderBy: [{ startAt: 'asc' }, { id: 'asc' }] }),
|
|
162
|
+
posts: list(postDataView, { orderBy: { createdAt: 'desc', id: 'desc' } }),
|
|
163
|
+
viewer: userDataView,
|
|
164
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
2
|
+
import { HTMLAttributes } from 'react';
|
|
3
|
+
import cx from '../lib/cx.tsx';
|
|
4
|
+
|
|
5
|
+
const badgeVariants = cva(
|
|
6
|
+
'squircle inline-flex items-center border px-1.5 py-0.5 text-xs font-semibold transition-colors focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-none',
|
|
7
|
+
{
|
|
8
|
+
defaultVariants: {
|
|
9
|
+
variant: 'default',
|
|
10
|
+
},
|
|
11
|
+
variants: {
|
|
12
|
+
variant: {
|
|
13
|
+
default: 'bg-primary text-primary-foreground hover:bg-primary/80 border-transparent',
|
|
14
|
+
destructive:
|
|
15
|
+
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
|
|
16
|
+
outline: 'text-foreground',
|
|
17
|
+
secondary:
|
|
18
|
+
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
export interface BadgeProps
|
|
25
|
+
extends HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}
|
|
26
|
+
|
|
27
|
+
function Badge({ className, variant, ...props }: BadgeProps) {
|
|
28
|
+
return <div className={cx(badgeVariants({ variant }), className)} {...props} />;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { Badge, badgeVariants };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
import {
|
|
4
|
+
type ButtonHTMLAttributes,
|
|
5
|
+
type MouseEvent,
|
|
6
|
+
type ReactNode,
|
|
7
|
+
useOptimistic,
|
|
8
|
+
useTransition,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import { useFormStatus } from 'react-dom';
|
|
11
|
+
import cx from '../lib/cx.tsx';
|
|
12
|
+
|
|
13
|
+
const buttonVariants = cva(
|
|
14
|
+
'squircle inline-flex cursor-pointer items-center justify-center gap-2 text-sm font-medium whitespace-nowrap ring-offset-background transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
|
15
|
+
{
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
size: 'default',
|
|
18
|
+
variant: 'default',
|
|
19
|
+
},
|
|
20
|
+
variants: {
|
|
21
|
+
size: {
|
|
22
|
+
default: 'h-10 px-3 py-2 active:pt-[11px] active:pb-[9px]',
|
|
23
|
+
icon: 'h-10 w-10',
|
|
24
|
+
lg: 'squircle h-11 px-6 active:pt-[11px] active:pb-[9px]',
|
|
25
|
+
sm: 'squircle h-9 px-2 active:pt-[11px] active:pb-[9px]',
|
|
26
|
+
},
|
|
27
|
+
variant: {
|
|
28
|
+
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
29
|
+
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
|
30
|
+
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
|
31
|
+
link: 'text-primary underline-offset-4 hover:underline',
|
|
32
|
+
outline: 'border-input border bg-background hover:bg-accent hover:text-accent-foreground',
|
|
33
|
+
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const Button = ({
|
|
40
|
+
action,
|
|
41
|
+
asChild = false,
|
|
42
|
+
children,
|
|
43
|
+
className,
|
|
44
|
+
disabled,
|
|
45
|
+
onClick: initialOnClick,
|
|
46
|
+
pendingPlaceholder = '...',
|
|
47
|
+
size,
|
|
48
|
+
type,
|
|
49
|
+
variant,
|
|
50
|
+
...props
|
|
51
|
+
}: ButtonHTMLAttributes<HTMLButtonElement> &
|
|
52
|
+
VariantProps<typeof buttonVariants> & {
|
|
53
|
+
action?: () => Promise<unknown> | void;
|
|
54
|
+
asChild?: boolean;
|
|
55
|
+
pendingPlaceholder?: ReactNode;
|
|
56
|
+
}) => {
|
|
57
|
+
const Component = asChild ? Slot : 'button';
|
|
58
|
+
|
|
59
|
+
const [optimisticIsPending, setOptimisticIsPending] = useOptimistic(false);
|
|
60
|
+
const [transitionIsPending, startTransition] = useTransition();
|
|
61
|
+
const { pending: formIsPending } = useFormStatus();
|
|
62
|
+
|
|
63
|
+
const onClick = (event: MouseEvent<HTMLButtonElement>) => {
|
|
64
|
+
initialOnClick?.(event);
|
|
65
|
+
|
|
66
|
+
if (!action || event.defaultPrevented) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
startTransition(async () => {
|
|
72
|
+
setOptimisticIsPending(true);
|
|
73
|
+
await action();
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const isPending = transitionIsPending || optimisticIsPending || formIsPending;
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<Component
|
|
81
|
+
className={cx(buttonVariants({ className, size, variant }))}
|
|
82
|
+
disabled={disabled || isPending || undefined}
|
|
83
|
+
onClick={initialOnClick || action ? onClick : undefined}
|
|
84
|
+
type={type}
|
|
85
|
+
{...props}
|
|
86
|
+
>
|
|
87
|
+
{isPending ? pendingPlaceholder : children}
|
|
88
|
+
</Component>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export { Button, buttonVariants };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { VStack } from '@nkzw/stack';
|
|
2
|
+
import cx from '../lib/cx.tsx';
|
|
3
|
+
|
|
4
|
+
export default function Card({
|
|
5
|
+
children,
|
|
6
|
+
className,
|
|
7
|
+
}: {
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
className?: string;
|
|
10
|
+
}) {
|
|
11
|
+
return (
|
|
12
|
+
<div
|
|
13
|
+
className={cx(
|
|
14
|
+
'squircle relative overflow-hidden border border-gray-200/70 bg-white/85 shadow-[0_16px_70px_-32px_rgba(15,23,42,0.45)] transition duration-200 ease-out hover:shadow-[0_24px_80px_-38px_rgba(15,23,42,0.55)] dark:border-neutral-800 dark:bg-neutral-900/80',
|
|
15
|
+
className,
|
|
16
|
+
)}
|
|
17
|
+
>
|
|
18
|
+
<div className="pointer-events-none absolute inset-0 bg-linear-to-br from-blue-500/10 via-transparent to-indigo-500/15 opacity-30 transition duration-200" />
|
|
19
|
+
<VStack
|
|
20
|
+
className="relative z-10 flex h-full w-full"
|
|
21
|
+
gap={16}
|
|
22
|
+
horizontalPadding={20}
|
|
23
|
+
verticalPadding={16}
|
|
24
|
+
>
|
|
25
|
+
{children}
|
|
26
|
+
</VStack>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import Stack, { VStack } from '@nkzw/stack';
|
|
2
|
+
import { useView, view, ViewRef } from 'react-fate';
|
|
3
|
+
import type { Category, Post } from '../fate/views.ts';
|
|
4
|
+
import { Badge } from '../ui/Badge.tsx';
|
|
5
|
+
import Card from '../ui/Card.tsx';
|
|
6
|
+
import TagBadge, { TagView } from '../ui/TagBadge.tsx';
|
|
7
|
+
import { UserView } from '../ui/UserCard.tsx';
|
|
8
|
+
import H3 from './H3.tsx';
|
|
9
|
+
import Link from './Link.tsx';
|
|
10
|
+
|
|
11
|
+
const CategoryPostView = view<Post>()({
|
|
12
|
+
author: UserView,
|
|
13
|
+
id: true,
|
|
14
|
+
likes: true,
|
|
15
|
+
tags: {
|
|
16
|
+
items: {
|
|
17
|
+
node: TagView,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
title: true,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const CategoryPost = ({ post: postRef }: { post: ViewRef<'Post'> }) => {
|
|
24
|
+
const post = useView(CategoryPostView, postRef);
|
|
25
|
+
const author = useView(UserView, post.author);
|
|
26
|
+
const tags = post.tags?.items ?? [];
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<VStack gap key={post.id}>
|
|
30
|
+
<Stack alignCenter between gap={12}>
|
|
31
|
+
<Link href={`/post/${post.id}`}>
|
|
32
|
+
<span className="font-medium text-blue-600 no-underline hover:underline dark:text-blue-200">
|
|
33
|
+
{post.title}
|
|
34
|
+
</span>
|
|
35
|
+
</Link>
|
|
36
|
+
<span className="text-xs text-muted-foreground">{post.likes} likes</span>
|
|
37
|
+
</Stack>
|
|
38
|
+
<Stack alignCenter gap wrap>
|
|
39
|
+
<span className="text-xs text-muted-foreground">
|
|
40
|
+
{author?.name ? `by ${author.name}` : 'By an anonymous collaborator'}
|
|
41
|
+
</span>
|
|
42
|
+
{tags.length ? (
|
|
43
|
+
<Stack gap wrap>
|
|
44
|
+
{tags.map(({ node }) => (
|
|
45
|
+
<TagBadge key={node.id} tag={node} />
|
|
46
|
+
))}
|
|
47
|
+
</Stack>
|
|
48
|
+
) : null}
|
|
49
|
+
</Stack>
|
|
50
|
+
</VStack>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const CategoryView = view<Category>()({
|
|
55
|
+
description: true,
|
|
56
|
+
id: true,
|
|
57
|
+
name: true,
|
|
58
|
+
postCount: true,
|
|
59
|
+
posts: {
|
|
60
|
+
items: {
|
|
61
|
+
cursor: true,
|
|
62
|
+
node: CategoryPostView,
|
|
63
|
+
},
|
|
64
|
+
pagination: {
|
|
65
|
+
hasNext: true,
|
|
66
|
+
nextCursor: true,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
export default function CategoryCard({ category: categoryRef }: { category: ViewRef<'Category'> }) {
|
|
72
|
+
const category = useView(CategoryView, categoryRef);
|
|
73
|
+
const posts = category.posts?.items ?? [];
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<Card key={category.id}>
|
|
77
|
+
<VStack gap={12}>
|
|
78
|
+
<Stack alignCenter between gap={12}>
|
|
79
|
+
<div>
|
|
80
|
+
<Link href={`/category/${category.id}`}>
|
|
81
|
+
<H3>{category.name}</H3>
|
|
82
|
+
</Link>
|
|
83
|
+
<p className="text-sm text-muted-foreground">{category.description}</p>
|
|
84
|
+
</div>
|
|
85
|
+
<Badge className="text-nowrap" variant="outline">
|
|
86
|
+
{category.postCount} posts
|
|
87
|
+
</Badge>
|
|
88
|
+
</Stack>
|
|
89
|
+
<VStack gap={12}>
|
|
90
|
+
{posts.map(({ cursor, node }) => {
|
|
91
|
+
if (cursor !== node.id) {
|
|
92
|
+
throw new Error(`fate: Cursor '${cursor}' does not match node ID '${node.id}'.`);
|
|
93
|
+
}
|
|
94
|
+
return <CategoryPost key={node.id} post={node} />;
|
|
95
|
+
})}
|
|
96
|
+
</VStack>
|
|
97
|
+
{category.posts?.pagination?.hasNext ? (
|
|
98
|
+
<span className="text-sm text-muted-foreground">
|
|
99
|
+
More posts available in this category...
|
|
100
|
+
</span>
|
|
101
|
+
) : null}
|
|
102
|
+
</VStack>
|
|
103
|
+
</Card>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import Stack from '@nkzw/stack';
|
|
2
|
+
import { useShared } from '@void/react';
|
|
3
|
+
import { X } from 'lucide-react';
|
|
4
|
+
import { useFateClient, useView, view, ViewRef } from 'react-fate';
|
|
5
|
+
import type { Comment } from '../fate/views.ts';
|
|
6
|
+
import type { SharedData } from '../lib/shared.ts';
|
|
7
|
+
import { Button } from './Button.tsx';
|
|
8
|
+
import Link from './Link.tsx';
|
|
9
|
+
|
|
10
|
+
export const CommentView = view<Comment>()({
|
|
11
|
+
author: {
|
|
12
|
+
id: true,
|
|
13
|
+
name: true,
|
|
14
|
+
username: true,
|
|
15
|
+
},
|
|
16
|
+
content: true,
|
|
17
|
+
id: true,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const CommentViewWithPostCount = view<Comment>()({
|
|
21
|
+
...CommentView,
|
|
22
|
+
post: { commentCount: true },
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export default function CommentCard({
|
|
26
|
+
comment: commentRef,
|
|
27
|
+
link,
|
|
28
|
+
post,
|
|
29
|
+
}: {
|
|
30
|
+
comment: ViewRef<'Comment'>;
|
|
31
|
+
link?: boolean;
|
|
32
|
+
post: { commentCount: number; id: string; title: string };
|
|
33
|
+
}) {
|
|
34
|
+
const fate = useFateClient();
|
|
35
|
+
const { auth } = useShared<SharedData>();
|
|
36
|
+
const comment = useView(CommentView, commentRef);
|
|
37
|
+
const { author } = comment;
|
|
38
|
+
const canDelete = auth.user?.id === author?.id;
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div
|
|
42
|
+
className="group squircle border border-gray-200/80 bg-gray-100/50 p-3 text-sm dark:border-neutral-800 dark:bg-neutral-900/40"
|
|
43
|
+
key={comment.id}
|
|
44
|
+
>
|
|
45
|
+
<Stack between gap={16}>
|
|
46
|
+
<p className="font-medium text-gray-900 dark:text-gray-200">
|
|
47
|
+
{author?.name ?? 'Anonymous'}
|
|
48
|
+
</p>
|
|
49
|
+
{canDelete ? (
|
|
50
|
+
<Button
|
|
51
|
+
className="opacity-0 transition-opacity group-hover:opacity-100 focus:opacity-100"
|
|
52
|
+
onClick={async () => {
|
|
53
|
+
await fate.mutations.comment.delete({
|
|
54
|
+
delete: true,
|
|
55
|
+
input: { id: comment.id },
|
|
56
|
+
optimistic: {
|
|
57
|
+
post: { commentCount: post.commentCount - 1, id: post.id },
|
|
58
|
+
},
|
|
59
|
+
view: view<Comment>()({
|
|
60
|
+
id: true,
|
|
61
|
+
post: { commentCount: true },
|
|
62
|
+
}),
|
|
63
|
+
});
|
|
64
|
+
}}
|
|
65
|
+
size="sm"
|
|
66
|
+
variant="ghost"
|
|
67
|
+
>
|
|
68
|
+
<X size={14} />
|
|
69
|
+
</Button>
|
|
70
|
+
) : null}
|
|
71
|
+
</Stack>
|
|
72
|
+
<p className="text-foreground/80">{comment.content}</p>
|
|
73
|
+
{link && (
|
|
74
|
+
<Link className="text-blue-500 underline hover:no-underline" href={`/post/${post.id}`}>
|
|
75
|
+
{post.title}
|
|
76
|
+
</Link>
|
|
77
|
+
)}
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import Stack, { VStack } from '@nkzw/stack';
|
|
2
|
+
import { KeyboardEvent, startTransition, useActionState, useState } from 'react';
|
|
3
|
+
import { useFateClient, useView, ViewRef } from 'react-fate';
|
|
4
|
+
import { Button } from '../ui/Button.tsx';
|
|
5
|
+
import Card from './Card.tsx';
|
|
6
|
+
import H3 from './H3.tsx';
|
|
7
|
+
import Input, { CheckBox } from './Input.tsx';
|
|
8
|
+
import { PostView } from './PostCard.tsx';
|
|
9
|
+
import { UserCardView } from './UserCard.tsx';
|
|
10
|
+
|
|
11
|
+
export default function CreatePost({ user: userRef }: { user: ViewRef<'User'> | null }) {
|
|
12
|
+
const fate = useFateClient();
|
|
13
|
+
const user = useView(UserCardView, userRef);
|
|
14
|
+
const [contentValue, setContentValue] = useState('');
|
|
15
|
+
const [titleValue, setTitleValue] = useState('');
|
|
16
|
+
const [missingOptimisticContent, setMissingOptimisticContent] = useState(false);
|
|
17
|
+
const [missingMutationSelection, setMissingMutationSelection] = useState(false);
|
|
18
|
+
|
|
19
|
+
const [, createPost, isPending] = useActionState(async () => {
|
|
20
|
+
const content = contentValue.trim();
|
|
21
|
+
const title = titleValue.trim();
|
|
22
|
+
|
|
23
|
+
if (!content || !title || !user) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const result = await fate.mutations.post.add({
|
|
28
|
+
input: { content, title },
|
|
29
|
+
insert: 'before',
|
|
30
|
+
optimistic: missingOptimisticContent
|
|
31
|
+
? {
|
|
32
|
+
author: user,
|
|
33
|
+
comments: [],
|
|
34
|
+
id: `optimistic:${Date.now().toString(36)}`,
|
|
35
|
+
title,
|
|
36
|
+
}
|
|
37
|
+
: {
|
|
38
|
+
author: user,
|
|
39
|
+
commentCount: 0,
|
|
40
|
+
comments: [],
|
|
41
|
+
content,
|
|
42
|
+
id: `optimistic:${Date.now().toString(36)}`,
|
|
43
|
+
likes: 0,
|
|
44
|
+
title,
|
|
45
|
+
},
|
|
46
|
+
...(missingMutationSelection ? null : { view: PostView }),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
setContentValue('');
|
|
50
|
+
setTitleValue('');
|
|
51
|
+
|
|
52
|
+
return result;
|
|
53
|
+
}, null);
|
|
54
|
+
|
|
55
|
+
const maybeSubmitPost = (event: KeyboardEvent<HTMLTextAreaElement | HTMLInputElement>) => {
|
|
56
|
+
if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) {
|
|
57
|
+
startTransition(createPost);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const postingIsDisabled =
|
|
62
|
+
isPending || titleValue.trim().length === 0 || contentValue.trim().length === 0;
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Card>
|
|
66
|
+
<VStack action={createPost} as="form" gap={16}>
|
|
67
|
+
<H3>Create a Post</H3>
|
|
68
|
+
<Input
|
|
69
|
+
className="w-full"
|
|
70
|
+
disabled={isPending}
|
|
71
|
+
onChange={(event) => setTitleValue(event.target.value)}
|
|
72
|
+
onKeyDown={maybeSubmitPost}
|
|
73
|
+
placeholder="Post Title"
|
|
74
|
+
value={titleValue}
|
|
75
|
+
/>
|
|
76
|
+
<textarea
|
|
77
|
+
className="squircle border-input flex min-h-20 w-full border bg-background px-3 py-2 text-sm text-foreground shadow-sm transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0 focus-visible:ring-offset-background focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 dark:bg-neutral-900/40"
|
|
78
|
+
disabled={isPending}
|
|
79
|
+
onChange={(event) => setContentValue(event.target.value)}
|
|
80
|
+
onKeyDown={maybeSubmitPost}
|
|
81
|
+
placeholder={'Share your thoughts about fate...'}
|
|
82
|
+
value={contentValue}
|
|
83
|
+
/>
|
|
84
|
+
<Stack alignCenter between className="text-sm" gap={16}>
|
|
85
|
+
<span className="font-bold">Mutation Debug Options</span>
|
|
86
|
+
<Stack alignCenter as="label" gap>
|
|
87
|
+
<CheckBox
|
|
88
|
+
checked={missingMutationSelection}
|
|
89
|
+
disabled={isPending}
|
|
90
|
+
onChange={(event) => setMissingMutationSelection(event.target.checked)}
|
|
91
|
+
/>
|
|
92
|
+
Missing mutation selection
|
|
93
|
+
</Stack>
|
|
94
|
+
<Stack alignCenter as="label" gap>
|
|
95
|
+
<CheckBox
|
|
96
|
+
checked={missingOptimisticContent}
|
|
97
|
+
disabled={isPending}
|
|
98
|
+
onChange={(event) => setMissingOptimisticContent(event.target.checked)}
|
|
99
|
+
/>
|
|
100
|
+
Missing optimistic content
|
|
101
|
+
</Stack>
|
|
102
|
+
</Stack>
|
|
103
|
+
<Stack alignCenter end gap={16}>
|
|
104
|
+
<Button disabled={postingIsDisabled} size="sm" type="submit" variant="secondary">
|
|
105
|
+
Post comment
|
|
106
|
+
</Button>
|
|
107
|
+
</Stack>
|
|
108
|
+
</VStack>
|
|
109
|
+
</Card>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { VStack } from '@nkzw/stack';
|
|
2
|
+
|
|
3
|
+
export default function Error({ error: initialError }: { error: unknown }) {
|
|
4
|
+
const error = initialError instanceof Error ? (initialError as Error) : null;
|
|
5
|
+
return (
|
|
6
|
+
<VStack gap={12}>
|
|
7
|
+
<h3 className="text-xl font-semibold text-red-700">Error</h3>
|
|
8
|
+
<code>{error ? error.stack || `fate Error: ${error.message}` : String(initialError)}</code>
|
|
9
|
+
<div>
|
|
10
|
+
<a
|
|
11
|
+
className="cursor-pointer underline decoration-gray-300 hover:no-underline"
|
|
12
|
+
onClick={() => window.location.reload()}
|
|
13
|
+
>
|
|
14
|
+
Try again
|
|
15
|
+
</a>
|
|
16
|
+
</div>
|
|
17
|
+
</VStack>
|
|
18
|
+
);
|
|
19
|
+
}
|