|
4 | 4 |
|
5 | 5 | import { supabase } from "./supabase"; |
6 | 6 | import { fetchAccountOwners, removeOwner } from "./supabase-accounts"; |
| 7 | +import { callOrgMembership, type SigningAccount } from "./org-membership/client"; |
7 | 8 | import type { MemberWithProfile } from "@/types/account"; |
8 | 9 |
|
9 | 10 | /** Fetch all members of an account with their user profiles. */ |
@@ -51,38 +52,51 @@ export async function fetchMembersWithProfiles( |
51 | 52 | return enriched; |
52 | 53 | } |
53 | 54 |
|
54 | | -/** Remove a member from an org (owner-only action). */ |
| 55 | +/** |
| 56 | + * Remove a member from an org (owner/admin action, or self-removal). Signed |
| 57 | + * by `account`; threads through to removeOwner, which calls the |
| 58 | + * org-membership edge function — no client-side pre-check needed, the edge |
| 59 | + * function enforces the owner/admin gate and the last-owner invariant. |
| 60 | + */ |
55 | 61 | export async function removeMember( |
| 62 | + account: SigningAccount, |
56 | 63 | accountId: string, |
57 | 64 | walletAddress: string |
58 | 65 | ): Promise<void> { |
59 | | - await removeOwner(accountId, walletAddress); |
| 66 | + await removeOwner(account, accountId, walletAddress); |
60 | 67 | } |
61 | 68 |
|
62 | | -/** Leave an org voluntarily. Blocks if sole owner. */ |
| 69 | +/** |
| 70 | + * Leave an org voluntarily. Signed by `account` (the leaving wallet is |
| 71 | + * derived server-side from the signature, never passed in the payload). The |
| 72 | + * local owner-count check is a fast client-side UX shortcut only — the |
| 73 | + * server enforces the real last-owner invariant via `delete_owner_guarded` |
| 74 | + * and returns LAST_OWNER if this check was stale. |
| 75 | + */ |
63 | 76 | export async function leaveOrg( |
64 | | - accountId: string, |
65 | | - walletAddress: string |
| 77 | + account: SigningAccount, |
| 78 | + accountId: string |
66 | 79 | ): Promise<void> { |
| 80 | + const walletAddress = account.address.toLowerCase(); |
67 | 81 | const owners = await fetchAccountOwners(accountId); |
68 | 82 | const ownerCount = owners.filter((o) => o.role === "owner").length; |
69 | 83 | const myRole = owners.find( |
70 | | - (o) => o.wallet_address === walletAddress.toLowerCase() |
| 84 | + (o) => o.wallet_address.toLowerCase() === walletAddress |
71 | 85 | )?.role; |
72 | 86 |
|
| 87 | + const lastOwnerMessage = |
| 88 | + "Du bist der einzige Inhaber. Übertrage die Inhaberschaft, bevor du die Organisation verlässt."; |
| 89 | + |
73 | 90 | if (myRole === "owner" && ownerCount <= 1) { |
74 | | - throw new Error( |
75 | | - "Du bist der einzige Inhaber. Übertrage die Inhaberschaft, bevor du die Organisation verlässt." |
76 | | - ); |
| 91 | + throw new Error(lastOwnerMessage); |
77 | 92 | } |
78 | 93 |
|
79 | | - const { error } = await supabase |
80 | | - .from("account_owners") |
81 | | - .delete() |
82 | | - .eq("account_id", accountId) |
83 | | - .eq("wallet_address", walletAddress.toLowerCase()); |
84 | | - |
85 | | - if (error) throw error; |
| 94 | + const res = await callOrgMembership(account, "leave", { accountId }); |
| 95 | + if (!res.ok) { |
| 96 | + if (res.code === "LAST_OWNER") throw new Error(lastOwnerMessage); |
| 97 | + console.error("leaveOrg error:", res.code, res.message); |
| 98 | + throw new Error(res.message || res.code || "leaveOrg failed"); |
| 99 | + } |
86 | 100 | } |
87 | 101 |
|
88 | 102 | /** Search users by name for the invite flow (excludes existing members). */ |
|
0 commit comments