Skip to content

Commit

Permalink
Create flow control demo and UI
Browse files Browse the repository at this point in the history
  • Loading branch information
djfarrelly committed Jun 13, 2024
1 parent 5263f17 commit e5a5520
Show file tree
Hide file tree
Showing 37 changed files with 1,959 additions and 839 deletions.
100 changes: 12 additions & 88 deletions README.md
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.

[![Deploy with Vercel](https://vercel.com/button)](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!
187 changes: 187 additions & 0 deletions app/Jobs.tsx
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>
);
}
8 changes: 8 additions & 0 deletions app/api/inngest/route.ts
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],
});
21 changes: 21 additions & 0 deletions app/api/jobs/route.ts
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 removed app/favicon.ico
Binary file not shown.
45 changes: 39 additions & 6 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { GeistSans } from "geist/font/sans";
import "./globals.css";
import { GeistSans } from 'geist/font/sans';
import './globals.css';

const defaultUrl = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: "http://localhost:3000";
: 'http://localhost:3000';

export const metadata = {
metadataBase: new URL(defaultUrl),
title: "Next.js and Supabase Starter Kit",
description: "The fastest way to build apps with Next.js and Supabase",
title: 'Inngest Flow Control Demo',
description: 'The best way to run reliable functions in production.',
icons: {
icon: [
{ url: '/favicon-black.png', media: '(prefers-color-scheme: light)' },
{ url: '/favicon-white.png', media: '(prefers-color-scheme: dark)' },
],
},
};

