Skip to content

Commit

Permalink
work item modal
Browse files Browse the repository at this point in the history
  • Loading branch information
jgettings committed Oct 3, 2024
1 parent f9fccff commit e9b108a
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 50 deletions.
11 changes: 11 additions & 0 deletions src/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ const AdditionalFields = z.object({
coordinates: z.tuple([z.number(), z.number()]).optional(),
}),
}),
work: z.array(
z.object({
skills: z
.object({
primary: z.array(z.string()).optional(),
tools: z.array(z.string()).optional(),
other: z.array(z.string()).optional(),
})
.optional(),
}),
),
education: z.array(
z.object({
name: z.string().optional(),
Expand Down
10 changes: 4 additions & 6 deletions src/data/resume.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
"startDate": "2023-05-22",
"endDate": "2024-01-10",
"highlights": [
"Working primarily on the front-end with React, TypeScript, Tailwind CSS, and NextJS",
"Working primarily on the front-end with React, TypeScript, Tailwind CSS, and NextJS while occasionally hopping into the Python/Django backend to help with minor changes.",
"Facilitated improved communications within the engineering team with iterative process improvements via retrospectives. Encouraging more automated testing, and more discussion before and during implementation of features",
"Architected end-to-end tests through critical product flows, saving development time on each release while also significantly improving confidence",
"Improved accessibility across the platform to better conform to WAI-ARIA standards and general useability",
Expand Down Expand Up @@ -140,15 +140,15 @@
"Kubernetes",
"Kotlin",
"Shopify",
"Azure Blob Storage - OAuth",
"Azure Blob Storage",
"Snowflake",
"Google Maps"
]
},
"startDate": "2020-08-17",
"endDate": "2023-01-05",
"highlights": [
"Working primarily on the front-end with React, TypeScript, and GraphQL",
"Working primarily on the front-end with React, TypeScript, and GraphQL while occasionally hopping into the Kotlin backend to help with minor changes.",
"Found a way to combine a POC effort with some existing functionality to wrap up the POC much faster with a much more complete feature set",
"Improved observability of major business functionality with a new UI to track status and diagnose issues by working with stakeholders and iterating on various designs",
"Created new UIs to allow users to connect to third-party systems using OAuth and other third party APIs",
Expand Down Expand Up @@ -320,8 +320,7 @@
"SVN",
"Balsamiq",
"Mercurial"
],
"other": []
]
},
"startDate": "2011-02-14",
"endDate": "2014-09-26",
Expand Down Expand Up @@ -357,7 +356,6 @@
"Java",
"HTML"
],
"tools": [],
"other": ["Linux", "Bash Scripting"]
},
"startDate": "2007-06-25",
Expand Down
11 changes: 8 additions & 3 deletions src/pages/ResumeTimeline/Dates.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
import { format } from 'date-fns';
import { Timeline } from 'flowbite-react';
import { twMerge } from 'tailwind-merge';

const dateFormat = 'LLL d, yyyy';

type ResumeTimelineDatesProps = {
className?: string;
startDate: string;
endDate: string;
};

const ResumeTimelineDates: React.FC<ResumeTimelineDatesProps> = ({
className,
startDate,
endDate,
}) => (
<div className="inline text-gray-400 dark:text-gray-500">
<Timeline.Time dateTime={startDate}>
<div
className={twMerge('inline text-gray-400 dark:text-gray-500', className)}
>
<Timeline.Time dateTime={startDate} className={className}>
{format(startDate, dateFormat)}
</Timeline.Time>{' '}
-{' '}
<Timeline.Time dateTime={endDate}>
<Timeline.Time dateTime={endDate} className={className}>
{format(endDate, dateFormat)}
</Timeline.Time>
</div>
Expand Down
28 changes: 28 additions & 0 deletions src/pages/ResumeTimeline/WorkItemLogoLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ResumeProfile } from 'data';

type WorkItemLogoLinkProps = {
work: NonNullable<ResumeProfile['work']>[0];
};

const logoWidth = 150;

const WorkItemLogoLink: React.FC<WorkItemLogoLinkProps> = ({ work }) => (
<a href={work.url}>
{work.iconDark && (
<img
alt={`${work.name} logo`}
src={`/images/company-icons/${work.iconDark}`}
width={logoWidth}
className="hidden dark:inline"
/>
)}
<img
alt={`${work.name} logo`}
src={`/images/company-icons/${work.icon}`}
width={logoWidth}
className={work.iconDark ? 'inline dark:hidden' : undefined}
/>
</a>
);

export default WorkItemLogoLink;
107 changes: 107 additions & 0 deletions src/pages/ResumeTimeline/WorkItemModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { ResumeProfile } from 'data';
import { Button, List, Modal } from 'flowbite-react';
import KeywordsPipeList from 'components/KeywordsPipeList';
import React from 'react';
import WorkItemLogoLink from './WorkItemLogoLink';
import ResumeTimelineDates from './Dates';

type WorkItemModalProps = {
work: NonNullable<ResumeProfile['work']>[0];
isOpen: boolean;
setIsOpen: (open: boolean) => void;
};

const keywordListClasses = 'ml-6 text-black dark:text-white';

