Skip to content

Completed appointment booking system task#8

Open
DHIRAVIYASUNDARAM wants to merge 1 commit intobotcode-com:mainfrom
DHIRAVIYASUNDARAM:DHIRAVIYA/develop
Open

Completed appointment booking system task#8
DHIRAVIYASUNDARAM wants to merge 1 commit intobotcode-com:mainfrom
DHIRAVIYASUNDARAM:DHIRAVIYA/develop

Conversation

@DHIRAVIYASUNDARAM
Copy link
Copy Markdown

@DHIRAVIYASUNDARAM DHIRAVIYASUNDARAM commented Apr 29, 2026

Implemented doctor, patient, and admin modules.

Features:

  • Patient login, booking, cancel appointments
  • Doctor login, view appointments, mark done/cancel
  • Admin login and appointment dashboard
  • Supabase database integration
  • Responsive UI using Next.js + Tailwind

Summary by CodeRabbit

  • New Features

    • Added admin authentication portal with dashboard to view and manage appointments
    • Implemented appointment booking functionality with validation to prevent double-booking
    • Added appointment cancellation with timing restrictions to prevent last-minute cancellations
    • Enabled doctors to mark appointments as complete
  • Improvements

    • Patient and doctor dashboards now refresh automatically after booking to reflect updated schedules

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces an admin dashboard and login system, along with API endpoints for booking, cancelling, and listing appointments. Several critical security and architectural issues were identified during the review. Multiple API endpoints lack authentication and authorization, exposing sensitive data and allowing unauthorized actions. Furthermore, passwords are being compared in plain text, and the admin dashboard relies on insecure client-side authentication via localStorage. The booking logic also contains a race condition that could lead to overbooking, and the patient dashboard's use of a full page reload should be replaced with a more efficient state update or router refresh.

Comment thread app/api/admin/appointments/route.ts
Comment thread app/api/appointments/book/route.ts
Comment thread app/api/appointments/cancel/route.ts
Comment thread app/api/appointments/book/route.ts
Comment thread app/api/admin/login/route.ts
Comment thread app/admin/dashboard/page.tsx
Comment thread app/patient/dashboard/page.tsx
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

Walkthrough

This PR implements admin authentication and appointment management features. It adds an admin login system with localStorage-based session tracking, an admin dashboard to view appointments, and completes appointment booking and cancellation workflows with validation rules such as preventing duplicate bookings and enforcing cancellation timing constraints.

Changes

Cohort / File(s) Summary
Admin Authentication
app/admin/login/page.tsx, app/api/admin/login/route.ts
Introduces admin login form with email/password input and POST endpoint that validates credentials against system_admins table, sets localStorage.adminLoggedIn on success, and displays error messages on failure.
Admin Dashboard
app/admin/dashboard/page.tsx, app/api/admin/appointments/route.ts
Adds protected admin dashboard page that checks authentication status and renders a table of all appointments with patient/doctor names and slot details. Includes a GET endpoint to fetch appointments from Supabase ordered by creation date.
Appointment Booking
app/api/appointments/book/route.ts
Replaces stub with functional Supabase-backed booking logic that validates slot availability, checks for existing active appointments, and creates new appointment records with slot status updates.
Appointment Cancellation
app/api/appointments/cancel/route.ts
Replaces stub with complete cancellation/completion logic supporting both cancel and done actions, enforcing authorization and timing constraints (1-hour restriction for patient cancellations), and updating appointment and slot statuses accordingly.
Frontend API Integration
app/doctor/dashboard/page.tsx, app/patient/dashboard/page.tsx
Updates doctor dashboard to include doctorId in cancel requests; updates patient dashboard to include patientId in booking/cancellation requests and adds window.location.reload() after successful bookings.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API as /api/appointments/book
    participant Supabase as Database
    
    Client->>API: POST { slotId, patientId, doctorId }
    API->>Supabase: Query slot by slotId
    alt Slot not found or already booked
        API-->>Client: 400 { error }
    else Slot available
        API->>Supabase: Query active appointments for patient & doctor
        alt Active appointment exists
            API-->>Client: 400 { error }
        else No conflict
            API->>Supabase: Insert new appointment (status: active)
            API->>Supabase: Update slot (is_booked: true)
            API-->>Client: 200 { success }
        end
    end
