Skip to content

Commit

Permalink
Add modal that gets displayed on successful checkin (#152)
Browse files Browse the repository at this point in the history
* Add modal that gets displayed on successful checkin

* address comments

* Fix merge stuff
  • Loading branch information
alexzhang1618 authored Feb 20, 2024
1 parent 6621e75 commit 18be91c
Show file tree
Hide file tree
Showing 9 changed files with 529 additions and 10 deletions.
329 changes: 329 additions & 0 deletions public/assets/graphics/portal/treasure.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/components/admin/event/EventDetailsForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ const EventDetailsForm = (props: IProps) => {
setLoading(false);
router.push(config.admin.events.homeRoute);
},
onFailCallback: error => {
setLoading(false);
reportError('Unable to create event', error);
},
});
};

Expand Down
4 changes: 3 additions & 1 deletion src/components/common/Modal/style.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@
}

@media screen and (width <= vars.$breakpoint-md) {
border-radius: 10px 10px 0 0;
&.bottomSheet {
border-radius: 10px 10px 0 0;
}
}
}
}
72 changes: 72 additions & 0 deletions src/components/events/CheckInModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Modal, Typography } from '@/components/common';
import { PublicEvent } from '@/lib/types/apiResponses';
import Image from 'next/image';
import { useMemo } from 'react';
import style from './style.module.scss';

interface CheckInModalProps {
open: boolean;
event?: PublicEvent;
onClose: () => void;
}

const CHECKIN_TITLES = [
'Yippee!',
'Brilliant!',
'Wahoo!',
'Awesome!',
'Nice to see you!',
'Congrats!',
'Hello World!',
'Welcome!',
];

const CheckInModal = ({ open, event, onClose }: CheckInModalProps) => {
const headerText = useMemo(
() => CHECKIN_TITLES[Math.floor(Math.random() * CHECKIN_TITLES.length)],
[]
);

if (!event) {
return null;
}

const { pointValue, title } = event;

return (
<Modal open={open} onClose={onClose}>
<div className={style.container}>
<div className={style.graphic}>
<Image
src="/assets/graphics/portal/treasure.svg"
alt="A diamond with a smiley face looks at an open treasure chest."
fill
/>
</div>
<div className={style.header}>
<Typography
variant="display/light/small"
className={style.headerText}
suppressHydrationWarning
>
{headerText}
</Typography>
<div className={style.subheader}>
<Typography variant="h4/regular" component="span" className={style.subheaderText}>
{'You just earned '}
<strong>{pointValue}</strong>
{' membership points for checking into '}
<strong>{title}</strong>!
</Typography>
<Typography variant="h4/regular" className={style.subheaderText} />
</div>
</div>
<button type="submit" className={style.done}>
<Typography variant="h4/bold">Done</Typography>
</button>
</div>
</Modal>
);
};

export default CheckInModal;
85 changes: 85 additions & 0 deletions src/components/events/CheckInModal/style.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
@use 'src/styles/vars.scss' as vars;

.container {
align-items: center;
display: flex;
flex-direction: column;
gap: 1rem;
max-width: 50rem;
padding: 2rem;

.graphic {
aspect-ratio: 560 / 283;
border-radius: 1rem;
overflow: hidden;
position: relative;
width: 100%;
}

.header {
display: flex;
flex-direction: column;
gap: 0.25rem;

.headerText {
text-align: center;

@media screen and (width <= vars.$breakpoint-md) {
font-size: 2rem !important;
line-height: 2.5rem !important;
}
}

.subheader {
align-items: center;
display: flex;
flex-direction: column;

.subheaderText {
text-align: center;

@media screen and (width <= vars.$breakpoint-md) {
font-size: 1rem !important;
line-height: 1.5rem !important;
}
}
}

.close {
align-items: center;
background: none;
display: flex;
height: 3.75rem;
margin-left: 1rem;
padding: 0;
}
}

.image {
aspect-ratio: 1920 / 1080;
border-radius: 10px;
flex-shrink: 0;
overflow: hidden;
position: relative;
width: 100%;
}

.done {
border-radius: 2rem;
padding: 1rem;
width: 50%;

@media screen and (width <= vars.$breakpoint-md) {
padding: 0.5rem;

div {
font-size: 1rem !important;
line-height: 1.5rem !important;
}
}
}

@media screen and (width <= vars.$breakpoint-md) {
max-width: calc(100vw - 2rem);
}
}
17 changes: 17 additions & 0 deletions src/components/events/CheckInModal/style.module.scss.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export type Styles = {
close: string;
container: string;
done: string;
graphic: string;
header: string;
headerText: string;
image: string;
subheader: string;
subheaderText: string;
};

