@pylonsync/create-pylon 0.3.274 → 0.3.276
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/bin/create-pylon.js +80 -0
- package/package.json +1 -1
- package/templates/ARCHETYPES.md +339 -0
- package/templates/agency/.env.example +12 -0
- package/templates/agency/AGENTS.md +61 -0
- package/templates/agency/README.md +90 -0
- package/templates/agency/app/auth-form.tsx +129 -0
- package/templates/agency/app/contact-form.tsx +258 -0
- package/templates/agency/app/dashboard/dashboard-client.tsx +1440 -0
- package/templates/agency/app/dashboard/page.tsx +70 -0
- package/templates/agency/app/error.tsx +26 -0
- package/templates/agency/app/globals.css +148 -0
- package/templates/agency/app/layout.tsx +174 -0
- package/templates/agency/app/login/page.tsx +39 -0
- package/templates/agency/app/not-found.tsx +19 -0
- package/templates/agency/app/page.tsx +249 -0
- package/templates/agency/app/robots.ts +12 -0
- package/templates/agency/app/seeder.tsx +26 -0
- package/templates/agency/app/sitemap.ts +9 -0
- package/templates/agency/app/work/[slug]/page.tsx +182 -0
- package/templates/agency/app/work/page.tsx +83 -0
- package/templates/agency/app.ts +284 -0
- package/templates/agency/components/marketing.tsx +187 -0
- package/templates/agency/components/section-scroller.tsx +35 -0
- package/templates/agency/components/ui/button.tsx +56 -0
- package/templates/agency/components/ui/card.tsx +90 -0
- package/templates/agency/components.json +20 -0
- package/templates/agency/functions/bookInquiry.ts +42 -0
- package/templates/agency/functions/clientsForOwner.ts +27 -0
- package/templates/agency/functions/declineInquiry.ts +41 -0
- package/templates/agency/functions/deleteClient.ts +27 -0
- package/templates/agency/functions/deleteInvoice.ts +19 -0
- package/templates/agency/functions/deleteProject.ts +20 -0
- package/templates/agency/functions/inquiriesForOwner.ts +31 -0
- package/templates/agency/functions/invoicesForOwner.ts +27 -0
- package/templates/agency/functions/seedCapacity.ts +26 -0
- package/templates/agency/functions/seedProjects.ts +41 -0
- package/templates/agency/functions/seedStudioBackoffice.ts +74 -0
- package/templates/agency/functions/setCapacity.ts +32 -0
- package/templates/agency/functions/setInvoiceStatus.ts +27 -0
- package/templates/agency/functions/setProjectFlags.ts +35 -0
- package/templates/agency/functions/submitInquiry.ts +55 -0
- package/templates/agency/functions/upsertClient.ts +73 -0
- package/templates/agency/functions/upsertInvoice.ts +113 -0
- package/templates/agency/functions/upsertProject.ts +97 -0
- package/templates/agency/gitignore +10 -0
- package/templates/agency/lib/agency.ts +189 -0
- package/templates/agency/lib/invoice-pdf.tsx +174 -0
- package/templates/agency/lib/owner.ts +26 -0
- package/templates/agency/lib/site.config.ts +418 -0
- package/templates/agency/lib/utils.ts +10 -0
- package/templates/agency/package.json +35 -0
- package/templates/agency/tsconfig.json +18 -0
- package/templates/ai-chat/.env.example +33 -0
- package/templates/ai-chat/AGENTS.md +61 -0
- package/templates/ai-chat/README.md +99 -0
- package/templates/ai-chat/app/auth-form.tsx +124 -0
- package/templates/ai-chat/app/chat-client.tsx +727 -0
- package/templates/ai-chat/app/error.tsx +26 -0
- package/templates/ai-chat/app/globals.css +148 -0
- package/templates/ai-chat/app/layout.tsx +75 -0
- package/templates/ai-chat/app/login/page.tsx +39 -0
- package/templates/ai-chat/app/not-found.tsx +19 -0
- package/templates/ai-chat/app/page.tsx +23 -0
- package/templates/ai-chat/app.ts +121 -0
- package/templates/ai-chat/components.json +20 -0
- package/templates/ai-chat/functions/deleteConversation.ts +33 -0
- package/templates/ai-chat/gitignore +10 -0
- package/templates/ai-chat/lib/site.config.ts +103 -0
- package/templates/ai-chat/lib/utils.ts +10 -0
- package/templates/ai-chat/package.json +34 -0
- package/templates/ai-chat/tsconfig.json +18 -0
- package/templates/ai-studio/.env.example +19 -0
- package/templates/ai-studio/AGENTS.md +61 -0
- package/templates/ai-studio/README.md +83 -0
- package/templates/ai-studio/app/auth-form.tsx +124 -0
- package/templates/ai-studio/app/error.tsx +26 -0
- package/templates/ai-studio/app/globals.css +148 -0
- package/templates/ai-studio/app/layout.tsx +75 -0
- package/templates/ai-studio/app/login/page.tsx +39 -0
- package/templates/ai-studio/app/not-found.tsx +19 -0
- package/templates/ai-studio/app/page.tsx +34 -0
- package/templates/ai-studio/app/studio-client.tsx +357 -0
- package/templates/ai-studio/app.ts +108 -0
- package/templates/ai-studio/components.json +20 -0
- package/templates/ai-studio/functions/_getGeneration.ts +25 -0
- package/templates/ai-studio/functions/_updateGeneration.ts +37 -0
- package/templates/ai-studio/functions/generate.ts +42 -0
- package/templates/ai-studio/functions/pollGeneration.ts +134 -0
- package/templates/ai-studio/gitignore +10 -0
- package/templates/ai-studio/lib/site.config.ts +80 -0
- package/templates/ai-studio/lib/studio.ts +52 -0
- package/templates/ai-studio/lib/utils.ts +10 -0
- package/templates/ai-studio/package.json +34 -0
- package/templates/ai-studio/tsconfig.json +18 -0
- package/templates/creator/.env.example +12 -0
- package/templates/creator/AGENTS.md +61 -0
- package/templates/creator/README.md +67 -0
- package/templates/creator/app/auth-form.tsx +129 -0
- package/templates/creator/app/dashboard/dashboard-client.tsx +297 -0
- package/templates/creator/app/dashboard/page.tsx +70 -0
- package/templates/creator/app/error.tsx +26 -0
- package/templates/creator/app/globals.css +148 -0
- package/templates/creator/app/layout.tsx +160 -0
- package/templates/creator/app/login/page.tsx +39 -0
- package/templates/creator/app/newsletter-signup.tsx +162 -0
- package/templates/creator/app/not-found.tsx +19 -0
- package/templates/creator/app/page.tsx +160 -0
- package/templates/creator/app/robots.ts +12 -0
- package/templates/creator/app/sitemap.ts +9 -0
- package/templates/creator/app.ts +134 -0
- package/templates/creator/components/marketing.tsx +148 -0
- package/templates/creator/components/section-scroller.tsx +35 -0
- package/templates/creator/components/ui/button.tsx +56 -0
- package/templates/creator/components/ui/card.tsx +90 -0
- package/templates/creator/components.json +20 -0
- package/templates/creator/functions/subscribe.ts +82 -0
- package/templates/creator/functions/subscriberStats.ts +75 -0
- package/templates/creator/gitignore +10 -0
- package/templates/creator/lib/owner.ts +26 -0
- package/templates/creator/lib/site.config.ts +173 -0
- package/templates/creator/lib/stats.ts +30 -0
- package/templates/creator/lib/utils.ts +10 -0
- package/templates/creator/package.json +34 -0
- package/templates/creator/tsconfig.json +18 -0
- package/templates/default/app/layout.tsx +26 -27
- package/templates/default/app/page.tsx +90 -274
- package/templates/default/lib/products.ts +9 -122
- package/templates/default/lib/site.config.ts +739 -0
- package/templates/default/lib/site.ts +14 -261
- package/templates/directory/.env.example +12 -0
- package/templates/directory/AGENTS.md +61 -0
- package/templates/directory/README.md +80 -0
- package/templates/directory/app/auth-form.tsx +129 -0
- package/templates/directory/app/dashboard/dashboard-client.tsx +205 -0
- package/templates/directory/app/dashboard/page.tsx +70 -0
- package/templates/directory/app/directory-browse.tsx +328 -0
- package/templates/directory/app/error.tsx +26 -0
- package/templates/directory/app/globals.css +148 -0
- package/templates/directory/app/layout.tsx +171 -0
- package/templates/directory/app/login/page.tsx +39 -0
- package/templates/directory/app/not-found.tsx +19 -0
- package/templates/directory/app/page.tsx +50 -0
- package/templates/directory/app/robots.ts +12 -0
- package/templates/directory/app/sitemap.ts +9 -0
- package/templates/directory/app/submit/page.tsx +30 -0
- package/templates/directory/app/submit-form.tsx +151 -0
- package/templates/directory/app.ts +146 -0
- package/templates/directory/components/marketing.tsx +148 -0
- package/templates/directory/components/section-scroller.tsx +35 -0
- package/templates/directory/components/ui/button.tsx +56 -0
- package/templates/directory/components/ui/card.tsx +90 -0
- package/templates/directory/components.json +20 -0
- package/templates/directory/functions/approveSubmission.ts +45 -0
- package/templates/directory/functions/rejectSubmission.ts +20 -0
- package/templates/directory/functions/seedListings.ts +33 -0
- package/templates/directory/functions/submissionsForOwner.ts +29 -0
- package/templates/directory/functions/submitListing.ts +63 -0
- package/templates/directory/functions/upvote.ts +24 -0
- package/templates/directory/gitignore +10 -0
- package/templates/directory/lib/directory.ts +45 -0
- package/templates/directory/lib/owner.ts +26 -0
- package/templates/directory/lib/site.config.ts +130 -0
- package/templates/directory/lib/utils.ts +10 -0
- package/templates/directory/package.json +34 -0
- package/templates/directory/tsconfig.json +18 -0
- package/templates/local-service/.env.example +12 -0
- package/templates/local-service/AGENTS.md +61 -0
- package/templates/local-service/README.md +82 -0
- package/templates/local-service/app/auth-form.tsx +129 -0
- package/templates/local-service/app/booking-widget.tsx +399 -0
- package/templates/local-service/app/dashboard/dashboard-client.tsx +304 -0
- package/templates/local-service/app/dashboard/page.tsx +63 -0
- package/templates/local-service/app/error.tsx +26 -0
- package/templates/local-service/app/globals.css +148 -0
- package/templates/local-service/app/layout.tsx +151 -0
- package/templates/local-service/app/login/page.tsx +39 -0
- package/templates/local-service/app/not-found.tsx +19 -0
- package/templates/local-service/app/page.tsx +233 -0
- package/templates/local-service/app/robots.ts +12 -0
- package/templates/local-service/app/sitemap.ts +9 -0
- package/templates/local-service/app.ts +131 -0
- package/templates/local-service/components/marketing.tsx +162 -0
- package/templates/local-service/components/section-scroller.tsx +35 -0
- package/templates/local-service/components/ui/button.tsx +56 -0
- package/templates/local-service/components/ui/card.tsx +90 -0
- package/templates/local-service/components.json +20 -0
- package/templates/local-service/functions/bookingsForOwner.ts +30 -0
- package/templates/local-service/functions/cancelBooking.ts +27 -0
- package/templates/local-service/functions/confirmBooking.ts +18 -0
- package/templates/local-service/functions/createBooking.ts +98 -0
- package/templates/local-service/gitignore +10 -0
- package/templates/local-service/lib/booking.ts +24 -0
- package/templates/local-service/lib/owner.ts +26 -0
- package/templates/local-service/lib/site.config.ts +232 -0
- package/templates/local-service/lib/slots.ts +97 -0
- package/templates/local-service/lib/utils.ts +10 -0
- package/templates/local-service/package.json +34 -0
- package/templates/local-service/tsconfig.json +18 -0
- package/templates/marketplace/.env.example +9 -0
- package/templates/marketplace/AGENTS.md +61 -0
- package/templates/marketplace/README.md +78 -0
- package/templates/marketplace/app/_components/CategoryIcon.tsx +40 -0
- package/templates/marketplace/app/error.tsx +26 -0
- package/templates/marketplace/app/globals.css +64 -0
- package/templates/marketplace/app/layout.tsx +60 -0
- package/templates/marketplace/app/listing/[id]/page.tsx +163 -0
- package/templates/marketplace/app/me/page.tsx +15 -0
- package/templates/marketplace/app/not-found.tsx +20 -0
- package/templates/marketplace/app/page.tsx +159 -0
- package/templates/marketplace/app/robots.ts +12 -0
- package/templates/marketplace/app/sell/page.tsx +26 -0
- package/templates/marketplace/app/sitemap.ts +14 -0
- package/templates/marketplace/app.ts +190 -0
- package/templates/marketplace/client/AuthNav.tsx +46 -0
- package/templates/marketplace/client/LiveTicker.tsx +104 -0
- package/templates/marketplace/client/LoginCard.tsx +130 -0
- package/templates/marketplace/client/MarketProvider.tsx +148 -0
- package/templates/marketplace/client/MyMarket.tsx +180 -0
- package/templates/marketplace/client/OfferPanel.tsx +355 -0
- package/templates/marketplace/client/SeedOnEmpty.tsx +26 -0
- package/templates/marketplace/client/SellForm.tsx +160 -0
- package/templates/marketplace/client/WatchButton.tsx +88 -0
- package/templates/marketplace/client/market.ts +341 -0
- package/templates/marketplace/functions/buyNow.ts +78 -0
- package/templates/marketplace/functions/makeOffer.ts +65 -0
- package/templates/marketplace/functions/respondToOffer.ts +62 -0
- package/templates/marketplace/functions/seedMarket.ts +90 -0
- package/templates/marketplace/gitignore +10 -0
- package/templates/marketplace/package.json +35 -0
- package/templates/marketplace/tsconfig.json +14 -0
- package/templates/marketplace/ui/badge.tsx +30 -0
- package/templates/marketplace/ui/button.tsx +49 -0
- package/templates/marketplace/ui/card.tsx +48 -0
- package/templates/marketplace/ui/input.tsx +17 -0
- package/templates/marketplace/ui/label.tsx +18 -0
- package/templates/marketplace/ui/textarea.tsx +17 -0
- package/templates/marketplace/ui/tokens.css +32 -0
- package/templates/marketplace/ui/utils.ts +6 -0
- package/templates/restaurant/.env.example +12 -0
- package/templates/restaurant/AGENTS.md +61 -0
- package/templates/restaurant/README.md +77 -0
- package/templates/restaurant/app/auth-form.tsx +129 -0
- package/templates/restaurant/app/dashboard/dashboard-client.tsx +263 -0
- package/templates/restaurant/app/dashboard/page.tsx +59 -0
- package/templates/restaurant/app/error.tsx +26 -0
- package/templates/restaurant/app/globals.css +148 -0
- package/templates/restaurant/app/layout.tsx +151 -0
- package/templates/restaurant/app/login/page.tsx +39 -0
- package/templates/restaurant/app/not-found.tsx +19 -0
- package/templates/restaurant/app/page.tsx +194 -0
- package/templates/restaurant/app/reservation-widget.tsx +359 -0
- package/templates/restaurant/app/robots.ts +12 -0
- package/templates/restaurant/app/sitemap.ts +9 -0
- package/templates/restaurant/app.ts +115 -0
- package/templates/restaurant/components/marketing.tsx +162 -0
- package/templates/restaurant/components/section-scroller.tsx +35 -0
- package/templates/restaurant/components/ui/button.tsx +56 -0
- package/templates/restaurant/components/ui/card.tsx +90 -0
- package/templates/restaurant/components.json +20 -0
- package/templates/restaurant/functions/cancelReservation.ts +26 -0
- package/templates/restaurant/functions/confirmReservation.ts +17 -0
- package/templates/restaurant/functions/createReservation.ts +92 -0
- package/templates/restaurant/functions/reservationsForOwner.ts +28 -0
- package/templates/restaurant/gitignore +10 -0
- package/templates/restaurant/lib/owner.ts +26 -0
- package/templates/restaurant/lib/reservation.ts +22 -0
- package/templates/restaurant/lib/site.config.ts +218 -0
- package/templates/restaurant/lib/slots.ts +55 -0
- package/templates/restaurant/lib/utils.ts +10 -0
- package/templates/restaurant/package.json +34 -0
- package/templates/restaurant/tsconfig.json +18 -0
- package/templates/shop/.env.example +32 -0
- package/templates/shop/AGENTS.md +61 -0
- package/templates/shop/README.md +102 -0
- package/templates/shop/app/auth-form.tsx +129 -0
- package/templates/shop/app/dashboard/dashboard-client.tsx +264 -0
- package/templates/shop/app/dashboard/page.tsx +59 -0
- package/templates/shop/app/error.tsx +26 -0
- package/templates/shop/app/globals.css +148 -0
- package/templates/shop/app/layout.tsx +160 -0
- package/templates/shop/app/login/page.tsx +39 -0
- package/templates/shop/app/not-found.tsx +19 -0
- package/templates/shop/app/page.tsx +95 -0
- package/templates/shop/app/robots.ts +12 -0
- package/templates/shop/app/shop-client.tsx +436 -0
- package/templates/shop/app/sitemap.ts +9 -0
- package/templates/shop/app/success/page.tsx +33 -0
- package/templates/shop/app.ts +134 -0
- package/templates/shop/components/marketing.tsx +96 -0
- package/templates/shop/components/section-scroller.tsx +35 -0
- package/templates/shop/components/ui/button.tsx +56 -0
- package/templates/shop/components/ui/card.tsx +90 -0
- package/templates/shop/components.json +20 -0
- package/templates/shop/functions/cancelOrder.ts +33 -0
- package/templates/shop/functions/checkout.ts +130 -0
- package/templates/shop/functions/fulfillOrder.ts +17 -0
- package/templates/shop/functions/markGroupPaid.ts +26 -0
- package/templates/shop/functions/ordersForOwner.ts +28 -0
- package/templates/shop/functions/releaseGroup.ts +36 -0
- package/templates/shop/functions/reserveCart.ts +87 -0
- package/templates/shop/functions/restockProduct.ts +23 -0
- package/templates/shop/functions/seedProducts.ts +30 -0
- package/templates/shop/functions/stripeWebhook.ts +72 -0
- package/templates/shop/gitignore +10 -0
- package/templates/shop/lib/owner.ts +26 -0
- package/templates/shop/lib/shop.ts +45 -0
- package/templates/shop/lib/site.config.ts +198 -0
- package/templates/shop/lib/utils.ts +10 -0
- package/templates/shop/package.json +35 -0
- package/templates/shop/tsconfig.json +18 -0
- package/templates/waitlist/.env.example +12 -0
- package/templates/waitlist/AGENTS.md +61 -0
- package/templates/waitlist/README.md +81 -0
- package/templates/waitlist/app/auth-form.tsx +129 -0
- package/templates/waitlist/app/dashboard/dashboard-client.tsx +297 -0
- package/templates/waitlist/app/dashboard/page.tsx +70 -0
- package/templates/waitlist/app/error.tsx +26 -0
- package/templates/waitlist/app/globals.css +148 -0
- package/templates/waitlist/app/layout.tsx +158 -0
- package/templates/waitlist/app/login/page.tsx +39 -0
- package/templates/waitlist/app/not-found.tsx +19 -0
- package/templates/waitlist/app/page.tsx +119 -0
- package/templates/waitlist/app/robots.ts +12 -0
- package/templates/waitlist/app/sitemap.ts +9 -0
- package/templates/waitlist/app/waitlist-hero.tsx +219 -0
- package/templates/waitlist/app.ts +134 -0
- package/templates/waitlist/components/marketing.tsx +96 -0
- package/templates/waitlist/components/ui/button.tsx +56 -0
- package/templates/waitlist/components/ui/card.tsx +90 -0
- package/templates/waitlist/components.json +20 -0
- package/templates/waitlist/functions/joinWaitlist.ts +82 -0
- package/templates/waitlist/functions/waitlistStats.ts +75 -0
- package/templates/waitlist/gitignore +10 -0
- package/templates/waitlist/lib/owner.ts +26 -0
- package/templates/waitlist/lib/site.config.ts +178 -0
- package/templates/waitlist/lib/stats.ts +30 -0
- package/templates/waitlist/lib/utils.ts +10 -0
- package/templates/waitlist/package.json +34 -0
- package/templates/waitlist/tsconfig.json +18 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# __APP_NAME__
|
|
2
|
+
|
|
3
|
+
A streaming **AI chat** app built with [Pylon](https://pylonsync.com) — token
|
|
4
|
+
streaming, multi-conversation history, and realtime cross-tab sync, all from one
|
|
5
|
+
binary on one port. No Next.js, no separate API server.
|
|
6
|
+
|
|
7
|
+
Tokens stream from the built-in `POST /api/ai/stream` endpoint, so your provider
|
|
8
|
+
API key never reaches the browser. Conversations are sync-backed and
|
|
9
|
+
owner-scoped — open two tabs and a chat you send in one shows up in the other.
|
|
10
|
+
|
|
11
|
+
## Develop
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
__RUN_DEV__
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Open http://localhost:4321. You can create chats right away; to get **replies**,
|
|
18
|
+
point it at an LLM provider (see below). Then **open a second tab** — your
|
|
19
|
+
conversations and messages stay in lockstep.
|
|
20
|
+
|
|
21
|
+
## Configure the model
|
|
22
|
+
|
|
23
|
+
The assistant replies only once you set a provider (the app boots fine without
|
|
24
|
+
it and shows a friendly notice):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# .env
|
|
28
|
+
PYLON_AI_PROVIDER=anthropic # or "openai" / "custom"
|
|
29
|
+
PYLON_AI_API_KEY=sk-ant-...
|
|
30
|
+
PYLON_AI_MODEL=claude-sonnet-4-6 # default when none is picked
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`/api/ai/stream` is auth-gated (it uses your signed-in session) and rate-limited
|
|
34
|
+
per user, so a drive-by caller can't burn your budget.
|
|
35
|
+
|
|
36
|
+
### Switching models / providers
|
|
37
|
+
|
|
38
|
+
The composer has a **model picker** — its options live in `lib/site.config.ts`
|
|
39
|
+
(`chat.models`, each with a provider label), and the chosen id is sent per
|
|
40
|
+
request. Client-chosen models must be allow-listed server-side:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
PYLON_AI_MODELS_ALLOWED=claude-sonnet-4-6,claude-opus-4-8,gpt-4o
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`/api/ai/stream` talks to **one** provider. To offer models from **multiple
|
|
47
|
+
providers** in one picker, route through an OpenAI-compatible gateway like
|
|
48
|
+
[OpenRouter](https://openrouter.ai):
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
PYLON_AI_PROVIDER=custom
|
|
52
|
+
PYLON_AI_BASE_URL=https://openrouter.ai/api/v1
|
|
53
|
+
PYLON_AI_API_KEY=<openrouter key>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
then set `chat.models` + `PYLON_AI_MODELS_ALLOWED` to the gateway's slugs
|
|
57
|
+
(`anthropic/claude-sonnet-4`, `openai/gpt-4o`, `google/gemini-2.5-pro`, …).
|
|
58
|
+
|
|
59
|
+
## How it works
|
|
60
|
+
|
|
61
|
+
- **Streaming.** `app/chat-client.tsx` POSTs the conversation to
|
|
62
|
+
`/api/ai/stream` and reads the SSE response (`data: {choices:[{delta:…}]}` …
|
|
63
|
+
`data: [DONE]`), appending tokens to the live assistant bubble.
|
|
64
|
+
- **Realtime history.** `Conversation` + `Message` are owner-scoped entities
|
|
65
|
+
read with `db.useQuery` — private to each user and synced across their tabs.
|
|
66
|
+
Messages are written with optimistic `db.insert` (userId is stamped from the
|
|
67
|
+
session via `field.owner()`), so no custom write functions are needed.
|
|
68
|
+
- **Sign-in required.** Chats are tied to your account, so the home page
|
|
69
|
+
redirects unauthenticated visitors to `/login`. Once signed in, your history
|
|
70
|
+
follows you across tabs and devices.
|
|
71
|
+
|
|
72
|
+
## Privacy
|
|
73
|
+
|
|
74
|
+
`Conversation` and `Message` policies are owner-scoped (`auth.userId ==
|
|
75
|
+
data.userId`) — you can only ever read or write your own. `User.passwordHash` is
|
|
76
|
+
`serverOnly`. The provider key lives only on the server.
|
|
77
|
+
|
|
78
|
+
## Rebrand it
|
|
79
|
+
|
|
80
|
+
Everything brand-specific — name, colors, the assistant's system prompt, the
|
|
81
|
+
empty-state copy, and starter prompts — lives in **`lib/site.config.ts`**.
|
|
82
|
+
|
|
83
|
+
## Layout
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
app.ts Conversation + Message (owner-scoped) + User
|
|
87
|
+
lib/site.config.ts brand + system prompt + suggestions (edit this)
|
|
88
|
+
app/page.tsx renders the chat island
|
|
89
|
+
app/chat-client.tsx sidebar + thread + streaming via /api/ai/stream
|
|
90
|
+
app/login/ optional sign-in (carries history across devices)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Deploy
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pylon deploy
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Docs: https://docs.pylonsync.com
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useState } from "react";
|
|
4
|
+
import {
|
|
5
|
+
passwordLogin,
|
|
6
|
+
passwordRegister,
|
|
7
|
+
persistSession,
|
|
8
|
+
ApiError,
|
|
9
|
+
} from "@pylonsync/client";
|
|
10
|
+
|
|
11
|
+
// The email/password form — one form, two modes. It calls the built-in auth API
|
|
12
|
+
// directly (`passwordLogin` / `passwordRegister` POST to `/api/auth/password/*`),
|
|
13
|
+
// then `persistSession` writes the freshly-minted token to local storage so the
|
|
14
|
+
// sync engine authenticates as the real account on the next load. We then do a
|
|
15
|
+
// full navigation to "/" so the SSR runtime re-resolves auth from the HttpOnly
|
|
16
|
+
// cookie and renders the chat.
|
|
17
|
+
//
|
|
18
|
+
// Sign-in is REQUIRED to use the chat: conversations are owner-scoped to your
|
|
19
|
+
// account, so the home page redirects unauthenticated visitors here.
|
|
20
|
+
export function AuthForm() {
|
|
21
|
+
const [mode, setMode] = useState<"login" | "register">("login");
|
|
22
|
+
const [email, setEmail] = useState("");
|
|
23
|
+
const [password, setPassword] = useState("");
|
|
24
|
+
const [error, setError] = useState<string | null>(null);
|
|
25
|
+
const [pending, setPending] = useState(false);
|
|
26
|
+
|
|
27
|
+
async function onSubmit(e: React.FormEvent) {
|
|
28
|
+
e.preventDefault();
|
|
29
|
+
setError(null);
|
|
30
|
+
setPending(true);
|
|
31
|
+
try {
|
|
32
|
+
const session =
|
|
33
|
+
mode === "login"
|
|
34
|
+
? await passwordLogin({ email, password })
|
|
35
|
+
: await passwordRegister({ email, password });
|
|
36
|
+
// Make this session authoritative, replacing any anonymous guest token.
|
|
37
|
+
persistSession(session);
|
|
38
|
+
window.location.assign("/");
|
|
39
|
+
} catch (err) {
|
|
40
|
+
setError(messageFor(err));
|
|
41
|
+
setPending(false);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className="space-y-5">
|
|
47
|
+
<form onSubmit={onSubmit} className="space-y-4">
|
|
48
|
+
<label className="block">
|
|
49
|
+
<span className="mb-1.5 block text-[13px] font-medium text-zinc-700">Email</span>
|
|
50
|
+
<input
|
|
51
|
+
type="email"
|
|
52
|
+
value={email}
|
|
53
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
54
|
+
required
|
|
55
|
+
autoComplete="email"
|
|
56
|
+
placeholder="you@yourbusiness.com"
|
|
57
|
+
className="h-10 w-full rounded-lg border border-zinc-300 bg-white px-3 text-sm text-zinc-900 outline-none transition placeholder:text-zinc-400 focus:border-brand focus:ring-2 focus:ring-brand/20"
|
|
58
|
+
/>
|
|
59
|
+
</label>
|
|
60
|
+
<label className="block">
|
|
61
|
+
<span className="mb-1.5 block text-[13px] font-medium text-zinc-700">Password</span>
|
|
62
|
+
<input
|
|
63
|
+
type="password"
|
|
64
|
+
value={password}
|
|
65
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
66
|
+
required
|
|
67
|
+
autoComplete={mode === "login" ? "current-password" : "new-password"}
|
|
68
|
+
placeholder={mode === "login" ? "Your password" : "At least 10 characters"}
|
|
69
|
+
className="h-10 w-full rounded-lg border border-zinc-300 bg-white px-3 text-sm text-zinc-900 outline-none transition placeholder:text-zinc-400 focus:border-brand focus:ring-2 focus:ring-brand/20"
|
|
70
|
+
/>
|
|
71
|
+
</label>
|
|
72
|
+
{error ? (
|
|
73
|
+
<p className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-[13px] leading-snug text-red-700">
|
|
74
|
+
{error}
|
|
75
|
+
</p>
|
|
76
|
+
) : null}
|
|
77
|
+
<button
|
|
78
|
+
type="submit"
|
|
79
|
+
disabled={pending}
|
|
80
|
+
className="inline-flex h-10 w-full items-center justify-center rounded-lg bg-zinc-900 text-sm font-medium text-white transition-colors hover:bg-zinc-700 disabled:opacity-60"
|
|
81
|
+
>
|
|
82
|
+
{pending ? "…" : mode === "login" ? "Sign in" : "Create account"}
|
|
83
|
+
</button>
|
|
84
|
+
</form>
|
|
85
|
+
|
|
86
|
+
<p className="text-center text-[13px] text-zinc-500">
|
|
87
|
+
{mode === "login" ? "First time here?" : "Already have an account?"}{" "}
|
|
88
|
+
<button
|
|
89
|
+
type="button"
|
|
90
|
+
onClick={() => {
|
|
91
|
+
setMode(mode === "login" ? "register" : "login");
|
|
92
|
+
setError(null);
|
|
93
|
+
}}
|
|
94
|
+
className="font-medium text-zinc-900 underline underline-offset-2"
|
|
95
|
+
>
|
|
96
|
+
{mode === "login" ? "Create an account" : "Sign in"}
|
|
97
|
+
</button>
|
|
98
|
+
</p>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Map the framework's auth error codes to friendly copy. `ApiError` carries a
|
|
104
|
+
// stable `.code` so you branch on the code, not the message.
|
|
105
|
+
function messageFor(err: unknown): string {
|
|
106
|
+
if (err instanceof ApiError) {
|
|
107
|
+
switch (err.code) {
|
|
108
|
+
case "INVALID_CREDENTIALS":
|
|
109
|
+
return "Wrong email or password.";
|
|
110
|
+
case "USER_EXISTS":
|
|
111
|
+
return "That email is already registered — sign in instead.";
|
|
112
|
+
case "WEAK_PASSWORD":
|
|
113
|
+
return "Pick a longer password — at least 10 characters.";
|
|
114
|
+
case "PWNED_PASSWORD":
|
|
115
|
+
return "That password has appeared in a known data breach. Choose a different one.";
|
|
116
|
+
case "RATE_LIMITED":
|
|
117
|
+
return "Too many attempts — try again in a minute.";
|
|
118
|
+
default:
|
|
119
|
+
return err.message;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (err instanceof Error) return err.message;
|
|
123
|
+
return "Something went wrong. Try again.";
|
|
124
|
+
}
|