diff --git a/apps/calendar/src/app/[locale]/(root)/scheduler/components/AlgorithmInsights.tsx b/apps/calendar/src/app/[locale]/(root)/scheduler/components/AlgorithmInsights.tsx index 375b13e6e..219e953d2 100644 --- a/apps/calendar/src/app/[locale]/(root)/scheduler/components/AlgorithmInsights.tsx +++ b/apps/calendar/src/app/[locale]/(root)/scheduler/components/AlgorithmInsights.tsx @@ -10,8 +10,6 @@ import { CardHeader, CardTitle, } from '@tuturuuu/ui/card'; -import { Progress } from '@tuturuuu/ui/progress'; -import { Separator } from '@tuturuuu/ui/separator'; import { BrainIcon, CheckCircleIcon, @@ -21,7 +19,9 @@ import { TrendingUpIcon, XCircleIcon, ZapIcon, -} from 'lucide-react'; +} from '@tuturuuu/ui/icons'; +import { Progress } from '@tuturuuu/ui/progress'; +import { Separator } from '@tuturuuu/ui/separator'; import { useMemo } from 'react'; interface AlgorithmInsightsProps { diff --git a/apps/calendar/src/app/[locale]/(root)/scheduler/components/ScheduleDisplay.tsx b/apps/calendar/src/app/[locale]/(root)/scheduler/components/ScheduleDisplay.tsx index 738a0327a..b678398e2 100644 --- a/apps/calendar/src/app/[locale]/(root)/scheduler/components/ScheduleDisplay.tsx +++ b/apps/calendar/src/app/[locale]/(root)/scheduler/components/ScheduleDisplay.tsx @@ -9,16 +9,16 @@ import { CardHeader, CardTitle, } from '@tuturuuu/ui/card'; -import { Tooltip, TooltipContent, TooltipTrigger } from '@tuturuuu/ui/tooltip'; -import dayjs from 'dayjs'; -import relativeTime from 'dayjs/plugin/relativeTime'; import { AlertTriangleIcon, CalendarIcon, ClockIcon, SparklesIcon, TrendingUpIcon, -} from 'lucide-react'; +} from '@tuturuuu/ui/icons'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@tuturuuu/ui/tooltip'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; import { useMemo } from 'react'; dayjs.extend(relativeTime); diff --git a/apps/calendar/src/app/[locale]/(root)/scheduler/components/TaskList.tsx b/apps/calendar/src/app/[locale]/(root)/scheduler/components/TaskList.tsx index 5c9513b4e..4429ae772 100644 --- a/apps/calendar/src/app/[locale]/(root)/scheduler/components/TaskList.tsx +++ b/apps/calendar/src/app/[locale]/(root)/scheduler/components/TaskList.tsx @@ -10,6 +10,14 @@ import { CardHeader, CardTitle, } from '@tuturuuu/ui/card'; +import { + CalendarIcon, + CheckCircleIcon, + ClockIcon, + PlusIcon, + Trash2Icon, + ZapIcon, +} from '@tuturuuu/ui/icons'; import { Input } from '@tuturuuu/ui/input'; import { Label } from '@tuturuuu/ui/label'; import { Progress } from '@tuturuuu/ui/progress'; @@ -22,14 +30,6 @@ import { } from '@tuturuuu/ui/select'; import { Tooltip, TooltipContent, TooltipTrigger } from '@tuturuuu/ui/tooltip'; import dayjs from 'dayjs'; -import { - CalendarIcon, - CheckCircleIcon, - ClockIcon, - PlusIcon, - Trash2Icon, - ZapIcon, -} from 'lucide-react'; import { useMemo } from 'react'; interface TaskListProps { diff --git a/apps/calendar/src/app/[locale]/(root)/scheduler/components/TaskModal.tsx b/apps/calendar/src/app/[locale]/(root)/scheduler/components/TaskModal.tsx index d0066ff3a..2c1287040 100644 --- a/apps/calendar/src/app/[locale]/(root)/scheduler/components/TaskModal.tsx +++ b/apps/calendar/src/app/[locale]/(root)/scheduler/components/TaskModal.tsx @@ -10,6 +10,7 @@ import { DialogHeader, DialogTitle, } from '@tuturuuu/ui/dialog'; +import { CalendarIcon, ClockIcon, PlusIcon, TagIcon } from '@tuturuuu/ui/icons'; import { Input } from '@tuturuuu/ui/input'; import { Label } from '@tuturuuu/ui/label'; import { @@ -22,7 +23,6 @@ import { import { Separator } from '@tuturuuu/ui/separator'; import { Textarea } from '@tuturuuu/ui/textarea'; import dayjs from 'dayjs'; -import { CalendarIcon, ClockIcon, PlusIcon, TagIcon } from 'lucide-react'; import { useState } from 'react'; interface TaskModalProps { diff --git a/apps/calendar/src/app/[locale]/(root)/scheduler/components/TemplateScenarios.tsx b/apps/calendar/src/app/[locale]/(root)/scheduler/components/TemplateScenarios.tsx index 5f8975acb..84d3cf607 100644 --- a/apps/calendar/src/app/[locale]/(root)/scheduler/components/TemplateScenarios.tsx +++ b/apps/calendar/src/app/[locale]/(root)/scheduler/components/TemplateScenarios.tsx @@ -24,7 +24,7 @@ import { TrendingUpIcon, UsersIcon, ZapIcon, -} from 'lucide-react'; +} from '@tuturuuu/ui/icons'; interface TemplateScenarioProps { onLoadTemplate: (template: TemplateScenario) => void; diff --git a/apps/calendar/src/app/[locale]/(root)/scheduler/page.tsx b/apps/calendar/src/app/[locale]/(root)/scheduler/page.tsx index 508d2c2ec..0d9537dae 100644 --- a/apps/calendar/src/app/[locale]/(root)/scheduler/page.tsx +++ b/apps/calendar/src/app/[locale]/(root)/scheduler/page.tsx @@ -25,18 +25,18 @@ import { CardHeader, CardTitle, } from '@tuturuuu/ui/card'; -import { Input } from '@tuturuuu/ui/input'; -import { Label } from '@tuturuuu/ui/label'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@tuturuuu/ui/tabs'; -import { TooltipProvider } from '@tuturuuu/ui/tooltip'; -import dayjs from 'dayjs'; import { BrainIcon, CalendarIcon, ClockIcon, SettingsIcon, SparklesIcon, -} from 'lucide-react'; +} from '@tuturuuu/ui/icons'; +import { Input } from '@tuturuuu/ui/input'; +import { Label } from '@tuturuuu/ui/label'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@tuturuuu/ui/tabs'; +import { TooltipProvider } from '@tuturuuu/ui/tooltip'; +import dayjs from 'dayjs'; import { useState } from 'react'; function SchedulerPage() { diff --git a/apps/db/supabase/migrations/20250618070621_changed_check_creator_function_policy.sql b/apps/db/supabase/migrations/20250618070621_changed_check_creator_function_policy.sql new file mode 100644 index 000000000..83661119a --- /dev/null +++ b/apps/db/supabase/migrations/20250618070621_changed_check_creator_function_policy.sql @@ -0,0 +1,48 @@ +set check_function_bodies = off; + +CREATE OR REPLACE FUNCTION public.check_ws_creator(ws_id uuid) + RETURNS boolean + LANGUAGE plpgsql +AS $function$BEGIN + RETURN ( + ( + SELECT creator_id FROM public.workspaces WHERE id = check_ws_creator.ws_id + ) = auth.uid() + AND NOT EXISTS ( + SELECT 1 FROM public.workspace_subscription + WHERE public.workspace_subscription.ws_id = check_ws_creator.ws_id + ) + ); +END;$function$ +; + +drop policy "only allow owner of the user to buy subscription" on "public"."workspace_subscription"; + +create policy "only allow owner of the user to buy subscription" +on "public"."workspace_subscription" +as permissive +for insert +to authenticated +with check ((EXISTS ( SELECT 1 + FROM workspaces + WHERE ((workspaces.id = workspace_subscription.ws_id) AND (workspaces.creator_id = auth.uid()))))); + +drop policy "allow select for users that are in the workspace" on "public"."workspace_subscription"; + +create policy "allow select for users that are in the workspace" +on "public"."workspace_subscription" +as permissive +for select +to authenticated +using ((EXISTS ( SELECT 1 + FROM workspace_members wm + WHERE ((wm.user_id = auth.uid()) AND (wm.ws_id = workspace_subscription.ws_id))))); + +create policy "allow delete access for creator workspace" +on "public"."workspace_subscription" +as permissive +for delete +to authenticated +using ((EXISTS ( SELECT 1 + FROM workspaces + WHERE ((workspaces.id = workspace_subscription.ws_id) AND (workspaces.creator_id = auth.uid()))))); \ No newline at end of file diff --git a/apps/db/supabase/migrations/20250618073214_add_workspace_subscription_products_table.sql b/apps/db/supabase/migrations/20250618073214_add_workspace_subscription_products_table.sql new file mode 100644 index 000000000..57fa8c588 --- /dev/null +++ b/apps/db/supabase/migrations/20250618073214_add_workspace_subscription_products_table.sql @@ -0,0 +1,69 @@ +create table "public"."workspace_subscription_products" ( + "id" uuid not null, + "created_at" timestamp with time zone not null default now(), + "name" text, + "description" text, + "price" real, + "recurring_interval" text default 'month'::text +); + +alter table "public"."workspace_subscription" add column "product_id" uuid; + +CREATE UNIQUE INDEX workspace_subscription_products_pkey ON public.workspace_subscription_products USING btree (id); + +alter table "public"."workspace_subscription_products" add constraint "workspace_subscription_products_pkey" PRIMARY KEY using index "workspace_subscription_products_pkey"; + +alter table "public"."workspace_subscription" add constraint "workspace_subscription_product_id_fkey" FOREIGN KEY (product_id) REFERENCES workspace_subscription_products(id) not valid; + +alter table "public"."workspace_subscription" validate constraint "workspace_subscription_product_id_fkey"; + +grant delete on table "public"."workspace_subscription_products" to "anon"; + +grant insert on table "public"."workspace_subscription_products" to "anon"; + +grant references on table "public"."workspace_subscription_products" to "anon"; + +grant select on table "public"."workspace_subscription_products" to "anon"; + +grant trigger on table "public"."workspace_subscription_products" to "anon"; + +grant truncate on table "public"."workspace_subscription_products" to "anon"; + +grant update on table "public"."workspace_subscription_products" to "anon"; + +grant delete on table "public"."workspace_subscription_products" to "authenticated"; + +grant insert on table "public"."workspace_subscription_products" to "authenticated"; + +grant references on table "public"."workspace_subscription_products" to "authenticated"; + +grant select on table "public"."workspace_subscription_products" to "authenticated"; + +grant trigger on table "public"."workspace_subscription_products" to "authenticated"; + +grant truncate on table "public"."workspace_subscription_products" to "authenticated"; + +grant update on table "public"."workspace_subscription_products" to "authenticated"; + +grant delete on table "public"."workspace_subscription_products" to "service_role"; + +grant insert on table "public"."workspace_subscription_products" to "service_role"; + +grant references on table "public"."workspace_subscription_products" to "service_role"; + +grant select on table "public"."workspace_subscription_products" to "service_role"; + +grant trigger on table "public"."workspace_subscription_products" to "service_role"; + +grant truncate on table "public"."workspace_subscription_products" to "service_role"; + +grant update on table "public"."workspace_subscription_products" to "service_role"; + +alter table "public"."workspace_subscription_products" enable row level security; + +create policy "allow view for all products" +on "public"."workspace_subscription_products" +as permissive +for select +to authenticated +using (true); \ No newline at end of file diff --git a/apps/upskii/src/components/request-education-banner.tsx b/apps/upskii/src/components/request-education-banner.tsx index 7683f058f..453be2a28 100644 --- a/apps/upskii/src/components/request-education-banner.tsx +++ b/apps/upskii/src/components/request-education-banner.tsx @@ -1,8 +1,8 @@ 'use client'; import { RequestAccessButton } from './request-access-button'; +import { BookOpenText, Sparkles } from '@tuturuuu/ui/icons'; import clsx from 'clsx'; -import { BookOpenText, Sparkles } from 'lucide-react'; interface EducationBannerProps { workspaceName: string; diff --git a/apps/web/.env.example b/apps/web/.env.example index c33ec3fd8..236d47582 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -44,4 +44,4 @@ NEXT_PUBLIC_PROXY_API_KEY=YOUR_NEXT_PUBLIC_PROXY_API_KEY # Payment Polar.sh POLAR_WEBHOOK_SECRET=YOUR_POLAR_WEBHOOK_SECRET -NEXT_PUBLIC_POLAR_ACCESS_TOKEN=YOUR_NEXT_PUBLIC_POLAR_ACCESS_TOKEN \ No newline at end of file +NEXT_PUBLIC_POLAR_ACCESS_TOKEN=YOUR_NEXT_PUBLIC_POLAR_ACCESS_TOKEN diff --git a/apps/web/src/app/[locale]/(dashboard)/[wsId]/billing/billing-client.tsx b/apps/web/src/app/[locale]/(dashboard)/[wsId]/billing/billing-client.tsx index 76da8eea1..a3a2979e0 100644 --- a/apps/web/src/app/[locale]/(dashboard)/[wsId]/billing/billing-client.tsx +++ b/apps/web/src/app/[locale]/(dashboard)/[wsId]/billing/billing-client.tsx @@ -2,7 +2,7 @@ import PurchaseLink from './data-polar-checkout'; import { Button } from '@tuturuuu/ui/button'; -import { ArrowUpCircle, CheckCircle, CreditCard } from 'lucide-react'; +import { ArrowUpCircle, CheckCircle } from '@tuturuuu/ui/icons'; import { useState } from 'react'; // Define types for the props we're passing from the server component @@ -30,7 +30,9 @@ interface BillingClientProps { currentPlan: Plan; upgradePlans: UpgradePlan[]; wsId: string; + product_id: string; isCreator: boolean; + activeSubscriptionId?: string; } export function BillingClient({ @@ -38,8 +40,41 @@ export function BillingClient({ upgradePlans, wsId, isCreator, + // _product_id, + // activeSubscriptionId, }: BillingClientProps) { const [showUpgradeOptions, setShowUpgradeOptions] = useState(false); + const [_isLoading, _setIsLoading] = useState(false); + const [message, _setMessage] = useState(''); + + // const handleCancelSubscription = async () => { + // setIsLoading(true); + // setMessage(''); + + // const response = await fetch(`/api/${wsId}/${product_id}/cancel`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json', + // }, + + // body: JSON.stringify({ polarSubscriptionId: activeSubscriptionId }), + // }); + + // setIsLoading(false); + + // if (response.ok) { + // setMessage( + // 'Your subscription will be canceled at the end of your billing period.' + // ); + // // Reload the page to show the updated subscription status + // window.location.reload(); + // } else { + // const errorData = await response.json(); + // setMessage( + // `Error: ${errorData.error || 'Could not cancel subscription.'}` + // ); + // } + // }; return ( <> @@ -65,8 +100,8 @@ export function BillingClient({ -
-
+
+

{currentPlan.name} @@ -79,7 +114,7 @@ export function BillingClient({

-
+

Start Date

@@ -96,59 +131,54 @@ export function BillingClient({

-
-

- Features: +
+

+ Plan Features:

-
    +
      {currentPlan.features?.map((feature, index) => (
    • - - {feature} + + {feature}
    • ))}
-
+ {message && ( +
+ {message} +
+ )} + +
- -
-
- -
-

- Payment Method -

-
- -
-

- Visa ending in 4242 -

-

Expires 05/2025

-
-
-
- + {isLoading ? 'Cancelling...' : 'Cancel Subscription'} + */}

diff --git a/apps/web/src/app/[locale]/(dashboard)/[wsId]/billing/billing-history.tsx b/apps/web/src/app/[locale]/(dashboard)/[wsId]/billing/billing-history.tsx new file mode 100644 index 000000000..fa33a526c --- /dev/null +++ b/apps/web/src/app/[locale]/(dashboard)/[wsId]/billing/billing-history.tsx @@ -0,0 +1,147 @@ +import { Receipt } from '@tuturuuu/ui/icons'; +import React from 'react'; + +interface BillingHistoryItem { + id: string; + created_at: string; + product_id: string | null; + status: string; + cancel_at_period_end: boolean | null; + product: { + name: string; + description: string | null; + price: number; + recurring_interval: string; + } | null; +} + +export default function BillingHistory({ + billingHistory, +}: { + billingHistory: BillingHistoryItem[]; +}) { + const getStatusColor = (status: string) => { + switch (status) { + case 'active': + return 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400'; + case 'past_due': + return 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400'; + case 'canceled': + case 'cancelled': + return 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-400'; + case 'incomplete': + case 'incomplete_expired': + return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400'; + default: + return 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400'; + } + }; + + const getDisplayDate = (item: BillingHistoryItem) => { + const createdDate = new Date(item.created_at).toLocaleDateString(); + + switch (item.status) { + case 'active': + return `Started: ${createdDate}`; + case 'past_due': + return `Due: ${createdDate}`; + case 'canceled': + case 'cancelled': + return `Cancelled: ${createdDate}`; + default: + return createdDate; + } + }; + + return ( +
+
+

+ Subscription History +

+ {billingHistory.length === 0 ? ( +

+ No subscription history available. +

+ ) : ( +
+ + + + + + + + + + + + + {billingHistory.map((subscription) => ( + + + + + + + + + ))} + +
+ Subscription # + + Date + + Plan + + Amount + + Status + + Actions +
+ {subscription.id} + + {getDisplayDate(subscription)} + +
+
+ {subscription.product?.name || 'Unknown Plan'} +
+
+
+ {subscription.product ? ( +
+
+ ${subscription.product.price} +
+
+ per {subscription.product.recurring_interval} +
+
+ ) : ( + 'N/A' + )} +
+ + {subscription.status.charAt(0).toUpperCase() + + subscription.status.slice(1)} + {subscription.cancel_at_period_end && ' (Ending)'} + + +

+ +

+
+
+ )} +
+
+ ); +} diff --git a/apps/web/src/app/[locale]/(dashboard)/[wsId]/billing/data-polar-checkout.tsx b/apps/web/src/app/[locale]/(dashboard)/[wsId]/billing/data-polar-checkout.tsx index 885139f59..3aaea540c 100644 --- a/apps/web/src/app/[locale]/(dashboard)/[wsId]/billing/data-polar-checkout.tsx +++ b/apps/web/src/app/[locale]/(dashboard)/[wsId]/billing/data-polar-checkout.tsx @@ -17,20 +17,7 @@ const PurchaseLink = ({ className, children, }: PropsWithChildren) => { - const testEmail = 't@test.com'; - - // if want to change embed checkout theme, - // useEffect(() => { - // PolarEmbedCheckout.init(); - // }, []); - - // ✅ UPDATED: Create the URL with the new dynamic path - const checkoutUrl = `/api/${wsId}/${productId}/payment?productId=${productId}&customerEmail=${testEmail}`; - - // You can still add other details as search parameters if you need them - // if (customerEmail) { - // checkoutUrl.searchParams.set('customerEmail', customerEmail); - // } + const checkoutUrl = `/api/${wsId}/${productId}/payment`; return ( { try { const res = await api.products.list({ isArchived: false }); + return res.result.items ?? []; } catch (err) { console.error('Failed to fetch products:', err); @@ -22,25 +23,21 @@ const checkCreator = async (wsId: string) => { return false; } - // Call the 'check_ws_creator' function with the 'ws_id' argument. - // The keys in the second object MUST match the argument names in your function. const { data, error } = await supabase.rpc('check_ws_creator', { ws_id: wsId, }); if (error) { - console.error('Error checking workspace creator status:', error); - // As a safe default, deny access if there's an error. + console.error('Error checking workspace creator:', error); return true; } - // The 'data' returned from the RPC will be the boolean result. return data; }; + const fetchSubscription = async (wsId: string) => { const sbAdmin = await createClient(); - // 1. Get the subscription record from your DB const { data: dbSub, error } = await sbAdmin .from('workspace_subscription') .select('*') @@ -49,25 +46,19 @@ const fetchSubscription = async (wsId: string) => { .single(); if (error || !dbSub) { - console.error( - 'Error fetching subscription or subscription not found:', - error - ); + console.error('Error fetching subscription:', error); return null; } - // 2. If it exists, get the full product details from Polar - const polarProduct = await api.products.get({ id: 'test-product-id' }); + const polarProduct = await api.products.get({ id: dbSub.product_id || '' }); - if (!polarProduct) { - return null; // Can't proceed without product details - } + if (!polarProduct) return null; - // 3. Combine the data into one clean object return { status: dbSub.status, currentPeriodStart: dbSub.current_period_start, currentPeriodEnd: dbSub.current_period_end, + polar_subscription_id: dbSub.polar_subscription_id, product: { id: polarProduct.id, name: polarProduct.name, @@ -77,20 +68,52 @@ const fetchSubscription = async (wsId: string) => { }; }; +const fetchWorkspaceSubscriptions = async (wsId: string) => { + const sbAdmin = await createClient(); + + const { data, error } = await sbAdmin + .from('workspace_subscription') + .select( + ` + id, + created_at, + product_id, + status, + cancel_at_period_end, + workspace_subscription_products ( + name, + description, + price, + recurring_interval + ) + ` + ) + .eq('ws_id', wsId) + .order('created_at', { ascending: false }) + .limit(5); + + if (error) { + console.error('Error fetching billing history:', error); + return []; + } + + return data ?? []; +}; + export default async function BillingPage({ params, }: { params: Promise<{ wsId: string }>; }) { - // const products = await fetchProducts(); - // const subscription = await fetchSubscription((await params).wsId); const { wsId } = await params; - const [products, subscription, isCreator] = await Promise.all([ - fetchProducts(), - fetchSubscription(wsId), - checkCreator(wsId), - ]); - // console.log(subscription, 'Subscription Data'); + + const [products, subscription, isCreator, subscriptionHistory] = + await Promise.all([ + fetchProducts(), + fetchSubscription(wsId), + checkCreator(wsId), + fetchWorkspaceSubscriptions(wsId), + ]); const currentPlan = subscription?.product ? { @@ -127,26 +150,22 @@ export default async function BillingPage({ features: ['Basic features', 'Limited usage', 'Community support'], }; - const paymentHistory = [ - { - id: 'INV-2023-06', - date: 'Jun 15, 2023', - amount: '$19.99', - status: 'Paid', - }, - { - id: 'INV-2023-05', - date: 'May 15, 2023', - amount: '$19.99', - status: 'Paid', - }, - { - id: 'INV-2023-04', - date: 'Apr 15, 2023', - amount: '$19.99', - status: 'Paid', - }, - ]; + const billingHistory = subscriptionHistory.map((sub, index) => ({ + id: sub.id ?? `SUB-${sub.product_id?.slice(-6) || index}`, + created_at: sub.created_at, + product_id: sub.product_id, + status: sub.status ?? 'unknown', + cancel_at_period_end: sub.cancel_at_period_end, + product: sub.workspace_subscription_products + ? { + name: sub.workspace_subscription_products.name || 'Unknown Plan', + description: sub.workspace_subscription_products.description, + price: sub.workspace_subscription_products.price || 0, + recurring_interval: + sub.workspace_subscription_products.recurring_interval || 'month', + } + : null, + })); const upgradePlans = products.map((product, index) => ({ id: product.id, @@ -163,7 +182,7 @@ export default async function BillingPage({ ? product.prices[0]?.recurringInterval || 'month' : 'one-time' : 'month', - popular: index === 1, // Make the second product popular as example + popular: index === 1, features: product.description ? [product.description, 'Customer support', 'Access to platform features'] : [ @@ -183,74 +202,14 @@ export default async function BillingPage({ - {/* Payment History (Static) */} -
-

- Payment History -

-
- - - - - - - - - - - - {paymentHistory.map((payment) => ( - - - - - - - - ))} - -
- Invoice # - - Date - - Amount - - Status - - Actions -
- {payment.id} - - {payment.date} - - {payment.amount} - - - {payment.status} - - - -
-
-
+
); } diff --git a/apps/web/src/app/[locale]/(dashboard)/[wsId]/billing/success/page.tsx b/apps/web/src/app/[locale]/(dashboard)/[wsId]/billing/success/page.tsx index d1cd26c51..53b7351a7 100644 --- a/apps/web/src/app/[locale]/(dashboard)/[wsId]/billing/success/page.tsx +++ b/apps/web/src/app/[locale]/(dashboard)/[wsId]/billing/success/page.tsx @@ -1,7 +1,12 @@ import { createClient } from '@tuturuuu/supabase/next/server'; import { Button } from '@tuturuuu/ui/button'; +import { + ArrowLeft, + CheckCircle, + CreditCard, + Download, +} from '@tuturuuu/ui/icons'; import { format } from 'date-fns'; -import { ArrowLeft, CheckCircle, CreditCard, Download } from 'lucide-react'; import Link from 'next/link'; const fetchWorkspaceSubscription = async ( diff --git a/apps/web/src/app/[locale]/(dashboard)/[wsId]/nav-link.tsx b/apps/web/src/app/[locale]/(dashboard)/[wsId]/nav-link.tsx index b50d2fe9b..7a98aa337 100644 --- a/apps/web/src/app/[locale]/(dashboard)/[wsId]/nav-link.tsx +++ b/apps/web/src/app/[locale]/(dashboard)/[wsId]/nav-link.tsx @@ -1,9 +1,9 @@ 'use client'; import { NavLink as NavLinkType } from '@/components/navigation'; +import { ChevronRight } from '@tuturuuu/ui/icons'; import { Tooltip, TooltipContent, TooltipTrigger } from '@tuturuuu/ui/tooltip'; import { cn } from '@tuturuuu/utils/format'; -import { ChevronRight } from 'lucide-react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; diff --git a/apps/web/src/app/[locale]/(dashboard)/settings/account/sessions/sessions-card.tsx b/apps/web/src/app/[locale]/(dashboard)/settings/account/sessions/sessions-card.tsx index acbd8dbd1..890349531 100644 --- a/apps/web/src/app/[locale]/(dashboard)/settings/account/sessions/sessions-card.tsx +++ b/apps/web/src/app/[locale]/(dashboard)/settings/account/sessions/sessions-card.tsx @@ -20,8 +20,6 @@ import { CardHeader, CardTitle, } from '@tuturuuu/ui/card'; -import { Progress } from '@tuturuuu/ui/progress'; -import { Separator } from '@tuturuuu/ui/separator'; import { Clock, LogOut, @@ -30,7 +28,9 @@ import { Smartphone, Tablet, Trash2, -} from 'lucide-react'; +} from '@tuturuuu/ui/icons'; +import { Progress } from '@tuturuuu/ui/progress'; +import { Separator } from '@tuturuuu/ui/separator'; import { useTranslations } from 'next-intl'; import { useEffect, useState } from 'react'; import { toast } from 'sonner'; diff --git a/apps/web/src/app/[locale]/(dashboard)/settings/account/settings-nav.tsx b/apps/web/src/app/[locale]/(dashboard)/settings/account/settings-nav.tsx index 0488117cd..001909c48 100644 --- a/apps/web/src/app/[locale]/(dashboard)/settings/account/settings-nav.tsx +++ b/apps/web/src/app/[locale]/(dashboard)/settings/account/settings-nav.tsx @@ -1,6 +1,5 @@ 'use client'; -import { cn } from '@tuturuuu/utils/format'; import { Bell, ChevronRight, @@ -9,7 +8,8 @@ import { Shield, Smartphone, User, -} from 'lucide-react'; +} from '@tuturuuu/ui/icons'; +import { cn } from '@tuturuuu/utils/format'; import { useTranslations } from 'next-intl'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; diff --git a/apps/web/src/app/api/[wsId]/[productId]/cancel/route.ts b/apps/web/src/app/api/[wsId]/[productId]/cancel/route.ts new file mode 100644 index 000000000..61cd1b6f2 --- /dev/null +++ b/apps/web/src/app/api/[wsId]/[productId]/cancel/route.ts @@ -0,0 +1,53 @@ +// import { api } from '@/lib/polar'; +// import { createClient } from '@tuturuuu/supabase/next/server'; +// import { getCurrentSupabaseUser } from '@tuturuuu/utils/user-helper'; +// import { NextRequest, NextResponse } from 'next/server'; + +// export async function POST( +// req: NextRequest, +// _res: { params: Promise<{ wsId: string; productId: string }> } +// ) { +// // const supabase = await createClient(); + +// const user = await getCurrentSupabaseUser(); + +// if (!user) { +// return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); +// } + +// const { polarSubscriptionId } = await req.json(); + +// if (!polarSubscriptionId) { +// return NextResponse.json( +// { error: 'Subscription ID is required' }, +// { status: 400 } +// ); +// } +// // const updatedSubscription = await api.subscriptions.update({ +// // id: polarSubscriptionId, +// // cancel_at_period_end: true, // This is the user-friendly way to cancel +// // }); +// const session = await api.customerSessions.create({ +// customerExternalId: '00000000-0000-0000-0000-000000000001', +// }); +// const result = await api.customers.getExternal({ +// externalId: user.id, +// }); + +// console.log(result, 'Customer external data retrieved:'); +// // console.log(session.token, 'Session created for user:'); + +// // const result = await api.customerPortal.subscriptions.cancel( +// // { +// // customerSession: session.token ?? '', +// // }, +// // { +// // id: polarSubscriptionId, +// // } +// // ); + +// return NextResponse.json({ +// success: true, +// // result: result, +// }); +// } diff --git a/apps/web/src/app/api/[wsId]/[productId]/payment/route.ts b/apps/web/src/app/api/[wsId]/[productId]/payment/route.ts index 0c9ad9201..b8bcd8364 100644 --- a/apps/web/src/app/api/[wsId]/[productId]/payment/route.ts +++ b/apps/web/src/app/api/[wsId]/[productId]/payment/route.ts @@ -1,11 +1,54 @@ import { api } from '@/lib/polar'; +import { createClient } from '@tuturuuu/supabase/next/server'; +import { getCurrentSupabaseUser } from '@tuturuuu/utils/user-helper'; import { NextRequest, NextResponse } from 'next/server'; export async function GET( _request: NextRequest, { params }: { params: Promise<{ wsId: string; productId: string }> } ) { + // const sbAdmin = await createAdminClient(); + const user = await getCurrentSupabaseUser(); + const { wsId, productId } = await params; + const supabase = await createClient(); + + const { data, error } = await supabase + .from('users') + .select('display_name') + .eq('id', user?.id || '') + .single(); + + if (error) { + console.error('Error fetching user display name:', error); + return new Response('Error fetching user display name', { + status: 500, + }); + } + const { data: isCreatorAllowed, error: rpcError } = await supabase.rpc( + 'check_ws_creator', + { + ws_id: wsId, + } + ); + if (rpcError) { + console.error('Error checking workspace creator:', rpcError); + return new Response(`Error checking creator status: ${rpcError.message}`, { + status: 500, + }); + } + + if (!isCreatorAllowed) { + console.warn( + `User (auth.uid()) is not authorized to create subscription for wsId: ${wsId} or subscription already exists.` + ); + return new Response( + 'Unauthorized: You are not the workspace creator or an active subscription already exists.', + { + status: 403, // Forbidden + } + ); + } // Validate that you have the info you need if (!productId || !wsId) { return new Response('Product ID and Workspace ID are required', { @@ -17,11 +60,14 @@ export async function GET( const checkoutSession = await api.checkouts.create({ products: [productId], successUrl: `http://localhost:7803/${wsId}/billing/success`, - + customerExternalId: user?.id || '', metadata: { wsId: wsId, }, }); - return NextResponse.redirect(checkoutSession.url); + return NextResponse.redirect( + checkoutSession.url + + `?customerEmail=${user?.email}&customer_name=${data.display_name}` + ); } diff --git a/apps/web/src/app/api/webhooks/route.ts b/apps/web/src/app/api/webhooks/route.ts index 9fbc34b9b..f552da3d6 100644 --- a/apps/web/src/app/api/webhooks/route.ts +++ b/apps/web/src/app/api/webhooks/route.ts @@ -2,27 +2,25 @@ import { Polar } from '@tuturuuu/payment/polar'; import { Webhooks } from '@tuturuuu/payment/polar/next'; import { createAdminClient } from '@tuturuuu/supabase/next/server'; -// Initialize a Polar ADMIN client with your secret token -// This is needed to report usage events securely. const polarAdmin = new Polar({ accessToken: process.env.NEXT_PUBLIC_POLAR_ACCESS_TOKEN, - server: 'sandbox', // Change to 'production' in production + server: 'sandbox', }); export const POST = Webhooks({ webhookSecret: process.env.POLAR_WEBHOOK_SECRET || '', onSubscriptionActive: async (payload) => { + console.log(payload, 'Received subscription active webhook payload'); try { const sbAdmin = await createAdminClient(); const subscriptionPayload = payload.data; - // Only process subscriptions that are truly 'active' if (subscriptionPayload.status !== 'active') { console.log( `Ignoring subscription with status: '${subscriptionPayload.status}'.` ); - // Use 'return' instead of 'throw' for proper API responses + throw new Response('Webhook handled: Status not active.', { status: 200, }); @@ -69,22 +67,20 @@ export const POST = Webhooks({ // --- 2. REPORT INITIAL USAGE (The fix to make the meter work) --- try { - // Count the users in the workspace at the moment of subscribing const { count: initialUserCount, error: countError } = await sbAdmin - .from('workspace_users') // Assumes you have a 'workspace_users' table + .from('workspace_users') .select('*', { count: 'exact', head: true }) .eq('ws_id', ws_id); - if (countError) throw countError; // This error will be caught below - console.log(initialUserCount, 'users found in workspace'); - // Report this initial count to your Polar Meter + if (countError) throw countError; + await polarAdmin.events.ingest({ events: [ { - name: 'workspace.seats.sync', // Must match the Event Name in your Meter + name: 'workspace.seats.sync', customerId: payload.data.customer.id, metadata: { - seat_count: initialUserCount ?? 0, // Must match the Property Name in your Meter + seat_count: initialUserCount ?? 0, }, }, ], @@ -109,7 +105,6 @@ export const POST = Webhooks({ }, onSubscriptionCanceled: async (payload) => { - // Your cancellation logic here... console.log('Subscription canceled:', payload); throw new Response('Cancellation webhook received.', { status: 200 }); }, diff --git a/apps/web/src/components/command/add-task-form.tsx b/apps/web/src/components/command/add-task-form.tsx index 52d41676b..91aba4497 100644 --- a/apps/web/src/components/command/add-task-form.tsx +++ b/apps/web/src/components/command/add-task-form.tsx @@ -6,6 +6,14 @@ import { Badge } from '@tuturuuu/ui/badge'; import { Button } from '@tuturuuu/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@tuturuuu/ui/card'; import { useToast } from '@tuturuuu/ui/hooks/use-toast'; +import { + AlertTriangle, + Check, + Clock, + List, + Loader, + Plus, +} from '@tuturuuu/ui/icons'; import { ScrollArea } from '@tuturuuu/ui/scroll-area'; import { Select, @@ -16,7 +24,6 @@ import { } from '@tuturuuu/ui/select'; import { Separator } from '@tuturuuu/ui/separator'; import { cn } from '@tuturuuu/utils/format'; -import { AlertTriangle, Check, Clock, List, Loader, Plus } from 'lucide-react'; import { useState } from 'react'; interface BoardWithLists { diff --git a/apps/web/src/components/command/board-navigation.tsx b/apps/web/src/components/command/board-navigation.tsx index 567fa4ac3..3adb9b9e7 100644 --- a/apps/web/src/components/command/board-navigation.tsx +++ b/apps/web/src/components/command/board-navigation.tsx @@ -2,7 +2,7 @@ import type { Board } from './types'; import { CommandGroup, CommandItem } from '@tuturuuu/ui/command'; -import { ExternalLink, LayoutDashboard, MapPin, Tag } from 'lucide-react'; +import { ExternalLink, LayoutDashboard, MapPin, Tag } from '@tuturuuu/ui/icons'; import * as React from 'react'; interface BoardNavigationProps { diff --git a/apps/web/src/components/command/coming-soon.tsx b/apps/web/src/components/command/coming-soon.tsx index daa36c2dd..26b1733b9 100644 --- a/apps/web/src/components/command/coming-soon.tsx +++ b/apps/web/src/components/command/coming-soon.tsx @@ -1,7 +1,7 @@ 'use client'; import { CommandGroup, CommandItem } from '@tuturuuu/ui/command'; -import { Search, Zap } from 'lucide-react'; +import { Search, Zap } from '@tuturuuu/ui/icons'; export function ComingSoonSection() { return ( diff --git a/apps/web/src/components/command/command-header.tsx b/apps/web/src/components/command/command-header.tsx index b02005642..c380a389b 100644 --- a/apps/web/src/components/command/command-header.tsx +++ b/apps/web/src/components/command/command-header.tsx @@ -1,7 +1,7 @@ 'use client'; +import { ArrowLeft, Loader, Sparkles } from '@tuturuuu/ui/icons'; import { Command as CommandPrimitive } from 'cmdk'; -import { ArrowLeft, Loader, Sparkles } from 'lucide-react'; interface CommandHeaderProps { page: string; diff --git a/apps/web/src/components/command/empty-state.tsx b/apps/web/src/components/command/empty-state.tsx index 6db447421..d3be2c145 100644 --- a/apps/web/src/components/command/empty-state.tsx +++ b/apps/web/src/components/command/empty-state.tsx @@ -1,7 +1,7 @@ 'use client'; import { CommandEmpty } from '@tuturuuu/ui/command'; -import { Search } from 'lucide-react'; +import { Search } from '@tuturuuu/ui/icons'; export function EmptyState() { return ( diff --git a/apps/web/src/components/command/quick-actions.tsx b/apps/web/src/components/command/quick-actions.tsx index 5d709127d..96043741e 100644 --- a/apps/web/src/components/command/quick-actions.tsx +++ b/apps/web/src/components/command/quick-actions.tsx @@ -8,7 +8,7 @@ import { PlusCircle, Timer, TrendingUp, -} from 'lucide-react'; +} from '@tuturuuu/ui/icons'; import { useMemo } from 'react'; // Peak productivity hours configuration diff --git a/apps/web/src/components/command/quick-time-tracker.tsx b/apps/web/src/components/command/quick-time-tracker.tsx index ea22685b5..8dbec372b 100644 --- a/apps/web/src/components/command/quick-time-tracker.tsx +++ b/apps/web/src/components/command/quick-time-tracker.tsx @@ -3,11 +3,17 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Button } from '@tuturuuu/ui/button'; import { CommandGroup } from '@tuturuuu/ui/command'; +import { + CheckCircle, + ExternalLink, + Play, + Square, + Timer, +} from '@tuturuuu/ui/icons'; import { Input } from '@tuturuuu/ui/input'; import { Label } from '@tuturuuu/ui/label'; import { toast } from '@tuturuuu/ui/sonner'; import { cn } from '@tuturuuu/utils/format'; -import { CheckCircle, ExternalLink, Play, Square, Timer } from 'lucide-react'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { useCallback, useEffect, useState } from 'react'; diff --git a/packages/types/src/supabase.ts b/packages/types/src/supabase.ts index 6d8effa46..ed133bedc 100644 --- a/packages/types/src/supabase.ts +++ b/packages/types/src/supabase.ts @@ -6173,6 +6173,7 @@ export type Database = { current_period_start: string | null; id: string; polar_subscription_id: string; + product_id: string | null; status: Database['public']['Enums']['subscription_status'] | null; updated_at: string | null; ws_id: string; @@ -6184,6 +6185,7 @@ export type Database = { current_period_start?: string | null; id?: string; polar_subscription_id: string; + product_id?: string | null; status?: Database['public']['Enums']['subscription_status'] | null; updated_at?: string | null; ws_id: string; @@ -6195,11 +6197,19 @@ export type Database = { current_period_start?: string | null; id?: string; polar_subscription_id?: string; + product_id?: string | null; status?: Database['public']['Enums']['subscription_status'] | null; updated_at?: string | null; ws_id?: string; }; Relationships: [ + { + foreignKeyName: 'workspace_subscription_product_id_fkey'; + columns: ['product_id']; + isOneToOne: false; + referencedRelation: 'workspace_subscription_products'; + referencedColumns: ['id']; + }, { foreignKeyName: 'workspace_subscription_ws_id_fkey'; columns: ['ws_id']; @@ -6209,6 +6219,33 @@ export type Database = { }, ]; }; + workspace_subscription_products: { + Row: { + created_at: string; + description: string | null; + id: string; + name: string | null; + price: number | null; + recurring_interval: string | null; + }; + Insert: { + created_at?: string; + description?: string | null; + id: string; + name?: string | null; + price?: number | null; + recurring_interval?: string | null; + }; + Update: { + created_at?: string; + description?: string | null; + id?: string; + name?: string | null; + price?: number | null; + recurring_interval?: string | null; + }; + Relationships: []; + }; workspace_teams: { Row: { created_at: string | null; @@ -7417,32 +7454,32 @@ export type Database = { }; count_search_users: { Args: - | { search_query: string } | { - search_query: string; role_filter?: string; + search_query: string; enabled_filter?: boolean; - }; + } + | { search_query: string }; Returns: number; }; create_ai_chat: { - Args: { title: string; model: string; message: string }; + Args: { model: string; title: string; message: string }; Returns: string; }; generate_cross_app_token: { Args: | { p_expiry_seconds?: number; + p_target_app: string; p_origin_app: string; - p_session_data?: Json; p_user_id: string; - p_target_app: string; } | { - p_user_id: string; p_origin_app: string; + p_user_id: string; p_target_app: string; p_expiry_seconds?: number; + p_session_data?: Json; }; Returns: string; }; @@ -7454,11 +7491,11 @@ export type Database = { }[]; }; get_daily_income_expense: { - Args: { past_days?: number; _ws_id: string }; + Args: { _ws_id: string; past_days?: number }; Returns: { - total_expense: number; - day: string; total_income: number; + day: string; + total_expense: number; }[]; }; get_daily_prompt_completion_tokens: { @@ -7492,9 +7529,9 @@ export type Database = { get_hourly_prompt_completion_tokens: { Args: { past_hours?: number }; Returns: { - total_prompt_tokens: number; - total_completion_tokens: number; hour: string; + total_completion_tokens: number; + total_prompt_tokens: number; }[]; }; get_inventory_batches_count: { @@ -7507,22 +7544,22 @@ export type Database = { }; get_inventory_products: { Args: { - _warehouse_ids?: string[]; - _has_unit?: boolean; _category_ids?: string[]; _ws_id?: string; + _warehouse_ids?: string[]; + _has_unit?: boolean; }; Returns: { + unit: string; id: string; name: string; manufacturer: string; - unit: string; unit_id: string; category: string; price: number; - created_at: string; amount: number; ws_id: string; + created_at: string; }[]; }; get_inventory_products_count: { @@ -7545,8 +7582,8 @@ export type Database = { Args: { _ws_id: string; past_months?: number }; Returns: { total_expense: number; - month: string; total_income: number; + month: string; }[]; }; get_monthly_prompt_completion_tokens: { @@ -7565,67 +7602,67 @@ export type Database = { Args: { _ws_id: string; included_groups: string[] }; Returns: { amount: number; - id: string; - name: string; ws_id: string; + name: string; + id: string; }[]; }; get_possible_excluded_tags: { Args: { included_tags: string[]; _ws_id: string }; Returns: { - id: string; - name: string; - ws_id: string; amount: number; + ws_id: string; + name: string; + id: string; }[]; }; get_session_statistics: { Args: Record; Returns: { - unique_users_count: number; - completed_count: number; total_count: number; - latest_session_date: string; + completed_count: number; active_count: number; + unique_users_count: number; + latest_session_date: string; }[]; }; get_session_templates: { Args: { - workspace_id: string; user_id_param: string; limit_count?: number; + workspace_id: string; }; Returns: { category_name: string; - title: string; - description: string; - category_id: string; - task_id: string; tags: string[]; + task_id: string; + category_id: string; + description: string; + title: string; + last_used: string; + avg_duration: number; + usage_count: number; category_color: string; task_name: string; - usage_count: number; - avg_duration: number; - last_used: string; }[]; }; get_submission_statistics: { Args: Record; Returns: { - total_count: number; unique_users_count: number; latest_submission_date: string; + total_count: number; }[]; }; get_transaction_categories_with_amount: { Args: Record; Returns: { - created_at: string; - ws_id: string; - is_expense: boolean; + amount: number; name: string; + is_expense: boolean; + ws_id: string; + created_at: string; id: string; - amount: number; }[]; }; get_user_role: { @@ -7635,34 +7672,34 @@ export type Database = { get_user_session_stats: { Args: { user_id: string }; Returns: { - current_session_age: unknown; total_sessions: number; active_sessions: number; + current_session_age: unknown; }[]; }; get_user_sessions: { Args: { user_id: string }; Returns: { - session_id: string; created_at: string; updated_at: string; user_agent: string; - is_current: boolean; ip: string; + is_current: boolean; + session_id: string; }[]; }; get_user_tasks: { Args: { _board_id: string }; Returns: { - list_id: string; - start_date: string; - completed: boolean; - priority: number; - description: string; - name: string; - id: string; board_id: string; + id: string; + name: string; + description: string; + priority: number; + completed: boolean; + start_date: string; end_date: string; + list_id: string; }[]; }; get_workspace_drive_size: { @@ -7678,24 +7715,24 @@ export type Database = { Returns: number; }; get_workspace_transactions_count: { - Args: { ws_id: string; end_date?: string; start_date?: string }; + Args: { start_date?: string; end_date?: string; ws_id: string }; Returns: number; }; get_workspace_user_groups: { Args: { - search_query: string; - excluded_tags: string[]; included_tags: string[]; + excluded_tags: string[]; + search_query: string; _ws_id: string; }; Returns: { - tags: string[]; - tag_count: number; created_at: string; id: string; - ws_id: string; - notes: string; name: string; + notes: string; + ws_id: string; + tags: string[]; + tag_count: number; }[]; }; get_workspace_user_groups_count: { @@ -7710,26 +7747,26 @@ export type Database = { search_query: string; }; Returns: { + id: string; + avatar_url: string; full_name: string; display_name: string; - updated_at: string; - created_at: string; - linked_users: Json; - group_count: number; - groups: string[]; - ws_id: string; - balance: number; - note: string; - national_id: string; - address: string; - guardian: string; - ethnicity: string; - birthday: string; - gender: string; - phone: string; email: string; - id: string; - avatar_url: string; + phone: string; + gender: string; + birthday: string; + ethnicity: string; + guardian: string; + address: string; + national_id: string; + note: string; + balance: number; + ws_id: string; + groups: string[]; + group_count: number; + linked_users: Json; + created_at: string; + updated_at: string; }[]; }; get_workspace_users_count: { @@ -7741,19 +7778,19 @@ export type Database = { Returns: number; }; get_workspace_wallets_expense: { - Args: { end_date?: string; start_date?: string; ws_id: string }; + Args: { end_date?: string; ws_id: string; start_date?: string }; Returns: number; }; get_workspace_wallets_income: { - Args: { end_date?: string; start_date?: string; ws_id: string }; + Args: { end_date?: string; ws_id: string; start_date?: string }; Returns: number; }; has_other_owner: { - Args: { _user_id: string; _ws_id: string }; + Args: { _ws_id: string; _user_id: string }; Returns: boolean; }; insert_ai_chat_message: { - Args: { message: string; chat_id: string; source: string }; + Args: { chat_id: string; source: string; message: string }; Returns: undefined; }; is_list_accessible: { @@ -7761,7 +7798,7 @@ export type Database = { Returns: boolean; }; is_member_invited: { - Args: { _org_id: string; _user_id: string }; + Args: { _user_id: string; _org_id: string }; Returns: boolean; }; is_nova_challenge_manager: { @@ -7773,7 +7810,7 @@ export type Database = { Returns: boolean; }; is_nova_user_email_in_team: { - Args: { _team_id: string; _user_email: string }; + Args: { _user_email: string; _team_id: string }; Returns: boolean; }; is_nova_user_id_in_team: { @@ -7781,7 +7818,7 @@ export type Database = { Returns: boolean; }; is_org_member: { - Args: { _user_id: string; _org_id: string }; + Args: { _org_id: string; _user_id: string }; Returns: boolean; }; is_project_member: { @@ -7793,7 +7830,7 @@ export type Database = { Returns: boolean; }; is_task_board_member: { - Args: { _user_id: string; _board_id: string }; + Args: { _board_id: string; _user_id: string }; Returns: boolean; }; is_user_task_in_board: { @@ -7805,7 +7842,7 @@ export type Database = { Returns: Json; }; nova_get_challenge_with_user_stats: { - Args: { challenge_id: string; user_id: string }; + Args: { user_id: string; challenge_id: string }; Returns: Json; }; nova_get_user_daily_sessions: { @@ -7825,50 +7862,50 @@ export type Database = { Returns: number; }; revoke_user_session: { - Args: { session_id: string; target_user_id: string }; + Args: { target_user_id: string; session_id: string }; Returns: boolean; }; search_users: { Args: + | { search_query: string; page_number: number; page_size: number } | { - role_filter?: string; - page_size: number; - page_number: number; search_query: string; + page_number: number; + page_size: number; + role_filter?: string; enabled_filter?: boolean; - } - | { search_query: string; page_number: number; page_size: number }; + }; Returns: { - handle: string; - id: string; - display_name: string; - deleted: boolean; - avatar_url: string; + allow_challenge_management: boolean; + allow_manage_all_challenges: boolean; bio: string; created_at: string; user_id: string; enabled: boolean; - allow_challenge_management: boolean; - allow_manage_all_challenges: boolean; - allow_role_management: boolean; - email: string; - new_email: string; - birthday: string; team_name: string[]; + birthday: string; + new_email: string; + email: string; + allow_role_management: boolean; + id: string; + display_name: string; + deleted: boolean; + avatar_url: string; + handle: string; }[]; }; search_users_by_name: { Args: { - search_query: string; - result_limit?: number; min_similarity?: number; + result_limit?: number; + search_query: string; }; Returns: { - handle: string; - id: string; display_name: string; - avatar_url: string; relevance: number; + avatar_url: string; + handle: string; + id: string; }[]; }; sum_quiz_scores: { @@ -7878,7 +7915,7 @@ export type Database = { }[]; }; transactions_have_same_abs_amount: { - Args: { transaction_id_2: string; transaction_id_1: string }; + Args: { transaction_id_1: string; transaction_id_2: string }; Returns: boolean; }; transactions_have_same_amount: { @@ -7900,8 +7937,8 @@ export type Database = { validate_cross_app_token_with_session: { Args: { p_target_app: string; p_token: string }; Returns: { - user_id: string; session_data: Json; + user_id: string; }[]; }; };