diff --git a/frontend/src/components/my-grant-profile-page-handler/index.tsx b/frontend/src/components/my-grant-profile-page-handler/index.tsx new file mode 100644 index 0000000000..9013250d8b --- /dev/null +++ b/frontend/src/components/my-grant-profile-page-handler/index.tsx @@ -0,0 +1,42 @@ +import { Heading, Page, Section } from "@python-italia/pycon-styleguide"; +import { FormattedMessage } from "react-intl"; + +import { useMyProfileWithGrantQuery } from "~/types"; + +import { MetaTags } from "../meta-tags"; +import { NoGrant } from "../my-grant-profile-page-handler/no-grant"; +import { MyGrant } from "./my-grant"; + +export const MyGrantProfilePageHandler = () => { + const { + data: { + me, + conference: { deadline }, + }, + error, + } = useMyProfileWithGrantQuery({ + variables: { + conference: process.env.conferenceCode, + }, + }); + console.log(error); + console.log("HOLA"); + const grant = me?.grant; + return ( + + + {(text) => } + + +
+ + + +
+
+ {grant && } + {!grant && } +
+
+ ); +}; diff --git a/frontend/src/components/my-grant-profile-page-handler/my-grant.tsx b/frontend/src/components/my-grant-profile-page-handler/my-grant.tsx new file mode 100644 index 0000000000..8fa2b52745 --- /dev/null +++ b/frontend/src/components/my-grant-profile-page-handler/my-grant.tsx @@ -0,0 +1,207 @@ +import { + Button, + CardPart, + Grid, + GridColumn, + HorizontalStack, + MultiplePartsCard, + Spacer, + Tag, + Text, + VerticalStack, +} from "@python-italia/pycon-styleguide"; +import { FormattedMessage } from "react-intl"; + +import { useCurrentLanguage } from "~/locale/context"; +import { DeadlineStatus, Status as GrantStatus } from "~/types"; +import type { MyProfileWithGrantQuery } from "~/types"; + +import { useCountries } from "~/helpers/use-countries"; +import { createHref } from "../link"; +import { Sidebar } from "./sidebar"; + +type Props = { + grant: MyProfileWithGrantQuery["me"]["grant"]; + deadline: MyProfileWithGrantQuery["conference"]["deadline"]; +}; + +export const MyGrant = ({ grant, deadline }: Props) => { + const language = useCurrentLanguage(); + const countries = useCountries(); + + const canManageGrant = [ + GrantStatus.WaitingForConfirmation, + GrantStatus.Confirmed, + GrantStatus.WaitingList, + GrantStatus.WaitingListMaybe, + ].includes(grant.status); + + const getCountryLabel = (value: string): string | undefined => { + const country = countries.find((country) => country.value === value); + return country ? country.label : undefined; + }; + + const dateFormatter = new Intl.DateTimeFormat(language, { + day: "numeric", + month: "long", + year: "numeric", + }); + + return ( + <> + + + + + + + +
+ + <FormattedMessage id="profile.myGrant.nextSteps" /> + + + + + {dateFormatter.format( + new Date(grant.applicantReplyDeadline), + )} + + ), + }} + /> + +
+ + + + + <FormattedMessage id="grants.form.fields.name" /> + + + {grant.name} + + + + + <FormattedMessage id="grants.form.fields.fullName" /> + + + {grant.fullName} + + + + + <FormattedMessage id="grants.form.fields.ageGroup" /> + + + + + + + + + + + <FormattedMessage id="grants.form.fields.travellingFrom" /> + + + {getCountryLabel(grant.travellingFrom)} + + + + + <FormattedMessage id="grants.form.fields.gender" /> + + + + + + + + + + <FormattedMessage id="grants.form.fields.occupation" /> + + + + + + + +
+
+
+ + + + + + {deadline.status === DeadlineStatus.HappeningNow && ( + + )} + + {canManageGrant && ( + <> + + + + )} + + + {deadline.status === DeadlineStatus.HappeningNow && ( + + + {dateFormatter.format(new Date(deadline.end))} + + ), + }} + /> + + )} + + + + ); +}; + +const Title = ({ children }: { children: React.ReactNode }) => ( + + {children} + +); diff --git a/frontend/src/components/my-grant-profile-page-handler/no-grant.tsx b/frontend/src/components/my-grant-profile-page-handler/no-grant.tsx new file mode 100644 index 0000000000..71bea68ac0 --- /dev/null +++ b/frontend/src/components/my-grant-profile-page-handler/no-grant.tsx @@ -0,0 +1,55 @@ +import { + Button, + Container, + Heading, + Spacer, + Text, +} from "@python-italia/pycon-styleguide"; +import { FormattedMessage } from "react-intl"; + +import { useCurrentLanguage } from "~/locale/context"; +import { DeadlineStatus, type MyProfileWithSubmissionsQuery } from "~/types"; + +import { createHref } from "../link"; + +type Props = { + deadline: MyProfileWithSubmissionsQuery["conference"]["deadline"]; +}; + +export const NoGrant = ({ deadline }: Props) => { + const deadlineStatus = deadline.status; + const language = useCurrentLanguage(); + + return ( + + + + + + + {deadlineStatus === DeadlineStatus.HappeningNow && ( + + )} + {deadlineStatus === DeadlineStatus.InThePast && ( + + )} + {deadlineStatus === DeadlineStatus.InTheFuture && ( + + )} + + + {(deadlineStatus === DeadlineStatus.HappeningNow || + deadlineStatus === DeadlineStatus.InTheFuture) && ( + + )} + + ); +}; diff --git a/frontend/src/components/my-grant-profile-page-handler/profile-with-my-grant.graphql b/frontend/src/components/my-grant-profile-page-handler/profile-with-my-grant.graphql new file mode 100644 index 0000000000..735e4a31c9 --- /dev/null +++ b/frontend/src/components/my-grant-profile-page-handler/profile-with-my-grant.graphql @@ -0,0 +1,33 @@ +query MyProfileWithGrant($conference: String!) { + me { + id + name + fullName + email + + grant(conference: $conference) { + id + status + name + fullName + ageGroup + travellingFrom + gender + occupation + applicantReplyDeadline + + needVisa + needsFundsForTravel + needAccommodation + grantType + } + } + conference(code: $conference) { + id + deadline(type: "grants") { + id + status + end + } + } +} diff --git a/frontend/src/components/my-grant-profile-page-handler/sidebar.tsx b/frontend/src/components/my-grant-profile-page-handler/sidebar.tsx new file mode 100644 index 0000000000..0a491212e8 --- /dev/null +++ b/frontend/src/components/my-grant-profile-page-handler/sidebar.tsx @@ -0,0 +1,96 @@ +import { + Button, + CardPart, + Grid, + GridColumn, + MultiplePartsCard, + Spacer, + Tag, + Text, + VerticalStack, +} from "@python-italia/pycon-styleguide"; +import type React from "react"; +import type { GrantType, Status } from "~/types"; + +import { FormattedMessage } from "react-intl"; + +type Props = { + status: Status; + grantType: GrantType; + needsFundsForTravel: boolean; + needAccommodation: boolean; +}; + +export const Sidebar = ({ + status, + grantType, + needsFundsForTravel, + needAccommodation, +}: Props) => { + const grantStatusColors = { + approved: "green", + confirmed: "green", + did_not_attend: "red", + pending: "gray", + refused: "red", + rejected: "red", + waiting_for_confirmation: "yellow", + waiting_list: "coral", + waiting_list_maybe: "coral", + }; + + return ( + <> + + }> + + + + + + }> + + + + }> + + + + {needsFundsForTravel && ( + + + + )} + + {needAccommodation && ( + + + + )} + + + + + ); +}; + +const GrantInfo = ({ + label, + children, +}: { + children: React.ReactNode; + label?: React.ReactNode; +}) => ( + + + {label} + + + + + {children} + + +); diff --git a/frontend/src/components/profile-page-handler/index.tsx b/frontend/src/components/profile-page-handler/index.tsx index 187976aba6..2f14df992f 100644 --- a/frontend/src/components/profile-page-handler/index.tsx +++ b/frontend/src/components/profile-page-handler/index.tsx @@ -119,6 +119,16 @@ export const ProfilePageHandler = () => { icon: "circle", iconBackground: "purple", }, + { + id: "grants", + link: createHref({ + path: "/profile/my-grant", + locale: language, + }), + label: , + icon: "star", + iconBackground: "yellow", + }, isStaffOrSponsor ? { id: "sponsors", diff --git a/frontend/src/components/schedule-event-detail/index.tsx b/frontend/src/components/schedule-event-detail/index.tsx index d0771cac98..49faec398b 100644 --- a/frontend/src/components/schedule-event-detail/index.tsx +++ b/frontend/src/components/schedule-event-detail/index.tsx @@ -131,6 +131,7 @@ export const ScheduleEventDetail = ({ )} +
diff --git a/frontend/src/locale/index.ts b/frontend/src/locale/index.ts index 003488ddec..a3a9cf8bcc 100644 --- a/frontend/src/locale/index.ts +++ b/frontend/src/locale/index.ts @@ -120,6 +120,60 @@ Let's get in touch to find the best solution for your business' needs!`, "profile.welcome": "Ciao {name}!", "profile.myProfile": "My Profile", + + "profile.myGrant": "My Grant", + "profile.myGrant.nextSteps": "Next steps", + "profile.myGrant.grantType": "Type of Grant", + "profile.myGrant.appliedFor": "Applied for", + "profile.myGrant.appliedFor.ticket": "Ticket", + "profile.myGrant.appliedFor.travel": "Travel", + "profile.myGrant.appliedFor.accommodation": "Accommodation", + "profile.myGrant.status": "Status", + "profile.myGrant.status.pending": "Pending", + "profile.myGrant.status.pending.nextSteps": + "Your application is under review. No further action is needed from you at this moment.", + "profile.myGrant.status.rejected": "Rejected", + "profile.myGrant.status.rejected.nextSteps": + "Unfortunately, your grant request has been rejected due to limited funds and a high number of applications. For details, check the email we sent you.", + "profile.myGrant.status.approved": "Approved", + "profile.myGrant.status.approved.nextSteps": + "Your grant request is approved. We will send you an email very soon with further instructions. Check your email regularly.", + "profile.myGrant.status.waiting_list": "Waiting List", + "profile.myGrant.status.waiting_list.nextSteps": `You are on the waiting list. We will notify you if additional funds become available or if someone withdraws their grant. + +If your circumstances change or you wish to withdraw your grant request, please inform us immediately.`, + "profile.myGrant.status.waiting_list_maybe": "Waiting List", + "profile.myGrant.status.waiting_list_maybe.nextSteps": `You are on the waiting list. We will notify you if additional funds become available or if someone withdraws their grant. + +If your circumstances change or you wish to withdraw your grant request, please inform us immediately.`, + "profile.myGrant.status.waiting_for_confirmation": + "Waiting for Confirmation", + "profile.myGrant.status.waiting_for_confirmation.nextSteps": `Your grant has been approved. Please CONFIRM your acceptance by clicking the 'Manage' button on this page as soon as possible.You have until {deadlineDate} to do so. + +If we don't hear from you before {replyDeadline}, we will allocate your grant to another person. + `, + "profile.myGrant.status.refused": "Refused", + "profile.myGrant.status.refused.nextSteps": + "You have declined the grant offer. If this was a mistake, please contact us immediately.", + "profile.myGrant.status.confirmed": "Confirmed", + "profile.myGrant.status.confirmed.nextSteps": + "Thank you for confirming your grant. We look forward to seeing you at the conference! We will send you the voucher for the ticket soon, please keep check your email regularly.", + "profile.myGrant.status.did_not_attend": "Did Not Attend", + "profile.myGrant.status.did_not_attend.nextSteps": + "You did not attend the conference.", + "profile.myGrant.edit": "Edit", + "profile.myGrant.editInfo": + "Ensure all the information provided are correct. You have until {editDeadline} to edit your information.", + "profile.myGrant.manage": "Manage", + "profile.myGrant.noGrant.heading": "You haven't requested a grant yet", + "profile.myGrant.noGrant.body.canSubmit": + "If you're facing financial difficulties and wish to attend PyCon Italia, our grant application form is currently open. Submit your grant request today!", + "profile.myGrant.noGrant.submitGrant": "Request a grant", + "profile.myGrant.noGrant.body.closed": + "The grant application form is currently closed. Stay tuned for future opportunities.", + "profile.myGrant.noGrant.body.openingSoon": + "Our grant application form will open soon! Check our information page for more details.", + "profile.logout": "Sign Out", "profile.logout.title": "Sign Out", "profile.logout.body": "{name}, are you sure you want to sign out?", @@ -1909,6 +1963,57 @@ Affrettati a comprare il biglietto!`, "profile.myProfile": "Il mio profilo", "profile.myProposals": "Le mie proposte", + "profile.myGrant": "Il mio grant", + "profile.myGrant.nextSteps": "I prossimi passi", + "profile.myGrant.grantType": "Tipo di Grant", + "profile.myGrant.appliedFor": "Richiesto", + "profile.myGrant.appliedFor.ticket": "Biglietto", + "profile.myGrant.appliedFor.travel": "Viaggio", + "profile.myGrant.appliedFor.accommodation": "Alloggio", + "profile.myGrant.edit": "Modifica", + "profile.myGrant.editInfo": + "Assicurati che tutte le informazioni fornite siano corrette. Hai tempo fino al {editDeadline} per modificare il tuo grant.", + "profile.myGrant.manage": "Gestisci", + "profile.myGrant.status": "Stato", + "profile.myGrant.status.pending": "In attesa", + "profile.myGrant.status.pending.nextSteps": + "La tua domanda è in fase di revisione. Non è richiesta alcuna azione in questo momento.", + "profile.myGrant.status.rejected": "Respinto", + "profile.myGrant.status.rejected.nextSteps": + "Purtroppo, a causa dei fondi limitati e del grande numero di richieste, la tua richiesta di grant non è stata approvata. Controlla la tua email per maggiori dettagli.", + "profile.myGrant.status.approved": "Approvato", + "profile.myGrant.status.approved.nextSteps": + "La tua richiesta di grant è stata approvata. Ti invieremo un'email molto presto con ulteriori istruzioni. Per favore, controlla la tua email per queste istruzioni.", + "profile.myGrant.status.waiting_list": "Lista d'attesa", + "profile.myGrant.status.waiting_list.nextSteps": `Sei in lista d'attesa. Ti informeremo non appena diventano disponibili ulteriori fondi o se qualcuno rifiuta il suo grant. + +Se le tue circostanze cambiano o non hai più bisogno del grant, informaci al più presto.`, + "profile.myGrant.status.waiting_list_maybe": "Lista d'attesa", + "profile.myGrant.status.waiting_list_maybe.nextSteps": `Sei in lista d'attesa. Ti informeremo non appena diventano disponibili ulteriori fondi o se qualcuno rifiuta il suo grant. + +Se le tue circostanze cambiano o non hai più bisogno del grant, informaci al più presto.`, + "profile.myGrant.status.waiting_for_confirmation": "Da confermare", + "profile.myGrant.status.waiting_for_confirmation.nextSteps": `Il tuo grant è stato approvato. Conferma la tua accettazione cliccando il pulsante 'Gestisci' in questa pagina il prima possibile. Hai tempo fino al {replyDeadline} per farlo. + +Se non riceviamo tue notizie entro il {replyDeadline}, assegneremo il tuo grant a un'altra persona. `, + "profile.myGrant.status.refused": "Rifiutato", + "profile.myGrant.status.refused.nextSteps": + "Hai rifiutato l'offerta di grant. Se si tratta di un errore, contattaci immediatamente.", + "profile.myGrant.status.confirmed": "Confermato", + "profile.myGrant.status.confirmed.nextSteps": + "Grazie per aver confermato il tuo grant. Non vediamo l'ora di vederti alla conferenza! Ti invieremo a breve il voucher per il biglietto, tieni controllata la tua email", + "profile.myGrant.status.did_not_attend": "Non ha partecipato", + "profile.myGrant.status.did_not_attend.nextSteps": + "Non hai partecipato alla conferenza.", + "profile.myGrant.noGrant.heading": "Non hai ancora richiesto un grant", + "profile.myGrant.noGrant.body.canSubmit": + "Se ti trovi in difficoltà economica e desideri partecipare a PyCon Italia, il nostro modulo per la richiesta di grant è attualmente aperto. Invia la tua richiesta oggi stesso!", + "profile.myGrant.noGrant.submitGrant": "Richiedi un grant", + "profile.myGrant.noGrant.body.closed": + "Il modulo per la richiesta di grant è attualmente chiuso. Continua a seguirci per future opportunità.", + "profile.myGrant.noGrant.body.openingSoon": + "Il nostro modulo per la richiesta di grant aprirà presto! Visita la nostra pagina informativa per ulteriori dettagli.", + "profile.editProfile.generalInformation": "Informazioni generali", "profile.editProfile.emailPreferences": "Preferenze Email", "profile.tickets.attendeeName": "Nome partecipante", diff --git a/frontend/src/pages/profile/my-grant.tsx b/frontend/src/pages/profile/my-grant.tsx new file mode 100644 index 0000000000..9886d8d3cb --- /dev/null +++ b/frontend/src/pages/profile/my-grant.tsx @@ -0,0 +1,49 @@ +import type { GetServerSideProps } from "next"; + +import { addApolloState, getApolloClient } from "~/apollo/client"; +import { prefetchSharedQueries } from "~/helpers/prefetch"; +import { queryCountries, queryMyProfileWithGrant } from "~/types"; + +export const getServerSideProps: GetServerSideProps = async ({ + req, + locale, +}) => { + const identityToken = req.cookies.pythonitalia_sessionid; + if (!identityToken) { + return { + redirect: { + destination: "/login", + permanent: false, + }, + }; + } + + const client = getApolloClient(null, req.cookies); + + try { + await Promise.all([ + prefetchSharedQueries(client, locale), + queryCountries(client), + queryMyProfileWithGrant(client, { + conference: process.env.conferenceCode, + }), + ]); + } catch (e) { + return { + redirect: { + destination: "/login", + permanent: false, + }, + }; + } + + return addApolloState( + client, + { + props: {}, + }, + null, + ); +}; + +export { MyGrantProfilePageHandler as default } from "~/components/my-grant-profile-page-handler";