wip: cleanup
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
|
||||||
import { X } from "@lucide/svelte";
|
import { X } from "@lucide/svelte";
|
||||||
import { cn } from "$lib/utils";
|
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||||
import { Button } from "$lib/components/ui/button";
|
import { Button } from "$lib/components/ui/button";
|
||||||
|
import { cn } from "$lib/utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export { default as RoleBadge } from "./role-badge.svelte";
|
|
||||||
export { default as ConfirmDialog } from "./confirm-dialog.svelte";
|
export { default as ConfirmDialog } from "./confirm-dialog.svelte";
|
||||||
|
export { default as RoleBadge } from "./role-badge.svelte";
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { AlertCircle, Building2, Loader2 } from "@lucide/svelte";
|
||||||
import { createQuery } from "@tanstack/svelte-query";
|
import { createQuery } from "@tanstack/svelte-query";
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import { Building2, Loader2, AlertCircle } from "@lucide/svelte";
|
|
||||||
import { api } from "$lib/api/client";
|
import { api } from "$lib/api/client";
|
||||||
import DashboardLayout from "$lib/components/layout/dashboard-layout.svelte";
|
import DashboardLayout from "$lib/components/layout/dashboard-layout.svelte";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "$lib/components/ui/card";
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "$lib/components/ui/card";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dashboard page - lists all organizations the user is a member of
|
* Dashboard page - lists all organizations the user is a member of
|
||||||
@@ -19,7 +24,9 @@ const orgsQuery = createQuery(() => ({
|
|||||||
// Redirect to login on auth error
|
// Redirect to login on auth error
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (orgsQuery.error) {
|
if (orgsQuery.error) {
|
||||||
goto("/auth/login?redirect=" + encodeURIComponent(window.location.pathname));
|
goto(
|
||||||
|
`/auth/login?redirect=${encodeURIComponent(window.location.pathname)}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -31,10 +38,18 @@ function formatDate(date: Date): string {
|
|||||||
const diff = now.getTime() - date.getTime();
|
const diff = now.getTime() - date.getTime();
|
||||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
if (days === 0) return "Today";
|
if (days === 0) {
|
||||||
if (days === 1) return "Yesterday";
|
return "Today";
|
||||||
if (days < 7) return `${days} days ago`;
|
}
|
||||||
if (days < 30) return `${Math.floor(days / 7)} weeks ago`;
|
if (days === 1) {
|
||||||
|
return "Yesterday";
|
||||||
|
}
|
||||||
|
if (days < 7) {
|
||||||
|
return `${days} days ago`;
|
||||||
|
}
|
||||||
|
if (days < 30) {
|
||||||
|
return `${Math.floor(days / 7)} weeks ago`;
|
||||||
|
}
|
||||||
|
|
||||||
return date.toLocaleDateString("en-US", {
|
return date.toLocaleDateString("en-US", {
|
||||||
month: "short",
|
month: "short",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Snippet } from "svelte";
|
import type { Snippet } from "svelte";
|
||||||
import { setContext } from "svelte";
|
|
||||||
import { createQuery } from "@tanstack/svelte-query";
|
import { createQuery } from "@tanstack/svelte-query";
|
||||||
|
import { setContext } from "svelte";
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import { page } from "$app/state";
|
import { page } from "$app/state";
|
||||||
import { api } from "$lib/api/client";
|
import { api } from "$lib/api/client";
|
||||||
@@ -39,46 +39,64 @@ const sitesQuery = createQuery(() => ({
|
|||||||
const currentUserRole = $derived.by(() => {
|
const currentUserRole = $derived.by(() => {
|
||||||
const me = userQuery.data;
|
const me = userQuery.data;
|
||||||
const members = membersQuery.data;
|
const members = membersQuery.data;
|
||||||
if (!me || !members) return null;
|
if (!(me && members)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return members.find((m) => m.userId === me.id)?.role ?? null;
|
return members.find((m) => m.userId === me.id)?.role ?? null;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if user can manage org (admin or owner)
|
// Check if user can manage org (admin or owner)
|
||||||
const canManageOrg = $derived(
|
const canManageOrg = $derived(
|
||||||
currentUserRole === "owner" || currentUserRole === "admin"
|
currentUserRole === "owner" || currentUserRole === "admin",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check if user is owner
|
// Check if user is owner
|
||||||
const isOwner = $derived(currentUserRole === "owner");
|
const isOwner = $derived(currentUserRole === "owner");
|
||||||
|
|
||||||
// Loading state
|
// Loading state
|
||||||
const isLoading = $derived(
|
const isLoading = $derived(userQuery.isPending || membersQuery.isPending);
|
||||||
userQuery.isPending || membersQuery.isPending
|
|
||||||
);
|
|
||||||
|
|
||||||
// Error state
|
// Error state
|
||||||
const error = $derived(
|
const error = $derived(!userQuery.error ? membersQuery.error : null);
|
||||||
!userQuery.error ? membersQuery.error : null
|
|
||||||
);
|
|
||||||
|
|
||||||
// Redirect to login on auth error
|
// Redirect to login on auth error
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (userQuery.error) {
|
if (userQuery.error) {
|
||||||
goto("/auth/login?redirect=" + encodeURIComponent(window.location.pathname));
|
goto(
|
||||||
|
`/auth/login?redirect=${encodeURIComponent(window.location.pathname)}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Provide context to child components
|
// Provide context to child components
|
||||||
setContext("orgContext", {
|
setContext("orgContext", {
|
||||||
get slug() { return slug; },
|
get slug() {
|
||||||
get userQuery() { return userQuery; },
|
return slug;
|
||||||
get membersQuery() { return membersQuery; },
|
},
|
||||||
get sitesQuery() { return sitesQuery; },
|
get userQuery() {
|
||||||
get currentUserRole() { return currentUserRole; },
|
return userQuery;
|
||||||
get canManageOrg() { return canManageOrg; },
|
},
|
||||||
get isOwner() { return isOwner; },
|
get membersQuery() {
|
||||||
get isLoading() { return isLoading; },
|
return membersQuery;
|
||||||
get error() { return error; },
|
},
|
||||||
|
get sitesQuery() {
|
||||||
|
return sitesQuery;
|
||||||
|
},
|
||||||
|
get currentUserRole() {
|
||||||
|
return currentUserRole;
|
||||||
|
},
|
||||||
|
get canManageOrg() {
|
||||||
|
return canManageOrg;
|
||||||
|
},
|
||||||
|
get isOwner() {
|
||||||
|
return isOwner;
|
||||||
|
},
|
||||||
|
get isLoading() {
|
||||||
|
return isLoading;
|
||||||
|
},
|
||||||
|
get error() {
|
||||||
|
return error;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,34 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from "svelte";
|
import {
|
||||||
|
AlertCircle,
|
||||||
|
Building2,
|
||||||
|
ChevronRight,
|
||||||
|
Globe,
|
||||||
|
Loader2,
|
||||||
|
Settings,
|
||||||
|
Users,
|
||||||
|
} from "@lucide/svelte";
|
||||||
import { createQuery } from "@tanstack/svelte-query";
|
import { createQuery } from "@tanstack/svelte-query";
|
||||||
import { Building2, Users, Globe, Settings, ChevronRight, Loader2, AlertCircle } from "@lucide/svelte";
|
import { getContext } from "svelte";
|
||||||
import { api } from "$lib/api/client";
|
import { api } from "$lib/api/client";
|
||||||
import DashboardLayout from "$lib/components/layout/dashboard-layout.svelte";
|
import DashboardLayout from "$lib/components/layout/dashboard-layout.svelte";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "$lib/components/ui/card";
|
|
||||||
import { Button } from "$lib/components/ui/button";
|
|
||||||
import { RoleBadge } from "$lib/components/org";
|
import { RoleBadge } from "$lib/components/org";
|
||||||
|
import { Button } from "$lib/components/ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "$lib/components/ui/card";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Org overview page - shows org details, stats, and navigation
|
* Org overview page - shows org details, stats, and navigation
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Types from API contract
|
// Types from API contract
|
||||||
type OrgMemberOutput = Awaited<ReturnType<typeof api.orgs.members.list>>[number];
|
type OrgMemberOutput = Awaited<
|
||||||
|
ReturnType<typeof api.orgs.members.list>
|
||||||
|
>[number];
|
||||||
type OrgSiteOutput = Awaited<ReturnType<typeof api.orgs.sites.list>>[number];
|
type OrgSiteOutput = Awaited<ReturnType<typeof api.orgs.sites.list>>[number];
|
||||||
|
|
||||||
// Get org context from layout
|
// Get org context from layout
|
||||||
|
|||||||
@@ -1,24 +1,47 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from "svelte";
|
import {
|
||||||
|
AlertCircle,
|
||||||
|
Clock,
|
||||||
|
Loader2,
|
||||||
|
UserPlus,
|
||||||
|
Users,
|
||||||
|
X,
|
||||||
|
} from "@lucide/svelte";
|
||||||
import { createQuery, useQueryClient } from "@tanstack/svelte-query";
|
import { createQuery, useQueryClient } from "@tanstack/svelte-query";
|
||||||
|
import { getContext } from "svelte";
|
||||||
import { toast } from "svelte-sonner";
|
import { toast } from "svelte-sonner";
|
||||||
import { Users, UserPlus, Loader2, AlertCircle, X, Clock } from "@lucide/svelte";
|
|
||||||
import { api } from "$lib/api/client";
|
import { api } from "$lib/api/client";
|
||||||
import DashboardLayout from "$lib/components/layout/dashboard-layout.svelte";
|
import DashboardLayout from "$lib/components/layout/dashboard-layout.svelte";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "$lib/components/ui/card";
|
import { ConfirmDialog, RoleBadge } from "$lib/components/org";
|
||||||
import { Button } from "$lib/components/ui/button";
|
import { Button } from "$lib/components/ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "$lib/components/ui/card";
|
||||||
import { Input } from "$lib/components/ui/input";
|
import { Input } from "$lib/components/ui/input";
|
||||||
import { Label } from "$lib/components/ui/label";
|
import { Label } from "$lib/components/ui/label";
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "$lib/components/ui/table";
|
import {
|
||||||
import { RoleBadge, ConfirmDialog } from "$lib/components/org";
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "$lib/components/ui/table";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Members management page
|
* Members management page
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Types from API contract
|
// Types from API contract
|
||||||
type OrgMemberOutput = Awaited<ReturnType<typeof api.orgs.members.list>>[number];
|
type OrgMemberOutput = Awaited<
|
||||||
type OrgInviteOutput = Awaited<ReturnType<typeof api.orgs.invites.list>>[number];
|
ReturnType<typeof api.orgs.members.list>
|
||||||
|
>[number];
|
||||||
|
type OrgInviteOutput = Awaited<
|
||||||
|
ReturnType<typeof api.orgs.invites.list>
|
||||||
|
>[number];
|
||||||
type UserProfile = Awaited<ReturnType<typeof api.me.get>>;
|
type UserProfile = Awaited<ReturnType<typeof api.me.get>>;
|
||||||
|
|
||||||
// Get org context from layout
|
// Get org context from layout
|
||||||
@@ -76,7 +99,11 @@ async function handleInvite() {
|
|||||||
|
|
||||||
isInviting = true;
|
isInviting = true;
|
||||||
try {
|
try {
|
||||||
await api.orgs.invites.create({ slug, email: inviteEmail.trim(), role: inviteRole });
|
await api.orgs.invites.create({
|
||||||
|
slug,
|
||||||
|
email: inviteEmail.trim(),
|
||||||
|
role: inviteRole,
|
||||||
|
});
|
||||||
toast.success("Invitation sent!");
|
toast.success("Invitation sent!");
|
||||||
inviteEmail = "";
|
inviteEmail = "";
|
||||||
inviteRole = "member";
|
inviteRole = "member";
|
||||||
@@ -99,9 +126,13 @@ async function handleCancelInvite(inviteId: number, email: string) {
|
|||||||
try {
|
try {
|
||||||
await api.orgs.invites.cancel({ slug, inviteId });
|
await api.orgs.invites.cancel({ slug, inviteId });
|
||||||
toast.success("Invitation cancelled");
|
toast.success("Invitation cancelled");
|
||||||
await queryClient.invalidateQueries({ queryKey: ["org", slug, "invites"] });
|
await queryClient.invalidateQueries({
|
||||||
|
queryKey: ["org", slug, "invites"],
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(e instanceof Error ? e.message : "Failed to cancel invitation");
|
toast.error(
|
||||||
|
e instanceof Error ? e.message : "Failed to cancel invitation",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
confirmDialogOpen = true;
|
confirmDialogOpen = true;
|
||||||
@@ -110,7 +141,10 @@ async function handleCancelInvite(inviteId: number, email: string) {
|
|||||||
/**
|
/**
|
||||||
* Update member role
|
* Update member role
|
||||||
*/
|
*/
|
||||||
async function handleUpdateRole(userId: number, newRole: "owner" | "admin" | "member") {
|
async function handleUpdateRole(
|
||||||
|
userId: number,
|
||||||
|
newRole: "owner" | "admin" | "member",
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
await api.orgs.members.updateRole({ slug, userId, role: newRole });
|
await api.orgs.members.updateRole({ slug, userId, role: newRole });
|
||||||
toast.success("Role updated");
|
toast.success("Role updated");
|
||||||
@@ -123,7 +157,11 @@ async function handleUpdateRole(userId: number, newRole: "owner" | "admin" | "me
|
|||||||
/**
|
/**
|
||||||
* Remove member
|
* Remove member
|
||||||
*/
|
*/
|
||||||
async function handleRemoveMember(userId: number, displayName: string | null, email: string) {
|
async function handleRemoveMember(
|
||||||
|
userId: number,
|
||||||
|
displayName: string | null,
|
||||||
|
email: string,
|
||||||
|
) {
|
||||||
confirmDialogTitle = "Remove Member";
|
confirmDialogTitle = "Remove Member";
|
||||||
confirmDialogDescription = `Are you sure you want to remove ${displayName || email} from this organization?`;
|
confirmDialogDescription = `Are you sure you want to remove ${displayName || email} from this organization?`;
|
||||||
confirmDialogVariant = "destructive";
|
confirmDialogVariant = "destructive";
|
||||||
@@ -131,7 +169,9 @@ async function handleRemoveMember(userId: number, displayName: string | null, em
|
|||||||
try {
|
try {
|
||||||
await api.orgs.members.remove({ slug, userId });
|
await api.orgs.members.remove({ slug, userId });
|
||||||
toast.success("Member removed");
|
toast.success("Member removed");
|
||||||
await queryClient.invalidateQueries({ queryKey: ["org", slug, "members"] });
|
await queryClient.invalidateQueries({
|
||||||
|
queryKey: ["org", slug, "members"],
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(e instanceof Error ? e.message : "Failed to remove member");
|
toast.error(e instanceof Error ? e.message : "Failed to remove member");
|
||||||
}
|
}
|
||||||
@@ -160,9 +200,15 @@ function formatRelativeTime(date: Date): string {
|
|||||||
const diff = date.getTime() - now.getTime();
|
const diff = date.getTime() - now.getTime();
|
||||||
const days = Math.ceil(diff / (1000 * 60 * 60 * 24));
|
const days = Math.ceil(diff / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
if (days < 0) return "Expired";
|
if (days < 0) {
|
||||||
if (days === 0) return "Today";
|
return "Expired";
|
||||||
if (days === 1) return "Tomorrow";
|
}
|
||||||
|
if (days === 0) {
|
||||||
|
return "Today";
|
||||||
|
}
|
||||||
|
if (days === 1) {
|
||||||
|
return "Tomorrow";
|
||||||
|
}
|
||||||
return `${days} days`;
|
return `${days} days`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,9 +216,15 @@ function formatRelativeTime(date: Date): string {
|
|||||||
* Check if user can remove a member
|
* Check if user can remove a member
|
||||||
*/
|
*/
|
||||||
function canRemoveMember(memberRole: string, memberId: number): boolean {
|
function canRemoveMember(memberRole: string, memberId: number): boolean {
|
||||||
if (memberId === currentUserId) return false; // Can't remove self
|
if (memberId === currentUserId) {
|
||||||
if (isOwner) return true; // Owners can remove anyone
|
return false; // Can't remove self
|
||||||
if (currentUserRole === "admin" && memberRole === "member") return true; // Admins can remove members
|
}
|
||||||
|
if (isOwner) {
|
||||||
|
return true; // Owners can remove anyone
|
||||||
|
}
|
||||||
|
if (currentUserRole === "admin" && memberRole === "member") {
|
||||||
|
return true; // Admins can remove members
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,8 +232,12 @@ function canRemoveMember(memberRole: string, memberId: number): boolean {
|
|||||||
* Get available roles for invite based on current user's role
|
* Get available roles for invite based on current user's role
|
||||||
*/
|
*/
|
||||||
const availableInviteRoles = $derived.by(() => {
|
const availableInviteRoles = $derived.by(() => {
|
||||||
if (isOwner) return ["member", "admin", "owner"] as const;
|
if (isOwner) {
|
||||||
if (currentUserRole === "admin") return ["member", "admin"] as const;
|
return ["member", "admin", "owner"] as const;
|
||||||
|
}
|
||||||
|
if (currentUserRole === "admin") {
|
||||||
|
return ["member", "admin"] as const;
|
||||||
|
}
|
||||||
return ["member"] as const;
|
return ["member"] as const;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,17 +1,30 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from "svelte";
|
import {
|
||||||
|
AlertCircle,
|
||||||
|
AlertTriangle,
|
||||||
|
Loader2,
|
||||||
|
LogOut,
|
||||||
|
Settings,
|
||||||
|
Trash2,
|
||||||
|
} from "@lucide/svelte";
|
||||||
import { createQuery, useQueryClient } from "@tanstack/svelte-query";
|
import { createQuery, useQueryClient } from "@tanstack/svelte-query";
|
||||||
import { goto } from "$app/navigation";
|
import { getContext } from "svelte";
|
||||||
import { toast } from "svelte-sonner";
|
import { toast } from "svelte-sonner";
|
||||||
import { Settings, Loader2, AlertCircle, AlertTriangle, LogOut, Trash2 } from "@lucide/svelte";
|
import { goto } from "$app/navigation";
|
||||||
import { api } from "$lib/api/client";
|
import { api } from "$lib/api/client";
|
||||||
import DashboardLayout from "$lib/components/layout/dashboard-layout.svelte";
|
import DashboardLayout from "$lib/components/layout/dashboard-layout.svelte";
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "$lib/components/ui/card";
|
import { ConfirmDialog } from "$lib/components/org";
|
||||||
|
import { Alert, AlertDescription } from "$lib/components/ui/alert";
|
||||||
import { Button } from "$lib/components/ui/button";
|
import { Button } from "$lib/components/ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "$lib/components/ui/card";
|
||||||
import { Input } from "$lib/components/ui/input";
|
import { Input } from "$lib/components/ui/input";
|
||||||
import { Label } from "$lib/components/ui/label";
|
import { Label } from "$lib/components/ui/label";
|
||||||
import { Alert, AlertDescription } from "$lib/components/ui/alert";
|
|
||||||
import { ConfirmDialog } from "$lib/components/org";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Org settings page
|
* Org settings page
|
||||||
@@ -59,8 +72,8 @@ $effect(() => {
|
|||||||
// Track if form is dirty
|
// Track if form is dirty
|
||||||
const isDirty = $derived(
|
const isDirty = $derived(
|
||||||
orgQuery.data &&
|
orgQuery.data &&
|
||||||
(displayName !== orgQuery.data.displayName ||
|
(displayName !== orgQuery.data.displayName ||
|
||||||
logoUrl !== (orgQuery.data.logoUrl || ""))
|
logoUrl !== (orgQuery.data.logoUrl || "")),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Confirmation dialog state
|
// Confirmation dialog state
|
||||||
@@ -76,7 +89,9 @@ let isConfirmLoading = $state(false);
|
|||||||
* Save org settings
|
* Save org settings
|
||||||
*/
|
*/
|
||||||
async function handleSave() {
|
async function handleSave() {
|
||||||
if (!canManageOrg) return;
|
if (!canManageOrg) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
isSaving = true;
|
isSaving = true;
|
||||||
try {
|
try {
|
||||||
@@ -100,7 +115,8 @@ async function handleSave() {
|
|||||||
*/
|
*/
|
||||||
function handleLeave() {
|
function handleLeave() {
|
||||||
confirmDialogTitle = "Leave Organization";
|
confirmDialogTitle = "Leave Organization";
|
||||||
confirmDialogDescription = "Are you sure you want to leave this organization? You will lose access to all resources and will need to be re-invited to rejoin.";
|
confirmDialogDescription =
|
||||||
|
"Are you sure you want to leave this organization? You will lose access to all resources and will need to be re-invited to rejoin.";
|
||||||
confirmDialogVariant = "destructive";
|
confirmDialogVariant = "destructive";
|
||||||
confirmDialogConfirmLabel = "Leave Organization";
|
confirmDialogConfirmLabel = "Leave Organization";
|
||||||
confirmAction = async () => {
|
confirmAction = async () => {
|
||||||
@@ -110,7 +126,9 @@ function handleLeave() {
|
|||||||
await queryClient.invalidateQueries({ queryKey: ["orgs"] });
|
await queryClient.invalidateQueries({ queryKey: ["orgs"] });
|
||||||
goto("/dashboard");
|
goto("/dashboard");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(e instanceof Error ? e.message : "Failed to leave organization");
|
toast.error(
|
||||||
|
e instanceof Error ? e.message : "Failed to leave organization",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
confirmDialogOpen = true;
|
confirmDialogOpen = true;
|
||||||
@@ -131,7 +149,9 @@ function handleDelete() {
|
|||||||
await queryClient.invalidateQueries({ queryKey: ["orgs"] });
|
await queryClient.invalidateQueries({ queryKey: ["orgs"] });
|
||||||
goto("/dashboard");
|
goto("/dashboard");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(e instanceof Error ? e.message : "Failed to delete organization");
|
toast.error(
|
||||||
|
e instanceof Error ? e.message : "Failed to delete organization",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
confirmDialogOpen = true;
|
confirmDialogOpen = true;
|
||||||
|
|||||||
@@ -61,11 +61,19 @@ async function acceptInvite(): Promise<void> {
|
|||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
// Handle specific error cases
|
// Handle specific error cases
|
||||||
if (e.message.includes("expired") || e.message.includes("invalid")) {
|
if (e.message.includes("expired") || e.message.includes("invalid")) {
|
||||||
error = "This invitation has expired or is invalid. Please ask for a new invitation.";
|
error =
|
||||||
} else if (e.message.includes("already") || e.message.includes("member")) {
|
"This invitation has expired or is invalid. Please ask for a new invitation.";
|
||||||
|
} else if (
|
||||||
|
e.message.includes("already") ||
|
||||||
|
e.message.includes("member")
|
||||||
|
) {
|
||||||
error = "You're already a member of this organization.";
|
error = "You're already a member of this organization.";
|
||||||
} else if (e.message.includes("email") || e.message.includes("mismatch")) {
|
} else if (
|
||||||
error = "This invitation was sent to a different email address. Please log in with the correct account.";
|
e.message.includes("email") ||
|
||||||
|
e.message.includes("mismatch")
|
||||||
|
) {
|
||||||
|
error =
|
||||||
|
"This invitation was sent to a different email address. Please log in with the correct account.";
|
||||||
} else {
|
} else {
|
||||||
error = e.message;
|
error = e.message;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user