From 13c2aa2121ffdbda18efd3f8ec2f031c3835ddd7 Mon Sep 17 00:00:00 2001 From: A Charlwood Date: Tue, 10 Feb 2026 16:54:25 +0000 Subject: [PATCH] feat: Build Education, Projects, and Contact sections (Task 9) --- src/App.tsx | 24 ++------ src/components/Contact.tsx | 108 +++++++++++++++++++++++++++++++++++ src/components/Education.tsx | 86 ++++++++++++++++++++++++++++ src/components/Projects.tsx | 105 ++++++++++++++++++++++++++++++++++ 4 files changed, 305 insertions(+), 18 deletions(-) create mode 100644 src/components/Contact.tsx create mode 100644 src/components/Education.tsx create mode 100644 src/components/Projects.tsx diff --git a/src/App.tsx b/src/App.tsx index 192efde..11b29d2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,6 +6,9 @@ import { FloatingNav } from './components/FloatingNav' import { Hero } from './components/Hero' import { Skills } from './components/Skills' import { Experience } from './components/Experience' +import { Education } from './components/Education' +import { Projects } from './components/Projects' +import { Contact } from './components/Contact' function App() { const [phase, setPhase] = useState('boot') @@ -30,26 +33,11 @@ function App() { -
-

- Education -

-

Education section will be built in Task 9

-
+ -
-

- Projects -

-

Projects section will be built in Task 9

-
+ -
-

- Contact -

-

Contact section will be built in Task 9

-
+ )} diff --git a/src/components/Contact.tsx b/src/components/Contact.tsx new file mode 100644 index 0000000..d555c1b --- /dev/null +++ b/src/components/Contact.tsx @@ -0,0 +1,108 @@ +import { motion } from 'framer-motion' +import { Phone, Mail, Linkedin, MapPin } from 'lucide-react' +import { useScrollReveal } from '@/hooks/useScrollReveal' +import type { ContactItem } from '@/types' + +const contactData: ContactItem[] = [ + { + icon: 'phone', + value: '07795553088', + label: 'Phone', + }, + { + icon: 'mail', + value: 'andy@charlwood.xyz', + label: 'Email', + href: 'mailto:andy@charlwood.xyz', + }, + { + icon: 'linkedin', + value: 'linkedin.com/in/andrewcharlwood', + label: 'LinkedIn', + href: 'https://linkedin.com/in/andrewcharlwood', + }, + { + icon: 'mapPin', + value: 'Norwich, UK', + label: 'Location', + }, +] + +const iconMap = { + phone: Phone, + mail: Mail, + linkedin: Linkedin, + mapPin: MapPin, +} + +const ContactItemCard = ({ + item, + delay, + isVisible, +}: { + item: ContactItem + delay: number + isVisible: boolean +}) => { + const Icon = iconMap[item.icon] + + return ( + +
+ +
+
+ {item.href ? ( + + {item.value} + + ) : ( + item.value + )} +
+
+ {item.label} +
+
+ ) +} + +export function Contact() { + const [sectionRef, isVisible] = useScrollReveal({ + threshold: 0.1, + }) + + return ( +
+ + Contact + + +
+ {contactData.map((item, index) => ( + + ))} +
+
+ ) +} diff --git a/src/components/Education.tsx b/src/components/Education.tsx new file mode 100644 index 0000000..d544d1f --- /dev/null +++ b/src/components/Education.tsx @@ -0,0 +1,86 @@ +import { motion } from 'framer-motion' +import { useScrollReveal } from '@/hooks/useScrollReveal' +import type { Education as EducationType } from '@/types' + +const educationData: EducationType[] = [ + { + degree: 'MPharm (Hons) Pharmacy', + institution: 'University of East Anglia', + period: '2011 — 2015', + detail: 'Upper Second-Class Honours (2:1)', + }, + { + degree: 'Mary Seacole Leadership Programme', + institution: 'NHS Leadership Academy', + period: '2018', + detail: 'National healthcare leadership development programme.', + }, +] + +const EducationCard = ({ + education, + delay, + isVisible, +}: { + education: EducationType + delay: number + isVisible: boolean +}) => { + return ( + +
+

+ {education.degree} +

+

{education.institution}

+

{education.period}

+

+ {education.detail} +

+ + ) +} + +export function Education() { + const [sectionRef, isVisible] = useScrollReveal({ + threshold: 0.1, + }) + + return ( +
+ + Education + + +
+ {educationData.map((education, index) => ( + + ))} +
+ + + A-Levels: Mathematics (A*), Chemistry (B), Politics (C) + +
+ ) +} diff --git a/src/components/Projects.tsx b/src/components/Projects.tsx new file mode 100644 index 0000000..ac206f5 --- /dev/null +++ b/src/components/Projects.tsx @@ -0,0 +1,105 @@ +import { motion } from 'framer-motion' +import { ExternalLink } from 'lucide-react' +import { useScrollReveal } from '@/hooks/useScrollReveal' +import type { Project as ProjectType } from '@/types' + +const projectsData: ProjectType[] = [ + { + title: 'PharMetrics', + description: + 'Real-time medicines expenditure dashboard providing actionable analytics for NHS decision-makers.', + link: 'https://medicines.charlwood.xyz/', + }, + { + title: 'Patient Pathway Analysis', + description: + 'Data-driven analysis of patient pathways to identify optimisation opportunities and improve clinical outcomes.', + }, + { + title: 'Blueteq Generator', + description: + 'Automation tool reducing high-cost drug approval processing time by 70%, saving 200+ hours annually.', + }, + { + title: 'NMS Video', + description: + 'Educational video resource supporting New Medicine Service consultations, improving patient engagement.', + }, +] + +const ProjectCard = ({ + project, + delay, + isVisible, +}: { + project: ProjectType + delay: number + isVisible: boolean +}) => { + return ( + +
+

+ {project.title} +

+

+ {project.description} +

+ {project.link && ( + + Visit Project + + + )} + + ) +} + +export function Projects() { + const [sectionRef, isVisible] = useScrollReveal({ + threshold: 0.1, + }) + + return ( +
+ + Projects + + +
+ {projectsData.map((project, index) => ( + + ))} +
+
+ ) +}