Skip to content

Commit 0be6af4

Browse files
committed
automatic prefill
1 parent b1396f6 commit 0be6af4

File tree

5 files changed

+504
-33
lines changed

5 files changed

+504
-33
lines changed

client/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"firebase": "^9.14.0",
99
"he": "^1.2.0",
1010
"luxon": "^3.3.0",
11+
"qrcode": "^1.5.4",
1112
"react": "^18.2.0",
1213
"react-color": "^2.19.3",
1314
"react-dom": "^18.2.0",
@@ -23,6 +24,7 @@
2324
"@tauri-apps/cli": "^1.2.3",
2425
"@types/he": "^1.1.2",
2526
"@types/luxon": "^3.2.0",
27+
"@types/qrcode": "^1.5.5",
2628
"@types/react": "^18.0.15",
2729
"@types/react-color": "^3.0.4",
2830
"@types/react-dom": "^18.0.6",

client/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import Upcoming from './pages/classes/Upcoming';
3030
import Materials from './pages/classes/Materials';
3131
import PageNotFound from './pages/404';
3232
import SgyAuthRedirect from './pages/SgyAuthRedirect';
33+
import Prefill from './pages/Prefill';
3334

3435
// Components
3536
import FaviconHandler from './components/schedule/FaviconHandler';
@@ -161,6 +162,7 @@ export default function App() {
161162
element={<Suspense><Testing /></Suspense>}
162163
/>
163164
<Route path="/schoology/auth" element={<SgyAuthRedirect />}/>
165+
<Route path="/prefill/:id" element={<Prefill />}/>
164166
<Route path="*" element={<PageNotFound />}/>
165167
</Routes>
166168

client/src/components/lists/ClubComponentModal.tsx

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { useContext } from 'react';
22
import { Dialog } from '@headlessui/react';
33
import { Club } from '@watt/shared/data/clubs';
44

5+
import { toCanvas, toDataURL, type QRCodeRenderersOptions } from 'qrcode';
6+
57
// Components
68
import CenteredModal from '../layout/CenteredModal';
79
import OutlineButton, { DangerOutlineButton } from '../layout/OutlineButton';
@@ -39,28 +41,12 @@ export default function ClubComponentModal(props: ClubComponentModalProps) {
3941
await updateUserData('clubs', userData.clubs.filter(clubID => clubID !== id), auth, firestore);
4042
}
4143

42-
const clubInputId = {
43-
'Monday': '1696394268',
44-
'Tuesday': '1286398699',
45-
'Wednesday': '1790776594',
46-
'Thursday': '438816767',
47-
'Friday': '834410885'
48-
}[day.split(',')[0]];
49-
50-
// Prefill the form link from club and user name, if it exists
51-
const prefilledData = {
52-
...(clubInputId && {
53-
[`entry.${clubInputId}`]: name
54-
}),
55-
...(user && {
56-
['entry.2019720537']: `950${user.email?.match(/\d+/)}`,
57-
['entry.245716957']: day.split(',')[0],
58-
['emailAddress']: user.email,
59-
['entry.878125936']: user.displayName
60-
})
61-
} as Record<string, string>;
62-
63-
const prefilledLink = `https://docs.google.com/forms/d/e/1FAIpQLSf_K0HpLJBe6SlmX8feUc_xCb2_bs75MLyzf8p2N3G1QcDA8Q/viewform?${new URLSearchParams(prefilledData)}`;
44+
const prefillId = Number(id).toString(36);
45+
const qrData = `https://gunn.app/prefill/${prefillId}`;
46+
const qrConfig: QRCodeRenderersOptions = {
47+
errorCorrectionLevel: "L",
48+
margin: 3
49+
};
6450

6551
return (
6652
<CenteredModal className="relative flex flex-col bg-content rounded-md max-w-md max-h-[90%] mx-2 p-6 shadow-xl" isOpen={isOpen} setIsOpen={setIsOpen}>
@@ -81,11 +67,51 @@ export default function ClubComponentModal(props: ClubComponentModalProps) {
8167
</section>
8268
<hr className="my-3" />
8369

84-
<section className="mb-4 overflow-scroll scroll-smooth scrollbar-none">
70+
<section className="overflow-scroll scroll-smooth scrollbar-none">
8571
<Dialog.Description>{desc}</Dialog.Description>
8672
</section>
8773

88-
<section className="flex gap-3 flex-wrap justify-end">
74+
<div className="hidden md:block outline outline-2 outline-tertiary rounded-md mt-4">
75+
<section className="flex h-full">
76+
<canvas
77+
className="rounded-sm rounded-r-none rounded-l-md"
78+
style={{ imageRendering: 'pixelated' }}
79+
ref={canvas => canvas && toCanvas(canvas, qrData, qrConfig)}
80+
/>
81+
<div className="flex flex-col grow m-auto text-secondary text-center">
82+
<p className="font-semibold">Club Attendance</p>
83+
<a
84+
href={qrData}
85+
target="_blank"
86+
rel="noopener noreferrer"
87+
className="rounded-sm m-auto px-4 py-1 bg-tertiary text-secondary font-bold"
88+
>
89+
{qrData.replace('https://', '')}
90+
</a>
91+
<div className="text-xs text-secondary mt-0.5">
92+
Copy{' '}
93+
<strong
94+
onClick={() => navigator.clipboard.writeText(qrData)}
95+
className="hover:cursor-pointer hover:underline"
96+
>
97+
Link
98+
</strong>
99+
<span> · </span>
100+
<strong
101+
onClick={async () => {
102+
const qr = await fetch(await toDataURL(qrData, qrConfig))
103+
navigator.clipboard.write([new ClipboardItem({ "image/png": await qr.blob() })]);
104+
}}
105+
className="hover:cursor-pointer hover:underline"
106+
>
107+
QR Code
108+
</strong>
109+
</div>
110+
</div>
111+
</section>
112+
</div>
113+
114+
<section className="flex gap-3 mt-4 flex-wrap justify-end">
89115
{pinned ? (
90116
<OutlineButton onClick={removeFromPinned}>
91117
Remove from my list
@@ -95,9 +121,6 @@ export default function ClubComponentModal(props: ClubComponentModalProps) {
95121
Add to my list
96122
</OutlineButton>
97123
)}
98-
<a href={prefilledLink} tabIndex={-1} target="_blank" rel="noopener noreferrer">
99-
<OutlineButton>Check In</OutlineButton>
100-
</a>
101124
<DangerOutlineButton onClick={() => setIsOpen(false)}>
102125
Close
103126
</DangerOutlineButton>

client/src/pages/Prefill.tsx

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import clubs from "@watt/shared/data/clubs";
2+
3+
import { useEffect } from "react";
4+
import { useParams } from "react-router-dom";
5+
import { useAnalytics, useAuth } from "reactfire";
6+
7+
import { logEvent } from "firebase/analytics";
8+
import { GoogleAuthProvider, onAuthStateChanged, signInWithRedirect } from 'firebase/auth'
9+
10+
import CenteredMessage from "../components/layout/CenteredMessage";
11+
import Loading from "../components/layout/Loading";
12+
13+
export default function Prefill() {
14+
const auth = useAuth();
15+
const analytics = useAnalytics();
16+
17+
const { id } = useParams();
18+
const club = clubs.data[id ? parseInt(id, 36) : ''];
19+
20+
useEffect(() => {
21+
if (!club) return;
22+
23+
const unsubscribe = onAuthStateChanged(auth, user => {
24+
unsubscribe();
25+
26+
if (!user) {
27+
const provider = new GoogleAuthProvider();
28+
provider.addScope('profile');
29+
provider.addScope('email');
30+
provider.setCustomParameters({ hd: 'pausd.us' });
31+
32+
signInWithRedirect(auth, provider);
33+
return;
34+
}
35+
36+
const clubInputId = {
37+
'Monday': '1696394268',
38+
'Tuesday': '1286398699',
39+
'Wednesday': '1790776594',
40+
'Thursday': '438816767',
41+
'Friday': '834410885'
42+
}[club.day.split(',')[0]];
43+
44+
const prefilledData = {
45+
...(clubInputId && {
46+
[`entry.${clubInputId}`]: club.name,
47+
['entry.245716957']: club.day.split(',')[0]
48+
}),
49+
...(user && {
50+
['entry.2019720537']: `950${user.email?.match(/\d+/)}`,
51+
['emailAddress']: user.email,
52+
['entry.878125936']: user.displayName
53+
})
54+
} as Record<string, string>;
55+
56+
logEvent(analytics, 'prefill', { club: club.name });
57+
window.location.href = `https://docs.google.com/forms/d/e/1FAIpQLSf_K0HpLJBe6SlmX8feUc_xCb2_bs75MLyzf8p2N3G1QcDA8Q/viewform?${new URLSearchParams(prefilledData)}`;
58+
});
59+
}, [club]);
60+
61+
return (
62+
<CenteredMessage>
63+
{!id ? (
64+
<p>Malformed or missing required query param <code>club</code>.</p>
65+
) : !club ? (
66+
<p>Club not found!</p>
67+
) : (
68+
<Loading>Preparing to redirect you...</Loading>
69+
)}
70+
</CenteredMessage>
71+
);
72+
}

0 commit comments

Comments
 (0)