introduce EffectiveDefault<T>

This commit is contained in:
DaKheera47 2026-01-21 19:47:11 +00:00
parent eb41c6237b
commit 610fc48a91
11 changed files with 114 additions and 142 deletions

View File

@ -129,12 +129,12 @@ const getDerivedSettings = (settings: AppSettings | null) => {
default: settings?.defaultJobCompleteWebhookUrl ?? "",
},
ukvisajobs: {
effectiveMaxJobs: settings?.ukvisajobsMaxJobs ?? 50,
defaultMaxJobs: settings?.defaultUkvisajobsMaxJobs ?? 50,
effective: settings?.ukvisajobsMaxJobs ?? 50,
default: settings?.defaultUkvisajobsMaxJobs ?? 50,
},
gradcracker: {
effectiveMaxJobsPerTerm: settings?.gradcrackerMaxJobsPerTerm ?? 50,
defaultMaxJobsPerTerm: settings?.defaultGradcrackerMaxJobsPerTerm ?? 50,
effective: settings?.gradcrackerMaxJobsPerTerm ?? 50,
default: settings?.defaultGradcrackerMaxJobsPerTerm ?? 50,
},
searchTerms: {
effective: settings?.searchTerms ?? [],
@ -167,8 +167,8 @@ const getDerivedSettings = (settings: AppSettings | null) => {
},
},
display: {
effectiveShowSponsorInfo: settings?.showSponsorInfo ?? true,
defaultShowSponsorInfo: settings?.defaultShowSponsorInfo ?? true,
effective: settings?.showSponsorInfo ?? true,
default: settings?.defaultShowSponsorInfo ?? true,
},
defaultResumeProjects: settings?.defaultResumeProjects ?? null,
profileProjects,
@ -253,8 +253,8 @@ export const SettingsPage: React.FC = () => {
pipelineWebhookUrl: normalizeString(data.pipelineWebhookUrl),
jobCompleteWebhookUrl: normalizeString(data.jobCompleteWebhookUrl),
resumeProjects: resumeProjectsOverride,
ukvisajobsMaxJobs: nullIfSame(data.ukvisajobsMaxJobs, ukvisajobs.defaultMaxJobs),
gradcrackerMaxJobsPerTerm: nullIfSame(data.gradcrackerMaxJobsPerTerm, gradcracker.defaultMaxJobsPerTerm),
ukvisajobsMaxJobs: nullIfSame(data.ukvisajobsMaxJobs, ukvisajobs.default),
gradcrackerMaxJobsPerTerm: nullIfSame(data.gradcrackerMaxJobsPerTerm, gradcracker.default),
searchTerms: nullIfSameList(data.searchTerms, searchTerms.default),
jobspyLocation: nullIfSame(data.jobspyLocation, jobspy.location.default),
jobspyResultsWanted: nullIfSame(data.jobspyResultsWanted, jobspy.resultsWanted.default),
@ -265,7 +265,7 @@ export const SettingsPage: React.FC = () => {
data.jobspyLinkedinFetchDescription,
jobspy.linkedinFetchDescription.default
),
showSponsorInfo: nullIfSame(data.showSponsorInfo, display.defaultShowSponsorInfo),
showSponsorInfo: nullIfSame(data.showSponsorInfo, display.default),
}
const updated = await api.updateSettings(payload)
@ -361,57 +361,37 @@ export const SettingsPage: React.FC = () => {
<main className="container mx-auto max-w-3xl space-y-6 px-4 py-6 pb-12">
<Accordion type="multiple" className="w-full space-y-4">
<ModelSettingsSection
effectiveModel={model.effective}
effectiveModelScorer={model.scorer}
effectiveModelTailoring={model.tailoring}
effectiveModelProjectSelection={model.projectSelection}
defaultModel={model.default}
values={model}
isLoading={isLoading}
isSaving={isSaving}
/>
<PipelineWebhookSection
defaultPipelineWebhookUrl={pipelineWebhook.default}
effectivePipelineWebhookUrl={pipelineWebhook.effective}
values={pipelineWebhook}
isLoading={isLoading}
isSaving={isSaving}
/>
<JobCompleteWebhookSection
defaultJobCompleteWebhookUrl={jobCompleteWebhook.default}
effectiveJobCompleteWebhookUrl={jobCompleteWebhook.effective}
values={jobCompleteWebhook}
isLoading={isLoading}
isSaving={isSaving}
/>
<UkvisajobsSection
defaultUkvisajobsMaxJobs={ukvisajobs.defaultMaxJobs}
effectiveUkvisajobsMaxJobs={ukvisajobs.effectiveMaxJobs}
values={ukvisajobs}
isLoading={isLoading}
isSaving={isSaving}
/>
<GradcrackerSection
defaultGradcrackerMaxJobsPerTerm={gradcracker.defaultMaxJobsPerTerm}
effectiveGradcrackerMaxJobsPerTerm={gradcracker.effectiveMaxJobsPerTerm}
values={gradcracker}
isLoading={isLoading}
isSaving={isSaving}
/>
<SearchTermsSection
defaultSearchTerms={searchTerms.default}
effectiveSearchTerms={searchTerms.effective}
values={searchTerms}
isLoading={isLoading}
isSaving={isSaving}
/>
<JobspySection
defaultJobspySites={jobspy.sites.default}
effectiveJobspySites={jobspy.sites.effective}
defaultJobspyLocation={jobspy.location.default}
effectiveJobspyLocation={jobspy.location.effective}
defaultJobspyResultsWanted={jobspy.resultsWanted.default}
effectiveJobspyResultsWanted={jobspy.resultsWanted.effective}
defaultJobspyHoursOld={jobspy.hoursOld.default}
effectiveJobspyHoursOld={jobspy.hoursOld.effective}
defaultJobspyCountryIndeed={jobspy.countryIndeed.default}
effectiveJobspyCountryIndeed={jobspy.countryIndeed.effective}
defaultJobspyLinkedinFetchDescription={jobspy.linkedinFetchDescription.default}
effectiveJobspyLinkedinFetchDescription={jobspy.linkedinFetchDescription.effective}
values={jobspy}
isLoading={isLoading}
isSaving={isSaving}
/>
@ -423,8 +403,7 @@ export const SettingsPage: React.FC = () => {
isSaving={isSaving}
/>
<DisplaySettingsSection
defaultShowSponsorInfo={display.defaultShowSponsorInfo}
effectiveShowSponsorInfo={display.effectiveShowSponsorInfo}
values={display}
isLoading={isLoading}
isSaving={isSaving}
/>

View File

@ -5,20 +5,20 @@ import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/
import { Checkbox } from "@/components/ui/checkbox"
import { Separator } from "@/components/ui/separator"
import { UpdateSettingsInput } from "@shared/settings-schema"
import type { DisplayValues } from "@client/pages/settings/types"
type DisplaySettingsSectionProps = {
defaultShowSponsorInfo: boolean
effectiveShowSponsorInfo: boolean
values: DisplayValues
isLoading: boolean
isSaving: boolean
}
export const DisplaySettingsSection: React.FC<DisplaySettingsSectionProps> = ({
defaultShowSponsorInfo,
effectiveShowSponsorInfo,
values,
isLoading,
isSaving,
}) => {
const { default: defaultShowSponsorInfo, effective: effectiveShowSponsorInfo } = values
const { control } = useFormContext<UpdateSettingsInput>()
return (
@ -79,4 +79,3 @@ export const DisplaySettingsSection: React.FC<DisplaySettingsSectionProps> = ({
</AccordionItem>
)
}

View File

@ -5,20 +5,20 @@ import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/
import { Input } from "@/components/ui/input"
import { Separator } from "@/components/ui/separator"
import { UpdateSettingsInput } from "@shared/settings-schema"
import type { NumericSettingValues } from "@client/pages/settings/types"
type GradcrackerSectionProps = {
defaultGradcrackerMaxJobsPerTerm: number
effectiveGradcrackerMaxJobsPerTerm: number
values: NumericSettingValues
isLoading: boolean
isSaving: boolean
}
export const GradcrackerSection: React.FC<GradcrackerSectionProps> = ({
defaultGradcrackerMaxJobsPerTerm,
effectiveGradcrackerMaxJobsPerTerm,
values,
isLoading,
isSaving,
}) => {
const { effective: effectiveGradcrackerMaxJobsPerTerm, default: defaultGradcrackerMaxJobsPerTerm } = values
const { control, formState: { errors } } = useFormContext<UpdateSettingsInput>()
return (
@ -75,4 +75,3 @@ export const GradcrackerSection: React.FC<GradcrackerSectionProps> = ({
</AccordionItem>
)
}

View File

@ -5,20 +5,20 @@ import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/
import { Input } from "@/components/ui/input"
import { Separator } from "@/components/ui/separator"
import { UpdateSettingsInput } from "@shared/settings-schema"
import type { WebhookValues } from "@client/pages/settings/types"
type JobCompleteWebhookSectionProps = {
defaultJobCompleteWebhookUrl: string
effectiveJobCompleteWebhookUrl: string
values: WebhookValues
isLoading: boolean
isSaving: boolean
}
export const JobCompleteWebhookSection: React.FC<JobCompleteWebhookSectionProps> = ({
defaultJobCompleteWebhookUrl,
effectiveJobCompleteWebhookUrl,
values,
isLoading,
isSaving,
}) => {
const { default: defaultJobCompleteWebhookUrl, effective: effectiveJobCompleteWebhookUrl } = values
const { register, formState: { errors } } = useFormContext<UpdateSettingsInput>()
return (
@ -58,4 +58,3 @@ export const JobCompleteWebhookSection: React.FC<JobCompleteWebhookSectionProps>
</AccordionItem>
)
}

View File

@ -22,18 +22,14 @@ const JobspyHarness = () => {
<FormProvider {...methods}>
<Accordion type="multiple" defaultValue={["jobspy"]}>
<JobspySection
defaultJobspySites={["indeed", "linkedin"]}
effectiveJobspySites={["indeed", "linkedin"]}
defaultJobspyLocation="UK"
effectiveJobspyLocation="UK"
defaultJobspyResultsWanted={200}
effectiveJobspyResultsWanted={200}
defaultJobspyHoursOld={72}
effectiveJobspyHoursOld={72}
defaultJobspyCountryIndeed="UK"
effectiveJobspyCountryIndeed="UK"
defaultJobspyLinkedinFetchDescription={true}
effectiveJobspyLinkedinFetchDescription={true}
values={{
sites: { default: ["indeed", "linkedin"], effective: ["indeed", "linkedin"] },
location: { default: "UK", effective: "UK" },
resultsWanted: { default: 200, effective: 200 },
hoursOld: { default: 72, effective: 72 },
countryIndeed: { default: "UK", effective: "UK" },
linkedinFetchDescription: { default: true, effective: true },
}}
isLoading={false}
isSaving={false}
/>

View File

@ -6,40 +6,27 @@ import { Checkbox } from "@/components/ui/checkbox"
import { Input } from "@/components/ui/input"
import { Separator } from "@/components/ui/separator"
import { UpdateSettingsInput } from "@shared/settings-schema"
import type { JobspyValues } from "@client/pages/settings/types"
type JobspySectionProps = {
defaultJobspySites: string[]
effectiveJobspySites: string[]
defaultJobspyLocation: string
effectiveJobspyLocation: string
defaultJobspyResultsWanted: number
effectiveJobspyResultsWanted: number
defaultJobspyHoursOld: number
effectiveJobspyHoursOld: number
defaultJobspyCountryIndeed: string
effectiveJobspyCountryIndeed: string
defaultJobspyLinkedinFetchDescription: boolean
effectiveJobspyLinkedinFetchDescription: boolean
values: JobspyValues
isLoading: boolean
isSaving: boolean
}
export const JobspySection: React.FC<JobspySectionProps> = ({
defaultJobspySites,
effectiveJobspySites,
defaultJobspyLocation,
effectiveJobspyLocation,
defaultJobspyResultsWanted,
effectiveJobspyResultsWanted,
defaultJobspyHoursOld,
effectiveJobspyHoursOld,
defaultJobspyCountryIndeed,
effectiveJobspyCountryIndeed,
defaultJobspyLinkedinFetchDescription,
effectiveJobspyLinkedinFetchDescription,
values,
isLoading,
isSaving,
}) => {
const {
sites,
location,
resultsWanted,
hoursOld,
countryIndeed,
linkedinFetchDescription,
} = values
const { control, register, formState: { errors } } = useFormContext<UpdateSettingsInput>()
return (
@ -59,9 +46,9 @@ export const JobspySection: React.FC<JobspySectionProps> = ({
render={({ field }) => (
<Checkbox
id="site-indeed"
checked={field.value?.includes('indeed') ?? defaultJobspySites.includes('indeed')}
checked={field.value?.includes('indeed') ?? sites.default.includes('indeed')}
onCheckedChange={(checked) => {
const current = field.value ?? defaultJobspySites
const current = field.value ?? sites.default
let next = [...current]
if (checked) {
if (!next.includes('indeed')) next.push('indeed')
@ -83,9 +70,9 @@ export const JobspySection: React.FC<JobspySectionProps> = ({
render={({ field }) => (
<Checkbox
id="site-linkedin"
checked={field.value?.includes('linkedin') ?? defaultJobspySites.includes('linkedin')}
checked={field.value?.includes('linkedin') ?? sites.default.includes('linkedin')}
onCheckedChange={(checked) => {
const current = field.value ?? defaultJobspySites
const current = field.value ?? sites.default
let next = [...current]
if (checked) {
if (!next.includes('linkedin')) next.push('linkedin')
@ -106,8 +93,8 @@ export const JobspySection: React.FC<JobspySectionProps> = ({
Select which sites JobSpy should scrape.
</div>
<div className="flex gap-2 text-xs text-muted-foreground">
<span>Effective: {(effectiveJobspySites || []).join(', ') || "None"}</span>
<span>Default: {(defaultJobspySites || []).join(', ')}</span>
<span>Effective: {(sites.effective || []).join(', ') || "None"}</span>
<span>Default: {(sites.default || []).join(', ')}</span>
</div>
</div>
@ -116,7 +103,7 @@ export const JobspySection: React.FC<JobspySectionProps> = ({
<div className="text-sm font-medium">Location</div>
<Input
{...register("jobspyLocation")}
placeholder={defaultJobspyLocation || "UK"}
placeholder={location.default || "UK"}
disabled={isLoading || isSaving}
/>
{errors.jobspyLocation && <p className="text-xs text-destructive">{errors.jobspyLocation.message}</p>}
@ -124,8 +111,8 @@ export const JobspySection: React.FC<JobspySectionProps> = ({
Location to search for jobs (e.g. "UK", "London", "Remote").
</div>
<div className="flex gap-2 text-xs text-muted-foreground">
<span>Effective: {effectiveJobspyLocation || "—"}</span>
<span>Default: {defaultJobspyLocation || "—"}</span>
<span>Effective: {location.effective || "—"}</span>
<span>Default: {location.default || "—"}</span>
</div>
</div>
@ -158,8 +145,8 @@ export const JobspySection: React.FC<JobspySectionProps> = ({
Number of results to fetch per term per site. Max 1000.
</div>
<div className="flex gap-2 text-xs text-muted-foreground">
<span>Effective: {effectiveJobspyResultsWanted}</span>
<span>Default: {defaultJobspyResultsWanted}</span>
<span>Effective: {resultsWanted.effective}</span>
<span>Default: {resultsWanted.default}</span>
</div>
</div>
@ -192,8 +179,8 @@ export const JobspySection: React.FC<JobspySectionProps> = ({
Max age of jobs in hours (e.g. 72 for 3 days). Max 720 (30 days).
</div>
<div className="flex gap-2 text-xs text-muted-foreground">
<span>Effective: {effectiveJobspyHoursOld}h</span>
<span>Default: {defaultJobspyHoursOld}h</span>
<span>Effective: {hoursOld.effective}h</span>
<span>Default: {hoursOld.default}h</span>
</div>
</div>
@ -201,7 +188,7 @@ export const JobspySection: React.FC<JobspySectionProps> = ({
<div className="text-sm font-medium">Indeed Country</div>
<Input
{...register("jobspyCountryIndeed")}
placeholder={defaultJobspyCountryIndeed || "UK"}
placeholder={countryIndeed.default || "UK"}
disabled={isLoading || isSaving}
/>
{errors.jobspyCountryIndeed && <p className="text-xs text-destructive">{errors.jobspyCountryIndeed.message}</p>}
@ -209,8 +196,8 @@ export const JobspySection: React.FC<JobspySectionProps> = ({
Country domain for Indeed (e.g. "UK" for indeed.co.uk).
</div>
<div className="flex gap-2 text-xs text-muted-foreground">
<span>Effective: {effectiveJobspyCountryIndeed || "—"}</span>
<span>Default: {defaultJobspyCountryIndeed || "—"}</span>
<span>Effective: {countryIndeed.effective || "—"}</span>
<span>Default: {countryIndeed.default || "—"}</span>
</div>
</div>
</div>
@ -224,7 +211,7 @@ export const JobspySection: React.FC<JobspySectionProps> = ({
render={({ field }) => (
<Checkbox
id="linkedin-desc"
checked={field.value ?? defaultJobspyLinkedinFetchDescription}
checked={field.value ?? linkedinFetchDescription.default}
onCheckedChange={(checked) => field.onChange(!!checked)}
disabled={isLoading || isSaving}
/>
@ -241,8 +228,8 @@ export const JobspySection: React.FC<JobspySectionProps> = ({
If enabled, JobSpy will make extra requests to fetch full descriptions. Slower but better data.
</p>
<div className="flex gap-2 text-xs text-muted-foreground">
<span>Effective: {effectiveJobspyLinkedinFetchDescription ? "Yes" : "No"}</span>
<span>Default: {defaultJobspyLinkedinFetchDescription ? "Yes" : "No"}</span>
<span>Effective: {linkedinFetchDescription.effective ? "Yes" : "No"}</span>
<span>Default: {linkedinFetchDescription.default ? "Yes" : "No"}</span>
</div>
</div>
</div>
@ -251,4 +238,3 @@ export const JobspySection: React.FC<JobspySectionProps> = ({
</AccordionItem>
)
}

View File

@ -5,26 +5,20 @@ import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/
import { Input } from "@/components/ui/input"
import { Separator } from "@/components/ui/separator"
import { UpdateSettingsInput } from "@shared/settings-schema"
import type { ModelValues } from "@client/pages/settings/types"
type ModelSettingsSectionProps = {
effectiveModel: string
effectiveModelScorer: string
effectiveModelTailoring: string
effectiveModelProjectSelection: string
defaultModel: string
values: ModelValues
isLoading: boolean
isSaving: boolean
}
export const ModelSettingsSection: React.FC<ModelSettingsSectionProps> = ({
effectiveModel,
effectiveModelScorer,
effectiveModelTailoring,
effectiveModelProjectSelection,
defaultModel,
values,
isLoading,
isSaving,
}) => {
const { effective, default: defaultModel, scorer, tailoring, projectSelection } = values
const { register, formState: { errors } } = useFormContext<UpdateSettingsInput>()
return (
@ -57,12 +51,12 @@ export const ModelSettingsSection: React.FC<ModelSettingsSectionProps> = ({
<div className="text-sm">Scoring Model</div>
<Input
{...register("modelScorer")}
placeholder={effectiveModel || "inherit"}
placeholder={effective || "inherit"}
disabled={isLoading || isSaving}
/>
{errors.modelScorer && <p className="text-xs text-destructive">{errors.modelScorer.message}</p>}
<div className="text-xs text-muted-foreground">
Effective: <span className="font-mono">{effectiveModelScorer || effectiveModel}</span>
Effective: <span className="font-mono">{scorer || effective}</span>
</div>
</div>
@ -70,12 +64,12 @@ export const ModelSettingsSection: React.FC<ModelSettingsSectionProps> = ({
<div className="text-sm">Tailoring Model</div>
<Input
{...register("modelTailoring")}
placeholder={effectiveModel || "inherit"}
placeholder={effective || "inherit"}
disabled={isLoading || isSaving}
/>
{errors.modelTailoring && <p className="text-xs text-destructive">{errors.modelTailoring.message}</p>}
<div className="text-xs text-muted-foreground">
Effective: <span className="font-mono">{effectiveModelTailoring || effectiveModel}</span>
Effective: <span className="font-mono">{tailoring || effective}</span>
</div>
</div>
@ -83,12 +77,12 @@ export const ModelSettingsSection: React.FC<ModelSettingsSectionProps> = ({
<div className="text-sm">Project Selection Model</div>
<Input
{...register("modelProjectSelection")}
placeholder={effectiveModel || "inherit"}
placeholder={effective || "inherit"}
disabled={isLoading || isSaving}
/>
{errors.modelProjectSelection && <p className="text-xs text-destructive">{errors.modelProjectSelection.message}</p>}
<div className="text-xs text-muted-foreground">
Effective: <span className="font-mono">{effectiveModelProjectSelection || effectiveModel}</span>
Effective: <span className="font-mono">{projectSelection || effective}</span>
</div>
</div>
</div>
@ -99,7 +93,7 @@ export const ModelSettingsSection: React.FC<ModelSettingsSectionProps> = ({
<div className="grid gap-2 text-sm sm:grid-cols-2">
<div>
<div className="text-xs text-muted-foreground">Global Effective</div>
<div className="break-words font-mono text-xs">{effectiveModel || "—"}</div>
<div className="break-words font-mono text-xs">{effective || "—"}</div>
</div>
<div>
<div className="text-xs text-muted-foreground">Default (env)</div>
@ -111,4 +105,3 @@ export const ModelSettingsSection: React.FC<ModelSettingsSectionProps> = ({
</AccordionItem>
)
}

View File

@ -5,20 +5,20 @@ import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/
import { Input } from "@/components/ui/input"
import { Separator } from "@/components/ui/separator"
import { UpdateSettingsInput } from "@shared/settings-schema"
import type { WebhookValues } from "@client/pages/settings/types"
type PipelineWebhookSectionProps = {
defaultPipelineWebhookUrl: string
effectivePipelineWebhookUrl: string
values: WebhookValues
isLoading: boolean
isSaving: boolean
}
export const PipelineWebhookSection: React.FC<PipelineWebhookSectionProps> = ({
defaultPipelineWebhookUrl,
effectivePipelineWebhookUrl,
values,
isLoading,
isSaving,
}) => {
const { default: defaultPipelineWebhookUrl, effective: effectivePipelineWebhookUrl } = values
const { register, formState: { errors } } = useFormContext<UpdateSettingsInput>()
return (
@ -58,4 +58,3 @@ export const PipelineWebhookSection: React.FC<PipelineWebhookSectionProps> = ({
</AccordionItem>
)
}

View File

@ -4,20 +4,20 @@ import { useFormContext, Controller } from "react-hook-form"
import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
import { Separator } from "@/components/ui/separator"
import { UpdateSettingsInput } from "@shared/settings-schema"
import type { SearchTermsValues } from "@client/pages/settings/types"
type SearchTermsSectionProps = {
defaultSearchTerms: string[]
effectiveSearchTerms: string[]
values: SearchTermsValues
isLoading: boolean
isSaving: boolean
}
export const SearchTermsSection: React.FC<SearchTermsSectionProps> = ({
defaultSearchTerms,
effectiveSearchTerms,
values,
isLoading,
isSaving,
}) => {
const { default: defaultSearchTerms, effective: effectiveSearchTerms } = values
const { control, formState: { errors } } = useFormContext<UpdateSettingsInput>()
return (
@ -75,4 +75,3 @@ export const SearchTermsSection: React.FC<SearchTermsSectionProps> = ({
</AccordionItem>
)
}

View File

@ -5,20 +5,20 @@ import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/
import { Input } from "@/components/ui/input"
import { Separator } from "@/components/ui/separator"
import { UpdateSettingsInput } from "@shared/settings-schema"
import type { NumericSettingValues } from "@client/pages/settings/types"
type UkvisajobsSectionProps = {
defaultUkvisajobsMaxJobs: number
effectiveUkvisajobsMaxJobs: number
values: NumericSettingValues
isLoading: boolean
isSaving: boolean
}
export const UkvisajobsSection: React.FC<UkvisajobsSectionProps> = ({
defaultUkvisajobsMaxJobs,
effectiveUkvisajobsMaxJobs,
values,
isLoading,
isSaving,
}) => {
const { effective: effectiveUkvisajobsMaxJobs, default: defaultUkvisajobsMaxJobs } = values
const { control, formState: { errors } } = useFormContext<UpdateSettingsInput>()
return (
@ -75,4 +75,3 @@ export const UkvisajobsSection: React.FC<UkvisajobsSectionProps> = ({
</AccordionItem>
)
}

View File

@ -0,0 +1,24 @@
export type EffectiveDefault<T> = {
effective: T
default: T
}
export type ModelValues = EffectiveDefault<string> & {
scorer: string
tailoring: string
projectSelection: string
}
export type WebhookValues = EffectiveDefault<string>
export type NumericSettingValues = EffectiveDefault<number>
export type SearchTermsValues = EffectiveDefault<string[]>
export type DisplayValues = EffectiveDefault<boolean>
export type JobspyValues = {
sites: EffectiveDefault<string[]>
location: EffectiveDefault<string>
resultsWanted: EffectiveDefault<number>
hoursOld: EffectiveDefault<number>
countryIndeed: EffectiveDefault<string>
linkedinFetchDescription: EffectiveDefault<boolean>
}