initial commit
This commit is contained in:
@@ -33,6 +33,8 @@ const jobFixture: Job = {
|
||||
selectedProjectIds: null,
|
||||
pdfPath: null,
|
||||
notionPageId: null,
|
||||
sponsorMatchScore: null,
|
||||
sponsorMatchNames: null,
|
||||
jobType: null,
|
||||
salarySource: null,
|
||||
salaryInterval: null,
|
||||
@@ -198,7 +200,7 @@ describe("OrchestratorPage", () => {
|
||||
// Clicking job-2 should update URL
|
||||
const job2Button = screen.getByTestId("select-job-2");
|
||||
fireEvent.click(job2Button);
|
||||
|
||||
|
||||
// Wait for URL to update
|
||||
await waitFor(() => {
|
||||
expect(locationText()).toContain("/all/job-2");
|
||||
|
||||
@@ -92,6 +92,9 @@ const baseSettings: AppSettings = {
|
||||
jobspyLinkedinFetchDescription: true,
|
||||
defaultJobspyLinkedinFetchDescription: true,
|
||||
overrideJobspyLinkedinFetchDescription: null,
|
||||
showSponsorInfo: true,
|
||||
defaultShowSponsorInfo: true,
|
||||
overrideShowSponsorInfo: null,
|
||||
}
|
||||
|
||||
const renderPage = () => {
|
||||
|
||||
@@ -14,6 +14,7 @@ import * as api from "../api"
|
||||
import { arraysEqual } from "@/lib/utils"
|
||||
import { resumeProjectsEqual } from "./settings/utils"
|
||||
import { DangerZoneSection } from "./settings/components/DangerZoneSection"
|
||||
import { DisplaySettingsSection } from "./settings/components/DisplaySettingsSection"
|
||||
import { GradcrackerSection } from "./settings/components/GradcrackerSection"
|
||||
import { JobCompleteWebhookSection } from "./settings/components/JobCompleteWebhookSection"
|
||||
import { JobspySection } from "./settings/components/JobspySection"
|
||||
@@ -41,6 +42,7 @@ export const SettingsPage: React.FC = () => {
|
||||
const [jobspyCountryIndeedDraft, setJobspyCountryIndeedDraft] = useState<string | null>(null)
|
||||
const [jobspySitesDraft, setJobspySitesDraft] = useState<string[] | null>(null)
|
||||
const [jobspyLinkedinFetchDescriptionDraft, setJobspyLinkedinFetchDescriptionDraft] = useState<boolean | null>(null)
|
||||
const [showSponsorInfoDraft, setShowSponsorInfoDraft] = useState<boolean | null>(null)
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [statusesToClear, setStatusesToClear] = useState<JobStatus[]>(['discovered'])
|
||||
@@ -69,6 +71,7 @@ export const SettingsPage: React.FC = () => {
|
||||
setJobspyCountryIndeedDraft(data.overrideJobspyCountryIndeed)
|
||||
setJobspySitesDraft(data.overrideJobspySites)
|
||||
setJobspyLinkedinFetchDescriptionDraft(data.overrideJobspyLinkedinFetchDescription)
|
||||
setShowSponsorInfoDraft(data.overrideShowSponsorInfo)
|
||||
})
|
||||
.catch((error) => {
|
||||
const message = error instanceof Error ? error.message : "Failed to load settings"
|
||||
@@ -126,6 +129,9 @@ export const SettingsPage: React.FC = () => {
|
||||
const effectiveJobspyLinkedinFetchDescription = settings?.jobspyLinkedinFetchDescription ?? true
|
||||
const defaultJobspyLinkedinFetchDescription = settings?.defaultJobspyLinkedinFetchDescription ?? true
|
||||
const overrideJobspyLinkedinFetchDescription = settings?.overrideJobspyLinkedinFetchDescription
|
||||
const effectiveShowSponsorInfo = settings?.showSponsorInfo ?? true
|
||||
const defaultShowSponsorInfo = settings?.defaultShowSponsorInfo ?? true
|
||||
const overrideShowSponsorInfo = settings?.overrideShowSponsorInfo
|
||||
const profileProjects = settings?.profileProjects ?? []
|
||||
const maxProjectsTotal = profileProjects.length
|
||||
const lockedCount = resumeProjectsDraft?.lockedProjectIds.length ?? 0
|
||||
@@ -163,7 +169,8 @@ export const SettingsPage: React.FC = () => {
|
||||
jobspyHoursOldDraft !== (overrideJobspyHoursOld ?? null) ||
|
||||
jobspyCountryIndeedDraft !== (overrideJobspyCountryIndeed ?? null) ||
|
||||
JSON.stringify((jobspySitesDraft ?? []).slice().sort()) !== JSON.stringify((overrideJobspySites ?? []).slice().sort()) ||
|
||||
jobspyLinkedinFetchDescriptionDraft !== (overrideJobspyLinkedinFetchDescription ?? null)
|
||||
jobspyLinkedinFetchDescriptionDraft !== (overrideJobspyLinkedinFetchDescription ?? null) ||
|
||||
showSponsorInfoDraft !== (overrideShowSponsorInfo ?? null)
|
||||
)
|
||||
}, [
|
||||
settings,
|
||||
@@ -192,12 +199,14 @@ export const SettingsPage: React.FC = () => {
|
||||
jobspyCountryIndeedDraft,
|
||||
jobspySitesDraft,
|
||||
jobspyLinkedinFetchDescriptionDraft,
|
||||
showSponsorInfoDraft,
|
||||
overrideJobspyLocation,
|
||||
overrideJobspyResultsWanted,
|
||||
overrideJobspyHoursOld,
|
||||
overrideJobspyCountryIndeed,
|
||||
overrideJobspySites,
|
||||
overrideJobspyLinkedinFetchDescription,
|
||||
overrideShowSponsorInfo,
|
||||
])
|
||||
|
||||
const handleSave = async () => {
|
||||
@@ -222,6 +231,7 @@ export const SettingsPage: React.FC = () => {
|
||||
const jobspyCountryIndeedOverride = jobspyCountryIndeedDraft === defaultJobspyCountryIndeed ? null : jobspyCountryIndeedDraft
|
||||
const jobspySitesOverride = arraysEqual((jobspySitesDraft ?? []).slice().sort(), (defaultJobspySites ?? []).slice().sort()) ? null : jobspySitesDraft
|
||||
const jobspyLinkedinFetchDescriptionOverride = jobspyLinkedinFetchDescriptionDraft === defaultJobspyLinkedinFetchDescription ? null : jobspyLinkedinFetchDescriptionDraft
|
||||
const showSponsorInfoOverride = showSponsorInfoDraft === defaultShowSponsorInfo ? null : showSponsorInfoDraft
|
||||
const updated = await api.updateSettings({
|
||||
model: trimmed.length > 0 ? trimmed : null,
|
||||
modelScorer: trimmedScorer.length > 0 ? trimmedScorer : null,
|
||||
@@ -239,6 +249,7 @@ export const SettingsPage: React.FC = () => {
|
||||
jobspyCountryIndeed: jobspyCountryIndeedOverride,
|
||||
jobspySites: jobspySitesOverride,
|
||||
jobspyLinkedinFetchDescription: jobspyLinkedinFetchDescriptionOverride,
|
||||
showSponsorInfo: showSponsorInfoOverride,
|
||||
})
|
||||
setSettings(updated)
|
||||
setModelDraft(updated.overrideModel ?? "")
|
||||
@@ -257,6 +268,7 @@ export const SettingsPage: React.FC = () => {
|
||||
setJobspyCountryIndeedDraft(updated.overrideJobspyCountryIndeed)
|
||||
setJobspySitesDraft(updated.overrideJobspySites)
|
||||
setJobspyLinkedinFetchDescriptionDraft(updated.overrideJobspyLinkedinFetchDescription)
|
||||
setShowSponsorInfoDraft(updated.overrideShowSponsorInfo)
|
||||
toast.success("Settings saved")
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Failed to save settings"
|
||||
@@ -340,6 +352,7 @@ export const SettingsPage: React.FC = () => {
|
||||
jobspyCountryIndeed: null,
|
||||
jobspySites: null,
|
||||
jobspyLinkedinFetchDescription: null,
|
||||
showSponsorInfo: null,
|
||||
})
|
||||
setSettings(updated)
|
||||
setModelDraft("")
|
||||
@@ -358,6 +371,7 @@ export const SettingsPage: React.FC = () => {
|
||||
setJobspyCountryIndeedDraft(null)
|
||||
setJobspySitesDraft(null)
|
||||
setJobspyLinkedinFetchDescriptionDraft(null)
|
||||
setShowSponsorInfoDraft(null)
|
||||
toast.success("Reset to default")
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Failed to reset settings"
|
||||
@@ -471,6 +485,14 @@ export const SettingsPage: React.FC = () => {
|
||||
isLoading={isLoading}
|
||||
isSaving={isSaving}
|
||||
/>
|
||||
<DisplaySettingsSection
|
||||
showSponsorInfoDraft={showSponsorInfoDraft}
|
||||
setShowSponsorInfoDraft={setShowSponsorInfoDraft}
|
||||
defaultShowSponsorInfo={defaultShowSponsorInfo}
|
||||
effectiveShowSponsorInfo={effectiveShowSponsorInfo}
|
||||
isLoading={isLoading}
|
||||
isSaving={isSaving}
|
||||
/>
|
||||
<DangerZoneSection
|
||||
statusesToClear={statusesToClear}
|
||||
toggleStatusToClear={toggleStatusToClear}
|
||||
|
||||
@@ -100,6 +100,8 @@ const createJob = (overrides: Partial<Job> = {}): Job => ({
|
||||
selectedProjectIds: null,
|
||||
pdfPath: null,
|
||||
notionPageId: null,
|
||||
sponsorMatchScore: null,
|
||||
sponsorMatchNames: null,
|
||||
jobType: null,
|
||||
salarySource: null,
|
||||
salaryInterval: null,
|
||||
|
||||
@@ -269,7 +269,13 @@ export const JobDetailPanel: React.FC<JobDetailPanelProps> = ({
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<JobHeader job={selectedJob} />
|
||||
<JobHeader
|
||||
job={selectedJob}
|
||||
onCheckSponsor={async () => {
|
||||
await api.checkSponsor(selectedJob.id);
|
||||
await onJobUpdated();
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
<Button asChild size="sm" variant="ghost" className="h-8 gap-1.5 text-xs">
|
||||
|
||||
@@ -31,6 +31,8 @@ const createJob = (overrides: Partial<Job> = {}): Job => ({
|
||||
selectedProjectIds: null,
|
||||
pdfPath: null,
|
||||
notionPageId: null,
|
||||
sponsorMatchScore: null,
|
||||
sponsorMatchNames: null,
|
||||
jobType: null,
|
||||
salarySource: null,
|
||||
salaryInterval: null,
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import React from "react"
|
||||
|
||||
import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
|
||||
type DisplaySettingsSectionProps = {
|
||||
showSponsorInfoDraft: boolean | null
|
||||
setShowSponsorInfoDraft: (value: boolean | null) => void
|
||||
defaultShowSponsorInfo: boolean
|
||||
effectiveShowSponsorInfo: boolean
|
||||
isLoading: boolean
|
||||
isSaving: boolean
|
||||
}
|
||||
|
||||
export const DisplaySettingsSection: React.FC<DisplaySettingsSectionProps> = ({
|
||||
showSponsorInfoDraft,
|
||||
setShowSponsorInfoDraft,
|
||||
defaultShowSponsorInfo,
|
||||
effectiveShowSponsorInfo,
|
||||
isLoading,
|
||||
isSaving,
|
||||
}) => {
|
||||
const isChecked = showSponsorInfoDraft ?? defaultShowSponsorInfo
|
||||
|
||||
return (
|
||||
<AccordionItem value="display" className="border rounded-lg px-4">
|
||||
<AccordionTrigger className="hover:no-underline py-4">
|
||||
<span className="text-base font-semibold">Display Settings</span>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="pb-4">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-start space-x-3">
|
||||
<Checkbox
|
||||
id="showSponsorInfo"
|
||||
checked={isChecked}
|
||||
onCheckedChange={(checked) => {
|
||||
setShowSponsorInfoDraft(checked === true ? true : false)
|
||||
}}
|
||||
disabled={isLoading || isSaving}
|
||||
/>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label
|
||||
htmlFor="showSponsorInfo"
|
||||
className="text-sm font-medium leading-none cursor-pointer"
|
||||
>
|
||||
Show visa sponsor information
|
||||
</label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Display a badge next to the employer name showing the match
|
||||
percentage with the UK visa sponsor list. This helps identify
|
||||
employers that are licensed to sponsor work visas.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="grid gap-2 text-sm sm:grid-cols-2">
|
||||
<div>
|
||||
<div className="text-xs text-muted-foreground">Effective</div>
|
||||
<div className="break-words font-mono text-xs">
|
||||
{effectiveShowSponsorInfo ? "Enabled" : "Disabled"}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-muted-foreground">Default</div>
|
||||
<div className="break-words font-mono text-xs font-semibold">
|
||||
{defaultShowSponsorInfo ? "Enabled" : "Disabled"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user