Skip to content

Commit

Permalink
Merge branch 'main' into infra-school-chapters
Browse files Browse the repository at this point in the history
  • Loading branch information
iperalta7 authored Sep 25, 2024
2 parents 4cacdc4 + 335c3f8 commit 3f9adb7
Show file tree
Hide file tree
Showing 25 changed files with 620 additions and 62 deletions.
1 change: 1 addition & 0 deletions CONTRIBUTORS.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,4 @@
- Ekene-Azubuko
- Dharld
- rod608
- mdg258
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ import {
} from '@remix-run/react';
import dayjs from 'dayjs';
import { type PropsWithChildren, useState } from 'react';
import { Info } from 'react-feather';
import { ChevronDown, Info } from 'react-feather';

import { type EducationLevel } from '@oyster/core/admin-dashboard/ui';
import {
acceptApplication,
getApplication,
rejectApplication,
} from '@oyster/core/applications';
import { ApplicationRejectionReason } from '@oyster/core/applications/types';
import { Application } from '@oyster/core/applications/ui';
import {
Application as ApplicationType,
Expand All @@ -28,7 +29,7 @@ import {
type OtherDemographic,
type Race,
} from '@oyster/types';
import { Button, Text } from '@oyster/ui';
import { Button, Dropdown, Form, Select, Text } from '@oyster/ui';

import { Route } from '@/shared/constants';
import { ENV } from '@/shared/constants.server';
Expand Down Expand Up @@ -91,7 +92,7 @@ export async function action({ params, request }: ActionFunctionArgs) {

const form = await request.formData();

const { action } = Object.fromEntries(form);
const { action, reason } = Object.fromEntries(form);

try {
switch (action) {
Expand All @@ -106,7 +107,11 @@ export async function action({ params, request }: ActionFunctionArgs) {
}

case 'reject': {
await rejectApplication(params.id as string, user(session));
await rejectApplication(
params.id as string,
user(session),
reason as ApplicationRejectionReason
);

toast(session, {
message: 'Application has been rejected.',
Expand Down Expand Up @@ -155,9 +160,6 @@ export default function ApplicationPage() {
const acceptButtonSubmitting =
state === 'submitting' && formData?.get('action') === 'accept';

const rejectButtonSubmitting =
state === 'submitting' && formData?.get('action') === 'reject';

const { application } = useLoaderData<typeof loader>();

function onToggleVisibility() {
Expand Down Expand Up @@ -206,15 +208,7 @@ export default function ApplicationPage() {
Accept
</Button>

<Button
color="error"
name="action"
submitting={rejectButtonSubmitting}
type="submit"
value="reject"
>
Reject
</Button>
<RejectDropdown />
</Button.Group>
</RemixForm>
)}
Expand Down Expand Up @@ -255,6 +249,68 @@ export default function ApplicationPage() {
);
}

function RejectDropdown() {
const [open, setOpen] = useState<boolean>(false);

const { formData, state } = useNavigation();

const submitting =
state === 'submitting' && formData?.get('action') === 'reject';

return (
<Dropdown.Container onClose={() => setOpen(false)}>
<Button
color="error"
onClick={() => setOpen(true)}
type="button"
variant="secondary"
>
Reject <ChevronDown size={16} />
</Button>

{open && (
<Dropdown className="p-2">
<RemixForm className="form" method="post">
<Form.Field
description="Select a reason for rejecting this application."
label="Rejection Reason"
required
>
<Select id="reason" name="reason" required>
<option value={ApplicationRejectionReason.BAD_LINKEDIN}>
Incorrect or suspicious LinkedIn
</option>
<option value={ApplicationRejectionReason.IS_INTERNATIONAL}>
Not enrolled in US or Canada
</option>
<option value={ApplicationRejectionReason.INELIGIBLE_MAJOR}>
Not the right major
</option>
<option value={ApplicationRejectionReason.NOT_UNDERGRADUATE}>
Not an undergrad student
</option>
<option value={ApplicationRejectionReason.OTHER}>Other</option>
</Select>
</Form.Field>

<Button
color="error"
fill
name="action"
size="small"
submitting={submitting}
type="submit"
value="reject"
>
Reject
</Button>
</RemixForm>
</Dropdown>
)}
</Dropdown.Container>
);
}

const keys = ApplyFormData.keyof().enum;

function ApplicationFieldGroup({
Expand Down
27 changes: 23 additions & 4 deletions apps/admin-dashboard/app/routes/_dashboard.applications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import {
import { useState } from 'react';
import { Edit } from 'react-feather';
import { generatePath } from 'react-router';
import { match } from 'ts-pattern';
import { z } from 'zod';

import { ListSearchParams } from '@oyster/core/admin-dashboard/ui';
import { listApplications } from '@oyster/core/applications';
import { type ApplicationRejectionReason } from '@oyster/core/applications/types';
import { ApplicationStatus } from '@oyster/core/applications/ui';
import { Application } from '@oyster/types';
import {
Expand Down Expand Up @@ -61,6 +63,7 @@ export async function loader({ request }: LoaderFunctionArgs) {

return json({
applications,
status: searchParams.status,
totalCount,
});
}
Expand Down Expand Up @@ -129,12 +132,10 @@ function FilterApplicationsForm() {
type ApplicationInView = SerializeFrom<typeof loader>['applications'][number];

function ApplicationsTable() {
const { applications } = useLoaderData<typeof loader>();
const { applications, status } = useLoaderData<typeof loader>();

const { search } = useLocation();

const [searchParams] = useSearchParams(ApplicationsSearchParams);

const columns: TableColumnProps<ApplicationInView>[] = [
{
displayName: 'Full Name',
Expand Down Expand Up @@ -179,6 +180,24 @@ function ApplicationsTable() {
},
size: '160',
},
{
displayName: 'Rejection Reason',
render: (application) => {
return match(application.rejectionReason as ApplicationRejectionReason)
.with('bad_linkedin', () => 'Incorrect or suspicious LinkedIn')
.with('email_already_used', () => 'Email already used')
.with('email_bounced', () => 'Email bounced')
.with('ineligible_major', () => 'Not the right major')
.with('is_international', () => 'Not enrolled in US or Canada')
.with('not_undergraduate', () => 'Not an undergrad student')
.with('other', () => 'Other')
.otherwise(() => '-');
},
size: '400',
show: () => {
return status === 'all' || status === 'rejected';
},
},
{
displayName: 'Applied On',
size: '240',
Expand All @@ -204,7 +223,7 @@ function ApplicationsTable() {
columns={columns}
data={applications}
emptyMessage="No pending applications left to review."
{...(['pending', 'rejected'].includes(searchParams.status) && {
{...(['pending', 'rejected'].includes(status) && {
Dropdown: ApplicationDropdown,
})}
/>
Expand Down
126 changes: 126 additions & 0 deletions apps/admin-dashboard/app/routes/_dashboard.students.$id.gift.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import {
type ActionFunctionArgs,
json,
type LoaderFunctionArgs,
redirect,
} from '@remix-run/node';
import { Form as RemixForm, useActionData } from '@remix-run/react';
import { z } from 'zod';

import { createGoodyOrder } from '@oyster/core/goody';
import { db } from '@oyster/db';
import { Button, Form, Modal, Textarea, validateForm } from '@oyster/ui';

import { Route } from '@/shared/constants';
import {
commitSession,
ensureUserAuthenticated,
toast,
} from '@/shared/session.server';

export async function loader({ params, request }: LoaderFunctionArgs) {
await ensureUserAuthenticated(request);

const member = await db
.selectFrom('students')
.where('id', '=', params.id as string)
.executeTakeFirst();

if (!member) {
return redirect(Route['/students']);
}

return json({});
}

export async function action({ params, request }: ActionFunctionArgs) {
const session = await ensureUserAuthenticated(request);

const {
data,
errors: _,
ok,
} = await validateForm(
request,
z.object({ message: z.string().trim().min(1) })
);

if (!ok) {
return json({ error: 'Please enter a message.' }, { status: 400 });
}

const member = await db
.selectFrom('students')
.select(['email', 'firstName', 'lastName'])
.where('id', '=', params.id as string)
.executeTakeFirst();

if (!member) {
return redirect(Route['/students']);
}

const result = await createGoodyOrder({
message: data.message,
recipients: [
{
email: member.email,
first_name: member.firstName,
last_name: member.lastName,
},
],
});

if (!result.ok) {
return json({ error: result.error }, { status: result.code });
}

toast(session, {
message: 'Sent Goody gift! 🎁',
});

return redirect(Route['/students'], {
headers: {
'Set-Cookie': await commitSession(session),
},
});
}

export default function SendGiftModal() {
const { error } = useActionData<typeof action>() || {};

return (
<Modal onCloseTo={Route['/students']}>
<Modal.Header>
<Modal.Title>Send Goody Gift</Modal.Title>
<Modal.CloseButton />
</Modal.Header>

<Modal.Description>
This will send a DoorDash gift card to this member.
</Modal.Description>

<RemixForm className="form" method="post">
<Form.Field
description="Add a message to the gift so the member knows why they are receiving this."
label="Message"
labelFor="message"
required
>
<Textarea
id="message"
minRows={3}
name="message"
placeholder="Congratulations..."
required
/>
</Form.Field>

<Form.ErrorMessage>{error}</Form.ErrorMessage>

<Button.Group>
<Button.Submit>Send</Button.Submit>
</Button.Group>
</RemixForm>
</Modal>
);
}
9 changes: 8 additions & 1 deletion apps/admin-dashboard/app/routes/_dashboard.students.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
ExternalLink,
Gift,
Hash,
Star,
Trash,
Upload,
Users,
Expand Down Expand Up @@ -286,7 +287,7 @@ function StudentDropdown({
<Link
to={generatePath(Route['/students/:id/points/grant'], { id })}
>
<Gift /> Grant Points
<Star /> Grant Points
</Link>
</Dropdown.Item>

Expand All @@ -310,6 +311,12 @@ function StudentDropdown({
</Link>
</Dropdown.Item>

<Dropdown.Item>
<Link to={generatePath(Route['/students/:id/gift'], { id })}>
<Gift /> Send Goody Gift
</Link>
</Dropdown.Item>

<Dropdown.Item>
<Link to={generatePath(Route['/students/:id/remove'], { id })}>
<Trash /> Remove Member
Expand Down
1 change: 1 addition & 0 deletions apps/admin-dashboard/app/shared/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const ROUTES = [
'/students/import/scholarships',
'/students/:id/activate',
'/students/:id/email',
'/students/:id/gift',
'/students/:id/points/grant',
'/students/:id/remove',
'/surveys',
Expand Down
Loading

0 comments on commit 3f9adb7

Please sign in to comment.