- implement actions section

- implement settings section
This commit is contained in:
Amruth Pillai
2020-07-12 12:55:08 +05:30
parent 8972a96afd
commit f468ca73c3
25 changed files with 327 additions and 143 deletions

View File

@ -1,8 +1,10 @@
.container {
width: 210mm;
height: 297mm;
zoom: 0.8;
overflow: scroll;
box-shadow: var(--shadow);
@apply bg-white rounded;
}
@media screen {
.container {
width: 210mm;
height: 297mm;
zoom: 0.8;
overflow: scroll;
box-shadow: var(--shadow);
@apply bg-white rounded;
}
}

View File

@ -8,6 +8,7 @@ import Fonts from './sections/Fonts';
import Layout from './sections/Layout';
import Templates from './sections/Templates';
import Actions from './sections/Actions';
import Settings from './sections/Settings';
const getComponent = (id) => {
switch (id) {
@ -21,6 +22,8 @@ const getComponent = (id) => {
return Fonts;
case 'actions':
return Actions;
case 'settings':
return Settings;
default:
throw new Error();
}

View File

@ -1,9 +1,7 @@
import { clone } from 'lodash';
import React, { memo, useContext, useState } from 'react';
import { MdImportExport } from 'react-icons/md';
import { FaFileExport, FaFileImport } from 'react-icons/fa';
import ModalContext from '../../../../contexts/ModalContext';
import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
import UserContext from '../../../../contexts/UserContext';
import Button from '../../../shared/Button';
import Heading from '../../../shared/Heading';
import Input from '../../../shared/Input';
@ -12,33 +10,14 @@ import styles from './Actions.module.css';
const Actions = () => {
const [loadDemoText, setLoadDemoText] = useState('Load Demo Data');
const [resetText, setResetText] = useState('Reset Everything');
const [deleteText, setDeleteText] = useState('Delete Account');
const state = useSelector();
const dispatch = useDispatch();
const { emitter, events } = useContext(ModalContext);
const { deleteAccount } = useContext(UserContext);
const handleImport = () => emitter.emit(events.IMPORT_MODAL);
const handleExportToJson = () => {
const backupObj = clone(state);
delete backupObj.id;
delete backupObj.user;
delete backupObj.name;
delete backupObj.createdAt;
delete backupObj.updatedAt;
const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(
JSON.stringify(backupObj),
)}`;
const dlAnchor = document.getElementById('downloadAnchor');
dlAnchor.setAttribute('href', dataStr);
dlAnchor.setAttribute(
'download',
`RxResume_${state.id}_${Date.now()}.json`,
);
dlAnchor.click();
};
const handleExport = () => emitter.emit(events.EXPORT_MODAL);
const getSharableUrl = () => {
const shareId = state.id;
@ -71,18 +50,6 @@ const Actions = () => {
dispatch({ type: 'reset_data' });
};
const handleDeleteAccount = () => {
if (deleteText === 'Delete Account') {
setDeleteText('Are you sure?');
return;
}
setDeleteText('Buh bye! :(');
setTimeout(() => {
deleteAccount();
}, 500);
};
return (
<section>
<Heading>Actions</Heading>
@ -96,7 +63,7 @@ const Actions = () => {
</p>
<div className="mt-4 flex">
<Button icon={MdImportExport} onClick={handleImport}>
<Button icon={FaFileImport} onClick={handleImport}>
Import
</Button>
</div>
@ -111,15 +78,10 @@ const Actions = () => {
</p>
<div className="mt-4 flex">
<Button>Save as PDF</Button>
<Button outline className="ml-6" onClick={handleExportToJson}>
Export as JSON
<Button icon={FaFileExport} onClick={handleExport}>
Export
</Button>
</div>
<a id="downloadAnchor" className="hidden">
Download Exported JSON
</a>
</div>
<div className={styles.container}>
@ -164,22 +126,6 @@ const Actions = () => {
<Button onClick={handleReset}>{resetText}</Button>
</div>
</div>
<div className={styles.container}>
<h5>Danger Zone</h5>
<p className="leading-loose">
If you would like to delete your account and erase all your resumes,
its just one button away. Please be weary as this is an irreversible
process.
</p>
<div className="mt-4 flex">
<Button isDelete onClick={handleDeleteAccount}>
{deleteText}
</Button>
</div>
</div>
</section>
);
};

View File

@ -0,0 +1,62 @@
import React, { memo, useContext, useState } from 'react';
import UserContext from '../../../../contexts/UserContext';
import Button from '../../../shared/Button';
import Heading from '../../../shared/Heading';
import styles from './Settings.module.css';
import Input from '../../../shared/Input';
import ThemeContext from '../../../../contexts/ThemeContext';
import themeConfig from '../../../../data/themeConfig';
const Settings = () => {
const [deleteText, setDeleteText] = useState('Delete Account');
const { deleteAccount } = useContext(UserContext);
const { theme, setTheme } = useContext(ThemeContext);
const handleChangeTheme = (e) => {
setTheme(e.target.value);
};
const handleDeleteAccount = () => {
if (deleteText === 'Delete Account') {
setDeleteText('Are you sure?');
return;
}
setDeleteText('Buh bye! :(');
setTimeout(() => {
deleteAccount();
}, 500);
};
return (
<section>
<Heading>Settings</Heading>
<Input
label="Theme"
type="dropdown"
options={Object.keys(themeConfig)}
value={theme}
onChange={handleChangeTheme}
/>
<div className={styles.container}>
<h5>Danger Zone</h5>
<p className="leading-loose">
If you would like to delete your account and erase all your resumes,
its just one button away. Please be weary as this is an irreversible
process.
</p>
<div className="mt-4 flex">
<Button isDelete onClick={handleDeleteAccount}>
{deleteText}
</Button>
</div>
</div>
</section>
);
};
export default memo(Settings);

View File

@ -0,0 +1,11 @@
.container {
@apply bg-primary-100 rounded grid gap-5 p-8;
}
.container h5 {
@apply text-lg font-semibold;
}
.container p {
@apply text-sm font-medium;
}

View File

@ -1,19 +1,16 @@
import cx from 'classnames';
import React, { memo, useContext } from 'react';
import React, { memo } from 'react';
import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
import templateOptions from '../../../../data/templateOptions';
import { handleKeyUp } from '../../../../utils';
import Heading from '../../../shared/Heading';
import styles from './Templates.module.css';
import ThemeContext from '../../../../contexts/ThemeContext';
const Templates = () => {
const dispatch = useDispatch();
const template = useSelector('metadata.template');
const { toggleDarkMode } = useContext(ThemeContext);
const handleClick = (value) => {
toggleDarkMode();
dispatch({
type: 'on_input',
payload: {

View File

@ -1,8 +1,6 @@
import { navigate } from 'gatsby';
import React, { memo, useContext } from 'react';
import { FaGithub } from 'react-icons/fa';
import ModalContext from '../../contexts/ModalContext';
import ThemeContext from '../../contexts/ThemeContext';
import UserContext from '../../contexts/UserContext';
import Button from '../shared/Button';
import Logo from '../shared/Logo';
@ -10,7 +8,6 @@ import Logo from '../shared/Logo';
const Hero = () => {
const { emitter, events } = useContext(ModalContext);
const { user, loading } = useContext(UserContext);
const { toggleDarkMode } = useContext(ThemeContext);
const handleLogin = () => emitter.emit(events.AUTH_MODAL);
@ -36,14 +33,6 @@ const Hero = () => {
Login
</Button>
)}
<Button
outline
className="ml-8"
icon={FaGithub}
onClick={toggleDarkMode}
>
GitHub
</Button>
</div>
</div>
</div>

View File

@ -12,6 +12,7 @@ const ModalEvents = {
LANGUAGE_MODAL: 'language_modal',
REFERENCE_MODAL: 'reference_modal',
IMPORT_MODAL: 'import_modal',
EXPORT_MODAL: 'export_modal',
};
export default ModalEvents;

View File

@ -1,64 +1,38 @@
import React, { createContext, memo, useEffect, useState } from 'react';
const COLOR_CONFIG = {
light: {
'--color-primary-50': '#FFFFFF',
'--color-primary-100': '#FAFAFA',
'--color-primary-200': '#F1F0F0',
'--color-primary-300': '#D8D2CD',
'--color-primary-400': '#CDC4BA',
'--color-primary-500': '#ABA59D',
'--color-primary-600': '#8A8680',
'--color-primary-700': '#686663',
'--color-primary-800': '#484745',
'--color-primary-900': '#282727',
},
dark: {
'--color-primary-50': '#212121',
'--color-primary-100': '#2c2c2c',
'--color-primary-200': '#424242',
'--color-primary-300': '#616161',
'--color-primary-400': '#757575',
'--color-primary-500': '#9e9e9e',
'--color-primary-600': '#bdbdbd',
'--color-primary-700': '#e0e0e0',
'--color-primary-800': '#eeeeee',
'--color-primary-900': '#f5f5f5',
},
};
import themeConfig from '../data/themeConfig';
const defaultState = {
darkMode: true,
toggleDarkMode: () => {},
theme: 'Dark',
setTheme: () => {},
};
const ThemeContext = createContext(defaultState);
const ThemeProvider = ({ children }) => {
const [darkMode, setDarkMode] = useState(defaultState.darkMode);
const [theme, setThemeX] = useState(defaultState.theme);
useEffect(() => {
const isDarkMode = JSON.parse(localStorage.getItem('darkMode'));
isDarkMode ? setDarkMode(true) : setDarkMode(false);
const prefTheme = localStorage.getItem('theme') || defaultState.theme;
setThemeX(prefTheme);
}, []);
useEffect(() => {
const colorConfig = darkMode ? COLOR_CONFIG.dark : COLOR_CONFIG.light;
const colorConfig = themeConfig[theme];
for (const [key, value] of Object.entries(colorConfig)) {
document.documentElement.style.setProperty(key, value);
}
}, [darkMode]);
}, [theme]);
const toggleDarkMode = () => {
setDarkMode(!darkMode);
localStorage.setItem('darkMode', JSON.stringify(!darkMode));
const setTheme = (themeRef) => {
setThemeX(themeRef);
localStorage.setItem('theme', themeRef);
};
return (
<ThemeContext.Provider
value={{
darkMode,
toggleDarkMode,
theme,
setTheme,
}}
>
{children}

View File

@ -3,6 +3,7 @@ import {
MdDashboard,
MdFontDownload,
MdImportExport,
MdSettings,
MdStyle,
} from 'react-icons/md';
@ -32,4 +33,9 @@ export default [
name: 'Actions',
icon: MdImportExport,
},
{
id: 'settings',
name: 'Settings',
icon: MdSettings,
},
];

40
src/data/themeConfig.js Normal file
View File

@ -0,0 +1,40 @@
const themeConfig = {
Light: {
'--color-primary-50': '#FFFFFF',
'--color-primary-100': '#FAFAFA',
'--color-primary-200': '#F1F0F0',
'--color-primary-300': '#D8D2CD',
'--color-primary-400': '#CDC4BA',
'--color-primary-500': '#ABA59D',
'--color-primary-600': '#8A8680',
'--color-primary-700': '#686663',
'--color-primary-800': '#484745',
'--color-primary-900': '#282727',
},
Dark: {
'--color-primary-50': '#212121',
'--color-primary-100': '#2c2c2c',
'--color-primary-200': '#424242',
'--color-primary-300': '#616161',
'--color-primary-400': '#757575',
'--color-primary-500': '#9e9e9e',
'--color-primary-600': '#bdbdbd',
'--color-primary-700': '#e0e0e0',
'--color-primary-800': '#eeeeee',
'--color-primary-900': '#f5f5f5',
},
AMOLED: {
'--color-primary-50': '#010101',
'--color-primary-100': '#121212',
'--color-primary-200': '#222222',
'--color-primary-300': '#333333',
'--color-primary-400': '#444',
'--color-primary-500': '#696969',
'--color-primary-600': '#8F8F8F',
'--color-primary-700': '#B4B4B4',
'--color-primary-800': '#DADADA',
'--color-primary-900': '#FFFFFF',
},
};
export default themeConfig;

View File

@ -4,6 +4,7 @@ import ResumeModal from './ResumeModal';
import AwardModal from './sections/AwardModal';
import CertificateModal from './sections/CertificateModal';
import EducationModal from './sections/EducationModal';
import ExportModal from './sections/ExportModal';
import HobbyModal from './sections/HobbyModal';
import ImportModal from './sections/ImportModal';
import LanguageModal from './sections/LanguageModal';
@ -29,6 +30,7 @@ const ModalRegistrar = () => {
<LanguageModal />
<ReferenceModal />
<ImportModal />
<ExportModal />
</>
);
};

View File

@ -0,0 +1,106 @@
import { clone } from 'lodash';
import React, { memo, useContext, useEffect, useState } from 'react';
import Button from '../../components/shared/Button';
import ModalContext from '../../contexts/ModalContext';
import { useSelector } from '../../contexts/ResumeContext';
import BaseModal from '../BaseModal';
const ExportModal = () => {
const state = useSelector();
const [open, setOpen] = useState(false);
const { emitter, events } = useContext(ModalContext);
useEffect(() => {
const unbind = emitter.on(events.EXPORT_MODAL, () => setOpen(true));
return () => unbind();
}, [emitter, events]);
const handleOpenPrintDialog = () => {
if (typeof window !== `undefined`) {
window && window.print();
}
};
const handleExportToJson = () => {
const backupObj = clone(state);
delete backupObj.id;
delete backupObj.user;
delete backupObj.name;
delete backupObj.createdAt;
delete backupObj.updatedAt;
const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(
JSON.stringify(backupObj),
)}`;
const dlAnchor = document.getElementById('downloadAnchor');
dlAnchor.setAttribute('href', dataStr);
dlAnchor.setAttribute(
'download',
`RxResume_${state.id}_${Date.now()}.json`,
);
dlAnchor.click();
};
return (
<BaseModal hideActions state={[open, setOpen]} title="Export Your Resume">
<div>
<h5 className="text-xl font-semibold mb-4">
Use Browser&apos;s Print Dialog
</h5>
<p className="leading-loose">
For those of you who want a quick solution, you need not look any
further than your browser. All you have to do is press Ctrl/Cmd + P
and open up the print dialog on your browser and get your resume
printed immediately.
</p>
<Button className="mt-5" onClick={handleOpenPrintDialog}>
Open Print Dialog
</Button>
</div>
<hr className="my-8" />
<div>
<h5 className="text-xl font-semibold mb-4">Download PDF</h5>
<p className="leading-loose">
These options allow you to print a single page, unconstrained version
of your resume, perfect for those who have a lot of content.
Alternatively, you could download a multi-page version of your resume
as well with just one click.
</p>
<div className="mt-5">
<div className="flex">
<Button>Single Page Resume</Button>
<Button className="ml-8">Multi Page Resume</Button>
</div>
</div>
</div>
<hr className="my-8" />
<div>
<h5 className="text-xl font-semibold mb-4">Export to JSON Format</h5>
<p className="leading-loose">
You can also export your data into JSON format for safe keeping so
that you can easily import it back into Reactive Resume whenever you
want to edit or generate a resume.
</p>
<div className="mt-5">
<Button onClick={handleExportToJson}>Export JSON</Button>
<a id="downloadAnchor" className="hidden">
Export JSON
</a>
</div>
</div>
</BaseModal>
);
};
export default memo(ExportModal);

View File

@ -29,7 +29,7 @@ const ImportModal = () => {
};
return (
<BaseModal hideActions state={[open, setOpen]} title="Import Data">
<BaseModal hideActions state={[open, setOpen]} title="Import Your Resume">
<div>
<h5 className="text-xl font-semibold mb-4">
Import from Reactive Resume

View File

@ -40,6 +40,7 @@ const ResumeViewer = ({ id }) => {
<title>{resume.name} | Reactive Resume</title>
<link rel="canonical" href={`https://rxresu.me/r/${id}`} />
</Helmet>
<div
className={styles.page}
style={{ backgroundColor: resume.metadata.colors.background }}

View File

@ -1,13 +1,15 @@
.container {
background-color: #212121;
@apply h-screen overflow-scroll col-span-5 flex flex-col items-center;
}
@media screen {
.container {
background-color: #212121;
@apply col-span-5 flex flex-col items-center;
}
.page {
width: 800px;
@apply block my-16 rounded shadow-2xl;
}
.footer {
@apply mb-16 text-white text-center opacity-50 leading-loose;
.page {
width: 800px;
@apply block my-16 rounded shadow-2xl;
}
.footer {
@apply mb-16 text-white text-center opacity-50 leading-loose;
}
}

View File

@ -1,7 +1,7 @@
html,
body {
font-size: 12px;
font-family: "Montserrat", sans-serif;
font-family: 'Montserrat', sans-serif;
@apply text-primary-900 bg-primary-50;
@apply transition-colors duration-200 ease-in-out;
}
@ -50,8 +50,37 @@ label > span:first-child {
100% {
transform: rotate(360deg);
}
}
}w
.markdown {
@apply leading-relaxed whitespace-pre-wrap;
}
}
@page {
margin: 0;
}
@media print {
html,
body,
body * {
-webkit-print-color-adjust: exact;
color-adjust: exact;
visibility: hidden;
}
#page,
#page * {
visibility: visible;
}
#page {
width: 21cm;
position: absolute;
top: 0;
left: 0;
right: 0;
zoom: 1;
box-shadow: none;
}
}

