Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Student Reschedule #8

Merged
merged 12 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ jobs:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
PUBLIC_SENTRY_DSN: ${{ secrets.PUBLIC_SENTRY_DSN }}
API_MASTER_KEY: ${{ secrets.API_MASTER_KEY }}
BASE_URL: ${{ vars.BASE_URL }}
steps:
- name: Checkout source
uses: actions/checkout@v4
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@
"sveltekit-superforms": "^2.20.0",
"tailwind-merge": "^2.5.4",
"ulid": "^2.3.0"
}
},
"packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
20 changes: 16 additions & 4 deletions src/lib/emails/AppointmentBooked.svelte
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
<script lang="ts">
import type { AppointmentBookedProps } from '$lib/emails/appointment_booked';
import { DateTime } from 'luxon';
import { BASE_URL } from '$env/static/private';

let { startTime, duration, mentorName, sessionId, type, timezone }: AppointmentBookedProps =
$props();
let {
startTime,
duration,
mentorName,
sessionId,
type,
timezone,
link_params,
reschedule
}: AppointmentBookedProps = $props();

let reschedule_link = `${BASE_URL}schedule${link_params}`;
let title = reschedule ? 'Appointment updated' : 'Appointment booked';
</script>

<h1>Appointment booked!</h1>
<h1>{title}</h1>

<p>This is your confirmation email for your upcoming session.</p>
<p><b>Session type:</b> {type}</p>
<p><b>Date/time:</b> {startTime.setZone(timezone).toLocaleString(DateTime.DATETIME_HUGE)}</p>
<p><b>Timezone:</b> {timezone}</p>
<p><b>Duration:</b> {duration} minutes</p>
<p><b>Mentor:</b> {mentorName}</p>
<i>Please reach out to your mentor directly if you need to cancel or reschedule this lesson.</i>
<a href={reschedule_link}>Cancel/Reschedule</a>

<p>---</p>

Expand Down
4 changes: 2 additions & 2 deletions src/lib/emails/AppointmentBooked.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ This is your confirmation email for your upcoming session.
-- Duration: {duration} minutes
-- Mentor: {mentorName}

Please reach out to your mentor directly if you need to cancel or reschedule this lesson.
Cancel/Reschedule: {rescheduleLink}

---

Confirmation ID {sessionId}.
You are receiving this email because you have booked a session with the ZTL ARTCC. If you believe to have received this email in error, please contact [email protected].
You are receiving this email because you have booked a session with the ZTL ARTCC. If you believe to have received this email in error, please contact [email protected].
8 changes: 7 additions & 1 deletion src/lib/emails/appointment_booked.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { type EmailContent, templateOut } from '$lib/email';
import plaintextTemplate from './AppointmentBooked.txt?raw';
import AppointmentBooked from './AppointmentBooked.svelte';
import { render } from 'svelte/server';
import { BASE_URL } from '$env/static/private';

