ship-create 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/README.md +39 -0
- package/create.mjs +301 -0
- package/package.json +25 -0
- package/templates/.cursorrules +51 -0
- package/templates/.windsurfrules +51 -0
- package/templates/AGENTS.md +51 -0
- package/templates/CLAUDE.md +51 -0
- package/templates/docs/HUMAN_FLOW.md +205 -0
- package/templates/docs/PROJECT.md +154 -0
- package/templates/docs/PROMPTS.md +246 -0
- package/templates/docs/product-types/CRM_TEMPLATE.md +78 -0
- package/templates/docs/product-types/DASHBOARD_TEMPLATE.md +78 -0
- package/templates/docs/product-types/DIRECTORY_TEMPLATE.md +75 -0
- package/templates/docs/product-types/INTERNAL_TOOL_TEMPLATE.md +77 -0
- package/templates/docs/product-types/LEADGEN_TEMPLATE.md +78 -0
- package/templates/docs/product-types/MARKETPLACE_TEMPLATE.md +81 -0
- package/templates/docs/product-types/MEMBERSHIP_TEMPLATE.md +80 -0
- package/templates/docs/product-types/SAAS_TEMPLATE.md +79 -0
- package/templates/starter-kit/README.md +64 -0
- package/templates/starter-kit/app/backoffice/content/page.tsx +93 -0
- package/templates/starter-kit/app/backoffice/layout.tsx +105 -0
- package/templates/starter-kit/app/backoffice/page.tsx +165 -0
- package/templates/starter-kit/app/backoffice/settings/page.tsx +145 -0
- package/templates/starter-kit/app/backoffice/users/page.tsx +134 -0
- package/templates/starter-kit/app/globals.css +141 -0
- package/templates/starter-kit/app/layout.tsx +43 -0
- package/templates/starter-kit/app/member/(app)/billing/page.tsx +137 -0
- package/templates/starter-kit/app/member/(app)/content/page.tsx +111 -0
- package/templates/starter-kit/app/member/(app)/dashboard/page.tsx +129 -0
- package/templates/starter-kit/app/member/(app)/layout.tsx +130 -0
- package/templates/starter-kit/app/member/(app)/settings/page.tsx +96 -0
- package/templates/starter-kit/app/member/login/page.tsx +106 -0
- package/templates/starter-kit/app/member/signup/page.tsx +120 -0
- package/templates/starter-kit/app/page.tsx +82 -0
- package/templates/starter-kit/app/sale/_components/cta-footer.tsx +66 -0
- package/templates/starter-kit/app/sale/_components/faq.tsx +107 -0
- package/templates/starter-kit/app/sale/_components/features.tsx +95 -0
- package/templates/starter-kit/app/sale/_components/hero.tsx +106 -0
- package/templates/starter-kit/app/sale/_components/pricing.tsx +133 -0
- package/templates/starter-kit/app/sale/_components/problem.tsx +59 -0
- package/templates/starter-kit/app/sale/_components/testimonials.tsx +100 -0
- package/templates/starter-kit/app/sale/page.tsx +21 -0
- package/templates/starter-kit/components/ui/avatar.tsx +50 -0
- package/templates/starter-kit/components/ui/badge.tsx +36 -0
- package/templates/starter-kit/components/ui/button.tsx +56 -0
- package/templates/starter-kit/components/ui/card.tsx +78 -0
- package/templates/starter-kit/components/ui/input.tsx +24 -0
- package/templates/starter-kit/components/ui/table.tsx +88 -0
- package/templates/starter-kit/components/ui/tabs.tsx +55 -0
- package/templates/starter-kit/lib/mock-data.ts +118 -0
- package/templates/starter-kit/lib/utils.ts +6 -0
- package/templates/starter-kit/next.config.mjs +6 -0
- package/templates/starter-kit/package.json +36 -0
- package/templates/starter-kit/postcss.config.mjs +9 -0
- package/templates/starter-kit/tailwind.config.ts +83 -0
- package/templates/starter-kit/tsconfig.json +41 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
const badgeVariants = cva(
|
|
7
|
+
"label-mono inline-flex items-center gap-1 rounded-full border px-2.5 py-1 transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default:
|
|
12
|
+
"border-transparent bg-primary/15 text-primary",
|
|
13
|
+
secondary:
|
|
14
|
+
"border-transparent bg-secondary/20 text-secondary",
|
|
15
|
+
outline: "border-border text-muted-foreground",
|
|
16
|
+
destructive:
|
|
17
|
+
"border-transparent bg-destructive/15 text-destructive",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultVariants: {
|
|
21
|
+
variant: "default",
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
export interface BadgeProps
|
|
27
|
+
extends React.HTMLAttributes<HTMLDivElement>,
|
|
28
|
+
VariantProps<typeof badgeVariants> {}
|
|
29
|
+
|
|
30
|
+
function Badge({ className, variant, ...props }: BadgeProps) {
|
|
31
|
+
return (
|
|
32
|
+
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export { Badge, badgeVariants };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
const buttonVariants = cva(
|
|
8
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-full text-sm font-medium ring-offset-background transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-40 active:scale-[0.97]",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default:
|
|
13
|
+
"bg-primary text-primary-foreground shadow-soft hover:bg-primary/90 hover:-translate-y-0.5",
|
|
14
|
+
secondary:
|
|
15
|
+
"bg-secondary text-secondary-foreground hover:bg-secondary/90 hover:-translate-y-0.5",
|
|
16
|
+
outline:
|
|
17
|
+
"border border-border bg-transparent text-foreground hover:border-primary/50 hover:bg-primary/10",
|
|
18
|
+
ghost: "text-foreground hover:bg-muted",
|
|
19
|
+
destructive:
|
|
20
|
+
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
21
|
+
},
|
|
22
|
+
size: {
|
|
23
|
+
default: "h-10 px-5 py-2",
|
|
24
|
+
sm: "h-8 rounded-full px-3.5 text-xs",
|
|
25
|
+
lg: "h-12 rounded-full px-7 text-[0.95rem]",
|
|
26
|
+
icon: "h-10 w-10 rounded-full",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
defaultVariants: {
|
|
30
|
+
variant: "default",
|
|
31
|
+
size: "default",
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
export interface ButtonProps
|
|
37
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
38
|
+
VariantProps<typeof buttonVariants> {
|
|
39
|
+
asChild?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
43
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
44
|
+
const Comp = asChild ? Slot : "button";
|
|
45
|
+
return (
|
|
46
|
+
<Comp
|
|
47
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
48
|
+
ref={ref}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
Button.displayName = "Button";
|
|
55
|
+
|
|
56
|
+
export { Button, buttonVariants };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
6
|
+
({ className, ...props }, ref) => (
|
|
7
|
+
<div
|
|
8
|
+
ref={ref}
|
|
9
|
+
className={cn(
|
|
10
|
+
"rounded-xl border border-border bg-card text-card-foreground shadow-soft transition-colors duration-200",
|
|
11
|
+
className
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
);
|
|
17
|
+
Card.displayName = "Card";
|
|
18
|
+
|
|
19
|
+
const CardHeader = React.forwardRef<
|
|
20
|
+
HTMLDivElement,
|
|
21
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
22
|
+
>(({ className, ...props }, ref) => (
|
|
23
|
+
<div
|
|
24
|
+
ref={ref}
|
|
25
|
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
));
|
|
29
|
+
CardHeader.displayName = "CardHeader";
|
|
30
|
+
|
|
31
|
+
const CardTitle = React.forwardRef<
|
|
32
|
+
HTMLParagraphElement,
|
|
33
|
+
React.HTMLAttributes<HTMLHeadingElement>
|
|
34
|
+
>(({ className, ...props }, ref) => (
|
|
35
|
+
<h3
|
|
36
|
+
ref={ref}
|
|
37
|
+
className={cn(
|
|
38
|
+
"font-display text-xl font-medium leading-tight tracking-tight text-foreground",
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
));
|
|
44
|
+
CardTitle.displayName = "CardTitle";
|
|
45
|
+
|
|
46
|
+
const CardDescription = React.forwardRef<
|
|
47
|
+
HTMLParagraphElement,
|
|
48
|
+
React.HTMLAttributes<HTMLParagraphElement>
|
|
49
|
+
>(({ className, ...props }, ref) => (
|
|
50
|
+
<p
|
|
51
|
+
ref={ref}
|
|
52
|
+
className={cn("text-sm leading-relaxed text-muted-foreground", className)}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
));
|
|
56
|
+
CardDescription.displayName = "CardDescription";
|
|
57
|
+
|
|
58
|
+
const CardContent = React.forwardRef<
|
|
59
|
+
HTMLDivElement,
|
|
60
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
61
|
+
>(({ className, ...props }, ref) => (
|
|
62
|
+
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
|
63
|
+
));
|
|
64
|
+
CardContent.displayName = "CardContent";
|
|
65
|
+
|
|
66
|
+
const CardFooter = React.forwardRef<
|
|
67
|
+
HTMLDivElement,
|
|
68
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
69
|
+
>(({ className, ...props }, ref) => (
|
|
70
|
+
<div
|
|
71
|
+
ref={ref}
|
|
72
|
+
className={cn("flex items-center p-6 pt-0", className)}
|
|
73
|
+
{...props}
|
|
74
|
+
/>
|
|
75
|
+
));
|
|
76
|
+
CardFooter.displayName = "CardFooter";
|
|
77
|
+
|
|
78
|
+
export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
|
|
6
|
+
|
|
7
|
+
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
8
|
+
({ className, type, ...props }, ref) => {
|
|
9
|
+
return (
|
|
10
|
+
<input
|
|
11
|
+
type={type}
|
|
12
|
+
className={cn(
|
|
13
|
+
"flex h-11 w-full rounded-lg border border-input bg-card/60 px-3.5 py-2 text-sm text-foreground ring-offset-background transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground/70 focus-visible:border-primary/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50",
|
|
14
|
+
className
|
|
15
|
+
)}
|
|
16
|
+
ref={ref}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
Input.displayName = "Input";
|
|
23
|
+
|
|
24
|
+
export { Input };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
const Table = React.forwardRef<
|
|
6
|
+
HTMLTableElement,
|
|
7
|
+
React.HTMLAttributes<HTMLTableElement>
|
|
8
|
+
>(({ className, ...props }, ref) => (
|
|
9
|
+
<div className="relative w-full overflow-auto">
|
|
10
|
+
<table
|
|
11
|
+
ref={ref}
|
|
12
|
+
className={cn("w-full caption-bottom text-sm", className)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
</div>
|
|
16
|
+
));
|
|
17
|
+
Table.displayName = "Table";
|
|
18
|
+
|
|
19
|
+
const TableHeader = React.forwardRef<
|
|
20
|
+
HTMLTableSectionElement,
|
|
21
|
+
React.HTMLAttributes<HTMLTableSectionElement>
|
|
22
|
+
>(({ className, ...props }, ref) => (
|
|
23
|
+
<thead
|
|
24
|
+
ref={ref}
|
|
25
|
+
className={cn("[&_tr]:border-b [&_tr]:border-border", className)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
));
|
|
29
|
+
TableHeader.displayName = "TableHeader";
|
|
30
|
+
|
|
31
|
+
const TableBody = React.forwardRef<
|
|
32
|
+
HTMLTableSectionElement,
|
|
33
|
+
React.HTMLAttributes<HTMLTableSectionElement>
|
|
34
|
+
>(({ className, ...props }, ref) => (
|
|
35
|
+
<tbody
|
|
36
|
+
ref={ref}
|
|
37
|
+
className={cn("[&_tr:last-child]:border-0", className)}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
));
|
|
41
|
+
TableBody.displayName = "TableBody";
|
|
42
|
+
|
|
43
|
+
const TableRow = React.forwardRef<
|
|
44
|
+
HTMLTableRowElement,
|
|
45
|
+
React.HTMLAttributes<HTMLTableRowElement>
|
|
46
|
+
>(({ className, ...props }, ref) => (
|
|
47
|
+
<tr
|
|
48
|
+
ref={ref}
|
|
49
|
+
className={cn(
|
|
50
|
+
"border-b border-border/70 transition-colors hover:bg-primary/[0.04] data-[state=selected]:bg-muted",
|
|
51
|
+
className
|
|
52
|
+
)}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
));
|
|
56
|
+
TableRow.displayName = "TableRow";
|
|
57
|
+
|
|
58
|
+
const TableHead = React.forwardRef<
|
|
59
|
+
HTMLTableCellElement,
|
|
60
|
+
React.ThHTMLAttributes<HTMLTableCellElement>
|
|
61
|
+
>(({ className, ...props }, ref) => (
|
|
62
|
+
<th
|
|
63
|
+
ref={ref}
|
|
64
|
+
className={cn(
|
|
65
|
+
"label-mono h-10 px-2 text-left align-middle text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
|
66
|
+
className
|
|
67
|
+
)}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
));
|
|
71
|
+
TableHead.displayName = "TableHead";
|
|
72
|
+
|
|
73
|
+
const TableCell = React.forwardRef<
|
|
74
|
+
HTMLTableCellElement,
|
|
75
|
+
React.TdHTMLAttributes<HTMLTableCellElement>
|
|
76
|
+
>(({ className, ...props }, ref) => (
|
|
77
|
+
<td
|
|
78
|
+
ref={ref}
|
|
79
|
+
className={cn(
|
|
80
|
+
"p-2 align-middle [&:has([role=checkbox])]:pr-0",
|
|
81
|
+
className
|
|
82
|
+
)}
|
|
83
|
+
{...props}
|
|
84
|
+
/>
|
|
85
|
+
));
|
|
86
|
+
TableCell.displayName = "TableCell";
|
|
87
|
+
|
|
88
|
+
export { Table, TableHeader, TableBody, TableRow, TableHead, TableCell };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
|
|
8
|
+
const Tabs = TabsPrimitive.Root;
|
|
9
|
+
|
|
10
|
+
const TabsList = React.forwardRef<
|
|
11
|
+
React.ElementRef<typeof TabsPrimitive.List>,
|
|
12
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
|
13
|
+
>(({ className, ...props }, ref) => (
|
|
14
|
+
<TabsPrimitive.List
|
|
15
|
+
ref={ref}
|
|
16
|
+
className={cn(
|
|
17
|
+
"inline-flex h-11 items-center justify-center gap-1 rounded-full border border-border bg-card/60 p-1 text-muted-foreground",
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
));
|
|
23
|
+
TabsList.displayName = TabsPrimitive.List.displayName;
|
|
24
|
+
|
|
25
|
+
const TabsTrigger = React.forwardRef<
|
|
26
|
+
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
|
27
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
|
28
|
+
>(({ className, ...props }, ref) => (
|
|
29
|
+
<TabsPrimitive.Trigger
|
|
30
|
+
ref={ref}
|
|
31
|
+
className={cn(
|
|
32
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-full px-4 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-primary data-[state=active]:text-primary-foreground data-[state=active]:shadow-soft",
|
|
33
|
+
className
|
|
34
|
+
)}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
));
|
|
38
|
+
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
|
39
|
+
|
|
40
|
+
const TabsContent = React.forwardRef<
|
|
41
|
+
React.ElementRef<typeof TabsPrimitive.Content>,
|
|
42
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
|
43
|
+
>(({ className, ...props }, ref) => (
|
|
44
|
+
<TabsPrimitive.Content
|
|
45
|
+
ref={ref}
|
|
46
|
+
className={cn(
|
|
47
|
+
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
48
|
+
className
|
|
49
|
+
)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
));
|
|
53
|
+
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
|
54
|
+
|
|
55
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Shared mock data for The SHIP Method OS starter kit.
|
|
2
|
+
// No backend is wired up yet — every route group (sale / member / backoffice)
|
|
3
|
+
// should import from here until real Supabase queries replace these arrays.
|
|
4
|
+
// See README.md for the migration note.
|
|
5
|
+
|
|
6
|
+
export interface MockUser {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
email: string;
|
|
10
|
+
plan: "Free" | "Starter" | "Pro" | "Lifetime";
|
|
11
|
+
status: "active" | "trialing" | "past_due" | "canceled";
|
|
12
|
+
joinedAt: string; // ISO date string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MockMemberContent {
|
|
16
|
+
id: string;
|
|
17
|
+
title: string;
|
|
18
|
+
tier: "Free" | "Starter" | "Pro" | "Lifetime";
|
|
19
|
+
progress: number; // 0-100
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface MockMetric {
|
|
23
|
+
label: string;
|
|
24
|
+
value: string;
|
|
25
|
+
change: number; // signed percentage, e.g. 12.4 or -3.1
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const users: MockUser[] = [
|
|
29
|
+
{
|
|
30
|
+
id: "usr_001",
|
|
31
|
+
name: "Anan Suwannarat",
|
|
32
|
+
email: "anan@shipmethod.dev",
|
|
33
|
+
plan: "Pro",
|
|
34
|
+
status: "active",
|
|
35
|
+
joinedAt: "2025-11-02",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "usr_002",
|
|
39
|
+
name: "Becky Tran",
|
|
40
|
+
email: "becky.tran@gmail.com",
|
|
41
|
+
plan: "Starter",
|
|
42
|
+
status: "active",
|
|
43
|
+
joinedAt: "2025-12-14",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: "usr_003",
|
|
47
|
+
name: "Chai Wattana",
|
|
48
|
+
email: "chai.w@outlook.com",
|
|
49
|
+
plan: "Lifetime",
|
|
50
|
+
status: "active",
|
|
51
|
+
joinedAt: "2025-09-21",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "usr_004",
|
|
55
|
+
name: "Dana Kim",
|
|
56
|
+
email: "dana.kim@proton.me",
|
|
57
|
+
plan: "Free",
|
|
58
|
+
status: "trialing",
|
|
59
|
+
joinedAt: "2026-01-30",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: "usr_005",
|
|
63
|
+
name: "Eric Sombat",
|
|
64
|
+
email: "eric.sombat@yahoo.com",
|
|
65
|
+
plan: "Pro",
|
|
66
|
+
status: "past_due",
|
|
67
|
+
joinedAt: "2025-10-08",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "usr_006",
|
|
71
|
+
name: "Faye Nguyen",
|
|
72
|
+
email: "faye.nguyen@icloud.com",
|
|
73
|
+
plan: "Starter",
|
|
74
|
+
status: "canceled",
|
|
75
|
+
joinedAt: "2025-08-17",
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
export const members: MockMemberContent[] = [
|
|
80
|
+
{
|
|
81
|
+
id: "crs_001",
|
|
82
|
+
title: "SHIP Method Foundations",
|
|
83
|
+
tier: "Free",
|
|
84
|
+
progress: 100,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: "crs_002",
|
|
88
|
+
title: "Offer Design & Pricing Mastery",
|
|
89
|
+
tier: "Starter",
|
|
90
|
+
progress: 62,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "crs_003",
|
|
94
|
+
title: "Building Your First Funnel",
|
|
95
|
+
tier: "Starter",
|
|
96
|
+
progress: 30,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: "crs_004",
|
|
100
|
+
title: "Advanced Backoffice Automation",
|
|
101
|
+
tier: "Pro",
|
|
102
|
+
progress: 5,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "crs_005",
|
|
106
|
+
title: "Lifetime Mastermind Vault",
|
|
107
|
+
tier: "Lifetime",
|
|
108
|
+
progress: 0,
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
export const metrics: MockMetric[] = [
|
|
113
|
+
{ label: "Monthly Recurring Revenue", value: "$18,420", change: 12.4 },
|
|
114
|
+
{ label: "Active Members", value: "342", change: 6.1 },
|
|
115
|
+
{ label: "Trial → Paid Conversion", value: "24.8%", change: -1.7 },
|
|
116
|
+
{ label: "Churn Rate", value: "3.2%", change: -0.5 },
|
|
117
|
+
{ label: "Avg. Course Completion", value: "58%", change: 4.3 },
|
|
118
|
+
];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ship-starter-kit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"lint": "next lint"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@radix-ui/react-avatar": "^1.1.0",
|
|
13
|
+
"@radix-ui/react-dialog": "^1.1.1",
|
|
14
|
+
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
|
15
|
+
"@radix-ui/react-slot": "^1.1.0",
|
|
16
|
+
"@radix-ui/react-tabs": "^1.1.0",
|
|
17
|
+
"class-variance-authority": "^0.7.0",
|
|
18
|
+
"clsx": "^2.1.1",
|
|
19
|
+
"lucide-react": "^0.439.0",
|
|
20
|
+
"next": "^16.2.9",
|
|
21
|
+
"react": "18.3.1",
|
|
22
|
+
"react-dom": "18.3.1",
|
|
23
|
+
"tailwind-merge": "^2.5.2"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^20.14.15",
|
|
27
|
+
"@types/react": "^18.3.4",
|
|
28
|
+
"@types/react-dom": "^18.3.0",
|
|
29
|
+
"autoprefixer": "^10.4.20",
|
|
30
|
+
"eslint": "^8.57.0",
|
|
31
|
+
"eslint-config-next": "14.2.5",
|
|
32
|
+
"postcss": "^8.4.41",
|
|
33
|
+
"tailwindcss": "^3.4.10",
|
|
34
|
+
"typescript": "^5.5.4"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Config } from "tailwindcss";
|
|
2
|
+
|
|
3
|
+
const config: Config = {
|
|
4
|
+
darkMode: ["class"],
|
|
5
|
+
content: ["./app/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}", "./lib/**/*.{ts,tsx}"],
|
|
6
|
+
theme: {
|
|
7
|
+
container: {
|
|
8
|
+
center: true,
|
|
9
|
+
padding: "1.5rem",
|
|
10
|
+
screens: {
|
|
11
|
+
"2xl": "1280px",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
extend: {
|
|
15
|
+
fontFamily: {
|
|
16
|
+
sans: ["var(--font-sans)", "ui-sans-serif", "system-ui"],
|
|
17
|
+
display: ["var(--font-display)", "ui-serif", "Georgia", "serif"],
|
|
18
|
+
mono: ["var(--font-mono)", "ui-monospace", "SFMono-Regular"],
|
|
19
|
+
},
|
|
20
|
+
colors: {
|
|
21
|
+
border: "hsl(var(--border))",
|
|
22
|
+
input: "hsl(var(--input))",
|
|
23
|
+
ring: "hsl(var(--ring))",
|
|
24
|
+
background: "hsl(var(--background))",
|
|
25
|
+
foreground: "hsl(var(--foreground))",
|
|
26
|
+
primary: {
|
|
27
|
+
DEFAULT: "hsl(var(--primary))",
|
|
28
|
+
foreground: "hsl(var(--primary-foreground))",
|
|
29
|
+
},
|
|
30
|
+
secondary: {
|
|
31
|
+
DEFAULT: "hsl(var(--secondary))",
|
|
32
|
+
foreground: "hsl(var(--secondary-foreground))",
|
|
33
|
+
},
|
|
34
|
+
muted: {
|
|
35
|
+
DEFAULT: "hsl(var(--muted))",
|
|
36
|
+
foreground: "hsl(var(--muted-foreground))",
|
|
37
|
+
},
|
|
38
|
+
accent: {
|
|
39
|
+
DEFAULT: "hsl(var(--accent))",
|
|
40
|
+
foreground: "hsl(var(--accent-foreground))",
|
|
41
|
+
},
|
|
42
|
+
destructive: {
|
|
43
|
+
DEFAULT: "hsl(var(--destructive))",
|
|
44
|
+
foreground: "hsl(var(--destructive-foreground))",
|
|
45
|
+
},
|
|
46
|
+
success: {
|
|
47
|
+
DEFAULT: "hsl(var(--success))",
|
|
48
|
+
foreground: "hsl(var(--success-foreground))",
|
|
49
|
+
},
|
|
50
|
+
card: {
|
|
51
|
+
DEFAULT: "hsl(var(--card))",
|
|
52
|
+
foreground: "hsl(var(--card-foreground))",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
borderRadius: {
|
|
56
|
+
lg: "var(--radius)",
|
|
57
|
+
md: "calc(var(--radius) - 4px)",
|
|
58
|
+
sm: "calc(var(--radius) - 6px)",
|
|
59
|
+
xl: "calc(var(--radius) + 6px)",
|
|
60
|
+
},
|
|
61
|
+
keyframes: {
|
|
62
|
+
"fade-up": {
|
|
63
|
+
from: { opacity: "0", transform: "translateY(14px)" },
|
|
64
|
+
to: { opacity: "1", transform: "translateY(0)" },
|
|
65
|
+
},
|
|
66
|
+
"fade-in": {
|
|
67
|
+
from: { opacity: "0" },
|
|
68
|
+
to: { opacity: "1" },
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
animation: {
|
|
72
|
+
"fade-up": "fade-up 0.7s cubic-bezier(0.16, 1, 0.3, 1) both",
|
|
73
|
+
"fade-in": "fade-in 0.6s ease-out both",
|
|
74
|
+
},
|
|
75
|
+
boxShadow: {
|
|
76
|
+
soft: "0 1px 2px hsl(27 30% 4% / 0.4), 0 16px 40px -20px hsl(27 30% 4% / 0.6)",
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
plugins: [],
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export default config;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": [
|
|
5
|
+
"dom",
|
|
6
|
+
"dom.iterable",
|
|
7
|
+
"esnext"
|
|
8
|
+
],
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"module": "esnext",
|
|
15
|
+
"moduleResolution": "bundler",
|
|
16
|
+
"resolveJsonModule": true,
|
|
17
|
+
"isolatedModules": true,
|
|
18
|
+
"jsx": "react-jsx",
|
|
19
|
+
"incremental": true,
|
|
20
|
+
"plugins": [
|
|
21
|
+
{
|
|
22
|
+
"name": "next"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"paths": {
|
|
26
|
+
"@/*": [
|
|
27
|
+
"./*"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"include": [
|
|
32
|
+
"next-env.d.ts",
|
|
33
|
+
"**/*.ts",
|
|
34
|
+
"**/*.tsx",
|
|
35
|
+
".next/types/**/*.ts",
|
|
36
|
+
".next/dev/types/**/*.ts"
|
|
37
|
+
],
|
|
38
|
+
"exclude": [
|
|
39
|
+
"node_modules"
|
|
40
|
+
]
|
|
41
|
+
}
|