diff --git a/apps/portal/prisma/migrations/20221024123252_add_upvotes_to_schema/migration.sql b/apps/portal/prisma/migrations/20221024123252_add_upvotes_to_schema/migration.sql new file mode 100644 index 00000000..81d336ec --- /dev/null +++ b/apps/portal/prisma/migrations/20221024123252_add_upvotes_to_schema/migration.sql @@ -0,0 +1,14 @@ +/* + Warnings: + + - Added the required column `upvotes` to the `QuestionsAnswerComment` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "QuestionsAnswer" ADD COLUMN "upvotes" INTEGER NOT NULL DEFAULT 0; + +-- AlterTable +ALTER TABLE "QuestionsAnswerComment" ADD COLUMN "upvotes" INTEGER NOT NULL; + +-- AlterTable +ALTER TABLE "QuestionsQuestionComment" ADD COLUMN "upvotes" INTEGER NOT NULL DEFAULT 0; diff --git a/apps/portal/prisma/migrations/20221024123849_add_upvotes_default_value/migration.sql b/apps/portal/prisma/migrations/20221024123849_add_upvotes_default_value/migration.sql new file mode 100644 index 00000000..f4a342af --- /dev/null +++ b/apps/portal/prisma/migrations/20221024123849_add_upvotes_default_value/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "QuestionsAnswerComment" ALTER COLUMN "upvotes" SET DEFAULT 0; diff --git a/apps/portal/prisma/migrations/20221024154046_remove_specialization_and_make_base_bonus_and_stocks_optional/migration.sql b/apps/portal/prisma/migrations/20221024154046_remove_specialization_and_make_base_bonus_and_stocks_optional/migration.sql new file mode 100644 index 00000000..b6002921 --- /dev/null +++ b/apps/portal/prisma/migrations/20221024154046_remove_specialization_and_make_base_bonus_and_stocks_optional/migration.sql @@ -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"; diff --git a/apps/portal/prisma/schema.prisma b/apps/portal/prisma/schema.prisma index fb263f80..c2c6938b 100644 --- a/apps/portal/prisma/schema.prisma +++ b/apps/portal/prisma/schema.prisma @@ -234,7 +234,6 @@ model OffersExperience { // Add more fields durationInMonths Int? - specialization String? location String? // FULLTIME fields @@ -340,7 +339,6 @@ model OffersIntern { id String @id @default(cuid()) title String - specialization String internshipCycle String startYear Int monthlySalary OffersCurrency @relation(fields: [monthlySalaryId], references: [id], onDelete: Cascade) @@ -350,18 +348,17 @@ model OffersIntern { } model OffersFullTime { - id String @id @default(cuid()) + id String @id @default(cuid()) title String - specialization String level String - totalCompensation OffersCurrency @relation("OfferTotalCompensation", fields: [totalCompensationId], references: [id], onDelete: Cascade) - totalCompensationId String @unique - baseSalary OffersCurrency @relation("OfferBaseSalary", fields: [baseSalaryId], references: [id], onDelete: Cascade) - baseSalaryId String @unique - bonus OffersCurrency @relation("OfferBonus", fields: [bonusId], references: [id], onDelete: Cascade) - bonusId String @unique - stocks OffersCurrency @relation("OfferStocks", fields: [stocksId], references: [id], onDelete: Cascade) - stocksId String @unique + totalCompensation OffersCurrency @relation("OfferTotalCompensation", fields: [totalCompensationId], references: [id], onDelete: Cascade) + totalCompensationId String @unique + baseSalary OffersCurrency? @relation("OfferBaseSalary", fields: [baseSalaryId], references: [id], onDelete: Cascade) + baseSalaryId String? @unique + bonus OffersCurrency? @relation("OfferBonus", fields: [bonusId], references: [id], onDelete: Cascade) + bonusId String? @unique + stocks OffersCurrency? @relation("OfferStocks", fields: [stocksId], references: [id], onDelete: Cascade) + stocksId String? @unique OffersOffer OffersOffer? } @@ -454,6 +451,7 @@ model QuestionsQuestionComment { id String @id @default(cuid()) questionId String userId String? + upvotes Int @default(0) content String @db.Text createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -482,6 +480,7 @@ model QuestionsAnswer { questionId String userId String? content String @db.Text + upvotes Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -510,6 +509,7 @@ model QuestionsAnswerComment { answerId String userId String? content String @db.Text + upvotes Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/apps/portal/src/components/offers/OffersNavigation.ts b/apps/portal/src/components/offers/OffersNavigation.ts index cfa314dc..0c43c644 100644 --- a/apps/portal/src/components/offers/OffersNavigation.ts +++ b/apps/portal/src/components/offers/OffersNavigation.ts @@ -1,13 +1,14 @@ import type { ProductNavigationItems } from '~/components/global/ProductNavigation'; const navigation: ProductNavigationItems = [ - { href: '/offers/submit', name: 'Benchmark your offer' }, + { href: '/offers/browse', name: 'Browse' }, + { href: '/offers/submit', name: 'Analyse your offers' }, ]; const config = { navigation, showGlobalNav: false, - title: 'Tech Offers Repo', + title: 'Offer Profile Repository', titleHref: '/offers', }; diff --git a/apps/portal/src/components/offers/OffersTitle.tsx b/apps/portal/src/components/offers/OffersTitle.tsx index 2668ac4e..52798611 100644 --- a/apps/portal/src/components/offers/OffersTitle.tsx +++ b/apps/portal/src/components/offers/OffersTitle.tsx @@ -3,14 +3,14 @@ export default function OffersTitle() { <>

- Tech Handbook Offers Repo + Offer Profile Repository

Reveal profile stories behind offers
- Benchmark your offers and profiles, learn from other's offer profile, + Click into offers to view profiles, benchmark your offers and profiles, and discuss with the community
diff --git a/apps/portal/src/components/offers/constants.ts b/apps/portal/src/components/offers/constants.ts index 10e3b0b1..e2b14d96 100644 --- a/apps/portal/src/components/offers/constants.ts +++ b/apps/portal/src/components/offers/constants.ts @@ -2,26 +2,6 @@ import { EducationBackgroundType } from './types'; export const emptyOption = '----'; -// TODO: use enums -export const titleOptions = [ - { - label: 'Software Engineer', - value: 'Software Engineer', - }, - { - label: 'Frontend Engineer', - value: 'Frontend Engineer', - }, - { - label: 'Backend Engineer', - value: 'Backend Engineer', - }, - { - label: 'Full-stack Engineer', - value: 'Full-stack Engineer', - }, -]; - export const locationOptions = [ { label: 'Singapore, Singapore', diff --git a/apps/portal/src/components/offers/landing/LeftTextCard.tsx b/apps/portal/src/components/offers/landing/LeftTextCard.tsx new file mode 100644 index 00000000..329a58f5 --- /dev/null +++ b/apps/portal/src/components/offers/landing/LeftTextCard.tsx @@ -0,0 +1,55 @@ +import type { ReactNode } from 'react'; + +import { HOME_URL } from '~/components/offers/types'; + +type LeftTextCardProps = Readonly<{ + description: string; + icon: ReactNode; + imageAlt: string; + imageSrc: string; + title: string; +}>; + +export default function LeftTextCard({ + description, + icon, + imageAlt, + imageSrc, + title, +}: LeftTextCardProps) { + return ( +
+
+
+
+ + {icon} + +
+
+

+ {title} +

+

{description}

+
+ + Get started + +
+
+
+
+
+
+ {imageAlt} +
+
+
+ ); +} diff --git a/apps/portal/src/components/offers/landing/RightTextCard.tsx b/apps/portal/src/components/offers/landing/RightTextCard.tsx new file mode 100644 index 00000000..9028ad16 --- /dev/null +++ b/apps/portal/src/components/offers/landing/RightTextCard.tsx @@ -0,0 +1,55 @@ +import type { ReactNode } from 'react'; + +import { HOME_URL } from '~/components/offers/types'; + +type RightTextCarddProps = Readonly<{ + description: string; + icon: ReactNode; + imageAlt: string; + imageSrc: string; + title: string; +}>; + +export default function RightTextCard({ + description, + icon, + imageAlt, + imageSrc, + title, +}: RightTextCarddProps) { + return ( +
+
+
+
+ + {icon} + +
+
+

+ {title} +

+

{description}

+
+ + Get started + +
+
+
+
+
+
+ {imageAlt} +
+
+
+ ); +} diff --git a/apps/portal/src/components/offers/offerAnalysis/OfferProfileCard.tsx b/apps/portal/src/components/offers/offerAnalysis/OfferProfileCard.tsx index af786c4b..13cbd2d8 100644 --- a/apps/portal/src/components/offers/offerAnalysis/OfferProfileCard.tsx +++ b/apps/portal/src/components/offers/offerAnalysis/OfferProfileCard.tsx @@ -4,6 +4,9 @@ import { } from '@heroicons/react/24/outline'; 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 { convertMoneyToString } from '~/utils/offers/currency'; import { formatDate } from '~/utils/offers/time'; @@ -54,7 +57,9 @@ export default function OfferProfileCard({
-

{title}

+

+ {getLabelForJobTitleType(title as JobTitleType)} +

Company: {company.name}, {location}

diff --git a/apps/portal/src/components/offers/offersSubmission/OffersSubmissionForm.tsx b/apps/portal/src/components/offers/offersSubmission/OffersSubmissionForm.tsx index 95d43be8..39544921 100644 --- a/apps/portal/src/components/offers/offersSubmission/OffersSubmissionForm.tsx +++ b/apps/portal/src/components/offers/offersSubmission/OffersSubmissionForm.tsx @@ -115,7 +115,7 @@ export default function OffersSubmissionForm({ ), hasNext: true, hasPrevious: false, - label: 'Offer details', + label: 'Offers', }, { component: , @@ -125,28 +125,33 @@ export default function OffersSubmissionForm({ }, { component: ( - ), hasNext: true, hasPrevious: false, - label: 'Analysis', + label: 'Save profile', }, { component: ( - +
+
+ Result +
+ +
), hasNext: false, - hasPrevious: false, - label: 'Save', + hasPrevious: true, + label: 'Analysis', }, ]; diff --git a/apps/portal/src/components/offers/offersSubmission/submissionForm/BackgroundForm.tsx b/apps/portal/src/components/offers/offersSubmission/submissionForm/BackgroundForm.tsx index 6c89684a..8dcfae54 100644 --- a/apps/portal/src/components/offers/offersSubmission/submissionForm/BackgroundForm.tsx +++ b/apps/portal/src/components/offers/offersSubmission/submissionForm/BackgroundForm.tsx @@ -8,13 +8,17 @@ import { emptyOption, FieldError, locationOptions, - titleOptions, } from '~/components/offers/constants'; import type { BackgroundPostData } from '~/components/offers/types'; import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead'; +import JobTitlesTypeahead from '~/components/shared/JobTitlesTypahead'; -import { CURRENCY_OPTIONS } from '~/utils/offers/currency/CurrencyEnum'; +import { + Currency, + CURRENCY_OPTIONS, +} from '~/utils/offers/currency/CurrencyEnum'; +import FormMonthYearPicker from '../../forms/FormMonthYearPicker'; import FormRadioList from '../../forms/FormRadioList'; import FormSelect from '../../forms/FormSelect'; import FormTextInput from '../../forms/FormTextInput'; @@ -92,13 +96,13 @@ function FullTimeJobFields() { return ( <>
- +
+ + setValue(`background.experiences.0.title`, value) + } + /> +
@@ -112,6 +116,7 @@ function FullTimeJobFields() { endAddOn={
- -
-
+
+
- +
+ + setValue(`background.experiences.0.title`, value) + } + /> +
@@ -197,6 +197,7 @@ function InternshipJobFields() { endAddOn={
-
+
+ + +
@@ -319,13 +331,9 @@ function EducationSection() { export default function BackgroundForm() { return (
-
+
Help us better gauge your offers
-
- This section is mostly optional, but your background information helps - us benchmark your offers. -
diff --git a/apps/portal/src/components/offers/offersSubmission/submissionForm/OfferDetailsForm.tsx b/apps/portal/src/components/offers/offersSubmission/submissionForm/OfferDetailsForm.tsx index eb9fcc3f..5b64586d 100644 --- a/apps/portal/src/components/offers/offersSubmission/submissionForm/OfferDetailsForm.tsx +++ b/apps/portal/src/components/offers/offersSubmission/submissionForm/OfferDetailsForm.tsx @@ -13,6 +13,7 @@ import { JobType } from '@prisma/client'; import { Button, Dialog } from '@tih/ui'; import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead'; +import JobTitlesTypeahead from '~/components/shared/JobTitlesTypahead'; import { defaultFullTimeOfferValues, @@ -23,7 +24,6 @@ import { FieldError, internshipCycleOptions, locationOptions, - titleOptions, yearOptions, } from '../../constants'; import FormMonthYearPicker from '../../forms/FormMonthYearPicker'; @@ -32,7 +32,10 @@ import FormTextArea from '../../forms/FormTextArea'; import FormTextInput from '../../forms/FormTextInput'; import type { OfferFormData } from '../../types'; import { JobTypeLabel } from '../../types'; -import { CURRENCY_OPTIONS } from '../../../../utils/offers/currency/CurrencyEnum'; +import { + Currency, + CURRENCY_OPTIONS, +} from '../../../../utils/offers/currency/CurrencyEnum'; type FullTimeOfferDetailsFormProps = Readonly<{ index: number; @@ -64,32 +67,11 @@ function FullTimeOfferDetailsForm({ return (
- - -
-
- - setValue(`offers.${index}.companyId`, value) + setValue(`offers.${index}.offersFullTime.title`, value) } />
@@ -103,7 +85,15 @@ function FullTimeOfferDetailsForm({ })} />
-
+
+
+ + setValue(`offers.${index}.companyId`, value) + } + /> +
+
+
} @@ -180,13 +171,11 @@ function FullTimeOfferDetailsForm({ errorMessage={offerFields?.offersFullTime?.baseSalary?.value?.message} label="Base Salary (Annual)" placeholder="0" - required={true} startAddOn="$" startAddOnType="label" type="number" {...register(`offers.${index}.offersFullTime.baseSalary.value`, { min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 }, - required: FieldError.REQUIRED, valueAsNumber: true, })} /> @@ -194,25 +183,22 @@ function FullTimeOfferDetailsForm({ endAddOn={ } endAddOnType="element" errorMessage={offerFields?.offersFullTime?.bonus?.value?.message} label="Bonus (Annual)" placeholder="0" - required={true} startAddOn="$" startAddOnType="label" type="number" {...register(`offers.${index}.offersFullTime.bonus.value`, { min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 }, - required: FieldError.REQUIRED, valueAsNumber: true, })} /> @@ -222,25 +208,22 @@ function FullTimeOfferDetailsForm({ endAddOn={ } endAddOnType="element" errorMessage={offerFields?.offersFullTime?.stocks?.value?.message} label="Stocks (Annual)" placeholder="0" - required={true} startAddOn="$" startAddOnType="label" type="number" {...register(`offers.${index}.offersFullTime.stocks.value`, { min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 }, - required: FieldError.REQUIRED, valueAsNumber: true, })} /> @@ -291,32 +274,19 @@ function InternshipOfferDetailsForm({ return (
- - +
+ + setValue(`offers.${index}.offersIntern.title`, value) + } + /> +
setValue(`offers.${index}.companyId`, value) } @@ -374,6 +344,7 @@ function InternshipOfferDetailsForm({ endAddOn={
- {totalCompensation || - (monthlySalary && ( -
- -

- {totalCompensation && `TC: ${totalCompensation}`} - {monthlySalary && `Monthly Salary: ${monthlySalary}`} -

-
- ))} + {(totalCompensation || monthlySalary) && ( +
+ +

+ {totalCompensation && `TC: ${totalCompensation}`} + {monthlySalary && `Monthly Salary: ${monthlySalary}`} +

+
+ )} {totalCompensation && ( -
+

Base / year: {base} ⋅ Stocks / year: {stocks} ⋅ Bonus / year:{' '} {bonus} diff --git a/apps/portal/src/components/offers/profile/ProfileComments.tsx b/apps/portal/src/components/offers/profile/ProfileComments.tsx index 5a8ba614..b26f78ae 100644 --- a/apps/portal/src/components/offers/profile/ProfileComments.tsx +++ b/apps/portal/src/components/offers/profile/ProfileComments.tsx @@ -142,32 +142,40 @@ export default function ProfileComments({ />

Discussions

-
-