Repository: UoaWDCC/uabc-web
Pull request #15: Implemented required backend schemas
Author: login: jji05
Created: 2025-03-30T10:13:30Z | Merged: 2025-04-01T03:22:46Z

# Description

<!-- Please include a summary of the changes towards the related issue. Please also  include relevant context. List any dependencies that are required for this change. -->

This pull request creates all the schemas that is listed in the documentation.

Also introduces type changes to use `enum` instead of `type`. 

Created a `common` folder for future. `fields` includes a new `date-time` field, this field just takes in a hour and minute section. Also created tests for this. 

Fixes #12 

## Type of change
<!-- Please delete options that are not relevant. -->

- [ ] New feature (non-breaking change which adds functionality)
- [ ] This change requires a documentation update

# How Has This Been Tested?

- [x] Integration tests written (requires checks to pass)

## Checklist before requesting a review

- [x] My code follows the style guidelines of this project
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] I have added thorough tests that prove my fix is effective and that my feature works
- [x] I've requested a review from another user


Linked issue #12: [BACKEND] Implement Required backend schemas
**Is your feature request related to a problem? Please describe.**

In the old uabc-portal repo, the team had successfully implemented a working version of the database schema which is now documented at https://github.com/UoaWDCC/uabc-web/wiki/MongoDB-Architecture. You should refer to the existing models at `src/collections` and especially the documentation at for [fields](https://payloadcms.com/docs/fields/overview)

The task is to fill out the `src/collections` with all the documented schemas.

