- /api/invites - GET/POST invites - /api/invites/accept - POST accept invite - /invite/[token] - Accept invite page - Settings page now has invite UI - Checks member limit for free tier - Shows upgrade prompt when limit reached Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
53 lines
No EOL
1.6 KiB
TypeScript
53 lines
No EOL
1.6 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { sql } from "@/db";
|
|
|
|
// POST /api/invites/accept - accept an invite with token
|
|
export async function POST(request: Request) {
|
|
try {
|
|
const body = await request.json();
|
|
const { token, userId } = body;
|
|
|
|
if (!token || !userId) {
|
|
return NextResponse.json({ error: "token and userId required" }, { status: 400 });
|
|
}
|
|
|
|
// Find invite
|
|
const invites = await sql.unsafe(
|
|
`SELECT * FROM family_invites WHERE token = $1 AND expires_at > NOW() AND accepted_at IS NULL`,
|
|
[token]
|
|
);
|
|
|
|
if (!invites || invites.length === 0) {
|
|
return NextResponse.json({ error: "Invalid or expired invite" }, { status: 404 });
|
|
}
|
|
|
|
const invite = invites[0];
|
|
|
|
// Check if user already in family
|
|
const existingMember = await sql.unsafe(
|
|
`SELECT id FROM family_members WHERE family_id = $1 AND user_id = $2`,
|
|
[invite.family_id, userId]
|
|
);
|
|
|
|
if (existingMember && existingMember.length > 0) {
|
|
return NextResponse.json({ error: "Already a member of this family" }, { status: 400 });
|
|
}
|
|
|
|
// Add member
|
|
await sql.unsafe(
|
|
`INSERT INTO family_members (family_id, user_id, role, display_name) VALUES ($1, $2, $3, $4)`,
|
|
[invite.family_id, userId, invite.role, invite.display_name]
|
|
);
|
|
|
|
// Mark invite as accepted
|
|
await sql.unsafe(
|
|
`UPDATE family_invites SET accepted_at = NOW() WHERE id = $1`,
|
|
[invite.id]
|
|
);
|
|
|
|
return NextResponse.json({ success: true });
|
|
} catch (error) {
|
|
console.error(error);
|
|
return NextResponse.json({ error: String(error) }, { status: 500 });
|
|
}
|
|
} |