@ -0,0 +1,13 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `location` on the `ResumesResume` table. All the data in the column will be lost.
|
||||
- Added the required column `locationId` to the `ResumesResume` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable. Set default location to Singapore.
|
||||
ALTER TABLE "ResumesResume" DROP COLUMN "location",
|
||||
ADD COLUMN "locationId" TEXT NOT NULL DEFAULT '196';
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ResumesResume" ADD CONSTRAINT "ResumesResume_locationId_fkey" FOREIGN KEY ("locationId") REFERENCES "Country"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "ResumesResume" ALTER COLUMN "locationId" DROP DEFAULT;
|
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Country" ADD COLUMN "ranking" INTEGER DEFAULT 0;
|
After Width: | Height: | Size: 144 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 130 KiB |
After Width: | Height: | Size: 164 KiB |
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 358 KiB |
Before Width: | Height: | Size: 994 KiB After Width: | Height: | Size: 366 KiB |
Before Width: | Height: | Size: 923 KiB After Width: | Height: | Size: 277 KiB |
@ -0,0 +1,30 @@
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { ArrowSmallLeftIcon } from '@heroicons/react/24/outline';
|
||||
import { Button } from '@tih/ui';
|
||||
|
||||
export type BackButtonLayoutProps = PropsWithChildren<{
|
||||
href: string;
|
||||
}>;
|
||||
|
||||
export default function BackButtonLayout({
|
||||
href,
|
||||
children,
|
||||
}: BackButtonLayoutProps) {
|
||||
return (
|
||||
<div className="flex w-full flex-1 flex-col items-stretch gap-4 p-4 lg:flex-row">
|
||||
<div>
|
||||
<Button
|
||||
addonPosition="start"
|
||||
display="inline"
|
||||
href={href}
|
||||
icon={ArrowSmallLeftIcon}
|
||||
label="Back"
|
||||
variant="secondary"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex w-full justify-center overflow-y-auto">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import type { ComponentProps } from 'react';
|
||||
import { useState } from 'react';
|
||||
import type { TypeaheadOption } from '@tih/ui';
|
||||
import { Typeahead } from '@tih/ui';
|
||||
|
||||
import { EXPERIENCES } from '~/utils/resumes/resumeFilters';
|
||||
|
||||
type BaseProps = Pick<
|
||||
ComponentProps<typeof Typeahead>,
|
||||
| 'disabled'
|
||||
| 'errorMessage'
|
||||
| 'isLabelHidden'
|
||||
| 'placeholder'
|
||||
| 'required'
|
||||
| 'textSize'
|
||||
>;
|
||||
|
||||
type Props = BaseProps &
|
||||
Readonly<{
|
||||
onSelect: (option: TypeaheadOption | null) => void;
|
||||
selectedValues?: Set<string>;
|
||||
value?: TypeaheadOption | null;
|
||||
}>;
|
||||
|
||||
export default function ResumeExperienceTypeahead({
|
||||
onSelect,
|
||||
selectedValues = new Set(),
|
||||
value,
|
||||
...props
|
||||
}: Props) {
|
||||
const [query, setQuery] = useState('');
|
||||
const options = EXPERIENCES.filter(
|
||||
(option) => !selectedValues.has(option.value),
|
||||
).filter(
|
||||
({ label }) =>
|
||||
label.toLocaleLowerCase().indexOf(query.toLocaleLowerCase()) > -1,
|
||||
);
|
||||
|
||||
return (
|
||||
<Typeahead
|
||||
label="Experiences"
|
||||
noResultsMessage="No available experiences."
|
||||
nullable={true}
|
||||
options={options}
|
||||
value={value}
|
||||
onQueryChange={setQuery}
|
||||
onSelect={onSelect}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,56 +1,82 @@
|
||||
export const JobTitleLabels = {
|
||||
'ai-engineer': 'Artificial Intelligence (AI) Engineer',
|
||||
'algorithms-engineer': 'Algorithms Engineer',
|
||||
'android-engineer': 'Android Software Engineer',
|
||||
'applications-engineer': 'Applications Engineer',
|
||||
'back-end-engineer': 'Back End Engineer',
|
||||
'business-analyst': 'Business Analyst',
|
||||
'business-engineer': 'Business Engineer',
|
||||
'capacity-engineer': 'Capacity Engineer',
|
||||
'customer-engineer': 'Customer Engineer',
|
||||
'data-analyst': 'Data Analyst',
|
||||
'data-engineer': 'Data Engineer',
|
||||
'data-scientist': 'Data Scientist',
|
||||
'devops-engineer': 'DevOps Engineer',
|
||||
'engineering-director': 'Engineering Director',
|
||||
'engineering-manager': 'Engineering Manager',
|
||||
'enterprise-engineer': 'Enterprise Engineer',
|
||||
'forward-deployed-engineer': 'Forward Deployed Engineer',
|
||||
'front-end-engineer': 'Front End Engineer',
|
||||
'full-stack-engineer': 'Full Stack Engineer',
|
||||
'gameplay-engineer': 'Gameplay Engineer',
|
||||
'hardware-engineer': 'Hardware Engineer',
|
||||
'infrastructure-engineer': 'Infrastructure Engineer',
|
||||
'ios-engineer': 'iOS Software Engineer',
|
||||
'machine-learning-engineer': 'Machine Learning (ML) Engineer',
|
||||
'machine-learning-researcher': 'Machine Learning (ML) Researcher',
|
||||
'mobile-engineer': 'Mobile Software Engineer (iOS + Android)',
|
||||
'networks-engineer': 'Networks Engineer',
|
||||
'partner-engineer': 'Partner Engineer',
|
||||
'product-engineer': 'Product Engineer',
|
||||
'product-manager': 'Product Manager',
|
||||
'production-engineer': 'Production Engineer',
|
||||
'project-manager': 'Project Manager',
|
||||
'release-engineer': 'Release Engineer',
|
||||
'research-engineer': 'Research Engineer',
|
||||
'research-scientist': 'Research Scientist',
|
||||
'rotational-engineer': 'Rotational Engineer',
|
||||
'sales-engineer': 'Sales Engineer',
|
||||
'security-engineer': 'Security Engineer',
|
||||
'site-reliability-engineer': 'Site Reliability Engineer (SRE)',
|
||||
'software-engineer': 'Software Engineer',
|
||||
'solutions-architect': 'Solutions Architect',
|
||||
'solutions-engineer': 'Solutions Engineer',
|
||||
'systems-analyst': 'Systems Analyst',
|
||||
'systems-engineer': 'Systems Engineer',
|
||||
'tech-ops-engineer': 'Tech Ops Engineer',
|
||||
'technical-program-manager': 'Technical Program Manager',
|
||||
'test-engineer': 'QA/Test Engineer (SDET)',
|
||||
'ux-engineer': 'User Experience (UX) Engineer',
|
||||
type JobTitleData = Record<
|
||||
string,
|
||||
Readonly<{
|
||||
label: string;
|
||||
ranking: number;
|
||||
}>
|
||||
>;
|
||||
|
||||
export const JobTitleLabels: JobTitleData = {
|
||||
'ai-engineer': { label: 'Artificial Intelligence (AI) Engineer', ranking: 5 },
|
||||
'algorithms-engineer': { label: 'Algorithms Engineer', ranking: 0 },
|
||||
'android-engineer': { label: 'Android Software Engineer', ranking: 8 },
|
||||
'applications-engineer': { label: 'Applications Engineer', ranking: 0 },
|
||||
'back-end-engineer': { label: 'Back End Engineer', ranking: 9 },
|
||||
'business-analyst': { label: 'Business Analyst', ranking: 0 },
|
||||
'business-engineer': { label: 'Business Engineer', ranking: 5 },
|
||||
'capacity-engineer': { label: 'Capacity Engineer', ranking: 0 },
|
||||
'customer-engineer': { label: 'Customer Engineer', ranking: 0 },
|
||||
'data-analyst': { label: 'Data Analyst', ranking: 0 },
|
||||
'data-engineer': { label: 'Data Engineer', ranking: 0 },
|
||||
'data-scientist': { label: 'Data Scientist', ranking: 5 },
|
||||
'devops-engineer': { label: 'DevOps Engineer', ranking: 0 },
|
||||
'engineering-director': { label: 'Engineering Director', ranking: 0 },
|
||||
'engineering-manager': { label: 'Engineering Manager', ranking: 0 },
|
||||
'enterprise-engineer': { label: 'Enterprise Engineer', ranking: 0 },
|
||||
'forward-deployed-engineer': {
|
||||
label: 'Forward Deployed Engineer (FDE)',
|
||||
ranking: 0,
|
||||
},
|
||||
'front-end-engineer': { label: 'Front End Engineer', ranking: 9 },
|
||||
'full-stack-engineer': { label: 'Full Stack Engineer', ranking: 9 },
|
||||
'gameplay-engineer': { label: 'Gameplay Engineer', ranking: 0 },
|
||||
'hardware-engineer': { label: 'Hardware Engineer', ranking: 0 },
|
||||
'infrastructure-engineer': { label: 'Infrastructure Engineer', ranking: 0 },
|
||||
'ios-engineer': { label: 'iOS Software Engineer', ranking: 0 },
|
||||
'machine-learning-engineer': {
|
||||
label: 'Machine Learning (ML) Engineer',
|
||||
ranking: 5,
|
||||
},
|
||||
'machine-learning-researcher': {
|
||||
label: 'Machine Learning (ML) Researcher',
|
||||
ranking: 0,
|
||||
},
|
||||
'mobile-engineer': {
|
||||
label: 'Mobile Software Engineer (iOS + Android)',
|
||||
ranking: 8,
|
||||
},
|
||||
'networks-engineer': { label: 'Networks Engineer', ranking: 0 },
|
||||
'partner-engineer': { label: 'Partner Engineer', ranking: 0 },
|
||||
'product-engineer': { label: 'Product Engineer', ranking: 7 },
|
||||
'product-manager': { label: 'Product Manager', ranking: 0 },
|
||||
'production-engineer': { label: 'Production Engineer', ranking: 8 },
|
||||
'project-manager': { label: 'Project Manager', ranking: 0 },
|
||||
'release-engineer': { label: 'Release Engineer', ranking: 0 },
|
||||
'research-engineer': { label: 'Research Engineer', ranking: 6 },
|
||||
'research-scientist': { label: 'Research Scientist', ranking: 7 },
|
||||
'rotational-engineer': { label: 'Rotational Engineer', ranking: 0 },
|
||||
'sales-engineer': { label: 'Sales Engineer', ranking: 0 },
|
||||
'security-engineer': { label: 'Security Engineer', ranking: 7 },
|
||||
'site-reliability-engineer': {
|
||||
label: 'Site Reliability Engineer (SRE)',
|
||||
ranking: 8,
|
||||
},
|
||||
'software-engineer': { label: 'Software Engineer', ranking: 10 },
|
||||
'solutions-architect': { label: 'Solutions Architect', ranking: 0 },
|
||||
'solutions-engineer': { label: 'Solutions Engineer', ranking: 0 },
|
||||
'systems-analyst': { label: 'Systems Analyst', ranking: 0 },
|
||||
'systems-engineer': { label: 'Systems Engineer', ranking: 0 },
|
||||
'tech-ops-engineer': { label: 'Tech Ops Engineer', ranking: 0 },
|
||||
'technical-program-manager': {
|
||||
label: 'Technical Program Manager',
|
||||
ranking: 0,
|
||||
},
|
||||
'test-engineer': { label: 'QA/Test Engineer (SDET)', ranking: 6 },
|
||||
'ux-engineer': { label: 'User Experience (UX) Engineer', ranking: 0 },
|
||||
};
|
||||
|
||||
export type JobTitleType = keyof typeof JobTitleLabels;
|
||||
|
||||
export function getLabelForJobTitleType(jobTitle: JobTitleType): string {
|
||||
return JobTitleLabels[jobTitle];
|
||||
return JobTitleLabels[jobTitle].label;
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
import Container from '~/components/shared/Container';
|
||||
|
||||
const people = [
|
||||
{
|
||||
bio: 'I like to play games so I treat life like a game.',
|
||||
imageUrl: '/team/bryann.jpg',
|
||||
name: 'Bryann Yeap',
|
||||
role: 'Back End Engineer',
|
||||
},
|
||||
{
|
||||
bio: 'I am always up for sushi.',
|
||||
imageUrl: '/team/ai-ling.jpg',
|
||||
name: 'Hong Ai Ling',
|
||||
role: 'Back End Engineer',
|
||||
},
|
||||
{
|
||||
bio: 'I love to watch football and code.',
|
||||
imageUrl: '/team/stuart.jpg',
|
||||
name: 'Stuart Long',
|
||||
role: 'Front End Engineer',
|
||||
},
|
||||
{
|
||||
bio: 'Ziqing is a human who thrives under pressure, coffee and cat. In her own time, she likes playing the flute, building fun stuff with friends and watching animes.',
|
||||
imageUrl: '/team/ziqing.jpg',
|
||||
name: 'Zhang Ziqing',
|
||||
role: 'Front End Engineer',
|
||||
},
|
||||
];
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<div className="lg:py-18 bg-white py-12">
|
||||
<Container variant="xs">
|
||||
<div className="space-y-12">
|
||||
<div className="space-y-8">
|
||||
<h1 className="text-3xl font-bold tracking-tight sm:text-4xl">
|
||||
About Tech Offers Repo
|
||||
</h1>
|
||||
<p className="text-lg text-slate-500">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
|
||||
enim ad minim veniam, quis nostrud exercitation ullamco laboris
|
||||
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
|
||||
reprehenderit in voluptate velit esse cillum dolore eu fugiat
|
||||
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
|
||||
sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-8">
|
||||
<h2 className="text-2xl font-bold tracking-tight sm:text-3xl">
|
||||
Meet the Team
|
||||
</h2>
|
||||
<ul
|
||||
className="grid grid-cols-2 space-y-12 md:grid-cols-1 md:items-start md:gap-x-8 md:gap-y-12 md:space-y-0"
|
||||
role="list">
|
||||
{people.map((person) => (
|
||||
<li key={person.name}>
|
||||
<div className="space-y-4 sm:grid sm:grid-cols-4 sm:gap-6 sm:space-y-0 lg:gap-8">
|
||||
<div className="aspect-w-2 aspect-h-2 h-0">
|
||||
<img
|
||||
alt=""
|
||||
className="rounded-lg object-cover shadow-lg"
|
||||
src={person.imageUrl}
|
||||
/>
|
||||
</div>
|
||||
<div className="sm:col-span-3">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-1 text-lg font-medium leading-6">
|
||||
<h3>{person.name}</h3>
|
||||
<p className="text-primary-600">{person.role}</p>
|
||||
</div>
|
||||
<div className="text-lg">
|
||||
<p className="text-slate-500">{person.bio}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics';
|
||||
|
||||
import { trpc } from '../trpc';
|
||||
|
||||
export function useAddQuestionToListAsync() {
|
||||
const { event } = useGoogleAnalytics();
|
||||
const utils = trpc.useContext();
|
||||
const { mutateAsync: addQuestionToListAsync } = trpc.useMutation(
|
||||
'questions.lists.createQuestionEntry',
|
||||
{
|
||||
// TODO: Add optimistic update
|
||||
onSuccess: () => {
|
||||
utils.invalidateQueries(['questions.lists.getListsByUser']);
|
||||
event({
|
||||
action: 'questions.lists',
|
||||
category: 'engagement',
|
||||
label: 'add question to list',
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return addQuestionToListAsync;
|
||||
}
|
||||
|
||||
export function useRemoveQuestionFromListAsync() {
|
||||
const utils = trpc.useContext();
|
||||
const { mutateAsync: removeQuestionFromListAsync } = trpc.useMutation(
|
||||
'questions.lists.deleteQuestionEntry',
|
||||
{
|
||||
// TODO: Add optimistic update
|
||||
onSuccess: () => {
|
||||
utils.invalidateQueries(['questions.lists.getListsByUser']);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return removeQuestionFromListAsync;
|
||||
}
|
||||
|
||||
export function useCreateListAsync() {
|
||||
const { event } = useGoogleAnalytics();
|
||||
const utils = trpc.useContext();
|
||||
const { mutateAsync: createListAsync } = trpc.useMutation(
|
||||
'questions.lists.create',
|
||||
{
|
||||
onSuccess: () => {
|
||||
// TODO: Add optimistic update
|
||||
utils.invalidateQueries(['questions.lists.getListsByUser']);
|
||||
event({
|
||||
action: 'questions.lists',
|
||||
category: 'engagement',
|
||||
label: 'create list',
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
return createListAsync;
|
||||
}
|
||||
|
||||
export function useDeleteListAsync() {
|
||||
const utils = trpc.useContext();
|
||||
const { mutateAsync: deleteListAsync } = trpc.useMutation(
|
||||
'questions.lists.delete',
|
||||
{
|
||||
onSuccess: () => {
|
||||
// TODO: Add optimistic update
|
||||
utils.invalidateQueries(['questions.lists.getListsByUser']);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return deleteListAsync;
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import type { Resume } from '~/types/resume';
|
||||
|
||||
export function resumeGetFilterCounts(data: Array<Resume>) {
|
||||
const roleCounts: Record<string, number> = {};
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const { role } = data[i];
|
||||
if (!(role in roleCounts)) {
|
||||
roleCounts[role] = 1;
|
||||
} else {
|
||||
roleCounts[role]++;
|
||||
}
|
||||
}
|
||||
|
||||
const experienceCounts: Record<string, number> = {};
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const { experience } = data[i];
|
||||
if (!(experience in experienceCounts)) {
|
||||
experienceCounts[experience] = 1;
|
||||
} else {
|
||||
experienceCounts[experience]++;
|
||||
}
|
||||
}
|
||||
|
||||
const locationCounts: Record<string, number> = {};
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const { locationId } = data[i];
|
||||
if (!(locationId in locationCounts)) {
|
||||
locationCounts[locationId] = 1;
|
||||
} else {
|
||||
locationCounts[locationId]++;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
experience: experienceCounts,
|
||||
location: locationCounts,
|
||||
role: roleCounts,
|
||||
};
|
||||
}
|