resume
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<head> | |
<style> | |
@media print { body { height: 297mm } } | |
body { | |
width: 210mm; | |
font-family: 'Courier New', Courier, monospace; | |
margin: 0; | |
display: flex; | |
} | |
aside { | |
width: 35%; | |
height: 100vh; | |
border-right: 1px solid black; | |
} | |
aside, | |
main { | |
display: flex; | |
flex-direction: column; | |
} | |
h1 { | |
font-size: x-large; | |
margin-block-start: 2rem; | |
margin-block-end: 1rem; | |
} | |
header { | |
padding-bottom: 2rem; | |
} | |
</style> | |
</head> | |
<body> | |
<aside style='padding: 0 1rem; text-align: right'> | |
<header> | |
<h1>Harry Nguyen</h1> | |
<a>+84 968 992 888</a> (traveling) | |
<a>sonhanguyen@outlook.com</a> | |
<a>linkedin.com/in/sonhanguyen</a> | |
<a>github.com/sonhanguyen</a> | |
</header> | |
<p style='text-align: left'> | |
Polyglot software developer. I am currently interested in web technologies, data engineering and domain driven design. My ideal role is where I work in a full-stack capacity and exposed to every part of the problems at hand. | |
</p> | |
<div> | |
<h1>Skill summary</h1> | |
<style> | |
.skill > :first-line { | |
font-weight: bold; | |
} | |
</style> | |
<div class=skill> | |
<p> Webpack, React, nextjs, styled-components, typescript (<a>git.io/fjWHd</a>, <a>git.io/fhmGY</a>, <a>git.io/vbRu8</a>, <a>git.io/vAHjn</a>) | |
<p> Nodejs, Java, Docker, AWS (CloudFormation, Fargate, Dynamo), Postgres, Terraform | |
<p> Exposure to other technologies: Xamarin, Python (<a>git.io/JTP93</a>), Kotlin (<a>git.io/vbRuQ</a>), | |
<p> Functional/reactive programming (RxJS) | |
<p> OOP design patterns | |
<p> Knowledge sharing (<a>git.io/vAHjC</a>) | |
</div> | |
</div> | |
</aside> | |
<main> | |
<style> | |
article { | |
font-weight: bold; | |
} | |
.row { | |
display: flex; | |
} | |
.col { | |
display: flex; | |
flex-direction: column; | |
} | |
.flex { | |
display: flex; | |
align-content: stretch; | |
flex: 1; | |
} | |
.padded { | |
margin: 1rem; | |
} | |
.right { | |
text-align: right; | |
} | |
</style> | |
<div class='padded' style='margin-top: 0; font-size: small'> | |
<h1>Employment</h1> | |
<template id=my-job> | |
<style> | |
[name=end]:not(:empty) + i { | |
display: none; | |
} | |
[name=end] + i { | |
display: flex; | |
order: 1 | |
} | |
[name=start] { | |
display: flex; | |
order: 2 | |
} | |
[name=end]:not(:empty):before { | |
content: '-'; | |
padding: 0 .5rem; | |
} | |
[name=end] { | |
display: flex; | |
order: 3 | |
} | |
</style> | |
<div> | |
<div class='row'> | |
<article class='flex' style='display: inline'> | |
<slot name=title></slot> | |
<span style='display: inline-block'> | <slot name=company></span> | |
</article> | |
<div class='row right'> | |
<slot name=start></slot> | |
<slot name=end></slot><i>since </i> | |
</div> | |
</div> | |
<slot name=description></slot> | |
<slot> | |
</div> | |
</template> | |
<my-job | |
description='Collaborative design platform (InVision Freehand)' | |
title='Growth Engineer' | |
start=07/2021 | |
> | |
<a slot=company href=invision.com>InVision</a> | |
<ul> | |
<li> Working across multiple services: backend, frontend, data to implement expirimental features | |
<li> Golang, React | |
</ul> | |
</my-job> | |
<my-job | |
title='Software Development Consultant' | |
description='Cloud training platform' | |
company='Shine Solutions' | |
start=01/2021 | |
end=07/2021 | |
> | |
<ul> | |
<li> <a href=acloud.guru>ACloudGuru</a> (data migration project): AWS Step Function, Serverless, GraphQL | |
</ul> | |
</my-job> | |
<my-job | |
description='FX broker, wallet & onboarding application' | |
title='Front-end lead' | |
start=04/2018 | |
end=12/2020 | |
> | |
<a slot=company href=pepperstone.com>PepperStone</a> | |
<ul> | |
<li> Sprint planning and running agile ceremonies | |
<li> Onboarding and monitoring developers from the offshore team | |
<li> React, Typescript, redux-saga | |
<li> Micro services with golang, OpenAPI, json-schema | |
<li> AWS Fargate, Buidkite | |
<li> TDD: Cypress, Jest | |
<li> Payments APIs (Paypal, Worldpay...) | |
<li> Design system (storybook) | |
</ul> | |
</my-job> | |
<my-job | |
description='Adtech cloud-based software' | |
title='Frontend Engineer' | |
start=12/2016 | |
end=03/2018 | |
> | |
<a slot=company href=adslot.com>Adslot</a> | |
<ul> | |
<li> Porting the front-end from an older to a more modern stack (angularjs/coffeescript to react/redux). | |
<li> Unit testing using enzyme. | |
<li> Maintainer of company's open source library, in a trello/travis/github workflow. | |
<li> Mentoring junior devs, code review | |
<li> Introducing typescript to the team. | |
</ul> | |
</my-job> | |
<my-job | |
description='In-house product team, cloud-based data management solution' | |
title='Software Engineer' | |
start=05/2016 | |
end=11/2016 | |
> | |
<a slot=company href=alexsolutions.com.au>Alex Solutions</a> | |
<ul> | |
<li> Refactor the front-end to introduce React, mobx, webpack & react-router. | |
<li> XML-based ETL engine: picked up junit, sbt, jabx in the process. The engine accommodates for execution of scripts (case in point: xQuery & SQL) remotely in the ETL job template. MVP in 3 weeks. The subsequent works utilise springboot and rxjava. | |
</ul> | |
</my-job> | |
<div style='page-break-inside: avoid'> | |
<h1>Education</h1> | |
<template id=my-qualification> | |
<div> | |
<div class='flex'> | |
<div class='flex col'> | |
<div> | |
<article class='flex'><slot name=course></article> | |
<slot name=description> | |
</div> | |
<slot name=university> | |
</div> | |
<div class='col'> | |
<div class='row' style='align-self: flex-end'> | |
<i>class of</i> <slot name=year> | |
</div> | |
<slot name=campus> | |
</div> | |
</div> | |
<slot> | |
</div> | |
</template> | |
<my-qualification | |
course='Master of Information Technology' | |
description='(Professional Computing)' | |
university='Swinburne University of Technology' | |
campus='Melbourne, Australia' | |
year=2016 | |
> | |
<p> Project work with OpenCV </p> | |
</my-qualification> | |
<my-qualification | |
course='Bachelor of Software Engineering' | |
university='Posts and Telecommunication Institute of Technology' | |
campus='Hanoi, Vietnam' | |
year=2013 | |
> | |
<ul> | |
<li> Capstone project in sentiment analysis (weka, lucence, SVM) | |
<li> ACM-ICPC contester | |
</ul> | |
</my-qualification> | |
</div> | |
</div> | |
</main> | |
<script> | |
/** | |
* @arg {Node} fragment | |
* @arg {NamedNodeMap} attributes | |
* | |
* @return {Node} | |
*/ | |
const withDefaults = (fragment, attributes) => { | |
const node = fragment.cloneNode(true) | |
for(let i = 0; i < attributes.length; ++i) { | |
const { value, name } = attributes.item(i) | |
const slot = node.querySelector(`slot[name=${name}]`) | |
if (slot) slot.innerText = value | |
} | |
return node | |
} | |
/** @template T @arg {Array<T | Array<T>>} @return {T[]} */ | |
const flatten = it => [].concat(...it) | |
/** | |
* @arg {{ adoptedStyleSheets: ReadonlyArray<CSSStyleSheet> }} target | |
* @arg {...CSSStyleSheet} styleSheets | |
*/ | |
const prependStyle = (target, ...styleSheets) => { | |
const rules = flatten(styleSheets.map(it => [...it.cssRules])) | |
const css = new CSSStyleSheet() // only tested on Chrome Beta as of Dec 2021 | |
rules.forEach(({ cssText }) => css.insertRule(cssText)) | |
target.adoptedStyleSheets = [ css, ...target.adoptedStyleSheets ] | |
} | |
/** | |
* @arg {Node} template | |
* @arg {{ styleSheets: CSSStyleSheet[] }} | |
* | |
* @return {CustomElementConstructor} | |
*/ | |
const createComponent = (template, { styleSheets = [] } = document) => | |
class extends HTMLElement { | |
constructor() { | |
super(); | |
this.attachShadow({ mode: 'open' }) | |
.appendChild(withDefaults(template, this.attributes)) | |
prependStyle(this.shadowRoot, ...styleSheets) | |
} | |
} | |
/** @arg {{ id: string, content: DocumentFragment }} */ | |
const registerComponent = ({ id, content }) => | |
customElements.define(id, createComponent(content)) | |
document | |
.querySelectorAll`template` | |
.forEach(registerComponent) | |
</script> | |
<script> | |
document | |
.querySelectorAll`a` | |
.forEach(link => { | |
const { innerText } = link | |
let href = link.getAttribute`href` || innerText | |
if (href.match(/^\+?[ -\d]+$/)) | |
href = 'tel:' + href.replace(/[ -]/g, '') | |
else if (href.includes`@`) | |
href = 'mailto:' + innerText | |
else href = href.replace(/^([^:]+:\/\/)?/, 'https://') | |
Object.assign(link, { href }) | |
}) | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment