"use client";
import { useState, useTransition } from "react";
import { ChipInput } from "@/components/chip-input";
import { AnimatePresence, motion } from "framer-motion";
import { AwardIcon, FolderGitIcon, GraduationCapIcon, PlusIcon, Trash2Icon, WrenchIcon } from "lucide-react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Badge } from "@/components/ui/badge";
import { SaveIndicator } from "@/components/save-indicator";
import { useAutosave } from "@/hooks/use-autosave";
import {
createCertificationAction,
createEducationAction,
createProjectAction,
createSkillGroupAction,
deleteCertificationAction,
deleteEducationAction,
deleteProjectAction,
deleteSkillGroupAction,
updateEducationAction,
updateProjectAction,
updateSkillGroupAction,
} from "@/server/actions";
type Education = {
id: string;
school: string;
degree: string;
field: string;
location: string;
startDate: string;
endDate: string;
gpa: string;
details: string;
};
type Project = { id: string; name: string; role: string; url: string; description: string; tags: string[] };
type SkillGroup = { id: string; name: string; skills: string[] };
type Certification = { id: string; name: string; issuer: string; date: string; url: string };
export function ExtrasPanel({
education,
projects,
skills,
certifications,
}: {
education: Education[];
projects: Project[];
skills: SkillGroup[];
certifications: Certification[];
}) {
return (
);
}
// ---------------------------------------------------------------------------
function SkillsCard({ skills }: { skills: SkillGroup[] }) {
const [pending, startTransition] = useTransition();
const [removed, setRemoved] = useState>(new Set());
return (
Skills
{skills.length === 0 && (
Group your skills, e.g. “Languages”, “Infrastructure”.
)}
{skills
.filter((group) => !removed.has(group.id))
.map((group) => (
setRemoved((prev) => new Set(prev).add(group.id))}
/>
))}
);
}
function SkillGroupRow({ group, onRemoved }: { group: SkillGroup; onRemoved: () => void }) {
const [values, setValues] = useState({ name: group.name, skills: group.skills });
const { state, push } = useAutosave<{ name: string; skills: string[] }>((next) =>
updateSkillGroupAction(group.id, { name: next.name, skills: next.skills }),
);
const set = (patch: Partial) => {
const next = { ...values, ...patch };
setValues(next);
push(next);
};
return (
set({ name: event.target.value })}
className="h-9 border-0 bg-transparent px-0 text-base font-semibold shadow-none focus-visible:ring-0 md:h-7 md:text-[13px]"
/>
set({ skills })}
placeholder="Python, Go, Rust, TypeScript"
/>
);
}
// ---------------------------------------------------------------------------
function EducationCard({ education }: { education: Education[] }) {
const [pending, startTransition] = useTransition();
const [removed, setRemoved] = useState>(new Set());
return (
Education
{education.length === 0 && (
Where you studied, what in, and when. Resumes draw on this after the roles.
)}
{education
.filter((item) => !removed.has(item.id))
.map((item) => (
setRemoved((prev) => new Set(prev).add(item.id))}
/>
))}
);
}
function EducationRow({ item, onRemoved }: { item: Education; onRemoved: () => void }) {
const [values, setValues] = useState({
school: item.school,
degree: item.degree,
field: item.field,
startDate: item.startDate,
endDate: item.endDate,
details: item.details,
});
const { state, push } = useAutosave((next) => updateEducationAction(item.id, next));
const set = (patch: Partial) => {
const next = { ...values, ...patch };
setValues(next);
push(next);
};
return (
set({ school: event.target.value })}
className="h-9 border-0 bg-transparent px-0 text-base font-semibold shadow-none focus-visible:ring-0 md:h-7 md:text-[13px]"
/>
set({ degree: event.target.value })}
placeholder="B.S."
/>
set({ field: event.target.value })}
placeholder="Computer Science"
/>
set({ startDate: event.target.value })}
/>
set({ endDate: event.target.value })}
/>
set({ details: event.target.value })}
placeholder="Honours, coursework, activities"
/>
);
}
// ---------------------------------------------------------------------------
function ProjectsCard({ projects }: { projects: Project[] }) {
const [pending, startTransition] = useTransition();
const [removed, setRemoved] = useState>(new Set());
return (
Projects
{projects.length === 0 && (
Side projects, open source, things you shipped outside a job.
)}
{projects
.filter((item) => !removed.has(item.id))
.map((item) => (
setRemoved((prev) => new Set(prev).add(item.id))}
/>
))}
);
}
function ProjectRow({ item, onRemoved }: { item: Project; onRemoved: () => void }) {
const [values, setValues] = useState({
name: item.name,
role: item.role,
url: item.url,
description: item.description,
});
const { state, push } = useAutosave((next) => updateProjectAction(item.id, next));
const set = (patch: Partial) => {
const next = { ...values, ...patch };
setValues(next);
push(next);
};
return (
set({ name: event.target.value })}
className="h-9 border-0 bg-transparent px-0 text-base font-semibold shadow-none focus-visible:ring-0 md:h-7 md:text-[13px]"
/>
set({ role: event.target.value })}
placeholder="Creator"
/>
set({ url: event.target.value })}
placeholder="github.com/…"
/>
);
}
// ---------------------------------------------------------------------------
function CertificationsCard({ certifications }: { certifications: Certification[] }) {
const [pending, startTransition] = useTransition();
const [removed, setRemoved] = useState>(new Set());
const [form, setForm] = useState({ name: "", issuer: "", date: "" });
const add = () => {
if (!form.name.trim()) return;
startTransition(async () => {
await createCertificationAction(form);
setForm({ name: "", issuer: "", date: "" });
toast.success("Certification added");
});
};
return (
Certifications
{certifications
.filter((item) => !removed.has(item.id))
.map((item) => (
{item.name}
{[item.issuer, item.date].filter(Boolean).join(" · ")}
))}
);
}