@newt-app/templates 0.2.0 → 0.2.1

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.
Files changed (2) hide show
  1. package/dist/index.js +420 -0
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1909,6 +1909,422 @@ export function Logo({ className }: LogoProps) {
1909
1909
  }`
1910
1910
  };
1911
1911
 
1912
+ // src/shadcn-ui/templates/layout.ts
1913
+ var layout_default2 = {
1914
+ filename: "apps/web/app/layout.tsx",
1915
+ template: `import type { Metadata } from "next";
1916
+ import localFont from "next/font/local";
1917
+ import Providers from "@/app/providers";
1918
+ import "@<%= projectName %>/ui/globals.css";
1919
+
1920
+ const geistSans = localFont({
1921
+ src: "./fonts/GeistVF.woff",
1922
+ variable: "--font-geist-sans",
1923
+ });
1924
+ const geistMono = localFont({
1925
+ src: "./fonts/GeistMonoVF.woff",
1926
+ variable: "--font-geist-mono",
1927
+ });
1928
+
1929
+ export const metadata: Metadata = {
1930
+ title: "<%= projectName %>",
1931
+ description: "Next + Nest = Newt",
1932
+ icons: {
1933
+ icon: [
1934
+ { url: "/icon0.svg", type: "image/svg+xml" },
1935
+ { url: "/icon1.png", type: "image/png" },
1936
+ ],
1937
+ apple: "/apple-icon.png",
1938
+ },
1939
+ };
1940
+
1941
+ export default function RootLayout({
1942
+ children,
1943
+ }: Readonly<{
1944
+ children: React.ReactNode;
1945
+ }>) {
1946
+ return (
1947
+ <html lang="en" className="dark">
1948
+ <body className={\`\${geistSans.variable} \${geistMono.variable} h-full\`}>
1949
+ <Providers>{children}</Providers>
1950
+ </body>
1951
+ </html>
1952
+ );
1953
+ }`
1954
+ };
1955
+
1956
+ // src/shadcn-ui/templates/page.ts
1957
+ var page_default2 = {
1958
+ filename: "apps/web/app/page.tsx",
1959
+ template: `'use client';
1960
+
1961
+ import { useQuery } from '@tanstack/react-query';
1962
+ import { authClient } from '@/lib/auth-client';
1963
+ import { AuthForm } from '@/app/auth-form';
1964
+ import { Link } from '@<%= projectName %>/ui/link';
1965
+ import { Logo } from '@<%= projectName %>/ui/logo';
1966
+ import { TodoList } from '@/app/todo-list';
1967
+ import { Card, CardContent, CardHeader, CardTitle } from '@<%= projectName %>/ui/card';
1968
+
1969
+ export default function Home() {
1970
+ const { data: session, isPending } = authClient.useSession();
1971
+
1972
+ const { data: hello } = useQuery({
1973
+ queryKey: ['hello'],
1974
+ queryFn: () => fetch('/api/hello').then((r) => r.json()),
1975
+ });
1976
+
1977
+ return (
1978
+ <main className="max-w-lg mx-auto min-h-full px-4 py-8 space-y-4">
1979
+ <div className="pb-4 border-b">
1980
+ <p className="font-mono text-xs text-muted-foreground">apps/web/page.tsx</p>
1981
+ <p className="text-sm text-muted-foreground">Delete me to get started!</p>
1982
+ </div>
1983
+
1984
+ <div className="flex items-center gap-3 py-2">
1985
+ <Logo className="w-10 h-auto text-foreground" />
1986
+ <div>
1987
+ <h1 className="text-4xl font-black tracking-tight"><%= projectName %></h1>
1988
+ <p className="text-sm text-muted-foreground tracking-widest uppercase">
1989
+ Next + Nest = Newt \u{1F49C}
1990
+ </p>
1991
+ </div>
1992
+ </div>
1993
+
1994
+ <Card>
1995
+ <CardHeader>
1996
+ <CardTitle className="text-xs font-mono uppercase tracking-widest text-muted-foreground">
1997
+ next.js
1998
+ </CardTitle>
1999
+ </CardHeader>
2000
+ <CardContent>
2001
+ <p className="font-mono text-sm">apps/web/layout.tsx</p>
2002
+ <p className="text-sm text-muted-foreground">Next.js rendering</p>
2003
+ </CardContent>
2004
+ </Card>
2005
+
2006
+ <Card>
2007
+ <CardHeader>
2008
+ <CardTitle className="text-xs font-mono uppercase tracking-widest text-muted-foreground">
2009
+ nest.js
2010
+ </CardTitle>
2011
+ </CardHeader>
2012
+ <CardContent>
2013
+ <p className="font-mono text-sm">GET /api/hello</p>
2014
+ <pre className="mt-2 rounded-md border p-3 text-sm bg-muted/50">
2015
+ <code>{JSON.stringify(hello, null, 2)}</code>
2016
+ </pre>
2017
+ </CardContent>
2018
+ </Card>
2019
+
2020
+ <Card>
2021
+ <CardHeader>
2022
+ <CardTitle className="text-xs font-mono uppercase tracking-widest text-muted-foreground">
2023
+ better-auth
2024
+ </CardTitle>
2025
+ </CardHeader>
2026
+ <CardContent>
2027
+ {isPending ? (
2028
+ <p className="text-sm text-muted-foreground">Loading\u2026</p>
2029
+ ) : session ? (
2030
+ <TodoList session={session} />
2031
+ ) : (
2032
+ <AuthForm />
2033
+ )}
2034
+ </CardContent>
2035
+ </Card>
2036
+
2037
+ <div className="px-2 py-4 text-sm">
2038
+ <p className="mb-2 text-muted-foreground">Learn more</p>
2039
+ <ul className="list-disc list-inside space-y-2 mt-2">
2040
+ <li>
2041
+ <Link href="https://github.com">GitHub</Link>
2042
+ </li>
2043
+ <li>
2044
+ <Link href="https://nextjs.org">Next.js</Link>
2045
+ </li>
2046
+ <li>
2047
+ <Link href="https://nestjs.com">NestJS</Link>
2048
+ </li>
2049
+ </ul>
2050
+ </div>
2051
+ </main>
2052
+ );
2053
+ }`
2054
+ };
2055
+
2056
+ // src/shadcn-ui/templates/auth-form.ts
2057
+ var auth_form_default2 = {
2058
+ filename: "apps/web/app/auth-form.tsx",
2059
+ template: `'use client';
2060
+
2061
+ import { useForm } from '@tanstack/react-form';
2062
+ import { useState } from 'react';
2063
+ import { authClient } from '@/lib/auth-client';
2064
+ import { Button } from '@<%= projectName %>/ui/button';
2065
+ import { Input } from '@<%= projectName %>/ui/input';
2066
+ import { Label } from '@<%= projectName %>/ui/label';
2067
+
2068
+ export function AuthForm() {
2069
+ const [tab, setTab] = useState<'signin' | 'signup'>('signup');
2070
+ const [error, setError] = useState('');
2071
+
2072
+ const form = useForm({
2073
+ defaultValues: { name: '', email: '', password: '' },
2074
+ onSubmit: async ({ value }) => {
2075
+ setError('');
2076
+ if (tab === 'signin') {
2077
+ const { error } = await authClient.signIn.email(value);
2078
+ if (error) setError(error.message ?? 'Sign in failed');
2079
+ } else {
2080
+ const { error } = await authClient.signUp.email(value);
2081
+ if (error) setError(error.message ?? 'Sign up failed');
2082
+ }
2083
+ },
2084
+ });
2085
+
2086
+ return (
2087
+ <>
2088
+ <div className="flex gap-1 mb-6">
2089
+ <Button
2090
+ type="button"
2091
+ variant={tab === 'signup' ? 'secondary' : 'ghost'}
2092
+ size="sm"
2093
+ onClick={() => setTab('signup')}
2094
+ >
2095
+ Sign up
2096
+ </Button>
2097
+ <Button
2098
+ type="button"
2099
+ variant={tab === 'signin' ? 'secondary' : 'ghost'}
2100
+ size="sm"
2101
+ onClick={() => setTab('signin')}
2102
+ >
2103
+ Sign in
2104
+ </Button>
2105
+ </div>
2106
+
2107
+ <form
2108
+ onSubmit={(e) => {
2109
+ e.preventDefault();
2110
+ form.handleSubmit();
2111
+ }}
2112
+ className="space-y-4"
2113
+ >
2114
+ {tab === 'signup' && (
2115
+ <form.Field name="name">
2116
+ {(field) => (
2117
+ <div className="space-y-1.5">
2118
+ <Label htmlFor="name">Name</Label>
2119
+ <Input
2120
+ id="name"
2121
+ type="text"
2122
+ value={field.state.value}
2123
+ onChange={(e) => field.handleChange(e.target.value)}
2124
+ required
2125
+ />
2126
+ </div>
2127
+ )}
2128
+ </form.Field>
2129
+ )}
2130
+
2131
+ <form.Field name="email">
2132
+ {(field) => (
2133
+ <div className="space-y-1.5">
2134
+ <Label htmlFor="email">Email</Label>
2135
+ <Input
2136
+ id="email"
2137
+ type="email"
2138
+ value={field.state.value}
2139
+ onChange={(e) => field.handleChange(e.target.value)}
2140
+ required
2141
+ />
2142
+ </div>
2143
+ )}
2144
+ </form.Field>
2145
+
2146
+ <form.Field name="password">
2147
+ {(field) => (
2148
+ <div className="space-y-1.5">
2149
+ <Label htmlFor="password">Password</Label>
2150
+ <Input
2151
+ id="password"
2152
+ type="password"
2153
+ value={field.state.value}
2154
+ onChange={(e) => field.handleChange(e.target.value)}
2155
+ required
2156
+ minLength={8}
2157
+ />
2158
+ </div>
2159
+ )}
2160
+ </form.Field>
2161
+
2162
+ {error && <p className="text-sm text-destructive">{error}</p>}
2163
+
2164
+ <form.Subscribe selector={(s) => s.isSubmitting}>
2165
+ {(isSubmitting) => (
2166
+ <Button type="submit" disabled={isSubmitting} className="w-full">
2167
+ {isSubmitting
2168
+ ? 'Loading\u2026'
2169
+ : tab === 'signin'
2170
+ ? 'Sign in'
2171
+ : 'Create account'}
2172
+ </Button>
2173
+ )}
2174
+ </form.Subscribe>
2175
+ </form>
2176
+ </>
2177
+ );
2178
+ }`
2179
+ };
2180
+
2181
+ // src/shadcn-ui/templates/todo-list.ts
2182
+ var todo_list_default2 = {
2183
+ filename: "apps/web/app/todo-list.tsx",
2184
+ template: `'use client';
2185
+
2186
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
2187
+ import { useForm } from '@tanstack/react-form';
2188
+ import { authClient } from '@/lib/auth-client';
2189
+ import { Button } from '@<%= projectName %>/ui/button';
2190
+ import { Input } from '@<%= projectName %>/ui/input';
2191
+ import { Checkbox } from '@<%= projectName %>/ui/checkbox';
2192
+
2193
+ interface Todo {
2194
+ id: number;
2195
+ title: string;
2196
+ done: boolean;
2197
+ }
2198
+
2199
+ const api = {
2200
+ getTodos: (): Promise<Todo[]> => fetch('/api/todos').then((r) => r.json()),
2201
+ createTodo: (title: string): Promise<Todo> =>
2202
+ fetch('/api/todos', {
2203
+ method: 'POST',
2204
+ headers: { 'Content-Type': 'application/json' },
2205
+ body: JSON.stringify({ title }),
2206
+ }).then((r) => r.json()),
2207
+ toggleTodo: (id: number): Promise<Todo> =>
2208
+ fetch(\`/api/todos/\${id}/toggle\`, { method: 'PATCH' }).then((r) => r.json()),
2209
+ deleteTodo: (id: number): Promise<void> =>
2210
+ fetch(\`/api/todos/\${id}\`, { method: 'DELETE' }).then(() => undefined),
2211
+ };
2212
+
2213
+ export function TodoList({
2214
+ session,
2215
+ }: {
2216
+ session: { user: { email: string } };
2217
+ }) {
2218
+ const queryClient = useQueryClient();
2219
+
2220
+ const { data: todos = [], isPending } = useQuery({
2221
+ queryKey: ['todos'],
2222
+ queryFn: api.getTodos,
2223
+ });
2224
+
2225
+ const createMutation = useMutation({
2226
+ mutationFn: api.createTodo,
2227
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
2228
+ });
2229
+
2230
+ const toggleMutation = useMutation({
2231
+ mutationFn: api.toggleTodo,
2232
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
2233
+ });
2234
+
2235
+ const deleteMutation = useMutation({
2236
+ mutationFn: api.deleteTodo,
2237
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
2238
+ });
2239
+
2240
+ const form = useForm({
2241
+ defaultValues: { title: '' },
2242
+ onSubmit: async ({ value }) => {
2243
+ if (!value.title.trim()) return;
2244
+ createMutation.mutate(value.title.trim());
2245
+ form.reset();
2246
+ },
2247
+ });
2248
+
2249
+ return (
2250
+ <>
2251
+ <div className="flex items-center justify-between mb-6">
2252
+ <h1 className="text-2xl font-semibold">Todos</h1>
2253
+ <div className="flex items-center gap-3 text-sm text-muted-foreground">
2254
+ <span>{session.user.email}</span>
2255
+ <Button
2256
+ variant="ghost"
2257
+ size="sm"
2258
+ onClick={() => authClient.signOut()}
2259
+ >
2260
+ Sign out
2261
+ </Button>
2262
+ </div>
2263
+ </div>
2264
+
2265
+ <form
2266
+ onSubmit={(e) => {
2267
+ e.preventDefault();
2268
+ form.handleSubmit();
2269
+ }}
2270
+ className="flex gap-2 mb-6"
2271
+ >
2272
+ <form.Field name="title">
2273
+ {(field) => (
2274
+ <Input
2275
+ value={field.state.value}
2276
+ onChange={(e) => field.handleChange(e.target.value)}
2277
+ placeholder="New todo\u2026"
2278
+ className="flex-1"
2279
+ />
2280
+ )}
2281
+ </form.Field>
2282
+
2283
+ <form.Subscribe selector={(s) => s.isSubmitting}>
2284
+ {(isSubmitting) => (
2285
+ <Button type="submit" disabled={isSubmitting || createMutation.isPending}>
2286
+ Add
2287
+ </Button>
2288
+ )}
2289
+ </form.Subscribe>
2290
+ </form>
2291
+
2292
+ {isPending ? (
2293
+ <p className="text-sm text-muted-foreground">Loading\u2026</p>
2294
+ ) : (
2295
+ <ul className="divide-y divide-border">
2296
+ {todos.map((todo) => (
2297
+ <li key={todo.id} className="flex items-center gap-3 py-3">
2298
+ <Checkbox
2299
+ checked={todo.done}
2300
+ onCheckedChange={() => toggleMutation.mutate(todo.id)}
2301
+ />
2302
+ <span
2303
+ className={\`flex-1 text-sm \${todo.done ? 'line-through text-muted-foreground' : ''}\`}
2304
+ >
2305
+ {todo.title}
2306
+ </span>
2307
+ <Button
2308
+ variant="ghost"
2309
+ size="sm"
2310
+ onClick={() => deleteMutation.mutate(todo.id)}
2311
+ className="text-muted-foreground hover:text-destructive h-7 w-7 p-0"
2312
+ >
2313
+ \xD7
2314
+ </Button>
2315
+ </li>
2316
+ ))}
2317
+ </ul>
2318
+ )}
2319
+
2320
+ {!isPending && todos.length === 0 && (
2321
+ <p className="text-sm text-muted-foreground">No todos yet.</p>
2322
+ )}
2323
+ </>
2324
+ );
2325
+ }`
2326
+ };
2327
+
1912
2328
  // src/shadcn-ui/templates/accordion.ts
1913
2329
  var accordion_default = {
1914
2330
  filename: "packages/ui/src/components/accordion.tsx",
@@ -7435,6 +7851,10 @@ var shadcnUi = {
7435
7851
  eslint_config_default4,
7436
7852
  link_default2,
7437
7853
  logo_default2,
7854
+ layout_default2,
7855
+ page_default2,
7856
+ auth_form_default2,
7857
+ todo_list_default2,
7438
7858
  accordion_default,
7439
7859
  alert_dialog_default,
7440
7860
  alert_default,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newt-app/templates",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "private": false,
5
5
  "description": "Templates for newt-app",
6
6
  "type": "module",