import { useState, useEffect } from "react"; import { useSearchParams } from "react-router-dom"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { api } from "../api/client"; import { listBands } from "../api/bands"; import { listInvites, revokeInvite } from "../api/invites"; import { useBandStore } from "../stores/bandStore"; import { getInitials } from "../utils"; // ── Types ───────────────────────────────────────────────────────────────────── interface MemberRead { id: string; display_name: string; email: string; avatar_url: string | null; nc_username: string | null; nc_url: string | null; nc_configured: boolean; } interface BandMember { id: string; display_name: string; email: string; role: string; joined_at: string; } interface BandInvite { id: string; token: string; role: string; expires_at: string | null; is_used: boolean; } interface Band { id: string; name: string; slug: string; genre_tags: string[]; nc_folder_path: string | null; } type Section = "profile" | "members" | "storage" | "band"; // ── Helpers ─────────────────────────────────────────────────────────────────── function formatExpiry(expiresAt: string | null | undefined): string { if (!expiresAt) return "No expiry"; const date = new Date(expiresAt); const diffHours = Math.floor((date.getTime() - Date.now()) / (1000 * 60 * 60)); if (diffHours <= 0) return "Expired"; if (diffHours < 24) return `Expires in ${diffHours}h`; return `Expires in ${Math.floor(diffHours / 24)}d`; } function isActive(invite: BandInvite): boolean { return !invite.is_used && !!invite.expires_at && new Date(invite.expires_at) > new Date(); } // ── Shared style helpers ────────────────────────────────────────────────────── const border = "rgba(255,255,255,0.06)"; const borderBright = "rgba(255,255,255,0.12)"; function Label({ children }: { children: React.ReactNode }) { return (
{subtitle}
}{error}
}{ncError}
}{currentPath}
Invite link (copied · valid 72h):
{inviteLink}
Loading…
) : (Loading invites…
) : activeInvites.length === 0 ? (No pending invites.
) : ({invite.token.slice(0, 8)}…{invite.token.slice(-4)}
No account needed to accept an invite.
> )}