Last active
March 17, 2023 00:08
-
-
Save sonhanguyen/357a0e5e8b2bbffdb69aed351a437950 to your computer and use it in GitHub Desktop.
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: Helvetica Neue, Helvetica, Arial, sans-serif; | |
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>Son Ha Nguyen</h1> | |
<a>+84 968 992 888</a> (travelling) | |
<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> | |
h2 { | |
font-size: 1.1rem; | |
} | |
.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> | |
<h2> | |
<slot name=title></slot> | |
<span style='display: inline-block'> | <slot name=company></span> | |
</h2> | |
<div class='row'> | |
<div class='flex'><slot name=description></slot></div> | |
<div class='row right'> | |
<slot name=start></slot> | |
<slot name=end></slot><i>since </i> | |
</div> | |
</div> | |
<slot> | |
</div> | |
</template> | |
<my-job | |
title='Freelancer' | |
start=09/2022 | |
> | |
I took some time off back home in Vietnam to take care of familly matters, then used this as an opportunity to travel SEA and work on smaller web projects | |
</my-job> | |
<my-job | |
description='Collaborative design platform' | |
title='Growth Engineer' | |
start=07/2021 | |
end=08/2022 | |
> | |
<a slot=company href=invision.com>InVision</a> | |
<ul> | |
<li> Working across multiple services: backend, frontend, data to drive adoption for InVision new colaborative whiteboard offering Freehand | |
<li> Golang, React | |
</ul> | |
</my-job> | |
<my-job | |
description='Cloud training platform' | |
title='Software Development Consultant' | |
start=01/2021 | |
end=07/2021 | |
> | |
<a slot=company>Shine Solutions</a> | |
<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> Mentoring and managing 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