Skip to content

Commit

Permalink
Konstantin/rka 51 ashby (#14)
Browse files Browse the repository at this point in the history
* rka-51: add ashby

* rka-51: fix jobs emails layout

* rka-51: add applied handling

* rka-48, rka-51: fix no results in list view
  • Loading branch information
konstrybakov authored Jul 28, 2024
1 parent 4a82362 commit dfe632f
Show file tree
Hide file tree
Showing 23 changed files with 1,218 additions and 125 deletions.
8 changes: 7 additions & 1 deletion app/add/components/variant/variants/url.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { HiringPlatformName } from '@/lib/db/schema'
import { normalizeURL } from '@/lib/utils/normalize-url'
import { zodResolver } from '@hookform/resolvers/zod'
import { CheckIcon, MagnifyingGlassIcon } from '@radix-ui/react-icons'
Expand All @@ -21,6 +22,11 @@ const schema = z.object({
url: z.string().min(1, 'URL must not be empty').url(),
})

const platformLogo: Record<HiringPlatformName, string> = {
ashby: 'ashby.png',
greenhouse: 'greenhouse.svg',
}

type FormType = z.infer<typeof schema>

export const VariantURL = () => {
Expand Down Expand Up @@ -104,7 +110,7 @@ export const VariantURL = () => {
<Card>
<Flex gap="3" align="center">
<Image
src={`/hiring-platforms/${platform.toLowerCase()}.svg`}
src={`/hiring-platforms/${platformLogo[platform]}`}
alt={`${platform} logo`}
width="32"
height="32"
Expand Down
7 changes: 7 additions & 0 deletions app/api/companies/process/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createPlatform } from '@/lib/hiring-platforms/registry'

import { logger } from '@/lib/logger'
import type { NonNullableProperty } from '@/lib/types/utils'
import { waitFor } from '@/lib/utils/wait-for'
import { ReasonPhrases, StatusCodes } from 'http-status-codes'
import { headers } from 'next/headers'

Expand Down Expand Up @@ -77,5 +78,11 @@ export const GET = async () => {
status: StatusCodes.INTERNAL_SERVER_ERROR,
},
)
} finally {
logger.info('Finished `job-processing` cron job')
logger.flush()

// TODO: vercel edge functions finish too quick, before the logger flushes
await waitFor(1000)
}
}
13 changes: 4 additions & 9 deletions app/api/email/jobs/route.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import Jobs from '@/emails/jobs'
import { queryGetJobs } from '@/lib/db/queries'
import { logger } from '@/lib/logger'
import { waitFor } from '@/lib/utils/wait-for'
import { render } from '@react-email/render'
import { ReasonPhrases, StatusCodes } from 'http-status-codes'
import { headers } from 'next/headers'
import { Resend } from 'resend'

const resend = new Resend(process.env.RESEND_API_KEY)

const delay = (ms: number) => {
return new Promise(resolve => setTimeout(resolve, ms))
}