>[!NOTE]
>Keep in mind that some documents have relations to each other (such as a [`gameSession`](https://github.com/UoaWDCC/uabc-web/wiki/MongoDB-Architecture#gamesession-collection) having **many** [`booking` ](https://github.com/UoaWDCC/uabc-web/wiki/MongoDB-Architecture#booking-collection) documents. Refer to the documentation on [relationships](https://payloadcms.com/docs/fields/relationship)


**BEFORE MERGING**
- [ ] Integration test written for services
- [ ] [Schemas annotated](https://link.com) if adding new models
- [ ] Code generation run (*hint*: ``)
- [ ] Appropriate mocks created where possible
- [ ] PR Reviewed (For non-trivial changes)
- [ ] Changes tested after rebasing on master or merging in master (*hint*: `git fetch origin master:master`, then `git rebase master` or `git merge master`)
- [ ] All required PR checks passing


Diff:
diff --git a/.github/docs/pull_request_template.md b/.github/pull_request_template.md
similarity index 100%
rename from .github/docs/pull_request_template.md
rename to .github/pull_request_template.md
diff --git a/.gitignore b/.gitignore
index 1d952a053..651bdc65c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 .idea/
+.DS_Store
 
 # Logs
 logs
diff --git a/src/app/(frontend)/(dev)/account/client-page.tsx b/src/app/(frontend)/(dev)/account/client-page.tsx
index 4ec3594cd..829cd1245 100644
--- a/src/app/(frontend)/(dev)/account/client-page.tsx
+++ b/src/app/(frontend)/(dev)/account/client-page.tsx
@@ -9,7 +9,7 @@ import { BackNavigationBar } from '@/components/BackNavigationBar'
 import { TextInput } from '@/components/TextInput'
 import { Button } from '@/components/ui/button'
 import { cn } from '@/lib/utils'
-import type { PlayLevel } from '@/types/types'
+import { PlayLevel } from '@/types/types'
 
 interface ClientAccountPageProps {
   firstName: string
@@ -20,8 +20,6 @@ interface ClientAccountPageProps {
   member: boolean
 }
 
-const PLAY_LEVELS: PlayLevel[] = ['beginner', 'intermediate', 'advanced']
-
 const formSchema = z.object({
   firstName: z.string().min(1, 'Field is required'),
   lastName: z.string().min(1, 'Field is required'),
@@ -90,7 +88,7 @@ export default function ClientAccountPage({
             {/* Play Level Selector */}
             <h2 className="mb-2 text-lg font-bold">Play Level</h2>
             <div className="grid grid-cols-3 gap-2 rounded-md border border-tertiary p-2">
-              {PLAY_LEVELS.map((level) => (
+              {Object.values(PlayLevel).map((level) => (
                 <label key={level} className="flex items-center">
                   <input type="radio" value={level} className="hidden" {...register('playLevel')} />
                   <span
diff --git a/src/app/(frontend)/(dev)/account/page.tsx b/src/app/(frontend)/(dev)/account/page.tsx
index e79fc728b..689cdda18 100644
--- a/src/app/(frontend)/(dev)/account/page.tsx
+++ b/src/app/(frontend)/(dev)/account/page.tsx
@@ -4,7 +4,7 @@ import { redirect } from 'next/navigation'
 import type { CurrentUserProps } from '@/lib/hoc/withCurrentUser'
 import withCurrentUser from '@/lib/hoc/withCurrentUser'
 import { getUserFromId } from '@/services/user'
-import type { PlayLevel } from '@/types/types'
+import { PlayLevel } from '@/types/types'
 import ClientAccountPage from './client-page'
 
 export const metadata = {
@@ -20,7 +20,7 @@ async function AccountPage({ currentUser }: CurrentUserProps) {
     redirect('/auth/login')
   }
 
-  const playLevel: PlayLevel = user?.playLevel ?? 'beginner'
+  const playLevel: PlayLevel = user?.playLevel ?? PlayLevel.beginner
 
   return (
     <div className="flex h-dvh flex-col">
diff --git a/src/collections/Booking.ts b/src/collections/Booking.ts
new file mode 100644
index 000000000..59f5b5502
--- /dev/null
+++ b/src/collections/Booking.ts
@@ -0,0 +1,36 @@
+import { PlayLevel } from '@/types/types'
+import { CollectionConfig } from 'payload'
+
+export const Booking: CollectionConfig = {
+  slug: 'booking',
+  access: {},
+  fields: [
+    {
+      name: 'user',
+      type: 'relationship',
+      relationTo: 'user',
+      required: true,
+      admin: {
+        description: 'The user who made the booking',
+      },
+    },
+    {
+      name: 'gameSession',
+      type: 'relationship',
+      relationTo: 'gameSession',
+      required: true,
+      admin: {
+        description: 'The game session that was booked',
+      },
+    },
+    {
+      name: 'playerLevel',
+      type: 'select',
+      options: Object.values(PlayLevel),
+      required: true,
+      admin: {
+        description: 'The skill level of the player',
+      },
+    },
+  ],
+}
diff --git a/src/collections/GameSession.ts b/src/collections/GameSession.ts
new file mode 100644
index 000000000..3b15ce7ea
--- /dev/null
+++ b/src/collections/GameSession.ts
@@ -0,0 +1,66 @@
+import { CollectionConfig } from 'payload'
+
+export const GameSession: CollectionConfig = {
+  slug: 'gameSession',
+  access: {},
+  fields: [
+    {
+      name: 'gameSessionSchedule',
+      type: 'relationship',
+      relationTo: 'gameSessionSchedule',
+      required: false,
+      admin: {
+        description:
+          "The game session schedule for this game session. This is optional in case there is an extra schedule that doesn't require a schedule.",
+      },
+    },
+    {
+      name: 'semester',
+      type: 'relationship',
+      relationTo: 'semester',
+      required: true,
+      admin: {
+        description:
+          "The semester this game session belongs to. This is required because a game session can't exist without a semester.",
+      },
+    },
+    {
+      name: 'startTime',
+      type: 'date',
+      required: true,
+      admin: {
+        date: {
+          pickerAppearance: 'dayAndTime',
+        },
+        description: 'The start time of the game session',
+      },
+    },
+    {
+      name: 'endTime',
+      type: 'date',
+      required: true,
+      admin: {
+        date: {
+          pickerAppearance: 'dayAndTime',
+        },
+        description: 'The end time of the game session',
+      },
+    },
+    {
+      name: 'capacity',
+      type: 'number',
+      required: true,
+      admin: {
+        description: 'The number of players that can join this game session',
+      },
+    },
+    {
+      name: 'casualCapacity',
+      type: 'number',
+      required: true,
+      admin: {
+        description: 'The number of casual players that can join this game session',
+      },
+    },
+  ],
+}
diff --git a/src/collections/GameSessionSchedule.ts b/src/collections/GameSessionSchedule.ts
new file mode 100644
index 000000000..72d4e057d
--- /dev/null
+++ b/src/collections/GameSessionSchedule.ts
@@ -0,0 +1,36 @@
+import type { CollectionConfig } from 'payload'
+import { createTimeField } from '@/common/fields/date-time'
+
+export const GameSessionSchedule: CollectionConfig = {
+  slug: 'gameSessionSchedule',
+  access: {},
+  fields: [
+    {
+      name: 'semester',
+      type: 'relationship',
+      relationTo: 'semester',
+      required: true,
+      admin: {
+        description: 'The semester this game session schedule belongs to',
+      },
+    },
+    createTimeField('startTime', 'The start time of the game session'),
+    createTimeField('endTime', 'The end time of the game session'),
+    {
+      name: 'capacity',
+      type: 'number',
+      required: true,
+      admin: {
+        description: 'The number of players that can join this game session',
+      },
+    },
+    {
+      name: 'casualCapacity',
+      type: 'number',
+      required: true,
+      admin: {
+        description: 'The number of casual players that can join this game session',
+      },
+    },
+  ],
+}
diff --git a/src/collections/Semester.ts b/src/collections/Semester.ts
new file mode 100644
index 000000000..bd8cecfa8
--- /dev/null
+++ b/src/collections/Semester.ts
@@ -0,0 +1,60 @@
+import type { CollectionConfig } from 'payload'
+import { createTimeField } from '@/common/fields/date-time'
+import { Weekday } from '@/types/types'
+
+export const Semester: CollectionConfig = {
+  slug: 'semester',
+  access: {},
+  fields: [
+    {
+      name: 'name',
+      type: 'text',
+      required: true,
+      admin: {
+        description: 'The name of the semester',
+      },
+    },
+    {
+      name: 'startDate',
+      type: 'date',
+      required: true,
+      admin: {
+        description: 'The start date of the semester',
+      },
+    },
+    {
+      name: 'endDate',
+      type: 'date',
+      required: true,
+      admin: {
+        description: 'The end date of the semester',
+      },
+    },
+    {
+      name: 'breakStart',
+      type: 'date',
+      required: true,
+      admin: {
+        description: 'The start date of the break',
+      },
+    },
+    {
+      name: 'breakEnd',
+      type: 'date',
+      required: true,
+      admin: {
+        description: 'The end date of the break',
+      },
+    },
+    {
+      name: 'bookingOpenDay',
+      type: 'select',
+      required: true,
+      options: Object.values(Weekday),
+      admin: {
+        description: 'The day when booking opens for this semester',
+      },
+    },
+    createTimeField('bookingOpenTime', 'The time when booking opens for this semester'),
+  ],
+}
diff --git a/src/collections/User.ts b/src/collections/User.ts
new file mode 100644
index 000000000..99aab5efc
--- /dev/null
+++ b/src/collections/User.ts
@@ -0,0 +1,52 @@
+import { MembershipType } from '@/types/types'
+import type { CollectionConfig } from 'payload'
+
+export const User: CollectionConfig = {
+  slug: 'user',
+  access: {},
+  fields: [
+    {
+      name: 'firstName',
+      type: 'text',
+      required: true,
+      admin: {
+        description: 'The first name of the user',
+      },
+    },
+    {
+      name: 'lastName',
+      type: 'text',
+      required: true,
+      admin: {
+        description: 'The last name of the user',
+      },
+    },
+    {
+      name: 'role',
+      type: 'select',
+      required: true,
+      options: Object.values(MembershipType),
+      defaultValue: MembershipType.casual,
+      admin: {
+        description: 'The role of the user',
+      },
+    },
+    {
+      name: 'remainingSessions',
+      type: 'number',
+      required: false,
+      admin: {
+        description: 'The number of remaining sessions the user has',
+      },
+    },
+    {
+      name: 'image',
+      type: 'relationship',
+      relationTo: 'media',
+      required: false,
+      admin: {
+        description: 'The image of the user',
+      },
+    },
+  ],
+}
diff --git a/src/common/fields/date-time.ts b/src/common/fields/date-time.ts
new file mode 100644
index 000000000..0bab638ef
--- /dev/null
+++ b/src/common/fields/date-time.ts
@@ -0,0 +1,31 @@
+import { DateField } from 'payload'
+
+/**
+ * Creates a date field but only shows the time picker
+ * @param name The name of the date time field
+ * @param description The description of the date time field
+ * @returns {Field} The date time field
+ */
+export function createTimeField(name: string, description: string): DateField {
+  return {
+    name: name,
+    type: 'date',
+    required: true,
+    admin: {
+      date: {
+        pickerAppearance: 'timeOnly',
+      },
+      description: description,
+    },
+    hooks: {
+      beforeChange: [
+        (args) => {
+          const date = new Date(args.value)
+          // Ensure that the hours and minutes are retained by setting the date to 1970-01-01
+          const fixedDate = new Date(1970, 0, 1, date.getHours(), date.getMinutes())
+          return fixedDate
+        },
+      ],
+    },
+  }
+}
diff --git a/src/common/tests/date-time.test.ts b/src/common/tests/date-time.test.ts
new file mode 100644
index 000000000..35f034da8
--- /dev/null
+++ b/src/common/tests/date-time.test.ts
@@ -0,0 +1,47 @@
+import { describe, expect, it } from 'vitest'
+import { createTimeField } from '@/common/fields/date-time'
+import {
+  FieldAffectingData,
+  FieldHookArgs,
+  PayloadRequest,
+  SanitizedCollectionConfig,
+  SanitizedGlobalConfig,
+} from 'payload'
+import { RequestContext } from 'payload'
+
+describe('createTimeField', () => {
+  it('should set date to 1970-01-01', () => {
+    const field = createTimeField('test', 'test')
+    const testDate = new Date()
+
+    if (!field.hooks?.beforeChange) {
+      throw new Error('beforeChange hook in createTimeField is not defined')
+    }
+
+    const mockArgs: FieldHookArgs = {
+      value: testDate,
+      originalDoc: {},
+      req: {} as PayloadRequest,
+      data: {},
+      siblingData: {},
+      field: {} as FieldAffectingData,
+      collection: {} as SanitizedCollectionConfig,
+      context: {} as RequestContext,
+      global: {} as SanitizedGlobalConfig,
+      operation: 'create',
+      blockData: {},
+      indexPath: [],
+      path: [],
+      schemaPath: [],
+      siblingFields: [],
+    }
+
+    const processedField = field.hooks.beforeChange[0](mockArgs)
+
+    expect(processedField.getFullYear()).toBe(1970)
+    expect(processedField.getMonth()).toBe(0)
+    expect(processedField.getDate()).toBe(1)
+    expect(processedField.getHours()).toBe(testDate.getHours())
+    expect(processedField.getMinutes()).toBe(testDate.getMinutes())
+  })
+})
diff --git a/src/components/booking/sessions/ExpandedSessionCard/LevelSelector.tsx b/src/components/booking/sessions/ExpandedSessionCard/LevelSelector.tsx
index e5ca5a70c..9ae73ec29 100644
--- a/src/components/booking/sessions/ExpandedSessionCard/LevelSelector.tsx
+++ b/src/components/booking/sessions/ExpandedSessionCard/LevelSelector.tsx
@@ -6,7 +6,7 @@ import React from 'react'
 
 import { Drawer, DrawerContent, DrawerTrigger } from '@/components/ui/drawer'
 import { useCartStore } from '@/stores/useCartStore'
-import type { PlayLevel } from '@/types/types'
+import { PlayLevel } from '@/types/types'
 import { LevelSelectorButton } from './LevelSelectorButton'
 
 interface LevelSelectorProps {
@@ -14,8 +14,6 @@ interface LevelSelectorProps {
   selectedLevel?: PlayLevel
 }
 
-const PLAY_LEVELS: PlayLevel[] = ['beginner', 'intermediate', 'advanced']
-
 export const LevelSelector = ({ id, selectedLevel }: LevelSelectorProps) => {
   const updatePlayLevelById = useCartStore((state) => state.updatePlayLevelById)
 
@@ -29,7 +27,7 @@ export const LevelSelector = ({ id, selectedLevel }: LevelSelectorProps) => {
       <DrawerContent className="bg-neutral p-4 pb-10">
         <span className="mb-4 text-center font-medium">Please select a play level</span>
         <div className="grid grid-cols-3 gap-2 rounded-xl bg-white p-2">
-          {PLAY_LEVELS.map((playLevel, index) => (
+          {Object.values(PlayLevel).map((playLevel, index) => (
             <LevelSelectorButton
               key={index}
               name={playLevel}
diff --git a/src/components/booking/sessions/ExpandedSessionCard/LevelSelectorButton.tsx b/src/components/booking/sessions/ExpandedSessionCard/LevelSelectorButton.tsx
index 60babbbd6..b8377136b 100644
--- a/src/components/booking/sessions/ExpandedSessionCard/LevelSelectorButton.tsx
+++ b/src/components/booking/sessions/ExpandedSessionCard/LevelSelectorButton.tsx
@@ -1,7 +1,7 @@
 import React from 'react'
 
 import { cn } from '@/lib/utils'
-import type { PlayLevel } from '@/types/types'
+import { PlayLevel } from '@/types/types'
 
 interface LevelSelectorButtonProps {
   name?: PlayLevel
diff --git a/src/hooks/mutations/booking.ts b/src/hooks/mutations/booking.ts
index a3114e6c5..f0110a150 100644
--- a/src/hooks/mutations/booking.ts
+++ b/src/hooks/mutations/booking.ts
@@ -1,6 +1,6 @@
 import { useMutation } from '@tanstack/react-query'
 
-import type { PlayLevel } from '@/types/types'
+import { PlayLevel } from '@/types/types'
 
 type Booking = {
   gameSessionId: number
diff --git a/src/payload-types.ts b/src/payload-types.ts
index 4826aa7b9..90124fec5 100644
--- a/src/payload-types.ts
+++ b/src/payload-types.ts
@@ -68,7 +68,12 @@ export interface Config {
   blocks: {};
   collections: {
     admins: Admin;
+    user: User;
     media: Media;
+    semester: Semester;
+    gameSessionSchedule: GameSessionSchedule;
+    gameSession: GameSession;
+    booking: Booking;
     'payload-locked-documents': PayloadLockedDocument;
     'payload-preferences': PayloadPreference;
     'payload-migrations': PayloadMigration;
@@ -76,7 +81,12 @@ export interface Config {
   collectionsJoins: {};
   collectionsSelect: {
     admins: AdminsSelect<false> | AdminsSelect<true>;
+    user: UserSelect<false> | UserSelect<true>;
     media: MediaSelect<false> | MediaSelect<true>;
+    semester: SemesterSelect<false> | SemesterSelect<true>;
+    gameSessionSchedule: GameSessionScheduleSelect<false> | GameSessionScheduleSelect<true>;
+    gameSession: GameSessionSelect<false> | GameSessionSelect<true>;
+    booking: BookingSelect<false> | BookingSelect<true>;
     'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
     'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
     'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
@@ -130,6 +140,35 @@ export interface Admin {
   lockUntil?: string | null;
   password?: string | null;
 }
+/**
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "user".
+ */
+export interface User {
+  id: string;
+  /**
+   * The first name of the user
+   */
+  firstName: string;
+  /**
+   * The last name of the user
+   */
+  lastName: string;
+  /**
+   * The role of the user
+   */
+  role: 'member' | 'casual';
+  /**
+   * The number of remaining sessions the user has
+   */
+  remainingSessions?: number | null;
+  /**
+   * The image of the user
+   */
+  image?: (string | null) | Media;
+  updatedAt: string;
+  createdAt: string;
+}
 /**
  * This interface was referenced by `Config`'s JSON-Schema
  * via the `definition` "media".
@@ -149,6 +188,126 @@ export interface Media {
   focalX?: number | null;
   focalY?: number | null;
 }
+/**
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "semester".
+ */
+export interface Semester {
+  id: string;
+  /**
+   * The name of the semester
+   */
+  name: string;
+  /**
+   * The start date of the semester
+   */
+  startDate: string;
+  /**
+   * The end date of the semester
+   */
+  endDate: string;
+  /**
+   * The start date of the break
+   */
+  breakStart: string;
+  /**
+   * The end date of the break
+   */
+  breakEnd: string;
+  /**
+   * The day when booking opens for this semester
+   */
+  bookingOpenDay: 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday' | 'sunday';
+  /**
+   * The time when booking opens for this semester
+   */
+  bookingOpenTime: string;
+  updatedAt: string;
+  createdAt: string;
+}
+/**
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "gameSessionSchedule".
+ */
+export interface GameSessionSchedule {
+  id: string;
+  /**
+   * The semester this game session schedule belongs to
+   */
+  semester: string | Semester;
+  /**
+   * The start time of the game session
+   */
+  startTime: string;
+  /**
+   * The end time of the game session
+   */
+  endTime: string;
+  /**
+   * The number of players that can join this game session
+   */
+  capacity: number;
+  /**
+   * The number of casual players that can join this game session
+   */
+  casualCapacity: number;
+  updatedAt: string;
+  createdAt: string;
+}
+/**
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "gameSession".
+ */
+export interface GameSession {
+  id: string;
+  /**
+   * The game session schedule for this game session. This is optional in case there is an extra schedule that doesn't require a schedule.
+   */
+  gameSessionSchedule?: (string | null) | GameSessionSchedule;
+  /**
+   * The semester this game session belongs to. This is required because a game session can't exist without a semester.
+   */
+  semester: string | Semester;
+  /**
+   * The start time of the game session
+   */
+  startTime: string;
+  /**
+   * The end time of the game session
+   */
+  endTime: string;
+  /**
+   * The number of players that can join this game session
+   */
+  capacity: number;
+  /**
+   * The number of casual players that can join this game session
+   */
+  casualCapacity: number;
+  updatedAt: string;
+  createdAt: string;
+}
+/**
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "booking".
+ */
+export interface Booking {
+  id: string;
+  /**
+   * The user who made the booking
+   */
+  user: string | User;
+  /**
+   * The game session that was booked
+   */
+  gameSession: string | GameSession;
+  /**
+   * The skill level of the player
+   */
+  playerLevel: 'beginner' | 'intermediate' | 'advanced';
+  updatedAt: string;
+  createdAt: string;
+}
 /**
  * This interface was referenced by `Config`'s JSON-Schema
  * via the `definition` "payload-locked-documents".
@@ -160,9 +319,29 @@ export interface PayloadLockedDocument {
         relationTo: 'admins';
         value: string | Admin;
       } | null)
+    | ({
+        relationTo: 'user';
+        value: string | User;
+      } | null)
     | ({
         relationTo: 'media';
         value: string | Media;
+      } | null)
+    | ({
+        relationTo: 'semester';
+        value: string | Semester;
+      } | null)
+    | ({
+        relationTo: 'gameSessionSchedule';
+        value: string | GameSessionSchedule;
+      } | null)
+    | ({
+        relationTo: 'gameSession';
+        value: string | GameSession;
+      } | null)
+    | ({
+        relationTo: 'booking';
+        value: string | Booking;
       } | null);
   globalSlug?: string | null;
   user: {
@@ -221,6 +400,19 @@ export interface AdminsSelect<T extends boolean = true> {
   loginAttempts?: T;
   lockUntil?: T;
 }
+/**
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "user_select".
+ */
+export interface UserSelect<T extends boolean = true> {
+  firstName?: T;
+  lastName?: T;
+  role?: T;
+  remainingSessions?: T;
+  image?: T;
+  updatedAt?: T;
+  createdAt?: T;
+}
 /**
  * This interface was referenced by `Config`'s JSON-Schema
  * via the `definition` "media_select".
@@ -239,6 +431,59 @@ export interface MediaSelect<T extends boolean = true> {
   focalX?: T;
   focalY?: T;
 }
+/**
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "semester_select".
+ */
+export interface SemesterSelect<T extends boolean = true> {
+  name?: T;
+  startDate?: T;
+  endDate?: T;
+  breakStart?: T;
+  breakEnd?: T;
+  bookingOpenDay?: T;
+  bookingOpenTime?: T;
+  updatedAt?: T;
+  createdAt?: T;
+}
+/**
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "gameSessionSchedule_select".
+ */
+export interface GameSessionScheduleSelect<T extends boolean = true> {
+  semester?: T;
+  startTime?: T;
+  endTime?: T;
+  capacity?: T;
+  casualCapacity?: T;
+  updatedAt?: T;
+  createdAt?: T;
+}
+/**
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "gameSession_select".
+ */
+export interface GameSessionSelect<T extends boolean = true> {
+  gameSessionSchedule?: T;
+  semester?: T;
+  startTime?: T;
+  endTime?: T;
+  capacity?: T;
+  casualCapacity?: T;
+  updatedAt?: T;
+  createdAt?: T;
+}
+/**
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "booking_select".
+ */
+export interface BookingSelect<T extends boolean = true> {
+  user?: T;
+  gameSession?: T;
+  playerLevel?: T;
+  updatedAt?: T;
+  createdAt?: T;
+}
 /**
  * This interface was referenced by `Config`'s JSON-Schema
  * via the `definition` "payload-locked-documents_select".
diff --git a/src/payload.config.ts b/src/payload.config.ts
index 22076da23..f1ad72477 100644
--- a/src/payload.config.ts
+++ b/src/payload.config.ts
@@ -8,19 +8,31 @@ import { fileURLToPath } from 'url'
 import sharp from 'sharp'
 
 import { Admin } from './collections/Admin'
+import { User } from './collections/User'
 import { Media } from './collections/Media'
+import { Semester } from './collections/Semester'
+import { GameSessionSchedule } from './collections/GameSessionSchedule'
+import { GameSession } from './collections/GameSession'
+import { Booking } from './collections/Booking'
 
 const filename = fileURLToPath(import.meta.url)
 const dirname = path.dirname(filename)
 
 export default buildConfig({
+  routes: {
+    admin: '/payload/admin',
+    api: '/payload/api',
+    graphQL: '/payload/graphql',
+    graphQLPlayground: '/payload/graphql-playground',
+  },
   admin: {
     user: Admin.slug,
     importMap: {
       baseDir: path.resolve(dirname),
+      importMapFile: path.resolve(dirname) + '/app/payload/admin/importMap.js',
     },
   },
-  collections: [Admin, Media],
+  collections: [Admin, User, Media, Semester, GameSessionSchedule, GameSession, Booking],
   editor: lexicalEditor(),
   secret: process.env.PAYLOAD_SECRET || '',
   typescript: {
diff --git a/src/services/stub-user.ts b/src/services/stub-user.ts
index 3accc715c..38331330e 100644
--- a/src/services/stub-user.ts
+++ b/src/services/stub-user.ts
@@ -1,3 +1,5 @@
+import { PlayLevel } from '@/types/types'
+
 /**
  * TODO: Delete this file and implement your own user service
  */
@@ -9,7 +11,7 @@ export const stubUser = (id: string) => {
     member: true,
     prepaidSessions: 69,
     email: 'not@implemented.com',
-    playLevel: 'beginner',
+    playLevel: PlayLevel.beginner,
   } as const
 }
 
diff --git a/src/stores/useCartStore.ts b/src/stores/useCartStore.ts
index df4fcc840..aa716ba3f 100644
--- a/src/stores/useCartStore.ts
+++ b/src/stores/useCartStore.ts
@@ -1,7 +1,7 @@
 import { create } from 'zustand'
 
 import type { CartGameSession } from '@/types/game-session'
-import type { PlayLevel } from '@/types/types'
+import { PlayLevel } from '@/types/types'
 
 interface CartState {
   cart: CartGameSession[]
diff --git a/src/types/types.ts b/src/types/types.ts
index 5985b0996..799d982df 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -1,10 +1,20 @@
-export type Weekday =
-  | 'Monday'
-  | 'Tuesday'
-  | 'Wednesday'
-  | 'Thursday'
-  | 'Friday'
-  | 'Saturday'
-  | 'Sunday'
+export enum MembershipType {
+  member = 'member',
+  casual = 'casual',
+}
 
-export type PlayLevel = 'beginner' | 'intermediate' | 'advanced'
+export enum Weekday {
+  monday = 'monday',
+  tuesday = 'tuesday',
+  wednesday = 'wednesday',
+  thursday = 'thursday',
+  friday = 'friday',
+  saturday = 'saturday',
+  sunday = 'sunday',
+}
+
+export enum PlayLevel {
+  beginner = 'beginner',
+  intermediate = 'intermediate',
+  advanced = 'advanced',
+}


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR implements the required backend schemas by adding several new collection configurations and corresponding TypeScript type definitions, while updating the Payload configuration to include the new collections.  
- Added new schemas for User, Semester, GameSessionSchedule, GameSession, and Booking  
- Updated the configuration and type definitions to integrate the new collections  
- Made minor adjustments to the import map

### Reviewed Changes

Copilot reviewed 9 out of 12 changed files in this pull request and generated 1 comment.

<details>
<summary>Show a summary per file</summary>

| File                                  | Description                                                  |
| ------------------------------------- | ------------------------------------------------------------ |
| src/payload.config.ts                 | Updated collections array to include new schemas             |
| src/payload-types.ts                  | Added interfaces and selection types for the new schemas     |
| src/collections/User.ts               | New collection configuration for user                        |
| src/collections/Semester.ts           | New collection configuration for semester, with a note on time field   |
| src/collections/GameSessionSchedule.ts| New collection configuration for game session schedule         |
| src/collections/GameSession.ts        | New collection configuration for game session                |
| src/collections/Booking.ts            | New collection configuration for booking                       |
| src/app/(payload)/admin/importMap.js   | Adjusted import map export declaration                         |
</details>


<details>
<summary>Files not reviewed (3)</summary>

* **.prettierignore**: Language not supported
* **package.json**: Language not supported
* **pnpm-lock.yaml**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (2)</summary>

**src/app/(payload)/admin/importMap.js:1**
* Remove the duplicate declaration of 'importMap' to avoid confusion; ensure it is only declared once.
```
export const importMap = {}
```
**src/payload-types.ts:319**
* [nitpick] Consider referencing the PlayerLevel enum from src/collections/Booking.ts for consistency between the collection configuration and its TypeScript interface.
```
playerLevel: 'beginner' | 'intermediate' | 'advanced';
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-30T11:37:22Z
user: login: choden-dev
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T05:03:40Z
user: login: jji05
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T05:58:49Z
user: login: choden-dev
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T07:06:31Z
user: login: choden-dev
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T07:07:04Z
user: login: jji05
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T11:09:58Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This pull request implements the required backend schemas according to the documentation and integrates them into the overall payload configuration.  
- Updated the Weekday type to an enum with lowercase values.  
- Added new collection configurations for User, Semester, GameSessionSchedule, GameSession, and Booking.  
- Extended payload types and selectors to support the new collections.

### Reviewed Changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 1 comment.

<details>
<summary>Show a summary per file</summary>

| File                                | Description                                                             |
| ----------------------------------- | ----------------------------------------------------------------------- |
| src/types/types.ts                  | Changed Weekday from a string union to an enum for consistent usage.    |
| src/payload.config.ts               | Added new collections and updated routes to support new backend schemas.|
| src/payload-types.ts                | Added new type definitions and selectors for the introduced collections.|
| src/collections/fields/dateTime.ts  | Created a utility function for time-only date fields with a hook fix.   |
| src/collections/User.ts             | Added a new collection for Users with basic fields and role options.    |
| src/collections/Semester.ts         | Added a new collection for Semester with date and time fields.          |
| src/collections/GameSessionSchedule.ts | Added a new collection for GameSessionSchedule with time fields and capacity settings. |
| src/collections/GameSession.ts      | Added a new collection for GameSession with relationships to schedule.  |
| src/collections/Booking.ts          | Added a new collection for Booking with relationship fields and select options.|
</details>


<details>
<summary>Files not reviewed (1)</summary>

* **.prettierignore**: Language not supported
</details>



state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-31T22:32:18Z
user: login: choden-dev
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T23:12:31Z
user: login: jji05
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T23:13:54Z
user: login: jji05
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T23:31:02Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This pull request implements the required backend schemas and updates several type definitions to use enums instead of string unions. Key changes include converting Weekday and PlayLevel types to enums, adding new collections (User, Semester, GameSessionSchedule, GameSession, Booking), and updating related imports and usages across the codebase.

### Reviewed Changes

Copilot reviewed 19 out of 20 changed files in this pull request and generated no comments.

<details>
<summary>Show a summary per file</summary>

| File | Description |
| ---- | ----------- |
| src/types/types.ts | Converted string union types for Weekday and PlayLevel to enums |
| src/stores/useCartStore.ts | Updated PlayLevel import to use the enum |
| src/services/stub-user.ts | Updated playLevel assignment to enum value |
| src/payload.config.ts | Included new collections and added custom routes |
| src/payload-types.ts | Added interfaces for new collections and updated relation mappings |
| src/hooks/mutations/booking.ts | Updated PlayLevel import to use the enum |
| src/components/booking/sessions/ExpandedSessionCard/*.tsx | Updated PlayLevel usage via enum in level selectors |
| src/common/tests/date-time.test.ts | Added tests for the createTimeField hook |
| src/common/fields/date-time.ts | Introduced createTimeField to generate time-only date fields |
| src/collections/* | New collection definitions for User, Semester, GameSessionSchedule, GameSession, and Booking |
| src/app/(frontend)/(dev)/account/*.tsx | Updated PlayLevel usage to rely on the enum |
</details>


<details>
<summary>Files not reviewed (1)</summary>

* **.prettierignore**: Language not supported
</details>



state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-31T23:34:13Z
user: login: choden-dev
body: when you're done best to post some screenshots etc
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-01T01:07:32Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR implements the required backend schemas by introducing new collections and updating type definitions. Key changes include:
- Replacing union types with enums for Weekday and PlayLevel in src/types/types.ts.
- Updating all references to PlayLevel to import the enum instead of a type across multiple files.
- Adding new collections and corresponding payload configurations for User, Semester, GameSessionSchedule, GameSession, and Booking.

### Reviewed Changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated no comments.

<details>
<summary>Show a summary per file</summary>

| File                                                          | Description                                                               |
| ------------------------------------------------------------- | ------------------------------------------------------------------------- |
| src/types/types.ts                                            | Updated Weekday, PlayLevel, and added MembershipType enums.               |
| src/stores/useCartStore.ts                                      | Changed PlayLevel import to use enum.                                     |
| src/services/stub-user.ts                                       | Updated playLevel assignment to use enum.                                 |
| src/payload.config.ts                                          | Added new collections and updated importMapFile configuration.            |
| src/payload-types.ts                                           | Added interfaces for new collections and corresponding select types.      |
| src/hooks/mutations/booking.ts                                 | Updated PlayLevel import to use enum.                                     |
| src/components/booking/sessions/ExpandedSessionCard/LevelSelectorButton.tsx | Updated PlayLevel import to use enum.                                     |
| src/components/booking/sessions/ExpandedSessionCard/LevelSelector.tsx | Replaced a hardcoded array with Object.values(PlayLevel).                 |
| src/common/tests/date-time.test.ts                             | Added tests for createTimeField to validate time-only behavior.           |
| src/common/fields/date-time.ts                                 | Introduced createTimeField to create a date field with time-only picker.    |
| src/collections/User.ts                                         | Created a new collection for User with required fields.                   |
| src/collections/Semester.ts                                     | Created a new Semester collection utilizing Weekday enum for bookingOpenDay.|
| src/collections/GameSessionSchedule.ts                          | Added GameSessionSchedule collection with time fields via createTimeField.  |
| src/collections/GameSession.ts                                  | Added GameSession collection with relationship fields.                    |
| src/collections/Booking.ts                                      | Added Booking collection using PlayLevel enum for playerLevel field.        |
| src/app/(frontend)/(dev)/account/page.tsx                       | Updated PlayLevel default to use enum.                                    |
| src/app/(frontend)/(dev)/account/client-page.tsx                | Replaced hardcoded PLAY_LEVELS array with Object.values(PlayLevel).         |
</details>



<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**src/payload.config.ts:32**
* [nitpick] Consider using path.join(dirname, 'app', 'payload', 'admin', 'importMap.js') for better cross-platform path consistency instead of string concatenation.
```
importMapFile: path.resolve(dirname) + '/app/payload/admin/importMap.js',
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T03:18:07Z

Review comments:
diff_hunk: @@ -0,0 +1,76 @@
+import type { CollectionConfig } from 'payload'
+
+export enum Weekday {
+  monday = 'monday',
+  tuesday = 'tuesday',
+  wednesday = 'wednesday',
+  thursday = 'thursday',
+  friday = 'friday',
+  saturday = 'saturday',
+  sunday = 'sunday',
+}
+
+export const Semester: CollectionConfig = {
+  slug: 'semester',
+  access: {},
+  fields: [
+    {
+      name: 'name',
+      type: 'text',
+      required: true,
+      admin: {
+        description: 'The name of the semester',
+      },
+    },
+    {
+      name: 'startDate',
+      type: 'date',
+      required: true,
+      admin: {
+        description: 'The start date of the semester',
+      },
+    },
+    {
+      name: 'endDate',
+      type: 'date',
+      required: true,
+      admin: {
+        description: 'The end date of the semester',
+      },
+    },
+    {
+      name: 'breakStart',
+      type: 'date',
+      required: true,
+      admin: {
+        description: 'The start date of the break',
+      },
+    },
+    {
+      name: 'breakEnd',
+      type: 'date',
+      required: true,
+      admin: {
+        description: 'The end date of the break',
+      },
+    },
+    {
+      name: 'bookingOpenDay',
+      type: 'select',
+      required: true,
+      options: Object.values(Weekday),
+      admin: {
+        description: 'The day when booking opens for this semester',
+      },
+    },
+    // Tech debt: This should be a time field
path: src/collections/Semester.ts
user: login: Copilot
body: Consider using a dedicated time field type (if available) instead of a text field for 'bookingOpenTime' to better capture time-related data.
created_at: 2025-03-30T11:37:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,117 @@
+import { CollectionConfig } from 'payload'
+
+export const GameSession: CollectionConfig = {
+  slug: 'gameSession',
+  access: {},
+  fields: [
+    {
+      name: 'bookingOpen',
+      type: 'date',
+      required: true,
+      admin: {
+        date: {
+          pickerAppearance: 'timeOnly',
+        },
+        description: 'The time when booking opens for this game session',
+      },
+      hooks: {
+        beforeChange: [
+          (args) => {
+            const date = new Date(args.value)
+            const totalMiliseconds =
+              date.getHours() * 60 * 60 * 1000 + date.getMinutes() * 60 * 1000
+            return totalMiliseconds
+          },
+        ],
+      },
+    },
+    {
+      name: 'bookingClose',
+      type: 'date',
+      required: true,
+      admin: {
+        date: {
+          pickerAppearance: 'timeOnly',
+        },
+        description: 'The time when booking closes for this game session',
+      },
+      hooks: {
+        beforeChange: [
+          (args) => {
+            const date = new Date(args.value)
+            const totalMiliseconds =
+              date.getHours() * 60 * 60 * 1000 + date.getMinutes() * 60 * 1000
+            return totalMiliseconds
+          },
+        ],
+      },
+    },
+    {
+      name: 'startTime',
+      type: 'date',
+      required: true,
+      admin: {
+        date: {
+          pickerAppearance: 'timeOnly',
+        },
+        description: 'The start time of the game session',
+      },
+      hooks: {
+        beforeChange: [
+          (args) => {
+            const date = new Date(args.value)
+            const totalMiliseconds =
+              date.getHours() * 60 * 60 * 1000 + date.getMinutes() * 60 * 1000
+            return totalMiliseconds
+          },
+        ],
+      },
+    },
+    {
+      name: 'endTime',
+      type: 'date',
+      required: true,
+      admin: {
+        date: {
+          pickerAppearance: 'timeOnly',
+        },
+        description: 'The end time of the game session',
+      },
+      hooks: {
+        beforeChange: [
+          (args) => {
+            const date = new Date(args.value)
+            const totalMiliseconds =
+              date.getHours() * 60 * 60 * 1000 + date.getMinutes() * 60 * 1000
+            return totalMiliseconds
+          },
+        ],
+      },
+    },
path: src/collections/GameSession.ts
user: login: choden-dev
body: I personally don't think this hack is worth it, why not just use a `datetime` for both start and end time and remove the date field? (the date is guaranteed to be the same as start and end date)
created_at: 2025-03-31T05:00:44Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,117 @@
+import type { CollectionConfig } from 'payload'
+
+export const GameSessionSchedule: CollectionConfig = {
+  slug: 'gameSessionSchedule',
+  access: {},
+  fields: [
+    {
+      name: 'bookingOpen',
+      type: 'date',
+      required: true,
+      admin: {
+        date: {
+          pickerAppearance: 'timeOnly',
+        },
+        description: 'The time when booking opens for this game session',
+      },
+      hooks: {
+        beforeChange: [
+          (args) => {
+            const date = new Date(args.value)
+            const totalMiliseconds =
+              date.getHours() * 60 * 60 * 1000 + date.getMinutes() * 60 * 1000
+            return totalMiliseconds
+          },
+        ],
+      },
+    },
+    {
+      name: 'bookingClose',
+      type: 'date',
+      required: true,
+      admin: {
+        date: {
+          pickerAppearance: 'timeOnly',
+        },
+        description: 'The time when booking closes for this game session',
+      },
+      hooks: {
+        beforeChange: [
+          (args) => {
+            const date = new Date(args.value)
+            const totalMiliseconds =
+              date.getHours() * 60 * 60 * 1000 + date.getMinutes() * 60 * 1000
+            return totalMiliseconds
+          },
+        ],
+      },
+    },
path: src/collections/GameSessionSchedule.ts
user: login: choden-dev
body: Again here is kind of a hack too, consider omitting `date`
created_at: 2025-03-31T05:01:35Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,88 @@
+import type { CollectionConfig } from 'payload'
+
+export enum Weekday {
+  monday = 'monday',
+  tuesday = 'tuesday',
+  wednesday = 'wednesday',
+  thursday = 'thursday',
+  friday = 'friday',
+  saturday = 'saturday',
+  sunday = 'sunday',
+}
path: src/collections/Semester.ts
user: login: choden-dev
body: They already have this: https://github.com/UoaWDCC/uabc-web/blob/master/src/types/types.ts
created_at: 2025-03-31T05:03:26Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,117 @@
+import { CollectionConfig } from 'payload'
+
+export const GameSession: CollectionConfig = {
+  slug: 'gameSession',
+  access: {},
+  fields: [
+    {
+      name: 'bookingOpen',
+      type: 'date',
+      required: true,
+      admin: {
+        date: {
+          pickerAppearance: 'timeOnly',
+        },
+        description: 'The time when booking opens for this game session',
+      },
+      hooks: {
+        beforeChange: [
+          (args) => {
+            const date = new Date(args.value)
+            const totalMiliseconds =
+              date.getHours() * 60 * 60 * 1000 + date.getMinutes() * 60 * 1000
+            return totalMiliseconds
+          },
+        ],
+      },
+    },
+    {
+      name: 'bookingClose',
+      type: 'date',
+      required: true,
+      admin: {
+        date: {
+          pickerAppearance: 'timeOnly',
+        },
+        description: 'The time when booking closes for this game session',
+      },
+      hooks: {
+        beforeChange: [
+          (args) => {
+            const date = new Date(args.value)
+            const totalMiliseconds =
+              date.getHours() * 60 * 60 * 1000 + date.getMinutes() * 60 * 1000
+            return totalMiliseconds
+          },
+        ],
+      },
+    },
+    {
+      name: 'startTime',
+      type: 'date',
+      required: true,
+      admin: {
+        date: {
+          pickerAppearance: 'timeOnly',
+        },
+        description: 'The start time of the game session',
+      },
+      hooks: {
+        beforeChange: [
+          (args) => {
+            const date = new Date(args.value)
+            const totalMiliseconds =
+              date.getHours() * 60 * 60 * 1000 + date.getMinutes() * 60 * 1000
+            return totalMiliseconds
+          },
+        ],
+      },
+    },
+    {
+      name: 'endTime',
+      type: 'date',
+      required: true,
+      admin: {
+        date: {
+          pickerAppearance: 'timeOnly',
+        },
+        description: 'The end time of the game session',
+      },
+      hooks: {
+        beforeChange: [
+          (args) => {
+            const date = new Date(args.value)
+            const totalMiliseconds =
+              date.getHours() * 60 * 60 * 1000 + date.getMinutes() * 60 * 1000
+            return totalMiliseconds
+          },
+        ],
+      },
+    },
path: src/collections/GameSession.ts
user: login: jji05
body: That's a great suggestion, although the `startTime` and `endTime` is used to handle when a venue booking starts and ends, i.e. a booking on Tuesday from 1pm-3pm.

I've gotten around making a fields folder that contains a generic `dateTime` field type. 
created_at: 2025-03-31T05:58:49Z
author_association: MEMBER
diff_hunk: @@ -1 +1,5 @@
-export const importMap = {}
path: src/app/payload/admin/importMap.js
user: login: choden-dev
body: I think this is actually your IDE playing up
created_at: 2025-03-31T07:06:08Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,117 @@
+import { CollectionConfig } from 'payload'
+
+export const GameSession: CollectionConfig = {
+  slug: 'gameSession',
+  access: {},
+  fields: [
+    {
+      name: 'bookingOpen',
+      type: 'date',
+      required: true,
+      admin: {
+        date: {
+          pickerAppearance: 'timeOnly',
+        },
+        description: 'The time when booking opens for this game session',
+      },
+      hooks: {
+        beforeChange: [
+          (args) => {
+            const date = new Date(args.value)
+            const totalMiliseconds =
+              date.getHours() * 60 * 60 * 1000 + date.getMinutes() * 60 * 1000
+            return totalMiliseconds
+          },
+        ],
+      },
+    },
+    {
+      name: 'bookingClose',
+      type: 'date',
+      required: true,
+      admin: {
+        date: {
+          pickerAppearance: 'timeOnly',
+        },
+        description: 'The time when booking closes for this game session',
+      },
+      hooks: {
+        beforeChange: [
+          (args) => {
+            const date = new Date(args.value)
+            const totalMiliseconds =
+              date.getHours() * 60 * 60 * 1000 + date.getMinutes() * 60 * 1000
+            return totalMiliseconds
+          },
+        ],
+      },
+    },
+    {
+      name: 'startTime',
+      type: 'date',
+      required: true,
+      admin: {
+        date: {
+          pickerAppearance: 'timeOnly',
+        },
+        description: 'The start time of the game session',
+      },
+      hooks: {
+        beforeChange: [
+          (args) => {
+            const date = new Date(args.value)
+            const totalMiliseconds =
+              date.getHours() * 60 * 60 * 1000 + date.getMinutes() * 60 * 1000
+            return totalMiliseconds
+          },
+        ],
+      },
+    },
+    {
+      name: 'endTime',
+      type: 'date',
+      required: true,
+      admin: {
+        date: {
+          pickerAppearance: 'timeOnly',
+        },
+        description: 'The end time of the game session',
+      },
+      hooks: {
+        beforeChange: [
+          (args) => {
+            const date = new Date(args.value)
+            const totalMiliseconds =
+              date.getHours() * 60 * 60 * 1000 + date.getMinutes() * 60 * 1000
+            return totalMiliseconds
+          },
+        ],
+      },
+    },
path: src/collections/GameSession.ts
user: login: choden-dev
body: sounds good
created_at: 2025-03-31T07:07:04Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,88 @@
+import type { CollectionConfig } from 'payload'
+
+export enum Weekday {
+  monday = 'monday',
+  tuesday = 'tuesday',
+  wednesday = 'wednesday',
+  thursday = 'thursday',
+  friday = 'friday',
+  saturday = 'saturday',
+  sunday = 'sunday',
+}
path: src/collections/Semester.ts
user: login: jji05
body: Sorted this by importing it. 
created_at: 2025-03-31T11:09:58Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,31 @@
+import { Field } from 'payload'
+
+/**
+ * Creates a date field but only shows the time picker
+ * @param name The name of the date time field
+ * @param description The description of the date time field
+ * @returns {Field} The date time field
+ */
+export function createTimeField(name: string, description: string): Field {
+  return {
+    name: name,
+    type: 'date',
+    required: true,
+    admin: {
+      date: {
+        pickerAppearance: 'timeOnly',
+      },
+      description: description,
+    },
+    hooks: {
+      beforeChange: [
+        (args) => {
+          const date = new Date(args.value)
+          // Ensure that the hours and mintues are retained by setting the date to 1970-01-01
path: src/collections/fields/dateTime.ts
user: login: Copilot
body: There is a typo in the comment: 'mintues' should be 'minutes'.
```suggestion
          // Ensure that the hours and minutes are retained by setting the date to 1970-01-01
```
created_at: 2025-03-31T22:32:17Z
author_association: CONTRIBUTOR
path: src/common/fields/dateTime.ts
user: login: choden-dev
body: nit: `snake-case` seems to be what we're using for file names, maybe have to document it in wiki
created_at: 2025-03-31T23:12:31Z
author_association: MEMBER
path: src/common/fields/dateTime.ts
user: login: jji05
body: Good catch! Will document in the wiki
created_at: 2025-03-31T23:13:54Z
author_association: MEMBER
path: src/common/fields/dateTime.ts
user: login: jji05
body: https://github.com/UoaWDCC/uabc-web/wiki/Styling-and-Convention
created_at: 2025-03-31T23:31:02Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,57 @@
+import type { CollectionConfig } from 'payload'
+
+export enum Roles {
+  member = 'member',
+  admin = 'admin',
path: src/collections/User.ts
user: login: choden-dev
body: We already have the `Admin` schema so this is not needed
created_at: 2025-04-01T01:06:10Z
author_association: MEMBER
diff_hunk: @@ -2,3 +2,4 @@ src/payload-types.ts
 pnpm-lock.yaml
 *.md
 .github/
+src/app/(payload)/admin/importMap.js
path: .prettierignore
user: login: choden-dev
body: not needed anymore
created_at: 2025-04-01T01:06:52Z
author_association: MEMBER

PR comments:
user: login: jji05
created_at: 2025-04-01T01:23:07Z
body: Collections:

<img width="1367" alt="Screenshot 2025-04-01 at 2 20 37 PM" src="https://github.com/user-attachments/assets/6535b2cf-b389-4db1-9804-5f6680c1b96b" />

Creating admin:
<img width="1352" alt="Screenshot 2025-04-01 at 2 20 54 PM" src="https://github.com/user-attachments/assets/3f704be3-8920-48e0-acfb-995d7270ee73" />

Creating user:
<img width="1348" alt="Screenshot 2025-04-01 at 2 21 12 PM" src="https://github.com/user-attachments/assets/0932ddba-c794-44f5-bdc0-c1079a7062ec" />

Creating media:
<img width="1350" alt="Screenshot 2025-04-01 at 2 21 24 PM" src="https://github.com/user-attachments/assets/06e75ead-2fc8-4acf-b832-488cea875232" />

Creating a semester:
<img width="1196" alt="Screenshot 2025-04-01 at 4 11 03 PM" src="https://github.com/user-attachments/assets/1a4649d6-200d-4347-a92a-bd9754e0fb29" />

Creating a game session schedule:
<img width="1204" alt="Screenshot 2025-04-01 at 4 11 21 PM" src="https://github.com/user-attachments/assets/5281daa0-eceb-45ed-8a47-7282ce0f5508" />

Creating a game session:
<img width="1177" alt="Screenshot 2025-04-01 at 4 12 02 PM" src="https://github.com/user-attachments/assets/42d86ef9-88ac-431b-9983-2160fe51c421" />

Creating a booking:
<img width="1153" alt="Screenshot 2025-04-01 at 4 12 18 PM" src="https://github.com/user-attachments/assets/46c8ecce-803e-4022-8fd9-9a5e956a3b9e" />

author_association: MEMBER
user: login: jji05
created_at: 2025-04-01T01:27:03Z
body: > when you're done best to post some screenshots etc

Great catch! That made me realised that the `GameSessionSchedule` collection didn't include a week day, as well as a couple more issues in the collections. 
author_association: MEMBER
user: login: jji05
created_at: 2025-04-01T03:15:25Z
body: Also fixed importMap in `payload.config.ts` to use a `importMapFile` as by default, Payload looks inside the `(payload)` folder which has been renamed to `payload`. 
author_association: MEMBER
user: login: zlrkw11
created_at: 2025-04-01T04:20:02Z
body: **BABE JI and DADDY CHO** 😭😭😭😭😭😭😭😭😭😭😭😭
author_association: MEMBER