Merge branch 'develop' of github.com:AmruthPillai/Reactive-Resume into l10n_develop

This commit is contained in:
Amruth Pillai
2020-04-01 16:13:42 +05:30
43 changed files with 593 additions and 240 deletions

View File

@ -1,5 +1,6 @@
import React, { useRef, useEffect, useContext, Suspense } from 'react';
import { useTranslation } from 'react-i18next';
import { PanZoom } from 'react-easy-panzoom';
import AppContext from '../../context/AppContext';
import PageContext from '../../context/PageContext';
@ -8,9 +9,11 @@ import LeftSidebar from '../LeftSidebar/LeftSidebar';
import RightSidebar from '../RightSidebar/RightSidebar';
import templates from '../../templates';
import PageController from '../../shared/PageController';
const App = () => {
const pageRef = useRef(null);
const panZoomRef = useRef(null);
const { i18n } = useTranslation();
const context = useContext(AppContext);
@ -18,24 +21,38 @@ const App = () => {
const { theme, settings } = state;
const pageContext = useContext(PageContext);
const { setPageElement } = pageContext;
const { setPageRef, setPanZoomRef } = pageContext;
useEffect(() => {
setPageElement(pageRef);
setPageRef(pageRef);
setPanZoomRef(panZoomRef);
i18n.changeLanguage(settings.language);
const storedState = JSON.parse(localStorage.getItem('state'));
dispatch({ type: 'import_data', payload: storedState });
}, [dispatch, setPageElement, i18n, settings.language]);
}, [dispatch, setPageRef, setPanZoomRef, i18n, settings.language]);
return (
<Suspense fallback="Loading...">
<div className="h-screen overflow-hidden grid grid-cols-5 items-center">
<div className="h-screen grid grid-cols-5 items-center">
<LeftSidebar />
<div className="z-0 h-screen col-span-3 flex overflow-scroll justify-center items-center">
<div id="page" ref={pageRef} className="shadow-2xl">
{templates.find(x => theme.layout.toLowerCase() === x.key).component()}
</div>
<div className="relative z-0 h-screen overflow-hidden col-span-3 flex justify-center items-center">
<PanZoom
ref={panZoomRef}
minZoom="0.4"
autoCenter
autoCenterZoomLevel={0.7}
enableBoundingBox
boundaryRatioVertical={0.8}
boundaryRatioHorizontal={0.8}
style={{ outline: 'none' }}
>
<div id="page" ref={pageRef} className="shadow-2xl break-words">
{templates.find(x => theme.layout.toLowerCase() === x.key).component()}
</div>
</PanZoom>
<PageController />
</div>
<RightSidebar />

View File

@ -13,7 +13,6 @@ import AddItemButton from '../../../shared/AddItemButton';
import ItemHeading from '../../../shared/ItemHeading';
const AwardsTab = ({ data, onChange }) => {
const { t } = useTranslation();
const context = useContext(AppContext);
const { dispatch } = context;
@ -28,7 +27,7 @@ const AwardsTab = ({ data, onChange }) => {
</div>
<div className="col-span-5">
<TextField
placeholder={t('heading.placeholder')}
placeholder="Heading"
value={data.awards.heading}
onChange={v => onChange('data.awards.heading', v)}
/>
@ -62,7 +61,7 @@ const Form = ({ item, onChange, identifier = '' }) => {
<TextField
className="mb-6"
label={t('awards.title.label')}
placeholder={t('awards.title.placeholder')}
placeholder="Code For Good Hackathon"
value={item.title}
onChange={v => onChange(`${identifier}title`, v)}
/>
@ -70,7 +69,7 @@ const Form = ({ item, onChange, identifier = '' }) => {
<TextField
className="mb-6"
label={t('awards.subtitle.label')}
placeholder={t('awards.subtitle.placeholder')}
placeholder="First Place, National Level"
value={item.subtitle}
onChange={v => onChange(`${identifier}subtitle`, v)}
/>
@ -78,7 +77,6 @@ const Form = ({ item, onChange, identifier = '' }) => {
<TextArea
className="mb-6"
label={t('app:item.description.label')}
placeholder={t('awards.description.placeholder')}
value={item.description}
onChange={v => onChange(`${identifier}description`, v)}
/>

View File

@ -13,7 +13,6 @@ import ItemHeading from '../../../shared/ItemHeading';
import AddItemButton from '../../../shared/AddItemButton';
const CertificationsTab = ({ data, onChange }) => {
const { t } = useTranslation();
const context = useContext(AppContext);
const { dispatch } = context;
@ -28,7 +27,7 @@ const CertificationsTab = ({ data, onChange }) => {
</div>
<div className="col-span-5">
<TextField
placeholder={t('heading.placeholder')}
placeholder="Heading"
value={data.certifications.heading}
onChange={v => onChange('data.certifications.heading', v)}
/>
@ -62,7 +61,7 @@ const Form = ({ item, onChange, identifier = '' }) => {
<TextField
className="mb-6"
label={t('certifications.title.label')}
placeholder={t('certifications.title.placeholder')}
placeholder="CS50: Intro to Computer Science"
value={item.title}
onChange={v => onChange(`${identifier}title`, v)}
/>
@ -70,7 +69,7 @@ const Form = ({ item, onChange, identifier = '' }) => {
<TextField
className="mb-6"
label={t('certifications.subtitle.label')}
placeholder={t('certifications.subtitle.placeholder')}
placeholder="Harvard University"
value={item.subtitle}
onChange={v => onChange(`${identifier}subtitle`, v)}
/>
@ -78,7 +77,6 @@ const Form = ({ item, onChange, identifier = '' }) => {
<TextArea
className="mb-6"
label={t('app:item.description.label')}
placeholder={t('certifications.description.placeholder')}
value={item.description}
onChange={v => onChange(`${identifier}description`, v)}
/>

View File

@ -13,7 +13,6 @@ import AddItemButton from '../../../shared/AddItemButton';
import ItemHeading from '../../../shared/ItemHeading';
const EducationTab = ({ data, onChange }) => {
const { t } = useTranslation();
const context = useContext(AppContext);
const { dispatch } = context;
@ -28,7 +27,7 @@ const EducationTab = ({ data, onChange }) => {
</div>
<div className="col-span-5">
<TextField
placeholder={t('heading.placeholder')}
placeholder="Heading"
value={data.education.heading}
onChange={v => onChange('data.education.heading', v)}
/>
@ -62,7 +61,7 @@ const Form = ({ item, onChange, identifier = '' }) => {
<TextField
className="mb-6"
label={t('education.name.label')}
placeholder={t('education.name.placeholder')}
placeholder="Harvard University"
value={item.name}
onChange={v => onChange(`${identifier}name`, v)}
/>
@ -70,7 +69,7 @@ const Form = ({ item, onChange, identifier = '' }) => {
<TextField
className="mb-6"
label={t('education.major.label')}
placeholder={t('education.major.placeholder')}
placeholder="Masters in Computer Science"
value={item.major}
onChange={v => onChange(`${identifier}major`, v)}
/>
@ -87,7 +86,7 @@ const Form = ({ item, onChange, identifier = '' }) => {
<TextField
className="mb-6"
label={t('app:item.startDate.label')}
placeholder={t('app:item.startDate.placeholder')}
placeholder="March 2018"
value={item.start}
onChange={v => onChange(`${identifier}start`, v)}
/>
@ -95,7 +94,7 @@ const Form = ({ item, onChange, identifier = '' }) => {
<TextField
className="mb-6"
label={t('app:item.endDate.label')}
placeholder={t('app:item.endDate.placeholder')}
placeholder="June 2022"
value={item.end}
onChange={v => onChange(`${identifier}end`, v)}
/>
@ -105,7 +104,6 @@ const Form = ({ item, onChange, identifier = '' }) => {
rows="5"
className="mb-6"
label={t('app:item.description.label')}
placeholder={t('education.description.placeholder')}
value={item.description}
onChange={v => onChange(`${identifier}description`, v)}
/>

View File

@ -12,7 +12,6 @@ import ItemHeading from '../../../shared/ItemHeading';
import AddItemButton from '../../../shared/AddItemButton';
const ExtrasTab = ({ data, onChange }) => {
const { t } = useTranslation();
const context = useContext(AppContext);
const { dispatch } = context;
@ -27,7 +26,7 @@ const ExtrasTab = ({ data, onChange }) => {
</div>
<div className="col-span-5">
<TextField
placeholder={t('heading.placeholder')}
placeholder="Heading"
value={data.extras.heading}
onChange={v => onChange('data.extras.heading', v)}
/>
@ -61,7 +60,7 @@ const Form = ({ item, onChange, identifier = '' }) => {
<TextField
className="mb-6"
label={t('extras.key.label')}
placeholder={t('extras.key.placeholder')}
placeholder="Date of Birth"
value={item.key}
onChange={v => onChange(`${identifier}key`, v)}
/>
@ -69,7 +68,7 @@ const Form = ({ item, onChange, identifier = '' }) => {
<TextField
className="mb-6"
label={t('extras.value.label')}
placeholder={t('extras.value.placeholder')}
placeholder="6th August 1995"
value={item.value}
onChange={v => onChange(`${identifier}value`, v)}
/>

View File

@ -13,7 +13,6 @@ import AddItemButton from '../../../shared/AddItemButton';
import ItemHeading from '../../../shared/ItemHeading';
const LanguagesTab = ({ data, onChange }) => {
const { t } = useTranslation('app');
const context = useContext(AppContext);
const { dispatch } = context;
@ -47,7 +46,7 @@ const LanguagesTab = ({ data, onChange }) => {
</div>
<div className="col-span-5">
<TextField
placeholder={t('heading.placeholder')}
placeholder="Heading"
value={data.languages.heading}
onChange={v => onChange('data.languages.heading', v)}
/>
@ -82,7 +81,7 @@ const Form = ({ item, onChange, identifier = '' }) => {
<TextField
className="mb-6"
label={t('languages.key.label')}
placeholder={t('languages.key.placeholder')}
placeholder="English"
value={item.key}
onChange={v => onChange(`${identifier}key`, v)}
/>

View File

@ -6,7 +6,7 @@ import TextField from '../../../shared/TextField';
import Checkbox from '../../../shared/Checkbox';
const ObjectiveTab = ({ data, onChange }) => {
const { t } = useTranslation(['leftSidebar', 'app']);
const { t } = useTranslation('leftSidebar');
return (
<div>
@ -19,7 +19,7 @@ const ObjectiveTab = ({ data, onChange }) => {
</div>
<div className="col-span-5">
<TextField
placeholder={t('app:heading.placeholder')}
placeholder="Heading"
value={data.objective.heading}
onChange={v => onChange('data.objective.heading', v)}
/>
@ -32,8 +32,8 @@ const ObjectiveTab = ({ data, onChange }) => {
rows="15"
className="mb-4"
label={t('objective.objective.label')}
placeholder={t('objective.objective.placeholder')}
value={data.objective.body}
placeholder="Looking for a challenging role in a reputable organization to utilize my technical, database, and management skills for the growth of the organization as well as to enhance my knowledge about new and emerging trends in the IT sector."
onChange={v => onChange('data.objective.body', v)}
/>
</div>

View File

@ -20,7 +20,7 @@ const ProfileTab = ({ data, onChange }) => {
<TextField
className="mb-6"
label={t('profile.firstName.label')}
placeholder={t('profile.firstName.placeholder')}
placeholder="Jane"
value={data.profile.firstName}
onChange={v => onChange('data.profile.firstName', v)}
/>
@ -28,7 +28,7 @@ const ProfileTab = ({ data, onChange }) => {
<TextField
className="mb-6"
label={t('profile.lastName.label')}
placeholder={t('profile.lastName.placeholder')}
placeholder="Doe"
value={data.profile.lastName}
onChange={v => onChange('data.profile.lastName', v)}
/>
@ -37,7 +37,7 @@ const ProfileTab = ({ data, onChange }) => {
<TextField
className="mb-6"
label={t('profile.subtitle.label')}
placeholder={t('profile.subtitle.placeholder')}
placeholder="Full-Stack Web Developer"
value={data.profile.subtitle}
onChange={v => onChange('data.profile.subtitle', v)}
/>
@ -47,7 +47,7 @@ const ProfileTab = ({ data, onChange }) => {
<TextField
className="mb-6"
label={t('profile.address.line1.label')}
placeholder={t('profile.address.line1.placeholder')}
placeholder="Palladium Complex"
value={data.profile.address.line1}
onChange={v => onChange('data.profile.address.line1', v)}
/>
@ -55,7 +55,7 @@ const ProfileTab = ({ data, onChange }) => {
<TextField
className="mb-6"
label={t('profile.address.line2.label')}
placeholder={t('profile.address.line2.placeholder')}
placeholder="140 E 14th St"
value={data.profile.address.line2}
onChange={v => onChange('data.profile.address.line2', v)}
/>
@ -63,7 +63,7 @@ const ProfileTab = ({ data, onChange }) => {
<TextField
className="mb-6"
label={t('profile.address.line3.label')}
placeholder={t('profile.address.line3.placeholder')}
placeholder="New York, NY 10003 USA"
value={data.profile.address.line3}
onChange={v => onChange('data.profile.address.line3', v)}
/>
@ -81,7 +81,7 @@ const ProfileTab = ({ data, onChange }) => {
<TextField
className="mb-6"
label={t('profile.website.label')}
placeholder="google.com"
placeholder="janedoe.me"
value={data.profile.website}
onChange={v => onChange('data.profile.website', v)}
/>
@ -89,7 +89,7 @@ const ProfileTab = ({ data, onChange }) => {
<TextField
className="mb-6"
label={t('profile.email.label')}
placeholder="john.doe@example.com"
placeholder="jane.doe@example.com"
value={data.profile.email}
onChange={v => onChange('data.profile.email', v)}
/>

View File

@ -13,7 +13,6 @@ import ItemHeading from '../../../shared/ItemHeading';
import AddItemButton from '../../../shared/AddItemButton';
const ReferencesTab = ({ data, onChange }) => {
const { t } = useTranslation();
const context = useContext(AppContext);
const { dispatch } = context;
@ -47,7 +46,7 @@ const ReferencesTab = ({ data, onChange }) => {
</div>
<div className="col-span-5">
<TextField
placeholder={t('heading.placeholder')}
placeholder="Heading"
value={data.references.heading}
onChange={v => onChange('data.references.heading', v)}
/>
@ -82,7 +81,7 @@ const Form = ({ item, onChange, identifier = '' }) => {
<TextField
className="mb-6"
label={t('references.name.label')}
placeholder={t('references.name.placeholder')}
placeholder="Richard Hendricks"
value={item.name}
onChange={v => onChange(`${identifier}name`, v)}
/>
@ -90,7 +89,7 @@ const Form = ({ item, onChange, identifier = '' }) => {
<TextField
className="mb-6"
label={t('references.position.label')}
placeholder={t('references.position.placeholder')}
placeholder="CEO, Pied Piper"
value={item.position}
onChange={v => onChange(`${identifier}position`, v)}
/>
@ -115,7 +114,6 @@ const Form = ({ item, onChange, identifier = '' }) => {
rows="5"
className="mb-6"
label={t('app:item.description.label')}
placeholder={t('references.description.placeholder')}
value={item.description}
onChange={v => onChange(`${identifier}description`, v)}
/>

View File

@ -1,5 +1,4 @@
import React, { useState, useContext } from 'react';
import { useTranslation } from 'react-i18next';
import AppContext from '../../../context/AppContext';
import Checkbox from '../../../shared/Checkbox';
@ -8,7 +7,6 @@ import { addItem, deleteItem } from '../../../utils';
import ItemHeading from '../../../shared/ItemHeading';
const SkillsTab = ({ data, onChange }) => {
const { t } = useTranslation();
const context = useContext(AppContext);
const { dispatch } = context;
@ -23,7 +21,7 @@ const SkillsTab = ({ data, onChange }) => {
</div>
<div className="col-span-5">
<TextField
placeholder={t('heading.placeholder')}
placeholder="Heading"
value={data.skills.heading}
onChange={v => onChange('data.skills.heading', v)}
/>
@ -42,12 +40,10 @@ const SkillsTab = ({ data, onChange }) => {
};
const Form = ({ item, onChange }) => {
const { t } = useTranslation('leftSidebar');
return (
<input
className="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
placeholder={t('skills.item.placeholder')}
placeholder="Team Building &amp; Training"
value={item}
onChange={e => onChange(e.target.value)}
type="text"

View File

@ -13,7 +13,6 @@ import AddItemButton from '../../../shared/AddItemButton';
import ItemHeading from '../../../shared/ItemHeading';
const WorkTab = ({ data, onChange }) => {
const { t } = useTranslation();
const context = useContext(AppContext);
const { dispatch } = context;
@ -25,7 +24,7 @@ const WorkTab = ({ data, onChange }) => {
</div>
<div className="col-span-5">
<TextField
placeholder={t('heading.placeholder')}
placeholder="Heading"
value={data.work.heading}
onChange={v => onChange('data.work.heading', v)}
/>
@ -59,7 +58,7 @@ const Form = ({ item, onChange, identifier = '' }) => {
<TextField
className="mb-6"
label={t('work.name.label')}
placeholder={t('work.name.placeholder')}
placeholder="Amazon"
value={item.title}
onChange={v => onChange(`${identifier}title`, v)}
/>
@ -67,7 +66,7 @@ const Form = ({ item, onChange, identifier = '' }) => {
<TextField
className="mb-6"
label={t('work.role.label')}
placeholder={t('work.role.placeholder')}
placeholder="Full-Stack Web Developer"
value={item.role}
onChange={v => onChange(`${identifier}role`, v)}
/>
@ -76,7 +75,7 @@ const Form = ({ item, onChange, identifier = '' }) => {
<TextField
className="mb-6"
label={t('app:item.startDate.label')}
placeholder={t('app:item.startDate.placeholder')}
placeholder="March 2018"
value={item.start}
onChange={v => onChange(`${identifier}start`, v)}
/>
@ -84,7 +83,7 @@ const Form = ({ item, onChange, identifier = '' }) => {
<TextField
className="mb-6"
label={t('app:item.endDate.label')}
placeholder={t('app:item.endDate.placeholder')}
placeholder="June 2022"
value={item.end}
onChange={v => onChange(`${identifier}end`, v)}
/>
@ -94,7 +93,6 @@ const Form = ({ item, onChange, identifier = '' }) => {
rows="5"
className="mb-6"
label={t('app:item.description.label')}
placeholder={t('work.description.placeholder')}
value={item.description}
onChange={v => onChange(`${identifier}description`, v)}
/>

View File

@ -43,7 +43,7 @@ const RightSidebar = () => {
name: t('about.title'),
},
];
const [currentTab, setCurrentTab] = useState(tabs[3].key);
const [currentTab, setCurrentTab] = useState(tabs[0].key);
const onChange = (key, value) => {
dispatch({

View File

@ -101,7 +101,7 @@ const AboutTab = () => {
<div className="mt-5">
<p className="text-xs font-gray-600 text-center">
<Trans t={t} i18nKey="about.footer.credit">
Reactive Resume is a project by
Made with Love by
<a
className="font-bold hover:underline"
href="https://www.amruthpillai.com/"
@ -110,7 +110,6 @@ const AboutTab = () => {
>
Amruth Pillai
</a>
.
</Trans>
</p>
<p className="text-xs font-gray-600 text-center">{t('about.footer.thanks')}</p>

View File

@ -2,60 +2,17 @@
/* eslint-disable jsx-a11y/anchor-has-content */
/* eslint-disable jsx-a11y/anchor-is-valid */
import React, { useRef, useContext } from 'react';
import { useTranslation, Trans } from 'react-i18next';
import html2canvas from 'html2canvas';
import * as jsPDF from 'jspdf';
import { useTranslation } from 'react-i18next';
import PageContext from '../../../context/PageContext';
import { importJson, saveAsPdf } from '../../../utils';
const ActionsTab = ({ data, theme, dispatch }) => {
const pageContext = useContext(PageContext);
const { pageElement } = pageContext;
const { pageRef, panZoomRef } = pageContext;
const { t } = useTranslation('rightSidebar');
const fileInputRef = useRef(null);
const importJson = event => {
const fr = new FileReader();
fr.addEventListener('load', () => {
const importedObject = JSON.parse(fr.result);
dispatch({ type: 'import_data', payload: importedObject });
dispatch({ type: 'save_data' });
});
fr.readAsText(event.target.files[0]);
};
const printAsPdf = () => {
pageElement.current.style.display = 'table';
pageElement.current.style.overflow = 'visible';
html2canvas(pageElement.current, {
scale: 5,
useCORS: true,
allowTaint: true,
}).then(canvas => {
const image = canvas.toDataURL('image/jpeg', 1.0);
const doc = new jsPDF('p', 'mm', 'a4');
const pageWidth = doc.internal.pageSize.getWidth();
const pageHeight = doc.internal.pageSize.getHeight();
const widthRatio = pageWidth / canvas.width;
const heightRatio = pageHeight / canvas.height;
const ratio = widthRatio > heightRatio ? heightRatio : widthRatio;
const canvasWidth = canvas.width * ratio;
const canvasHeight = canvas.height * ratio;
const marginX = (pageWidth - canvasWidth) / 2;
const marginY = (pageHeight - canvasHeight) / 2;
pageElement.current.style.display = 'block';
pageElement.current.style.overflow = 'scroll';
doc.addImage(image, 'JPEG', marginX, marginY, canvasWidth, canvasHeight, null, 'SLOW');
doc.save(`RxResume_${Date.now()}.pdf`);
});
};
const exportToJson = () => {
const backupObj = { data, theme };
const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(JSON.stringify(backupObj))}`;
@ -86,7 +43,12 @@ const ActionsTab = ({ data, theme, dispatch }) => {
<p className="text-sm">{t('actions.importExport.body')}</p>
<input ref={fileInputRef} type="file" className="hidden" onChange={importJson} />
<input
ref={fileInputRef}
type="file"
className="hidden"
onChange={e => importJson(e, dispatch)}
/>
<a id="downloadAnchor" className="hidden" />
<div className="mt-4 grid grid-cols-2 col-gap-6">
@ -117,39 +79,19 @@ const ActionsTab = ({ data, theme, dispatch }) => {
<hr className="my-6" />
<div className="shadow text-center p-5">
<h6 className="font-bold text-sm mb-2">{t('actions.printResume.heading')}</h6>
<h6 className="font-bold text-sm mb-2">{t('actions.downloadResume.heading')}</h6>
<div className="text-sm">{t('actions.downloadResume.body')}</div>
<div className="text-sm">
<Trans t={t} i18nKey="actions.printResume.body">
You can click on the button below to generate a PDF instantly. Alternatively, you can
also use <pre className="inline font-bold">Cmd/Ctrl + P</pre> but it would have
different effects.
</Trans>
</div>
<div className="mt-1 grid grid-cols-2 col-gap-6">
<button
type="button"
onClick={printAsPdf}
className="mt-4 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium py-2 px-5 rounded"
>
<div className="flex justify-center items-center">
<i className="material-icons mr-2 font-bold text-base">import_export</i>
<span className="text-sm">{t('actions.printResume.buttons.export')}</span>
</div>
</button>
<button
type="button"
onClick={printAsPdf}
className="mt-4 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium py-2 px-5 rounded"
>
<div className="flex justify-center items-center">
<i className="material-icons mr-2 font-bold text-base">print</i>
<span className="text-sm">{t('actions.printResume.buttons.print')}</span>
</div>
</button>
</div>
<button
type="button"
onClick={() => saveAsPdf(pageRef, panZoomRef)}
className="mt-4 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium py-2 px-5 rounded"
>
<div className="flex justify-center items-center">
<i className="material-icons mr-2 font-bold text-base">save</i>
<span className="text-sm">{t('actions.downloadResume.buttons.saveAsPdf')}</span>
</div>
</button>
</div>
<hr className="my-6" />

View File

@ -4,12 +4,16 @@ const PageContext = React.createContext(null);
const { Provider } = PageContext;
const StateProvider = ({ children }) => {
const [pageElement, setPageElement] = useState(null);
const [panZoomRef, setPanZoomRef] = useState(null);
const [pageRef, setPageRef] = useState(null);
return (
<Provider
value={{
pageElement,
setPageElement,
pageRef,
setPageRef,
panZoomRef,
setPanZoomRef,
}}
>
{children}

View File

@ -1,16 +1,11 @@
{
"heading": {
"placeholder": "Heading"
},
"item": {
"add": "Add {{- heading}}",
"startDate": {
"label": "Start Date",
"placeholder": "March 2018"
"label": "Start Date"
},
"endDate": {
"label": "End Date",
"placeholder": "March 2022"
"label": "End Date"
},
"description": {
"label": "Description"

View File

@ -3,7 +3,9 @@ import leftSidebar from './leftSidebar';
import rightSidebar from './rightSidebar';
export default {
app,
leftSidebar,
rightSidebar,
en: {
app,
leftSidebar,
rightSidebar,
},
};

View File

@ -1,13 +1,8 @@
{
"title": {
"label": "Title",
"placeholder": "Math & Science Olympiad"
"label": "Title"
},
"subtitle": {
"label": "Subtitle",
"placeholder": "First Place, International Level"
},
"description": {
"placeholder": "You can write about what qualities made you succeed in getting this award."
"label": "Subtitle"
}
}

View File

@ -1,13 +1,8 @@
{
"title": {
"label": "Title",
"placeholder": "Android Development Nanodegree"
"label": "Name"
},
"subtitle": {
"label": "Subtitle",
"placeholder": "Udacity"
},
"description": {
"placeholder": "You can write about what you learned from your certification program."
"label": "Authority"
}
}

View File

@ -1,16 +1,11 @@
{
"name": {
"label": "Name",
"placeholder": "Harvard University"
"label": "Name"
},
"major": {
"label": "Major",
"placeholder": "Masters in Computer Science"
"label": "Major"
},
"grade": {
"label": "Grade"
},
"description": {
"placeholder": "You can write about projects or special credit classes that you took while studying at this school."
}
}

View File

@ -1,10 +1,8 @@
{
"key": {
"label": "Key",
"placeholder": "Date of Birth"
"label": "Key"
},
"value": {
"label": "Value",
"placeholder": "6th August 1995"
"label": "Value"
}
}

View File

@ -4,7 +4,6 @@ import work from './work.json';
import education from './education.json';
import awards from './awards.json';
import certifications from './certifications.json';
import skills from './skills.json';
import languages from './languages.json';
import references from './references.json';
import extras from './extras.json';
@ -16,7 +15,6 @@ export default {
education,
awards,
certifications,
skills,
languages,
references,
extras,

View File

@ -1,7 +1,6 @@
{
"key": {
"label": "Key",
"placeholder": "Dothraki"
"label": "Name"
},
"rating": {
"label": "Rating"

View File

@ -1,6 +1,5 @@
{
"objective": {
"label": "Objective",
"placeholder": "Looking for a challenging role in a reputable organization to utilize my technical, database, and management skills for the growth of the organization as well as to enhance my knowledge about new and emerging trends in the IT sector."
"label": "Objective"
}
}

View File

@ -3,29 +3,24 @@
"label": "Photo URL"
},
"firstName": {
"label": "First Name",
"placeholder": "Jane"
"label": "First Name"
},
"lastName": {
"label": "Last Name",
"placeholder": "Doe"
"label": "Last Name"
},
"subtitle": {
"label": "Subtitle",
"placeholder": "Full Stack Web Developer"
"label": "Subtitle"
},
"address": {
"label": "Address",
"line1": {
"label": "Address Line 1",
"placeholder": "Palladium Complex"
"label": "Address Line 1"
},
"line2": {
"label": "Address Line 2",
"placeholder": "140 E 14th St"
"label": "Address Line 2"
},
"line3": {
"label": "Address Line 3",
"placeholder": "New York, NY 10003 USA"
"label": "Address Line 3"
}
},
"phone": {

View File

@ -1,19 +1,14 @@
{
"name": {
"label": "Name",
"placeholder": "Richard Hendricks"
"label": "Name"
},
"position": {
"label": "Position",
"placeholder": "CEO, Pied Piper"
"label": "Position"
},
"phone": {
"label": "Phone Number"
},
"email": {
"label": "Email Address"
},
"description": {
"placeholder": "You can write about how you and the reference contact worked together and which projects you were a part of."
}
}

View File

@ -1,5 +0,0 @@
{
"item": {
"placeholder": "Cooking"
}
}

View File

@ -1,13 +1,8 @@
{
"name": {
"label": "Name",
"placeholder": "Amazon"
"label": "Name"
},
"role": {
"label": "Role",
"placeholder": "Front-end Web Developer"
},
"description": {
"placeholder": "You can write about what you specialized in while working at the company and what projects you were a part of."
"label": "Role"
}
}

View File

@ -2,7 +2,7 @@
"title": "About",
"documentation": {
"heading": "Documentation",
"body": "Want to know more about the app? Wouldn't it be nice if there was a guide to setting it up on your local machine? Need information on how to contribute to the project? Look no further, there's comprehensive documentation made just for you.",
"body": "Want to know more about the app? Need information on how to contribute to the project? Look no further, there's comprehensive guide made just for you.",
"buttons": {
"documentation": "Documentation"
}
@ -30,7 +30,7 @@
}
},
"footer": {
"credit": "Reactive Resume is a project by <1>Amruth Pillai</1>.",
"credit": "Made with Love by <1>Amruth Pillai</1>",
"thanks": "Thank you for using Reactive Resume!"
}
}

View File

@ -9,12 +9,11 @@
"export": "Export"
}
},
"printResume": {
"heading": "Print Your Resume",
"body": "You can click on the button below to generate a PDF instantly. Alternatively, you can also use <1>Cmd/Ctrl + P</1> but it would have different effects.",
"downloadResume": {
"heading": "Download Your Resume",
"body": "You can click on the button below to download a PDF version of your resume instantly. For best results, please use the latest version of Google Chrome.",
"buttons": {
"export": "Export",
"print": "Print"
"saveAsPdf": "Save as PDF"
}
},
"loadDemoData": {

View File

@ -2,6 +2,6 @@
"title": "Colors",
"colorOptions": "Color Options",
"primaryColor": "Primary Color",
"accentColor": "Accent Color",
"accentColor": "Secondary Color",
"clipboardCopyAction": "{{color}} has been copied to the clipboard."
}

View File

@ -8,6 +8,15 @@
@import url('https://fonts.googleapis.com/css2?family=Titillium+Web:wght@400;600;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Ubuntu:wght@400;500;700&display=swap');
* {
-ms-overflow-style: none;
scrollbar-width: none;
}
*::-webkit-scrollbar {
display: none;
}
html,
body {
height: 100%;
@ -54,25 +63,21 @@ ul li {
#tabs {
scroll-behavior: smooth;
scrollbar-width: none;
-ms-overflow-style: none;
}
#tabs::-webkit-scrollbar {
width: 0px;
height: 0px;
background: transparent;
}
#page {
width: 21cm;
min-height: 29.7cm;
max-height: 29.7cm;
transform: scale(0.8);
transform-origin: center;
overflow: scroll;
background-color: white;
}
#pageController {
bottom: 25px;
}
#pageController > div {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
}
}
@page {

View File

@ -0,0 +1,58 @@
import React, { useContext } from 'react';
import PageContext from '../context/PageContext';
import { saveAsPdf } from '../utils';
const PageController = () => {
const pageContext = useContext(PageContext);
const { pageRef, panZoomRef } = pageContext;
const zoomIn = () => panZoomRef.current.zoomIn(2);
const zoomOut = () => panZoomRef.current.zoomOut(2);
const centerReset = () => {
panZoomRef.current.autoCenter(1);
panZoomRef.current.reset(1);
};
return (
<div
id="pageController"
className="absolute z-20 opacity-75 hover:opacity-100 transition-all duration-150"
>
<div className="px-8 border border-gray-200 rounded-full bg-white flex justify-center items-center select-none">
<div className="p-3 hover:bg-gray-200 cursor-pointer flex" onClick={zoomIn}>
<i className="material-icons">zoom_in</i>
</div>
<div className="p-3 hover:bg-gray-200 cursor-pointer flex" onClick={zoomOut}>
<i className="material-icons">zoom_out</i>
</div>
<div className="p-3 hover:bg-gray-200 cursor-pointer flex" onClick={centerReset}>
<i className="material-icons">center_focus_strong</i>
</div>
<div className="text-gray-400 p-3">|</div>
<div
className="p-3 hover:bg-gray-200 cursor-pointer flex"
onClick={() => saveAsPdf(pageRef, panZoomRef)}
>
<i className="material-icons">save</i>
</div>
<div className="text-gray-400 p-3">|</div>
<a
className="p-3 hover:bg-gray-200 cursor-pointer flex"
href="https://docs.rxresu.me/"
target="_blank"
rel="noopener noreferrer"
>
<i className="material-icons">help_outline</i>
</a>
</div>
</div>
);
};
export default PageController;

View File

@ -0,0 +1,298 @@
import React, { useContext } from 'react';
import ReactMarkdown from 'react-markdown';
import AppContext from '../../context/AppContext';
import { hexToRgb } from '../../utils';
const Glalie = () => {
const context = useContext(AppContext);
const { state } = context;
const { data, theme } = state;
const { r, g, b } = hexToRgb(theme.colors.accent) || {};
const Photo = () =>
data.profile.photo !== '' && (
<img
className="w-40 h-40 rounded-full mx-auto"
src={data.profile.photo}
alt="Resume Photograph"
/>
);
const FullName = () => (
<div className="text-4xl font-bold leading-none">
<h1>{data.profile.firstName}</h1>
<h1>{data.profile.lastName}</h1>
</div>
);
const Subtitle = () => (
<div className="tracking-wide text-xs uppercase font-medium">{data.profile.subtitle}</div>
);
const Divider = () => (
<div className="w-1/2 mx-auto my-2 border-b-2" style={{ borderColor: theme.colors.accent }} />
);
const ContactItem = ({ title, value }) =>
value && (
<div className="flex flex-col">
<h6 className="text-xs font-bold" style={{ color: theme.colors.accent }}>
{title}
</h6>
<p className="text-sm">{value}</p>
</div>
);
const ContactInformation = () => (
<div
className="w-full border-2 pl-4 pr-4 pb-6"
style={{
borderColor: theme.colors.accent,
}}
>
<div
className="inline-block relative px-4"
style={{ top: '-.9em', color: theme.colors.accent }}
>
<h2 className="flex">
<i className="material-icons">flare</i>
</h2>
</div>
<div className="grid grid-cols-1 row-gap-4">
<ContactItem title="Phone Number" value={data.profile.phone} />
<ContactItem title="Email Address" value={data.profile.email} />
<ContactItem title="Website" value={data.profile.website} />
<div className="flex flex-col">
<i className="material-icons text-lg" style={{ color: theme.colors.accent }}>
home
</i>
<p className="text-sm">{data.profile.address.line1}</p>
<p className="text-sm">{data.profile.address.line2}</p>
<p className="text-sm">{data.profile.address.line3}</p>
</div>
</div>
</div>
);
const Heading = ({ title }) => (
<h6
className="text-sm font-semibold uppercase pb-1 mb-2 border-b"
style={{ borderColor: theme.colors.accent, color: theme.colors.accent }}
>
{title}
</h6>
);
const Objective = () =>
data.objective.enable && (
<div>
<Heading title={data.objective.heading} />
<p className="text-sm text-justify">{data.objective.body}</p>
</div>
);
const WorkItem = x => (
<div key={x.title} className="mt-3">
<div className="flex justify-between">
<div>
<h6 className="font-semibold text-sm">{x.title}</h6>
<p className="text-xs opacity-75 font-medium">
{x.role} / {x.start} - {x.end}
</p>
</div>
</div>
<ReactMarkdown className="mt-2 text-sm" source={x.description} />
</div>
);
const Work = () =>
data.work &&
data.work.enable && (
<div>
<Heading title={data.work.heading} />
{data.work.items.filter(x => x.enable).map(WorkItem)}
</div>
);
const EducationItem = x => (
<div key={x.name} className="mt-3">
<div>
<h6 className="font-semibold text-xs">{x.name}</h6>
<p className="text-xs opacity-75">{x.major}</p>
<p className="text-xs opacity-75">
{x.start} - {x.end}
</p>
</div>
<ReactMarkdown className="mt-2 text-sm" source={x.description} />
</div>
);
const Education = () =>
data.education &&
data.education.enable && (
<div>
<Heading title={data.education.heading} />
<div className="grid grid-cols-2 gap-4">
{data.education.items.filter(x => x.enable).map(EducationItem)}
</div>
</div>
);
const AwardItem = x => (
<div key={x.title} className="mt-3 text-left">
<h6 className="font-semibold">{x.title}</h6>
<p className="text-xs">{x.subtitle}</p>
<ReactMarkdown className="mt-2 text-sm" source={x.description} />
</div>
);
const Awards = () =>
data.awards &&
data.awards.enable && (
<div>
<Heading title={data.awards.heading} />
{data.awards.items.filter(x => x.enable).map(AwardItem)}
</div>
);
const CertificationItem = x => (
<div key={x.title} className="mt-3 text-left">
<h6 className="font-semibold">{x.title}</h6>
<p className="text-xs">{x.subtitle}</p>
<ReactMarkdown className="mt-2 text-sm" source={x.description} />
</div>
);
const Certifications = () =>
data.certifications &&
data.certifications.enable && (
<div>
<Heading title={data.certifications.heading} />
{data.certifications.items.filter(x => x.enable).map(CertificationItem)}
</div>
);
const SkillItem = x => (
<li key={x} className="text-xs font-medium">
{x}
</li>
);
const Skills = () =>
data.skills &&
data.skills.enable && (
<div>
<Heading title={data.skills.heading} />
<ul className="pt-2 grid grid-cols-2 gap-3">{data.skills.items.map(SkillItem)}</ul>
</div>
);
const LanguageItem = x => (
<div key={x.id} className="grid grid-cols-2 items-center py-2">
<h6 className="text-xs font-medium text-left">{x.key}</h6>
<div className="flex">
{Array.from(Array(x.value)).map((_, i) => (
<i key={i} className="material-icons text-lg" style={{ color: theme.colors.accent }}>
star
</i>
))}
</div>
</div>
);
const Languages = () =>
data.languages &&
data.languages.enable && (
<div>
<Heading title={data.languages.heading} />
<div className="w-3/4">{data.languages.items.filter(x => x.enable).map(LanguageItem)}</div>
</div>
);
const ReferenceItem = x => (
<div key={x.id} className="flex flex-col">
<h6 className="text-sm font-medium">{x.name}</h6>
<span className="text-xs">{x.position}</span>
<span className="text-xs">{x.phone}</span>
<span className="text-xs">{x.email}</span>
<ReactMarkdown className="mt-2 text-sm" source={x.description} />
</div>
);
const References = () =>
data.references &&
data.references.enable && (
<div>
<Heading title={data.references.heading} />
<div className="grid grid-cols-3 gap-8">
{data.references.items.filter(x => x.enable).map(ReferenceItem)}
</div>
</div>
);
const ExtraItem = x => (
<tr key={x.id}>
<td className="border font-medium px-4 py-2 text-xs">{x.key}</td>
<td className="border px-4 py-2 text-xs">{x.value}</td>
</tr>
);
const Extras = () =>
data.extras &&
data.extras.enable && (
<div>
<Heading title={data.extras.heading} />
<table className="mt-4 w-2/3 table-auto">
<tbody>{data.extras.items.filter(x => x.enable).map(ExtraItem)}</tbody>
</table>
</div>
);
return (
<div
style={{
fontFamily: theme.font.family,
backgroundColor: theme.colors.background,
color: theme.colors.primary,
}}
>
<div className="grid grid-cols-12">
<div
className="h-full col-span-4 p-8 grid grid-cols-1 row-gap-8 text-center"
style={{ backgroundColor: `rgba(${r}, ${g}, ${b}, 0.1)` }}
>
<div className="grid grid-cols-1 row-gap-4">
<Photo />
<FullName />
<Subtitle />
</div>
<Divider />
<ContactInformation />
<Divider />
<Objective />
<Languages />
<Certifications />
</div>
<div className="col-span-8 p-8">
<div className="grid grid-cols-1 row-gap-6">
<Work />
<Education />
<Skills />
<Awards />
<References />
<Extras />
</div>
</div>
</div>
</div>
);
};
export default Glalie;

View File

@ -0,0 +1,5 @@
import Glalie from './Glalie';
import image from './preview.png';
export const Image = image;
export default Glalie;

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

View File

@ -2,6 +2,7 @@ import Onyx, { Image as OnyxPreview } from './onyx';
import Pikachu, { Image as PikachuPreview } from './pikachu';
import Gengar, { Image as GengarPreview } from './gengar';
import Castform, { Image as CastformPreview } from './castform';
import Glalie, { Image as GlaliePreview } from './glalie';
export default [
{
@ -28,4 +29,10 @@ export default [
component: Castform,
preview: CastformPreview,
},
{
key: 'glalie',
name: 'Glalie',
component: Glalie,
preview: GlaliePreview,
},
];

View File

@ -1,3 +1,7 @@
/* eslint-disable new-cap */
import html2canvas from 'html2canvas';
import * as jsPDF from 'jspdf';
const move = (array, element, delta) => {
const index = array.indexOf(element);
const newIndex = index + delta;
@ -90,4 +94,58 @@ const moveItemDown = (dispatch, key, value) => {
saveData(dispatch);
};
export { move, hexToRgb, copyToClipboard, saveData, addItem, deleteItem, moveItemUp, moveItemDown };
const importJson = (event, dispatch) => {
const fr = new FileReader();
fr.addEventListener('load', () => {
const importedObject = JSON.parse(fr.result);
dispatch({ type: 'import_data', payload: importedObject });
dispatch({ type: 'save_data' });
});
fr.readAsText(event.target.files[0]);
};
const saveAsPdf = (pageRef, panZoomRef) => {
panZoomRef.current.autoCenter(1);
panZoomRef.current.reset();
setTimeout(() => {
html2canvas(pageRef.current, {
scale: 5,
useCORS: true,
allowTaint: true,
}).then(canvas => {
const image = canvas.toDataURL('image/jpeg', 1.0);
const doc = new jsPDF('p', 'mm', 'a4');
const pageWidth = doc.internal.pageSize.getWidth();
const pageHeight = doc.internal.pageSize.getHeight();
const widthRatio = pageWidth / canvas.width;
const heightRatio = pageHeight / canvas.height;
const ratio = widthRatio > heightRatio ? heightRatio : widthRatio;
const canvasWidth = canvas.width * ratio;
const canvasHeight = canvas.height * ratio;
const marginX = (pageWidth - canvasWidth) / 2;
const marginY = (pageHeight - canvasHeight) / 2;
panZoomRef.current.autoCenter(0.7);
doc.addImage(image, 'JPEG', marginX, marginY, canvasWidth, canvasHeight, null, 'SLOW');
doc.save(`RxResume_${Date.now()}.pdf`);
});
}, 250);
};
export {
move,
hexToRgb,
copyToClipboard,
saveData,
addItem,
deleteItem,
moveItemUp,
moveItemDown,
importJson,
saveAsPdf,
};