export const GET = async () => {
try {
logger.info('Starting `send-email` cron job')
Expand Down Expand Up @@ -67,11 +64,9 @@ export const GET = async () => {
)
} finally {
logger.info('Finished `send-email` cron job')
logger.flush()

logger.flush(() => {
console.log('Logger flushed')
})

await delay(1000)
// TODO: vercel edge functions finish too quick, before the logger flushes
await waitFor(1000)
}
}
2 changes: 2 additions & 0 deletions app/components/list/actions/mark-property.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use server'
import {
queryMarkApplied,
queryMarkHidden,
queryMarkSeen,
queryMarkTopChoice,
Expand All @@ -9,3 +10,4 @@ import { createAction } from './create-action'
export const actionMarkTopChoice = createAction(queryMarkTopChoice)
export const actionMarkHidden = createAction(queryMarkHidden)
export const actionMarkSeen = createAction(queryMarkSeen)
export const actionMarkApplied = createAction(queryMarkApplied)
109 changes: 18 additions & 91 deletions app/components/list/filter-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,100 +1,27 @@
'use client'

import type { GetJobsFilter } from '@/lib/db/queries'
import { Button, Flex, Reset } from '@radix-ui/themes'
import Link from 'next/link'
import { usePathname, useSearchParams } from 'next/navigation'
import { useCallback } from 'react'

const FilterButton = ({
searchParams,
title,
active = false,
}: { searchParams: string; title: string; active?: boolean }) => {
const pathname = usePathname()

return (
<Link href={`${pathname}?${searchParams}`}>
<Button variant={active ? 'solid' : 'outline'}>{title}</Button>
</Link>
)
}
import { SegmentedControl } from '@radix-ui/themes'
import { usePathname, useRouter } from 'next/navigation'

export const FilterPanel = () => {
const searchParams = useSearchParams()

const createSearchParams = useCallback(
(filter: GetJobsFilter, shouldReset = filter === 'all') => {
const newSearchParams = new URLSearchParams(searchParams)

const shouldRemove = newSearchParams
.getAll('filter')
.some(existingFilter => existingFilter === filter)

if (!newSearchParams.has('filter')) {
newSearchParams.set('filter', 'new')
}

if (shouldRemove) {
newSearchParams.delete('filter', filter)
} else if (shouldReset) {
newSearchParams.set('filter', filter)
} else {
newSearchParams.delete('filter', 'all')
newSearchParams.append('filter', filter)
}
const pathname = usePathname()
const { push } = useRouter()

newSearchParams.delete('page')
const handleValueChange = (value: string) => {
const searchParams = new URLSearchParams({ filter: value })

return newSearchParams.toString()
},
[searchParams],
)
push(`${pathname}?${searchParams}`)
}

return (
<Flex gap="3" asChild>
<Reset>
<ul>
<li>
<FilterButton
title="New / Unseen"
searchParams={createSearchParams('new')}
active={
searchParams.getAll('filter').includes('new') ||
!searchParams.has('filter')
}
/>
</li>
<li>
<FilterButton
title="Top Choice"
searchParams={createSearchParams('topChoice')}
active={searchParams.getAll('filter').includes('topChoice')}
/>
</li>
<li>
<FilterButton
title="Seen"
searchParams={createSearchParams('seen')}
active={searchParams.getAll('filter').includes('seen')}
/>
</li>
<li>
<FilterButton
title="Hidden"
searchParams={createSearchParams('hidden')}
active={searchParams.getAll('filter').includes('hidden')}
/>
</li>
<li>
<FilterButton
title="All"
searchParams={createSearchParams('all')}
active={searchParams.getAll('filter').includes('all')}
/>
</li>
</ul>
</Reset>
</Flex>
<SegmentedControl.Root onValueChange={handleValueChange} defaultValue="new">
<SegmentedControl.Item value="new">New / Unseen</SegmentedControl.Item>
<SegmentedControl.Item value="topChoice">
Top Choice
</SegmentedControl.Item>
<SegmentedControl.Item value="seen">Seen</SegmentedControl.Item>
<SegmentedControl.Item value="hidden">Hidden</SegmentedControl.Item>
<SegmentedControl.Item value="applied">Applied</SegmentedControl.Item>
<SegmentedControl.Item value="all">All</SegmentedControl.Item>
</SegmentedControl.Root>
)
}
11 changes: 11 additions & 0 deletions app/components/list/job-card-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import {
EyeNoneIcon,
EyeOpenIcon,
LightningBoltIcon,
RocketIcon,
} from '@radix-ui/react-icons'
import { Text } from '@radix-ui/themes'
import { ActionButton } from './action-button'
import {
actionMarkApplied,
actionMarkHidden,
actionMarkSeen,
actionMarkTopChoice,
Expand All @@ -18,6 +20,7 @@ export const JobCardActions = ({ job }: { job: SelectJob }) => {
const hidden = useJobAction(job.isHidden, actionMarkHidden)
const topChoice = useJobAction(job.isTopChoice, actionMarkTopChoice)
const seen = useJobAction(job.isSeen, actionMarkSeen)
const applied = useJobAction(job.isApplied, actionMarkApplied)

return (
<>
Expand Down Expand Up @@ -48,6 +51,14 @@ export const JobCardActions = ({ job }: { job: SelectJob }) => {
icon={<EyeOpenIcon />}
label="Seen"
/>
<ActionButton
isActive={applied.isActive}
loading={applied.loading}
clickHandler={() => applied.clickHandler(job.id)}
colorActive="pink"
icon={<RocketIcon />}
label="Applied"
/>
</>
)
}
15 changes: 10 additions & 5 deletions app/components/list/job-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,16 @@ export const JobCard = async ({ job }: JobCardProps) => {
}).format(new Date(job.lastUpdatedAt))}
</Text>
</Flex>
{isAdmin(user) && (
<Flex align="center" gridColumn="1/-1" gap="2" justify="end">
<JobCardActions job={job} />
</Flex>
)}
<Flex justify="between" gridColumn="1/-1">
<Text size="2" color="gray">
{job.compensationSummary}
</Text>
{isAdmin(user) && (
<Flex align="center" gap="2" justify="between">
<JobCardActions job={job} />
</Flex>
)}
</Flex>
</Grid>
</Card>
)
Expand Down
38 changes: 26 additions & 12 deletions app/components/list/job-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { type GetJobsFilter, queryGetJobs } from '@/lib/db/queries'
import { JobCard } from './job-card'

