Skip to content

Commit 6bb1aed

Browse files
committed
feat: add Telegram link code generation and dashboard UI
1 parent 7539d62 commit 6bb1aed

2 files changed

Lines changed: 118 additions & 0 deletions

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
import { getCurrentUser } from "@/lib/auth";
3+
import { prisma } from "@/lib/prisma";
4+
import { getProfileAccess } from "@/lib/access";
5+
import crypto from "crypto";
6+
7+
export async function POST(
8+
req: NextRequest,
9+
{ params }: { params: Promise<{ id: string }> }
10+
) {
11+
const user = await getCurrentUser();
12+
if (!user)
13+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
14+
15+
const { id } = await params;
16+
17+
const role = await getProfileAccess(user, id);
18+
if (!role)
19+
return NextResponse.json({ error: "Not found" }, { status: 404 });
20+
21+
const { caregiverId } = await req.json();
22+
if (!caregiverId)
23+
return NextResponse.json(
24+
{ error: "caregiverId is required" },
25+
{ status: 400 }
26+
);
27+
28+
const caregiver = await prisma.caregiver.findFirst({
29+
where: { id: caregiverId, elderlyProfileId: id },
30+
});
31+
if (!caregiver)
32+
return NextResponse.json(
33+
{ error: "Caregiver not found" },
34+
{ status: 404 }
35+
);
36+
37+
const linkCode = crypto.randomBytes(3).toString("hex").toUpperCase();
38+
const linkCodeExpiresAt = new Date(Date.now() + 15 * 60 * 1000);
39+
40+
await prisma.caregiver.update({
41+
where: { id: caregiverId },
42+
data: { linkCode, linkCodeExpiresAt },
43+
});
44+
45+
return NextResponse.json({
46+
linkCode,
47+
expiresAt: linkCodeExpiresAt.toISOString(),
48+
botUsername: process.env.TELEGRAM_BOT_USERNAME || "SenioraBot",
49+
});
50+
}

src/app/elderly/[id]/page.tsx

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ interface Caregiver {
1515
name: string;
1616
phone: string;
1717
phoneVerified: boolean;
18+
telegramChatId?: string | null;
1819
}
1920

2021
interface Medication {
@@ -65,6 +66,12 @@ export default function ElderlyDetailPage() {
6566
const [deleting, setDeleting] = useState(false);
6667
const [uploadingAvatar, setUploadingAvatar] = useState(false);
6768
const [savingVoice, setSavingVoice] = useState(false);
69+
const [telegramLink, setTelegramLink] = useState<{
70+
linkCode: string;
71+
botUsername: string;
72+
caregiverId: string;
73+
} | null>(null);
74+
const [generatingLink, setGeneratingLink] = useState<string | null>(null);
6875

6976
const refreshCaregivers = useCallback(() => {
7077
setCaregiverRefreshKey((k) => k + 1);
@@ -163,6 +170,24 @@ export default function ElderlyDetailPage() {
163170
}
164171
}
165172

173+
async function handleGenerateTelegramLink(caregiverId: string) {
174+
setGeneratingLink(caregiverId);
175+
try {
176+
const res = await apiFetch(`/api/elderly/${id}/telegram-link`, {
177+
method: "POST",
178+
headers: { "Content-Type": "application/json" },
179+
body: JSON.stringify({ caregiverId }),
180+
});
181+
if (!res.ok) throw new Error("Failed to generate link");
182+
const data = await res.json();
183+
setTelegramLink({ ...data, caregiverId });
184+
} catch (err) {
185+
setError(err instanceof Error ? err.message : "Failed to generate link");
186+
} finally {
187+
setGeneratingLink(null);
188+
}
189+
}
190+
166191
const languageLabel = profile.language === "ar" ? "Arabic" : "English";
167192
const allVerified = profile.phoneVerified && profile.emergencyPhoneVerified;
168193

@@ -348,6 +373,49 @@ export default function ElderlyDetailPage() {
348373
onSuccess={refreshCaregivers}
349374
/>
350375
</div>
376+
{profile.caregivers.length > 0 && (
377+
<div className="mt-4 border-t border-gray-100 pt-4">
378+
<h3 className="text-sm font-medium text-gray-700 mb-3">
379+
Link Telegram Bot
380+
</h3>
381+
<div className="space-y-2">
382+
{profile.caregivers.map((cg) => (
383+
<div key={cg.id} className="flex items-center justify-between text-sm">
384+
<span className="text-gray-700">
385+
{cg.name}
386+
{cg.telegramChatId && (
387+
<span className="ml-2 inline-flex items-center rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-700">
388+
Telegram linked
389+
</span>
390+
)}
391+
</span>
392+
{!cg.telegramChatId && (
393+
<button
394+
onClick={() => handleGenerateTelegramLink(cg.id)}
395+
disabled={generatingLink === cg.id}
396+
className="rounded-lg border border-blue-300 px-3 py-1 text-xs font-medium text-blue-600 hover:bg-blue-50 transition-colors disabled:opacity-50"
397+
>
398+
{generatingLink === cg.id ? "Generating..." : "Generate Link Code"}
399+
</button>
400+
)}
401+
</div>
402+
))}
403+
</div>
404+
{telegramLink && (
405+
<div className="mt-3 rounded-lg bg-blue-50 border border-blue-200 p-3 text-sm">
406+
<p className="font-medium text-blue-800 mb-1">
407+
Send this to the Telegram bot:
408+
</p>
409+
<code className="block bg-white rounded px-2 py-1 text-blue-900 font-mono text-xs">
410+
/start {telegramLink.linkCode}
411+
</code>
412+
<p className="mt-1 text-xs text-blue-600">
413+
Bot: @{telegramLink.botUsername} &middot; Expires in 15 minutes
414+
</p>
415+
</div>
416+
)}
417+
</div>
418+
)}
351419
</section>
352420

353421
{/* Medications */}

0 commit comments

Comments
 (0)