Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions app/admin/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { redirect } from "next/navigation";
import { cookies } from "next/headers";
import { supabaseAdmin } from "@/lib/supabaseAdmin";

function formatDateTime(iso: string) {
return new Date(iso).toLocaleString("en-IN", {
dateStyle: "medium",
timeStyle: "short",
});
}

export default async function AdminDashboard() {
const cookieStore = await cookies();
const adminToken = cookieStore.get("admin_token");

if (!adminToken) {
redirect("/admin/login");
}
Comment thread
afifa17112005 marked this conversation as resolved.

const { data: appointments, error } = await supabaseAdmin
.from("appointments")
.select("id, status, created_at, patients(name, email), doctors(name, specialty, email), slots(start_time, end_time)")
.order("created_at", { ascending: false });

if (error) {
return <div className="p-8 text-red-500">Failed to load appointments.</div>;
}

return (
<main className="min-h-screen bg-gray-50 p-6">
<div className="mx-auto max-w-6xl">
<div className="mb-6 flex items-center justify-between">
<h1 className="text-2xl font-bold">System Admin Dashboard</h1>
<a
href="/"
className="rounded-lg border px-4 py-2 text-sm text-gray-600 hover:bg-gray-100"
>
Go Home
</a>
</div>

<section>
<h2 className="mb-3 text-lg font-semibold">All Appointments Hub</h2>
{(!appointments || appointments.length === 0) ? (
<p className="text-sm text-gray-500">No appointments in the system.</p>
) : (
<div className="overflow-hidden rounded-xl border bg-white">
<table className="w-full text-sm">
<thead className="bg-gray-50 text-left text-gray-600">
<tr>
<th className="px-4 py-3 font-medium">Patient</th>
<th className="px-4 py-3 font-medium">Doctor</th>
<th className="px-4 py-3 font-medium">Slot Time</th>
<th className="px-4 py-3 font-medium">Status</th>
</tr>
</thead>
<tbody className="divide-y">
{appointments.map((appt: any) => (
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid using the any type for the appointment object. Defining a proper interface or type for the appointment data will improve type safety and maintainability.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. I agree that defining a proper TypeScript interface would improve type safety and maintainability. The any type was used for faster development during this implementation, but this can be refactored by introducing a well-defined appointment type based on the schema.

<tr key={appt.id}>
<td className="px-4 py-3">
<div className="font-medium text-gray-900">{appt.patients?.name || "Unknown"}</div>
<div className="text-xs text-gray-500">{appt.patients?.email}</div>
</td>
<td className="px-4 py-3">
<div className="font-medium text-gray-900">{appt.doctors?.name || "Unknown"}</div>
<div className="text-xs text-gray-500">{appt.doctors?.specialty} β€’ {appt.doctors?.email}</div>
</td>
<td className="px-4 py-3 text-gray-500">
{appt.slots?.start_time ? formatDateTime(appt.slots.start_time) : "N/A"}
</td>
<td className="px-4 py-3">
<span
className={`rounded-full px-2 py-0.5 text-xs font-medium ${
appt.status === "active"
? "bg-blue-100 text-blue-700"
: appt.status === "done"
? "bg-green-100 text-green-700"
: "bg-gray-100 text-gray-600"
}`}
>
{appt.status}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</section>
</div>
</main>
);
}
82 changes: 82 additions & 0 deletions app/admin/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"use client";

import { useState } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";

export default function AdminLogin() {
const router = useRouter();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);

async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setError("");
setLoading(true);

const res = await fetch("/api/admin/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
});

if (!res.ok) {
const data = await res.json();
setError(data.error || "Login failed");
setLoading(false);
return;
}

router.push("/admin/dashboard");
}
Comment thread
afifa17112005 marked this conversation as resolved.

return (
<main className="flex min-h-screen items-center justify-center bg-gray-50">
<div className="w-full max-w-md rounded-xl border bg-white p-8 shadow-sm">
<h1 className="mb-6 text-2xl font-bold">Admin Login</h1>
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
<div>
<label className="mb-1 block text-sm font-medium text-gray-700">
Email
</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
placeholder="admin@test.com"
className="w-full rounded-lg border px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label className="mb-1 block text-sm font-medium text-gray-700">
Password
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
className="w-full rounded-lg border px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
{error && <p className="text-sm text-red-600">{error}</p>}
<button
type="submit"
disabled={loading}
className="rounded-lg bg-blue-600 py-2 font-medium text-white hover:bg-blue-700 disabled:opacity-50"
>
{loading ? "Logging in..." : "Login"}
</button>
</form>
<p className="mt-4 text-center text-sm text-gray-500">
<Link href="/" className="text-blue-600 hover:underline">
← Back to home
</Link>
</p>
</div>
</main>
);
}
33 changes: 33 additions & 0 deletions app/api/admin/login/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { NextRequest, NextResponse } from "next/server";
import { supabaseAdmin } from "@/lib/supabaseAdmin";

export async function POST(req: NextRequest) {
try {
const { email, password } = await req.json();

const { data: admin, error } = await supabaseAdmin
.from("system_admins")
.select("*")
.eq("email", email)
.single();

if (error || !admin) {
return NextResponse.json({ error: "Invalid email or password" }, { status: 401 });
}

if (admin.password !== password) {
return NextResponse.json({ error: "Invalid email or password" }, { status: 401 });
}
Comment thread
afifa17112005 marked this conversation as resolved.
Comment on lines +18 to +20
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

Do not authenticate with plaintext password comparison.

Line 18 indicates reversible/plaintext credential handling. This is a blocker: store one-way password hashes and verify against hash, not raw text.

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/admin/login/route.ts` around lines 18 - 20, The code currently
compares plaintext passwords (admin.password !== password); replace this with a
hashed-password verification flow: store one-way hashes (e.g., bcrypt/argon2)
for admin passwords (e.g., admin.passwordHash) and use the library's
verify/compare function (e.g., bcrypt.compare or argon2.verify) in the login
route to compare the incoming password to the stored hash; update any
registration/update code to hash passwords before persisting and change the
check in route.ts to await the hash-compare call and return 401 only if
verification fails (refer to the admin.password/admin.passwordHash and the login
route handler/NextResponse.json).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for highlighting this. I agree that comparing plaintext passwords is not secure and is not suitable for production systems.

In this implementation, plaintext comparison was used for simplicity and to focus on core application logic. In a production environment, passwords would be securely hashed using a strong algorithm such as bcrypt or Argon2, and comparisons would be performed against the hashed values.

This is a valid security improvement and would be addressed in a production-ready version.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@afifa17112005, thanks for the clear explanation. That's a reasonable trade-off for a demo/task context. For production, the upgrade path would look roughly like:

  1. Hash on creation β€” when inserting an admin record, store a bcrypt/Argon2 hash instead of the raw password:

    import bcrypt from "bcryptjs";
    const passwordHash = await bcrypt.hash(plainPassword, 12);
    // INSERT INTO system_admins (email, password_hash) VALUES (email, passwordHash)
  2. Verify on login β€” replace the equality check in app/api/admin/login/route.ts:

    // Before (insecure):
    if (admin.password !== password) { ... }
    
    // After (secure):
    const valid = await bcrypt.compare(password, admin.password_hash);
    if (!valid) { ... }
  3. Replace the cookie value β€” store a signed JWT (e.g., via jose) or an opaque session token instead of the raw admin.id.

Would you like me to open a follow-up GitHub issue to track these production-readiness improvements?


🧠 Learnings used
Learnt from: afifa17112005
Repo: botcode-com/task PR: 2
File: app/admin/dashboard/page.tsx:13-18
Timestamp: 2026-04-28T05:48:57.287Z
Learning: In the Next.js admin area, do not implement authentication/authorization by (1) checking cookie presence only (e.g., `admin_token`) or storing sensitive values like `admin.id` as plain text, and do not authenticate by comparing passwords in plaintext (no hashing). For admin auth, use a secure, verifiable mechanism insteadβ€”e.g., signed JWTs validated with `jose` (with proper signature/claims validation) or Supabase Auth sessions with role/claim-based authorization. If plaintext/insecure logic is present for a demo/task, ensure the code is explicitly labeled and that production-bound code paths are gated/removed before deployment.


const response = NextResponse.json({ success: true });
response.cookies.set("admin_token", admin.id, {
httpOnly: true,
path: "/",
maxAge: 60 * 60 * 24 * 7, // 1 week
});
Comment thread
afifa17112005 marked this conversation as resolved.

return response;
} catch (error) {
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
}
}
106 changes: 103 additions & 3 deletions app/api/appointments/book/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,105 @@
import { NextRequest, NextResponse } from "next/server";
import { supabaseAdmin } from "@/lib/supabaseAdmin";
import { canPatientBookSlot, canPatientBookDoctor } from "@/lib/appointment-rules";

export async function POST(_req: NextRequest) {
return NextResponse.json({ error: "Not implemented" }, { status: 501 });
}
export async function POST(req: NextRequest) {
try {
const body = await req.json();

const { patient_id, doctor_id, slot_id } = body;

console.log("RECEIVED:", { patient_id, doctor_id, slot_id });

// 1. Validate input
if (!patient_id || !doctor_id || !slot_id) {
return NextResponse.json(
{ error: "Missing required fields" },
{ status: 400 }
);
}
Comment on lines +9 to +19
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

Do not accept patient_id directly from client for booking authority.

A caller can book on behalf of any patient by sending another patient_id. Derive patient identity from authenticated session/JWT on the server, then ignore/override client-provided patient identifiers.

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/appointments/book/route.ts` around lines 9 - 19, The code currently
trusts the client-provided patient_id; instead, extract the authenticated
user/patient id from the server-side session/JWT and ignore/override any
patient_id from the request body. In the route handler that reads const {
patient_id, doctor_id, slot_id } = body, replace use of patient_id with a
server-derived patientId obtained from your auth utility (e.g. verify JWT or get
session) and return a 401 if no authenticated patient is found; continue to
validate doctor_id and slot_id and use NextResponse for errors as before. Ensure
you remove or ignore the client-sent patient_id so bookings are always
attributed to the authenticated user.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for highlighting this. I agree that accepting patient_id directly from the client is not secure, as it can allow users to act on behalf of others.

In this implementation, it was kept simple for demonstration purposes. In a production system, the patient identity would be derived from the authenticated session or JWT on the server side, and any client-provided identifiers would be ignored or validated against the session.

This is a valid security improvement and would be enforced in a production-ready setup.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@afifa17112005, that's a fair and well-reasoned response. For a demonstration/task project, keeping auth simple is understandable. When moving to production, here's a concise reference for what the server-side extraction would look like using Supabase's built-in auth:

// Production pattern: derive patient identity server-side
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";

export async function POST(req: NextRequest) {
  const cookieStore = cookies();
  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    { cookies: { get: (name) => cookieStore.get(name)?.value } }
  );

  const { data: { user }, error } = await supabase.auth.getUser();
  if (!user) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  // Use user.id as the authoritative patient_id β€” ignore any client-supplied value
  const patient_id = user.id;
  const { doctor_id, slot_id } = await req.json();
  // ... rest of booking logic
}

Key production hardening steps to track for later:

  • Auth: Extract identity from session/JWT server-side; never trust client-supplied patient_id.
  • RLS: Tighten Supabase Row Level Security so patients can only read/write their own rows.
  • Password hashing: Replace plain-text comparison in /api/admin/login with bcrypt/argon2.

✏️ Learnings added
Learnt from: afifa17112005
Repo: botcode-com/task PR: 2
File: app/api/appointments/book/route.ts:9-19
Timestamp: 2026-04-28T05:51:44.724Z
Learning: In the `app/api/appointments/book/route.ts` file of the botcode-com/task repository (Next.js + Supabase appointment booking system), `patient_id` is intentionally accepted from the client body for demonstration/task purposes. In a production system, the patient identity should be derived from the server-side Supabase session/JWT and the client-provided `patient_id` should be ignored or overridden.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


// 2. Fetch slot safely
const { data: slots, error } = await supabaseAdmin
.from("slots")
.select("*");
Comment thread
afifa17112005 marked this conversation as resolved.

if (error) {
console.error(error);
return NextResponse.json(
{ error: "Error fetching slots" },
{ status: 500 }
);
}

const slot = slots?.find((s) => s.id === slot_id);

Comment on lines +22 to +35
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

Booking flow is race-prone and can create double bookings.

Current read-then-insert-then-update sequence is non-atomic. Concurrent requests can both pass checks and insert active appointments. Atomically claim slot first (is_booked=false -> true with conditional update), then insert appointment, and rollback claim on insert failure.

Atomic claim pattern (recommended direction)
-    const { data: slots, error } = await supabaseAdmin
-      .from("slots")
-      .select("*");
-    ...
-    const slot = slots?.find((s) => s.id === slot_id);
+    const { data: claimedSlot, error: claimError } = await supabaseAdmin
+      .from("slots")
+      .update({ is_booked: true })
+      .eq("id", slot_id)
+      .eq("doctor_id", doctor_id)
+      .eq("is_booked", false)
+      .select("id, doctor_id")
+      .single();
+
+    if (claimError || !claimedSlot) {
+      return NextResponse.json({ error: "Slot already booked or invalid" }, { status: 409 });
+    }
...
-    await supabaseAdmin
-      .from("slots")
-      .update({ is_booked: true })
-      .eq("id", slot_id);
+    // Slot already claimed above. If insert fails, release claim.
+    if (insertError) {
+      await supabaseAdmin.from("slots").update({ is_booked: false }).eq("id", slot_id);
+      ...
+    }

Also applies to: 65-91

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/appointments/book/route.ts` around lines 22 - 35, The current
read-then-insert flow is race-prone; change it to an atomic claim pattern using
supabaseAdmin to conditionally flip the slot's is_booked flag and only proceed
if the update affected a row: perform an update on "slots" setting
is_booked=true where id=slot_id and is_booked=false, verify the update returned
a matched row (if not, return a 409 or similar), then insert the appointment
record; if the insert fails, perform a compensating update to set
is_booked=false on that slot to roll back the claim. Use the same pattern for
the other booking path mentioned (the code around the second insert/update
block) and handle/log errors via NextResponse.json with appropriate status
codes.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for highlighting this. I agree that the current read–then–insert–then–update flow is not atomic and can lead to race conditions where multiple users may book the same slot concurrently.

In this implementation, the flow was kept simple for clarity. In a production system, this would be handled using an atomic slot-claim approach, such as a conditional update (is_booked = false β†’ true) at the database level, or by using transactions / database procedures to ensure consistency.

Additionally, rollback mechanisms would be applied in case of failures after the slot is claimed.

This is a valid improvement and would be addressed in a production-ready implementation.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@afifa17112005, thanks for the detailed explanation! That makes sense for a demonstration/task context where simplicity is prioritized over production-grade concurrency safety.

It's also worth noting that your PR description's sequence diagram (Image 1) already illustrates the correct atomic approach β€” UPDATE slots SET is_booked = true WHERE id = slot_id AND is_booked = false β€” along with the revert-slot-on-conflict path. So the production design is well thought out; it's just not yet reflected in the code itself.

When you do productionize this, the key steps from your own diagram would translate roughly to:

  1. Atomic claim β€” conditional update (is_booked = false β†’ true); if 0 rows updated β†’ 409
  2. Duplicate check β€” check for existing active appointment with same doctor; if conflict β†’ revert slot (is_booked = false) β†’ 409
  3. Insert appointment β€” if insert fails β†’ revert slot β†’ 500

That matches exactly what you described. Happy to generate a code diff when you're ready to implement it!

(γ₯οΏ£ Β³οΏ£)γ₯


✏️ Learnings added
Learnt from: afifa17112005
Repo: botcode-com/task PR: 2
File: app/api/appointments/book/route.ts:22-35
Timestamp: 2026-04-28T05:52:20.230Z
Learning: In `app/api/appointments/book/route.ts` of the botcode-com/task repository (Next.js + Supabase appointment booking system), the non-atomic read-then-insert-then-update booking flow (race condition risk) is a known, intentional simplification for demo/task purposes. In production, an atomic slot-claim pattern (conditional UPDATE is_booked=false→true, then INSERT appointment, with compensating UPDATE to revert is_booked on insert failure) is planned. The PR description sequence diagram already documents this intended production flow.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: afifa17112005
Repo: botcode-com/task PR: 2
File: app/api/appointments/book/route.ts:9-19
Timestamp: 2026-04-28T05:51:44.724Z
Learning: In the `app/api/appointments/book/route.ts` file of the botcode-com/task repository (Next.js + Supabase appointment booking system), `patient_id` is intentionally accepted from the client body for demonstration/task purposes. In a production system, the patient identity should be derived from the server-side Supabase session/JWT and the client-provided `patient_id` should be ignored or overridden.

if (!slot) {
return NextResponse.json(
{ error: "Slot not found" },
{ status: 404 }
);
}

// 3. Check if already booked
if (!canPatientBookSlot(slot)) {
return NextResponse.json(
{ error: "Slot already booked" },
{ status: 400 }
);
}

// 4. Check existing appointment with same doctor
const { data: existing } = await supabaseAdmin
.from("appointments")
.select("status")
.eq("patient_id", patient_id)
.eq("doctor_id", doctor_id);

if (existing && !canPatientBookDoctor(existing)) {
Comment on lines +52 to +58
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle appointment lookup errors before rule evaluation.

The appointments query error is ignored. On DB error, existing may be undefined and booking proceeds incorrectly. Return 500 when the query fails.

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/appointments/book/route.ts` around lines 52 - 58, The DB query result
from supabaseAdmin (.from("appointments").select(...)) can return an error that
is currently ignored; update the booking route to inspect the response's error
(the object returned alongside data from supabaseAdmin) immediately after the
query and if an error exists return a 500 response instead of proceeding to call
canPatientBookDoctor; specifically, check the query response (the variable
holding { data: existing, error }) right after the supabaseAdmin call and handle
error before using existing or calling canPatientBookDoctor so you don't
evaluate rules on an undefined dataset.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing this out. I agree that the current implementation does not explicitly handle errors from the appointments query, which could lead to incorrect behavior if the database call fails.

In a production setup, proper error handling would be added to check for query failures and return an appropriate 500 response before proceeding with any business rule evaluation.

This is a valid improvement to ensure reliability and will be incorporated in a production-ready version.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@afifa17112005, thanks for the acknowledgment! That's a fair approach for a demo/task context. When you're ready to harden this for production, here's the minimal change needed around lines 52–58:

// 4. Check existing appointment with same doctor
const { data: existing, error: existingError } = await supabaseAdmin
  .from("appointments")
  .select("status")
  .eq("patient_id", patient_id)
  .eq("doctor_id", doctor_id);

if (existingError) {
  console.error(existingError);
  return NextResponse.json(
    { error: "Error checking existing appointments" },
    { status: 500 }
  );
}

if (existing && !canPatientBookDoctor(existing)) {
  return NextResponse.json(
    { error: "Already have an active appointment with this doctor" },
    { status: 400 }
  );
}

This ensures a DB failure on the duplicate-check query surfaces as a 500 rather than silently allowing the booking to proceed on an undefined dataset.


🧠 Learnings used
Learnt from: afifa17112005
Repo: botcode-com/task PR: 2
File: app/api/appointments/book/route.ts:9-19
Timestamp: 2026-04-28T05:51:44.724Z
Learning: In the `app/api/appointments/book/route.ts` file of the botcode-com/task repository (Next.js + Supabase appointment booking system), `patient_id` is intentionally accepted from the client body for demonstration/task purposes. In a production system, the patient identity should be derived from the server-side Supabase session/JWT and the client-provided `patient_id` should be ignored or overridden.

return NextResponse.json(
{ error: "Already have an active appointment with this doctor" },
{ status: 400 }
);
}

// 5. Insert appointment
const { data: insertData, error: insertError } = await supabaseAdmin
.from("appointments")
.insert([
{
patient_id,
doctor_id,
slot_id,
status: "active",
},
])
.select("id, status, slots(id, start_time, end_time), doctors(id, name, specialty)")
.single();

if (insertError) {
console.error(insertError);
return NextResponse.json(
{ error: insertError.message },
{ status: 500 }
);
}

// 6. Update slot
await supabaseAdmin
.from("slots")
.update({ is_booked: true })
.eq("id", slot_id);
Comment thread
afifa17112005 marked this conversation as resolved.

return NextResponse.json({
message: "Appointment booked successfully",
appointment: insertData
});

} catch (err: any) {
console.error("ERROR:", err);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}
Loading