Files
Reactive-Resume/src/routes/_home/-sections/hero.tsx
T
Luka Fagundes 3e16586d7a feat(jobs): add job listings with AI-powered resume tailoring (#2788)
* feat: add job listings feature with JSearch API integration, resume tailoring, and per-user rate limiting

* feat(jobs): add search filters UI, filter helper functions with tests, and job_search_quota DB migration

* feat(jobs): add pagination with 30 results per page and prev/next navigation

* refactor(job-detail): Adjust sheet width and scroll area height

* feat(ai): Add resume tailoring feature and prompt

* refactor(ai): Revise tailoring prompts and schema for full skill rewrite

* feat(ai): Add reference tailoring and output sanitization

* feat(testing): Add Vitest testing framework

* fix: address PR review - atomic rate limiting, calendar-month quota, skill sync warning, gitignore routeTree.gen.ts

* feat(jobs): Add location filter to job listings

* feat(job-listings): Add DOCX document generation

* feat(job-listings): Enable search by location and on Enter key

* feat(job-listings): Split location filter into city, state, and country

* feat(jobs): Implement job search adapter and JSearch

* Update 'locale/' directory

* feat(resume): Simplify filename generation and add tests

* fix(JSearch): reduce JSearch API usage to 1 request per search to prevent quota exhaustion

* fix(JSearch): Displayed quota amounts on Job Search functionality and settings fixed to pull from RapidAPI/JSearch response

* fix(internal rate limit): Removed internal rate limit and .env.example addition, cloud based implementation handles.

* style(job-filters): Adjust layout of switch filters

* fix(typecheck): Fixed typecheck issues introduced to sync with origin

* feat(jobs): Enhance tailor dialog with apply link and tags

* feat(locale files): updated locale files with the latest build

* feat(jobs): Add job search provider and integrate testing functionality

- Introduced `createJobSearchProvider` function to instantiate a JSearchProvider.
- Enhanced job search provider with methods for searching jobs, retrieving job details, and testing connection.
- Updated `vite.config.ts` to include new testing configurations and plugins.
- Added new dependencies in `package.json` for testing and document generation.
- Removed obsolete `vitest.config.ts` file.
- Improved job search provider tests for better coverage and reliability.

* refactor: Update job search routes and remove obsolete test configurations

- Removed the test configuration from `vite.config.ts`.
- Updated localization files to reflect changes in job search routes, renaming references from `jobs` to `job-search` across multiple languages.
- Adjusted autofix workflow to run formatting without the `--fix` flag for better control over code style adjustments.

* chore: Update dependencies and improve animation performance

- Added `jsdom` as a new dependency in `package.json`.
- Updated `vite-plus` and `vitest` to the latest versions for better compatibility.
- Enhanced animation components with `willChange` styles to optimize rendering performance.
- Adjusted various UI components to improve responsiveness and visual effects.
- Removed obsolete job details functionality from the job search provider and related tests.

* chore(locales): Update localization files for job search improvements

- Modified job search related strings to remove references to "this month" for a more concise format.
- Updated file references in localization entries to reflect changes in the job search component structure.
- Added new strings for API usage, quota remaining, and job fetching error messages across multiple languages.
- Removed obsolete "Monthly Usage" string from localization files.

* chore(dependencies): Update @typescript/native-preview to version 7.0.0-dev.20260319.1

---------

Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
2026-03-19 09:48:02 +01:00

158 lines
5.7 KiB
TypeScript

import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import { ArrowRightIcon, BookIcon, SparkleIcon } from "@phosphor-icons/react";
import { Link } from "@tanstack/react-router";
import { motion } from "motion/react";
import { CometCard } from "@/components/animation/comet-card";
import { Spotlight } from "@/components/animation/spotlight";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
export function Hero() {
return (
<section
id="hero"
className="relative flex min-h-svh w-svw flex-col items-center justify-center overflow-hidden border-b py-24"
>
<Spotlight />
<motion.div
initial={{ opacity: 0, y: 100 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 1.1, ease: "easeOut" }}
style={{ willChange: "transform, opacity" }}
>
<CometCard glareOpacity={0} className="3xl:max-w-7xl relative -mb-12 max-w-4xl px-8 md:-mb-24 md:px-12 lg:px-0">
<video
loop
muted
autoPlay
playsInline
// @ts-expect-error - typescript doesn't know about fetchPriority for video elements
fetchPriority="high"
src="/videos/timelapse.mp4"
aria-label={t`Timelapse demonstration of building a resume with Reactive Resume`}
className="pointer-events-none size-full rounded-md border object-cover"
/>
<div
aria-hidden="true"
className="pointer-events-none absolute inset-0 bg-linear-to-b from-transparent via-transparent via-40% to-background"
/>
</CometCard>
</motion.div>
<div className="xs:px-0 relative z-10 flex max-w-2xl flex-col items-center gap-y-6 px-4 text-center">
{/* Badge */}
<motion.a
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.45, delay: 0.55 }}
whileHover={{ y: -2, scale: 1.01 }}
whileTap={{ scale: 0.985 }}
style={{ willChange: "transform, opacity" }}
target="_blank"
rel="noopener noreferrer"
href="https://docs.rxresu.me/getting-started"
>
<Badge variant="secondary" className="h-auto gap-1.5 px-3 py-0.5">
<SparkleIcon aria-hidden="true" className="size-3.5" weight="fill" />
<Trans>What's new in the latest version?</Trans>
</Badge>
</motion.a>
{/* Headline */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.45, delay: 0.7 }}
style={{ willChange: "transform, opacity" }}
>
<Trans>
<p className="font-medium tracking-tight text-muted-foreground md:text-lg">Finally,</p>
<h1 className="mt-1 text-4xl font-bold tracking-tight md:text-5xl lg:text-6xl">
A free and open-source resume builder
</h1>
</Trans>
</motion.div>
{/* Description */}
<motion.p
className="max-w-xl text-base leading-relaxed text-muted-foreground md:text-lg"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.45, delay: 0.82 }}
style={{ willChange: "transform, opacity" }}
>
<Trans>
Reactive Resume is a free and open-source resume builder that simplifies the process of creating, updating,
and sharing your resume.
</Trans>
</motion.p>
{/* CTA Buttons */}
<motion.div
className="flex flex-col items-center gap-3 sm:flex-row sm:gap-4"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.45, delay: 0.95 }}
style={{ willChange: "transform, opacity" }}
>
<Button
size="lg"
nativeButton={false}
className="group relative overflow-hidden px-4"
render={
<Link to="/dashboard">
<span className="relative z-10 flex items-center gap-2">
<Trans>Get Started</Trans>
<ArrowRightIcon
aria-hidden="true"
className="size-4 transition-transform group-hover:translate-x-0.5"
/>
</span>
</Link>
}
/>
<Button
size="lg"
variant="ghost"
className="gap-2 px-4"
nativeButton={false}
render={
<a href="https://docs.rxresu.me" target="_blank" rel="noopener noreferrer">
<BookIcon aria-hidden="true" className="size-4" />
<Trans>Learn More</Trans>
<span className="sr-only">
<Trans>(opens in new tab)</Trans>
</span>
</a>
}
/>
</motion.div>
</div>
{/* Scroll indicator - decorative */}
<motion.div
aria-hidden="true"
role="presentation"
className="absolute inset-s-1/2 bottom-8 -translate-x-1/2"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1.25, duration: 0.7 }}
>
<motion.div
className="flex h-8 w-5 items-start justify-center rounded-full border border-muted-foreground/30 p-1.5"
animate={{ y: [0, 5, 0] }}
transition={{ duration: 1.5, repeat: Infinity, ease: "easeInOut" }}
style={{ willChange: "transform" }}
>
<motion.div className="h-1.5 w-1 rounded-full bg-muted-foreground/50" />
</motion.div>
</motion.div>
</section>
);
}