diff --git a/apps/web/app/(dashboard)/home/[account]/_components/app-sidebar.tsx b/apps/web/app/(dashboard)/home/[account]/_components/app-sidebar.tsx index 652474e27..455743b29 100644 --- a/apps/web/app/(dashboard)/home/[account]/_components/app-sidebar.tsx +++ b/apps/web/app/(dashboard)/home/[account]/_components/app-sidebar.tsx @@ -17,7 +17,7 @@ import { import { Trans } from '@kit/ui/trans'; import { cn } from '@kit/ui/utils'; -import { ProfileDropdownContainer } from '~/(dashboard)/home/_components/personal-account-dropdown'; +import { ProfileAccountDropdownContainer } from '~/(dashboard)/home/_components/personal-account-dropdown'; import featureFlagsConfig from '~/config/feature-flags.config'; import pathsConfig from '~/config/paths.config'; @@ -89,7 +89,7 @@ function SidebarContainer(props: {
- diff --git a/apps/web/app/(dashboard)/home/_components/home-sidebar.tsx b/apps/web/app/(dashboard)/home/_components/home-sidebar.tsx index 77c2678dd..458e8eae0 100644 --- a/apps/web/app/(dashboard)/home/_components/home-sidebar.tsx +++ b/apps/web/app/(dashboard)/home/_components/home-sidebar.tsx @@ -5,7 +5,7 @@ import { cookies } from 'next/headers'; import { Sidebar, SidebarContent, SidebarNavigation } from '@kit/ui/sidebar'; import { HomeSidebarAccountSelector } from '~/(dashboard)/home/_components/home-sidebar-account-selector'; -import { ProfileDropdownContainer } from '~/(dashboard)/home/_components/personal-account-dropdown'; +import { ProfileAccountDropdownContainer } from '~/(dashboard)/home/_components/personal-account-dropdown'; import { loadUserWorkspace } from '~/(dashboard)/home/_lib/load-user-workspace'; import { personalAccountSidebarConfig } from '~/config/personal-account-sidebar.config'; @@ -25,7 +25,10 @@ export function HomeSidebar() {
- +
diff --git a/apps/web/app/(dashboard)/home/_components/personal-account-dropdown.tsx b/apps/web/app/(dashboard)/home/_components/personal-account-dropdown.tsx index deffa2ed4..dc1ee9f1d 100644 --- a/apps/web/app/(dashboard)/home/_components/personal-account-dropdown.tsx +++ b/apps/web/app/(dashboard)/home/_components/personal-account-dropdown.tsx @@ -5,9 +5,10 @@ import type { Session } from '@supabase/supabase-js'; import { PersonalAccountDropdown } from '@kit/accounts/personal-account-dropdown'; import { useSignOut } from '@kit/supabase/hooks/use-sign-out'; +import featuresFlagConfig from '~/config/feature-flags.config'; import pathsConfig from '~/config/paths.config'; -export function ProfileDropdownContainer(props: { +export function ProfileAccountDropdownContainer(props: { collapsed: boolean; session: Session | null; }) { @@ -19,6 +20,9 @@ export function ProfileDropdownContainer(props: { paths={{ home: pathsConfig.app.home, }} + features={{ + enableThemeToggle: featuresFlagConfig.enableThemeToggle, + }} className={'w-full'} showProfileName={!props.collapsed} session={props.session} diff --git a/apps/web/app/(marketing)/_components/site-header-account-section.tsx b/apps/web/app/(marketing)/_components/site-header-account-section.tsx index d39f04976..02abd90f0 100644 --- a/apps/web/app/(marketing)/_components/site-header-account-section.tsx +++ b/apps/web/app/(marketing)/_components/site-header-account-section.tsx @@ -13,6 +13,7 @@ import { Button } from '@kit/ui/button'; import { If } from '@kit/ui/if'; import { Trans } from '@kit/ui/trans'; +import featuresFlagConfig from '~/config/feature-flags.config'; import pathsConfig from '~/config/paths.config'; export function SiteHeaderAccountSection( @@ -38,6 +39,9 @@ function SuspendedPersonalAccountDropdown(props: { session: Session | null }) { paths={{ home: pathsConfig.app.home, }} + features={{ + enableThemeToggle: featuresFlagConfig.enableThemeToggle, + }} session={session} signOutRequested={() => signOut.mutateAsync()} /> diff --git a/apps/web/app/(marketing)/_components/site-header.tsx b/apps/web/app/(marketing)/_components/site-header.tsx index 8ea99589e..d24c8a0fb 100644 --- a/apps/web/app/(marketing)/_components/site-header.tsx +++ b/apps/web/app/(marketing)/_components/site-header.tsx @@ -1,5 +1,7 @@ import type { Session } from '@supabase/supabase-js'; +import { ModeToggle } from '@kit/ui/mode-toggle'; + import { SiteHeaderAccountSection } from '~/(marketing)/_components/site-header-account-section'; import { SiteNavigation } from '~/(marketing)/_components/site-navigation'; import { AppLogo } from '~/components/app-logo'; @@ -17,13 +19,11 @@ export function SiteHeader(props: { session?: Session | null }) {
-
+
+ +
- -
- -
diff --git a/apps/web/app/join/page.tsx b/apps/web/app/join/page.tsx index 889e62117..e47ed93d0 100644 --- a/apps/web/app/join/page.tsx +++ b/apps/web/app/join/page.tsx @@ -1,7 +1,11 @@ -import { notFound } from 'next/navigation'; +import { notFound, redirect } from 'next/navigation'; +import { Logger } from '@kit/shared/logger'; +import { requireAuth } from '@kit/supabase/require-auth'; import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client'; +import { AcceptInvitationContainer } from '@kit/team-accounts/components'; +import pathsConfig from '~/config/paths.config'; import { withI18n } from '~/lib/i18n/with-i18n'; interface Context { @@ -10,23 +14,98 @@ interface Context { }; } -export const metadata = { - title: `Join Organization`, +export const generateMetadata = () => { + return { + title: 'Join Team Account', + }; }; async function JoinTeamAccountPage({ searchParams }: Context) { const token = searchParams.invite_token; - const data = await getInviteDataFromInviteToken(token); - if (!data) { + // no token, redirect to 404 + if (!token) { notFound(); } - return <>; + const client = getSupabaseServerComponentClient(); + const session = await requireAuth(client); + + // if the user is not logged in or there is an error + // redirect to the sign up page with the invite token + // so that they will get back to this page after signing up + if (session.error ?? !session.data) { + redirect(pathsConfig.auth.signUp + '?invite_token=' + token); + } + + // the user is logged in, we can now check if the token is valid + const invitation = await getInviteDataFromInviteToken(token); + + if (!invitation) { + notFound(); + } + + // we need to verify the user isn't already in the account + const isInAccount = await isCurrentUserAlreadyInAccount( + invitation.account.id, + ); + + if (isInAccount) { + Logger.warn( + { + name: 'join-team-account', + accountId: invitation.account.id, + userId: session.data.user.id, + }, + 'User is already in the account. Redirecting to account page.', + ); + + // if the user is already in the account redirect to the home page + redirect(pathsConfig.app.home); + } + + // if the user decides to sign in with a different account + // we redirect them to the sign in page with the invite token + const signOutNext = pathsConfig.auth.signIn + '?invite_token=' + token; + + // once the user accepts the invitation, we redirect them to the account home page + const accountHome = pathsConfig.app.accountHome.replace( + '[account]', + invitation.account.slug, + ); + + return ( + + ); } export default withI18n(JoinTeamAccountPage); +/** + * Verifies that the current user is not already in the account by + * reading the document from the `accounts` table. If the user can read it + * it means they are already in the account. + * @param accountId + */ +async function isCurrentUserAlreadyInAccount(accountId: string) { + const client = getSupabaseServerComponentClient(); + + const { data } = await client + .from('accounts') + .select('id') + .eq('id', accountId) + .maybeSingle(); + + return !!data?.id; +} + async function getInviteDataFromInviteToken(token: string) { // we use an admin client to be able to read the pending membership // without having to be logged in @@ -34,7 +113,18 @@ async function getInviteDataFromInviteToken(token: string) { const { data: invitation, error } = await adminClient .from('invitations') - .select() + .select< + string, + { + id: string; + account: { + id: string; + name: string; + slug: string; + picture_url: string; + }; + } + >('id, account: account_id !inner (id, name, slug, picture_url)') .eq('invite_token', token) .single(); diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index fefb88ed7..2c54f38b7 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -40,7 +40,7 @@ function getClassName() { const theme = themeCookie ?? appConfig.theme; const dark = theme === 'dark'; - return cn('antialiased', { + return cn('min-h-screen bg-background antialiased', { dark, [sans.className]: true, }); diff --git a/apps/web/components/root-providers.tsx b/apps/web/components/root-providers.tsx index c19cd04dc..b81244144 100644 --- a/apps/web/components/root-providers.tsx +++ b/apps/web/components/root-providers.tsx @@ -2,10 +2,12 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental'; +import { ThemeProvider } from 'next-themes'; import { I18nProvider } from '@kit/i18n/provider'; import { AuthChangeListener } from '@kit/supabase/components/auth-change-listener'; +import appConfig from '~/config/app.config'; import pathsConfig from '~/config/paths.config'; import { i18nResolver } from '~/lib/i18n/i18n.resolver'; @@ -22,7 +24,14 @@ export function RootProviders({ - {children} + + {children} + diff --git a/apps/web/config/feature-flags.config.ts b/apps/web/config/feature-flags.config.ts index 43dbbcade..bc2bc2acd 100644 --- a/apps/web/config/feature-flags.config.ts +++ b/apps/web/config/feature-flags.config.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; const FeatureFlagsSchema = z.object({ - enableThemeSwitcher: z.boolean(), + enableThemeToggle: z.boolean(), enableAccountDeletion: z.boolean(), enableTeamDeletion: z.boolean(), enableTeamAccounts: z.boolean(), @@ -11,7 +11,7 @@ const FeatureFlagsSchema = z.object({ }); const featuresFlagConfig = FeatureFlagsSchema.parse({ - enableThemeSwitcher: true, + enableThemeToggle: true, enableAccountDeletion: getBoolean( process.env.NEXT_PUBLIC_ENABLE_ACCOUNT_DELETION, false, diff --git a/apps/web/public/locales/en/teams.json b/apps/web/public/locales/en/teams.json index a58b698fd..5a44ba639 100644 --- a/apps/web/public/locales/en/teams.json +++ b/apps/web/public/locales/en/teams.json @@ -121,5 +121,12 @@ "updateRoleErrorMessage": "We encountered an error updating the role of the selected member. Please try again.", "searchInvitations": "Search Invitations", "updateInvitation": "Update Invitation", - "removeInvitation": "Remove Invitation" + "removeInvitation": "Remove Invitation", + "acceptInvitation": "Accept Invitation", + "signInWithDifferentAccount": "Sign in with a different account", + "signInWithDifferentAccountDescription": "If you wish to accept the invitation with a different account, please sign out and back in with the account you wish to use.", + "acceptInvitationHeading": "Accept Invitation to join {{accountName}}", + "acceptInvitationDescription": "You have been invited to join the team {{accountName}}. If you wish to accept the invitation, please click the button below.", + "joinTeam": "Join {{accountName}}", + "joiningTeam": "Joining team..." } diff --git a/packages/features/accounts/package.json b/packages/features/accounts/package.json index f1799554e..05bbe21b1 100644 --- a/packages/features/accounts/package.json +++ b/packages/features/accounts/package.json @@ -29,6 +29,7 @@ "@radix-ui/react-icons": "^1.3.0", "@tanstack/react-query": "5.28.6", "lucide-react": "^0.363.0", + "next-themes": "0.3.0", "react-hook-form": "^7.51.2", "react-i18next": "^14.1.0", "sonner": "^1.4.41", diff --git a/packages/features/accounts/src/components/personal-account-dropdown.tsx b/packages/features/accounts/src/components/personal-account-dropdown.tsx index 465ac7bb6..e0cc39f3c 100644 --- a/packages/features/accounts/src/components/personal-account-dropdown.tsx +++ b/packages/features/accounts/src/components/personal-account-dropdown.tsx @@ -22,6 +22,7 @@ import { DropdownMenuTrigger, } from '@kit/ui/dropdown-menu'; import { If } from '@kit/ui/if'; +import { SubMenuModeToggle } from '@kit/ui/mode-toggle'; import { ProfileAvatar } from '@kit/ui/profile-avatar'; import { Trans } from '@kit/ui/trans'; import { cn } from '@kit/ui/utils'; @@ -34,6 +35,7 @@ export function PersonalAccountDropdown({ signOutRequested, showProfileName, paths, + features, }: { className?: string; session: Session | null; @@ -42,6 +44,9 @@ export function PersonalAccountDropdown({ paths: { home: string; }; + features: { + enableThemeToggle: boolean; + }; }) { const { data: personalAccountData } = usePersonalAccountData(); const authUser = session?.user; @@ -156,6 +161,12 @@ export function PersonalAccountDropdown({ + + + + + + - + @@ -42,7 +40,7 @@ export function SignUpMethodsContainer(props: { - + ); } + +function getCallbackUrl(props: { + paths: { + callback: string; + appHome: string; + }; + + inviteToken?: string; +}) { + if (!isBrowser()) { + return ''; + } + + const redirectPath = props.paths.callback; + const origin = window.location.origin; + const url = new URL(redirectPath, origin); + + if (props.inviteToken) { + url.searchParams.set('invite_token', props.inviteToken); + } + + return url.href; +} diff --git a/packages/features/team-accounts/src/components/index.ts b/packages/features/team-accounts/src/components/index.ts index 69c53e978..bf908ef14 100644 --- a/packages/features/team-accounts/src/components/index.ts +++ b/packages/features/team-accounts/src/components/index.ts @@ -3,3 +3,4 @@ export * from './members/invite-members-dialog-container'; export * from './settings/team-account-danger-zone'; export * from './invitations/account-invitations-table'; export * from './settings/team-account-settings-container'; +export * from './invitations/accept-invitation-container'; diff --git a/packages/features/team-accounts/src/components/invitations/accept-invitation-container.tsx b/packages/features/team-accounts/src/components/invitations/accept-invitation-container.tsx new file mode 100644 index 000000000..ead04bbfc --- /dev/null +++ b/packages/features/team-accounts/src/components/invitations/accept-invitation-container.tsx @@ -0,0 +1,85 @@ +import Image from 'next/image'; + +import { Heading } from '@kit/ui/heading'; +import { If } from '@kit/ui/if'; +import { Separator } from '@kit/ui/separator'; +import { Trans } from '@kit/ui/trans'; + +import { acceptInvitationAction } from '../../server/actions/team-invitations-server-actions'; +import { InvitationSubmitButton } from './invitation-submit-button'; +import { SignOutInvitationButton } from './sign-out-invitation-button'; + +export function AcceptInvitationContainer(props: { + inviteToken: string; + + invitation: { + id: string; + + account: { + name: string; + id: string; + picture_url: string | null; + }; + }; + + paths: { + signOutNext: string; + accountHome: string; + }; +}) { + return ( +
+ + + + + + {(url) => ( + {`Logo`} + )} + + +
+ +
+ +
+
+ + + + + + + + + + + + + + +
+
+ ); +} diff --git a/packages/features/team-accounts/src/components/invitations/invitation-submit-button.tsx b/packages/features/team-accounts/src/components/invitations/invitation-submit-button.tsx new file mode 100644 index 000000000..e0060367e --- /dev/null +++ b/packages/features/team-accounts/src/components/invitations/invitation-submit-button.tsx @@ -0,0 +1,21 @@ +'use client'; + +import { useFormStatus } from 'react-dom'; + +import { Button } from '@kit/ui/button'; +import { Trans } from '@kit/ui/trans'; + +export function InvitationSubmitButton(props: { accountName: string }) { + const { pending } = useFormStatus(); + + return ( + + ); +} diff --git a/packages/features/team-accounts/src/components/invitations/sign-out-invitation-button.tsx b/packages/features/team-accounts/src/components/invitations/sign-out-invitation-button.tsx new file mode 100644 index 000000000..d9e1b15dc --- /dev/null +++ b/packages/features/team-accounts/src/components/invitations/sign-out-invitation-button.tsx @@ -0,0 +1,25 @@ +'use client'; + +import { useSignOut } from '@kit/supabase/hooks/use-sign-out'; +import { Button } from '@kit/ui/button'; +import { Trans } from '@kit/ui/trans'; + +export function SignOutInvitationButton( + props: React.PropsWithChildren<{ + nextPath: string; + }>, +) { + const signOut = useSignOut(); + + return ( + + ); +} diff --git a/packages/features/team-accounts/src/components/members/role-badge.tsx b/packages/features/team-accounts/src/components/members/role-badge.tsx index 2a9ee37e4..2e749fd81 100644 --- a/packages/features/team-accounts/src/components/members/role-badge.tsx +++ b/packages/features/team-accounts/src/components/members/role-badge.tsx @@ -9,8 +9,9 @@ type Role = Database['public']['Enums']['account_role']; const roleClassNameBuilder = cva('font-medium capitalize', { variants: { role: { - owner: 'bg-primary', - member: 'bg-blue-50 text-blue-500 dark:bg-blue-500/10', + owner: '', + member: + 'bg-blue-50 hover:bg-blue-50 text-blue-500 dark:bg-blue-500/10 dark:hover:bg-blue-500/10', }, }, }); diff --git a/packages/features/team-accounts/src/components/members/transfer-ownership-dialog.tsx b/packages/features/team-accounts/src/components/members/transfer-ownership-dialog.tsx index da5ae08ea..d3e76a96d 100644 --- a/packages/features/team-accounts/src/components/members/transfer-ownership-dialog.tsx +++ b/packages/features/team-accounts/src/components/members/transfer-ownership-dialog.tsx @@ -6,14 +6,16 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert'; -import { Button } from '@kit/ui/button'; import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from '@kit/ui/dialog'; + AlertDialog, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@kit/ui/alert-dialog'; +import { Button } from '@kit/ui/button'; import { Form, FormControl, @@ -38,17 +40,17 @@ export const TransferOwnershipDialog: React.FC<{ targetDisplayName: string; }> = ({ isOpen, setIsOpen, targetDisplayName, accountId, userId }) => { return ( - - - - + + + + - + - + - - + + - - + + ); }; @@ -100,7 +102,7 @@ function TransferOrganizationOwnershipForm({ return (
@@ -117,10 +119,6 @@ function TransferOrganizationOwnershipForm({ />

-

- -

- { @@ -144,19 +142,31 @@ function TransferOrganizationOwnershipForm({ }} /> - + } + > + + + + ); diff --git a/packages/features/team-accounts/src/components/members/update-member-role-dialog.tsx b/packages/features/team-accounts/src/components/members/update-member-role-dialog.tsx index f29ff4bd7..5e2387f95 100644 --- a/packages/features/team-accounts/src/components/members/update-member-role-dialog.tsx +++ b/packages/features/team-accounts/src/components/members/update-member-role-dialog.tsx @@ -124,7 +124,7 @@ function UpdateMemberForm({ render={({ field }) => { return ( - {t('memberRole')} + {t('roleLabel')} ) { - const { data, error } = await client.auth.getUser(); +export async function acceptInvitationAction(data: FormData) { + const client = getSupabaseServerActionClient(); - if (error ?? !data.user) { + const { inviteToken, nextPath } = AcceptInvitationSchema.parse( + Object.fromEntries(data), + ); + + const { user } = await assertSession(client); + + const service = new AccountInvitationsService(client); + + await service.acceptInvitationToTeam({ + adminClient: getSupabaseServerActionClient({ admin: true }), + inviteToken, + userId: user.id, + }); + + return redirect(nextPath); +} + +async function assertSession(client: SupabaseClient) { + const { error, data } = await requireAuth(client); + + if (error) { throw new Error(`Authentication required`); } + + return data; } diff --git a/packages/features/team-accounts/src/server/services/account-invitations.service.ts b/packages/features/team-accounts/src/server/services/account-invitations.service.ts index 738ec9503..d28f9ff6d 100644 --- a/packages/features/team-accounts/src/server/services/account-invitations.service.ts +++ b/packages/features/team-accounts/src/server/services/account-invitations.service.ts @@ -6,6 +6,7 @@ import { z } from 'zod'; import { Mailer } from '@kit/mailers'; import { Logger } from '@kit/shared/logger'; import { Database } from '@kit/supabase/database'; +import { requireAuth } from '@kit/supabase/require-auth'; import { DeleteInvitationSchema } from '../../schema/delete-invitation.schema'; import { InviteMembersSchema } from '../../schema/invite-members.schema'; @@ -206,8 +207,28 @@ export class AccountInvitationsService { ); } + /** + * Accepts an invitation to join a team. + */ + async acceptInvitationToTeam(params: { + userId: string; + inviteToken: string; + adminClient: SupabaseClient; + }) { + const { error, data } = await params.adminClient.rpc('accept_invitation', { + token: params.inviteToken, + user_id: params.userId, + }); + + if (error) { + throw error; + } + + return data; + } + private async getUser() { - const { data, error } = await this.client.auth.getUser(); + const { data, error } = await requireAuth(this.client); if (error ?? !data) { throw new Error('Authentication required'); @@ -217,6 +238,6 @@ export class AccountInvitationsService { } private getInvitationLink(token: string) { - return new URL(env.invitePath, env.siteURL).href + `?token=${token}`; + return new URL(env.siteURL, env.siteURL).href + `?invite_token=${token}`; } } diff --git a/packages/supabase/src/database.types.ts b/packages/supabase/src/database.types.ts index fb0a9b170..d87daea35 100644 --- a/packages/supabase/src/database.types.ts +++ b/packages/supabase/src/database.types.ts @@ -4,965 +4,980 @@ export type Json = | boolean | null | { [key: string]: Json | undefined } - | Json[]; + | Json[] export type Database = { graphql_public: { Tables: { - [_ in never]: never; - }; + [_ in never]: never + } Views: { - [_ in never]: never; - }; + [_ in never]: never + } Functions: { graphql: { Args: { - operationName?: string; - query?: string; - variables?: Json; - extensions?: Json; - }; - Returns: Json; - }; - }; + operationName?: string + query?: string + variables?: Json + extensions?: Json + } + Returns: Json + } + } Enums: { - [_ in never]: never; - }; + [_ in never]: never + } CompositeTypes: { - [_ in never]: never; - }; - }; + [_ in never]: never + } + } public: { Tables: { account_roles: { Row: { - account_id: string; - id: number; - role: Database['public']['Enums']['account_role']; - }; + account_id: string + id: number + role: Database["public"]["Enums"]["account_role"] + } Insert: { - account_id: string; - id?: number; - role: Database['public']['Enums']['account_role']; - }; + account_id: string + id?: number + role: Database["public"]["Enums"]["account_role"] + } Update: { - account_id?: string; - id?: number; - role?: Database['public']['Enums']['account_role']; - }; + account_id?: string + id?: number + role?: Database["public"]["Enums"]["account_role"] + } Relationships: [ { - foreignKeyName: 'account_roles_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'accounts'; - referencedColumns: ['id']; + foreignKeyName: "account_roles_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "accounts" + referencedColumns: ["id"] }, { - foreignKeyName: 'account_roles_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_account_workspace'; - referencedColumns: ['id']; + foreignKeyName: "account_roles_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_account_workspace" + referencedColumns: ["id"] }, { - foreignKeyName: 'account_roles_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_accounts'; - referencedColumns: ['id']; + foreignKeyName: "account_roles_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_accounts" + referencedColumns: ["id"] }, - ]; - }; + ] + } accounts: { Row: { - created_at: string | null; - created_by: string | null; - email: string | null; - id: string; - is_personal_account: boolean; - name: string; - picture_url: string | null; - primary_owner_user_id: string; - slug: string | null; - updated_at: string | null; - updated_by: string | null; - }; + created_at: string | null + created_by: string | null + email: string | null + id: string + is_personal_account: boolean + name: string + picture_url: string | null + primary_owner_user_id: string + slug: string | null + updated_at: string | null + updated_by: string | null + } Insert: { - created_at?: string | null; - created_by?: string | null; - email?: string | null; - id?: string; - is_personal_account?: boolean; - name: string; - picture_url?: string | null; - primary_owner_user_id?: string; - slug?: string | null; - updated_at?: string | null; - updated_by?: string | null; - }; + created_at?: string | null + created_by?: string | null + email?: string | null + id?: string + is_personal_account?: boolean + name: string + picture_url?: string | null + primary_owner_user_id?: string + slug?: string | null + updated_at?: string | null + updated_by?: string | null + } Update: { - created_at?: string | null; - created_by?: string | null; - email?: string | null; - id?: string; - is_personal_account?: boolean; - name?: string; - picture_url?: string | null; - primary_owner_user_id?: string; - slug?: string | null; - updated_at?: string | null; - updated_by?: string | null; - }; + created_at?: string | null + created_by?: string | null + email?: string | null + id?: string + is_personal_account?: boolean + name?: string + picture_url?: string | null + primary_owner_user_id?: string + slug?: string | null + updated_at?: string | null + updated_by?: string | null + } Relationships: [ { - foreignKeyName: 'accounts_created_by_fkey'; - columns: ['created_by']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; + foreignKeyName: "accounts_created_by_fkey" + columns: ["created_by"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] }, { - foreignKeyName: 'accounts_primary_owner_user_id_fkey'; - columns: ['primary_owner_user_id']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; + foreignKeyName: "accounts_primary_owner_user_id_fkey" + columns: ["primary_owner_user_id"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] }, { - foreignKeyName: 'accounts_updated_by_fkey'; - columns: ['updated_by']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; + foreignKeyName: "accounts_updated_by_fkey" + columns: ["updated_by"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] }, - ]; - }; + ] + } accounts_memberships: { Row: { - account_id: string; - account_role: Database['public']['Enums']['account_role']; - created_at: string; - created_by: string | null; - updated_at: string; - updated_by: string | null; - user_id: string; - }; + account_id: string + account_role: Database["public"]["Enums"]["account_role"] + created_at: string + created_by: string | null + updated_at: string + updated_by: string | null + user_id: string + } Insert: { - account_id: string; - account_role: Database['public']['Enums']['account_role']; - created_at?: string; - created_by?: string | null; - updated_at?: string; - updated_by?: string | null; - user_id: string; - }; + account_id: string + account_role: Database["public"]["Enums"]["account_role"] + created_at?: string + created_by?: string | null + updated_at?: string + updated_by?: string | null + user_id: string + } Update: { - account_id?: string; - account_role?: Database['public']['Enums']['account_role']; - created_at?: string; - created_by?: string | null; - updated_at?: string; - updated_by?: string | null; - user_id?: string; - }; + account_id?: string + account_role?: Database["public"]["Enums"]["account_role"] + created_at?: string + created_by?: string | null + updated_at?: string + updated_by?: string | null + user_id?: string + } Relationships: [ { - foreignKeyName: 'accounts_memberships_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'accounts'; - referencedColumns: ['id']; + foreignKeyName: "accounts_memberships_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "accounts" + referencedColumns: ["id"] }, { - foreignKeyName: 'accounts_memberships_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_account_workspace'; - referencedColumns: ['id']; + foreignKeyName: "accounts_memberships_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_account_workspace" + referencedColumns: ["id"] }, { - foreignKeyName: 'accounts_memberships_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_accounts'; - referencedColumns: ['id']; + foreignKeyName: "accounts_memberships_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_accounts" + referencedColumns: ["id"] }, { - foreignKeyName: 'accounts_memberships_created_by_fkey'; - columns: ['created_by']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; + foreignKeyName: "accounts_memberships_created_by_fkey" + columns: ["created_by"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] }, { - foreignKeyName: 'accounts_memberships_updated_by_fkey'; - columns: ['updated_by']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; + foreignKeyName: "accounts_memberships_updated_by_fkey" + columns: ["updated_by"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] }, { - foreignKeyName: 'accounts_memberships_user_id_fkey'; - columns: ['user_id']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; + foreignKeyName: "accounts_memberships_user_id_fkey" + columns: ["user_id"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] }, - ]; - }; + ] + } billing_customers: { Row: { - account_id: string; - customer_id: string; - email: string | null; - id: number; - provider: Database['public']['Enums']['billing_provider']; - }; + account_id: string + customer_id: string + email: string | null + id: number + provider: Database["public"]["Enums"]["billing_provider"] + } Insert: { - account_id: string; - customer_id: string; - email?: string | null; - id?: number; - provider: Database['public']['Enums']['billing_provider']; - }; + account_id: string + customer_id: string + email?: string | null + id?: number + provider: Database["public"]["Enums"]["billing_provider"] + } Update: { - account_id?: string; - customer_id?: string; - email?: string | null; - id?: number; - provider?: Database['public']['Enums']['billing_provider']; - }; + account_id?: string + customer_id?: string + email?: string | null + id?: number + provider?: Database["public"]["Enums"]["billing_provider"] + } Relationships: [ { - foreignKeyName: 'billing_customers_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'accounts'; - referencedColumns: ['id']; + foreignKeyName: "billing_customers_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "accounts" + referencedColumns: ["id"] }, { - foreignKeyName: 'billing_customers_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_account_workspace'; - referencedColumns: ['id']; + foreignKeyName: "billing_customers_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_account_workspace" + referencedColumns: ["id"] }, { - foreignKeyName: 'billing_customers_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_accounts'; - referencedColumns: ['id']; + foreignKeyName: "billing_customers_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_accounts" + referencedColumns: ["id"] }, - ]; - }; + ] + } config: { Row: { - billing_provider: Database['public']['Enums']['billing_provider']; - enable_account_billing: boolean; - enable_organization_accounts: boolean; - enable_organization_billing: boolean; - }; + billing_provider: Database["public"]["Enums"]["billing_provider"] + enable_account_billing: boolean + enable_organization_accounts: boolean + enable_organization_billing: boolean + } Insert: { - billing_provider?: Database['public']['Enums']['billing_provider']; - enable_account_billing?: boolean; - enable_organization_accounts?: boolean; - enable_organization_billing?: boolean; - }; + billing_provider?: Database["public"]["Enums"]["billing_provider"] + enable_account_billing?: boolean + enable_organization_accounts?: boolean + enable_organization_billing?: boolean + } Update: { - billing_provider?: Database['public']['Enums']['billing_provider']; - enable_account_billing?: boolean; - enable_organization_accounts?: boolean; - enable_organization_billing?: boolean; - }; - Relationships: []; - }; + billing_provider?: Database["public"]["Enums"]["billing_provider"] + enable_account_billing?: boolean + enable_organization_accounts?: boolean + enable_organization_billing?: boolean + } + Relationships: [] + } invitations: { Row: { - account_id: string; - created_at: string; - email: string; - expires_at: string; - id: number; - invite_token: string; - invited_by: string; - role: Database['public']['Enums']['account_role']; - updated_at: string; - }; + account_id: string + created_at: string + email: string + expires_at: string + id: number + invite_token: string + invited_by: string + role: Database["public"]["Enums"]["account_role"] + updated_at: string + } Insert: { - account_id: string; - created_at?: string; - email: string; - expires_at?: string; - id?: number; - invite_token: string; - invited_by: string; - role: Database['public']['Enums']['account_role']; - updated_at?: string; - }; + account_id: string + created_at?: string + email: string + expires_at?: string + id?: number + invite_token: string + invited_by: string + role: Database["public"]["Enums"]["account_role"] + updated_at?: string + } Update: { - account_id?: string; - created_at?: string; - email?: string; - expires_at?: string; - id?: number; - invite_token?: string; - invited_by?: string; - role?: Database['public']['Enums']['account_role']; - updated_at?: string; - }; + account_id?: string + created_at?: string + email?: string + expires_at?: string + id?: number + invite_token?: string + invited_by?: string + role?: Database["public"]["Enums"]["account_role"] + updated_at?: string + } Relationships: [ { - foreignKeyName: 'invitations_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'accounts'; - referencedColumns: ['id']; + foreignKeyName: "invitations_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "accounts" + referencedColumns: ["id"] }, { - foreignKeyName: 'invitations_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_account_workspace'; - referencedColumns: ['id']; + foreignKeyName: "invitations_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_account_workspace" + referencedColumns: ["id"] }, { - foreignKeyName: 'invitations_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_accounts'; - referencedColumns: ['id']; + foreignKeyName: "invitations_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_accounts" + referencedColumns: ["id"] }, { - foreignKeyName: 'invitations_invited_by_fkey'; - columns: ['invited_by']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; + foreignKeyName: "invitations_invited_by_fkey" + columns: ["invited_by"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] }, - ]; - }; + ] + } role_permissions: { Row: { - id: number; - permission: Database['public']['Enums']['app_permissions']; - role: Database['public']['Enums']['account_role']; - }; + id: number + permission: Database["public"]["Enums"]["app_permissions"] + role: Database["public"]["Enums"]["account_role"] + } Insert: { - id?: number; - permission: Database['public']['Enums']['app_permissions']; - role: Database['public']['Enums']['account_role']; - }; + id?: number + permission: Database["public"]["Enums"]["app_permissions"] + role: Database["public"]["Enums"]["account_role"] + } Update: { - id?: number; - permission?: Database['public']['Enums']['app_permissions']; - role?: Database['public']['Enums']['account_role']; - }; - Relationships: []; - }; + id?: number + permission?: Database["public"]["Enums"]["app_permissions"] + role?: Database["public"]["Enums"]["account_role"] + } + Relationships: [] + } subscriptions: { Row: { - account_id: string; - active: boolean; - billing_customer_id: number; - billing_provider: Database['public']['Enums']['billing_provider']; - cancel_at_period_end: boolean; - created_at: string; - currency: string; - id: string; - interval: string; - interval_count: number; - period_ends_at: string | null; - period_starts_at: string | null; - price_amount: number | null; - product_id: string; - status: Database['public']['Enums']['subscription_status']; - trial_ends_at: string | null; - trial_starts_at: string | null; - updated_at: string; - variant_id: string; - }; + account_id: string + active: boolean + billing_customer_id: number + billing_provider: Database["public"]["Enums"]["billing_provider"] + cancel_at_period_end: boolean + created_at: string + currency: string + id: string + interval: string + interval_count: number + period_ends_at: string | null + period_starts_at: string | null + price_amount: number | null + product_id: string + status: Database["public"]["Enums"]["subscription_status"] + trial_ends_at: string | null + trial_starts_at: string | null + updated_at: string + variant_id: string + } Insert: { - account_id: string; - active: boolean; - billing_customer_id: number; - billing_provider: Database['public']['Enums']['billing_provider']; - cancel_at_period_end: boolean; - created_at?: string; - currency: string; - id: string; - interval: string; - interval_count: number; - period_ends_at?: string | null; - period_starts_at?: string | null; - price_amount?: number | null; - product_id: string; - status: Database['public']['Enums']['subscription_status']; - trial_ends_at?: string | null; - trial_starts_at?: string | null; - updated_at?: string; - variant_id: string; - }; + account_id: string + active: boolean + billing_customer_id: number + billing_provider: Database["public"]["Enums"]["billing_provider"] + cancel_at_period_end: boolean + created_at?: string + currency: string + id: string + interval: string + interval_count: number + period_ends_at?: string | null + period_starts_at?: string | null + price_amount?: number | null + product_id: string + status: Database["public"]["Enums"]["subscription_status"] + trial_ends_at?: string | null + trial_starts_at?: string | null + updated_at?: string + variant_id: string + } Update: { - account_id?: string; - active?: boolean; - billing_customer_id?: number; - billing_provider?: Database['public']['Enums']['billing_provider']; - cancel_at_period_end?: boolean; - created_at?: string; - currency?: string; - id?: string; - interval?: string; - interval_count?: number; - period_ends_at?: string | null; - period_starts_at?: string | null; - price_amount?: number | null; - product_id?: string; - status?: Database['public']['Enums']['subscription_status']; - trial_ends_at?: string | null; - trial_starts_at?: string | null; - updated_at?: string; - variant_id?: string; - }; + account_id?: string + active?: boolean + billing_customer_id?: number + billing_provider?: Database["public"]["Enums"]["billing_provider"] + cancel_at_period_end?: boolean + created_at?: string + currency?: string + id?: string + interval?: string + interval_count?: number + period_ends_at?: string | null + period_starts_at?: string | null + price_amount?: number | null + product_id?: string + status?: Database["public"]["Enums"]["subscription_status"] + trial_ends_at?: string | null + trial_starts_at?: string | null + updated_at?: string + variant_id?: string + } Relationships: [ { - foreignKeyName: 'subscriptions_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'accounts'; - referencedColumns: ['id']; + foreignKeyName: "subscriptions_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "accounts" + referencedColumns: ["id"] }, { - foreignKeyName: 'subscriptions_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_account_workspace'; - referencedColumns: ['id']; + foreignKeyName: "subscriptions_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_account_workspace" + referencedColumns: ["id"] }, { - foreignKeyName: 'subscriptions_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_accounts'; - referencedColumns: ['id']; + foreignKeyName: "subscriptions_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_accounts" + referencedColumns: ["id"] }, { - foreignKeyName: 'subscriptions_billing_customer_id_fkey'; - columns: ['billing_customer_id']; - isOneToOne: false; - referencedRelation: 'billing_customers'; - referencedColumns: ['id']; + foreignKeyName: "subscriptions_billing_customer_id_fkey" + columns: ["billing_customer_id"] + isOneToOne: false + referencedRelation: "billing_customers" + referencedColumns: ["id"] }, - ]; - }; - }; + ] + } + } Views: { user_account_workspace: { Row: { - id: string | null; - name: string | null; - picture_url: string | null; + id: string | null + name: string | null + picture_url: string | null subscription_status: - | Database['public']['Enums']['subscription_status'] - | null; - }; - Relationships: []; - }; + | Database["public"]["Enums"]["subscription_status"] + | null + } + Relationships: [] + } user_accounts: { Row: { - id: string | null; - name: string | null; - picture_url: string | null; - role: Database['public']['Enums']['account_role'] | null; - slug: string | null; - }; - Relationships: []; - }; - }; + id: string | null + name: string | null + picture_url: string | null + role: Database["public"]["Enums"]["account_role"] | null + slug: string | null + } + Relationships: [] + } + } Functions: { + accept_invitation: + | { + Args: { + invite_token: string + } + Returns: undefined + } + | { + Args: { + token: string + user_id: string + } + Returns: undefined + } add_invitations_to_account: { Args: { - account_slug: string; - invitations: unknown[]; - }; - Returns: Database['public']['Tables']['invitations']['Row'][]; - }; + account_slug: string + invitations: unknown[] + } + Returns: Database["public"]["Tables"]["invitations"]["Row"][] + } add_subscription: { Args: { - account_id: string; - subscription_id: string; - active: boolean; - status: Database['public']['Enums']['subscription_status']; - billing_provider: Database['public']['Enums']['billing_provider']; - product_id: string; - variant_id: string; - price_amount: number; - cancel_at_period_end: boolean; - currency: string; - interval: string; - interval_count: number; - period_starts_at: string; - period_ends_at: string; - trial_starts_at: string; - trial_ends_at: string; - customer_id: string; - }; + account_id: string + subscription_id: string + active: boolean + status: Database["public"]["Enums"]["subscription_status"] + billing_provider: Database["public"]["Enums"]["billing_provider"] + product_id: string + variant_id: string + price_amount: number + cancel_at_period_end: boolean + currency: string + interval: string + interval_count: number + period_starts_at: string + period_ends_at: string + trial_starts_at: string + trial_ends_at: string + customer_id: string + } Returns: { - account_id: string; - active: boolean; - billing_customer_id: number; - billing_provider: Database['public']['Enums']['billing_provider']; - cancel_at_period_end: boolean; - created_at: string; - currency: string; - id: string; - interval: string; - interval_count: number; - period_ends_at: string | null; - period_starts_at: string | null; - price_amount: number | null; - product_id: string; - status: Database['public']['Enums']['subscription_status']; - trial_ends_at: string | null; - trial_starts_at: string | null; - updated_at: string; - variant_id: string; - }; - }; + account_id: string + active: boolean + billing_customer_id: number + billing_provider: Database["public"]["Enums"]["billing_provider"] + cancel_at_period_end: boolean + created_at: string + currency: string + id: string + interval: string + interval_count: number + period_ends_at: string | null + period_starts_at: string | null + price_amount: number | null + product_id: string + status: Database["public"]["Enums"]["subscription_status"] + trial_ends_at: string | null + trial_starts_at: string | null + updated_at: string + variant_id: string + } + } create_account: { Args: { - account_name: string; - }; + account_name: string + } Returns: { - created_at: string | null; - created_by: string | null; - email: string | null; - id: string; - is_personal_account: boolean; - name: string; - picture_url: string | null; - primary_owner_user_id: string; - slug: string | null; - updated_at: string | null; - updated_by: string | null; - }; - }; + created_at: string | null + created_by: string | null + email: string | null + id: string + is_personal_account: boolean + name: string + picture_url: string | null + primary_owner_user_id: string + slug: string | null + updated_at: string | null + updated_by: string | null + } + } create_invitation: { Args: { - account_id: string; - email: string; - role: Database['public']['Enums']['account_role']; - }; + account_id: string + email: string + role: Database["public"]["Enums"]["account_role"] + } Returns: { - account_id: string; - created_at: string; - email: string; - expires_at: string; - id: number; - invite_token: string; - invited_by: string; - role: Database['public']['Enums']['account_role']; - updated_at: string; - }; - }; + account_id: string + created_at: string + email: string + expires_at: string + id: number + invite_token: string + invited_by: string + role: Database["public"]["Enums"]["account_role"] + updated_at: string + } + } get_account_invitations: { Args: { - account_slug: string; - }; + account_slug: string + } Returns: { - id: number; - email: string; - account_id: string; - invited_by: string; - role: Database['public']['Enums']['account_role']; - created_at: string; - updated_at: string; - inviter_name: string; - inviter_email: string; - }[]; - }; + id: number + email: string + account_id: string + invited_by: string + role: Database["public"]["Enums"]["account_role"] + created_at: string + updated_at: string + inviter_name: string + inviter_email: string + }[] + } get_account_members: { Args: { - account_slug: string; - }; + account_slug: string + } Returns: { - id: string; - user_id: string; - account_id: string; - role: Database['public']['Enums']['account_role']; - primary_owner_user_id: string; - name: string; - email: string; - picture_url: string; - created_at: string; - updated_at: string; - }[]; - }; + id: string + user_id: string + account_id: string + role: Database["public"]["Enums"]["account_role"] + primary_owner_user_id: string + name: string + email: string + picture_url: string + created_at: string + updated_at: string + }[] + } get_config: { - Args: Record; - Returns: Json; - }; + Args: Record + Returns: Json + } get_user_accounts: { - Args: Record; + Args: Record Returns: { - created_at: string | null; - created_by: string | null; - email: string | null; - id: string; - is_personal_account: boolean; - name: string; - picture_url: string | null; - primary_owner_user_id: string; - slug: string | null; - updated_at: string | null; - updated_by: string | null; - }[]; - }; + created_at: string | null + created_by: string | null + email: string | null + id: string + is_personal_account: boolean + name: string + picture_url: string | null + primary_owner_user_id: string + slug: string | null + updated_at: string | null + updated_by: string | null + }[] + } has_permission: { Args: { - user_id: string; - account_id: string; - permission_name: Database['public']['Enums']['app_permissions']; - }; - Returns: boolean; - }; + user_id: string + account_id: string + permission_name: Database["public"]["Enums"]["app_permissions"] + } + Returns: boolean + } has_role_on_account: { Args: { - account_id: string; - account_role?: Database['public']['Enums']['account_role']; - }; - Returns: boolean; - }; + account_id: string + account_role?: Database["public"]["Enums"]["account_role"] + } + Returns: boolean + } is_account_owner: { Args: { - account_id: string; - }; - Returns: boolean; - }; + account_id: string + } + Returns: boolean + } is_set: { Args: { - field_name: string; - }; - Returns: boolean; - }; + field_name: string + } + Returns: boolean + } is_team_member: { Args: { - account_id: string; - user_id: string; - }; - Returns: boolean; - }; + account_id: string + user_id: string + } + Returns: boolean + } organization_account_workspace: { Args: { - account_slug: string; - }; + account_slug: string + } Returns: { - id: string; - name: string; - picture_url: string; - slug: string; - role: Database['public']['Enums']['account_role']; - primary_owner_user_id: string; - subscription_status: Database['public']['Enums']['subscription_status']; - permissions: Database['public']['Enums']['app_permissions'][]; - }[]; - }; + id: string + name: string + picture_url: string + slug: string + role: Database["public"]["Enums"]["account_role"] + primary_owner_user_id: string + subscription_status: Database["public"]["Enums"]["subscription_status"] + permissions: Database["public"]["Enums"]["app_permissions"][] + }[] + } unaccent: { Args: { - '': string; - }; - Returns: string; - }; + "": string + } + Returns: string + } unaccent_init: { Args: { - '': unknown; - }; - Returns: unknown; - }; - }; + "": unknown + } + Returns: unknown + } + } Enums: { - account_role: 'owner' | 'member'; + account_role: "owner" | "member" app_permissions: - | 'roles.manage' - | 'billing.manage' - | 'settings.manage' - | 'members.manage' - | 'invites.manage'; - billing_provider: 'stripe' | 'lemon-squeezy' | 'paddle'; + | "roles.manage" + | "billing.manage" + | "settings.manage" + | "members.manage" + | "invites.manage" + billing_provider: "stripe" | "lemon-squeezy" | "paddle" subscription_status: - | 'active' - | 'trialing' - | 'past_due' - | 'canceled' - | 'unpaid' - | 'incomplete' - | 'incomplete_expired' - | 'paused'; - }; + | "active" + | "trialing" + | "past_due" + | "canceled" + | "unpaid" + | "incomplete" + | "incomplete_expired" + | "paused" + } CompositeTypes: { - [_ in never]: never; - }; - }; + [_ in never]: never + } + } storage: { Tables: { buckets: { Row: { - allowed_mime_types: string[] | null; - avif_autodetection: boolean | null; - created_at: string | null; - file_size_limit: number | null; - id: string; - name: string; - owner: string | null; - owner_id: string | null; - public: boolean | null; - updated_at: string | null; - }; + allowed_mime_types: string[] | null + avif_autodetection: boolean | null + created_at: string | null + file_size_limit: number | null + id: string + name: string + owner: string | null + owner_id: string | null + public: boolean | null + updated_at: string | null + } Insert: { - allowed_mime_types?: string[] | null; - avif_autodetection?: boolean | null; - created_at?: string | null; - file_size_limit?: number | null; - id: string; - name: string; - owner?: string | null; - owner_id?: string | null; - public?: boolean | null; - updated_at?: string | null; - }; + allowed_mime_types?: string[] | null + avif_autodetection?: boolean | null + created_at?: string | null + file_size_limit?: number | null + id: string + name: string + owner?: string | null + owner_id?: string | null + public?: boolean | null + updated_at?: string | null + } Update: { - allowed_mime_types?: string[] | null; - avif_autodetection?: boolean | null; - created_at?: string | null; - file_size_limit?: number | null; - id?: string; - name?: string; - owner?: string | null; - owner_id?: string | null; - public?: boolean | null; - updated_at?: string | null; - }; - Relationships: []; - }; + allowed_mime_types?: string[] | null + avif_autodetection?: boolean | null + created_at?: string | null + file_size_limit?: number | null + id?: string + name?: string + owner?: string | null + owner_id?: string | null + public?: boolean | null + updated_at?: string | null + } + Relationships: [] + } migrations: { Row: { - executed_at: string | null; - hash: string; - id: number; - name: string; - }; + executed_at: string | null + hash: string + id: number + name: string + } Insert: { - executed_at?: string | null; - hash: string; - id: number; - name: string; - }; + executed_at?: string | null + hash: string + id: number + name: string + } Update: { - executed_at?: string | null; - hash?: string; - id?: number; - name?: string; - }; - Relationships: []; - }; + executed_at?: string | null + hash?: string + id?: number + name?: string + } + Relationships: [] + } objects: { Row: { - bucket_id: string | null; - created_at: string | null; - id: string; - last_accessed_at: string | null; - metadata: Json | null; - name: string | null; - owner: string | null; - owner_id: string | null; - path_tokens: string[] | null; - updated_at: string | null; - version: string | null; - }; + bucket_id: string | null + created_at: string | null + id: string + last_accessed_at: string | null + metadata: Json | null + name: string | null + owner: string | null + owner_id: string | null + path_tokens: string[] | null + updated_at: string | null + version: string | null + } Insert: { - bucket_id?: string | null; - created_at?: string | null; - id?: string; - last_accessed_at?: string | null; - metadata?: Json | null; - name?: string | null; - owner?: string | null; - owner_id?: string | null; - path_tokens?: string[] | null; - updated_at?: string | null; - version?: string | null; - }; + bucket_id?: string | null + created_at?: string | null + id?: string + last_accessed_at?: string | null + metadata?: Json | null + name?: string | null + owner?: string | null + owner_id?: string | null + path_tokens?: string[] | null + updated_at?: string | null + version?: string | null + } Update: { - bucket_id?: string | null; - created_at?: string | null; - id?: string; - last_accessed_at?: string | null; - metadata?: Json | null; - name?: string | null; - owner?: string | null; - owner_id?: string | null; - path_tokens?: string[] | null; - updated_at?: string | null; - version?: string | null; - }; + bucket_id?: string | null + created_at?: string | null + id?: string + last_accessed_at?: string | null + metadata?: Json | null + name?: string | null + owner?: string | null + owner_id?: string | null + path_tokens?: string[] | null + updated_at?: string | null + version?: string | null + } Relationships: [ { - foreignKeyName: 'objects_bucketId_fkey'; - columns: ['bucket_id']; - isOneToOne: false; - referencedRelation: 'buckets'; - referencedColumns: ['id']; + foreignKeyName: "objects_bucketId_fkey" + columns: ["bucket_id"] + isOneToOne: false + referencedRelation: "buckets" + referencedColumns: ["id"] }, - ]; - }; - }; + ] + } + } Views: { - [_ in never]: never; - }; + [_ in never]: never + } Functions: { can_insert_object: { Args: { - bucketid: string; - name: string; - owner: string; - metadata: Json; - }; - Returns: undefined; - }; + bucketid: string + name: string + owner: string + metadata: Json + } + Returns: undefined + } extension: { Args: { - name: string; - }; - Returns: string; - }; + name: string + } + Returns: string + } filename: { Args: { - name: string; - }; - Returns: string; - }; + name: string + } + Returns: string + } foldername: { Args: { - name: string; - }; - Returns: string[]; - }; + name: string + } + Returns: string[] + } get_size_by_bucket: { - Args: Record; + Args: Record Returns: { - size: number; - bucket_id: string; - }[]; - }; + size: number + bucket_id: string + }[] + } search: { Args: { - prefix: string; - bucketname: string; - limits?: number; - levels?: number; - offsets?: number; - search?: string; - sortcolumn?: string; - sortorder?: string; - }; + prefix: string + bucketname: string + limits?: number + levels?: number + offsets?: number + search?: string + sortcolumn?: string + sortorder?: string + } Returns: { - name: string; - id: string; - updated_at: string; - created_at: string; - last_accessed_at: string; - metadata: Json; - }[]; - }; - }; + name: string + id: string + updated_at: string + created_at: string + last_accessed_at: string + metadata: Json + }[] + } + } Enums: { - [_ in never]: never; - }; + [_ in never]: never + } CompositeTypes: { - [_ in never]: never; - }; - }; -}; + [_ in never]: never + } + } +} -type PublicSchema = Database[Extract]; +type PublicSchema = Database[Extract] export type Tables< PublicTableNameOrOptions extends - | keyof (PublicSchema['Tables'] & PublicSchema['Views']) + | keyof (PublicSchema["Tables"] & PublicSchema["Views"]) | { schema: keyof Database }, TableName extends PublicTableNameOrOptions extends { schema: keyof Database } - ? keyof (Database[PublicTableNameOrOptions['schema']]['Tables'] & - Database[PublicTableNameOrOptions['schema']]['Views']) + ? 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; + ? (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; + : PublicTableNameOrOptions extends keyof (PublicSchema["Tables"] & + PublicSchema["Views"]) + ? (PublicSchema["Tables"] & + PublicSchema["Views"])[PublicTableNameOrOptions] extends { + Row: infer R } ? R : never - : never; + : never export type TablesInsert< PublicTableNameOrOptions extends - | keyof PublicSchema['Tables'] + | keyof PublicSchema["Tables"] | { schema: keyof Database }, TableName extends PublicTableNameOrOptions extends { schema: keyof Database } - ? keyof Database[PublicTableNameOrOptions['schema']]['Tables'] + ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"] : never = never, > = PublicTableNameOrOptions extends { schema: keyof Database } - ? Database[PublicTableNameOrOptions['schema']]['Tables'][TableName] extends { - Insert: infer I; + ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Insert: infer I } ? I : never - : PublicTableNameOrOptions extends keyof PublicSchema['Tables'] - ? PublicSchema['Tables'][PublicTableNameOrOptions] extends { - Insert: infer I; + : PublicTableNameOrOptions extends keyof PublicSchema["Tables"] + ? PublicSchema["Tables"][PublicTableNameOrOptions] extends { + Insert: infer I } ? I : never - : never; + : never export type TablesUpdate< PublicTableNameOrOptions extends - | keyof PublicSchema['Tables'] + | keyof PublicSchema["Tables"] | { schema: keyof Database }, TableName extends PublicTableNameOrOptions extends { schema: keyof Database } - ? keyof Database[PublicTableNameOrOptions['schema']]['Tables'] + ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"] : never = never, > = PublicTableNameOrOptions extends { schema: keyof Database } - ? Database[PublicTableNameOrOptions['schema']]['Tables'][TableName] extends { - Update: infer U; + ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Update: infer U } ? U : never - : PublicTableNameOrOptions extends keyof PublicSchema['Tables'] - ? PublicSchema['Tables'][PublicTableNameOrOptions] extends { - Update: infer U; + : PublicTableNameOrOptions extends keyof PublicSchema["Tables"] + ? PublicSchema["Tables"][PublicTableNameOrOptions] extends { + Update: infer U } ? U : never - : never; + : never export type Enums< PublicEnumNameOrOptions extends - | keyof PublicSchema['Enums'] + | keyof PublicSchema["Enums"] | { schema: keyof Database }, EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database } - ? keyof Database[PublicEnumNameOrOptions['schema']]['Enums'] + ? 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; + ? Database[PublicEnumNameOrOptions["schema"]]["Enums"][EnumName] + : PublicEnumNameOrOptions extends keyof PublicSchema["Enums"] + ? PublicSchema["Enums"][PublicEnumNameOrOptions] + : never + diff --git a/packages/ui/package.json b/packages/ui/package.json index 62d8101ed..62d80bd35 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -119,7 +119,8 @@ "./auth-change-listener": "./src/makerkit/auth-change-listener.tsx", "./loading-overlay": "./src/makerkit/loading-overlay.tsx", "./profile-avatar": "./src/makerkit/profile-avatar.tsx", - "./mdx": "./src/makerkit/mdx/mdx-renderer.tsx" + "./mdx": "./src/makerkit/mdx/mdx-renderer.tsx", + "./mode-toggle": "./src/makerkit/mode-toggle.tsx" }, "typesVersions": { "*": { diff --git a/packages/ui/src/makerkit/mode-toggle.tsx b/packages/ui/src/makerkit/mode-toggle.tsx new file mode 100644 index 000000000..5f1c876a0 --- /dev/null +++ b/packages/ui/src/makerkit/mode-toggle.tsx @@ -0,0 +1,99 @@ +'use client'; + +import { useMemo } from 'react'; + +import { Check, Moon, Sun } from 'lucide-react'; +import { useTheme } from 'next-themes'; + +import { Button } from '../shadcn/button'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +} from '../shadcn/dropdown-menu'; +import { If } from './if'; +import { Trans } from './trans'; + +const MODES = ['light', 'dark', 'system']; + +export function ModeToggle() { + const { setTheme } = useTheme(); + + const Items = useMemo(() => { + return MODES.map((mode) => { + return ( + { + setTheme(mode); + }} + > + + + ); + }); + }, [setTheme]); + + return ( + + + + + {Items} + + ); +} + +export function SubMenuModeToggle() { + const { setTheme, theme, resolvedTheme } = useTheme(); + + const MenuItems = useMemo( + () => + ['light', 'dark', 'system'].map((item) => { + return ( + { + setTheme(item); + }} + > + + + + + + + ); + }), + [setTheme, theme], + ); + + return ( + + + + {resolvedTheme === 'light' ? ( + + ) : ( + + )} + + + + + + + + {MenuItems} + + ); +} diff --git a/packages/ui/src/shadcn/separator.tsx b/packages/ui/src/shadcn/separator.tsx index 4b86c1c29..4a584571d 100644 --- a/packages/ui/src/shadcn/separator.tsx +++ b/packages/ui/src/shadcn/separator.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import * as SeparatorPrimitive from '@radix-ui/react-separator'; -import { cn } from '@kit/ui/utils'; +import { cn } from '../utils'; const Separator = React.forwardRef< React.ElementRef, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 440bebd0f..c33ae3b55 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -328,6 +328,9 @@ importers: lucide-react: specifier: ^0.363.0 version: 0.363.0(react@18.2.0) + next-themes: + specifier: 0.3.0 + version: 0.3.0(react-dom@18.2.0)(react@18.2.0) react-hook-form: specifier: ^7.51.2 version: 7.51.2(react@18.2.0) @@ -8843,6 +8846,16 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /next-themes@0.3.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==} + peerDependencies: + react: ^16.8 || ^17 || ^18 + react-dom: ^16.8 || ^17 || ^18 + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: true + /next@14.1.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-wlzrsbfeSU48YQBjZhDzOwhWhGsy+uQycR8bHAOt1LY1bn3zZEcDyHQOEoN3aWzQ8LHCAJ1nqrWCc9XF2+O45Q==} engines: {node: '>=18.17.0'} diff --git a/supabase/migrations/20221215192558_schema.sql b/supabase/migrations/20221215192558_schema.sql index acd37e264..a47c6f862 100644 --- a/supabase/migrations/20221215192558_schema.sql +++ b/supabase/migrations/20221215192558_schema.sql @@ -735,6 +735,41 @@ insert has_role_on_account (account_id) and public.has_permission (auth.uid (), account_id, 'invites.manage'::app_permissions)); +-- Functions +-- Function to accept an invitation to an account +create or replace function accept_invitation(token text, user_id uuid) returns void as $$ +declare + target_account_id uuid; + target_role public.account_role; +begin + select + account_id, + role + into + target_account_id, + target_role + from + public.invitations + where + invite_token = token; + + insert into + public.accounts_memberships( + user_id, + account_id, + account_role) + values + (accept_invitation.user_id, target_account_id, target_role); + + delete from + public.invitations + where + invite_token = token; + end; +$$ language plpgsql; + +grant execute on function accept_invitation (uuid) to service_role; + /* * ------------------------------------------------------- * Section: Billing Customers