refactor(v4.0.0-alpha): beginning of a new era

This commit is contained in:
Amruth Pillai
2023-11-05 12:31:42 +01:00
parent 0ba6a444e2
commit 22933bd412
505 changed files with 81829 additions and 0 deletions

View File

@ -0,0 +1,46 @@
import { Book, SignOut } from "@phosphor-icons/react";
import { Button } from "@reactive-resume/ui";
import { Link } from "react-router-dom";
import { useLogout } from "@/client/services/auth";
import { useAuthStore } from "@/client/stores/auth";
export const HeroCTA = () => {
const { logout } = useLogout();
const isLoggedIn = useAuthStore((state) => !!state.user);
if (isLoggedIn) {
return (
<>
<Button asChild size="lg">
<Link to="/dashboard">Go to Dashboard</Link>
</Button>
<Button size="lg" variant="link" onClick={() => logout()}>
<SignOut className="mr-3" />
Logout
</Button>
</>
);
}
if (!isLoggedIn) {
return (
<>
<Button asChild size="lg">
<Link to="/auth/login">Get started</Link>
</Button>
<Button asChild size="lg" variant="link">
<a href="https://docs.rxresu.me" target="_blank" rel="noopener noreferrer nofollow">
<Book className="mr-3" />
Learn more
</a>
</Button>
</>
);
}
return null;
};

View File

@ -0,0 +1,47 @@
export const Decoration = {
Grid: () => (
<svg
aria-hidden="true"
className="absolute inset-0 -z-10 h-full w-full stroke-foreground/10 opacity-60 [mask-image:radial-gradient(100%_100%_at_top_right,white,transparent)] dark:opacity-40"
>
<defs>
<pattern
id="983e3e4c-de6d-4c3f-8d64-b9761d1534cc"
width={200}
height={200}
x="50%"
y={-1}
patternUnits="userSpaceOnUse"
>
<path d="M.5 200V.5H200" fill="none" />
</pattern>
</defs>
<svg x="50%" y={-1} className="overflow-visible fill-border/20">
<path
d="M-200 0h201v201h-201Z M600 0h201v201h-201Z M-400 600h201v201h-201Z M200 800h201v201h-201Z"
strokeWidth={0}
/>
</svg>
<rect
width="100%"
height="100%"
strokeWidth={0}
fill="url(#983e3e4c-de6d-4c3f-8d64-b9761d1534cc)"
/>
</svg>
),
Gradient: () => (
<div
aria-hidden="true"
className="absolute left-[calc(50%-4rem)] top-10 -z-10 transform-gpu blur-3xl sm:left-[calc(50%-18rem)] lg:left-48 lg:top-[calc(50%-30rem)] xl:left-[calc(50%-24rem)]"
>
<div
className="aspect-[1108/632] h-96 w-[69.25rem] bg-gradient-to-r from-[#6f8cbb] to-[#c93b37] opacity-40 dark:opacity-20"
style={{
clipPath:
"polygon(73.6% 51.7%, 91.7% 11.8%, 100% 46.4%, 97.4% 82.2%, 92.5% 84.9%, 75.7% 64%, 55.3% 47.5%, 46.5% 49.4%, 45% 62.9%, 50.3% 87.2%, 21.3% 64.1%, 0.1% 100%, 5.4% 51.1%, 21.4% 63.9%, 58.9% 0.2%, 73.6% 51.7%)",
}}
/>
</div>
),
};

View File

@ -0,0 +1,65 @@
import { ArrowRight } from "@phosphor-icons/react";
import { Badge, Button } from "@reactive-resume/ui";
import { motion } from "framer-motion";
import Tilt from "react-parallax-tilt";
import { defaultTiltProps } from "@/client/constants/parallax-tilt";
import { HeroCTA } from "./call-to-action";
import { Decoration } from "./decoration";
export const HeroSection = () => (
<section className="relative">
<Decoration.Grid />
<Decoration.Gradient />
<div className="mx-auto max-w-7xl px-6 lg:flex lg:h-screen lg:items-center lg:px-12">
<motion.div
className="mx-auto max-w-3xl shrink-0 lg:mx-0 lg:max-w-xl lg:pt-8"
initial={{ opacity: 0, x: -100 }}
whileInView={{ opacity: 1, x: 0 }}
>
<div className="mt-24 flex items-center gap-x-4 sm:mt-32 lg:mt-0">
<Badge>Version 4</Badge>
<Button variant="link" className="space-x-2 text-left">
<p>What's new in the latest version</p>
<ArrowRight />
</Button>
</div>
<div className="mt-10 space-y-2">
<h6 className="text-base font-bold tracking-wide">Finally,</h6>
<h1 className="text-4xl font-bold tracking-tight sm:text-6xl">
A free and open-source resume builder.
</h1>
</div>
<p className="prose prose-base prose-zinc mt-6 text-lg leading-8 dark:prose-invert">
A free and open-source resume builder that simplifies the process of creating, updating,
and sharing your resume.
</p>
<div className="mt-10 flex items-center gap-x-8">
<HeroCTA />
</div>
</motion.div>
<div className="mx-auto mt-16 flex max-w-2xl sm:mt-24 lg:ml-10 lg:mr-0 lg:mt-0 lg:max-w-none lg:flex-none xl:ml-20">
<div className="max-w-3xl flex-none sm:max-w-5xl lg:max-w-none">
<motion.div initial={{ opacity: 0, x: 100 }} whileInView={{ opacity: 1, x: 0 }}>
<Tilt {...defaultTiltProps}>
<img
width={3600}
height={2078}
src="/screenshots/builder.png"
alt="Reactive Resume - Screenshot - Builder Screen"
className="w-[76rem] rounded-lg bg-background/5 shadow-2xl ring-1 ring-foreground/10"
/>
</Tilt>
</motion.div>
</div>
</div>
</div>
</section>
);

