mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-29 21:23:14 +08:00
[offers][feat] add token check to profile and add company filter to table (#373)
* [offers][feat] add token check to profile, company filter to table and currency formatter * [offers][feat] add currency formatter
This commit is contained in:
@ -14,16 +14,16 @@ type Props = Readonly<{
|
|||||||
|
|
||||||
export default function OfferCard({
|
export default function OfferCard({
|
||||||
offer: {
|
offer: {
|
||||||
companyName = 'Meta',
|
base,
|
||||||
jobTitle = 'Senior Engineer',
|
bonus,
|
||||||
jobLevel,
|
companyName,
|
||||||
location = 'Singapore',
|
|
||||||
receivedMonth = 'Jun 2021',
|
|
||||||
totalCompensation = '350.1k',
|
|
||||||
base = '0k',
|
|
||||||
stocks = '0k',
|
|
||||||
bonus = '0k',
|
|
||||||
duration,
|
duration,
|
||||||
|
jobTitle,
|
||||||
|
jobLevel,
|
||||||
|
location,
|
||||||
|
receivedMonth,
|
||||||
|
totalCompensation,
|
||||||
|
stocks,
|
||||||
monthlySalary,
|
monthlySalary,
|
||||||
negotiationStrategy,
|
negotiationStrategy,
|
||||||
otherComment,
|
otherComment,
|
||||||
|
@ -19,7 +19,10 @@ export type OffersTableProps = Readonly<{
|
|||||||
companyFilter: string;
|
companyFilter: string;
|
||||||
jobTitleFilter: string;
|
jobTitleFilter: string;
|
||||||
}>;
|
}>;
|
||||||
export default function OffersTable({ jobTitleFilter }: OffersTableProps) {
|
export default function OffersTable({
|
||||||
|
companyFilter,
|
||||||
|
jobTitleFilter,
|
||||||
|
}: OffersTableProps) {
|
||||||
const [currency, setCurrency] = useState('SGD'); // TODO: Detect location
|
const [currency, setCurrency] = useState('SGD'); // TODO: Detect location
|
||||||
const [selectedTab, setSelectedTab] = useState(YOE_CATEGORY.ENTRY);
|
const [selectedTab, setSelectedTab] = useState(YOE_CATEGORY.ENTRY);
|
||||||
const [pagination, setPagination] = useState<PaginationType>({
|
const [pagination, setPagination] = useState<PaginationType>({
|
||||||
@ -42,10 +45,9 @@ export default function OffersTable({ jobTitleFilter }: OffersTableProps) {
|
|||||||
[
|
[
|
||||||
'offers.list',
|
'offers.list',
|
||||||
{
|
{
|
||||||
// Company: companyFilter, // TODO
|
companyId: companyFilter,
|
||||||
limit: NUMBER_OF_OFFERS_IN_PAGE,
|
limit: NUMBER_OF_OFFERS_IN_PAGE,
|
||||||
|
location: 'Singapore, Singapore', // TODO: Geolocation
|
||||||
location: 'Singapore, Singapore',
|
|
||||||
offset: pagination.currentPage - 1,
|
offset: pagination.currentPage - 1,
|
||||||
sortBy: '-monthYearReceived',
|
sortBy: '-monthYearReceived',
|
||||||
title: jobTitleFilter,
|
title: jobTitleFilter,
|
||||||
|
@ -137,11 +137,11 @@ type EducationDisplay = {
|
|||||||
export type OfferEntity = {
|
export type OfferEntity = {
|
||||||
base?: string;
|
base?: string;
|
||||||
bonus?: string;
|
bonus?: string;
|
||||||
companyName: string;
|
companyName?: string;
|
||||||
duration?: string;
|
duration?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
jobLevel?: string;
|
jobLevel?: string;
|
||||||
jobTitle: string;
|
jobTitle?: string;
|
||||||
location?: string;
|
location?: string;
|
||||||
monthlySalary?: string;
|
monthlySalary?: string;
|
||||||
negotiationStrategy?: string;
|
negotiationStrategy?: string;
|
||||||
|
@ -7,7 +7,7 @@ import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
|
|||||||
|
|
||||||
export default function OffersHomePage() {
|
export default function OffersHomePage() {
|
||||||
const [jobTitleFilter, setjobTitleFilter] = useState('Software Engineer');
|
const [jobTitleFilter, setjobTitleFilter] = useState('Software Engineer');
|
||||||
const [companyFilter, setCompanyFilter] = useState('All companies');
|
const [companyFilter, setCompanyFilter] = useState('');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex-1 overflow-y-auto">
|
<main className="flex-1 overflow-y-auto">
|
||||||
|
@ -8,6 +8,7 @@ import ProfileHeader from '~/components/offers/profile/ProfileHeader';
|
|||||||
import type { OfferEntity } from '~/components/offers/types';
|
import type { OfferEntity } from '~/components/offers/types';
|
||||||
import type { BackgroundCard } from '~/components/offers/types';
|
import type { BackgroundCard } from '~/components/offers/types';
|
||||||
|
|
||||||
|
import { convertCurrencyToString } from '~/utils/offers/currency';
|
||||||
import { formatDate } from '~/utils/offers/time';
|
import { formatDate } from '~/utils/offers/time';
|
||||||
import { trpc } from '~/utils/trpc';
|
import { trpc } from '~/utils/trpc';
|
||||||
export default function OfferProfile() {
|
export default function OfferProfile() {
|
||||||
@ -33,22 +34,24 @@ export default function OfferProfile() {
|
|||||||
if (!data) {
|
if (!data) {
|
||||||
router.push('/offers');
|
router.push('/offers');
|
||||||
}
|
}
|
||||||
|
// If the profile is not editable with a wrong token, redirect to the profile page
|
||||||
if (!data?.isEditable && token !== '') {
|
if (!data?.isEditable && token !== '') {
|
||||||
router.push(`/offers/profile/${offerProfileId}`);
|
router.push(`/offers/profile/${offerProfileId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsEditable(data?.isEditable ?? false);
|
setIsEditable(data?.isEditable ?? false);
|
||||||
|
|
||||||
|
if (data?.offers) {
|
||||||
const filteredOffers: Array<OfferEntity> = data
|
const filteredOffers: Array<OfferEntity> = data
|
||||||
? data?.offers.map((res) => {
|
? data?.offers.map((res) => {
|
||||||
if (res.OffersFullTime) {
|
if (res.OffersFullTime) {
|
||||||
const filteredOffer: OfferEntity = {
|
const filteredOffer: OfferEntity = {
|
||||||
base: res.OffersFullTime.baseSalary.value
|
base: convertCurrencyToString(
|
||||||
? `${res.OffersFullTime.baseSalary.value} ${res.OffersFullTime.baseSalary.currency}`
|
res.OffersFullTime.baseSalary.value,
|
||||||
: '',
|
),
|
||||||
bonus: res.OffersFullTime.bonus.value
|
bonus: convertCurrencyToString(
|
||||||
? `${res.OffersFullTime.bonus.value} ${res.OffersFullTime.bonus.currency}`
|
res.OffersFullTime.bonus.value,
|
||||||
: '',
|
),
|
||||||
companyName: res.company.name,
|
companyName: res.company.name,
|
||||||
id: res.OffersFullTime.id,
|
id: res.OffersFullTime.id,
|
||||||
jobLevel: res.OffersFullTime.level,
|
jobLevel: res.OffersFullTime.level,
|
||||||
@ -57,12 +60,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: res.OffersFullTime.stocks.value
|
stocks: convertCurrencyToString(res.OffersFullTime.stocks),
|
||||||
? `${res.OffersFullTime.stocks.value} ${res.OffersFullTime.stocks.currency}`
|
totalCompensation: convertCurrencyToString(
|
||||||
: '',
|
res.OffersFullTime.totalCompensation,
|
||||||
totalCompensation: res.OffersFullTime.totalCompensation.value
|
),
|
||||||
? `${res.OffersFullTime.totalCompensation.value} ${res.OffersFullTime.totalCompensation.currency}`
|
|
||||||
: '',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return filteredOffer;
|
return filteredOffer;
|
||||||
@ -72,11 +73,9 @@ export default function OfferProfile() {
|
|||||||
id: res.OffersIntern!.id,
|
id: res.OffersIntern!.id,
|
||||||
jobTitle: res.OffersIntern!.title,
|
jobTitle: res.OffersIntern!.title,
|
||||||
location: res.location,
|
location: res.location,
|
||||||
monthlySalary: res.OffersIntern!.monthlySalary.value
|
monthlySalary: convertCurrencyToString(
|
||||||
? `${res.OffersIntern!.monthlySalary.value} ${
|
res.OffersIntern!.monthlySalary,
|
||||||
res.OffersIntern!.monthlySalary.currency
|
),
|
||||||
}`
|
|
||||||
: '',
|
|
||||||
negotiationStrategy: res.negotiationStrategy || '',
|
negotiationStrategy: res.negotiationStrategy || '',
|
||||||
otherComment: res.comments || '',
|
otherComment: res.comments || '',
|
||||||
receivedMonth: formatDate(res.monthYearReceived),
|
receivedMonth: formatDate(res.monthYearReceived),
|
||||||
@ -85,6 +84,7 @@ export default function OfferProfile() {
|
|||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
setOffers(filteredOffers);
|
setOffers(filteredOffers);
|
||||||
|
}
|
||||||
|
|
||||||
if (data?.background) {
|
if (data?.background) {
|
||||||
const transformedBackground = {
|
const transformedBackground = {
|
||||||
@ -102,7 +102,9 @@ export default function OfferProfile() {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
experiences: [
|
experiences: [
|
||||||
{
|
data.background.experiences &&
|
||||||
|
data.background.experiences.length > 0
|
||||||
|
? {
|
||||||
companyName:
|
companyName:
|
||||||
data.background.experiences[0].company?.name ?? '-',
|
data.background.experiences[0].company?.name ?? '-',
|
||||||
duration:
|
duration:
|
||||||
@ -111,14 +113,18 @@ export default function OfferProfile() {
|
|||||||
jobLevel: data.background.experiences[0].level ?? '',
|
jobLevel: data.background.experiences[0].level ?? '',
|
||||||
jobTitle: data.background.experiences[0].title ?? '-',
|
jobTitle: data.background.experiences[0].title ?? '-',
|
||||||
monthlySalary: data.background.experiences[0].monthlySalary
|
monthlySalary: data.background.experiences[0].monthlySalary
|
||||||
?.value
|
? convertCurrencyToString(
|
||||||
? `${data.background.experiences[0].monthlySalary?.value} ${data.background.experiences[0].monthlySalary?.currency}`
|
data.background.experiences[0].monthlySalary,
|
||||||
: `-`,
|
)
|
||||||
|
: '-',
|
||||||
totalCompensation: data.background.experiences[0]
|
totalCompensation: data.background.experiences[0]
|
||||||
.totalCompensation?.value
|
.totalCompensation
|
||||||
? `${data.background.experiences[0].totalCompensation?.value} ${data.background.experiences[0].totalCompensation?.currency}`
|
? convertCurrencyToString(
|
||||||
: ``,
|
data.background.experiences[0].totalCompensation,
|
||||||
},
|
)
|
||||||
|
: '-',
|
||||||
|
}
|
||||||
|
: {},
|
||||||
],
|
],
|
||||||
profileName: data.profileName,
|
profileName: data.profileName,
|
||||||
specificYoes: data.background.specificYoes ?? [],
|
specificYoes: data.background.specificYoes ?? [],
|
||||||
@ -131,16 +137,22 @@ export default function OfferProfile() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const trpcContext = trpc.useContext();
|
const trpcContext = trpc.useContext();
|
||||||
const deleteMutation = trpc.useMutation(['offers.profile.delete']);
|
const deleteMutation = trpc.useMutation(['offers.profile.delete'], {
|
||||||
|
onError: () => {
|
||||||
|
alert('Error deleting profile'); // TODO: replace with toast
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
trpcContext.invalidateQueries(['offers.profile.listOne']);
|
||||||
|
router.push('/offers');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
if (isEditable) {
|
if (isEditable) {
|
||||||
deleteMutation.mutate({
|
deleteMutation.mutate({
|
||||||
profileId: offerProfileId as string,
|
profileId: offerProfileId as string,
|
||||||
token: 'CHANGE THIS PART TO URL PARAM @ ZIQING', // TODO: token: token as string,
|
token: token as string,
|
||||||
});
|
});
|
||||||
trpcContext.invalidateQueries(['offers.profile.listOne']);
|
|
||||||
router.push('/offers');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
14
apps/portal/src/utils/offers/currency/index.tsx
Normal file
14
apps/portal/src/utils/offers/currency/index.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import type { Money } from '~/components/offers/types';
|
||||||
|
|
||||||
|
export function convertCurrencyToString({ currency, value }: Money) {
|
||||||
|
if (!value) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
const formatter = new Intl.NumberFormat('en-US', {
|
||||||
|
currency,
|
||||||
|
maximumFractionDigits: 0, // (this suffices for whole numbers, but will print 2500.10 as $2,500.1)
|
||||||
|
minimumFractionDigits: 0, // (causes 2500.99 to be printed as $2,501)
|
||||||
|
style: 'currency',
|
||||||
|
});
|
||||||
|
return `${formatter.format(10000)}`; /* $2,500.00 */
|
||||||
|
}
|
Reference in New Issue
Block a user