diff --git a/src/components/builder/center/Artboard.module.css b/src/components/builder/center/Artboard.module.css index dc50c524..ebf30ae8 100644 --- a/src/components/builder/center/Artboard.module.css +++ b/src/components/builder/center/Artboard.module.css @@ -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; + } +} \ No newline at end of file diff --git a/src/components/builder/right/RightSidebar.js b/src/components/builder/right/RightSidebar.js index 5dc9c374..1eed06be 100644 --- a/src/components/builder/right/RightSidebar.js +++ b/src/components/builder/right/RightSidebar.js @@ -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(); } diff --git a/src/components/builder/right/sections/Actions.js b/src/components/builder/right/sections/Actions.js index 57fb85ab..db95dd2a 100644 --- a/src/components/builder/right/sections/Actions.js +++ b/src/components/builder/right/sections/Actions.js @@ -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 (
Actions @@ -96,7 +63,7 @@ const Actions = () => {

-
@@ -111,15 +78,10 @@ const Actions = () => {

- -
- - - Download Exported JSON -
@@ -164,22 +126,6 @@ const Actions = () => {
- -
-
Danger Zone
- -

- 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. -

- -
- -
-
); }; diff --git a/src/components/builder/right/sections/Settings.js b/src/components/builder/right/sections/Settings.js new file mode 100644 index 00000000..d51ac347 --- /dev/null +++ b/src/components/builder/right/sections/Settings.js @@ -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 ( +
+ Settings + + + +
+
Danger Zone
+ +

+ 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. +

+ +
+ +
+
+
+ ); +}; + +export default memo(Settings); diff --git a/src/components/builder/right/sections/Settings.module.css b/src/components/builder/right/sections/Settings.module.css new file mode 100644 index 00000000..7943f429 --- /dev/null +++ b/src/components/builder/right/sections/Settings.module.css @@ -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; +} \ No newline at end of file diff --git a/src/components/builder/right/sections/Templates.js b/src/components/builder/right/sections/Templates.js index 5f2d6a15..19b1793f 100644 --- a/src/components/builder/right/sections/Templates.js +++ b/src/components/builder/right/sections/Templates.js @@ -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: { diff --git a/src/components/landing/Hero.js b/src/components/landing/Hero.js index 52dc9162..374ff973 100644 --- a/src/components/landing/Hero.js +++ b/src/components/landing/Hero.js @@ -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 )} - diff --git a/src/constants/ModalEvents.js b/src/constants/ModalEvents.js index d2ee186c..ce62309d 100644 --- a/src/constants/ModalEvents.js +++ b/src/constants/ModalEvents.js @@ -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; diff --git a/src/contexts/ThemeContext.js b/src/contexts/ThemeContext.js index 3fc63bcb..9d865a48 100644 --- a/src/contexts/ThemeContext.js +++ b/src/contexts/ThemeContext.js @@ -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 ( {children} diff --git a/src/data/rightSections.js b/src/data/rightSections.js index a699c3fe..c05e431b 100644 --- a/src/data/rightSections.js +++ b/src/data/rightSections.js @@ -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, + }, ]; diff --git a/src/data/themeConfig.js b/src/data/themeConfig.js new file mode 100644 index 00000000..5f708461 --- /dev/null +++ b/src/data/themeConfig.js @@ -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; diff --git a/src/modals/ModalRegistrar.js b/src/modals/ModalRegistrar.js index 86717ec6..b19a1b5c 100644 --- a/src/modals/ModalRegistrar.js +++ b/src/modals/ModalRegistrar.js @@ -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 = () => { + ); }; diff --git a/src/modals/sections/ExportModal.js b/src/modals/sections/ExportModal.js new file mode 100644 index 00000000..970bfb1f --- /dev/null +++ b/src/modals/sections/ExportModal.js @@ -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 ( + +
+
+ Use Browser's Print Dialog +
+ +

+ 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. +

+ + +
+ +
+ +
+
Download PDF
+ +

+ 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. +

+ +
+
+ + +
+
+
+ +
+ +
+
Export to JSON Format
+ +

+ 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. +

+ +
+ + + Export JSON + +
+
+
+ ); +}; + +export default memo(ExportModal); diff --git a/src/modals/sections/ImportModal.js b/src/modals/sections/ImportModal.js index c5954b84..6808cf03 100644 --- a/src/modals/sections/ImportModal.js +++ b/src/modals/sections/ImportModal.js @@ -29,7 +29,7 @@ const ImportModal = () => { }; return ( - +
Import from Reactive Resume diff --git a/src/pages/r/view.js b/src/pages/r/view.js index 4b51de7b..59e4f0cc 100644 --- a/src/pages/r/view.js +++ b/src/pages/r/view.js @@ -40,6 +40,7 @@ const ResumeViewer = ({ id }) => { {resume.name} | Reactive Resume +
span:first-child { 100% { transform: rotate(360deg); } -} +}w .markdown { @apply leading-relaxed whitespace-pre-wrap; -} \ No newline at end of file +} + +@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; + } +} diff --git a/src/templates/Onyx.js b/src/templates/Onyx.js index 2fb7a597..968a0217 100644 --- a/src/templates/Onyx.js +++ b/src/templates/Onyx.js @@ -37,6 +37,7 @@ const Onyx = ({ data }) => { return (
(
)}
- + {x.summary && ( + + )} ); diff --git a/src/templates/blocks/Certifications/CertificationsA.js b/src/templates/blocks/Certifications/CertificationsA.js index f6120a2f..69e31069 100644 --- a/src/templates/blocks/Certifications/CertificationsA.js +++ b/src/templates/blocks/Certifications/CertificationsA.js @@ -17,7 +17,9 @@ const CertificationItem = (x) => ( )} - + {x.summary && ( + + )} ); diff --git a/src/templates/blocks/Education/EducationA.js b/src/templates/blocks/Education/EducationA.js index 200ff3a0..aee2cc33 100644 --- a/src/templates/blocks/Education/EducationA.js +++ b/src/templates/blocks/Education/EducationA.js @@ -21,7 +21,9 @@ const EducationItem = (x) => ( {x.gpa} - + {x.summary && ( + + )} ); diff --git a/src/templates/blocks/Projects/ProjectsA.js b/src/templates/blocks/Projects/ProjectsA.js index ce61f1a1..f150d1fc 100644 --- a/src/templates/blocks/Projects/ProjectsA.js +++ b/src/templates/blocks/Projects/ProjectsA.js @@ -21,7 +21,9 @@ const ProjectItem = (x) => ( )} - + {x.summary && ( + + )} ); diff --git a/src/templates/blocks/References/ReferencesA.js b/src/templates/blocks/References/ReferencesA.js index 428d56cc..5c9aa1a8 100644 --- a/src/templates/blocks/References/ReferencesA.js +++ b/src/templates/blocks/References/ReferencesA.js @@ -9,7 +9,9 @@ const ReferenceItem = (x) => ( {x.position} {x.phone} {x.email} - + {x.summary && ( + + )} ); diff --git a/src/templates/blocks/Skills/SkillsA.js b/src/templates/blocks/Skills/SkillsA.js index b890b5c9..a8b06941 100644 --- a/src/templates/blocks/Skills/SkillsA.js +++ b/src/templates/blocks/Skills/SkillsA.js @@ -15,7 +15,7 @@ const SkillsA = () => { return safetyCheck(data.skills) ? (
{data.skills.heading} -
+
{data.skills.items.map(SkillItem)}
diff --git a/src/templates/blocks/Work/WorkA.js b/src/templates/blocks/Work/WorkA.js index d061b436..bc5d108c 100644 --- a/src/templates/blocks/Work/WorkA.js +++ b/src/templates/blocks/Work/WorkA.js @@ -16,7 +16,9 @@ const WorkItem = (x) => ( )}
- + {x.summary && ( + + )} );