mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-10 04:22:27 +10:00
- implement actions section
- implement settings section
This commit is contained in:
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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,
|
||||
it’s 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>
|
||||
);
|
||||
};
|
||||
|
||||
62
src/components/builder/right/sections/Settings.js
Normal file
62
src/components/builder/right/sections/Settings.js
Normal 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,
|
||||
it’s 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);
|
||||
11
src/components/builder/right/sections/Settings.module.css
Normal file
11
src/components/builder/right/sections/Settings.module.css
Normal 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;
|
||||
}
|
||||
@ -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: {
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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
40
src/data/themeConfig.js
Normal 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;
|
||||
@ -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 />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
106
src/modals/sections/ExportModal.js
Normal file
106
src/modals/sections/ExportModal.js
Normal 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'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);
|
||||
@ -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
|
||||
|
||||
@ -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 }}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user