export type ClassNames = keyof Styles;

declare const styles: Styles;

export default styles;
1 change: 1 addition & 0 deletions src/components/events/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { default as CalendarButtons } from './CalendarButtons';
export { default as CheckInModal } from './CheckInModal';
export { default as EventCard } from './EventCard';
export { default as EventCarousel } from './EventCarousel';
export { default as EventDisplay } from './EventDisplay';
Expand Down
1 change: 1 addition & 0 deletions src/components/profile/Switch/style.module.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@use 'src/styles/vars.scss' as vars;

.label {
align-items: center;
display: flex;
gap: 0.5em;

Expand Down
26 changes: 17 additions & 9 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { EventCarousel } from '@/components/events';
import { CheckInModal, EventCarousel } from '@/components/events';
import Hero from '@/components/home/Hero';
import { config, showToast } from '@/lib';
import { showToast } from '@/lib';
import { EventAPI, UserAPI } from '@/lib/api';
import withAccessType from '@/lib/hoc/withAccessType';
import { attendEvent } from '@/lib/managers/EventManager';
Expand All @@ -25,9 +25,6 @@ const processCheckInResponse = (
): PublicEvent | undefined => {
if ('uuid' in response) {
// If the response contains a uuid, the response is a PublicEvent.
const title = `Checked in to ${response.title}!`;
const subtitle = `Thanks for checking in! You earned ${response.pointValue} points.`;
showToast(title, subtitle);
return response;
}
showToast('Unable to checkin!', response.error);
Expand All @@ -43,6 +40,8 @@ const PortalHomePage = ({
checkInResponse,
}: HomePageProps) => {
const [points, setPoints] = useState<number>(user.points);
const [checkinEvent, setCheckinEvent] = useState<PublicEvent | undefined>(undefined);
const [checkinModalVisible, setCheckinModalVisible] = useState<boolean>(false);
const [attendance, setAttendance] = useState<PublicAttendance[]>(attendances);

const checkin = async (attendanceCode: string): Promise<void> => {
Expand All @@ -61,21 +60,30 @@ const PortalHomePage = ({
feedback: [],
};
setAttendance(prevAttendances => [...prevAttendances, newAttendance]);
setCheckinEvent(event);
setCheckinModalVisible(true);
}
};

useEffect(() => {
if (checkInResponse) {
// In dev mode, this runs twice because of reactStrictMode in nextConfig.
// This will only be run once in prod or deployment.
processCheckInResponse(checkInResponse);
// Clear the query params without re-triggering getServerSideProps.
window.history.replaceState(null, '', config.homeRoute);
const event = processCheckInResponse(checkInResponse);
if (event) {
setCheckinEvent(event);
setCheckinModalVisible(true);
}
}
}, [checkInResponse]);
}, [checkInResponse, user]);

return (
<div className={styles.page}>
<CheckInModal
open={checkinModalVisible}
event={checkinEvent}
onClose={() => setCheckinModalVisible(false)}
/>
<Hero firstName={user.firstName} points={points} checkin={code => checkin(code)} />

{liveEvents.length > 0 ? (
Expand Down

0 comments on commit 18be91c

Please sign in to comment.