|
2 | 2 | // @ts-nocheck |
3 | 3 | "use client"; |
4 | 4 | /* eslint-disable @next/next/no-img-element -- decorative/user-uploaded images not suited for next/image */ |
5 | | -import React, { useRef, useState } from 'react'; |
| 5 | +import React, { useEffect, useRef, useState } from 'react'; |
6 | 6 | import { useDialogFocus } from './useDialogFocus'; |
7 | 7 | import type { V } from './types'; |
8 | 8 | import { Logo } from './icons'; |
9 | 9 | import EntryPassCard from './EntryPassCard'; |
10 | | -import SeatCounter from './SeatCounter'; |
| 10 | +import SeatCounter, { useFemaleAccess, useSeatCount } from './SeatCounter'; |
11 | 11 | import styles from './AuthOverlay.module.css'; |
12 | 12 | import { isIterSoaCollege, isIterSoaEmail, ITER_SOA_ERROR_MESSAGE } from '@/lib/validation'; |
| 13 | +import { FEMALE_SEATS_FULL_MESSAGE, genderChoices } from '@/lib/seats'; |
13 | 14 |
|
14 | 15 | /* ── Payment QR lightbox state lives here, outside JSX ── */ |
15 | 16 |
|
@@ -72,6 +73,21 @@ export default function AuthOverlay({ v }: { v: V }) { |
72 | 73 | const offer = v.payOffer; |
73 | 74 | const payOpen = v.payCheck !== 'busy' && v.payCheck !== 'failed' && offer?.state === 'pay'; |
74 | 75 |
|
| 76 | + // Only polled while the registration form is open. At the female seat cap, Female and Others are marked FULL unless the |
| 77 | + // server says this student keeps them (or until it answers: the server has the last word either way). |
| 78 | + const seats = useSeatCount(!!v.showSeats); |
| 79 | + const femaleFull = !!seats?.femaleFull; |
| 80 | + const femaleAccess = useFemaleAccess(femaleFull, v.user?.id, !!v.showSeats); |
| 81 | + const genderCapped = femaleFull && femaleAccess !== true; |
| 82 | + const genderFieldset = useRef<HTMLFieldSetElement>(null); |
| 83 | + // A disabled radio that was already chosen still counts as chosen, so clear it once it's known to be closed to them. |
| 84 | + useEffect(() => { |
| 85 | + if (!femaleFull || femaleAccess !== false) return; |
| 86 | + genderFieldset.current?.querySelectorAll<HTMLInputElement>('input:disabled').forEach((input) => { |
| 87 | + input.checked = false; |
| 88 | + }); |
| 89 | + }, [femaleFull, femaleAccess]); |
| 90 | + |
75 | 91 | return ( |
76 | 92 | <> |
77 | 93 | <div data-auth-root="" aria-hidden={v.authHidden} style={{ position: "fixed", inset: "0", zIndex: "62", visibility: "hidden", pointerEvents: "none" }}> |
@@ -109,7 +125,7 @@ export default function AuthOverlay({ v }: { v: V }) { |
109 | 125 | <div data-a-in=""> |
110 | 126 | <h2 data-m-in="" style={{ margin: "0", fontFamily: "var(--font-display)", fontWeight: "400", fontSize: "var(--auth-title-size)", lineHeight: ".98", letterSpacing: ".01em", textWrap: "balance" }}>{v.authTitle}</h2> |
111 | 127 | <p data-m-in="" style={{ margin: "18px 0 0", maxWidth: "34ch", fontSize: "clamp(15px,1.2vw,18px)", lineHeight: "1.55", color: "rgba(236,232,223,.78)", textWrap: "pretty" }}>{v.authSub}</p> |
112 | | - {v.showSeats ? <SeatCounter /> : null} |
| 128 | + {v.showSeats ? <SeatCounter seats={seats} /> : null} |
113 | 129 | </div> |
114 | 130 | <ol data-a-in="" aria-label="Registration progress" style={{ display: "none", flexDirection: "column", margin: "0", padding: "0", listStyle: "none" }}> |
115 | 131 | {v.prog.map((p, pI) => ( |
@@ -160,17 +176,20 @@ export default function AuthOverlay({ v }: { v: V }) { |
160 | 176 | <input name="name" defaultValue={v.regVals.name} autoComplete="name" placeholder="As on your college ID" aria-invalid={v.inv.name} style={{ height: "54px", padding: "0 16px", borderRadius: "0", background: "rgba(236,232,223,.04)", fontSize: "16px", color: "#ECE8DF", outline: "none", transition: "border-color .3s,background-color .3s", border: `1.5px solid ${v.bc.name}` }} style-focus="border-color:oklch(0.78 0.075 85);background:rgba(236,232,223,.08)" /> |
161 | 177 | {v.err.name ? (<span role="alert" style={{ fontSize: "14px", lineHeight: "1.4", color: "oklch(0.76 0.14 35)" }}>{v.err.name}</span>) : null} |
162 | 178 | </label> |
163 | | - <fieldset data-s-in="" aria-invalid={v.inv.gender} style={{ display: "flex", flexDirection: "column", gap: "8px", margin: "0", padding: "0", border: "0", minWidth: "0" }}> |
| 179 | + <fieldset ref={genderFieldset} data-s-in="" aria-invalid={v.inv.gender} style={{ display: "flex", flexDirection: "column", gap: "8px", margin: "0", padding: "0", border: "0", minWidth: "0" }}> |
164 | 180 | <legend style={{ padding: "0", marginBottom: "8px", fontSize: "12px", fontWeight: "700", letterSpacing: ".16em", color: "rgba(236,232,223,.86)" }}>GENDER</legend> |
165 | 181 | <span role="radiogroup" aria-label="Gender" style={{ display: "grid", gridTemplateColumns: "repeat(3,minmax(0,1fr))", gap: "8px" }}> |
166 | | - {v.genderOptions.map((g) => ( |
167 | | - <label key={g.value} style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: "10px", minHeight: "54px", padding: "0 12px", border: `1.5px solid ${v.bc.gender}`, background: "rgba(236,232,223,.04)", fontSize: "15px", fontWeight: "500", color: "#ECE8DF", cursor: "pointer", transition: "border-color .3s,background-color .3s" }} style-hover="border-color:oklch(0.78 0.075 85)"> |
168 | | - <input type="radio" name="gender" value={g.value} defaultChecked={v.regVals.gender === g.value} style={{ width: "16px", height: "16px", margin: "0", accentColor: "oklch(0.78 0.075 85)", cursor: "pointer" }} /> |
| 182 | + {genderChoices(v.genderOptions, genderCapped).map((g) => ( |
| 183 | + <label key={g.value} style={{ display: "flex", flexWrap: "wrap", alignItems: "center", justifyContent: "center", gap: "4px 10px", minHeight: "54px", padding: "6px 12px", border: `1.5px solid ${g.full ? "rgba(236,232,223,.16)" : v.bc.gender}`, background: g.full ? "transparent" : "rgba(236,232,223,.04)", fontSize: "15px", fontWeight: "500", color: g.full ? "rgba(236,232,223,.5)" : "#ECE8DF", cursor: g.full ? "not-allowed" : "pointer", transition: "border-color .3s,background-color .3s" }} style-hover={g.full ? undefined : "border-color:oklch(0.78 0.075 85)"}> |
| 184 | + <input type="radio" name="gender" value={g.value} disabled={g.full} defaultChecked={!g.full && v.regVals.gender === g.value} style={{ width: "16px", height: "16px", margin: "0", accentColor: "oklch(0.78 0.075 85)", cursor: g.full ? "not-allowed" : "pointer" }} /> |
169 | 185 | {g.label} |
| 186 | + {g.full ? <span style={{ fontSize: "11px", fontWeight: "700", letterSpacing: ".12em", color: "oklch(0.76 0.14 35)" }}>FULL</span> : null} |
170 | 187 | </label> |
171 | 188 | ))} |
172 | 189 | </span> |
173 | | - {v.err.gender ? (<span role="alert" style={{ fontSize: "14px", lineHeight: "1.4", color: "oklch(0.76 0.14 35)" }}>{v.err.gender}</span>) : null} |
| 190 | + {genderCapped ? (<span role="status" style={{ fontSize: "14px", lineHeight: "1.45", color: "rgba(236,232,223,.86)" }}>{FEMALE_SEATS_FULL_MESSAGE}</span>) : null} |
| 191 | + {/* Not the cap's message a second time: a stale form can get it from the server before its next refresh. */} |
| 192 | + {v.err.gender && !(genderCapped && v.err.gender === FEMALE_SEATS_FULL_MESSAGE) ? (<span role="alert" style={{ fontSize: "14px", lineHeight: "1.4", color: "oklch(0.76 0.14 35)" }}>{v.err.gender}</span>) : null} |
174 | 193 | </fieldset> |
175 | 194 | <label data-s-in="" style={{ display: "flex", flexDirection: "column", gap: "8px" }}> |
176 | 195 | <span style={{ fontSize: "12px", fontWeight: "700", letterSpacing: ".16em", color: "rgba(236,232,223,.86)" }}>COLLEGE NAME</span> |
@@ -211,9 +230,9 @@ export default function AuthOverlay({ v }: { v: V }) { |
211 | 230 | </label> |
212 | 231 | <label data-s-in="" style={{ display: "flex", flexDirection: "column", gap: "8px" }}> |
213 | 232 | <span style={{ fontSize: "12px", fontWeight: "700", letterSpacing: ".16em", color: "rgba(236,232,223,.86)" }}> |
214 | | - COLLEGE ROLL / ENROLLMENT NUMBER |
| 233 | + COLLEGE ROLL / ENROLLMENT NUMBER / EMPLOYEE ID |
215 | 234 | </span> |
216 | | - <input name="enrollment_no" defaultValue={v.regVals.enrollment_no} autoComplete="off" required placeholder="Your college roll / enrollment number" aria-invalid={v.inv.enrollment_no} style={{ height: "54px", padding: "0 16px", borderRadius: "0", background: "rgba(236,232,223,.04)", fontSize: "16px", color: "#ECE8DF", outline: "none", transition: "border-color .3s,background-color .3s", border: `1.5px solid ${v.bc.enrollment_no}` }} style-focus="border-color:oklch(0.78 0.075 85);background:rgba(236,232,223,.08)" /> |
| 235 | + <input name="enrollment_no" defaultValue={v.regVals.enrollment_no} autoComplete="off" required placeholder="Your roll / enrollment number or employee ID"aria-invalid={v.inv.enrollment_no} style={{ height: "54px", padding: "0 16px", borderRadius: "0", background: "rgba(236,232,223,.04)", fontSize: "16px", color: "#ECE8DF", outline: "none", transition: "border-color .3s,background-color .3s", border: `1.5px solid ${v.bc.enrollment_no}` }} style-focus="border-color:oklch(0.78 0.075 85);background:rgba(236,232,223,.08)" /> |
217 | 236 | {v.err.enrollment_no ? (<span role="alert" style={{ fontSize: "14px", lineHeight: "1.4", color: "oklch(0.76 0.14 35)" }}>{v.err.enrollment_no}</span>) : null} |
218 | 237 | </label> |
219 | 238 | <div style={{ display: "flex", flexDirection: "column", gap: "10px" }}> |
|
0 commit comments