copy webhook payload

This commit is contained in:
DaKheera47 2026-01-02 16:56:15 +00:00
parent a6310af294
commit e070ff9d60
3 changed files with 18 additions and 87 deletions

View File

@ -21,7 +21,7 @@ import { toast } from "sonner";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { copyTextToClipboard, formatJobForLlmContext } from "@client/lib/jobCopy";
import { copyTextToClipboard, formatJobForWebhook } from "@client/lib/jobCopy";
import type { Job } from "../../shared/types";
import { ScoreIndicator } from "./ScoreIndicator";
import { StatusBadge } from "./StatusBadge";
@ -64,6 +64,7 @@ export const JobCard: React.FC<JobCardProps> = ({
gradcracker: "Gradcracker",
indeed: "Indeed",
linkedin: "LinkedIn",
ukvisajobs: "UK Visa Jobs",
};
const hasPdf = !!job.pdfPath;
@ -78,8 +79,8 @@ export const JobCard: React.FC<JobCardProps> = ({
const handleCopyInfo = async () => {
try {
await copyTextToClipboard(formatJobForLlmContext(job));
toast.success("Copied job info", { description: "LLM-ready context copied to clipboard." });
await copyTextToClipboard(formatJobForWebhook(job));
toast.success("Copied job info", { description: "Webhook payload copied to clipboard." });
} catch {
toast.error("Could not copy job info");
}

View File

@ -29,7 +29,7 @@ import {
} from "@/components/ui/dropdown-menu";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { cn } from "@/lib/utils";
import { copyTextToClipboard, formatJobForLlmContext } from "@client/lib/jobCopy";
import { copyTextToClipboard, formatJobForWebhook } from "@client/lib/jobCopy";
import type { Job } from "../../shared/types";
import { StatusBadge } from "./StatusBadge";
@ -67,6 +67,7 @@ const sourceLabel: Record<Job["source"], string> = {
gradcracker: "Gradcracker",
indeed: "Indeed",
linkedin: "LinkedIn",
ukvisajobs: "UK Visa Jobs",
};
const defaultSortDirection: Record<JobSortKey, JobSortDirection> = {
@ -146,8 +147,8 @@ export const JobTable: React.FC<JobTableProps> = ({
const handleCopyInfo = async (job: Job) => {
try {
await copyTextToClipboard(formatJobForLlmContext(job));
toast.success("Copied job info", { description: "LLM-ready context copied to clipboard." });
await copyTextToClipboard(formatJobForWebhook(job));
toast.success("Copied job info", { description: "Webhook payload copied to clipboard." });
} catch {
toast.error("Could not copy job info");
}

View File

@ -1,86 +1,15 @@
import type { Job } from "@shared/types";
const pushLine = (lines: string[], label: string, value: unknown) => {
if (value == null) return;
const normalized = typeof value === "string" ? value.trim() : String(value);
if (!normalized) return;
lines.push(`${label}: ${normalized}`);
};
const pushBlock = (lines: string[], heading: string, value: string | null | undefined) => {
const normalized = value?.trim();
if (!normalized) return;
lines.push("");
lines.push(`${heading}:`);
lines.push(normalized);
};
export const formatJobForLlmContext = (job: Job) => {
const jobLink = job.applicationLink || job.jobUrl;
const lines: string[] = [];
lines.push("JOB CONTEXT");
pushLine(lines, "Title", job.title);
pushLine(lines, "Company", job.employer);
pushLine(lines, "Source", job.source);
pushLine(lines, "Status", job.status);
pushLine(lines, "Job URL", job.jobUrl);
pushLine(lines, "Application link", job.applicationLink);
pushLine(lines, "Best link", jobLink);
pushLine(lines, "Direct URL", job.jobUrlDirect);
pushLine(lines, "Source job id", job.sourceJobId);
pushLine(lines, "Location", job.location);
pushLine(lines, "Remote", job.isRemote);
pushLine(lines, "Disciplines", job.disciplines);
pushLine(lines, "Job type", job.jobType);
pushLine(lines, "Job level", job.jobLevel);
pushLine(lines, "Job function", job.jobFunction);
pushLine(lines, "Listing type", job.listingType);
pushLine(lines, "Salary", job.salary);
if (job.salaryMinAmount != null || job.salaryMaxAmount != null) {
pushLine(
lines,
"Salary range",
[
job.salaryMinAmount != null ? String(job.salaryMinAmount) : null,
job.salaryMaxAmount != null ? String(job.salaryMaxAmount) : null,
]
.filter(Boolean)
.join(" - "),
export const formatJobForWebhook = (job: Job) => {
return JSON.stringify(
{
event: "job.completed",
sentAt: new Date().toISOString(),
job,
},
null,
2,
);
}
pushLine(lines, "Salary interval", job.salaryInterval);
pushLine(lines, "Salary currency", job.salaryCurrency);
pushLine(lines, "Salary source", job.salarySource);
pushLine(lines, "Degree required", job.degreeRequired);
pushLine(lines, "Starting", job.starting);
pushLine(lines, "Deadline", job.deadline);
pushLine(lines, "Date posted", job.datePosted);
pushLine(lines, "Skills", job.skills);
pushLine(lines, "Experience", job.experienceRange);
pushLine(lines, "Emails", job.emails);
pushLine(lines, "Company industry", job.companyIndustry);
pushLine(lines, "Company URL", job.companyUrlDirect || job.employerUrl);
pushLine(lines, "Company employees", job.companyNumEmployees);
pushLine(lines, "Company revenue", job.companyRevenue);
pushLine(lines, "Company rating", job.companyRating);
pushLine(lines, "Company reviews", job.companyReviewsCount);
pushLine(lines, "Company addresses", job.companyAddresses);
pushLine(lines, "Discovered", job.discoveredAt);
pushLine(lines, "Processed", job.processedAt);
pushBlock(lines, "Job description", job.jobDescription);
pushBlock(lines, "Company description", job.companyDescription);
return lines.join("\n").trim() + "\n";
};
export async function copyTextToClipboard(text: string) {