@vocoweb/tenant 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,179 @@
1
+ import { createClient } from '@supabase/supabase-js';
2
+
3
+ // src/client.ts
4
+ var CURRENT_ORG_KEY = "voco_current_organization";
5
+ async function switchOrganization(organizationId) {
6
+ if (typeof window === "undefined") {
7
+ throw new Error("switchOrganization can only be called client-side");
8
+ }
9
+ localStorage.setItem(CURRENT_ORG_KEY, organizationId);
10
+ window.dispatchEvent(
11
+ new StorageEvent("storage", {
12
+ key: CURRENT_ORG_KEY,
13
+ newValue: organizationId
14
+ })
15
+ );
16
+ }
17
+ function getCurrentOrganization() {
18
+ if (typeof window === "undefined") {
19
+ return null;
20
+ }
21
+ return localStorage.getItem(CURRENT_ORG_KEY);
22
+ }
23
+ function onOrganizationChange(callback) {
24
+ if (typeof window === "undefined") {
25
+ return () => {
26
+ };
27
+ }
28
+ const handler = (e) => {
29
+ if (e.key === CURRENT_ORG_KEY) {
30
+ callback(e.newValue);
31
+ }
32
+ };
33
+ window.addEventListener("storage", handler);
34
+ return () => {
35
+ window.removeEventListener("storage", handler);
36
+ };
37
+ }
38
+ var getSupabaseClient = () => {
39
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
40
+ const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
41
+ if (!supabaseUrl || !supabaseKey) {
42
+ throw new Error("Supabase credentials not configured");
43
+ }
44
+ return createClient(supabaseUrl, supabaseKey);
45
+ };
46
+ async function createOrganization(options) {
47
+ const supabase = getSupabaseClient();
48
+ const { data, error } = await supabase.from("organizations").insert({
49
+ name: options.name,
50
+ owner_id: options.ownerId
51
+ }).select().single();
52
+ if (error) {
53
+ throw new Error(`Failed to create organization: ${error.message}`);
54
+ }
55
+ await supabase.from("organization_members").insert({
56
+ organization_id: data.id,
57
+ user_id: options.ownerId,
58
+ role: "owner"
59
+ });
60
+ return {
61
+ id: data.id,
62
+ name: data.name,
63
+ ownerId: data.owner_id,
64
+ createdAt: new Date(data.created_at)
65
+ };
66
+ }
67
+ async function inviteMember(options) {
68
+ const supabase = getSupabaseClient();
69
+ const token = crypto.randomUUID();
70
+ const expiresAt = /* @__PURE__ */ new Date();
71
+ expiresAt.setDate(expiresAt.getDate() + 7);
72
+ const { data, error } = await supabase.from("organization_invites").insert({
73
+ organization_id: options.organizationId,
74
+ email: options.email,
75
+ role: options.role,
76
+ token,
77
+ expires_at: expiresAt.toISOString()
78
+ }).select().single();
79
+ if (error) {
80
+ throw new Error(`Failed to create invite: ${error.message}`);
81
+ }
82
+ return {
83
+ id: data.id,
84
+ organizationId: data.organization_id,
85
+ email: data.email,
86
+ role: data.role,
87
+ token: data.token,
88
+ expiresAt: new Date(data.expires_at),
89
+ createdAt: new Date(data.created_at)
90
+ };
91
+ }
92
+ async function acceptInvite(options) {
93
+ const supabase = getSupabaseClient();
94
+ const { data: invite, error: inviteError } = await supabase.from("organization_invites").select("*").eq("token", options.token).single();
95
+ if (inviteError || !invite) {
96
+ throw new Error("Invalid or expired invite");
97
+ }
98
+ if (new Date(invite.expires_at) < /* @__PURE__ */ new Date()) {
99
+ throw new Error("Invite has expired");
100
+ }
101
+ const { data, error } = await supabase.from("organization_members").insert({
102
+ organization_id: invite.organization_id,
103
+ user_id: options.userId,
104
+ role: invite.role
105
+ }).select().single();
106
+ if (error) {
107
+ throw new Error(`Failed to add member: ${error.message}`);
108
+ }
109
+ await supabase.from("organization_invites").delete().eq("id", invite.id);
110
+ return {
111
+ id: data.id,
112
+ organizationId: data.organization_id,
113
+ userId: data.user_id,
114
+ role: data.role,
115
+ createdAt: new Date(data.created_at)
116
+ };
117
+ }
118
+ async function setMemberRole(options) {
119
+ const supabase = getSupabaseClient();
120
+ const { error } = await supabase.from("organization_members").update({ role: options.role }).eq("organization_id", options.organizationId).eq("user_id", options.userId);
121
+ if (error) {
122
+ throw new Error(`Failed to update role: ${error.message}`);
123
+ }
124
+ }
125
+ async function removeMember(options) {
126
+ const supabase = getSupabaseClient();
127
+ const { error } = await supabase.from("organization_members").delete().eq("organization_id", options.organizationId).eq("user_id", options.userId);
128
+ if (error) {
129
+ throw new Error(`Failed to remove member: ${error.message}`);
130
+ }
131
+ }
132
+ async function getOrganizationMembers(organizationId) {
133
+ const supabase = getSupabaseClient();
134
+ const { data, error } = await supabase.from("organization_members").select("*").eq("organization_id", organizationId);
135
+ if (error) {
136
+ throw new Error(`Failed to get members: ${error.message}`);
137
+ }
138
+ return data.map((row) => ({
139
+ id: row.id,
140
+ organizationId: row.organization_id,
141
+ userId: row.user_id,
142
+ role: row.role,
143
+ createdAt: new Date(row.created_at)
144
+ }));
145
+ }
146
+ async function getUserOrganizations(userId) {
147
+ const supabase = getSupabaseClient();
148
+ const { data, error } = await supabase.from("organization_members").select("organizations(*)").eq("user_id", userId);
149
+ if (error) {
150
+ throw new Error(`Failed to get organizations: ${error.message}`);
151
+ }
152
+ return data.map((row) => ({
153
+ id: row.organizations.id,
154
+ name: row.organizations.name,
155
+ ownerId: row.organizations.owner_id,
156
+ createdAt: new Date(row.organizations.created_at)
157
+ }));
158
+ }
159
+
160
+ // src/index.ts
161
+ var tenant = {
162
+ // Client methods
163
+ switchOrganization,
164
+ getCurrentOrganization,
165
+ onOrganizationChange,
166
+ // Server methods
167
+ createOrganization,
168
+ inviteMember,
169
+ acceptInvite,
170
+ setMemberRole,
171
+ removeMember,
172
+ getOrganizationMembers,
173
+ getUserOrganizations
174
+ };
175
+ var VERSION = "1.0.0";
176
+
177
+ export { VERSION, acceptInvite, createOrganization, getCurrentOrganization, getOrganizationMembers, getUserOrganizations, inviteMember, onOrganizationChange, removeMember, setMemberRole, switchOrganization, tenant };
178
+ //# sourceMappingURL=index.mjs.map
179
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts","../src/server.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Client-side functions for @vocoweb/tenant\n */\n\n\n\nconst CURRENT_ORG_KEY = 'voco_current_organization';\n\n/**\n * Switch active organization\n */\nexport async function switchOrganization(organizationId: string): Promise<void> {\n if (typeof window === 'undefined') {\n throw new Error('switchOrganization can only be called client-side');\n }\n\n localStorage.setItem(CURRENT_ORG_KEY, organizationId);\n\n // Trigger storage event for cross-tab synchronization\n window.dispatchEvent(\n new StorageEvent('storage', {\n key: CURRENT_ORG_KEY,\n newValue: organizationId,\n })\n );\n}\n\n/**\n * Get current active organization\n */\nexport function getCurrentOrganization(): string | null {\n if (typeof window === 'undefined') {\n return null;\n }\n\n return localStorage.getItem(CURRENT_ORG_KEY);\n}\n\n/**\n * Listen to organization changes\n */\nexport function onOrganizationChange(\n callback: (organizationId: string | null) => void\n): () => void {\n if (typeof window === 'undefined') {\n return () => { };\n }\n\n const handler = (e: StorageEvent) => {\n if (e.key === CURRENT_ORG_KEY) {\n callback(e.newValue);\n }\n };\n\n window.addEventListener('storage', handler);\n\n return () => {\n window.removeEventListener('storage', handler);\n };\n}\n","/**\n * Server-side functions for @vocoweb/tenant\n */\n\nimport { createClient } from '@supabase/supabase-js';\nimport type {\n Organization,\n OrganizationMember,\n OrganizationInvite,\n CreateOrganizationOptions,\n InviteMemberOptions,\n AcceptInviteOptions,\n SetMemberRoleOptions,\n RemoveMemberOptions,\n} from './types';\n\nconst getSupabaseClient = () => {\n const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;\n const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY;\n\n if (!supabaseUrl || !supabaseKey) {\n throw new Error('Supabase credentials not configured');\n }\n\n return createClient(supabaseUrl, supabaseKey);\n};\n\n/**\n * Create a new organization\n */\nexport async function createOrganization(\n options: CreateOrganizationOptions\n): Promise<Organization> {\n const supabase = getSupabaseClient();\n\n const { data, error } = await supabase\n .from('organizations')\n .insert({\n name: options.name,\n owner_id: options.ownerId,\n })\n .select()\n .single();\n\n if (error) {\n throw new Error(`Failed to create organization: ${error.message}`);\n }\n\n // Add owner as admin member\n await supabase.from('organization_members').insert({\n organization_id: data.id,\n user_id: options.ownerId,\n role: 'owner',\n });\n\n return {\n id: data.id,\n name: data.name,\n ownerId: data.owner_id,\n createdAt: new Date(data.created_at),\n };\n}\n\n/**\n * Invite a member to an organization\n */\nexport async function inviteMember(\n options: InviteMemberOptions\n): Promise<OrganizationInvite> {\n const supabase = getSupabaseClient();\n\n // Generate unique token\n const token = crypto.randomUUID();\n const expiresAt = new Date();\n expiresAt.setDate(expiresAt.getDate() + 7); // 7 days expiry\n\n const { data, error } = await supabase\n .from('organization_invites')\n .insert({\n organization_id: options.organizationId,\n email: options.email,\n role: options.role,\n token,\n expires_at: expiresAt.toISOString(),\n })\n .select()\n .single();\n\n if (error) {\n throw new Error(`Failed to create invite: ${error.message}`);\n }\n\n // TODO: Send email notification\n // await notify.sendEmail({\n // to: options.email,\n // template: 'organization-invite',\n // data: { organizationId: options.organizationId, token }\n // });\n\n return {\n id: data.id,\n organizationId: data.organization_id,\n email: data.email,\n role: data.role,\n token: data.token,\n expiresAt: new Date(data.expires_at),\n createdAt: new Date(data.created_at),\n };\n}\n\n/**\n * Accept an organization invite\n */\nexport async function acceptInvite(\n options: AcceptInviteOptions\n): Promise<OrganizationMember> {\n const supabase = getSupabaseClient();\n\n // Get invite\n const { data: invite, error: inviteError } = await supabase\n .from('organization_invites')\n .select('*')\n .eq('token', options.token)\n .single();\n\n if (inviteError || !invite) {\n throw new Error('Invalid or expired invite');\n }\n\n // Check expiry\n if (new Date(invite.expires_at) < new Date()) {\n throw new Error('Invite has expired');\n }\n\n // Add member\n const { data, error } = await supabase\n .from('organization_members')\n .insert({\n organization_id: invite.organization_id,\n user_id: options.userId,\n role: invite.role,\n })\n .select()\n .single();\n\n if (error) {\n throw new Error(`Failed to add member: ${error.message}`);\n }\n\n // Delete invite\n await supabase.from('organization_invites').delete().eq('id', invite.id);\n\n return {\n id: data.id,\n organizationId: data.organization_id,\n userId: data.user_id,\n role: data.role,\n createdAt: new Date(data.created_at),\n };\n}\n\n/**\n * Set member role\n */\nexport async function setMemberRole(\n options: SetMemberRoleOptions\n): Promise<void> {\n const supabase = getSupabaseClient();\n\n const { error } = await supabase\n .from('organization_members')\n .update({ role: options.role })\n .eq('organization_id', options.organizationId)\n .eq('user_id', options.userId);\n\n if (error) {\n throw new Error(`Failed to update role: ${error.message}`);\n }\n}\n\n/**\n * Remove member from organization\n */\nexport async function removeMember(\n options: RemoveMemberOptions\n): Promise<void> {\n const supabase = getSupabaseClient();\n\n const { error } = await supabase\n .from('organization_members')\n .delete()\n .eq('organization_id', options.organizationId)\n .eq('user_id', options.userId);\n\n if (error) {\n throw new Error(`Failed to remove member: ${error.message}`);\n }\n}\n\n/**\n * Get organization members\n */\nexport async function getOrganizationMembers(\n organizationId: string\n): Promise<OrganizationMember[]> {\n const supabase = getSupabaseClient();\n\n const { data, error } = await supabase\n .from('organization_members')\n .select('*')\n .eq('organization_id', organizationId);\n\n if (error) {\n throw new Error(`Failed to get members: ${error.message}`);\n }\n\n return data.map((row) => ({\n id: row.id,\n organizationId: row.organization_id,\n userId: row.user_id,\n role: row.role,\n createdAt: new Date(row.created_at),\n }));\n}\n\n/**\n * Get user's organizations\n */\nexport async function getUserOrganizations(\n userId: string\n): Promise<Organization[]> {\n const supabase = getSupabaseClient();\n\n const { data, error } = await supabase\n .from('organization_members')\n .select('organizations(*)')\n .eq('user_id', userId);\n\n if (error) {\n throw new Error(`Failed to get organizations: ${error.message}`);\n }\n\n return data.map((row: any) => ({\n id: row.organizations.id,\n name: row.organizations.name,\n ownerId: row.organizations.owner_id,\n createdAt: new Date(row.organizations.created_at),\n }));\n}\n","/**\n * @vocoweb/tenant\n * Production-ready multi-tenancy and RBAC for B2B SaaS\n */\n\n// Types\nexport * from './types';\n\n// Client-side\nexport * from './client';\n\n// Server-side\nexport * from './server';\n\n// Import for unified API\nimport * as clientTenant from './client';\nimport * as serverTenant from './server';\n\nimport type {\n Organization,\n OrganizationMember,\n OrganizationInvite,\n CreateOrganizationOptions,\n InviteMemberOptions,\n AcceptInviteOptions,\n SetMemberRoleOptions,\n RemoveMemberOptions,\n UserRole,\n} from './types';\n\n/**\n * Main tenant API\n *\n * @example\n * import { tenant } from '@vocoweb/tenant';\n *\n * // Server-side\n * const org = await tenant.createOrganization({ name: 'Acme Inc', ownerId: userId });\n * await tenant.inviteMember({ organizationId, email, role: 'member' });\n *\n * // Client-side\n * await tenant.switchOrganization(orgId);\n */\nexport const tenant = {\n // Client methods\n switchOrganization: clientTenant.switchOrganization,\n getCurrentOrganization: clientTenant.getCurrentOrganization,\n onOrganizationChange: clientTenant.onOrganizationChange,\n\n // Server methods\n createOrganization: serverTenant.createOrganization,\n inviteMember: serverTenant.inviteMember,\n acceptInvite: serverTenant.acceptInvite,\n setMemberRole: serverTenant.setMemberRole,\n removeMember: serverTenant.removeMember,\n getOrganizationMembers: serverTenant.getOrganizationMembers,\n getUserOrganizations: serverTenant.getUserOrganizations,\n};\n\n// Re-export types\nexport type {\n Organization,\n OrganizationMember,\n OrganizationInvite,\n CreateOrganizationOptions,\n InviteMemberOptions,\n AcceptInviteOptions,\n SetMemberRoleOptions,\n RemoveMemberOptions,\n UserRole,\n};\n\n// Version\nexport const VERSION = '1.0.0';\n"]}
@@ -0,0 +1,26 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { U as UserRole } from './types-BX4GZa4r.mjs';
3
+
4
+ /**
5
+ * VocoTeamSwitcher Component
6
+ * Notion-style workspace/team switcher
7
+ */
8
+ interface VocoTeamSwitcherProps {
9
+ organizations?: Array<{
10
+ id: string;
11
+ name: string;
12
+ }>;
13
+ onCreateNew?: () => void;
14
+ className?: string;
15
+ }
16
+ declare function VocoTeamSwitcher({ organizations, onCreateNew, className, }: VocoTeamSwitcherProps): react_jsx_runtime.JSX.Element;
17
+
18
+ interface VocoInviteMemberProps {
19
+ organizationId: string;
20
+ onInvite?: (email: string, role: UserRole) => Promise<void>;
21
+ defaultRole?: UserRole;
22
+ className?: string;
23
+ }
24
+ declare function VocoInviteMember({ organizationId, onInvite, defaultRole, className, }: VocoInviteMemberProps): react_jsx_runtime.JSX.Element;
25
+
26
+ export { VocoInviteMember, VocoTeamSwitcher };
@@ -0,0 +1,26 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { U as UserRole } from './types-BX4GZa4r.js';
3
+
4
+ /**
5
+ * VocoTeamSwitcher Component
6
+ * Notion-style workspace/team switcher
7
+ */
8
+ interface VocoTeamSwitcherProps {
9
+ organizations?: Array<{
10
+ id: string;
11
+ name: string;
12
+ }>;
13
+ onCreateNew?: () => void;
14
+ className?: string;
15
+ }
16
+ declare function VocoTeamSwitcher({ organizations, onCreateNew, className, }: VocoTeamSwitcherProps): react_jsx_runtime.JSX.Element;
17
+
18
+ interface VocoInviteMemberProps {
19
+ organizationId: string;
20
+ onInvite?: (email: string, role: UserRole) => Promise<void>;
21
+ defaultRole?: UserRole;
22
+ className?: string;
23
+ }
24
+ declare function VocoInviteMember({ organizationId, onInvite, defaultRole, className, }: VocoInviteMemberProps): react_jsx_runtime.JSX.Element;
25
+
26
+ export { VocoInviteMember, VocoTeamSwitcher };
package/dist/react.js ADDED
@@ -0,0 +1,221 @@
1
+ 'use strict';
2
+
3
+ var React2 = require('react');
4
+ var lucideReact = require('lucide-react');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ function _interopNamespace(e) {
8
+ if (e && e.__esModule) return e;
9
+ var n = Object.create(null);
10
+ if (e) {
11
+ Object.keys(e).forEach(function (k) {
12
+ if (k !== 'default') {
13
+ var d = Object.getOwnPropertyDescriptor(e, k);
14
+ Object.defineProperty(n, k, d.get ? d : {
15
+ enumerable: true,
16
+ get: function () { return e[k]; }
17
+ });
18
+ }
19
+ });
20
+ }
21
+ n.default = e;
22
+ return Object.freeze(n);
23
+ }
24
+
25
+ var React2__namespace = /*#__PURE__*/_interopNamespace(React2);
26
+
27
+ // src/components/VocoTeamSwitcher.tsx
28
+
29
+ // src/client.ts
30
+ var CURRENT_ORG_KEY = "voco_current_organization";
31
+ async function switchOrganization(organizationId) {
32
+ if (typeof window === "undefined") {
33
+ throw new Error("switchOrganization can only be called client-side");
34
+ }
35
+ localStorage.setItem(CURRENT_ORG_KEY, organizationId);
36
+ window.dispatchEvent(
37
+ new StorageEvent("storage", {
38
+ key: CURRENT_ORG_KEY,
39
+ newValue: organizationId
40
+ })
41
+ );
42
+ }
43
+ function getCurrentOrganization() {
44
+ if (typeof window === "undefined") {
45
+ return null;
46
+ }
47
+ return localStorage.getItem(CURRENT_ORG_KEY);
48
+ }
49
+ function VocoTeamSwitcher({
50
+ organizations = [],
51
+ onCreateNew,
52
+ className = ""
53
+ }) {
54
+ const [currentOrgId, setCurrentOrgId] = React2__namespace.useState(null);
55
+ const [isOpen, setIsOpen] = React2__namespace.useState(false);
56
+ React2__namespace.useEffect(() => {
57
+ setCurrentOrgId(getCurrentOrganization());
58
+ }, []);
59
+ const currentOrg = organizations.find((org) => org.id === currentOrgId);
60
+ const handleSwitch = async (orgId) => {
61
+ await switchOrganization(orgId);
62
+ setCurrentOrgId(orgId);
63
+ setIsOpen(false);
64
+ };
65
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative ${className}`, children: [
66
+ /* @__PURE__ */ jsxRuntime.jsxs(
67
+ "button",
68
+ {
69
+ onClick: () => setIsOpen(!isOpen),
70
+ className: "flex items-center gap-2 rounded-lg border px-3 py-2 text-sm hover:bg-gray-50",
71
+ children: [
72
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: currentOrg?.name || "Select Organization" }),
73
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronsUpDown, { className: "h-4 w-4 opacity-50" })
74
+ ]
75
+ }
76
+ ),
77
+ isOpen && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
78
+ /* @__PURE__ */ jsxRuntime.jsx(
79
+ "div",
80
+ {
81
+ className: "fixed inset-0 z-40",
82
+ onClick: () => setIsOpen(false)
83
+ }
84
+ ),
85
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute left-0 top-full z-50 mt-1 w-64 rounded-lg border bg-white p-1 shadow-lg", children: [
86
+ organizations.map((org) => /* @__PURE__ */ jsxRuntime.jsxs(
87
+ "button",
88
+ {
89
+ onClick: () => handleSwitch(org.id),
90
+ className: "flex w-full items-center gap-2 rounded px-2 py-2 text-sm hover:bg-gray-100",
91
+ children: [
92
+ /* @__PURE__ */ jsxRuntime.jsx(
93
+ lucideReact.Check,
94
+ {
95
+ className: `h-4 w-4 ${org.id === currentOrgId ? "opacity-100" : "opacity-0"}`
96
+ }
97
+ ),
98
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: org.name })
99
+ ]
100
+ },
101
+ org.id
102
+ )),
103
+ onCreateNew && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
104
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "my-1 h-px bg-gray-200" }),
105
+ /* @__PURE__ */ jsxRuntime.jsxs(
106
+ "button",
107
+ {
108
+ onClick: () => {
109
+ onCreateNew();
110
+ setIsOpen(false);
111
+ },
112
+ className: "flex w-full items-center gap-2 rounded px-2 py-2 text-sm hover:bg-gray-100",
113
+ children: [
114
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-4 w-4" }),
115
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Create Organization" })
116
+ ]
117
+ }
118
+ )
119
+ ] })
120
+ ] })
121
+ ] })
122
+ ] });
123
+ }
124
+ function VocoInviteMember({
125
+ organizationId,
126
+ onInvite,
127
+ defaultRole = "member",
128
+ className = ""
129
+ }) {
130
+ const [email, setEmail] = React2__namespace.useState("");
131
+ const [role, setRole] = React2__namespace.useState(defaultRole);
132
+ const [loading, setLoading] = React2__namespace.useState(false);
133
+ const [message, setMessage] = React2__namespace.useState("");
134
+ const handleSubmit = async (e) => {
135
+ e.preventDefault();
136
+ setLoading(true);
137
+ setMessage("");
138
+ try {
139
+ if (onInvite) {
140
+ await onInvite(email, role);
141
+ } else {
142
+ const res = await fetch("/api/organizations/invite", {
143
+ method: "POST",
144
+ headers: { "Content-Type": "application/json" },
145
+ body: JSON.stringify({ organizationId, email, role })
146
+ });
147
+ if (!res.ok) {
148
+ throw new Error("Failed to send invite");
149
+ }
150
+ }
151
+ setMessage("Invite sent successfully!");
152
+ setEmail("");
153
+ } catch (error) {
154
+ setMessage("Failed to send invite. Please try again.");
155
+ } finally {
156
+ setLoading(false);
157
+ }
158
+ };
159
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `rounded-lg border p-4 ${className}`, children: [
160
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-4 flex items-center gap-2", children: [
161
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mail, { className: "h-5 w-5" }),
162
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold", children: "Invite Team Member" })
163
+ ] }),
164
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
165
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
166
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "mb-1 block text-sm font-medium", children: "Email" }),
167
+ /* @__PURE__ */ jsxRuntime.jsx(
168
+ "input",
169
+ {
170
+ type: "email",
171
+ value: email,
172
+ onChange: (e) => setEmail(e.target.value),
173
+ placeholder: "colleague@example.com",
174
+ required: true,
175
+ className: "w-full rounded border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
176
+ }
177
+ )
178
+ ] }),
179
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
180
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "mb-1 block text-sm font-medium", children: "Role" }),
181
+ /* @__PURE__ */ jsxRuntime.jsxs(
182
+ "select",
183
+ {
184
+ value: role,
185
+ onChange: (e) => setRole(e.target.value),
186
+ className: "w-full rounded border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500",
187
+ children: [
188
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "viewer", children: "Viewer" }),
189
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "member", children: "Member" }),
190
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "admin", children: "Admin" })
191
+ ]
192
+ }
193
+ )
194
+ ] }),
195
+ /* @__PURE__ */ jsxRuntime.jsxs(
196
+ "button",
197
+ {
198
+ type: "submit",
199
+ disabled: loading,
200
+ className: "flex w-full items-center justify-center gap-2 rounded bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-50",
201
+ children: [
202
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Send, { className: "h-4 w-4" }),
203
+ loading ? "Sending..." : "Send Invite"
204
+ ]
205
+ }
206
+ ),
207
+ message && /* @__PURE__ */ jsxRuntime.jsx(
208
+ "p",
209
+ {
210
+ className: `text-sm ${message.includes("success") ? "text-green-600" : "text-red-600"}`,
211
+ children: message
212
+ }
213
+ )
214
+ ] })
215
+ ] });
216
+ }
217
+
218
+ exports.VocoInviteMember = VocoInviteMember;
219
+ exports.VocoTeamSwitcher = VocoTeamSwitcher;
220
+ //# sourceMappingURL=react.js.map
221
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts","../src/components/VocoTeamSwitcher.tsx","../src/components/VocoInviteMember.tsx"],"names":["React","jsxs","jsx","ChevronsUpDown","Fragment","Check","Plus","React2","Mail","Send"],"mappings":"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","file":"react.js","sourcesContent":["/**\n * Client-side functions for @vocoweb/tenant\n */\n\n\n\nconst CURRENT_ORG_KEY = 'voco_current_organization';\n\n/**\n * Switch active organization\n */\nexport async function switchOrganization(organizationId: string): Promise<void> {\n if (typeof window === 'undefined') {\n throw new Error('switchOrganization can only be called client-side');\n }\n\n localStorage.setItem(CURRENT_ORG_KEY, organizationId);\n\n // Trigger storage event for cross-tab synchronization\n window.dispatchEvent(\n new StorageEvent('storage', {\n key: CURRENT_ORG_KEY,\n newValue: organizationId,\n })\n );\n}\n\n/**\n * Get current active organization\n */\nexport function getCurrentOrganization(): string | null {\n if (typeof window === 'undefined') {\n return null;\n }\n\n return localStorage.getItem(CURRENT_ORG_KEY);\n}\n\n/**\n * Listen to organization changes\n */\nexport function onOrganizationChange(\n callback: (organizationId: string | null) => void\n): () => void {\n if (typeof window === 'undefined') {\n return () => { };\n }\n\n const handler = (e: StorageEvent) => {\n if (e.key === CURRENT_ORG_KEY) {\n callback(e.newValue);\n }\n };\n\n window.addEventListener('storage', handler);\n\n return () => {\n window.removeEventListener('storage', handler);\n };\n}\n","/**\n * VocoTeamSwitcher Component\n * Notion-style workspace/team switcher\n */\n\n'use client';\n\nimport * as React from 'react';\nimport { Check, ChevronsUpDown, Plus } from 'lucide-react';\nimport { switchOrganization, getCurrentOrganization } from '../client';\n\nexport interface VocoTeamSwitcherProps {\n organizations?: Array<{ id: string; name: string }>;\n onCreateNew?: () => void;\n className?: string;\n}\n\nexport function VocoTeamSwitcher({\n organizations = [],\n onCreateNew,\n className = '',\n}: VocoTeamSwitcherProps) {\n const [currentOrgId, setCurrentOrgId] = React.useState<string | null>(null);\n const [isOpen, setIsOpen] = React.useState(false);\n\n React.useEffect(() => {\n setCurrentOrgId(getCurrentOrganization());\n }, []);\n\n const currentOrg = organizations.find((org) => org.id === currentOrgId);\n\n const handleSwitch = async (orgId: string) => {\n await switchOrganization(orgId);\n setCurrentOrgId(orgId);\n setIsOpen(false);\n };\n\n return (\n <div className={`relative ${className}`}>\n <button\n onClick={() => setIsOpen(!isOpen)}\n className=\"flex items-center gap-2 rounded-lg border px-3 py-2 text-sm hover:bg-gray-50\"\n >\n <span className=\"font-medium\">\n {currentOrg?.name || 'Select Organization'}\n </span>\n <ChevronsUpDown className=\"h-4 w-4 opacity-50\" />\n </button>\n\n {isOpen && (\n <>\n <div\n className=\"fixed inset-0 z-40\"\n onClick={() => setIsOpen(false)}\n />\n <div className=\"absolute left-0 top-full z-50 mt-1 w-64 rounded-lg border bg-white p-1 shadow-lg\">\n {organizations.map((org) => (\n <button\n key={org.id}\n onClick={() => handleSwitch(org.id)}\n className=\"flex w-full items-center gap-2 rounded px-2 py-2 text-sm hover:bg-gray-100\"\n >\n <Check\n className={`h-4 w-4 ${org.id === currentOrgId ? 'opacity-100' : 'opacity-0'\n }`}\n />\n <span>{org.name}</span>\n </button>\n ))}\n {onCreateNew && (\n <>\n <div className=\"my-1 h-px bg-gray-200\" />\n <button\n onClick={() => {\n onCreateNew();\n setIsOpen(false);\n }}\n className=\"flex w-full items-center gap-2 rounded px-2 py-2 text-sm hover:bg-gray-100\"\n >\n <Plus className=\"h-4 w-4\" />\n <span>Create Organization</span>\n </button>\n </>\n )}\n </div>\n </>\n )}\n </div>\n );\n}\n","/**\n * VocoInviteMember Component\n * Pre-built UI to invite users via email\n */\n\n'use client';\n\nimport * as React from 'react';\nimport { Mail, Send } from 'lucide-react';\nimport type { UserRole } from '../types';\n\nexport interface VocoInviteMemberProps {\n organizationId: string;\n onInvite?: (email: string, role: UserRole) => Promise<void>;\n defaultRole?: UserRole;\n className?: string;\n}\n\nexport function VocoInviteMember({\n organizationId,\n onInvite,\n defaultRole = 'member',\n className = '',\n}: VocoInviteMemberProps) {\n const [email, setEmail] = React.useState('');\n const [role, setRole] = React.useState<UserRole>(defaultRole);\n const [loading, setLoading] = React.useState(false);\n const [message, setMessage] = React.useState('');\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setLoading(true);\n setMessage('');\n\n try {\n if (onInvite) {\n await onInvite(email, role);\n } else {\n // Default API call\n const res = await fetch('/api/organizations/invite', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ organizationId, email, role }),\n });\n\n if (!res.ok) {\n throw new Error('Failed to send invite');\n }\n }\n\n setMessage('Invite sent successfully!');\n setEmail('');\n } catch (error) {\n setMessage('Failed to send invite. Please try again.');\n } finally {\n setLoading(false);\n }\n };\n\n return (\n <div className={`rounded-lg border p-4 ${className}`}>\n <div className=\"mb-4 flex items-center gap-2\">\n <Mail className=\"h-5 w-5\" />\n <h3 className=\"font-semibold\">Invite Team Member</h3>\n </div>\n\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n <div>\n <label className=\"mb-1 block text-sm font-medium\">Email</label>\n <input\n type=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n placeholder=\"colleague@example.com\"\n required\n className=\"w-full rounded border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500\"\n />\n </div>\n\n <div>\n <label className=\"mb-1 block text-sm font-medium\">Role</label>\n <select\n value={role}\n onChange={(e) => setRole(e.target.value as UserRole)}\n className=\"w-full rounded border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500\"\n >\n <option value=\"viewer\">Viewer</option>\n <option value=\"member\">Member</option>\n <option value=\"admin\">Admin</option>\n </select>\n </div>\n\n <button\n type=\"submit\"\n disabled={loading}\n className=\"flex w-full items-center justify-center gap-2 rounded bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-50\"\n >\n <Send className=\"h-4 w-4\" />\n {loading ? 'Sending...' : 'Send Invite'}\n </button>\n\n {message && (\n <p\n className={`text-sm ${message.includes('success') ? 'text-green-600' : 'text-red-600'\n }`}\n >\n {message}\n </p>\n )}\n </form>\n </div>\n );\n}\n"]}