@jsonresume/jsonresume-theme-consultant-polished 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -0
- package/package.json +30 -0
- package/src/index.js +83 -0
- package/src/ui/Awards.js +65 -0
- package/src/ui/Certificates.js +65 -0
- package/src/ui/Education.js +76 -0
- package/src/ui/Hero.js +80 -0
- package/src/ui/Interests.js +45 -0
- package/src/ui/Languages.js +43 -0
- package/src/ui/Projects.js +107 -0
- package/src/ui/Publications.js +74 -0
- package/src/ui/References.js +36 -0
- package/src/ui/Resume.js +46 -0
- package/src/ui/Section.js +26 -0
- package/src/ui/Skills.js +45 -0
- package/src/ui/Summary.js +21 -0
- package/src/ui/Volunteer.js +96 -0
- package/src/ui/Work.js +100 -0
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# JSON Resume Theme - Consultant Polished
|
|
2
|
+
|
|
3
|
+
An elegant, structured JSON Resume theme optimized for narrative-driven consulting roles.
|
|
4
|
+
|
|
5
|
+
## Design Philosophy
|
|
6
|
+
|
|
7
|
+
**Consultant Polished** feels like a McKinsey deck turned resume — sophisticated, restrained, and balanced. No color overload, just consistent contrast and impeccable rhythm.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Transitional serif headers**: Georgia, Times New Roman for headings
|
|
12
|
+
- **Modern sans body**: Clean system fonts for readability
|
|
13
|
+
- **Navy accent color**: #0b1f3a for professional sophistication
|
|
14
|
+
- **Single column layout**: Clear section spacing for easy scanning
|
|
15
|
+
- **Elegant typography**: Deliberate spacing and hierarchy
|
|
16
|
+
- **Print-optimized**: Ready for PDF export
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @jsonresume/jsonresume-theme-consultant-polished
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Design Specifications
|
|
25
|
+
|
|
26
|
+
- **Vibe**: Elegant, structured, deliberate
|
|
27
|
+
- **Layout**: Clean single column with clear section spacing
|
|
28
|
+
- **Typography**: Transitional serif for headers; modern sans body
|
|
29
|
+
- **Accent Color**: #0b1f3a (navy)
|
|
30
|
+
- **Target Audience**: Consulting, strategy, advisory roles
|
|
31
|
+
|
|
32
|
+
## License
|
|
33
|
+
|
|
34
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"private": false,
|
|
3
|
+
"name": "@jsonresume/jsonresume-theme-consultant-polished",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"main": "./src/index.js",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"description": "Elegant, structured JSON Resume theme optimized for narrative-driven consulting roles",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"jsonresume",
|
|
10
|
+
"theme",
|
|
11
|
+
"consultant",
|
|
12
|
+
"elegant",
|
|
13
|
+
"polished"
|
|
14
|
+
],
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"react": "^18",
|
|
17
|
+
"styled-components": "^6",
|
|
18
|
+
"@repo/eslint-config-custom": "^0.0.0"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"styled-components": "^6"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"marked": "^16.3.0",
|
|
25
|
+
"react-icons": "^5.0.1"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"lint": "eslint ."
|
|
29
|
+
}
|
|
30
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { renderToString } from 'react-dom/server';
|
|
2
|
+
import { ServerStyleSheet } from 'styled-components';
|
|
3
|
+
import Resume from './ui/Resume';
|
|
4
|
+
|
|
5
|
+
export const render = (resume) => {
|
|
6
|
+
const sheet = new ServerStyleSheet();
|
|
7
|
+
const html = renderToString(sheet.collectStyles(<Resume resume={resume} />));
|
|
8
|
+
const styles = sheet.getStyleTags();
|
|
9
|
+
return `<!DOCTYPE html><head>
|
|
10
|
+
<title>${resume.basics.name} - Resume</title>
|
|
11
|
+
<meta charset="utf-8">
|
|
12
|
+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
13
|
+
<style>
|
|
14
|
+
html {
|
|
15
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', sans-serif;
|
|
16
|
+
background: #fff;
|
|
17
|
+
font-size: 16px;
|
|
18
|
+
line-height: 1.6;
|
|
19
|
+
color: #1a1a1a;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
body {
|
|
23
|
+
margin: 0;
|
|
24
|
+
padding: 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
h1, h2, h3, h4, h5, h6 {
|
|
28
|
+
font-family: Georgia, 'Times New Roman', serif;
|
|
29
|
+
font-weight: 600;
|
|
30
|
+
color: #0b1f3a;
|
|
31
|
+
margin: 0;
|
|
32
|
+
line-height: 1.3;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
h1 {
|
|
36
|
+
font-size: 2.5rem;
|
|
37
|
+
margin-bottom: 0.5rem;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
h2 {
|
|
41
|
+
font-size: 1.5rem;
|
|
42
|
+
margin-bottom: 1rem;
|
|
43
|
+
letter-spacing: 0.01em;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
h3 {
|
|
47
|
+
font-size: 1.125rem;
|
|
48
|
+
margin-bottom: 0.5rem;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
p {
|
|
52
|
+
padding: 0;
|
|
53
|
+
margin: 0 0 0.75rem 0;
|
|
54
|
+
font-size: 1rem;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
a {
|
|
58
|
+
color: #0b1f3a;
|
|
59
|
+
text-decoration: none;
|
|
60
|
+
border-bottom: 1px solid transparent;
|
|
61
|
+
transition: border-color 0.2s ease;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
a:hover {
|
|
65
|
+
border-bottom-color: #0b1f3a;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
ul {
|
|
69
|
+
list-style: none;
|
|
70
|
+
margin: 0;
|
|
71
|
+
padding: 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
*,
|
|
75
|
+
*::before,
|
|
76
|
+
*::after {
|
|
77
|
+
box-sizing: border-box;
|
|
78
|
+
}
|
|
79
|
+
</style>
|
|
80
|
+
${styles}</head><body>${html}</body></html>`;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export { Resume };
|
package/src/ui/Awards.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import Section from './Section';
|
|
3
|
+
|
|
4
|
+
const AwardItem = styled.div`
|
|
5
|
+
margin-bottom: 1.25rem;
|
|
6
|
+
`;
|
|
7
|
+
|
|
8
|
+
const Header = styled.div`
|
|
9
|
+
display: flex;
|
|
10
|
+
justify-content: space-between;
|
|
11
|
+
align-items: baseline;
|
|
12
|
+
margin-bottom: 0.25rem;
|
|
13
|
+
flex-wrap: wrap;
|
|
14
|
+
gap: 0.5rem;
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
const Title = styled.h3`
|
|
18
|
+
font-size: 1rem;
|
|
19
|
+
color: #0b1f3a;
|
|
20
|
+
margin: 0;
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
const Date = styled.div`
|
|
24
|
+
font-size: 0.9rem;
|
|
25
|
+
color: #666;
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
const Awarder = styled.div`
|
|
29
|
+
font-size: 0.95rem;
|
|
30
|
+
color: #555;
|
|
31
|
+
margin-bottom: 0.25rem;
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
const Summary = styled.div`
|
|
35
|
+
font-size: 0.95rem;
|
|
36
|
+
color: #666;
|
|
37
|
+
line-height: 1.5;
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
const formatDate = (date) => {
|
|
41
|
+
if (!date) return '';
|
|
42
|
+
const d = new Date(date);
|
|
43
|
+
return d.toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const Awards = ({ awards }) => {
|
|
47
|
+
if (!awards || awards.length === 0) return null;
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Section title="Awards & Honors">
|
|
51
|
+
{awards.map((award, i) => (
|
|
52
|
+
<AwardItem key={i}>
|
|
53
|
+
<Header>
|
|
54
|
+
<Title>{award.title}</Title>
|
|
55
|
+
{award.date && <Date>{formatDate(award.date)}</Date>}
|
|
56
|
+
</Header>
|
|
57
|
+
{award.awarder && <Awarder>{award.awarder}</Awarder>}
|
|
58
|
+
{award.summary && <Summary>{award.summary}</Summary>}
|
|
59
|
+
</AwardItem>
|
|
60
|
+
))}
|
|
61
|
+
</Section>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default Awards;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import Section from './Section';
|
|
3
|
+
|
|
4
|
+
const CertificateItem = styled.div`
|
|
5
|
+
margin-bottom: 1.25rem;
|
|
6
|
+
`;
|
|
7
|
+
|
|
8
|
+
const Header = styled.div`
|
|
9
|
+
display: flex;
|
|
10
|
+
justify-content: space-between;
|
|
11
|
+
align-items: baseline;
|
|
12
|
+
margin-bottom: 0.25rem;
|
|
13
|
+
flex-wrap: wrap;
|
|
14
|
+
gap: 0.5rem;
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
const Name = styled.h3`
|
|
18
|
+
font-size: 1rem;
|
|
19
|
+
color: #0b1f3a;
|
|
20
|
+
margin: 0;
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
const Date = styled.div`
|
|
24
|
+
font-size: 0.9rem;
|
|
25
|
+
color: #666;
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
const Issuer = styled.div`
|
|
29
|
+
font-size: 0.95rem;
|
|
30
|
+
color: #555;
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
const formatDate = (date) => {
|
|
34
|
+
if (!date) return '';
|
|
35
|
+
const d = new Date(date);
|
|
36
|
+
return d.toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const Certificates = ({ certificates }) => {
|
|
40
|
+
if (!certificates || certificates.length === 0) return null;
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Section title="Certifications">
|
|
44
|
+
{certificates.map((cert, i) => (
|
|
45
|
+
<CertificateItem key={i}>
|
|
46
|
+
<Header>
|
|
47
|
+
<Name>
|
|
48
|
+
{cert.url ? (
|
|
49
|
+
<a href={cert.url} target="_blank" rel="noopener noreferrer">
|
|
50
|
+
{cert.name}
|
|
51
|
+
</a>
|
|
52
|
+
) : (
|
|
53
|
+
cert.name
|
|
54
|
+
)}
|
|
55
|
+
</Name>
|
|
56
|
+
{cert.date && <Date>{formatDate(cert.date)}</Date>}
|
|
57
|
+
</Header>
|
|
58
|
+
{cert.issuer && <Issuer>{cert.issuer}</Issuer>}
|
|
59
|
+
</CertificateItem>
|
|
60
|
+
))}
|
|
61
|
+
</Section>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default Certificates;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import Section from './Section';
|
|
3
|
+
|
|
4
|
+
const EducationItem = styled.div`
|
|
5
|
+
margin-bottom: 1.5rem;
|
|
6
|
+
`;
|
|
7
|
+
|
|
8
|
+
const Header = styled.div`
|
|
9
|
+
display: flex;
|
|
10
|
+
justify-content: space-between;
|
|
11
|
+
align-items: baseline;
|
|
12
|
+
margin-bottom: 0.25rem;
|
|
13
|
+
flex-wrap: wrap;
|
|
14
|
+
gap: 0.5rem;
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
const Degree = styled.h3`
|
|
18
|
+
font-size: 1.0625rem;
|
|
19
|
+
color: #0b1f3a;
|
|
20
|
+
margin: 0;
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
const DateRange = styled.div`
|
|
24
|
+
font-size: 0.9rem;
|
|
25
|
+
color: #666;
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
const Institution = styled.div`
|
|
29
|
+
font-size: 1rem;
|
|
30
|
+
color: #555;
|
|
31
|
+
margin-bottom: 0.5rem;
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
const Details = styled.div`
|
|
35
|
+
font-size: 0.95rem;
|
|
36
|
+
color: #666;
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
const formatDate = (date) => {
|
|
40
|
+
if (!date) return '';
|
|
41
|
+
const d = new Date(date);
|
|
42
|
+
return d.toLocaleDateString('en-US', { year: 'numeric' });
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const Education = ({ education }) => {
|
|
46
|
+
if (!education || education.length === 0) return null;
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Section title="Education">
|
|
50
|
+
{education.map((item, i) => (
|
|
51
|
+
<EducationItem key={i}>
|
|
52
|
+
<Header>
|
|
53
|
+
<Degree>
|
|
54
|
+
{item.studyType} {item.area && `in ${item.area}`}
|
|
55
|
+
</Degree>
|
|
56
|
+
<DateRange>
|
|
57
|
+
{item.startDate && formatDate(item.startDate)}
|
|
58
|
+
{item.endDate && ` - ${formatDate(item.endDate)}`}
|
|
59
|
+
</DateRange>
|
|
60
|
+
</Header>
|
|
61
|
+
<Institution>{item.institution}</Institution>
|
|
62
|
+
{(item.score || item.courses) && (
|
|
63
|
+
<Details>
|
|
64
|
+
{item.score && <div>GPA: {item.score}</div>}
|
|
65
|
+
{item.courses && item.courses.length > 0 && (
|
|
66
|
+
<div>Relevant Coursework: {item.courses.join(', ')}</div>
|
|
67
|
+
)}
|
|
68
|
+
</Details>
|
|
69
|
+
)}
|
|
70
|
+
</EducationItem>
|
|
71
|
+
))}
|
|
72
|
+
</Section>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export default Education;
|
package/src/ui/Hero.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
const Header = styled.header`
|
|
4
|
+
margin-bottom: 3rem;
|
|
5
|
+
padding-bottom: 2rem;
|
|
6
|
+
border-bottom: 2px solid #0b1f3a;
|
|
7
|
+
`;
|
|
8
|
+
|
|
9
|
+
const Name = styled.h1`
|
|
10
|
+
font-size: 2.5rem;
|
|
11
|
+
color: #0b1f3a;
|
|
12
|
+
margin-bottom: 0.5rem;
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
const Label = styled.p`
|
|
16
|
+
font-size: 1.125rem;
|
|
17
|
+
color: #555;
|
|
18
|
+
margin-bottom: 1.5rem;
|
|
19
|
+
font-weight: 400;
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
const ContactInfo = styled.div`
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-wrap: wrap;
|
|
25
|
+
gap: 1.5rem;
|
|
26
|
+
font-size: 0.95rem;
|
|
27
|
+
color: #666;
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
const ContactItem = styled.div`
|
|
31
|
+
a {
|
|
32
|
+
color: #0b1f3a;
|
|
33
|
+
text-decoration: none;
|
|
34
|
+
|
|
35
|
+
&:hover {
|
|
36
|
+
border-bottom: 1px solid #0b1f3a;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
const Hero = ({ basics }) => {
|
|
42
|
+
if (!basics) return null;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Header>
|
|
46
|
+
<Name>{basics.name}</Name>
|
|
47
|
+
{basics.label && <Label>{basics.label}</Label>}
|
|
48
|
+
<ContactInfo>
|
|
49
|
+
{basics.email && (
|
|
50
|
+
<ContactItem>
|
|
51
|
+
<a href={`mailto:${basics.email}`}>{basics.email}</a>
|
|
52
|
+
</ContactItem>
|
|
53
|
+
)}
|
|
54
|
+
{basics.phone && <ContactItem>{basics.phone}</ContactItem>}
|
|
55
|
+
{basics.location?.city && (
|
|
56
|
+
<ContactItem>
|
|
57
|
+
{basics.location.city}
|
|
58
|
+
{basics.location.region && `, ${basics.location.region}`}
|
|
59
|
+
</ContactItem>
|
|
60
|
+
)}
|
|
61
|
+
{basics.url && (
|
|
62
|
+
<ContactItem>
|
|
63
|
+
<a href={basics.url} target="_blank" rel="noopener noreferrer">
|
|
64
|
+
{basics.url.replace(/^https?:\/\//, '')}
|
|
65
|
+
</a>
|
|
66
|
+
</ContactItem>
|
|
67
|
+
)}
|
|
68
|
+
{basics.profiles?.map((profile, i) => (
|
|
69
|
+
<ContactItem key={i}>
|
|
70
|
+
<a href={profile.url} target="_blank" rel="noopener noreferrer">
|
|
71
|
+
{profile.network}
|
|
72
|
+
</a>
|
|
73
|
+
</ContactItem>
|
|
74
|
+
))}
|
|
75
|
+
</ContactInfo>
|
|
76
|
+
</Header>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export default Hero;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import Section from './Section';
|
|
3
|
+
|
|
4
|
+
const InterestList = styled.div`
|
|
5
|
+
display: grid;
|
|
6
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
7
|
+
gap: 1rem;
|
|
8
|
+
`;
|
|
9
|
+
|
|
10
|
+
const InterestItem = styled.div`
|
|
11
|
+
margin-bottom: 0.75rem;
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
const InterestName = styled.h3`
|
|
15
|
+
font-size: 1rem;
|
|
16
|
+
color: #0b1f3a;
|
|
17
|
+
margin-bottom: 0.25rem;
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
const Keywords = styled.div`
|
|
21
|
+
font-size: 0.9rem;
|
|
22
|
+
color: #666;
|
|
23
|
+
line-height: 1.5;
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
const Interests = ({ interests }) => {
|
|
27
|
+
if (!interests || interests.length === 0) return null;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Section title="Interests">
|
|
31
|
+
<InterestList>
|
|
32
|
+
{interests.map((interest, i) => (
|
|
33
|
+
<InterestItem key={i}>
|
|
34
|
+
<InterestName>{interest.name}</InterestName>
|
|
35
|
+
{interest.keywords && interest.keywords.length > 0 && (
|
|
36
|
+
<Keywords>{interest.keywords.join(', ')}</Keywords>
|
|
37
|
+
)}
|
|
38
|
+
</InterestItem>
|
|
39
|
+
))}
|
|
40
|
+
</InterestList>
|
|
41
|
+
</Section>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default Interests;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import Section from './Section';
|
|
3
|
+
|
|
4
|
+
const LanguageList = styled.div`
|
|
5
|
+
display: grid;
|
|
6
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
7
|
+
gap: 1rem;
|
|
8
|
+
`;
|
|
9
|
+
|
|
10
|
+
const LanguageItem = styled.div`
|
|
11
|
+
display: flex;
|
|
12
|
+
justify-content: space-between;
|
|
13
|
+
align-items: baseline;
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
const LanguageName = styled.span`
|
|
17
|
+
font-size: 1rem;
|
|
18
|
+
color: #333;
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
const Fluency = styled.span`
|
|
22
|
+
font-size: 0.9rem;
|
|
23
|
+
color: #666;
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
const Languages = ({ languages }) => {
|
|
27
|
+
if (!languages || languages.length === 0) return null;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Section title="Languages">
|
|
31
|
+
<LanguageList>
|
|
32
|
+
{languages.map((lang, i) => (
|
|
33
|
+
<LanguageItem key={i}>
|
|
34
|
+
<LanguageName>{lang.language}</LanguageName>
|
|
35
|
+
{lang.fluency && <Fluency>{lang.fluency}</Fluency>}
|
|
36
|
+
</LanguageItem>
|
|
37
|
+
))}
|
|
38
|
+
</LanguageList>
|
|
39
|
+
</Section>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default Languages;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import Section from './Section';
|
|
3
|
+
import { marked } from 'marked';
|
|
4
|
+
|
|
5
|
+
const ProjectItem = styled.div`
|
|
6
|
+
margin-bottom: 1.75rem;
|
|
7
|
+
`;
|
|
8
|
+
|
|
9
|
+
const Header = styled.div`
|
|
10
|
+
display: flex;
|
|
11
|
+
justify-content: space-between;
|
|
12
|
+
align-items: baseline;
|
|
13
|
+
margin-bottom: 0.5rem;
|
|
14
|
+
flex-wrap: wrap;
|
|
15
|
+
gap: 0.5rem;
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
const ProjectName = styled.h3`
|
|
19
|
+
font-size: 1.0625rem;
|
|
20
|
+
color: #0b1f3a;
|
|
21
|
+
margin: 0;
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
const DateRange = styled.div`
|
|
25
|
+
font-size: 0.9rem;
|
|
26
|
+
color: #666;
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
const Entity = styled.div`
|
|
30
|
+
font-size: 0.95rem;
|
|
31
|
+
color: #555;
|
|
32
|
+
margin-bottom: 0.5rem;
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
const Description = styled.div`
|
|
36
|
+
font-size: 1rem;
|
|
37
|
+
line-height: 1.6;
|
|
38
|
+
color: #444;
|
|
39
|
+
margin-bottom: 0.5rem;
|
|
40
|
+
|
|
41
|
+
p {
|
|
42
|
+
margin-bottom: 0.5rem;
|
|
43
|
+
}
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
const Highlights = styled.ul`
|
|
47
|
+
margin-top: 0.5rem;
|
|
48
|
+
padding-left: 1.5rem;
|
|
49
|
+
list-style: disc;
|
|
50
|
+
|
|
51
|
+
li {
|
|
52
|
+
margin-bottom: 0.4rem;
|
|
53
|
+
line-height: 1.6;
|
|
54
|
+
color: #444;
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
const formatDate = (date) => {
|
|
59
|
+
if (!date) return '';
|
|
60
|
+
const d = new Date(date);
|
|
61
|
+
return d.toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const Projects = ({ projects }) => {
|
|
65
|
+
if (!projects || projects.length === 0) return null;
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<Section title="Projects">
|
|
69
|
+
{projects.map((project, i) => (
|
|
70
|
+
<ProjectItem key={i}>
|
|
71
|
+
<Header>
|
|
72
|
+
<ProjectName>
|
|
73
|
+
{project.url ? (
|
|
74
|
+
<a href={project.url} target="_blank" rel="noopener noreferrer">
|
|
75
|
+
{project.name}
|
|
76
|
+
</a>
|
|
77
|
+
) : (
|
|
78
|
+
project.name
|
|
79
|
+
)}
|
|
80
|
+
</ProjectName>
|
|
81
|
+
{(project.startDate || project.endDate) && (
|
|
82
|
+
<DateRange>
|
|
83
|
+
{formatDate(project.startDate)}
|
|
84
|
+
{project.endDate && ` - ${formatDate(project.endDate)}`}
|
|
85
|
+
</DateRange>
|
|
86
|
+
)}
|
|
87
|
+
</Header>
|
|
88
|
+
{project.entity && <Entity>{project.entity}</Entity>}
|
|
89
|
+
{project.description && (
|
|
90
|
+
<Description
|
|
91
|
+
dangerouslySetInnerHTML={{ __html: marked(project.description) }}
|
|
92
|
+
/>
|
|
93
|
+
)}
|
|
94
|
+
{project.highlights && project.highlights.length > 0 && (
|
|
95
|
+
<Highlights>
|
|
96
|
+
{project.highlights.map((highlight, j) => (
|
|
97
|
+
<li key={j}>{highlight}</li>
|
|
98
|
+
))}
|
|
99
|
+
</Highlights>
|
|
100
|
+
)}
|
|
101
|
+
</ProjectItem>
|
|
102
|
+
))}
|
|
103
|
+
</Section>
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export default Projects;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import Section from './Section';
|
|
3
|
+
|
|
4
|
+
const PublicationItem = styled.div`
|
|
5
|
+
margin-bottom: 1.25rem;
|
|
6
|
+
`;
|
|
7
|
+
|
|
8
|
+
const Header = styled.div`
|
|
9
|
+
display: flex;
|
|
10
|
+
justify-content: space-between;
|
|
11
|
+
align-items: baseline;
|
|
12
|
+
margin-bottom: 0.25rem;
|
|
13
|
+
flex-wrap: wrap;
|
|
14
|
+
gap: 0.5rem;
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
const Name = styled.h3`
|
|
18
|
+
font-size: 1rem;
|
|
19
|
+
color: #0b1f3a;
|
|
20
|
+
margin: 0;
|
|
21
|
+
font-style: italic;
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
const Date = styled.div`
|
|
25
|
+
font-size: 0.9rem;
|
|
26
|
+
color: #666;
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
const Publisher = styled.div`
|
|
30
|
+
font-size: 0.95rem;
|
|
31
|
+
color: #555;
|
|
32
|
+
margin-bottom: 0.25rem;
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
const Summary = styled.div`
|
|
36
|
+
font-size: 0.95rem;
|
|
37
|
+
color: #666;
|
|
38
|
+
line-height: 1.5;
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
const formatDate = (date) => {
|
|
42
|
+
if (!date) return '';
|
|
43
|
+
const d = new Date(date);
|
|
44
|
+
return d.toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const Publications = ({ publications }) => {
|
|
48
|
+
if (!publications || publications.length === 0) return null;
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<Section title="Publications">
|
|
52
|
+
{publications.map((pub, i) => (
|
|
53
|
+
<PublicationItem key={i}>
|
|
54
|
+
<Header>
|
|
55
|
+
<Name>
|
|
56
|
+
{pub.url ? (
|
|
57
|
+
<a href={pub.url} target="_blank" rel="noopener noreferrer">
|
|
58
|
+
{pub.name}
|
|
59
|
+
</a>
|
|
60
|
+
) : (
|
|
61
|
+
pub.name
|
|
62
|
+
)}
|
|
63
|
+
</Name>
|
|
64
|
+
{pub.releaseDate && <Date>{formatDate(pub.releaseDate)}</Date>}
|
|
65
|
+
</Header>
|
|
66
|
+
{pub.publisher && <Publisher>{pub.publisher}</Publisher>}
|
|
67
|
+
{pub.summary && <Summary>{pub.summary}</Summary>}
|
|
68
|
+
</PublicationItem>
|
|
69
|
+
))}
|
|
70
|
+
</Section>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export default Publications;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import Section from './Section';
|
|
3
|
+
|
|
4
|
+
const ReferenceItem = styled.div`
|
|
5
|
+
margin-bottom: 1.25rem;
|
|
6
|
+
`;
|
|
7
|
+
|
|
8
|
+
const Name = styled.h3`
|
|
9
|
+
font-size: 1rem;
|
|
10
|
+
color: #0b1f3a;
|
|
11
|
+
margin-bottom: 0.25rem;
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
const Reference = styled.div`
|
|
15
|
+
font-size: 0.95rem;
|
|
16
|
+
color: #666;
|
|
17
|
+
line-height: 1.5;
|
|
18
|
+
font-style: italic;
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
const References = ({ references }) => {
|
|
22
|
+
if (!references || references.length === 0) return null;
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Section title="References">
|
|
26
|
+
{references.map((ref, i) => (
|
|
27
|
+
<ReferenceItem key={i}>
|
|
28
|
+
<Name>{ref.name}</Name>
|
|
29
|
+
{ref.reference && <Reference>"{ref.reference}"</Reference>}
|
|
30
|
+
</ReferenceItem>
|
|
31
|
+
))}
|
|
32
|
+
</Section>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default References;
|
package/src/ui/Resume.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import Hero from './Hero';
|
|
3
|
+
import Summary from './Summary';
|
|
4
|
+
import Work from './Work';
|
|
5
|
+
import Projects from './Projects';
|
|
6
|
+
import Education from './Education';
|
|
7
|
+
import Certificates from './Certificates';
|
|
8
|
+
import Publications from './Publications';
|
|
9
|
+
import Awards from './Awards';
|
|
10
|
+
import Skills from './Skills';
|
|
11
|
+
import Interests from './Interests';
|
|
12
|
+
import Languages from './Languages';
|
|
13
|
+
import References from './References';
|
|
14
|
+
import Volunteer from './Volunteer';
|
|
15
|
+
|
|
16
|
+
const Layout = styled.div`
|
|
17
|
+
max-width: 800px;
|
|
18
|
+
margin: 0 auto;
|
|
19
|
+
padding: 3rem 2rem;
|
|
20
|
+
|
|
21
|
+
@media print {
|
|
22
|
+
padding: 0;
|
|
23
|
+
}
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
const Resume = ({ resume }) => {
|
|
27
|
+
return (
|
|
28
|
+
<Layout>
|
|
29
|
+
<Hero basics={resume.basics} />
|
|
30
|
+
<Summary basics={resume.basics} />
|
|
31
|
+
<Work work={resume.work} />
|
|
32
|
+
<Projects projects={resume.projects} />
|
|
33
|
+
<Education education={resume.education} />
|
|
34
|
+
<Certificates certificates={resume.certificates} />
|
|
35
|
+
<Publications publications={resume.publications} />
|
|
36
|
+
<Awards awards={resume.awards} />
|
|
37
|
+
<Volunteer volunteer={resume.volunteer} />
|
|
38
|
+
<Skills skills={resume.skills} />
|
|
39
|
+
<Languages languages={resume.languages} />
|
|
40
|
+
<Interests interests={resume.interests} />
|
|
41
|
+
<References references={resume.references} />
|
|
42
|
+
</Layout>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default Resume;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
const SectionContainer = styled.section`
|
|
4
|
+
margin-bottom: 2.5rem;
|
|
5
|
+
`;
|
|
6
|
+
|
|
7
|
+
const SectionTitle = styled.h2`
|
|
8
|
+
font-size: 1.5rem;
|
|
9
|
+
color: #0b1f3a;
|
|
10
|
+
margin-bottom: 1.25rem;
|
|
11
|
+
padding-bottom: 0.5rem;
|
|
12
|
+
border-bottom: 1px solid #e0e0e0;
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
const Section = ({ title, children }) => {
|
|
16
|
+
if (!children) return null;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<SectionContainer>
|
|
20
|
+
{title && <SectionTitle>{title}</SectionTitle>}
|
|
21
|
+
{children}
|
|
22
|
+
</SectionContainer>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default Section;
|
package/src/ui/Skills.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import Section from './Section';
|
|
3
|
+
|
|
4
|
+
const SkillsGrid = styled.div`
|
|
5
|
+
display: grid;
|
|
6
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
7
|
+
gap: 1.5rem;
|
|
8
|
+
`;
|
|
9
|
+
|
|
10
|
+
const SkillCategory = styled.div`
|
|
11
|
+
margin-bottom: 1rem;
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
const CategoryName = styled.h3`
|
|
15
|
+
font-size: 1rem;
|
|
16
|
+
color: #0b1f3a;
|
|
17
|
+
margin-bottom: 0.5rem;
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
const KeywordList = styled.div`
|
|
21
|
+
font-size: 0.95rem;
|
|
22
|
+
color: #555;
|
|
23
|
+
line-height: 1.5;
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
const Skills = ({ skills }) => {
|
|
27
|
+
if (!skills || skills.length === 0) return null;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Section title="Skills">
|
|
31
|
+
<SkillsGrid>
|
|
32
|
+
{skills.map((skill, i) => (
|
|
33
|
+
<SkillCategory key={i}>
|
|
34
|
+
<CategoryName>{skill.name}</CategoryName>
|
|
35
|
+
{skill.keywords && skill.keywords.length > 0 && (
|
|
36
|
+
<KeywordList>{skill.keywords.join(' • ')}</KeywordList>
|
|
37
|
+
)}
|
|
38
|
+
</SkillCategory>
|
|
39
|
+
))}
|
|
40
|
+
</SkillsGrid>
|
|
41
|
+
</Section>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default Skills;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import Section from './Section';
|
|
3
|
+
|
|
4
|
+
const SummaryText = styled.p`
|
|
5
|
+
font-size: 1.0625rem;
|
|
6
|
+
line-height: 1.7;
|
|
7
|
+
color: #333;
|
|
8
|
+
margin: 0;
|
|
9
|
+
`;
|
|
10
|
+
|
|
11
|
+
const Summary = ({ basics }) => {
|
|
12
|
+
if (!basics?.summary) return null;
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Section title="Profile">
|
|
16
|
+
<SummaryText>{basics.summary}</SummaryText>
|
|
17
|
+
</Section>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default Summary;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import Section from './Section';
|
|
3
|
+
import { marked } from 'marked';
|
|
4
|
+
|
|
5
|
+
const VolunteerItem = styled.div`
|
|
6
|
+
margin-bottom: 1.75rem;
|
|
7
|
+
`;
|
|
8
|
+
|
|
9
|
+
const Header = styled.div`
|
|
10
|
+
display: flex;
|
|
11
|
+
justify-content: space-between;
|
|
12
|
+
align-items: baseline;
|
|
13
|
+
margin-bottom: 0.5rem;
|
|
14
|
+
flex-wrap: wrap;
|
|
15
|
+
gap: 0.5rem;
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
const Position = styled.h3`
|
|
19
|
+
font-size: 1.0625rem;
|
|
20
|
+
color: #0b1f3a;
|
|
21
|
+
margin: 0;
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
const DateRange = styled.div`
|
|
25
|
+
font-size: 0.9rem;
|
|
26
|
+
color: #666;
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
const Organization = styled.div`
|
|
30
|
+
font-size: 1rem;
|
|
31
|
+
color: #555;
|
|
32
|
+
margin-bottom: 0.5rem;
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
const Description = styled.div`
|
|
36
|
+
font-size: 1rem;
|
|
37
|
+
line-height: 1.6;
|
|
38
|
+
color: #444;
|
|
39
|
+
margin-bottom: 0.5rem;
|
|
40
|
+
|
|
41
|
+
p {
|
|
42
|
+
margin-bottom: 0.5rem;
|
|
43
|
+
}
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
const Highlights = styled.ul`
|
|
47
|
+
margin-top: 0.5rem;
|
|
48
|
+
padding-left: 1.5rem;
|
|
49
|
+
list-style: disc;
|
|
50
|
+
|
|
51
|
+
li {
|
|
52
|
+
margin-bottom: 0.4rem;
|
|
53
|
+
line-height: 1.6;
|
|
54
|
+
color: #444;
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
const formatDate = (date) => {
|
|
59
|
+
if (!date) return 'Present';
|
|
60
|
+
const d = new Date(date);
|
|
61
|
+
return d.toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const Volunteer = ({ volunteer }) => {
|
|
65
|
+
if (!volunteer || volunteer.length === 0) return null;
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<Section title="Volunteer Work">
|
|
69
|
+
{volunteer.map((item, i) => (
|
|
70
|
+
<VolunteerItem key={i}>
|
|
71
|
+
<Header>
|
|
72
|
+
<Position>{item.position}</Position>
|
|
73
|
+
<DateRange>
|
|
74
|
+
{formatDate(item.startDate)} - {formatDate(item.endDate)}
|
|
75
|
+
</DateRange>
|
|
76
|
+
</Header>
|
|
77
|
+
<Organization>{item.organization}</Organization>
|
|
78
|
+
{item.summary && (
|
|
79
|
+
<Description
|
|
80
|
+
dangerouslySetInnerHTML={{ __html: marked(item.summary) }}
|
|
81
|
+
/>
|
|
82
|
+
)}
|
|
83
|
+
{item.highlights && item.highlights.length > 0 && (
|
|
84
|
+
<Highlights>
|
|
85
|
+
{item.highlights.map((highlight, j) => (
|
|
86
|
+
<li key={j}>{highlight}</li>
|
|
87
|
+
))}
|
|
88
|
+
</Highlights>
|
|
89
|
+
)}
|
|
90
|
+
</VolunteerItem>
|
|
91
|
+
))}
|
|
92
|
+
</Section>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export default Volunteer;
|
package/src/ui/Work.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import Section from './Section';
|
|
3
|
+
import { marked } from 'marked';
|
|
4
|
+
|
|
5
|
+
const WorkItem = styled.div`
|
|
6
|
+
margin-bottom: 2rem;
|
|
7
|
+
`;
|
|
8
|
+
|
|
9
|
+
const WorkHeader = styled.div`
|
|
10
|
+
display: flex;
|
|
11
|
+
justify-content: space-between;
|
|
12
|
+
align-items: baseline;
|
|
13
|
+
margin-bottom: 0.5rem;
|
|
14
|
+
flex-wrap: wrap;
|
|
15
|
+
gap: 0.5rem;
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
const Position = styled.h3`
|
|
19
|
+
font-size: 1.125rem;
|
|
20
|
+
color: #0b1f3a;
|
|
21
|
+
margin: 0;
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
const DateRange = styled.div`
|
|
25
|
+
font-size: 0.9rem;
|
|
26
|
+
color: #666;
|
|
27
|
+
font-weight: 400;
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
const Company = styled.div`
|
|
31
|
+
font-size: 1rem;
|
|
32
|
+
color: #555;
|
|
33
|
+
margin-bottom: 0.75rem;
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
const Description = styled.div`
|
|
37
|
+
font-size: 1rem;
|
|
38
|
+
line-height: 1.6;
|
|
39
|
+
color: #444;
|
|
40
|
+
margin-bottom: 0.5rem;
|
|
41
|
+
|
|
42
|
+
p {
|
|
43
|
+
margin-bottom: 0.5rem;
|
|
44
|
+
}
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
const Highlights = styled.ul`
|
|
48
|
+
margin-top: 0.75rem;
|
|
49
|
+
padding-left: 1.5rem;
|
|
50
|
+
list-style: disc;
|
|
51
|
+
|
|
52
|
+
li {
|
|
53
|
+
margin-bottom: 0.5rem;
|
|
54
|
+
line-height: 1.6;
|
|
55
|
+
color: #444;
|
|
56
|
+
}
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
const formatDate = (date) => {
|
|
60
|
+
if (!date) return 'Present';
|
|
61
|
+
const d = new Date(date);
|
|
62
|
+
return d.toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const Work = ({ work }) => {
|
|
66
|
+
if (!work || work.length === 0) return null;
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<Section title="Experience">
|
|
70
|
+
{work.map((item, i) => (
|
|
71
|
+
<WorkItem key={i}>
|
|
72
|
+
<WorkHeader>
|
|
73
|
+
<Position>{item.position}</Position>
|
|
74
|
+
<DateRange>
|
|
75
|
+
{formatDate(item.startDate)} - {formatDate(item.endDate)}
|
|
76
|
+
</DateRange>
|
|
77
|
+
</WorkHeader>
|
|
78
|
+
<Company>
|
|
79
|
+
{item.name}
|
|
80
|
+
{item.location && ` • ${item.location}`}
|
|
81
|
+
</Company>
|
|
82
|
+
{item.summary && (
|
|
83
|
+
<Description
|
|
84
|
+
dangerouslySetInnerHTML={{ __html: marked(item.summary) }}
|
|
85
|
+
/>
|
|
86
|
+
)}
|
|
87
|
+
{item.highlights && item.highlights.length > 0 && (
|
|
88
|
+
<Highlights>
|
|
89
|
+
{item.highlights.map((highlight, j) => (
|
|
90
|
+
<li key={j}>{highlight}</li>
|
|
91
|
+
))}
|
|
92
|
+
</Highlights>
|
|
93
|
+
)}
|
|
94
|
+
</WorkItem>
|
|
95
|
+
))}
|
|
96
|
+
</Section>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export default Work;
|