Skip to content

Commit

Permalink
feat: availability page functionality (#85)
Browse files Browse the repository at this point in the history
* feat: ✨ fix auth

* fix: 🐛 add relation info for meeting and availabilities

* feat: ✨ insert/update availabilities

* feat: ✨ read and display availability from DB

* feat: ✨ (stable) saving and loading availability

* feat: ✨ save guest availability

* fix: 🐛 add in auth_methods, keep schema as notNull()

* Revert "fix: 🐛 add in auth_methods, keep schema as notNull()"

This reverts commit 9320a05.

* Revert "Revert "fix: 🐛 add in auth_methods, keep schema as notNull()""

This reverts commit d8fb1d7.

* fix: 🐛 revert schema changes (for real)

* feat: ✨ correct err message for duplicate username

* feat: ✨ get guest login to semi-work

* chore: 🔧 cleanup logs

* refactor: ♻️ cleanup guest form submission in login modal

* refactor: ♻️ cleanup api calls and error handling in login modal

* refactor: ♻️ clean up how meeting id and data is passed around

* docs: 📚️ add note about .submit()

* chore: 🔧 misc. cleanup

* refactor: ♻️ correct var names

* docs: 📚️ add in meeting creation util

* feat: ✨ create meeting dates w/ meeting, fix retrieval of availabilities

* fix: 🐛 reference isAvailable in reactive block

* fix: 🐛 misc. fixes

* fix: 🐛 unblock insert

* feat: ✨ wrap multi-insert with transaction

* fix: 🐛 simplify type

* fix: 🐛 match parse to ZotDate[]

* fix: 🐛 unasync map func

* chore: 🔧 use pgTable

---------

Co-authored-by: Sean Fong <[email protected]>
  • Loading branch information
KevinWu098 and seancfong authored May 10, 2024
1 parent d589378 commit cd42556
Show file tree
Hide file tree
Showing 19 changed files with 599 additions and 120 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@
"cz-conventional-changelog": "3.3.0",
"daisyui": "^4.6.2",
"devmoji": "2.3.0",
"drizzle-kit": "0.20.14",
"dotenv": "^16.4.5",
"drizzle-kit": "0.20.17",
"eslint": "8.54.0",
"eslint-config-prettier": "9.0.0",
"eslint-plugin-import": "2.29.0",
Expand Down
39 changes: 28 additions & 11 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/lib/components/availability/AvailabilityBlock.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
$: {
updateBlockColor(selectionState);
isAvailable;
}
</script>

Expand Down
111 changes: 71 additions & 40 deletions src/lib/components/availability/LoginModal.svelte
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
<script lang="ts">
import type { ActionResult } from "@sveltejs/kit";
import { superForm } from "sveltekit-superforms/client";
import { guestSchema, userSchema } from "$lib/config/zod-schemas";
import { isEditingAvailability, isStateUnsaved } from "$lib/stores/availabilityStores";
import type { LoginModalProps } from "$lib/types/availability";
import type { PageData } from "../../../routes/availability/$types";
import { deserialize } from "$app/forms";
import { userSchema } from "$lib/config/zod-schemas";
import {
isEditingAvailability,
isStateUnsaved,
guestSession,
} from "$lib/stores/availabilityStores";
import BrightnessAlert from "~icons/material-symbols/brightness-alert-outline-rounded";
import EmailIcon from "~icons/mdi/email";
import KeyIcon from "~icons/mdi/key";
import Loader from "~icons/mdi/loading";
import UserIcon from "~icons/mdi/user";
export let data: LoginModalProps;
export let data: PageData;
const loginSchema = userSchema.pick({ email: true, password: true });
const guestLoginSchema = guestSchema.pick({ username: true });
const { form, errors, enhance, delayed } = superForm(data.form, {
taintedMessage: null,
Expand All @@ -26,36 +32,68 @@
authModal.close();
}
$isEditingAvailability = false;
$isStateUnsaved = false;
// TODO: Update DB with data
}
},
});
const availabilitySaveForm: HTMLFormElement | null = document.getElementById(
"availability-save-form",
) as HTMLFormElement;
const {
form: guestForm,
errors: guestErrors,
enhance: guestEnhance,
delayed: guestDelayed,
} = superForm(data.guestForm, {
taintedMessage: null,
validators: guestLoginSchema,
delayMs: 0,
onUpdated({ form }) {
if (form.valid) {
const authModal = document.getElementById("auth-modal") as HTMLDialogElement;
if (authModal) {
authModal.close();
if (availabilitySaveForm) {
/**
* This triggers a regular form submission (no enhancement)
*/
availabilitySaveForm.submit();
}
$isEditingAvailability = false;
$isStateUnsaved = false;
// TODO: Update DB with guest data
}
},
});
/**
* Some bespoke state for the guest form
*/
let guestForm: HTMLFormElement;
let formState: "success" | "failure";
let formError: string;
/**
* Guest form submissions are handled through a standard fetch
* This prevents the full page refresh of a non-enhanced form action,
* which would lose the current guest session (which is in a Svelte store)
*/
const handleGuestSubmit = async (meetingId: string) => {
const formData = new FormData(guestForm);
formData.append("meetingId", meetingId);
const response = await fetch("/auth/guest", {
method: "POST",
body: formData,
});
const guestData: ActionResult = deserialize(await response.text());
if (guestData.type === "failure") {
formState = "failure";
// TODO: Handle cases other than duplicate username
formError = guestData.data?.form.errors.username[0];
}
if (guestData.type === "success") {
const authModal = document.getElementById("auth-modal");
if (authModal && authModal instanceof HTMLDialogElement) {
authModal.close();
}
$guestSession = {
guestName: guestData.data?.username,
meetingId: data.meetingId,
};
$isEditingAvailability = false;
$isStateUnsaved = false;
}
};
</script>

<dialog id="auth-modal" class="modal">
Expand Down Expand Up @@ -142,19 +180,18 @@
<h3 class="h-fit px-2 text-left text-xl font-bold">Save as Guest</h3>

<form
method="POST"
action="TODO"
use:guestEnhance
bind:this={guestForm}
class="flex-center w-full grow flex-col items-center space-y-4 md:w-[250px]"
on:submit|preventDefault={() => handleGuestSubmit(data.meetingId)}
>
{#if $guestErrors._errors}
{#if formState === "failure"}
<aside class="variant-filled-error alert">
<div><BrightnessAlert /></div>

<!-- Message -->
<div class="alert-message">
<h3 class="h3">Login Problem</h3>
<p>{$guestErrors._errors}</p>
<p>{formError ?? "An error has occurred..."}</p>
</div>
</aside>
{/if}
Expand All @@ -166,17 +203,11 @@
type="text"
class="grow appearance-none border-none focus:border-none focus:outline-none focus:ring-0"
placeholder="username"
bind:value={$guestForm.username}
name="username"
/>
</label>

<button type="submit" class="variant-filled-primary btn h-10 w-full">
{#if $guestDelayed}
<Loader class="animate-spin" />
{:else}
Save
{/if}
</button>
<button type="submit" class="variant-filled-primary btn h-10 w-full">Save</button>
</div>
</form>
</div>
Expand Down
29 changes: 23 additions & 6 deletions src/lib/components/availability/PersonalAvailability.svelte
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
<script lang="ts">
import { onMount } from "svelte";
import type { PageData } from "../../../routes/availability/$types";
import LoginFlow from "./LoginModal.svelte";
import AvailabilityBlock from "$lib/components/availability/AvailabilityBlock.svelte";
import {
availabilityDates,
availabilityTimeBlocks,
guestSession,
isEditingAvailability,
isStateUnsaved,
} from "$lib/stores/availabilityStores";
import type {
AvailabilityBlockType,
LoginModalProps,
SelectionStateType,
} from "$lib/types/availability";
import type { AvailabilityBlockType, SelectionStateType } from "$lib/types/availability";
import { ZotDate } from "$lib/utils/ZotDate";
import { getGeneralAvailability } from "$lib/utils/availability";
import { cn } from "$lib/utils/utils";
export let columns: number;
export let data: LoginModalProps;
export let data: PageData;
let itemsPerPage: number = columns;
$: {
Expand All @@ -34,13 +36,15 @@
let endBlockSelection: AvailabilityBlockType | null = null;
let currentPage = 0;
let currentPageAvailability: (ZotDate | null)[];
let selectionState: SelectionStateType | null = null;
// Triggers on every pagination change and selection confirmation
$: {
const datesToOffset = currentPage * itemsPerPage;
currentPageAvailability = $availabilityDates.slice(datesToOffset, datesToOffset + itemsPerPage);
if (currentPage === lastPage) {
Expand Down Expand Up @@ -148,6 +152,19 @@
}
});
}
onMount(async () => {
$guestSession.meetingId = data.meetingId;
const generalAvailability = await getGeneralAvailability(data, $guestSession);
const defaultMeetingDates = data.defaultDates.map((item) => new ZotDate(item.date, false, []));
ZotDate.initializeAvailabilities(defaultMeetingDates);
$availabilityDates =
generalAvailability && generalAvailability.length > 0
? generalAvailability
: defaultMeetingDates;
});
</script>

<div class="flex items-center justify-between overflow-x-auto font-dm-sans">
Expand Down
1 change: 1 addition & 0 deletions src/lib/config/zod-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ export const guestSchema = z.object({
.string({ required_error: "Username is required" })
.min(1, { message: "Username is required" })
.trim(),
meetingId: z.string().min(1, { message: "Username is required" }).trim(),
});
Loading

0 comments on commit cd42556

Please sign in to comment.