@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,104 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { Link, db } from "@pylonsync/react";
|
|
5
|
+
import { Tag, BadgeCheck } from "lucide-react";
|
|
6
|
+
import { MarketProvider } from "./MarketProvider";
|
|
7
|
+
import { money, timeAgo, type Listing, type Offer } from "./market";
|
|
8
|
+
|
|
9
|
+
// Realtime activity strip. Two live queries — new listings AND completed sales
|
|
10
|
+
// (accepted offers) — merged into one feed. The moment anyone lists an item or
|
|
11
|
+
// a sale closes (in another tab, by another visitor), it slides in here. No
|
|
12
|
+
// polling, no refetch. This is the part SSR can't do; it's why the marketplace
|
|
13
|
+
// feels alive.
|
|
14
|
+
type Activity = {
|
|
15
|
+
key: string;
|
|
16
|
+
kind: "listed" | "sold";
|
|
17
|
+
title: string;
|
|
18
|
+
href: string;
|
|
19
|
+
amount?: number;
|
|
20
|
+
at: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function Ticker() {
|
|
24
|
+
const { data: listingData } = db.useQuery<Listing>("Listing", {
|
|
25
|
+
where: { status: "active" },
|
|
26
|
+
orderBy: { createdAt: "desc" },
|
|
27
|
+
limit: 8,
|
|
28
|
+
});
|
|
29
|
+
const { data: saleData } = db.useQuery<Offer>("Offer", {
|
|
30
|
+
where: { status: "accepted" },
|
|
31
|
+
orderBy: { createdAt: "desc" },
|
|
32
|
+
limit: 8,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const events: Activity[] = [
|
|
36
|
+
...(listingData ?? []).map((l) => ({
|
|
37
|
+
key: `l-${l.id}`,
|
|
38
|
+
kind: "listed" as const,
|
|
39
|
+
title: l.title,
|
|
40
|
+
href: `/listing/${l.slug || l.id}`,
|
|
41
|
+
at: l.createdAt,
|
|
42
|
+
})),
|
|
43
|
+
...(saleData ?? []).map((o) => ({
|
|
44
|
+
key: `s-${o.id}`,
|
|
45
|
+
kind: "sold" as const,
|
|
46
|
+
title: o.listingTitle,
|
|
47
|
+
href: `/listing/${o.listingId}`,
|
|
48
|
+
amount: o.amount,
|
|
49
|
+
at: o.createdAt,
|
|
50
|
+
})),
|
|
51
|
+
]
|
|
52
|
+
.sort((a, b) => Date.parse(b.at) - Date.parse(a.at))
|
|
53
|
+
.slice(0, 10);
|
|
54
|
+
|
|
55
|
+
if (events.length === 0) return null;
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div className="flex items-center gap-3 overflow-hidden rounded-lg border bg-card px-3 py-2 text-sm">
|
|
59
|
+
<span className="flex shrink-0 items-center gap-1.5 font-medium text-emerald-600">
|
|
60
|
+
<span className="relative flex size-2">
|
|
61
|
+
<span className="absolute inline-flex size-full animate-ping rounded-full bg-emerald-400 opacity-75" />
|
|
62
|
+
<span className="relative inline-flex size-2 rounded-full bg-emerald-500" />
|
|
63
|
+
</span>
|
|
64
|
+
Live
|
|
65
|
+
</span>
|
|
66
|
+
{/* Auto-scrolling marquee — no scrollbar. Items are doubled so the loop
|
|
67
|
+
(translateX -50%) is seamless; hover pauses so you can click. The
|
|
68
|
+
edge mask fades items in/out for a clean "live feed" look. */}
|
|
69
|
+
<div className="relative flex-1 overflow-hidden [mask-image:linear-gradient(to_right,transparent,#000_1.5rem,#000_calc(100%-1.5rem),transparent)]">
|
|
70
|
+
<div className="market-marquee flex w-max items-center gap-6">
|
|
71
|
+
{[...events, ...events].map((e, i) => (
|
|
72
|
+
<Link
|
|
73
|
+
key={`${e.key}-${i}`}
|
|
74
|
+
href={e.href}
|
|
75
|
+
className="flex shrink-0 items-center gap-1.5 whitespace-nowrap hover:underline"
|
|
76
|
+
aria-hidden={i >= events.length}
|
|
77
|
+
tabIndex={i >= events.length ? -1 : undefined}
|
|
78
|
+
>
|
|
79
|
+
{e.kind === "sold" ? (
|
|
80
|
+
<BadgeCheck className="size-3.5 text-emerald-600" />
|
|
81
|
+
) : (
|
|
82
|
+
<Tag className="size-3.5 text-muted-foreground" />
|
|
83
|
+
)}
|
|
84
|
+
<span className="font-medium">{e.title}</span>
|
|
85
|
+
<span className="text-muted-foreground">
|
|
86
|
+
{e.kind === "sold" && e.amount != null
|
|
87
|
+
? `sold · ${money(e.amount)}`
|
|
88
|
+
: timeAgo(e.at)}
|
|
89
|
+
</span>
|
|
90
|
+
</Link>
|
|
91
|
+
))}
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function LiveTicker() {
|
|
99
|
+
return (
|
|
100
|
+
<MarketProvider fallback={null}>
|
|
101
|
+
<Ticker />
|
|
102
|
+
</MarketProvider>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useState } from "react";
|
|
4
|
+
import { Button } from "../ui/button";
|
|
5
|
+
import { Input } from "../ui/input";
|
|
6
|
+
import { Label } from "../ui/label";
|
|
7
|
+
import { useAuth } from "./MarketProvider";
|
|
8
|
+
import { DEMO } from "./market";
|
|
9
|
+
|
|
10
|
+
// The sign-in surface shown wherever a write needs a real user (sell, make an
|
|
11
|
+
// offer, manage your market). Email/password, no verification email. Prefilled
|
|
12
|
+
// with the seeded demo account so it's one click to a working session.
|
|
13
|
+
export function LoginCard({
|
|
14
|
+
title = "Sign in to continue",
|
|
15
|
+
blurb = "Real email/password auth — no verification email. The demo account is prefilled; just hit Log in.",
|
|
16
|
+
}: {
|
|
17
|
+
title?: string;
|
|
18
|
+
blurb?: string;
|
|
19
|
+
}) {
|
|
20
|
+
const { signIn, signUp } = useAuth();
|
|
21
|
+
const [mode, setMode] = useState<"login" | "register">("login");
|
|
22
|
+
// Prefill the demo credentials so a reviewer can sign in instantly.
|
|
23
|
+
const [email, setEmail] = useState<string>(DEMO.email);
|
|
24
|
+
const [password, setPassword] = useState<string>(DEMO.password);
|
|
25
|
+
const [name, setName] = useState("");
|
|
26
|
+
const [busy, setBusy] = useState(false);
|
|
27
|
+
const [err, setErr] = useState<string | null>(null);
|
|
28
|
+
|
|
29
|
+
async function submit(e: React.FormEvent) {
|
|
30
|
+
e.preventDefault();
|
|
31
|
+
setBusy(true);
|
|
32
|
+
setErr(null);
|
|
33
|
+
try {
|
|
34
|
+
if (mode === "login") await signIn(email, password);
|
|
35
|
+
else await signUp(email, password, name);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
setErr((e as Error).message ?? "Auth failed");
|
|
38
|
+
} finally {
|
|
39
|
+
setBusy(false);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className="mx-auto max-w-sm space-y-4 rounded-xl border bg-card p-6">
|
|
45
|
+
<div>
|
|
46
|
+
<h2 className="text-lg font-semibold tracking-tight">{title}</h2>
|
|
47
|
+
<p className="mt-1 text-sm text-muted-foreground">{blurb}</p>
|
|
48
|
+
</div>
|
|
49
|
+
<form onSubmit={submit} className="space-y-3">
|
|
50
|
+
{mode === "register" ? (
|
|
51
|
+
<div className="space-y-1.5">
|
|
52
|
+
<Label htmlFor="lc-name">Name</Label>
|
|
53
|
+
<Input
|
|
54
|
+
id="lc-name"
|
|
55
|
+
value={name}
|
|
56
|
+
onChange={(e) => setName(e.target.value)}
|
|
57
|
+
placeholder="Pat Pylon"
|
|
58
|
+
/>
|
|
59
|
+
</div>
|
|
60
|
+
) : null}
|
|
61
|
+
<div className="space-y-1.5">
|
|
62
|
+
<Label htmlFor="lc-email">Email</Label>
|
|
63
|
+
<Input
|
|
64
|
+
id="lc-email"
|
|
65
|
+
type="email"
|
|
66
|
+
required
|
|
67
|
+
value={email}
|
|
68
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
69
|
+
placeholder="you@example.com"
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
72
|
+
<div className="space-y-1.5">
|
|
73
|
+
<Label htmlFor="lc-password">Password</Label>
|
|
74
|
+
<Input
|
|
75
|
+
id="lc-password"
|
|
76
|
+
type="password"
|
|
77
|
+
required
|
|
78
|
+
minLength={8}
|
|
79
|
+
value={password}
|
|
80
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
81
|
+
placeholder={mode === "register" ? "8+ characters" : ""}
|
|
82
|
+
/>
|
|
83
|
+
</div>
|
|
84
|
+
{err ? <p className="text-sm text-destructive">{err}</p> : null}
|
|
85
|
+
<Button type="submit" disabled={busy} className="w-full">
|
|
86
|
+
{busy
|
|
87
|
+
? "…"
|
|
88
|
+
: mode === "login"
|
|
89
|
+
? "Log in"
|
|
90
|
+
: "Create account"}
|
|
91
|
+
</Button>
|
|
92
|
+
</form>
|
|
93
|
+
<p className="text-center text-xs text-muted-foreground">
|
|
94
|
+
{mode === "login" ? (
|
|
95
|
+
<>
|
|
96
|
+
No account?{" "}
|
|
97
|
+
<button
|
|
98
|
+
type="button"
|
|
99
|
+
className="font-medium text-foreground hover:underline"
|
|
100
|
+
onClick={() => {
|
|
101
|
+
setMode("register");
|
|
102
|
+
setEmail("");
|
|
103
|
+
setPassword("");
|
|
104
|
+
setErr(null);
|
|
105
|
+
}}
|
|
106
|
+
>
|
|
107
|
+
Sign up
|
|
108
|
+
</button>
|
|
109
|
+
</>
|
|
110
|
+
) : (
|
|
111
|
+
<>
|
|
112
|
+
Have an account?{" "}
|
|
113
|
+
<button
|
|
114
|
+
type="button"
|
|
115
|
+
className="font-medium text-foreground hover:underline"
|
|
116
|
+
onClick={() => {
|
|
117
|
+
setMode("login");
|
|
118
|
+
setEmail(DEMO.email);
|
|
119
|
+
setPassword(DEMO.password);
|
|
120
|
+
setErr(null);
|
|
121
|
+
}}
|
|
122
|
+
>
|
|
123
|
+
Log in
|
|
124
|
+
</button>
|
|
125
|
+
</>
|
|
126
|
+
)}
|
|
127
|
+
</p>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, {
|
|
4
|
+
createContext,
|
|
5
|
+
useCallback,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useState,
|
|
9
|
+
} from "react";
|
|
10
|
+
import { db } from "@pylonsync/react";
|
|
11
|
+
import {
|
|
12
|
+
cacheDisplayName,
|
|
13
|
+
ensureDemoSeed,
|
|
14
|
+
ensureReadSession,
|
|
15
|
+
readIdentity,
|
|
16
|
+
signIn as doSignIn,
|
|
17
|
+
signOut as doSignOut,
|
|
18
|
+
signUp as doSignUp,
|
|
19
|
+
type Identity,
|
|
20
|
+
} from "./market";
|
|
21
|
+
import { LoginCard } from "./LoginCard";
|
|
22
|
+
|
|
23
|
+
// The sync engine (live queries, WebSocket) is browser-only, so every
|
|
24
|
+
// interactive island mounts behind this provider. It boots the client, keeps
|
|
25
|
+
// a guest session for READ connectivity (so the public ticker/grid run live
|
|
26
|
+
// for signed-out visitors), seeds the demo account, and exposes the current
|
|
27
|
+
// identity + auth actions. `identity` is null until the visitor signs in.
|
|
28
|
+
|
|
29
|
+
interface AuthContextValue {
|
|
30
|
+
/** The signed-in user, or null when browsing anonymously. */
|
|
31
|
+
identity: Identity | null;
|
|
32
|
+
/** True until the client has booted + read connectivity is established. */
|
|
33
|
+
ready: boolean;
|
|
34
|
+
signIn: (email: string, password: string) => Promise<void>;
|
|
35
|
+
signUp: (email: string, password: string, name: string) => Promise<void>;
|
|
36
|
+
signOut: () => Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const Ctx = createContext<AuthContextValue | null>(null);
|
|
40
|
+
|
|
41
|
+
/** The signed-in user, or null when anonymous. */
|
|
42
|
+
export function useIdentity(): Identity | null {
|
|
43
|
+
return useAuth().identity;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function useAuth(): AuthContextValue {
|
|
47
|
+
const v = useContext(Ctx);
|
|
48
|
+
if (!v) throw new Error("useAuth must be used inside <MarketProvider>");
|
|
49
|
+
return v;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function MarketProvider({
|
|
53
|
+
children,
|
|
54
|
+
fallback,
|
|
55
|
+
}: {
|
|
56
|
+
children: React.ReactNode;
|
|
57
|
+
fallback?: React.ReactNode;
|
|
58
|
+
}) {
|
|
59
|
+
const [ready, setReady] = useState(false);
|
|
60
|
+
const [identity, setIdentity] = useState<Identity | null>(null);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
let alive = true;
|
|
64
|
+
void (async () => {
|
|
65
|
+
await ensureReadSession();
|
|
66
|
+
// Fire-and-forget: makes the demo login work + seeds the catalog.
|
|
67
|
+
void ensureDemoSeed();
|
|
68
|
+
if (!alive) return;
|
|
69
|
+
setIdentity(readIdentity());
|
|
70
|
+
setReady(true);
|
|
71
|
+
})();
|
|
72
|
+
|
|
73
|
+
const onChange = () => setIdentity(readIdentity());
|
|
74
|
+
window.addEventListener("pylon-auth-changed", onChange);
|
|
75
|
+
window.addEventListener("storage", onChange);
|
|
76
|
+
return () => {
|
|
77
|
+
alive = false;
|
|
78
|
+
window.removeEventListener("pylon-auth-changed", onChange);
|
|
79
|
+
window.removeEventListener("storage", onChange);
|
|
80
|
+
};
|
|
81
|
+
}, []);
|
|
82
|
+
|
|
83
|
+
const signIn = useCallback(async (email: string, password: string) => {
|
|
84
|
+
await doSignIn(email, password);
|
|
85
|
+
setIdentity(readIdentity());
|
|
86
|
+
}, []);
|
|
87
|
+
const signUp = useCallback(
|
|
88
|
+
async (email: string, password: string, name: string) => {
|
|
89
|
+
await doSignUp(email, password, name);
|
|
90
|
+
setIdentity(readIdentity());
|
|
91
|
+
},
|
|
92
|
+
[],
|
|
93
|
+
);
|
|
94
|
+
const signOut = useCallback(async () => {
|
|
95
|
+
await doSignOut();
|
|
96
|
+
setIdentity(readIdentity());
|
|
97
|
+
}, []);
|
|
98
|
+
|
|
99
|
+
if (!ready) {
|
|
100
|
+
return (
|
|
101
|
+
<>
|
|
102
|
+
{fallback ?? (
|
|
103
|
+
<span className="text-xs text-muted-foreground">connecting…</span>
|
|
104
|
+
)}
|
|
105
|
+
</>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<Ctx.Provider value={{ identity, ready, signIn, signUp, signOut }}>
|
|
111
|
+
{identity ? <DisplayNameSync userId={identity.userId} /> : null}
|
|
112
|
+
{children}
|
|
113
|
+
</Ctx.Provider>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Keeps the cached displayName fresh from the live User row, so identity.name
|
|
118
|
+
// upgrades from the email handle to the real name once the row syncs in.
|
|
119
|
+
function DisplayNameSync({ userId }: { userId: string }) {
|
|
120
|
+
const { data } = db.useQueryOne<{ id: string; displayName?: string }>(
|
|
121
|
+
"User",
|
|
122
|
+
userId,
|
|
123
|
+
);
|
|
124
|
+
const name = data?.displayName;
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (name) cacheDisplayName(name);
|
|
127
|
+
}, [name]);
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Gate a write surface behind sign-in. Renders the children when the visitor
|
|
133
|
+
* is signed in; otherwise a prefilled demo login card. Reads (browse, ticker,
|
|
134
|
+
* listing detail) don't use this — they stay public.
|
|
135
|
+
*/
|
|
136
|
+
export function AuthGate({
|
|
137
|
+
children,
|
|
138
|
+
title,
|
|
139
|
+
blurb,
|
|
140
|
+
}: {
|
|
141
|
+
children: React.ReactNode;
|
|
142
|
+
title?: string;
|
|
143
|
+
blurb?: string;
|
|
144
|
+
}) {
|
|
145
|
+
const { identity } = useAuth();
|
|
146
|
+
if (!identity) return <LoginCard title={title} blurb={blurb} />;
|
|
147
|
+
return <>{children}</>;
|
|
148
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { Link, db } from "@pylonsync/react";
|
|
5
|
+
import { Badge } from "../ui/badge";
|
|
6
|
+
import { Button } from "../ui/button";
|
|
7
|
+
import { AuthGate, MarketProvider, useIdentity } from "./MarketProvider";
|
|
8
|
+
import { Heart } from "lucide-react";
|
|
9
|
+
import { money, timeAgo, type Listing, type Offer, type Watch } from "./market";
|
|
10
|
+
|
|
11
|
+
type BadgeVariant = "default" | "secondary" | "destructive" | "outline" | "success" | "warning";
|
|
12
|
+
|
|
13
|
+
const statusVariant: Record<string, BadgeVariant> = {
|
|
14
|
+
pending: "warning",
|
|
15
|
+
accepted: "success",
|
|
16
|
+
declined: "outline",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function Dashboard() {
|
|
20
|
+
// Rendered inside <AuthGate>, so identity is non-null here.
|
|
21
|
+
const identity = useIdentity();
|
|
22
|
+
const userId = identity?.userId ?? "";
|
|
23
|
+
const name = identity?.name ?? "you";
|
|
24
|
+
|
|
25
|
+
// Three live queries, all scoped to me. Everything updates in place: a new
|
|
26
|
+
// offer on my listing, a seller answering my bid — no refresh.
|
|
27
|
+
const { data: listings } = db.useQuery<Listing>("Listing", {
|
|
28
|
+
where: { sellerId: userId },
|
|
29
|
+
orderBy: { createdAt: "desc" },
|
|
30
|
+
});
|
|
31
|
+
const { data: received } = db.useQuery<Offer>("Offer", {
|
|
32
|
+
where: { sellerId: userId },
|
|
33
|
+
orderBy: { createdAt: "desc" },
|
|
34
|
+
});
|
|
35
|
+
const { data: watching } = db.useQuery<Watch>("Watch", {
|
|
36
|
+
where: { userId },
|
|
37
|
+
orderBy: { createdAt: "desc" },
|
|
38
|
+
});
|
|
39
|
+
const { data: sent } = db.useQuery<Offer>("Offer", {
|
|
40
|
+
where: { buyerId: userId },
|
|
41
|
+
orderBy: { createdAt: "desc" },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const myListings = listings ?? [];
|
|
45
|
+
const myOffers = sent ?? [];
|
|
46
|
+
const watchlist = watching ?? [];
|
|
47
|
+
const inbound = received ?? [];
|
|
48
|
+
const pendingFor = (listingId: string) =>
|
|
49
|
+
inbound.filter((o) => o.listingId === listingId && o.status === "pending")
|
|
50
|
+
.length;
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div className="space-y-10">
|
|
54
|
+
<header className="flex items-center justify-between">
|
|
55
|
+
<div>
|
|
56
|
+
<h1 className="text-2xl font-semibold tracking-tight">My Market</h1>
|
|
57
|
+
<p className="text-sm text-muted-foreground">
|
|
58
|
+
You're trading as <span className="font-medium">{name}</span>.
|
|
59
|
+
</p>
|
|
60
|
+
</div>
|
|
61
|
+
<Button asChild>
|
|
62
|
+
<Link href="/sell">Sell something</Link>
|
|
63
|
+
</Button>
|
|
64
|
+
</header>
|
|
65
|
+
|
|
66
|
+
<section className="space-y-3">
|
|
67
|
+
<h2 className="font-semibold">Your listings ({myListings.length})</h2>
|
|
68
|
+
{myListings.length === 0 ? (
|
|
69
|
+
<p className="rounded-lg border border-dashed p-6 text-center text-sm text-muted-foreground">
|
|
70
|
+
Nothing listed yet.{" "}
|
|
71
|
+
<Link href="/sell" className="underline">
|
|
72
|
+
Post your first item
|
|
73
|
+
</Link>
|
|
74
|
+
.
|
|
75
|
+
</p>
|
|
76
|
+
) : (
|
|
77
|
+
<ul className="divide-y rounded-lg border">
|
|
78
|
+
{myListings.map((l) => (
|
|
79
|
+
<li key={l.id} className="flex items-center justify-between gap-3 p-3">
|
|
80
|
+
<Link href={`/listing/${l.slug || l.id}`} className="min-w-0 hover:underline">
|
|
81
|
+
<span className="truncate font-medium">{l.title}</span>
|
|
82
|
+
</Link>
|
|
83
|
+
<div className="flex shrink-0 items-center gap-2 text-sm">
|
|
84
|
+
<span className="font-semibold">{money(l.price)}</span>
|
|
85
|
+
{l.status === "sold" ? (
|
|
86
|
+
<Badge variant="success">Sold</Badge>
|
|
87
|
+
) : pendingFor(l.id) > 0 ? (
|
|
88
|
+
<Badge variant="warning">
|
|
89
|
+
{pendingFor(l.id)} offer{pendingFor(l.id) > 1 ? "s" : ""}
|
|
90
|
+
</Badge>
|
|
91
|
+
) : (
|
|
92
|
+
<Badge variant="outline">Active</Badge>
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
95
|
+
</li>
|
|
96
|
+
))}
|
|
97
|
+
</ul>
|
|
98
|
+
)}
|
|
99
|
+
</section>
|
|
100
|
+
|
|
101
|
+
<section className="space-y-3">
|
|
102
|
+
<h2 className="font-semibold">Offers you've made ({myOffers.length})</h2>
|
|
103
|
+
{myOffers.length === 0 ? (
|
|
104
|
+
<p className="rounded-lg border border-dashed p-6 text-center text-sm text-muted-foreground">
|
|
105
|
+
No offers out.{" "}
|
|
106
|
+
<Link href="/" className="underline">
|
|
107
|
+
Browse the market
|
|
108
|
+
</Link>
|
|
109
|
+
.
|
|
110
|
+
</p>
|
|
111
|
+
) : (
|
|
112
|
+
<ul className="divide-y rounded-lg border">
|
|
113
|
+
{myOffers.map((o) => (
|
|
114
|
+
<li key={o.id} className="flex items-center justify-between gap-3 p-3">
|
|
115
|
+
<Link
|
|
116
|
+
href={`/listing/${o.listingId}`}
|
|
117
|
+
className="min-w-0 hover:underline"
|
|
118
|
+
>
|
|
119
|
+
<span className="truncate font-medium">{o.listingTitle}</span>
|
|
120
|
+
<span className="ml-2 text-sm text-muted-foreground">
|
|
121
|
+
{timeAgo(o.createdAt)}
|
|
122
|
+
</span>
|
|
123
|
+
</Link>
|
|
124
|
+
<div className="flex shrink-0 items-center gap-2 text-sm">
|
|
125
|
+
<span className="font-semibold tabular-nums">{money(o.amount)}</span>
|
|
126
|
+
<Badge variant={statusVariant[o.status] ?? "outline"}>
|
|
127
|
+
{o.status}
|
|
128
|
+
</Badge>
|
|
129
|
+
</div>
|
|
130
|
+
</li>
|
|
131
|
+
))}
|
|
132
|
+
</ul>
|
|
133
|
+
)}
|
|
134
|
+
</section>
|
|
135
|
+
|
|
136
|
+
<section className="space-y-3">
|
|
137
|
+
<h2 className="flex items-center gap-2 font-semibold">
|
|
138
|
+
<Heart className="size-4 text-rose-500" />
|
|
139
|
+
Watching ({watchlist.length})
|
|
140
|
+
</h2>
|
|
141
|
+
{watchlist.length === 0 ? (
|
|
142
|
+
<p className="rounded-lg border border-dashed p-6 text-center text-sm text-muted-foreground">
|
|
143
|
+
Nothing saved yet. Tap the{" "}
|
|
144
|
+
<Heart className="inline size-3.5 align-text-bottom" /> on any
|
|
145
|
+
listing to watch it — your watchlist is private and syncs live.
|
|
146
|
+
</p>
|
|
147
|
+
) : (
|
|
148
|
+
<ul className="divide-y rounded-lg border">
|
|
149
|
+
{watchlist.map((w) => (
|
|
150
|
+
<li key={w.id} className="flex items-center justify-between gap-3 p-3">
|
|
151
|
+
<Link
|
|
152
|
+
href={`/listing/${w.listingId}`}
|
|
153
|
+
className="min-w-0 truncate font-medium hover:underline"
|
|
154
|
+
>
|
|
155
|
+
{w.listingTitle}
|
|
156
|
+
</Link>
|
|
157
|
+
<span className="shrink-0 text-xs text-muted-foreground">
|
|
158
|
+
saved {timeAgo(w.createdAt)}
|
|
159
|
+
</span>
|
|
160
|
+
</li>
|
|
161
|
+
))}
|
|
162
|
+
</ul>
|
|
163
|
+
)}
|
|
164
|
+
</section>
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function MyMarket() {
|
|
170
|
+
return (
|
|
171
|
+
<MarketProvider fallback={<p className="text-sm text-muted-foreground">Loading your market…</p>}>
|
|
172
|
+
<AuthGate
|
|
173
|
+
title="Sign in to see your market"
|
|
174
|
+
blurb="Your listings, offers received, and bids you've sent — all live. The demo account is prefilled; just hit Log in."
|
|
175
|
+
>
|
|
176
|
+
<Dashboard />
|
|
177
|
+
</AuthGate>
|
|
178
|
+
</MarketProvider>
|
|
179
|
+
);
|
|
180
|
+
}
|