Files
tower/apps/web/app/my/circles/CircleJoinButton.tsx
T
maaz519 57fe9d9bf1 feat: member portal Sprint 7 — Circles (interest/affinity sub-groups)
- Circle + CircleMembership models + migration (CircleRole: MEMBER/LEAD, public/invite-only)
- CirclesModule: admin CRUD + member list; unique circle name per tenant
- Member endpoints in MyController: GET /my/circles, POST /my/circles/:id/join|leave
- listForMember returns public circles + private ones the member belongs to, with isMember/myRole/memberCount
- join() enforces invite-only; leave() removes membership
- /my/circles page: "My circles" + "Discover" split, join/leave button, LEAD + Private badges
- Member + admin BFF routes; Circles nav item
- Register CirclesModule; add CIRCLE_CREATED/DELETED audit actions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 16:52:04 +05:30

54 lines
1.4 KiB
TypeScript

'use client';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
interface Props {
circleId: string;
initialMember: boolean;
isPublic: boolean;
}
export function CircleJoinButton({ circleId, initialMember, isPublic }: Props) {
const router = useRouter();
const [member, setMember] = useState(initialMember);
const [loading, setLoading] = useState(false);
const act = async (action: 'join' | 'leave') => {
setLoading(true);
try {
const res = await fetch(`/api/my/circles/${circleId}/${action}`, { method: 'POST' });
if (res.ok) {
setMember(action === 'join');
router.refresh();
}
} finally {
setLoading(false);
}
};
if (member) {
return (
<button
type="button"
disabled={loading}
onClick={() => act('leave')}
className="text-xs rounded-full px-3 py-1.5 font-medium border border-gray-200 text-gray-600 hover:bg-gray-50 disabled:opacity-50"
>
{loading ? '…' : 'Joined · Leave'}
</button>
);
}
return (
<button
type="button"
disabled={loading || !isPublic}
onClick={() => act('join')}
className="text-xs rounded-full px-4 py-1.5 font-medium bg-indigo-600 text-white hover:bg-indigo-700 disabled:opacity-50 disabled:cursor-not-allowed"
>
{!isPublic ? 'Invite only' : loading ? '…' : 'Join'}
</button>
);
}