Custom welcome message (#234)

* json

* connect

* useWelcomMessage

* Update shared/src/messages/jobs-welcome.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Shaheer Sarfaraz 2026-02-25 02:59:38 +00:00 committed by GitHub
parent 7514aa1b28
commit 26dbed15b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 456 additions and 36 deletions

View File

@ -0,0 +1,57 @@
import welcomeMessages from "@shared/messages/jobs-welcome.json";
import { useMemo } from "react";
import { useProfile } from "./useProfile";
function hashCode(str: string): number {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash |= 0; // Convert to 32bit integer
}
return hash;
}
export function useWelcomeMessage(): string {
const { personName } = useProfile();
return useMemo(() => {
const firstName = personName?.split(" ")[0] || "User";
const today = new Date().toDateString();
let isFirstDay = true;
try {
let firstSeenDate = localStorage.getItem("jobOps_firstWelcomeDate");
if (!firstSeenDate) {
firstSeenDate = today;
localStorage.setItem("jobOps_firstWelcomeDate", today);
}
isFirstDay = firstSeenDate === today;
} catch (_e) {
// Ignore localStorage errors (e.g. private mode restrictions)
// Fallback to true so we just show the first message
}
const lines = welcomeMessages.lines;
let selectedIndex = 0; // Always default to the first message
if (!isFirstDay) {
// If it's not their first day, randomize consistently for the day
const seed = Math.abs(hashCode(`${firstName}-${today}`));
selectedIndex = seed % lines.length;
}
const line = lines[selectedIndex];
switch (line.placement) {
case "inline":
return line.text.replace("{name}", firstName);
case "prefix":
return `${firstName}, ${line.text}`;
case "suffix":
return `${line.text}, ${firstName}.`;
default:
return line.text;
}
}, [personName]);
}

View File

