Skip to content

Commit cae3997

Browse files
committed
migrate npm to pnpm
1 parent 86230db commit cae3997

17 files changed

Lines changed: 19168 additions & 19357 deletions

.github/workflows/deploy-vercel.yml

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,27 @@ jobs:
1414

1515
steps:
1616
- name: Checkout code
17-
uses: actions/checkout@v3
17+
uses: actions/checkout@v4
1818

19-
- name: Install Vercel CLI
20-
run: npm install -g vercel
19+
- name: Setup pnpm
20+
uses: pnpm/action-setup@v4
21+
with:
22+
version: 11.4.0
2123

22-
- name: Pull Vercel Environment Info
23-
run: vercel pull --yes --environment=production --token ${{ secrets.VERCEL_TOKEN }}
24+
- name: Setup Node.js
25+
uses: actions/setup-node@v4
26+
with:
27+
node-version: 22
28+
cache: pnpm
29+
30+
- name: Install dependencies
31+
run: pnpm install --frozen-lockfile
32+
33+
- name: Pull Vercel environment
34+
run: pnpm dlx vercel@latest pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
35+
36+
- name: Build
37+
run: pnpm build
2438

2539
- name: Deploy to Production
26-
run: |
27-
vercel --prod --confirm \
28-
--token ${{ secrets.VERCEL_TOKEN }} \
29-
-e RESEND_API_KEY=${{ secrets.RESEND_API_KEY }}
40+
run: pnpm dlx vercel@latest deploy --prod --yes --token=${{ secrets.VERCEL_TOKEN }} -e RESEND_API_KEY=${{ secrets.RESEND_API_KEY }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
/node_modules
55
/.pnp
66
.pnp.*
7+
.pnpm-store/
78
.yarn/*
89
!.yarn/patches
910
!.yarn/plugins

app/components/Navbar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { Button } from "@/components/ui/button";
44
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
5-
import { motion, useReducedMotion } from "framer-motion";
5+
import { motion, useReducedMotion } from "motion/react";
66
import { MenuIcon } from "lucide-react";
77
import Link from "next/link";
88
import Logo from "./Logo";

app/components/animation/CharacterFadupText.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { motion, Variants } from "framer-motion";
3+
import { motion, Variants } from "motion/react";
44

55
type Props = {
66
text: string;

app/components/animation/FadeUp.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"use client";
2-
import { motion, useInView, Variants } from "framer-motion";
2+
import { motion, useInView, Variants } from "motion/react";
33
import * as React from "react";
44

55
interface FadeUpProps {

app/components/animation/Reveal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import { cn } from "@/lib/utils";
4-
import { motion, useInView, useReducedMotion } from "framer-motion";
4+
import { motion, useInView, useReducedMotion } from "motion/react";
55
import { ReactNode, useRef } from "react";
66

77
type RevealProps = {

app/pages/home/BlogsSection.tsx

Lines changed: 126 additions & 133 deletions
Large diffs are not rendered by default.

app/pages/home/ContactSection.tsx

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export default function ContactSection() {
7878
};
7979

8080
return (
81-
<section className="mx-auto w-full max-w-6xl px-6 pb-24 pt-10 md:px-16" id="contact">
81+
<section className="mx-auto w-full max-w-7xl px-6 pb-24 pt-10 md:px-16" id="contact">
8282
<div className="space-y-8">
8383
<Reveal
8484
direction="up"
@@ -87,15 +87,11 @@ export default function ContactSection() {
8787
<div className="space-y-4">
8888
<p className="text-[0.72rem] uppercase tracking-[0.32em] text-primary">Contact</p>
8989
<h2 className="max-w-xl font-incognito text-[clamp(2.6rem,5.2vw,4.8rem)] leading-[0.95] tracking-[-0.04em]">
90-
Open for projects, roles, and direct conversations.
90+
Open for conversations.
9191
</h2>
9292
</div>
9393

9494
<div className="flex flex-col gap-4 xl:items-end">
95-
<p className="max-w-lg text-base leading-7 text-muted-foreground md:text-lg xl:text-right">
96-
If there is something worth building or discussing, this is the cleanest way to reach
97-
me.
98-
</p>
9995
<div className="flex flex-wrap gap-3 xl:justify-end">
10096
{["reply by email", "project discussions", "freelance and product work"].map(
10197
(item) => (
@@ -131,22 +127,19 @@ export default function ContactSection() {
131127
}}
132128
>
133129
<div className="flex items-start justify-between gap-4">
134-
<div className="space-y-3">
130+
<div className="flex items-center gap-2">
135131
<span className="inline-flex h-11 w-11 items-center justify-center rounded-full border border-border/70 bg-background/70">
136132
<Icon className="h-4 w-4" />
137133
</span>
138134
<div className="space-y-2">
139135
<h3 className="font-incognito text-3xl leading-none">{channel.title}</h3>
140-
<p className="max-w-md text-sm leading-6 text-muted-foreground">
141-
{channel.description}
142-
</p>
143136
</div>
144137
</div>
145138

146139
<ArrowUpRight className="h-5 w-5 shrink-0 text-muted-foreground transition-transform duration-300 group-hover:-translate-y-0.5 group-hover:translate-x-0.5 group-hover:text-foreground" />
147140
</div>
148141

149-
<p className="mt-6 text-sm font-medium tracking-[0.02em] text-foreground/90">
142+
<p className="mt-4 text-sm font-medium tracking-[0.02em] text-foreground/90">
150143
{channel.value}
151144
</p>
152145
</Link>

app/pages/home/ExperienceList.tsx

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
"use client";
2+
3+
import { Badge } from "@/components/ui/badge";
4+
import { formatDate } from "@/lib/utils";
5+
import { Experience } from "@/types/experience";
6+
import { BriefcaseBusiness, MapPin } from "lucide-react";
7+
import { motion } from "motion/react";
8+
import Image from "next/image";
9+
10+
function getEmploymentLabel(type?: string) {
11+
if (!type) return "Role";
12+
13+
const labels: Record<string, string> = {
14+
fulltime: "Full-time",
15+
parttime: "Part-time",
16+
internship: "Internship",
17+
freelance: "Freelance",
18+
};
19+
20+
return labels[type] || type;
21+
}
22+
23+
function getExperienceRange(startDate: string, endDate?: string | null, isCurrent?: boolean) {
24+
return `${formatDate(startDate)} - ${isCurrent ? "Present" : formatDate(endDate || startDate)}`;
25+
}
26+
27+
function ExperienceLogo({ experience }: { experience: Experience }) {
28+
return (
29+
<div className="relative flex h-14 w-14 shrink-0 items-center justify-center overflow-hidden rounded-2xl border border-border/70 bg-background/70">
30+
{experience.logo?.url ? (
31+
<Image
32+
src={experience.logo.url}
33+
alt={experience.logo.alt || `${experience.company} logo`}
34+
fill
35+
className="object-cover"
36+
/>
37+
) : (
38+
<BriefcaseBusiness className="h-5 w-5 text-foreground/70" />
39+
)}
40+
</div>
41+
);
42+
}
43+
44+
function ExperienceDetail({ experience }: { experience: Experience }) {
45+
return (
46+
<motion.article
47+
initial={{ opacity: 0, y: 18, filter: "blur(8px)" }}
48+
whileInView={{ opacity: 1, y: 0, filter: "blur(0px)" }}
49+
viewport={{ once: true, margin: "-12% 0px" }}
50+
transition={{ duration: 0.34, ease: [0.22, 1, 0.36, 1] }}
51+
className="space-y-6"
52+
>
53+
<div className="flex flex-wrap items-start justify-between gap-4">
54+
<div className="flex items-start gap-4">
55+
<ExperienceLogo experience={experience} />
56+
<div className="space-y-2">
57+
<div className="flex flex-wrap items-center gap-2">
58+
<h3 className="font-incognito text-[2rem] leading-none md:text-[2.35rem]">
59+
{experience.company}
60+
</h3>
61+
{experience.isCurrent ? (
62+
<span className="rounded-full bg-primary px-3 py-1 text-[0.68rem] font-medium uppercase tracking-[0.22em] text-primary-foreground">
63+
Current
64+
</span>
65+
) : null}
66+
</div>
67+
<p className="text-sm text-muted-foreground md:text-base">
68+
<span className="font-medium text-foreground/88">{experience.role}</span>
69+
<span className="px-2 text-primary">/</span>
70+
{getEmploymentLabel(experience.employmentType)}
71+
</p>
72+
</div>
73+
</div>
74+
</div>
75+
76+
<div className="grid gap-3 text-sm text-muted-foreground md:grid-cols-2">
77+
{experience.location ? (
78+
<p className="flex items-center gap-2">
79+
<MapPin className="h-4 w-4 text-primary" />
80+
{experience.location}
81+
</p>
82+
) : null}
83+
<p>{getExperienceRange(experience.startDate, experience.endDate, experience.isCurrent)}</p>
84+
</div>
85+
86+
{experience.description ? (
87+
<p className="max-w-3xl text-sm leading-7 text-muted-foreground md:text-base">
88+
{experience.description}
89+
</p>
90+
) : null}
91+
92+
{experience.highlights?.length ? (
93+
<ul className="grid gap-x-8 gap-y-3 md:grid-cols-2">
94+
{experience.highlights.map((point, index) => (
95+
<li
96+
key={`${experience._id}-detail-${index}`}
97+
className="flex gap-3 text-sm leading-7 text-muted-foreground"
98+
>
99+
<span className="mt-3 h-1.5 w-1.5 shrink-0 rounded-full bg-primary" />
100+
<span>{point}</span>
101+
</li>
102+
))}
103+
</ul>
104+
) : null}
105+
106+
{experience.technologies?.length ? (
107+
<div className="flex flex-wrap gap-2">
108+
{experience.technologies.map((tech) => (
109+
<Badge
110+
key={`${experience._id}-${tech}`}
111+
variant="secondary"
112+
className="rounded-full border px-3 py-1 text-[0.72rem] font-medium uppercase tracking-[0.16em] text-foreground/88"
113+
style={{
114+
borderColor: "color-mix(in oklch, var(--border) 56%, var(--primary) 44%)",
115+
background:
116+
"linear-gradient(135deg, color-mix(in oklch, var(--background) 84%, var(--primary) 16%), color-mix(in oklch, var(--background) 94%, var(--primary) 6%))",
117+
}}
118+
>
119+
{tech}
120+
</Badge>
121+
))}
122+
</div>
123+
) : null}
124+
</motion.article>
125+
);
126+
}
127+
128+
export default function ExperienceList({ experiences }: { experiences: Experience[] }) {
129+
if (!experiences.length) {
130+
return (
131+
<div className="rounded-2xl border border-dashed border-border/70 px-6 py-10 text-muted-foreground">
132+
No experience entries are available yet.
133+
</div>
134+
);
135+
}
136+
137+
return (
138+
<div className="relative">
139+
<div
140+
aria-hidden="true"
141+
className="absolute bottom-0 left-4 top-0 w-px md:left-[10.25rem]"
142+
style={{
143+
background:
144+
"linear-gradient(180deg, color-mix(in oklch, var(--primary) 66%, transparent), color-mix(in oklch, var(--secondary) 42%, transparent) 48%, color-mix(in oklch, var(--primary) 22%, transparent))",
145+
}}
146+
/>
147+
148+
<div className="space-y-6 md:space-y-8">
149+
{experiences.map((experience, index) => {
150+
const tint = index % 2 === 0 ? "var(--primary)" : "var(--secondary)";
151+
152+
return (
153+
<article
154+
key={experience._id || `${experience.company}-${experience.role}-${index}`}
155+
className="relative grid gap-5 pl-12 md:grid-cols-[8.75rem_3rem_minmax(0,1fr)] md:gap-0 md:pl-0"
156+
>
157+
<div className="hidden pr-5 pt-5 text-right md:block">
158+
<p className="text-[0.7rem] uppercase tracking-[0.24em] text-muted-foreground">
159+
{experience.isCurrent ? "Current role" : "Previous role"}
160+
</p>
161+
</div>
162+
163+
<div
164+
aria-hidden="true"
165+
className="absolute left-4 top-9 h-3 w-3 -translate-x-1/2 rounded-full border-2 border-background shadow-[0_0_0_8px_var(--background)] md:hidden"
166+
style={{
167+
background: `color-mix(in oklch, ${tint} 82%, var(--background) 18%)`,
168+
}}
169+
/>
170+
171+
<div className="relative hidden justify-center pt-5 md:flex">
172+
<div
173+
className="h-3.5 w-3.5 rounded-full border-2 border-background shadow-[0_0_0_10px_var(--background)]"
174+
style={{
175+
background: `color-mix(in oklch, ${tint} 82%, var(--background) 18%)`,
176+
}}
177+
/>
178+
</div>
179+
180+
<div className="relative flex flex-col gap-5 border-t border-border/65 pt-5 md:pt-6">
181+
<div
182+
aria-hidden="true"
183+
className="absolute left-0 top-0 h-px w-28"
184+
style={{
185+
background: `linear-gradient(90deg, color-mix(in oklch, ${tint} 58%, transparent), transparent)`,
186+
}}
187+
/>
188+
189+
<ExperienceDetail experience={experience} />
190+
</div>
191+
</article>
192+
);
193+
})}
194+
</div>
195+
</div>
196+
);
197+
}

0 commit comments

Comments
 (0)