Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
aba25d5
feat: add Organization model to Prisma schema
gbchill Mar 13, 2026
2393e1d
feat: add OrgAdmin role and org-scoped permissions
gbchill Mar 13, 2026
fd69b28
feat: scope all Prisma queries by orgId
gbchill Mar 13, 2026
7fe26d6
feat: rename Horse model to Animal with species field
gbchill Mar 13, 2026
48dbc03
refactor: rename horse admin routes and utilities to animal
gbchill Mar 13, 2026
96fe1ee
refactor: update data.ts and volunteer descriptions to animal-agnosti…
gbchill Mar 13, 2026
f63f186
refactor: update all route files to use animal naming conventions
gbchill Mar 13, 2026
93280be
Merge feature/multi-tenant/generalize-animals: Horse to Animal genera…
gbchill Mar 13, 2026
a8565fb
feat: add organization signup flow and org setup page
gbchill Mar 13, 2026
8d07216
Merge feature/multi-tenant/signup-flow: Org registration flow
gbchill Mar 13, 2026
d15ffcd
feat: scope remaining admin and calendar routes by orgId
gbchill Mar 13, 2026
52b6266
feat: scope event edit route by orgId and add super admin orgs page
gbchill Mar 13, 2026
50d4770
Merge feature/multi-tenant/auth-system: Complete tenant isolation
gbchill Mar 13, 2026
597e2cd
style: update color tokens and meta for multi-tenant platform
gbchill Mar 13, 2026
8b8dd2e
style: redesign sidebar navigation layout and app shell
gbchill Mar 13, 2026
91d56cc
style: redesign public landing page hero and features section
gbchill Mar 13, 2026
6604316
style: update signup and login pages to two-column layout
gbchill Mar 13, 2026
03990d2
style: redesign volunteer table with filter bar and improved header
gbchill Mar 13, 2026
ddffd3e
style: redesign dashboard stat cards and calendar layout
gbchill Mar 13, 2026
ea0fac1
style: add empty states to data views and improve animals page layout
gbchill Mar 13, 2026
6b2993f
feat: merge Phase 6 UI redesign — sidebar, two-column auth, empty states
gbchill Mar 13, 2026
0aa1ac8
Update UI/UX to fix ui things
gbchill Mar 14, 2026
6a53792
Update login and sign up add images
gbchill Mar 14, 2026
7c4cf30
Working on fixing admin home page
gbchill Mar 19, 2026
12c7054
Fix calendar bug
gbchill Mar 21, 2026
1c63db4
Fix calendar
gbchill Mar 21, 2026
e16c343
Remove the hourse text and change the horse icon to cat icon
gbchill Mar 21, 2026
ac88a4e
Fix TypeScript/ESLint errors and replace emoji icons with lucide-react
gbchill Mar 23, 2026
4dcdf2a
Change the color of the buttons of the landing page
gbchill Mar 23, 2026
d35ee98
Polish org-signup spacing and email page layout
gbchill Mar 24, 2026
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
30 changes: 15 additions & 15 deletions app/components/listboxes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,27 @@ const listboxButtonClassName =
const listBoxOptionsClassname =
'z-50 absolute mt-1 max-h-60 w-full overflow-auto rounded-md py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm border-input border border-1'

