@kennethsolomon/shipkit 3.2.0 → 3.4.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.
@@ -0,0 +1,189 @@
1
+ # Next.js + Tailwind — Stack Reference
2
+
3
+ ## Scaffold
4
+
5
+ ```bash
6
+ npx create-next-app@latest {project-name} --typescript --tailwind --eslint --app --src-dir --no-import-alias
7
+ ```
8
+
9
+ Then `cd {project-name} && npm install`.
10
+
11
+ ## Directory Structure
12
+
13
+ ```
14
+ {project-name}/
15
+ ├── src/
16
+ │ ├── app/
17
+ │ │ ├── layout.tsx ← root layout (fonts, global styles)
18
+ │ │ ├── page.tsx ← landing page
19
+ │ │ ├── globals.css ← Tailwind directives + custom CSS
20
+ │ │ ├── api/
21
+ │ │ │ └── waitlist/
22
+ │ │ │ └── route.ts ← waitlist API handler
23
+ │ │ ├── dashboard/
24
+ │ │ │ └── page.tsx ← dashboard page
25
+ │ │ ├── {feature-1}/
26
+ │ │ │ └── page.tsx
27
+ │ │ ├── {feature-2}/
28
+ │ │ │ └── page.tsx
29
+ │ │ └── settings/
30
+ │ │ └── page.tsx
31
+ │ └── components/
32
+ │ ├── landing/
33
+ │ │ ├── Navbar.tsx
34
+ │ │ ├── Hero.tsx
35
+ │ │ ├── Features.tsx
36
+ │ │ ├── HowItWorks.tsx
37
+ │ │ ├── Pricing.tsx
38
+ │ │ ├── Testimonials.tsx
39
+ │ │ ├── WaitlistForm.tsx
40
+ │ │ └── Footer.tsx
41
+ │ ├── app/
42
+ │ │ ├── Sidebar.tsx
43
+ │ │ ├── DashboardCards.tsx
44
+ │ │ └── {feature components}
45
+ │ └── ui/
46
+ │ ├── Button.tsx
47
+ │ ├── Input.tsx
48
+ │ ├── Card.tsx
49
+ │ ├── Modal.tsx
50
+ │ └── Toast.tsx
51
+ ├── public/
52
+ │ └── {static assets}
53
+ ├── waitlist.json ← email storage (auto-created by API)
54
+ ├── tailwind.config.ts
55
+ ├── next.config.ts
56
+ └── package.json
57
+ ```
58
+
59
+ ## Root Layout Pattern
60
+
61
+ `src/app/layout.tsx`:
62
+ - Import Google Fonts via `next/font/google`.
63
+ - Apply font CSS variables to `<html>` element.
64
+ - Include global nav only for app pages (not landing page — it has its own navbar).
65
+
66
+ ```tsx
67
+ import { {DisplayFont}, {BodyFont} } from 'next/font/google'
68
+
69
+ const display = {DisplayFont}({ subsets: ['latin'], variable: '--font-display' })
70
+ const body = {BodyFont}({ subsets: ['latin'], variable: '--font-body' })
71
+
72
+ export default function RootLayout({ children }) {
73
+ return (
74
+ <html className={`${display.variable} ${body.variable}`}>
75
+ <body className="font-body antialiased">{children}</body>
76
+ </html>
77
+ )
78
+ }
79
+ ```
80
+
81
+ ## Tailwind Config
82
+
83
+ `tailwind.config.ts` — extend with custom palette and fonts:
84
+
85
+ ```ts
86
+ export default {
87
+ theme: {
88
+ extend: {
89
+ colors: {
90
+ bg: 'var(--color-bg)',
91
+ fg: 'var(--color-fg)',
92
+ accent: 'var(--color-accent)',
93
+ muted: 'var(--color-muted)',
94
+ // add more as needed
95
+ },
96
+ fontFamily: {
97
+ display: ['var(--font-display)', 'serif'],
98
+ body: ['var(--font-body)', 'sans-serif'],
99
+ },
100
+ },
101
+ },
102
+ }
103
+ ```
104
+
105
+ Define CSS variables in `globals.css`:
106
+
107
+ ```css
108
+ @tailwind base;
109
+ @tailwind components;
110
+ @tailwind utilities;
111
+
112
+ :root {
113
+ --color-bg: #xxxxxx;
114
+ --color-fg: #xxxxxx;
115
+ --color-accent: #xxxxxx;
116
+ --color-muted: #xxxxxx;
117
+ }
118
+ ```
119
+
120
+ ## Waitlist API Route
121
+
122
+ `src/app/api/waitlist/route.ts`:
123
+
124
+ ```ts
125
+ import { NextResponse } from 'next/server'
126
+ import { readFile, writeFile } from 'fs/promises'
127
+ import { join } from 'path'
128
+
129
+ const WAITLIST_PATH = join(process.cwd(), 'waitlist.json')
130
+
131
+ export async function POST(request: Request) {
132
+ const { email } = await request.json()
133
+
134
+ // Validate
135
+ if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
136
+ return NextResponse.json({ success: false, message: 'Please enter a valid email.' }, { status: 400 })
137
+ }
138
+
139
+ // Read or create
140
+ let data = { entries: [] as Array<{ email: string; timestamp: string; source: string }> }
141
+ try {
142
+ const raw = await readFile(WAITLIST_PATH, 'utf-8')
143
+ data = JSON.parse(raw)
144
+ } catch {
145
+ // File doesn't exist yet — use empty default
146
+ }
147
+
148
+ // Check duplicate
149
+ if (data.entries.some(e => e.email === email)) {
150
+ return NextResponse.json({ success: true, message: "You're already on the list!" })
151
+ }
152
+
153
+ // Append
154
+ data.entries.push({ email, timestamp: new Date().toISOString(), source: 'landing-page' })
155
+ await writeFile(WAITLIST_PATH, JSON.stringify(data, null, 2))
156
+
157
+ return NextResponse.json({ success: true, message: "You're on the list!" })
158
+ }
159
+ ```
160
+
161
+ ## Component Patterns
162
+
163
+ - Landing page components are **server components** by default (no `"use client"`).
164
+ - Interactive components (WaitlistForm, modals, toasts) need `"use client"` directive.
165
+ - Use `useState` for local UI state (form values, modal open/close).
166
+ - Navigation between app pages: use `<Link>` from `next/link`.
167
+ - App pages can share a layout: `src/app/(app)/layout.tsx` with sidebar.
168
+
169
+ ### App Layout (grouped routes)
170
+
171
+ ```
172
+ src/app/
173
+ ├── page.tsx ← landing page (no app layout)
174
+ ├── (app)/
175
+ │ ├── layout.tsx ← shared sidebar + header
176
+ │ ├── dashboard/page.tsx
177
+ │ ├── {feature-1}/page.tsx
178
+ │ ├── {feature-2}/page.tsx
179
+ │ └── settings/page.tsx
180
+ ```
181
+
182
+ `(app)/layout.tsx` wraps app pages with sidebar navigation without affecting the landing page.
183
+
184
+ ## Dev Server
185
+
186
+ ```bash
187
+ npm run dev
188
+ # Runs on http://localhost:3000
189
+ ```
@@ -0,0 +1,250 @@
1
+ # Nuxt + Tailwind — Stack Reference
2
+
3
+ ## Scaffold
4
+
5
+ ```bash
6
+ npx nuxi@latest init {project-name}
7
+ cd {project-name}
8
+ npm install
9
+ npx nuxi module add @nuxtjs/tailwindcss
10
+ npx nuxi module add @nuxtjs/google-fonts
11
+ ```
12
+
13
+ ## Directory Structure
14
+
15
+ ```
16
+ {project-name}/
17
+ ├── pages/
18
+ │ ├── index.vue ← landing page
19
+ │ ├── dashboard.vue ← dashboard
20
+ │ ├── {feature-1}.vue
21
+ │ ├── {feature-2}.vue
22
+ │ └── settings.vue
23
+ ├── components/
24
+ │ ├── landing/
25
+ │ │ ├── Navbar.vue
26
+ │ │ ├── Hero.vue
27
+ │ │ ├── Features.vue
28
+ │ │ ├── HowItWorks.vue
29
+ │ │ ├── Pricing.vue
30
+ │ │ ├── Testimonials.vue
31
+ │ │ ├── WaitlistForm.vue
32
+ │ │ └── Footer.vue
33
+ │ ├── app/
34
+ │ │ ├── Sidebar.vue
35
+ │ │ ├── DashboardCards.vue
36
+ │ │ └── {feature components}
37
+ │ └── ui/
38
+ │ ├── UButton.vue
39
+ │ ├── UInput.vue
40
+ │ ├── UCard.vue
41
+ │ ├── UModal.vue
42
+ │ └── UToast.vue
43
+ ├── layouts/
44
+ │ ├── default.vue ← app layout (sidebar + header)
45
+ │ └── landing.vue ← landing page layout (no sidebar)
46
+ ├── server/
47
+ │ └── api/
48
+ │ └── waitlist.post.ts ← waitlist API handler
49
+ ├── public/
50
+ │ └── {static assets}
51
+ ├── waitlist.json ← email storage (auto-created by API)
52
+ ├── tailwind.config.ts
53
+ ├── nuxt.config.ts
54
+ └── package.json
55
+ ```
56
+
57
+ ## Nuxt Config
58
+
59
+ `nuxt.config.ts`:
60
+
61
+ ```ts
62
+ export default defineNuxtConfig({
63
+ modules: [
64
+ '@nuxtjs/tailwindcss',
65
+ '@nuxtjs/google-fonts',
66
+ ],
67
+ googleFonts: {
68
+ families: {
69
+ '{DisplayFont}': [400, 600, 700, 800],
70
+ '{BodyFont}': [400, 500, 600],
71
+ },
72
+ },
73
+ app: {
74
+ head: {
75
+ title: '{Product Name}',
76
+ meta: [
77
+ { name: 'description', content: '{product description}' },
78
+ ],
79
+ },
80
+ },
81
+ })
82
+ ```
83
+
84
+ ## Tailwind Config
85
+
86
+ `tailwind.config.ts`:
87
+
88
+ ```ts
89
+ export default {
90
+ theme: {
91
+ extend: {
92
+ colors: {
93
+ bg: 'var(--color-bg)',
94
+ fg: 'var(--color-fg)',
95
+ accent: 'var(--color-accent)',
96
+ muted: 'var(--color-muted)',
97
+ },
98
+ fontFamily: {
99
+ display: ['{DisplayFont}', 'serif'],
100
+ body: ['{BodyFont}', 'sans-serif'],
101
+ },
102
+ },
103
+ },
104
+ }
105
+ ```
106
+
107
+ CSS variables in `assets/css/main.css` (referenced in nuxt.config):
108
+
109
+ ```css
110
+ :root {
111
+ --color-bg: #xxxxxx;
112
+ --color-fg: #xxxxxx;
113
+ --color-accent: #xxxxxx;
114
+ --color-muted: #xxxxxx;
115
+ }
116
+
117
+ body {
118
+ font-family: '{BodyFont}', sans-serif;
119
+ }
120
+ ```
121
+
122
+ ## Layouts
123
+
124
+ ### Landing Layout (`layouts/landing.vue`)
125
+
126
+ ```vue
127
+ <template>
128
+ <div class="min-h-screen bg-bg text-fg">
129
+ <slot />
130
+ </div>
131
+ </template>
132
+ ```
133
+
134
+ Landing page uses this layout via `definePageMeta({ layout: 'landing' })`.
135
+
136
+ ### App Layout (`layouts/default.vue`)
137
+
138
+ ```vue
139
+ <template>
140
+ <div class="flex min-h-screen bg-bg text-fg">
141
+ <AppSidebar />
142
+ <main class="flex-1 p-6 lg:p-8">
143
+ <slot />
144
+ </main>
145
+ </div>
146
+ </template>
147
+ ```
148
+
149
+ All app pages use this layout by default.
150
+
151
+ ## Waitlist API Route
152
+
153
+ `server/api/waitlist.post.ts`:
154
+
155
+ ```ts
156
+ import { readFile, writeFile } from 'fs/promises'
157
+ import { join } from 'path'
158
+
159
+ const WAITLIST_PATH = join(process.cwd(), 'waitlist.json')
160
+
161
+ export default defineEventHandler(async (event) => {
162
+ const { email } = await readBody(event)
163
+
164
+ // Validate
165
+ if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
166
+ throw createError({ statusCode: 400, message: 'Please enter a valid email.' })
167
+ }
168
+
169
+ // Read or create
170
+ let data: { entries: Array<{ email: string; timestamp: string; source: string }> } = { entries: [] }
171
+ try {
172
+ const raw = await readFile(WAITLIST_PATH, 'utf-8')
173
+ data = JSON.parse(raw)
174
+ } catch {
175
+ // File doesn't exist yet
176
+ }
177
+
178
+ // Check duplicate
179
+ if (data.entries.some(e => e.email === email)) {
180
+ return { success: true, message: "You're already on the list!" }
181
+ }
182
+
183
+ // Append
184
+ data.entries.push({ email, timestamp: new Date().toISOString(), source: 'landing-page' })
185
+ await writeFile(WAITLIST_PATH, JSON.stringify(data, null, 2))
186
+
187
+ return { success: true, message: "You're on the list!" }
188
+ })
189
+ ```
190
+
191
+ ## Component Patterns
192
+
193
+ - Use Vue 3 Composition API with `<script setup lang="ts">`.
194
+ - Components are auto-imported by Nuxt (no manual imports needed).
195
+ - Use `ref()` and `reactive()` for state management.
196
+ - Navigation: `<NuxtLink to="/dashboard">`.
197
+ - Pages set layout via `definePageMeta({ layout: 'landing' })`.
198
+
199
+ ### Page Example
200
+
201
+ ```vue
202
+ <script setup lang="ts">
203
+ definePageMeta({ layout: 'landing' })
204
+ </script>
205
+
206
+ <template>
207
+ <div>
208
+ <LandingNavbar />
209
+ <LandingHero />
210
+ <LandingFeatures />
211
+ <LandingHowItWorks />
212
+ <LandingPricing />
213
+ <LandingTestimonials />
214
+ <LandingWaitlistForm />
215
+ <LandingFooter />
216
+ </div>
217
+ </template>
218
+ ```
219
+
220
+ ### WaitlistForm Component
221
+
222
+ ```vue
223
+ <script setup lang="ts">
224
+ const email = ref('')
225
+ const status = ref<'idle' | 'loading' | 'success' | 'error'>('idle')
226
+ const message = ref('')
227
+
228
+ async function submit() {
229
+ status.value = 'loading'
230
+ try {
231
+ const res = await $fetch('/api/waitlist', {
232
+ method: 'POST',
233
+ body: { email: email.value },
234
+ })
235
+ status.value = 'success'
236
+ message.value = res.message
237
+ } catch (err: any) {
238
+ status.value = 'error'
239
+ message.value = err.data?.message || 'Something went wrong.'
240
+ }
241
+ }
242
+ </script>
243
+ ```
244
+
245
+ ## Dev Server
246
+
247
+ ```bash
248
+ npm run dev
249
+ # Runs on http://localhost:3000
250
+ ```