View File

@ -37,6 +37,7 @@ const Onyx = ({ data }) => {
return (
<PageContext.Provider value={{ data, heading: Heading }}>
<div
id="page"
className="p-10 rounded"
style={{
fontFamily: data.metadata.font,

View File

@ -17,7 +17,9 @@ const AwardItem = (x) => (
</h6>
)}
</div>
<ReactMarkdown className="markdown mt-2 text-sm" source={x.summary} />
{x.summary && (
<ReactMarkdown className="markdown mt-2 text-sm" source={x.summary} />
)}
</div>
);

View File

@ -17,7 +17,9 @@ const CertificationItem = (x) => (
</h6>
)}
</div>
<ReactMarkdown className="markdown mt-2 text-sm" source={x.summary} />
{x.summary && (
<ReactMarkdown className="markdown mt-2 text-sm" source={x.summary} />
)}
</div>
);

View File

@ -21,7 +21,9 @@ const EducationItem = (x) => (
<span className="text-sm font-medium">{x.gpa}</span>
</div>
</div>
<ReactMarkdown className="markdown mt-2 text-sm" source={x.summary} />
{x.summary && (
<ReactMarkdown className="markdown mt-2 text-sm" source={x.summary} />
)}
</div>
);

View File

@ -21,7 +21,9 @@ const ProjectItem = (x) => (
</h6>
)}
</div>
<ReactMarkdown className="markdown mt-2 text-sm" source={x.summary} />
{x.summary && (
<ReactMarkdown className="markdown mt-2 text-sm" source={x.summary} />
)}
</div>
);

View File

@ -9,7 +9,9 @@ const ReferenceItem = (x) => (
<span className="text-xs">{x.position}</span>
<span className="text-xs">{x.phone}</span>
<span className="text-xs">{x.email}</span>
<ReactMarkdown className="markdown mt-2 text-sm" source={x.summary} />
{x.summary && (
<ReactMarkdown className="markdown mt-2 text-sm" source={x.summary} />
)}
</div>
);

View File

@ -15,7 +15,7 @@ const SkillsA = () => {
return safetyCheck(data.skills) ? (
<div>
<Heading>{data.skills.heading}</Heading>
<div className="grid grid-cols-2 gap-2">
<div className="grid grid-cols-2 row-gap-2 col-gap-4">
{data.skills.items.map(SkillItem)}
</div>
</div>

View File

@ -16,7 +16,9 @@ const WorkItem = (x) => (
</h6>
)}
</div>
<ReactMarkdown className="markdown mt-2 text-sm" source={x.summary} />
{x.summary && (
<ReactMarkdown className="markdown mt-2 text-sm" source={x.summary} />
)}
</div>
);