Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Grant to profile dashboard #3993

Merged
merged 25 commits into from
Oct 12, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 25 additions & 23 deletions frontend/src/components/grant-form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { FormattedMessage } from "react-intl";
import { useFormState } from "react-use-form-state";

import { Alert } from "~/components/alert";
import { MyGrant } from "~/components/profile/my-grant";
import { useCountries } from "~/helpers/use-countries";
import { useCurrentUser } from "~/helpers/use-current-user";
import { useTranslatedMessage } from "~/helpers/use-translated-message";
Expand All @@ -37,7 +36,7 @@ import {
type SendGrantMutation,
type UpdateGrantInput,
type UpdateGrantMutation,
useMyGrantQuery,
useGrantDeadlineQuery,
useSendGrantMutation,
} from "~/types";

Expand Down Expand Up @@ -76,18 +75,9 @@ export type GrantFormFields = {
acceptedPrivacyPolicy: boolean;
};

export const MyGrantOrForm = () => {
export const GrantSendForm = () => {
const code = process.env.conferenceCode;

const { error, data } = useMyGrantQuery({
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the user lands on pycon.it/grants page, if they have a grant, it automatically redirects to profile/my-grant. This component is only used when the user doesn't have a grant, so I removed the grant's fetching as it's no longer needed.

errorPolicy: "all",
variables: {
conference: code,
},
skip: typeof window === "undefined",
});
const grant = data?.me?.grant;

const [submitGrant, { loading, error: grantError, data: grantData }] =
useSendGrantMutation({
onError(err) {
Expand All @@ -103,14 +93,6 @@ export const MyGrantOrForm = () => {
});
};

if (error) {
return <Alert variant="alert">{error.message}</Alert>;
}

if (grant) {
return <MyGrant />;
}

return (
<>
<Section>
Expand Down Expand Up @@ -155,6 +137,16 @@ export const GrantForm = ({
const language = useCurrentLanguage();
const countries = useCountries();

const {
data: {
conference: { deadline },
},
} = useGrantDeadlineQuery({
variables: {
conference: conference,
},
});

const inputPlaceholderText = useTranslatedMessage("input.placeholder");
const { user, loading: loadingUser } = useCurrentUser({});
const [formState, { text, textarea, select, checkbox }] =
Expand All @@ -167,6 +159,12 @@ export const GrantForm = ({
},
);

const dateFormatter = new Intl.DateTimeFormat(language, {
day: "numeric",
month: "long",
year: "numeric",
});

useEffect(() => {
// to not override if we are editing the grant
if (user && !grant) {
Expand Down Expand Up @@ -274,20 +272,24 @@ export const GrantForm = ({
<FormattedMessage
id="grants.form.sent"
values={{
linkGrant: (
linkMyGrant: (
<Link
href={createHref({ path: "/grants/edit", locale: language })}
href={createHref({
path: "/profile/my-grant",
locale: language,
})}
>
<Text
color="none"
decoration="underline"
size="inherit"
weight="strong"
>
<FormattedMessage id="grants.form.sent.linkGrant.text" />
<FormattedMessage id="grants.form.sent.linkMyGrant.text" />
</Text>
</Link>
),
grantsDeadline: dateFormatter.format(new Date(deadline.end)),
}}
/>
</Text>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Grid, Spacer, Text } from "@python-italia/pycon-styleguide";
import { FormattedMessage } from "react-intl";

import type { MyProfileWithGrantQuery } from "~/types";

import { TableItemHeader } from "~/components/table-item-header";
import { getCountryLabel } from "~/helpers/country-utils";
import { useCountries } from "~/helpers/use-countries";

type Props = {
grant: MyProfileWithGrantQuery["me"]["grant"];
};

export const GrantTableInfo = ({ grant }: Props) => {
const countries = useCountries();

return (
<Grid cols={3} gap="small" fullWidth>
<GrantInfo label={<FormattedMessage id="grants.form.fields.name" />}>
{grant.name}
</GrantInfo>

<GrantInfo label={<FormattedMessage id="grants.form.fields.fullName" />}>
{grant.fullName}
</GrantInfo>

<GrantInfo label={<FormattedMessage id="grants.form.fields.ageGroup" />}>
{grant.ageGroup && (
<FormattedMessage
id={`grants.form.fields.ageGroup.values.${grant.ageGroup}`}
/>
)}
</GrantInfo>

<GrantInfo
label={<FormattedMessage id="grants.form.fields.travellingFrom" />}
>
{getCountryLabel(countries, grant.travellingFrom)}
</GrantInfo>

<GrantInfo label={<FormattedMessage id="grants.form.fields.gender" />}>
{grant.gender ? (
<FormattedMessage id={`profile.gender.${grant.gender}`} />
) : (
"-"
)}
</GrantInfo>

<GrantInfo
label={<FormattedMessage id="grants.form.fields.occupation" />}
>
<FormattedMessage
id={`grants.form.fields.occupation.values.${grant.occupation}`}
/>
</GrantInfo>
</Grid>
);
};

const GrantInfo = ({
label,
children,
}: {
label: React.ReactNode;
children: React.ReactNode;
}) => {
return (
<div>
<TableItemHeader>{label}</TableItemHeader>
<Spacer size="xs" />
<Text>{children}</Text>
</div>
);
};
40 changes: 40 additions & 0 deletions frontend/src/components/my-grant-profile-page-handler/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Heading, Page, Section } from "@python-italia/pycon-styleguide";
import { FormattedMessage } from "react-intl";

import { useMyProfileWithGrantQuery } from "~/types";

import { MyGrant } from "~/components/my-grant-profile-page-handler/my-grant";
import { NoGrant } from "~/components/my-grant-profile-page-handler/no-grant";
import { MetaTags } from "../meta-tags";

export const MyGrantProfilePageHandler = () => {
const {
data: {
me: { grant },
conference: { deadline },
},
} = useMyProfileWithGrantQuery({
variables: {
conference: process.env.conferenceCode,
},
});

return (
<Page endSeparator={false}>
<FormattedMessage id="profile.myGrant">
{(text) => <MetaTags title={text} />}
</FormattedMessage>

<Section background="green">
<Heading size="display2">
<FormattedMessage id="profile.myGrant" />
</Heading>
</Section>

<Section>
{grant && <MyGrant grant={grant} deadline={deadline} />}
{!grant && <NoGrant deadline={deadline} />}
</Section>
</Page>
);
};
135 changes: 135 additions & 0 deletions frontend/src/components/my-grant-profile-page-handler/my-grant.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import {
Button,
Grid,
GridColumn,
Link,
Spacer,
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 { createHref } from "../link";
import { GrantTableInfo } from "./grant-table-info";
import { Sidebar } from "./sidebar";

type Props = {
grant: MyProfileWithGrantQuery["me"]["grant"];
deadline: MyProfileWithGrantQuery["conference"]["deadline"];
};

const grantManageableStatuses = [
GrantStatus.WaitingForConfirmation,
GrantStatus.Confirmed,
GrantStatus.WaitingList,
GrantStatus.WaitingListMaybe,
];

export const MyGrant = ({ grant, deadline }: Props) => {
const language = useCurrentLanguage();

const canManageGrant = grantManageableStatuses.includes(grant.status);

const dateFormatter = new Intl.DateTimeFormat(language, {
day: "numeric",
month: "long",
year: "numeric",
});

return (
<>
<Grid cols={12}>
<GridColumn colSpan={4}>
<Sidebar
status={grant.status}
grantType={grant.grantType}
needsFundsForTravel={grant.needsFundsForTravel}
needAccommodation={grant.needAccommodation}
/>
{deadline.status === DeadlineStatus.HappeningNow && (
<>
<Spacer size="medium" />
<Button
href={createHref({
path: "/grants/edit",
locale: language,
})}
size="small"
variant="secondary"
>
<FormattedMessage id="profile.myGrant.edit" />
</Button>
</>
)}
</GridColumn>

<GridColumn colSpan={8}>
<VerticalStack gap="medium" justifyContent="spaceBetween" fullHeight>
<div>
<Text size="label3" uppercase weight="strong">
<FormattedMessage id="profile.myGrant.nextSteps" />
</Text>
<Spacer size="xs" />
<Text>
<FormattedMessage
id={`profile.myGrant.status.${grant.status}.nextSteps`}
values={{
replyDeadline: (
<Text as="span" weight="strong">
{dateFormatter.format(
new Date(grant.applicantReplyDeadline),
)}
</Text>
),
}}
/>
</Text>
</div>

{canManageGrant && (
<div>
<Button
href={createHref({
path: "/grants/reply",
locale: language,
})}
size="small"
variant="secondary"
>
<FormattedMessage id="profile.myGrant.manage" />
</Button>
</div>
)}

<GrantTableInfo grant={grant} />

{deadline.status === DeadlineStatus.HappeningNow && (
<Text>
<FormattedMessage
id="profile.myGrant.editInfo"
values={{
editDeadline: (
<Text as="span" weight="strong">
{dateFormatter.format(new Date(deadline.end))}
</Text>
),
}}
/>
</Text>
)}

{canManageGrant && (
<Text>
<FormattedMessage id="profile.myGrant.manage.warning" />
</Text>
)}
</VerticalStack>
</GridColumn>
</Grid>
</>
);
};
Loading