Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: init new example #5595

Open
wants to merge 3 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/.experimental/next-app-dir-new/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
AUTH_SECRET=FlIbvvIt9KQXVBcg0wKpcrHMwGA5RNxpxn826LdbBF8=
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't store stuff like this in git

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha just wanted the example to start right up 😅😅 not like that auth token does anythign secret

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, but if people clone the project they will get the a bad setup, .env should be in .gitignore :)

36 changes: 36 additions & 0 deletions examples/.experimental/next-app-dir-new/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should the db really be checked in? shouldn't that be lazily created with migrations or smth?

can we do something that works to deploy on vercel? like sqlite locally and pg on vercel?

Binary file not shown.
4 changes: 4 additions & 0 deletions examples/.experimental/next-app-dir-new/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};

export default nextConfig;
37 changes: 37 additions & 0 deletions examples/.experimental/next-app-dir-new/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "examples-next-app-dir-new",
"private": true,
"type": "module",
"scripts": {
"db:push": "drizzle-kit push:sqlite --config src/db/config.ts",
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@hookform/resolvers": "npm:@hookform/resolvers@3.3.4",
"@trpc/server": "npm:@trpc/server@next",
"better-sqlite3": "^9.4.3",
"drizzle-orm": "^0.30.5",
"drizzle-zod": "^0.5.1",
"next": "npm:next@14.2.0-canary.42",
"next-auth": "npm:next-auth@5.0.0-beta.16",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.43.3",
"server-only": "0.0.1",
"sonner": "^1.4.41",
"zod": "^3.0.0"
},
"devDependencies": {
"@types/node": "^20.10.0",
"@types/react": "^18.2.33",
"@types/react-dom": "^18.2.14",
"drizzle-kit": "^0.20.14",
"eslint": "^8.56.0",
"postcss": "^8.4.14",
"tailwindcss": "^3.3.0",
"typescript": "^5.4.0"
}
}
3 changes: 3 additions & 0 deletions examples/.experimental/next-app-dir-new/postcss.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
plugins: { tailwindcss: {} },
};
64 changes: 64 additions & 0 deletions examples/.experimental/next-app-dir-new/src/app/AddPostForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use client';

import { addPostSchema } from '@/db/schema';
import { toast } from 'sonner';
import { addPost } from './_action';
import { Form, useZodForm } from './Form';

export function AddPostForm() {
const form = useZodForm({
schema: addPostSchema,
defaultValues: {
title: 'hello world',
content: 'this is a test post',
},
});

console.log(form.formState.errors);

return (
<Form
className="flex max-w-md flex-col gap-2"
form={form}
handleSubmit={async (values) => {
const promise = addPost(values);
return toast.promise(promise, {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this toast twice on errors? 🤔

loading: 'Adding post...',
success: 'Post added!',
error: (error) => 'Failed to add post: ' + error.message,
});
}}
>
<div>
<input
className="rounded bg-zinc-300 p-1 text-zinc-900"
type="text"
{...form.register('title')}
defaultValue={form.control._defaultValues.title}
/>
{form.formState.errors.title && (
<div>Invalid title: {form.formState.errors.title.message}</div>
)}
</div>

<div>
<input
className="rounded bg-zinc-300 p-1 text-zinc-900"
type="text"
{...form.register('content')}
defaultValue={form.control._defaultValues.content}
/>
{form.formState.errors.content && (
<div>Invalid content: {form.formState.errors.content.message}</div>
)}
</div>
<button
className="rounded bg-zinc-800 p-1"
type="submit"
disabled={form.formState.isSubmitting}
>
Add post
</button>
</Form>
);
}
52 changes: 52 additions & 0 deletions examples/.experimental/next-app-dir-new/src/app/Form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
'use client';

import { zodResolver } from '@hookform/resolvers/zod';
import type { FieldValues, UseFormProps, UseFormReturn } from 'react-hook-form';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'sonner';
import type { z } from 'zod';

/**
* Reusable hook for zod + react-hook-form
*/
export function useZodForm<TInput extends FieldValues>(
props: Omit<UseFormProps<TInput>, 'resolver'> & {
schema: z.ZodType<any, any, TInput>;
},
) {
const form = useForm<TInput>({
...props,
resolver: zodResolver(props.schema, undefined, {
raw: true,
}),
});

return form;
}

export const Form = <TInput extends FieldValues = never>(props: {
children: React.ReactNode;
form: UseFormReturn<TInput>;
handleSubmit: (values: NoInfer<TInput>) => Promise<unknown>;
className?: string;
}) => {
return (
<FormProvider {...props.form}>
<form
className={props.className}
onSubmit={(event) => {
return props.form.handleSubmit(async (values) => {
try {
await props.handleSubmit(values);
} catch (error) {
console.error('Uncaught error in form', error);
toast.error('Failed to submit form');
}
})(event);
}}
>
{props.children}
</form>
</FormProvider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { db } from '@/db/client';
import { Post } from '@/db/schema';
import { publicAction } from '@/server/trpc';
import { eq } from 'drizzle-orm';
import Link from 'next/link';
import { notFound } from 'next/navigation';
import { cache } from 'react';
import { z } from 'zod';

const postById = cache(
publicAction.input(z.string()).query(async (opts) => {
const post = await db.query.Post.findFirst({
where: eq(Post.id, opts.input),
});

if (!post) notFound();
return post;
}),
);

export default async function Page(props: {
params: {
postId: string;
};
}) {
const post = await postById(props.params.postId);

return (
<div className="p-16">
<Link href="/" className="rounded bg-zinc-800 p-1">{`< Home`}</Link>
<h1>{post.title}</h1>

<p>{post.content}</p>
</div>
);
}
36 changes: 36 additions & 0 deletions examples/.experimental/next-app-dir-new/src/app/_action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use server';

import { randomUUID } from 'node:crypto';
import { db } from '@/db/client';
import { addPostSchema, Post } from '@/db/schema';
import { protectedAction, redirect } from '@/server/trpc';
import { eq } from 'drizzle-orm';
import { revalidatePath } from 'next/cache';

export const addPost = protectedAction
.input(
addPostSchema.superRefine(async (it, ctx) => {
const posts = await db.query.Post.findFirst({
where: eq(Post.title, it.title),
});
if (posts) {
ctx.addIssue({
code: 'custom',
message: 'Title already exists',
path: ['title'],
});
}
}),
)
.mutation(async (opts) => {
const id = randomUUID().replace(/-/g, '');
console.log('run');
const res = await db.insert(Post).values({
...opts.input,
id,
authorId: opts.ctx.user.id,
});
console.log('res', res);
revalidatePath('/');
return redirect(`/${id}`);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { GET, POST } from '@/server/auth';
33 changes: 33 additions & 0 deletions examples/.experimental/next-app-dir-new/src/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}

@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}

body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}

@layer utilities {
.text-balance {
text-wrap: balance;
}
}
24 changes: 24 additions & 0 deletions examples/.experimental/next-app-dir-new/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';
import { Toaster } from 'sonner';

const inter = Inter({ subsets: ['latin'] });

export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
<Toaster />
</html>
);
}