Merge branch 'main' into hongpo/add-pagination-questions

pull/410/head
hpkoh 3 years ago
commit 9e8f2dd2ce

@ -85,8 +85,8 @@ function ProfileJewel() {
{({ active }) => ( {({ active }) => (
<Link <Link
className={clsx( className={clsx(
active ? 'bg-gray-100' : '', active ? 'bg-slate-100' : '',
'block px-4 py-2 text-sm text-gray-700', 'block px-4 py-2 text-sm text-slate-700',
)} )}
href={item.href} href={item.href}
onClick={item.onClick}> onClick={item.onClick}>
@ -178,9 +178,9 @@ export default function AppShell({ children }: Props) {
{/* Content area */} {/* Content area */}
<div className="flex h-screen flex-1 flex-col overflow-hidden"> <div className="flex h-screen flex-1 flex-col overflow-hidden">
<header className="w-full"> <header className="w-full">
<div className="relative z-10 flex h-16 flex-shrink-0 border-b border-gray-200 bg-white shadow-sm"> <div className="relative z-10 flex h-16 flex-shrink-0 border-b border-slate-200 bg-white shadow-sm">
<button <button
className="focus:ring-primary-500 border-r border-gray-200 px-4 text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset md:hidden" className="focus:ring-primary-500 border-r border-slate-200 px-4 text-slate-500 focus:outline-none focus:ring-2 focus:ring-inset md:hidden"
type="button" type="button"
onClick={() => setMobileMenuOpen(true)}> onClick={() => setMobileMenuOpen(true)}>
<span className="sr-only">Open sidebar</span> <span className="sr-only">Open sidebar</span>

@ -34,7 +34,7 @@ export default function MobileNavigation({
leave="transition-opacity ease-linear duration-300" leave="transition-opacity ease-linear duration-300"
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0"> leaveTo="opacity-0">
<div className="fixed inset-0 bg-gray-600 bg-opacity-75" /> <div className="fixed inset-0 bg-slate-600 bg-opacity-75" />
</Transition.Child> </Transition.Child>
<div className="fixed inset-0 z-40 flex"> <div className="fixed inset-0 z-40 flex">
<Transition.Child <Transition.Child

@ -9,12 +9,12 @@ export function Breadcrumbs({ stepLabels, currentStep }: BreadcrumbsProps) {
{stepLabels.map((label, index) => ( {stepLabels.map((label, index) => (
<div key={label} className="flex space-x-1"> <div key={label} className="flex space-x-1">
{index === currentStep ? ( {index === currentStep ? (
<p className="text-sm text-purple-700">{label}</p> <p className="text-primary-700 text-sm">{label}</p>
) : ( ) : (
<p className="text-sm text-gray-400">{label}</p> <p className="text-sm text-slate-400">{label}</p>
)} )}
{index !== stepLabels.length - 1 && ( {index !== stepLabels.length - 1 && (
<p className="text-sm text-gray-400">{'>'}</p> <p className="text-sm text-slate-400">{'>'}</p>
)} )}
</div> </div>
))} ))}

@ -2,11 +2,11 @@ export default function OffersTitle() {
return ( return (
<> <>
<div className="flex items-end justify-center"> <div className="flex items-end justify-center">
<h1 className="mt-16 text-center text-4xl font-bold text-indigo-600"> <h1 className="text-primary-600 mt-16 text-center text-4xl font-bold">
Tech Handbook Offers Repo Tech Handbook Offers Repo
</h1> </h1>
</div> </div>
<div className="mt-2 text-center text-2xl font-normal text-indigo-500"> <div className="text-primary-500 mt-2 text-center text-2xl font-normal">
Reveal profile stories behind offers Reveal profile stories behind offers
</div> </div>
<div className="items-top flex justify-center text-xl font-normal"> <div className="items-top flex justify-center text-xl font-normal">

@ -1,9 +1,9 @@
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useState } from 'react'; // Import { useState } from 'react';
import { setTimeout } from 'timers'; // import { setTimeout } from 'timers';
import { CheckIcon, DocumentDuplicateIcon } from '@heroicons/react/20/solid'; import { DocumentDuplicateIcon } from '@heroicons/react/20/solid';
import { BookmarkSquareIcon, EyeIcon } from '@heroicons/react/24/outline'; import { EyeIcon } from '@heroicons/react/24/outline';
import { Button, TextInput } from '@tih/ui'; import { Button, TextInput, useToast } from '@tih/ui';
import { import {
copyProfileLink, copyProfileLink,
@ -20,31 +20,31 @@ export default function OffersProfileSave({
profileId, profileId,
token, token,
}: OfferProfileSaveProps) { }: OfferProfileSaveProps) {
const [linkCopied, setLinkCopied] = useState(false); 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 router = useRouter();
const saveProfile = () => { // Const saveProfile = () => {
setSaving(true); // setSaving(true);
setTimeout(() => { // setTimeout(() => {
setSaving(false); // setSaving(false);
setSaved(true); // setSaved(true);
}, 5); // }, 5);
}; // };
return ( return (
<div className="flex w-full justify-center"> <div className="flex w-full justify-center">
<div className="max-w-2xl text-center"> <div className="max-w-2xl text-center">
<h5 className="mb-6 text-4xl font-bold text-gray-900"> <h5 className="mb-6 text-4xl font-bold text-slate-900">
Save for future edits Save for future edits
</h5> </h5>
<p className="mb-2 text-gray-900">We value your privacy.</p> <p className="mb-2 text-slate-900">We value your privacy.</p>
<p className="mb-5 text-gray-900"> <p className="mb-5 text-slate-900">
To keep you offer profile strictly anonymous, only people who have the To keep you offer profile strictly anonymous, only people who have the
link below can edit it. link below can edit it.
</p> </p>
<div className="mb-5 grid grid-cols-12 gap-4"> <div className="mb-20 grid grid-cols-12 gap-4">
<div className="col-span-11"> <div className="col-span-11">
<TextInput <TextInput
disabled={true} disabled={true}
@ -59,17 +59,15 @@ export default function OffersProfileSave({
label="Copy" label="Copy"
variant="primary" variant="primary"
onClick={() => { onClick={() => {
copyProfileLink(profileId, token), setLinkCopied(true); copyProfileLink(profileId, token);
showToast({
title: `Profile edit link copied to clipboard!`,
variant: 'success',
});
}} }}
/> />
</div> </div>
<div className="mb-20"> {/* <p className="mb-5 text-slate-900">
{linkCopied && (
<p className="text-purple-700">Link copied to clipboard!</p>
)}
</div>
<p className="mb-5 text-gray-900">
If you do not want to keep the edit link, you can opt to save this If you do not want to keep the edit link, you can opt to save this
profile under your user account. It will still only be editable by profile under your user account. It will still only be editable by
you. you.
@ -83,7 +81,7 @@ export default function OffersProfileSave({
variant="primary" variant="primary"
onClick={saveProfile} onClick={saveProfile}
/> />
</div> </div> */}
<div> <div>
<Button <Button
icon={EyeIcon} icon={EyeIcon}

@ -26,11 +26,11 @@ function YoeSection() {
const backgroundFields = formState.errors.background; const backgroundFields = formState.errors.background;
return ( return (
<> <>
<h6 className="mb-2 text-left text-xl font-medium text-gray-400"> <h6 className="mb-2 text-left text-xl font-medium text-slate-400">
Years of Experience (YOE) Years of Experience (YOE)
</h6> </h6>
<div className="mb-5 rounded-lg border border-gray-200 px-10 py-5"> <div className="mb-5 rounded-lg border border-slate-200 px-10 py-5">
<div className="mb-2 grid grid-cols-3 space-x-3"> <div className="mb-2 grid grid-cols-3 space-x-3">
<FormTextInput <FormTextInput
errorMessage={backgroundFields?.totalYoe?.message} errorMessage={backgroundFields?.totalYoe?.message}
@ -245,10 +245,10 @@ function CurrentJobSection() {
return ( return (
<> <>
<h6 className="mb-2 text-left text-xl font-medium text-gray-400"> <h6 className="mb-2 text-left text-xl font-medium text-slate-400">
Current / Previous Job Current / Previous Job
</h6> </h6>
<div className="mb-5 rounded-lg border border-gray-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={JobType.FULLTIME}
@ -282,10 +282,10 @@ function EducationSection() {
const { register } = useFormContext(); const { register } = useFormContext();
return ( return (
<> <>
<h6 className="mb-2 text-left text-xl font-medium text-gray-400"> <h6 className="mb-2 text-left text-xl font-medium text-slate-400">
Education Education
</h6> </h6>
<div className="mb-5 rounded-lg border border-gray-200 px-10 py-5"> <div className="mb-5 rounded-lg border border-slate-200 px-10 py-5">
<div className="mb-5 grid grid-cols-2 space-x-3"> <div className="mb-5 grid grid-cols-2 space-x-3">
<FormSelect <FormSelect
display="block" display="block"
@ -319,10 +319,10 @@ function EducationSection() {
export default function BackgroundForm() { export default function BackgroundForm() {
return ( return (
<div> <div>
<h5 className="mb-2 text-center text-4xl font-bold text-gray-900"> <h5 className="mb-2 text-center text-4xl font-bold text-slate-900">
Help us better gauge your offers Help us better gauge your offers
</h5> </h5>
<h6 className="text-md mx-10 mb-8 text-center font-light text-gray-600"> <h6 className="text-md mx-10 mb-8 text-center font-light text-slate-600">
This section is mostly optional, but your background information helps This section is mostly optional, but your background information helps
us benchmark your offers. us benchmark your offers.
</h6> </h6>

@ -62,7 +62,7 @@ function FullTimeOfferDetailsForm({
}, [watchCurrency, index, setValue]); }, [watchCurrency, index, setValue]);
return ( return (
<div className="my-5 rounded-lg border border-gray-200 px-10 py-5"> <div className="my-5 rounded-lg border border-slate-200 px-10 py-5">
<div className="mb-5 grid grid-cols-2 space-x-3"> <div className="mb-5 grid grid-cols-2 space-x-3">
<FormSelect <FormSelect
display="block" display="block"
@ -289,7 +289,7 @@ function InternshipOfferDetailsForm({
const offerFields = formState.errors.offers?.[index]; const offerFields = formState.errors.offers?.[index];
return ( return (
<div className="my-5 rounded-lg border border-gray-200 px-10 py-5"> <div className="my-5 rounded-lg border border-slate-200 px-10 py-5">
<div className="mb-5 grid grid-cols-2 space-x-3"> <div className="mb-5 grid grid-cols-2 space-x-3">
<FormSelect <FormSelect
display="block" display="block"
@ -503,7 +503,7 @@ export default function OfferDetailsForm({
return ( return (
<div className="mb-5"> <div className="mb-5">
<h5 className="mb-8 text-center text-4xl font-bold text-gray-900"> <h5 className="mb-8 text-center text-4xl font-bold text-slate-900">
Fill in your offer details Fill in your offer details
</h5> </h5>
<div className="flex w-full justify-center"> <div className="flex w-full justify-center">

@ -30,7 +30,7 @@ export default function EducationCard({
)} )}
</div> </div>
{(startDate || endDate) && ( {(startDate || endDate) && (
<div className="font-light text-gray-400"> <div className="font-light text-slate-400">
<p>{`${startDate || 'N/A'} - ${endDate || 'N/A'}`}</p> <p>{`${startDate || 'N/A'} - ${endDate || 'N/A'}`}</p>
</div> </div>
)} )}

@ -44,12 +44,12 @@ export default function OfferCard({
</div> </div>
</div> </div>
{!duration && receivedMonth && ( {!duration && receivedMonth && (
<div className="font-light text-gray-400"> <div className="font-light text-slate-400">
<p>{receivedMonth}</p> <p>{receivedMonth}</p>
</div> </div>
)} )}
{duration && ( {duration && (
<div className="font-light text-gray-400"> <div className="font-light text-slate-400">
<p>{`${duration} months`}</p> <p>{`${duration} months`}</p>
</div> </div>
)} )}
@ -83,7 +83,7 @@ export default function OfferCard({
</div> </div>
))} ))}
{totalCompensation && ( {totalCompensation && (
<div className="ml-6 flex flex-row font-light text-gray-400"> <div className="ml-6 flex flex-row font-light text-slate-400">
<p> <p>
Base / year: {base} Stocks / year: {stocks} Bonus / year:{' '} Base / year: {base} Stocks / year: {stocks} Bonus / year:{' '}
{bonus} {bonus}

@ -1,7 +1,13 @@
import { signIn, useSession } from 'next-auth/react'; import { signIn, useSession } from 'next-auth/react';
import { useState } from 'react'; import { useState } from 'react';
import { ClipboardDocumentIcon, ShareIcon } from '@heroicons/react/24/outline'; import { ClipboardDocumentIcon, ShareIcon } from '@heroicons/react/24/outline';
import { Button, HorizontalDivider, Spinner, TextArea } from '@tih/ui'; import {
Button,
HorizontalDivider,
Spinner,
TextArea,
useToast,
} from '@tih/ui';
import ExpandableCommentCard from '~/components/offers/profile/comments/ExpandableCommentCard'; import ExpandableCommentCard from '~/components/offers/profile/comments/ExpandableCommentCard';
@ -30,6 +36,7 @@ export default function ProfileComments({
const { data: session, status } = useSession(); const { data: session, status } = useSession();
const [currentReply, setCurrentReply] = useState<string>(''); const [currentReply, setCurrentReply] = useState<string>('');
const [replies, setReplies] = useState<Array<Reply>>(); const [replies, setReplies] = useState<Array<Reply>>();
const { showToast } = useToast();
const commentsQuery = trpc.useQuery( const commentsQuery = trpc.useQuery(
['offers.comments.getComments', { profileId }], ['offers.comments.getComments', { profileId }],
@ -51,6 +58,10 @@ export default function ProfileComments({
}); });
function handleComment(message: string) { function handleComment(message: string) {
if (!currentReply.length) {
return;
}
if (isEditable) { if (isEditable) {
// If it is with edit permission, send comment to API with username = null // If it is with edit permission, send comment to API with username = null
createCommentMutation.mutate( createCommentMutation.mutate(
@ -104,7 +115,13 @@ export default function ProfileComments({
label="Copy profile edit link" label="Copy profile edit link"
size="sm" size="sm"
variant="secondary" variant="secondary"
onClick={() => copyProfileLink(profileId, token)} onClick={() => {
copyProfileLink(profileId, token);
showToast({
title: `Profile edit link copied to clipboard!`,
variant: 'success',
});
}}
/> />
)} )}
<Button <Button
@ -115,7 +132,13 @@ export default function ProfileComments({
label="Copy public link" label="Copy public link"
size="sm" size="sm"
variant="secondary" variant="secondary"
onClick={() => copyProfileLink(profileId)} onClick={() => {
copyProfileLink(profileId);
showToast({
title: `Public profile link copied to clipboard!`,
variant: 'success',
});
}}
/> />
</div> </div>
<h2 className="mt-2 mb-6 text-2xl font-bold">Discussions</h2> <h2 className="mt-2 mb-6 text-2xl font-bold">Discussions</h2>
@ -131,7 +154,7 @@ export default function ProfileComments({
<div className="mt-2 flex w-full justify-end"> <div className="mt-2 flex w-full justify-end">
<div className="w-fit"> <div className="w-fit">
<Button <Button
disabled={commentsQuery.isLoading} disabled={commentsQuery.isLoading || !currentReply.length}
display="block" display="block"
isLabelHidden={false} isLabelHidden={false}
isLoading={createCommentMutation.isLoading} isLoading={createCommentMutation.isLoading}

@ -8,9 +8,9 @@ export default function ProfilePhotoHolder({
const sizeMap = { lg: '16', sm: '12' }; const sizeMap = { lg: '16', sm: '12' };
return ( return (
<span <span
className={`inline-block h-${sizeMap[size]} w-${sizeMap[size]} overflow-hidden rounded-full bg-gray-100`}> className={`inline-block h-${sizeMap[size]} w-${sizeMap[size]} overflow-hidden rounded-full bg-slate-100`}>
<svg <svg
className="h-full w-full text-gray-300" className="h-full w-full text-slate-300"
fill="currentColor" fill="currentColor"
viewBox="0 0 24 24"> viewBox="0 0 24 24">
<path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" /> <path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" />

@ -43,6 +43,10 @@ export default function CommentCard({
}); });
function handleReply() { function handleReply() {
if (!currentReply.length) {
return;
}
if (token && token.length > 0) { if (token && token.length > 0) {
// If it is with edit permission, send comment to API with username = null // If it is with edit permission, send comment to API with username = null
createCommentMutation.mutate( createCommentMutation.mutate(
@ -96,12 +100,12 @@ export default function CommentCard({
</div> </div>
<div className="mt-2 mb-2 flex flex-row ">{message}</div> <div className="mt-2 mb-2 flex flex-row ">{message}</div>
<div className="flex flex-row items-center justify-start space-x-4 "> <div className="flex flex-row items-center justify-start space-x-4 ">
<div className="flex flex-col text-sm font-light text-gray-400">{`${timeSinceNow( <div className="flex flex-col text-sm font-light text-slate-400">{`${timeSinceNow(
createdAt, createdAt,
)} ago`}</div> )} ago`}</div>
{replyLength > 0 && ( {replyLength > 0 && (
<div <div
className="flex cursor-pointer flex-col text-sm text-purple-600 hover:underline" className="text-primary-600 flex cursor-pointer flex-col text-sm hover:underline"
onClick={handleExpanded}> onClick={handleExpanded}>
{isExpanded ? `Hide replies` : `View replies (${replyLength})`} {isExpanded ? `Hide replies` : `View replies (${replyLength})`}
</div> </div>
@ -132,6 +136,7 @@ export default function CommentCard({
<div className="mt-2 flex w-full justify-end"> <div className="mt-2 flex w-full justify-end">
<div className="w-fit"> <div className="w-fit">
<Button <Button
disabled={!currentReply.length}
display="block" display="block"
isLabelHidden={false} isLabelHidden={false}
isLoading={createCommentMutation.isLoading} isLoading={createCommentMutation.isLoading}

@ -13,9 +13,9 @@ export default function OfferTableRow({
return ( return (
<tr <tr
key={id} key={id}
className="border-b bg-white hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:hover:bg-gray-600"> className="border-b bg-white hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-800 dark:hover:bg-slate-600">
<th <th
className="whitespace-nowrap py-4 px-6 font-medium text-gray-900 dark:text-white" className="whitespace-nowrap py-4 px-6 font-medium text-slate-900 dark:text-white"
scope="row"> scope="row">
{company.name} {company.name}
</th> </th>
@ -25,7 +25,7 @@ export default function OfferTableRow({
<td className="py-4 px-6">{formatDate(monthYearReceived)}</td> <td className="py-4 px-6">{formatDate(monthYearReceived)}</td>
<td className="space-x-4 py-4 px-6"> <td className="space-x-4 py-4 px-6">
<Link <Link
className="font-medium text-indigo-600 hover:underline dark:text-indigo-500" className="text-primary-600 dark:text-primary-500 font-medium hover:underline"
href={`/offers/profile/${profileId}`}> href={`/offers/profile/${profileId}`}>
View Profile View Profile
</Link> </Link>

@ -109,7 +109,7 @@ export default function OffersTable({
function renderHeader() { function renderHeader() {
return ( return (
<thead className="bg-gray-50 text-xs uppercase text-gray-700 dark:bg-gray-700 dark:text-gray-400"> <thead className="bg-slate-50 text-xs uppercase text-slate-700">
<tr> <tr>
{[ {[
'Company', 'Company',
@ -145,7 +145,7 @@ export default function OffersTable({
<Spinner display="block" size="lg" /> <Spinner display="block" size="lg" />
</div> </div>
) : ( ) : (
<table className="w-full text-left text-sm text-gray-500 dark:text-gray-400"> <table className="w-full text-left text-sm text-slate-500">
{renderHeader()} {renderHeader()}
<tbody> <tbody>
{offers.map((offer) => ( {offers.map((offer) => (

@ -19,13 +19,13 @@ export default function OffersTablePagination({
<nav <nav
aria-label="Table navigation" aria-label="Table navigation"
className="flex items-center justify-between p-4"> className="flex items-center justify-between p-4">
<span className="text-sm font-normal text-gray-500 dark:text-gray-400"> <span className="text-sm font-normal text-slate-500">
Showing Showing
<span className="font-semibold text-gray-900 dark:text-white"> <span className="font-semibold text-slate-900">
{` ${startNumber} - ${endNumber} `} {` ${startNumber} - ${endNumber} `}
</span> </span>
{`of `} {`of `}
<span className="font-semibold text-gray-900 dark:text-white"> <span className="font-semibold text-slate-900">
{pagination.totalItems} {pagination.totalItems}
</span> </span>
</span> </span>

@ -30,7 +30,7 @@ export default function ContributeQuestionCard({
return ( return (
<div> <div>
<button <button
className="flex flex-col items-stretch justify-center gap-2 rounded-md border border-slate-300 bg-white p-4 text-left hover:bg-gray-100" className="flex flex-col items-stretch justify-center gap-2 rounded-md border border-slate-300 bg-white p-4 text-left hover:bg-slate-100"
type="button" type="button"
onClick={handleOpenContribute}> onClick={handleOpenContribute}>
<TextInput <TextInput

@ -48,7 +48,7 @@ export default function ContributeQuestionDialog({
leave="ease-in duration-200" leave="ease-in duration-200"
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0"> leaveTo="opacity-0">
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" /> <div className="fixed inset-0 bg-slate-500 bg-opacity-75 transition-opacity" />
</Transition.Child> </Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto"> <div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0"> <div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
@ -66,7 +66,7 @@ export default function ContributeQuestionDialog({
<div className="mt-3 w-full sm:mt-0 sm:text-left"> <div className="mt-3 w-full sm:mt-0 sm:text-left">
<Dialog.Title <Dialog.Title
as="h3" as="h3"
className="text-lg font-medium leading-6 text-gray-900"> className="text-lg font-medium leading-6 text-slate-900">
Contribute question Contribute question
</Dialog.Title> </Dialog.Title>
<div className="w-full"> <div className="w-full">

@ -212,7 +212,7 @@ export default function BaseQuestionCard({
className={`group flex gap-4 rounded-md border border-slate-300 bg-white p-4 ${hoverClass}`}> className={`group flex gap-4 rounded-md border border-slate-300 bg-white p-4 ${hoverClass}`}>
{cardContent} {cardContent}
{showDeleteButton && ( {showDeleteButton && (
<div className="invisible self-center fill-red-700 group-hover:visible"> <div className="fill-danger-700 invisible self-center group-hover:visible">
<Button <Button
icon={TrashIcon} icon={TrashIcon}
isLabelHidden={true} isLabelHidden={true}

@ -186,13 +186,13 @@ export default function ContributeQuestionForm({
</div> </div>
<div className="flex gap-x-2"> <div className="flex gap-x-2">
<button <button
className="inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm" className="focus:ring-primary-500 inline-flex w-full justify-center rounded-md border border-slate-300 bg-white px-4 py-2 text-base font-medium text-slate-700 shadow-sm hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
type="button" type="button"
onClick={onDiscard}> onClick={onDiscard}>
Discard Discard
</button> </button>
<Button <Button
className="bg-primary-600 hover:bg-primary-700 focus:ring-primary-500 inline-flex w-full justify-center rounded-md border border-transparent px-4 py-2 text-base font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:bg-gray-400 sm:ml-3 sm:w-auto sm:text-sm" className="bg-primary-600 hover:bg-primary-700 focus:ring-primary-500 inline-flex w-full justify-center rounded-md border border-transparent px-4 py-2 text-base font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:bg-slate-400 sm:ml-3 sm:w-auto sm:text-sm"
disabled={!canSubmit} disabled={!canSubmit}
label="Contribute" label="Contribute"
type="submit" type="submit"

@ -14,8 +14,8 @@ export default function ResumeFilterPill({
return ( return (
<button <button
className={clsx( className={clsx(
'rounded-xl border border-indigo-500 border-transparent px-2 py-1 text-xs font-medium focus:bg-indigo-500 focus:text-white', 'border-primary-500 focus:bg-primary-500 rounded-xl border border-transparent px-2 py-1 text-xs font-medium focus:text-white',
isSelected ? 'bg-indigo-500 text-white' : 'bg-white text-indigo-500', isSelected ? 'bg-primary-500 text-white' : 'text-primary-500 bg-white',
)} )}
type="button" type="button"
onClick={onClick}> onClick={onClick}>

@ -22,7 +22,7 @@ export default function ResumeListItem({ href, resumeInfo }: Props) {
<div className="grid grid-cols-8 gap-4 border-b border-slate-200 p-4 hover:bg-slate-100"> <div className="grid grid-cols-8 gap-4 border-b border-slate-200 p-4 hover:bg-slate-100">
<div className="col-span-4"> <div className="col-span-4">
{resumeInfo.title} {resumeInfo.title}
<div className="mt-2 flex items-center justify-start text-xs text-indigo-500"> <div className="text-primary-500 mt-2 flex items-center justify-start text-xs">
<div className="flex"> <div className="flex">
<BriefcaseIcon <BriefcaseIcon
aria-hidden="true" aria-hidden="true"

@ -1,4 +1,5 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { formatDistanceToNow } from 'date-fns';
import { useState } from 'react'; import { useState } from 'react';
import { ChevronUpIcon } from '@heroicons/react/20/solid'; import { ChevronUpIcon } from '@heroicons/react/20/solid';
import { FaceSmileIcon } from '@heroicons/react/24/outline'; import { FaceSmileIcon } from '@heroicons/react/24/outline';
@ -26,12 +27,7 @@ export default function ResumeCommentListItem({
const [showReplies, setShowReplies] = useState(true); const [showReplies, setShowReplies] = useState(true);
return ( return (
<div <div className="min-w-fit">
className={clsx(
'min-w-fit rounded-md bg-white ',
!comment.parentId &&
'w-11/12 border-2 border-indigo-300 p-2 drop-shadow-md',
)}>
<div className="flex flex-row space-x-2 p-1 align-top"> <div className="flex flex-row space-x-2 p-1 align-top">
{/* Image Icon */} {/* Image Icon */}
{comment.user.image ? ( {comment.user.image ? (
@ -58,23 +54,22 @@ export default function ResumeCommentListItem({
<div className="flex flex-row items-center space-x-1"> <div className="flex flex-row items-center space-x-1">
<p <p
className={clsx( className={clsx(
'font-medium text-black', 'font-medium text-gray-800',
!!comment.parentId && 'text-sm', !!comment.parentId && 'text-sm',
)}> )}>
{comment.user.name ?? 'Reviewer ABC'} {comment.user.name ?? 'Reviewer ABC'}
</p> </p>
<p className="text-xs font-medium text-indigo-800"> <p className="text-primary-800 text-xs font-medium">
{isCommentOwner ? '(Me)' : ''} {isCommentOwner ? '(Me)' : ''}
</p> </p>
<ResumeUserBadges userId={comment.user.userId} /> <ResumeUserBadges userId={comment.user.userId} />
</div> </div>
<div className="px-2 text-xs text-gray-600"> <div className="px-2 text-xs text-slate-600">
{comment.createdAt.toLocaleString('en-US', { {formatDistanceToNow(comment.createdAt, {
dateStyle: 'medium', addSuffix: true,
timeStyle: 'short',
})} })}
</div> </div>
</div> </div>
@ -86,10 +81,12 @@ export default function ResumeCommentListItem({
setIsEditingComment={setIsEditingComment} setIsEditingComment={setIsEditingComment}
/> />
) : ( ) : (
<div className="text-gray-800">
<ResumeExpandableText <ResumeExpandableText
key={comment.description} key={comment.description}
text={comment.description} text={comment.description}
/> />
</div>
)} )}
{/* Upvote and edit */} {/* Upvote and edit */}
@ -101,7 +98,7 @@ export default function ResumeCommentListItem({
<> <>
{isCommentOwner && ( {isCommentOwner && (
<button <button
className="px-1 text-xs text-indigo-800 hover:text-indigo-400" className="text-primary-800 hover:text-primary-400 px-1 text-xs"
type="button" type="button"
onClick={() => setIsEditingComment(true)}> onClick={() => setIsEditingComment(true)}>
Edit Edit
@ -110,7 +107,7 @@ export default function ResumeCommentListItem({
{!comment.parentId && ( {!comment.parentId && (
<button <button
className="px-1 text-xs text-indigo-800 hover:text-indigo-400" className="text-primary-800 hover:text-primary-400 px-1 text-xs"
type="button" type="button"
onClick={() => setIsReplyingComment(true)}> onClick={() => setIsReplyingComment(true)}>
Reply Reply
@ -134,7 +131,7 @@ export default function ResumeCommentListItem({
{comment.children.length > 0 && ( {comment.children.length > 0 && (
<div className="min-w-fit space-y-1 pt-2"> <div className="min-w-fit space-y-1 pt-2">
<button <button
className="flex items-center space-x-1 rounded-md text-xs font-medium text-indigo-800 hover:text-indigo-300" className="text-primary-800 hover:text-primary-300 flex items-center space-x-1 rounded-md text-xs font-medium"
type="button" type="button"
onClick={() => setShowReplies(!showReplies)}> onClick={() => setShowReplies(!showReplies)}>
<ChevronUpIcon <ChevronUpIcon
@ -143,16 +140,24 @@ export default function ResumeCommentListItem({
!showReplies && 'rotate-180 transform', !showReplies && 'rotate-180 transform',
)} )}
/> />
<span>{showReplies ? 'Hide replies' : 'Show replies'}</span> <span>
{showReplies
? `Hide ${
comment.children.length === 1 ? 'reply' : 'replies'
}`
: `Show ${comment.children.length} ${
comment.children.length === 1 ? 'reply' : 'replies'
}`}
</span>
</button> </button>
{showReplies && ( {showReplies && (
<div className="flex flex-row"> <div className="flex flex-row">
<div className="relative flex flex-col px-2 py-2"> <div className="relative flex flex-col px-2 py-2">
<div className="flex-grow border-r border-gray-300" /> <div className="flex-grow border-r border-slate-300" />
</div> </div>
<div className="flex flex-col space-y-1"> <div className="flex flex-1 flex-col space-y-1">
{comment.children.map((child) => { {comment.children.map((child) => {
return ( return (
<ResumeCommentListItem <ResumeCommentListItem

@ -83,14 +83,14 @@ export default function ResumeCommentsForm({
}; };
return ( return (
<div className="h-[calc(100vh-13rem)] overflow-y-auto"> <div className="h-[calc(100vh-13rem)] overflow-y-auto pb-4">
<h2 className="text-xl font-semibold text-gray-800">Add your review</h2> <h2 className="text-xl font-semibold text-slate-800">Add your review</h2>
<p className="text-gray-800"> <p className="text-slate-800">
Please fill in at least one section to submit your review Please fill in at least one section to submit your review
</p> </p>
<form <form
className="w-full space-y-8 divide-y divide-gray-200" className="w-full space-y-8 divide-y divide-slate-200"
onSubmit={handleSubmit(onSubmit)}> onSubmit={handleSubmit(onSubmit)}>
<div className="mt-4 space-y-4"> <div className="mt-4 space-y-4">
<TextArea <TextArea

@ -1,7 +1,9 @@
import clsx from 'clsx';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import { import {
BookOpenIcon, BookOpenIcon,
BriefcaseIcon, BriefcaseIcon,
ChatBubbleLeftRightIcon,
CodeBracketSquareIcon, CodeBracketSquareIcon,
FaceSmileIcon, FaceSmileIcon,
IdentificationIcon, IdentificationIcon,
@ -9,24 +11,20 @@ import {
} from '@heroicons/react/24/outline'; } from '@heroicons/react/24/outline';
import { ResumesSection } from '@prisma/client'; import { ResumesSection } from '@prisma/client';
import { Spinner } from '@tih/ui'; import { Spinner } from '@tih/ui';
import { Button } from '@tih/ui';
import { trpc } from '~/utils/trpc'; import { trpc } from '~/utils/trpc';
import { RESUME_COMMENTS_SECTIONS } from './resumeCommentConstants'; import { RESUME_COMMENTS_SECTIONS } from './resumeCommentConstants';
import ResumeCommentListItem from './ResumeCommentListItem'; import ResumeCommentListItem from './ResumeCommentListItem';
import ResumeSignInButton from '../shared/ResumeSignInButton';
import type { ResumeComment } from '~/types/resume-comments'; import type { ResumeComment } from '~/types/resume-comments';
type ResumeCommentsListProps = Readonly<{ type ResumeCommentsListProps = Readonly<{
resumeId: string; resumeId: string;
setShowCommentsForm: (show: boolean) => void;
}>; }>;
export default function ResumeCommentsList({ export default function ResumeCommentsList({
resumeId, resumeId,
setShowCommentsForm,
}: ResumeCommentsListProps) { }: ResumeCommentsListProps) {
const { data: sessionData } = useSession(); const { data: sessionData } = useSession();
@ -50,31 +48,14 @@ export default function ResumeCommentsList({
} }
}; };
const renderButton = () => {
if (sessionData === null) {
return <ResumeSignInButton text="to join discussion" />;
}
return (
<Button
className="-mb-2"
display="block"
label="Add your review"
variant="tertiary"
onClick={() => setShowCommentsForm(true)}
/>
);
};
return ( return (
<div className="space-y-3"> <div className="space-y-3">
{renderButton()}
{commentsQuery.isLoading ? ( {commentsQuery.isLoading ? (
<div className="col-span-10 pt-4"> <div className="col-span-10 pt-4">
<Spinner display="block" size="lg" /> <Spinner display="block" size="lg" />
</div> </div>
) : ( ) : (
<div className="m-2 flow-root h-[calc(100vh-17rem)] w-full flex-col space-y-4 overflow-y-auto overflow-x-hidden pt-14 pb-6"> <div className="mb-8 flow-root h-[calc(100vh-13rem)] w-full flex-col space-y-4 overflow-y-auto overflow-x-hidden">
{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) => {
@ -85,12 +66,18 @@ export default function ResumeCommentsList({
return ( return (
<div key={value} className="mb-4 space-y-4"> <div key={value} className="mb-4 space-y-4">
<div className="flex flex-row items-center space-x-2 text-indigo-800"> <div className="text-primary-800 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
className={clsx(
'space-y-2 rounded-md border-2 bg-white px-4 py-3 drop-shadow-md',
commentCount ? 'border-slate-300' : 'border-slate-300',
)}>
{commentCount > 0 ? ( {commentCount > 0 ? (
comments.map((comment) => { comments.map((comment) => {
return ( return (
@ -102,9 +89,21 @@ export default function ResumeCommentsList({
); );
}) })
) : ( ) : (
<div>There are no comments for this section yet!</div> <div className="flex flex-row items-center text-sm">
<ChatBubbleLeftRightIcon className="mr-2 h-6 w-6 text-slate-500" />
<div className="text-slate-500">
There are no comments for this section yet!
</div>
</div>
)} )}
</div> </div>
</div>
<div className="relative flex flex-row pr-6 pt-2">
<div className="flex-grow border-t border-gray-300" />
</div>
</div>
); );
})} })}
</div> </div>

@ -1,40 +0,0 @@
import { useState } from 'react';
import ResumeCommentsForm from './ResumeCommentsForm';
import ResumeCommentsList from './ResumeCommentsList';
type CommentsSectionProps = {
resumeId: string;
};
export default function ResumeCommentsSection({
resumeId,
}: CommentsSectionProps) {
const [showCommentsForm, setShowCommentsForm] = useState(false);
return (
<>
<div className="relative p-2 lg:hidden">
<div aria-hidden="true" className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300" />
</div>
<div className="relative flex justify-center">
<span className="bg-gray-50 px-3 text-lg font-medium text-gray-900">
Reviews
</span>
</div>
</div>
{showCommentsForm ? (
<ResumeCommentsForm
resumeId={resumeId}
setShowCommentsForm={setShowCommentsForm}
/>
) : (
<ResumeCommentsList
resumeId={resumeId}
setShowCommentsForm={setShowCommentsForm}
/>
)}
</>
);
}

@ -86,18 +86,18 @@ export default function ResumeCommentVoteButtons({
'h-4 w-4', 'h-4 w-4',
commentVotesQuery.data?.userVote?.value === Vote.UPVOTE || commentVotesQuery.data?.userVote?.value === Vote.UPVOTE ||
upvoteAnimation upvoteAnimation
? 'fill-indigo-500' ? 'fill-primary-500'
: 'fill-gray-400', : 'fill-slate-400',
userId && userId &&
!downvoteAnimation && !downvoteAnimation &&
!upvoteAnimation && !upvoteAnimation &&
'hover:fill-indigo-500', 'hover:fill-primary-500',
upvoteAnimation && 'animate-[bounce_0.5s_infinite] cursor-default', upvoteAnimation && 'animate-[bounce_0.5s_infinite] cursor-default',
)} )}
/> />
</button> </button>
<div className="flex min-w-[1rem] justify-center text-xs"> <div className="flex min-w-[1rem] justify-center text-xs font-semibold text-gray-700">
{commentVotesQuery.data?.numVotes ?? 0} {commentVotesQuery.data?.numVotes ?? 0}
</div> </div>
@ -115,12 +115,12 @@ export default function ResumeCommentVoteButtons({
'h-4 w-4', 'h-4 w-4',
commentVotesQuery.data?.userVote?.value === Vote.DOWNVOTE || commentVotesQuery.data?.userVote?.value === Vote.DOWNVOTE ||
downvoteAnimation downvoteAnimation
? 'fill-red-500' ? 'fill-danger-500'
: 'fill-gray-400', : 'fill-slate-400',
userId && userId &&
!downvoteAnimation && !downvoteAnimation &&
!upvoteAnimation && !upvoteAnimation &&
'hover:fill-red-500', 'hover:fill-danger-500',
downvoteAnimation && downvoteAnimation &&
'animate-[bounce_0.5s_infinite] cursor-default', 'animate-[bounce_0.5s_infinite] cursor-default',
)} )}

@ -7,16 +7,16 @@ export function CallToAction() {
<section className="relative overflow-hidden py-32" id="get-started-today"> <section className="relative overflow-hidden py-32" id="get-started-today">
<Container className="relative"> <Container className="relative">
<div className="mx-auto max-w-lg text-center"> <div className="mx-auto max-w-lg text-center">
<h2 className="font-display text-3xl tracking-tight text-gray-900 sm:text-4xl"> <h2 className="font-display text-3xl tracking-tight text-slate-900 sm:text-4xl">
Resume review can start right now. Resume review can start right now.
</h2> </h2>
<p className="mt-4 text-lg tracking-tight text-gray-600"> <p className="mt-4 text-lg tracking-tight text-slate-600">
It's free! Take charge of your resume game by learning from the top It's free! Take charge of your resume game by learning from the top
engineers in the field. engineers in the field.
</p> </p>
<Link href="/resumes/browse"> <Link href="/resumes/browse">
<button <button
className="mt-4 rounded-md bg-indigo-500 py-2 px-3 text-sm font-medium text-white" className="bg-primary-500 mt-4 rounded-md py-2 px-3 text-sm font-medium text-white"
type="button"> type="button">
Start browsing now Start browsing now
</button> </button>

@ -7,7 +7,7 @@ export function Hero() {
<Container className="pb-36 pt-20 text-center lg:pt-32"> <Container className="pb-36 pt-20 text-center lg:pt-32">
<h1 className="font-display mx-auto max-w-4xl text-5xl font-medium tracking-tight text-slate-900 sm:text-7xl"> <h1 className="font-display mx-auto max-w-4xl text-5xl font-medium tracking-tight text-slate-900 sm:text-7xl">
Resume review{' '} Resume review{' '}
<span className="relative whitespace-nowrap text-indigo-500"> <span className="text-primary-500 relative whitespace-nowrap">
<svg <svg
aria-hidden="true" aria-hidden="true"
className="absolute top-2/3 left-0 h-[0.58em] w-full fill-blue-300/70" className="absolute top-2/3 left-0 h-[0.58em] w-full fill-blue-300/70"
@ -26,18 +26,18 @@ export function Hero() {
<div className="mt-10 flex justify-center gap-x-4"> <div className="mt-10 flex justify-center gap-x-4">
<Link href="/resumes/browse"> <Link href="/resumes/browse">
<button <button
className="rounded-md bg-indigo-500 py-2 px-3 text-sm font-medium text-white" className="bg-primary-500 rounded-md py-2 px-3 text-sm font-medium text-white"
type="button"> type="button">
Start browsing now Start browsing now
</button> </button>
</Link> </Link>
<Link href="https://www.youtube.com/watch?v=dQw4w9WgXcQ"> <Link href="https://www.youtube.com/watch?v=dQw4w9WgXcQ">
<button <button
className="group inline-flex items-center justify-center rounded-md py-2 px-4 text-sm ring-1 ring-slate-200 hover:text-slate-900 hover:ring-slate-300 focus:outline-none focus-visible:outline-indigo-600 focus-visible:ring-slate-300 active:bg-slate-100 active:text-slate-600" className="focus-visible:outline-primary-600 group inline-flex items-center justify-center rounded-md py-2 px-4 text-sm ring-1 ring-slate-200 hover:text-slate-900 hover:ring-slate-300 focus:outline-none focus-visible:ring-slate-300 active:bg-slate-100 active:text-slate-600"
type="button"> type="button">
<svg <svg
aria-hidden="true" aria-hidden="true"
className="h-3 w-3 flex-none fill-indigo-600 group-active:fill-current"> className="fill-primary-600 h-3 w-3 flex-none group-active:fill-current">
<path d="m9.997 6.91-7.583 3.447A1 1 0 0 1 1 9.447V2.553a1 1 0 0 1 1.414-.91L9.997 5.09c.782.355.782 1.465 0 1.82Z" /> <path d="m9.997 6.91-7.583 3.447A1 1 0 0 1 1 9.447V2.553a1 1 0 0 1 1.414-.91L9.997 5.09c.782.355.782 1.465 0 1.82Z" />
</svg> </svg>
<span className="ml-3">Watch video</span> <span className="ml-3">Watch video</span>

@ -49,7 +49,7 @@ export function PrimaryFeatures() {
return ( return (
<section <section
className="relative overflow-hidden bg-gradient-to-r from-indigo-400 to-indigo-700 pt-20 pb-28 sm:py-32" className="from-primary-400 to-primary-700 relative overflow-hidden bg-gradient-to-r pt-20 pb-28 sm:py-32"
id="features"> id="features">
<Container className="relative"> <Container className="relative">
<div className="max-w-2xl md:mx-auto md:text-center xl:max-w-none"> <div className="max-w-2xl md:mx-auto md:text-center xl:max-w-none">

@ -94,7 +94,7 @@ function QuoteIcon(props: QuoteProps) {
export function Testimonials() { export function Testimonials() {
return ( return (
<section <section
className="bg-gradient-to-r from-indigo-700 to-indigo-400 py-20 sm:py-32" className="from-primary-700 to-primary-400 bg-gradient-to-r py-20 sm:py-32"
id="testimonials"> id="testimonials">
<Container> <Container>
<div className="mx-auto max-w-2xl md:text-center"> <div className="mx-auto max-w-2xl md:text-center">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 652 KiB

After

Width:  |  Height:  |  Size: 586 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1006 KiB

After

Width:  |  Height:  |  Size: 909 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 437 KiB

After

Width:  |  Height:  |  Size: 396 KiB

@ -36,7 +36,7 @@ export default function ResumeExpandableText({
</span> </span>
{descriptionOverflow && ( {descriptionOverflow && (
<p <p
className="mt-1 cursor-pointer text-xs text-indigo-500 hover:text-indigo-300" className="text-primary-500 hover:text-primary-300 mt-1 cursor-pointer text-xs"
onClick={onSeeActionClicked}> onClick={onSeeActionClicked}>
{isExpanded ? 'See Less' : 'See More'} {isExpanded ? 'See Less' : 'See More'}
</p> </p>

@ -8,10 +8,10 @@ type Props = Readonly<{
export default function ResumeSignInButton({ text, className }: Props) { export default function ResumeSignInButton({ text, className }: Props) {
return ( return (
<div className={clsx('flex justify-center pt-4', className)}> <div className={clsx('flex justify-center', className)}>
<p> <p>
<a <a
className="text-primary-800 hover:text-primary-500" className="text-indigo-500 hover:text-indigo-600"
href="/api/auth/signin" href="/api/auth/signin"
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();

@ -9,6 +9,7 @@ type Props = Readonly<{
isLabelHidden?: boolean; isLabelHidden?: boolean;
onSelect: (option: TypeaheadOption) => void; onSelect: (option: TypeaheadOption) => void;
placeHolder?: string; placeHolder?: string;
required?: boolean;
}>; }>;
export default function CompaniesTypeahead({ export default function CompaniesTypeahead({
@ -16,6 +17,7 @@ export default function CompaniesTypeahead({
onSelect, onSelect,
isLabelHidden, isLabelHidden,
placeHolder, placeHolder,
required,
}: Props) { }: Props) {
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
const companies = trpc.useQuery([ const companies = trpc.useQuery([
@ -42,6 +44,7 @@ export default function CompaniesTypeahead({
})) ?? [] })) ?? []
} }
placeholder={placeHolder} placeholder={placeHolder}
required={required}
onQueryChange={setQuery} onQueryChange={setQuery}
onSelect={onSelect} onSelect={onSelect}
/> />

@ -0,0 +1,31 @@
export const JobTitleLabels = {
'ai-ml-engineer': 'AI/ML Engineer',
'algorithms-engineer': 'Algorithms Engineer',
'android-engineer': 'Android Software Engineer',
'applications-engineer': 'Applications Engineer',
'back-end-engineer': 'Back End Engineer',
'business-engineer': 'Business Engineer',
'data-engineer': 'Data Engineer',
'devops-engineer': 'DevOps Engineer',
'enterprise-engineer': 'Enterprise Engineer',
'front-end-engineer': 'Front End Engineer',
'hardware-engineer': 'Hardware Engineer',
'ios-engineer': 'iOS Software Engineer',
'mobile-engineer': 'Mobile Software Engineer (iOS + Android)',
'networks-engineer': 'Networks Engineer',
'partner-engineer': 'Partner Engineer',
'production-engineer': 'Production Engineer',
'research-engineer': 'Research Engineer',
'sales-engineer': 'Sales Engineer',
'security-engineer': 'Security Engineer',
'site-reliability-engineer': 'Site Reliability Engineer (SRE)',
'software-engineer': 'Software Engineer',
'systems-engineer': 'Systems Engineer',
'test-engineer': 'QA/Test Engineer (SDET)',
};
export type JobTitleType = keyof typeof JobTitleLabels;
export function getLabelForJobTitleType(jobTitle: JobTitleType): string {
return JobTitleLabels[jobTitle];
}

@ -0,0 +1,48 @@
import { useState } from 'react';
import type { TypeaheadOption } from '@tih/ui';
import { Typeahead } from '@tih/ui';
import { JobTitleLabels } from './JobTitles';
type Props = Readonly<{
disabled?: boolean;
isLabelHidden?: boolean;
onSelect: (option: TypeaheadOption) => void;
placeHolder?: string;
required?: boolean;
}>;
export default function JobTitlesTypeahead({
disabled,
onSelect,
isLabelHidden,
placeHolder,
required,
}: Props) {
const [query, setQuery] = useState('');
const options = Object.entries(JobTitleLabels)
.map(([slug, label]) => ({
id: slug,
label,
value: slug,
}))
.filter(
({ label }) =>
label.toLocaleLowerCase().indexOf(query.toLocaleLowerCase()) > -1,
);
return (
<Typeahead
disabled={disabled}
isLabelHidden={isLabelHidden}
label="Job Title"
noResultsMessage="No available job titles."
nullable={true}
options={options}
placeholder={placeHolder}
required={required}
onQueryChange={setQuery}
onSelect={onSelect}
/>
);
}

@ -2,7 +2,7 @@ import { Head, Html, Main, NextScript } from 'next/document';
export default function Document() { export default function Document() {
return ( return (
<Html className="h-full bg-gray-50"> <Html className="h-full bg-slate-50">
<Head /> <Head />
<body className="h-full overflow-hidden"> <body className="h-full overflow-hidden">
<Main /> <Main />

@ -12,7 +12,7 @@ export default function OffersHomePage() {
return ( return (
<main className="flex-1 overflow-y-auto"> <main className="flex-1 overflow-y-auto">
<div className="grid-rows grid h-1/2 bg-gray-100"> <div className="grid-rows grid h-1/2 bg-slate-100">
<OffersTitle /> <OffersTitle />
<div className="flex items-start justify-center"> <div className="flex items-start justify-center">
<div className="mt-4 flex items-center"> <div className="mt-4 flex items-center">

@ -11,6 +11,7 @@ import type {
OfferDisplayData, OfferDisplayData,
} from '~/components/offers/types'; } from '~/components/offers/types';
import { useToast } from '~/../../../packages/ui/dist';
import { convertMoneyToString } from '~/utils/offers/currency'; import { convertMoneyToString } from '~/utils/offers/currency';
import { getProfilePath } from '~/utils/offers/link'; import { getProfilePath } from '~/utils/offers/link';
import { formatDate } from '~/utils/offers/time'; import { formatDate } from '~/utils/offers/time';
@ -19,6 +20,7 @@ import { trpc } from '~/utils/trpc';
import type { Profile, ProfileAnalysis, ProfileOffer } from '~/types/offers'; import type { Profile, ProfileAnalysis, ProfileOffer } from '~/types/offers';
export default function OfferProfile() { export default function OfferProfile() {
const { showToast } = useToast();
const ErrorPage = ( const ErrorPage = (
<Error statusCode={404} title="Requested profile does not exist." /> <Error statusCode={404} title="Requested profile does not exist." />
); );
@ -131,11 +133,18 @@ 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: () => { onError: () => {
alert('Error deleting profile'); // TODO: replace with toast showToast({
title: `Error deleting offers profile.`,
variant: 'failure',
});
}, },
onSuccess: () => { onSuccess: () => {
trpcContext.invalidateQueries(['offers.profile.listOne']); trpcContext.invalidateQueries(['offers.profile.listOne']);
router.push('/offers'); router.push('/offers');
showToast({
title: `Offers profile successfully deleted!`,
variant: 'success',
});
}, },
}); });

@ -50,7 +50,7 @@ export default function ListPage() {
{lists.map((list) => ( {lists.map((list) => (
<li <li
key={list.id} key={list.id}
className={`flex items-center hover:bg-gray-50 ${ className={`flex items-center hover:bg-slate-50 ${
selectedList === list.id ? 'bg-primary-100' : '' selectedList === list.id ? 'bg-primary-100' : ''
}`}> }`}>
<button <button
@ -83,7 +83,7 @@ export default function ListPage() {
className={`${ className={`${
active active
? 'bg-violet-500 text-white' ? 'bg-violet-500 text-white'
: 'text-gray-900' : 'text-slate-900'
} group flex w-full items-center rounded-md px-2 py-2 text-sm`} } group flex w-full items-center rounded-md px-2 py-2 text-sm`}
type="button"> type="button">
Delete Delete

@ -3,7 +3,7 @@ import formatDistanceToNow from 'date-fns/formatDistanceToNow';
import Error from 'next/error'; import Error from 'next/error';
import Head from 'next/head'; import Head from 'next/head';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useSession } from 'next-auth/react'; import { signIn, useSession } from 'next-auth/react';
import { useState } from 'react'; import { useState } from 'react';
import { import {
AcademicCapIcon, AcademicCapIcon,
@ -14,9 +14,10 @@ import {
PencilSquareIcon, PencilSquareIcon,
StarIcon, StarIcon,
} from '@heroicons/react/20/solid'; } from '@heroicons/react/20/solid';
import { Spinner } from '@tih/ui'; import { Button, Spinner } from '@tih/ui';
import ResumeCommentsSection from '~/components/resumes/comments/ResumeCommentsSection'; import ResumeCommentsForm from '~/components/resumes/comments/ResumeCommentsForm';
import ResumeCommentsList from '~/components/resumes/comments/ResumeCommentsList';
import ResumePdf from '~/components/resumes/ResumePdf'; import ResumePdf from '~/components/resumes/ResumePdf';
import ResumeExpandableText from '~/components/resumes/shared/ResumeExpandableText'; import ResumeExpandableText from '~/components/resumes/shared/ResumeExpandableText';
@ -59,6 +60,7 @@ export default function ResumeReviewPage() {
session?.user?.id != null && session.user.id === detailsQuery.data?.userId; session?.user?.id != null && session.user.id === detailsQuery.data?.userId;
const [isEditMode, setIsEditMode] = useState(false); const [isEditMode, setIsEditMode] = useState(false);
const [showCommentsForm, setShowCommentsForm] = useState(false);
const onStarButtonClick = () => { const onStarButtonClick = () => {
if (session?.user?.id == null) { if (session?.user?.id == null) {
@ -81,6 +83,32 @@ export default function ResumeReviewPage() {
setIsEditMode(true); setIsEditMode(true);
}; };
const renderReviewButton = () => {
if (session === null) {
return (
<div className=" flex h-10 justify-center rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-[400] hover:cursor-pointer hover:bg-slate-50">
<a
href="/api/auth/signin"
onClick={(event) => {
event.preventDefault();
signIn();
}}>
Sign in to join discussion
</a>
</div>
);
}
return (
<Button
className="h-10 py-2"
display="block"
label="Add your review"
variant="tertiary"
onClick={() => setShowCommentsForm(true)}
/>
);
};
if (isEditMode && detailsQuery.data != null) { if (isEditMode && detailsQuery.data != null) {
return ( return (
<SubmitResumeForm <SubmitResumeForm
@ -120,12 +148,21 @@ export default function ResumeReviewPage() {
</Head> </Head>
<main className="h-[calc(100vh-2rem)] flex-1 space-y-2 overflow-y-auto py-4 px-8 xl:px-12 2xl:pr-16"> <main className="h-[calc(100vh-2rem)] flex-1 space-y-2 overflow-y-auto py-4 px-8 xl:px-12 2xl:pr-16">
<div className="flex justify-between"> <div className="flex justify-between">
<h1 className="text-2xl font-semibold leading-7 text-gray-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"> <div className="flex gap-4 xl:pr-4">
{userIsOwner && (
<button
className="p h-10 rounded-md border border-slate-300 bg-white py-1 px-2 text-center"
type="button"
onClick={onEditButtonClick}>
<PencilSquareIcon className="text-primary-600 hover:text-primary-300 h-6 w-6" />
</button>
)}
<button <button
className="isolate inline-flex h-10 items-center space-x-4 rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-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-sm 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}>
@ -141,7 +178,7 @@ export default function ResumeReviewPage() {
className={clsx( className={clsx(
detailsQuery.data?.stars.length detailsQuery.data?.stars.length
? 'text-orange-400' ? 'text-orange-400'
: 'text-gray-400', : 'text-slate-400',
)} )}
/> />
)} )}
@ -152,42 +189,36 @@ export default function ResumeReviewPage() {
{detailsQuery.data?._count.stars} {detailsQuery.data?._count.stars}
</span> </span>
</button> </button>
{userIsOwner && (
<button <div className="hidden xl:block">{renderReviewButton()}</div>
className="p h-10 rounded-md border border-gray-300 bg-white py-1 px-2 text-center"
type="button"
onClick={onEditButtonClick}>
<PencilSquareIcon className="h-6 w-6 text-indigo-600 hover:text-indigo-300" />
</button>
)}
</div> </div>
</div> </div>
<div className="flex flex-col lg:mt-0 lg:flex-row lg:flex-wrap lg:space-x-8"> <div className="flex flex-col lg:mt-0 lg:flex-row lg:flex-wrap lg:space-x-8">
<div className="mt-2 flex items-center text-sm text-gray-500"> <div className="mt-2 flex items-center text-sm text-slate-600 xl:mt-1">
<BriefcaseIcon <BriefcaseIcon
aria-hidden="true" aria-hidden="true"
className="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400" className="mr-1.5 h-5 w-5 flex-shrink-0 text-indigo-400"
/> />
{detailsQuery.data.role} {detailsQuery.data.role}
</div> </div>
<div className="flex items-center pt-2 text-sm text-gray-500"> <div className="flex items-center pt-2 text-sm text-slate-600 xl:pt-1">
<MapPinIcon <MapPinIcon
aria-hidden="true" aria-hidden="true"
className="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400" className="mr-1.5 h-5 w-5 flex-shrink-0 text-indigo-400"
/> />
{detailsQuery.data.location} {detailsQuery.data.location}
</div> </div>
<div className="flex items-center pt-2 text-sm text-gray-500"> <div className="flex items-center pt-2 text-sm text-slate-600 xl:pt-1">
<AcademicCapIcon <AcademicCapIcon
aria-hidden="true" aria-hidden="true"
className="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400" className="mr-1.5 h-5 w-5 flex-shrink-0 text-indigo-400"
/> />
{detailsQuery.data.experience} {detailsQuery.data.experience}
</div> </div>
<div className="flex items-center pt-2 text-sm text-gray-500"> <div className="flex items-center pt-2 text-sm text-slate-600 xl:pt-1">
<CalendarIcon <CalendarIcon
aria-hidden="true" aria-hidden="true"
className="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400" className="mr-1.5 h-5 w-5 flex-shrink-0 text-indigo-400"
/> />
{`Uploaded ${formatDistanceToNow(detailsQuery.data.createdAt, { {`Uploaded ${formatDistanceToNow(detailsQuery.data.createdAt, {
addSuffix: true, addSuffix: true,
@ -195,10 +226,10 @@ export default function ResumeReviewPage() {
</div> </div>
</div> </div>
{detailsQuery.data.additionalInfo && ( {detailsQuery.data.additionalInfo && (
<div className="flex items-start whitespace-pre-wrap pt-2 text-sm text-gray-500"> <div className="flex items-start whitespace-pre-wrap pt-2 text-sm text-slate-600 xl:pt-1">
<InformationCircleIcon <InformationCircleIcon
aria-hidden="true" aria-hidden="true"
className="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400" className="mr-1.5 h-5 w-5 flex-shrink-0 text-indigo-400"
/> />
<ResumeExpandableText <ResumeExpandableText
key={detailsQuery.data.additionalInfo} key={detailsQuery.data.additionalInfo}
@ -206,12 +237,35 @@ export default function ResumeReviewPage() {
/> />
</div> </div>
)} )}
<div className="flex w-full flex-col gap-6 py-4 lg:flex-row">
<div className="w-full lg:w-[780px]"> <div className="flex w-full flex-col gap-6 py-4 xl:flex-row xl:py-0">
<div className="w-full xl:w-1/2">
<ResumePdf url={detailsQuery.data.url} /> <ResumePdf url={detailsQuery.data.url} />
</div> </div>
<div className="grow"> <div className="grow">
<ResumeCommentsSection resumeId={resumeId as string} /> <div className="relative p-2 xl:hidden">
<div
aria-hidden="true"
className="absolute inset-0 flex items-center">
<div className="w-full border-t border-slate-300" />
</div>
<div className="relative flex justify-center">
<span className="bg-slate-50 px-3 text-lg font-medium text-slate-900">
Reviews
</span>
</div>
</div>
<div className="mb-4 xl:hidden">{renderReviewButton()}</div>
{showCommentsForm ? (
<ResumeCommentsForm
resumeId={resumeId as string}
setShowCommentsForm={setShowCommentsForm}
/>
) : (
<ResumeCommentsList resumeId={resumeId as string} />
)}
</div> </div>
</div> </div>
</main> </main>

@ -113,7 +113,7 @@ export default function ResumeHomePage() {
useEffect(() => { useEffect(() => {
setCurrentPage(1); setCurrentPage(1);
}, [userFilters, sortOrder]); }, [userFilters, sortOrder, searchValue]);
const allResumesQuery = trpc.useQuery( const allResumesQuery = trpc.useQuery(
[ [
@ -126,6 +126,7 @@ export default function ResumeHomePage() {
searchValue: useDebounceValue(searchValue, DEBOUNCE_DELAY), searchValue: useDebounceValue(searchValue, DEBOUNCE_DELAY),
skip, skip,
sortOrder, sortOrder,
take: PAGE_LIMIT,
}, },
], ],
{ {
@ -144,6 +145,7 @@ export default function ResumeHomePage() {
searchValue: useDebounceValue(searchValue, DEBOUNCE_DELAY), searchValue: useDebounceValue(searchValue, DEBOUNCE_DELAY),
skip, skip,
sortOrder, sortOrder,
take: PAGE_LIMIT,
}, },
], ],
{ {
@ -163,6 +165,7 @@ export default function ResumeHomePage() {
searchValue: useDebounceValue(searchValue, DEBOUNCE_DELAY), searchValue: useDebounceValue(searchValue, DEBOUNCE_DELAY),
skip, skip,
sortOrder, sortOrder,
take: PAGE_LIMIT,
}, },
], ],
{ {
@ -279,11 +282,11 @@ export default function ResumeHomePage() {
leaveTo="translate-x-full"> leaveTo="translate-x-full">
<Dialog.Panel className="relative ml-auto flex h-full w-full max-w-xs flex-col overflow-y-scroll bg-white py-4 pb-12 shadow-xl"> <Dialog.Panel className="relative ml-auto flex h-full w-full max-w-xs flex-col overflow-y-scroll bg-white py-4 pb-12 shadow-xl">
<div className="flex items-center justify-between px-4"> <div className="flex items-center justify-between px-4">
<h2 className="text-lg font-medium text-gray-900"> <h2 className="text-lg font-medium text-slate-900">
Shortcuts Shortcuts
</h2> </h2>
<button <button
className="-mr-2 flex h-10 w-10 items-center justify-center rounded-md bg-white p-2 text-gray-400" className="-mr-2 flex h-10 w-10 items-center justify-center rounded-md bg-white p-2 text-slate-400"
type="button" type="button"
onClick={() => setMobileFiltersOpen(false)}> onClick={() => setMobileFiltersOpen(false)}>
<span className="sr-only">Close menu</span> <span className="sr-only">Close menu</span>
@ -291,9 +294,9 @@ export default function ResumeHomePage() {
</button> </button>
</div> </div>
<form className="mt-4 border-t border-gray-200"> <form className="mt-4 border-t border-slate-200">
<ul <ul
className="flex flex-wrap justify-start gap-4 px-4 py-3 font-medium text-gray-900" className="flex flex-wrap justify-start gap-4 px-4 py-3 font-medium text-slate-900"
role="list"> role="list">
{SHORTCUTS.map((shortcut) => ( {SHORTCUTS.map((shortcut) => (
<li key={shortcut.name}> <li key={shortcut.name}>
@ -310,12 +313,12 @@ export default function ResumeHomePage() {
<Disclosure <Disclosure
key={filter.id} key={filter.id}
as="div" as="div"
className="border-t border-gray-200 px-4 py-6"> className="border-t border-slate-200 px-4 py-6">
{({ open }) => ( {({ open }) => (
<> <>
<h3 className="-mx-2 -my-3 flow-root"> <h3 className="-mx-2 -my-3 flow-root">
<Disclosure.Button className="flex w-full items-center justify-between bg-white px-2 py-3 text-gray-400 hover:text-gray-500"> <Disclosure.Button className="flex w-full items-center justify-between bg-white px-2 py-3 text-slate-400 hover:text-slate-500">
<span className="font-medium text-gray-900"> <span className="font-medium text-slate-900">
{filter.label} {filter.label}
</span> </span>
<span className="ml-6 flex items-center"> <span className="ml-6 flex items-center">
@ -338,7 +341,7 @@ export default function ResumeHomePage() {
{filter.options.map((option) => ( {filter.options.map((option) => (
<div <div
key={option.value} key={option.value}
className="[&>div>div:nth-child(2)>label]:font-normal [&>div>div:nth-child(1)>input]:text-indigo-600 [&>div>div:nth-child(1)>input]:ring-indigo-500"> className="[&>div>div:nth-child(1)>input]:text-primary-600 [&>div>div:nth-child(1)>input]:ring-primary-500 [&>div>div:nth-child(2)>label]:font-normal">
<CheckboxInput <CheckboxInput
label={option.label} label={option.label}
value={userFilters[filter.id].includes( value={userFilters[filter.id].includes(
@ -368,16 +371,16 @@ export default function ResumeHomePage() {
</Transition.Root> </Transition.Root>
</div> </div>
<main className="h-[calc(100vh-4rem)] flex-auto overflow-y-scroll px-8 pt-6 pb-4"> <main className="h-[calc(100vh-4rem)] flex-auto px-8 pb-4">
<div className="flex justify-start"> <div className="flex justify-start">
<div className="hidden w-1/6 pt-2 lg:block"> <div className="fixed top-0 bottom-0 mt-24 hidden w-64 overflow-auto lg:block">
<h3 className="text-md font-medium tracking-tight text-gray-900"> <h3 className="text-md font-medium tracking-tight text-gray-900">
Shortcuts Shortcuts
</h3> </h3>
<div className="w-100 pt-4 sm:pr-0 md:pr-4"> <div className="w-100 pt-4 sm:pr-0 md:pr-4">
<form> <form>
<ul <ul
className="flex w-11/12 flex-wrap justify-start gap-2 pb-6 text-sm font-medium text-gray-900" className="flex w-11/12 flex-wrap justify-start gap-2 pb-6 text-sm font-medium text-slate-900"
role="list"> role="list">
{SHORTCUTS.map((shortcut) => ( {SHORTCUTS.map((shortcut) => (
<li key={shortcut.name}> <li key={shortcut.name}>
@ -389,19 +392,19 @@ export default function ResumeHomePage() {
</li> </li>
))} ))}
</ul> </ul>
<h3 className="text-md font-medium tracking-tight text-gray-900"> <h3 className="text-md font-medium tracking-tight text-slate-900">
Explore these filters Explore these filters
</h3> </h3>
{filters.map((filter) => ( {filters.map((filter) => (
<Disclosure <Disclosure
key={filter.id} key={filter.id}
as="div" as="div"
className="border-b border-gray-200 py-6"> className="border-b border-slate-200 py-6">
{({ open }) => ( {({ open }) => (
<> <>
<h3 className="-my-3 flow-root"> <h3 className="-my-3 flow-root">
<Disclosure.Button className="flex w-full items-center justify-between py-3 text-sm text-gray-400 hover:text-gray-500"> <Disclosure.Button className="flex w-full items-center justify-between py-3 text-sm text-slate-400 hover:text-slate-500">
<span className="font-medium text-gray-900"> <span className="font-medium text-slate-900">
{filter.label} {filter.label}
</span> </span>
<span className="ml-6 flex items-center"> <span className="ml-6 flex items-center">
@ -428,7 +431,7 @@ export default function ResumeHomePage() {
{filter.options.map((option) => ( {filter.options.map((option) => (
<div <div
key={option.value} key={option.value}
className="[&>div>div:nth-child(2)>label]:font-normal [&>div>div:nth-child(1)>input]:text-indigo-600 [&>div>div:nth-child(1)>input]:ring-indigo-500"> className="[&>div>div:nth-child(1)>input]:text-primary-600 [&>div>div:nth-child(1)>input]:ring-primary-500 [&>div>div:nth-child(2)>label]:font-normal">
<CheckboxInput <CheckboxInput
label={option.label} label={option.label}
value={userFilters[filter.id].includes( value={userFilters[filter.id].includes(
@ -453,8 +456,8 @@ export default function ResumeHomePage() {
</form> </form>
</div> </div>
</div> </div>
<div className="w-full"> <div className="relative lg:left-64 lg:w-[calc(100%-16rem)]">
<div className="lg:border-grey-200 flex flex-wrap items-center justify-between pb-2 lg:border-b"> <div className="lg:border-grey-200 sticky top-0 z-10 flex flex-wrap items-center justify-between bg-gray-50 pt-6 pb-2 lg:border-b">
<div className="border-grey-200 mb-4 flex w-full justify-between border-b pb-2 lg:mb-0 lg:w-auto lg:border-none lg:pb-0"> <div className="border-grey-200 mb-4 flex w-full justify-between border-b pb-2 lg:mb-0 lg:w-auto lg:border-none lg:pb-0">
<div> <div>
<Tabs <Tabs
@ -477,10 +480,9 @@ export default function ResumeHomePage() {
onChange={onTabChange} onChange={onTabChange}
/> />
</div> </div>
<div> <div>
<button <button
className="ml-4 rounded-md bg-indigo-500 py-2 px-3 text-sm font-medium text-white lg:hidden" className="bg-primary-500 ml-4 rounded-md py-2 px-3 text-sm font-medium text-white lg:hidden"
type="button" type="button"
onClick={onSubmitResume}> onClick={onSubmitResume}>
Submit Resume Submit Resume
@ -489,9 +491,9 @@ export default function ResumeHomePage() {
</div> </div>
<div className="flex flex-wrap items-center justify-start gap-8"> <div className="flex flex-wrap items-center justify-start gap-8">
<div className="w-64"> <div className="w-64">
<form>
<TextInput <TextInput
label="" isLabelHidden={true}
label="search"
placeholder="Search Resumes" placeholder="Search Resumes"
startAddOn={MagnifyingGlassIcon} startAddOn={MagnifyingGlassIcon}
startAddOnType="icon" startAddOnType="icon"
@ -499,7 +501,6 @@ export default function ResumeHomePage() {
value={searchValue} value={searchValue}
onChange={setSearchValue} onChange={setSearchValue}
/> />
</form>
</div> </div>
<div> <div>
<DropdownMenu align="end" label={SORT_OPTIONS[sortOrder]}> <DropdownMenu align="end" label={SORT_OPTIONS[sortOrder]}>
@ -513,16 +514,15 @@ export default function ResumeHomePage() {
</DropdownMenu> </DropdownMenu>
</div> </div>
<button <button
className="-m-2 text-gray-400 hover:text-gray-500 lg:hidden" className="-m-2 text-slate-400 hover:text-slate-500 lg:hidden"
type="button" type="button"
onClick={() => setMobileFiltersOpen(true)}> onClick={() => setMobileFiltersOpen(true)}>
<span className="sr-only">Filters</span> <span className="sr-only">Filters</span>
<FunnelIcon aria-hidden="true" className="h-6 w-6" /> <FunnelIcon aria-hidden="true" className="h-6 w-6" />
</button> </button>
<div> <div>
<button <button
className="hidden w-36 rounded-md bg-indigo-500 py-2 px-3 text-sm font-medium text-white lg:block" className="bg-primary-500 hidden w-36 rounded-md py-2 px-3 text-sm font-medium text-white lg:block"
type="button" type="button"
onClick={onSubmitResume}> onClick={onSubmitResume}>
Submit Resume Submit Resume
@ -550,10 +550,15 @@ export default function ResumeHomePage() {
{getEmptyDataText(tabsValue, searchValue, userFilters)} {getEmptyDataText(tabsValue, searchValue, userFilters)}
</div> </div>
) : ( ) : (
<> <div className="h-[calc(100vh-9rem)] pb-10 lg:h-[calc(100vh-6rem)]">
<div className="h-[85%] overflow-y-auto">
<div>
<ResumeListItems resumes={getTabResumes()} /> <ResumeListItems resumes={getTabResumes()} />
</div>
</div>
<div className="flex h-[15%] items-center justify-center">
{getTabTotalPages() > 1 && ( {getTabTotalPages() > 1 && (
<div className="mt-4 flex justify-center"> <div>
<Pagination <Pagination
current={currentPage} current={currentPage}
end={getTabTotalPages()} end={getTabTotalPages()}
@ -563,7 +568,8 @@ export default function ResumeHomePage() {
/> />
</div> </div>
)} )}
</> </div>
</div>
)} )}
</div> </div>
</div> </div>

@ -14,6 +14,7 @@ import {
CheckboxInput, CheckboxInput,
Dialog, Dialog,
Select, Select,
Spinner,
TextArea, TextArea,
TextInput, TextInput,
} from '@tih/ui'; } from '@tih/ui';
@ -73,7 +74,7 @@ export default function SubmitResumeForm({
>(null); >(null);
const [isDialogShown, setIsDialogShown] = useState(false); const [isDialogShown, setIsDialogShown] = useState(false);
const { data: session, status } = useSession(); const { status } = useSession();
const router = useRouter(); const router = useRouter();
const trpcContext = trpc.useContext(); const trpcContext = trpc.useContext();
const resumeUpsertMutation = trpc.useMutation('resumes.resume.user.upsert'); const resumeUpsertMutation = trpc.useMutation('resumes.resume.user.upsert');
@ -122,12 +123,10 @@ export default function SubmitResumeForm({
// Route user to sign in if not logged in // Route user to sign in if not logged in
useEffect(() => { useEffect(() => {
if (status !== 'loading') { if (status === 'unauthenticated') {
if (session?.user?.id == null) {
router.push('/api/auth/signin'); router.push('/api/auth/signin');
} }
} }, [router, status]);
}, [router, session, status]);
const onSubmit: SubmitHandler<IFormInput> = async (data) => { const onSubmit: SubmitHandler<IFormInput> = async (data) => {
setIsLoading(true); setIsLoading(true);
@ -221,7 +220,7 @@ export default function SubmitResumeForm({
}, [errors?.file, invalidFileUploadError]); }, [errors?.file, invalidFileUploadError]);
const onValueChange = (section: InputKeys, value: string) => { const onValueChange = (section: InputKeys, value: string) => {
setValue(section, value.trim(), { shouldTouch: false }); setValue(section, value.trim(), { shouldDirty: true });
}; };
return ( return (
@ -229,6 +228,13 @@ export default function SubmitResumeForm({
<Head> <Head>
<title>Upload a Resume</title> <title>Upload a Resume</title>
</Head> </Head>
{status === 'loading' && (
<div className="w-full pt-4">
{' '}
<Spinner display="block" size="lg" />{' '}
</div>
)}
{status === 'authenticated' && (
<main className="h-[calc(100vh-4rem)] flex-1 overflow-y-auto"> <main className="h-[calc(100vh-4rem)] flex-1 overflow-y-auto">
<section <section
aria-labelledby="primary-heading" aria-labelledby="primary-heading"
@ -268,12 +274,18 @@ export default function SubmitResumeForm({
</h1> </h1>
{/* Title Section */} {/* Title Section */}
<TextInput <TextInput
{...register('title', { required: true })} {...(register('title', { required: true }), {})}
defaultValue={initFormDetails?.title}
disabled={isLoading} disabled={isLoading}
errorMessage={
errors.title?.message != null
? 'Title cannot be empty'
: undefined
}
label="Title" label="Title"
placeholder={TITLE_PLACEHOLDER} placeholder={TITLE_PLACEHOLDER}
required={true} required={true}
onChange={(val) => setValue('title', val)} onChange={(val) => onValueChange('title', val)}
/> />
<div className="flex gap-8"> <div className="flex gap-8">
<Select <Select
@ -318,27 +330,31 @@ export default function SubmitResumeForm({
<div <div
{...getRootProps()} {...getRootProps()}
className={clsx( className={clsx(
fileUploadError ? 'border-danger-600' : 'border-gray-300', fileUploadError
'flex cursor-pointer justify-center rounded-md border-2 border-dashed bg-gray-100 py-4', ? 'border-danger-600'
: 'border-slate-300',
'flex cursor-pointer justify-center rounded-md border-2 border-dashed bg-slate-100 py-4',
)}> )}>
<div className="space-y-1 text-center"> <div className="space-y-1 text-center">
{resumeFile == null ? ( {resumeFile == null ? (
<ArrowUpCircleIcon className="m-auto h-10 w-10 text-indigo-500" /> <ArrowUpCircleIcon className="text-primary-500 m-auto h-10 w-10" />
) : ( ) : (
<p <p
className="cursor-pointer underline underline-offset-1 hover:text-indigo-600" className="hover:text-primary-600 cursor-pointer underline underline-offset-1"
onClick={onClickDownload}> onClick={onClickDownload}>
{resumeFile.name} {resumeFile.name}
</p> </p>
)} )}
<div className="flex items-center text-sm"> <div className="flex items-center text-sm">
<label <label
className="rounded-md focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-500 focus-within:ring-offset-2" className="focus-within:ring-primary-500 rounded-md 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="cursor-pointer font-medium text-indigo-600 hover:text-indigo-400"> <span className="text-primary-600 hover:text-primary-400 cursor-pointer font-medium">
{resumeFile == null ? 'Select file' : 'Replace file'} {resumeFile == null
? 'Select file'
: 'Replace file'}
</span> </span>
<input <input
{...register('file', { required: true })} {...register('file', { required: true })}
@ -352,7 +368,7 @@ export default function SubmitResumeForm({
/> />
</label> </label>
</div> </div>
<p className="text-xs text-gray-500"> <p className="text-xs text-slate-500">
PDF up to {FILE_SIZE_LIMIT_MB}MB PDF up to {FILE_SIZE_LIMIT_MB}MB
</p> </p>
</div> </div>
@ -364,7 +380,8 @@ export default function SubmitResumeForm({
)} )}
{/* Additional Info Section */} {/* Additional Info Section */}
<TextArea <TextArea
{...(register('additionalInfo'), {})} {...(register('additionalInfo'),
{ defaultValue: initFormDetails?.additionalInfo })}
disabled={isLoading} disabled={isLoading}
label="Additional Information" label="Additional Information"
placeholder={ADDITIONAL_INFO_PLACEHOLDER} placeholder={ADDITIONAL_INFO_PLACEHOLDER}
@ -403,6 +420,7 @@ export default function SubmitResumeForm({
</form> </form>
</section> </section>
</main> </main>
)}
</> </>
); );
} }

@ -5,12 +5,15 @@ import { useToast } from '@tih/ui';
import { HorizontalDivider } from '@tih/ui'; import { HorizontalDivider } from '@tih/ui';
import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead'; import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
import JobTitlesTypeahead from '~/components/shared/JobTitlesTypahead';
import type { Month, MonthYear } from '~/components/shared/MonthYearPicker'; import type { Month, MonthYear } from '~/components/shared/MonthYearPicker';
import MonthYearPicker from '~/components/shared/MonthYearPicker'; import MonthYearPicker from '~/components/shared/MonthYearPicker';
export default function HomePage() { export default function HomePage() {
const [selectedCompany, setSelectedCompany] = const [selectedCompany, setSelectedCompany] =
useState<TypeaheadOption | null>(null); useState<TypeaheadOption | null>(null);
const [selectedJobTitle, setSelectedJobTitle] =
useState<TypeaheadOption | null>(null);
const [monthYear, setMonthYear] = useState<MonthYear>({ const [monthYear, setMonthYear] = useState<MonthYear>({
month: (new Date().getMonth() + 1) as Month, month: (new Date().getMonth() + 1) as Month,
year: new Date().getFullYear(), year: new Date().getFullYear(),
@ -30,6 +33,11 @@ export default function HomePage() {
/> />
<pre>{JSON.stringify(selectedCompany, null, 2)}</pre> <pre>{JSON.stringify(selectedCompany, null, 2)}</pre>
<HorizontalDivider /> <HorizontalDivider />
<JobTitlesTypeahead
onSelect={(option) => setSelectedJobTitle(option)}
/>
<pre>{JSON.stringify(selectedJobTitle, null, 2)}</pre>
<HorizontalDivider />
<MonthYearPicker value={monthYear} onChange={setMonthYear} /> <MonthYearPicker value={monthYear} onChange={setMonthYear} />
<HorizontalDivider /> <HorizontalDivider />
<Button <Button

@ -34,14 +34,14 @@ export default function TodoList() {
<div className="mx-auto px-4 py-8 sm:px-6 lg:px-8"> <div className="mx-auto px-4 py-8 sm:px-6 lg:px-8">
<div className="sm:flex sm:items-center"> <div className="sm:flex sm:items-center">
<div className="sm:flex-auto"> <div className="sm:flex-auto">
<h1 className="text-xl font-semibold text-gray-900">Todos</h1> <h1 className="text-xl font-semibold text-slate-900">Todos</h1>
<p className="mt-2 text-sm text-gray-700"> <p className="mt-2 text-sm text-slate-700">
A list of all Todos added by everyone. A list of all Todos added by everyone.
</p> </p>
</div> </div>
<div className="mt-4 sm:mt-0 sm:ml-16 sm:flex-none"> <div className="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
<Link <Link
className="inline-flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:w-auto" className="bg-primary-600 hover:bg-primary-700 focus:ring-primary-500 inline-flex items-center justify-center rounded-md border border-transparent px-4 py-2 text-sm font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 sm:w-auto"
href="/todos/new"> href="/todos/new">
Add Todo Add Todo
</Link> </Link>
@ -54,40 +54,40 @@ export default function TodoList() {
<div className="-my-2 -mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8"> <div className="-my-2 -mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div className="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8"> <div className="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8">
<div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg"> <div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
<table className="min-w-full divide-y divide-gray-300"> <table className="min-w-full divide-y divide-slate-300">
<thead className="bg-gray-50"> <thead className="bg-slate-50">
<tr className="divide-x divide-gray-200"> <tr className="divide-x divide-slate-200">
<th <th
className="py-3.5 pl-4 pr-4 text-left text-sm font-semibold text-gray-900 sm:pl-6" className="py-3.5 pl-4 pr-4 text-left text-sm font-semibold text-slate-900 sm:pl-6"
scope="col"> scope="col">
Description Description
</th> </th>
<th <th
className="px-4 py-3.5 text-left text-sm font-semibold text-gray-900" className="px-4 py-3.5 text-left text-sm font-semibold text-slate-900"
scope="col"> scope="col">
Creator Creator
</th> </th>
<th <th
className="px-4 py-3.5 text-left text-sm font-semibold text-gray-900" className="px-4 py-3.5 text-left text-sm font-semibold text-slate-900"
scope="col"> scope="col">
Last Updated Last Updated
</th> </th>
<th <th
className="py-3.5 pl-4 pr-4 text-left text-sm font-semibold text-gray-900 sm:pr-6" className="py-3.5 pl-4 pr-4 text-left text-sm font-semibold text-slate-900 sm:pr-6"
scope="col"> scope="col">
Status Status
</th> </th>
<th <th
className="py-3.5 pl-4 pr-4 text-left text-sm font-semibold text-gray-900 sm:pr-6" className="py-3.5 pl-4 pr-4 text-left text-sm font-semibold text-slate-900 sm:pr-6"
scope="col"> scope="col">
Actions Actions
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-gray-200 bg-white"> <tbody className="divide-y divide-slate-200 bg-white">
{todosQuery.data?.map((todo) => ( {todosQuery.data?.map((todo) => (
<tr key={todo.id} className="divide-x divide-gray-200"> <tr key={todo.id} className="divide-x divide-slate-200">
<td className="whitespace-nowrap py-4 pl-4 pr-4 text-sm text-gray-500 sm:pl-6"> <td className="whitespace-nowrap py-4 pl-4 pr-4 text-sm text-slate-500 sm:pl-6">
{todo.id === currentlyEditingTodo ? ( {todo.id === currentlyEditingTodo ? (
<form <form
ref={formRef} ref={formRef}
@ -120,7 +120,7 @@ export default function TodoList() {
}}> }}>
<input <input
autoFocus={true} autoFocus={true}
className="block w-full min-w-0 flex-1 rounded-md border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" className="focus:border-primary-500 focus:ring-primary-500 block w-full min-w-0 flex-1 rounded-md border-slate-300 sm:text-sm"
defaultValue={todo.text} defaultValue={todo.text}
name="text" name="text"
type="text" type="text"
@ -130,19 +130,19 @@ export default function TodoList() {
todo.text todo.text
)} )}
</td> </td>
<td className="whitespace-nowrap p-4 text-sm text-gray-500"> <td className="whitespace-nowrap p-4 text-sm text-slate-500">
{todo.user.name} {todo.user.name}
</td> </td>
<td className="whitespace-nowrap p-4 text-sm text-gray-500"> <td className="whitespace-nowrap p-4 text-sm text-slate-500">
{todo.updatedAt.toLocaleString('en-US', { {todo.updatedAt.toLocaleString('en-US', {
dateStyle: 'long', dateStyle: 'long',
timeStyle: 'medium', timeStyle: 'medium',
})} })}
</td> </td>
<td className="whitespace-nowrap py-4 pl-4 pr-4 text-sm text-gray-500 sm:pr-6"> <td className="whitespace-nowrap py-4 pl-4 pr-4 text-sm text-slate-500 sm:pr-6">
<input <input
checked={todo.status === 'COMPLETE'} checked={todo.status === 'COMPLETE'}
className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500" className="text-primary-600 focus:ring-primary-500 h-4 w-4 rounded border-slate-300"
type="checkbox" type="checkbox"
onChange={() => { onChange={() => {
todoUpdateMutation.mutate({ todoUpdateMutation.mutate({
@ -155,12 +155,12 @@ export default function TodoList() {
}} }}
/> />
</td> </td>
<td className="space-x-4 whitespace-nowrap py-4 pl-4 pr-4 text-sm text-gray-500 sm:pr-6"> <td className="space-x-4 whitespace-nowrap py-4 pl-4 pr-4 text-sm text-slate-500 sm:pr-6">
{data?.user?.id === todo.userId && ( {data?.user?.id === todo.userId && (
<> <>
{currentlyEditingTodo === todo.id ? ( {currentlyEditingTodo === todo.id ? (
<a <a
className="text-indigo-600 hover:text-indigo-900" className="text-primary-600 hover:text-primary-900"
href="#" href="#"
onClick={() => { onClick={() => {
setCurrentlyEditingTodo(null); setCurrentlyEditingTodo(null);
@ -169,7 +169,7 @@ export default function TodoList() {
</a> </a>
) : ( ) : (
<a <a
className="text-indigo-600 hover:text-indigo-900" className="text-primary-600 hover:text-primary-900"
href="#" href="#"
onClick={async () => { onClick={async () => {
setCurrentlyEditingTodo(todo.id); setCurrentlyEditingTodo(todo.id);
@ -178,7 +178,7 @@ export default function TodoList() {
</a> </a>
)} )}
<a <a
className="text-indigo-600 hover:text-indigo-900" className="text-primary-600 hover:text-primary-900"
href="#" href="#"
onClick={async () => { onClick={async () => {
const confirmDelete = window.confirm( const confirmDelete = window.confirm(

@ -27,7 +27,7 @@ export default function TodosCreate() {
</h1> </h1>
<form <form
ref={formRef} ref={formRef}
className="w-full space-y-8 divide-y divide-gray-200" className="w-full space-y-8 divide-y divide-slate-200"
onSubmit={async (event) => { onSubmit={async (event) => {
event.preventDefault(); event.preventDefault();
if (!formRef.current) { if (!formRef.current) {
@ -52,14 +52,14 @@ export default function TodosCreate() {
}}> }}>
<div className="mt-6"> <div className="mt-6">
<label <label
className="block text-sm font-medium text-gray-700" className="block text-sm font-medium text-slate-700"
htmlFor="text"> htmlFor="text">
Text Text
</label> </label>
<div className="mt-1 flex rounded-md shadow-sm"> <div className="mt-1 flex rounded-md shadow-sm">
<input <input
autoFocus={true} autoFocus={true}
className="block w-full min-w-0 flex-1 rounded-md border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" className="focus:border-primary-500 focus:ring-primary-500 block w-full min-w-0 flex-1 rounded-md border-slate-300 sm:text-sm"
id="text" id="text"
name="text" name="text"
type="text" type="text"
@ -71,12 +71,12 @@ export default function TodosCreate() {
<div className="pt-5"> <div className="pt-5">
<div className="flex justify-end"> <div className="flex justify-end">
<Link <Link
className="rounded-md border border-gray-300 bg-white py-2 px-4 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2" className="focus:ring-primary-500 rounded-md border border-slate-300 bg-white py-2 px-4 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-offset-2"
href="/todos"> href="/todos">
Cancel Cancel
</Link> </Link>
<button <button
className="ml-3 inline-flex justify-center rounded-md border border-transparent bg-indigo-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2" className="bg-primary-600 hover:bg-primary-700 focus:ring-primary-500 ml-3 inline-flex justify-center rounded-md border border-transparent py-2 px-4 text-sm font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2"
type="submit"> type="submit">
Save Save
</button> </button>

@ -707,7 +707,7 @@ export const offersProfileRouter = createRouter()
// Update existing experience // Update existing experience
await ctx.prisma.offersExperience.update({ await ctx.prisma.offersExperience.update({
data: { data: {
companyId: exp.companyId, companyId: exp.companyId, // TODO: check if can change with connect or whether there is a difference
durationInMonths: exp.durationInMonths, durationInMonths: exp.durationInMonths,
level: exp.level, level: exp.level,
specialization: exp.specialization, specialization: exp.specialization,
@ -718,6 +718,7 @@ export const offersProfileRouter = createRouter()
}); });
if (exp.monthlySalary) { if (exp.monthlySalary) {
if (exp.monthlySalary.id) {
await ctx.prisma.offersCurrency.update({ await ctx.prisma.offersCurrency.update({
data: { data: {
baseCurrency: baseCurrencyString, baseCurrency: baseCurrencyString,
@ -733,9 +734,31 @@ export const offersProfileRouter = createRouter()
id: exp.monthlySalary.id, id: exp.monthlySalary.id,
}, },
}); });
} else {
await ctx.prisma.offersExperience.update({
data: {
monthlySalary: {
create: {
baseCurrency: baseCurrencyString,
baseValue: await convert(
exp.monthlySalary.value,
exp.monthlySalary.currency,
baseCurrencyString,
),
currency: exp.monthlySalary.currency,
value: exp.monthlySalary.value,
},
},
},
where: {
id: exp.id,
},
});
}
} }
if (exp.totalCompensation) { if (exp.totalCompensation) {
if (exp.totalCompensation.id) {
await ctx.prisma.offersCurrency.update({ await ctx.prisma.offersCurrency.update({
data: { data: {
baseCurrency: baseCurrencyString, baseCurrency: baseCurrencyString,
@ -751,12 +774,35 @@ export const offersProfileRouter = createRouter()
id: exp.totalCompensation.id, id: exp.totalCompensation.id,
}, },
}); });
} else {
await ctx.prisma.offersExperience.update({
data: {
totalCompensation: {
create: {
baseCurrency: baseCurrencyString,
baseValue: await convert(
exp.totalCompensation.value,
exp.totalCompensation.currency,
baseCurrencyString,
),
currency: exp.totalCompensation.currency,
value: exp.totalCompensation.value,
},
},
},
where: {
id: exp.id,
},
});
}
} }
} else if (!exp.id) { } else if (!exp.id) {
// Create new experience // Create new experience
if (exp.jobType === JobType.FULLTIME) { if (exp.jobType === JobType.FULLTIME) {
if (exp.totalCompensation?.currency != null && if (
exp.totalCompensation?.value != null) { exp.totalCompensation?.currency != null &&
exp.totalCompensation?.value != null
) {
if (exp.companyId) { if (exp.companyId) {
await ctx.prisma.offersBackground.update({ await ctx.prisma.offersBackground.update({
data: { data: {
@ -866,8 +912,10 @@ export const offersProfileRouter = createRouter()
}); });
} }
} else if (exp.jobType === JobType.INTERN) { } else if (exp.jobType === JobType.INTERN) {
if (exp.monthlySalary?.currency != null && if (
exp.monthlySalary?.value != null) { exp.monthlySalary?.currency != null &&
exp.monthlySalary?.value != null
) {
if (exp.companyId) { if (exp.companyId) {
await ctx.prisma.offersBackground.update({ await ctx.prisma.offersBackground.update({
data: { data: {

@ -5,8 +5,8 @@ import {
dashboardOfferDtoMapper, dashboardOfferDtoMapper,
getOffersResponseMapper, getOffersResponseMapper,
} from '~/mappers/offers-mappers'; } from '~/mappers/offers-mappers';
import { convertWithDate } from '~/utils/offers/currency/currencyExchange';
import { Currency } from '~/utils/offers/currency/CurrencyEnum'; import { Currency } from '~/utils/offers/currency/CurrencyEnum';
import { convertWithDate } from '~/utils/offers/currency/currencyExchange';
import { createValidationRegex } from '~/utils/offers/zodRegex'; import { createValidationRegex } from '~/utils/offers/zodRegex';
import { createRouter } from '../context'; import { createRouter } from '../context';

@ -15,6 +15,7 @@ export const resumesRouter = createRouter()
searchValue: z.string(), searchValue: z.string(),
skip: z.number(), skip: z.number(),
sortOrder: z.string(), sortOrder: z.string(),
take: z.number(),
}), }),
async resolve({ ctx, input }) { async resolve({ ctx, input }) {
const { const {
@ -25,6 +26,7 @@ export const resumesRouter = createRouter()
numComments, numComments,
skip, skip,
searchValue, searchValue,
take,
} = input; } = input;
const userId = ctx.session?.user?.id; const userId = ctx.session?.user?.id;
const totalRecords = await ctx.prisma.resumesResume.count({ const totalRecords = await ctx.prisma.resumesResume.count({
@ -37,6 +39,7 @@ export const resumesRouter = createRouter()
experience: { in: experienceFilters }, experience: { in: experienceFilters },
location: { in: locationFilters }, location: { in: locationFilters },
role: { in: roleFilters }, role: { in: roleFilters },
title: { contains: searchValue, mode: 'insensitive' },
}, },
}); });
const resumesData = await ctx.prisma.resumesResume.findMany({ const resumesData = await ctx.prisma.resumesResume.findMany({
@ -74,7 +77,7 @@ export const resumesRouter = createRouter()
} }
: { comments: { _count: 'desc' } }, : { comments: { _count: 'desc' } },
skip, skip,
take: 10, take,
where: { where: {
...(numComments === 0 && { ...(numComments === 0 && {
comments: { comments: {

@ -53,6 +53,7 @@ export const resumesResumeUserRouter = createProtectedRouter()
searchValue: z.string(), searchValue: z.string(),
skip: z.number(), skip: z.number(),
sortOrder: z.string(), sortOrder: z.string(),
take: z.number(),
}), }),
async resolve({ ctx, input }) { async resolve({ ctx, input }) {
const userId = ctx.session.user.id; const userId = ctx.session.user.id;
@ -64,6 +65,7 @@ export const resumesResumeUserRouter = createProtectedRouter()
sortOrder, sortOrder,
numComments, numComments,
skip, skip,
take,
} = input; } = input;
const totalRecords = await ctx.prisma.resumesStar.count({ const totalRecords = await ctx.prisma.resumesStar.count({
where: { where: {
@ -76,6 +78,7 @@ export const resumesResumeUserRouter = createProtectedRouter()
experience: { in: experienceFilters }, experience: { in: experienceFilters },
location: { in: locationFilters }, location: { in: locationFilters },
role: { in: roleFilters }, role: { in: roleFilters },
title: { contains: searchValue, mode: 'insensitive' },
}, },
userId, userId,
}, },
@ -121,7 +124,7 @@ export const resumesResumeUserRouter = createProtectedRouter()
}, },
}, },
skip, skip,
take: 10, take,
where: { where: {
resume: { resume: {
...(numComments === 0 && { ...(numComments === 0 && {
@ -167,6 +170,7 @@ export const resumesResumeUserRouter = createProtectedRouter()
searchValue: z.string(), searchValue: z.string(),
skip: z.number(), skip: z.number(),
sortOrder: z.string(), sortOrder: z.string(),
take: z.number(),
}), }),
async resolve({ ctx, input }) { async resolve({ ctx, input }) {
const userId = ctx.session.user.id; const userId = ctx.session.user.id;
@ -177,6 +181,7 @@ export const resumesResumeUserRouter = createProtectedRouter()
sortOrder, sortOrder,
searchValue, searchValue,
numComments, numComments,
take,
skip, skip,
} = input; } = input;
const totalRecords = await ctx.prisma.resumesResume.count({ const totalRecords = await ctx.prisma.resumesResume.count({
@ -189,6 +194,7 @@ export const resumesResumeUserRouter = createProtectedRouter()
experience: { in: experienceFilters }, experience: { in: experienceFilters },
location: { in: locationFilters }, location: { in: locationFilters },
role: { in: roleFilters }, role: { in: roleFilters },
title: { contains: searchValue, mode: 'insensitive' },
userId, userId,
}, },
}); });
@ -224,7 +230,7 @@ export const resumesResumeUserRouter = createProtectedRouter()
} }
: { comments: { _count: 'desc' } }, : { comments: { _count: 'desc' } },
skip, skip,
take: 10, take,
where: { where: {
...(numComments === 0 && { ...(numComments === 0 && {
comments: { comments: {

@ -1,170 +1,169 @@
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow
export enum Currency { export enum Currency {
AED = "AED", // 'UNITED ARAB EMIRATES DIRHAM' AED = 'AED', // 'UNITED ARAB EMIRATES DIRHAM'
AFN = "AFN", // 'AFGHAN AFGHANI' AFN = 'AFN', // 'AFGHAN AFGHANI'
ALL = "ALL", // 'ALBANIAN LEK' ALL = 'ALL', // 'ALBANIAN LEK'
AMD = "AMD", // 'ARMENIAN DRAM' AMD = 'AMD', // 'ARMENIAN DRAM'
ANG = "ANG", // 'NETHERLANDS ANTILLEAN GUILDER' ANG = 'ANG', // 'NETHERLANDS ANTILLEAN GUILDER'
AOA = "AOA", // 'ANGOLAN KWANZA' AOA = 'AOA', // 'ANGOLAN KWANZA'
ARS = "ARS", // 'ARGENTINE PESO' ARS = 'ARS', // 'ARGENTINE PESO'
AUD = "AUD", // 'AUSTRALIAN DOLLAR' AUD = 'AUD', // 'AUSTRALIAN DOLLAR'
AWG = "AWG", // 'ARUBAN FLORIN' AWG = 'AWG', // 'ARUBAN FLORIN'
AZN = "AZN", // 'AZERBAIJANI MANAT' AZN = 'AZN', // 'AZERBAIJANI MANAT'
BAM = "BAM", // 'BOSNIA-HERZEGOVINA CONVERTIBLE MARK' BAM = 'BAM', // 'BOSNIA-HERZEGOVINA CONVERTIBLE MARK'
BBD = "BBD", // 'BAJAN DOLLAR' BBD = 'BBD', // 'BAJAN DOLLAR'
BDT = "BDT", // 'BANGLADESHI TAKA' BDT = 'BDT', // 'BANGLADESHI TAKA'
BGN = "BGN", // 'BULGARIAN LEV' BGN = 'BGN', // 'BULGARIAN LEV'
BHD = "BHD", // 'BAHRAINI DINAR' BHD = 'BHD', // 'BAHRAINI DINAR'
BIF = "BIF", // 'BURUNDIAN FRANC' BIF = 'BIF', // 'BURUNDIAN FRANC'
BMD = "BMD", // 'BERMUDAN DOLLAR' BMD = 'BMD', // 'BERMUDAN DOLLAR'
BND = "BND", // 'BRUNEI DOLLAR' BND = 'BND', // 'BRUNEI DOLLAR'
BOB = "BOB", // 'BOLIVIAN BOLIVIANO' BOB = 'BOB', // 'BOLIVIAN BOLIVIANO'
BRL = "BRL", // 'BRAZILIAN REAL' BRL = 'BRL', // 'BRAZILIAN REAL'
BSD = "BSD", // 'BAHAMIAN DOLLAR' BSD = 'BSD', // 'BAHAMIAN DOLLAR'
BTN = "BTN", // 'BHUTAN CURRENCY' BTN = 'BTN', // 'BHUTAN CURRENCY'
BWP = "BWP", // 'BOTSWANAN PULA' BWP = 'BWP', // 'BOTSWANAN PULA'
BYN = "BYN", // 'NEW BELARUSIAN RUBLE' BYN = 'BYN', // 'NEW BELARUSIAN RUBLE'
BYR = "BYR", // 'BELARUSIAN RUBLE' BYR = 'BYR', // 'BELARUSIAN RUBLE'
BZD = "BZD", // 'BELIZE DOLLAR' BZD = 'BZD', // 'BELIZE DOLLAR'
CAD = "CAD", // 'CANADIAN DOLLAR' CAD = 'CAD', // 'CANADIAN DOLLAR'
CDF = "CDF", // 'CONGOLESE FRANC' CDF = 'CDF', // 'CONGOLESE FRANC'
CHF = "CHF", // 'SWISS FRANC' CHF = 'CHF', // 'SWISS FRANC'
CLF = "CLF", // 'CHILEAN UNIT OF ACCOUNT (UF)' CLF = 'CLF', // 'CHILEAN UNIT OF ACCOUNT (UF)'
CLP = "CLP", // 'CHILEAN PESO' CLP = 'CLP', // 'CHILEAN PESO'
CNY = "CNY", // 'CHINESE YUAN' CNY = 'CNY', // 'CHINESE YUAN'
COP = "COP", // 'COLOMBIAN PESO' COP = 'COP', // 'COLOMBIAN PESO'
CRC = "CRC", // 'COSTA RICAN COLÓN' CRC = 'CRC', // 'COSTA RICAN COLÓN'
CUC = "CUC", // 'CUBAN CONVERTIBLE PESO' CUC = 'CUC', // 'CUBAN CONVERTIBLE PESO'
CUP = "CUP", // 'CUBAN PESO' CUP = 'CUP', // 'CUBAN PESO'
CVE = "CVE", // 'CAPE VERDEAN ESCUDO' CVE = 'CVE', // 'CAPE VERDEAN ESCUDO'
CVX = "CVX", // 'CONVEX FINANCE' CVX = 'CVX', // 'CONVEX FINANCE'
CZK = "CZK", // 'CZECH KORUNA' CZK = 'CZK', // 'CZECH KORUNA'
DJF = "DJF", // 'DJIBOUTIAN FRANC' DJF = 'DJF', // 'DJIBOUTIAN FRANC'
DKK = "DKK", // 'DANISH KRONE' DKK = 'DKK', // 'DANISH KRONE'
DOP = "DOP", // 'DOMINICAN PESO' DOP = 'DOP', // 'DOMINICAN PESO'
DZD = "DZD", // 'ALGERIAN DINAR' DZD = 'DZD', // 'ALGERIAN DINAR'
EGP = "EGP", // 'EGYPTIAN POUND' EGP = 'EGP', // 'EGYPTIAN POUND'
ERN = "ERN", // 'ERITREAN NAKFA' ERN = 'ERN', // 'ERITREAN NAKFA'
ETB = "ETB", // 'ETHIOPIAN BIRR' ETB = 'ETB', // 'ETHIOPIAN BIRR'
ETC = "ETC", // 'ETHEREUM CLASSIC' ETC = 'ETC', // 'ETHEREUM CLASSIC'
EUR = "EUR", // 'EURO' EUR = 'EUR', // 'EURO'
FEI = "FEI", // 'FEI USD' FEI = 'FEI', // 'FEI USD'
FJD = "FJD", // 'FIJIAN DOLLAR' FJD = 'FJD', // 'FIJIAN DOLLAR'
FKP = "FKP", // 'FALKLAND ISLANDS POUND' FKP = 'FKP', // 'FALKLAND ISLANDS POUND'
GBP = "GBP", // 'POUND STERLING' GBP = 'GBP', // 'POUND STERLING'
GEL = "GEL", // 'GEORGIAN LARI' GEL = 'GEL', // 'GEORGIAN LARI'
GHS = "GHS", // 'GHANAIAN CEDI' GHS = 'GHS', // 'GHANAIAN CEDI'
GIP = "GIP", // 'GIBRALTAR POUND' GIP = 'GIP', // 'GIBRALTAR POUND'
GMD = "GMD", // 'GAMBIAN DALASI' GMD = 'GMD', // 'GAMBIAN DALASI'
GNF = "GNF", // 'GUINEAN FRANC' GNF = 'GNF', // 'GUINEAN FRANC'
GTQ = "GTQ", // 'GUATEMALAN QUETZAL' GTQ = 'GTQ', // 'GUATEMALAN QUETZAL'
GYD = "GYD", // 'GUYANAESE DOLLAR' GYD = 'GYD', // 'GUYANAESE DOLLAR'
HKD = "HKD", // 'HONG KONG DOLLAR' HKD = 'HKD', // 'HONG KONG DOLLAR'
HNL = "HNL", // 'HONDURAN LEMPIRA' HNL = 'HNL', // 'HONDURAN LEMPIRA'
HRK = "HRK", // 'CROATIAN KUNA' HRK = 'HRK', // 'CROATIAN KUNA'
HTG = "HTG", // 'HAITIAN GOURDE' HTG = 'HTG', // 'HAITIAN GOURDE'
HUF = "HUF", // 'HUNGARIAN FORINT' HUF = 'HUF', // 'HUNGARIAN FORINT'
ICP = "ICP", // 'INTERNET COMPUTER' ICP = 'ICP', // 'INTERNET COMPUTER'
IDR = "IDR", // 'INDONESIAN RUPIAH' IDR = 'IDR', // 'INDONESIAN RUPIAH'
ILS = "ILS", // 'ISRAELI NEW SHEKEL' ILS = 'ILS', // 'ISRAELI NEW SHEKEL'
INR = "INR", // 'INDIAN RUPEE' INR = 'INR', // 'INDIAN RUPEE'
IQD = "IQD", // 'IRAQI DINAR' IQD = 'IQD', // 'IRAQI DINAR'
IRR = "IRR", // 'IRANIAN RIAL' IRR = 'IRR', // 'IRANIAN RIAL'
ISK = "ISK", // 'ICELANDIC KRÓNA' ISK = 'ISK', // 'ICELANDIC KRÓNA'
JEP = "JEP", // 'JERSEY POUND' JEP = 'JEP', // 'JERSEY POUND'
JMD = "JMD", // 'JAMAICAN DOLLAR' JMD = 'JMD', // 'JAMAICAN DOLLAR'
JOD = "JOD", // 'JORDANIAN DINAR' JOD = 'JOD', // 'JORDANIAN DINAR'
JPY = "JPY", // 'JAPANESE YEN' JPY = 'JPY', // 'JAPANESE YEN'
KES = "KES", // 'KENYAN SHILLING' KES = 'KES', // 'KENYAN SHILLING'
KGS = "KGS", // 'KYRGYSTANI SOM' KGS = 'KGS', // 'KYRGYSTANI SOM'
KHR = "KHR", // 'CAMBODIAN RIEL' KHR = 'KHR', // 'CAMBODIAN RIEL'
KMF = "KMF", // 'COMORIAN FRANC' KMF = 'KMF', // 'COMORIAN FRANC'
KPW = "KPW", // 'NORTH KOREAN WON' KPW = 'KPW', // 'NORTH KOREAN WON'
KRW = "KRW", // 'SOUTH KOREAN WON' KRW = 'KRW', // 'SOUTH KOREAN WON'
KWD = "KWD", // 'KUWAITI DINAR' KWD = 'KWD', // 'KUWAITI DINAR'
KYD = "KYD", // 'CAYMAN ISLANDS DOLLAR' KYD = 'KYD', // 'CAYMAN ISLANDS DOLLAR'
KZT = "KZT", // 'KAZAKHSTANI TENGE' KZT = 'KZT', // 'KAZAKHSTANI TENGE'
LAK = "LAK", // 'LAOTIAN KIP' LAK = 'LAK', // 'LAOTIAN KIP'
LBP = "LPB", // 'LEBANESE POUND' LBP = 'LPB', // 'LEBANESE POUND'
LKR = "LKR", // 'SRI LANKAN RUPEE' LKR = 'LKR', // 'SRI LANKAN RUPEE'
LRD = "LRD", // 'LIBERIAN DOLLAR' LRD = 'LRD', // 'LIBERIAN DOLLAR'
LSL = "LSL", // 'LESOTHO LOTI' LSL = 'LSL', // 'LESOTHO LOTI'
LTL = "LTL", // 'LITHUANIAN LITAS' LTL = 'LTL', // 'LITHUANIAN LITAS'
LVL = "LVL", // 'LATVIAN LATS' LVL = 'LVL', // 'LATVIAN LATS'
LYD = "LYD", // 'LIBYAN DINAR' LYD = 'LYD', // 'LIBYAN DINAR'
MAD = "MAD", // 'MOROCCAN DIRHAM' MAD = 'MAD', // 'MOROCCAN DIRHAM'
MDL = "MDL", // 'MOLDOVAN LEU' MDL = 'MDL', // 'MOLDOVAN LEU'
MGA = "MGA", // 'MALAGASY ARIARY' MGA = 'MGA', // 'MALAGASY ARIARY'
MKD = "MKD", // 'MACEDONIAN DENAR' MKD = 'MKD', // 'MACEDONIAN DENAR'
MMK = "MMK", // 'MYANMAR KYAT' MMK = 'MMK', // 'MYANMAR KYAT'
MNT = "MNT", // 'MONGOLIAN TUGRIK' MNT = 'MNT', // 'MONGOLIAN TUGRIK'
MOP = "MOP", // 'MACANESE PATACA' MOP = 'MOP', // 'MACANESE PATACA'
MRO = "MRO", // 'MAURITANIAN OUGUIYA' MRO = 'MRO', // 'MAURITANIAN OUGUIYA'
MUR = "MUR", // 'MAURITIAN RUPEE' MUR = 'MUR', // 'MAURITIAN RUPEE'
MVR = "MVR", // 'MALDIVIAN RUFIYAA' MVR = 'MVR', // 'MALDIVIAN RUFIYAA'
MWK = "MWK", // 'MALAWIAN KWACHA' MWK = 'MWK', // 'MALAWIAN KWACHA'
MXN = "MXN", // 'MEXICAN PESO' MXN = 'MXN', // 'MEXICAN PESO'
MYR = "MYR", // 'MALAYSIAN RINGGIT' MYR = 'MYR', // 'MALAYSIAN RINGGIT'
MZN = "MZN", // 'MOZAMBICAN METICAL' MZN = 'MZN', // 'MOZAMBICAN METICAL'
NAD = "NAD", // 'NAMIBIAN DOLLAR' NAD = 'NAD', // 'NAMIBIAN DOLLAR'
NGN = "NGN", // 'NIGERIAN NAIRA' NGN = 'NGN', // 'NIGERIAN NAIRA'
NIO = "NIO", // 'NICARAGUAN CÓRDOBA' NIO = 'NIO', // 'NICARAGUAN CÓRDOBA'
NOK = "NOK", // 'NORWEGIAN KRONE' NOK = 'NOK', // 'NORWEGIAN KRONE'
NPR = "NPR", // 'NEPALESE RUPEE' NPR = 'NPR', // 'NEPALESE RUPEE'
NZD = "NZD", // 'NEW ZEALAND DOLLAR' NZD = 'NZD', // 'NEW ZEALAND DOLLAR'
OMR = "OMR", // 'OMANI RIAL' OMR = 'OMR', // 'OMANI RIAL'
ONE = "ONE", // 'MENLO ONE' ONE = 'ONE', // 'MENLO ONE'
PAB = "PAB", // 'PANAMANIAN BALBOA' PAB = 'PAB', // 'PANAMANIAN BALBOA'
PGK = "PGK", // 'PAPUA NEW GUINEAN KINA' PGK = 'PGK', // 'PAPUA NEW GUINEAN KINA'
PHP = "PHP", // 'PHILIPPINE PESO' PHP = 'PHP', // 'PHILIPPINE PESO'
PKR = "PKR", // 'PAKISTANI RUPEE' PKR = 'PKR', // 'PAKISTANI RUPEE'
PLN = "PLN", // 'POLAND ZŁOTY' PLN = 'PLN', // 'POLAND ZŁOTY'
PYG = "PYG", // 'PARAGUAYAN GUARANI' PYG = 'PYG', // 'PARAGUAYAN GUARANI'
QAR = "QAR", // 'QATARI RIAL' QAR = 'QAR', // 'QATARI RIAL'
RON = "RON", // 'ROMANIAN LEU' RON = 'RON', // 'ROMANIAN LEU'
RSD = "RSD", // 'SERBIAN DINAR' RSD = 'RSD', // 'SERBIAN DINAR'
RUB = "RUB", // 'RUSSIAN RUBLE' RUB = 'RUB', // 'RUSSIAN RUBLE'
RWF = "RWF", // 'RWANDAN FRANC' RWF = 'RWF', // 'RWANDAN FRANC'
SAR = "SAR", // 'SAUDI RIYAL' SAR = 'SAR', // 'SAUDI RIYAL'
SBD = "SBD", // 'SOLOMON ISLANDS DOLLAR' SBD = 'SBD', // 'SOLOMON ISLANDS DOLLAR'
SCR = "SCR", // 'SEYCHELLOIS RUPEE' SCR = 'SCR', // 'SEYCHELLOIS RUPEE'
SDG = "SDG", // 'SUDANESE POUND' SDG = 'SDG', // 'SUDANESE POUND'
SEK = "SEK", // 'SWEDISH KRONA' SEK = 'SEK', // 'SWEDISH KRONA'
SGD = "SGD", // 'SINGAPORE DOLLAR' SGD = 'SGD', // 'SINGAPORE DOLLAR'
SHIB = "SHIB", // 'SHIBA INU' SHP = 'SHP', // 'SAINT HELENA POUND'
SHP = "SHP", // 'SAINT HELENA POUND' SLL = 'SLL', // 'SIERRA LEONEAN LEONE'
SLL = "SLL", // 'SIERRA LEONEAN LEONE' SOS = 'SOS', // 'SOMALI SHILLING'
SOS = "SOS", // 'SOMALI SHILLING' SRD = 'SRD', // 'SURINAMESE DOLLAR'
SRD = "SRD", // 'SURINAMESE DOLLAR' STD = 'STD', // 'SÃO TOMÉ AND PRÍNCIPE DOBRA (PRE-2018)'
STD = "STD", // 'SÃO TOMÉ AND PRÍNCIPE DOBRA (PRE-2018)' SVC = 'SVC', // 'SALVADORAN COLÓN'
SVC = "SVC", // 'SALVADORAN COLÓN' SYP = 'SYP', // 'SYRIAN POUND'
SYP = "SYP", // 'SYRIAN POUND' SZL = 'SZL', // 'SWAZI LILANGENI'
SZL = "SZL", // 'SWAZI LILANGENI' THB = 'THB', // 'THAI BAHT'
THB = "THB", // 'THAI BAHT' TJS = 'TJS', // 'TAJIKISTANI SOMONI'
TJS = "TJS", // 'TAJIKISTANI SOMONI' TMT = 'TMT', // 'TURKMENISTANI MANAT'
TMT = "TMT", // 'TURKMENISTANI MANAT' TND = 'TND', // 'TUNISIAN DINAR'
TND = "TND", // 'TUNISIAN DINAR' TOP = 'TOP', // "TONGAN PA'ANGA"
TOP = "TOP", // "TONGAN PA'ANGA" TRY = 'TRY', // 'TURKISH LIRA'
TRY = "TRY", // 'TURKISH LIRA' TTD = 'TTD', // 'TRINIDAD & TOBAGO DOLLAR'
TTD = "TTD", // 'TRINIDAD & TOBAGO DOLLAR' TWD = 'TWD', // 'NEW TAIWAN DOLLAR'
TWD = "TWD", // 'NEW TAIWAN DOLLAR' TZS = 'TZS', // 'TANZANIAN SHILLING'
TZS = "TZS", // 'TANZANIAN SHILLING' UAH = 'UAH', // 'UKRAINIAN HRYVNIA'
UAH = "UAH", // 'UKRAINIAN HRYVNIA' UGX = 'UGX', // 'UGANDAN SHILLING'
UGX = "UGX", // 'UGANDAN SHILLING' USD = 'USD', // 'UNITED STATES DOLLAR'
USD = "USD", // 'UNITED STATES DOLLAR' UYU = 'UYU', // 'URUGUAYAN PESO'
UYU = "UYU", // 'URUGUAYAN PESO' UZS = 'UZS', // 'UZBEKISTANI SOM'
UZS = "UZS", // 'UZBEKISTANI SOM' VND = 'VND', // 'VIETNAMESE DONG'
VND = "VND", // 'VIETNAMESE DONG' VUV = 'VUV', // 'VANUATU VATU'
VUV = "VUV", // 'VANUATU VATU' WST = 'WST', // 'SAMOAN TALA'
WST = "WST", // 'SAMOAN TALA' XAF = 'XAF', // 'CENTRAL AFRICAN CFA FRANC'
XAF = "XAF", // 'CENTRAL AFRICAN CFA FRANC' XCD = 'XCD', // 'EAST CARIBBEAN DOLLAR'
XCD = "XCD", // 'EAST CARIBBEAN DOLLAR' XOF = 'XOF', // 'WEST AFRICAN CFA FRANC'
XOF = "XOF", // 'WEST AFRICAN CFA FRANC' XPF = 'XPF', // 'CFP FRANC'
XPF = "XPF", // 'CFP FRANC' YER = 'YER', // 'YEMENI RIAL'
YER = "YER", // 'YEMENI RIAL' ZAR = 'ZAR', // 'SOUTH AFRICAN RAND'
ZAR = "ZAR", // 'SOUTH AFRICAN RAND' ZMW = 'ZMW', // 'ZAMBIAN KWACHA'
ZMW = "ZMW", // 'ZAMBIAN KWACHA' ZWL = 'ZWL', // 'ZIMBABWEAN DOLLAR'
ZWL = "ZWL", // 'ZIMBABWEAN DOLLAR'
} }
export const CURRENCY_OPTIONS = Object.entries(Currency).map( export const CURRENCY_OPTIONS = Object.entries(Currency).map(

@ -3,7 +3,6 @@ export function getProfileLink(profileId: string, token?: string) {
} }
export function copyProfileLink(profileId: string, token?: string) { export function copyProfileLink(profileId: string, token?: string) {
// TODO: Add notification
navigator.clipboard.writeText(getProfileLink(profileId, token)); navigator.clipboard.writeText(getProfileLink(profileId, token));
} }

@ -69,6 +69,7 @@ export default function Pagination({
pageNumberSet.add(page); pageNumberSet.add(page);
elements.push( elements.push(
<PaginationPage <PaginationPage
key={page}
isCurrent={current === page} isCurrent={current === page}
label={page} label={page}
onClick={(event) => { onClick={(event) => {
@ -83,7 +84,7 @@ export default function Pagination({
addPage(i); addPage(i);
} }
if (lastAddedPage < current - pagePadding) { if (lastAddedPage < current - pagePadding - 1) {
elements.push(<PaginationEllipsis />); elements.push(<PaginationEllipsis />);
} }
@ -91,7 +92,7 @@ export default function Pagination({
addPage(i); addPage(i);
} }
if (lastAddedPage < end - pagePadding) { if (lastAddedPage < end - pagePadding - 1) {
elements.push(<PaginationEllipsis />); elements.push(<PaginationEllipsis />);
} }

@ -88,7 +88,7 @@ function Select<T>(
aria-label={isLabelHidden ? label : undefined} aria-label={isLabelHidden ? label : undefined}
className={clsx( className={clsx(
display === 'block' && 'block w-full', display === 'block' && 'block w-full',
'rounded-md py-2 pl-3 pr-8 text-base focus:outline-none sm:text-sm', 'rounded-md py-2 pl-3 pr-8 text-sm focus:outline-none',
stateClasses[state], stateClasses[state],
borderClasses[borderStyle], borderClasses[borderStyle],
disabled && 'bg-slate-100', disabled && 'bg-slate-100',

@ -108,7 +108,7 @@ function TextArea(
aria-describedby={hasError ? errorId : undefined} aria-describedby={hasError ? errorId : undefined}
aria-invalid={hasError ? true : undefined} aria-invalid={hasError ? true : undefined}
className={clsx( className={clsx(
'block w-full rounded-md sm:text-sm', 'block w-full rounded-md text-sm',
stateClasses[state].textArea, stateClasses[state].textArea,
disabled && 'bg-slate-100', disabled && 'bg-slate-100',
resizeClasses[resize], resizeClasses[resize],

@ -142,7 +142,7 @@ function TextInput(
</label> </label>
<div <div
className={clsx( className={clsx(
'flex w-full overflow-hidden rounded-md border focus-within:ring-1 sm:text-sm', 'flex w-full overflow-hidden rounded-md border text-sm focus-within:ring-1',
disabled && 'pointer-events-none select-none bg-slate-100', disabled && 'pointer-events-none select-none bg-slate-100',
containerClass, containerClass,
)}> )}>
@ -178,7 +178,7 @@ function TextInput(
aria-describedby={hasError ? errorId : undefined} aria-describedby={hasError ? errorId : undefined}
aria-invalid={hasError ? true : undefined} aria-invalid={hasError ? true : undefined}
className={clsx( className={clsx(
'flex-1 border-none focus:outline-none focus:ring-0 sm:text-sm', 'w-0 flex-1 border-none text-sm focus:outline-none focus:ring-0',
inputClass, inputClass,
disabled && 'bg-transparent', disabled && 'bg-transparent',
)} )}

@ -88,7 +88,7 @@ export default function Typeahead({
)} )}
</Combobox.Label> </Combobox.Label>
<div className="relative"> <div className="relative">
<div className="focus-visible:ring-offset-primary-300 relative w-full cursor-default overflow-hidden rounded-lg border border-slate-300 bg-white text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 sm:text-sm"> <div className="focus-visible:ring-offset-primary-300 relative w-full cursor-default overflow-hidden rounded-lg border border-slate-300 bg-white text-left text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2">
<Combobox.Input <Combobox.Input
className={clsx( className={clsx(
'w-full border-none py-2 pl-3 pr-10 text-sm leading-5 text-slate-900 focus:ring-0', 'w-full border-none py-2 pl-3 pr-10 text-sm leading-5 text-slate-900 focus:ring-0',
@ -117,7 +117,7 @@ export default function Typeahead({
leave="transition ease-in duration-100" leave="transition ease-in duration-100"
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0"> leaveTo="opacity-0">
<Combobox.Options className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"> <Combobox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-sm shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
{options.length === 0 && query !== '' ? ( {options.length === 0 && query !== '' ? (
<div className="relative cursor-default select-none py-2 px-4 text-slate-700"> <div className="relative cursor-default select-none py-2 px-4 text-slate-700">
{noResultsMessage} {noResultsMessage}

Loading…
Cancel
Save