diff --git a/client/src/components/CharacterCard.tsx b/client/src/components/CharacterCard.tsx index 51dad84..5333c8f 100644 --- a/client/src/components/CharacterCard.tsx +++ b/client/src/components/CharacterCard.tsx @@ -20,6 +20,7 @@ interface CharacterCardProps { onHpChange: (characterId: number, hp: number) => void; onUpdate: (characterId: number, data: Partial) => void; onClick: (characterId: number) => void; + canEdit?: boolean; } export default function CharacterCard({ @@ -27,6 +28,7 @@ export default function CharacterCard({ onHpChange, onUpdate, onClick, + canEdit = true, }: CharacterCardProps) { return (
void; onDelete: (id: number) => void; onClose: () => void; + canEdit?: boolean; } export default function CharacterDetail({ @@ -42,6 +43,7 @@ export default function CharacterDetail({ onRemoveTalent, onDelete, onClose, + canEdit = true, }: CharacterDetailProps) { const [mode, setMode] = useState<"view" | "edit">("view"); @@ -49,12 +51,14 @@ export default function CharacterDetail({
e.stopPropagation()}>
- + {canEdit && ( + + )} diff --git a/client/src/pages/CampaignView.tsx b/client/src/pages/CampaignView.tsx index 535b8c0..2a8fb8a 100644 --- a/client/src/pages/CampaignView.tsx +++ b/client/src/pages/CampaignView.tsx @@ -13,7 +13,10 @@ import { addTalent, removeTalent, getRolls, + getMyCampaignRole, + generateInvite, } from "../api"; +import { useAuth } from "../context/AuthContext"; import type { Character, Gear, Talent, GameItem, RollResult } from "../types"; import CharacterCard from "../components/CharacterCard"; import CharacterDetail from "../components/CharacterDetail"; @@ -34,6 +37,8 @@ const ANCESTRIES = ["Human", "Elf", "Dwarf", "Halfling", "Goblin", "Half-Orc"]; export default function CampaignView() { const { id } = useParams<{ id: string }>(); const campaignId = Number(id); + const { user } = useAuth(); + const [role, setRole] = useState<"dm" | "player" | null>(null); const [characters, setCharacters] = useState([]); const [selectedId, setSelectedId] = useState(null); const [showCreate, setShowCreate] = useState(false); @@ -65,6 +70,7 @@ export default function CampaignView() { useEffect(() => { getCharacters(campaignId).then(setCharacters); getRolls(campaignId).then(setRolls); + getMyCampaignRole(campaignId).then((r) => setRole(r.role)).catch(() => {}); getCampaigns().then((camps) => { const c = camps.find((x) => x.id === campaignId); if (c) setCampaignName(c.name); @@ -352,6 +358,16 @@ export default function CampaignView() { await removeTalent(characterId, talentId); } + async function handleInvite() { + try { + const { url } = await generateInvite(campaignId); + await navigator.clipboard.writeText(url); + alert("Invite link copied to clipboard!"); + } catch { + alert("Failed to generate invite link"); + } + } + const selectedCharacter = characters.find((c) => c.id === selectedId) ?? null; return ( @@ -365,10 +381,17 @@ export default function CampaignView() { {campaignName || "Campaign"}
- + {role === "dm" && ( + + )} + {role === "dm" && ( + + )}
@@ -409,6 +433,7 @@ export default function CampaignView() { onRemoveTalent={handleRemoveTalent} onDelete={handleDelete} onClose={() => setSelectedId(null)} + canEdit={role === "dm" || selectedCharacter.user_id === user?.userId} /> )} diff --git a/client/src/types.ts b/client/src/types.ts index 7ba0950..c3c7b48 100644 --- a/client/src/types.ts +++ b/client/src/types.ts @@ -1,7 +1,7 @@ export interface Campaign { id: number; name: string; - created_by: string; + role?: string; created_at: string; } @@ -33,6 +33,7 @@ export interface Talent { export interface Character { id: number; campaign_id: number; + user_id?: number | null; created_by: string; name: string; class: string;