@ -1,4 +1,5 @@
import { PipelineProgress } from "@client/components";
import { useWelcomeMessage } from "@client/hooks/useWelcomeMessage";
import type { JobStatus } from "@shared/types.js";
import type React from "react";
@ -8,15 +9,14 @@ interface OrchestratorSummaryProps {
}
export const OrchestratorSummary: React.FC<OrchestratorSummaryProps> = ({
stats,
isPipelineRunning,
}) => {
const totalJobs = Object.values(stats).reduce((a, b) => a + b, 0);
const welcomeText = useWelcomeMessage();
return (
<section className="space-y-4">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold tracking-tight">Jobs</h1>
<h1 className="text-lg font-medium tracking-tight">{welcomeText}</h1>
</div>
{isPipelineRunning && (
@ -24,39 +24,6 @@ export const OrchestratorSummary: React.FC<OrchestratorSummaryProps> = ({
<PipelineProgress isRunning={isPipelineRunning} />
</div>
)}
{/* Compact metrics summary - demoted visual weight */}
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground/80">
<span>
<span className="tabular-nums">{stats.ready}</span> ready
</span>
<span className="text-border"></span>
<span>
<span className="tabular-nums">
{stats.discovered + stats.processing}
</span>{" "}
discovered
</span>
<span className="text-border"></span>
<span>
<span className="tabular-nums">{stats.applied}</span> applied
</span>
<span className="text-border"></span>
<span className="font-medium text-foreground/60">
{totalJobs} jobs total
</span>
{(stats.skipped > 0 || stats.expired > 0) && (
<>
<span className="text-border"></span>
<span className="text-muted-foreground/60">
<span className="tabular-nums">
{stats.skipped + stats.expired}
</span>{" "}
skipped
</span>
</>
)}
</div>
</section>
);
};

View File

@ -0,0 +1,396 @@
{
"version": "1.0.1",
"notes": {
"placementRule": "placement tells you where to put the first name relative to the line",
"placements": {
"prefix": "Render as: `${firstName}, ${line}`",
"suffix": "Render as: `${line}, ${firstName}.` (or just append the name with punctuation)",
"inline": "Line already contains `{name}` token. Replace it.",
"none": "No name needed for this one"
}
},
"lines": [
{
"id": "g001",
"placement": "inline",
"text": "Welcome back, {name}. The jobs missed you."
},
{
"id": "g002",
"placement": "none",
"text": "Welcome back. The backlog is still undefeated, but we can change that."
},
{
"id": "g003",
"placement": "inline",
"text": "{name} detected. Deploying mild chaos."
},
{
"id": "g004",
"placement": "none",
"text": "Good morning. Or whatever time it is. Lets find you a job."
},
{
"id": "g005",
"placement": "inline",
"text": "Welcome back, {name}. Same jobs. New ambition."
},
{
"id": "g006",
"placement": "none",
"text": "Alright. Lets pretend were excited to be here. Its more fun that way."
},
{
"id": "g007",
"placement": "inline",
"text": "Hi {name}. Your future self asked me to keep you honest."
},
{
"id": "g008",
"placement": "none",
"text": "The pipeline is quiet. Suspiciously quiet."
},
{
"id": "g009",
"placement": "inline",
"text": "Welcome back, {name}. Try not to break the ATS today. Its sensitive."
},
{
"id": "g010",
"placement": "none",
"text": "Lets find a job where “were a family” means we leave at 5."
},
{
"id": "g011",
"placement": "inline",
"text": "Nice of you to show up, {name}. The jobs didnt apply to themselves. Unfortunately."
},
{
"id": "g012",
"placement": "none",
"text": "We go again. With feelings. And also jobs."
},
{
"id": "g013",
"placement": "inline",
"text": "Welcome back, {name}. I saved you a fresh batch of jobs. Youre welcome."
},
{
"id": "g014",
"placement": "none",
"text": "Somewhere, a recruiter is about to misread your title."
},
{
"id": "g015",
"placement": "inline",
"text": "Hey {name}. Lets turn applied into done. Or at least applied into less sad."
},
{
"id": "g016",
"placement": "none",
"text": "If youre here, youre either ambitious or avoiding something. Lets assume the former."
},
{
"id": "g017",
"placement": "inline",
"text": "Welcome back, {name}. Time to bully the backlog (respectfully)."
},
{
"id": "g018",
"placement": "none",
"text": "No meetings here. Just you, the jobs, and your existential dread. Lets go."
},
{
"id": "g019",
"placement": "inline",
"text": "Youve returned, {name}. The system approves. Barely."
},
{
"id": "g020",
"placement": "none",
"text": "Your dream job doesnt exist. But one that pays the bills does. Lets find it."
},
{
"id": "g021",
"placement": "inline",
"text": "Welcome back, {name}. Today we choose now over later. Again."
},
{
"id": "g022",
"placement": "none",
"text": "I found more jobs. Youre welcome. Im sorry."
},
{
"id": "g023",
"placement": "inline",
"text": "Hi {name}. Your keyboard missed the violence."
},
{
"id": "g024",
"placement": "none",
"text": "Time to turn browsing into outcomes."
},
{
"id": "g025",
"placement": "inline",
"text": "Welcome back, {name}. Please do not feed the procrastination gremlin."
},
{
"id": "g026",
"placement": "none",
"text": "New day. Same mission. Lets get it."
},
{
"id": "g027",
"placement": "inline",
"text": "Alright {name}. Pick a job and make it nervous."
},
{
"id": "g028",
"placement": "none",
"text": "Your to do list called. It sounded stressed."
},
{
"id": "g029",
"placement": "inline",
"text": "Welcome back, {name}. Jobs are like buses. Miss one, and another will come. But lets not miss this one."
},
{
"id": "g030",
"placement": "none",
"text": "If its not tracked, it didnt happen."
},
{
"id": "g031",
"placement": "inline",
"text": "Hey {name}. Were doing the sensible thing today. Applying to jobs."
},
{
"id": "g032",
"placement": "none",
"text": "You have jobs. You have time. You have questionable optimism."
},
{
"id": "g033",
"placement": "inline",
"text": "Welcome back, {name}. Make the ATS regret its life choices."
},
{
"id": "g034",
"placement": "none",
"text": "Apply. Get auto rejected. Build character. Repeat."
},
{
"id": "g035",
"placement": "inline",
"text": "Good to see you, {name}. Lets turn Ready into Hired."
},
{
"id": "g036",
"placement": "none",
"text": "You cant pay rent with exposure. Lets go."
},
{
"id": "g037",
"placement": "inline",
"text": "Welcome back, {name}. Fresh opportunities and mild dread."
},
{
"id": "g038",
"placement": "none",
"text": "A clean inbox is a problem, lets get to work."
},
{
"id": "g039",
"placement": "inline",
"text": "Hi {name}. Your next offer is hiding behind consistency."
},
{
"id": "g040",
"placement": "none",
"text": "Cover letters are dead. Lets write one anyway, for the love of the game."
},
{
"id": "g041",
"placement": "inline",
"text": "Welcome back, {name}. Light chaos. Heavy output."
},
{
"id": "g042",
"placement": "none",
"text": "I checked. The jobs are still out there being jobs."
},
{
"id": "g043",
"placement": "inline",
"text": "Okay {name}. No drama. Just execution."
},
{
"id": "g044",
"placement": "none",
"text": "If youre waiting for motivation, youll be waiting a while."
},
{
"id": "g045",
"placement": "inline",
"text": "Welcome back, {name}. Increase your snacks today, just because."
},
{
"id": "g046",
"placement": "none",
"text": "The ATS isnt going to confuse itself. Get in there."
},
{
"id": "g047",
"placement": "inline",
"text": "Hey {name}. Im rooting for you. Quietly. Like a normal app."
},
{
"id": "g048",
"placement": "none",
"text": "Job search mode is on. Feelings are optional."
},
{
"id": "g049",
"placement": "inline",
"text": "Welcome back, {name}. Your 'later' has arrived."
},
{
"id": "g050",
"placement": "none",
"text": "Lets be annoyingly consistent. It works. You know it works. Lets do it."
},
{
"id": "g051",
"placement": "inline",
"text": "Hi {name}. Id say take your time but Id be lying, so lets not."
},
{
"id": "g052",
"placement": "none",
"text": "Ive seen your screen time. Lets focus on the job board."
},
{
"id": "g053",
"placement": "inline",
"text": "Welcome back, {name}. Turn effort into results."
},
{
"id": "g054",
"placement": "none",
"text": "Somewhere, an ATS is preparing to be unimpressed."
},
{
"id": "g055",
"placement": "inline",
"text": "Hey {name}. Apply like you mean it."
},
{
"id": "g056",
"placement": "none",
"text": "Your dream job is out there. But so are a lot of other jobs. Lets find one thats real."
},
{
"id": "g057",
"placement": "inline",
"text": "Welcome back, {name}. Minimal drama. Maximum output."
},
{
"id": "g058",
"placement": "none",
"text": "Todays vibe is avoiding the phrase 'fast paced environment'."
},
{
"id": "g059",
"placement": "inline",
"text": "Alright {name}. Lets make the algorithm regret ignoring you."
},
{
"id": "g060",
"placement": "none",
"text": "Your calendar is empty. That means we apply, not scroll."
},
{
"id": "g061",
"placement": "inline",
"text": "Welcome back, {name}. The pipeline yearns for activity."
},
{
"id": "g062",
"placement": "none",
"text": "Open the job. Read it. Actually read it."
},
{
"id": "g063",
"placement": "inline",
"text": "Hi {name}. Your next win is one follow up away."
},
{
"id": "g064",
"placement": "none",
"text": "If the job description asks for a rockstar, run."
},
{
"id": "g065",
"placement": "inline",
"text": "Welcome back, {name}. jobops believes in you more than you do."
},
{
"id": "g066",
"placement": "none",
"text": "Were not here to vibe. Were here to convert."
},
{
"id": "g067",
"placement": "inline",
"text": "Hey {name}. Youve got this. And if you dont, youve got this."
},
{
"id": "g068",
"placement": "none",
"text": "Stop refreshing your inbox. Its been four seconds."
},
{
"id": "g069",
"placement": "inline",
"text": "Welcome back, {name}. The Ready tab is feeling ignored."
},
{
"id": "g070",
"placement": "none",
"text": "Perfection is the enemy of good enough to get an interview."
},
{
"id": "g071",
"placement": "inline",
"text": "Welcome back, {name}. Youre one good week from a different life."
},
{
"id": "g072",
"placement": "none",
"text": "I brought jobs. You bring focus. Deal."
},
{
"id": "g073",
"placement": "inline",
"text": "Hi {name}. Overthinking wont write your cover letter."
},
{
"id": "g074",
"placement": "none",
"text": "A recruiter somewhere just typed ninja. Stay strong."
},
{
"id": "g075",
"placement": "inline",
"text": "Welcome back, {name}. This is where the luck gets manufactured."
}
]
}