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,112 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="/public/fate-logo-dark.svg">
|
|
4
|
+
<source media="(prefers-color-scheme: light)" srcset="/public/fate-logo.svg">
|
|
5
|
+
<img alt="Logo" src="/public/fate-logo.svg" width="50%">
|
|
6
|
+
</picture>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
**_fate_** is a modern data client for React inspired by [Relay](https://relay.dev/) and [GraphQL](https://graphql.org/). It combines view composition, normalized caching, data masking, Async React features, and type-safe data fetching.
|
|
10
|
+
|
|
11
|
+
Check out [fate.technology](https://fate.technology) for documentation, examples, and guides.
|
|
12
|
+
|
|
13
|
+
# _fate_ Quick Start Template
|
|
14
|
+
|
|
15
|
+
Create a new fate app with Vite+:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
vp create fate my-app --template drizzle
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Getting Started with _fate_
|
|
22
|
+
|
|
23
|
+
Read the [Getting Started Guide](https://fate.technology/guide/core-concepts) to learn how to use _fate_ in your React applications.
|
|
24
|
+
|
|
25
|
+
## Technologies
|
|
26
|
+
|
|
27
|
+
This template combines the Nakazawa Tech's [Web App Template](https://github.com/nkzw-tech/web-app-template) and [Server Template](https://github.com/nkzw-tech/server-template) into a monorepo, with unified tooling and _fate_ as the data client. It uses tRPC with Drizzle.
|
|
28
|
+
|
|
29
|
+
_This template lives on the edge._ It's a turbocharged starting point on top of an already optimized stack. It uses TypeScript Go, Vite+, and ships with sensible defaults to unlock an incredibly fast development experience. It follows the principles laid out in [Building Scalable Applications](https://www.youtube.com/watch?v=rxPTEko8J7c&t=36s).
|
|
30
|
+
|
|
31
|
+
Next to [_fate_](https://fate.technology), it comes with the following technologies:
|
|
32
|
+
|
|
33
|
+
### Client Technologies
|
|
34
|
+
|
|
35
|
+
- [Vite 8](https://vitejs.dev/)
|
|
36
|
+
- [React](https://reactjs.org/) with [React Compiler](https://react.dev/learn/react-compiler) enabled
|
|
37
|
+
- [Tailwind](https://tailwindcss.com/)
|
|
38
|
+
- [fbtee](https://github.com/nkzw-tech/fbtee) for i18n
|
|
39
|
+
- [Better Auth](https://www.better-auth.com/) for authentication
|
|
40
|
+
- [Void Pages Router](https://void.cloud/docs)
|
|
41
|
+
- [TypeScript](https://www.typescriptlang.org)
|
|
42
|
+
- [pnpm](https://pnpm.io/)
|
|
43
|
+
|
|
44
|
+
### Server Technologies
|
|
45
|
+
|
|
46
|
+
- [Drizzle](https://orm.drizzle.team/) as the ORM.
|
|
47
|
+
- [tRPC](https://trpc.io/) for type-safe APIs.
|
|
48
|
+
- [Hono](https://hono.dev/)
|
|
49
|
+
- [Better Auth](https://better-auth.com/) for Authentication.
|
|
50
|
+
|
|
51
|
+
### Folder Structure
|
|
52
|
+
|
|
53
|
+
- `client/` - The React client application using _fate_.
|
|
54
|
+
- `server/` - The fate server using tRPC with Drizzle.
|
|
55
|
+
|
|
56
|
+
## Initial Setup
|
|
57
|
+
|
|
58
|
+
You'll need Node.js 24+ and [Vite+](https://viteplus.dev/guide/).
|
|
59
|
+
|
|
60
|
+
Install dependencies:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
vp install
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Review `server/.env`, which is copied from `server/.env.example` when the app is created. The default local values expect:
|
|
67
|
+
|
|
68
|
+
- Postgres at `postgresql://fate:echo@localhost:5432/fate`.
|
|
69
|
+
- The server at `http://localhost:9000`.
|
|
70
|
+
- The client at `http://localhost:5173`.
|
|
71
|
+
|
|
72
|
+
Start Postgres with Docker:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
docker-compose up -d
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Alternatively, create the database manually:
|
|
79
|
+
|
|
80
|
+
```SQL
|
|
81
|
+
CREATE ROLE fate WITH LOGIN PASSWORD 'echo';
|
|
82
|
+
CREATE DATABASE fate;
|
|
83
|
+
ALTER DATABASE fate OWNER TO fate;
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Then set up the schema, seed data, translations, and generated fate client:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
vp run dev:setup
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Start the app:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
vp run dev
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The client runs at `http://localhost:5173` and the server runs at `http://localhost:9000`. tRPC requests go to `/trpc`; fate live updates use the SSE endpoint under `/fate/live`.
|
|
99
|
+
|
|
100
|
+
## Development
|
|
101
|
+
|
|
102
|
+
Common commands from the project root:
|
|
103
|
+
|
|
104
|
+
- `vp run dev` starts the client and server together.
|
|
105
|
+
- `vp run dev:client` starts only the client.
|
|
106
|
+
- `vp run dev:server` starts only the server.
|
|
107
|
+
- `vp run dev:setup` pushes the Drizzle schema, seeds the database, runs fbtee setup, and regenerates the fate client.
|
|
108
|
+
- `vp run fate:generate` regenerates `client/.fate/client.generated.ts` after changing server views, roots, or routers.
|
|
109
|
+
- `vp run drizzle` opens Drizzle Kit commands for the server package.
|
|
110
|
+
- `vp check --fix` formats, lints, and type-checks the workspace.
|
|
111
|
+
- `vp test` runs the test suite.
|
|
112
|
+
- `vp run build` builds the client and server.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.fate/
|
|
2
|
+
**/.fate/
|
|
3
|
+
.void/
|
|
4
|
+
**/.void/
|
|
5
|
+
.eslintcache
|
|
6
|
+
.pnpm-debug.log
|
|
7
|
+
**/node_modules/
|
|
8
|
+
/node_modules/
|
|
9
|
+
client/.enum_manifest.json
|
|
10
|
+
client/dist/
|
|
11
|
+
client/source_strings.json
|
|
12
|
+
client/src/translations/
|
|
13
|
+
coverage/
|
|
14
|
+
dist/
|
|
15
|
+
server/.env
|
|
16
|
+
server/.prod.env
|
|
17
|
+
server/dist/
|
|
18
|
+
server/src/prisma/prisma-client/
|
|
19
|
+
tsconfig.tsbuildinfo
|
|
20
|
+
vite.config.ts.timestamp-*
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@app/client",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"author": "Christoph Nakazawa <christoph.pojer@gmail.com>",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git://github.com/nkzw-tech/fate.git"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "vp build",
|
|
13
|
+
"dev": "vp dev",
|
|
14
|
+
"dev:setup": "vp run fbtee:all",
|
|
15
|
+
"fate:generate": "vp exec fate generate",
|
|
16
|
+
"fbtee:all": "vpx fbtee collect && vpx fbtee translate"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@app/server": "workspace:*",
|
|
20
|
+
"@hono/node-server": "^2.0.1",
|
|
21
|
+
"@nkzw/core": "^1.3.1",
|
|
22
|
+
"@nkzw/define-env": "^1.1.0",
|
|
23
|
+
"@nkzw/stack": "^2.3.2",
|
|
24
|
+
"@radix-ui/react-slot": "^1.2.4",
|
|
25
|
+
"@trpc/client": "^11.17.0",
|
|
26
|
+
"@void/react": "^0.7.5",
|
|
27
|
+
"better-auth": "^1.6.9",
|
|
28
|
+
"class-variance-authority": "^0.7.1",
|
|
29
|
+
"clsx": "^2.1.1",
|
|
30
|
+
"fbtee": "^1.7.4",
|
|
31
|
+
"lucide-react": "^1.14.0",
|
|
32
|
+
"react": "^19.2.5",
|
|
33
|
+
"react-dom": "^19.2.5",
|
|
34
|
+
"react-error-boundary": "^6.1.1",
|
|
35
|
+
"react-fate": "latest",
|
|
36
|
+
"tailwind-merge": "^3.5.0",
|
|
37
|
+
"void": "^0.7.5"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@nkzw/babel-preset-fbtee": "^1.7.4",
|
|
41
|
+
"@rolldown/plugin-babel": "^0.2.3",
|
|
42
|
+
"@tailwindcss/vite": "^4.2.4",
|
|
43
|
+
"@trpc/server": "^11.17.0",
|
|
44
|
+
"@types/babel__core": "^7.20.5",
|
|
45
|
+
"@types/node": "^25.6.0",
|
|
46
|
+
"@types/react": "^19.2.14",
|
|
47
|
+
"@types/react-dom": "^19.2.3",
|
|
48
|
+
"@vitejs/plugin-react": "^6.0.1",
|
|
49
|
+
"tailwindcss": "^4.2.4",
|
|
50
|
+
"vite": "catalog:",
|
|
51
|
+
"vite-plus": "catalog:"
|
|
52
|
+
},
|
|
53
|
+
"browserslist": [
|
|
54
|
+
">0.2%",
|
|
55
|
+
"not dead",
|
|
56
|
+
"not op_mini all"
|
|
57
|
+
]
|
|
58
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import Stack, { VStack } from '@nkzw/stack';
|
|
2
|
+
import { ConnectionRef, useListView, useRequest } from 'react-fate';
|
|
3
|
+
import cx from '../src/lib/cx.tsx';
|
|
4
|
+
import { Button } from '../src/ui/Button.tsx';
|
|
5
|
+
import Card from '../src/ui/Card.tsx';
|
|
6
|
+
import H2 from '../src/ui/H2.tsx';
|
|
7
|
+
import Link from '../src/ui/Link.tsx';
|
|
8
|
+
import { PostCard, PostView } from '../src/ui/PostCard.tsx';
|
|
9
|
+
import Section from '../src/ui/Section.tsx';
|
|
10
|
+
import UserCard, { UserView } from '../src/ui/UserCard.tsx';
|
|
11
|
+
|
|
12
|
+
const PostConnectionView = {
|
|
13
|
+
args: { first: 3 },
|
|
14
|
+
items: {
|
|
15
|
+
node: PostView,
|
|
16
|
+
},
|
|
17
|
+
pagination: {
|
|
18
|
+
hasNext: true,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const PostFeed = ({ posts: postsRef }: { posts: ConnectionRef<'Post'> }) => {
|
|
23
|
+
const [posts, loadNext] = useListView(PostConnectionView, postsRef);
|
|
24
|
+
|
|
25
|
+
return posts.length ? (
|
|
26
|
+
<VStack gap={16}>
|
|
27
|
+
<H2 className="pl-5">
|
|
28
|
+
<fbt desc="Latest posts header">Latest posts</fbt>
|
|
29
|
+
</H2>
|
|
30
|
+
<VStack gap={32}>
|
|
31
|
+
{posts.map(({ node }) => (
|
|
32
|
+
<PostCard key={node.id} post={node} />
|
|
33
|
+
))}
|
|
34
|
+
{loadNext ? (
|
|
35
|
+
<Stack center>
|
|
36
|
+
<Button onClick={loadNext} variant="ghost">
|
|
37
|
+
<fbt desc="Load more posts button">Load more posts</fbt>
|
|
38
|
+
</Button>
|
|
39
|
+
</Stack>
|
|
40
|
+
) : null}
|
|
41
|
+
</VStack>
|
|
42
|
+
</VStack>
|
|
43
|
+
) : null;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default function HomePage() {
|
|
47
|
+
const { posts, viewer } = useRequest({
|
|
48
|
+
posts: { list: PostConnectionView },
|
|
49
|
+
viewer: { view: UserView },
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<Section gap={32}>
|
|
54
|
+
<div
|
|
55
|
+
className={cx('grid gap-8 lg:items-stretch', viewer?.id ? 'lg:grid-cols-[1.6fr_1fr]' : '')}
|
|
56
|
+
>
|
|
57
|
+
<Card className="border border-white/20 bg-linear-to-r from-blue-500 to-sky-500 text-white dark:from-blue-600 dark:to-sky-600">
|
|
58
|
+
<Stack alignCenter gap={12} wrap>
|
|
59
|
+
<span className="squircle bg-white/20 px-2 py-1 text-xs font-semibold tracking-widest uppercase">
|
|
60
|
+
<span className="lowercase italic">fate</span> demo
|
|
61
|
+
</span>
|
|
62
|
+
</Stack>
|
|
63
|
+
<div className="space-y-3">
|
|
64
|
+
<h1 className="text-3xl leading-tight font-semibold text-balance lg:text-4xl">
|
|
65
|
+
<fbt desc="Description of fate">
|
|
66
|
+
fate is a modern data client for React inspired by Relay and GraphQL.
|
|
67
|
+
</fbt>
|
|
68
|
+
</h1>
|
|
69
|
+
<p className="text-sm text-white/80 lg:text-base">
|
|
70
|
+
<fbt desc="Tagline for fate">
|
|
71
|
+
fate combines view composition, normalized caching, data masking, Async React
|
|
72
|
+
features, and type-safe data fetching.
|
|
73
|
+
</fbt>
|
|
74
|
+
</p>
|
|
75
|
+
</div>
|
|
76
|
+
<Stack alignCenter gap={12} wrap>
|
|
77
|
+
{!viewer?.id && (
|
|
78
|
+
<>
|
|
79
|
+
<Button asChild size="sm" variant="secondary">
|
|
80
|
+
<Link className="squircle px-4 py-2 text-sm font-semibold" to="/login">
|
|
81
|
+
<fbt desc="Login button">Login</fbt>
|
|
82
|
+
</Link>
|
|
83
|
+
</Button>
|
|
84
|
+
<span className="text-sm text-white/80">
|
|
85
|
+
<fbt desc="Login button label">Sign in to post comments.</fbt>
|
|
86
|
+
</span>
|
|
87
|
+
</>
|
|
88
|
+
)}
|
|
89
|
+
</Stack>
|
|
90
|
+
</Card>
|
|
91
|
+
{viewer?.id && <UserCard user={viewer} />}
|
|
92
|
+
</div>
|
|
93
|
+
<PostFeed posts={posts} />
|
|
94
|
+
</Section>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/// <reference types="fbtee/ReactTypes.d.ts" />
|
|
2
|
+
|
|
3
|
+
import '../src/App.css';
|
|
4
|
+
import Stack from '@nkzw/stack';
|
|
5
|
+
import { httpBatchLink } from '@trpc/client';
|
|
6
|
+
import { createLocaleContext } from 'fbtee';
|
|
7
|
+
import { ReactNode, Suspense, useMemo } from 'react';
|
|
8
|
+
import { ErrorBoundary } from 'react-error-boundary';
|
|
9
|
+
import { FateClient } from 'react-fate';
|
|
10
|
+
import { createFateClient } from 'react-fate/client';
|
|
11
|
+
import AvailableLanguages from '../src/lib/AvailableLanguages.tsx';
|
|
12
|
+
import env from '../src/lib/env.tsx';
|
|
13
|
+
import Card from '../src/ui/Card.tsx';
|
|
14
|
+
import Error from '../src/ui/Error.tsx';
|
|
15
|
+
import Header from '../src/ui/Header.tsx';
|
|
16
|
+
import Section from '../src/ui/Section.tsx';
|
|
17
|
+
import AuthClient from '../src/user/AuthClient.tsx';
|
|
18
|
+
|
|
19
|
+
const Thinking = () => (
|
|
20
|
+
<Section>
|
|
21
|
+
<Stack center className="animate-pulse text-gray-500 italic" verticalPadding={48}>
|
|
22
|
+
<fbt desc="Text for thinking/loading screen">Thinking...</fbt>
|
|
23
|
+
</Stack>
|
|
24
|
+
</Section>
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const LocaleContext = createLocaleContext({
|
|
28
|
+
availableLanguages: AvailableLanguages,
|
|
29
|
+
clientLocales: [navigator.language, ...navigator.languages],
|
|
30
|
+
loadLocale: async (locale: string) => {
|
|
31
|
+
if (locale !== 'en_US' && AvailableLanguages.has(locale)) {
|
|
32
|
+
return (await import(`../src/translations/${locale}.json`)).default[locale];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {};
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export default function Layout({ children }: { children: ReactNode }) {
|
|
40
|
+
const { data: session, isPending } = AuthClient.useSession();
|
|
41
|
+
const userId = session?.user.id;
|
|
42
|
+
|
|
43
|
+
const fate = useMemo(
|
|
44
|
+
() =>
|
|
45
|
+
createFateClient({
|
|
46
|
+
links: [
|
|
47
|
+
httpBatchLink({
|
|
48
|
+
fetch: (input, init) =>
|
|
49
|
+
fetch(input, {
|
|
50
|
+
...init,
|
|
51
|
+
credentials: userId ? 'include' : undefined,
|
|
52
|
+
}),
|
|
53
|
+
url: `${env('SERVER_URL')}/trpc`,
|
|
54
|
+
}),
|
|
55
|
+
],
|
|
56
|
+
liveUrl: `${env('SERVER_URL')}/fate`,
|
|
57
|
+
...(userId
|
|
58
|
+
? {
|
|
59
|
+
fetch: (input: RequestInfo | URL, init?: RequestInit) =>
|
|
60
|
+
fetch(input, {
|
|
61
|
+
...init,
|
|
62
|
+
credentials: 'include',
|
|
63
|
+
}),
|
|
64
|
+
}
|
|
65
|
+
: null),
|
|
66
|
+
}),
|
|
67
|
+
[userId],
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
if (isPending) {
|
|
71
|
+
return (
|
|
72
|
+
<LocaleContext>
|
|
73
|
+
<div className="min-h-screen bg-background text-foreground">
|
|
74
|
+
<div className="min-h-screen bg-[radial-gradient(circle_at_20%_20%,rgba(59,130,246,0.08),transparent_35%),radial-gradient(circle_at_80%_0,rgba(99,102,241,0.08),transparent_28%)]">
|
|
75
|
+
<Thinking />
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</LocaleContext>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<LocaleContext>
|
|
84
|
+
<FateClient client={fate} key={userId}>
|
|
85
|
+
<div className="min-h-screen bg-background text-foreground">
|
|
86
|
+
<div className="min-h-screen bg-[radial-gradient(circle_at_20%_20%,rgba(59,130,246,0.08),transparent_35%),radial-gradient(circle_at_80%_0,rgba(99,102,241,0.08),transparent_28%)]">
|
|
87
|
+
<Header />
|
|
88
|
+
<ErrorBoundary
|
|
89
|
+
fallbackRender={({ error }) => (
|
|
90
|
+
<Section>
|
|
91
|
+
<Card>
|
|
92
|
+
<Error error={error} />
|
|
93
|
+
</Card>
|
|
94
|
+
</Section>
|
|
95
|
+
)}
|
|
96
|
+
>
|
|
97
|
+
<Suspense fallback={<Thinking />}>{children}</Suspense>
|
|
98
|
+
</ErrorBoundary>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</FateClient>
|
|
102
|
+
</LocaleContext>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import Stack, { VStack } from '@nkzw/stack';
|
|
2
|
+
import { useRouter } from '@void/react';
|
|
3
|
+
import { fbs } from 'fbtee';
|
|
4
|
+
import { ExternalLinkIcon } from 'lucide-react';
|
|
5
|
+
import { useActionState, useEffect, useState } from 'react';
|
|
6
|
+
import { AsyncButton } from '../src/ui/Button.tsx';
|
|
7
|
+
import Card from '../src/ui/Card.tsx';
|
|
8
|
+
import H2 from '../src/ui/H2.tsx';
|
|
9
|
+
import Input from '../src/ui/Input.tsx';
|
|
10
|
+
import Section from '../src/ui/Section.tsx';
|
|
11
|
+
import AuthClient from '../src/user/AuthClient.tsx';
|
|
12
|
+
|
|
13
|
+
export default function LoginPage() {
|
|
14
|
+
const [email, setEmail] = useState('');
|
|
15
|
+
const [password, setPassword] = useState('');
|
|
16
|
+
const router = useRouter();
|
|
17
|
+
const { data: session } = AuthClient.useSession();
|
|
18
|
+
|
|
19
|
+
const [, signInAction] = useActionState(async () => {
|
|
20
|
+
await AuthClient.signIn.email(
|
|
21
|
+
{
|
|
22
|
+
email,
|
|
23
|
+
password,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
onError: () => {},
|
|
27
|
+
onRequest: () => {},
|
|
28
|
+
onSuccess: () => {
|
|
29
|
+
router.flushAll();
|
|
30
|
+
void router.visit('/', { replace: true });
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return null;
|
|
36
|
+
}, null);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (session) {
|
|
40
|
+
router.flushAll();
|
|
41
|
+
void router.visit('/', { replace: true });
|
|
42
|
+
}
|
|
43
|
+
}, [router, session]);
|
|
44
|
+
|
|
45
|
+
if (session) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Section>
|
|
51
|
+
<VStack center gap={16}>
|
|
52
|
+
<H2 className="pl-5">
|
|
53
|
+
<fbt desc="Sign In headline">Sign In</fbt>
|
|
54
|
+
</H2>
|
|
55
|
+
<Stack gap={32} wrap>
|
|
56
|
+
<Card className="w-84">
|
|
57
|
+
<Stack gap vertical>
|
|
58
|
+
<VStack action={signInAction} as="form" gap={12}>
|
|
59
|
+
<Input
|
|
60
|
+
className="w-48"
|
|
61
|
+
name="email"
|
|
62
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
63
|
+
placeholder={fbs('email', 'Email placeholder')}
|
|
64
|
+
type="email"
|
|
65
|
+
value={email}
|
|
66
|
+
/>
|
|
67
|
+
<Input
|
|
68
|
+
className="w-48"
|
|
69
|
+
name="password"
|
|
70
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
71
|
+
placeholder={fbs('password', 'Password placeholder')}
|
|
72
|
+
type="password"
|
|
73
|
+
value={password}
|
|
74
|
+
/>
|
|
75
|
+
<div>
|
|
76
|
+
<AsyncButton type="submit" variant="outline">
|
|
77
|
+
<fbt desc="Sign in button">Sign In</fbt>
|
|
78
|
+
</AsyncButton>
|
|
79
|
+
</div>
|
|
80
|
+
</VStack>
|
|
81
|
+
</Stack>
|
|
82
|
+
</Card>
|
|
83
|
+
<Card className="w-84">
|
|
84
|
+
<p>
|
|
85
|
+
<fbt desc="Login details">
|
|
86
|
+
Try one of the
|
|
87
|
+
<Stack
|
|
88
|
+
alignCenter
|
|
89
|
+
as="a"
|
|
90
|
+
className="inline-flex! px-1 underline hover:no-underline"
|
|
91
|
+
gap={4}
|
|
92
|
+
href="https://github.com/nkzw-tech/fate/blob/main/packages/create-fate/templates/fate/drizzle/server/src/drizzle/seedData.ts#L1"
|
|
93
|
+
rel="noreferrer"
|
|
94
|
+
target="_blank"
|
|
95
|
+
>
|
|
96
|
+
Example Accounts
|
|
97
|
+
<ExternalLinkIcon className="h-4 w-4" />
|
|
98
|
+
</Stack>{' '}
|
|
99
|
+
in the seed data.
|
|
100
|
+
</fbt>
|
|
101
|
+
</p>
|
|
102
|
+
</Card>
|
|
103
|
+
</Stack>
|
|
104
|
+
</VStack>
|
|
105
|
+
</Section>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useParams } from '@void/react';
|
|
2
|
+
import { useRequest } from 'react-fate';
|
|
3
|
+
import { PostCard, PostView } from '../../src/ui/PostCard.tsx';
|
|
4
|
+
import Section from '../../src/ui/Section.tsx';
|
|
5
|
+
|
|
6
|
+
export default function PostPage() {
|
|
7
|
+
const { id } = useParams<{ id: string }>();
|
|
8
|
+
|
|
9
|
+
if (!id) {
|
|
10
|
+
throw new Error('fate: Post ID is required.');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { post } = useRequest({
|
|
14
|
+
post: { id, view: PostView },
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Section>
|
|
19
|
+
<PostCard detail post={post} />
|
|
20
|
+
</Section>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { Comment, Post } from '@app/server/src/trpc/views.ts';
|
|
2
|
+
import Stack, { VStack } from '@nkzw/stack';
|
|
3
|
+
import { fbs } from 'fbtee';
|
|
4
|
+
import { Suspense, useDeferredValue, useState } from 'react';
|
|
5
|
+
import { ErrorBoundary } from 'react-error-boundary';
|
|
6
|
+
import { useRequest, useView, view, ViewRef } from 'react-fate';
|
|
7
|
+
import cx from '../src/lib/cx.tsx';
|
|
8
|
+
import Card from '../src/ui/Card.tsx';
|
|
9
|
+
import CommentCard, { CommentView } from '../src/ui/CommentCard.tsx';
|
|
10
|
+
import Error from '../src/ui/Error.tsx';
|
|
11
|
+
import Input from '../src/ui/Input.tsx';
|
|
12
|
+
import Section from '../src/ui/Section.tsx';
|
|
13
|
+
|
|
14
|
+
const CommentPostView = view<Post>()({
|
|
15
|
+
commentCount: true,
|
|
16
|
+
id: true,
|
|
17
|
+
title: true,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const CommentSearchView = view<Comment>()({
|
|
21
|
+
...CommentView,
|
|
22
|
+
id: true,
|
|
23
|
+
post: CommentPostView,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const CommentResult = ({ comment: commentRef }: { comment: ViewRef<'Comment'> }) => {
|
|
27
|
+
const comment = useView(CommentSearchView, commentRef);
|
|
28
|
+
const post = useView(CommentPostView, comment.post);
|
|
29
|
+
|
|
30
|
+
return <CommentCard comment={comment} link post={post} />;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const SearchResults = ({ isStale, query }: { isStale: boolean; query: string }) => {
|
|
34
|
+
const { commentSearch } = useRequest({
|
|
35
|
+
commentSearch: { args: { query }, list: CommentSearchView },
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (commentSearch.length === 0) {
|
|
39
|
+
return (
|
|
40
|
+
<p>
|
|
41
|
+
<fbt desc="No matches text">
|
|
42
|
+
No matches for{' '}
|
|
43
|
+
<i>
|
|
44
|
+
"<fbt:param name="query">{query}</fbt:param>"
|
|
45
|
+
</i>
|
|
46
|
+
</fbt>
|
|
47
|
+
</p>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<VStack className={cx(isStale && 'opacity-50')} gap={12}>
|
|
53
|
+
{commentSearch.map((comment) => (
|
|
54
|
+
<CommentResult comment={comment} key={comment.id} />
|
|
55
|
+
))}
|
|
56
|
+
</VStack>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default function SearchPage() {
|
|
61
|
+
const [query, setQuery] = useState('');
|
|
62
|
+
const deferredQuery = useDeferredValue(query);
|
|
63
|
+
const isStale = query !== deferredQuery;
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<Section>
|
|
67
|
+
<Card>
|
|
68
|
+
<Stack alignCenter between gap={16}>
|
|
69
|
+
<Input
|
|
70
|
+
className="w-64"
|
|
71
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
72
|
+
placeholder={fbs('Search comments...', 'Search input placeholder')}
|
|
73
|
+
ref={(ref) => ref?.focus()}
|
|
74
|
+
value={query}
|
|
75
|
+
/>
|
|
76
|
+
<div className="text-xs text-muted-foreground">
|
|
77
|
+
<fbt desc="Slowdown label explanation">500ms artificial slowdown</fbt>
|
|
78
|
+
</div>
|
|
79
|
+
</Stack>
|
|
80
|
+
|
|
81
|
+
<ErrorBoundary FallbackComponent={Error}>
|
|
82
|
+
<Suspense
|
|
83
|
+
fallback={
|
|
84
|
+
<h2>
|
|
85
|
+
<fbt desc="Text for thinking/loading screen">Thinking…</fbt>
|
|
86
|
+
</h2>
|
|
87
|
+
}
|
|
88
|
+
>
|
|
89
|
+
{query.trim().length > 0 ? <SearchResults isStale={isStale} query={query} /> : null}
|
|
90
|
+
</Suspense>
|
|
91
|
+
</ErrorBoundary>
|
|
92
|
+
</Card>
|
|
93
|
+
</Section>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMax" viewBox="0 0 160 160"><path d="M108.301 17.465h.272c1.797-.005 3.563.177 5.331.496l.349.063c5.588 1.02 10.731 3.447 15.201 6.933l.347.267c1.579 1.26 3.083 2.684 4.386 4.227l.161.186c4.018 4.719 6.924 10.686 7.767 16.852l.033.239c.262 1.848.351 3.688.352 5.551l.001.307c-.001 1.623-.092 3.188-.386 4.788l-.05.292c-1.003 5.874-3.476 11.447-7.286 16.034l-.29.358c-1.422 1.721-3.011 3.382-4.797 4.729l-.444.354c-3.046 2.405-6.436 4.147-10.084 5.442l-.497.186c-1.766.627-3.615.974-5.465 1.266l-.364.058c-1.591.238-3.185.286-4.791.281h-.499l-1.339-.002h-1.408c-.886-.001-1.772-.001-2.656-.003-3.085-.001-6.163-.005-9.247-.008v-12.78l3.606-.006 2.282-.007c1.205-.006 2.407-.01 3.611-.011.878 0 1.754-.003 2.63-.007.462-.002.928-.005 1.389-.002 6.302.01 12.003-1.72 16.65-6.117l.291-.272c3.951-3.807 6.097-9.302 6.241-14.736.04-3.078-.476-6.048-1.68-8.892l-.104-.244c-2.382-5.559-6.98-9.439-12.504-11.675-2.204-.816-4.522-1.16-6.863-1.134-.301.004-.606.004-.909.001-1.677.003-3.304.127-4.924.591l-.331.098c-2.625.758-5.093 1.928-7.254 3.607l-.323.249c-1.046.817-2.031 1.682-2.873 2.71l-.154.179c-2.354 2.782-3.9 5.988-4.727 9.529l-.054.229c-.391 1.8-.423 3.633-.42 5.471l-.001.74c.002 1.4.001 2.801-.004 4.201l-.004 3.779c0 1.824-.001 3.649-.003 5.473l-.006 2.813-.005 6.076-.001.267-.008 8.614c-.001 2.023-.004 4.045-.006 6.066v.259l-.004 2.55-.006 5.46c0 1.331-.001 2.658-.003 3.985-.002.72-.002 1.438-.002 2.156 0 .658 0 1.317-.002 1.978v.708c.004 2.098-.07 4.214-.545 6.263l-.077.359-.161.716c-.071.302-.136.605-.199.909-.55 2.538-1.6 5.067-2.832 7.348l-.214.42c-.965 1.921-2.22 3.714-3.574 5.38l-.152.183c-.78.973-1.639 1.866-2.519 2.751l-.308.313a25.81 25.81 0 0 1-2.226 1.955l-.22.179c-3.698 2.953-7.975 5.044-12.538 6.262-.212.056-.422.115-.631.176-8.366 2.287-17.8.738-25.278-3.512-3.639-2.122-7.171-4.945-9.721-8.307-.121-.15-.241-.298-.356-.448a36.247 36.247 0 0 1-2.605-3.814l-.144-.234c-.889-1.466-1.562-3.034-2.222-4.617l-.11-.265c-3.253-7.791-2.911-17.367.184-25.133.865-2.069 1.869-4.089 3.123-5.951l.172-.261a31.91 31.91 0 0 1 2.547-3.29l.229-.263c.13-.152.266-.304.396-.452l.425-.493c1.216-1.389 2.593-2.615 4.068-3.723.208-.157.414-.315.62-.476 1.184-.901 2.437-1.665 3.729-2.4l.272-.159c1.586-.893 3.235-1.573 4.934-2.21l.253-.097c4.232-1.567 8.615-1.836 13.079-1.82 1.359 0 2.732.002 4.093.007 1.209.005 2.417.006 3.621.006 1.899.002 3.794.007 5.688.012v12.78l-3.612.013-2.282.011c-1.201.005-2.401.012-3.61.016-.876.003-1.751.005-2.629.013-.463.002-.924.005-1.387.006-4.322.005-8.193.592-12.033 2.662l-.217.114c-1.458.773-2.697 1.731-3.908 2.849-.206.185-.413.362-.63.535-.435.376-.794.788-1.16 1.232l-.153.18a21.18 21.18 0 0 0-4.474 8.651l-.094.342c-.42 1.682-.54 3.395-.536 5.127v.272c.019 5.672 2.392 10.671 6.206 14.769l.215.237c3.296 3.453 8.483 5.917 13.247 6.046.448.008.896.01 1.345.01.276 0 .553.003.835.005 4.917.019 9.864-1.73 13.577-4.995l.303-.267c.299-.263.592-.527.881-.799l.262-.247c.787-.756 1.451-1.599 2.104-2.474l.151-.201c1.929-2.606 3.117-5.723 3.635-8.909l.059-.362c.194-1.296.21-2.573.207-3.883l.003-.74v-2.02c0-.73 0-1.459.005-2.188v-3.782c0-1.827.004-3.654.007-5.481 0-.939 0-1.877.004-2.816l.003-6.082v-.268c.005-2.876.008-5.75.008-8.627l.013-8.883c.004-1.823.004-3.646.004-5.469 0-1.33.004-2.659.004-3.989.003-.721.003-1.442.003-2.159v-1.98c.005-.237.005-.473 0-.711-.011-8.523 3.072-17.023 8.941-23.268.244-.263.483-.531.717-.805.416-.48.861-.918 1.324-1.352l.252-.232a26.259 26.259 0 0 1 2.184-1.812l.46-.349c1.39-1.058 2.837-1.951 4.375-2.775l.318-.172c5.005-2.666 10.5-3.877 16.15-3.889Z" style="transform-box:fill-box;transform-origin:50% 50%" transform="rotate(-31 0 0)"/></svg>
|