diff --git a/nextjs-supabase/.env.local.example b/nextjs-supabase/.env.local.example
new file mode 100644
index 0000000..787a424
--- /dev/null
+++ b/nextjs-supabase/.env.local.example
@@ -0,0 +1,2 @@
+NEXT_PUBLIC_SUPABASE_URL=
+NEXT_PUBLIC_SUPABASE_ANON_KEY=
\ No newline at end of file
diff --git a/nextjs-supabase/.gitignore b/nextjs-supabase/.gitignore
new file mode 100644
index 0000000..6327180
--- /dev/null
+++ b/nextjs-supabase/.gitignore
@@ -0,0 +1,39 @@
+# dependencies
+/node_modules
+/.pnp
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/versions
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# env files (can opt-in for committing if needed)
+.env*
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/nextjs-supabase/README.md b/nextjs-supabase/README.md
new file mode 100644
index 0000000..2d3225e
--- /dev/null
+++ b/nextjs-supabase/README.md
@@ -0,0 +1,54 @@
+## nextjs-supabase-realtime-task example
+
+A full-stack collaborative solution demonstrating real-time synchronization using Next.js, powered by Legend-State for granular state management and Supabase for backend services. Core capabilities include:
+
+- Real-time bidirectional synchronization: Instant updates with millisecond latency via Supabase Realtime
+- Automatic timestamp tracking: Built-in creation and modification time recording
+- End-to-end type safety: Strict TypeScript definitions across database models
+- Optimized reactivity: Efficient state updates through Legend-State's observable system
+
+---
+
+## Setup
+
+Create `.env.local`
+
+```bash
+cp .env.local.example .env.local
+```
+
+Link Supabase
+
+```bash
+supabase link
+```
+
+Apply Database
+
+```bash
+supabase db push
+
+```
+
+## Run
+
+Install
+
+```bash
+pnpm install
+```
+
+Start
+
+```bash
+pnpm run dev
+```
+
+## Types
+
+Generate `types.ts`
+
+```bash
+supabase start
+supabase gen types --lang=typescript --local > src/lib/types.ts
+```
diff --git a/nextjs-supabase/app/globals.css b/nextjs-supabase/app/globals.css
new file mode 100644
index 0000000..7bf60c9
--- /dev/null
+++ b/nextjs-supabase/app/globals.css
@@ -0,0 +1,12 @@
+@import "tailwindcss";
+
+:root {
+ --background: #171717;
+ --foreground: #ffffff;
+}
+
+@theme inline {
+ --color-mobai-background: var(--background);
+ --color-mobai-foreground: var(--foreground);
+ --ease-mobai-bounce: cubic-bezier(0.84, 0, 0.165, 1);
+}
diff --git a/nextjs-supabase/app/layout.tsx b/nextjs-supabase/app/layout.tsx
new file mode 100644
index 0000000..0c94aaa
--- /dev/null
+++ b/nextjs-supabase/app/layout.tsx
@@ -0,0 +1,13 @@
+import "./globals.css";
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
{children}
+
+ );
+}
diff --git a/nextjs-supabase/app/page.tsx b/nextjs-supabase/app/page.tsx
new file mode 100644
index 0000000..476a6e2
--- /dev/null
+++ b/nextjs-supabase/app/page.tsx
@@ -0,0 +1,27 @@
+"use client";
+
+import { observer } from "@legendapp/state/react";
+import NewTask from "@/components/NewTask";
+import TaskList from "@/components/TaskList";
+
+const Home = observer(() => {
+ return (
+ <>
+
+
+ Task Demo
+
+
+
+ >
+ );
+});
+
+export default Home;
diff --git a/nextjs-supabase/components/NewTask.tsx b/nextjs-supabase/components/NewTask.tsx
new file mode 100644
index 0000000..f343c57
--- /dev/null
+++ b/nextjs-supabase/components/NewTask.tsx
@@ -0,0 +1,28 @@
+"use client";
+
+import { addTask } from "../lib/store";
+import React, { useState } from "react";
+
+const NewTask = () => {
+ const [text, setText] = useState("");
+ const handleSubmitEditing = () => {
+ setText("");
+ addTask(text);
+ };
+ return (
+
+ setText(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") {
+ handleSubmitEditing();
+ }
+ }}
+ className="text-mobai-foreground border-mobai-foreground border-2 transition-all duration-500 ease-mobai-bounce font-bold w-full outline-none p-3 sm:p-4 hover:bg-mobai-foreground hover:text-mobai-background"
+ />
+
+ );
+};
+
+export default NewTask;
diff --git a/nextjs-supabase/components/TaskItem.tsx b/nextjs-supabase/components/TaskItem.tsx
new file mode 100644
index 0000000..8dfebbd
--- /dev/null
+++ b/nextjs-supabase/components/TaskItem.tsx
@@ -0,0 +1,29 @@
+import Check from "@/components/icons/Check";
+import Circle from "@/components/icons/Circle";
+import { Tables } from "../types/store.types";
+import { toggleDone } from "../lib/store";
+
+const TaskItem = ({ task }: { task: Tables<"tasks"> }) => {
+ const handlePress = () => {
+ toggleDone(task.id);
+ };
+ return (
+
+
+
+ );
+};
+
+export default TaskItem;
diff --git a/nextjs-supabase/components/TaskList.tsx b/nextjs-supabase/components/TaskList.tsx
new file mode 100644
index 0000000..30ceda8
--- /dev/null
+++ b/nextjs-supabase/components/TaskList.tsx
@@ -0,0 +1,30 @@
+"use client";
+
+import { tasks$ } from "../lib/store";
+import { Tables } from "../types/store.types";
+import TaskItem from "@/components/TaskItem";
+import { useEffect, useState } from "react";
+import { observer } from "@legendapp/state/react";
+
+const TaskList = observer(() => {
+ const [isClient, setIsClient] = useState(false);
+ const tasks = tasks$.get();
+ const taskArray = Object.values(tasks || {}).reverse();
+
+ useEffect(() => {
+ setIsClient(true);
+ }, []);
+
+ return (
+ isClient &&
+ tasks && (
+
+ {taskArray.map((task: Tables<"tasks">) => (
+
+ ))}
+
+ )
+ );
+});
+
+export default TaskList;
diff --git a/nextjs-supabase/components/icons/Check.tsx b/nextjs-supabase/components/icons/Check.tsx
new file mode 100644
index 0000000..bb77b57
--- /dev/null
+++ b/nextjs-supabase/components/icons/Check.tsx
@@ -0,0 +1,22 @@
+import { IconProps } from "@/types/icon.types";
+
+const Check: React.FC = ({ className }) => {
+ return (
+
+ );
+};
+
+export default Check;
diff --git a/nextjs-supabase/components/icons/Circle.tsx b/nextjs-supabase/components/icons/Circle.tsx
new file mode 100644
index 0000000..42def31
--- /dev/null
+++ b/nextjs-supabase/components/icons/Circle.tsx
@@ -0,0 +1,22 @@
+import { IconProps } from "@/types/icon.types";
+
+const Circle: React.FC = ({ className }) => {
+ return (
+
+ );
+};
+
+export default Circle;
diff --git a/nextjs-supabase/eslint.config.mjs b/nextjs-supabase/eslint.config.mjs
new file mode 100644
index 0000000..c85fb67
--- /dev/null
+++ b/nextjs-supabase/eslint.config.mjs
@@ -0,0 +1,16 @@
+import { dirname } from "path";
+import { fileURLToPath } from "url";
+import { FlatCompat } from "@eslint/eslintrc";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+const compat = new FlatCompat({
+ baseDirectory: __dirname,
+});
+
+const eslintConfig = [
+ ...compat.extends("next/core-web-vitals", "next/typescript"),
+];
+
+export default eslintConfig;
diff --git a/nextjs-supabase/lib/store.ts b/nextjs-supabase/lib/store.ts
new file mode 100644
index 0000000..7d7a7e6
--- /dev/null
+++ b/nextjs-supabase/lib/store.ts
@@ -0,0 +1,57 @@
+import { createClient } from "@supabase/supabase-js";
+import { configureSynced } from "@legendapp/state/sync";
+import { observable } from "@legendapp/state";
+import { ObservablePersistLocalStorage } from "@legendapp/state/persist-plugins/local-storage";
+import { syncedSupabase } from "@legendapp/state/sync-plugins/supabase";
+import { v4 as uuidv4 } from "uuid";
+
+import { Database } from "../types/store.types";
+
+const supabase = createClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
+);
+
+const generateId = () => uuidv4();
+
+const customSynced = configureSynced(syncedSupabase, {
+ persist: {
+ plugin: ObservablePersistLocalStorage,
+ },
+ generateId,
+ supabase,
+ changesSince: "last-sync",
+ fieldCreatedAt: "created_at",
+ fieldUpdatedAt: "updated_at",
+ fieldDeleted: "deleted",
+});
+
+export const tasks$ = observable(
+ customSynced({
+ supabase,
+ collection: "tasks",
+ select: (from) =>
+ from.select("id,counter,text,done,created_at,updated_at,deleted"),
+ actions: ["read", "create", "update", "delete"],
+ realtime: true,
+ persist: {
+ name: "tasks",
+ retrySync: true,
+ },
+ retry: {
+ infinite: true,
+ },
+ })
+);
+
+export const addTask = (text: string) => {
+ const id = generateId();
+ tasks$[id].assign({
+ id,
+ text,
+ });
+};
+
+export const toggleDone = (id: string) => {
+ tasks$[id].done.set((prev) => !prev);
+};
diff --git a/nextjs-supabase/next.config.ts b/nextjs-supabase/next.config.ts
new file mode 100644
index 0000000..e9ffa30
--- /dev/null
+++ b/nextjs-supabase/next.config.ts
@@ -0,0 +1,7 @@
+import type { NextConfig } from "next";
+
+const nextConfig: NextConfig = {
+ /* config options here */
+};
+
+export default nextConfig;
diff --git a/nextjs-supabase/package.json b/nextjs-supabase/package.json
new file mode 100644
index 0000000..c70bf1b
--- /dev/null
+++ b/nextjs-supabase/package.json
@@ -0,0 +1,28 @@
+{
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "@legendapp/state": "^3.0.0-beta.30",
+ "@supabase/supabase-js": "^2.49.1",
+ "next": "latest",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ },
+ "devDependencies": {
+ "@eslint/eslintrc": "^3",
+ "@tailwindcss/postcss": "^4",
+ "@types/node": "^20",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "eslint": "^9",
+ "eslint-config-next": "15.2.3",
+ "tailwindcss": "^4",
+ "typescript": "^5",
+ "uuid": "^11.1.0"
+ }
+}
diff --git a/nextjs-supabase/postcss.config.mjs b/nextjs-supabase/postcss.config.mjs
new file mode 100644
index 0000000..c7bcb4b
--- /dev/null
+++ b/nextjs-supabase/postcss.config.mjs
@@ -0,0 +1,5 @@
+const config = {
+ plugins: ["@tailwindcss/postcss"],
+};
+
+export default config;
diff --git a/nextjs-supabase/supabase/.gitignore b/nextjs-supabase/supabase/.gitignore
new file mode 100644
index 0000000..ad9264f
--- /dev/null
+++ b/nextjs-supabase/supabase/.gitignore
@@ -0,0 +1,8 @@
+# Supabase
+.branches
+.temp
+
+# dotenvx
+.env.keys
+.env.local
+.env.*.local
diff --git a/nextjs-supabase/supabase/config.toml b/nextjs-supabase/supabase/config.toml
new file mode 100644
index 0000000..81b6032
--- /dev/null
+++ b/nextjs-supabase/supabase/config.toml
@@ -0,0 +1,307 @@
+# For detailed configuration reference documentation, visit:
+# https://supabase.com/docs/guides/local-development/cli/config
+# A string used to distinguish different Supabase projects on the same host. Defaults to the
+# working directory name when running `supabase init`.
+project_id = "with-legend-state-supabase"
+
+[api]
+enabled = true
+# Port to use for the API URL.
+port = 54321
+# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API
+# endpoints. `public` and `graphql_public` schemas are included by default.
+schemas = ["public", "graphql_public"]
+# Extra schemas to add to the search_path of every request.
+extra_search_path = ["public", "extensions"]
+# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size
+# for accidental or malicious requests.
+max_rows = 1000
+
+[api.tls]
+# Enable HTTPS endpoints locally using a self-signed certificate.
+enabled = false
+
+[db]
+# Port to use for the local database URL.
+port = 54322
+# Port used by db diff command to initialize the shadow database.
+shadow_port = 54320
+# The database major version to use. This has to be the same as your remote database's. Run `SHOW
+# server_version;` on the remote database to check.
+major_version = 15
+
+[db.pooler]
+enabled = false
+# Port to use for the local connection pooler.
+port = 54329
+# Specifies when a server connection can be reused by other clients.
+# Configure one of the supported pooler modes: `transaction`, `session`.
+pool_mode = "transaction"
+# How many server connections to allow per user/database pair.
+default_pool_size = 20
+# Maximum number of client connections allowed.
+max_client_conn = 100
+
+# [db.vault]
+# secret_key = "env(SECRET_VALUE)"
+
+[db.migrations]
+# Specifies an ordered list of schema files that describe your database.
+# Supports glob patterns relative to supabase directory: "./schemas/*.sql"
+schema_paths = []
+
+[db.seed]
+# If enabled, seeds the database after migrations during a db reset.
+enabled = true
+# Specifies an ordered list of seed files to load during db reset.
+# Supports glob patterns relative to supabase directory: "./seeds/*.sql"
+sql_paths = ["./seed.sql"]
+
+[realtime]
+enabled = true
+# Bind realtime via either IPv4 or IPv6. (default: IPv4)
+# ip_version = "IPv6"
+# The maximum length in bytes of HTTP request headers. (default: 4096)
+# max_header_length = 4096
+
+[studio]
+enabled = true
+# Port to use for Supabase Studio.
+port = 54323
+# External URL of the API server that frontend connects to.
+api_url = "http://127.0.0.1"
+# OpenAI API Key to use for Supabase AI in the Supabase Studio.
+openai_api_key = "env(OPENAI_API_KEY)"
+
+# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they
+# are monitored, and you can view the emails that would have been sent from the web interface.
+[inbucket]
+enabled = true
+# Port to use for the email testing server web interface.
+port = 54324
+# Uncomment to expose additional ports for testing user applications that send emails.
+# smtp_port = 54325
+# pop3_port = 54326
+# admin_email = "admin@email.com"
+# sender_name = "Admin"
+
+[storage]
+enabled = true
+# The maximum file size allowed (e.g. "5MB", "500KB").
+file_size_limit = "50MiB"
+
+# Image transformation API is available to Supabase Pro plan.
+# [storage.image_transformation]
+# enabled = true
+
+# Uncomment to configure local storage buckets
+# [storage.buckets.images]
+# public = false
+# file_size_limit = "50MiB"
+# allowed_mime_types = ["image/png", "image/jpeg"]
+# objects_path = "./images"
+
+[auth]
+enabled = true
+# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
+# in emails.
+site_url = "http://127.0.0.1:3000"
+# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
+additional_redirect_urls = ["https://127.0.0.1:3000"]
+# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week).
+jwt_expiry = 3600
+# If disabled, the refresh token will never expire.
+enable_refresh_token_rotation = true
+# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds.
+# Requires enable_refresh_token_rotation = true.
+refresh_token_reuse_interval = 10
+# Allow/disallow new user signups to your project.
+enable_signup = true
+# Allow/disallow anonymous sign-ins to your project.
+enable_anonymous_sign_ins = false
+# Allow/disallow testing manual linking of accounts
+enable_manual_linking = false
+# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more.
+minimum_password_length = 6
+# Passwords that do not meet the following requirements will be rejected as weak. Supported values
+# are: `letters_digits`, `lower_upper_letters_digits`, `lower_upper_letters_digits_symbols`
+password_requirements = ""
+
+[auth.rate_limit]
+# Number of emails that can be sent per hour. Requires auth.email.smtp to be enabled.
+email_sent = 2
+# Number of SMS messages that can be sent per hour. Requires auth.sms to be enabled.
+sms_sent = 30
+# Number of anonymous sign-ins that can be made per hour per IP address. Requires enable_anonymous_sign_ins = true.
+anonymous_users = 30
+# Number of sessions that can be refreshed in a 5 minute interval per IP address.
+token_refresh = 150
+# Number of sign up and sign-in requests that can be made in a 5 minute interval per IP address (excludes anonymous users).
+sign_in_sign_ups = 30
+# Number of OTP / Magic link verifications that can be made in a 5 minute interval per IP address.
+token_verifications = 30
+
+
+# Configure one of the supported captcha providers: `hcaptcha`, `turnstile`.
+# [auth.captcha]
+# enabled = true
+# provider = "hcaptcha"
+# secret = ""
+
+[auth.email]
+# Allow/disallow new user signups via email to your project.
+enable_signup = true
+# If enabled, a user will be required to confirm any email change on both the old, and new email
+# addresses. If disabled, only the new email is required to confirm.
+double_confirm_changes = true
+# If enabled, users need to confirm their email address before signing in.
+enable_confirmations = false
+# If enabled, users will need to reauthenticate or have logged in recently to change their password.
+secure_password_change = false
+# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email.
+max_frequency = "1s"
+# Number of characters used in the email OTP.
+otp_length = 6
+# Number of seconds before the email OTP expires (defaults to 1 hour).
+otp_expiry = 3600
+
+# Use a production-ready SMTP server
+# [auth.email.smtp]
+# enabled = true
+# host = "smtp.sendgrid.net"
+# port = 587
+# user = "apikey"
+# pass = "env(SENDGRID_API_KEY)"
+# admin_email = "admin@email.com"
+# sender_name = "Admin"
+
+# Uncomment to customize email template
+# [auth.email.template.invite]
+# subject = "You have been invited"
+# content_path = "./supabase/templates/invite.html"
+
+[auth.sms]
+# Allow/disallow new user signups via SMS to your project.
+enable_signup = false
+# If enabled, users need to confirm their phone number before signing in.
+enable_confirmations = false
+# Template for sending OTP to users
+template = "Your code is {{ .Code }}"
+# Controls the minimum amount of time that must pass before sending another sms otp.
+max_frequency = "5s"
+
+# Use pre-defined map of phone number to OTP for testing.
+# [auth.sms.test_otp]
+# 4152127777 = "123456"
+
+# Configure logged in session timeouts.
+# [auth.sessions]
+# Force log out after the specified duration.
+# timebox = "24h"
+# Force log out if the user has been inactive longer than the specified duration.
+# inactivity_timeout = "8h"
+
+# This hook runs before a token is issued and allows you to add additional claims based on the authentication method used.
+# [auth.hook.custom_access_token]
+# enabled = true
+# uri = "pg-functions:////"
+
+# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`.
+[auth.sms.twilio]
+enabled = false
+account_sid = ""
+message_service_sid = ""
+# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead:
+auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)"
+
+# Multi-factor-authentication is available to Supabase Pro plan.
+[auth.mfa]
+# Control how many MFA factors can be enrolled at once per user.
+max_enrolled_factors = 10
+
+# Control MFA via App Authenticator (TOTP)
+[auth.mfa.totp]
+enroll_enabled = false
+verify_enabled = false
+
+# Configure MFA via Phone Messaging
+[auth.mfa.phone]
+enroll_enabled = false
+verify_enabled = false
+otp_length = 6
+template = "Your code is {{ .Code }}"
+max_frequency = "5s"
+
+# Configure MFA via WebAuthn
+# [auth.mfa.web_authn]
+# enroll_enabled = true
+# verify_enabled = true
+
+# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`,
+# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`,
+# `twitter`, `slack`, `spotify`, `workos`, `zoom`.
+[auth.external.apple]
+enabled = false
+client_id = ""
+# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead:
+secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)"
+# Overrides the default auth redirectUrl.
+redirect_uri = ""
+# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure,
+# or any other third-party OIDC providers.
+url = ""
+# If enabled, the nonce check will be skipped. Required for local sign in with Google auth.
+skip_nonce_check = false
+
+# Use Firebase Auth as a third-party provider alongside Supabase Auth.
+[auth.third_party.firebase]
+enabled = false
+# project_id = "my-firebase-project"
+
+# Use Auth0 as a third-party provider alongside Supabase Auth.
+[auth.third_party.auth0]
+enabled = false
+# tenant = "my-auth0-tenant"
+# tenant_region = "us"
+
+# Use AWS Cognito (Amplify) as a third-party provider alongside Supabase Auth.
+[auth.third_party.aws_cognito]
+enabled = false
+# user_pool_id = "my-user-pool-id"
+# user_pool_region = "us-east-1"
+
+# Use Clerk as a third-party provider alongside Supabase Auth.
+[auth.third_party.clerk]
+enabled = false
+# Obtain from https://clerk.com/setup/supabase
+# domain = "example.clerk.accounts.dev"
+
+[edge_runtime]
+enabled = true
+# Configure one of the supported request policies: `oneshot`, `per_worker`.
+# Use `oneshot` for hot reload, or `per_worker` for load testing.
+policy = "oneshot"
+# Port to attach the Chrome inspector for debugging edge functions.
+inspector_port = 8083
+
+# [edge_runtime.secrets]
+# secret_key = "env(SECRET_VALUE)"
+
+[analytics]
+enabled = true
+port = 54327
+# Configure one of the supported backends: `postgres`, `bigquery`.
+backend = "postgres"
+
+# Experimental features may be deprecated any time
+[experimental]
+# Configures Postgres storage engine to use OrioleDB (S3)
+orioledb_version = ""
+# Configures S3 bucket URL, eg. .s3-.amazonaws.com
+s3_host = "env(S3_HOST)"
+# Configures S3 bucket region, eg. us-east-1
+s3_region = "env(S3_REGION)"
+# Configures AWS_ACCESS_KEY_ID for S3 bucket
+s3_access_key = "env(S3_ACCESS_KEY)"
+# Configures AWS_SECRET_ACCESS_KEY for S3 bucket
+s3_secret_key = "env(S3_SECRET_KEY)"
diff --git a/nextjs-supabase/supabase/migrations/20250322000000_init.sql b/nextjs-supabase/supabase/migrations/20250322000000_init.sql
new file mode 100644
index 0000000..724a12a
--- /dev/null
+++ b/nextjs-supabase/supabase/migrations/20250322000000_init.sql
@@ -0,0 +1,34 @@
+create table tasks (
+ id uuid default gen_random_uuid() primary key,
+ counter bigint generated by default as identity,
+ text text,
+ done boolean default false,
+ created_at timestamptz default now(),
+ updated_at timestamptz default now(),
+ deleted boolean default false
+);
+
+alter
+ publication supabase_realtime add table tasks;
+
+CREATE OR REPLACE FUNCTION handle_times()
+ RETURNS trigger AS
+ $$
+ BEGIN
+ IF (TG_OP = 'INSERT') THEN
+ NEW.created_at := now();
+ NEW.updated_at := now();
+ ELSEIF (TG_OP = 'UPDATE') THEN
+ NEW.created_at = OLD.created_at;
+ NEW.updated_at = now();
+ END IF;
+ RETURN NEW;
+ END;
+ $$ language plpgsql;
+
+CREATE TRIGGER handle_times
+ BEFORE INSERT OR UPDATE ON tasks
+ FOR EACH ROW
+EXECUTE PROCEDURE handle_times();
+
+-- If you have any questions, please contact @Artist-MOBAI on GitHub
\ No newline at end of file
diff --git a/nextjs-supabase/tsconfig.json b/nextjs-supabase/tsconfig.json
new file mode 100644
index 0000000..d8b9323
--- /dev/null
+++ b/nextjs-supabase/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ "target": "ES2017",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./*"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/nextjs-supabase/types/icon.types.ts b/nextjs-supabase/types/icon.types.ts
new file mode 100644
index 0000000..f9b9794
--- /dev/null
+++ b/nextjs-supabase/types/icon.types.ts
@@ -0,0 +1,3 @@
+export type IconProps = {
+ className?: string;
+};
diff --git a/nextjs-supabase/types/store.types.ts b/nextjs-supabase/types/store.types.ts
new file mode 100644
index 0000000..1d99e48
--- /dev/null
+++ b/nextjs-supabase/types/store.types.ts
@@ -0,0 +1,179 @@
+export type Json =
+ | string
+ | number
+ | boolean
+ | null
+ | { [key: string]: Json | undefined }
+ | Json[]
+
+export type Database = {
+ graphql_public: {
+ Tables: {
+ [_ in never]: never
+ }
+ Views: {
+ [_ in never]: never
+ }
+ Functions: {
+ graphql: {
+ Args: {
+ operationName?: string
+ query?: string
+ variables?: Json
+ extensions?: Json
+ }
+ Returns: Json
+ }
+ }
+ Enums: {
+ [_ in never]: never
+ }
+ CompositeTypes: {
+ [_ in never]: never
+ }
+ }
+ public: {
+ Tables: {
+ tasks: {
+ Row: {
+ counter: number
+ created_at: string | null
+ deleted: boolean | null
+ done: boolean | null
+ id: string
+ text: string | null
+ updated_at: string | null
+ }
+ Insert: {
+ counter?: number
+ created_at?: string | null
+ deleted?: boolean | null
+ done?: boolean | null
+ id?: string
+ text?: string | null
+ updated_at?: string | null
+ }
+ Update: {
+ counter?: number
+ created_at?: string | null
+ deleted?: boolean | null
+ done?: boolean | null
+ id?: string
+ text?: string | null
+ updated_at?: string | null
+ }
+ Relationships: []
+ }
+ }
+ Views: {
+ [_ in never]: never
+ }
+ Functions: {
+ [_ in never]: never
+ }
+ Enums: {
+ [_ in never]: never
+ }
+ CompositeTypes: {
+ [_ in never]: never
+ }
+ }
+}
+
+type PublicSchema = Database[Extract]
+
+export type Tables<
+ PublicTableNameOrOptions extends
+ | keyof (PublicSchema["Tables"] & PublicSchema["Views"])
+ | { schema: keyof Database },
+ TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
+ ? keyof (Database[PublicTableNameOrOptions["schema"]]["Tables"] &
+ Database[PublicTableNameOrOptions["schema"]]["Views"])
+ : never = never,
+> = PublicTableNameOrOptions extends { schema: keyof Database }
+ ? (Database[PublicTableNameOrOptions["schema"]]["Tables"] &
+ Database[PublicTableNameOrOptions["schema"]]["Views"])[TableName] extends {
+ Row: infer R
+ }
+ ? R
+ : never
+ : PublicTableNameOrOptions extends keyof (PublicSchema["Tables"] &
+ PublicSchema["Views"])
+ ? (PublicSchema["Tables"] &
+ PublicSchema["Views"])[PublicTableNameOrOptions] extends {
+ Row: infer R
+ }
+ ? R
+ : never
+ : never
+
+export type TablesInsert<
+ PublicTableNameOrOptions extends
+ | keyof PublicSchema["Tables"]
+ | { schema: keyof Database },
+ TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
+ ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"]
+ : never = never,
+> = PublicTableNameOrOptions extends { schema: keyof Database }
+ ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends {
+ Insert: infer I
+ }
+ ? I
+ : never
+ : PublicTableNameOrOptions extends keyof PublicSchema["Tables"]
+ ? PublicSchema["Tables"][PublicTableNameOrOptions] extends {
+ Insert: infer I
+ }
+ ? I
+ : never
+ : never
+
+export type TablesUpdate<
+ PublicTableNameOrOptions extends
+ | keyof PublicSchema["Tables"]
+ | { schema: keyof Database },
+ TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
+ ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"]
+ : never = never,
+> = PublicTableNameOrOptions extends { schema: keyof Database }
+ ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends {
+ Update: infer U
+ }
+ ? U
+ : never
+ : PublicTableNameOrOptions extends keyof PublicSchema["Tables"]
+ ? PublicSchema["Tables"][PublicTableNameOrOptions] extends {
+ Update: infer U
+ }
+ ? U
+ : never
+ : never
+
+export type Enums<
+ PublicEnumNameOrOptions extends
+ | keyof PublicSchema["Enums"]
+ | { schema: keyof Database },
+ EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database }
+ ? keyof Database[PublicEnumNameOrOptions["schema"]]["Enums"]
+ : never = never,
+> = PublicEnumNameOrOptions extends { schema: keyof Database }
+ ? Database[PublicEnumNameOrOptions["schema"]]["Enums"][EnumName]
+ : PublicEnumNameOrOptions extends keyof PublicSchema["Enums"]
+ ? PublicSchema["Enums"][PublicEnumNameOrOptions]
+ : never
+
+export type CompositeTypes<
+ PublicCompositeTypeNameOrOptions extends
+ | keyof PublicSchema["CompositeTypes"]
+ | { schema: keyof Database },
+ CompositeTypeName extends PublicCompositeTypeNameOrOptions extends {
+ schema: keyof Database
+ }
+ ? keyof Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"]
+ : never = never,
+> = PublicCompositeTypeNameOrOptions extends { schema: keyof Database }
+ ? Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName]
+ : PublicCompositeTypeNameOrOptions extends keyof PublicSchema["CompositeTypes"]
+ ? PublicSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions]
+ : never
+