Skip to content

UTDallasEPICS/the-samaritan-inn

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

162 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Samaritan Inn Scheduling App

Samaritan Inn Scheduling App is a web application serving the residents and staff of Samaritan Inn, a nonprofit homeless shelter dedicated to community support. Staff members can create, schedule, edit, and remove announcements to keep everyone informed and organized. Residents can register for life skills classes, book appointments, request curfew extensions, and manage personal schedules through an integrated calendar. In addition, residents can submit pass forms to request extended curfew or time away from the shelter. Residents can see if their requests have been approved or denied by staff. Staff can approve or deny residents' requests and see past forms. The platform leverages Next.js, Prisma, and SQLite to deliver a secure, scalable, and user-friendly experience.


Table of Contents


Features

Role-Based Access Control

  • Residents
    • View announcements posted by staff.
    • Schedule life skills classes to enhance their personal development.
    • Request curfew extensions.
    • Manage their schedules using a personal calendar to organize appointments and busy times.
  • Staff
    • Create, post, and schedule announcements for residents.
    • Manage life skills classes, including scheduling and capacity limits.
    • Deny or approve resident passes.

Announcements Management

  • Staff can create, edit, and delete announcements.
  • Announcements include timestamps and author details.

Curfew Extension Requests

  • Residents
    • Residents can request curfew extensions through the platform.
    • Residents can see if their requests have been approved or denied by staff.
  • Staff
    • Staff can approve or deny residents' requests
    • Staff can see past forms

Authentication

  • Secure login and signup using NextAuth.js.
  • Role-based access for staff and residents.

Responsive Design

  • Optimized for both desktop and mobile devices.

Tech Stack

  • Frontend: Next.js (React Framework)
  • Backend: Node.js with Next.js API routes
  • Database: SQLite with Prisma ORM
  • Authentication: NextAuth.js
  • Styling: Tailwind CSS

Getting Started

Prerequisites

  • Node.js (v18 or higher)
  • npm or Yarn
  • SQLite (comes pre-installed with Prisma)

Installation

  1. Clone the repository:
    git clone https://github.com/your-username/samaritan-inn.git
    cd samaritan-inn
  2. Install dependencies:
    • npm install
    • (create .env file and add the required information)
    • npx prisma migrate dev
    • npx prisma generate
  3. Configure environment variables: Create a .env file in the root directory with:
    DATABASE_URL="file:./dev.db"
    NEXTAUTH_URL="http://localhost:3000"
    NEXTAUTH_SECRET="your-secret-key-for-jwt-encryption"
  4. Start the development server:
    npm run dev
    # or
    yarn dev
  5. Open your browser and navigate to http://localhost:3000.

Project Structure

β”œβ”€β”€ .env  
β”œβ”€β”€ .gitignore  
β”œβ”€β”€ eslint.config.mjs  
β”œβ”€β”€ next-env.d.ts  
β”œβ”€β”€ next.config.ts  
β”œβ”€β”€ package.json  
β”œβ”€β”€ postcss.config.mjs  
β”œβ”€β”€ README.md  
β”œβ”€β”€ tailwind.config.ts  
β”œβ”€β”€ tsconfig.json  
β”œβ”€β”€ prisma  
β”‚   β”œβ”€β”€ schema.prisma  
β”‚   β”œβ”€β”€ dev.db  
β”‚   └── migrations  
β”œβ”€β”€ public  
β”‚   β”œβ”€β”€ file.svg  
β”‚   β”œβ”€β”€ globe.svg  
β”‚   β”œβ”€β”€ next.svg  
β”‚   β”œβ”€β”€ vercel.svg  
β”‚   └── window.svg  
β”œβ”€β”€ src  
β”‚   β”œβ”€β”€ app  
β”‚   β”‚   β”œβ”€β”€ favicon.ico  
β”‚   β”‚   β”œβ”€β”€ globals.css  
β”‚   β”‚   β”œβ”€β”€ layout.tsx  
β”‚   β”‚   β”œβ”€β”€ page.tsx
β”‚   β”‚   β”œβ”€β”€ admin-forms  
β”‚   β”‚   β”œβ”€β”€ announcements  
β”‚   β”‚   β”œβ”€β”€ api  
β”‚   β”‚   β”‚   β”œβ”€β”€ announcements  
β”‚   β”‚   β”‚   β”œβ”€β”€ auth  
β”‚   β”‚   β”‚   β”œβ”€β”€ login  
β”‚   β”‚   β”‚   └── register  
β”‚   β”‚   β”œβ”€β”€ auth-status
β”‚   β”‚   β”œβ”€β”€ calendar-form  
β”‚   β”‚   β”œβ”€β”€ caseworker  
β”‚   β”‚   β”œβ”€β”€ curfew  
β”‚   β”‚   β”œβ”€β”€ dashboard  
β”‚   β”‚   β”œβ”€β”€ homepage  
β”‚   β”‚   β”œβ”€β”€ login
β”‚   β”‚   β”œβ”€β”€ pass-form  
β”‚   β”‚   β”œβ”€β”€ signup  
β”‚   β”‚   β”œβ”€β”€ unauthorized  
β”‚   β”‚   └── Resources  
β”‚   β”œβ”€β”€ components  
β”‚   β”‚   β”œβ”€β”€ Navigation.tsx
β”‚   β”‚   └── admin
β”‚   β”‚       └── DecisionPanel.tsx
β”‚   β”‚       └── ExtendedCurfewDetailModal.tsx
β”‚   β”‚       └── PassRequestDetailModal.tsx
β”‚   β”‚       └── WorkScheduleDetailModal.tsx   
β”‚   β”‚   └── providers  
β”‚   β”‚       └── SessionProvider.tsx  
β”‚   β”œβ”€β”€ lib  
β”‚   β”‚   β”œβ”€β”€ auth.ts  
β”‚   β”‚   └── prisma.ts  
β”‚   └── types  
β”‚       └── next-auth.d.ts  
└── .next  
    β”œβ”€β”€ app-build-manifest.json  
    β”œβ”€β”€ build-manifest.json  
    β”œβ”€β”€ cache  
    β”œβ”€β”€ server  
    └── static  


