Start working on invitations

This commit is contained in:
Arthur Belleville 2025-07-01 22:28:49 +02:00
parent 5366524602
commit 7a17c95fec
No known key found for this signature in database
12 changed files with 741 additions and 179 deletions

19
api/package-lock.json generated
View file

@ -11,10 +11,12 @@
"dotenv": "^16.5.0",
"hono": "^4.7.7",
"hono-sessions": "^0.7.2",
"nodemailer": "^7.0.4",
"stream-chat": "^9.8.0"
},
"devDependencies": {
"@types/node": "^20.11.17",
"@types/nodemailer": "^6.4.17",
"tsx": "^4.7.1",
"typescript": "^5.8.3"
}
@ -555,6 +557,15 @@
"undici-types": "~6.21.0"
}
},
"node_modules/@types/nodemailer": {
"version": "6.4.17",
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz",
"integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/phoenix": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz",
@ -1093,6 +1104,14 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/nodemailer": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.4.tgz",
"integrity": "sha512-9O00Vh89/Ld2EcVCqJ/etd7u20UhME0f/NToPfArwPEe1Don1zy4mAIz6ariRr7mJ2RDxtaDzN0WJVdVXPtZaw==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",

View file

@ -12,10 +12,12 @@
"dotenv": "^16.5.0",
"hono": "^4.7.7",
"hono-sessions": "^0.7.2",
"nodemailer": "^7.0.4",
"stream-chat": "^9.8.0"
},
"devDependencies": {
"@types/node": "^20.11.17",
"@types/nodemailer": "^6.4.17",
"tsx": "^4.7.1",
"typescript": "^5.8.3"
}

309
api/src/database.types.ts Normal file
View file