import type { PageSearchParams } from '@/app/types'
import { Flex, Reset } from '@radix-ui/themes'
import { ValueIcon } from '@radix-ui/react-icons'
import { Callout, Flex, Reset } from '@radix-ui/themes'
import { FilterPanel } from './filter-panel'
import { Pagination } from './pagination'

Expand All @@ -17,22 +18,35 @@ export const JobList = async ({ searchParams }: JobListProps) => {
searchParams.page ? Number(searchParams.page) : 1,
)

const hasJobs = result.data.length > 0

return (
<Flex direction="column" gap="3">
<FilterPanel />
<Flex asChild direction="column" gap="3">
<Reset>
{/* biome-ignore lint/a11y/noRedundantRoles: safari 👀 */}
<ul role="list">
{result.data.map(job => (
<li key={job.id}>
<JobCard job={job} />
</li>
))}
</ul>
</Reset>
{hasJobs ? (
<Reset>
{/* biome-ignore lint/a11y/noRedundantRoles: safari 👀 */}
<ul role="list">
{result.data.map(job => (
<li key={job.id}>
<JobCard job={job} />
</li>
))}
</ul>
</Reset>
) : (
<Callout.Root size="3">
<Callout.Icon>
<ValueIcon />
</Callout.Icon>
<Callout.Text>
No jobs found. Try changing the filters.
</Callout.Text>
</Callout.Root>
)}
</Flex>
<Pagination total={result.total} />
{hasJobs ? <Pagination total={result.total} /> : null}
</Flex>
)
}
1 change: 1 addition & 0 deletions drizzle/0008_plain_midnight.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TYPE "hiring_platform" ADD VALUE 'ashby';
25 changes: 25 additions & 0 deletions drizzle/0009_strong_mariko_yashida.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
DO $$ BEGIN
CREATE TYPE "public"."compensation_type" AS ENUM('salary', 'equity');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "compensations" (
"id" serial PRIMARY KEY NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"job_id" integer NOT NULL,
"type" "compensation_type" NOT NULL,
"currency_code" text,
"min_value" numeric(10, 3) NOT NULL,
"max_value" numeric(10, 3),
"summary" text,
"interval" text
);
--> statement-breakpoint
ALTER TABLE "jobs" ADD COLUMN "is_remote" boolean;--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "compensations" ADD CONSTRAINT "compensations_job_id_jobs_id_fk" FOREIGN KEY ("job_id") REFERENCES "public"."jobs"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
8 changes: 8 additions & 0 deletions drizzle/0010_silly_joseph.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
DROP TABLE "compensations";--> statement-breakpoint
ALTER TABLE "jobs" ADD COLUMN "currency_code" text;--> statement-breakpoint
ALTER TABLE "jobs" ADD COLUMN "compensation_interval" text;--> statement-breakpoint
ALTER TABLE "jobs" ADD COLUMN "compensation_summary" text;--> statement-breakpoint
ALTER TABLE "jobs" ADD COLUMN "salary_min" numeric(10, 3);--> statement-breakpoint
ALTER TABLE "jobs" ADD COLUMN "salary_max" numeric(10, 3);--> statement-breakpoint
ALTER TABLE "jobs" ADD COLUMN "equity_min" numeric(10, 3);--> statement-breakpoint
ALTER TABLE "jobs" ADD COLUMN "equity_max" numeric(10, 3);
Loading

0 comments on commit dfe632f

Please sign in to comment.