interface HorseData {
interface AnimalData {
id: string
name: string
}

interface HorseListboxProps {
horses: HorseData[]
interface AnimalListboxProps {
animals: AnimalData[]
name: string
defaultValues?: HorseData[]
defaultValues?: AnimalData[]
error: boolean
}

export function HorseListbox({
horses,
export function AnimalListbox({
animals,
name,
defaultValues = [],
error,
}: HorseListboxProps) {
const initialValues = horses.filter(horse => {
}: AnimalListboxProps) {
const initialValues = animals.filter(animal => {
for (const value of defaultValues) {
if (value.id == horse.id) {
if (value.id == animal.id) {
return true
}
}
Expand All @@ -51,7 +51,7 @@ export function HorseListbox({
aria-invalid={error ? true : undefined}
>
<span className="block truncate">
{selected.map(horse => horse.name).join(', ')}
{selected.map(animal => animal.name).join(', ')}
</span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<Icon className="text-body-md" name="caret-sort" />
Expand All @@ -65,22 +65,22 @@ export function HorseListbox({
leaveTo="opacity-0"
>
<Listbox.Options className={listBoxOptionsClassname}>
{horses.map((horse, horseIdx) => (
{animals.map((animal, animalIdx) => (
<Listbox.Option
key={horseIdx}
key={animalIdx}
className={({ active }) =>
`relative cursor-default select-none py-2 pl-10 pr-4
${active ? 'bg-teal-600 text-white' : 'bg-background text-primary'}`
}
value={horse}
value={animal}
>
{({ selected, active }) => (
<>
<span
className={`block truncate
className={`block truncate
${selected ? 'font-semibold' : 'font-normal'}`}
>
{horse.name}
{animal.name}
</span>
{selected ? (
<span
Expand Down
98 changes: 98 additions & 0 deletions app/components/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { useRef } from 'react'
import { Form, Link, NavLink } from '@remix-run/react'
import { Cat } from 'lucide-react'
import { Icon } from '~/components/ui/icon.tsx'
import { ThemeSwitch } from '~/routes/resources+/theme/index.tsx'
import { useUser } from '~/utils/user.ts'
import { getUserImgSrc } from '~/utils/misc.ts'

export function Sidebar({
userPreference,
}: {
userPreference: 'light' | 'dark' | null
}) {
const user = useUser()
const formRef = useRef<HTMLFormElement>(null)
const userIsAdmin = user?.roles.find(r => r.name === 'admin')

const navLinkClass = ({ isActive }: { isActive: boolean }) =>
`flex items-center gap-3 rounded-md px-3 py-2 text-body-sm font-medium transition-colors ${
isActive
? 'bg-sidebar-active text-sidebar-active-foreground'
: 'text-sidebar-foreground hover:bg-sidebar-border'
}`

return (
<aside className="fixed inset-y-0 left-0 z-40 flex w-60 flex-col border-r border-sidebar-border bg-sidebar">
{/* Logo */}
<div className="flex h-16 shrink-0 items-center border-b border-sidebar-border px-5">
<Link to="/" className="flex items-center gap-2">
<span className="text-h6 font-bold text-sidebar-active">The Barn</span>
</Link>
</div>

{/* Navigation */}
<nav className="flex flex-1 flex-col gap-1 overflow-y-auto px-3 py-4">
<NavLink to="/calendar" className={navLinkClass}>
<Icon name="calendar" className="h-4 w-4 shrink-0" />
Calendar
</NavLink>

{userIsAdmin ? (
<>
<p className="mt-5 px-3 pb-1 text-body-2xs font-semibold uppercase tracking-wider text-muted-foreground">
Admin
</p>
<NavLink to="/admin/users" className={navLinkClass}>
<Icon name="person" className="h-4 w-4 shrink-0" />
Users
</NavLink>
<NavLink to="/admin/animals" className={navLinkClass}>
<Cat className="h-4 w-4 shrink-0" />
Animals
</NavLink>
<NavLink to="/admin/email" className={navLinkClass}>
<Icon name="email" className="h-4 w-4 shrink-0" />
Email
</NavLink>
</>
) : null}
</nav>

{/* Footer: theme toggle + user */}
<div className="shrink-0 border-t border-sidebar-border p-3">
<div className="mb-2 flex items-center justify-between px-2">
<span className="text-body-2xs text-muted-foreground">Theme</span>
<ThemeSwitch userPreference={userPreference} />
</div>
<div className="flex items-center gap-3 rounded-md px-2 py-2">
<img
className="h-8 w-8 shrink-0 rounded-full object-cover"
alt={user.name ?? user.username}
src={getUserImgSrc(user.imageId)}
/>
<div className="flex min-w-0 flex-1 flex-col">
<span className="truncate text-body-xs font-semibold text-sidebar-foreground">
{user.name ?? user.username}
</span>
<Link
to={`/users/${user.username}`}
className="truncate text-body-2xs text-muted-foreground hover:underline"
>
View profile
</Link>
</div>
<Form action="/logout" method="POST" ref={formRef}>
<button
type="submit"
className="rounded p-1 text-muted-foreground hover:text-sidebar-foreground"
aria-label="Logout"
>
<Icon name="exit" className="h-4 w-4" />
</button>
</Form>
</div>
</div>
</aside>
)
}
21 changes: 16 additions & 5 deletions app/components/ui/data_table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@ import { Button } from '~/components/ui/button.tsx'
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
emptyMessage?: string
emptyDescription?: string
}

export function DataTable<TData, TValue>({
columns,
data,
emptyMessage = 'No results found',
emptyDescription,
}: DataTableProps<TData, TValue>) {
const table = useReactTable({
data,
Expand Down Expand Up @@ -80,11 +84,18 @@ export function DataTable<TData, TValue>({
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
<TableCell colSpan={columns.length}>
<div className="flex flex-col items-center justify-center py-16 text-center">
<div className="mb-3 text-4xl text-muted-foreground/40">—</div>
<p className="text-body-sm font-medium text-muted-foreground">
{emptyMessage}
</p>
{emptyDescription && (
<p className="mt-1 text-body-xs text-muted-foreground/70">
{emptyDescription}
</p>
)}
</div>
</TableCell>
</TableRow>
)}
Expand Down
42 changes: 21 additions & 21 deletions app/data.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
import { Prisma } from '@prisma/client'

export const siteName = 'The Barn Volunteer Portal'
export const siteEmailAddress = 'hello@email.trottrack.org'
export const siteEmailAddress = 'hello@thebarnaz.com'
export const siteEmailAddressWithName =
siteName + ' <hello@email.trottrack.org>'
export const siteBaseUrl = 'https://thebarn.trottrack.org'
siteName + ' <hello@thebarnaz.com>'
export const siteBaseUrl = 'https://thebarnaz.com'

export const volunteerTypes = [
{
displayName: 'cleaning crew',
field: 'cleaningCrew',
reqField: 'cleaningCrewReq',
description:
'Cleaning crew volunteers help clean all pastures and stalls in the barn, check automatic waterers, sweep the feed room and tack room, and handle other miscellaneous cleaning jobs. No prior experience with horses is required.',
'Cleaning crew volunteers help maintain the facility, check waterers, sweep common areas, and handle other miscellaneous cleaning tasks. No prior experience with animals is required.',
},
{
displayName: 'side walkers',
field: 'sideWalkers',
reqField: 'sideWalkersReq',
description:
'Side walkers walk alongside students helping to support them during lessons.No prior experience with horses needed. Must be able to walk on uneven surfaces.',
'Side walkers walk alongside participants helping to support them during sessions. No prior experience with animals needed. Must be able to walk on uneven surfaces.',
},
{
displayName: 'lesson assistants',
field: 'lessonAssistants',
reqField: 'lessonAssistantsReq',
description:
'Lesson assistants should have 1+ years of experience with horses. They must be able to groom and tack horses, and to communicate effectively with both students and instructors.',
'Lesson assistants should have 1+ years of experience with the animals. They assist instructors and communicate effectively with both participants and staff.',
},
{
displayName: 'horse leaders',
field: 'horseLeaders',
reqField: 'horseLeadersReq',
displayName: 'animal handlers',
field: 'animalHandlers',
reqField: 'animalHandlersReq',
description:
'Leads horses during lessons. Should have 1+ years of experiences with horses, and must be able to walk on uneven surfaces.',
'Animal handlers guide and manage animals during sessions. Should have 1+ years of experience with animals, and must be able to walk on uneven surfaces.',
},
] as const

Expand All @@ -49,7 +49,7 @@ export interface UserData {
yearsOfExperience: number | null
}

export interface HorseData {
export interface AnimalData {
id: string
name: string
imageId: string | null
Expand All @@ -60,9 +60,9 @@ export interface HorseData {
cooldownEndDate: Date | null
}

export interface HorseAssignment {
export interface AnimalAssignment {
userId: string
horseId: string
animalId: string
}

export interface CalEvent {
Expand All @@ -72,28 +72,28 @@ export interface CalEvent {
end: Date

instructors: UserData[]
horses: HorseData[]
animals: AnimalData[]

cleaningCrewReq: number
lessonAssistantsReq: number
horseLeadersReq: number
animalHandlersReq: number
sideWalkersReq: number

cleaningCrew: UserData[]
lessonAssistants: UserData[]
horseLeaders: UserData[]
animalHandlers: UserData[]
sideWalkers: UserData[]
}

const EventWithAllRelations = Prisma.validator<Prisma.EventArgs>()({
include: {
horses: true,
animals: true,
instructors: true,
cleaningCrew: true,
lessonAssistants: true,
horseLeaders: true,
animalHandlers: true,
sideWalkers: true,
horseAssignments: true,
animalAssignments: true,
},
})

Expand All @@ -102,11 +102,11 @@ export type EventWithAllRelations = Prisma.EventGetPayload<
>
const EventWithVolunteers = Prisma.validator<Prisma.EventArgs>()({
include: {
horses: true,
animals: true,
instructors: true,
cleaningCrew: true,
lessonAssistants: true,
horseLeaders: true,
animalHandlers: true,
sideWalkers: true,
},
})
Expand Down
Loading