@ -0,0 +1,309 @@
export type Json =
| string
| number
| boolean
| null
| { [key: string]: Json | undefined }
| Json[]
export type Database = {
public: {
Tables: {
devis: {
Row: {
client_email: string
created_at: string
date: string
due_date: string
id: string
items: Json
notes: string | null
number: string
status: Database["public"]["Enums"]["devis_status"]
subtotal: number
tax: number
terms: string | null
total: number
updated_at: string
user_id: string
}
Insert: {
client_email: string
created_at?: string
date: string
due_date: string
id?: string
items?: Json
notes?: string | null
number: string
status?: Database["public"]["Enums"]["devis_status"]
subtotal: number
tax: number
terms?: string | null
total: number
updated_at?: string
user_id: string
}
Update: {
client_email?: string
created_at?: string
date?: string
due_date?: string
id?: string
items?: Json
notes?: string | null
number?: string
status?: Database["public"]["Enums"]["devis_status"]
subtotal?: number
tax?: number
terms?: string | null
total?: number
updated_at?: string
user_id?: string
}
Relationships: []
}
feedbacks: {
Row: {
created_at: string | null
fd_type: string
id: number
message: string
user_id: string
}
Insert: {
created_at?: string | null
fd_type: string
id?: number
message: string
user_id: string
}
Update: {
created_at?: string | null
fd_type?: string
id?: number
message?: string
user_id?: string
}
Relationships: []
}
profiles: {
Row: {
avatar_url: string | null
email: string | null
id: string
name: string | null
}
Insert: {
avatar_url?: string | null
email?: string | null
id: string
name?: string | null
}
Update: {
avatar_url?: string | null
email?: string | null
id?: string
name?: string | null
}
Relationships: []
}
tablo_invitations: {
Row: {
created_at: string | null
id: number
invited_by: string
invited_email: string
status: string
tablo_id: number
}
Insert: {
created_at?: string | null
id?: number
invited_by: string
invited_email: string
status?: string
tablo_id: number
}
Update: {
created_at?: string | null
id?: number
invited_by?: string
invited_email?: string
status?: string
tablo_id?: number
}
Relationships: [
{
foreignKeyName: "fk_tablo_invitations_tablo_id"
columns: ["tablo_id"]
isOneToOne: false
referencedRelation: "tablos"
referencedColumns: ["id"]
},
]
}
tablos: {
Row: {
color: string | null
created_at: string | null
deleted_at: string | null
id: number
image: string | null
name: string
position: number
status: string
user_id: string
}
Insert: {
color?: string | null
created_at?: string | null
deleted_at?: string | null
id?: number
image?: string | null
name: string
position?: number
status?: string
user_id: string
}
Update: {
color?: string | null
created_at?: string | null
deleted_at?: string | null
id?: number
image?: string | null
name?: string
position?: number
status?: string
user_id?: string
}
Relationships: []
}
}
Views: {
[_ in never]: never
}
Functions: {
[_ in never]: never
}
Enums: {
devis_status: "draft" | "sent" | "accepted" | "rejected" | "expired"
}
CompositeTypes: {
[_ in never]: never
}
}
}
type DefaultSchema = Database[Extract<keyof Database, "public">]
export type Tables<
DefaultSchemaTableNameOrOptions extends
| keyof (DefaultSchema["Tables"] & DefaultSchema["Views"])
| { schema: keyof Database },
TableName extends DefaultSchemaTableNameOrOptions extends {
schema: keyof Database
}
? keyof (Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] &
Database[DefaultSchemaTableNameOrOptions["schema"]]["Views"])
: never = never,
> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database }
? (Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] &
Database[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends {
Row: infer R
}
? R
: never
: DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] &
DefaultSchema["Views"])
? (DefaultSchema["Tables"] &
DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends {
Row: infer R
}
? R
: never
: never
export type TablesInsert<
DefaultSchemaTableNameOrOptions extends
| keyof DefaultSchema["Tables"]
| { schema: keyof Database },
TableName extends DefaultSchemaTableNameOrOptions extends {
schema: keyof Database
}
? keyof Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"]
: never = never,
> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database }
? Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends {
Insert: infer I
}
? I
: never
: DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"]
? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends {
Insert: infer I
}
? I
: never
: never
export type TablesUpdate<
DefaultSchemaTableNameOrOptions extends
| keyof DefaultSchema["Tables"]
| { schema: keyof Database },
TableName extends DefaultSchemaTableNameOrOptions extends {
schema: keyof Database
}
? keyof Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"]
: never = never,
> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database }
? Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends {
Update: infer U
}
? U
: never
: DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"]
? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends {
Update: infer U
}
? U
: never
: never
export type Enums<
DefaultSchemaEnumNameOrOptions extends
| keyof DefaultSchema["Enums"]
| { schema: keyof Database },
EnumName extends DefaultSchemaEnumNameOrOptions extends {
schema: keyof Database
}
? keyof Database[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"]
: never = never,
> = DefaultSchemaEnumNameOrOptions extends { schema: keyof Database }
? Database[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName]
: DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"]
? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions]
: never
export type CompositeTypes<
PublicCompositeTypeNameOrOptions extends
| keyof DefaultSchema["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 DefaultSchema["CompositeTypes"]
? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions]
: never
export const Constants = {
public: {
Enums: {
devis_status: ["draft", "sent", "accepted", "rejected", "expired"],
},
},
} as const

View file

@ -1,5 +1,6 @@
import { createClient, type User } from "@supabase/supabase-js";
import type { Context, Next } from "hono";
import nodemailer from "nodemailer";
// Create authentication middleware
export const authMiddleware = async (c: Context, next: Next) => {
@ -35,3 +36,17 @@ export const supabaseMiddleware = async (c: Context, next: Next) => {
c.set("supabase", supabase);
await next();
};
export const emailMiddleware = async (c: Context, next: Next) => {
const transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_KEY,
},
});
c.set("transporter", transporter);
await next();
};

View file

@ -1,15 +1,19 @@
import { Hono } from "hono";
import { authMiddleware } from "./middleware.js";
import type { User } from "@supabase/supabase-js";
import { authMiddleware, emailMiddleware } from "./middleware.js";
import type { SupabaseClient, User } from "@supabase/supabase-js";
import { StreamChat } from "stream-chat";
import type { Transporter } from "nodemailer";
export const userRouter = new Hono<{
Variables: {
user: User;
supabase: SupabaseClient;
transporter: Transporter;
};
}>();
userRouter.use(authMiddleware);
userRouter.use(emailMiddleware);
userRouter.get("/get-stream-token", async (c) => {
const user = c.get("user");
@ -29,3 +33,28 @@ userRouter.get("/get-stream-token", async (c) => {
token,
});
});
userRouter.post("/invite", async (c) => {
const sender = c.get("user");
// const supabase = c.get("supabase");
const transporter = c.get("transporter");
const { email: recipientmail, tablo_id } = await c.req.json();
const info = await transporter.sendMail({
from: `${sender.email} via XTablo <noreply@xtablo.com>`,
to: recipientmail,
subject: "You have been invited to a tablo",
html: `<p>You have been invited to a tablo with the following link: <a href="https://xtablo.com/tablo/${tablo_id}">https://xtablo.com/tablo/${tablo_id}</a></p>`,
});
// const { data, error } = await supabase.auth.admin.inviteUserByEmail(
// recipientmail,
// {
// data: {
// tablo_id,
// },
// }
// );
return c.json({ data: info });
});

