mirror of
https://github.com/kremalicious/portfolio.git
synced 2025-01-03 10:25:00 +01:00
add resume
This commit is contained in:
parent
13bdde4686
commit
f44e0557ed
11
README.md
11
README.md
@ -19,6 +19,7 @@
|
||||
- [💍 One data file to rule all pages](#-one-data-file-to-rule-all-pages)
|
||||
- [🐱 GitHub repositories](#-github-repositories)
|
||||
- [💅 Theme switcher](#-theme-switcher)
|
||||
- [🗂 Resume](#-resume)
|
||||
- [🏆 SEO component](#-seo-component)
|
||||
- [📇 Client-side vCard creation](#-client-side-vcard-creation)
|
||||
- [💫 Page transitions](#-page-transitions)
|
||||
@ -76,6 +77,16 @@ If you want to know how, have a look at the respective components:
|
||||
- [`src/components/molecules/ThemeSwitch.jsx`](src/components/molecules/ThemeSwitch.jsx)
|
||||
- [`src/hooks/use-dark-mode.jsx`](src/hooks/use-dark-mode.jsx)
|
||||
|
||||
### 🗂 Resume
|
||||
|
||||
Resume page based on [JSON Resume](https://jsonresume.org) standard.
|
||||
|
||||
If you want to know how, have a look at the respective components:
|
||||
|
||||
- [`src/pages/resume.jsx`](src/pages/resume.jsx)
|
||||
- [`content/resume.json`](content/resume.json)
|
||||
- [`src/hooks/use-resume.js`](src/hooks/use-resume.js)
|
||||
|
||||
### 🏆 SEO component
|
||||
|
||||
Includes a SEO component which automatically switches all required `meta` tags for search engines, Twitter Cards, and Facebook OpenGraph tags based on the browsed route/page.
|
||||
|
@ -1,19 +1,11 @@
|
||||
# more personal metadata can be found in ./resume.json
|
||||
|
||||
- title: Matthias Kretschmann
|
||||
tagline: Designer & Developer
|
||||
description: Portfolio of web & ui designer/developer hybrid Matthias Kretschmann.
|
||||
url: https://matthiaskretschmann.com
|
||||
email: m@kretschmann.io
|
||||
avatar: ../src/images/avatar.jpg
|
||||
img: ../src/images/twitter-card.png
|
||||
|
||||
social:
|
||||
Mail: mailto:m@kretschmann.io
|
||||
Blog: https://kremalicious.com
|
||||
Twitter: https://twitter.com/kremalicious
|
||||
GitHub: https://github.com/kremalicious
|
||||
Dribbble: https://dribbble.com/kremalicious
|
||||
Keybase: https://keybase.io/kremalicious
|
||||
|
||||
availability:
|
||||
status: false
|
||||
available: '👔 Available for new projects. <a href="mailto:m@kretschmann.io">Let’s talk</a>!'
|
||||
|
237
content/resume.json
Normal file
237
content/resume.json
Normal file
@ -0,0 +1,237 @@
|
||||
{
|
||||
"basics": {
|
||||
"name": "Matthias Kretschmann",
|
||||
"label": "Designer & Developer",
|
||||
"picture": "../src/images/avatar.jpg",
|
||||
"email": "m@kretschmann.io",
|
||||
"website": "https://matthiaskretschmann.com",
|
||||
"summary": "",
|
||||
"profiles": [
|
||||
{
|
||||
"network": "Blog",
|
||||
"url": "https://kremalicious.com"
|
||||
},
|
||||
{
|
||||
"network": "Twitter",
|
||||
"username": "kremalicious",
|
||||
"url": "https://twitter.com/kremalicious"
|
||||
},
|
||||
{
|
||||
"network": "GitHub",
|
||||
"username": "kremalicious",
|
||||
"url": "https://github.com/kremalicious"
|
||||
},
|
||||
{
|
||||
"network": "Dribbble",
|
||||
"username": "kremalicious",
|
||||
"url": "https://dribbble.com/kremalicious"
|
||||
},
|
||||
{
|
||||
"network": "Keybase",
|
||||
"username": "kremalicious",
|
||||
"url": "https://keybase.io/kremalicious"
|
||||
}
|
||||
],
|
||||
"location": {
|
||||
"city": "Berlin",
|
||||
"country": "Germany",
|
||||
"countryCode": "DE"
|
||||
}
|
||||
},
|
||||
"work": [
|
||||
{
|
||||
"company": "Ocean Protocol Foundation",
|
||||
"position": "Lead UI Designer & Developer",
|
||||
"website": "https://oceanprotocol.com",
|
||||
"startDate": "2017-01-01",
|
||||
"summary": "Leading the UI design & development of Ocean Protocol, iterating on a components-based UI design system spanning all of Ocean Protocol's web properties. Additionally, I conceptualize, execute and iterate on the creative and visual direction of the Ocean Protocol brand.",
|
||||
"highlights": ["Started the company"]
|
||||
},
|
||||
{
|
||||
"company": "BigchainDB GmbH",
|
||||
"position": "Lead UI Designer & Developer",
|
||||
"website": "https://bigchaindb.com",
|
||||
"startDate": "2016-12-01",
|
||||
"endDate": "2018-12-31",
|
||||
"summary": "Leading the UI design & development of all BigchainDB web properties. I created the initial BigchainDB brand and further conceptualized, executed and iterated on the creative and visual direction of BigchainDB. This included creating and iterating on a components-based UI design system for all of BigchainDB's web properties.",
|
||||
"highlights": ["Started the company"]
|
||||
},
|
||||
{
|
||||
"company": "ascribe GmbH",
|
||||
"position": "UI Designer & Developer",
|
||||
"website": "https://ascribe.io",
|
||||
"startDate": "2016-01-01",
|
||||
"endDate": "2017-12-31",
|
||||
"summary": "Description...",
|
||||
"highlights": ["Started the company"]
|
||||
},
|
||||
{
|
||||
"company": "ChartMogul Ltd.",
|
||||
"position": "Lead UI Engineer",
|
||||
"website": "https://chartmogul.com",
|
||||
"startDate": "2015-07-15",
|
||||
"endDate": "2017-02-01",
|
||||
"summary": "Co-designing and leading the UI design & development of various ChartMogul web properties. This included the creation of a components-based UI design system and implementing it across all web touch points. Besides designing and implementing new features, I maintained the front-end of the ChartMogul application and implemented the UI design system by refactoring most of its front-end codebase.",
|
||||
"highlights": ["Started the company"]
|
||||
},
|
||||
{
|
||||
"company": "UN World Food Programme/ShareTheMeal",
|
||||
"position": "UI Engineer",
|
||||
"website": "https://sharethemeal.org",
|
||||
"startDate": "2014-10-01",
|
||||
"endDate": "2015-06-01",
|
||||
"summary": "...",
|
||||
"highlights": ["Started the company"]
|
||||
},
|
||||
{
|
||||
"company": "ezeep GmbH",
|
||||
"position": "Lead Designer & Front End Developer",
|
||||
"website": "https://ezeep.com",
|
||||
"startDate": "2012-01-01",
|
||||
"endDate": "2014-09-01",
|
||||
"summary": "Creating an unprecedented, market-leading & award-winning user experience around printing based on the principles of emotional design way ahead of all competitors. This included defining the product based on user & market research in an iterative process and designing & building ezeep’s numerous touch points, like the web app, web site, desktop apps for Windows & Mac OS X and apps for iOS & Android. On top of that I created the corporate identity and a consistent visual branding including the logo."
|
||||
},
|
||||
{
|
||||
"company": "Martin Luther University Halle-Wittenberg",
|
||||
"position": "UI/UX Designer & Front End Developer",
|
||||
"startDate": "2009-02-01",
|
||||
"endDate": "2012-01-01",
|
||||
"summary": "Conceptualizing & implementing numerous in-house and public facing interfaces for thousands of students and staff. Additionally, conceptualizing, creating and maintaining the blog network & community for all students & staff."
|
||||
},
|
||||
{
|
||||
"company": "Harz University of Applied Sciences",
|
||||
"position": "Consultant & Teacher",
|
||||
"startDate": "2011-02-01",
|
||||
"endDate": "2011-05-01",
|
||||
"summary": "Conceptualizing a web design & development university seminar and building a responsive & fluid grid framework with a basic HTML/CSS template for students of Media Informatics at the Harz University of Applied Sciences to learn and use."
|
||||
},
|
||||
{
|
||||
"company": "Martin Luther University Halle-Wittenberg",
|
||||
"position": "Consultant & Teacher",
|
||||
"startDate": "2011-02-01",
|
||||
"endDate": "2011-05-01",
|
||||
"summary": "Conceptualizing a WordPress-based web design university seminar and building a minimal starting theme for students of media & communication science at the MLU Halle-Wittenberg to learn and use."
|
||||
},
|
||||
{
|
||||
"company": "Shortmoves",
|
||||
"position": "Web Designer & Developer",
|
||||
"startDate": "2009-01-01",
|
||||
"endDate": "2010-01-01",
|
||||
"summary": "Creating & managing the web presence and marketing material of the International Shortfilm Festival Shortmoves in Halle (Saale), Germany."
|
||||
},
|
||||
{
|
||||
"company": "Agentur Ahron",
|
||||
"position": "Co-Founder & Photojournalist & Photographer",
|
||||
"startDate": "2005-01-01",
|
||||
"endDate": "2008-12-31",
|
||||
"summary": "Co-founded and built up a photo agency from the ground up and worked as a photographer ranging from journalistic works for news agencies & newspapers to photographic work for private clients."
|
||||
},
|
||||
{
|
||||
"company": "Freelance",
|
||||
"position": "Designer & Developer",
|
||||
"startDate": "2004-01-01",
|
||||
"summary": "Numerous projects and clients as a UI/UX Designer, Front End Developer, Icon Designer & Photographer."
|
||||
}
|
||||
],
|
||||
"education": [
|
||||
{
|
||||
"institution": "Self-taught",
|
||||
"area": "UI Design & Web Development",
|
||||
"studyType": "Autodidactic",
|
||||
"startDate": "1999-01-01",
|
||||
"endDate": "2004-01-01"
|
||||
},
|
||||
{
|
||||
"institution": "Martin Luther University Halle-Wittenberg",
|
||||
"area": "Media/Communication Science & Art History",
|
||||
"studyType": "Bachelor",
|
||||
"startDate": "2008-01-01",
|
||||
"endDate": "2012-01-01"
|
||||
},
|
||||
{
|
||||
"institution": "Martin Luther University Halle-Wittenberg",
|
||||
"area": "Political Science & Sociology",
|
||||
"studyType": "Magister Artium",
|
||||
"startDate": "2006-01-01",
|
||||
"endDate": "2008-01-01"
|
||||
}
|
||||
],
|
||||
"awards": [
|
||||
{
|
||||
"title": "German Design Award",
|
||||
"date": "2015-11-01",
|
||||
"awarder": "ezeep GmbH"
|
||||
},
|
||||
{
|
||||
"title": "CeBIT Preview Award",
|
||||
"date": "2013-11-01",
|
||||
"awarder": "ezeep GmbH"
|
||||
}
|
||||
],
|
||||
"skills": [
|
||||
{
|
||||
"name": "Design",
|
||||
"level": "Master",
|
||||
"keywords": [
|
||||
"Product Design",
|
||||
"Service Design",
|
||||
"Interface Design",
|
||||
"User Experience Design",
|
||||
"Communication Design",
|
||||
"Interaction Design",
|
||||
"Information Architecture",
|
||||
"Icon Design",
|
||||
"Web Design",
|
||||
"Typography",
|
||||
"Design management"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Web Development",
|
||||
"level": "Master",
|
||||
"keywords": [
|
||||
"HTML",
|
||||
"CSS",
|
||||
"Javascript",
|
||||
"Node.js",
|
||||
"npm ecosystem",
|
||||
"SASS/SCSS",
|
||||
"Less",
|
||||
"Stylus",
|
||||
"Gulp",
|
||||
"Gatsby",
|
||||
"React",
|
||||
"Styled Components",
|
||||
"JAMstack"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "General Software Development",
|
||||
"level": "Master",
|
||||
"keywords": [
|
||||
"Git",
|
||||
"GitHub",
|
||||
"Bash",
|
||||
"UNIX",
|
||||
"Agile: Kanban & Scrum",
|
||||
"Prototyping",
|
||||
"Incremental"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "DevOps",
|
||||
"level": "Intermediate",
|
||||
"keywords": ["AWS", "Now", "Serverless", "Cloudflare", "NGINX", "Apache"]
|
||||
}
|
||||
],
|
||||
"languages": [
|
||||
{
|
||||
"language": "German",
|
||||
"fluency": "Native speaker"
|
||||
},
|
||||
{
|
||||
"language": "English",
|
||||
"fluency": "Advanced speaker"
|
||||
}
|
||||
]
|
||||
}
|
@ -11,39 +11,21 @@ module.exports = {
|
||||
siteUrl: `${url}`
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
resolve: 'gatsby-transformer-yaml',
|
||||
options: {
|
||||
plugins: [
|
||||
'gatsby-transformer-yaml',
|
||||
'gatsby-transformer-json',
|
||||
{
|
||||
resolve: 'gatsby-source-filesystem',
|
||||
options: {
|
||||
name: 'content',
|
||||
path: path.join(__dirname, 'content')
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
resolve: 'gatsby-transformer-json',
|
||||
options: {
|
||||
plugins: [
|
||||
{
|
||||
resolve: 'gatsby-source-filesystem',
|
||||
options: {
|
||||
name: 'pkg',
|
||||
path: path.join(__dirname, 'package.json')
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
resolve: 'gatsby-plugin-sass',
|
||||
options: {
|
||||
includePaths: [`${__dirname}/node_modules`, `${__dirname}/src/styles`]
|
||||
}
|
||||
},
|
||||
{
|
||||
resolve: 'gatsby-source-filesystem',
|
||||
@ -52,6 +34,12 @@ module.exports = {
|
||||
path: path.join(__dirname, 'src', 'images')
|
||||
}
|
||||
},
|
||||
{
|
||||
resolve: 'gatsby-plugin-sass',
|
||||
options: {
|
||||
includePaths: [`${__dirname}/node_modules`, `${__dirname}/src/styles`]
|
||||
}
|
||||
},
|
||||
{
|
||||
resolve: 'gatsby-plugin-svgr',
|
||||
options: {
|
||||
|
@ -4,32 +4,6 @@
|
||||
"tagline": "Designer & Developer",
|
||||
"description": "Portfolio of web & ui designer/developer hybrid Matthias Kretschmann.",
|
||||
"url": "https://matthiaskretschmann.com",
|
||||
"email": "m@kretschmann.io",
|
||||
"avatar": {
|
||||
"childImageSharp": {
|
||||
"fluid": {
|
||||
"src": "/static/b45f45aa8d98d4e4019a242d38f2f248/bc3a8/avatar.jpg"
|
||||
},
|
||||
"resize": {
|
||||
"src": "/static/b45f45aa8d98d4e4019a242d38f2f248/bc3a8/avatar.jpg"
|
||||
}
|
||||
}
|
||||
},
|
||||
"img": {
|
||||
"childImageSharp": {
|
||||
"resize": {
|
||||
"src": "/static/5ecbb5694b0b2152aa71398164af38b2/da1a7/twitter-card.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
"social": {
|
||||
"Email": "mailto:m@kretschmann.io",
|
||||
"Blog": "https://kremalicious.com",
|
||||
"Twitter": "https://twitter.com/kremalicious",
|
||||
"GitHub": "https://github.com/kremalicious",
|
||||
"Dribbble": "https://dribbble.com/kremalicious",
|
||||
"Keybase": "https://keybase.io/kremalicious"
|
||||
},
|
||||
"availability": {
|
||||
"status": false,
|
||||
"available": "👔 Available for new projects. <a href=\"mailto:m@kretschmann.io\">Let’s talk</a>!",
|
||||
|
@ -28,6 +28,7 @@ export default function Layout({ children, location }) {
|
||||
const isHomepage =
|
||||
location.pathname === '/' ||
|
||||
location.pathname === '/offline-plugin-app-shell-fallback/'
|
||||
const isResume = location.pathname === '/resume'
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -40,7 +41,7 @@ export default function Layout({ children, location }) {
|
||||
delay={timeout}
|
||||
delayChildren={timeout}
|
||||
>
|
||||
<Header minimal={!isHomepage} />
|
||||
<Header minimal={!isHomepage} isResume={isResume} />
|
||||
<main className={styles.screen}>{children}</main>
|
||||
</RoutesContainer>
|
||||
</PoseGroup>
|
||||
|
@ -1,7 +1,14 @@
|
||||
import React from 'react'
|
||||
import Helmet from 'react-helmet'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useMeta } from '../../hooks/use-meta'
|
||||
import { useMeta } from '../../hooks/use-meta'`
|
||||
import { useResume } from '../../hooks/use-resume'
|
||||
|
||||
const MetaTags = ({ title, description, url, image, meta }) => {
|
||||
const resume = useResume()
|
||||
const twitterHandle = resume.basics.profiles.filter(
|
||||
({ network }) => network === 'Twitter'
|
||||
)[0].username
|
||||
|
||||
const MetaTags = ({ title, description, url, image, meta }) => (
|
||||
<Helmet
|
||||
@ -24,7 +31,7 @@ const MetaTags = ({ title, description, url, image, meta }) => (
|
||||
|
||||
{/* Twitter Card tags */}
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:creator" content={meta.social.Twitter} />
|
||||
<meta name="twitter:creator" content={twitterHandle} />
|
||||
<meta name="twitter:title" content={title} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
<meta name="twitter:image" content={`${meta.url}${image}`} />
|
||||
|
@ -2,20 +2,34 @@ import React from 'react'
|
||||
import saveAs from 'file-saver'
|
||||
import vCard from 'vcf'
|
||||
import { useMeta } from '../../hooks/use-meta'
|
||||
import { useResume } from '../../hooks/use-resume'
|
||||
|
||||
export default function Vcard() {
|
||||
const metaYaml = useMeta()
|
||||
const { basics } = useResume()
|
||||
const data = useStaticQuery(query)
|
||||
const photoSrc = basics.picture.childImageSharp.fixed.src
|
||||
const { name, label, email, profiles } = basics
|
||||
|
||||
const meta = {
|
||||
...metaYaml,
|
||||
photoSrc,
|
||||
name,
|
||||
label,
|
||||
email,
|
||||
profiles
|
||||
}
|
||||
|
||||
const handleAddressbookClick = e => {
|
||||
e.preventDefault()
|
||||
init(metaYaml)
|
||||
init(meta)
|
||||
}
|
||||
|
||||
return (
|
||||
<a
|
||||
// href is kinda fake, only there for usability
|
||||
// so user knows what to expect when hovering the link before clicking
|
||||
href={metaYaml.addressbook}
|
||||
href={meta.addressbook}
|
||||
onClick={handleAddressbookClick}
|
||||
>
|
||||
Add to addressbook
|
||||
@ -24,10 +38,8 @@ export default function Vcard() {
|
||||
}
|
||||
|
||||
export const init = async meta => {
|
||||
const photoSrc = meta.avatar.childImageSharp.resize.src
|
||||
|
||||
// first, convert the avatar to base64, then construct all vCard elements
|
||||
const dataUrl = await toDataURL(photoSrc, 'image/jpeg')
|
||||
const dataUrl = await toDataURL(meta.photoSrc, 'image/jpeg')
|
||||
const vcard = await constructVcard(dataUrl, meta)
|
||||
|
||||
downloadVcard(vcard, meta)
|
||||
@ -49,12 +61,12 @@ export const constructVcard = async (dataUrl, meta) => {
|
||||
// for vcard to actually display the image for whatever reason
|
||||
// const dataUrlCleaned = dataUrl.split('data:image/jpeg;base64,').join('')
|
||||
// contact.set('photo', dataUrlCleaned, { encoding: 'b', type: 'JPEG' })
|
||||
contact.set('fn', meta.title)
|
||||
contact.set('title', meta.tagline)
|
||||
contact.set('fn', meta.name)
|
||||
contact.set('title', meta.label)
|
||||
contact.set('email', meta.email)
|
||||
contact.set('nickname', 'kremalicious')
|
||||
contact.set('url', meta.url, { type: 'Portfolio' })
|
||||
contact.add('url', meta.social.Blog, { type: 'Blog' })
|
||||
contact.set('nickname', 'kremalicious')
|
||||
contact.add('x-socialprofile', meta.social.Twitter, { type: 'twitter' })
|
||||
contact.add('x-socialprofile', meta.social.GitHub, { type: 'GitHub' })
|
||||
|
||||
|
@ -8,7 +8,8 @@ import { ReactComponent as Logo } from '../../images/logo.svg'
|
||||
import styles from './LogoUnit.module.scss'
|
||||
|
||||
LogoUnit.propTypes = {
|
||||
minimal: PropTypes.bool
|
||||
minimal: PropTypes.bool,
|
||||
isResume: PropTypes.bool
|
||||
}
|
||||
|
||||
export default function LogoUnit({ minimal }) {
|
||||
|
@ -4,8 +4,25 @@ import posed from 'react-pose'
|
||||
import { moveInTop } from '../atoms/Transitions'
|
||||
import Icon from '../atoms/Icon'
|
||||
import { useMeta } from '../../hooks/use-meta'
|
||||
import { useResume } from '../../hooks/use-resume'
|
||||
import styles from './Networks.module.scss'
|
||||
|
||||
const NetworkLink = ({ name, url }) => (
|
||||
<a
|
||||
className={linkClasses(name)}
|
||||
href={url}
|
||||
data-testid={`network-${name.toLowerCase()}`}
|
||||
>
|
||||
<Icon name={name} />
|
||||
<span className={styles.title}>{name}</span>
|
||||
</a>
|
||||
)
|
||||
|
||||
NetworkLink.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
url: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default function Networks({ small, hide }) {
|
||||
const { social } = useMeta()
|
||||
if (hide) return null
|
||||
@ -17,16 +34,14 @@ export default function Networks({ small, hide }) {
|
||||
|
||||
return (
|
||||
<Animation className={small ? styles.small : styles.networks}>
|
||||
{Object.keys(social).map((key, i) => (
|
||||
<a
|
||||
className={linkClasses(key)}
|
||||
href={social[key]}
|
||||
key={i}
|
||||
data-testid={`network-${key.toLowerCase()}`}
|
||||
>
|
||||
<Icon name={key} />
|
||||
<span className={styles.title}>{key}</span>
|
||||
</a>
|
||||
<NetworkLink name={'Email'} url={`mailto:${basics.email}`} />
|
||||
|
||||
{profiles.map(profile => (
|
||||
<NetworkLink
|
||||
key={profile.network}
|
||||
name={profile.network}
|
||||
url={profile.url}
|
||||
/>
|
||||
))}
|
||||
</Animation>
|
||||
)
|
||||
|
@ -21,11 +21,11 @@
|
||||
}
|
||||
|
||||
.link {
|
||||
margin-left: $spacer / 2;
|
||||
margin-right: $spacer / 2;
|
||||
margin-left: $spacer / $line-height;
|
||||
margin-right: $spacer / $line-height;
|
||||
margin-bottom: $spacer / 2;
|
||||
text-align: center;
|
||||
display: block;
|
||||
display: inline-block;
|
||||
flex: 0 1;
|
||||
min-width: 2.5rem;
|
||||
|
||||
|
@ -8,16 +8,17 @@ import styles from './Header.module.scss'
|
||||
import { useMeta } from '../../hooks/use-meta'
|
||||
|
||||
Header.propTypes = {
|
||||
minimal: PropTypes.bool
|
||||
minimal: PropTypes.bool,
|
||||
isResume: PropTypes.bool
|
||||
}
|
||||
|
||||
export default function Header({ minimal }) {
|
||||
export default function Header({ minimal, isResume }) {
|
||||
const { availability } = useMeta()
|
||||
|
||||
return (
|
||||
<header className={minimal ? styles.minimal : styles.header}>
|
||||
<ThemeSwitch />
|
||||
<LogoUnit minimal={minimal} />
|
||||
<LogoUnit minimal={minimal} isResume={isResume} />
|
||||
<Networks hide={minimal} />
|
||||
<Availability hide={minimal && !availability.status} />
|
||||
</header>
|
||||
|
62
src/hooks/use-resume.js
Normal file
62
src/hooks/use-resume.js
Normal file
@ -0,0 +1,62 @@
|
||||
import { useStaticQuery, graphql } from 'gatsby'
|
||||
|
||||
const query = graphql`
|
||||
query Resume {
|
||||
contentJson {
|
||||
basics {
|
||||
name
|
||||
label
|
||||
picture {
|
||||
childImageSharp {
|
||||
fixed(width: 256, height: 256) {
|
||||
...GatsbyImageSharpFixed_withWebp_noBase64
|
||||
}
|
||||
}
|
||||
}
|
||||
email
|
||||
website
|
||||
summary
|
||||
profiles {
|
||||
network
|
||||
url
|
||||
username
|
||||
}
|
||||
location {
|
||||
city
|
||||
country
|
||||
countryCode
|
||||
}
|
||||
}
|
||||
education {
|
||||
institution
|
||||
area
|
||||
studyType
|
||||
startDate
|
||||
endDate
|
||||
}
|
||||
languages {
|
||||
language
|
||||
fluency
|
||||
}
|
||||
skills {
|
||||
name
|
||||
level
|
||||
keywords
|
||||
}
|
||||
work {
|
||||
company
|
||||
position
|
||||
website
|
||||
startDate
|
||||
endDate
|
||||
summary
|
||||
highlights
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const useResume = () => {
|
||||
const { contentJson } = useStaticQuery(query)
|
||||
return contentJson
|
||||
}
|
10
src/pages/__tests__/resume.test.jsx
Normal file
10
src/pages/__tests__/resume.test.jsx
Normal file
@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
import { render } from '@testing-library/react'
|
||||
import Resume from '../resume'
|
||||
|
||||
describe('Resume', () => {
|
||||
it('renders correctly from data file values', () => {
|
||||
const { container } = render(<Resume />)
|
||||
expect(container.firstChild).toBeInTheDocument()
|
||||
})
|
||||
})
|
45
src/pages/resume/ResumeItem.jsx
Normal file
45
src/pages/resume/ResumeItem.jsx
Normal file
@ -0,0 +1,45 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styles from './ResumeItem.module.scss'
|
||||
|
||||
export default function ResumeItem({ workPlace, eduPlace }) {
|
||||
const title = workPlace ? workPlace.company : eduPlace.institution
|
||||
const subTitle = workPlace ? workPlace.position : eduPlace.area
|
||||
const text = workPlace ? workPlace.summary : eduPlace.studyType
|
||||
const { startDate, endDate } = workPlace || eduPlace
|
||||
|
||||
const dateStart = new Date(startDate).getFullYear()
|
||||
const dateEnd = endDate && new Date(endDate).getFullYear()
|
||||
const isSameYear = dateStart === dateEnd
|
||||
|
||||
return (
|
||||
<div className={styles.resumeItem}>
|
||||
<span className={styles.time}>
|
||||
{dateStart}
|
||||
{dateEnd ? !isSameYear && `–${dateEnd}` : '–present'}{' '}
|
||||
</span>
|
||||
<h4 className={styles.title}>{title}</h4>
|
||||
<h5 className={styles.subTitle}>{subTitle}</h5>
|
||||
<p>
|
||||
<em>{text}</em>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ResumeItem.propTypes = {
|
||||
workPlace: PropTypes.shape({
|
||||
startDate: PropTypes.string.isRequired,
|
||||
endDate: PropTypes.string,
|
||||
company: PropTypes.string.isRequired,
|
||||
position: PropTypes.string.isRequired,
|
||||
summary: PropTypes.string
|
||||
}),
|
||||
eduPlace: PropTypes.shape({
|
||||
startDate: PropTypes.string.isRequired,
|
||||
endDate: PropTypes.string,
|
||||
institution: PropTypes.string.isRequired,
|
||||
area: PropTypes.string.isRequired,
|
||||
studyType: PropTypes.string
|
||||
})
|
||||
}
|
52
src/pages/resume/ResumeItem.module.scss
Normal file
52
src/pages/resume/ResumeItem.module.scss
Normal file
@ -0,0 +1,52 @@
|
||||
@import 'variables';
|
||||
|
||||
.resumeItem {
|
||||
padding-bottom: $spacer * 3;
|
||||
padding-left: $spacer;
|
||||
position: relative;
|
||||
border-left: 1px solid $brand-grey-light;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border-radius: 50%;
|
||||
background: $brand-grey-light;
|
||||
position: absolute;
|
||||
left: -0.5rem;
|
||||
}
|
||||
|
||||
p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: $spacer / 3;
|
||||
font-size: $font-size-h4;
|
||||
position: relative;
|
||||
top: -($spacer / 6);
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
color: $brand-grey-light;
|
||||
font-size: $font-size-h5;
|
||||
|
||||
:global(.dark) & {
|
||||
color: $brand-grey-dimmed;
|
||||
}
|
||||
}
|
||||
|
||||
.time {
|
||||
display: block;
|
||||
margin-bottom: $spacer / 2;
|
||||
white-space: nowrap;
|
||||
|
||||
@media (min-width: $screen-md) {
|
||||
text-align: right;
|
||||
position: absolute;
|
||||
top: -0.3rem;
|
||||
right: 105%;
|
||||
}
|
||||
}
|
70
src/pages/resume/index.jsx
Normal file
70
src/pages/resume/index.jsx
Normal file
@ -0,0 +1,70 @@
|
||||
import React from 'react'
|
||||
import shortid from 'shortid'
|
||||
import SEO from '../../components/atoms/SEO'
|
||||
import LinkIcon from '../../components/atoms/LinkIcon'
|
||||
import { useResume } from '../../hooks/use-resume'
|
||||
import styles from './index.module.scss'
|
||||
import ResumeItem from './ResumeItem'
|
||||
|
||||
export default function Resume() {
|
||||
const { basics, education, languages, work } = useResume()
|
||||
|
||||
return (
|
||||
<>
|
||||
<SEO />
|
||||
|
||||
<div className={styles.resume}>
|
||||
<header>
|
||||
<p>Résumé</p>
|
||||
<h1 className={styles.title}>{basics.name}</h1>
|
||||
<h2 className={styles.label}>{basics.label}</h2>
|
||||
</header>
|
||||
|
||||
<div>
|
||||
<ul className={styles.contact}>
|
||||
<li>
|
||||
<a href={basics.website}>
|
||||
<LinkIcon type="website" />
|
||||
{basics.website.replace('https://', '')}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<LinkIcon type="Email" />
|
||||
{basics.email}
|
||||
</li>
|
||||
<li>
|
||||
<LinkIcon type="Info" />
|
||||
{basics.location.city}
|
||||
</li>
|
||||
<li>
|
||||
<LinkIcon type="Info" />
|
||||
{languages.map(item => (
|
||||
<span
|
||||
key={item.language}
|
||||
>{`${item.language} (${item.fluency})`}</span>
|
||||
))}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className={styles.subTitle}>Work</h3>
|
||||
</div>
|
||||
<div>
|
||||
{work.map(workPlace => (
|
||||
<ResumeItem key={shortid.generate()} workPlace={workPlace} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className={styles.subTitle}>Education</h3>
|
||||
</div>
|
||||
<div>
|
||||
{education.map(eduPlace => (
|
||||
<ResumeItem key={shortid.generate()} eduPlace={eduPlace} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
66
src/pages/resume/index.module.scss
Normal file
66
src/pages/resume/index.module.scss
Normal file
@ -0,0 +1,66 @@
|
||||
@import 'variables';
|
||||
|
||||
.resume {
|
||||
padding: $spacer;
|
||||
display: grid;
|
||||
grid-gap: $spacer * 4;
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@media (min-width: $screen-md) {
|
||||
grid-template-columns: 1fr 2fr;
|
||||
max-width: calc(#{$projectImageMaxWidth} + #{$spacer * 2});
|
||||
margin: 0 auto;
|
||||
padding: $spacer $spacer * 3;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: $font-size-h2;
|
||||
margin-bottom: $spacer / 4;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: $font-size-h3;
|
||||
color: $brand-grey-light;
|
||||
margin-bottom: 0;
|
||||
|
||||
:global(.dark) & {
|
||||
color: $brand-grey-dimmed;
|
||||
}
|
||||
}
|
||||
|
||||
.contact {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
||||
svg {
|
||||
fill: $brand-grey-light;
|
||||
width: $font-size-small;
|
||||
height: $font-size-small;
|
||||
opacity: 0.5;
|
||||
margin-right: $spacer / 4;
|
||||
|
||||
:global(.dark) & {
|
||||
fill: $brand-grey;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: $screen-md) {
|
||||
margin-top: $spacer * 2;
|
||||
}
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
font-size: $font-size-h3;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.resume {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
:global(html) {
|
||||
font-size: 10pt;
|
||||
}
|
||||
}
|
@ -40,10 +40,10 @@ $font-size-mini: 0.7rem;
|
||||
|
||||
$font-size-h1: 2.5rem;
|
||||
$font-size-h2: 2rem;
|
||||
$font-size-h3: 1.75rem;
|
||||
$font-size-h4: $font-size-large;
|
||||
$font-size-h5: $font-size-base;
|
||||
$font-size-h6: $font-size-small;
|
||||
$font-size-h3: 1.65rem;
|
||||
$font-size-h4: 1.45rem;
|
||||
$font-size-h5: $font-size-large;
|
||||
$font-size-h6: $font-size-base;
|
||||
|
||||
$line-height: 1.5;
|
||||
$line-height-small: 1.1428571429;
|
||||
|
@ -2,3 +2,4 @@
|
||||
# www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449
|
||||
|
||||
User-agent: *
|
||||
Disallow: /resume
|
||||
|
Loading…
Reference in New Issue
Block a user