const Heading: React.FC<{ children: string }> = ({ children }) => (
<h5 className="mb-4 mt-10 font-heading text-xl">{children}</h5>
);

const WorkItemModal: React.FC<WorkItemModalProps> = ({
work,
isOpen,
setIsOpen,
}) => (
<Modal show={isOpen} onClose={() => setIsOpen(false)} size="3xl" dismissible>
<Modal.Header
as="h4"
className="flex flex-col justify-between gap-6 md:flex-row"
>
<WorkItemLogoLink work={work} />
</Modal.Header>
<Modal.Body>
<p className="mb-4 text-lg font-semibold">
{work.position}
<span className="sr-only"> at </span>
<div className="font-light italic">
<a href={work.url}>{work.name}</a>
</div>
<ResumeTimelineDates
className="text-gray-500 dark:text-gray-300"
startDate={work.startDate as string}
endDate={work.endDate as string}
/>
</p>

<p>{work.summary}</p>

<Heading>Highlights</Heading>
<List className="ml-6 list-outside text-black dark:text-white">
{work.highlights?.map((h) => <List.Item>{h}</List.Item>)}
</List>

{work.skills && (
<>
<Heading>Technical Skills and Tools</Heading>
<dl className="text-gray-500 dark:text-gray-300">
{work.skills.primary && (
<>
<dt>Primary</dt>
<dd>
<KeywordsPipeList
className={keywordListClasses}
keywords={work.skills.primary}
/>
</dd>
</>
)}
{work.skills.tools && (
<>
<dt>Tools</dt>
<dd>
<KeywordsPipeList
className={keywordListClasses}
keywords={work.skills.tools}
/>
</dd>
</>
)}
{work.skills.other && (
<>
<dt>Other</dt>
<dd>
<KeywordsPipeList
className={keywordListClasses}
keywords={work.skills.other}
/>
</dd>
</>
)}
</dl>
</>
)}

{/* <Heading>Projects</Heading>
<List className="ml-6 list-outside text-black dark:text-white">
{work.projects?.map((p) => <List.Item>{p}</List.Item>)}
</List> */}
</Modal.Body>
<Modal.Footer>
<Button onClick={() => setIsOpen(false)}>Close</Button>
</Modal.Footer>
</Modal>
);

// It'd be cool if you could next/prev through these like a carousel

export default WorkItemModal;
78 changes: 37 additions & 41 deletions src/pages/ResumeTimeline/WorkTimelineItem.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,48 @@
import { useState } from 'react';
import { PiBuildingOfficeFill } from 'react-icons/pi';
import { Timeline, Card } from 'flowbite-react';
import { Timeline, Card, Button } from 'flowbite-react';
import { ResumeProfile } from 'data';
import ResumeTimelineDates from './Dates';
import WorkItemModal from './WorkItemModal';
import WorkItemLogoLink from './WorkItemLogoLink';

type WorkTimelineItemProps = {
work: NonNullable<ResumeProfile['work']>[0];
};

const logoWidth = 150;
const WorkTimelineItem: React.FC<WorkTimelineItemProps> = ({ work }) => {
const [showModal, setShowModal] = useState(false);

const WorkTimelineItem: React.FC<WorkTimelineItemProps> = ({ work }) => (
<Timeline.Item key={work.name}>
<Timeline.Point icon={PiBuildingOfficeFill} />
<Timeline.Content>
<Card>
<ResumeTimelineDates
startDate={work.startDate as string}
endDate={work.endDate as string}
/>
<Timeline.Title className="flex flex-col justify-between gap-6 md:flex-row">
<div>
{work.position}
<div className="font-light italic">{work.name}</div>
</div>
<div className="flex-none">
<a href={work.url}>
{work.iconDark && (
<img
alt={`${work.name} logo`}
src={`/images/company-icons/${work.iconDark}`}
width={logoWidth}
className="hidden dark:inline"
/>
)}
<img
alt={`${work.name} logo`}
src={`/images/company-icons/${work.icon}`}
width={logoWidth}
className={work.iconDark ? 'inline dark:hidden' : undefined}
/>
</a>
</div>
</Timeline.Title>
<Timeline.Body>{work.summary}</Timeline.Body>
{/* <Button>Read more</Button> */}
</Card>
</Timeline.Content>
</Timeline.Item>
);
return (
<Timeline.Item key={work.name}>
<Timeline.Point icon={PiBuildingOfficeFill} />
<Timeline.Content>
<Card>
<ResumeTimelineDates
startDate={work.startDate as string}
endDate={work.endDate as string}
/>
<Timeline.Title className="flex flex-col justify-between gap-6 md:flex-row">
<div>
{work.position}
<span className="sr-only"> at </span>
<div className="font-light italic">{work.name}</div>
</div>
<div className="flex-none">
<WorkItemLogoLink work={work} />
</div>
</Timeline.Title>
<Timeline.Body>{work.summary}</Timeline.Body>
<Button onClick={() => setShowModal(true)}>Read more</Button>
<WorkItemModal
work={work}
isOpen={showModal}
setIsOpen={setShowModal}
/>
</Card>
</Timeline.Content>
</Timeline.Item>
);
};

export default WorkTimelineItem;

0 comments on commit e9b108a

Please sign in to comment.