View file

@ -0,0 +1,39 @@
-- Create tablo_invitations table
CREATE TABLE IF NOT EXISTS tablo_invitations (
id SERIAL PRIMARY KEY,
tablo_id INTEGER NOT NULL,
invited_email VARCHAR(255) NOT NULL,
invited_by UUID NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
-- Foreign key constraint to tablos table
CONSTRAINT fk_tablo_invitations_tablo_id
FOREIGN KEY (tablo_id) REFERENCES tablos(id) ON DELETE CASCADE,
-- Constraint to ensure status is one of the allowed values
CONSTRAINT tablo_invitations_status_check
CHECK (status IN ('pending', 'accepted', 'declined')),
-- Unique constraint to prevent duplicate invitations
CONSTRAINT unique_tablo_invitation
UNIQUE (tablo_id, invited_email)
);
-- Enable Row Level Security
ALTER TABLE tablo_invitations ENABLE ROW LEVEL SECURITY;
-- Create policy to allow tablo owners to insert invitations
CREATE POLICY "Tablo owners can insert invitations" ON tablo_invitations
FOR INSERT WITH CHECK (
auth.uid() = invited_by AND
EXISTS (
SELECT 1 FROM tablos
WHERE tablos.id = tablo_invitations.tablo_id
AND tablos.user_id = auth.uid()
)
);
-- Create index for better query performance
CREATE INDEX idx_tablo_invitations_tablo_id ON tablo_invitations(tablo_id);
CREATE INDEX idx_tablo_invitations_invited_email ON tablo_invitations(invited_email);

View file

@ -31,7 +31,9 @@ export const App = () => {
<SessionProvider>
<UserStoreProvider>
<Router>
<div className={twMerge("min-h-screen bg-white", "dark:bg-white")}>
<div
className={twMerge("min-h-screen bg-white", "dark:bg-gray-900")}
>
<Routes>
<Route path="/" element={<ProtectedRoute fallback="/login" />}>
<Route

View file

@ -10,7 +10,10 @@ type StatusType = "todo" | "in_progress" | "done";
interface CreateTabloModalProps {
onClose: () => void;
onCreate: (
tabloData: Omit<Tablo, "id" | "user_id" | "created_at" | "deleted_at">
tabloData: Omit<
Tablo,
"id" | "user_id" | "created_at" | "deleted_at" | "position"
>
) => void;
}

View file

@ -3,6 +3,7 @@ import { useState } from "react";
import { ImageColorPicker } from "./ImageColorPicker";
import { StatusPicker } from "./StatusPicker";
import { Database } from "@ui/types/database.types";
import { useInviteUser } from "@ui/hooks/invite";
type Tablo = Database["public"]["Tables"]["tablos"]["Row"];
type StatusType = "todo" | "in_progress" | "done";
@ -22,6 +23,9 @@ export const TabloModal = ({ tablo, onClose, onEdit }: TabloModalProps) => {
tablo?.color || "bg-blue-500"
);
const [inviteEmail, setInviteEmail] = useState("");
const inviteUser = useInviteUser();
const handleCancelEdit = () => {
setEditData(null);
};
@ -38,6 +42,18 @@ export const TabloModal = ({ tablo, onClose, onEdit }: TabloModalProps) => {
}
};
const handleSendInvite = () => {
if (inviteEmail.trim()) {
inviteUser({ email: inviteEmail, tablo_id: tablo?.id ?? 0 });
setInviteEmail("");
}
};
const isEmailValid = (email: string): boolean => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
if (!tablo) return null;
const currentData = editData || tablo;
@ -96,6 +112,30 @@ export const TabloModal = ({ tablo, onClose, onEdit }: TabloModalProps) => {
}
/>
</div>
{/* Invite User Section */}
<div className="border-t border-gray-200 dark:border-gray-700 pt-4">
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-3">
Inviter un utilisateur
</h3>
<div className="flex space-x-2">
<input
type="email"
value={inviteEmail}
onChange={(e) => setInviteEmail(e.target.value)}
placeholder="Email de l'utilisateur à inviter"
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
/>
<button
type="button"
onClick={handleSendInvite}
disabled={!isEmailValid(inviteEmail)}
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed rounded-md transition-colors"
>
Inviter
</button>
</div>
</div>
</div>
</div>

32
ui/src/hooks/invite.ts Normal file
View file

@ -0,0 +1,32 @@
import { useMutation } from "@tanstack/react-query";
import { api } from "@ui/lib/api";
import { useSession } from "@ui/contexts/SessionContext";
// Invite user by email
export const useInviteUser = () => {
const { session } = useSession();
const { mutate } = useMutation({
mutationFn: async ({
email,
tablo_id,
}: {
email: string;
tablo_id: number;
}) => {
const { data } = await api.post(
"/api/v1/users/invite",
{
email,
tablo_id,
},
{
headers: {
Authorization: `Bearer ${session?.access_token}`,
},
}
);
return data;
},
});
return mutate;
};

View file

@ -11,6 +11,7 @@ import {
} from "@ui/hooks/tablos";
import { Database } from "@ui/types/database.types";
import { LoadingSpinner } from "@ui/components/LoadingSpinner";
import { useSession } from "@ui/contexts/SessionContext";
type Tablo = Database["public"]["Tables"]["tablos"]["Row"];
@ -21,6 +22,7 @@ export const TabloPage = () => {
const [deletingTablo, setDeletingTablo] = useState<Tablo | null>(null);
const [isDeleting, setIsDeleting] = useState(false);
const { session } = useSession();
const { data: tablos, isLoading, error } = useTablosList();
const createTabloMutation = useCreateTablo();
const { mutateAsync: updateTablo } = useUpdateTablo();
@ -41,7 +43,10 @@ export const TabloPage = () => {
};
const createNewTablo = async (
tabloData: Omit<Tablo, "id" | "user_id" | "created_at" | "deleted_at">
tabloData: Omit<
Tablo,
"id" | "user_id" | "created_at" | "deleted_at" | "position"
>
) => {
try {
await createTabloMutation.mutateAsync(tabloData);
@ -145,6 +150,18 @@ export const TabloPage = () => {
setIsDeleting(false);
};
const getUserRole = (tablo: Tablo) => {
if (!session?.user) return "Invité";
return tablo.user_id === session.user.id ? "Admin" : "Invité";
};
const getRoleColor = (tablo: Tablo) => {
if (!session?.user) return "text-gray-500 dark:text-gray-400";
return tablo.user_id === session.user.id
? "text-blue-600 dark:text-blue-400"
: "text-gray-500 dark:text-gray-400";
};
// Show loading state
if (isLoading) {
return (
@ -303,17 +320,34 @@ export const TabloPage = () => {
{/* Content */}
<div className="p-4">
<div className="flex items-center justify-between">
<h3 className="text-gray-900 dark:text-white font-semibold text-lg truncate">
{tablo.name}
</h3>
{/* Status badge */}
<div className="space-y-2">
<div className="flex items-center gap-2">
<h3 className="text-gray-900 dark:text-white font-semibold text-lg truncate">
{tablo.name}
</h3>
{/* Status badge */}
<div
className={`px-2 py-1 rounded-full text-xs font-medium ${getStatusBadgeColor(
tablo.status
)} flex-shrink-0`}
>
<span>{getStatusLabel(tablo.status)}</span>
</div>
</div>
<div
className={`px-2 py-1 rounded-full text-xs font-medium ${getStatusBadgeColor(
tablo.status
)} ml-2 flex-shrink-0`}
className={`flex items-center gap-1 text-xs font-medium ${getRoleColor(
tablo
)}`}
>
<span>{getStatusLabel(tablo.status)}</span>
<svg
className="w-3 h-3"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M5 4a2 2 0 012-2h6a2 2 0 012 2v14l-5-2.5L5 18V4z" />
</svg>
<span>{getUserRole(tablo)}</span>
</div>
</div>
</div>
@ -458,7 +492,7 @@ export const TabloPage = () => {
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div className="container mx-auto px-4 py-8">
{tablos && tablos.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8">
{/* Render tablos */}
{tablos.map((tablo) => renderTablo(tablo))}
</div>

View file

@ -4,263 +4,301 @@ export type Json =
| boolean
| null
| { [key: string]: Json | undefined }
| Json[];
| Json[]
export type Database = {
public: {
Tables: {
devis: {
Row: {
client_email: string;
created_at: string;
date: string;
due_date: string;
id: string;
items: Json;
notes: string | null;
number: string;
status: Database["public"]["Enums"]["devis_status"];
subtotal: number;
tax: number;
terms: string | null;
total: number;
updated_at: string;
user_id: string;
};
client_email: string
created_at: string
date: string
due_date: string
id: string
items: Json
notes: string | null
number: string
status: Database["public"]["Enums"]["devis_status"]
subtotal: number
tax: number
terms: string | null
total: number
updated_at: string
user_id: string
}
Insert: {
client_email: string;
created_at?: string;
date: string;
due_date: string;
id?: string;
items?: Json;
notes?: string | null;
number: string;
status?: Database["public"]["Enums"]["devis_status"];
subtotal: number;
tax: number;
terms?: string | null;
total: number;
updated_at?: string;
user_id: string;
};
client_email: string
created_at?: string
date: string
due_date: string
id?: string
items?: Json
notes?: string | null
number: string
status?: Database["public"]["Enums"]["devis_status"]
subtotal: number
tax: number
terms?: string | null
total: number
updated_at?: string
user_id: string
}
Update: {
client_email?: string;
created_at?: string;
date?: string;
due_date?: string;
id?: string;
items?: Json;
notes?: string | null;
number?: string;
status?: Database["public"]["Enums"]["devis_status"];
subtotal?: number;
tax?: number;
terms?: string | null;
total?: number;
updated_at?: string;
user_id?: string;
};
Relationships: [];
};
client_email?: string
created_at?: string
date?: string
due_date?: string
id?: string
items?: Json
notes?: string | null
number?: string
status?: Database["public"]["Enums"]["devis_status"]
subtotal?: number
tax?: number
terms?: string | null
total?: number
updated_at?: string
user_id?: string
}
Relationships: []
}
feedbacks: {
Row: {
created_at: string | null;
fd_type: string;
id: number;
message: string;
user_id: string;
};
created_at: string | null
fd_type: string
id: number
message: string
user_id: string
}
Insert: {
created_at?: string | null;
fd_type: string;
id?: number;
message: string;
user_id: string;
};
created_at?: string | null
fd_type: string
id?: number
message: string
user_id: string
}
Update: {
created_at?: string | null;
fd_type?: string;
id?: number;
message?: string;
user_id?: string;
};
Relationships: [];
};
created_at?: string | null
fd_type?: string
id?: number
message?: string
user_id?: string
}
Relationships: []
}
profiles: {
Row: {
avatar_url: string | null;
email: string | null;
id: string;
name: string | null;
};
avatar_url: string | null
email: string | null
id: string
name: string | null
}
Insert: {
avatar_url?: string | null;
email?: string | null;
id: string;
name?: string | null;
};
avatar_url?: string | null
email?: string | null
id: string
name?: string | null
}
Update: {
avatar_url?: string | null;
email?: string | null;
id?: string;
name?: string | null;
};
Relationships: [];
};
avatar_url?: string | null
email?: string | null
id?: string
name?: string | null
}
Relationships: []
}
tablo_invitations: {
Row: {
created_at: string | null
id: number
invited_by: string
invited_email: string
status: string
tablo_id: number
}
Insert: {
created_at?: string | null
id?: number
invited_by: string
invited_email: string
status?: string
tablo_id: number
}
Update: {
created_at?: string | null
id?: number
invited_by?: string
invited_email?: string
status?: string
tablo_id?: number
}
Relationships: [
{
foreignKeyName: "fk_tablo_invitations_tablo_id"
columns: ["tablo_id"]
isOneToOne: false
referencedRelation: "tablos"
referencedColumns: ["id"]
},
]
}
tablos: {
Row: {
color: string | null;
created_at: string | null;
deleted_at: string | null;
id: number;
image: string | null;
name: string;
status: string;
user_id: string;
};
color: string | null
created_at: string | null
deleted_at: string | null
id: number
image: string | null
name: string
position: number
status: string
user_id: string
}
Insert: {
color?: string | null;
created_at?: string | null;
deleted_at?: string | null;
id?: number;
image?: string | null;
name: string;
status?: string;
user_id: string;
};
color?: string | null
created_at?: string | null
deleted_at?: string | null
id?: number
image?: string | null
name: string
position?: number
status?: string
user_id: string
}
Update: {
color?: string | null;
created_at?: string | null;
deleted_at?: string | null;
id?: number;
image?: string | null;
name?: string;
status?: string;
user_id?: string;
};
Relationships: [];
};
};
color?: string | null
created_at?: string | null
deleted_at?: string | null
id?: number
image?: string | null
name?: string
position?: number
status?: string
user_id?: string
}
Relationships: []
}
}
Views: {
[_ in never]: never;
};
[_ in never]: never
}
Functions: {
[_ in never]: never;
};
[_ in never]: never
}
Enums: {
devis_status: "draft" | "sent" | "accepted" | "rejected" | "expired";
};
devis_status: "draft" | "sent" | "accepted" | "rejected" | "expired"
}
CompositeTypes: {
[_ in never]: never;
};
};
};
[_ in never]: never
}
}
}
type DefaultSchema = Database[Extract<keyof Database, "public">];
type DefaultSchema = Database[Extract<keyof Database, "public">]
export type Tables<
DefaultSchemaTableNameOrOptions extends
| keyof (DefaultSchema["Tables"] & DefaultSchema["Views"])
| { schema: keyof Database },
TableName extends DefaultSchemaTableNameOrOptions extends {
schema: keyof Database;
schema: keyof Database
}
? keyof (Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] &
Database[DefaultSchemaTableNameOrOptions["schema"]]["Views"])
: never = never
: never = never,
> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database }
? (Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] &
Database[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends {
Row: infer R;
Row: infer R
}
? R
: never
: DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] &
DefaultSchema["Views"])
? (DefaultSchema["Tables"] &
DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends {
Row: infer R;
}
? R
DefaultSchema["Views"])
? (DefaultSchema["Tables"] &
DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends {
Row: infer R
}
? R
: never
: never
: never;
export type TablesInsert<
DefaultSchemaTableNameOrOptions extends
| keyof DefaultSchema["Tables"]
| { schema: keyof Database },
TableName extends DefaultSchemaTableNameOrOptions extends {
schema: keyof Database;
schema: keyof Database
}
? keyof Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"]
: never = never
: never = never,
> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database }
? Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends {
Insert: infer I;
Insert: infer I
}
? I
: never
: DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"]
? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends {
Insert: infer I;
}
? I
? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends {
Insert: infer I
}
? I
: never
: never
: never;
export type TablesUpdate<
DefaultSchemaTableNameOrOptions extends
| keyof DefaultSchema["Tables"]
| { schema: keyof Database },
TableName extends DefaultSchemaTableNameOrOptions extends {
schema: keyof Database;
schema: keyof Database
}
? keyof Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"]
: never = never
: never = never,
> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database }
? Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends {
Update: infer U;
Update: infer U
}
? U
: never
: DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"]
? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends {
Update: infer U;
}
? U
? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends {
Update: infer U
}
? U
: never
: never
: never;
export type Enums<
DefaultSchemaEnumNameOrOptions extends
| keyof DefaultSchema["Enums"]
| { schema: keyof Database },
EnumName extends DefaultSchemaEnumNameOrOptions extends {
schema: keyof Database;
schema: keyof Database
}
? keyof Database[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"]
: never = never
: never = never,
> = DefaultSchemaEnumNameOrOptions extends { schema: keyof Database }
? Database[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName]
: DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"]
? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions]
: never;
? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions]
: never
export type CompositeTypes<
PublicCompositeTypeNameOrOptions extends
| keyof DefaultSchema["CompositeTypes"]
| { schema: keyof Database },
CompositeTypeName extends PublicCompositeTypeNameOrOptions extends {
schema: keyof Database;
schema: keyof Database
}
? keyof Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"]
: never = never
: never = never,
> = PublicCompositeTypeNameOrOptions extends { schema: keyof Database }
? Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName]
: PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"]
? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions]
: never;
? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions]
: never
export const Constants = {
public: {
@ -268,4 +306,4 @@ export const Constants = {
devis_status: ["draft", "sent", "accepted", "rejected", "expired"],
},
},
} as const;
} as const