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,130 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { usePathname, useRouter } from "next/navigation";
|
|
6
|
+
import {
|
|
7
|
+
LayoutDashboard,
|
|
8
|
+
BookOpen,
|
|
9
|
+
CreditCard,
|
|
10
|
+
Settings,
|
|
11
|
+
LogOut,
|
|
12
|
+
} from "lucide-react";
|
|
13
|
+
|
|
14
|
+
import { cn } from "@/lib/utils";
|
|
15
|
+
import { users } from "@/lib/mock-data";
|
|
16
|
+
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
|
17
|
+
|
|
18
|
+
const mockUser = users[0];
|
|
19
|
+
|
|
20
|
+
const navItems = [
|
|
21
|
+
{ label: "Dashboard", href: "/member/dashboard", icon: LayoutDashboard },
|
|
22
|
+
{ label: "Courses", href: "/member/content", icon: BookOpen },
|
|
23
|
+
{ label: "Billing", href: "/member/billing", icon: CreditCard },
|
|
24
|
+
{ label: "Settings", href: "/member/settings", icon: Settings },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
function initials(name: string) {
|
|
28
|
+
return name
|
|
29
|
+
.split(" ")
|
|
30
|
+
.map((part) => part[0])
|
|
31
|
+
.join("")
|
|
32
|
+
.slice(0, 2)
|
|
33
|
+
.toUpperCase();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const pageTitles: Record<string, string> = {
|
|
37
|
+
"/member/dashboard": "Dashboard",
|
|
38
|
+
"/member/content": "Courses",
|
|
39
|
+
"/member/billing": "Billing",
|
|
40
|
+
"/member/settings": "Settings",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default function MemberAppLayout({
|
|
44
|
+
children,
|
|
45
|
+
}: {
|
|
46
|
+
children: React.ReactNode;
|
|
47
|
+
}) {
|
|
48
|
+
const pathname = usePathname();
|
|
49
|
+
const router = useRouter();
|
|
50
|
+
|
|
51
|
+
const title = pageTitles[pathname] ?? "Member Area";
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className="flex min-h-screen bg-background">
|
|
55
|
+
<aside className="flex w-64 flex-col border-r border-border/70 bg-card shadow-soft">
|
|
56
|
+
<div className="flex items-center gap-3 border-b border-border/70 px-6 py-6">
|
|
57
|
+
<span className="font-display text-xl font-medium tracking-tight text-foreground">
|
|
58
|
+
SHIP<span className="text-primary">.</span>
|
|
59
|
+
</span>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div className="flex items-center gap-3 border-b border-border/70 px-6 py-5">
|
|
63
|
+
<Avatar>
|
|
64
|
+
<AvatarFallback>{initials(mockUser.name)}</AvatarFallback>
|
|
65
|
+
</Avatar>
|
|
66
|
+
<div className="min-w-0">
|
|
67
|
+
<p className="truncate text-sm font-semibold leading-tight">
|
|
68
|
+
{mockUser.name}
|
|
69
|
+
</p>
|
|
70
|
+
<p className="label-mono truncate text-muted-foreground">
|
|
71
|
+
{mockUser.plan} plan
|
|
72
|
+
</p>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<nav className="flex-1 space-y-1 px-3 py-5">
|
|
77
|
+
<p className="label-mono px-3 pb-2 text-muted-foreground/70">Menu</p>
|
|
78
|
+
{navItems.map((item) => {
|
|
79
|
+
const Icon = item.icon;
|
|
80
|
+
const active = pathname === item.href;
|
|
81
|
+
return (
|
|
82
|
+
<Link
|
|
83
|
+
key={item.href}
|
|
84
|
+
href={item.href}
|
|
85
|
+
className={cn(
|
|
86
|
+
"group relative flex items-center gap-3 rounded-full px-3 py-2.5 text-sm font-medium transition-all duration-200",
|
|
87
|
+
active
|
|
88
|
+
? "bg-primary text-primary-foreground shadow-soft"
|
|
89
|
+
: "text-foreground/70 hover:bg-muted hover:text-foreground"
|
|
90
|
+
)}
|
|
91
|
+
>
|
|
92
|
+
{active && (
|
|
93
|
+
<span className="absolute -left-3 top-1/2 h-5 w-1 -translate-y-1/2 rounded-full bg-primary" />
|
|
94
|
+
)}
|
|
95
|
+
<Icon className="h-4 w-4" />
|
|
96
|
+
{item.label}
|
|
97
|
+
</Link>
|
|
98
|
+
);
|
|
99
|
+
})}
|
|
100
|
+
</nav>
|
|
101
|
+
|
|
102
|
+
<div className="border-t border-border/70 px-3 py-4">
|
|
103
|
+
<button
|
|
104
|
+
type="button"
|
|
105
|
+
onClick={() => router.push("/member/login")}
|
|
106
|
+
className="flex w-full items-center gap-3 rounded-full px-3 py-2.5 text-sm font-medium text-foreground/70 transition-all duration-200 hover:bg-muted hover:text-foreground"
|
|
107
|
+
>
|
|
108
|
+
<LogOut className="h-4 w-4" />
|
|
109
|
+
Logout
|
|
110
|
+
</button>
|
|
111
|
+
</div>
|
|
112
|
+
</aside>
|
|
113
|
+
|
|
114
|
+
<div className="flex flex-1 flex-col">
|
|
115
|
+
<header className="flex items-center justify-between border-b border-border/70 bg-card/60 px-8 py-4">
|
|
116
|
+
<h1 className="font-display text-lg font-medium tracking-tight">{title}</h1>
|
|
117
|
+
<div className="flex items-center gap-3">
|
|
118
|
+
<Avatar className="h-8 w-8">
|
|
119
|
+
<AvatarFallback className="text-xs">
|
|
120
|
+
{initials(mockUser.name)}
|
|
121
|
+
</AvatarFallback>
|
|
122
|
+
</Avatar>
|
|
123
|
+
</div>
|
|
124
|
+
</header>
|
|
125
|
+
|
|
126
|
+
<main className="flex-1 p-8">{children}</main>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import { users } from "@/lib/mock-data";
|
|
6
|
+
import {
|
|
7
|
+
Card,
|
|
8
|
+
CardHeader,
|
|
9
|
+
CardTitle,
|
|
10
|
+
CardDescription,
|
|
11
|
+
CardContent,
|
|
12
|
+
CardFooter,
|
|
13
|
+
} from "@/components/ui/card";
|
|
14
|
+
import { Input } from "@/components/ui/input";
|
|
15
|
+
import { Button } from "@/components/ui/button";
|
|
16
|
+
|
|
17
|
+
const mockUser = users[0];
|
|
18
|
+
|
|
19
|
+
export default function MemberSettingsPage() {
|
|
20
|
+
const [name, setName] = React.useState(mockUser.name);
|
|
21
|
+
const [email, setEmail] = React.useState(mockUser.email);
|
|
22
|
+
|
|
23
|
+
function handleSave(e: React.FormEvent) {
|
|
24
|
+
e.preventDefault();
|
|
25
|
+
// Mock save only — no backend wired up.
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className="space-y-6">
|
|
30
|
+
<div>
|
|
31
|
+
<h2 className="font-display text-3xl font-medium tracking-tight">
|
|
32
|
+
Settings
|
|
33
|
+
</h2>
|
|
34
|
+
<p className="mt-1 text-muted-foreground">
|
|
35
|
+
Manage your account details and preferences.
|
|
36
|
+
</p>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<Card className="animate-fade-up">
|
|
40
|
+
<CardHeader>
|
|
41
|
+
<CardTitle className="text-base">Account Information</CardTitle>
|
|
42
|
+
<CardDescription>
|
|
43
|
+
Update your name and email address.
|
|
44
|
+
</CardDescription>
|
|
45
|
+
</CardHeader>
|
|
46
|
+
<form onSubmit={handleSave}>
|
|
47
|
+
<CardContent className="space-y-4">
|
|
48
|
+
<div className="space-y-2">
|
|
49
|
+
<label htmlFor="settings-name" className="text-sm font-medium">
|
|
50
|
+
Name
|
|
51
|
+
</label>
|
|
52
|
+
<Input
|
|
53
|
+
id="settings-name"
|
|
54
|
+
value={name}
|
|
55
|
+
onChange={(e) => setName(e.target.value)}
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
<div className="space-y-2">
|
|
59
|
+
<label htmlFor="settings-email" className="text-sm font-medium">
|
|
60
|
+
Email
|
|
61
|
+
</label>
|
|
62
|
+
<Input
|
|
63
|
+
id="settings-email"
|
|
64
|
+
type="email"
|
|
65
|
+
value={email}
|
|
66
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
67
|
+
/>
|
|
68
|
+
</div>
|
|
69
|
+
</CardContent>
|
|
70
|
+
<CardFooter>
|
|
71
|
+
<Button type="submit">Save Changes</Button>
|
|
72
|
+
</CardFooter>
|
|
73
|
+
</form>
|
|
74
|
+
</Card>
|
|
75
|
+
|
|
76
|
+
<Card
|
|
77
|
+
className="animate-fade-up border-destructive/40 bg-destructive/[0.06]"
|
|
78
|
+
style={{ animationDelay: "80ms" }}
|
|
79
|
+
>
|
|
80
|
+
<CardHeader>
|
|
81
|
+
<span className="label-mono text-destructive">Danger Zone</span>
|
|
82
|
+
<CardTitle className="text-base text-destructive">
|
|
83
|
+
Delete account
|
|
84
|
+
</CardTitle>
|
|
85
|
+
<CardDescription>
|
|
86
|
+
Permanently delete your account and all associated data. This
|
|
87
|
+
action cannot be undone.
|
|
88
|
+
</CardDescription>
|
|
89
|
+
</CardHeader>
|
|
90
|
+
<CardFooter>
|
|
91
|
+
<Button variant="destructive">Delete Account</Button>
|
|
92
|
+
</CardFooter>
|
|
93
|
+
</Card>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { useRouter } from "next/navigation";
|
|
6
|
+
|
|
7
|
+
import { Button } from "@/components/ui/button";
|
|
8
|
+
import { Input } from "@/components/ui/input";
|
|
9
|
+
import {
|
|
10
|
+
Card,
|
|
11
|
+
CardHeader,
|
|
12
|
+
CardTitle,
|
|
13
|
+
CardDescription,
|
|
14
|
+
CardContent,
|
|
15
|
+
CardFooter,
|
|
16
|
+
} from "@/components/ui/card";
|
|
17
|
+
|
|
18
|
+
export default function MemberLoginPage() {
|
|
19
|
+
const router = useRouter();
|
|
20
|
+
const [email, setEmail] = React.useState("");
|
|
21
|
+
const [password, setPassword] = React.useState("");
|
|
22
|
+
|
|
23
|
+
function handleLogin(e: React.FormEvent) {
|
|
24
|
+
e.preventDefault();
|
|
25
|
+
router.push("/member/dashboard");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className="flex min-h-screen bg-background">
|
|
30
|
+
{/* Decorative panel — desktop only */}
|
|
31
|
+
<div className="relative hidden w-1/2 flex-col justify-between overflow-hidden bg-card px-12 py-12 lg:flex">
|
|
32
|
+
<div className="bg-grid absolute inset-0" />
|
|
33
|
+
<div className="relative z-10">
|
|
34
|
+
<span className="label-mono text-muted-foreground">SHIP Method OS</span>
|
|
35
|
+
</div>
|
|
36
|
+
<div className="relative z-10 max-w-md">
|
|
37
|
+
<p className="font-display text-4xl leading-tight tracking-tight text-foreground">
|
|
38
|
+
“Build the thing, ship the thing,
|
|
39
|
+
<span className="text-primary"> repeat</span> the thing.”
|
|
40
|
+
</p>
|
|
41
|
+
<p className="label-mono mt-6 text-muted-foreground">
|
|
42
|
+
Member Area — 2026
|
|
43
|
+
</p>
|
|
44
|
+
</div>
|
|
45
|
+
<div className="relative z-10 h-px w-24 bg-primary/40" />
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
{/* Form panel */}
|
|
49
|
+
<div className="flex w-full flex-1 items-center justify-center px-4 py-12 lg:w-1/2">
|
|
50
|
+
<Card className="animate-fade-up w-full max-w-sm">
|
|
51
|
+
<CardHeader className="space-y-1">
|
|
52
|
+
<span className="label-mono text-primary">Welcome back</span>
|
|
53
|
+
<CardTitle className="text-3xl">Log in</CardTitle>
|
|
54
|
+
<CardDescription>
|
|
55
|
+
Access your member dashboard and pick up where you left off.
|
|
56
|
+
</CardDescription>
|
|
57
|
+
</CardHeader>
|
|
58
|
+
<form onSubmit={handleLogin}>
|
|
59
|
+
<CardContent className="space-y-4">
|
|
60
|
+
<div className="space-y-2">
|
|
61
|
+
<label htmlFor="email" className="label-mono text-muted-foreground">
|
|
62
|
+
Email
|
|
63
|
+
</label>
|
|
64
|
+
<Input
|
|
65
|
+
id="email"
|
|
66
|
+
type="email"
|
|
67
|
+
placeholder="you@example.com"
|
|
68
|
+
value={email}
|
|
69
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
70
|
+
required
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
<div className="space-y-2">
|
|
74
|
+
<label htmlFor="password" className="label-mono text-muted-foreground">
|
|
75
|
+
Password
|
|
76
|
+
</label>
|
|
77
|
+
<Input
|
|
78
|
+
id="password"
|
|
79
|
+
type="password"
|
|
80
|
+
placeholder="••••••••"
|
|
81
|
+
value={password}
|
|
82
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
83
|
+
required
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
</CardContent>
|
|
87
|
+
<CardFooter className="flex flex-col gap-4">
|
|
88
|
+
<Button type="submit" className="w-full">
|
|
89
|
+
Log In
|
|
90
|
+
</Button>
|
|
91
|
+
<p className="text-center text-sm text-muted-foreground">
|
|
92
|
+
Don't have an account?{" "}
|
|
93
|
+
<Link
|
|
94
|
+
href="/member/signup"
|
|
95
|
+
className="font-medium text-primary hover:underline"
|
|
96
|
+
>
|
|
97
|
+
Sign up
|
|
98
|
+
</Link>
|
|
99
|
+
</p>
|
|
100
|
+
</CardFooter>
|
|
101
|
+
</form>
|
|
102
|
+
</Card>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { useRouter } from "next/navigation";
|
|
6
|
+
|
|
7
|
+
import { Button } from "@/components/ui/button";
|
|
8
|
+
import { Input } from "@/components/ui/input";
|
|
9
|
+
import {
|
|
10
|
+
Card,
|
|
11
|
+
CardHeader,
|
|
12
|
+
CardTitle,
|
|
13
|
+
CardDescription,
|
|
14
|
+
CardContent,
|
|
15
|
+
CardFooter,
|
|
16
|
+
} from "@/components/ui/card";
|
|
17
|
+
|
|
18
|
+
export default function MemberSignupPage() {
|
|
19
|
+
const router = useRouter();
|
|
20
|
+
const [name, setName] = React.useState("");
|
|
21
|
+
const [email, setEmail] = React.useState("");
|
|
22
|
+
const [password, setPassword] = React.useState("");
|
|
23
|
+
|
|
24
|
+
function handleSignup(e: React.FormEvent) {
|
|
25
|
+
e.preventDefault();
|
|
26
|
+
router.push("/member/dashboard");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className="flex min-h-screen bg-background">
|
|
31
|
+
{/* Decorative panel — desktop only */}
|
|
32
|
+
<div className="relative hidden w-1/2 flex-col justify-between overflow-hidden bg-card px-12 py-12 lg:flex">
|
|
33
|
+
<div className="bg-grid absolute inset-0" />
|
|
34
|
+
<div className="relative z-10">
|
|
35
|
+
<span className="label-mono text-muted-foreground">SHIP Method OS</span>
|
|
36
|
+
</div>
|
|
37
|
+
<div className="relative z-10 max-w-md">
|
|
38
|
+
<p className="font-display text-4xl leading-tight tracking-tight text-foreground">
|
|
39
|
+
“Every course you finish is a
|
|
40
|
+
<span className="text-primary"> compounding</span> decision.”
|
|
41
|
+
</p>
|
|
42
|
+
<p className="label-mono mt-6 text-muted-foreground">
|
|
43
|
+
Member Area — 2026
|
|
44
|
+
</p>
|
|
45
|
+
</div>
|
|
46
|
+
<div className="relative z-10 h-px w-24 bg-primary/40" />
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
{/* Form panel */}
|
|
50
|
+
<div className="flex w-full flex-1 items-center justify-center px-4 py-12 lg:w-1/2">
|
|
51
|
+
<Card className="animate-fade-up w-full max-w-sm">
|
|
52
|
+
<CardHeader className="space-y-1">
|
|
53
|
+
<span className="label-mono text-primary">Get started</span>
|
|
54
|
+
<CardTitle className="text-3xl">Create your account</CardTitle>
|
|
55
|
+
<CardDescription>
|
|
56
|
+
Join the member area to start your courses.
|
|
57
|
+
</CardDescription>
|
|
58
|
+
</CardHeader>
|
|
59
|
+
<form onSubmit={handleSignup}>
|
|
60
|
+
<CardContent className="space-y-4">
|
|
61
|
+
<div className="space-y-2">
|
|
62
|
+
<label htmlFor="name" className="label-mono text-muted-foreground">
|
|
63
|
+
Name
|
|
64
|
+
</label>
|
|
65
|
+
<Input
|
|
66
|
+
id="name"
|
|
67
|
+
type="text"
|
|
68
|
+
placeholder="Jane Doe"
|
|
69
|
+
value={name}
|
|
70
|
+
onChange={(e) => setName(e.target.value)}
|
|
71
|
+
required
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
<div className="space-y-2">
|
|
75
|
+
<label htmlFor="email" className="label-mono text-muted-foreground">
|
|
76
|
+
Email
|
|
77
|
+
</label>
|
|
78
|
+
<Input
|
|
79
|
+
id="email"
|
|
80
|
+
type="email"
|
|
81
|
+
placeholder="you@example.com"
|
|
82
|
+
value={email}
|
|
83
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
84
|
+
required
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
<div className="space-y-2">
|
|
88
|
+
<label htmlFor="password" className="label-mono text-muted-foreground">
|
|
89
|
+
Password
|
|
90
|
+
</label>
|
|
91
|
+
<Input
|
|
92
|
+
id="password"
|
|
93
|
+
type="password"
|
|
94
|
+
placeholder="••••••••"
|
|
95
|
+
value={password}
|
|
96
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
97
|
+
required
|
|
98
|
+
/>
|
|
99
|
+
</div>
|
|
100
|
+
</CardContent>
|
|
101
|
+
<CardFooter className="flex flex-col gap-4">
|
|
102
|
+
<Button type="submit" className="w-full">
|
|
103
|
+
Create Account
|
|
104
|
+
</Button>
|
|
105
|
+
<p className="text-center text-sm text-muted-foreground">
|
|
106
|
+
Already have an account?{" "}
|
|
107
|
+
<Link
|
|
108
|
+
href="/member/login"
|
|
109
|
+
className="font-medium text-primary hover:underline"
|
|
110
|
+
>
|
|
111
|
+
Log in
|
|
112
|
+
</Link>
|
|
113
|
+
</p>
|
|
114
|
+
</CardFooter>
|
|
115
|
+
</form>
|
|
116
|
+
</Card>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { Rocket, GraduationCap, Settings2, ArrowRight } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
Card,
|
|
6
|
+
CardContent,
|
|
7
|
+
CardDescription,
|
|
8
|
+
CardFooter,
|
|
9
|
+
CardHeader,
|
|
10
|
+
CardTitle,
|
|
11
|
+
} from "@/components/ui/card";
|
|
12
|
+
import { Button } from "@/components/ui/button";
|
|
13
|
+
|
|
14
|
+
const areas = [
|
|
15
|
+
{
|
|
16
|
+
href: "/sale",
|
|
17
|
+
icon: Rocket,
|
|
18
|
+
title: "Sale Page",
|
|
19
|
+
description:
|
|
20
|
+
"Public-facing offer page. Headline, pitch, pricing, and the CTA that turns visitors into members.",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
href: "/member/login",
|
|
24
|
+
icon: GraduationCap,
|
|
25
|
+
title: "Member Area",
|
|
26
|
+
description:
|
|
27
|
+
"Logged-in member dashboard. Course access, progress tracking, and account settings.",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
href: "/backoffice",
|
|
31
|
+
icon: Settings2,
|
|
32
|
+
title: "Backoffice",
|
|
33
|
+
description:
|
|
34
|
+
"Internal admin console. Member management, metrics, and operational tools.",
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
export default function Home() {
|
|
39
|
+
return (
|
|
40
|
+
<main className="flex min-h-screen flex-col items-center justify-center bg-background px-6 py-24">
|
|
41
|
+
<div className="mx-auto max-w-3xl text-center">
|
|
42
|
+
<p className="text-sm font-medium uppercase tracking-wide text-muted-foreground">
|
|
43
|
+
The SHIP Method OS
|
|
44
|
+
</p>
|
|
45
|
+
<h1 className="mt-2 text-4xl font-bold tracking-tight text-foreground sm:text-5xl">
|
|
46
|
+
Pick your area
|
|
47
|
+
</h1>
|
|
48
|
+
<p className="mt-4 text-base text-muted-foreground">
|
|
49
|
+
This starter kit ships with three route groups. Each one is being
|
|
50
|
+
built independently on top of the shared UI and mock data layer
|
|
51
|
+
defined here.
|
|
52
|
+
</p>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div className="mt-12 grid w-full max-w-5xl gap-6 sm:grid-cols-3">
|
|
56
|
+
{areas.map((area) => {
|
|
57
|
+
const Icon = area.icon;
|
|
58
|
+
return (
|
|
59
|
+
<Card key={area.href} className="flex flex-col">
|
|
60
|
+
<CardHeader>
|
|
61
|
+
<div className="mb-2 flex h-10 w-10 items-center justify-center rounded-md bg-primary/10 text-primary">
|
|
62
|
+
<Icon className="h-5 w-5" />
|
|
63
|
+
</div>
|
|
64
|
+
<CardTitle>{area.title}</CardTitle>
|
|
65
|
+
<CardDescription>{area.description}</CardDescription>
|
|
66
|
+
</CardHeader>
|
|
67
|
+
<CardContent className="flex-1" />
|
|
68
|
+
<CardFooter>
|
|
69
|
+
<Button asChild className="w-full">
|
|
70
|
+
<Link href={area.href}>
|
|
71
|
+
Enter
|
|
72
|
+
<ArrowRight className="h-4 w-4" />
|
|
73
|
+
</Link>
|
|
74
|
+
</Button>
|
|
75
|
+
</CardFooter>
|
|
76
|
+
</Card>
|
|
77
|
+
);
|
|
78
|
+
})}
|
|
79
|
+
</div>
|
|
80
|
+
</main>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ArrowRight } from "lucide-react";
|
|
2
|
+
|
|
3
|
+
import { Button } from "@/components/ui/button";
|
|
4
|
+
|
|
5
|
+
const footerLinks = [
|
|
6
|
+
{ label: "Product", href: "#" },
|
|
7
|
+
{ label: "Pricing", href: "#" },
|
|
8
|
+
{ label: "About", href: "#" },
|
|
9
|
+
{ label: "Contact", href: "#" },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export function CtaFooter() {
|
|
13
|
+
return (
|
|
14
|
+
<>
|
|
15
|
+
<section className="container mx-auto px-4 py-28">
|
|
16
|
+
<div className="relative overflow-hidden rounded-3xl border border-primary/30 bg-card px-6 py-20 sm:px-16">
|
|
17
|
+
<div className="bg-grid pointer-events-none absolute inset-0 opacity-40" />
|
|
18
|
+
<div className="pointer-events-none absolute -right-24 -top-24 h-72 w-72 rounded-full bg-primary/30 blur-[100px]" />
|
|
19
|
+
<span
|
|
20
|
+
aria-hidden
|
|
21
|
+
className="pointer-events-none absolute -bottom-10 -left-6 select-none font-display text-[10rem] font-medium leading-none text-foreground/[0.05]"
|
|
22
|
+
>
|
|
23
|
+
→
|
|
24
|
+
</span>
|
|
25
|
+
|
|
26
|
+
<div className="relative flex flex-col items-start gap-6 text-left">
|
|
27
|
+
<p className="label-mono text-primary">Ready when you are</p>
|
|
28
|
+
<h2 className="max-w-2xl font-display text-4xl font-medium leading-tight tracking-tight sm:text-5xl">
|
|
29
|
+
Ready to <span className="italic text-primary">ship</span>{" "}
|
|
30
|
+
faster?
|
|
31
|
+
</h2>
|
|
32
|
+
<p className="max-w-xl text-muted-foreground">
|
|
33
|
+
Join hundreds of teams who've already ditched the
|
|
34
|
+
spreadsheet chaos. Start your free trial today — no credit card
|
|
35
|
+
required.
|
|
36
|
+
</p>
|
|
37
|
+
<Button size="lg" className="glow-primary gap-2">
|
|
38
|
+
Get Started
|
|
39
|
+
<ArrowRight className="h-4 w-4" />
|
|
40
|
+
</Button>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</section>
|
|
44
|
+
|
|
45
|
+
<footer className="border-t border-border">
|
|
46
|
+
<div className="container mx-auto flex flex-col items-center justify-between gap-6 px-4 py-10 sm:flex-row">
|
|
47
|
+
<p className="font-display text-sm font-medium">SHIP Method OS</p>
|
|
48
|
+
<nav className="flex flex-wrap items-center justify-center gap-6 text-sm text-muted-foreground">
|
|
49
|
+
{footerLinks.map((link) => (
|
|
50
|
+
<a
|
|
51
|
+
key={link.label}
|
|
52
|
+
href={link.href}
|
|
53
|
+
className="transition-colors hover:text-foreground"
|
|
54
|
+
>
|
|
55
|
+
{link.label}
|
|
56
|
+
</a>
|
|
57
|
+
))}
|
|
58
|
+
</nav>
|
|
59
|
+
<p className="label-mono text-muted-foreground">
|
|
60
|
+
Built with The SHIP Method OS
|
|
61
|
+
</p>
|
|
62
|
+
</div>
|
|
63
|
+
</footer>
|
|
64
|
+
</>
|
|
65
|
+
);
|
|
66
|
+
}
|