Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
notrab committed Apr 28, 2024
0 parents commit f5b57c4
Show file tree
Hide file tree
Showing 29 changed files with 6,266 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
36 changes: 36 additions & 0 deletions .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
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# turso-platforms-starter

WIP

## Demo

## Quickstart

Create a `.env.local` file in the root of the project with the following content:

```md
# Sign up to Clerk

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=

# Create a new Clerk webhook (user.created event) with your app URL and append /webhooks/clerk

# Set the webhook secret below provided by Clerk

CLERK_WEBHOOK_SECRET=

# turso auth api-tokens mint clerk

TURSO_USER_API_TOKEN=

# your personal or organization name

TURSO_ORG_NAME=

# turso db create [database-name] --type schema

TURSO_SCHEMA_DATABASE_NAME=

# turso group tokens create <group-name>

TURSO_DATABASE_GROUP_AUTH_TOKEN=
```
38 changes: 38 additions & 0 deletions app/(authenticated)/dashboard/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"use server";

import { revalidatePath } from "next/cache";

import { getDatabaseClient } from "@/app/utils";

export type TodoItem = {
id: number;
description: string;
};

export const addTodo = async (formData: FormData) => {
const client = await getDatabaseClient();

const description = formData.get("description") as string;

if (!client) return null;

await client.execute({
sql: "INSERT INTO todos (description) VALUES (?)",
args: [description],
});

revalidatePath("/dashboard");
};

export const removeTodo = async (id: number) => {
const client = await getDatabaseClient();

if (!client) return null;

await client.execute({
sql: "DELETE FROM todos WHERE id = ?",
args: [id],
});

revalidatePath("/dashboard");
};
48 changes: 48 additions & 0 deletions app/(authenticated)/dashboard/form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"use client";

import { useRef } from "react";
import { useFormStatus } from "react-dom";

import { addTodo } from "./actions";

export function Form() {
const formRef = useRef<HTMLFormElement>(null);

formRef.current?.reset();

return (
<form
action={async (formData) => {
await addTodo(formData);
formRef.current?.reset();
}}
className="flex items-center justify-between rounded-md bg-neutral-500 p-3 shadow-sm"
ref={formRef}
>
<div className="flex w-full items-center space-x-3">
<span className="p-1 text-3xl">☑️</span>
<input
id="description"
name="description"
placeholder="Insert new todo"
className="w-full text-white bg-neutral-500 placeholder:text-white outline-none"
required
aria-label="Description of todo"
type="text"
autoFocus
/>
</div>
<Submit />
</form>
);
}

export function Submit() {
const { pending } = useFormStatus();

return (
<button type="submit" aria-disabled={pending} className="sr-only">
Add
</button>
);
}
11 changes: 11 additions & 0 deletions app/(authenticated)/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Todos } from "./todos";
import { Form } from "./form";

export default async function Page() {
return (
<div className="space-y-3">
<Todos />
<Form />
</div>
);
}
33 changes: 33 additions & 0 deletions app/(authenticated)/dashboard/todo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"use client";

import { useTransition } from "react";

import { removeTodo, type TodoItem } from "./actions";

export function Todo({ item }: { item: TodoItem }) {
const [_, startTransition] = useTransition();

return (
<li className="flex items-center justify-between rounded-md bg-neutral-800 p-6 text-white">
<div className="flex w-full items-center space-x-3 ">
<button
className="p-1 text-3xl"
onClick={() => alert("Implement later")}
>
☑️
</button>
<span className="flex-1">{item.description}</span>
</div>
<button
className="p-1 text-3xl"
onClick={() => {
startTransition(() => {
removeTodo(item.id);
});
}}
>
&times;
</button>
</li>
);
}
17 changes: 17 additions & 0 deletions app/(authenticated)/dashboard/todos.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { getDatabaseClient } from "@/app/utils";

import { Todo } from "./todo";

export async function Todos() {
const client = await getDatabaseClient();

if (!client) {
return <p>No such table `todos`</p>;
}

const { rows } = await client.execute("SELECT * FROM todos");

if (!rows || rows.length === 0) return null;

return rows.map((todo, index) => <Todo key={index} item={todo} />);
}
44 changes: 44 additions & 0 deletions app/(authenticated)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { redirect } from "next/navigation";

