-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5263f17
commit e5a5520
Showing
37 changed files
with
1,959 additions
and
839 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,93 +1,17 @@ | ||
<a href="https://demo-nextjs-with-supabase.vercel.app/"> | ||
<img alt="Next.js and Supabase Starter Kit - the fastest way to build apps with Next.js and Supabase" src="https://demo-nextjs-with-supabase.vercel.app/opengraph-image.png"> | ||
<h1 align="center">Next.js and Supabase Starter Kit</h1> | ||
</a> | ||
# Inngest Flow Control demo | ||
|
||
<p align="center"> | ||
The fastest way to build apps with Next.js and Supabase | ||
</p> | ||
## Requirements: | ||
|
||
<p align="center"> | ||
<a href="#features"><strong>Features</strong></a> · | ||
<a href="#demo"><strong>Demo</strong></a> · | ||
<a href="#deploy-to-vercel"><strong>Deploy to Vercel</strong></a> · | ||
<a href="#clone-and-run-locally"><strong>Clone and run locally</strong></a> · | ||
<a href="#feedback-and-issues"><strong>Feedback and issues</strong></a> | ||
<a href="#more-supabase-examples"><strong>More Examples</strong></a> | ||
</p> | ||
<br/> | ||
- Inngest Dev Server | ||
- Supabase database | ||
|
||
## Features | ||
## Setup | ||
|
||
- Works across the entire [Next.js](https://nextjs.org) stack | ||
- App Router | ||
- Pages Router | ||
- Middleware | ||
- Client | ||
- Server | ||
- It just works! | ||
- supabase-ssr. A package to configure Supabase Auth to use cookies | ||
- Styling with [Tailwind CSS](https://tailwindcss.com) | ||
- Optional deployment with [Supabase Vercel Integration and Vercel deploy](#deploy-your-own) | ||
- Environment variables automatically assigned to Vercel project | ||
1. Create a new Supabase database | ||
2. Add the credentials to a new `.env.local` file (see `.env.example`) | ||
3. Run `init.sql` in the Supabase dashboard | ||
4. Install dependencies: `pnpm install` | ||
5. Start the server: `pnpm run dev` | ||
6. Start the Inngest Dev Server: `npx inngest-cli@latest dev -u http://localhost:3000/api/inngest --no-discovery | ||
|
||
## Demo | ||
|
||
You can view a fully working demo at [demo-nextjs-with-supabase.vercel.app](https://demo-nextjs-with-supabase.vercel.app/). | ||
|
||
## Deploy to Vercel | ||
|
||
Vercel deployment will guide you through creating a Supabase account and project. | ||
|
||
After installation of the Supabase integration, all relevant environment variables will be assigned to the project so the deployment is fully functioning. | ||
|
||
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fwith-supabase&project-name=nextjs-with-supabase&repository-name=nextjs-with-supabase&demo-title=nextjs-with-supabase&demo-description=This%20starter%20configures%20Supabase%20Auth%20to%20use%20cookies%2C%20making%20the%20user's%20session%20available%20throughout%20the%20entire%20Next.js%20app%20-%20Client%20Components%2C%20Server%20Components%2C%20Route%20Handlers%2C%20Server%20Actions%20and%20Middleware.&demo-url=https%3A%2F%2Fdemo-nextjs-with-supabase.vercel.app%2F&external-id=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fwith-supabase&demo-image=https%3A%2F%2Fdemo-nextjs-with-supabase.vercel.app%2Fopengraph-image.png&integration-ids=oac_VqOgBHqhEoFTPzGkPd7L0iH6) | ||
|
||
The above will also clone the Starter kit to your GitHub, you can clone that locally and develop locally. | ||
|
||
If you wish to just develop locally and not deploy to Vercel, [follow the steps below](#clone-and-run-locally). | ||
|
||
## Clone and run locally | ||
|
||
1. You'll first need a Supabase project which can be made [via the Supabase dashboard](https://database.new) | ||
|
||
2. Create a Next.js app using the Supabase Starter template npx command | ||
|
||
```bash | ||
npx create-next-app -e with-supabase | ||
``` | ||
|
||
3. Use `cd` to change into the app's directory | ||
|
||
```bash | ||
cd name-of-new-app | ||
``` | ||
|
||
4. Rename `.env.local.example` to `.env.local` and update the following: | ||
|
||
``` | ||
NEXT_PUBLIC_SUPABASE_URL=[INSERT SUPABASE PROJECT URL] | ||
NEXT_PUBLIC_SUPABASE_ANON_KEY=[INSERT SUPABASE PROJECT API ANON KEY] | ||
``` | ||
|
||
Both `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY` can be found in [your Supabase project's API settings](https://app.supabase.com/project/_/settings/api) | ||
|
||
5. You can now run the Next.js local development server: | ||
|
||
```bash | ||
npm run dev | ||
``` | ||
|
||
The starter kit should now be running on [localhost:3000](http://localhost:3000/). | ||
|
||
> Check out [the docs for Local Development](https://supabase.com/docs/guides/getting-started/local-development) to also run Supabase locally. | ||
## Feedback and issues | ||
|
||
Please file feedback and issues over on the [Supabase GitHub org](https://github.com/supabase/supabase/issues/new/choose). | ||
|
||
## More Supabase examples | ||
|
||
- [Next.js Subscription Payments Starter](https://github.com/vercel/nextjs-subscription-payments) | ||
- [Cookie-based Auth and the Next.js 13 App Router (free course)](https://youtube.com/playlist?list=PL5S4mPUpp4OtMhpnp93EFSo42iQ40XjbF) | ||
- [Supabase Auth and the Next.js App Router](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs) | ||
Open `http://localhost:3000` in your browser to demo! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
'use client'; | ||
import { useEffect, useState, use } from 'react'; | ||
import { createClient } from '@/utils/supabase/client'; | ||
import { NewJob } from '@/types'; | ||
import { Tables } from '@/database.types'; | ||
import { capFirst } from '@/utils/strings'; | ||
|
||
const users = ['dan', 'sylwia', 'tony', 'ana']; | ||
|
||
async function createJob(jobs: NewJob[]) { | ||
const data = await fetch('/api/jobs', { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
body: JSON.stringify(jobs), | ||
}).then((res) => res.json()); | ||
return data; | ||
} | ||
|
||
function useJobs() { | ||
const [jobs, setState] = useState<{ [keyof: number]: Tables<'jobs'> }>({}); | ||
|
||
function setJobs(newJobs: Tables<'jobs'>[]) { | ||
const newState = newJobs.reduce<{ [key: number]: Tables<'jobs'> }>( | ||
(acc, job) => { | ||
acc[job.id] = job; | ||
return acc; | ||
}, | ||
{} | ||
); | ||
setState(newState); | ||
} | ||
|
||
function upsertJobs(newJobs: Tables<'jobs'>[]) { | ||
const jobsToUpsert: { [keyof: string]: Tables<'jobs'> } = {}; | ||
for (const job of newJobs) { | ||
jobsToUpsert[job.id] = job; | ||
} | ||
setState((existing) => ({ ...existing, ...jobsToUpsert })); | ||
console.log( | ||
'Update:', | ||
newJobs.map((j) => j.id) | ||
); | ||
} | ||
|
||
const supabase = createClient(); | ||
|
||
async function fetchJobs() { | ||
const { data } = await supabase.from('jobs').select(); | ||
setJobs(data || []); | ||
} | ||
async function clearJobs() { | ||
await supabase.from('jobs').delete().gt('id', 0); | ||
setJobs([]); | ||
} | ||
|
||
// Initial load | ||
useEffect(() => { | ||
fetchJobs(); | ||
}, []); | ||
|
||
useEffect(() => { | ||
const id = setInterval(fetchJobs, 200); | ||
return () => { | ||
clearInterval(id); | ||
}; | ||
}, [fetchJobs]); | ||
|
||
// Realtime updates | ||
// useEffect(() => { | ||
// console.log('subscribing to updates'); | ||
// const channel = supabase | ||
// .channel('db') | ||
// .on( | ||
// 'postgres_changes', | ||
// { event: 'UPDATE', schema: 'public', table: 'jobs' }, | ||
// (payload) => { | ||
// const updated = payload.new as Tables<'jobs'>; | ||
// upsertJobs([updated]); | ||
// } | ||
// ) | ||
// .subscribe(); | ||
// return () => { | ||
// supabase.removeChannel(channel); | ||
// }; | ||
// }, [supabase, jobs, setJobs]); | ||
|
||
return { jobs, upsertJobs, clearJobs }; | ||
} | ||
|
||
const STATUS_SORT: { [keyof: Tables<'jobs'>['status']]: number } = { | ||
PENDING: 0, | ||
RUNNING: 1, | ||
COMPLETED: 2, | ||
}; | ||
|
||
export default function Jobs() { | ||
const { jobs, upsertJobs, clearJobs } = useJobs(); | ||
|
||
async function onCreateJob(user: string, count: number = 1) { | ||
const jobs = Array.from({ length: count }).map(() => ({ | ||
user_slug: user, | ||
status: 'PENDING', | ||
})); | ||
|
||
const data = await createJob(jobs); | ||
upsertJobs(data); | ||
} | ||
|
||
// TODO - Sort by status | ||
const sortedJobs = Object.values(jobs).sort((a, b) => { | ||
return STATUS_SORT[a.status] > STATUS_SORT[b.status] ? -1 : 1; | ||
}); | ||
const aggregateJobs = sortedJobs.reduce<{ [key: string]: Tables<'jobs'>[] }>( | ||
(acc, job) => { | ||
if (job.user_slug in acc) { | ||
acc[job.user_slug].push(job); | ||
} | ||
return acc; | ||
}, | ||
{ | ||
dan: [], | ||
tony: [], | ||
sylwia: [], | ||
ana: [], | ||
} | ||
); | ||
|
||
// console.log('Render', Object.keys(jobs), aggregateJobs.dan); | ||
|
||
return ( | ||
<div> | ||
<h2 className="text-xl font-semibold mb-8">Jobs</h2> | ||
<div className="flex flex-col gap-4"> | ||
{users.map((user, idx) => ( | ||
<div key={user}> | ||
<div className="mb-2">{capFirst(user)}</div> | ||
<div className="flex flex-row gap-8"> | ||
<div className="flex flex-row gap-2"> | ||
<button | ||
onClick={onCreateJob.bind(null, user, 1)} | ||
className="py-1 px-3 rounded-full border border-slate-500 hover:border-slate-700 hover:bg-slate-200" | ||
> | ||
+1 | ||
</button> | ||
<button | ||
onClick={onCreateJob.bind(null, user, 5)} | ||
className="py-1 px-3 rounded-full border border-slate-500 hover:border-slate-700 hover:bg-slate-200" | ||
> | ||
+5 | ||
</button> | ||
</div> | ||
|
||
<div className="flex flex-row gap-2 items-center"> | ||
{aggregateJobs[user].map((job) => { | ||
return ( | ||
<div | ||
key={`${user}-${job.id}`} | ||
className={`w-8 h-8 rounded-sm ${ | ||
job.status === 'RUNNING' | ||
? 'animate-pulse bg-amber-500' | ||
: job.status === 'COMPLETED' | ||
? 'bg-emerald-500' | ||
: 'bg-cyan-400' | ||
}`} | ||
title={`${job.id} - ${job.status}`} | ||
></div> | ||
); | ||
})} | ||
</div> | ||
</div> | ||
</div> | ||
))} | ||
</div> | ||
|
||
<div className="mt-16"> | ||
<button | ||
onClick={clearJobs} | ||
className="py-2 px-4 rounded-md border border-rose-600 hover:border-rose-700 text-rose-600 hover:text-rose-700 hover:bg-rose-50" | ||
> | ||
Clear jobs | ||
</button> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { serve } from 'inngest/next'; | ||
import { inngest } from '@/inngest/client'; | ||
import { multiTenantConcurrency } from '@/inngest/functions'; | ||
|
||
export const { GET, POST, PUT } = serve({ | ||
client: inngest, | ||
functions: [multiTenantConcurrency], | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { inngest } from '@/inngest'; | ||
import type { JobCreated } from '@/inngest/types'; | ||
import { createClient } from '@/utils/supabase/server'; | ||
import type { NewJob } from '@/types'; | ||
|
||
export async function POST(req: Request) { | ||
const jobs = (await req.json()) as NewJob[]; | ||
|
||
const events: JobCreated[] = jobs.map((job) => ({ | ||
name: 'demo/job.created', | ||
data: job, | ||
})); | ||
const { ids } = await inngest.send(events); | ||
|
||
const newJobs = jobs.map((job, i) => ({ ...job, event_id: ids[i] })); | ||
|
||
const supabase = createClient(); | ||
const { data } = await supabase.from('jobs').insert(newJobs).select(); | ||
|
||
return Response.json(data); | ||
} |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.