Merge branch 'main' into stuart/seed-db

* main:
  [offers][chore] Speed up fetching of dashboard offers
  [resumes][feat] center text in file drop
  Revert "[questions][feat] add text search (#412)" (#428)
  [resumes][feat] mobile responsiveness for form
  [resumes][feat] shift badge to bottom
  [offers][fix] Fix offers form experience section (#427)
  [questions][feat] add text search (#412)
  [questions][feat] add list crud (#393)
  [ui][text input] change pl to px for startAddOn
  [offers][fix] Fix UI and remove specialization on the backend (#426)
  [resumes][feat] improve badge UI
  [resumes][feat] add shadow to buttons

# Conflicts:
#	apps/portal/src/server/router/offers/offers-analysis-router.ts
pull/501/head^2
Bryann Yeap Kok Keong 3 years ago
commit bd7a034cbc

@ -0,0 +1,36 @@
-- CreateTable
CREATE TABLE "QuestionsList" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"name" VARCHAR(256) NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "QuestionsList_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "QuestionsListQuestionEntry" (
"id" TEXT NOT NULL,
"listId" TEXT NOT NULL,
"questionId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "QuestionsListQuestionEntry_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "QuestionsList_userId_name_key" ON "QuestionsList"("userId", "name");
-- CreateIndex
CREATE UNIQUE INDEX "QuestionsListQuestionEntry_listId_questionId_key" ON "QuestionsListQuestionEntry"("listId", "questionId");
-- AddForeignKey
ALTER TABLE "QuestionsList" ADD CONSTRAINT "QuestionsList_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "QuestionsListQuestionEntry" ADD CONSTRAINT "QuestionsListQuestionEntry_listId_fkey" FOREIGN KEY ("listId") REFERENCES "QuestionsList"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "QuestionsListQuestionEntry" ADD CONSTRAINT "QuestionsListQuestionEntry_questionId_fkey" FOREIGN KEY ("questionId") REFERENCES "QuestionsQuestion"("id") ON DELETE CASCADE ON UPDATE CASCADE;

@ -0,0 +1,19 @@
/*
Warnings:
- You are about to drop the column `specialization` on the `OffersExperience` table. All the data in the column will be lost.
- You are about to drop the column `specialization` on the `OffersFullTime` table. All the data in the column will be lost.
- You are about to drop the column `specialization` on the `OffersIntern` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "OffersExperience" DROP COLUMN "specialization";
-- AlterTable
ALTER TABLE "OffersFullTime" DROP COLUMN "specialization",
ALTER COLUMN "baseSalaryId" DROP NOT NULL,
ALTER COLUMN "bonusId" DROP NOT NULL,
ALTER COLUMN "stocksId" DROP NOT NULL;
-- AlterTable
ALTER TABLE "OffersIntern" DROP COLUMN "specialization";

@ -60,6 +60,7 @@ model User {
questionsAnswerCommentVotes QuestionsAnswerCommentVote[] questionsAnswerCommentVotes QuestionsAnswerCommentVote[]
OffersProfile OffersProfile[] OffersProfile OffersProfile[]
offersDiscussion OffersReply[] offersDiscussion OffersReply[]
questionsLists QuestionsList[]
} }
enum Vote { enum Vote {
@ -234,7 +235,6 @@ model OffersExperience {
// Add more fields // Add more fields
durationInMonths Int? durationInMonths Int?
specialization String?
location String? location String?
// FULLTIME fields // FULLTIME fields
@ -340,7 +340,6 @@ model OffersIntern {
id String @id @default(cuid()) id String @id @default(cuid())
title String title String
specialization String
internshipCycle String internshipCycle String
startYear Int startYear Int
monthlySalary OffersCurrency @relation(fields: [monthlySalaryId], references: [id], onDelete: Cascade) monthlySalary OffersCurrency @relation(fields: [monthlySalaryId], references: [id], onDelete: Cascade)
@ -352,16 +351,15 @@ model OffersIntern {
model OffersFullTime { model OffersFullTime {
id String @id @default(cuid()) id String @id @default(cuid())
title String title String
specialization String
level String level String
totalCompensation OffersCurrency @relation("OfferTotalCompensation", fields: [totalCompensationId], references: [id], onDelete: Cascade) totalCompensation OffersCurrency @relation("OfferTotalCompensation", fields: [totalCompensationId], references: [id], onDelete: Cascade)
totalCompensationId String @unique totalCompensationId String @unique
baseSalary OffersCurrency @relation("OfferBaseSalary", fields: [baseSalaryId], references: [id], onDelete: Cascade) baseSalary OffersCurrency? @relation("OfferBaseSalary", fields: [baseSalaryId], references: [id], onDelete: Cascade)
baseSalaryId String @unique baseSalaryId String? @unique
bonus OffersCurrency @relation("OfferBonus", fields: [bonusId], references: [id], onDelete: Cascade) bonus OffersCurrency? @relation("OfferBonus", fields: [bonusId], references: [id], onDelete: Cascade)
bonusId String @unique bonusId String? @unique
stocks OffersCurrency @relation("OfferStocks", fields: [stocksId], references: [id], onDelete: Cascade) stocks OffersCurrency? @relation("OfferStocks", fields: [stocksId], references: [id], onDelete: Cascade)
stocksId String @unique stocksId String? @unique
OffersOffer OffersOffer? OffersOffer OffersOffer?
} }
@ -414,6 +412,7 @@ model QuestionsQuestion {
votes QuestionsQuestionVote[] votes QuestionsQuestionVote[]
comments QuestionsQuestionComment[] comments QuestionsQuestionComment[]
answers QuestionsAnswer[] answers QuestionsAnswer[]
QuestionsListQuestionEntry QuestionsListQuestionEntry[]
@@index([lastSeenAt, id]) @@index([lastSeenAt, id])
@@index([upvotes, id]) @@index([upvotes, id])
@ -535,4 +534,30 @@ model QuestionsAnswerCommentVote {
@@unique([answerCommentId, userId]) @@unique([answerCommentId, userId])
} }
model QuestionsList {
id String @id @default(cuid())
userId String
name String @db.VarChar(256)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
questionEntries QuestionsListQuestionEntry[]
@@unique([userId, name])
}
model QuestionsListQuestionEntry {
id String @id @default(cuid())
listId String
questionId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
list QuestionsList @relation(fields: [listId], references: [id], onDelete: Cascade)
question QuestionsQuestion @relation(fields: [questionId], references: [id], onDelete: Cascade)
@@unique([listId, questionId])
}
// End of Questions project models. // End of Questions project models.

@ -4,6 +4,9 @@ import {
} from '@heroicons/react/24/outline'; } from '@heroicons/react/24/outline';
import { JobType } from '@prisma/client'; import { JobType } from '@prisma/client';
import type { JobTitleType } from '~/components/shared/JobTitles';
import { getLabelForJobTitleType } from '~/components/shared/JobTitles';
import { HorizontalDivider } from '~/../../../packages/ui/dist'; import { HorizontalDivider } from '~/../../../packages/ui/dist';
import { convertMoneyToString } from '~/utils/offers/currency'; import { convertMoneyToString } from '~/utils/offers/currency';
import { formatDate } from '~/utils/offers/time'; import { formatDate } from '~/utils/offers/time';
@ -54,7 +57,9 @@ export default function OfferProfileCard({
<HorizontalDivider /> <HorizontalDivider />
<div className="flex items-end justify-between"> <div className="flex items-end justify-between">
<div className="col-span-1 row-span-3"> <div className="col-span-1 row-span-3">
<p className="font-bold">{title}</p> <p className="font-bold">
{getLabelForJobTitleType(title as JobTitleType)}
</p>
<p> <p>
Company: {company.name}, {location} Company: {company.name}, {location}
</p> </p>

@ -1,15 +1,9 @@
import { useRouter } from 'next/router';
// Import { useState } from 'react'; // Import { useState } from 'react';
// import { setTimeout } from 'timers'; // import { setTimeout } from 'timers';
import { DocumentDuplicateIcon } from '@heroicons/react/20/solid'; import { DocumentDuplicateIcon } from '@heroicons/react/20/solid';
import { EyeIcon } from '@heroicons/react/24/outline';
import { Button, TextInput, useToast } from '@tih/ui'; import { Button, TextInput, useToast } from '@tih/ui';
import { import { copyProfileLink, getProfileLink } from '~/utils/offers/link';
copyProfileLink,
getProfileLink,
getProfilePath,
} from '~/utils/offers/link';
type OfferProfileSaveProps = Readonly<{ type OfferProfileSaveProps = Readonly<{
profileId: string; profileId: string;
@ -23,7 +17,6 @@ export default function OffersProfileSave({
const { showToast } = useToast(); const { showToast } = useToast();
// Const [isSaving, setSaving] = useState(false); // Const [isSaving, setSaving] = useState(false);
// const [isSaved, setSaved] = useState(false); // const [isSaved, setSaved] = useState(false);
const router = useRouter();
// Const saveProfile = () => { // Const saveProfile = () => {
// setSaving(true); // setSaving(true);
@ -82,14 +75,6 @@ export default function OffersProfileSave({
onClick={saveProfile} onClick={saveProfile}
/> />
</div> */} </div> */}
<div>
<Button
icon={EyeIcon}
label="View your profile"
variant="special"
onClick={() => router.push(getProfilePath(profileId, token))}
/>
</div>
</div> </div>
</div> </div>
); );

@ -0,0 +1,49 @@
import { useRouter } from 'next/router';
import { EyeIcon } from '@heroicons/react/24/outline';
import { Button } from '~/../../../packages/ui/dist';
import { getProfilePath } from '~/utils/offers/link';
import OfferAnalysis from '../offerAnalysis/OfferAnalysis';
import type { ProfileAnalysis } from '~/types/offers';
type Props = Readonly<{
analysis?: ProfileAnalysis | null;
isError: boolean;
isLoading: boolean;
profileId?: string;
token?: string;
}>;
export default function OffersSubmissionAnalysis({
analysis,
isError,
isLoading,
profileId = '',
token = '',
}: Props) {
const router = useRouter();
return (
<div>
<h5 className="mb-8 text-center text-4xl font-bold text-slate-900">
Result
</h5>
<OfferAnalysis
key={3}
allAnalysis={analysis}
isError={isError}
isLoading={isLoading}
/>
<div className="mt-8 text-center">
<Button
icon={EyeIcon}
label="View your profile"
variant="special"
onClick={() => router.push(getProfilePath(profileId, token))}
/>
</div>
</div>
);
}

@ -15,16 +15,17 @@ import type {
} from '~/components/offers/types'; } from '~/components/offers/types';
import type { Month } from '~/components/shared/MonthYearPicker'; import type { Month } from '~/components/shared/MonthYearPicker';
import { cleanObject, removeInvalidMoneyData } from '~/utils/offers/form'; import {
cleanObject,
removeEmptyObjects,
removeInvalidMoneyData,
} from '~/utils/offers/form';
import { getCurrentMonth, getCurrentYear } from '~/utils/offers/time'; import { getCurrentMonth, getCurrentYear } from '~/utils/offers/time';
import { trpc } from '~/utils/trpc'; import { trpc } from '~/utils/trpc';
import OfferAnalysis from '../offerAnalysis/OfferAnalysis'; import OffersSubmissionAnalysis from './OffersSubmissionAnalysis';
import type { import type { ProfileAnalysis } from '~/types/offers';
CreateOfferProfileResponse,
ProfileAnalysis,
} from '~/types/offers';
const defaultOfferValues = { const defaultOfferValues = {
comments: '', comments: '',
@ -73,15 +74,12 @@ type Props = Readonly<{
export default function OffersSubmissionForm({ export default function OffersSubmissionForm({
initialOfferProfileValues = defaultOfferProfileValues, initialOfferProfileValues = defaultOfferProfileValues,
profileId, profileId: editProfileId = '',
token, token: editToken = '',
}: Props) { }: Props) {
const [formStep, setFormStep] = useState(0); const [formStep, setFormStep] = useState(0);
const [createProfileResponse, setCreateProfileResponse] = const [profileId, setProfileId] = useState(editProfileId);
useState<CreateOfferProfileResponse>({ const [token, setToken] = useState(editToken);
id: profileId || '',
token: token || '',
});
const [analysis, setAnalysis] = useState<ProfileAnalysis | null>(null); const [analysis, setAnalysis] = useState<ProfileAnalysis | null>(null);
const pageRef = useRef<HTMLDivElement>(null); const pageRef = useRef<HTMLDivElement>(null);
@ -125,11 +123,7 @@ export default function OffersSubmissionForm({
}, },
{ {
component: ( component: (
<OffersProfileSave <OffersProfileSave key={2} profileId={profileId} token={token} />
key={2}
profileId={createProfileResponse.id || ''}
token={createProfileResponse.token}
/>
), ),
hasNext: true, hasNext: true,
hasPrevious: false, hasPrevious: false,
@ -137,17 +131,13 @@ export default function OffersSubmissionForm({
}, },
{ {
component: ( component: (
<div> <OffersSubmissionAnalysis
<h5 className="mb-8 text-center text-4xl font-bold text-slate-900"> analysis={analysis}
Result
</h5>
<OfferAnalysis
key={3}
allAnalysis={analysis}
isError={generateAnalysisMutation.isError} isError={generateAnalysisMutation.isError}
isLoading={generateAnalysisMutation.isLoading} isLoading={generateAnalysisMutation.isLoading}
profileId={profileId}
token={token}
/> />
</div>
), ),
hasNext: false, hasNext: false,
hasPrevious: true, hasPrevious: true,
@ -184,7 +174,8 @@ export default function OffersSubmissionForm({
generateAnalysisMutation.mutate({ generateAnalysisMutation.mutate({
profileId: data?.id || '', profileId: data?.id || '',
}); });
setCreateProfileResponse(data); setProfileId(data.id);
setToken(data.token);
setFormStep(formStep + 1); setFormStep(formStep + 1);
scrollToTop(); scrollToTop();
}, },
@ -197,6 +188,7 @@ export default function OffersSubmissionForm({
} }
data = removeInvalidMoneyData(data); data = removeInvalidMoneyData(data);
data.offers = removeEmptyObjects(data.offers);
const background = cleanObject(data.background); const background = cleanObject(data.background);
background.specificYoes = data.background.specificYoes.filter( background.specificYoes = data.background.specificYoes.filter(
@ -236,7 +228,7 @@ export default function OffersSubmissionForm({
<FormProvider {...formMethods}> <FormProvider {...formMethods}>
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
{formSteps[formStep].component} {formSteps[formStep].component}
<pre>{JSON.stringify(formMethods.watch(), null, 2)}</pre> {/* <pre>{JSON.stringify(formMethods.watch(), null, 2)}</pre> */}
{formSteps[formStep].hasNext && ( {formSteps[formStep].hasNext && (
<div className="flex justify-end"> <div className="flex justify-end">
<Button <Button

@ -18,7 +18,6 @@ import {
CURRENCY_OPTIONS, CURRENCY_OPTIONS,
} from '~/utils/offers/currency/CurrencyEnum'; } from '~/utils/offers/currency/CurrencyEnum';
import FormMonthYearPicker from '../../forms/FormMonthYearPicker';
import FormRadioList from '../../forms/FormRadioList'; import FormRadioList from '../../forms/FormRadioList';
import FormSelect from '../../forms/FormSelect'; import FormSelect from '../../forms/FormSelect';
import FormTextInput from '../../forms/FormTextInput'; import FormTextInput from '../../forms/FormTextInput';
@ -140,24 +139,19 @@ function FullTimeJobFields() {
</div> </div>
<Collapsible label="Add more details"> <Collapsible label="Add more details">
<div className="mb-5 grid grid-cols-2 space-x-3"> <div className="mb-5 grid grid-cols-2 space-x-3">
<FormTextInput
label="Focus / Specialization"
placeholder="e.g. Front End"
{...register(`background.experiences.0.specialization`)}
/>
<FormTextInput <FormTextInput
label="Level" label="Level"
placeholder="e.g. L4, Junior" placeholder="e.g. L4, Junior"
{...register(`background.experiences.0.level`)} {...register(`background.experiences.0.level`)}
/> />
</div>
<div className="mb-5 grid grid-cols-2 space-x-3">
<FormSelect <FormSelect
display="block" display="block"
label="Location" label="Location"
options={locationOptions} options={locationOptions}
{...register(`background.experiences.0.location`)} {...register(`background.experiences.0.location`)}
/> />
</div>
<div className="mb-5 grid grid-cols-2 space-x-3">
<FormTextInput <FormTextInput
errorMessage={experiencesField?.durationInMonths?.message} errorMessage={experiencesField?.durationInMonths?.message}
label="Duration (months)" label="Duration (months)"
@ -224,11 +218,6 @@ function InternshipJobFields() {
</div> </div>
<Collapsible label="Add more details"> <Collapsible label="Add more details">
<div className="mb-5 grid grid-cols-2 space-x-3"> <div className="mb-5 grid grid-cols-2 space-x-3">
<FormTextInput
label="Focus / Specialization"
placeholder="e.g. Front End"
{...register(`background.experiences.0.specialization`)}
/>
<FormSelect <FormSelect
display="block" display="block"
label="Location" label="Location"
@ -245,7 +234,6 @@ function InternshipJobFields() {
function CurrentJobSection() { function CurrentJobSection() {
const { register } = useFormContext(); const { register } = useFormContext();
const watchJobType = useWatch({ const watchJobType = useWatch({
defaultValue: JobType.FULLTIME,
name: 'background.experiences.0.jobType', name: 'background.experiences.0.jobType',
}); });
@ -257,7 +245,7 @@ function CurrentJobSection() {
<div className="mb-5 rounded-lg border border-slate-200 px-10 py-5"> <div className="mb-5 rounded-lg border border-slate-200 px-10 py-5">
<div className="mb-5"> <div className="mb-5">
<FormRadioList <FormRadioList
defaultValue={JobType.FULLTIME} defaultValue={watchJobType}
isLabelHidden={true} isLabelHidden={true}
label="Job Type" label="Job Type"
orientation="horizontal" orientation="horizontal"
@ -316,22 +304,6 @@ function EducationSection() {
{...register(`background.educations.0.school`)} {...register(`background.educations.0.school`)}
/> />
</div> </div>
<div className="grid grid-cols-2 space-x-3">
<FormMonthYearPicker
monthLabel="Candidature Start"
yearLabel=""
{...register(`background.educations.0.startDate`, {
required: FieldError.REQUIRED,
})}
/>
<FormMonthYearPicker
monthLabel="Candidature End"
yearLabel=""
{...register(`background.educations.0.endDate`, {
required: FieldError.REQUIRED,
})}
/>
</div>
</Collapsible> </Collapsible>
</div> </div>
</> </>

@ -72,8 +72,7 @@ export default function OfferCard({
<HorizontalDivider /> <HorizontalDivider />
<div className="px-8"> <div className="px-8">
<div className="flex flex-col py-2"> <div className="flex flex-col py-2">
{totalCompensation || {(totalCompensation || monthlySalary) && (
(monthlySalary && (
<div className="flex flex-row"> <div className="flex flex-row">
<CurrencyDollarIcon className="mr-1 h-5" /> <CurrencyDollarIcon className="mr-1 h-5" />
<p> <p>
@ -81,9 +80,9 @@ export default function OfferCard({
{monthlySalary && `Monthly Salary: ${monthlySalary}`} {monthlySalary && `Monthly Salary: ${monthlySalary}`}
</p> </p>
</div> </div>
))} )}
{totalCompensation && ( {totalCompensation && (
<div className="ml-6 flex flex-row font-light text-slate-400"> <div className="ml-6 flex flex-row font-light">
<p> <p>
Base / year: {base} Stocks / year: {stocks} Bonus / year:{' '} Base / year: {base} Stocks / year: {stocks} Bonus / year:{' '}
{bonus} {bonus}

@ -121,7 +121,7 @@ function ProfileAnalysis({
<Button <Button
addonPosition="start" addonPosition="start"
icon={ArrowPathIcon} icon={ArrowPathIcon}
label="Refresh Analysis" label="Regenerate Analysis"
variant="secondary" variant="secondary"
onClick={() => generateAnalysisMutation.mutate({ profileId })} onClick={() => generateAnalysisMutation.mutate({ profileId })}
/> />

@ -51,7 +51,6 @@ type ExperiencePostData = {
level?: string | null; level?: string | null;
location?: string | null; location?: string | null;
monthlySalary?: Money | null; monthlySalary?: Money | null;
specialization?: string | null;
title?: string | null; title?: string | null;
totalCompensation?: Money | null; totalCompensation?: Money | null;
totalCompensationId?: string | null; totalCompensationId?: string | null;
@ -91,12 +90,11 @@ export type OfferFormData = Omit<OfferPostData, 'monthYearReceived'> & {
}; };
export type OfferFullTimePostData = { export type OfferFullTimePostData = {
baseSalary: Money; baseSalary: Money | null;
bonus: Money; bonus: Money | null;
id?: string; id?: string;
level: string; level: string;
specialization: string; stocks: Money | null;
stocks: Money;
title: string; title: string;
totalCompensation: Money; totalCompensation: Money;
}; };
@ -105,7 +103,6 @@ export type OfferInternPostData = {
id?: string; id?: string;
internshipCycle: string; internshipCycle: string;
monthlySalary: Money; monthlySalary: Money;
specialization: string;
startYear: number; startYear: number;
title: string; title: string;
}; };

@ -14,18 +14,20 @@ export default function ResumeUserBadge({
return ( return (
<div className="group relative flex items-center justify-center"> <div className="group relative flex items-center justify-center">
<div <div
className="absolute left-6 z-10 hidden w-48 flex-col className="absolute top-7 z-10 hidden h-36 w-48 flex-col justify-center
justify-center gap-1 rounded-xl bg-white px-2 py-2 text-center drop-shadow-lg gap-1 rounded-xl bg-white pb-2 text-center drop-shadow-lg
before:absolute before:top-12 before:-translate-x-6 after:absolute after:left-1/2 after:top-[-11%] after:-translate-x-1/2
before:border-8 before:border-y-transparent before:border-l-transparent after:border-8 after:border-x-transparent after:border-t-transparent
before:border-r-white before:drop-shadow-lg before:content-[''] after:border-b-slate-200 after:drop-shadow-sm after:content-['']
group-hover:flex"> group-hover:flex">
<Icon className="h-12 w-12 self-center" /> <Icon className="h-16 w-full self-center rounded-t-xl bg-slate-200 py-2" />
<div className="flex h-20 flex-col justify-evenly px-2">
<p className="font-medium">{title}</p> <p className="font-medium">{title}</p>
<p className="text-sm">{description}.</p> <p className="text-sm">{description}.</p>
</div> </div>
<Icon className="h-4 w-4" /> </div>
<Icon className="h-5 w-5 rounded-xl border bg-slate-200 shadow-sm" />
</div> </div>
); );
} }

@ -55,7 +55,7 @@ export default function ResumeCommentsList({
<Spinner display="block" size="lg" /> <Spinner display="block" size="lg" />
</div> </div>
) : ( ) : (
<div className="mb-8 flow-root h-[calc(100vh-13rem)] w-full flex-col space-y-4 overflow-y-auto overflow-x-hidden"> <div className="mb-8 flow-root h-[calc(100vh-13rem)] w-full flex-col space-y-4 overflow-y-auto overflow-x-hidden pb-16">
{RESUME_COMMENTS_SECTIONS.map(({ label, value }) => { {RESUME_COMMENTS_SECTIONS.map(({ label, value }) => {
const comments = commentsQuery.data const comments = commentsQuery.data
? commentsQuery.data.filter((comment: ResumeComment) => { ? commentsQuery.data.filter((comment: ResumeComment) => {
@ -65,14 +65,13 @@ export default function ResumeCommentsList({
const commentCount = comments.length; const commentCount = comments.length;
return ( return (
<div key={value} className="mb-4 space-y-4"> <div key={value} className="space-y-6 pr-4">
<div className="text-primary-800 flex flex-row items-center space-x-2"> <div className="text-primary-800 -mb-2 flex flex-row items-center space-x-2">
{renderIcon(value)} {renderIcon(value)}
<div className="w-fit text-lg font-medium">{label}</div> <div className="w-fit text-lg font-medium">{label}</div>
</div> </div>
<div className="w-full space-y-4 pr-4">
<div <div
className={clsx( className={clsx(
'space-y-2 rounded-md border-2 bg-white px-4 py-3 drop-shadow-md', 'space-y-2 rounded-md border-2 bg-white px-4 py-3 drop-shadow-md',
@ -98,11 +97,8 @@ export default function ResumeCommentsList({
</div> </div>
)} )}
</div> </div>
</div>
<div className="relative flex flex-row pr-6 pt-2"> <hr className="border-gray-300" />
<div className="flex-grow border-t border-gray-300" />
</div>
</div> </div>
); );
})} })}

@ -73,10 +73,6 @@ const analysisOfferDtoMapper = (
?.filter((exp) => exp.company != null) ?.filter((exp) => exp.company != null)
.map((exp) => exp.company?.name ?? '') ?? [], .map((exp) => exp.company?.name ?? '') ?? [],
profileName, profileName,
specialization:
offer.jobType === JobType.FULLTIME
? offer.offersFullTime?.specialization ?? ''
: offer.offersIntern?.specialization ?? '',
title: title:
offer.jobType === JobType.FULLTIME offer.jobType === JobType.FULLTIME
? offer.offersFullTime?.title ?? '' ? offer.offersFullTime?.title ?? ''
@ -120,22 +116,14 @@ const analysisDtoMapper = (
OffersOffer & { OffersOffer & {
company: Company; company: Company;
offersFullTime: offersFullTime:
| (OffersFullTime & { | (OffersFullTime & { totalCompensation: OffersCurrency })
totalCompensation: OffersCurrency;
})
| null;
offersIntern:
| (OffersIntern & {
monthlySalary: OffersCurrency;
})
| null; | null;
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
profile: OffersProfile & { profile: OffersProfile & {
background: background:
| (OffersBackground & { | (OffersBackground & {
experiences: Array< experiences: Array<
OffersExperience & { OffersExperience & { company: Company | null }
company: Company | null;
}
>; >;
}) })
| null; | null;
@ -168,10 +156,6 @@ const analysisHighestOfferDtoMapper = (
id: offer.id, id: offer.id,
level: offer.offersFullTime?.level ?? '', level: offer.offersFullTime?.level ?? '',
location: offer.location, location: offer.location,
specialization:
offer.jobType === JobType.FULLTIME
? offer.offersFullTime?.specialization ?? ''
: offer.offersIntern?.specialization ?? '',
totalYoe: offer.profile.background?.totalYoe ?? -1, totalYoe: offer.profile.background?.totalYoe ?? -1,
}; };
return analysisHighestOfferDto; return analysisHighestOfferDto;
@ -327,12 +311,11 @@ export const experienceDtoMapper = (
location: experience.location, location: experience.location,
monthlySalary: experience.monthlySalary monthlySalary: experience.monthlySalary
? valuationDtoMapper(experience.monthlySalary) ? valuationDtoMapper(experience.monthlySalary)
: experience.monthlySalary, : null,
specialization: experience.specialization,
title: experience.title, title: experience.title,
totalCompensation: experience.totalCompensation totalCompensation: experience.totalCompensation
? valuationDtoMapper(experience.totalCompensation) ? valuationDtoMapper(experience.totalCompensation)
: experience.totalCompensation, : null,
}; };
return experienceDto; return experienceDto;
}; };
@ -398,9 +381,9 @@ export const profileOfferDtoMapper = (
company: Company; company: Company;
offersFullTime: offersFullTime:
| (OffersFullTime & { | (OffersFullTime & {
baseSalary: OffersCurrency; baseSalary: OffersCurrency | null;
bonus: OffersCurrency; bonus: OffersCurrency | null;
stocks: OffersCurrency; stocks: OffersCurrency | null;
totalCompensation: OffersCurrency; totalCompensation: OffersCurrency;
}) })
| null; | null;
@ -421,12 +404,20 @@ export const profileOfferDtoMapper = (
if (offer.offersFullTime) { if (offer.offersFullTime) {
profileOfferDto.offersFullTime = { profileOfferDto.offersFullTime = {
baseSalary: valuationDtoMapper(offer.offersFullTime.baseSalary), baseSalary:
bonus: valuationDtoMapper(offer.offersFullTime.bonus), offer.offersFullTime?.baseSalary != null
? valuationDtoMapper(offer.offersFullTime.baseSalary)
: null,
bonus:
offer.offersFullTime?.bonus != null
? valuationDtoMapper(offer.offersFullTime.bonus)
: null,
id: offer.offersFullTime.id, id: offer.offersFullTime.id,
level: offer.offersFullTime.level, level: offer.offersFullTime.level,
specialization: offer.offersFullTime.specialization, stocks:
stocks: valuationDtoMapper(offer.offersFullTime.stocks), offer.offersFullTime?.stocks != null
? valuationDtoMapper(offer.offersFullTime.stocks)
: null,
title: offer.offersFullTime.title, title: offer.offersFullTime.title,
totalCompensation: valuationDtoMapper( totalCompensation: valuationDtoMapper(
offer.offersFullTime.totalCompensation, offer.offersFullTime.totalCompensation,
@ -437,7 +428,6 @@ export const profileOfferDtoMapper = (
id: offer.offersIntern.id, id: offer.offersIntern.id,
internshipCycle: offer.offersIntern.internshipCycle, internshipCycle: offer.offersIntern.internshipCycle,
monthlySalary: valuationDtoMapper(offer.offersIntern.monthlySalary), monthlySalary: valuationDtoMapper(offer.offersIntern.monthlySalary),
specialization: offer.offersIntern.specialization,
startYear: offer.offersIntern.startYear, startYear: offer.offersIntern.startYear,
title: offer.offersIntern.title, title: offer.offersIntern.title,
}; };
@ -527,9 +517,9 @@ export const profileDtoMapper = (
company: Company; company: Company;
offersFullTime: offersFullTime:
| (OffersFullTime & { | (OffersFullTime & {
baseSalary: OffersCurrency; baseSalary: OffersCurrency | null;
bonus: OffersCurrency; bonus: OffersCurrency | null;
stocks: OffersCurrency; stocks: OffersCurrency | null;
totalCompensation: OffersCurrency; totalCompensation: OffersCurrency;
}) })
| null; | null;
@ -550,7 +540,7 @@ export const profileDtoMapper = (
}; };
if (inputToken === profile.editToken) { if (inputToken === profile.editToken) {
profileDto.editToken = profile.editToken; profileDto.editToken = profile.editToken ?? null;
profileDto.isEditable = true; profileDto.isEditable = true;
} }
@ -587,9 +577,9 @@ export const dashboardOfferDtoMapper = (
company: Company; company: Company;
offersFullTime: offersFullTime:
| (OffersFullTime & { | (OffersFullTime & {
baseSalary: OffersCurrency; baseSalary: OffersCurrency | null;
bonus: OffersCurrency; bonus: OffersCurrency | null;
stocks: OffersCurrency; stocks: OffersCurrency | null;
totalCompensation: OffersCurrency; totalCompensation: OffersCurrency;
}) })
| null; | null;

@ -60,8 +60,14 @@ export default function OfferProfile() {
? data?.offers.map((res: ProfileOffer) => { ? data?.offers.map((res: ProfileOffer) => {
if (res.offersFullTime) { if (res.offersFullTime) {
const filteredOffer: OfferDisplayData = { const filteredOffer: OfferDisplayData = {
base: convertMoneyToString(res.offersFullTime.baseSalary), base:
bonus: convertMoneyToString(res.offersFullTime.bonus), res.offersFullTime.baseSalary != null
? convertMoneyToString(res.offersFullTime.baseSalary)
: undefined,
bonus:
res.offersFullTime.bonus != null
? convertMoneyToString(res.offersFullTime.bonus)
: undefined,
companyName: res.company.name, companyName: res.company.name,
id: res.offersFullTime.id, id: res.offersFullTime.id,
jobLevel: res.offersFullTime.level, jobLevel: res.offersFullTime.level,
@ -72,7 +78,10 @@ export default function OfferProfile() {
negotiationStrategy: res.negotiationStrategy, negotiationStrategy: res.negotiationStrategy,
otherComment: res.comments, otherComment: res.comments,
receivedMonth: formatDate(res.monthYearReceived), receivedMonth: formatDate(res.monthYearReceived),
stocks: convertMoneyToString(res.offersFullTime.stocks), stocks:
res.offersFullTime.stocks != null
? convertMoneyToString(res.offersFullTime.stocks)
: undefined,
totalCompensation: convertMoneyToString( totalCompensation: convertMoneyToString(
res.offersFullTime.totalCompensation, res.offersFullTime.totalCompensation,
), ),

@ -34,7 +34,17 @@ export default function OffersEditPage() {
experiences: experiences:
experiences.length === 0 experiences.length === 0
? [{ jobType: JobType.FULLTIME }] ? [{ jobType: JobType.FULLTIME }]
: experiences, : experiences.map((exp) => ({
companyId: exp.company?.id,
durationInMonths: exp.durationInMonths,
id: exp.id,
jobType: exp.jobType,
level: exp.level,
location: exp.location,
monthlySalary: exp.monthlySalary,
title: exp.title,
totalCompensation: exp.totalCompensation,
})),
id, id,
specificYoes, specificYoes,
totalYoe, totalYoe,

@ -107,8 +107,7 @@ function Test() {
durationInMonths: 24, durationInMonths: 24,
jobType: 'FULLTIME', jobType: 'FULLTIME',
level: 'Junior', level: 'Junior',
specialization: 'Front End', title: 'software-engineer',
title: 'Software Engineer',
totalCompensation: { totalCompensation: {
currency: 'SGD', currency: 'SGD',
value: 104100, value: 104100,
@ -146,12 +145,11 @@ function Test() {
value: 2222, value: 2222,
}, },
level: 'Junior', level: 'Junior',
specialization: 'Front End',
stocks: { stocks: {
currency: 'SGD', currency: 'SGD',
value: 0, value: 0,
}, },
title: 'Software Engineer', title: 'software-engineer',
totalCompensation: { totalCompensation: {
currency: 'SGD', currency: 'SGD',
value: 4444, value: 4444,
@ -175,12 +173,11 @@ function Test() {
value: 20000, value: 20000,
}, },
level: 'Junior', level: 'Junior',
specialization: 'Front End',
stocks: { stocks: {
currency: 'SGD', currency: 'SGD',
value: 100, value: 100,
}, },
title: 'Software Engineer', title: 'software-engineer',
totalCompensation: { totalCompensation: {
currency: 'SGD', currency: 'SGD',
value: 104100, value: 104100,
@ -269,8 +266,7 @@ function Test() {
level: 'Junior', level: 'Junior',
monthlySalary: null, monthlySalary: null,
monthlySalaryId: null, monthlySalaryId: null,
specialization: 'Front End', title: 'software-engineer',
title: 'Software Engineer',
totalCompensation: { totalCompensation: {
currency: 'SGD', currency: 'SGD',
id: 'cl9i68fvc0005tthj7r1rhvb1', id: 'cl9i68fvc0005tthj7r1rhvb1',
@ -335,14 +331,13 @@ function Test() {
bonusId: 'cl9i68fve000rtthjqo2ktljt', bonusId: 'cl9i68fve000rtthjqo2ktljt',
id: 'cl9i68fve000otthjqk0g01k0', id: 'cl9i68fve000otthjqk0g01k0',
level: 'EXPERT', level: 'EXPERT',
specialization: 'FRONTEND',
stocks: { stocks: {
currency: 'SGD', currency: 'SGD',
id: 'cl9i68fvf000ttthjt2ode0cc', id: 'cl9i68fvf000ttthjt2ode0cc',
value: -558038585, value: -558038585,
}, },
stocksId: 'cl9i68fvf000ttthjt2ode0cc', stocksId: 'cl9i68fvf000ttthjt2ode0cc',
title: 'Software Engineer', title: 'software-engineer',
totalCompensation: { totalCompensation: {
currency: 'SGD', currency: 'SGD',
id: 'cl9i68fvf000vtthjg90s48nj', id: 'cl9i68fvf000vtthjg90s48nj',
@ -355,220 +350,8 @@ function Test() {
offersInternId: null, offersInternId: null,
profileId: 'cl9i68fv60000tthj8t3zkox0', profileId: 'cl9i68fv60000tthj8t3zkox0',
}, },
// {
// comments: '',
// company: {
// createdAt: new Date('2022-10-12T16:19:05.196Z'),
// description:
// 'Meta Platforms, Inc., doing business as Meta and formerly named Facebook, Inc., and TheFacebook, Inc., is an American multinational technology conglomerate based in Menlo Park, California. The company owns Facebook, Instagram, and WhatsApp, among other products and services.',
// id: 'cl9j4yawz0003utlp1uaa1t8o',
// logoUrl: 'https://logo.clearbit.com/meta.com',
// name: 'Meta',
// slug: 'meta',
// updatedAt: new Date('2022-10-12T16:19:05.196Z'),
// },
// companyId: 'cl9j4yawz0003utlp1uaa1t8o',
// id: 'cl9i68fvf000ytthj0ltsqt1d',
// jobType: 'FULLTIME',
// location: 'Singapore, Singapore',
// monthYearReceived: new Date('2022-09-30T07:58:54.000Z'),
// negotiationStrategy: 'Leveraged having million offers',
// offersFullTime: {
// baseSalary: {
// currency: 'SGD',
// id: 'cl9i68fvf0010tthj0iym6woh',
// value: 84000,
// },
// baseSalaryId: 'cl9i68fvf0010tthj0iym6woh',
// bonus: {
// currency: 'SGD',
// id: 'cl9i68fvf0012tthjioltnspk',
// value: 123456789,
// },
// bonusId: 'cl9i68fvf0012tthjioltnspk',
// id: 'cl9i68fvf000ztthjcovbiehc',
// level: 'Junior',
// specialization: 'Front End',
// stocks: {
// currency: 'SGD',
// id: 'cl9i68fvf0014tthjz2gff3hs',
// value: 100,
// },
// stocksId: 'cl9i68fvf0014tthjz2gff3hs',
// title: 'Software Engineer',
// totalCompensation: {
// currency: 'SGD',
// id: 'cl9i68fvf0016tthjrtb7iuvj',
// value: 104100,
// },
// totalCompensationId: 'cl9i68fvf0016tthjrtb7iuvj',
// },
// offersFullTimeId: 'cl9i68fvf000ztthjcovbiehc',
// offersIntern: null,
// offersInternId: null,
// profileId: 'cl9i68fv60000tthj8t3zkox0',
// },
// {
// comments: '',
// company: {
// createdAt: new Date('2022-10-12T16:19:05.196Z'),
// description:
// 'Meta Platforms, Inc., doing business as Meta and formerly named Facebook, Inc., and TheFacebook, Inc., is an American multinational technology conglomerate based in Menlo Park, California. The company owns Facebook, Instagram, and WhatsApp, among other products and services.',
// id: 'cl9j4yawz0003utlp1uaa1t8o',
// logoUrl: 'https://logo.clearbit.com/meta.com',
// name: 'Meta',
// slug: 'meta',
// updatedAt: new Date('2022-10-12T16:19:05.196Z'),
// },
// companyId: 'cl9j4yawz0003utlp1uaa1t8o',
// id: 'cl96stky9003bw32gc3l955vr',
// jobType: 'FULLTIME',
// location: 'Singapore, Singapore',
// monthYearReceived: new Date('2022-09-30T07:58:54.000Z'),
// negotiationStrategy: 'LOst out having multiple offers',
// offersFullTime: {
// baseSalary: {
// currency: 'SGD',
// id: 'cl96stky9003dw32gcvqbijlo',
// value: 1,
// },
// baseSalaryId: 'cl96stky9003dw32gcvqbijlo',
// bonus: {
// currency: 'SGD',
// id: 'cl96stky9003fw32goc3zqxwr',
// value: 0,
// },
// bonusId: 'cl96stky9003fw32goc3zqxwr',
// id: 'cl96stky9003cw32g5v10izfu',
// level: 'Senior',
// specialization: 'Front End',
// stocks: {
// currency: 'SGD',
// id: 'cl96stky9003hw32g1lbbkqqr',
// value: 999999,
// },
// stocksId: 'cl96stky9003hw32g1lbbkqqr',
// title: 'Software Engineer DOG',
// totalCompensation: {
// currency: 'SGD',
// id: 'cl96stky9003jw32gzumcoi7v',
// value: 999999,
// },
// totalCompensationId: 'cl96stky9003jw32gzumcoi7v',
// },
// offersFullTimeId: 'cl96stky9003cw32g5v10izfu',
// offersIntern: null,
// offersInternId: null,
// profileId: 'cl96stky5002ew32gx2kale2x',
// },
// {
// comments: 'this IS SO COOL',
// company: {
// createdAt: new Date('2022-10-12T16:19:05.196Z'),
// description:
// 'Meta Platforms, Inc., doing business as Meta and formerly named Facebook, Inc., and TheFacebook, Inc., is an American multinational technology conglomerate based in Menlo Park, California. The company owns Facebook, Instagram, and WhatsApp, among other products and services.',
// id: 'cl9j4yawz0003utlp1uaa1t8o',
// logoUrl: 'https://logo.clearbit.com/meta.com',
// name: 'Meta',
// slug: 'meta',
// updatedAt: new Date('2022-10-12T16:19:05.196Z'),
// },
// companyId: 'cl9j4yawz0003utlp1uaa1t8o',
// id: 'cl976wf28000t7iyga4noyz7s',
// jobType: 'FULLTIME',
// location: 'Singapore, Singapore',
// monthYearReceived: new Date('2022-09-30T07:58:54.000Z'),
// negotiationStrategy: 'Charmed the guy with my face',
// offersFullTime: {
// baseSalary: {
// currency: 'SGD',
// id: 'cl976wf28000v7iygmk1b7qaq',
// value: 1999999999,
// },
// baseSalaryId: 'cl976wf28000v7iygmk1b7qaq',
// bonus: {
// currency: 'SGD',
// id: 'cl976wf28000x7iyg63w7kcli',
// value: 1410065407,
// },
// bonusId: 'cl976wf28000x7iyg63w7kcli',
// id: 'cl976wf28000u7iyg6euei8e9',
// level: 'EXPERT',
// specialization: 'FRONTEND',
// stocks: {
// currency: 'SGD',
// id: 'cl976wf28000z7iyg9ivun6ap',
// value: 111222333,
// },
// stocksId: 'cl976wf28000z7iyg9ivun6ap',
// title: 'Software Engineer',
// totalCompensation: {
// currency: 'SGD',
// id: 'cl976wf2800117iygmzsc0xit',
// value: 55555555,
// },
// totalCompensationId: 'cl976wf2800117iygmzsc0xit',
// },
// offersFullTimeId: 'cl976wf28000u7iyg6euei8e9',
// offersIntern: null,
// offersInternId: null,
// profileId: 'cl96stky5002ew32gx2kale2x',
// },
// {
// comments: 'this rocks',
// company: {
// createdAt: new Date('2022-10-12T16:19:05.196Z'),
// description:
// 'Meta Platforms, Inc., doing business as Meta and formerly named Facebook, Inc., and TheFacebook, Inc., is an American multinational technology conglomerate based in Menlo Park, California. The company owns Facebook, Instagram, and WhatsApp, among other products and services.',
// id: 'cl9j4yawz0003utlp1uaa1t8o',
// logoUrl: 'https://logo.clearbit.com/meta.com',
// name: 'Meta',
// slug: 'meta',
// updatedAt: new Date('2022-10-12T16:19:05.196Z'),
// },
// companyId: 'cl9j4yawz0003utlp1uaa1t8o',
// id: 'cl96tbb3o0051w32gjrpaiiit',
// jobType: 'FULLTIME',
// location: 'Singapore, Singapore',
// monthYearReceived: new Date('2022-09-30T07:58:54.000Z'),
// negotiationStrategy: 'Charmed the guy with my face',
// offersFullTime: {
// baseSalary: {
// currency: 'SGD',
// id: 'cl96tbb3o0053w32gz11paaxu',
// value: 1999999999,
// },
// baseSalaryId: 'cl96tbb3o0053w32gz11paaxu',
// bonus: {
// currency: 'SGD',
// id: 'cl96tbb3o0055w32gpyqgz5hx',
// value: 1410065407,
// },
// bonusId: 'cl96tbb3o0055w32gpyqgz5hx',
// id: 'cl96tbb3o0052w32guguajzin',
// level: 'EXPERT',
// specialization: 'FRONTEND',
// stocks: {
// currency: 'SGD',
// id: 'cl96tbb3o0057w32gu4nyxguf',
// value: 500,
// },
// stocksId: 'cl96tbb3o0057w32gu4nyxguf',
// title: 'Software Engineer',
// totalCompensation: {
// currency: 'SGD',
// id: 'cl96tbb3o0059w32gm3iy1zk4',
// value: 55555555,
// },
// totalCompensationId: 'cl96tbb3o0059w32gm3iy1zk4',
// },
// offersFullTimeId: 'cl96tbb3o0052w32guguajzin',
// offersIntern: null,
// offersInternId: null,
// profileId: 'cl96stky5002ew32gx2kale2x',
// },
], ],
// ProfileName: 'ailing bryann stuart ziqing',
token: '24bafa6fef803f447d7f2e229b14cb8ee43f0c22dffbe41ee1c1e5e6e870f117', token: '24bafa6fef803f447d7f2e229b14cb8ee43f0c22dffbe41ee1c1e5e6e870f117',
userId: null, userId: null,
}); });

@ -100,7 +100,7 @@ export default function ResumeReviewPage() {
} }
return ( return (
<Button <Button
className="h-10 py-2" className="h-10 py-2 shadow-md"
display="block" display="block"
label="Add your review" label="Add your review"
variant="tertiary" variant="tertiary"
@ -151,18 +151,18 @@ export default function ResumeReviewPage() {
<h1 className="pr-2 text-2xl font-semibold leading-7 text-slate-900 sm:truncate sm:text-3xl sm:tracking-tight"> <h1 className="pr-2 text-2xl font-semibold leading-7 text-slate-900 sm:truncate sm:text-3xl sm:tracking-tight">
{detailsQuery.data.title} {detailsQuery.data.title}
</h1> </h1>
<div className="flex gap-4 xl:pr-4"> <div className="flex gap-3 xl:pr-4">
{userIsOwner && ( {userIsOwner && (
<button <button
className="p h-10 rounded-md border border-slate-300 bg-white py-1 px-2 text-center" className="h-10 rounded-md border border-slate-300 bg-white py-1 px-2 text-center shadow-md hover:bg-slate-50"
type="button" type="button"
onClick={onEditButtonClick}> onClick={onEditButtonClick}>
<PencilSquareIcon className="text-primary-600 hover:text-primary-300 h-6 w-6" /> <PencilSquareIcon className="text-primary-600 h-6 w-6" />
</button> </button>
)} )}
<button <button
className="isolate inline-flex h-10 items-center space-x-4 rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 disabled:hover:bg-white" className="isolate inline-flex h-10 items-center space-x-4 rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-md hover:bg-slate-50 disabled:hover:bg-white"
disabled={starMutation.isLoading || unstarMutation.isLoading} disabled={starMutation.isLoading || unstarMutation.isLoading}
type="button" type="button"
onClick={onStarButtonClick}> onClick={onStarButtonClick}>

@ -293,7 +293,7 @@ export default function SubmitResumeForm({
required={true} required={true}
onChange={(val) => onValueChange('title', val)} onChange={(val) => onValueChange('title', val)}
/> />
<div className="flex gap-8"> <div className="flex flex-wrap gap-6">
<Select <Select
{...register('role', { required: true })} {...register('role', { required: true })}
defaultValue={undefined} defaultValue={undefined}
@ -339,7 +339,7 @@ export default function SubmitResumeForm({
fileUploadError fileUploadError
? 'border-danger-600' ? 'border-danger-600'
: 'border-slate-300', : 'border-slate-300',
'flex cursor-pointer justify-center rounded-md border-2 border-dashed bg-slate-100 py-4', 'cursor-pointer flex-col items-center space-y-1 rounded-md border-2 border-dashed bg-slate-100 py-4 px-4 text-center',
)}> )}>
<input <input
{...register('file', { required: true })} {...register('file', { required: true })}
@ -351,7 +351,6 @@ export default function SubmitResumeForm({
name="file-upload" name="file-upload"
type="file" type="file"
/> />
<div className="space-y-1 text-center">
{resumeFile == null ? ( {resumeFile == null ? (
<ArrowUpCircleIcon className="text-primary-500 m-auto h-10 w-10" /> <ArrowUpCircleIcon className="text-primary-500 m-auto h-10 w-10" />
) : ( ) : (
@ -362,11 +361,11 @@ export default function SubmitResumeForm({
</p> </p>
)} )}
<label <label
className="focus-within:ring-primary-500 flex items-center rounded-md text-sm focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2" className="focus-within:ring-primary-500 cursor-pointer text-sm focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2"
htmlFor="file-upload"> htmlFor="file-upload">
<span className="font-medium">Drop file here</span> <span className="font-medium">Drop file here</span>
<span className="mr-1 ml-1 font-light">or</span> <span className="mr-1 ml-1 font-light">or</span>
<span className="text-primary-600 hover:text-primary-400 cursor-pointer font-medium"> <span className="text-primary-600 hover:text-primary-400 font-medium">
{resumeFile == null ? 'Select file' : 'Replace file'} {resumeFile == null ? 'Select file' : 'Replace file'}
</span> </span>
</label> </label>
@ -374,7 +373,6 @@ export default function SubmitResumeForm({
PDF up to {FILE_SIZE_LIMIT_MB}MB PDF up to {FILE_SIZE_LIMIT_MB}MB
</p> </p>
</div> </div>
</div>
{fileUploadError && ( {fileUploadError && (
<p className="text-danger-600 text-sm">{fileUploadError}</p> <p className="text-danger-600 text-sm">{fileUploadError}</p>
)} )}

@ -10,7 +10,10 @@ import {
} from '~/mappers/offers-mappers'; } from '~/mappers/offers-mappers';
import { baseCurrencyString } from '~/utils/offers/currency'; import { baseCurrencyString } from '~/utils/offers/currency';
import { convert } from '~/utils/offers/currency/currencyExchange'; import { convert } from '~/utils/offers/currency/currencyExchange';
import { generateRandomName, generateRandomStringForToken } from '~/utils/offers/randomGenerator'; import {
generateRandomName,
generateRandomStringForToken,
} from '~/utils/offers/randomGenerator';
import { createValidationRegex } from '~/utils/offers/zodRegex'; import { createValidationRegex } from '~/utils/offers/zodRegex';
import { createRouter } from '../context'; import { createRouter } from '../context';
@ -48,7 +51,6 @@ const offer = z.object({
bonusId: z.string().nullish(), bonusId: z.string().nullish(),
id: z.string().optional(), id: z.string().optional(),
level: z.string().nullish(), level: z.string().nullish(),
specialization: z.string(),
stocks: valuation.nullish(), stocks: valuation.nullish(),
stocksId: z.string().nullish(), stocksId: z.string().nullish(),
title: z.string(), title: z.string(),
@ -62,7 +64,6 @@ const offer = z.object({
id: z.string().optional(), id: z.string().optional(),
internshipCycle: z.string().nullish(), internshipCycle: z.string().nullish(),
monthlySalary: valuation.nullish(), monthlySalary: valuation.nullish(),
specialization: z.string(),
startYear: z.number().nullish(), startYear: z.number().nullish(),
title: z.string(), title: z.string(),
totalCompensation: valuation.nullish(), // Full time totalCompensation: valuation.nullish(), // Full time
@ -86,7 +87,6 @@ const experience = z.object({
location: z.string().nullish(), location: z.string().nullish(),
monthlySalary: valuation.nullish(), monthlySalary: valuation.nullish(),
monthlySalaryId: z.string().nullish(), monthlySalaryId: z.string().nullish(),
specialization: z.string().nullish(),
title: z.string().nullish(), title: z.string().nullish(),
totalCompensation: valuation.nullish(), totalCompensation: valuation.nullish(),
totalCompensationId: z.string().nullish(), totalCompensationId: z.string().nullish(),
@ -285,11 +285,7 @@ export const offersProfileRouter = createRouter()
}, },
experiences: { experiences: {
create: input.background.experiences.map(async (x) => { create: input.background.experiences.map(async (x) => {
if ( if (x.jobType === JobType.FULLTIME) {
x.jobType === JobType.FULLTIME &&
x.totalCompensation?.currency != null &&
x.totalCompensation?.value != null
) {
if (x.companyId) { if (x.companyId) {
return { return {
company: { company: {
@ -300,9 +296,10 @@ export const offersProfileRouter = createRouter()
durationInMonths: x.durationInMonths, durationInMonths: x.durationInMonths,
jobType: x.jobType, jobType: x.jobType,
level: x.level, level: x.level,
specialization: x.specialization,
title: x.title, title: x.title,
totalCompensation: { totalCompensation:
x.totalCompensation != null
? {
create: { create: {
baseCurrency: baseCurrencyString, baseCurrency: baseCurrencyString,
baseValue: await convert( baseValue: await convert(
@ -313,7 +310,8 @@ export const offersProfileRouter = createRouter()
currency: x.totalCompensation.currency, currency: x.totalCompensation.currency,
value: x.totalCompensation.value, value: x.totalCompensation.value,
}, },
}, }
: undefined,
}; };
} }
return { return {
@ -321,9 +319,10 @@ export const offersProfileRouter = createRouter()
jobType: x.jobType, jobType: x.jobType,
level: x.level, level: x.level,
location: x.location, location: x.location,
specialization: x.specialization,
title: x.title, title: x.title,
totalCompensation: { totalCompensation:
x.totalCompensation != null
? {
create: { create: {
baseCurrency: baseCurrencyString, baseCurrency: baseCurrencyString,
baseValue: await convert( baseValue: await convert(
@ -334,14 +333,11 @@ export const offersProfileRouter = createRouter()
currency: x.totalCompensation.currency, currency: x.totalCompensation.currency,
value: x.totalCompensation.value, value: x.totalCompensation.value,
}, },
}, }
: undefined,
}; };
} }
if ( if (x.jobType === JobType.INTERN) {
x.jobType === JobType.INTERN &&
x.monthlySalary?.currency != null &&
x.monthlySalary?.value != null
) {
if (x.companyId) { if (x.companyId) {
return { return {
company: { company: {
@ -351,7 +347,9 @@ export const offersProfileRouter = createRouter()
}, },
durationInMonths: x.durationInMonths, durationInMonths: x.durationInMonths,
jobType: x.jobType, jobType: x.jobType,
monthlySalary: { monthlySalary:
x.monthlySalary != null
? {
create: { create: {
baseCurrency: baseCurrencyString, baseCurrency: baseCurrencyString,
baseValue: await convert( baseValue: await convert(
@ -362,15 +360,17 @@ export const offersProfileRouter = createRouter()
currency: x.monthlySalary.currency, currency: x.monthlySalary.currency,
value: x.monthlySalary.value, value: x.monthlySalary.value,
}, },
}, }
specialization: x.specialization, : undefined,
title: x.title, title: x.title,
}; };
} }
return { return {
durationInMonths: x.durationInMonths, durationInMonths: x.durationInMonths,
jobType: x.jobType, jobType: x.jobType,
monthlySalary: { monthlySalary:
x.monthlySalary != null
? {
create: { create: {
baseCurrency: baseCurrencyString, baseCurrency: baseCurrencyString,
baseValue: await convert( baseValue: await convert(
@ -381,8 +381,8 @@ export const offersProfileRouter = createRouter()
currency: x.monthlySalary.currency, currency: x.monthlySalary.currency,
value: x.monthlySalary.value, value: x.monthlySalary.value,
}, },
}, }
specialization: x.specialization, : undefined,
title: x.title, title: x.title,
}; };
} }
@ -442,7 +442,6 @@ export const offersProfileRouter = createRouter()
value: x.offersIntern.monthlySalary.value, value: x.offersIntern.monthlySalary.value,
}, },
}, },
specialization: x.offersIntern.specialization,
startYear: x.offersIntern.startYear, startYear: x.offersIntern.startYear,
title: x.offersIntern.title, title: x.offersIntern.title,
}, },
@ -452,17 +451,10 @@ export const offersProfileRouter = createRouter()
if ( if (
x.jobType === JobType.FULLTIME && x.jobType === JobType.FULLTIME &&
x.offersFullTime && x.offersFullTime &&
x.offersFullTime.baseSalary?.currency != null &&
x.offersFullTime.baseSalary?.value != null &&
x.offersFullTime.bonus?.currency != null &&
x.offersFullTime.bonus?.value != null &&
x.offersFullTime.stocks?.currency != null &&
x.offersFullTime.stocks?.value != null &&
x.offersFullTime.totalCompensation?.currency != null && x.offersFullTime.totalCompensation?.currency != null &&
x.offersFullTime.totalCompensation?.value != null && x.offersFullTime.totalCompensation?.value != null &&
x.offersFullTime.level != null && x.offersFullTime.level != null &&
x.offersFullTime.title != null && x.offersFullTime.title != null
x.offersFullTime.specialization != null
) { ) {
return { return {
comments: x.comments, comments: x.comments,
@ -477,7 +469,9 @@ export const offersProfileRouter = createRouter()
negotiationStrategy: x.negotiationStrategy, negotiationStrategy: x.negotiationStrategy,
offersFullTime: { offersFullTime: {
create: { create: {
baseSalary: { baseSalary:
x.offersFullTime?.baseSalary != null
? {
create: { create: {
baseCurrency: baseCurrencyString, baseCurrency: baseCurrencyString,
baseValue: await convert( baseValue: await convert(
@ -485,11 +479,15 @@ export const offersProfileRouter = createRouter()
x.offersFullTime.baseSalary.currency, x.offersFullTime.baseSalary.currency,
baseCurrencyString, baseCurrencyString,
), ),
currency: x.offersFullTime.baseSalary.currency, currency:
x.offersFullTime.baseSalary.currency,
value: x.offersFullTime.baseSalary.value, value: x.offersFullTime.baseSalary.value,
}, },
}, }
bonus: { : undefined,
bonus:
x.offersFullTime?.bonus != null
? {
create: { create: {
baseCurrency: baseCurrencyString, baseCurrency: baseCurrencyString,
baseValue: await convert( baseValue: await convert(
@ -500,10 +498,12 @@ export const offersProfileRouter = createRouter()
currency: x.offersFullTime.bonus.currency, currency: x.offersFullTime.bonus.currency,
value: x.offersFullTime.bonus.value, value: x.offersFullTime.bonus.value,
}, },
}, }
: undefined,
level: x.offersFullTime.level, level: x.offersFullTime.level,
specialization: x.offersFullTime.specialization, stocks:
stocks: { x.offersFullTime?.stocks != null
? {
create: { create: {
baseCurrency: baseCurrencyString, baseCurrency: baseCurrencyString,
baseValue: await convert( baseValue: await convert(
@ -514,7 +514,8 @@ export const offersProfileRouter = createRouter()
currency: x.offersFullTime.stocks.currency, currency: x.offersFullTime.stocks.currency,
value: x.offersFullTime.stocks.value, value: x.offersFullTime.stocks.value,
}, },
}, }
: undefined,
title: x.offersFullTime.title, title: x.offersFullTime.title,
totalCompensation: { totalCompensation: {
create: { create: {
@ -713,8 +714,8 @@ export const offersProfileRouter = createRouter()
data: { data: {
companyId: exp.companyId, // TODO: check if can change with connect or whether there is a difference companyId: exp.companyId, // TODO: check if can change with connect or whether there is a difference
durationInMonths: exp.durationInMonths, durationInMonths: exp.durationInMonths,
jobType: exp.jobType as JobType,
level: exp.level, level: exp.level,
specialization: exp.specialization,
}, },
where: { where: {
id: exp.id, id: exp.id,
@ -821,9 +822,9 @@ export const offersProfileRouter = createRouter()
jobType: exp.jobType, jobType: exp.jobType,
level: exp.level, level: exp.level,
location: exp.location, location: exp.location,
specialization: exp.specialization,
title: exp.title, title: exp.title,
totalCompensation: { totalCompensation: exp.totalCompensation
? {
create: { create: {
baseCurrency: baseCurrencyString, baseCurrency: baseCurrencyString,
baseValue: await convert( baseValue: await convert(
@ -834,7 +835,8 @@ export const offersProfileRouter = createRouter()
currency: exp.totalCompensation.currency, currency: exp.totalCompensation.currency,
value: exp.totalCompensation.value, value: exp.totalCompensation.value,
}, },
}, }
: undefined,
}, },
}, },
}, },
@ -851,7 +853,6 @@ export const offersProfileRouter = createRouter()
jobType: exp.jobType, jobType: exp.jobType,
level: exp.level, level: exp.level,
location: exp.location, location: exp.location,
specialization: exp.specialization,
title: exp.title, title: exp.title,
totalCompensation: { totalCompensation: {
create: { create: {
@ -887,7 +888,6 @@ export const offersProfileRouter = createRouter()
jobType: exp.jobType, jobType: exp.jobType,
level: exp.level, level: exp.level,
location: exp.location, location: exp.location,
specialization: exp.specialization,
title: exp.title, title: exp.title,
}, },
}, },
@ -905,7 +905,6 @@ export const offersProfileRouter = createRouter()
jobType: exp.jobType, jobType: exp.jobType,
level: exp.level, level: exp.level,
location: exp.location, location: exp.location,
specialization: exp.specialization,
title: exp.title, title: exp.title,
}, },
}, },
@ -945,7 +944,6 @@ export const offersProfileRouter = createRouter()
value: exp.monthlySalary.value, value: exp.monthlySalary.value,
}, },
}, },
specialization: exp.specialization,
title: exp.title, title: exp.title,
}, },
}, },
@ -974,7 +972,6 @@ export const offersProfileRouter = createRouter()
value: exp.monthlySalary.value, value: exp.monthlySalary.value,
}, },
}, },
specialization: exp.specialization,
title: exp.title, title: exp.title,
}, },
}, },
@ -997,7 +994,6 @@ export const offersProfileRouter = createRouter()
durationInMonths: exp.durationInMonths, durationInMonths: exp.durationInMonths,
jobType: exp.jobType, jobType: exp.jobType,
location: exp.location, location: exp.location,
specialization: exp.specialization,
title: exp.title, title: exp.title,
}, },
}, },
@ -1014,7 +1010,6 @@ export const offersProfileRouter = createRouter()
durationInMonths: exp.durationInMonths, durationInMonths: exp.durationInMonths,
jobType: exp.jobType, jobType: exp.jobType,
location: exp.location, location: exp.location,
specialization: exp.specialization,
title: exp.title, title: exp.title,
}, },
}, },
@ -1121,7 +1116,6 @@ export const offersProfileRouter = createRouter()
data: { data: {
internshipCycle: internshipCycle:
offerToUpdate.offersIntern.internshipCycle ?? undefined, offerToUpdate.offersIntern.internshipCycle ?? undefined,
specialization: offerToUpdate.offersIntern.specialization,
startYear: offerToUpdate.offersIntern.startYear ?? undefined, startYear: offerToUpdate.offersIntern.startYear ?? undefined,
title: offerToUpdate.offersIntern.title, title: offerToUpdate.offersIntern.title,
}, },
@ -1150,7 +1144,6 @@ export const offersProfileRouter = createRouter()
await ctx.prisma.offersFullTime.update({ await ctx.prisma.offersFullTime.update({
data: { data: {
level: offerToUpdate.offersFullTime.level ?? undefined, level: offerToUpdate.offersFullTime.level ?? undefined,
specialization: offerToUpdate.offersFullTime.specialization,
title: offerToUpdate.offersFullTime.title, title: offerToUpdate.offersFullTime.title,
}, },
where: { where: {
@ -1174,7 +1167,7 @@ export const offersProfileRouter = createRouter()
}, },
}); });
} }
if (offerToUpdate.offersFullTime.bonus) { if (offerToUpdate.offersFullTime.bonus != null) {
await ctx.prisma.offersCurrency.update({ await ctx.prisma.offersCurrency.update({
data: { data: {
baseCurrency: baseCurrencyString, baseCurrency: baseCurrencyString,
@ -1191,7 +1184,7 @@ export const offersProfileRouter = createRouter()
}, },
}); });
} }
if (offerToUpdate.offersFullTime.stocks) { if (offerToUpdate.offersFullTime.stocks != null) {
await ctx.prisma.offersCurrency.update({ await ctx.prisma.offersCurrency.update({
data: { data: {
baseCurrency: baseCurrencyString, baseCurrency: baseCurrencyString,
@ -1269,8 +1262,6 @@ export const offersProfileRouter = createRouter()
offerToUpdate.offersIntern.monthlySalary.value, offerToUpdate.offersIntern.monthlySalary.value,
}, },
}, },
specialization:
offerToUpdate.offersIntern.specialization,
startYear: offerToUpdate.offersIntern.startYear, startYear: offerToUpdate.offersIntern.startYear,
title: offerToUpdate.offersIntern.title, title: offerToUpdate.offersIntern.title,
}, },
@ -1286,12 +1277,6 @@ export const offersProfileRouter = createRouter()
if ( if (
offerToUpdate.jobType === JobType.FULLTIME && offerToUpdate.jobType === JobType.FULLTIME &&
offerToUpdate.offersFullTime && offerToUpdate.offersFullTime &&
offerToUpdate.offersFullTime.baseSalary?.currency != null &&
offerToUpdate.offersFullTime.baseSalary?.value != null &&
offerToUpdate.offersFullTime.bonus?.currency != null &&
offerToUpdate.offersFullTime.bonus?.value != null &&
offerToUpdate.offersFullTime.stocks?.currency != null &&
offerToUpdate.offersFullTime.stocks?.value != null &&
offerToUpdate.offersFullTime.totalCompensation?.currency != offerToUpdate.offersFullTime.totalCompensation?.currency !=
null && null &&
offerToUpdate.offersFullTime.totalCompensation?.value != null && offerToUpdate.offersFullTime.totalCompensation?.value != null &&
@ -1313,11 +1298,14 @@ export const offersProfileRouter = createRouter()
negotiationStrategy: offerToUpdate.negotiationStrategy, negotiationStrategy: offerToUpdate.negotiationStrategy,
offersFullTime: { offersFullTime: {
create: { create: {
baseSalary: { baseSalary:
offerToUpdate.offersFullTime?.baseSalary != null
? {
create: { create: {
baseCurrency: baseCurrencyString, baseCurrency: baseCurrencyString,
baseValue: await convert( baseValue: await convert(
offerToUpdate.offersFullTime.baseSalary.value, offerToUpdate.offersFullTime.baseSalary
.value,
offerToUpdate.offersFullTime.baseSalary offerToUpdate.offersFullTime.baseSalary
.currency, .currency,
baseCurrencyString, baseCurrencyString,
@ -1326,38 +1314,50 @@ export const offersProfileRouter = createRouter()
offerToUpdate.offersFullTime.baseSalary offerToUpdate.offersFullTime.baseSalary
.currency, .currency,
value: value:
offerToUpdate.offersFullTime.baseSalary.value, offerToUpdate.offersFullTime.baseSalary
}, .value,
}, },
bonus: { }
: undefined,
bonus:
offerToUpdate.offersFullTime?.bonus != null
? {
create: { create: {
baseCurrency: baseCurrencyString, baseCurrency: baseCurrencyString,
baseValue: await convert( baseValue: await convert(
offerToUpdate.offersFullTime.bonus.value, offerToUpdate.offersFullTime.bonus.value,
offerToUpdate.offersFullTime.bonus.currency, offerToUpdate.offersFullTime.bonus
.currency,
baseCurrencyString, baseCurrencyString,
), ),
currency: currency:
offerToUpdate.offersFullTime.bonus.currency, offerToUpdate.offersFullTime.bonus
value: offerToUpdate.offersFullTime.bonus.value, .currency,
}, value:
offerToUpdate.offersFullTime.bonus.value,
}, },
}
: undefined,
level: offerToUpdate.offersFullTime.level, level: offerToUpdate.offersFullTime.level,
specialization: stocks:
offerToUpdate.offersFullTime.specialization, offerToUpdate.offersFullTime?.stocks != null
stocks: { ? {
create: { create: {
baseCurrency: baseCurrencyString, baseCurrency: baseCurrencyString,
baseValue: await convert( baseValue: await convert(
offerToUpdate.offersFullTime.stocks.value, offerToUpdate.offersFullTime.stocks.value,
offerToUpdate.offersFullTime.stocks.currency, offerToUpdate.offersFullTime.stocks
.currency,
baseCurrencyString, baseCurrencyString,
), ),
currency: currency:
offerToUpdate.offersFullTime.stocks.currency, offerToUpdate.offersFullTime.stocks
value: offerToUpdate.offersFullTime.stocks.value, .currency,
}, value:
offerToUpdate.offersFullTime.stocks.value,
}, },
}
: undefined,
title: offerToUpdate.offersFullTime.title, title: offerToUpdate.offersFullTime.title,
totalCompensation: { totalCompensation: {
create: { create: {

@ -73,7 +73,7 @@ export const offersRouter = createRouter().query('list', {
const order = getOrder(input.sortBy.charAt(0)); const order = getOrder(input.sortBy.charAt(0));
const sortingKey = input.sortBy.substring(1); const sortingKey = input.sortBy.substring(1);
let data = !yoeRange const data = !yoeRange
? await ctx.prisma.offersOffer.findMany({ ? await ctx.prisma.offersOffer.findMany({
// Internship // Internship
include: { include: {
@ -303,11 +303,18 @@ export const offersRouter = createRouter().query('list', {
}, },
}); });
const startRecordIndex: number = input.limit * input.offset;
const endRecordIndex: number =
startRecordIndex + input.limit <= data.length
? startRecordIndex + input.limit
: data.length;
let paginatedData = data.slice(startRecordIndex, endRecordIndex);
// CONVERTING // CONVERTING
const currency = input.currency?.toUpperCase(); const currency = input.currency?.toUpperCase();
if (currency != null && currency in Currency) { if (currency != null && currency in Currency) {
data = await Promise.all( paginatedData = await Promise.all(
data.map(async (offer) => { paginatedData.map(async (offer) => {
if (offer.offersFullTime?.totalCompensation != null) { if (offer.offersFullTime?.totalCompensation != null) {
offer.offersFullTime.totalCompensation.value = offer.offersFullTime.totalCompensation.value =
await convertWithDate( await convertWithDate(
@ -317,6 +324,8 @@ export const offersRouter = createRouter().query('list', {
offer.offersFullTime.totalCompensation.updatedAt, offer.offersFullTime.totalCompensation.updatedAt,
); );
offer.offersFullTime.totalCompensation.currency = currency; offer.offersFullTime.totalCompensation.currency = currency;
if (offer.offersFullTime?.baseSalary != null) {
offer.offersFullTime.baseSalary.value = await convertWithDate( offer.offersFullTime.baseSalary.value = await convertWithDate(
offer.offersFullTime.baseSalary.value, offer.offersFullTime.baseSalary.value,
offer.offersFullTime.baseSalary.currency, offer.offersFullTime.baseSalary.currency,
@ -324,6 +333,9 @@ export const offersRouter = createRouter().query('list', {
offer.offersFullTime.baseSalary.updatedAt, offer.offersFullTime.baseSalary.updatedAt,
); );
offer.offersFullTime.baseSalary.currency = currency; offer.offersFullTime.baseSalary.currency = currency;
}
if (offer.offersFullTime?.stocks != null) {
offer.offersFullTime.stocks.value = await convertWithDate( offer.offersFullTime.stocks.value = await convertWithDate(
offer.offersFullTime.stocks.value, offer.offersFullTime.stocks.value,
offer.offersFullTime.stocks.currency, offer.offersFullTime.stocks.currency,
@ -331,6 +343,9 @@ export const offersRouter = createRouter().query('list', {
offer.offersFullTime.stocks.updatedAt, offer.offersFullTime.stocks.updatedAt,
); );
offer.offersFullTime.stocks.currency = currency; offer.offersFullTime.stocks.currency = currency;
}
if (offer.offersFullTime?.bonus != null) {
offer.offersFullTime.bonus.value = await convertWithDate( offer.offersFullTime.bonus.value = await convertWithDate(
offer.offersFullTime.bonus.value, offer.offersFullTime.bonus.value,
offer.offersFullTime.bonus.currency, offer.offersFullTime.bonus.currency,
@ -338,6 +353,7 @@ export const offersRouter = createRouter().query('list', {
offer.offersFullTime.bonus.updatedAt, offer.offersFullTime.bonus.updatedAt,
); );
offer.offersFullTime.bonus.currency = currency; offer.offersFullTime.bonus.currency = currency;
}
} else if (offer.offersIntern?.monthlySalary != null) { } else if (offer.offersIntern?.monthlySalary != null) {
offer.offersIntern.monthlySalary.value = await convertWithDate( offer.offersIntern.monthlySalary.value = await convertWithDate(
offer.offersIntern.monthlySalary.value, offer.offersIntern.monthlySalary.value,
@ -358,13 +374,6 @@ export const offersRouter = createRouter().query('list', {
); );
} }
const startRecordIndex: number = input.limit * input.offset;
const endRecordIndex: number =
startRecordIndex + input.limit <= data.length
? startRecordIndex + input.limit
: data.length;
const paginatedData = data.slice(startRecordIndex, endRecordIndex);
return getOffersResponseMapper( return getOffersResponseMapper(
paginatedData.map((offer) => dashboardOfferDtoMapper(offer)), paginatedData.map((offer) => dashboardOfferDtoMapper(offer)),
{ {

@ -0,0 +1,199 @@
import { z } from 'zod';
import { TRPCError } from '@trpc/server';
import { createProtectedRouter } from './context';
export const questionListRouter = createProtectedRouter()
.query('getListsByUser', {
async resolve({ ctx }) {
const userId = ctx.session?.user?.id;
return await ctx.prisma.questionsList.findMany({
include: {
questionEntries: {
include: {
question: true,
},
}
},
orderBy: {
createdAt: 'asc',
},
where: {
id: userId,
},
});
}
})
.query('getListById', {
input: z.object({
listId: z.string(),
}),
async resolve({ ctx }) {
const userId = ctx.session?.user?.id;
return await ctx.prisma.questionsList.findMany({
include: {
questionEntries: {
include: {
question: true,
},
}
},
orderBy: {
createdAt: 'asc',
},
where: {
id: userId,
},
});
}
})
.mutation('create', {
input: z.object({
name: z.string(),
}),
async resolve({ ctx, input }) {
const userId = ctx.session?.user?.id;
const { name } = input;
return await ctx.prisma.questionsList.create({
data: {
name,
userId,
},
});
},
})
.mutation('update', {
input: z.object({
id: z.string(),
name: z.string().optional(),
}),
async resolve({ ctx, input }) {
const userId = ctx.session?.user?.id;
const { name, id } = input;
const listToUpdate = await ctx.prisma.questionsList.findUnique({
where: {
id: input.id,
},
});
if (listToUpdate?.id !== userId) {
throw new TRPCError({
code: 'UNAUTHORIZED',
message: 'User have no authorization to record.',
});
}
return await ctx.prisma.questionsList.update({
data: {
name,
},
where: {
id,
},
});
},
})
.mutation('delete', {
input: z.object({
id: z.string(),
}),
async resolve({ ctx, input }) {
const userId = ctx.session?.user?.id;
const listToDelete = await ctx.prisma.questionsList.findUnique({
where: {
id: input.id,
},
});
if (listToDelete?.id !== userId) {
throw new TRPCError({
code: 'UNAUTHORIZED',
message: 'User have no authorization to record.',
});
}
return await ctx.prisma.questionsList.delete({
where: {
id: input.id,
},
});
},
})
.mutation('createQuestionEntry', {
input: z.object({
listId: z.string(),
questionId: z.string(),
}),
async resolve({ ctx, input }) {
const userId = ctx.session?.user?.id;
const listToAugment = await ctx.prisma.questionsList.findUnique({
where: {
id: input.listId,
},
});
if (listToAugment?.id !== userId) {
throw new TRPCError({
code: 'UNAUTHORIZED',
message: 'User have no authorization to record.',
});
}
const { questionId, listId } = input;
return await ctx.prisma.questionsListQuestionEntry.create({
data: {
listId,
questionId,
},
});
},
})
.mutation('deleteQuestionEntry', {
input: z.object({
id: z.string(),
}),
async resolve({ ctx, input }) {
const userId = ctx.session?.user?.id;
const entryToDelete = await ctx.prisma.questionsListQuestionEntry.findUnique({
where: {
id: input.id,
},
});
if (entryToDelete?.id !== userId) {
throw new TRPCError({
code: 'UNAUTHORIZED',
message: 'User have no authorization to record.',
});
}
const listToAugment = await ctx.prisma.questionsList.findUnique({
where: {
id: entryToDelete.listId,
},
});
if (listToAugment?.id !== userId) {
throw new TRPCError({
code: 'UNAUTHORIZED',
message: 'User have no authorization to record.',
});
}
return await ctx.prisma.questionsListQuestionEntry.delete({
where: {
id: input.id,
},
});
},
});

@ -26,7 +26,6 @@ export type Experience = {
level: string?; level: string?;
location: string?; location: string?;
monthlySalary: Valuation?; monthlySalary: Valuation?;
specialization: string?;
title: string?; title: string?;
totalCompensation: Valuation?; totalCompensation: Valuation?;
}; };
@ -87,12 +86,11 @@ export type ProfileOffer = {
}; };
export type FullTime = { export type FullTime = {
baseSalary: Valuation; baseSalary: Valuation?;
bonus: Valuation; bonus: Valuation?;
id: string; id: string;
level: string; level: string;
specialization: string; stocks: Valuation?;
stocks: Valuation;
title: string; title: string;
totalCompensation: Valuation; totalCompensation: Valuation;
}; };
@ -101,7 +99,6 @@ export type Intern = {
id: string; id: string;
internshipCycle: string; internshipCycle: string;
monthlySalary: Valuation; monthlySalary: Valuation;
specialization: string;
startYear: number; startYear: number;
title: string; title: string;
}; };
@ -163,7 +160,6 @@ export type AnalysisHighestOffer = {
id: string; id: string;
level: string; level: string;
location: string; location: string;
specialization: string;
totalYoe: number; totalYoe: number;
}; };
@ -178,7 +174,6 @@ export type AnalysisOffer = {
negotiationStrategy: string; negotiationStrategy: string;
previousCompanies: Array<string>; previousCompanies: Array<string>;
profileName: string; profileName: string;
specialization: string;
title: string; title: string;
totalYoe: number; totalYoe: number;
}; };

@ -19,9 +19,9 @@ const searchOfferPercentile = (
company: Company; company: Company;
offersFullTime: offersFullTime:
| (OffersFullTime & { | (OffersFullTime & {
baseSalary: OffersCurrency; baseSalary: OffersCurrency | null;
bonus: OffersCurrency; bonus: OffersCurrency | null;
stocks: OffersCurrency; stocks: OffersCurrency | null;
totalCompensation: OffersCurrency; totalCompensation: OffersCurrency;
}) })
| null; | null;

@ -32,6 +32,33 @@ export function cleanObject(object: any) {
return object; return object;
} }
/**
* Removes empty objects from an object.
* @param object
* @returns object without empty values or objects.
*/
export function removeEmptyObjects(object: any) {
Object.entries(object).forEach(([k, v]) => {
if ((v && typeof v === 'object') || Array.isArray(v)) {
removeEmptyObjects(v);
}
if (
v &&
typeof v === 'object' &&
!Object.keys(v).length &&
!Array.isArray(v)
) {
if (Array.isArray(object)) {
const index = object.indexOf(v);
object.splice(index, 1);
} else if (!(v instanceof Date)) {
delete object[k];
}
}
});
return object;
}
/** /**
* Removes invalid money data from an object. * Removes invalid money data from an object.
* If currency is present but value is not present, money object is removed. * If currency is present but value is not present, money object is removed.

@ -154,14 +154,14 @@ function TextInput(
switch (startAddOnType) { switch (startAddOnType) {
case 'label': case 'label':
return ( return (
<div className="pointer-events-none flex items-center pl-3 text-slate-500"> <div className="pointer-events-none flex items-center px-2 text-slate-500">
{startAddOn} {startAddOn}
</div> </div>
); );
case 'icon': { case 'icon': {
const StartAddOn = startAddOn; const StartAddOn = startAddOn;
return ( return (
<div className="pointer-events-none flex items-center pl-3"> <div className="pointer-events-none flex items-center px-2">
<StartAddOn <StartAddOn
aria-hidden="true" aria-hidden="true"
className="h-5 w-5 text-slate-400" className="h-5 w-5 text-slate-400"

Loading…
Cancel
Save