export interface AppointmentBookedProps {
startTime: DateTime;
Expand All @@ -11,6 +12,8 @@ export interface AppointmentBookedProps {
mentorName: string;
sessionId: string;
timezone: string;
link_params: string;
reschedule?: boolean;
}

export function appointment_booked(props: AppointmentBookedProps): EmailContent {
Expand All @@ -21,7 +24,10 @@ export function appointment_booked(props: AppointmentBookedProps): EmailContent
duration: props.duration.toString(),
mentorName: props.mentorName,
sessionId: props.sessionId,
timezone: props.timezone
timezone: props.timezone,
link_params: props.link_params,
reschedule: props.reschedule ? 'rescheduled' : '',
reschedule_link: `${BASE_URL}schedule/${props.link_params}`
}),
html: render(AppointmentBooked, {
props: props
Expand Down
61 changes: 54 additions & 7 deletions src/routes/(authed)/schedule/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { loadUserData } from '$lib/userInfo';
import { ROLE_DEVELOPER, ROLE_MENTOR, ROLE_STAFF, roleString } from '$lib/utils';
import { roleOf } from '$lib';
import { db } from '$lib/server/db';
import { sessions, sessionTypes, users } from '$lib/server/db/schema';
import { mentors, sessions, sessionTypes, students, users } from '$lib/server/db/schema';
import { eq, gte, or } from 'drizzle-orm';
import type { DayAvailability, MentorAvailability } from '$lib/availability';
import { DateTime, Duration, Interval } from 'luxon';
import { fail } from '@sveltejs/kit';
import { fail, redirect } from '@sveltejs/kit';
import { MAX_BOOKING_AHEAD_DAYS } from '$env/static/private';
import { ulid } from 'ulid';
import { appointment_booked } from '$lib/emails/appointment_booked';
Expand Down Expand Up @@ -185,7 +185,7 @@ function slottificate(
return slotData;
}

export const load: PageServerLoad = async ({ cookies }) => {
export const load: PageServerLoad = async ({ cookies, url }) => {
const { user } = (await loadUserData(cookies))!;

const sTypes = await db.select().from(sessionTypes);
Expand All @@ -198,25 +198,39 @@ export const load: PageServerLoad = async ({ cookies }) => {

const slotData = slottificate(sTypes, mentors, allSessions);

const originalSessionType = url.searchParams.has('reschedule')
? url.searchParams.get('type')
: null;

const originalSessionId = url.searchParams.get('sessionId')!;
const ogSession = await db.select().from(sessions).where(eq(sessions.id, originalSessionId));

return {
user,
role: roleString(roleOf(user)),
isTrainer: roleOf(user) >= ROLE_MENTOR,
isStaff: roleOf(user) >= ROLE_STAFF,
isDeveloper: roleOf(user) >= ROLE_DEVELOPER,
sessionTypes: sTypes,
slotData
slotData,
originalSessionType,
originalSessionId,
ogSession
};
};

export const actions: Actions = {
default: async ({ cookies, request }) => {
book: async ({ cookies, request }) => {
const { user } = (await loadUserData(cookies))!;

const formData = await request.formData();
const requestedSlotId = formData.get('timeslot')!;
const requestedType = formData.get('type')!;
const timezone = formData.get('timezone')!;
const orginalSessionId = formData.get('sessionId');
const reschedule = formData.has('reschedule') || false;

console.log(formData);

const sTypes = await db.select().from(sessionTypes);
const mentors = await db
Expand Down Expand Up @@ -270,7 +284,9 @@ export const actions: Actions = {
mentorName: mentor.firstName + ' ' + mentor.lastName,
duration,
sessionId: id,
type: typename
type: typename,
link_params: `?sessionId=${id}&reschedule=true&type=${requestedType}`,
reschedule
});
const mentorEmailContent = new_session({
startTime: start.setZone(mentor.timezone),
Expand All @@ -290,18 +306,49 @@ export const actions: Actions = {
timezone
});

if (reschedule) {
await db.delete(sessions).where(eq(sessions.id, orginalSessionId));
}

await sendEmail(
user.email,
'Appointment booked - ' + start.setZone(timezone).toLocaleString(DateTime.DATETIME_HUGE),
reschedule
? 'Appointment updated'
: 'Appointment booked' +
' - ' +
start.setZone(timezone).toLocaleString(DateTime.DATETIME_HUGE),
studentEmailContent.raw,
studentEmailContent.html
);

await sendEmail(
mentor.email,
'New session booked - ' +
start.setZone(mentor.timezone).toLocaleString(DateTime.DATETIME_HUGE),
mentorEmailContent.raw,
mentorEmailContent.html
);
},
cancel: async ({ cookies, request }) => {
const { user } = (await loadUserData(cookies))!;
const sessionList = await db
.select()
.from(sessions)
.leftJoin(sessionTypes, eq(sessionTypes.id, sessions.type))
.leftJoin(mentors, eq(mentors.id, sessions.mentor))
.leftJoin(students, eq(students.id, sessions.student))
.where(eq(sessions.id, request.sessionId));
const sessionAndFriends = sessionList[0];

if (
roleOf(user) < ROLE_STAFF &&
!(user.id == sessionAndFriends.session.student || user.id == sessionAndFriends.session.mentor)
) {
redirect(307, '/schedule');
}

const formData = await request.formData();

await db.delete(sessions).where(eq(sessions.id, formData.get('sessionId')!.toString()));
}
};
Loading