export default function RootLayout({
Expand All @@ -19,7 +25,34 @@ export default function RootLayout({
return (
<html lang="en" className={GeistSans.className}>
<body className="bg-background text-foreground">
<main className="min-h-screen flex flex-col items-center">
<header>
<nav className="w-full flex justify-center border-b border-b-foreground/10 h-16">
<div className="w-full max-w-4xl flex justify-between items-center p-3 text-sm">
<a
href="https://www.inngest.com"
target="_blank"
rel="noreferrer"
>
<svg
width="100"
height="30"
className="w-full"
viewBox="0 0 100 30"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M0.927856 0.728778C0.309282 1.21463 0 1.89482 0 2.76936C0 3.63418 0.309282 4.3095 0.927856 4.79536C1.54643 5.28121 2.27458 5.52414 3.11234 5.52414C3.94035 5.52414 4.6612 5.27878 5.2749 4.78807C5.8886 4.29736 6.19545 3.62446 6.19545 2.76936C6.19545 1.89482 5.8886 1.21463 5.2749 0.728778C4.6612 0.242924 3.94035 0 3.11234 0C2.27458 0 1.54643 0.242924 0.927856 0.728778ZM5.85938 6.80679H0.336074V21.3095H5.85938V6.80679ZM7.1082 21.3095V6.559H11.974L12.2224 8.17689C12.8166 7.48698 13.5326 6.98169 14.3703 6.66103C15.1594 6.3598 15.9484 6.20919 16.7375 6.20919H16.8836C17.5362 6.21891 18.1378 6.29421 18.6881 6.43511C19.2385 6.57601 19.7621 6.81407 20.2589 7.14931C20.7557 7.48455 21.1795 7.9121 21.5302 8.43196C21.8808 8.95183 22.1585 9.61015 22.363 10.4069C22.5676 11.2038 22.6699 12.1123 22.6699 13.1326V21.3095H17.1612V13.1617C17.1612 12.3163 16.9615 11.6969 16.5621 11.3033C16.1627 10.9098 15.6708 10.713 15.0863 10.713C14.3752 10.713 13.7737 10.9584 13.2817 11.4491C12.7898 11.9398 12.5438 12.6419 12.5438 13.5553V21.3095H7.1082ZM23.7142 6.559V21.3095H29.1498V13.5553C29.1498 12.6419 29.3957 11.9398 29.8877 11.4491C30.3796 10.9584 30.9811 10.713 31.6923 10.713C32.2767 10.713 32.7687 10.9098 33.1681 11.3033C33.5675 11.6969 33.7671 12.3163 33.7671 13.1617V21.3095H39.2758V13.1326C39.2758 12.1123 39.1735 11.2038 38.969 10.4069C38.7644 9.61015 38.4868 8.95183 38.1361 8.43196C37.7854 7.9121 37.3617 7.48455 36.8649 7.14931C36.3681 6.81407 35.8445 6.57601 35.2941 6.43511C34.7437 6.29421 34.1422 6.21891 33.4895 6.20919H33.3434C32.5544 6.20919 31.7653 6.3598 30.9763 6.66103C30.1385 6.98169 29.4225 7.48698 28.8283 8.17689L28.5799 6.559H23.7142ZM40.25 13.6651C40.25 12.1589 40.6177 10.8277 41.3532 9.67137C42.0886 8.51504 43.0774 7.63322 44.3194 7.02591C45.5614 6.41859 46.9471 6.11493 48.4765 6.11493C49.5091 6.11493 50.527 6.29955 51.5304 6.6688L52.7324 5.14933C52.8942 4.94282 53.0244 4.77398 53.1231 4.6428L55.669 5.26499L57.0975 7.48504L55.1833 9.74425C56.1283 10.7937 56.6007 12.0229 56.6007 13.4319V13.7234C56.5812 14.6562 56.3377 15.555 55.8701 16.4199C55.4025 17.2847 54.745 18.0183 53.8975 18.6208C54.5307 18.9803 55.0591 19.3835 55.4829 19.8305C55.9066 20.2775 56.2062 20.7172 56.3815 21.1496C56.5569 21.582 56.6713 21.9391 56.7249 22.2209C56.7785 22.5027 56.8053 22.7845 56.8053 23.0663C56.8053 23.9894 56.6494 24.8251 56.3377 25.5733C56.026 26.3215 55.6047 26.9434 55.0738 27.439C54.5429 27.9345 53.9145 28.3499 53.1888 28.6852C52.4631 29.0204 51.7057 29.2609 50.9167 29.4067C50.1276 29.5524 49.2947 29.6253 48.418 29.6253C45.7781 29.6253 43.7568 28.9791 42.3541 27.6868C41.0877 26.5207 40.4545 24.9611 40.4545 23.008C40.4545 22.8039 40.4594 22.595 40.4691 22.3813H45.6418V22.4833C45.6418 22.9615 45.7006 23.3685 45.8181 23.7043L45.8463 23.7805C45.9827 24.1692 46.1897 24.4607 46.4673 24.655C46.745 24.8494 47.0396 24.9854 47.3514 25.0632C47.6631 25.1409 48.0186 25.1798 48.418 25.1798C49.1876 25.1798 49.8354 24.9976 50.3614 24.6332C50.8874 24.2688 51.1505 23.7465 51.1505 23.0663C51.1505 22.4736 50.9386 21.9974 50.5148 21.6379C50.1074 21.2922 49.4635 21.1127 48.5832 21.0994L48.4765 21.0986C45.9827 21.0986 43.9882 20.4281 42.4929 19.0872C40.9976 17.7462 40.25 15.9389 40.25 13.6651ZM46.4235 15.7343C45.8829 15.2047 45.6125 14.5075 45.6125 13.6427C45.6125 12.6904 45.8804 11.9447 46.4162 11.4054C46.952 10.8661 47.6387 10.5964 48.4765 10.5964C49.2753 10.5964 49.9401 10.8928 50.471 11.4855C51.0019 12.0783 51.2673 12.7973 51.2673 13.6427C51.2673 14.5756 50.9995 15.2898 50.4637 15.7853C49.9279 16.2809 49.2655 16.5287 48.4765 16.5287C47.6485 16.5287 46.9641 16.2639 46.4235 15.7343ZM57.3995 13.9634C57.3995 12.9237 57.5578 11.969 57.8744 11.0993C58.191 10.2296 58.6147 9.49841 59.1456 8.90567C59.6765 8.31292 60.2951 7.81007 61.0013 7.3971C61.7076 6.98412 62.4503 6.6829 63.2296 6.49341C64.0089 6.30393 64.8077 6.20919 65.626 6.20919C66.9411 6.20919 68.1368 6.42053 69.2132 6.84323C70.2896 7.26592 71.1834 7.87566 71.8945 8.67246C72.6056 9.46926 73.0975 10.441 73.3703 11.5876C73.4854 12.0734 73.5442 12.5834 73.5466 13.1174L73.5456 13.2783C73.5456 13.9682 73.4628 14.7067 73.2972 15.4938H62.9666C63.1517 16.0963 63.5754 16.5603 64.2378 16.8858C64.9003 17.2113 65.5773 17.3741 66.2689 17.3741C67.7301 17.3741 68.9964 16.9757 70.068 16.1789L72.7566 19.196C71.8896 20.0025 70.8741 20.6123 69.71 21.0252C68.5459 21.4382 67.3161 21.6447 66.0205 21.6447C64.8223 21.6447 63.7143 21.4941 62.6963 21.1929C61.6783 20.8916 60.77 20.4374 59.9712 19.83C59.1724 19.2227 58.5441 18.4187 58.0862 17.4178C57.6284 16.4169 57.3995 15.2655 57.3995 13.9634ZM68.3584 11.9665H62.9958C63.064 11.5098 63.2905 11.126 63.6753 10.8151C64.0601 10.5041 64.5057 10.3146 65.0123 10.2466C65.2461 10.2175 65.475 10.2029 65.699 10.2029C65.9718 10.2029 66.2397 10.2223 66.5027 10.2612C66.9995 10.3389 67.4257 10.5308 67.7812 10.8369C68.1368 11.143 68.3292 11.5195 68.3584 11.9665ZM73.57 19.7645L75.3672 15.7562C76.0589 16.2032 76.7651 16.5554 77.486 16.8129C78.2068 17.0704 78.7694 17.2259 79.1737 17.2793C79.5779 17.3328 80.0528 17.3741 80.5983 17.4032C81.6017 17.4032 82.1033 17.1652 82.1033 16.689C82.1033 16.2323 81.5822 15.9602 80.5399 15.8728C79.9262 15.8534 79.3344 15.7902 78.7645 15.6833C78.1946 15.5764 77.6272 15.4015 77.0622 15.1586C76.4972 14.9157 76.0077 14.6169 75.5937 14.2622C75.1797 13.9075 74.8437 13.4532 74.5855 12.8994C74.3274 12.3455 74.1983 11.7236 74.1983 11.0337C74.1983 10.1786 74.3883 9.43039 74.7682 8.78906C75.1481 8.14773 75.6644 7.64488 76.317 7.28049C76.9697 6.9161 77.6905 6.64646 78.4796 6.47155C79.2686 6.29664 80.1161 6.20919 81.0221 6.20919C82.2008 6.20919 83.321 6.36952 84.3828 6.69018C85.4446 7.01085 86.3408 7.44811 87.0714 8.00198L84.7335 11.2815C84.3576 11.0437 83.8307 10.8404 83.1528 10.6714L83.0239 10.6402C82.3225 10.4653 81.6845 10.3778 81.1097 10.3778H81.0221C80.1453 10.3778 79.707 10.6207 79.707 11.1066C79.707 11.2815 79.7922 11.4418 79.9627 11.5876C80.1218 11.7236 80.3806 11.8004 80.7391 11.8179L80.8175 11.8208C81.4994 11.8402 82.1326 11.8937 82.717 11.9811C83.3015 12.0686 83.903 12.224 84.5216 12.4475C85.1402 12.671 85.6686 12.9577 86.107 13.3075C86.5454 13.6573 86.9033 14.1213 87.181 14.6995C87.4586 15.2776 87.5974 15.9408 87.5974 16.689C87.5974 17.3984 87.4732 18.0373 87.2248 18.6057C86.9764 19.1742 86.6379 19.6479 86.2093 20.0268C85.7807 20.4058 85.2692 20.724 84.675 20.9815C84.0808 21.239 83.4549 21.4236 82.7974 21.5354C82.1399 21.6471 81.4458 21.703 80.7152 21.703C79.4001 21.703 78.0851 21.5305 76.77 21.1856C75.4549 20.8406 74.3883 20.3669 73.57 19.7645ZM87.8565 6.64645V10.8588H90.2237V15.9457C90.2139 16.7425 90.2967 17.4567 90.4721 18.0883C90.6669 18.7879 90.9299 19.3588 91.2611 19.8009C91.5923 20.243 91.9966 20.6074 92.4739 20.8941C92.9512 21.1807 93.4529 21.3823 93.9789 21.4989C94.505 21.6156 95.0748 21.6739 95.6885 21.6739C97.364 21.6739 98.7814 21.3435 99.9406 20.6827L98.7424 16.6161C98.2164 16.9562 97.6806 17.1263 97.1351 17.1263C96.1804 17.1263 95.6885 16.6502 95.6593 15.6979V10.8588H99.02V6.64645H95.6885V2.39039L90.2237 2.98799V6.64645H87.8565Z"
fill="currentColor"
></path>
</svg>
</a>
</div>
</nav>
</header>
<main className="min-h-screen flex flex-col items-center w-full">
{children}
</main>
</body>
Expand Down
Loading

0 comments on commit e5a5520

Please sign in to comment.