@ -0,0 +1,13 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `company` on the `QuestionsQuestionEncounter` table. All the data in the column will be lost.
|
||||
- Added the required column `companyId` to the `QuestionsQuestionEncounter` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "QuestionsQuestionEncounter" DROP COLUMN "company",
|
||||
ADD COLUMN "companyId" TEXT NOT NULL;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "QuestionsQuestionEncounter" ADD CONSTRAINT "QuestionsQuestionEncounter_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
@ -0,0 +1,60 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "OffersAnalysis" (
|
||||
"id" TEXT NOT NULL,
|
||||
"profileId" TEXT NOT NULL,
|
||||
"offerId" TEXT NOT NULL,
|
||||
"overallPercentile" INTEGER NOT NULL,
|
||||
"noOfSimilarOffers" INTEGER NOT NULL,
|
||||
"companyPercentile" INTEGER NOT NULL,
|
||||
"noOfSimilarCompanyOffers" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "OffersAnalysis_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_TopOverallOffers" (
|
||||
"A" TEXT NOT NULL,
|
||||
"B" TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_TopCompanyOffers" (
|
||||
"A" TEXT NOT NULL,
|
||||
"B" TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "OffersAnalysis_profileId_key" ON "OffersAnalysis"("profileId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "OffersAnalysis_offerId_key" ON "OffersAnalysis"("offerId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_TopOverallOffers_AB_unique" ON "_TopOverallOffers"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_TopOverallOffers_B_index" ON "_TopOverallOffers"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_TopCompanyOffers_AB_unique" ON "_TopCompanyOffers"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_TopCompanyOffers_B_index" ON "_TopCompanyOffers"("B");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "OffersAnalysis" ADD CONSTRAINT "OffersAnalysis_profileId_fkey" FOREIGN KEY ("profileId") REFERENCES "OffersProfile"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "OffersAnalysis" ADD CONSTRAINT "OffersAnalysis_offerId_fkey" FOREIGN KEY ("offerId") REFERENCES "OffersOffer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_TopOverallOffers" ADD CONSTRAINT "_TopOverallOffers_A_fkey" FOREIGN KEY ("A") REFERENCES "OffersAnalysis"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_TopOverallOffers" ADD CONSTRAINT "_TopOverallOffers_B_fkey" FOREIGN KEY ("B") REFERENCES "OffersOffer"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_TopCompanyOffers" ADD CONSTRAINT "_TopCompanyOffers_A_fkey" FOREIGN KEY ("A") REFERENCES "OffersAnalysis"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_TopCompanyOffers" ADD CONSTRAINT "_TopCompanyOffers_B_fkey" FOREIGN KEY ("B") REFERENCES "OffersOffer"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -0,0 +1,3 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "OffersAnalysis" ALTER COLUMN "overallPercentile" SET DATA TYPE DOUBLE PRECISION,
|
||||
ALTER COLUMN "companyPercentile" SET DATA TYPE DOUBLE PRECISION;
|
@ -0,0 +1,11 @@
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "OffersAnalysis" DROP CONSTRAINT "OffersAnalysis_offerId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "OffersAnalysis" DROP CONSTRAINT "OffersAnalysis_profileId_fkey";
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "OffersAnalysis" ADD CONSTRAINT "OffersAnalysis_profileId_fkey" FOREIGN KEY ("profileId") REFERENCES "OffersProfile"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "OffersAnalysis" ADD CONSTRAINT "OffersAnalysis_offerId_fkey" FOREIGN KEY ("offerId") REFERENCES "OffersOffer"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -0,0 +1,14 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Made the column `totalYoe` on table `OffersBackground` required. This step will fail if there are existing NULL values in that column.
|
||||
- Made the column `negotiationStrategy` on table `OffersOffer` required. This step will fail if there are existing NULL values in that column.
|
||||
- Made the column `comments` on table `OffersOffer` required. This step will fail if there are existing NULL values in that column.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "OffersBackground" ALTER COLUMN "totalYoe" SET NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "OffersOffer" ALTER COLUMN "negotiationStrategy" SET NOT NULL,
|
||||
ALTER COLUMN "comments" SET NOT NULL;
|
@ -0,0 +1,5 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "ResumesComment" ADD COLUMN "parentId" TEXT;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ResumesComment" ADD CONSTRAINT "ResumesComment_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "ResumesComment"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "OffersExperience" ADD COLUMN "location" TEXT;
|
@ -0,0 +1,12 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `upvotes` to the `QuestionsQuestion` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "QuestionsQuestion" ADD COLUMN "lastSeenAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
ADD COLUMN "upvotes" INTEGER NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "QuestionsQuestionEncounter" ADD COLUMN "netVotes" INTEGER NOT NULL DEFAULT 0;
|
@ -0,0 +1,12 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `netVotes` on the `QuestionsQuestionEncounter` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "QuestionsQuestion" ALTER COLUMN "lastSeenAt" DROP DEFAULT,
|
||||
ALTER COLUMN "upvotes" SET DEFAULT 0;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "QuestionsQuestionEncounter" DROP COLUMN "netVotes";
|
@ -0,0 +1,5 @@
|
||||
-- CreateIndex
|
||||
CREATE INDEX "QuestionsQuestion_lastSeenAt_id_idx" ON "QuestionsQuestion"("lastSeenAt", "id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "QuestionsQuestion_upvotes_id_idx" ON "QuestionsQuestion"("upvotes", "id");
|
@ -0,0 +1,12 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `baseValue` to the `OffersCurrency` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `updatedAt` to the `OffersCurrency` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "OffersCurrency" ADD COLUMN "baseCurrency" TEXT NOT NULL DEFAULT 'USD',
|
||||
ADD COLUMN "baseValue" INTEGER NOT NULL,
|
||||
ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL;
|
@ -0,0 +1,3 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "OffersCurrency" ALTER COLUMN "value" SET DATA TYPE DOUBLE PRECISION,
|
||||
ALTER COLUMN "baseValue" SET DATA TYPE DOUBLE PRECISION;
|
@ -1,100 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import { UserCircleIcon } from '@heroicons/react/20/solid';
|
||||
import { HorizontalDivider, Tabs } from '@tih/ui';
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
label: 'Overall',
|
||||
value: 'overall',
|
||||
},
|
||||
{
|
||||
label: 'Shopee',
|
||||
value: 'company-id',
|
||||
},
|
||||
];
|
||||
|
||||
function OfferPercentileAnalysis() {
|
||||
const result = {
|
||||
company: 'Shopee',
|
||||
numberOfOffers: 105,
|
||||
percentile: 56,
|
||||
};
|
||||
|
||||
return (
|
||||
<p>
|
||||
Your highest offer is from {result.company}, which is {result.percentile}{' '}
|
||||
percentile out of {result.numberOfOffers} offers received in Singapore for
|
||||
the same job type, same level, and same YOE in the last year.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
function OfferProfileCard() {
|
||||
return (
|
||||
<div className="my-5 block rounded-lg border p-4">
|
||||
<div className="grid grid-flow-col grid-cols-12 gap-x-10">
|
||||
<div className="col-span-1">
|
||||
<UserCircleIcon width={50} />
|
||||
</div>
|
||||
<div className="col-span-10">
|
||||
<p className="text-sm font-semibold">profile-name</p>
|
||||
<p className="text-xs ">Previous company: Meta, Singapore</p>
|
||||
<p className="text-xs ">YOE: 4 years</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<HorizontalDivider />
|
||||
<div className="grid grid-flow-col grid-cols-2 gap-x-10">
|
||||
<div className="col-span-1 row-span-3">
|
||||
<p className="text-sm font-semibold">Software engineer</p>
|
||||
<p className="text-xs ">Company: Google, Singapore</p>
|
||||
<p className="text-xs ">Level: G4</p>
|
||||
</div>
|
||||
<div className="col-span-1 row-span-3">
|
||||
<p className="text-end text-sm">Sept 2022</p>
|
||||
<p className="text-end text-xl">$125,000 / year</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TopOfferProfileList() {
|
||||
return (
|
||||
<>
|
||||
<OfferProfileCard />
|
||||
<OfferProfileCard />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function OfferAnalysisContent() {
|
||||
return (
|
||||
<>
|
||||
<OfferPercentileAnalysis />
|
||||
<TopOfferProfileList />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function OfferAnalysis() {
|
||||
const [tab, setTab] = useState('Overall');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h5 className="mb-2 text-center text-4xl font-bold text-gray-900">
|
||||
Result
|
||||
</h5>
|
||||
<div>
|
||||
<Tabs
|
||||
label="Result Navigation"
|
||||
tabs={tabs}
|
||||
value={tab}
|
||||
onChange={setTab}
|
||||
/>
|
||||
<HorizontalDivider className="mb-5" />
|
||||
<OfferAnalysisContent />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,243 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import type { SubmitHandler } from 'react-hook-form';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/20/solid';
|
||||
import { Button } from '@tih/ui';
|
||||
|
||||
import { Breadcrumbs } from '~/components/offers/Breadcrumb';
|
||||
import OfferAnalysis from '~/components/offers/offersSubmission/analysis/OfferAnalysis';
|
||||
import OfferProfileSave from '~/components/offers/offersSubmission/OfferProfileSave';
|
||||
import BackgroundForm from '~/components/offers/offersSubmission/submissionForm/BackgroundForm';
|
||||
import OfferDetailsForm from '~/components/offers/offersSubmission/submissionForm/OfferDetailsForm';
|
||||
import type {
|
||||
OfferFormData,
|
||||
OffersProfileFormData,
|
||||
} from '~/components/offers/types';
|
||||
import { JobType } from '~/components/offers/types';
|
||||
import type { Month } from '~/components/shared/MonthYearPicker';
|
||||
|
||||
import { cleanObject, removeInvalidMoneyData } from '~/utils/offers/form';
|
||||
import { getCurrentMonth, getCurrentYear } from '~/utils/offers/time';
|
||||
import { trpc } from '~/utils/trpc';
|
||||
|
||||
import type { CreateOfferProfileResponse } from '~/types/offers';
|
||||
|
||||
const defaultOfferValues = {
|
||||
comments: '',
|
||||
companyId: '',
|
||||
jobType: JobType.FullTime,
|
||||
location: '',
|
||||
monthYearReceived: {
|
||||
month: getCurrentMonth() as Month,
|
||||
year: getCurrentYear(),
|
||||
},
|
||||
negotiationStrategy: '',
|
||||
};
|
||||
|
||||
export const defaultFullTimeOfferValues = {
|
||||
...defaultOfferValues,
|
||||
jobType: JobType.FullTime,
|
||||
};
|
||||
|
||||
export const defaultInternshipOfferValues = {
|
||||
...defaultOfferValues,
|
||||
jobType: JobType.Intern,
|
||||
};
|
||||
|
||||
const defaultOfferProfileValues = {
|
||||
background: {
|
||||
educations: [],
|
||||
experiences: [{ jobType: JobType.FullTime }],
|
||||
specificYoes: [],
|
||||
totalYoe: 0,
|
||||
},
|
||||
offers: [defaultOfferValues],
|
||||
};
|
||||
|
||||
type FormStep = {
|
||||
component: JSX.Element;
|
||||
hasNext: boolean;
|
||||
hasPrevious: boolean;
|
||||
label: string;
|
||||
};
|
||||
|
||||
type Props = Readonly<{
|
||||
initialOfferProfileValues?: OffersProfileFormData;
|
||||
profileId?: string;
|
||||
token?: string;
|
||||
}>;
|
||||
|
||||
export default function OffersSubmissionForm({
|
||||
initialOfferProfileValues = defaultOfferProfileValues,
|
||||
profileId,
|
||||
token,
|
||||
}: Props) {
|
||||
const [formStep, setFormStep] = useState(0);
|
||||
const [createProfileResponse, setCreateProfileResponse] =
|
||||
useState<CreateOfferProfileResponse>({
|
||||
id: profileId || '',
|
||||
token: token || '',
|
||||
});
|
||||
|
||||
const pageRef = useRef<HTMLDivElement>(null);
|
||||
const scrollToTop = () =>
|
||||
pageRef.current?.scrollTo({ behavior: 'smooth', top: 0 });
|
||||
const formMethods = useForm<OffersProfileFormData>({
|
||||
defaultValues: initialOfferProfileValues,
|
||||
mode: 'all',
|
||||
});
|
||||
const { handleSubmit, trigger } = formMethods;
|
||||
|
||||
const formSteps: Array<FormStep> = [
|
||||
{
|
||||
component: <OfferDetailsForm key={0} />,
|
||||
hasNext: true,
|
||||
hasPrevious: false,
|
||||
label: 'Offer details',
|
||||
},
|
||||
{
|
||||
component: <BackgroundForm key={1} />,
|
||||
hasNext: false,
|
||||
hasPrevious: true,
|
||||
label: 'Background',
|
||||
},
|
||||
{
|
||||
component: <OfferAnalysis key={2} profileId={createProfileResponse.id} />,
|
||||
hasNext: true,
|
||||
hasPrevious: false,
|
||||
label: 'Analysis',
|
||||
},
|
||||
{
|
||||
component: (
|
||||
<OfferProfileSave
|
||||
key={3}
|
||||
profileId={createProfileResponse.id || ''}
|
||||
token={createProfileResponse.token}
|
||||
/>
|
||||
),
|
||||
hasNext: false,
|
||||
hasPrevious: false,
|
||||
label: 'Save',
|
||||
},
|
||||
];
|
||||
|
||||
const formStepsLabels = formSteps.map((step) => step.label);
|
||||
|
||||
const nextStep = async (currStep: number) => {
|
||||
if (currStep === 0) {
|
||||
const result = await trigger('offers');
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
setFormStep(formStep + 1);
|
||||
scrollToTop();
|
||||
};
|
||||
|
||||
const previousStep = () => {
|
||||
setFormStep(formStep - 1);
|
||||
scrollToTop();
|
||||
};
|
||||
|
||||
const generateAnalysisMutation = trpc.useMutation(
|
||||
['offers.analysis.generate'],
|
||||
{
|
||||
onError(error) {
|
||||
console.error(error.message);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const mutationpath =
|
||||
profileId && token ? 'offers.profile.update' : 'offers.profile.create';
|
||||
|
||||
const createOrUpdateMutation = trpc.useMutation([mutationpath], {
|
||||
onError(error) {
|
||||
console.error(error.message);
|
||||
},
|
||||
onSuccess(data) {
|
||||
generateAnalysisMutation.mutate({
|
||||
profileId: data?.id || '',
|
||||
});
|
||||
setCreateProfileResponse(data);
|
||||
setFormStep(formStep + 1);
|
||||
scrollToTop();
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit: SubmitHandler<OffersProfileFormData> = async (data) => {
|
||||
const result = await trigger();
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
data = removeInvalidMoneyData(data);
|
||||
|
||||
const background = cleanObject(data.background);
|
||||
background.specificYoes = data.background.specificYoes.filter(
|
||||
(specificYoe) => specificYoe.domain && specificYoe.yoe > 0,
|
||||
);
|
||||
if (Object.entries(background.experiences[0]).length === 1) {
|
||||
background.experiences = [];
|
||||
}
|
||||
|
||||
const offers = data.offers.map((offer: OfferFormData) => ({
|
||||
...offer,
|
||||
monthYearReceived: new Date(
|
||||
offer.monthYearReceived.year,
|
||||
offer.monthYearReceived.month - 1, // Convert month to monthIndex
|
||||
),
|
||||
}));
|
||||
|
||||
if (profileId && token) {
|
||||
createOrUpdateMutation.mutate({
|
||||
background,
|
||||
id: profileId,
|
||||
offers,
|
||||
token,
|
||||
});
|
||||
} else {
|
||||
createOrUpdateMutation.mutate({ background, offers });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={pageRef} className="fixed h-full w-full overflow-y-scroll">
|
||||
<div className="mb-20 flex justify-center">
|
||||
<div className="my-5 block w-full max-w-screen-md rounded-lg bg-white py-10 px-10 shadow-lg">
|
||||
<div className="mb-4 flex justify-end">
|
||||
<Breadcrumbs currentStep={formStep} stepLabels={formStepsLabels} />
|
||||
</div>
|
||||
<FormProvider {...formMethods}>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{formSteps[formStep].component}
|
||||
{/* <pre>{JSON.stringify(formMethods.watch(), null, 2)}</pre> */}
|
||||
{formSteps[formStep].hasNext && (
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
disabled={false}
|
||||
icon={ArrowRightIcon}
|
||||
label="Next"
|
||||
variant="secondary"
|
||||
onClick={() => nextStep(formStep)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{formStep === 1 && (
|
||||
<div className="flex items-center justify-between">
|
||||
<Button
|
||||
icon={ArrowLeftIcon}
|
||||
label="Previous"
|
||||
variant="secondary"
|
||||
onClick={previousStep}
|
||||
/>
|
||||
<Button label="Submit" type="submit" variant="primary" />{' '}
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</FormProvider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { HorizontalDivider, Spinner, Tabs } from '@tih/ui';
|
||||
|
||||
import { trpc } from '~/utils/trpc';
|
||||
|
||||
import OfferPercentileAnalysis from './OfferPercentileAnalysis';
|
||||
import OfferProfileCard from './OfferProfileCard';
|
||||
import { OVERALL_TAB } from '../../constants';
|
||||
|
||||
import type {
|
||||
Analysis,
|
||||
AnalysisHighestOffer,
|
||||
ProfileAnalysis,
|
||||
} from '~/types/offers';
|
||||
|
||||
type OfferAnalysisData = {
|
||||
offer?: AnalysisHighestOffer;
|
||||
offerAnalysis?: Analysis;
|
||||
};
|
||||
|
||||
type OfferAnalysisContentProps = Readonly<{
|
||||
analysis: OfferAnalysisData;
|
||||
tab: string;
|
||||
}>;
|
||||
|
||||
function OfferAnalysisContent({
|
||||
analysis: { offer, offerAnalysis },
|
||||
tab,
|
||||
}: OfferAnalysisContentProps) {
|
||||
if (!offerAnalysis || !offer || offerAnalysis.noOfOffers === 0) {
|
||||
return (
|
||||
<p className="m-10">
|
||||
You are the first to submit an offer for these companies! Check back
|
||||
later when there are more submissions.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<OfferPercentileAnalysis
|
||||
companyName={offer.company.name}
|
||||
offerAnalysis={offerAnalysis}
|
||||
tab={tab}
|
||||
/>
|
||||
{offerAnalysis.topPercentileOffers.map((topPercentileOffer) => (
|
||||
<OfferProfileCard
|
||||
key={topPercentileOffer.id}
|
||||
offerProfile={topPercentileOffer}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
type OfferAnalysisProps = Readonly<{
|
||||
profileId?: string;
|
||||
}>;
|
||||
|
||||
export default function OfferAnalysis({ profileId }: OfferAnalysisProps) {
|
||||
const [tab, setTab] = useState(OVERALL_TAB);
|
||||
const [allAnalysis, setAllAnalysis] = useState<ProfileAnalysis | null>(null);
|
||||
const [analysis, setAnalysis] = useState<OfferAnalysisData | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (tab === OVERALL_TAB) {
|
||||
setAnalysis({
|
||||
offer: allAnalysis?.overallHighestOffer,
|
||||
offerAnalysis: allAnalysis?.overallAnalysis,
|
||||
});
|
||||
} else {
|
||||
setAnalysis({
|
||||
offer: allAnalysis?.overallHighestOffer,
|
||||
offerAnalysis: allAnalysis?.companyAnalysis[0],
|
||||
});
|
||||
}
|
||||
}, [tab, allAnalysis]);
|
||||
|
||||
if (!profileId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const getAnalysisResult = trpc.useQuery(
|
||||
['offers.analysis.get', { profileId }],
|
||||
{
|
||||
onError(error) {
|
||||
console.error(error.message);
|
||||
},
|
||||
onSuccess(data) {
|
||||
setAllAnalysis(data);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const tabOptions = [
|
||||
{
|
||||
label: OVERALL_TAB,
|
||||
value: OVERALL_TAB,
|
||||
},
|
||||
{
|
||||
label: allAnalysis?.overallHighestOffer.company.name || '',
|
||||
value: allAnalysis?.overallHighestOffer.company.id || '',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
analysis && (
|
||||
<div>
|
||||
<h5 className="mb-2 text-center text-4xl font-bold text-gray-900">
|
||||
Result
|
||||
</h5>
|
||||
{getAnalysisResult.isError && (
|
||||
<p className="m-10 text-center">
|
||||
An error occurred while generating profile analysis.
|
||||
</p>
|
||||
)}
|
||||
{getAnalysisResult.isLoading && (
|
||||
<Spinner className="m-10" display="block" size="lg" />
|
||||
)}
|
||||
{!getAnalysisResult.isError && !getAnalysisResult.isLoading && (
|
||||
<div>
|
||||
<Tabs
|
||||
label="Result Navigation"
|
||||
tabs={tabOptions}
|
||||
value={tab}
|
||||
onChange={setTab}
|
||||
/>
|
||||
<HorizontalDivider className="mb-5" />
|
||||
<OfferAnalysisContent analysis={analysis} tab={tab} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
import type { Analysis } from '~/types/offers';
|
||||
|
||||
type OfferPercentileAnalysisProps = Readonly<{
|
||||
companyName: string;
|
||||
offerAnalysis: Analysis;
|
||||
tab: string;
|
||||
}>;
|
||||
|
||||
export default function OfferPercentileAnalysis({
|
||||
tab,
|
||||
companyName,
|
||||
offerAnalysis: { noOfOffers, percentile },
|
||||
}: OfferPercentileAnalysisProps) {
|
||||
return tab === 'Overall' ? (
|
||||
<p>
|
||||
Your highest offer is from {companyName}, which is {percentile} percentile
|
||||
out of {noOfOffers} offers received for the same job type, same level, and
|
||||
same YOE(+/-1) in the last year.
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
Your offer from {companyName} is {percentile} percentile out of{' '}
|
||||
{noOfOffers} offers received in {companyName} for the same job type, same
|
||||
level, and same YOE(+/-1) in the last year.
|
||||
</p>
|
||||
);
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
import { UserCircleIcon } from '@heroicons/react/24/outline';
|
||||
|
||||
import { HorizontalDivider } from '~/../../../packages/ui/dist';
|
||||
import { formatDate } from '~/utils/offers/time';
|
||||
|
||||
import { JobType } from '../../types';
|
||||
|
||||
import type { AnalysisOffer } from '~/types/offers';
|
||||
|
||||
type OfferProfileCardProps = Readonly<{
|
||||
offerProfile: AnalysisOffer;
|
||||
}>;
|
||||
|
||||
export default function OfferProfileCard({
|
||||
offerProfile: {
|
||||
company,
|
||||
income,
|
||||
profileName,
|
||||
totalYoe,
|
||||
level,
|
||||
monthYearReceived,
|
||||
jobType,
|
||||
location,
|
||||
title,
|
||||
previousCompanies,
|
||||
},
|
||||
}: OfferProfileCardProps) {
|
||||
return (
|
||||
<div className="my-5 block rounded-lg border p-4">
|
||||
<div className="grid grid-flow-col grid-cols-12 gap-x-10">
|
||||
<div className="col-span-1">
|
||||
<UserCircleIcon width={50} />
|
||||
</div>
|
||||
<div className="col-span-10">
|
||||
<p className="text-sm font-semibold">{profileName}</p>
|
||||
<p className="text-xs ">Previous company: {previousCompanies[0]}</p>
|
||||
<p className="text-xs ">YOE: {totalYoe} year(s)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<HorizontalDivider />
|
||||
<div className="grid grid-flow-col grid-cols-2 gap-x-10">
|
||||
<div className="col-span-1 row-span-3">
|
||||
<p className="text-sm font-semibold">{title}</p>
|
||||
<p className="text-xs ">
|
||||
Company: {company.name}, {location}
|
||||
</p>
|
||||
<p className="text-xs ">Level: {level}</p>
|
||||
</div>
|
||||
<div className="col-span-1 row-span-3">
|
||||
<p className="text-end text-sm">{formatDate(monthYearReceived)}</p>
|
||||
<p className="text-end text-xl">
|
||||
{jobType === JobType.FullTime
|
||||
? `$${income} / year`
|
||||
: `$${income} / month`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
import { signIn, useSession } from 'next-auth/react';
|
||||
import { useState } from 'react';
|
||||
import { ChatBubbleBottomCenterIcon } from '@heroicons/react/24/outline';
|
||||
import { Button, HorizontalDivider, TextArea } from '@tih/ui';
|
||||
|
||||
import { timeSinceNow } from '~/utils/offers/time';
|
||||
|
||||
import { trpc } from '../../../../utils/trpc';
|
||||
|
||||
import type { Reply } from '~/types/offers';
|
||||
|
||||
type Props = Readonly<{
|
||||
comment: Reply;
|
||||
disableReply?: boolean;
|
||||
handleExpanded?: () => void;
|
||||
isExpanded?: boolean;
|
||||
profileId: string;
|
||||
replyLength?: number;
|
||||
token?: string;
|
||||
}>;
|
||||
|
||||
export default function CommentCard({
|
||||
comment: { createdAt, id, message, user },
|
||||
disableReply,
|
||||
handleExpanded,
|
||||
isExpanded,
|
||||
profileId,
|
||||
token = '',
|
||||
replyLength = 0,
|
||||
}: Props) {
|
||||
const { data: session, status } = useSession();
|
||||
const [isReplying, setIsReplying] = useState(false);
|
||||
const [currentReply, setCurrentReply] = useState<string>('');
|
||||
|
||||
const trpcContext = trpc.useContext();
|
||||
const createCommentMutation = trpc.useMutation(['offers.comments.create'], {
|
||||
onSuccess() {
|
||||
trpcContext.invalidateQueries([
|
||||
'offers.comments.getComments',
|
||||
{ profileId },
|
||||
]);
|
||||
},
|
||||
});
|
||||
|
||||
function handleReply() {
|
||||
if (token && token.length > 0) {
|
||||
// If it is with edit permission, send comment to API with username = null
|
||||
createCommentMutation.mutate(
|
||||
{
|
||||
message: currentReply,
|
||||
profileId,
|
||||
replyingToId: id,
|
||||
token,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
setCurrentReply('');
|
||||
setIsReplying(false);
|
||||
if (!isExpanded) {
|
||||
handleExpanded?.();
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
} else if (status === 'authenticated') {
|
||||
// If not the OP and logged in, send comment to API
|
||||
createCommentMutation.mutate(
|
||||
{
|
||||
message: currentReply,
|
||||
profileId,
|
||||
replyingToId: id,
|
||||
userId: session.user?.id,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
setCurrentReply('');
|
||||
setIsReplying(false);
|
||||
if (!isExpanded) {
|
||||
handleExpanded?.();
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// If not the OP and not logged in, direct users to log in
|
||||
signIn();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex pl-2">
|
||||
<div className="flex w-full flex-col">
|
||||
<div className="flex flex-row font-bold">
|
||||
{user?.name ?? 'unknown user'}
|
||||
</div>
|
||||
<div className="mt-2 mb-2 flex flex-row ">{message}</div>
|
||||
<div className="flex flex-row items-center justify-start space-x-4 ">
|
||||
<div className="flex flex-col text-sm font-light text-gray-400">{`${timeSinceNow(
|
||||
createdAt,
|
||||
)} ago`}</div>
|
||||
{replyLength > 0 && (
|
||||
<div
|
||||
className="flex cursor-pointer flex-col text-sm text-purple-600 hover:underline"
|
||||
onClick={handleExpanded}>
|
||||
{isExpanded ? `Hide replies` : `View replies (${replyLength})`}
|
||||
</div>
|
||||
)}
|
||||
{!disableReply && (
|
||||
<div className="flex flex-col">
|
||||
<Button
|
||||
icon={ChatBubbleBottomCenterIcon}
|
||||
isLabelHidden={true}
|
||||
label="Reply"
|
||||
size="sm"
|
||||
variant="tertiary"
|
||||
onClick={() => setIsReplying(!isReplying)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!disableReply && isReplying && (
|
||||
<div className="mt-2 mr-2">
|
||||
<TextArea
|
||||
isLabelHidden={true}
|
||||
label="Comment"
|
||||
placeholder="Type your comment here"
|
||||
resize="none"
|
||||
value={currentReply}
|
||||
onChange={(value) => setCurrentReply(value)}
|
||||
/>
|
||||
<div className="mt-2 flex w-full justify-end">
|
||||
<div className="w-fit">
|
||||
<Button
|
||||
display="block"
|
||||
isLabelHidden={false}
|
||||
isLoading={createCommentMutation.isLoading}
|
||||
label="Reply"
|
||||
size="sm"
|
||||
variant="primary"
|
||||
onClick={handleReply}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<HorizontalDivider />
|
||||
</>
|
||||
);
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import CommentCard from '~/components/offers/profile/comments/CommentCard';
|
||||
|
||||
import type { Reply } from '~/types/offers';
|
||||
|
||||
type Props = Readonly<{
|
||||
comment: Reply;
|
||||
profileId: string;
|
||||
token?: string;
|
||||
}>;
|
||||
|
||||
export default function ExpandableCommentCard({
|
||||
comment,
|
||||
profileId,
|
||||
token = '',
|
||||
}: Props) {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
return (
|
||||
<div>
|
||||
<CommentCard
|
||||
comment={comment}
|
||||
handleExpanded={() => setIsExpanded(!isExpanded)}
|
||||
isExpanded={isExpanded}
|
||||
profileId={profileId}
|
||||
replyLength={comment.replies?.length ?? 0}
|
||||
token={token}
|
||||
/>
|
||||
{comment.replies && (
|
||||
<div className="pl-8">
|
||||
{isExpanded &&
|
||||
comment.replies.map((reply) => (
|
||||
<CommentCard
|
||||
key={reply.id}
|
||||
comment={reply}
|
||||
disableReply={true}
|
||||
profileId={profileId}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
import type { ResumeBadgeProps } from '../resume-badge';
|
||||
|
||||
export default function ResumeBadgeCoolIcon({ className }: ResumeBadgeProps) {
|
||||
return (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
viewBox="0 0 511.999 511.999"
|
||||
x="0px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
y="0px">
|
||||
<circle cx="247.796" cy="255.997" fill="#FFDB6C" r="247.796" />
|
||||
<path
|
||||
d="M300.895,467.216c-136.853,0-247.794-110.941-247.794-247.794c0-73.116,31.673-138.825,82.04-184.181
|
||||
C54.919,76.258,0,159.716,0,256.003c0,136.853,110.941,247.794,247.794,247.794c63.738,0,121.848-24.073,165.754-63.612
|
||||
C379.75,457.466,341.462,467.216,300.895,467.216z"
|
||||
fill="#FCC56B"
|
||||
/>
|
||||
<g>
|
||||
<path
|
||||
d="M141.308,259.555c-18.402,0-33.321,14.918-33.321,33.32h66.641
|
||||
C174.628,274.473,159.71,259.555,141.308,259.555z"
|
||||
fill="#F9A880"
|
||||
/>
|
||||
<path
|
||||
d="M431.948,259.555c-18.402,0-33.321,14.918-33.321,33.32h66.641
|
||||
C465.269,274.473,450.349,259.555,431.948,259.555z"
|
||||
fill="#F9A880"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="M105.165,121.895c64.702-14.849,117.079-9.739,175.098,3.782c8.604,2.004,17.692,4.239,27.29,4.532
|
||||
c15.985,0.489,33.956-3.489,49.449-7.382c61.168-15.366,108.95-7.374,154.996,2.465l-3.402,27.211
|
||||
c-7.188,0.159-9.449,3.511-11.503,10.054c-10.747,34.242-1.594,93.16-81.048,86.233c-52.27-4.558-67.239-18.879-92.152-81.847
|
||||
c-2.12-5.356-3.497-14.207-15.602-13.88c-6.835,0.184-12.948,1.392-15.079,13.267c-3.973,22.126-34.188,82.245-95.535,82.179
|
||||
c-54.185-0.058-74.855-28.184-77.323-90.159c-0.306-7.695-7.012-9.156-11.035-9.246L105.165,121.895L105.165,121.895z"
|
||||
fill="#56586F"
|
||||
/>
|
||||
<g>
|
||||
<path
|
||||
d="M199.128,113.331l-37.84,129.044c9.958,4.097,21.979,6.12,36.392,6.134
|
||||
c0.254,0,0.504-0.009,0.758-0.011l38.499-131.292C224.347,115.304,211.809,113.972,199.128,113.331z"
|
||||
fill="#737891"
|
||||
/>
|
||||
<path
|
||||
d="M434.438,114.376c-12.593-0.403-25.665,0-39.395,1.534l-33.781,115.202
|
||||
c9.238,7.758,20.144,12.263,34.543,15.016L434.438,114.376z"
|
||||
fill="#737891"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="M319.673,395.914c-16.785,0-33.382-5.73-46.784-16.718c-4.305-3.53-4.933-9.882-1.403-14.187
|
||||
c3.53-4.306,9.882-4.933,14.188-1.403c15.016,12.314,35.551,15.539,53.597,8.423c17.582-6.937,30.535-23.491,33.802-43.202
|
||||
c0.913-5.492,6.101-9.207,11.594-8.296c5.493,0.911,9.207,6.102,8.297,11.594c-4.422,26.66-22.161,49.137-46.296,58.657
|
||||
C337.935,394.228,328.776,395.914,319.673,395.914z"
|
||||
fill="#7F184C"
|
||||
/>
|
||||
<ellipse
|
||||
cx="298.209"
|
||||
cy="78.261"
|
||||
fill="#FCEB88"
|
||||
rx="28.897"
|
||||
ry="51.747"
|
||||
transform="matrix(0.2723 -0.9622 0.9622 0.2723 141.702 343.89)"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
import type { ResumeBadgeProps } from '../resume-badge';
|
||||
|
||||
export default function ResumeBadgeRocketIcon({ className }: ResumeBadgeProps) {
|
||||
return (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
viewBox="0 0 496.158 496.158"
|
||||
x="36px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
y="36px">
|
||||
<path
|
||||
d="M248.082,0.003C111.07,0.003,0,111.063,0,248.085c0,137.001,111.07,248.07,248.082,248.07 c137.006,0,248.076-111.069,248.076-248.07C496.158,111.062,385.088,0.003,248.082,0.003z"
|
||||
fill="#334D5C"
|
||||
/>
|
||||
<g>
|
||||
<polygon
|
||||
fill="#DBBB00"
|
||||
points="130.14,198.865 112.329,198.237 106.733,181.859 101.138,198.237 83.327,198.865 97.68,208.88 92.267,226.381 106.733,215.458 121.199,226.381 115.787,208.88 "
|
||||
/>
|
||||
<polygon
|
||||
fill="#DBBB00"
|
||||
points="112.416,202.889 115.484,191.248 105.788,198.382 95.265,191.881 99.455,203.294 89.618,211.306 102.168,210.835 106.348,222.679 110.18,210.584 122.334,210.282 "
|
||||
/>
|
||||
<polygon
|
||||
fill="#DBBB00"
|
||||
points="357.01,69.501 339.199,68.873 333.603,52.496 328.008,68.873 310.197,69.501 324.55,79.516 319.138,97.017 333.603,86.094 348.069,97.017 342.657,79.516 "
|
||||
/>
|
||||
<polygon
|
||||
fill="#DBBB00"
|
||||
points="339.286,73.525 342.354,61.884 332.658,69.018 322.135,62.517 326.325,73.93 316.488,81.942 329.038,81.472 333.218,93.315 337.05,81.221 349.204,80.918 "
|
||||
/>
|
||||
<polygon
|
||||
fill="#DBBB00"
|
||||
points="429.005,224.008 411.194,223.38 405.599,207.003 400.003,223.38 382.192,224.008 396.545,234.023 391.133,251.524 405.599,240.601 420.064,251.524 414.652,234.023 "
|
||||
/>
|
||||
<polygon
|
||||
fill="#DBBB00"
|
||||
points="411.281,228.032 414.35,216.392 404.653,223.526 394.13,217.024 398.32,228.437 388.483,236.449 401.033,235.979 405.213,247.822 409.045,235.728 421.199,235.426 "
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="M383.34,314.795c-5.941-14.345-21.202-36.571-46.212-55.931 c-19.131-14.808-50.218-32.46-89.678-32.46c-39.018,0-69.746,16.634-88.654,30.588c-25.352,18.71-40.673,40.56-46.559,54.769 c-4.417,10.663-4.502,18.883-0.239,23.145c3.465,3.465,7.585,5.079,12.965,5.079c6.495,0,14.247-2.294,24.975-5.469 c20.098-5.947,50.469-14.936,97.513-14.936c48.545,0,80.322,8.617,101.35,14.318c10.673,2.894,18.384,4.985,24.472,4.986h0.003 c4.713,0,8.172-1.264,10.886-3.979C387.635,331.431,387.35,324.477,383.34,314.795z"
|
||||
fill="#EA6307"
|
||||
/>
|
||||
<path
|
||||
d="M286.255,121.222c-14.873-40.687-31.176-66.481-38.176-66.481c-6.988,0-23.253,25.596-38.118,66.13 c-15.702,42.815-29.844,102.297-29.844,165.89c0,40.446,6.193,56.536,6.193,56.536s25.869,13.801,62.818,13.801 s60.716-13.801,60.716-13.801s6.101-16.404,6.101-57.03C315.945,223.234,301.891,163.997,286.255,121.222z"
|
||||
fill="#DFEADC"
|
||||
/>
|
||||
<path
|
||||
d="M248.166,54.741c-8.74,0-24.42,24.539-38.204,66.13c10.715,2.375,24.12,4.325,39.314,4.325 c14.394,0,26.884-1.749,36.92-3.953C272.454,79.654,256.87,54.741,248.166,54.741z"
|
||||
fill="#CE5800"
|
||||
/>
|
||||
<path
|
||||
d="M248.165,54.741c-8.343,0-23.005,22.365-36.309,60.561c10.384,2.186,23.106,3.916,37.418,3.916 c13.501,0,25.329-1.54,35.026-3.549C271.044,77.446,256.471,54.741,248.165,54.741z"
|
||||
fill="#EA6307"
|
||||
/>
|
||||
<circle cx="248.079" cy="183.889" fill="#DBBB00" r="30.677" />
|
||||
<circle cx="248.079" cy="183.889" fill="#FFDB29" r="25.486" />
|
||||
<path
|
||||
d="M262.936,167.597c-8.602-8.601-22.547-8.602-31.148,0s-8.602,22.547,0,31.149 S271.538,176.199,262.936,167.597z"
|
||||
fill="#FFE36E"
|
||||
/>
|
||||
<path
|
||||
d="M249.007,368.151c-16.392,0.012-32.76,0.337-32.76,8.403c0,16.16,32.564,81.608,32.564,81.608 s33.101-65.882,33.101-81.608C281.912,368.464,265.447,368.139,249.007,368.151z"
|
||||
fill="#E17A2D"
|
||||
/>
|
||||
<path
|
||||
d="M249.079,371.948c-11.66,0-23.32-0.845-23.32,4.894c0,11.479,23.131,57.964,23.131,57.964 s23.51-46.794,23.51-57.964C272.399,371.103,260.739,371.948,249.079,371.948z"
|
||||
fill="#F4E028"
|
||||
/>
|
||||
<path
|
||||
d="M249.079,376.829c-7.005,0-14.011-1.99-14.011,1.458c0,6.896,13.897,34.824,13.897,34.824 s14.124-28.113,14.124-34.824C263.09,374.839,256.084,376.829,249.079,376.829z"
|
||||
fill="#FFFFFF"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
@ -0,0 +1,198 @@
|
||||
import type { ResumeBadgeProps } from '../resume-badge';
|
||||
|
||||
export default function ResumeBadgeTreasureIcon({
|
||||
className,
|
||||
}: ResumeBadgeProps) {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
viewBox="0 0 511.672 511.672"
|
||||
x="0px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
y="0px">
|
||||
<path
|
||||
d="M473.853,222.264l37.757-57.073c0,0,4.779-141.878-85.273-149.217h-170.47h-0.078H85.32
|
||||
C-4.731,23.313,0.047,165.19,0.047,165.19l37.929,56.526L0,325.057l42.629,170.579l426.398,0.062l42.645-170.595L473.853,222.264z"
|
||||
fill="#A85D5D"
|
||||
/>
|
||||
<g opacity={0.2}>
|
||||
<path
|
||||
d="M0.593,165.987C3.186,126.403,16.771,42.878,85.32,37.273h170.469h0.078h170.469
|
||||
c68.551,5.606,82.15,89.162,84.728,128.73l0.546-0.812c0,0,4.779-141.878-85.273-149.217h-170.47h-0.078H85.32
|
||||
C-4.731,23.313,0.047,165.19,0.047,165.19L0.593,165.987z"
|
||||
fill="#FFFFFF"
|
||||
/>
|
||||
</g>
|
||||
<polygon
|
||||
fill="#723F3F"
|
||||
points="511.672,325.104 0,325.057 37.976,221.717 473.853,222.264 "
|
||||
/>
|
||||
<polygon
|
||||
fill="#8C4C4C"
|
||||
points="473.853,222.264 37.976,221.717 0.047,165.19 511.609,165.19 "
|
||||
/>
|
||||
<path
|
||||
d="M266.485,410.315c0,5.887-4.778,10.665-10.649,10.665c-5.887,0-10.665-4.778-10.665-10.665
|
||||
c0-5.888,4.778-10.649,10.665-10.649C261.707,399.666,266.485,404.428,266.485,410.315z"
|
||||
/>
|
||||
<g>
|
||||
<path
|
||||
d="M170.251,295.045c-12.258,0-24.079-2.701-32.448-7.401c-6.387-3.591-10.181-8.042-10.181-11.914
|
||||
c0-7.87,16.615-19.315,42.629-19.315c12.257,0,24.094,2.701,32.463,7.417c6.371,3.575,10.181,8.026,10.181,11.898
|
||||
C212.895,283.615,196.28,295.045,170.251,295.045z"
|
||||
fill="#FFCE54"
|
||||
/>
|
||||
<path
|
||||
d="M255.524,295.045c-12.258,0-24.079-2.701-32.464-7.401c-6.371-3.591-10.165-8.042-10.165-11.914
|
||||
c0-7.87,16.599-19.315,42.629-19.315c12.257,0,24.078,2.701,32.463,7.417c6.371,3.575,10.165,8.026,10.165,11.898
|
||||
C298.152,283.615,281.555,295.045,255.524,295.045z"
|
||||
fill="#FFCE54"
|
||||
/>
|
||||
<path
|
||||
d="M340.797,295.045c-12.258,0-24.094-2.701-32.463-7.401c-6.371-3.591-10.182-8.042-10.182-11.914
|
||||
c0-7.87,16.615-19.315,42.645-19.315c12.258,0,24.078,2.701,32.448,7.417c6.371,3.575,10.181,8.026,10.181,11.898
|
||||
C383.426,283.615,366.813,295.045,340.797,295.045z"
|
||||
fill="#FFCE54"
|
||||
/>
|
||||
<path
|
||||
d="M212.895,265.065c-12.258,0-24.094-2.686-32.464-7.401c-6.371-3.592-10.181-8.026-10.181-11.899
|
||||
c0-7.885,16.614-19.332,42.645-19.332c12.242,0,24.078,2.717,32.448,7.417c6.371,3.576,10.181,8.042,10.181,11.915
|
||||
C255.524,253.634,238.909,265.065,212.895,265.065z"
|
||||
fill="#FFCE54"
|
||||
/>
|
||||
<path
|
||||
d="M298.152,265.065c-12.242,0-24.078-2.686-32.447-7.401c-6.371-3.592-10.181-8.026-10.181-11.899
|
||||
c0-7.885,16.615-19.332,42.628-19.332c12.258,0,24.094,2.717,32.464,7.417c6.371,3.576,10.181,8.042,10.181,11.915
|
||||
C340.797,253.634,324.184,265.065,298.152,265.065z"
|
||||
fill="#FFCE54"
|
||||
/>
|
||||
<path
|
||||
d="M255.524,235.099c-12.258,0-24.079-2.702-32.464-7.417c-6.371-3.575-10.165-8.026-10.165-11.898
|
||||
c0-7.87,16.599-19.315,42.629-19.315c12.257,0,24.078,2.701,32.463,7.417c6.371,3.576,10.165,8.026,10.165,11.898
|
||||
C298.152,223.653,281.555,235.099,255.524,235.099z"
|
||||
fill="#FFCE54"
|
||||
/>
|
||||
<path
|
||||
d="M91.629,325.104h72.516c4.153-3.123,6.417-6.511,6.417-9.415c0-3.873-3.81-8.308-10.181-11.899
|
||||
c-8.37-4.715-20.206-7.401-32.463-7.401c-26.015,0-42.629,11.431-42.629,19.301C85.289,318.718,87.6,322.074,91.629,325.104z"
|
||||
fill="#FFCE54"
|
||||
/>
|
||||
<path
|
||||
d="M176.902,325.104h72.516c4.153-3.123,6.402-6.511,6.402-9.415c0-3.873-3.794-8.308-10.165-11.899
|
||||
c-8.37-4.715-20.206-7.401-32.464-7.401c-26.03,0-42.629,11.431-42.629,19.301C170.563,318.718,172.874,322.074,176.902,325.104z"
|
||||
fill="#FFCE54"
|
||||
/>
|
||||
</g>
|
||||
<rect
|
||||
fill="#CCD1D9"
|
||||
height="98.75"
|
||||
width="96.27"
|
||||
x="206.586"
|
||||
y="325.106"
|
||||
/>
|
||||
<g>
|
||||
<path
|
||||
d="M262.16,325.104h72.516c4.154-3.123,6.418-6.511,6.418-9.415c0-3.873-3.795-8.308-10.165-11.899
|
||||
c-8.386-4.715-20.206-7.401-32.464-7.401c-26.029,0-42.645,11.431-42.645,19.301C255.82,318.718,258.147,322.074,262.16,325.104z"
|
||||
fill="#FFCE54"
|
||||
/>
|
||||
<path
|
||||
d="M347.434,325.104h72.516c4.154-3.123,6.418-6.511,6.418-9.415c0-3.873-3.81-8.308-10.181-11.899
|
||||
c-8.37-4.715-20.206-7.401-32.448-7.401c-26.029,0-42.645,11.431-42.645,19.301C341.094,318.718,343.404,322.074,347.434,325.104z"
|
||||
fill="#FFCE54"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="M434.331,325.104c1.733-2.951,2.702-6.121,2.702-9.415c0-15.179-20.098-27.732-46.158-29.7
|
||||
c2.076-3.186,3.217-6.652,3.217-10.259c0-14.507-18.332-26.593-42.66-29.372c0-0.203,0.016-0.406,0.016-0.593
|
||||
c0-14.522-18.316-26.608-42.66-29.387c0.016-0.188,0.031-0.391,0.031-0.594c0-16.552-23.86-29.98-53.294-29.98
|
||||
c-29.435,0-53.294,13.429-53.294,29.98c0,0.203,0.016,0.406,0.031,0.594c-24.344,2.779-42.661,14.865-42.661,29.387
|
||||
c0,0.188,0.016,0.39,0.016,0.593c-24.328,2.779-42.66,14.865-42.66,29.372c0,3.622,1.14,7.104,3.248,10.321
|
||||
c-25.78,2.093-45.58,14.568-45.58,29.638c0,3.294,0.968,6.464,2.701,9.415H434.331z M236.989,319.998
|
||||
c-6.605,2.811-15.053,4.356-23.797,4.356s-17.192-1.546-23.782-4.356c-3.654-1.562-5.934-3.154-7.198-4.31
|
||||
c1.265-1.124,3.544-2.733,7.198-4.278c6.59-2.812,15.038-4.373,23.782-4.373c8.745,0,17.192,1.562,23.797,4.373
|
||||
c3.638,1.545,5.918,3.154,7.199,4.278C242.907,316.844,240.627,318.437,236.989,319.998z M231.727,280.024
|
||||
c-3.638-1.546-5.918-3.154-7.199-4.294c0.297-0.266,0.656-0.547,1.062-0.858c9.322-1.281,17.676-3.936,24.344-7.59
|
||||
c1.843-0.125,3.716-0.203,5.59-0.203s3.748,0.078,5.574,0.203c6.684,3.654,15.038,6.309,24.359,7.59
|
||||
c0.406,0.312,0.766,0.593,1.062,0.858c-1.28,1.14-3.561,2.748-7.199,4.294c-6.604,2.811-15.053,4.372-23.796,4.372
|
||||
C246.779,284.396,238.332,282.834,231.727,280.024z M322.246,319.998c-6.589,2.811-15.037,4.356-23.781,4.356
|
||||
s-17.191-1.546-23.797-4.356c-3.639-1.562-5.918-3.154-7.199-4.31c1.281-1.124,3.561-2.733,7.199-4.278
|
||||
c6.605-2.812,15.053-4.373,23.797-4.373s17.192,1.562,23.781,4.373c3.654,1.545,5.934,3.154,7.199,4.278
|
||||
C328.18,316.844,325.9,318.437,322.246,319.998z M407.52,311.41c3.654,1.545,5.935,3.154,7.199,4.278
|
||||
c-1.265,1.155-3.545,2.748-7.199,4.31c-6.589,2.811-15.053,4.356-23.781,4.356c-8.744,0-17.191-1.546-23.797-4.356
|
||||
c-3.654-1.562-5.934-3.154-7.199-4.31c1.266-1.124,3.545-2.733,7.199-4.278c6.605-2.812,15.053-4.373,23.797-4.373
|
||||
C392.467,307.037,400.931,308.599,407.52,311.41z M340.797,267.078c8.744,0,17.192,1.547,23.782,4.357
|
||||
c3.653,1.562,5.934,3.154,7.198,4.294c-1.265,1.14-3.545,2.748-7.198,4.294c-6.59,2.811-15.038,4.372-23.782,4.372
|
||||
s-17.191-1.562-23.797-4.372c-3.654-1.546-5.934-3.154-7.198-4.294c0.296-0.266,0.655-0.547,1.062-0.858
|
||||
c9.322-1.281,17.676-3.936,24.344-7.59C337.05,267.156,338.908,267.078,340.797,267.078z M298.152,237.098
|
||||
c8.744,0,17.192,1.546,23.798,4.356c3.653,1.562,5.934,3.154,7.198,4.31c-0.297,0.25-0.656,0.546-1.062,0.859
|
||||
c-9.322,1.28-17.677,3.95-24.345,7.573c-1.842,0.141-3.7,0.219-5.59,0.219c-1.873,0-3.731-0.078-5.574-0.219
|
||||
c-6.684-3.623-15.037-6.293-24.359-7.573c-0.406-0.312-0.75-0.609-1.047-0.859c0.297-0.281,0.641-0.562,1.047-0.875
|
||||
c9.322-1.28,17.676-3.935,24.359-7.573C294.405,237.176,296.279,237.098,298.152,237.098z M231.727,211.489
|
||||
c6.605-2.811,15.053-4.356,23.797-4.356s17.192,1.546,23.796,4.356c3.639,1.546,5.919,3.154,7.199,4.294
|
||||
c-0.297,0.266-0.656,0.562-1.062,0.875c-9.321,1.281-17.676,3.935-24.359,7.558c-1.826,0.156-3.7,0.218-5.574,0.218
|
||||
s-3.748-0.062-5.59-0.218c-6.667-3.623-15.021-6.277-24.344-7.558c-0.406-0.312-0.765-0.609-1.062-0.875
|
||||
C225.809,214.644,228.088,213.035,231.727,211.489z M189.098,241.454c6.605-2.811,15.053-4.356,23.797-4.356
|
||||
c1.874,0,3.732,0.078,5.574,0.219c6.684,3.639,15.038,6.293,24.359,7.573c0.406,0.312,0.75,0.594,1.046,0.875
|
||||
c-0.297,0.25-0.64,0.546-1.046,0.859c-9.322,1.28-17.691,3.95-24.359,7.573c-1.842,0.141-3.701,0.219-5.574,0.219
|
||||
c-1.89,0-3.748-0.078-5.59-0.219c-6.667-3.623-15.021-6.293-24.344-7.573c-0.406-0.312-0.765-0.609-1.062-0.859
|
||||
C183.164,244.608,185.444,243.016,189.098,241.454z M146.469,271.436c6.589-2.811,15.037-4.357,23.782-4.357
|
||||
c1.889,0,3.748,0.078,5.59,0.203c6.667,3.654,15.021,6.309,24.344,7.59c0.406,0.312,0.765,0.593,1.062,0.858
|
||||
c-1.28,1.14-3.544,2.748-7.199,4.294c-6.605,2.811-15.053,4.372-23.797,4.372c-8.745,0-17.192-1.562-23.782-4.372
|
||||
c-3.654-1.546-5.934-3.154-7.199-4.294C140.535,274.59,142.815,272.997,146.469,271.436z M104.136,311.41
|
||||
c6.59-2.812,15.053-4.373,23.782-4.373c8.745,0,17.192,1.562,23.797,4.373c3.654,1.545,5.934,3.154,7.198,4.278
|
||||
c-1.265,1.155-3.544,2.748-7.198,4.31c-6.605,2.811-15.053,4.356-23.797,4.356s-17.192-1.546-23.782-4.356
|
||||
c-3.654-1.562-5.934-3.154-7.198-4.31C98.203,314.565,100.482,312.955,104.136,311.41z"
|
||||
fill="#F6BB42"
|
||||
/>
|
||||
<rect
|
||||
fill="#AAB2BC"
|
||||
height="26.702"
|
||||
width="21.331"
|
||||
x="244.856"
|
||||
y="383.616"
|
||||
/>
|
||||
<path
|
||||
d="M270.936,369.982c0,8.448-6.855,15.287-15.287,15.287c-8.448,0-15.303-6.839-15.303-15.287
|
||||
c0-8.447,6.855-15.287,15.303-15.287C264.08,354.694,270.936,361.534,270.936,369.982z"
|
||||
fill="#434A54"
|
||||
/>
|
||||
<path
|
||||
d="M319.467,165.19c0,0,0.016-0.016,0.016-0.031V122.53c0-5.871-4.777-10.649-10.664-10.649H202.23
|
||||
c-5.887,0-10.665,4.778-10.665,10.649v42.629c0,0.016,0,0.031,0,0.031H319.467z"
|
||||
fill="#CCD1D9"
|
||||
/>
|
||||
<g>
|
||||
<path
|
||||
d="M212.895,165.19v-31.995h85.257v31.995h21.314c0-21.798,0.016-42.66,0.016-42.66
|
||||
c0-5.871-4.777-10.649-10.664-10.649H202.23c-5.887,0-10.665,4.778-10.665,10.649c0,0,0,20.862,0,42.66H212.895z"
|
||||
fill="#AAB2BC"
|
||||
/>
|
||||
<polygon
|
||||
fill="#AAB2BC"
|
||||
points="55.152,325.057 92.894,495.651 114.723,495.651 76.998,325.057 "
|
||||
/>
|
||||
<polygon
|
||||
fill="#AAB2BC"
|
||||
points="434.159,325.088 396.387,495.698 418.217,495.698 455.989,325.088 "
|
||||
/>
|
||||
<path
|
||||
d="M298.152,325.088v95.893h-85.257v-95.908h-21.33v106.573c0,5.871,4.778,10.649,10.665,10.649
|
||||
h106.588c5.887,0,10.664-4.778,10.664-10.649V325.088H298.152z"
|
||||
fill="#AAB2BC"
|
||||
/>
|
||||
<path
|
||||
d="M255.836,335.707c-17.661,0-31.979,14.318-31.979,31.979c0,17.66,14.319,31.979,31.979,31.979
|
||||
c17.645,0,31.964-14.319,31.964-31.979C287.8,350.025,273.481,335.707,255.836,335.707z M255.836,378.336
|
||||
c-5.887,0-10.665-4.778-10.665-10.649c0-5.872,4.778-10.665,10.665-10.665c5.871,0,10.649,4.793,10.649,10.665
|
||||
C266.485,373.558,261.707,378.336,255.836,378.336z"
|
||||
fill="#AAB2BC"
|
||||
/>
|
||||
</g>
|
||||
<polygon
|
||||
opacity={0.1}
|
||||
points="0,325.057 7.823,303.727 503.803,303.727 511.672,325.057"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export type ResumeBadgeProps = Readonly<{
|
||||
className: string;
|
||||
}>;
|
@ -0,0 +1,59 @@
|
||||
import type { ResumeBadgeProps } from '../resume-badge';
|
||||
|
||||
export default function ResumeBadgeDetectiveIcon({
|
||||
className,
|
||||
}: ResumeBadgeProps) {
|
||||
return (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
height="36px"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
role="img"
|
||||
viewBox="0 0 36 36"
|
||||
width="36px"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M33 36v-1a6 6 0 0 0-6-6H9a6 6 0 0 0-6 6v1h30zm-6.25-15.565c1.188.208 2.619.129 2.416.917c-.479 1.854-2.604 1.167-2.979 1.188c-.375.02.563-2.105.563-2.105z"
|
||||
fill="#66757F"></path>
|
||||
<path
|
||||
d="M27.062 20.645c1.875.25 2.541.416 1.166.958c-.772.305-2.243 4.803-3.331 4.118c-1.087-.685 2.165-5.076 2.165-5.076z"
|
||||
fill="#292F33"></path>
|
||||
<path
|
||||
d="M9.255 20.435c-1.188.208-2.619.129-2.416.917c.479 1.854 2.604 1.167 2.979 1.188c.375.02-.563-2.105-.563-2.105z"
|
||||
fill="#66757F"></path>
|
||||
<path
|
||||
d="M8.943 20.645c-1.875.25-2.541.416-1.166.958c.772.305 2.243 4.803 3.331 4.118c1.088-.685-2.165-5.076-2.165-5.076z"
|
||||
fill="#292F33"></path>
|
||||
<path
|
||||
d="M21.771 4.017c-1.958-.634-6.566-.461-7.718 1.037c-2.995.058-6.508 2.764-6.969 6.335c-.456 3.534.56 5.175.922 7.833c.409 3.011 2.102 3.974 3.456 4.377c1.947 2.572 4.017 2.462 7.492 2.462c6.787 0 10.019-4.541 10.305-12.253c.172-4.665-2.565-8.198-7.488-9.791z"
|
||||
fill="#FFAC33"></path>
|
||||
<path
|
||||
d="M25.652 14.137c-.657-.909-1.497-1.641-3.34-1.901c.691.317 1.353 1.411 1.44 2.016c.086.605.173 1.094-.374.49c-2.192-2.423-4.579-1.469-6.944-2.949c-1.652-1.034-2.155-2.177-2.155-2.177s-.202 1.526-2.707 3.081c-.726.451-1.593 1.455-2.073 2.937c-.346 1.066-.238 2.016-.238 3.64c0 4.74 3.906 8.726 8.726 8.726s8.726-4.02 8.726-8.726c-.004-2.948-.312-4.1-1.061-5.137z"
|
||||
fill="#FFDC5D"></path>
|
||||
<path
|
||||
d="M18.934 21.565h-1.922a.481.481 0 0 1-.481-.481v-.174c0-.265.215-.482.481-.482h1.922c.265 0 .482.216.482.482v.174a.481.481 0 0 1-.482.481"
|
||||
fill="#C1694F"></path>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M7.657 14.788c.148.147.888.591 1.036 1.034c.148.443.445 2.954 1.333 3.693c.916.762 4.37.478 5.032.149c1.48-.738 1.662-2.798 1.924-3.842c.148-.591 1.036-.591 1.036-.591s.888 0 1.036.591c.262 1.044.444 3.104 1.924 3.841c.662.33 4.116.614 5.034-.147c.887-.739 1.183-3.25 1.331-3.694c.146-.443.888-.886 1.035-1.034c.148-.148.148-.739 0-.887c-.296-.295-3.788-.559-7.548-.148c-.75.082-1.035.295-2.812.295c-1.776 0-2.062-.214-2.812-.295c-3.759-.411-7.252-.148-7.548.148c-.149.148-.149.74-.001.887z"
|
||||
fill="#292F33"
|
||||
fill-rule="evenodd"></path>
|
||||
<path
|
||||
d="M7.858 8.395S9.217-.506 13.79.023c3.512.406 4.89.825 7.833.097c1.947-.482 4.065 1.136 5.342 4.379a27.72 27.72 0 0 1 1.224 4.041s3.938-.385 4.165 1.732c.228 2.117-4.354 4.716-15.889 4.716C10 14.987 3.33 12.63 3.013 10.657c-.317-1.973 4.845-2.262 4.845-2.262z"
|
||||
fill="#66757F"></path>
|
||||
<path
|
||||
d="M8.125 7.15s-.27 1.104-.406 1.871c-.136.768.226 1.296 2.705 1.824c3.287.7 10.679.692 15.058-.383c1.759-.432 2.886-.72 2.751-1.583c-.167-1.068-.196-1.066-.541-2.208c0 0-1.477.502-3.427.96c-2.66.624-9.964.911-13.481.144c-1.874-.41-2.659-.625-2.659-.625zm-.136 13.953c-.354.145 2.921 1.378 7.48 1.458c4.771.084 6.234.39 5.146 1.459c-1.146 1.125-.852 2.894-.771 3.418c.081.524 2.047 1.916 2.208 2.56c.161.645-1.229 5.961-1.229 5.961l-8.729-.252c-2.565-8.844-2.883-8.501-4.105-13.604c-.241-1.008 0-1 0-1z"
|
||||
fill="#292F33"></path>
|
||||
<path
|
||||
d="M6.989 21.144c-.354.146 2.921 1.378 7.48 1.458c4.771.084 6.234.39 5.146 1.459c-1.146 1.125-.664 2.894-.583 3.418c.081.524 1.859 1.916 2.021 2.561c.16.644-1.231 5.96-1.231 5.96l-8.729-.252c-2.565-8.844-2.883-8.501-4.105-13.604c-.24-1.008.001-1 .001-1z"
|
||||
fill="#66757F"></path>
|
||||
<path
|
||||
d="M28.052 21.103c.354.145-2.921 1.378-7.479 1.458c-4.771.084-6.234.39-5.146 1.459c1.146 1.125 2.976 2.892 2.896 3.416c-.081.524-4.172 1.918-4.333 2.562c-.161.645 1.229 5.961 1.229 5.961l8.729-.252c2.565-8.844 2.883-8.501 4.104-13.604c.241-1.008 0-1 0-1z"
|
||||
fill="#292F33"></path>
|
||||
<path
|
||||
d="M28.958 21.103c.354.145-2.921 1.378-7.479 1.458c-4.771.084-6.234.39-5.146 1.459c1.146 1.125 2.977 2.892 2.896 3.416c-.081.524-4.172 1.918-4.333 2.562c-.161.645 1.229 5.961 1.229 5.961l8.657.01c2.565-8.844 2.955-8.763 4.177-13.866c.24-1.008-.001-1-.001-1z"
|
||||
fill="#66757F"></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
import type { ResumeBadgeProps } from '../resume-badge';
|
||||
|
||||
export default function ResumeBadgeEagleIcon({ className }: ResumeBadgeProps) {
|
||||
return (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
height="36px"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
viewBox="0 0 36 36"
|
||||
width="36px"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M7.042 26c.33 0 .651.121.963.331c1.368-8.106 20.362-8.248 21.755-.29c1.666.412 3.08 4.378 3.748 9.959h-31c.793-5.899 2.522-10 4.534-10z"
|
||||
fill="#292F33"></path>
|
||||
<path
|
||||
d="M7.043 23.688C10.966 12.533 6.508 3 17.508 3s8.736 8.173 13.193 19.125c1.119 2.75-1.443 5.908-1.443 5.908s-2.612-4.756-4.75-5.846c-.591 3.277-1.75 6.938-1.75 6.938s-2.581-2.965-5.587-5.587c-.879 1.009-2.065 2.183-3.663 3.462c-.349-1.048-.943-2.339-1.568-3.576c-1.468 2.238-3.182 4.951-3.182 4.951s-2.507-2.435-1.715-4.687z"
|
||||
fill="#E1E8ED"></path>
|
||||
<path
|
||||
d="M11.507 5c-4.36 3.059-5.542 2.16-7.812 3.562c-2.125 1.312-2 4.938-.125 8.062c.579-2.661-.5-3.149 6.938-3.149c5 0 7.928.289 7-1c-.927-1.289-10.027.459-6.001-7.475z"
|
||||
fill="#FFCC4D"></path>
|
||||
<path
|
||||
d="M16.535 7.517a1.483 1.483 0 1 1-2.967 0c0-.157.031-.305.076-.446h2.816c.044.141.075.289.075.446z"
|
||||
fill="#292F33"></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
import type { ResumeBadgeProps } from '../resume-badge';
|
||||
|
||||
export default function ResumeBadgeSuperheroIcon({
|
||||
className,
|
||||
}: ResumeBadgeProps) {
|
||||
return (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
height="36px"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
role="img"
|
||||
viewBox="0 0 36 36"
|
||||
width="36px"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M33.035 28.055c-3.843-2.612-14.989 2.92-15.037 2.944c-.047-.024-11.193-5.556-15.037-2.944C-.021 30.082 0 36 0 36h35.996s.021-5.918-2.961-7.945z"
|
||||
fill="#A0041E"></path>
|
||||
<path
|
||||
d="M32 29c-2.155-1.085-4 0-4 0l-10-1l-10 1s-1.845-1.085-4 0c-3.995 2.011-2 7-2 7h32s1.995-4.989-2-7z"
|
||||
fill="#55ACEE"></path>
|
||||
<path
|
||||
d="M24.056 36c-1.211-1.194-3.466-2-6.056-2s-4.845.806-6.056 2h12.112z"
|
||||
fill="#DD2E44"></path>
|
||||
<path
|
||||
d="M13.64 28.537C15.384 29.805 16.487 30.5 18 30.5c1.512 0 2.615-.696 4.359-1.963V24.29h-8.72v4.247z"
|
||||
fill="#D4AB88"></path>
|
||||
<path
|
||||
d="M30.453 27c-1.953-.266-3.594.547-3.594.547s-.845-.594-1.845-.614c-1.469-.03-2.442.935-3.014 1.755C21.281 29.719 19 30 18 30s-3.281-.281-4-1.312c-.572-.82-1.545-1.784-3.014-1.755c-1 .02-1.845.614-1.845.614S7.5 26.734 5.547 27c-1.305.177-2.357.764-2.846 1.248c2.83-1.685 4.757-.229 6.065.643C10.074 29.763 11 32 11 32c2-1 7-1 7-1s5 0 7 1c0 0 .926-2.237 2.234-3.109c1.308-.872 3.234-2.328 6.065-.643c-.489-.484-1.541-1.071-2.846-1.248z"
|
||||
fill="#DD2E44"></path>
|
||||
<path
|
||||
d="M13.632 25.5c.368 2.027 2.724 2.219 4.364 2.219c1.639 0 4.004-.191 4.363-2.219v-3.019h-8.728V25.5z"
|
||||
fill="#CC9B7A"></path>
|
||||
<path
|
||||
d="M11.444 15.936c0 1.448-.734 2.622-1.639 2.622s-1.639-1.174-1.639-2.622s.734-2.623 1.639-2.623c.905-.001 1.639 1.174 1.639 2.623m16.389 0c0 1.448-.733 2.622-1.639 2.622c-.905 0-1.639-1.174-1.639-2.622s.733-2.623 1.639-2.623c.906-.001 1.639 1.174 1.639 2.623"
|
||||
fill="#D4AB88"></path>
|
||||
<path
|
||||
d="M18 7c-5 0-8 2-8 5s0 9 2 12s4 3 6 3s4 0 6-3s2-9 2-12s-3-5-8-5z"
|
||||
fill="#D4AB88"></path>
|
||||
<path
|
||||
d="M18.821 3.118c6.004.49 8.356 4.246 8.356 7.851c0 3.604-.706 5.047-1.412 3.604c-.706-1.441-1.356-3.368-1.356-3.368s-4.292.485-5.704-.957c0 0 2.118 4.326-2.118 0c0 0 .706 2.884-3.53-.72c0 0-2.118 1.442-2.824 5.046c-.196 1.001-1.412 0-1.412-3.604c.001-2.677.179-6.652 4.908-6.17c1.028-1.639 3.018-1.851 5.092-1.682z"
|
||||
fill="#963B22"></path>
|
||||
<path
|
||||
d="M25 12c-3 0-5 1-7 1s-4-1-7-1s-1 5.72 0 6.72s5-1 7-1s6 2 7 1S28 12 25 12z"
|
||||
fill="#269"></path>
|
||||
<path
|
||||
d="M14 17c-.55 0-1-.45-1-1v-1c0-.55.45-1 1-1s1 .45 1 1v1c0 .55-.45 1-1 1m8 0c-.55 0-1-.45-1-1v-1c0-.55.45-1 1-1s1 .45 1 1v1c0 .55-.45 1-1 1"
|
||||
fill="#88C9F9"></path>
|
||||
<path
|
||||
d="M18.75 19.75h-1.5c-.413 0-.75-.337-.75-.75s.337-.75.75-.75h1.5c.413 0 .75.337.75.75s-.337.75-.75.75m-.75 3.5c-2.058 0-3.594-.504-3.658-.525a.5.5 0 0 1 .316-.949c.014.004 1.455.474 3.342.474s3.328-.47 3.343-.475a.5.5 0 0 1 .316.949c-.065.022-1.601.526-3.659.526z"
|
||||
fill="#C1694F"></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
import type { ResumeBadgeProps } from '../resume-badge';
|
||||
|
||||
export default function ResumeBadgeBookIcon({ className }: ResumeBadgeProps) {
|
||||
return (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
viewBox="0 0 512 512"
|
||||
x="0px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
y="0px">
|
||||
<path
|
||||
d="M8.17,90.446v350.268H224.7c2.367,0,3.648,1.543,4.089,2.206l9.701,11.315l35.025-0.003l9.699-11.316
|
||||
c0.439-0.661,1.719-2.202,4.086-2.202H503.83V90.446H8.17z"
|
||||
fill="#FF7226"
|
||||
/>
|
||||
<path
|
||||
d="M224.699,57.766H40.851v350.268h183.848c13.061,0,24.571,6.669,31.301,16.786l21.787-175.126
|
||||
L256,74.567C249.271,64.442,237.767,57.766,224.699,57.766z"
|
||||
fill="#F7EBD4"
|
||||
/>
|
||||
<path
|
||||
d="M287.301,57.766c-13.068,0-24.573,6.677-31.301,16.801v350.252
|
||||
c6.729-10.119,18.238-16.786,31.301-16.786h183.848V57.766H287.301z"
|
||||
fill="#D2F0E7"
|
||||
/>
|
||||
<rect fill="#F99FB6" height="67.028" width="128" x="84.426" y="297.428" />
|
||||
<g>
|
||||
<path
|
||||
d="M256,148.099c4.513,0,8.17-3.658,8.17-8.17v-32.681c0-4.512-3.657-8.17-8.17-8.17
|
||||
s-8.17,3.658-8.17,8.17v32.681C247.83,144.441,251.487,148.099,256,148.099z"
|
||||
fill="#3E0412"
|
||||
/>
|
||||
<path
|
||||
d="M256,390.861c4.513,0,8.17-3.658,8.17-8.17V172.609c0-4.512-3.657-8.17-8.17-8.17
|
||||
s-8.17,3.658-8.17,8.17v210.081C247.83,387.203,251.487,390.861,256,390.861z"
|
||||
fill="#3E0412"
|
||||
/>
|
||||
<path
|
||||
d="M503.83,82.276c-4.513,0-8.17,3.658-8.17,8.17v342.098H287.3c-4.182,0-8.077,1.987-10.54,5.346
|
||||
l-7.004,8.172l-27.511,0.002l-7.007-8.172c-2.467-3.36-6.363-5.348-10.541-5.348H16.34V90.446c0-4.512-3.657-8.17-8.17-8.17
|
||||
S0,85.934,0,90.446v350.268c0,4.512,3.657,8.17,8.17,8.17h214.971l9.146,10.668c1.552,1.81,3.818,2.852,6.203,2.852l35.025-0.003
|
||||
c2.385,0,4.652-1.043,6.203-2.854l9.139-10.664H503.83c4.513,0,8.17-3.658,8.17-8.17V90.446
|
||||
C512,85.934,508.343,82.276,503.83,82.276z"
|
||||
fill="#3E0412"
|
||||
/>
|
||||
<path
|
||||
d="M40.851,416.204H224.7c9.866,0,19.024,4.912,24.498,13.141c1.515,2.277,4.068,3.645,6.803,3.645
|
||||
c2.734,0,5.288-1.368,6.802-3.646c5.471-8.228,14.629-13.14,24.496-13.14h183.849c4.513,0,8.17-3.658,8.17-8.17V57.766
|
||||
c0-4.512-3.657-8.17-8.17-8.17H287.3c-11.783,0-22.915,4.503-31.3,12.389c-8.386-7.885-19.517-12.389-31.3-12.389H40.851
|
||||
c-4.513,0-8.17,3.658-8.17,8.17v350.268C32.681,412.546,36.338,416.204,40.851,416.204z M49.021,65.936H224.7
|
||||
c9.865,0,19.022,4.917,24.495,13.154c1.514,2.279,4.068,3.648,6.804,3.648c2.736,0,5.29-1.369,6.805-3.648
|
||||
c5.472-8.237,14.629-13.153,24.494-13.153h175.679v333.927H287.3c-11.784,0-22.915,4.5-31.301,12.378
|
||||
c-8.386-7.878-19.517-12.378-31.298-12.378H49.021V65.936z"
|
||||
fill="#3E0412"
|
||||
/>
|
||||
<path
|
||||
d="M212.426,93.17h-128c-4.513,0-8.17,3.658-8.17,8.17c0,4.512,3.657,8.17,8.17,8.17h128
|
||||
c4.513,0,8.17-3.658,8.17-8.17C220.596,96.828,216.939,93.17,212.426,93.17z"
|
||||
fill="#3E0412"
|
||||
/>
|
||||
<path
|
||||
d="M212.426,125.851h-128c-4.513,0-8.17,3.658-8.17,8.17s3.657,8.17,8.17,8.17h128
|
||||
c4.513,0,8.17-3.658,8.17-8.17C220.596,129.509,216.939,125.851,212.426,125.851z"
|
||||
fill="#3E0412"
|
||||
/>
|
||||
<path
|
||||
d="M212.426,158.532h-128c-4.513,0-8.17,3.658-8.17,8.17c0,4.512,3.657,8.17,8.17,8.17h128
|
||||
c4.513,0,8.17-3.658,8.17-8.17C220.596,162.19,216.939,158.532,212.426,158.532z"
|
||||
fill="#3E0412"
|
||||
/>
|
||||
<path
|
||||
d="M212.426,191.212h-128c-4.513,0-8.17,3.658-8.17,8.17c0,4.512,3.657,8.17,8.17,8.17h128
|
||||
c4.513,0,8.17-3.658,8.17-8.17C220.596,194.87,216.939,191.212,212.426,191.212z"
|
||||
fill="#3E0412"
|
||||
/>
|
||||
<path
|
||||
d="M212.426,223.893h-128c-4.513,0-8.17,3.658-8.17,8.17s3.657,8.17,8.17,8.17h128
|
||||
c4.513,0,8.17-3.658,8.17-8.17S216.939,223.893,212.426,223.893z"
|
||||
fill="#3E0412"
|
||||
/>
|
||||
<path
|
||||
d="M84.426,272.914h64c4.513,0,8.17-3.658,8.17-8.17c0-4.512-3.657-8.17-8.17-8.17h-64
|
||||
c-4.513,0-8.17,3.658-8.17,8.17C76.255,269.256,79.912,272.914,84.426,272.914z"
|
||||
fill="#3E0412"
|
||||
/>
|
||||
<path
|
||||
d="M212.426,289.255h-128c-4.513,0-8.17,3.658-8.17,8.17v67.034c0,4.512,3.657,8.17,8.17,8.17h128
|
||||
c4.513,0,8.17-3.658,8.17-8.17v-67.034C220.596,292.913,216.939,289.255,212.426,289.255z M204.255,356.289H92.596v-50.693h111.66
|
||||
V356.289z"
|
||||
fill="#3E0412"
|
||||
/>
|
||||
<path
|
||||
d="M299.574,241.906h128c4.513,0,8.17-3.658,8.17-8.17c0-4.512-3.657-8.17-8.17-8.17h-128
|
||||
c-4.513,0-8.17,3.658-8.17,8.17C291.404,238.248,295.061,241.906,299.574,241.906z"
|
||||
fill="#3E0412"
|
||||
/>
|
||||
<path
|
||||
d="M299.574,274.587h128c4.513,0,8.17-3.658,8.17-8.17c0-4.512-3.657-8.17-8.17-8.17h-128
|
||||
c-4.513,0-8.17,3.658-8.17,8.17C291.404,270.929,295.061,274.587,299.574,274.587z"
|
||||
fill="#3E0412"
|
||||
/>
|
||||
<path
|
||||
d="M299.574,307.268h128c4.513,0,8.17-3.658,8.17-8.17s-3.657-8.17-8.17-8.17h-128
|
||||
c-4.513,0-8.17,3.658-8.17,8.17S295.061,307.268,299.574,307.268z"
|
||||
fill="#3E0412"
|
||||
/>
|
||||
<path
|
||||
d="M299.574,339.948h128c4.513,0,8.17-3.658,8.17-8.17s-3.657-8.17-8.17-8.17h-128
|
||||
c-4.513,0-8.17,3.658-8.17,8.17S295.061,339.948,299.574,339.948z"
|
||||
fill="#3E0412"
|
||||
/>
|
||||
<path
|
||||
d="M299.574,372.629h64c4.513,0,8.17-3.658,8.17-8.17c0-4.512-3.657-8.17-8.17-8.17h-64
|
||||
c-4.513,0-8.17,3.658-8.17,8.17C291.404,368.971,295.061,372.629,299.574,372.629z"
|
||||
fill="#3E0412"
|
||||
/>
|
||||
<path
|
||||
d="M299.574,192.885c-4.513,0-8.17,3.658-8.17,8.17s3.657,8.17,8.17,8.17
|
||||
c11.714,0,21.543-2.95,30.036-7.774c0.43-0.204,0.84-0.44,1.226-0.712c13.658-8.171,23.803-21.223,32.739-34.574
|
||||
c8.933,13.346,19.073,26.393,32.723,34.564c0.398,0.282,0.821,0.528,1.268,0.736c8.486,4.814,18.308,7.758,30.01,7.758
|
||||
c4.513,0,8.17-3.658,8.17-8.17s-3.657-8.17-8.17-8.17c-6.915,0-12.958-1.35-18.383-3.759v-75.856
|
||||
c5.425-2.41,11.468-3.759,18.383-3.759c4.513,0,8.17-3.658,8.17-8.17c0-4.512-3.657-8.17-8.17-8.17
|
||||
c-11.706,0-21.531,2.947-30.021,7.764c-0.439,0.207-0.858,0.449-1.251,0.727c-13.654,8.171-23.795,21.22-32.729,34.568
|
||||
c-8.938-13.353-19.083-26.406-32.745-34.577c-0.38-0.268-0.784-0.501-1.208-0.702c-8.495-4.827-18.327-7.779-30.047-7.779
|
||||
c-4.513,0-8.17,3.658-8.17,8.17c0,4.512,3.657,8.17,8.17,8.17c6.915,0,12.958,1.35,18.383,3.759v75.856
|
||||
C312.532,191.535,306.49,192.885,299.574,192.885z M392.851,125.053v52.288c-7.034-7.18-13.2-16.294-19.562-26.144
|
||||
C379.651,141.347,385.817,132.232,392.851,125.053z M353.86,151.197c-6.363,9.85-12.528,18.965-19.562,26.144v-52.288
|
||||
C341.332,132.232,347.498,141.347,353.86,151.197z"
|
||||
fill="#3E0412"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
import type { ResumeBadgeProps } from '../resume-badge';
|
||||
|
||||
export default function ResumeBadgeOwlIcon({ className }: ResumeBadgeProps) {
|
||||
return (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
viewBox="0 0 511.988 511.988"
|
||||
x="0px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
y="0px">
|
||||
<g>
|
||||
<path
|
||||
d="M366.867,149.324c-5.891,0-10.672-4.766-10.672-10.656s4.781-10.672,10.672-10.672
|
||||
c16.781,0,23.312-4.469,25.719-7.141c1.562-1.718,2.062-3.515,2.062-3.53c0-5.891,4.781-10.656,10.688-10.656
|
||||
c5.875,0,10.656,4.766,10.656,10.656c0,1.578-0.375,9.843-7.562,17.812C399.961,144.557,385.961,149.324,366.867,149.324z"
|
||||
fill="#434A54"
|
||||
/>
|
||||
<path
|
||||
d="M145.122,127.995c-11.554,0-20.382-2.234-24.866-6.281c-2.438-2.202-2.859-4.296-2.93-4.765
|
||||
c0-0.031,0.008-0.062,0.008-0.078c0-5.891-4.781-10.672-10.672-10.672c-5.89,0-10.664,4.781-10.664,10.672
|
||||
c0,0.156,0.016,0.297,0.023,0.453h-0.023c0,1.578,0.367,9.843,7.555,17.812c8.484,9.422,22.476,14.188,41.569,14.188
|
||||
c5.891,0,10.672-4.766,10.672-10.656S151.013,127.995,145.122,127.995z M117.334,117.325h-0.023c0-0.109,0.016-0.234,0.016-0.344
|
||||
C117.342,117.2,117.334,117.325,117.334,117.325z"
|
||||
fill="#434A54"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="M255.995,63.998c-76.459,0-138.661,62.201-138.661,138.669c0,17.906,5.039,42.093,14.57,69.936
|
||||
c8.781,25.641,20.773,52.921,34.687,78.936c14.227,26.578,29.188,49.39,43.273,65.969c17.437,20.515,32.522,30.483,46.131,30.483
|
||||
s28.687-9.969,46.124-30.483c14.094-16.579,29.062-39.391,43.28-65.969c13.905-26.015,25.905-53.295,34.687-78.936
|
||||
c9.531-27.843,14.562-52.029,14.562-69.936C394.648,126.199,332.462,63.998,255.995,63.998z"
|
||||
fill="#A85D5D"
|
||||
/>
|
||||
<path
|
||||
d="M501.334,362.663H10.664C4.774,362.663,0,357.882,0,351.991c0-5.89,4.773-10.671,10.664-10.671
|
||||
h490.669c5.874,0,10.655,4.781,10.655,10.671C511.989,357.882,507.208,362.663,501.334,362.663z"
|
||||
fill="#FFD2A6"
|
||||
/>
|
||||
<g>
|
||||
<path
|
||||
d="M213.332,362.663c-5.891,0-10.672-4.781-10.672-10.672V341.32c0-5.891,4.781-10.656,10.672-10.656
|
||||
c5.89,0,10.664,4.766,10.664,10.656v10.671C223.996,357.882,219.223,362.663,213.332,362.663z"
|
||||
fill="#F6BB42"
|
||||
/>
|
||||
<path
|
||||
d="M234.66,362.663c-5.891,0-10.664-4.781-10.664-10.672V341.32c0-5.891,4.773-10.656,10.664-10.656
|
||||
c5.89,0,10.671,4.766,10.671,10.656v10.671C245.331,357.882,240.55,362.663,234.66,362.663z"
|
||||
fill="#F6BB42"
|
||||
/>
|
||||
<path
|
||||
d="M277.338,362.663c-5.897,0-10.679-4.781-10.679-10.672V341.32c0-5.891,4.781-10.656,10.679-10.656
|
||||
c5.875,0,10.656,4.766,10.656,10.656v10.671C287.994,357.882,283.213,362.663,277.338,362.663z"
|
||||
fill="#F6BB42"
|
||||
/>
|
||||
<path
|
||||
d="M298.65,362.663c-5.875,0-10.656-4.781-10.656-10.672V341.32c0-5.891,4.781-10.656,10.656-10.656
|
||||
c5.906,0,10.688,4.766,10.688,10.656v10.671C309.338,357.882,304.557,362.663,298.65,362.663z"
|
||||
fill="#F6BB42"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="M255.995,234.665c-5.89,0-10.664-4.781-10.664-10.671v-21.328c0-5.891,4.773-10.672,10.664-10.672
|
||||
c5.891,0,10.664,4.781,10.664,10.672v21.328C266.659,229.885,261.886,234.665,255.995,234.665z"
|
||||
fill="#FFCE54"
|
||||
/>
|
||||
<g>
|
||||
<path
|
||||
d="M387.914,159.947c-18.047-55.623-70.357-95.95-131.919-95.95
|
||||
c-61.295,0-113.419,39.983-131.685,95.231l0.148,0.766c35.375-71.326,131.537-42.67,131.537,21.328
|
||||
C255.995,117.34,352.523,88.684,387.914,159.947z"
|
||||
fill="#7F4545"
|
||||
/>
|
||||
<path
|
||||
d="M255.995,383.99c-5.89,0-10.664,4.781-10.664,10.672v51.391c3.656,1.297,7.211,1.938,10.664,1.938
|
||||
s7.008-0.641,10.664-1.938v-51.391C266.659,388.771,261.886,383.99,255.995,383.99z"
|
||||
fill="#7F4545"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="M255.995,63.998c-1.828,0-3.656,0.031-5.468,0.109
|
||||
c74.061,2.75,133.465,63.842,133.465,138.56c0,17.906-5.031,42.093-14.578,69.936c-8.766,25.641-20.765,52.921-34.687,78.936
|
||||
c-14.218,26.578-29.187,49.39-43.265,65.969c-15.188,17.874-28.601,27.733-40.803,29.983c1.805,0.328,3.586,0.5,5.335,0.5
|
||||
c13.609,0,28.687-9.969,46.124-30.483c14.094-16.579,29.062-39.391,43.28-65.969c13.905-26.015,25.905-53.295,34.687-78.936
|
||||
c9.531-27.843,14.562-52.029,14.562-69.936C394.648,126.199,332.462,63.998,255.995,63.998z"
|
||||
fill="#FFFFFF"
|
||||
opacity={0.1}
|
||||
/>
|
||||
<g>
|
||||
<path
|
||||
d="M219.98,193.322c0,15.094-13.187,27.344-29.444,27.344c-16.266,0-29.445-12.25-29.445-27.344
|
||||
s13.179-27.328,29.445-27.328C206.793,165.995,219.98,178.229,219.98,193.322z"
|
||||
fill="#F6BB42"
|
||||
/>
|
||||
<path
|
||||
d="M344.649,191.214c0,14.672-12.297,26.562-27.452,26.562c-15.156,0-27.453-11.891-27.453-26.562
|
||||
c0-14.656,12.297-26.547,27.453-26.547C332.352,164.667,344.649,176.557,344.649,191.214z"
|
||||
fill="#F6BB42"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="M191.997,181.322c-5.891,0-10.664,4.781-10.664,10.672s4.773,10.672,10.664,10.672
|
||||
s10.664-4.781,10.664-10.672S197.888,181.322,191.997,181.322z"
|
||||
fill="#434A54"
|
||||
/>
|
||||
<path
|
||||
d="M191.997,149.324c-23.523,0-42.664,19.14-42.664,42.671s19.14,42.671,42.664,42.671
|
||||
c23.523,0,42.663-19.14,42.663-42.671S215.52,149.324,191.997,149.324z M191.997,213.322c-11.766,0-21.336-9.562-21.336-21.328
|
||||
s9.57-21.328,21.336-21.328c11.765,0,21.335,9.562,21.335,21.328S203.762,213.322,191.997,213.322z"
|
||||
fill="#FFCE54"
|
||||
/>
|
||||
<path
|
||||
d="M319.994,181.322c-5.891,0-10.656,4.781-10.656,10.672s4.766,10.672,10.656,10.672
|
||||
s10.656-4.781,10.656-10.672S325.885,181.322,319.994,181.322z"
|
||||
fill="#434A54"
|
||||
/>
|
||||
<path
|
||||
d="M319.994,149.324c-23.531,0-42.656,19.14-42.656,42.671s19.125,42.671,42.656,42.671
|
||||
c23.53,0,42.654-19.14,42.654-42.671S343.524,149.324,319.994,149.324z M319.994,213.322c-11.766,0-21.344-9.562-21.344-21.328
|
||||
s9.578-21.328,21.344-21.328s21.343,9.562,21.343,21.328S331.76,213.322,319.994,213.322z"
|
||||
fill="#FFCE54"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
import type { ResumeBadgeProps } from '../resume-badge';
|
||||
|
||||
export default function ResumeBadgeSageIcon({ className }: ResumeBadgeProps) {
|
||||
return (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
viewBox="0 0 512 512"
|
||||
x="0px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
y="0px">
|
||||
<g>
|
||||
<path
|
||||
d="M252.356,4.59c-2.05-4.1-7.035-5.762-11.14-3.713c-4.1,2.051-5.763,7.037-3.713,11.14l16.605,33.211
|
||||
l14.852-7.427L252.356,4.59z"
|
||||
fill="#FFF3D4"
|
||||
/>
|
||||
<path
|
||||
d="M219.145,4.59c-2.051-4.1-7.035-5.762-11.14-3.713c-4.1,2.051-5.763,7.037-3.713,11.14
|
||||
l18.714,37.429l14.852-7.427L219.145,4.59z"
|
||||
fill="#FFF3D4"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="M106.661,141.146h-0.111c-18.342,0-33.211,14.868-33.211,33.211s14.868,33.211,33.211,33.211h149.448
|
||||
v-66.421C255.999,141.146,106.661,141.146,106.661,141.146z"
|
||||
fill="#FFCDC1"
|
||||
/>
|
||||
<path
|
||||
d="M405.448,141.146h-0.111H256v66.421h149.448c18.342,0,33.211-14.868,33.211-33.211
|
||||
S423.789,141.146,405.448,141.146z"
|
||||
fill="#FFAB97"
|
||||
/>
|
||||
<path
|
||||
d="M482.94,334.876c0-24.455-19.825-44.281-44.281-44.281l-33.211,94.097l33.211,94.097h44.281V334.876z
|
||||
"
|
||||
fill="#7F7774"
|
||||
/>
|
||||
<path
|
||||
d="M73.341,290.595c-24.456,0-44.281,19.826-44.281,44.281v143.913h409.599V290.595H73.341z"
|
||||
fill="#A99E9B"
|
||||
/>
|
||||
<path
|
||||
d="M386.948,74.725h-44.281L372.237,512c18.342,0,33.211-14.868,33.211-33.211V146.682
|
||||
C405.448,120.611,398.731,96.084,386.948,74.725z"
|
||||
fill="#FFEAB2"
|
||||
/>
|
||||
<path
|
||||
d="M342.667,74.725H125.051c-11.783,21.359-18.501,45.886-18.501,71.957v332.107
|
||||
c0,18.342,14.868,33.211,33.211,33.211c12.507,0,23.396-6.918,29.059-17.132C174.484,505.082,185.373,512,197.88,512
|
||||
s23.396-6.918,29.059-17.132C232.603,505.082,243.492,512,255.999,512c12.507,0,23.396-6.918,29.059-17.132
|
||||
C290.722,505.082,301.61,512,314.118,512s23.396-6.918,29.059-17.132C348.84,505.082,359.729,512,372.236,512V146.682
|
||||
L342.667,74.725z"
|
||||
fill="#FFF3D4"
|
||||
/>
|
||||
<path
|
||||
d="M256,30.444l83.027,149.448h33.211v-33.211C372.237,82.485,320.195,30.444,256,30.444z"
|
||||
fill="#FFAB97"
|
||||
/>
|
||||
<path
|
||||
d="M256,30.444c-64.196,0-116.238,52.041-116.238,116.238v33.211h199.264v-33.211
|
||||
C339.027,82.485,301.854,30.444,256,30.444z"
|
||||
fill="#FFCDC1"
|
||||
/>
|
||||
<g>
|
||||
<path
|
||||
d="M172.973,298.897c-4.586,0-8.303-3.716-8.303-8.303v-11.07c0-4.586,3.716-8.303,8.303-8.303
|
||||
s8.303,3.716,8.303,8.303v11.07C181.276,295.181,177.558,298.897,172.973,298.897z"
|
||||
fill="#FFD159"
|
||||
/>
|
||||
<path
|
||||
d="M339.027,387.459c-4.586,0-8.303-3.716-8.303-8.303v-11.07c0-4.586,3.716-8.303,8.303-8.303
|
||||
c4.586,0,8.303,3.716,8.303,8.303v11.07C347.329,383.743,343.612,387.459,339.027,387.459z"
|
||||
fill="#FFD159"
|
||||
/>
|
||||
<path
|
||||
d="M305.816,420.67c-4.586,0-8.303-3.716-8.303-8.303v-11.07c0-4.586,3.716-8.303,8.303-8.303
|
||||
c4.586,0,8.303,3.716,8.303,8.303v11.07C314.119,416.954,310.401,420.67,305.816,420.67z"
|
||||
fill="#FFD159"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="M256,234.297c-11.69,0-22.174-7.399-26.088-18.412c-1.537-4.321,0.721-9.069,5.042-10.604
|
||||
c4.315-1.54,9.068,0.72,10.603,5.041c1.568,4.408,5.764,7.369,10.444,7.369c4.679,0,8.876-2.961,10.444-7.369
|
||||
c1.535-4.32,6.285-6.58,10.603-5.041c4.321,1.535,6.578,6.283,5.042,10.604C278.174,226.898,267.689,234.297,256,234.297z"
|
||||
fill="#E26142"
|
||||
/>
|
||||
<g>
|
||||
<path
|
||||
d="M225.003,154.984c-3.616,0-6.691-2.31-7.83-5.535h-10.989c-4.586,0-8.303-3.716-8.303-8.303
|
||||
c0-4.586,3.716-8.303,8.303-8.303h18.819c4.586,0,8.303,3.716,8.303,8.303v5.535C233.306,151.268,229.588,154.984,225.003,154.984z
|
||||
"
|
||||
fill="#554F4E"
|
||||
/>
|
||||
<path
|
||||
d="M286.997,154.984c-4.586,0-8.303-3.716-8.303-8.303v-5.535c0-4.586,3.716-8.303,8.303-8.303h18.819
|
||||
c4.586,0,8.303,3.716,8.303,8.303c0,4.586-3.716,8.303-8.303,8.303h-10.989C293.686,152.674,290.611,154.984,286.997,154.984z"
|
||||
fill="#554F4E"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
import type { BadgeIcon } from './resumeBadgeConstants';
|
||||
|
||||
type Props = Readonly<{
|
||||
description: string;
|
||||
icon: BadgeIcon;
|
||||
title: string;
|
||||
}>;
|
||||
|
||||
export default function ResumeUserBadge({
|
||||
description,
|
||||
icon: Icon,
|
||||
title,
|
||||
}: Props) {
|
||||
return (
|
||||
<div className="group relative flex items-center justify-center">
|
||||
<div
|
||||
className="absolute -top-3 hidden w-48 -translate-y-full flex-col
|
||||
justify-center gap-1 rounded-lg bg-white px-2 py-2 text-center drop-shadow-xl
|
||||
after:absolute after:left-1/2 after:top-[100%] after:-translate-x-1/2
|
||||
after:border-8 after:border-x-transparent after:border-b-transparent
|
||||
after:border-t-white after:drop-shadow-lg after:content-['']
|
||||
group-hover:flex">
|
||||
<Icon className="h-12 w-12 self-center" />
|
||||
<p className="font-medium">{title}</p>
|
||||
<p className="text-sm">{description}.</p>
|
||||
</div>
|
||||
<Icon className="h-4 w-4" />
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import { trpc } from '~/utils/trpc';
|
||||
|
||||
import type { BadgePayload } from './resumeBadgeConstants';
|
||||
import { RESUME_USER_BADGES } from './resumeBadgeConstants';
|
||||
import ResumeUserBadge from './ResumeUserBadge';
|
||||
|
||||
type Props = Readonly<{
|
||||
userId: string;
|
||||
}>;
|
||||
|
||||
export default function ResumeUserBadges({ userId }: Props) {
|
||||
const userReviewedResumeCountQuery = trpc.useQuery([
|
||||
'resumes.resume.findUserReviewedResumeCount',
|
||||
{ userId },
|
||||
]);
|
||||
const userMaxResumeUpvoteCountQuery = trpc.useQuery([
|
||||
'resumes.resume.findUserMaxResumeUpvoteCount',
|
||||
{ userId },
|
||||
]);
|
||||
const userTopUpvotedCommentCountQuery = trpc.useQuery([
|
||||
'resumes.resume.findUserTopUpvotedCommentCount',
|
||||
{ userId },
|
||||
]);
|
||||
|
||||
const payload: BadgePayload = {
|
||||
maxResumeUpvoteCount: userMaxResumeUpvoteCountQuery.data ?? 0,
|
||||
reviewedResumesCount: userReviewedResumeCountQuery.data ?? 0,
|
||||
topUpvotedCommentCount: userTopUpvotedCommentCountQuery.data ?? 0,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center gap-1">
|
||||
{RESUME_USER_BADGES.filter((badge) => badge.isValid(payload)).map(
|
||||
(badge) => (
|
||||
<ResumeUserBadge
|
||||
key={badge.id}
|
||||
description={badge.description}
|
||||
icon={badge.icon}
|
||||
title={badge.title}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
import ResumeBadgeCoolIcon from '../badgeIcons/popularResumes/ResumeBadgeCoolIcon';
|
||||
import ResumeBadgeRocketIcon from '../badgeIcons/popularResumes/ResumeBadgeRocketIcon';
|
||||
import ResumeBadgeTreasureIcon from '../badgeIcons/popularResumes/ResumeBadgeTreasureIcon';
|
||||
import ResumeBadgeDetectiveIcon from '../badgeIcons/reviewer/ResumeBadgeDetectiveIcon';
|
||||
import ResumeBadgeEagleIcon from '../badgeIcons/reviewer/ResumeBadgeEagleIcon';
|
||||
import ResumeBadgeSuperheroIcon from '../badgeIcons/reviewer/ResumeBadgeSuperheroIcon';
|
||||
import ResumeBadgeBookIcon from '../badgeIcons/topComment/ResumeBadgeBookIcon';
|
||||
import ResumeBadgeOwlIcon from '../badgeIcons/topComment/ResumeBadgeOwlIcon';
|
||||
import ResumeBadgeSageIcon from '../badgeIcons/topComment/ResumeBadgeSageIcon';
|
||||
|
||||
export type BadgeIcon = (
|
||||
props: React.ComponentProps<typeof ResumeBadgeDetectiveIcon>,
|
||||
) => JSX.Element;
|
||||
|
||||
export type BadgeInfo = {
|
||||
description: string;
|
||||
icon: BadgeIcon;
|
||||
id: string;
|
||||
isValid: (payload: BadgePayload) => boolean;
|
||||
title: string;
|
||||
};
|
||||
|
||||
// TODO: Add other badges in
|
||||
export type BadgePayload = {
|
||||
maxResumeUpvoteCount: number;
|
||||
reviewedResumesCount: number;
|
||||
topUpvotedCommentCount: number;
|
||||
};
|
||||
|
||||
const TIER_THREE = 20;
|
||||
const TIER_TWO = 10;
|
||||
const TIER_ONE = 5;
|
||||
|
||||
export const RESUME_USER_BADGES: Array<BadgeInfo> = [
|
||||
{
|
||||
description: `Reviewed over ${TIER_THREE} resumes`,
|
||||
icon: ResumeBadgeSuperheroIcon,
|
||||
id: 'Superhero',
|
||||
isValid: (payload: BadgePayload) =>
|
||||
payload.reviewedResumesCount >= TIER_THREE,
|
||||
title: 'True saviour of the people',
|
||||
},
|
||||
{
|
||||
description: `Reviewed over ${TIER_TWO} resumes`,
|
||||
icon: ResumeBadgeDetectiveIcon,
|
||||
id: 'Detective',
|
||||
isValid: (payload: BadgePayload) =>
|
||||
payload.reviewedResumesCount >= TIER_TWO &&
|
||||
payload.reviewedResumesCount < TIER_THREE,
|
||||
title: 'Keen eye for details like a private eye',
|
||||
},
|
||||
{
|
||||
description: `Reviewed over ${TIER_ONE} resumes`,
|
||||
icon: ResumeBadgeEagleIcon,
|
||||
id: 'Eagle',
|
||||
isValid: (payload: BadgePayload) =>
|
||||
payload.reviewedResumesCount >= TIER_ONE &&
|
||||
payload.reviewedResumesCount < TIER_TWO,
|
||||
title: 'As sharp as an eagle',
|
||||
},
|
||||
{
|
||||
description: `${TIER_THREE} upvotes on a resume`,
|
||||
icon: ResumeBadgeRocketIcon,
|
||||
id: 'Rocket',
|
||||
isValid: (payload: BadgePayload) =>
|
||||
payload.maxResumeUpvoteCount >= TIER_THREE,
|
||||
title: 'To the moon!',
|
||||
},
|
||||
{
|
||||
description: `${TIER_TWO} upvotes on a resume`,
|
||||
icon: ResumeBadgeTreasureIcon,
|
||||
id: 'Treasure',
|
||||
isValid: (payload: BadgePayload) =>
|
||||
payload.maxResumeUpvoteCount >= TIER_TWO &&
|
||||
payload.maxResumeUpvoteCount < TIER_THREE,
|
||||
title: "Can't get enough of this!",
|
||||
},
|
||||
{
|
||||
description: `${TIER_ONE} upvotes on a resume`,
|
||||
icon: ResumeBadgeCoolIcon,
|
||||
id: 'Cool',
|
||||
isValid: (payload: BadgePayload) =>
|
||||
payload.maxResumeUpvoteCount >= TIER_ONE &&
|
||||
payload.maxResumeUpvoteCount < TIER_TWO,
|
||||
title: 'Like the cool kids',
|
||||
},
|
||||
{
|
||||
description: `${TIER_THREE} top upvoted comment`,
|
||||
icon: ResumeBadgeSageIcon,
|
||||
id: 'Sage',
|
||||
isValid: (payload: BadgePayload) =>
|
||||
payload.topUpvotedCommentCount >= TIER_THREE,
|
||||
title: 'I am wisdom',
|
||||
},
|
||||
{
|
||||
description: `${TIER_TWO} top upvoted comment`,
|
||||
icon: ResumeBadgeBookIcon,
|
||||
id: 'Book',
|
||||
isValid: (payload: BadgePayload) =>
|
||||
payload.topUpvotedCommentCount >= TIER_TWO &&
|
||||
payload.topUpvotedCommentCount < TIER_THREE,
|
||||
title: 'The walking encyclopaedia',
|
||||
},
|
||||
{
|
||||
description: `${TIER_ONE} top upvoted comment`,
|
||||
icon: ResumeBadgeOwlIcon,
|
||||
id: 'Owl',
|
||||
isValid: (payload: BadgePayload) =>
|
||||
payload.topUpvotedCommentCount >= TIER_ONE &&
|
||||
payload.topUpvotedCommentCount < TIER_TWO,
|
||||
title: 'Wise as an owl',
|
||||
},
|
||||
];
|
@ -1,96 +0,0 @@
|
||||
export const BROWSE_TABS_VALUES = {
|
||||
ALL: 'all',
|
||||
MY: 'my',
|
||||
STARRED: 'starred',
|
||||
};
|
||||
|
||||
export type SortOrder = 'latest' | 'popular' | 'topComments';
|
||||
type SortOption = {
|
||||
name: string;
|
||||
value: SortOrder;
|
||||
};
|
||||
|
||||
export const SORT_OPTIONS: Array<SortOption> = [
|
||||
{ name: 'Latest', value: 'latest' },
|
||||
{ name: 'Popular', value: 'popular' },
|
||||
{ name: 'Top Comments', value: 'topComments' },
|
||||
];
|
||||
|
||||
export const TOP_HITS = [
|
||||
{ href: '#', name: 'Unreviewed' },
|
||||
{ href: '#', name: 'Fresh Grad' },
|
||||
{ href: '#', name: 'GOATs' },
|
||||
{ href: '#', name: 'US Only' },
|
||||
];
|
||||
|
||||
export type FilterOption = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export const ROLE: Array<FilterOption> = [
|
||||
{
|
||||
label: 'Full-Stack Engineer',
|
||||
value: 'Full-Stack Engineer',
|
||||
},
|
||||
{ label: 'Frontend Engineer', value: 'Frontend Engineer' },
|
||||
{ label: 'Backend Engineer', value: 'Backend Engineer' },
|
||||
{ label: 'DevOps Engineer', value: 'DevOps Engineer' },
|
||||
{ label: 'iOS Engineer', value: 'iOS Engineer' },
|
||||
{ label: 'Android Engineer', value: 'Android Engineer' },
|
||||
];
|
||||
|
||||
export const EXPERIENCE: Array<FilterOption> = [
|
||||
{ label: 'Freshman', value: 'Freshman' },
|
||||
{ label: 'Sophomore', value: 'Sophomore' },
|
||||
{ label: 'Junior', value: 'Junior' },
|
||||
{ label: 'Senior', value: 'Senior' },
|
||||
{
|
||||
label: 'Entry Level (0 - 2 years)',
|
||||
value: 'Entry Level (0 - 2 years)',
|
||||
},
|
||||
{
|
||||
label: 'Mid Level (3 - 5 years)',
|
||||
value: 'Mid Level (3 - 5 years)',
|
||||
},
|
||||
{
|
||||
label: 'Senior Level (5+ years)',
|
||||
value: 'Senior Level (5+ years)',
|
||||
},
|
||||
];
|
||||
|
||||
export const LOCATION: Array<FilterOption> = [
|
||||
{ label: 'Singapore', value: 'Singapore' },
|
||||
{ label: 'United States', value: 'United States' },
|
||||
{ label: 'India', value: 'India' },
|
||||
];
|
||||
|
||||
export const TEST_RESUMES = [
|
||||
{
|
||||
createdAt: new Date(),
|
||||
experience: 'Fresh Grad (0-1 years)',
|
||||
numComments: 9,
|
||||
numStars: 1,
|
||||
role: 'Backend Engineer',
|
||||
title: 'Rejected from multiple companies, please help...:(',
|
||||
user: 'Git Ji Ra',
|
||||
},
|
||||
{
|
||||
createdAt: new Date(),
|
||||
experience: 'Fresh Grad (0-1 years)',
|
||||
numComments: 9,
|
||||
numStars: 1,
|
||||
role: 'Backend Engineer',
|
||||
title: 'Rejected from multiple companies, please help...:(',
|
||||
user: 'Git Ji Ra',
|
||||
},
|
||||
{
|
||||
createdAt: new Date(),
|
||||
experience: 'Fresh Grad (0-1 years)',
|
||||
numComments: 9,
|
||||
numStars: 1,
|
||||
role: 'Backend Engineer',
|
||||
title: 'Rejected from multiple companies, please help...:(',
|
||||
user: 'Git Ji Ra',
|
||||
},
|
||||
];
|
@ -0,0 +1,151 @@
|
||||
export type FilterId = 'experience' | 'location' | 'role';
|
||||
|
||||
export type CustomFilter = {
|
||||
numComments: number;
|
||||
};
|
||||
|
||||
type RoleFilter =
|
||||
| 'Android Engineer'
|
||||
| 'Backend Engineer'
|
||||
| 'DevOps Engineer'
|
||||
| 'Frontend Engineer'
|
||||
| 'Full-Stack Engineer'
|
||||
| 'iOS Engineer';
|
||||
|
||||
type ExperienceFilter =
|
||||
| 'Entry Level (0 - 2 years)'
|
||||
| 'Freshman'
|
||||
| 'Junior'
|
||||
| 'Mid Level (3 - 5 years)'
|
||||
| 'Senior Level (5+ years)'
|
||||
| 'Senior'
|
||||
| 'Sophomore';
|
||||
|
||||
type LocationFilter = 'India' | 'Singapore' | 'United States';
|
||||
|
||||
export type FilterValue = ExperienceFilter | LocationFilter | RoleFilter;
|
||||
|
||||
export type FilterOption<T> = {
|
||||
label: string;
|
||||
value: T;
|
||||
};
|
||||
|
||||
export type Filter = {
|
||||
id: FilterId;
|
||||
label: string;
|
||||
options: Array<FilterOption<FilterValue>>;
|
||||
};
|
||||
|
||||
export type FilterState = Partial<CustomFilter> &
|
||||
Record<FilterId, Array<FilterValue>>;
|
||||
|
||||
export type SortOrder = 'latest' | 'popular' | 'topComments';
|
||||
|
||||
export type Shortcut = {
|
||||
customFilters?: CustomFilter;
|
||||
filters: FilterState;
|
||||
name: string;
|
||||
sortOrder: SortOrder;
|
||||
};
|
||||
|
||||
export const BROWSE_TABS_VALUES = {
|
||||
ALL: 'all',
|
||||
MY: 'my',
|
||||
STARRED: 'starred',
|
||||
};
|
||||
|
||||
export const SORT_OPTIONS: Record<string, string> = {
|
||||
latest: 'Latest',
|
||||
popular: 'Popular',
|
||||
topComments: 'Top Comments',
|
||||
};
|
||||
|
||||
export const ROLE: Array<FilterOption<RoleFilter>> = [
|
||||
{
|
||||
label: 'Full-Stack Engineer',
|
||||
value: 'Full-Stack Engineer',
|
||||
},
|
||||
{ label: 'Frontend Engineer', value: 'Frontend Engineer' },
|
||||
{ label: 'Backend Engineer', value: 'Backend Engineer' },
|
||||
{ label: 'DevOps Engineer', value: 'DevOps Engineer' },
|
||||
{ label: 'iOS Engineer', value: 'iOS Engineer' },
|
||||
{ label: 'Android Engineer', value: 'Android Engineer' },
|
||||
];
|
||||
|
||||
export const EXPERIENCE: Array<FilterOption<ExperienceFilter>> = [
|
||||
{ label: 'Freshman', value: 'Freshman' },
|
||||
{ label: 'Sophomore', value: 'Sophomore' },
|
||||
{ label: 'Junior', value: 'Junior' },
|
||||
{ label: 'Senior', value: 'Senior' },
|
||||
{
|
||||
label: 'Entry Level (0 - 2 years)',
|
||||
value: 'Entry Level (0 - 2 years)',
|
||||
},
|
||||
{
|
||||
label: 'Mid Level (3 - 5 years)',
|
||||
value: 'Mid Level (3 - 5 years)',
|
||||
},
|
||||
{
|
||||
label: 'Senior Level (5+ years)',
|
||||
value: 'Senior Level (5+ years)',
|
||||
},
|
||||
];
|
||||
|
||||
export const LOCATION: Array<FilterOption<LocationFilter>> = [
|
||||
{ label: 'Singapore', value: 'Singapore' },
|
||||
{ label: 'United States', value: 'United States' },
|
||||
{ label: 'India', value: 'India' },
|
||||
];
|
||||
|
||||
export const INITIAL_FILTER_STATE: FilterState = {
|
||||
experience: Object.values(EXPERIENCE).map(({ value }) => value),
|
||||
location: Object.values(LOCATION).map(({ value }) => value),
|
||||
role: Object.values(ROLE).map(({ value }) => value),
|
||||
};
|
||||
|
||||
export const SHORTCUTS: Array<Shortcut> = [
|
||||
{
|
||||
filters: INITIAL_FILTER_STATE,
|
||||
name: 'All',
|
||||
sortOrder: 'latest',
|
||||
},
|
||||
{
|
||||
filters: {
|
||||
...INITIAL_FILTER_STATE,
|
||||
numComments: 0,
|
||||
},
|
||||
name: 'Unreviewed',
|
||||
sortOrder: 'latest',
|
||||
},
|
||||
{
|
||||
filters: {
|
||||
...INITIAL_FILTER_STATE,
|
||||
experience: ['Entry Level (0 - 2 years)'],
|
||||
},
|
||||
name: 'Fresh Grad',
|
||||
sortOrder: 'latest',
|
||||
},
|
||||
{
|
||||
filters: INITIAL_FILTER_STATE,
|
||||
name: 'GOATs',
|
||||
sortOrder: 'popular',
|
||||
},
|
||||
{
|
||||
filters: {
|
||||
...INITIAL_FILTER_STATE,
|
||||
location: ['United States'],
|
||||
},
|
||||
name: 'US Only',
|
||||
sortOrder: 'latest',
|
||||
},
|
||||
];
|
||||
|
||||
export const isInitialFilterState = (filters: FilterState) =>
|
||||
Object.keys(filters).every((filter) => {
|
||||
if (!['experience', 'location', 'role'].includes(filter)) {
|
||||
return true;
|
||||
}
|
||||
return INITIAL_FILTER_STATE[filter as FilterId].every((value) =>
|
||||
filters[filter as FilterId].includes(value),
|
||||
);
|
||||
});
|
@ -0,0 +1,106 @@
|
||||
import type { SubmitHandler } from 'react-hook-form';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Button, TextArea } from '@tih/ui';
|
||||
|
||||
import { trpc } from '~/utils/trpc';
|
||||
|
||||
import type { ResumeComment } from '~/types/resume-comments';
|
||||
|
||||
type ResumeCommentEditFormProps = {
|
||||
comment: ResumeComment;
|
||||
setIsEditingComment: (value: boolean) => void;
|
||||
};
|
||||
|
||||
type ICommentInput = {
|
||||
description: string;
|
||||
};
|
||||
|
||||
export default function ResumeCommentEditForm({
|
||||
comment,
|
||||
setIsEditingComment,
|
||||
}: ResumeCommentEditFormProps) {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors, isDirty },
|
||||
reset,
|
||||
} = useForm<ICommentInput>({
|
||||
defaultValues: {
|
||||
description: comment.description,
|
||||
},
|
||||
});
|
||||
|
||||
const trpcContext = trpc.useContext();
|
||||
const commentUpdateMutation = trpc.useMutation(
|
||||
'resumes.comments.user.update',
|
||||
{
|
||||
onSuccess: () => {
|
||||
// Comment updated, invalidate query to trigger refetch
|
||||
trpcContext.invalidateQueries(['resumes.comments.list']);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const onCancel = () => {
|
||||
reset({ description: comment.description });
|
||||
setIsEditingComment(false);
|
||||
};
|
||||
|
||||
const onSubmit: SubmitHandler<ICommentInput> = async (data) => {
|
||||
const { id } = comment;
|
||||
return commentUpdateMutation.mutate(
|
||||
{
|
||||
id,
|
||||
...data,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
setIsEditingComment(false);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const setFormValue = (value: string) => {
|
||||
setValue('description', value.trim(), { shouldDirty: true });
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="flex-column mt-1 space-y-2">
|
||||
<TextArea
|
||||
{...(register('description', {
|
||||
required: 'Comments cannot be empty!',
|
||||
}),
|
||||
{})}
|
||||
defaultValue={comment.description}
|
||||
disabled={commentUpdateMutation.isLoading}
|
||||
errorMessage={errors.description?.message}
|
||||
label=""
|
||||
placeholder="Leave your comment here"
|
||||
onChange={setFormValue}
|
||||
/>
|
||||
|
||||
<div className="flex-row space-x-2">
|
||||
<Button
|
||||
disabled={commentUpdateMutation.isLoading}
|
||||
label="Cancel"
|
||||
size="sm"
|
||||
variant="tertiary"
|
||||
onClick={onCancel}
|
||||
/>
|
||||
|
||||
<Button
|
||||
disabled={!isDirty || commentUpdateMutation.isLoading}
|
||||
isLoading={commentUpdateMutation.isLoading}
|
||||
label="Confirm"
|
||||
size="sm"
|
||||
type="submit"
|
||||
variant="primary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
import type { SubmitHandler } from 'react-hook-form';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type { ResumesSection } from '@prisma/client';
|
||||
import { Button, TextArea } from '@tih/ui';
|
||||
|
||||
import { trpc } from '~/utils/trpc';
|
||||
|
||||
type ResumeCommentEditFormProps = {
|
||||
parentId: string;
|
||||
resumeId: string;
|
||||
section: ResumesSection;
|
||||
setIsReplyingComment: (value: boolean) => void;
|
||||
};
|
||||
|
||||
type IReplyInput = {
|
||||
description: string;
|
||||
};
|
||||
|
||||
export default function ResumeCommentReplyForm({
|
||||
parentId,
|
||||
setIsReplyingComment,
|
||||
resumeId,
|
||||
section,
|
||||
}: ResumeCommentEditFormProps) {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors, isDirty },
|
||||
reset,
|
||||
} = useForm<IReplyInput>({
|
||||
defaultValues: {
|
||||
description: '',
|
||||
},
|
||||
});
|
||||
|
||||
const trpcContext = trpc.useContext();
|
||||
const commentReplyMutation = trpc.useMutation('resumes.comments.user.reply', {
|
||||
onSuccess: () => {
|
||||
// Comment updated, invalidate query to trigger refetch
|
||||
trpcContext.invalidateQueries(['resumes.comments.list']);
|
||||
},
|
||||
});
|
||||
|
||||
const onCancel = () => {
|
||||
reset({ description: '' });
|
||||
setIsReplyingComment(false);
|
||||
};
|
||||
|
||||
const onSubmit: SubmitHandler<IReplyInput> = async (data) => {
|
||||
return commentReplyMutation.mutate(
|
||||
{
|
||||
parentId,
|
||||
resumeId,
|
||||
section,
|
||||
...data,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
setIsReplyingComment(false);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const setFormValue = (value: string) => {
|
||||
setValue('description', value.trim(), { shouldDirty: true });
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="flex-column space-y-2 pt-2">
|
||||
<TextArea
|
||||
{...(register('description', {
|
||||
required: 'Reply cannot be empty!',
|
||||
}),
|
||||
{})}
|
||||
defaultValue=""
|
||||
disabled={commentReplyMutation.isLoading}
|
||||
errorMessage={errors.description?.message}
|
||||
label=""
|
||||
placeholder="Leave your reply here"
|
||||
onChange={setFormValue}
|
||||
/>
|
||||
|
||||
<div className="flex-row space-x-2">
|
||||
<Button
|
||||
disabled={commentReplyMutation.isLoading}
|
||||
label="Cancel"
|
||||
size="sm"
|
||||
variant="tertiary"
|
||||
onClick={onCancel}
|
||||
/>
|
||||
|
||||
<Button
|
||||
disabled={!isDirty || commentReplyMutation.isLoading}
|
||||
isLoading={commentReplyMutation.isLoading}
|
||||
label="Confirm"
|
||||
size="sm"
|
||||
type="submit"
|
||||
variant="primary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
import clsx from 'clsx';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
ArrowDownCircleIcon,
|
||||
ArrowUpCircleIcon,
|
||||
} from '@heroicons/react/20/solid';
|
||||
import { Vote } from '@prisma/client';
|
||||
|
||||
import { trpc } from '~/utils/trpc';
|
||||
|
||||
type ResumeCommentVoteButtonsProps = {
|
||||
commentId: string;
|
||||
userId: string | undefined;
|
||||
};
|
||||
|
||||
export default function ResumeCommentVoteButtons({
|
||||
commentId,
|
||||
userId,
|
||||
}: ResumeCommentVoteButtonsProps) {
|
||||
const [upvoteAnimation, setUpvoteAnimation] = useState(false);
|
||||
const [downvoteAnimation, setDownvoteAnimation] = useState(false);
|
||||
|
||||
const trpcContext = trpc.useContext();
|
||||
|
||||
// COMMENT VOTES
|
||||
const commentVotesQuery = trpc.useQuery([
|
||||
'resumes.comments.votes.list',
|
||||
{ commentId },
|
||||
]);
|
||||
const commentVotesUpsertMutation = trpc.useMutation(
|
||||
'resumes.comments.votes.user.upsert',
|
||||
{
|
||||
onSuccess: () => {
|
||||
// Comment updated, invalidate query to trigger refetch
|
||||
trpcContext.invalidateQueries(['resumes.comments.votes.list']);
|
||||
},
|
||||
},
|
||||
);
|
||||
const commentVotesDeleteMutation = trpc.useMutation(
|
||||
'resumes.comments.votes.user.delete',
|
||||
{
|
||||
onSuccess: () => {
|
||||
// Comment updated, invalidate query to trigger refetch
|
||||
trpcContext.invalidateQueries(['resumes.comments.votes.list']);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const onVote = async (value: Vote, setAnimation: (_: boolean) => void) => {
|
||||
setAnimation(true);
|
||||
|
||||
if (commentVotesQuery.data?.userVote?.value === value) {
|
||||
return commentVotesDeleteMutation.mutate(
|
||||
{
|
||||
commentId,
|
||||
},
|
||||
{
|
||||
onSettled: async () => setAnimation(false),
|
||||
},
|
||||
);
|
||||
}
|
||||
return commentVotesUpsertMutation.mutate(
|
||||
{
|
||||
commentId,
|
||||
value,
|
||||
},
|
||||
{
|
||||
onSettled: async () => setAnimation(false),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
disabled={
|
||||
!userId ||
|
||||
commentVotesQuery.isLoading ||
|
||||
commentVotesUpsertMutation.isLoading ||
|
||||
commentVotesDeleteMutation.isLoading
|
||||
}
|
||||
type="button"
|
||||
onClick={() => onVote(Vote.UPVOTE, setUpvoteAnimation)}>
|
||||
<ArrowUpCircleIcon
|
||||
className={clsx(
|
||||
'h-4 w-4',
|
||||
commentVotesQuery.data?.userVote?.value === Vote.UPVOTE ||
|
||||
upvoteAnimation
|
||||
? 'fill-indigo-500'
|
||||
: 'fill-gray-400',
|
||||
userId &&
|
||||
!downvoteAnimation &&
|
||||
!upvoteAnimation &&
|
||||
'hover:fill-indigo-500',
|
||||
upvoteAnimation && 'animate-[bounce_0.5s_infinite] cursor-default',
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
|
||||
<div className="flex min-w-[1rem] justify-center text-xs">
|
||||
{commentVotesQuery.data?.numVotes ?? 0}
|
||||
</div>
|
||||
|
||||
<button
|
||||
disabled={
|
||||
!userId ||
|
||||
commentVotesQuery.isLoading ||
|
||||
commentVotesUpsertMutation.isLoading ||
|
||||
commentVotesDeleteMutation.isLoading
|
||||
}
|
||||
type="button"
|
||||
onClick={() => onVote(Vote.DOWNVOTE, setDownvoteAnimation)}>
|
||||
<ArrowDownCircleIcon
|
||||
className={clsx(
|
||||
'h-4 w-4',
|
||||
commentVotesQuery.data?.userVote?.value === Vote.DOWNVOTE ||
|
||||
downvoteAnimation
|
||||
? 'fill-red-500'
|
||||
: 'fill-gray-400',
|
||||
userId &&
|
||||
!downvoteAnimation &&
|
||||
!upvoteAnimation &&
|
||||
'hover:fill-red-500',
|
||||
downvoteAnimation &&
|
||||
'animate-[bounce_0.5s_infinite] cursor-default',
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
import clsx from 'clsx';
|
||||
import Link from 'next/link';
|
||||
|
||||
const baseStyles = {
|
||||
outline:
|
||||
'group inline-flex ring-1 items-center justify-center rounded-full py-2 px-4 text-sm focus:outline-none',
|
||||
solid:
|
||||
'group inline-flex items-center justify-center rounded-full py-2 px-4 text-sm font-semibold focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2',
|
||||
};
|
||||
|
||||
const variantStyles = {
|
||||
outline: {
|
||||
slate:
|
||||
'ring-slate-200 text-slate-700 hover:text-slate-900 hover:ring-slate-300 active:bg-slate-100 active:text-slate-600 focus-visible:outline-blue-600 focus-visible:ring-slate-300',
|
||||
white:
|
||||
'ring-slate-700 text-white hover:ring-slate-500 active:ring-slate-700 active:text-slate-400 focus-visible:outline-white',
|
||||
},
|
||||
solid: {
|
||||
blue: 'bg-blue-600 text-white hover:text-slate-100 hover:bg-blue-500 active:bg-blue-800 active:text-blue-100 focus-visible:outline-blue-600',
|
||||
slate:
|
||||
'bg-slate-900 text-white hover:bg-slate-700 hover:text-slate-100 active:bg-slate-800 active:text-slate-300 focus-visible:outline-slate-900',
|
||||
white:
|
||||
'bg-white text-slate-900 hover:bg-blue-50 active:bg-blue-200 active:text-slate-600 focus-visible:outline-white',
|
||||
},
|
||||
};
|
||||
|
||||
export function Button({
|
||||
variant = 'solid',
|
||||
color = 'slate',
|
||||
className,
|
||||
href,
|
||||
...props
|
||||
}) {
|
||||
className = clsx(
|
||||
baseStyles[variant],
|
||||
variantStyles[variant][color],
|
||||
className,
|
||||
);
|
||||
|
||||
return href ? (
|
||||
<Link className={className} href={href} {...props} />
|
||||
) : (
|
||||
<button className={className} type="button" {...props} />
|
||||
);
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
import Image from 'next/future/image';
|
||||
|
||||
import { Button } from './Button';
|
||||
import { Container } from './Container';
|
||||
import backgroundImage from './images/background-call-to-action.jpg';
|
||||
|
||||
export function CallToAction() {
|
||||
return (
|
||||
<section
|
||||
className="relative overflow-hidden bg-blue-600 py-32"
|
||||
id="get-started-today">
|
||||
<Image
|
||||
alt=""
|
||||
className="absolute top-1/2 left-1/2 max-w-none -translate-x-1/2 -translate-y-1/2"
|
||||
height={1244}
|
||||
src={backgroundImage}
|
||||
unoptimized={true}
|
||||
width={2347}
|
||||
/>
|
||||
<Container className="relative">
|
||||
<div className="mx-auto max-w-lg text-center">
|
||||
<h2 className="font-display text-3xl tracking-tight text-white sm:text-4xl">
|
||||
Resume review can start right now.
|
||||
</h2>
|
||||
<p className="mt-4 text-lg tracking-tight text-white">
|
||||
It's free! Take charge of your resume game by learning from the top
|
||||
engineers in the field.
|
||||
</p>
|
||||
<Button className="mt-10" color="white" href="/resumes/browse">
|
||||
Start browsing now
|
||||
</Button>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import { Container } from './Container';
|
||||
|
||||
export function CallToAction() {
|
||||
return (
|
||||
<section className="relative overflow-hidden py-32" id="get-started-today">
|
||||
<Container className="relative">
|
||||
<div className="mx-auto max-w-lg text-center">
|
||||
<h2 className="font-display text-3xl tracking-tight text-gray-900 sm:text-4xl">
|
||||
Resume review can start right now.
|
||||
</h2>
|
||||
<p className="mt-4 text-lg tracking-tight text-gray-600">
|
||||
It's free! Take charge of your resume game by learning from the top
|
||||
engineers in the field.
|
||||
</p>
|
||||
<Link href="/resumes/browse">
|
||||
<button
|
||||
className="mt-4 rounded-md bg-indigo-500 py-2 px-3 text-sm font-medium text-white"
|
||||
type="button">
|
||||
Start browsing now
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import clsx from 'clsx'
|
||||
|
||||
export function Container({ className, ...props }) {
|
||||
return (
|
||||
<div
|
||||
className={clsx('mx-auto max-w-7xl px-4 sm:px-6 lg:px-8', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import clsx from 'clsx';
|
||||
import type { FC } from 'react';
|
||||
|
||||
type ContainerProps = {
|
||||
children: Array<JSX.Element> | JSX.Element;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const Container: FC<ContainerProps> = ({ className, ...props }) => {
|
||||
return (
|
||||
<div
|
||||
className={clsx('mx-auto max-w-7xl px-4 lg:px-2', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,50 +0,0 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import { Container } from './Container';
|
||||
import { Logo } from './Logo';
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="bg-slate-50">
|
||||
<Container>
|
||||
<div className="py-16">
|
||||
<Logo className="mx-auto h-10 w-auto" />
|
||||
<nav aria-label="quick links" className="mt-10 text-sm">
|
||||
<div className="-my-1 flex justify-center gap-x-6">
|
||||
<Link href="#features">Features</Link>
|
||||
<Link href="#testimonials">Testimonials</Link>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="flex flex-col items-center border-t border-slate-400/10 py-10 sm:flex-row-reverse sm:justify-between">
|
||||
<div className="flex gap-x-6">
|
||||
<Link
|
||||
aria-label="TaxPal on Twitter"
|
||||
className="group"
|
||||
href="https://twitter.com">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="h-6 w-6 fill-slate-500 group-hover:fill-slate-700">
|
||||
<path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0 0 22 5.92a8.19 8.19 0 0 1-2.357.646 4.118 4.118 0 0 0 1.804-2.27 8.224 8.224 0 0 1-2.605.996 4.107 4.107 0 0 0-6.993 3.743 11.65 11.65 0 0 1-8.457-4.287 4.106 4.106 0 0 0 1.27 5.477A4.073 4.073 0 0 1 2.8 9.713v.052a4.105 4.105 0 0 0 3.292 4.022 4.093 4.093 0 0 1-1.853.07 4.108 4.108 0 0 0 3.834 2.85A8.233 8.233 0 0 1 2 18.407a11.615 11.615 0 0 0 6.29 1.84" />
|
||||
</svg>
|
||||
</Link>
|
||||
<Link
|
||||
aria-label="TaxPal on GitHub"
|
||||
className="group"
|
||||
href="https://github.com">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="h-6 w-6 fill-slate-500 group-hover:fill-slate-700">
|
||||
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2Z" />
|
||||
</svg>
|
||||
</Link>
|
||||
</div>
|
||||
<p className="mt-6 text-sm text-slate-500 sm:mt-0">
|
||||
Copyright © {new Date().getFullYear()} Resume Review. All
|
||||
rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</Container>
|
||||
</footer>
|
||||
);
|
||||
}
|
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 162 KiB |
Before Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 181 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 967 B |
Before Width: | Height: | Size: 181 KiB |
Before Width: | Height: | Size: 179 KiB |
Before Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 174 KiB |
Before Width: | Height: | Size: 93 KiB |
Before Width: | Height: | Size: 186 KiB |
After Width: | Height: | Size: 652 KiB |
After Width: | Height: | Size: 1006 KiB |
After Width: | Height: | Size: 437 KiB |
Before Width: | Height: | Size: 149 KiB |
@ -1,48 +1,46 @@
|
||||
import clsx from 'clsx';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useLayoutEffect, useRef, useState } from 'react';
|
||||
|
||||
type ResumeExpandableTextProps = Readonly<{
|
||||
children: ReactNode;
|
||||
text: string;
|
||||
}>;
|
||||
|
||||
export default function ResumeExpandableText({
|
||||
children,
|
||||
text,
|
||||
}: ResumeExpandableTextProps) {
|
||||
const ref = useRef<HTMLSpanElement>(null);
|
||||
const [descriptionExpanded, setDescriptionExpanded] = useState(false);
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const [descriptionOverflow, setDescriptionOverflow] = useState(false);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (ref.current && ref.current.clientHeight < ref.current.scrollHeight) {
|
||||
setDescriptionOverflow(true);
|
||||
} else {
|
||||
setDescriptionOverflow(false);
|
||||
}
|
||||
}, [ref]);
|
||||
|
||||
const onSeeActionClicked = () => {
|
||||
setDescriptionExpanded(!descriptionExpanded);
|
||||
setIsExpanded((prevExpanded) => !prevExpanded);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<span
|
||||
ref={ref}
|
||||
className={clsx(
|
||||
'whitespace-pre-wrap text-sm',
|
||||
'line-clamp-3',
|
||||
descriptionExpanded ? 'line-clamp-none' : '',
|
||||
'line-clamp-3 whitespace-pre-wrap text-sm',
|
||||
isExpanded ? 'line-clamp-none' : '',
|
||||
)}>
|
||||
{children}
|
||||
{text}
|
||||
</span>
|
||||
{descriptionOverflow && (
|
||||
<div className="flex flex-row">
|
||||
<div
|
||||
className="text-xs text-indigo-500 hover:text-indigo-300"
|
||||
<p
|
||||
className="mt-1 cursor-pointer text-xs text-indigo-500 hover:text-indigo-300"
|
||||
onClick={onSeeActionClicked}>
|
||||
{descriptionExpanded ? 'See Less' : 'See More'}
|
||||
</div>
|
||||
</div>
|
||||
{isExpanded ? 'See Less' : 'See More'}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
export default function SubmissionGuidelines() {
|
||||
return (
|
||||
<div className="mb-4 text-left text-sm text-slate-700">
|
||||
<h2 className="mb-2 text-xl font-medium">Submission Guidelines</h2>
|
||||
<p>
|
||||
Before you submit, please review and acknolwedge our
|
||||
<span className="font-bold"> submission guidelines </span>
|
||||
stated below.
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-lg font-bold">• </span>
|
||||
Ensure that you do not divulge any of your
|
||||
<span className="font-bold"> personal particulars</span>.
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-lg font-bold">• </span>
|
||||
Ensure that you do not divulge any
|
||||
<span className="font-bold">
|
||||
{' '}
|
||||
company's proprietary and confidential information
|
||||
</span>
|
||||
.
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-lg font-bold">• </span>
|
||||
Proof-read your resumes to look for grammatical/spelling errors.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,628 @@
|
||||
import type {
|
||||
Company,
|
||||
OffersAnalysis,
|
||||
OffersBackground,
|
||||
OffersCurrency,
|
||||
OffersEducation,
|
||||
OffersExperience,
|
||||
OffersFullTime,
|
||||
OffersIntern,
|
||||
OffersOffer,
|
||||
OffersProfile,
|
||||
OffersReply,
|
||||
OffersSpecificYoe,
|
||||
User,
|
||||
} from '@prisma/client';
|
||||
import { JobType } from '@prisma/client';
|
||||
import { TRPCError } from '@trpc/server';
|
||||
|
||||
import type {
|
||||
AddToProfileResponse,
|
||||
Analysis,
|
||||
AnalysisHighestOffer,
|
||||
AnalysisOffer,
|
||||
Background,
|
||||
CreateOfferProfileResponse,
|
||||
DashboardOffer,
|
||||
Education,
|
||||
Experience,
|
||||
GetOffersResponse,
|
||||
OffersCompany,
|
||||
Paging,
|
||||
Profile,
|
||||
ProfileAnalysis,
|
||||
ProfileOffer,
|
||||
SpecificYoe,
|
||||
Valuation,
|
||||
} from '~/types/offers';
|
||||
|
||||
const analysisOfferDtoMapper = (
|
||||
offer: OffersOffer & {
|
||||
company: Company;
|
||||
offersFullTime:
|
||||
| (OffersFullTime & { totalCompensation: OffersCurrency })
|
||||
| null;
|
||||
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
|
||||
profile: OffersProfile & {
|
||||
background:
|
||||
| (OffersBackground & {
|
||||
experiences: Array<OffersExperience & { company: Company | null }>;
|
||||
})
|
||||
| null;
|
||||
};
|
||||
},
|
||||
) => {
|
||||
const { background, profileName } = offer.profile;
|
||||
const analysisOfferDto: AnalysisOffer = {
|
||||
company: offersCompanyDtoMapper(offer.company),
|
||||
id: offer.id,
|
||||
income: { baseCurrency: '', baseValue: -1, currency: '', value: -1 },
|
||||
jobType: offer.jobType,
|
||||
level: offer.offersFullTime?.level ?? '',
|
||||
location: offer.location,
|
||||
monthYearReceived: offer.monthYearReceived,
|
||||
negotiationStrategy: offer.negotiationStrategy,
|
||||
previousCompanies:
|
||||
background?.experiences
|
||||
?.filter((exp) => exp.company != null)
|
||||
.map((exp) => exp.company?.name ?? '') ?? [],
|
||||
profileName,
|
||||
specialization:
|
||||
offer.jobType === JobType.FULLTIME
|
||||
? offer.offersFullTime?.specialization ?? ''
|
||||
: offer.offersIntern?.specialization ?? '',
|
||||
title:
|
||||
offer.jobType === JobType.FULLTIME
|
||||
? offer.offersFullTime?.title ?? ''
|
||||
: offer.offersIntern?.title ?? '',
|
||||
totalYoe: background?.totalYoe ?? -1,
|
||||
};
|
||||
|
||||
if (offer.offersFullTime?.totalCompensation) {
|
||||
analysisOfferDto.income.value =
|
||||
offer.offersFullTime.totalCompensation.value;
|
||||
analysisOfferDto.income.currency =
|
||||
offer.offersFullTime.totalCompensation.currency;
|
||||
analysisOfferDto.income.baseValue =
|
||||
offer.offersFullTime.totalCompensation.baseValue;
|
||||
analysisOfferDto.income.baseCurrency =
|
||||
offer.offersFullTime.totalCompensation.baseCurrency;
|
||||
} else if (offer.offersIntern?.monthlySalary) {
|
||||
analysisOfferDto.income.value = offer.offersIntern.monthlySalary.value;
|
||||
analysisOfferDto.income.currency =
|
||||
offer.offersIntern.monthlySalary.currency;
|
||||
analysisOfferDto.income.baseValue =
|
||||
offer.offersIntern.monthlySalary.baseValue;
|
||||
analysisOfferDto.income.baseCurrency =
|
||||
offer.offersIntern.monthlySalary.baseCurrency;
|
||||
} else {
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
message: 'Total Compensation or Salary not found',
|
||||
});
|
||||
}
|
||||
|
||||
return analysisOfferDto;
|
||||
};
|
||||
|
||||
const analysisDtoMapper = (
|
||||
noOfOffers: number,
|
||||
percentile: number,
|
||||
topPercentileOffers: Array<
|
||||
OffersOffer & {
|
||||
company: Company;
|
||||
offersFullTime:
|
||||
| (OffersFullTime & {
|
||||
totalCompensation: OffersCurrency;
|
||||
})
|
||||
| null;
|
||||
offersIntern:
|
||||
| (OffersIntern & {
|
||||
monthlySalary: OffersCurrency;
|
||||
})
|
||||
| null;
|
||||
profile: OffersProfile & {
|
||||
background:
|
||||
| (OffersBackground & {
|
||||
experiences: Array<
|
||||
OffersExperience & {
|
||||
company: Company | null;
|
||||
}
|
||||
>;
|
||||
})
|
||||
| null;
|
||||
};
|
||||
}
|
||||
>,
|
||||
) => {
|
||||
const analysisDto: Analysis = {
|
||||
noOfOffers,
|
||||
percentile,
|
||||
topPercentileOffers: topPercentileOffers.map((offer) =>
|
||||
analysisOfferDtoMapper(offer),
|
||||
),
|
||||
};
|
||||
return analysisDto;
|
||||
};
|
||||
|
||||
const analysisHighestOfferDtoMapper = (
|
||||
offer: OffersOffer & {
|
||||
company: Company;
|
||||
offersFullTime:
|
||||
| (OffersFullTime & { totalCompensation: OffersCurrency })
|
||||
| null;
|
||||
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
|
||||
profile: OffersProfile & { background: OffersBackground | null };
|
||||
},
|
||||
) => {
|
||||
const analysisHighestOfferDto: AnalysisHighestOffer = {
|
||||
company: offersCompanyDtoMapper(offer.company),
|
||||
id: offer.id,
|
||||
level: offer.offersFullTime?.level ?? '',
|
||||
location: offer.location,
|
||||
specialization:
|
||||
offer.jobType === JobType.FULLTIME
|
||||
? offer.offersFullTime?.specialization ?? ''
|
||||
: offer.offersIntern?.specialization ?? '',
|
||||
totalYoe: offer.profile.background?.totalYoe ?? -1,
|
||||
};
|
||||
return analysisHighestOfferDto;
|
||||
};
|
||||
|
||||
export const profileAnalysisDtoMapper = (
|
||||
analysis:
|
||||
| (OffersAnalysis & {
|
||||
overallHighestOffer: OffersOffer & {
|
||||
company: Company;
|
||||
offersFullTime:
|
||||
| (OffersFullTime & { totalCompensation: OffersCurrency })
|
||||
| null;
|
||||
offersIntern:
|
||||
| (OffersIntern & { monthlySalary: OffersCurrency })
|
||||
| null;
|
||||
profile: OffersProfile & { background: OffersBackground | null };
|
||||
};
|
||||
topCompanyOffers: Array<
|
||||
OffersOffer & {
|
||||
company: Company;
|
||||
offersFullTime:
|
||||
| (OffersFullTime & { totalCompensation: OffersCurrency })
|
||||
| null;
|
||||
offersIntern:
|
||||
| (OffersIntern & { monthlySalary: OffersCurrency })
|
||||
| null;
|
||||
profile: OffersProfile & {
|
||||
background:
|
||||
| (OffersBackground & {
|
||||
experiences: Array<
|
||||
OffersExperience & { company: Company | null }
|
||||
>;
|
||||
})
|
||||
| null;
|
||||
};
|
||||
}
|
||||
>;
|
||||
topOverallOffers: Array<
|
||||
OffersOffer & {
|
||||
company: Company;
|
||||
offersFullTime:
|
||||
| (OffersFullTime & { totalCompensation: OffersCurrency })
|
||||
| null;
|
||||
offersIntern:
|
||||
| (OffersIntern & { monthlySalary: OffersCurrency })
|
||||
| null;
|
||||
profile: OffersProfile & {
|
||||
background:
|
||||
| (OffersBackground & {
|
||||
experiences: Array<
|
||||
OffersExperience & { company: Company | null }
|
||||
>;
|
||||
})
|
||||
| null;
|
||||
};
|
||||
}
|
||||
>;
|
||||
})
|
||||
| null,
|
||||
) => {
|
||||
if (!analysis) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const profileAnalysisDto: ProfileAnalysis = {
|
||||
companyAnalysis: [
|
||||
analysisDtoMapper(
|
||||
analysis.noOfSimilarCompanyOffers,
|
||||
analysis.companyPercentile,
|
||||
analysis.topCompanyOffers,
|
||||
),
|
||||
],
|
||||
id: analysis.id,
|
||||
overallAnalysis: analysisDtoMapper(
|
||||
analysis.noOfSimilarOffers,
|
||||
analysis.overallPercentile,
|
||||
analysis.topOverallOffers,
|
||||
),
|
||||
overallHighestOffer: analysisHighestOfferDtoMapper(
|
||||
analysis.overallHighestOffer,
|
||||
),
|
||||
profileId: analysis.profileId,
|
||||
};
|
||||
return profileAnalysisDto;
|
||||
};
|
||||
|
||||
export const valuationDtoMapper = (currency: {
|
||||
baseCurrency: string;
|
||||
baseValue: number;
|
||||
currency: string;
|
||||
id?: string;
|
||||
value: number;
|
||||
}) => {
|
||||
const valuationDto: Valuation = {
|
||||
baseCurrency: currency.baseCurrency,
|
||||
baseValue: currency.baseValue,
|
||||
currency: currency.currency,
|
||||
value: currency.value,
|
||||
};
|
||||
return valuationDto;
|
||||
};
|
||||
|
||||
export const offersCompanyDtoMapper = (company: Company) => {
|
||||
const companyDto: OffersCompany = {
|
||||
createdAt: company.createdAt,
|
||||
description: company?.description ?? '',
|
||||
id: company.id,
|
||||
logoUrl: company.logoUrl ?? '',
|
||||
name: company.name,
|
||||
slug: company.slug,
|
||||
updatedAt: company.updatedAt,
|
||||
};
|
||||
return companyDto;
|
||||
};
|
||||
|
||||
export const educationDtoMapper = (education: {
|
||||
backgroundId?: string;
|
||||
endDate: Date | null;
|
||||
field: string | null;
|
||||
id: string;
|
||||
school: string | null;
|
||||
startDate: Date | null;
|
||||
type: string | null;
|
||||
}) => {
|
||||
const educationDto: Education = {
|
||||
endDate: education.endDate,
|
||||
field: education.field,
|
||||
id: education.id,
|
||||
school: education.school,
|
||||
startDate: education.startDate,
|
||||
type: education.type,
|
||||
};
|
||||
return educationDto;
|
||||
};
|
||||
|
||||
export const experienceDtoMapper = (
|
||||
experience: OffersExperience & {
|
||||
company: Company | null;
|
||||
monthlySalary: OffersCurrency | null;
|
||||
totalCompensation: OffersCurrency | null;
|
||||
},
|
||||
) => {
|
||||
const experienceDto: Experience = {
|
||||
company: experience.company
|
||||
? offersCompanyDtoMapper(experience.company)
|
||||
: null,
|
||||
durationInMonths: experience.durationInMonths,
|
||||
id: experience.id,
|
||||
jobType: experience.jobType,
|
||||
level: experience.level,
|
||||
location: experience.location,
|
||||
monthlySalary: experience.monthlySalary
|
||||
? valuationDtoMapper(experience.monthlySalary)
|
||||
: experience.monthlySalary,
|
||||
specialization: experience.specialization,
|
||||
title: experience.title,
|
||||
totalCompensation: experience.totalCompensation
|
||||
? valuationDtoMapper(experience.totalCompensation)
|
||||
: experience.totalCompensation,
|
||||
};
|
||||
return experienceDto;
|
||||
};
|
||||
|
||||
export const specificYoeDtoMapper = (specificYoe: {
|
||||
backgroundId?: string;
|
||||
domain: string;
|
||||
id: string;
|
||||
yoe: number;
|
||||
}) => {
|
||||
const specificYoeDto: SpecificYoe = {
|
||||
domain: specificYoe.domain,
|
||||
id: specificYoe.id,
|
||||
yoe: specificYoe.yoe,
|
||||
};
|
||||
return specificYoeDto;
|
||||
};
|
||||
|
||||
export const backgroundDtoMapper = (
|
||||
background:
|
||||
| (OffersBackground & {
|
||||
educations: Array<OffersEducation>;
|
||||
experiences: Array<
|
||||
OffersExperience & {
|
||||
company: Company | null;
|
||||
monthlySalary: OffersCurrency | null;
|
||||
totalCompensation: OffersCurrency | null;
|
||||
}
|
||||
>;
|
||||
specificYoes: Array<OffersSpecificYoe>;
|
||||
})
|
||||
| null,
|
||||
) => {
|
||||
if (!background) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const educations = background.educations.map((education) =>
|
||||
educationDtoMapper(education),
|
||||
);
|
||||
|
||||
const experiences = background.experiences.map((experience) =>
|
||||
experienceDtoMapper(experience),
|
||||
);
|
||||
|
||||
const specificYoes = background.specificYoes.map((specificYoe) =>
|
||||
specificYoeDtoMapper(specificYoe),
|
||||
);
|
||||
|
||||
const backgroundDto: Background = {
|
||||
educations,
|
||||
experiences,
|
||||
id: background.id,
|
||||
specificYoes,
|
||||
totalYoe: background.totalYoe,
|
||||
};
|
||||
|
||||
return backgroundDto;
|
||||
};
|
||||
|
||||
export const profileOfferDtoMapper = (
|
||||
offer: OffersOffer & {
|
||||
company: Company;
|
||||
offersFullTime:
|
||||
| (OffersFullTime & {
|
||||
baseSalary: OffersCurrency;
|
||||
bonus: OffersCurrency;
|
||||
stocks: OffersCurrency;
|
||||
totalCompensation: OffersCurrency;
|
||||
})
|
||||
| null;
|
||||
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
|
||||
},
|
||||
) => {
|
||||
const profileOfferDto: ProfileOffer = {
|
||||
comments: offer.comments,
|
||||
company: offersCompanyDtoMapper(offer.company),
|
||||
id: offer.id,
|
||||
jobType: offer.jobType,
|
||||
location: offer.location,
|
||||
monthYearReceived: offer.monthYearReceived,
|
||||
negotiationStrategy: offer.negotiationStrategy,
|
||||
offersFullTime: offer.offersFullTime,
|
||||
offersIntern: offer.offersIntern,
|
||||
};
|
||||
|
||||
if (offer.offersFullTime) {
|
||||
profileOfferDto.offersFullTime = {
|
||||
baseSalary: valuationDtoMapper(offer.offersFullTime.baseSalary),
|
||||
bonus: valuationDtoMapper(offer.offersFullTime.bonus),
|
||||
id: offer.offersFullTime.id,
|
||||
level: offer.offersFullTime.level,
|
||||
specialization: offer.offersFullTime.specialization,
|
||||
stocks: valuationDtoMapper(offer.offersFullTime.stocks),
|
||||
title: offer.offersFullTime.title,
|
||||
totalCompensation: valuationDtoMapper(
|
||||
offer.offersFullTime.totalCompensation,
|
||||
),
|
||||
};
|
||||
} else if (offer.offersIntern) {
|
||||
profileOfferDto.offersIntern = {
|
||||
id: offer.offersIntern.id,
|
||||
internshipCycle: offer.offersIntern.internshipCycle,
|
||||
monthlySalary: valuationDtoMapper(offer.offersIntern.monthlySalary),
|
||||
specialization: offer.offersIntern.specialization,
|
||||
startYear: offer.offersIntern.startYear,
|
||||
title: offer.offersIntern.title,
|
||||
};
|
||||
}
|
||||
|
||||
return profileOfferDto;
|
||||
};
|
||||
|
||||
export const profileDtoMapper = (
|
||||
profile: OffersProfile & {
|
||||
analysis:
|
||||
| (OffersAnalysis & {
|
||||
overallHighestOffer: OffersOffer & {
|
||||
company: Company;
|
||||
offersFullTime:
|
||||
| (OffersFullTime & { totalCompensation: OffersCurrency })
|
||||
| null;
|
||||
offersIntern:
|
||||
| (OffersIntern & { monthlySalary: OffersCurrency })
|
||||
| null;
|
||||
profile: OffersProfile & { background: OffersBackground | null };
|
||||
};
|
||||
topCompanyOffers: Array<
|
||||
OffersOffer & {
|
||||
company: Company;
|
||||
offersFullTime:
|
||||
| (OffersFullTime & { totalCompensation: OffersCurrency })
|
||||
| null;
|
||||
offersIntern:
|
||||
| (OffersIntern & { monthlySalary: OffersCurrency })
|
||||
| null;
|
||||
profile: OffersProfile & {
|
||||
background:
|
||||
| (OffersBackground & {
|
||||
experiences: Array<
|
||||
OffersExperience & { company: Company | null }
|
||||
>;
|
||||
})
|
||||
| null;
|
||||
};
|
||||
}
|
||||
>;
|
||||
topOverallOffers: Array<
|
||||
OffersOffer & {
|
||||
company: Company;
|
||||
offersFullTime:
|
||||
| (OffersFullTime & { totalCompensation: OffersCurrency })
|
||||
| null;
|
||||
offersIntern:
|
||||
| (OffersIntern & { monthlySalary: OffersCurrency })
|
||||
| null;
|
||||
profile: OffersProfile & {
|
||||
background:
|
||||
| (OffersBackground & {
|
||||
experiences: Array<
|
||||
OffersExperience & { company: Company | null }
|
||||
>;
|
||||
})
|
||||
| null;
|
||||
};
|
||||
}
|
||||
>;
|
||||
})
|
||||
| null;
|
||||
background:
|
||||
| (OffersBackground & {
|
||||
educations: Array<OffersEducation>;
|
||||
experiences: Array<
|
||||
OffersExperience & {
|
||||
company: Company | null;
|
||||
monthlySalary: OffersCurrency | null;
|
||||
totalCompensation: OffersCurrency | null;
|
||||
}
|
||||
>;
|
||||
specificYoes: Array<OffersSpecificYoe>;
|
||||
})
|
||||
| null;
|
||||
discussion: Array<
|
||||
OffersReply & {
|
||||
replies: Array<OffersReply>;
|
||||
replyingTo: OffersReply | null;
|
||||
user: User | null;
|
||||
}
|
||||
>;
|
||||
offers: Array<
|
||||
OffersOffer & {
|
||||
company: Company;
|
||||
offersFullTime:
|
||||
| (OffersFullTime & {
|
||||
baseSalary: OffersCurrency;
|
||||
bonus: OffersCurrency;
|
||||
stocks: OffersCurrency;
|
||||
totalCompensation: OffersCurrency;
|
||||
})
|
||||
| null;
|
||||
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
|
||||
}
|
||||
>;
|
||||
},
|
||||
inputToken: string | undefined,
|
||||
) => {
|
||||
const profileDto: Profile = {
|
||||
analysis: profileAnalysisDtoMapper(profile.analysis),
|
||||
background: backgroundDtoMapper(profile.background),
|
||||
editToken: null,
|
||||
id: profile.id,
|
||||
isEditable: false,
|
||||
offers: profile.offers.map((offer) => profileOfferDtoMapper(offer)),
|
||||
profileName: profile.profileName,
|
||||
};
|
||||
|
||||
if (inputToken === profile.editToken) {
|
||||
profileDto.editToken = profile.editToken;
|
||||
profileDto.isEditable = true;
|
||||
}
|
||||
|
||||
return profileDto;
|
||||
};
|
||||
|
||||
export const createOfferProfileResponseMapper = (
|
||||
profile: { id: string },
|
||||
token: string,
|
||||
) => {
|
||||
const res: CreateOfferProfileResponse = {
|
||||
id: profile.id,
|
||||
token,
|
||||
};
|
||||
return res;
|
||||
};
|
||||
|
||||
export const addToProfileResponseMapper = (updatedProfile: {
|
||||
id: string;
|
||||
profileName: string;
|
||||
userId?: string | null;
|
||||
}) => {
|
||||
const addToProfileResponse: AddToProfileResponse = {
|
||||
id: updatedProfile.id,
|
||||
profileName: updatedProfile.profileName,
|
||||
userId: updatedProfile.userId ?? '',
|
||||
};
|
||||
|
||||
return addToProfileResponse;
|
||||
};
|
||||
|
||||
export const dashboardOfferDtoMapper = (
|
||||
offer: OffersOffer & {
|
||||
company: Company;
|
||||
offersFullTime:
|
||||
| (OffersFullTime & {
|
||||
baseSalary: OffersCurrency;
|
||||
bonus: OffersCurrency;
|
||||
stocks: OffersCurrency;
|
||||
totalCompensation: OffersCurrency;
|
||||
})
|
||||
| null;
|
||||
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
|
||||
profile: OffersProfile & { background: OffersBackground | null };
|
||||
},
|
||||
) => {
|
||||
const dashboardOfferDto: DashboardOffer = {
|
||||
company: offersCompanyDtoMapper(offer.company),
|
||||
id: offer.id,
|
||||
income: valuationDtoMapper({
|
||||
baseCurrency: '',
|
||||
baseValue: -1,
|
||||
currency: '',
|
||||
value: -1,
|
||||
}),
|
||||
monthYearReceived: offer.monthYearReceived,
|
||||
profileId: offer.profileId,
|
||||
title: offer.offersFullTime?.title ?? '',
|
||||
totalYoe: offer.profile.background?.totalYoe ?? -1,
|
||||
};
|
||||
|
||||
if (offer.offersFullTime) {
|
||||
dashboardOfferDto.income = valuationDtoMapper(
|
||||
offer.offersFullTime.totalCompensation,
|
||||
);
|
||||
} else if (offer.offersIntern) {
|
||||
dashboardOfferDto.income = valuationDtoMapper(
|
||||
offer.offersIntern.monthlySalary,
|
||||
);
|
||||
}
|
||||
|
||||
return dashboardOfferDto;
|
||||
};
|
||||
|
||||
export const getOffersResponseMapper = (
|
||||
data: Array<DashboardOffer>,
|
||||
paging: Paging,
|
||||
) => {
|
||||
const getOffersResponse: GetOffersResponse = {
|
||||
data,
|
||||
paging,
|
||||
};
|
||||
return getOffersResponse;
|
||||
};
|