Loading
sequenceDiagram
    participant Client
    participant API as /api/appointments/cancel
    participant Supabase as Database
    
    Client->>API: POST { appointmentId, action, patientId? }
    API->>Supabase: Query appointment with related slot
    alt Appointment not found or not active
        API-->>Client: 404 or 400 { error }
    else Valid appointment
        alt action: cancel
            alt patientId provided and within 1 hour
                API-->>Client: 403 { error: "Cannot cancel within 1 hour" }
            else Time constraint met or doctorId action
                API->>Supabase: Update appointment (status: cancelled/done)
                API->>Supabase: Update slot (is_booked: false)
                API-->>Client: 200 { success }
            end
        else action: done
            API->>Supabase: Update appointment (status: done)
            API->>Supabase: Update slot (is_booked: false)
            API-->>Client: 200 { success }
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Hoppy hops through code so fine,
New admin gates and slots align!
Bookings bloom and cancels dance,
With validation's watchful glance.
A dashboard gleams—the system's bright,
All appointments sorted right! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title "Completed appointment booking system task" is partially related to the changeset, referring to a significant aspect of the changes, but lacks specificity about the actual implementation scope.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/admin/dashboard/page.tsx`:
- Around line 34-44: In loadAppointments(), validate the fetch response and the
parsed JSON before calling setRows: first check res.ok and handle non-2xx
responses (e.g., setRows([]) and surface/log the error or handle 401 specially),
then ensure the parsed body is an array using Array.isArray(data) and only call
setRows(data) when it is; if it’s not an array, log the unexpected payload
(include status and body) and call setRows([]) to avoid later rows.map errors.
Ensure these checks occur in the try block before setRows and preserve the
existing setLoading(false) in finally.

In `@app/admin/login/page.tsx`:
- Around line 14-28: The submit path currently assumes fetch succeeds and the
response is JSON; wrap the network call and parsing in a try/catch around the
fetch(...) and res.json() so rejections or non-JSON bodies don't throw and leave
the user without feedback. Specifically, in the login submit handler that calls
fetch("/api/admin/login"), catch network errors and call setError(...) with a
friendly message, and when res.ok is false attempt to parse JSON in a guarded
way (fall back to res.text() if JSON.parse fails) and then call
setError(data.error || text || "Login failed"); keep the existing success
actions (localStorage.setItem("adminLoggedIn", "true") and window.location.href
= "/admin/dashboard") only when res.ok and parsing succeeds.

In `@app/api/admin/appointments/route.ts`:
- Around line 23-29: The code is turning DB/query failures into a 200 with an
empty array; update both error paths to return a 5xx response and include the
error information so the client can show an error state. Specifically, in the
branch that currently does "if (error) { return NextResponse.json([], { status:
200 }); }" and in the catch block that returns 200, change them to return an
appropriate server error response (e.g., NextResponse.json({ error: 'Internal
Server Error', details: String(error) }, { status: 500 })) so the API surfaces
failures instead of pretending there are no appointments; preserve usage of
NextResponse.json and include the caught/received error variable for
logging/response.

In `@app/api/appointments/book/route.ts`:
- Around line 20-24: The handler currently trusts the incoming doctorId instead
of using the slot owner; fetch the slot (the existing `slot` result from the
`slots` query) and derive the appointment's doctor as `const doctorId =
slot.doctor_id` (after checking `slot` exists), then use that derived `doctorId`
everywhere: in the duplicate-check query that looks for an active appointment
with the same doctor and patient, and in the `insert` payload for the new
appointment; ignore or remove the doctorId coming from the request body so
callers cannot substitute a different doctor. Ensure the same change is applied
to the other checks/inserts in the function (the duplicate-check block and the
appointment insert blocks referenced around lines 33-54 and 58-61).

In `@app/api/appointments/cancel/route.ts`:
- Around line 20-24: The handler uses SUPABASE_SERVICE_ROLE_KEY so RLS is
bypassed; before updating an appointment's status, fetch the appointment (you
already query into appt) and verify ownership: if cancelling, assert that the
provided patientId equals appt.patient_id; if marking done, assert provided
doctorId equals appt.doctor_id; return 403/appropriate error if the IDs don't
match or if appt is missing, then proceed to call
supabase.from("appointments").update(...) only after the ownership check passes.
Use the existing variables appointmentId, patientId, doctorId, appt, and the
"appointments" table/fields patient_id and doctor_id to locate where to add the
checks.

In `@app/patient/dashboard/page.tsx`:
- Around line 106-113: Remove the window.location.reload() call and instead
update the local React state that drives the dashboard: keep the setActionMsg
and setAvailableSlots logic, then either push the new appointment into the
appointments state (e.g., call setAppointments(prev => [newAppointment,
...prev])) or call the existing fetch/refetch helper (e.g., fetchAppointments()
or mutateAppointments()) to refresh only the appointment list; locate this
change near setActionMsg, setAvailableSlots and slotId in page.tsx and ensure no
full-page reload is used.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6a27f12a-c221-440c-a3a4-281e79889173

📥 Commits

Reviewing files that changed from the base of the PR and between bfd0d0d and ba1868f.

📒 Files selected for processing (8)
  • app/admin/dashboard/page.tsx
  • app/admin/login/page.tsx
  • app/api/admin/appointments/route.ts
  • app/api/admin/login/route.ts
  • app/api/appointments/book/route.ts
  • app/api/appointments/cancel/route.ts
  • app/doctor/dashboard/page.tsx
  • app/patient/dashboard/page.tsx

Comment on lines +34 to +44
async function loadAppointments() {
try {
const res = await fetch("/api/admin/appointments");
const data = await res.json();

setRows(data || []);
} catch (error) {
console.log(error);
} finally {
setLoading(false);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate the response before storing it in rows.

loadAppointments() assumes every JSON body is an array. Once the API starts returning proper 401/500 payloads, setRows(data || []) can store an object, and the render path later calls rows.map(...) on a non-array value.

Suggested fix
+  const [error, setError] = useState("");
+
   async function loadAppointments() {
     try {
       const res = await fetch("/api/admin/appointments");
       const data = await res.json();
-
-      setRows(data || []);
+      if (!res.ok || !Array.isArray(data)) {
+        throw new Error("Failed to load appointments");
+      }
+      setRows(data);
     } catch (error) {
-      console.log(error);
+      setError("Failed to load appointments");
+      setRows([]);
     } finally {
       setLoading(false);
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/admin/dashboard/page.tsx` around lines 34 - 44, In loadAppointments(),
validate the fetch response and the parsed JSON before calling setRows: first
check res.ok and handle non-2xx responses (e.g., setRows([]) and surface/log the
error or handle 401 specially), then ensure the parsed body is an array using
Array.isArray(data) and only call setRows(data) when it is; if it’s not an
array, log the unexpected payload (include status and body) and call setRows([])
to avoid later rows.map errors. Ensure these checks occur in the try block
before setRows and preserve the existing setLoading(false) in finally.