import { checkDatabaseExists } from "../utils";
import { UserButton } from "@clerk/nextjs";

export default async function Layout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const databaseExists = await checkDatabaseExists();

if (!databaseExists) redirect("/welcome");

return (
<>
<header className="bg-neutral-900">
<div className="max-w-5xl mx-auto px-6">
<div className="flex justify-between items-center h-32">
<div className="flex items-center gap-3 text-[#4FF8D2]">
<svg
viewBox="0 0 201 170"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="h-6"
aria-hidden="true"
>
<path
d="M200.035 48.61C195.365 20.67 170.875 0 170.875 0V30.78L156.335 34.53L147.225 23.56L142.415 33.02C132.495 30.32 118.835 28.58 100.045 28.58C81.2549 28.58 67.5949 30.33 57.6749 33.02L52.8649 23.56L43.7549 34.53L29.2149 30.78V0C29.2149 0 4.72493 20.67 0.0549316 48.61L32.1949 59.73C33.2449 79.16 41.9849 131.61 44.4849 136.37C47.1449 141.44 61.2649 155.93 72.3149 161.5C72.3149 161.5 76.3149 157.27 78.7549 153.54C81.8549 157.19 97.8649 169.99 100.055 169.99C102.245 169.99 118.255 157.2 121.355 153.54C123.795 157.27 127.795 161.5 127.795 161.5C138.845 155.93 152.965 141.44 155.625 136.37C158.125 131.61 166.865 79.16 167.915 59.73L200.055 48.61H200.035ZM153.845 93.35L132.095 95.29L134.005 121.96C134.005 121.96 120.775 132.91 100.045 132.91C79.3149 132.91 66.0849 121.96 66.0849 121.96L67.9949 95.29L46.2449 93.35L42.5249 63.31L78.5749 75.79L75.7749 113.18C82.4749 114.88 89.5249 116.57 100.055 116.57C110.585 116.57 117.625 114.88 124.325 113.18L121.525 75.79L157.575 63.31L153.855 93.35H153.845Z"
fill="currentColor"
></path>
</svg>
<span className="text-sm font-medium select-none">
Turso Platforms Starter
</span>
</div>
<UserButton />
</div>
</div>
</header>
<div className="max-w-5xl mx-auto px-6 py-12">{children}</div>
</>
);
}
9 changes: 9 additions & 0 deletions app/(unauthenticated)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default async function Layout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<div className="h-screen flex items-center justify-center">{children}</div>
);
}
5 changes: 5 additions & 0 deletions app/(unauthenticated)/sign-in/[[...sign-in]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { SignIn } from "@clerk/nextjs";

export default function Page() {
return <SignIn path="/sign-in" fallbackRedirectUrl="/welcome" />;
}
5 changes: 5 additions & 0 deletions app/(unauthenticated)/sign-up/[[...sign-up]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { SignUp } from "@clerk/nextjs";

export default function Page() {
return <SignUp path="/sign-up" fallbackRedirectUrl="/welcome" />;
}
Binary file added app/favicon.ico
Binary file not shown.
3 changes: 3 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
28 changes: 28 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import { ClerkProvider } from "@clerk/nextjs";

import "./globals.css";

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

export const metadata: Metadata = {
title: "Turso Platforms Starter",
description: "Database per user starter with Turso, Clerk, and SQLite",
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<ClerkProvider>
<html lang="en">
<body className={`bg-neutral-900 overscroll-none ${inter.className}`}>
{children}
</body>
</html>
</ClerkProvider>
);
}
27 changes: 27 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Link from "next/link";
import { auth } from "@clerk/nextjs/server";
import { redirect } from "next/navigation";

export default function Page() {
const { userId } = auth();

if (userId) return redirect("/dashboard");

return (
<div className="h-screen flex flex-col items-center justify-center gap-3">
<h1 className="text-5xl font-black text-center tracking-tight text-white">
Turso Platforms Starter
</h1>
<p className="text-lg text-white/60">
<Link href="/sign-in" className="underline text-[#4FF8D2]">
Sign in
</Link>{" "}
or{" "}
<Link href="/sign-up" className="underline text-[#4FF8D2]">
register
</Link>{" "}
to get your own account and database
</p>
</div>
);
}
Loading

0 comments on commit f5b57c4

Please sign in to comment.