Wire production domain, CORS for tenant subdomains, celery volume mounts, and nginx reverse proxy configs for apex, API, identity, auth, and wildcard tenants. Co-authored-by: Cursor <cursoragent@cursor.com>
58 lines
1.8 KiB
TypeScript
58 lines
1.8 KiB
TypeScript
"use client";
|
||
|
||
import { useState } from "react";
|
||
import { api, ApiError, type MembershipSummary } from "@/lib/api";
|
||
|
||
export interface TenantSwitcherProps {
|
||
memberships: MembershipSummary[];
|
||
currentTenantId: string | null;
|
||
onSwitched: () => void;
|
||
}
|
||
|
||
/** انتخابگر ساده tenant جاری — فقط زمانی نمایش داده میشود که کاربر بیش از یک workspace داشته باشد. */
|
||
export function TenantSwitcher({
|
||
memberships,
|
||
currentTenantId,
|
||
onSwitched,
|
||
}: TenantSwitcherProps) {
|
||
const [loading, setLoading] = useState(false);
|
||
const [error, setError] = useState("");
|
||
|
||
if (memberships.length <= 1) return null;
|
||
|
||
const handleChange = async (tenantId: string) => {
|
||
if (tenantId === currentTenantId) return;
|
||
setError("");
|
||
setLoading(true);
|
||
try {
|
||
await api.tenantContext.switchTenant(tenantId);
|
||
onSwitched();
|
||
} catch (err) {
|
||
setError(err instanceof ApiError ? err.message : "تغییر workspace ناموفق بود");
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div>
|
||
<label className="block text-xs font-medium text-gray-500">
|
||
workspace جاری
|
||
<select
|
||
value={currentTenantId ?? ""}
|
||
disabled={loading}
|
||
onChange={(e) => handleChange(e.target.value)}
|
||
className="mt-1 rounded-xl border border-gray-200 bg-white px-3 py-2 text-sm font-medium text-secondary focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary disabled:bg-gray-50"
|
||
>
|
||
{memberships.map((m) => (
|
||
<option key={m.tenant_id} value={m.tenant_id}>
|
||
{m.tenant_name}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</label>
|
||
{error && <p className="mt-1 text-xs text-red-600">{error}</p>}
|
||
</div>
|
||
);
|
||
}
|