Comment thread app/admin/login/page.tsx
Comment on lines +14 to +28
const res = await fetch("/api/admin/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email, password }),
});

const data = await res.json();

if (res.ok) {
localStorage.setItem("adminLoggedIn", "true");
window.location.href = "/admin/dashboard";
} else {
setError(data.error || "Login failed");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle request and JSON parse failures in the submit path.

If fetch() rejects, or the server returns a non-JSON error body, this function throws before setError(...) runs. The login flow then fails with no feedback to the user.

Suggested fix
   async function handleLogin(e: React.FormEvent) {
     e.preventDefault();
     setError("");
-
-    const res = await fetch("/api/admin/login", {
-      method: "POST",
-      headers: {
-        "Content-Type": "application/json",
-      },
-      body: JSON.stringify({ email, password }),
-    });
-
-    const data = await res.json();
-
-    if (res.ok) {
-      localStorage.setItem("adminLoggedIn", "true");
-      window.location.href = "/admin/dashboard";
-    } else {
-      setError(data.error || "Login failed");
+    try {
+      const res = await fetch("/api/admin/login", {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/json",
+        },
+        body: JSON.stringify({ email, password }),
+      });
+
+      const data = await res.json().catch(() => ({}));
+
+      if (res.ok) {
+        localStorage.setItem("adminLoggedIn", "true");
+        window.location.href = "/admin/dashboard";
+      } else {
+        setError(data.error || "Login failed");
+      }
+    } catch {
+      setError("Login failed");
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/admin/login/page.tsx` around lines 14 - 28, The submit path currently
assumes fetch succeeds and the response is JSON; wrap the network call and
parsing in a try/catch around the fetch(...) and res.json() so rejections or
non-JSON bodies don't throw and leave the user without feedback. Specifically,
in the login submit handler that calls fetch("/api/admin/login"), catch network
errors and call setError(...) with a friendly message, and when res.ok is false
attempt to parse JSON in a guarded way (fall back to res.text() if JSON.parse
fails) and then call setError(data.error || text || "Login failed"); keep the
existing success actions (localStorage.setItem("adminLoggedIn", "true") and
window.location.href = "/admin/dashboard") only when res.ok and parsing
succeeds.

Comment on lines +23 to +29
if (error) {
return NextResponse.json([], { status: 200 });
}

return NextResponse.json(data || []);
} catch (error) {
return NextResponse.json([], { status: 200 });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not convert database failures into 200 [].

Both failure paths currently masquerade as an empty dashboard, so a broken query or missing env var becomes “No appointments found” instead of an actionable server error. Return a 5xx here and let the client render an error state.

Suggested fix
     if (error) {
-      return NextResponse.json([], { status: 200 });
+      return NextResponse.json(
+        { error: "Failed to load appointments" },
+        { status: 500 }
+      );
     }
 
     return NextResponse.json(data || []);
   } catch (error) {
-    return NextResponse.json([], { status: 200 });
+    return NextResponse.json(
+      { error: "Failed to load appointments" },
+      { status: 500 }
+    );
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (error) {
return NextResponse.json([], { status: 200 });
}
return NextResponse.json(data || []);
} catch (error) {
return NextResponse.json([], { status: 200 });
if (error) {
return NextResponse.json(
{ error: "Failed to load appointments" },
{ status: 500 }
);
}
return NextResponse.json(data || []);
} catch (error) {
return NextResponse.json(
{ error: "Failed to load appointments" },
{ status: 500 }
);
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/admin/appointments/route.ts` around lines 23 - 29, The code is
turning DB/query failures into a 200 with an empty array; update both error
paths to return a 5xx response and include the error information so the client
can show an error state. Specifically, in the branch that currently does "if
(error) { return NextResponse.json([], { status: 200 }); }" and in the catch
block that returns 200, change them to return an appropriate server error
response (e.g., NextResponse.json({ error: 'Internal Server Error', details:
String(error) }, { status: 500 })) so the API surfaces failures instead of
pretending there are no appointments; preserve usage of NextResponse.json and
include the caught/received error variable for logging/response.

Comment on lines +20 to +24
const { data: slot } = await supabase
.from("slots")
.select("*")
.eq("id", slotId)
.single();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Derive the appointment doctor from the slot, not from the request.

The handler never checks that doctorId matches slot.doctor_id, then uses the client value for both the duplicate check and the inserted appointment. A caller can therefore book doctor A's slot while storing doctor B on the appointment, which breaks dashboard joins and bypasses the "already have an active appointment with this doctor" rule for the real slot owner.

🩹 Suggested fix
-    const { slotId, doctorId, patientId } = await req.json();
+    const { slotId, patientId } = await req.json();
@@
-    if (!slotId || !doctorId || !patientId) {
+    if (!slotId || !patientId) {
       return NextResponse.json(
         { error: "Missing required fields" },
         { status: 400 }
       );
     }
@@
-    const { data: slot } = await supabase
+    const { data: slot } = await supabase
       .from("slots")
-      .select("*")
+      .select("id, doctor_id, is_booked")
       .eq("id", slotId)
       .single();
@@
+    const doctorId = slot.doctor_id;
+
     const { data: existing } = await supabase
       .from("appointments")
       .select("id")
       .eq("patient_id", patientId)
       .eq("doctor_id", doctorId)
       .eq("status", "active");

Also applies to: 33-54, 58-61

🤖 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 20 - 24, The handler
currently trusts the incoming doctorId instead of using the slot owner; fetch
the slot (the existing `slot` result from the `slots` query) and derive the
appointment's doctor as `const doctorId = slot.doctor_id` (after checking `slot`
exists), then use that derived `doctorId` everywhere: in the duplicate-check
query that looks for an active appointment with the same doctor and patient, and
in the `insert` payload for the new appointment; ignore or remove the doctorId
coming from the request body so callers cannot substitute a different doctor.
Ensure the same change is applied to the other checks/inserts in the function
(the duplicate-check block and the appointment insert blocks referenced around
lines 33-54 and 58-61).

Comment on lines +20 to +24
const { data: appt } = await supabase
.from("appointments")
.select("*, slots(start_time)")
.eq("id", appointmentId)
.single();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Enforce appointment ownership before changing status.

This handler uses SUPABASE_SERVICE_ROLE_KEY, so the RLS policies in schema.sql are bypassed. Right now, any caller who knows an appointmentId can cancel it by sending any truthy patientId, or mark it done by sending any doctorId, because neither value is checked against the appointment row.

🔒 Suggested fix
-    const { data: appt } = await supabase
+    const { data: appt } = await supabase
       .from("appointments")
-      .select("*, slots(start_time)")
+      .select("id, status, patient_id, doctor_id, slot_id, slots(start_time)")
       .eq("id", appointmentId)
       .single();
@@
     if (action === "cancel") {
+      if (!patientId || appt.patient_id !== patientId) {
+        return NextResponse.json({ error: "Forbidden" }, { status: 403 });
+      }
+
-      if (patientId) {
-        const start = new Date(appt.slots.start_time).getTime();
-        const now = Date.now();
-        const diffHours = (start - now) / (1000 * 60 * 60);
-
-        if (diffHours < 1) {
-          return NextResponse.json(
-            { error: "Cannot cancel less than 1 hour before appointment" },
-            { status: 400 }
-          );
-        }
-      }
+      const start = new Date(appt.slots.start_time).getTime();
+      const now = Date.now();
+      const diffHours = (start - now) / (1000 * 60 * 60);
+
+      if (diffHours < 1) {
+        return NextResponse.json(
+          { error: "Cannot cancel less than 1 hour before appointment" },
+          { status: 400 }
+        );
+      }
@@
     if (action === "done") {
-      if (!doctorId) {
+      if (!doctorId || appt.doctor_id !== doctorId) {
         return NextResponse.json(
-          { error: "Doctor only action" },
+          { error: "Forbidden" },
           { status: 403 }
         );
       }

Also applies to: 40-79

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/appointments/cancel/route.ts` around lines 20 - 24, The handler uses
SUPABASE_SERVICE_ROLE_KEY so RLS is bypassed; before updating an appointment's
status, fetch the appointment (you already query into appt) and verify
ownership: if cancelling, assert that the provided patientId equals
appt.patient_id; if marking done, assert provided doctorId equals
appt.doctor_id; return 403/appropriate error if the IDs don't match or if appt
is missing, then proceed to call supabase.from("appointments").update(...) only
after the ownership check passes. Use the existing variables appointmentId,
patientId, doctorId, appt, and the "appointments" table/fields patient_id and
doctor_id to locate where to add the checks.

Comment on lines +106 to +113
setActionMsg("Appointment booked successfully!");

setAvailableSlots((prev) =>
prev.filter((s) => s.id !== slotId)
);

window.location.reload();
} else {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Avoid a full-page reload after booking.

window.location.reload() throws away the success state you just set and reruns the entire dashboard bootstrap on every successful booking. Refresh the affected data/state instead so the appointment list updates without a hard navigation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/patient/dashboard/page.tsx` around lines 106 - 113, Remove the
window.location.reload() call and instead update the local React state that
drives the dashboard: keep the setActionMsg and setAvailableSlots logic, then
either push the new appointment into the appointments state (e.g., call
setAppointments(prev => [newAppointment, ...prev])) or call the existing
fetch/refetch helper (e.g., fetchAppointments() or mutateAppointments()) to
refresh only the appointment list; locate this change near setActionMsg,
setAvailableSlots and slotId in page.tsx and ensure no full-page reload is used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant