mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2026-02-03 02:24:47 +08:00
[offers][feat] Add token check and refactor UI (#499)
* [offers][fix] Fix and refactor UI * [offers][feat] Add token check in submission results page * [offers][refactor] Refactor text display
This commit is contained in:
16
apps/portal/src/components/offers/EducationFields.ts
Normal file
16
apps/portal/src/components/offers/EducationFields.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { emptyOption } from './constants';
|
||||
|
||||
export const EducationFieldLabels = [
|
||||
'Business Analytics',
|
||||
'Computer Science',
|
||||
'Data Science and Analytics',
|
||||
'Information Security',
|
||||
'Information Systems',
|
||||
];
|
||||
|
||||
export const EducationFieldOptions = [emptyOption].concat(
|
||||
EducationFieldLabels.map((label) => ({
|
||||
label,
|
||||
value: label.replace(/\s+/g, '-').toLowerCase(),
|
||||
})),
|
||||
);
|
||||
18
apps/portal/src/components/offers/EducationLevels.ts
Normal file
18
apps/portal/src/components/offers/EducationLevels.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { emptyOption } from './constants';
|
||||
|
||||
export const EducationLevelLabels = [
|
||||
'Bachelor',
|
||||
'Diploma',
|
||||
'Masters',
|
||||
'PhD',
|
||||
'Professional',
|
||||
'Secondary',
|
||||
'Self-taught',
|
||||
];
|
||||
|
||||
export const EducationLevelOptions = [emptyOption].concat(
|
||||
EducationLevelLabels.map((label) => ({
|
||||
label,
|
||||
value: label.replace(/\s+/g, '-').toLowerCase(),
|
||||
})),
|
||||
);
|
||||
18
apps/portal/src/components/offers/InternshipCycles.ts
Normal file
18
apps/portal/src/components/offers/InternshipCycles.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { emptyOption } from './constants';
|
||||
|
||||
export const InternshipCycleLabels = [
|
||||
'Spring',
|
||||
'Summer',
|
||||
'Fall',
|
||||
'Winter',
|
||||
'Half year',
|
||||
'Full year',
|
||||
'Others',
|
||||
];
|
||||
|
||||
export const InternshipCycleOptions = [emptyOption].concat(
|
||||
InternshipCycleLabels.map((label) => ({
|
||||
label,
|
||||
value: label.replace(/\s+/g, '-').toLowerCase(),
|
||||
})),
|
||||
);
|
||||
@@ -1,7 +1,7 @@
|
||||
import clsx from 'clsx';
|
||||
import { JobType } from '@prisma/client';
|
||||
|
||||
import { JobTypeLabel } from './types';
|
||||
import { JobTypeLabel } from '~/components/offers/constants';
|
||||
|
||||
type Props = Readonly<{
|
||||
onChange: (jobType: JobType) => void;
|
||||
|
||||
8
apps/portal/src/components/offers/Years.ts
Normal file
8
apps/portal/src/components/offers/Years.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
const NUM_YEARS = 5;
|
||||
export const FutureYearsOptions = Array.from({ length: NUM_YEARS }, (_, i) => {
|
||||
const year = new Date().getFullYear() + i;
|
||||
return {
|
||||
label: String(year),
|
||||
value: year,
|
||||
};
|
||||
});
|
||||
@@ -1,78 +1,14 @@
|
||||
import { EducationBackgroundType } from './types';
|
||||
export const HOME_URL = '/offers';
|
||||
|
||||
export const emptyOption = '----';
|
||||
export const JobTypeLabel = {
|
||||
FULLTIME: 'Full-time',
|
||||
INTERN: 'Internship',
|
||||
};
|
||||
|
||||
export const internshipCycleOptions = [
|
||||
{
|
||||
label: 'Summer',
|
||||
value: 'Summer',
|
||||
},
|
||||
{
|
||||
label: 'Winter',
|
||||
value: 'Winter',
|
||||
},
|
||||
{
|
||||
label: 'Spring',
|
||||
value: 'Spring',
|
||||
},
|
||||
{
|
||||
label: 'Fall',
|
||||
value: 'Fall',
|
||||
},
|
||||
{
|
||||
label: 'Full year',
|
||||
value: 'Full year',
|
||||
},
|
||||
];
|
||||
|
||||
export const yearOptions = [
|
||||
{
|
||||
label: '2021',
|
||||
value: 2021,
|
||||
},
|
||||
{
|
||||
label: '2022',
|
||||
value: 2022,
|
||||
},
|
||||
{
|
||||
label: '2023',
|
||||
value: 2023,
|
||||
},
|
||||
{
|
||||
label: '2024',
|
||||
value: 2024,
|
||||
},
|
||||
];
|
||||
|
||||
export const educationLevelOptions = Object.entries(
|
||||
EducationBackgroundType,
|
||||
).map(([, value]) => ({
|
||||
label: value,
|
||||
value,
|
||||
}));
|
||||
|
||||
export const educationFieldOptions = [
|
||||
{
|
||||
label: 'Computer Science',
|
||||
value: 'Computer Science',
|
||||
},
|
||||
{
|
||||
label: 'Information Security',
|
||||
value: 'Information Security',
|
||||
},
|
||||
{
|
||||
label: 'Information Systems',
|
||||
value: 'Information Systems',
|
||||
},
|
||||
{
|
||||
label: 'Business Analytics',
|
||||
value: 'Business Analytics',
|
||||
},
|
||||
{
|
||||
label: 'Data Science and Analytics',
|
||||
value: 'Data Science and Analytics',
|
||||
},
|
||||
];
|
||||
export const emptyOption = {
|
||||
label: '',
|
||||
value: '',
|
||||
};
|
||||
|
||||
export enum FieldError {
|
||||
NON_NEGATIVE_NUMBER = 'Please fill in a non-negative number in this field.',
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { StaticImageData } from 'next/image';
|
||||
import Image from 'next/image';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { HOME_URL } from '~/components/offers/types';
|
||||
import { HOME_URL } from '../constants';
|
||||
|
||||
type LeftTextCardProps = Readonly<{
|
||||
description: string;
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { StaticImageData } from 'next/image';
|
||||
import Image from 'next/image';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { HOME_URL } from '~/components/offers/types';
|
||||
import { HOME_URL } from '../constants';
|
||||
|
||||
type RightTextCarddProps = Readonly<{
|
||||
description: string;
|
||||
|
||||
@@ -9,10 +9,11 @@ import { getLabelForJobTitleType } from '~/components/shared/JobTitles';
|
||||
|
||||
import { HorizontalDivider } from '~/../../../packages/ui/dist';
|
||||
import { convertMoneyToString } from '~/utils/offers/currency';
|
||||
import { getCompanyDisplayText } from '~/utils/offers/string';
|
||||
import { formatDate } from '~/utils/offers/time';
|
||||
|
||||
import { JobTypeLabel } from '../constants';
|
||||
import ProfilePhotoHolder from '../profile/ProfilePhotoHolder';
|
||||
import { JobTypeLabel } from '../types';
|
||||
|
||||
import type { AnalysisOffer } from '~/types/offers';
|
||||
|
||||
@@ -69,7 +70,7 @@ export default function OfferProfileCard({
|
||||
{getLabelForJobTitleType(title as JobTitleType)}{' '}
|
||||
{`(${JobTypeLabel[jobType]})`}
|
||||
</p>
|
||||
<p>{`Company: ${company.name}, ${location}`}</p>
|
||||
<p>{`Company: ${getCompanyDisplayText(company.name, location)}`}</p>
|
||||
{level && <p>Level: {level}</p>}
|
||||
</div>
|
||||
<div className="col-span-1 row-span-3">
|
||||
|
||||
@@ -93,7 +93,6 @@ export default function OffersProfileSave({
|
||||
<div className="mt-4 flex gap-4">
|
||||
<div className="grow">
|
||||
<TextInput
|
||||
disabled={true}
|
||||
isLabelHidden={true}
|
||||
label="Edit link"
|
||||
value={getProfileLink(profileId, token)}
|
||||
|
||||
@@ -4,11 +4,11 @@ import type { SubmitHandler } from 'react-hook-form';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/20/solid';
|
||||
import { JobType } from '@prisma/client';
|
||||
import { Button } from '@tih/ui';
|
||||
import { Button, useToast } from '@tih/ui';
|
||||
|
||||
import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics';
|
||||
import type { BreadcrumbStep } from '~/components/offers/Breadcrumb';
|
||||
import { Breadcrumbs } from '~/components/offers/Breadcrumb';
|
||||
import type { BreadcrumbStep } from '~/components/offers/Breadcrumbs';
|
||||
import { Breadcrumbs } from '~/components/offers/Breadcrumbs';
|
||||
import BackgroundForm from '~/components/offers/offersSubmission/submissionForm/BackgroundForm';
|
||||
import OfferDetailsForm from '~/components/offers/offersSubmission/submissionForm/OfferDetailsForm';
|
||||
import type {
|
||||
@@ -102,8 +102,9 @@ export default function OffersSubmissionForm({
|
||||
token: editToken,
|
||||
});
|
||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||
const { event: gaEvent } = useGoogleAnalytics();
|
||||
|
||||
const { event: gaEvent } = useGoogleAnalytics();
|
||||
const { showToast } = useToast();
|
||||
const router = useRouter();
|
||||
const pageRef = useRef<HTMLDivElement>(null);
|
||||
const scrollToTop = () =>
|
||||
@@ -132,13 +133,7 @@ export default function OffersSubmissionForm({
|
||||
},
|
||||
);
|
||||
|
||||
const steps = [
|
||||
<OfferDetailsForm
|
||||
key={0}
|
||||
defaultJobType={initialOfferProfileValues.offers[0].jobType}
|
||||
/>,
|
||||
<BackgroundForm key={1} />,
|
||||
];
|
||||
const steps = [<OfferDetailsForm key={0} />, <BackgroundForm key={1} />];
|
||||
|
||||
const breadcrumbSteps: Array<BreadcrumbStep> = [
|
||||
{
|
||||
@@ -157,14 +152,14 @@ export default function OffersSubmissionForm({
|
||||
},
|
||||
];
|
||||
|
||||
const goToNextStep = async (currStep: number) => {
|
||||
if (currStep === 0) {
|
||||
const setStepWithValidation = async (nextStep: number) => {
|
||||
if (nextStep === 1) {
|
||||
const result = await trigger('offers');
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
setStep(step + 1);
|
||||
setStep(nextStep);
|
||||
};
|
||||
|
||||
const mutationpath =
|
||||
@@ -175,10 +170,24 @@ export default function OffersSubmissionForm({
|
||||
const createOrUpdateMutation = trpc.useMutation([mutationpath], {
|
||||
onError(error) {
|
||||
console.error(error.message);
|
||||
showToast({
|
||||
title:
|
||||
editProfileId && editToken
|
||||
? 'Error updating offer profile.'
|
||||
: 'Error creating offer profile',
|
||||
variant: 'failure',
|
||||
});
|
||||
},
|
||||
onSuccess(data) {
|
||||
setParams({ profileId: data.id, token: data.token });
|
||||
setIsSubmitted(true);
|
||||
showToast({
|
||||
title:
|
||||
editProfileId && editToken
|
||||
? 'Offer profile updated successfully!'
|
||||
: 'Offer profile created successfully!',
|
||||
variant: 'success',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -270,7 +279,7 @@ export default function OffersSubmissionForm({
|
||||
<div className="flex justify-center bg-slate-100 px-4 py-4 sm:px-6 lg:px-8">
|
||||
<Breadcrumbs
|
||||
currentStep={step}
|
||||
setStep={setStep}
|
||||
setStep={setStepWithValidation}
|
||||
steps={breadcrumbSteps}
|
||||
/>
|
||||
</div>
|
||||
@@ -288,7 +297,7 @@ export default function OffersSubmissionForm({
|
||||
label="Next"
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
goToNextStep(step);
|
||||
setStepWithValidation(step + 1);
|
||||
gaEvent({
|
||||
action: 'offers.profile_submission_navigate_next',
|
||||
category: 'submission',
|
||||
|
||||
@@ -2,12 +2,7 @@ import { useFormContext, useWatch } from 'react-hook-form';
|
||||
import { JobType } from '@prisma/client';
|
||||
import { Collapsible, RadioList } from '@tih/ui';
|
||||
|
||||
import {
|
||||
educationFieldOptions,
|
||||
educationLevelOptions,
|
||||
emptyOption,
|
||||
FieldError,
|
||||
} from '~/components/offers/constants';
|
||||
import { FieldError } from '~/components/offers/constants';
|
||||
import type { BackgroundPostData } from '~/components/offers/types';
|
||||
import CitiesTypeahead from '~/components/shared/CitiesTypeahead';
|
||||
import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
|
||||
@@ -20,6 +15,8 @@ import {
|
||||
CURRENCY_OPTIONS,
|
||||
} from '~/utils/offers/currency/CurrencyEnum';
|
||||
|
||||
import { EducationFieldOptions } from '../../EducationFields';
|
||||
import { EducationLevelOptions } from '../../EducationLevels';
|
||||
import FormRadioList from '../../forms/FormRadioList';
|
||||
import FormSection from '../../forms/FormSection';
|
||||
import FormSelect from '../../forms/FormSelect';
|
||||
@@ -134,6 +131,9 @@ function FullTimeJobFields() {
|
||||
if (option) {
|
||||
setValue('background.experiences.0.companyId', option.value);
|
||||
setValue('background.experiences.0.companyName', option.label);
|
||||
} else {
|
||||
setValue('background.experiences.0.companyId', '');
|
||||
setValue('background.experiences.0.companyName', '');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -343,15 +343,13 @@ function EducationSection() {
|
||||
<FormSelect
|
||||
display="block"
|
||||
label="Education Level"
|
||||
options={educationLevelOptions}
|
||||
placeholder={emptyOption}
|
||||
options={EducationLevelOptions}
|
||||
{...register(`background.educations.0.type`)}
|
||||
/>
|
||||
<FormSelect
|
||||
display="block"
|
||||
label="Field"
|
||||
options={educationFieldOptions}
|
||||
placeholder={emptyOption}
|
||||
options={EducationFieldOptions}
|
||||
{...register(`background.educations.0.field`)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -22,20 +22,16 @@ import {
|
||||
defaultFullTimeOfferValues,
|
||||
defaultInternshipOfferValues,
|
||||
} from '../OffersSubmissionForm';
|
||||
import {
|
||||
emptyOption,
|
||||
FieldError,
|
||||
internshipCycleOptions,
|
||||
yearOptions,
|
||||
} from '../../constants';
|
||||
import { FieldError, JobTypeLabel } from '../../constants';
|
||||
import FormMonthYearPicker from '../../forms/FormMonthYearPicker';
|
||||
import FormSection from '../../forms/FormSection';
|
||||
import FormSelect from '../../forms/FormSelect';
|
||||
import FormTextArea from '../../forms/FormTextArea';
|
||||
import FormTextInput from '../../forms/FormTextInput';
|
||||
import { InternshipCycleOptions } from '../../InternshipCycles';
|
||||
import JobTypeTabs from '../../JobTypeTabs';
|
||||
import type { OfferFormData } from '../../types';
|
||||
import { JobTypeLabel } from '../../types';
|
||||
import { FutureYearsOptions } from '../../Years';
|
||||
import {
|
||||
Currency,
|
||||
CURRENCY_OPTIONS,
|
||||
@@ -384,8 +380,7 @@ function InternshipOfferDetailsForm({
|
||||
display="block"
|
||||
errorMessage={offerFields?.offersIntern?.internshipCycle?.message}
|
||||
label="Internship Cycle"
|
||||
options={internshipCycleOptions}
|
||||
placeholder={emptyOption}
|
||||
options={InternshipCycleOptions}
|
||||
required={true}
|
||||
{...register(`offers.${index}.offersIntern.internshipCycle`, {
|
||||
required: FieldError.REQUIRED,
|
||||
@@ -395,8 +390,7 @@ function InternshipOfferDetailsForm({
|
||||
display="block"
|
||||
errorMessage={offerFields?.offersIntern?.startYear?.message}
|
||||
label="Internship Year"
|
||||
options={yearOptions}
|
||||
placeholder={emptyOption}
|
||||
options={FutureYearsOptions}
|
||||
required={true}
|
||||
{...register(`offers.${index}.offersIntern.startYear`, {
|
||||
required: FieldError.REQUIRED,
|
||||
@@ -522,14 +516,11 @@ function OfferDetailsFormArray({
|
||||
);
|
||||
}
|
||||
|
||||
type OfferDetailsFormProps = Readonly<{
|
||||
defaultJobType?: JobType;
|
||||
}>;
|
||||
|
||||
export default function OfferDetailsForm({
|
||||
defaultJobType = JobType.FULLTIME,
|
||||
}: OfferDetailsFormProps) {
|
||||
const [jobType, setJobType] = useState(defaultJobType);
|
||||
export default function OfferDetailsForm() {
|
||||
const watchJobType = useWatch({
|
||||
name: `offers.0.jobType`,
|
||||
});
|
||||
const [jobType, setJobType] = useState(watchJobType as JobType);
|
||||
const [isDialogOpen, setDialogOpen] = useState(false);
|
||||
const { control } = useFormContext();
|
||||
const fieldArrayValues = useFieldArray({ control, name: 'offers' });
|
||||
@@ -576,8 +567,8 @@ export default function OfferDetailsForm({
|
||||
label="Switch"
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
toggleJobType();
|
||||
setDialogOpen(false);
|
||||
toggleJobType();
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -7,7 +7,11 @@ import {
|
||||
import { HorizontalDivider } from '@tih/ui';
|
||||
|
||||
import type { OfferDisplayData } from '~/components/offers/types';
|
||||
import { JobTypeLabel } from '~/components/offers/types';
|
||||
|
||||
import {
|
||||
getCompanyDisplayText,
|
||||
getJobDisplayText,
|
||||
} from '~/utils/offers/string';
|
||||
|
||||
type Props = Readonly<{
|
||||
offer: OfferDisplayData;
|
||||
@@ -35,19 +39,18 @@ export default function OfferCard({
|
||||
return (
|
||||
<div className="flex justify-between px-8">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row">
|
||||
<span>
|
||||
<BuildingOffice2Icon className="mr-3 h-5" />
|
||||
</span>
|
||||
<span className="font-bold">
|
||||
{location ? `${companyName}, ${location.cityName}` : companyName}
|
||||
</span>
|
||||
</div>
|
||||
{(companyName || location) && (
|
||||
<div className="flex flex-row">
|
||||
<span>
|
||||
<BuildingOffice2Icon className="mr-3 h-5" />
|
||||
</span>
|
||||
<span className="font-bold">
|
||||
{getCompanyDisplayText(companyName, location)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="ml-8 flex flex-row">
|
||||
<p>
|
||||
{jobLevel ? `${jobTitle}, ${jobLevel}` : jobTitle}{' '}
|
||||
{jobType && `(${JobTypeLabel[jobType]})`}
|
||||
</p>
|
||||
<p>{getJobDisplayText(jobTitle, jobLevel, jobType)}</p>
|
||||
</div>
|
||||
</div>
|
||||
{!duration && receivedMonth && (
|
||||
|
||||
@@ -13,10 +13,10 @@ import { Button, Dialog, Spinner, Tabs, useToast } from '@tih/ui';
|
||||
|
||||
import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics';
|
||||
import type { ProfileDetailTab } from '~/components/offers/constants';
|
||||
import { JobTypeLabel } from '~/components/offers/constants';
|
||||
import { profileDetailTabs } from '~/components/offers/constants';
|
||||
import ProfilePhotoHolder from '~/components/offers/profile/ProfilePhotoHolder';
|
||||
import type { BackgroundDisplayData } from '~/components/offers/types';
|
||||
import { JobTypeLabel } from '~/components/offers/types';
|
||||
import Tooltip from '~/components/offers/util/Tooltip';
|
||||
|
||||
import { getProfileEditPath } from '~/utils/offers/link';
|
||||
|
||||
@@ -2,7 +2,7 @@ import clsx from 'clsx';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { JobType } from '@prisma/client';
|
||||
import { DropdownMenu, Spinner } from '@tih/ui';
|
||||
import { DropdownMenu, Spinner, useToast } from '@tih/ui';
|
||||
|
||||
import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics';
|
||||
import OffersTablePagination from '~/components/offers/table/OffersTablePagination';
|
||||
@@ -66,6 +66,7 @@ export default function OffersTable({
|
||||
event?.preventDefault();
|
||||
}, [yoeCategory]);
|
||||
|
||||
const { showToast } = useToast();
|
||||
trpc.useQuery(
|
||||
[
|
||||
'offers.list',
|
||||
@@ -81,8 +82,11 @@ export default function OffersTable({
|
||||
},
|
||||
],
|
||||
{
|
||||
onError: (err) => {
|
||||
alert(err);
|
||||
onError: () => {
|
||||
showToast({
|
||||
title: 'Error loading the page.',
|
||||
variant: 'failure',
|
||||
});
|
||||
},
|
||||
onSuccess: (response: GetOffersResponse) => {
|
||||
setOffers(response.data);
|
||||
@@ -246,7 +250,7 @@ export default function OffersTable({
|
||||
{!offers ||
|
||||
(offers.length === 0 && (
|
||||
<div className="py-16 text-lg">
|
||||
<div className="flex justify-center">No data yet🥺</div>
|
||||
<div className="flex justify-center">No data yet 🥺</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,7 @@ export default function OffersTablePagination({
|
||||
<div className="mb-2 text-sm font-normal text-slate-500 md:mb-0">
|
||||
Showing
|
||||
<span className="font-semibold text-slate-900">
|
||||
{` ${startNumber} - ${endNumber} `}
|
||||
{` ${endNumber > 0 ? startNumber : 0} - ${endNumber} `}
|
||||
</span>
|
||||
{`of `}
|
||||
<span className="font-semibold text-slate-900">
|
||||
|
||||
@@ -4,27 +4,6 @@ import type { MonthYear } from '~/components/shared/MonthYearPicker';
|
||||
|
||||
import type { Location } from '~/types/offers';
|
||||
|
||||
export const HOME_URL = '/offers';
|
||||
|
||||
/*
|
||||
* Offer Profile
|
||||
*/
|
||||
|
||||
export const JobTypeLabel = {
|
||||
FULLTIME: 'Full-time',
|
||||
INTERN: 'Internship',
|
||||
};
|
||||
|
||||
export enum EducationBackgroundType {
|
||||
Bachelor = 'Bachelor',
|
||||
Diploma = 'Diploma',
|
||||
Masters = 'Masters',
|
||||
PhD = 'PhD',
|
||||
Professional = 'Professional',
|
||||
Secondary = 'Secondary',
|
||||
SelfTaught = 'Self-taught',
|
||||
}
|
||||
|
||||
export type OffersProfilePostData = {
|
||||
background: BackgroundPostData;
|
||||
id?: string;
|
||||
|
||||
@@ -8,12 +8,12 @@ import {
|
||||
UsersIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
|
||||
import { HOME_URL } from '~/components/offers/constants';
|
||||
import offersAnalysis from '~/components/offers/features/images/offers-analysis.png';
|
||||
import offersBrowse from '~/components/offers/features/images/offers-browse.png';
|
||||
import offersProfile from '~/components/offers/features/images/offers-profile.png';
|
||||
import LeftTextCard from '~/components/offers/features/LeftTextCard';
|
||||
import RightTextCard from '~/components/offers/features/RightTextCard';
|
||||
import { HOME_URL } from '~/components/offers/types';
|
||||
|
||||
const features = [
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Spinner, useToast } from '@tih/ui';
|
||||
|
||||
import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics';
|
||||
import { ProfileDetailTab } from '~/components/offers/constants';
|
||||
import { HOME_URL } from '~/components/offers/constants';
|
||||
import ProfileComments from '~/components/offers/profile/ProfileComments';
|
||||
import ProfileDetails from '~/components/offers/profile/ProfileDetails';
|
||||
import ProfileHeader from '~/components/offers/profile/ProfileHeader';
|
||||
@@ -13,7 +14,6 @@ import type {
|
||||
BackgroundDisplayData,
|
||||
OfferDisplayData,
|
||||
} from '~/components/offers/types';
|
||||
import { HOME_URL } from '~/components/offers/types';
|
||||
import type { JobTitleType } from '~/components/shared/JobTitles';
|
||||
import { getLabelForJobTitleType } from '~/components/shared/JobTitles';
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import Error from 'next/error';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/20/solid';
|
||||
import { EyeIcon } from '@heroicons/react/24/outline';
|
||||
import { Button, Spinner } from '@tih/ui';
|
||||
|
||||
import type { BreadcrumbStep } from '~/components/offers/Breadcrumb';
|
||||
import { Breadcrumbs } from '~/components/offers/Breadcrumb';
|
||||
import type { BreadcrumbStep } from '~/components/offers/Breadcrumbs';
|
||||
import { Breadcrumbs } from '~/components/offers/Breadcrumbs';
|
||||
import OffersProfileSave from '~/components/offers/offersSubmission/OffersProfileSave';
|
||||
import OffersSubmissionAnalysis from '~/components/offers/offersSubmission/OffersSubmissionAnalysis';
|
||||
|
||||
@@ -21,12 +22,21 @@ export default function OffersSubmissionResult() {
|
||||
token = token as string;
|
||||
const [step, setStep] = useState(0);
|
||||
const [analysis, setAnalysis] = useState<ProfileAnalysis | null>(null);
|
||||
const [isValidToken, setIsValidToken] = useState(false);
|
||||
|
||||
const pageRef = useRef<HTMLDivElement>(null);
|
||||
const scrollToTop = () =>
|
||||
pageRef.current?.scrollTo({ behavior: 'smooth', top: 0 });
|
||||
|
||||
// TODO: Check if the token is valid before showing this page
|
||||
const checkToken = trpc.useQuery(
|
||||
['offers.profile.isValidToken', { profileId: offerProfileId, token }],
|
||||
{
|
||||
onSuccess(data) {
|
||||
setIsValidToken(data);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const getAnalysis = trpc.useQuery(
|
||||
['offers.analysis.get', { profileId: offerProfileId }],
|
||||
{
|
||||
@@ -69,7 +79,7 @@ export default function OffersSubmissionResult() {
|
||||
|
||||
return (
|
||||
<>
|
||||
{getAnalysis.isLoading && (
|
||||
{(checkToken.isLoading || getAnalysis.isLoading) && (
|
||||
<div className="flex h-screen w-screen">
|
||||
<div className="m-auto mx-auto w-screen justify-center font-medium text-slate-500">
|
||||
<Spinner display="block" size="lg" />
|
||||
@@ -77,7 +87,13 @@ export default function OffersSubmissionResult() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!getAnalysis.isLoading && (
|
||||
{checkToken.isSuccess && !isValidToken && (
|
||||
<Error
|
||||
statusCode={403}
|
||||
title="You do not have permissions to access this page"
|
||||
/>
|
||||
)}
|
||||
{getAnalysis.isSuccess && (
|
||||
<div ref={pageRef} className="w-full">
|
||||
<div className="flex justify-center">
|
||||
<div className="block w-full max-w-screen-md overflow-hidden rounded-lg sm:shadow-lg md:my-10">
|
||||
|
||||
@@ -20,7 +20,7 @@ export default function CurrencySelector({
|
||||
<Select
|
||||
display="inline"
|
||||
isLabelHidden={true}
|
||||
label="Select fruit"
|
||||
label="Currency"
|
||||
name=""
|
||||
options={currencyOptions}
|
||||
value={selectedCurrency}
|
||||
|
||||
37
apps/portal/src/utils/offers/string.tsx
Normal file
37
apps/portal/src/utils/offers/string.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { JobType } from '@prisma/client';
|
||||
|
||||
import { JobTypeLabel } from '~/components/offers/constants';
|
||||
|
||||
import type { Location } from '~/types/offers';
|
||||
|
||||
function joinWithComma(...strings: Array<string | null | undefined>) {
|
||||
return strings.filter((value) => !!value).join(', ');
|
||||
}
|
||||
|
||||
function getLocationDisplayText({ cityName, countryName }: Location) {
|
||||
return cityName === countryName
|
||||
? cityName
|
||||
: joinWithComma(cityName, countryName);
|
||||
}
|
||||
|
||||
export function getCompanyDisplayText(
|
||||
companyName?: string | null,
|
||||
location?: Location | null,
|
||||
) {
|
||||
if (!location) {
|
||||
return companyName;
|
||||
}
|
||||
return joinWithComma(companyName, getLocationDisplayText(location));
|
||||
}
|
||||
|
||||
export function getJobDisplayText(
|
||||
jobTitle?: string | null,
|
||||
jobLevel?: string | null,
|
||||
jobType?: JobType | null,
|
||||
) {
|
||||
let jobDisplay = joinWithComma(jobTitle, jobLevel);
|
||||
if (jobType) {
|
||||
jobDisplay = jobDisplay.concat(` (${JobTypeLabel[jobType]})`);
|
||||
}
|
||||
return jobDisplay;
|
||||
}
|
||||
Reference in New Issue
Block a user