View File

@ -0,0 +1,60 @@
import { Button } from "@reactive-resume/ui";
import { cn } from "@reactive-resume/utils";
type LogoProps = { company: string };
const Logo = ({ company }: LogoProps) => (
<div
className={cn(
"col-span-2 col-start-2 sm:col-start-auto lg:col-span-1",
company === "twilio" && "sm:col-start-2",
)}
>
{/* Show on Light Theme */}
<img
className="block max-h-12 object-contain dark:hidden"
src={`/brand-logos/dark/${company}.svg`}
alt={company}
width={212}
height={48}
/>
{/* Show on Dark Theme */}
<img
className="hidden max-h-12 object-contain dark:block"
src={`/brand-logos/light/${company}.svg`}
alt={company}
width={212}
height={48}
/>
</div>
);
const logoList: string[] = ["amazon", "google", "postman", "twilio", "zalando"];
export const LogoCloudSection = () => (
<section className="relative py-24 sm:py-32">
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<p className="text-center text-lg leading-relaxed">
Reactive Resume has helped people land jobs at these great companies:
</p>
<div className="mx-auto mt-10 grid max-w-lg grid-cols-4 items-center gap-x-8 gap-y-10 sm:max-w-xl sm:grid-cols-6 sm:gap-x-10 lg:mx-0 lg:max-w-none lg:grid-cols-5">
{logoList.map((company) => (
<Logo key={company} company={company} />
))}
</div>
<p className="mx-auto mt-8 max-w-sm text-center leading-relaxed">
If this app has helped you with your job hunt, let me know by reaching out through{" "}
<Button asChild variant="link" className="p-0">
<a
href="https://www.amruthpillai.com/#contact"
target="_blank"
rel="noopener noreferrer nofollow"
>
this contact form
</a>
</Button>
.
</p>
</div>
</section>
);

View File

@ -0,0 +1,57 @@
import { animate, motion } from "framer-motion";
import { useEffect, useRef, useState } from "react";
type CounterProps = { from: number; to: number };
export const Counter = ({ from, to }: CounterProps) => {
const [isInView, setIsInView] = useState(false);
const nodeRef = useRef<HTMLParagraphElement | null>(null);
useEffect(() => {
const node = nodeRef.current;
if (!node) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setIsInView(true);
}
});
},
{ threshold: 0.1 },
);
observer.observe(node);
return () => {
observer.unobserve(node);
};
}, []);
useEffect(() => {
if (!isInView) return;
const node = nodeRef.current;
if (!node) return;
const controls = animate(from, to, {
duration: 1,
onUpdate(value) {
node.textContent = Math.round(value).toLocaleString();
},
});
return () => controls.stop();
}, [from, to, isInView]);
return (
<motion.span
ref={nodeRef}
transition={{ duration: 0.5 }}
initial={{ opacity: 0, scale: 0.1 }}
animate={isInView ? { opacity: 1, scale: 1 } : {}}
/>
);
};

View File

@ -0,0 +1,29 @@
import { Counter } from "./counter";
type Statistic = {
name: string;
value: number;
};
const stats: Statistic[] = [
{ name: "GitHub Stars", value: 11800 },
{ name: "Users Signed Up", value: 300000 },
{ name: "Resumes Generated", value: 400000 },
];
export const StatisticsSection = () => (
<section className="relative py-24 sm:py-32">
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<dl className="grid grid-cols-1 gap-x-8 gap-y-16 text-center lg:grid-cols-3">
{stats.map((stat, index) => (
<div key={index} className="mx-auto flex max-w-xs flex-col gap-y-4">
<dt className="text-base leading-7 opacity-60">{stat.name}</dt>
<dd className="order-first text-3xl font-semibold tracking-tight sm:text-5xl">
<Counter from={0} to={stat.value} />+
</dd>
</div>
))}
</dl>
</div>
</section>
);