### Scripts

- `npm run dev` – Start the development server  
- `npm run build` – Build for production  
- `npm start` – Start the production server  
- `npm run lint` – Run linter  

---

## Database Management

- **Prisma Studio:**  
  ```bash
  npx prisma studio
  • Migrations:
    npx prisma migrate dev --name <migration_name>

Authentication

This project uses NextAuth.js for authentication with two roles:

  • Staff (Admin): Can post and manage announcements.
  • Resident: Can view announcements and request curfew extensions.

Salesforce Integration and APIs

The resident appointment flow is integrated with Salesforce so that scheduled appointments are created as Salesforce Event records and also mirrored in the local SQLite database for app-specific rules and UI rendering. In the current codebase, this Salesforce-backed flow is used by /my-events and /calendar-form. The separate /schedule page still embeds a Calendly widget for classes and is not part of the Salesforce event flow.

Overview

  • Frontend pages expose a fixed list of selectable calendars backed by NEXT_PUBLIC_SF_OWNER_1, NEXT_PUBLIC_SF_OWNER_2, and NEXT_PUBLIC_SF_OWNER_3.
  • The app checks Salesforce for existing Event records before offering time slots.
  • When a resident books an appointment, the app first reserves a local Appointment, then creates a Salesforce Event, then stores the returned Salesforce id in Appointment.salesforceEventId.
  • The app also maintains a local ScheduledEvent mirror so the resident-facing event list can show a caseworker label and render quickly from Prisma.
  • Salesforce authentication is server-to-server OAuth using the client credentials grant. It is not tied to the logged-in resident's Salesforce identity.

Architecture and Data Flow

  1. A signed-in user opens /my-events or /calendar-form and selects a caseworker calendar owner id from the frontend environment-configured list.
  2. The page calls GET /api/get-available-slots?date=YYYY-MM-DD&ownerId=<salesforce-owner-id>.
  3. The route validates the date and owner id, queries Salesforce Event records for overlapping time on that owner's calendar, and returns available 30-minute slots inside business hours.
  4. The user submits POST /api/submit-event with title, startDate, endDate, and ownerId.
  5. The route revalidates the interval, rechecks Salesforce for conflicts, creates a local Appointment reservation, creates the Salesforce Event, writes the returned Salesforce event id back to the appointment, and syncs the ScheduledEvent mirror.
  6. The resident event list calls GET /api/my-events, which returns appointments from the local database and refreshes any existing ScheduledEvent mirror rows.
  7. Deleting an event calls DELETE /api/my-events/[id], which attempts to delete the Salesforce Event, then deletes the local mirror and local appointment row.

Salesforce Authentication

The shared Salesforce client in samaritan_inn/src/lib/salesforce.ts uses:

  • POST {SF_LOGIN_URL}/services/oauth2/token
  • grant_type=client_credentials
  • client_id={SF_CLIENT_ID}
  • client_secret={SF_CLIENT_SECRET}

The returned bearer token is then used for REST calls against SF_INSTANCE_URL. Missing Salesforce environment variables throw server-side errors. Salesforce API failures are normalized into SalesforceError and, for the main scheduling routes, are returned to the client as 502 responses with a generic scheduling-service error message.

API Endpoints

Endpoint Method Auth required What it does Response
/api/get-available-slots GET No resident auth required for basic lookup; extra per-user limits only apply when a user session is present Queries Salesforce Event records for one owner and date, then returns open 30-minute slots [{ start, end, label }] or { error }
/api/submit-event POST Yes Validates a booking, reserves a local appointment, creates a Salesforce Event, and stores the Salesforce id locally { success: true, id } or { error }
/api/my-events GET Yes Returns the signed-in user's locally stored appointments and refreshes local mirror rows [{ id, title, startTime, endTime, caseWorker, salesforceId, createdAt }] or { error }
/api/my-events/[id] DELETE Yes Deletes the user's local appointment and attempts to delete the linked Salesforce Event { success: true } or { error }
/api/get-calendar GET No Queries Salesforce Event records between startDate and endDate and maps them into a simplified array [{ id, title, start, end, description, location }]

GET /api/get-available-slots

Query parameters:

Name Required Notes
date Yes Must be YYYY-MM-DD and within the current booking window.
ownerId Yes Must resolve to a valid 15-18 character Salesforce id. If omitted, server code falls back to SF_OWNER_ID.

Behavior:

  • Calls Salesforce with a SOQL query against Event:
    • SELECT Id, Subject, StartDateTime, EndDateTime, Description, Location FROM Event WHERE StartDateTime <= <day-end> AND EndDateTime >= <day-start> AND OwnerId = '<ownerId>'
  • Uses only overlapping event intervals to compute availability.
  • Returns 30-minute slots between 9:00 AM and 5:00 PM Central Time.
  • If a user session exists, the route also checks local appointment caps before returning slots.

Common errors:

  • 400 when date is missing, malformed, outside the one-month window, or ownerId is invalid.
  • 409 when the signed-in user already has an active appointment on that Central Time date.
  • 429 when the signed-in user already has 7 active appointments.
  • 502 when Salesforce authentication or querying fails.

POST /api/submit-event

Request body:

Field Required Notes
title Yes Trimmed server-side before use.
startDate Yes Parsed with new Date(...); must be a valid appointment start instant.
endDate Yes Parsed with new Date(...); must be after startDate.
ownerId Yes Must resolve to a valid Salesforce id or the request fails.
description No Supported by the API and forwarded to Salesforce if present. Current first-party forms do not send it.
location No Supported by the API and forwarded to Salesforce if present. Current first-party forms do not send it.

Response shape:

{ "success": true, "id": "<salesforce-event-id>" }

Behavior:

  • Requires an authenticated application user via NextAuth JWT.
  • Rechecks Salesforce availability immediately before creating the event to reduce race conditions.
  • Creates a local Appointment first, then creates the Salesforce Event, then updates the local appointment with salesforceEventId.
  • Syncs a local ScheduledEvent mirror row with the human-readable caseworker label.
  • On failure after the local reservation is created, the route cleans up by deleting the local reservation, deleting the mirror row, and deleting the Salesforce event if one was already created.

Common errors:

  • 400 for missing required JSON fields, invalid JSON, invalid owner id, invalid date format, out-of-window bookings, invalid duration, off-boundary start times, cross-day events, same-day bookings made with less than 5 hours lead time, or events outside 9:00 AM to 5:00 PM Central Time.
  • 409 when the chosen interval overlaps a Salesforce Event or the user already has an active booking on that day.
  • 429 when the user already has 7 active appointments.
  • 502 when Salesforce token exchange, event creation, or cleanup fails.
  • 500 for unexpected server errors.

GET /api/my-events

Response items:

Field Notes
id Local Prisma Appointment.id, not the Salesforce id.
title Local appointment title.
startTime / endTime Stored locally as DateTime.
caseWorker Derived from the selected owner id using frontend environment-backed labels.
salesforceId Copied from Appointment.salesforceEventId.
createdAt Local creation timestamp.

Notes:

  • The route reads from Prisma, not directly from Salesforce.
  • Before returning results, it refreshes the local ScheduledEvent mirror for appointments that already have a Salesforce id.
  • If an owner id no longer matches one of the configured NEXT_PUBLIC_SF_OWNER_* values, the fallback label returned is the raw owner id string.

DELETE /api/my-events/[id]

Behavior:

  • Requires an authenticated application user.
  • Verifies that the appointment belongs to the requesting user.
  • Attempts to delete the Salesforce Event if salesforceEventId exists.
  • Deletes the local ScheduledEvent mirror and local Appointment even if the Salesforce delete call fails.

Common errors:

  • 401 when the user is not signed in.
  • 404 when the appointment does not exist or does not belong to the user.

GET /api/get-calendar

Current code indicates this is a separate, older Salesforce route:

  • It performs its own token exchange instead of reusing src/lib/salesforce.ts.
  • It accepts startDate and endDate query parameters.
  • It queries Salesforce Event records in that date range and returns a simplified array.
  • It does not filter by OwnerId.
  • No current UI references to this route were found in the repository.
  • It logs the token response and Salesforce response to the server console and does not have explicit route-level error handling.

Salesforce Objects and Field Mapping

The current implementation only reads and writes Salesforce Event records.

Salesforce field Source in app
Subject Appointment.title / request title
StartDateTime Request startDate
EndDateTime Request endDate
Description Request description when present
Location Request location when present
OwnerId Selected calendar owner id
WhatId Hardcoded to 001gK00000hBCOaQAO during event creation

Notes:

  • Current code indicates WhatId is fixed for every created event; the repository does not explain what Salesforce record that id belongs to.
  • The app stores the returned Salesforce event id locally as Appointment.salesforceEventId and mirrors it again as ScheduledEvent.salesforceId.
  • Caseworker labels are not fetched from Salesforce. They are derived from locally configured owner ids and hardcoded labels such as Case Worker 1.

Local Models Used by the Integration

Local model Purpose
Appointment Source of truth inside the app for resident bookings, user ownership, booking caps, owner id, and the linked salesforceEventId
ScheduledEvent Local display mirror keyed by appointmentId, with a resolved caseworker label and optional salesforceId

The current schema also includes Appointment.canceledAt, but the Salesforce scheduling flow shown in this repository does not currently use that field.

Validation and Business Rules

  • Booking time zone is fixed to America/Chicago.
  • Only 30-minute and 60-minute appointments are allowed.
  • Appointments must start on the hour or half hour.
  • Appointments must start and end on the same Central Time calendar day.
  • Appointments must stay within 9:00 AM to 5:00 PM Central Time.
  • Same-day appointments must start at least 5 hours in the future.
  • The booking window is from today through one month ahead.
  • A user may have at most 7 active appointments.
  • A user may have only 1 active appointment per Central Time date.
  • One-hour bookings depend on two consecutive 30-minute slots being available.
  • There is no dedicated schema-validation library in this flow; validation is implemented manually in route handlers and helper functions.

Environment Variables

Variable Required Used for
SF_LOGIN_URL Yes OAuth token endpoint base URL
SF_CLIENT_ID Yes Salesforce client credentials grant
SF_CLIENT_SECRET Yes Salesforce client credentials grant
SF_INSTANCE_URL Yes Base URL for Salesforce REST API calls after token exchange
SF_OWNER_ID Optional fallback Default owner id if an API caller omits ownerId
NEXT_PUBLIC_SF_OWNER_1 Yes for current UI Frontend-selectable calendar owner id and label mapping
NEXT_PUBLIC_SF_OWNER_2 Yes for current UI Frontend-selectable calendar owner id and label mapping
NEXT_PUBLIC_SF_OWNER_3 Yes for current UI Frontend-selectable calendar owner id and label mapping
NEXTAUTH_SECRET Yes Required to authenticate application users before booking or deleting events

The root README setup example already includes DATABASE_URL, NEXTAUTH_URL, and NEXTAUTH_SECRET. For Salesforce-backed scheduling to work locally, the Salesforce variables above must also be present.

Local Development, Testing, and Current Limitations

  • Local development requires valid Salesforce credentials and reachable Salesforce org URLs; otherwise Salesforce-backed routes fail.
  • The repository includes mock user scripts at samaritan_inn/prisma/add-mock-caseworkers.js and samaritan_inn/prisma/add-mock-residents.js, but these only seed local Prisma users. They do not seed Salesforce calendars, owners, or events.
  • No automated tests, fixtures, or seed data specific to Salesforce event creation were found in the current repository.
  • /my-events and /calendar-form both implement the same Salesforce-backed scheduling UI flow.
  • /schedule still uses a Calendly embed for classes, so the app currently contains both a Salesforce-backed appointment flow and a separate legacy Calendly flow.
  • /api/get-calendar appears to be older or auxiliary code and is not clearly integrated into the current UI.

Deployment

  1. Build the application:
    npm run build
  2. Start the production server:
    npm start

Future requirements:

  1. Allow admin to add attachements to announcements and events
  2. Change curfew to more of a form and then sends admin response to user in form of email or notification
  3. Retire the legacy Calendly scheduling page in favor of the Salesforce-backed scheduling flow already used by /my-events and /calendar-form
  4. User request form to fulfill order in inflow.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

2 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors