@pylonsync/create-pylon 0.3.356 → 0.3.358
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/package.json +1 -1
- package/templates/marketplace/README.md +29 -14
- package/templates/marketplace/app/error.tsx +30 -16
- package/templates/marketplace/app/globals.css +140 -24
- package/templates/marketplace/app/layout.tsx +60 -31
- package/templates/marketplace/app/listing/[id]/page.tsx +160 -60
- package/templates/marketplace/app/me/page.tsx +3 -3
- package/templates/marketplace/app/not-found.tsx +19 -10
- package/templates/marketplace/app/page.tsx +287 -97
- package/templates/marketplace/app/sell/page.tsx +11 -9
- package/templates/marketplace/app.ts +5 -4
- package/templates/marketplace/client/AuthNav.tsx +4 -8
- package/templates/marketplace/client/LiveListingStatus.tsx +60 -0
- package/templates/marketplace/client/LiveTicker.tsx +22 -10
- package/templates/marketplace/client/LoginCard.tsx +108 -62
- package/templates/marketplace/client/MarketProvider.tsx +16 -2
- package/templates/marketplace/client/MyMarket.tsx +126 -56
- package/templates/marketplace/client/OfferPanel.tsx +222 -112
- package/templates/marketplace/client/ScrollToListingsLink.tsx +29 -0
- package/templates/marketplace/client/SeedOnEmpty.tsx +7 -6
- package/templates/marketplace/client/SellForm.tsx +198 -40
- package/templates/marketplace/client/ThemeToggle.tsx +62 -0
- package/templates/marketplace/client/WatchButton.tsx +19 -8
- package/templates/marketplace/client/market.ts +10 -4
- package/templates/marketplace/components/ui/alert.tsx +66 -0
- package/templates/marketplace/components/ui/badge.tsx +5 -1
- package/templates/marketplace/components/ui/button.tsx +9 -7
- package/templates/marketplace/components/ui/card.tsx +2 -2
- package/templates/marketplace/components/ui/empty.tsx +104 -0
- package/templates/marketplace/components/ui/field.tsx +246 -0
- package/templates/marketplace/components/ui/input.tsx +2 -2
- package/templates/marketplace/components/ui/label.tsx +4 -3
- package/templates/marketplace/components/ui/native-select.tsx +62 -0
- package/templates/marketplace/components/ui/separator.tsx +26 -0
- package/templates/marketplace/components/ui/skeleton.tsx +13 -0
- package/templates/marketplace/components/ui/spinner.tsx +16 -0
- package/templates/marketplace/components/ui/textarea.tsx +2 -2
- package/templates/marketplace/{ui → components/ui}/tokens.css +1 -1
- package/templates/marketplace/components.json +20 -0
- package/templates/marketplace/functions/seedMarket.ts +14 -12
- package/templates/marketplace/lib/catalog.ts +32 -0
- package/templates/marketplace/package.json +10 -9
- package/templates/marketplace/public/images/marketplace-hero.webp +0 -0
- package/templates/marketplace/tests/example.test.ts +76 -8
- package/templates/marketplace/tsconfig.json +13 -2
- package/templates/marketplace/components/ui/select.tsx +0 -39
- package/templates/marketplace/ui/badge.tsx +0 -30
- package/templates/marketplace/ui/button.tsx +0 -49
- package/templates/marketplace/ui/card.tsx +0 -48
- package/templates/marketplace/ui/input.tsx +0 -17
- package/templates/marketplace/ui/label.tsx +0 -18
- package/templates/marketplace/ui/textarea.tsx +0 -17
- package/templates/marketplace/ui/utils.ts +0 -6
|
@@ -1,12 +1,80 @@
|
|
|
1
1
|
import { expect, test } from "bun:test";
|
|
2
|
+
import { browseListings } from "../lib/catalog";
|
|
3
|
+
import { makeSlug, type Listing } from "../client/market";
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
const listings: Listing[] = [
|
|
6
|
+
{
|
|
7
|
+
id: "1",
|
|
8
|
+
sellerId: "seller-1",
|
|
9
|
+
sellerName: "maple-fox",
|
|
10
|
+
title: "Walnut lounge chair",
|
|
11
|
+
slug: "walnut-lounge-chair-1",
|
|
12
|
+
description: "Restored frame with new upholstery.",
|
|
13
|
+
price: 420,
|
|
14
|
+
category: "furniture",
|
|
15
|
+
condition: "good",
|
|
16
|
+
status: "active",
|
|
17
|
+
seed: "one",
|
|
18
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "2",
|
|
22
|
+
sellerId: "seller-2",
|
|
23
|
+
sellerName: "slate-heron",
|
|
24
|
+
title: "Compact mirrorless camera",
|
|
25
|
+
slug: "compact-mirrorless-camera-2",
|
|
26
|
+
description: "Clean sensor and two batteries.",
|
|
27
|
+
price: 680,
|
|
28
|
+
category: "cameras",
|
|
29
|
+
condition: "like-new",
|
|
30
|
+
status: "active",
|
|
31
|
+
seed: "two",
|
|
32
|
+
createdAt: "2026-02-01T00:00:00.000Z",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "3",
|
|
36
|
+
sellerId: "seller-3",
|
|
37
|
+
sellerName: "amber-lynx",
|
|
38
|
+
title: "Teak side table",
|
|
39
|
+
slug: "teak-side-table-3",
|
|
40
|
+
description: "Compact bedside table.",
|
|
41
|
+
price: 140,
|
|
42
|
+
category: "furniture",
|
|
43
|
+
condition: "good",
|
|
44
|
+
status: "active",
|
|
45
|
+
seed: "three",
|
|
46
|
+
createdAt: "2026-03-01T00:00:00.000Z",
|
|
47
|
+
},
|
|
48
|
+
];
|
|
9
49
|
|
|
10
|
-
test("
|
|
11
|
-
expect(
|
|
50
|
+
test("browse filters by category and searches listing copy", () => {
|
|
51
|
+
expect(
|
|
52
|
+
browseListings(listings, { category: "furniture", query: "walnut" }).map(
|
|
53
|
+
(listing) => listing.id,
|
|
54
|
+
),
|
|
55
|
+
).toEqual(["1"]);
|
|
56
|
+
expect(
|
|
57
|
+
browseListings(listings, { query: "slate-heron" }).map(
|
|
58
|
+
(listing) => listing.id,
|
|
59
|
+
),
|
|
60
|
+
).toEqual(["2"]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("browse sorts newest and by price", () => {
|
|
64
|
+
expect(browseListings(listings, {}).map((listing) => listing.id)).toEqual([
|
|
65
|
+
"3",
|
|
66
|
+
"2",
|
|
67
|
+
"1",
|
|
68
|
+
]);
|
|
69
|
+
expect(
|
|
70
|
+
browseListings(listings, { sort: "price-low" }).map(
|
|
71
|
+
(listing) => listing.price,
|
|
72
|
+
),
|
|
73
|
+
).toEqual([140, 420, 680]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("listing slugs remain readable and unique", () => {
|
|
77
|
+
expect(makeSlug(" Vintage Technics SL-1200 MK2 ", "a1f3")).toBe(
|
|
78
|
+
"vintage-technics-sl-1200-mk2-a1f3",
|
|
79
|
+
);
|
|
12
80
|
});
|
|
@@ -8,7 +8,18 @@
|
|
|
8
8
|
"strict": true,
|
|
9
9
|
"skipLibCheck": true,
|
|
10
10
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
11
|
-
"types": ["react", "react-dom", "node"]
|
|
11
|
+
"types": ["react", "react-dom", "node"],
|
|
12
|
+
"baseUrl": ".",
|
|
13
|
+
"paths": {
|
|
14
|
+
"@/*": ["./*"]
|
|
15
|
+
}
|
|
12
16
|
},
|
|
13
|
-
"include": [
|
|
17
|
+
"include": [
|
|
18
|
+
"app.ts",
|
|
19
|
+
"app/**/*",
|
|
20
|
+
"client/**/*",
|
|
21
|
+
"components/**/*",
|
|
22
|
+
"functions/**/*",
|
|
23
|
+
"lib/**/*"
|
|
24
|
+
]
|
|
14
25
|
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { ChevronDown } from "lucide-react";
|
|
3
|
-
|
|
4
|
-
import { cn } from "@/lib/utils";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* A styled native `<select>`.
|
|
8
|
-
*
|
|
9
|
-
* shadcn's Select is a Radix listbox; this deliberately isn't. A native select
|
|
10
|
-
* needs no extra dependency, is keyboard- and screen-reader-correct for free,
|
|
11
|
-
* and opens as the platform picker on mobile — which is what you want for the
|
|
12
|
-
* plain "pick one value" case that scaffolded forms are full of. Reach for
|
|
13
|
-
* `npx shadcn add select` when you need custom option rendering or search.
|
|
14
|
-
*/
|
|
15
|
-
function Select({
|
|
16
|
-
className,
|
|
17
|
-
children,
|
|
18
|
-
...props
|
|
19
|
-
}: React.SelectHTMLAttributes<HTMLSelectElement>) {
|
|
20
|
-
return (
|
|
21
|
-
<div className="relative">
|
|
22
|
-
<select
|
|
23
|
-
data-slot="select"
|
|
24
|
-
className={cn(
|
|
25
|
-
"flex h-9 w-full appearance-none rounded-md border border-input bg-transparent py-1 pl-3 pr-8 text-sm shadow-sm transition-colors",
|
|
26
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
27
|
-
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
28
|
-
className,
|
|
29
|
-
)}
|
|
30
|
-
{...props}
|
|
31
|
-
>
|
|
32
|
-
{children}
|
|
33
|
-
</select>
|
|
34
|
-
<ChevronDown className="pointer-events-none absolute right-2.5 top-1/2 size-4 -translate-y-1/2 opacity-50" />
|
|
35
|
-
</div>
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export { Select };
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
-
import { cn } from "./utils";
|
|
4
|
-
|
|
5
|
-
const badgeVariants = cva(
|
|
6
|
-
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-medium transition-colors",
|
|
7
|
-
{
|
|
8
|
-
variants: {
|
|
9
|
-
variant: {
|
|
10
|
-
default: "border-transparent bg-primary text-primary-foreground",
|
|
11
|
-
secondary: "border-transparent bg-secondary text-secondary-foreground",
|
|
12
|
-
destructive: "border-transparent bg-destructive text-destructive-foreground",
|
|
13
|
-
outline: "text-foreground",
|
|
14
|
-
success: "border-transparent bg-emerald-500/15 text-emerald-700 dark:text-emerald-300",
|
|
15
|
-
warning: "border-transparent bg-amber-500/15 text-amber-700 dark:text-amber-300",
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
defaultVariants: { variant: "default" },
|
|
19
|
-
},
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
export interface BadgeProps
|
|
23
|
-
extends React.HTMLAttributes<HTMLDivElement>,
|
|
24
|
-
VariantProps<typeof badgeVariants> {}
|
|
25
|
-
|
|
26
|
-
export function Badge({ className, variant, ...props }: BadgeProps) {
|
|
27
|
-
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export { badgeVariants };
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { Slot } from "@radix-ui/react-slot";
|
|
3
|
-
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
-
import { cn } from "./utils";
|
|
5
|
-
|
|
6
|
-
const buttonVariants = cva(
|
|
7
|
-
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0",
|
|
8
|
-
{
|
|
9
|
-
variants: {
|
|
10
|
-
variant: {
|
|
11
|
-
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
12
|
-
destructive:
|
|
13
|
-
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
14
|
-
outline:
|
|
15
|
-
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
16
|
-
secondary:
|
|
17
|
-
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
18
|
-
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
19
|
-
link: "text-primary underline-offset-4 hover:underline",
|
|
20
|
-
},
|
|
21
|
-
size: {
|
|
22
|
-
default: "h-9 px-4 py-2",
|
|
23
|
-
sm: "h-8 rounded-md px-3 text-xs",
|
|
24
|
-
lg: "h-10 rounded-md px-6",
|
|
25
|
-
icon: "h-9 w-9",
|
|
26
|
-
xs: "h-7 rounded px-2 text-xs",
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
defaultVariants: { variant: "default", size: "default" },
|
|
30
|
-
},
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
export interface ButtonProps
|
|
34
|
-
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
35
|
-
VariantProps<typeof buttonVariants> {
|
|
36
|
-
asChild?: boolean;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
40
|
-
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
41
|
-
const Comp = asChild ? Slot : "button";
|
|
42
|
-
return (
|
|
43
|
-
<Comp className={cn(buttonVariants({ variant, size }), className)} ref={ref} {...props} />
|
|
44
|
-
);
|
|
45
|
-
},
|
|
46
|
-
);
|
|
47
|
-
Button.displayName = "Button";
|
|
48
|
-
|
|
49
|
-
export { buttonVariants };
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { cn } from "./utils";
|
|
3
|
-
|
|
4
|
-
export const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
5
|
-
({ className, ...props }, ref) => (
|
|
6
|
-
<div
|
|
7
|
-
ref={ref}
|
|
8
|
-
className={cn("rounded-lg border bg-card text-card-foreground", className)}
|
|
9
|
-
{...props}
|
|
10
|
-
/>
|
|
11
|
-
),
|
|
12
|
-
);
|
|
13
|
-
Card.displayName = "Card";
|
|
14
|
-
|
|
15
|
-
export const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
16
|
-
({ className, ...props }, ref) => (
|
|
17
|
-
<div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
|
|
18
|
-
),
|
|
19
|
-
);
|
|
20
|
-
CardHeader.displayName = "CardHeader";
|
|
21
|
-
|
|
22
|
-
export const CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
23
|
-
({ className, ...props }, ref) => (
|
|
24
|
-
<div ref={ref} className={cn("font-semibold leading-none tracking-tight", className)} {...props} />
|
|
25
|
-
),
|
|
26
|
-
);
|
|
27
|
-
CardTitle.displayName = "CardTitle";
|
|
28
|
-
|
|
29
|
-
export const CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
30
|
-
({ className, ...props }, ref) => (
|
|
31
|
-
<div ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
|
|
32
|
-
),
|
|
33
|
-
);
|
|
34
|
-
CardDescription.displayName = "CardDescription";
|
|
35
|
-
|
|
36
|
-
export const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
37
|
-
({ className, ...props }, ref) => (
|
|
38
|
-
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
|
39
|
-
),
|
|
40
|
-
);
|
|
41
|
-
CardContent.displayName = "CardContent";
|
|
42
|
-
|
|
43
|
-
export const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
44
|
-
({ className, ...props }, ref) => (
|
|
45
|
-
<div ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} />
|
|
46
|
-
),
|
|
47
|
-
);
|
|
48
|
-
CardFooter.displayName = "CardFooter";
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { cn } from "./utils";
|
|
3
|
-
|
|
4
|
-
export const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
|
5
|
-
({ className, type, ...props }, ref) => (
|
|
6
|
-
<input
|
|
7
|
-
type={type}
|
|
8
|
-
className={cn(
|
|
9
|
-
"flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
10
|
-
className,
|
|
11
|
-
)}
|
|
12
|
-
ref={ref}
|
|
13
|
-
{...props}
|
|
14
|
-
/>
|
|
15
|
-
),
|
|
16
|
-
);
|
|
17
|
-
Input.displayName = "Input";
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import * as LabelPrimitive from "@radix-ui/react-label";
|
|
3
|
-
import { cn } from "./utils";
|
|
4
|
-
|
|
5
|
-
export const Label = React.forwardRef<
|
|
6
|
-
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
7
|
-
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
|
8
|
-
>(({ className, ...props }, ref) => (
|
|
9
|
-
<LabelPrimitive.Root
|
|
10
|
-
ref={ref}
|
|
11
|
-
className={cn(
|
|
12
|
-
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
|
13
|
-
className,
|
|
14
|
-
)}
|
|
15
|
-
{...props}
|
|
16
|
-
/>
|
|
17
|
-
));
|
|
18
|
-
Label.displayName = LabelPrimitive.Root.displayName;
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { cn } from "./utils";
|
|
3
|
-
|
|
4
|
-
export const Textarea = React.forwardRef<
|
|
5
|
-
HTMLTextAreaElement,
|
|
6
|
-
React.ComponentProps<"textarea">
|
|
7
|
-
>(({ className, ...props }, ref) => (
|
|
8
|
-
<textarea
|
|
9
|
-
className={cn(
|
|
10
|
-
"flex min-h-[60px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
11
|
-
className,
|
|
12
|
-
)}
|
|
13
|
-
ref={ref}
|
|
14
|
-
{...props}
|
|
15
|
-
/>
|
|
16
|
-
));
|
|
17
|
-
Textarea.displayName = "Textarea";
|