diff --git a/api/controller/stripeController.ts b/api/controller/stripeController.ts index 93b23c0..141e0a6 100644 --- a/api/controller/stripeController.ts +++ b/api/controller/stripeController.ts @@ -49,7 +49,7 @@ export const createCheckout = asyncHandler( } else { let ticketAvailable = await isTicketAvailableByPriceId(priceId); if (!ticketAvailable) { - return res.send({ + return res.status(400).send({ error: "There are no tickets available for this event. Please come back later to see if more tickets become available.", }); @@ -121,11 +121,12 @@ export const handleWebhook = asyncHandler( if (event.type === "checkout.session.completed") { const session: Stripe.Checkout.Session = event.data.object; - if ( - !session.metadata && - !session.metadata!["priceId"] && - !session.metadata!["isEventTicket"] + !( + !session.metadata && + !session.metadata!["priceId"] && + !session.metadata!["isEventTicket"] + ) ) { if (session.metadata!["isEventTicket"] === "y") { completeTicketPurchase(session.id); diff --git a/api/controller/userController.ts b/api/controller/userController.ts index be42a4a..035534b 100644 --- a/api/controller/userController.ts +++ b/api/controller/userController.ts @@ -12,7 +12,6 @@ import { insertUserBySuperToken } from "../gateway/userGateway"; export const updateUserTicketInfo = asyncHandler( async (req: Request, res: Response) => { - console.log(process.env.DOMAIN_DB); try { const { name, email, phoneNumber, ticketId, answers } = req.body; diff --git a/api/gateway/userGateway.ts b/api/gateway/userGateway.ts index 01abb73..a946af8 100644 --- a/api/gateway/userGateway.ts +++ b/api/gateway/userGateway.ts @@ -239,6 +239,7 @@ export async function insertUserTicket(data: { return newUserTicket[0]; } +// TODO: Add check to make sure server does not crash if an expiry date already exists export async function updateUserMembershipExpiryDate( sessionId: string ): Promise { diff --git a/api/index.ts b/api/index.ts index 01d767a..1cdb80b 100644 --- a/api/index.ts +++ b/api/index.ts @@ -49,6 +49,7 @@ app.use( cors({ origin: [ `${process.env.DOMAIN_FRONTEND}`, //FE + `${process.env.DOMAIN_FRONTEND_AAAA}`, `${process.env.DOMAIN_STRAPI}`, //Strapi `${process.env.DOMAIN_SUPERTOKENS}`, //ST user Dashboard `${process.env.DATABASE_HOST}:${process.env.DATABASE_PORT}`, //DB diff --git a/web/__test__/screens/CheckoutScreen.test.tsx b/web/__test__/screens/CheckoutScreen.test.tsx index 8fc73c8..2521b61 100644 --- a/web/__test__/screens/CheckoutScreen.test.tsx +++ b/web/__test__/screens/CheckoutScreen.test.tsx @@ -5,6 +5,7 @@ import CheckoutScreen from "../../src/screens/CheckoutScreen"; import React from "react"; import "@testing-library/jest-dom"; import { MemoryRouter } from "react-router"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; const mockedUseNavigate = vi.fn(); vi.mock("react-router-dom", async () => { @@ -24,13 +25,17 @@ vi.mock("@components/checkout-page/CheckoutError", () => ({ }, })); +const queryClient = new QueryClient(); + describe("AboutUsScreen", () => { it("renders error screen properly", () => { render( - - - + + + + + ); diff --git a/web/__test__/screens/SignUpInformationScreen.test.tsx b/web/__test__/screens/SignUpInformationScreen.test.tsx index 8ae1d9b..781c76c 100644 --- a/web/__test__/screens/SignUpInformationScreen.test.tsx +++ b/web/__test__/screens/SignUpInformationScreen.test.tsx @@ -5,6 +5,7 @@ import SignUpInformationScreen from "../../src/screens/SignUpInformationScreen"; import React from "react"; import "@testing-library/jest-dom"; import { MemoryRouter } from "react-router"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; const mockedUseNavigate = vi.fn(); vi.mock("react-router-dom", async () => { @@ -17,14 +18,17 @@ vi.mock("react-router-dom", async () => { useNavigate: () => mockedUseNavigate, }; }); +const queryClient = new QueryClient(); describe("AboutUsScreen", () => { it("renders questions correctly", async () => { render( - - } /> - + + + } /> + + ); @@ -50,9 +54,11 @@ describe("AboutUsScreen", () => { it("does not render errors on load up", async () => { render( - - } /> - + + + } /> + + ); expect(await screen.queryByText("Please enter your full name")).toBeNull(); @@ -73,9 +79,11 @@ describe("AboutUsScreen", () => { it("renders error correctly", async () => { render( - - } /> - + + + } /> + + ); diff --git a/web/src/api/apiRequests.ts b/web/src/api/apiRequests.ts index f952b10..209f23c 100644 --- a/web/src/api/apiRequests.ts +++ b/web/src/api/apiRequests.ts @@ -1,7 +1,11 @@ import axios, { AxiosResponse } from "axios"; import { + AnswerList, AttendanceList, + EventOrMembershipReturn, MembershipExpiryDate as MembershipExpiryDate, + stripeSessionStatus, + UpdateUserInfoOrNewUser, } from "../types/types"; const apiClient = axios.create({ @@ -10,37 +14,64 @@ const apiClient = axios.create({ }); // Get user metadata -export const getUserMetadaData = async (): Promise => { +export const getUserMetaData = async (): Promise => { const response = await apiClient.get("/api/user/get-metadata", { headers: { "Content-Type": "application/json", }, }); - return response; }; // Update user info -export const updateUserInfo = async (data: object): Promise => { +export const updateUserInfo = async ( + name: string, + universityId: string, + upi: string, + yearOfStudy: string, + fieldOfStudy: string, + isDomestic: string, + institution: string +): Promise => { + const data = { + name, + universityId, + upi, + yearOfStudy, + fieldOfStudy, + isDomestic, + institution, + }; + const response = await apiClient.post("/api/user/update-user-info", data, { headers: { "Content-Type": "application/json", }, - data: { data }, + data, }); return response; }; export const updateUserTicketInfo = async ( - data: object -): Promise => { + ticketId: number, + name: string, + email: string, + phoneNumber: string, + answers: AnswerList[] +): Promise => { + const data = { + ticketId, + name, + email, + phoneNumber, + answers, + }; const response = await apiClient.post("/api/user/user-ticket-info", data, { headers: { "Content-Type": "application/json", }, }); - - return response; + return response.data; }; // User membership expiry @@ -85,22 +116,22 @@ export const postAttendanceUpdate = async ( // Get session status export const getSessionStatus = async ( sessionId: string -): Promise => { +): Promise => { const response = await apiClient.get( `/api/stripe/session-status?session_id=${sessionId}`, { headers: { "Content-Type": "application/json" }, } ); - - return response; + return response.data; }; //Use this one to automatically create an Event or Membership checkout. Event checkout will decrement a ticket. -export const fetchEventOrMembershipCheckoutSecret = async (payload: { - priceId: string; - userTicketId: number; -}): Promise => { +export const fetchEventOrMembershipCheckoutSecret = async ( + priceId: string, + userTicketId: number +): Promise => { + const payload = { priceId, userTicketId }; const response = await apiClient.post( "/api/stripe/create-checkout", payload, @@ -109,35 +140,5 @@ export const fetchEventOrMembershipCheckoutSecret = async (payload: { headers: { "Content-Type": "application/json" }, } ); - - return response.data.clientSecret; -}; - -// TODO: Delete these? ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - -// the ones below are for a separate approach. Safe to use. -export const fetchEventCheckoutSecret = async (payload: { - stripeKey: string; -}): Promise => { - return await fetch("/api/stripe/create-event-checkout", { - method: "POST", - headers: { "Content-Type": "application/json" }, - // add our own priceId here later for different products - body: JSON.stringify(payload), - }) - .then((res) => res.json()) - .then((data) => data.clientSecret); -}; - -export const fetchMembershipCheckoutSecret = async (payload: { - stripeKey: string; -}): Promise => { - return await fetch("/api/stripe/create-membership-checkout", { - method: "POST", - headers: { "Content-Type": "application/json" }, - // add our own priceId here later for different products - body: JSON.stringify(payload), - }) - .then((res) => res.json()) - .then((data) => data.clientSecret); + return response.data; }; diff --git a/web/src/components/forms/CheckoutInformation.tsx b/web/src/components/forms/CheckoutInformation.tsx index bae9e65..51126f4 100644 --- a/web/src/components/forms/CheckoutInformation.tsx +++ b/web/src/components/forms/CheckoutInformation.tsx @@ -6,7 +6,7 @@ import { Mapper } from "@utils/Mapper"; import LoadingSpinner from "@components/navigation/LoadingSpinner"; import { useNavigate } from "react-router"; import CheckoutInformationForm from "./CheckoutInformationForm"; -import { updateUserTicketInfo } from "../../api/apiRequests"; +import { useUpdateUserTicketInfo } from "../../hooks/api/useUpdateUserTicketInfo"; interface CheckoutInformationProps { ticketId: number; @@ -65,28 +65,32 @@ export default function CheckoutInformation({ const [submitError, setSubmitError] = useState(false); const [submitLoading, setSubmitLoading] = useState(false); - // Submit ticket information to backend - const onSubmit = async (data: any) => { - try { - const response = await updateUserTicketInfo(data); - if (response.status === 200) { - // Form Submission Successful - setSubmitLoading(false); - // Move user to payment screen after the user ticket id is received - navigateToPaymentScreen( - response.data.updateUserInfoOrNewUser.userTicketId - ); - } else { - setSubmitLoading(false); - setSubmitError(true); - } - } catch (error) { + const { + data: updateUserTicketInfoData, + mutateAsync, + status, + } = useUpdateUserTicketInfo(); + // Update status text as it changes + useEffect(() => { + if (status === "success") { + setSubmitLoading(false); + navigateToPaymentScreen( + updateUserTicketInfoData.updateUserInfoOrNewUser.userTicketId + ); + } + + if (status == "pending") { + setSubmitLoading(true); + } else { setSubmitLoading(false); + } + + if (status == "error") { setSubmitError(true); } - }; + }, [status]); - const handleSubmit = ( + const onSubmit = async ( event: React.FormEvent, name: string, email: string, @@ -101,8 +105,8 @@ export default function CheckoutInformation({ return rest; }); - // call post request - onSubmit({ + // Mutation hook + mutateAsync({ ticketId: ticketId, name: name, email: email, @@ -125,7 +129,7 @@ export default function CheckoutInformation({ return ticketAndQuestions ? (
diff --git a/web/src/components/forms/CheckoutInformationForm.tsx b/web/src/components/forms/CheckoutInformationForm.tsx index d546ba7..b6137a1 100644 --- a/web/src/components/forms/CheckoutInformationForm.tsx +++ b/web/src/components/forms/CheckoutInformationForm.tsx @@ -187,7 +187,8 @@ export default function CheckoutInformationForm({
If this is a double ticket, both ticket holders must be members.
- If this is a Member ticket, make sure you haven't already purchased a Member ticket. + If this is a Member ticket, make sure you haven't already purchased + a Member ticket.

) : ( <> diff --git a/web/src/components/membership-page/PurchaseMembershipCard.tsx b/web/src/components/membership-page/PurchaseMembershipCard.tsx index cb95f6e..f11fc18 100644 --- a/web/src/components/membership-page/PurchaseMembershipCard.tsx +++ b/web/src/components/membership-page/PurchaseMembershipCard.tsx @@ -49,7 +49,7 @@ export default function PurchaseMembershipCard({ } return ( -
+
+
+
+
+

+ Payment Failed +

+
+
+
+

+ Please try again, or contact{" "} + + {EmailLink} + + . +

+
+ +
+ +
+
+ + ); +} diff --git a/web/src/hooks/api/useAttendanceUpdateMutation.ts b/web/src/hooks/api/useAttendanceUpdateMutation.ts index 3190521..a2243fa 100644 --- a/web/src/hooks/api/useAttendanceUpdateMutation.ts +++ b/web/src/hooks/api/useAttendanceUpdateMutation.ts @@ -1,5 +1,5 @@ import { useMutation } from "@tanstack/react-query"; -import { postAttendanceUpdate as PatchAttendanceUpdate } from "../../api/apiRequests"; +import { postAttendanceUpdate } from "../../api/apiRequests"; import { AttendanceReturn } from "../../types/types"; export const useUpdateAttendance = () => { @@ -9,6 +9,6 @@ export const useUpdateAttendance = () => { { peopleTicketId: number; attendance: boolean } >({ mutationFn: ({ peopleTicketId, attendance }) => - PatchAttendanceUpdate(peopleTicketId, attendance), + postAttendanceUpdate(peopleTicketId, attendance), }); }; diff --git a/web/src/hooks/api/useEventOrMembershipCheckoutSecret.ts b/web/src/hooks/api/useEventOrMembershipCheckoutSecret.ts new file mode 100644 index 0000000..c6e863b --- /dev/null +++ b/web/src/hooks/api/useEventOrMembershipCheckoutSecret.ts @@ -0,0 +1,14 @@ +import { useMutation } from "@tanstack/react-query"; +import { fetchEventOrMembershipCheckoutSecret } from "../../api/apiRequests"; +import { EventOrMembershipReturn } from "../../types/types"; + +export const useEventOrMembershipCheckoutSecret = () => { + return useMutation< + EventOrMembershipReturn, + Error, + { priceId: string; userTicketId: number } + >({ + mutationFn: ({ priceId, userTicketId }) => + fetchEventOrMembershipCheckoutSecret(priceId, userTicketId), + }); +}; diff --git a/web/src/hooks/api/useSessionStatus.ts b/web/src/hooks/api/useSessionStatus.ts new file mode 100644 index 0000000..61e94d7 --- /dev/null +++ b/web/src/hooks/api/useSessionStatus.ts @@ -0,0 +1,10 @@ +import { useQuery } from "@tanstack/react-query"; +import { getSessionStatus } from "../../api/apiRequests"; +import { stripeSessionStatus } from "../../types/types"; + +export const useSessionStatus = (sessionId: string) => { + return useQuery({ + queryKey: ["attendanceList"], + queryFn: () => getSessionStatus(sessionId), + }); +}; diff --git a/web/src/hooks/api/useUpdateUserInfo.ts b/web/src/hooks/api/useUpdateUserInfo.ts new file mode 100644 index 0000000..24fd3f3 --- /dev/null +++ b/web/src/hooks/api/useUpdateUserInfo.ts @@ -0,0 +1,38 @@ +import { useMutation } from "@tanstack/react-query"; +import { updateUserInfo } from "../../api/apiRequests"; +import { AxiosResponse } from "axios"; + +export const useUpdateUserInfo = () => { + return useMutation< + AxiosResponse, + Error, + { + name: string; + universityId: string; + upi: string; + yearOfStudy: string; + fieldOfStudy: string; + isDomestic: string; + institution: string; + } + >({ + mutationFn: ({ + name, + universityId, + upi, + yearOfStudy, + fieldOfStudy, + isDomestic, + institution, + }) => + updateUserInfo( + name, + universityId, + upi, + yearOfStudy, + fieldOfStudy, + isDomestic, + institution + ), + }); +}; diff --git a/web/src/hooks/api/useUpdateUserTicketInfo.ts b/web/src/hooks/api/useUpdateUserTicketInfo.ts new file mode 100644 index 0000000..dd06e6c --- /dev/null +++ b/web/src/hooks/api/useUpdateUserTicketInfo.ts @@ -0,0 +1,20 @@ +import { useMutation } from "@tanstack/react-query"; +import { updateUserTicketInfo } from "../../api/apiRequests"; +import { AnswerList, UpdateUserInfoOrNewUser } from "../../types/types"; + +export const useUpdateUserTicketInfo = () => { + return useMutation< + UpdateUserInfoOrNewUser, + Error, + { + ticketId: number; + name: string; + email: string; + phoneNumber: string; + answers: AnswerList[]; + } + >({ + mutationFn: ({ ticketId, name, email, phoneNumber, answers }) => + updateUserTicketInfo(ticketId, name, email, phoneNumber, answers), + }); +}; diff --git a/web/src/main.tsx b/web/src/main.tsx index 349ac76..c46b09e 100644 --- a/web/src/main.tsx +++ b/web/src/main.tsx @@ -38,7 +38,8 @@ import CheckoutInformationScreen from "./screens/CheckoutInformationScreen.tsx"; import AttendanceScreen from "./screens/AttendanceScreen.tsx"; import EventAttendanceSelectScreen from "./screens/EventAttendanceSelectScreen.tsx"; import { ExecRoute } from "@utils/AdminRouteProtection.tsx"; -import { getUserMetadaData } from "./api/apiRequests.ts"; +import { getUserMetaData } from "./api/apiRequests.ts"; +import { UserRoute } from "@utils/UserRouteProtection.tsx"; //supertokens code SuperTokens.init({ @@ -62,10 +63,8 @@ SuperTokens.init({ getRedirectionURL: async (context) => { if (context.action === "SUCCESS" && context.newSessionCreated) { let redirectionURL = "/"; - try { - const userMetadata = await getUserMetadaData(); - + const userMetadata = await getUserMetaData(); if (userMetadata.status === 200) { if (userMetadata.data!.bIsUserInfoComplete === false) { redirectionURL = "/signup/information"; @@ -87,7 +86,6 @@ SuperTokens.init({ "There was error after logging in. Please contact the AUIS admin for further assistance." ); } - return redirectionURL; } else if (context.action === "TO_AUTH") { return "/signup"; @@ -127,7 +125,11 @@ const router = createBrowserRouter( /> } />} + element={ + + } /> + + } />

Our Values

{errorValues ? ( -
There are no values to display
+
+ There are no values to display +
) : (
{values.map((value) => ( diff --git a/web/src/screens/AttendanceScreen.tsx b/web/src/screens/AttendanceScreen.tsx index fce1711..b847646 100644 --- a/web/src/screens/AttendanceScreen.tsx +++ b/web/src/screens/AttendanceScreen.tsx @@ -138,10 +138,6 @@ export default function AttendanceScreen({ navbar }: { navbar: JSX.Element }) { return ; } - const abc: MediaTrackConstraints = { - deviceId: cameraId, - }; - return (
{navbar} @@ -162,8 +158,9 @@ export default function AttendanceScreen({ navbar }: { navbar: JSX.Element }) { onScan={(result) => onQRcodeScanned(result)} allowMultiple={true} scanDelay={1000} - constraints={abc} + constraints={{ deviceId: cameraId }} onError={() => setCameraError(true)} + styles={{}} /> )}
@@ -231,7 +228,7 @@ export default function AttendanceScreen({ navbar }: { navbar: JSX.Element }) { Attendance: {totalCheckedIn} {" / "} {totalAttendees}

-
+

Select the camera you wish to use

diff --git a/web/src/screens/CheckoutScreen.tsx b/web/src/screens/CheckoutScreen.tsx index 7c2581c..a41d1f5 100644 --- a/web/src/screens/CheckoutScreen.tsx +++ b/web/src/screens/CheckoutScreen.tsx @@ -1,12 +1,13 @@ -import { useCallback } from "react"; +import { useEffect } from "react"; import { loadStripe } from "@stripe/stripe-js"; import { EmbeddedCheckoutProvider, EmbeddedCheckout, } from "@stripe/react-stripe-js"; -import { useLocation } from "react-router"; -import { fetchEventOrMembershipCheckoutSecret } from "../api/apiRequests"; +import { useLocation, useNavigate } from "react-router"; import CheckoutError from "@components/checkout-page/CheckoutError"; +import { useEventOrMembershipCheckoutSecret } from "../hooks/api/useEventOrMembershipCheckoutSecret"; +import LoadingSpinner from "@components/navigation/LoadingSpinner"; const STRIPE_PUBLISHABLE_KEY = import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY; @@ -15,8 +16,11 @@ const STRIPE_PUBLISHABLE_KEY = import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY; const stripePromise = loadStripe(`${STRIPE_PUBLISHABLE_KEY}`); let bodyData: { priceId: string; userTicketId: number }; export default function CheckoutScreen() { + const { data, mutateAsync, status } = useEventOrMembershipCheckoutSecret(); const location = useLocation(); + const navigate = useNavigate(); // ensure data required for checkout is here + try { bodyData = { priceId: location.state.data.priceId, @@ -26,29 +30,58 @@ export default function CheckoutScreen() { return ; } - // Stripe payments - const fetchClientSecret = useCallback(async () => { - // Create a Checkout Session - return await fetchEventOrMembershipCheckoutSecret(bodyData); + useEffect(() => { + mutateAsync(bodyData); }, []); - // can be null to options.clientSecret or options.fetchClientSecret if you are performing an initial server-side render or when generating a static site. - const options = { fetchClientSecret }; + if (status === "pending") { + return ; + } - return ( -
-
-

Checkout

+ if (status === "error" || data?.clientSecret === undefined) { + return ( +
+
+

Checkout

+
+
+

+ Sorry an error occurred, please try again +
+ This could be caused by ticket being sold out, or some other error +

+
+
+ +
-
-
- - - + ); + } + + if (status === "success") { + return ( +
+
+

Checkout

+
+
+
+ + + +
-
- ); + ); + } } diff --git a/web/src/screens/HomeScreen.tsx b/web/src/screens/HomeScreen.tsx index ebceb70..0aeca09 100644 --- a/web/src/screens/HomeScreen.tsx +++ b/web/src/screens/HomeScreen.tsx @@ -54,11 +54,9 @@ export default function HomeScreen({ navbar }: { navbar: JSX.Element }) { if (!photosLoading) { setLoadingPhotos(false); } - if (photosError) { setErrorPhotos(true); } - if (photosData) { try { const mappedPhotos = Mapper.mapToSomePhotos(photosData); diff --git a/web/src/screens/ReturnScreen.tsx b/web/src/screens/ReturnScreen.tsx index 344032e..43199f4 100644 --- a/web/src/screens/ReturnScreen.tsx +++ b/web/src/screens/ReturnScreen.tsx @@ -1,84 +1,65 @@ import { useState, useEffect } from "react"; import { Navigate, useNavigate } from "react-router"; import { EmailLink } from "../data/data"; -import { getSessionStatus } from "../api/apiRequests"; +import { useSessionStatus } from "../hooks/api/useSessionStatus"; +import LoadingSpinner from "@components/navigation/LoadingSpinner"; +import { useSearchParams } from "react-router-dom"; +import ErrorReturn from "@components/return-page/ErrorReturn"; export default function ReturnScreen() { + let sessionId: string; + const [searchParams] = useSearchParams(); + const session_id = searchParams.get("session_id"); + if (session_id !== null && session_id.length !== 0) { + sessionId = session_id; + } else { + return ; + } + const navigate = useNavigate(); - const [status, setStatus] = useState(null); + const [status, setStatus] = useState(""); const [customerEmail, setCustomerEmail] = useState(""); - //TODO: @gmat224: Refactor this useEffect function. + const { data: sessionStatus, status: sessionStatusHookStatus } = + useSessionStatus(sessionId); + + // update values once data is fetched useEffect(() => { - async function getSessionData() { - const queryString = window.location.search; - const urlParams = new URLSearchParams(queryString); - const sessionId = urlParams.get("session_id"); - const response = await getSessionStatus(sessionId!); - return response; + if (sessionStatusHookStatus === "success") { + setStatus(sessionStatus.status); + setCustomerEmail(sessionStatus.customer_email); } + }, [sessionStatusHookStatus, sessionStatus]); - getSessionData().then((response) => { - setStatus(response.data.status); - setCustomerEmail(response.data.customer_email); - }); - }, []); - - if (status === "open") { - return ; + if (sessionStatusHookStatus === "pending") { + return ; } - if (status === "complete") { - return ( -
-
-
-

- Payment Successful -

-
-
-
-

- Payment successful, a confirmation email will be sent to{" "} - {customerEmail}. -

-

- If you have any questions, please email{" "} - - {EmailLink} - - . -

-
+ if (sessionStatusHookStatus === "error") { + return ; + } -
- -
-
- ); - } else { - return ( - <> + if (sessionStatusHookStatus === "success") { + if (status === "open") { + return ; // switch to useNavigate TODO + } + if (status === "complete") { + return (

- Payment Failed + Payment Successful

-

- Please try again, or contact{" "} +

+ Payment successful, a confirmation email will be sent to{" "} + {customerEmail}. +

+

+ If you have any questions, please email{" "} {EmailLink} @@ -98,7 +79,9 @@ export default function ReturnScreen() {

- - ); + ); + } + } else { + ; } } diff --git a/web/src/screens/SignUpInformationScreen.tsx b/web/src/screens/SignUpInformationScreen.tsx index 0db5b61..e7023c6 100644 --- a/web/src/screens/SignUpInformationScreen.tsx +++ b/web/src/screens/SignUpInformationScreen.tsx @@ -4,7 +4,8 @@ import { zodResolver } from "@hookform/resolvers/zod"; import TextQuestion from "@components/forms/TextQuestion"; import DropdownQuestion from "@components/forms/DropdownQuestion"; import { useState } from "react"; -import { updateUserInfo } from "../api/apiRequests"; +import { useUpdateUserInfo } from "../hooks/api/useUpdateUserInfo"; +import LoadingSpinner from "@components/navigation/LoadingSpinner"; const SignUpSchema = z.object({ name: z.string().max(40).min(1), @@ -47,19 +48,10 @@ export default function SignUpInformationScreen({ const [formError, setFormError] = useState(false); - const sendSignUpData = async (data: object) => { - try { - const response = await updateUserInfo(data); + const { status, mutateAsync } = useUpdateUserInfo(); - if (response.status === 200) { - window.location.href = "/membership"; - } else { - // Form Submission Failed - setFormError(true); - } - } catch (error) { - setFormError(true); - } + const sendSignUpData = async (data: SignUpSchemaType) => { + mutateAsync(data); }; const onSubmit: SubmitHandler = (data) => { @@ -92,6 +84,18 @@ export default function SignUpInformationScreen({ { id: 3, text: "None" }, ]; + if (status === "success") { + window.location.href = "/membership"; + } + + if (status === "error") { + setFormError(true); + } + + if (status === "pending") { + return ; + } + return (
{navbar} diff --git a/web/src/types/types.ts b/web/src/types/types.ts index bd025da..5091e8c 100644 --- a/web/src/types/types.ts +++ b/web/src/types/types.ts @@ -134,3 +134,36 @@ export interface AttendanceList { export interface AttendanceReturn { name: string; } + +export interface EventOrMembershipReturn { + clientSecret: string; +} + +export interface stripeSessionStatus { + customer_email: string; + status: string; +} +// data { updateUserInfoOrNewUser { userTicketId}} +export interface UpdateUserInfoOrNewUser { + updateUserInfoOrNewUser: { + userTicketId: number; + }; +} + +export interface SubmitUpdateUserInfoOrNewUser { + ticketId: number; + name: string; + email: string; + phoneNumber: string; + answers: QuestionAnswer[]; +} + +export interface AnswerList { + questionId: number; + answer: string; +} + +export interface UserMetaData { + bIsUserInfoComplete: boolean; + bIsMembershipPaymentComplete: boolean; +} diff --git a/web/src/utils/UserRouteProtection.tsx b/web/src/utils/UserRouteProtection.tsx new file mode 100644 index 0000000..7a044ed --- /dev/null +++ b/web/src/utils/UserRouteProtection.tsx @@ -0,0 +1,15 @@ +import { SessionAuth } from "supertokens-auth-react/recipe/session"; +import ErrorScreen from "../screens/ErrorScreen"; + +export const UserRoute = (props: React.PropsWithChildren) => { + return ( + [ + ...globalValidators, + ]} + > + {props.children} + + ); +};