mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-16 17:51:43 +10:00
clearing the slate
This commit is contained in:
@ -1,74 +0,0 @@
|
||||
/* eslint-disable jsx-a11y/media-has-caption */
|
||||
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';
|
||||
|
||||
import LeftSidebar from '../LeftSidebar/LeftSidebar';
|
||||
import RightSidebar from '../RightSidebar/RightSidebar';
|
||||
|
||||
import templates from '../../templates';
|
||||
import PageController from '../../shared/PageController';
|
||||
import PrintDialog from '../../shared/PrintDialog';
|
||||
import PanZoomAnimation from '../../shared/PanZoomAnimation';
|
||||
|
||||
const App = () => {
|
||||
const pageRef = useRef(null);
|
||||
const panZoomRef = useRef(null);
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
const context = useContext(AppContext);
|
||||
const { state, dispatch } = context;
|
||||
const { theme, settings } = state;
|
||||
|
||||
const pageContext = useContext(PageContext);
|
||||
const { setPageRef, setPanZoomRef } = pageContext;
|
||||
|
||||
useEffect(() => {
|
||||
setPageRef(pageRef);
|
||||
setPanZoomRef(panZoomRef);
|
||||
i18n.changeLanguage(settings.language);
|
||||
const storedState = JSON.parse(localStorage.getItem('state'));
|
||||
dispatch({ type: 'import_data', payload: storedState });
|
||||
}, [dispatch, setPageRef, setPanZoomRef, i18n, settings.language]);
|
||||
|
||||
return (
|
||||
<Suspense fallback="Loading...">
|
||||
<div className="h-screen grid grid-cols-5 items-center">
|
||||
<LeftSidebar />
|
||||
|
||||
<div className="relative z-10 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>
|
||||
|
||||
<div id="printPage" className="break-words">
|
||||
{templates.find(x => theme.layout.toLowerCase() === x.key).component()}
|
||||
</div>
|
||||
|
||||
<RightSidebar />
|
||||
|
||||
<PanZoomAnimation />
|
||||
<PrintDialog />
|
||||
</div>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
@ -1,88 +0,0 @@
|
||||
import React, { useState, useContext } from 'react';
|
||||
|
||||
import AppContext from '../../context/AppContext';
|
||||
import TabBar from '../../shared/TabBar';
|
||||
import ProfileTab from './tabs/Profile';
|
||||
import ObjectiveTab from './tabs/Objective';
|
||||
import WorkTab from './tabs/Work';
|
||||
import EducationTab from './tabs/Education';
|
||||
import AwardsTab from './tabs/Awards';
|
||||
import CertificationsTab from './tabs/Certifications';
|
||||
import SkillsTab from './tabs/Skills';
|
||||
import ExtrasTab from './tabs/Extras';
|
||||
import LanguagesTab from './tabs/Languages';
|
||||
import ReferencesTab from './tabs/References';
|
||||
import HobbiesTab from './tabs/Hobbies';
|
||||
|
||||
const LeftSidebar = () => {
|
||||
const context = useContext(AppContext);
|
||||
const { state, dispatch } = context;
|
||||
const { data } = state;
|
||||
|
||||
const tabs = [
|
||||
{ key: 'profile', name: data.profile.heading },
|
||||
{ key: 'objective', name: data.objective.heading },
|
||||
{ key: 'work', name: data.work.heading },
|
||||
{ key: 'education', name: data.education.heading },
|
||||
{ key: 'awards', name: data.awards.heading },
|
||||
{ key: 'certifications', name: data.certifications.heading },
|
||||
{ key: 'skills', name: data.skills.heading },
|
||||
{ key: 'hobbies', name: data.hobbies.heading },
|
||||
{ key: 'languages', name: data.languages.heading },
|
||||
{ key: 'references', name: data.references.heading },
|
||||
{ key: 'extras', name: data.extras.heading },
|
||||
];
|
||||
const [currentTab, setCurrentTab] = useState(tabs[0].key);
|
||||
const onChange = (key, value) => {
|
||||
dispatch({
|
||||
type: 'on_input',
|
||||
payload: {
|
||||
key,
|
||||
value,
|
||||
},
|
||||
});
|
||||
|
||||
dispatch({ type: 'save_data' });
|
||||
};
|
||||
|
||||
const renderTabs = () => {
|
||||
switch (currentTab) {
|
||||
case 'profile':
|
||||
return <ProfileTab data={data} onChange={onChange} />;
|
||||
case 'objective':
|
||||
return <ObjectiveTab data={data} onChange={onChange} />;
|
||||
case 'work':
|
||||
return <WorkTab data={data} onChange={onChange} />;
|
||||
case 'education':
|
||||
return <EducationTab data={data} onChange={onChange} />;
|
||||
case 'awards':
|
||||
return <AwardsTab data={data} onChange={onChange} />;
|
||||
case 'certifications':
|
||||
return <CertificationsTab data={data} onChange={onChange} />;
|
||||
case 'skills':
|
||||
return <SkillsTab data={data} onChange={onChange} />;
|
||||
case 'hobbies':
|
||||
return <HobbiesTab data={data} onChange={onChange} />;
|
||||
case 'languages':
|
||||
return <LanguagesTab data={data} onChange={onChange} />;
|
||||
case 'references':
|
||||
return <ReferencesTab data={data} onChange={onChange} />;
|
||||
case 'extras':
|
||||
return <ExtrasTab data={data} onChange={onChange} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
id="leftSidebar"
|
||||
className="animated slideInLeft z-10 py-6 h-screen bg-white col-span-1 shadow-2xl overflow-y-scroll"
|
||||
>
|
||||
<TabBar tabs={tabs} currentTab={currentTab} setCurrentTab={setCurrentTab} />
|
||||
<div className="px-6">{renderTabs()}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LeftSidebar;
|
||||
@ -1,153 +0,0 @@
|
||||
import React, { useState, useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import set from 'lodash/set';
|
||||
|
||||
import TextField from '../../../shared/TextField';
|
||||
import AppContext from '../../../context/AppContext';
|
||||
import Checkbox from '../../../shared/Checkbox';
|
||||
import TextArea from '../../../shared/TextArea';
|
||||
import { addItem } from '../../../utils';
|
||||
import ItemActions from '../../../shared/ItemActions';
|
||||
import AddItemButton from '../../../shared/AddItemButton';
|
||||
import ItemHeading from '../../../shared/ItemHeading';
|
||||
|
||||
const AwardsTab = ({ data, onChange }) => {
|
||||
const context = useContext(AppContext);
|
||||
const { dispatch } = context;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-6 grid grid-cols-6 items-center">
|
||||
<div className="col-span-1">
|
||||
<Checkbox
|
||||
checked={data.awards.enable}
|
||||
onChange={v => onChange('data.awards.enable', v)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-5">
|
||||
<TextField
|
||||
placeholder="Heading"
|
||||
value={data.awards.heading}
|
||||
onChange={v => onChange('data.awards.heading', v)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="my-6" />
|
||||
|
||||
{data.awards.items.map((x, index) => (
|
||||
<Item
|
||||
item={x}
|
||||
key={x.id}
|
||||
index={index}
|
||||
onChange={onChange}
|
||||
dispatch={dispatch}
|
||||
first={index === 0}
|
||||
last={index === data.awards.items.length - 1}
|
||||
/>
|
||||
))}
|
||||
|
||||
<AddItem heading={data.awards.heading} dispatch={dispatch} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Form = ({ item, onChange, identifier = '' }) => {
|
||||
const { t } = useTranslation(['leftSidebar', 'app']);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('awards.title.label')}
|
||||
placeholder="Code For Good Hackathon"
|
||||
value={item.title}
|
||||
onChange={v => onChange(`${identifier}title`, v)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('awards.subtitle.label')}
|
||||
placeholder="First Place, National Level"
|
||||
value={item.subtitle}
|
||||
onChange={v => onChange(`${identifier}subtitle`, v)}
|
||||
/>
|
||||
|
||||
<TextArea
|
||||
className="mb-6"
|
||||
label={t('app:item.description.label')}
|
||||
value={item.description}
|
||||
onChange={v => onChange(`${identifier}description`, v)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AddItem = ({ heading, dispatch }) => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const [item, setItem] = useState({
|
||||
id: uuidv4(),
|
||||
enable: true,
|
||||
title: '',
|
||||
subtitle: '',
|
||||
description: '',
|
||||
});
|
||||
|
||||
const onChange = (key, value) => setItem(set({ ...item }, key, value));
|
||||
|
||||
const onSubmit = () => {
|
||||
if (item.title === '') return;
|
||||
|
||||
addItem(dispatch, 'awards', item);
|
||||
|
||||
setItem({
|
||||
id: uuidv4(),
|
||||
enable: true,
|
||||
title: '',
|
||||
subtitle: '',
|
||||
description: '',
|
||||
});
|
||||
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="my-4 border border-gray-200 rounded p-5">
|
||||
<ItemHeading heading={heading} setOpen={setOpen} isOpen={isOpen} />
|
||||
|
||||
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
|
||||
<Form item={item} onChange={onChange} />
|
||||
|
||||
<AddItemButton onSubmit={onSubmit} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Item = ({ item, index, onChange, dispatch, first, last }) => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const identifier = `data.awards.items[${index}].`;
|
||||
|
||||
return (
|
||||
<div className="my-4 border border-gray-200 rounded p-5">
|
||||
<ItemHeading title={item.title} setOpen={setOpen} isOpen={isOpen} />
|
||||
|
||||
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
|
||||
<Form item={item} onChange={onChange} identifier={identifier} />
|
||||
|
||||
<ItemActions
|
||||
dispatch={dispatch}
|
||||
first={first}
|
||||
identifier={identifier}
|
||||
item={item}
|
||||
last={last}
|
||||
onChange={onChange}
|
||||
type="awards"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AwardsTab;
|
||||
@ -1,153 +0,0 @@
|
||||
import React, { useState, useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import set from 'lodash/set';
|
||||
|
||||
import TextField from '../../../shared/TextField';
|
||||
import AppContext from '../../../context/AppContext';
|
||||
import Checkbox from '../../../shared/Checkbox';
|
||||
import TextArea from '../../../shared/TextArea';
|
||||
import { addItem } from '../../../utils';
|
||||
import ItemActions from '../../../shared/ItemActions';
|
||||
import ItemHeading from '../../../shared/ItemHeading';
|
||||
import AddItemButton from '../../../shared/AddItemButton';
|
||||
|
||||
const CertificationsTab = ({ data, onChange }) => {
|
||||
const context = useContext(AppContext);
|
||||
const { dispatch } = context;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="my-6 grid grid-cols-6 items-center">
|
||||
<div className="col-span-1">
|
||||
<Checkbox
|
||||
checked={data.certifications.enable}
|
||||
onChange={v => onChange('data.certifications.enable', v)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-5">
|
||||
<TextField
|
||||
placeholder="Heading"
|
||||
value={data.certifications.heading}
|
||||
onChange={v => onChange('data.certifications.heading', v)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="my-6" />
|
||||
|
||||
{data.certifications.items.map((x, index) => (
|
||||
<Item
|
||||
item={x}
|
||||
key={x.id}
|
||||
index={index}
|
||||
onChange={onChange}
|
||||
dispatch={dispatch}
|
||||
first={index === 0}
|
||||
last={index === data.certifications.items.length - 1}
|
||||
/>
|
||||
))}
|
||||
|
||||
<AddItem heading={data.certifications.heading} dispatch={dispatch} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Form = ({ item, onChange, identifier = '' }) => {
|
||||
const { t } = useTranslation(['leftSidebar', 'app']);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('certifications.title.label')}
|
||||
placeholder="CS50: Intro to Computer Science"
|
||||
value={item.title}
|
||||
onChange={v => onChange(`${identifier}title`, v)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('certifications.subtitle.label')}
|
||||
placeholder="Harvard University"
|
||||
value={item.subtitle}
|
||||
onChange={v => onChange(`${identifier}subtitle`, v)}
|
||||
/>
|
||||
|
||||
<TextArea
|
||||
className="mb-6"
|
||||
label={t('app:item.description.label')}
|
||||
value={item.description}
|
||||
onChange={v => onChange(`${identifier}description`, v)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AddItem = ({ heading, dispatch }) => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const [item, setItem] = useState({
|
||||
id: uuidv4(),
|
||||
enable: true,
|
||||
title: '',
|
||||
subtitle: '',
|
||||
description: '',
|
||||
});
|
||||
|
||||
const onChange = (key, value) => setItem(set({ ...item }, key, value));
|
||||
|
||||
const onSubmit = () => {
|
||||
if (item.title === '') return;
|
||||
|
||||
addItem(dispatch, 'certifications', item);
|
||||
|
||||
setItem({
|
||||
id: uuidv4(),
|
||||
enable: true,
|
||||
title: '',
|
||||
subtitle: '',
|
||||
description: '',
|
||||
});
|
||||
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="my-4 border border-gray-200 rounded p-5">
|
||||
<ItemHeading heading={heading} setOpen={setOpen} isOpen={isOpen} />
|
||||
|
||||
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
|
||||
<Form item={item} onChange={onChange} />
|
||||
|
||||
<AddItemButton onSubmit={onSubmit} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Item = ({ item, index, onChange, dispatch, first, last }) => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const identifier = `data.certifications.items[${index}].`;
|
||||
|
||||
return (
|
||||
<div className="my-4 border border-gray-200 rounded p-5">
|
||||
<ItemHeading title={item.title} setOpen={setOpen} isOpen={isOpen} />
|
||||
|
||||
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
|
||||
<Form item={item} onChange={onChange} identifier={identifier} />
|
||||
|
||||
<ItemActions
|
||||
dispatch={dispatch}
|
||||
first={first}
|
||||
identifier={identifier}
|
||||
item={item}
|
||||
last={last}
|
||||
onChange={onChange}
|
||||
type="certifications"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CertificationsTab;
|
||||
@ -1,185 +0,0 @@
|
||||
import React, { useState, useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import set from 'lodash/set';
|
||||
|
||||
import TextField from '../../../shared/TextField';
|
||||
import TextArea from '../../../shared/TextArea';
|
||||
import AppContext from '../../../context/AppContext';
|
||||
import Checkbox from '../../../shared/Checkbox';
|
||||
import { addItem } from '../../../utils';
|
||||
import ItemActions from '../../../shared/ItemActions';
|
||||
import AddItemButton from '../../../shared/AddItemButton';
|
||||
import ItemHeading from '../../../shared/ItemHeading';
|
||||
|
||||
const EducationTab = ({ data, onChange }) => {
|
||||
const context = useContext(AppContext);
|
||||
const { dispatch } = context;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-6 grid grid-cols-6 items-center">
|
||||
<div className="col-span-1">
|
||||
<Checkbox
|
||||
checked={data.education.enable}
|
||||
onChange={v => onChange('data.education.enable', v)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-5">
|
||||
<TextField
|
||||
placeholder="Heading"
|
||||
value={data.education.heading}
|
||||
onChange={v => onChange('data.education.heading', v)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="my-6" />
|
||||
|
||||
{data.education.items.map((x, index) => (
|
||||
<Item
|
||||
item={x}
|
||||
key={x.id}
|
||||
index={index}
|
||||
onChange={onChange}
|
||||
dispatch={dispatch}
|
||||
first={index === 0}
|
||||
last={index === data.education.items.length - 1}
|
||||
/>
|
||||
))}
|
||||
|
||||
<AddItem heading={data.education.heading} dispatch={dispatch} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Form = ({ item, onChange, identifier = '' }) => {
|
||||
const { t } = useTranslation(['leftSidebar', 'app']);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('education.name.label')}
|
||||
placeholder="Harvard University"
|
||||
value={item.name}
|
||||
onChange={v => onChange(`${identifier}name`, v)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('education.major.label')}
|
||||
placeholder="Masters in Computer Science"
|
||||
value={item.major}
|
||||
onChange={v => onChange(`${identifier}major`, v)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('education.grade.label')}
|
||||
placeholder="7.2 CGPA"
|
||||
value={item.grade}
|
||||
onChange={v => onChange(`${identifier}grade`, v)}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-2 col-gap-4">
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('app:item.startDate.label')}
|
||||
placeholder="March 2018"
|
||||
value={item.start}
|
||||
onChange={v => onChange(`${identifier}start`, v)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('app:item.endDate.label')}
|
||||
placeholder="June 2022"
|
||||
value={item.end}
|
||||
onChange={v => onChange(`${identifier}end`, v)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TextArea
|
||||
rows="5"
|
||||
className="mb-6"
|
||||
label={t('app:item.description.label')}
|
||||
value={item.description}
|
||||
onChange={v => onChange(`${identifier}description`, v)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AddItem = ({ heading, dispatch }) => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const [item, setItem] = useState({
|
||||
id: uuidv4(),
|
||||
enable: true,
|
||||
name: '',
|
||||
major: '',
|
||||
start: '',
|
||||
end: '',
|
||||
grade: '',
|
||||
description: '',
|
||||
});
|
||||
|
||||
const onChange = (key, value) => setItem(set({ ...item }, key, value));
|
||||
|
||||
const onSubmit = () => {
|
||||
if (item.name === '' || item.major === '') return;
|
||||
|
||||
addItem(dispatch, 'education', item);
|
||||
|
||||
setItem({
|
||||
id: uuidv4(),
|
||||
enable: true,
|
||||
name: '',
|
||||
role: '',
|
||||
start: '',
|
||||
end: '',
|
||||
grade: '',
|
||||
description: '',
|
||||
});
|
||||
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="my-4 border border-gray-200 rounded p-5">
|
||||
<ItemHeading heading={heading} setOpen={setOpen} isOpen={isOpen} />
|
||||
|
||||
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
|
||||
<Form item={item} onChange={onChange} />
|
||||
<AddItemButton onSubmit={onSubmit} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Item = ({ item, index, onChange, dispatch, first, last }) => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const identifier = `data.education.items[${index}].`;
|
||||
|
||||
return (
|
||||
<div className="my-4 border border-gray-200 rounded p-5">
|
||||
<ItemHeading title={item.name} setOpen={setOpen} isOpen={isOpen} />
|
||||
|
||||
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
|
||||
<Form item={item} onChange={onChange} identifier={identifier} />
|
||||
|
||||
<ItemActions
|
||||
dispatch={dispatch}
|
||||
first={first}
|
||||
identifier={identifier}
|
||||
item={item}
|
||||
last={last}
|
||||
onChange={onChange}
|
||||
type="education"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EducationTab;
|
||||
@ -1,143 +0,0 @@
|
||||
import React, { useState, useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import set from 'lodash/set';
|
||||
|
||||
import TextField from '../../../shared/TextField';
|
||||
import AppContext from '../../../context/AppContext';
|
||||
import Checkbox from '../../../shared/Checkbox';
|
||||
import { addItem } from '../../../utils';
|
||||
import ItemActions from '../../../shared/ItemActions';
|
||||
import ItemHeading from '../../../shared/ItemHeading';
|
||||
import AddItemButton from '../../../shared/AddItemButton';
|
||||
|
||||
const ExtrasTab = ({ data, onChange }) => {
|
||||
const context = useContext(AppContext);
|
||||
const { dispatch } = context;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-6 grid grid-cols-6 items-center">
|
||||
<div className="col-span-1">
|
||||
<Checkbox
|
||||
checked={data.extras.enable}
|
||||
onChange={v => onChange('data.extras.enable', v)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-5">
|
||||
<TextField
|
||||
placeholder="Heading"
|
||||
value={data.extras.heading}
|
||||
onChange={v => onChange('data.extras.heading', v)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="my-6" />
|
||||
|
||||
{data.extras.items.map((x, index) => (
|
||||
<Item
|
||||
item={x}
|
||||
key={x.id}
|
||||
index={index}
|
||||
onChange={onChange}
|
||||
dispatch={dispatch}
|
||||
first={index === 0}
|
||||
last={index === data.extras.items.length - 1}
|
||||
/>
|
||||
))}
|
||||
|
||||
<AddItem heading={data.extras.heading} dispatch={dispatch} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Form = ({ item, onChange, identifier = '' }) => {
|
||||
const { t } = useTranslation('leftSidebar');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('extras.key.label')}
|
||||
placeholder="Date of Birth"
|
||||
value={item.key}
|
||||
onChange={v => onChange(`${identifier}key`, v)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('extras.value.label')}
|
||||
placeholder="6th August 1995"
|
||||
value={item.value}
|
||||
onChange={v => onChange(`${identifier}value`, v)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AddItem = ({ heading, dispatch }) => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const [item, setItem] = useState({
|
||||
id: uuidv4(),
|
||||
enable: true,
|
||||
key: '',
|
||||
value: '',
|
||||
});
|
||||
|
||||
const onChange = (key, value) => setItem(items => set({ ...items }, key, value));
|
||||
|
||||
const onSubmit = () => {
|
||||
if (item.key === '' || item.value === '') return;
|
||||
|
||||
addItem(dispatch, 'extras', item);
|
||||
|
||||
setItem({
|
||||
id: uuidv4(),
|
||||
enable: true,
|
||||
key: '',
|
||||
value: '',
|
||||
});
|
||||
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="my-4 border border-gray-200 rounded p-5">
|
||||
<ItemHeading heading={heading} setOpen={setOpen} isOpen={isOpen} />
|
||||
|
||||
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
|
||||
<Form item={item} onChange={onChange} />
|
||||
|
||||
<AddItemButton onSubmit={onSubmit} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Item = ({ item, index, onChange, dispatch, first, last }) => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const identifier = `data.extras.items[${index}].`;
|
||||
|
||||
return (
|
||||
<div className="my-4 border border-gray-200 rounded p-5">
|
||||
<ItemHeading title={item.key} setOpen={setOpen} isOpen={isOpen} />
|
||||
|
||||
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
|
||||
<Form item={item} onChange={onChange} identifier={identifier} />
|
||||
|
||||
<ItemActions
|
||||
dispatch={dispatch}
|
||||
first={first}
|
||||
identifier={identifier}
|
||||
item={item}
|
||||
last={last}
|
||||
onChange={onChange}
|
||||
type="extras"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExtrasTab;
|
||||
@ -1,140 +0,0 @@
|
||||
import React, { useState, useContext } from 'react';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import AppContext from '../../../context/AppContext';
|
||||
import Checkbox from '../../../shared/Checkbox';
|
||||
import TextField from '../../../shared/TextField';
|
||||
import { addItem, deleteItem, moveItemUp, moveItemDown } from '../../../utils';
|
||||
import ItemHeading from '../../../shared/ItemHeading';
|
||||
|
||||
const HobbiesTab = ({ data, onChange }) => {
|
||||
const context = useContext(AppContext);
|
||||
const { dispatch } = context;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="my-6 grid grid-cols-6 items-center">
|
||||
<div className="col-span-1">
|
||||
<Checkbox
|
||||
checked={data.hobbies.enable}
|
||||
onChange={v => onChange('data.hobbies.enable', v)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-5">
|
||||
<TextField
|
||||
placeholder="Heading"
|
||||
value={data.hobbies.heading}
|
||||
onChange={v => onChange('data.hobbies.heading', v)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="my-6" />
|
||||
|
||||
{data.hobbies.items.map((x, index) => (
|
||||
<Item item={x} key={index} index={index} onChange={onChange} dispatch={dispatch} />
|
||||
))}
|
||||
|
||||
<AddItem heading={data.hobbies.heading} dispatch={dispatch} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Form = ({ item, onChange }) => {
|
||||
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="Beatboxing"
|
||||
value={item.hobby}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
type="text"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const AddItem = ({ heading, dispatch }) => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const [item, setItem] = useState({
|
||||
id: uuidv4(),
|
||||
hobby: ''
|
||||
});
|
||||
|
||||
const add = () => {
|
||||
if (item.hobby === '') return;
|
||||
|
||||
addItem(dispatch, 'hobbies', item);
|
||||
|
||||
setItem({
|
||||
id: uuidv4(),
|
||||
hobby: ''
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="my-4 border border-gray-200 rounded p-5">
|
||||
<ItemHeading heading={heading} setOpen={setOpen} isOpen={isOpen} />
|
||||
|
||||
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
<div className="col-span-3">
|
||||
<Form item={item} onChange={v => setItem({...item, hobby: v})} />
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={add}
|
||||
className="col-span-1 bg-gray-600 hover:bg-gray-700 text-sm font-medium rounded"
|
||||
>
|
||||
<div className="flex justify-center items-center">
|
||||
<i className="material-icons font-bold text-white text-lg">add</i>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Item = ({ item, index, onChange, dispatch }) => {
|
||||
const identifier = `data.hobbies.items[${index}]`;
|
||||
|
||||
return (
|
||||
<div className="my-4 grid grid-cols-12">
|
||||
<div className="col-span-9">
|
||||
<Form item={item} onChange={v => onChange(identifier, v)} />
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => moveItemUp(dispatch, 'hobbies', item)}
|
||||
className="col-span-1 text-gray-600 hover:text-red-600 text-sm font-medium"
|
||||
>
|
||||
<div className="flex justify-end items-center">
|
||||
<i className="material-icons font-bold text-lg">arrow_upward</i>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => moveItemDown(dispatch, 'hobbies', item)}
|
||||
className="col-span-1 text-gray-600 hover:text-red-600 text-sm font-medium"
|
||||
>
|
||||
<div className="flex justify-end items-center">
|
||||
<i className="material-icons font-bold text-lg">arrow_downward</i>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => deleteItem(dispatch, 'hobbies', item)}
|
||||
className="col-span-1 text-gray-600 hover:text-red-600 text-sm font-medium"
|
||||
>
|
||||
<div className="flex justify-end items-center">
|
||||
<i className="material-icons font-bold text-lg">close</i>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HobbiesTab;
|
||||
@ -1,182 +0,0 @@
|
||||
import set from 'lodash/set';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import AppContext from '../../../context/AppContext';
|
||||
import AddItemButton from '../../../shared/AddItemButton';
|
||||
import Checkbox from '../../../shared/Checkbox';
|
||||
import Counter from '../../../shared/Counter';
|
||||
import ItemActions from '../../../shared/ItemActions';
|
||||
import ItemHeading from '../../../shared/ItemHeading';
|
||||
import TextField from '../../../shared/TextField';
|
||||
import { addItem } from '../../../utils';
|
||||
|
||||
const LanguagesTab = ({ data, onChange }) => {
|
||||
const context = useContext(AppContext);
|
||||
const { dispatch } = context;
|
||||
|
||||
useEffect(() => {
|
||||
if (!('languages' in data)) {
|
||||
dispatch({
|
||||
type: 'migrate_section',
|
||||
payload: {
|
||||
key: 'languages',
|
||||
value: {
|
||||
enable: false,
|
||||
heading: 'Languages',
|
||||
items: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
dispatch({ type: 'save_data' });
|
||||
}
|
||||
}, [data, dispatch]);
|
||||
|
||||
return (
|
||||
'languages' in data && (
|
||||
<>
|
||||
<div className="mb-6 grid grid-cols-6 items-center">
|
||||
<div className="col-span-1">
|
||||
<Checkbox
|
||||
checked={data.languages.enable}
|
||||
onChange={v => onChange('data.languages.enable', v)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-5">
|
||||
<TextField
|
||||
placeholder="Heading"
|
||||
value={data.languages.heading}
|
||||
onChange={v => onChange('data.languages.heading', v)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="my-6" />
|
||||
|
||||
{data.languages.items.map((x, index) => (
|
||||
<Item
|
||||
item={x}
|
||||
key={x.id}
|
||||
index={index}
|
||||
onChange={onChange}
|
||||
dispatch={dispatch}
|
||||
first={index === 0}
|
||||
last={index === data.languages.items.length - 1}
|
||||
/>
|
||||
))}
|
||||
|
||||
<AddItem heading={data.languages.heading} dispatch={dispatch} />
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const Form = ({ item, onChange, identifier = '' }) => {
|
||||
const { t } = useTranslation('leftSidebar');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('languages.key.label')}
|
||||
placeholder="English"
|
||||
value={item.key}
|
||||
onChange={v => onChange(`${identifier}key`, v)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('languages.level.label')}
|
||||
placeholder="B1"
|
||||
value={item.level}
|
||||
onChange={v => onChange(`${identifier}level`, v)}
|
||||
/>
|
||||
|
||||
<Counter
|
||||
className="mb-6"
|
||||
label={t('languages.rating.label')}
|
||||
value={item.rating}
|
||||
onDecrement={() =>
|
||||
item.rating > 1
|
||||
? onChange(`${identifier}rating`, item.rating - 1)
|
||||
: onChange(`${identifier}rating`, 0)
|
||||
}
|
||||
onIncrement={() =>
|
||||
item.rating < 5
|
||||
? onChange(`${identifier}rating`, item.rating + 1)
|
||||
: onChange(`${identifier}rating`, 0)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AddItem = ({ heading, dispatch }) => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const [item, setItem] = useState({
|
||||
id: uuidv4(),
|
||||
enable: true,
|
||||
key: '',
|
||||
value: '',
|
||||
rating: 1,
|
||||
});
|
||||
|
||||
const onChange = (key, value) => setItem(items => set({ ...items }, key, value));
|
||||
|
||||
const onSubmit = () => {
|
||||
if (item.key === '') return;
|
||||
|
||||
addItem(dispatch, 'languages', item);
|
||||
|
||||
setItem({
|
||||
id: uuidv4(),
|
||||
enable: true,
|
||||
key: '',
|
||||
value: '',
|
||||
rating: 1,
|
||||
});
|
||||
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="my-4 border border-gray-200 rounded p-5">
|
||||
<ItemHeading heading={heading} setOpen={setOpen} isOpen={isOpen} />
|
||||
|
||||
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
|
||||
<Form item={item} onChange={onChange} />
|
||||
|
||||
<AddItemButton onSubmit={onSubmit} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Item = ({ item, index, onChange, dispatch, first, last }) => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const identifier = `data.languages.items[${index}].`;
|
||||
|
||||
return (
|
||||
<div className="my-4 border border-gray-200 rounded p-5">
|
||||
<ItemHeading title={item.key} setOpen={setOpen} isOpen={isOpen} />
|
||||
|
||||
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
|
||||
<Form item={item} onChange={onChange} identifier={identifier} />
|
||||
|
||||
<ItemActions
|
||||
dispatch={dispatch}
|
||||
first={first}
|
||||
identifier={identifier}
|
||||
item={item}
|
||||
last={last}
|
||||
onChange={onChange}
|
||||
type="languages"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LanguagesTab;
|
||||
@ -1,43 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import TextArea from '../../../shared/TextArea';
|
||||
import TextField from '../../../shared/TextField';
|
||||
import Checkbox from '../../../shared/Checkbox';
|
||||
|
||||
const ObjectiveTab = ({ data, onChange }) => {
|
||||
const { t } = useTranslation('leftSidebar');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-6 grid grid-cols-6 items-center">
|
||||
<div className="col-span-1">
|
||||
<Checkbox
|
||||
checked={data.objective.enable}
|
||||
onChange={v => onChange('data.objective.enable', v)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-5">
|
||||
<TextField
|
||||
placeholder="Heading"
|
||||
value={data.objective.heading}
|
||||
onChange={v => onChange('data.objective.heading', v)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="my-6" />
|
||||
|
||||
<TextArea
|
||||
rows="15"
|
||||
className="mb-4"
|
||||
label={t('objective.objective.label')}
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
export default ObjectiveTab;
|
||||
@ -1,109 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import TextField from '../../../shared/TextField';
|
||||
|
||||
const ProfileTab = ({ data, onChange }) => {
|
||||
const { t } = useTranslation('leftSidebar');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TextField
|
||||
className="mb-6"
|
||||
placeholder="Heading"
|
||||
value={data.profile.heading}
|
||||
onChange={v => onChange('data.profile.heading', v)}
|
||||
/>
|
||||
|
||||
<hr className="my-6" />
|
||||
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('profile.photoUrl.label')}
|
||||
placeholder="https://i.imgur.com/..."
|
||||
value={data.profile.photo}
|
||||
onChange={v => onChange('data.profile.photo', v)}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-2 col-gap-4">
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('profile.firstName.label')}
|
||||
placeholder="Jane"
|
||||
value={data.profile.firstName}
|
||||
onChange={v => onChange('data.profile.firstName', v)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('profile.lastName.label')}
|
||||
placeholder="Doe"
|
||||
value={data.profile.lastName}
|
||||
onChange={v => onChange('data.profile.lastName', v)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('profile.subtitle.label')}
|
||||
placeholder="Full-Stack Web Developer"
|
||||
value={data.profile.subtitle}
|
||||
onChange={v => onChange('data.profile.subtitle', v)}
|
||||
/>
|
||||
|
||||
<hr className="my-6" />
|
||||
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('profile.address.line1.label')}
|
||||
placeholder="Palladium Complex"
|
||||
value={data.profile.address.line1}
|
||||
onChange={v => onChange('data.profile.address.line1', v)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('profile.address.line2.label')}
|
||||
placeholder="140 E 14th St"
|
||||
value={data.profile.address.line2}
|
||||
onChange={v => onChange('data.profile.address.line2', v)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('profile.address.line3.label')}
|
||||
placeholder="New York, NY 10003 USA"
|
||||
value={data.profile.address.line3}
|
||||
onChange={v => onChange('data.profile.address.line3', v)}
|
||||
/>
|
||||
|
||||
<hr className="my-6" />
|
||||
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('profile.phone.label')}
|
||||
placeholder="+1 541 754 3010"
|
||||
value={data.profile.phone}
|
||||
onChange={v => onChange('data.profile.phone', v)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('profile.website.label')}
|
||||
placeholder="janedoe.me"
|
||||
value={data.profile.website}
|
||||
onChange={v => onChange('data.profile.website', v)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('profile.email.label')}
|
||||
placeholder="jane.doe@example.com"
|
||||
value={data.profile.email}
|
||||
onChange={v => onChange('data.profile.email', v)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileTab;
|
||||
@ -1,194 +0,0 @@
|
||||
import React, { useState, useEffect, useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import set from 'lodash/set';
|
||||
|
||||
import TextField from '../../../shared/TextField';
|
||||
import TextArea from '../../../shared/TextArea';
|
||||
import AppContext from '../../../context/AppContext';
|
||||
import Checkbox from '../../../shared/Checkbox';
|
||||
import { addItem } from '../../../utils';
|
||||
import ItemActions from '../../../shared/ItemActions';
|
||||
import ItemHeading from '../../../shared/ItemHeading';
|
||||
import AddItemButton from '../../../shared/AddItemButton';
|
||||
|
||||
const ReferencesTab = ({ data, onChange }) => {
|
||||
const context = useContext(AppContext);
|
||||
const { dispatch } = context;
|
||||
|
||||
useEffect(() => {
|
||||
if (!('references' in data)) {
|
||||
dispatch({
|
||||
type: 'migrate_section',
|
||||
payload: {
|
||||
key: 'references',
|
||||
value: {
|
||||
enable: false,
|
||||
heading: 'References',
|
||||
items: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
dispatch({ type: 'save_data' });
|
||||
}
|
||||
}, [data, dispatch]);
|
||||
|
||||
return (
|
||||
'references' in data && (
|
||||
<>
|
||||
<div className="mb-6 grid grid-cols-6 items-center">
|
||||
<div className="col-span-1">
|
||||
<Checkbox
|
||||
checked={data.references.enable}
|
||||
onChange={v => onChange('data.references.enable', v)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-5">
|
||||
<TextField
|
||||
placeholder="Heading"
|
||||
value={data.references.heading}
|
||||
onChange={v => onChange('data.references.heading', v)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="my-6" />
|
||||
|
||||
{data.references.items.map((x, index) => (
|
||||
<Item
|
||||
item={x}
|
||||
key={x.id}
|
||||
index={index}
|
||||
onChange={onChange}
|
||||
dispatch={dispatch}
|
||||
first={index === 0}
|
||||
last={index === data.references.items.length - 1}
|
||||
/>
|
||||
))}
|
||||
|
||||
<AddItem heading={data.references.heading} dispatch={dispatch} />
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const Form = ({ item, onChange, identifier = '' }) => {
|
||||
const { t } = useTranslation(['leftSidebar', 'app']);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('references.name.label')}
|
||||
placeholder="Richard Hendricks"
|
||||
value={item.name}
|
||||
onChange={v => onChange(`${identifier}name`, v)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('references.position.label')}
|
||||
placeholder="CEO, Pied Piper"
|
||||
value={item.position}
|
||||
onChange={v => onChange(`${identifier}position`, v)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('references.phone.label')}
|
||||
placeholder="+1 541 754 3010"
|
||||
value={item.phone}
|
||||
onChange={v => onChange(`${identifier}phone`, v)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('references.email.label')}
|
||||
placeholder="richard@piedpiper.com"
|
||||
value={item.email}
|
||||
onChange={v => onChange(`${identifier}email`, v)}
|
||||
/>
|
||||
|
||||
<TextArea
|
||||
rows="5"
|
||||
className="mb-6"
|
||||
label={t('app:item.description.label')}
|
||||
value={item.description}
|
||||
onChange={v => onChange(`${identifier}description`, v)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AddItem = ({ heading, dispatch }) => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const [item, setItem] = useState({
|
||||
id: uuidv4(),
|
||||
enable: true,
|
||||
name: '',
|
||||
position: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
description: '',
|
||||
});
|
||||
|
||||
const onChange = (key, value) => setItem(set({ ...item }, key, value));
|
||||
|
||||
const onSubmit = () => {
|
||||
if (item.name === '' || item.position === '') return;
|
||||
|
||||
addItem(dispatch, 'references', item);
|
||||
|
||||
setItem({
|
||||
id: uuidv4(),
|
||||
enable: true,
|
||||
name: '',
|
||||
position: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
description: '',
|
||||
});
|
||||
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="my-4 border border-gray-200 rounded p-5">
|
||||
<ItemHeading heading={heading} setOpen={setOpen} isOpen={isOpen} />
|
||||
|
||||
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
|
||||
<Form item={item} onChange={onChange} />
|
||||
|
||||
<AddItemButton onSubmit={onSubmit} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Item = ({ item, index, onChange, dispatch, first, last }) => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const identifier = `data.references.items[${index}].`;
|
||||
|
||||
return (
|
||||
<div className="my-4 border border-gray-200 rounded p-5">
|
||||
<ItemHeading title={item.name} setOpen={setOpen} isOpen={isOpen} />
|
||||
|
||||
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
|
||||
<Form item={item} onChange={onChange} identifier={identifier} />
|
||||
|
||||
<ItemActions
|
||||
dispatch={dispatch}
|
||||
first={first}
|
||||
identifier={identifier}
|
||||
item={item}
|
||||
last={last}
|
||||
onChange={onChange}
|
||||
type="references"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReferencesTab;
|
||||
@ -1,140 +0,0 @@
|
||||
import React, { useState, useContext } from 'react';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import AppContext from '../../../context/AppContext';
|
||||
import Checkbox from '../../../shared/Checkbox';
|
||||
import TextField from '../../../shared/TextField';
|
||||
import { addItem, deleteItem, moveItemUp, moveItemDown } from '../../../utils';
|
||||
import ItemHeading from '../../../shared/ItemHeading';
|
||||
|
||||
const SkillsTab = ({ data, onChange }) => {
|
||||
const context = useContext(AppContext);
|
||||
const { dispatch } = context;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="my-6 grid grid-cols-6 items-center">
|
||||
<div className="col-span-1">
|
||||
<Checkbox
|
||||
checked={data.skills.enable}
|
||||
onChange={v => onChange('data.skills.enable', v)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-5">
|
||||
<TextField
|
||||
placeholder="Heading"
|
||||
value={data.skills.heading}
|
||||
onChange={v => onChange('data.skills.heading', v)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="my-6" />
|
||||
|
||||
{data.skills.items.map((x, index) => (
|
||||
<Item item={x} key={x.id} index={index} onChange={onChange} dispatch={dispatch} />
|
||||
))}
|
||||
|
||||
<AddItem heading={data.skills.heading} dispatch={dispatch} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Form = ({ item, onChange }) => {
|
||||
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="Team Building & Training"
|
||||
value={item.skill}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
type="text"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const AddItem = ({ heading, dispatch }) => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const [item, setItem] = useState({
|
||||
id: uuidv4(),
|
||||
skill: ''
|
||||
});
|
||||
|
||||
const add = () => {
|
||||
if (item.skill === '') return;
|
||||
|
||||
addItem(dispatch, 'skills', item);
|
||||
|
||||
setItem({
|
||||
id: uuidv4(),
|
||||
skill: ''
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="my-4 border border-gray-200 rounded p-5">
|
||||
<ItemHeading heading={heading} setOpen={setOpen} isOpen={isOpen} />
|
||||
|
||||
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
<div className="col-span-3">
|
||||
<Form item={item} onChange={v => setItem({...item, skill: v})} />
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={add}
|
||||
className="col-span-1 bg-gray-600 hover:bg-gray-700 text-sm font-medium rounded"
|
||||
>
|
||||
<div className="flex justify-center items-center">
|
||||
<i className="material-icons font-bold text-white text-lg">add</i>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Item = ({ item, index, onChange, dispatch }) => {
|
||||
const identifier = `data.skills.items[${index}]`;
|
||||
|
||||
return (
|
||||
<div className="my-4 grid grid-cols-12">
|
||||
<div className="col-span-9">
|
||||
<Form item={item} onChange={v => onChange(identifier, {...item, skill: v})} />
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => moveItemUp(dispatch, 'skills', item)}
|
||||
className="col-span-1 text-gray-600 hover:text-red-600 text-sm font-medium"
|
||||
>
|
||||
<div className="flex justify-end items-center">
|
||||
<i className="material-icons font-bold text-lg">arrow_upward</i>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => moveItemDown(dispatch, 'skills', item)}
|
||||
className="col-span-1 text-gray-600 hover:text-red-600 text-sm font-medium"
|
||||
>
|
||||
<div className="flex justify-end items-center">
|
||||
<i className="material-icons font-bold text-lg">arrow_downward</i>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => deleteItem(dispatch, 'skills', item)}
|
||||
className="col-span-1 text-gray-600 hover:text-red-600 text-sm font-medium"
|
||||
>
|
||||
<div className="flex justify-end items-center">
|
||||
<i className="material-icons font-bold text-lg">close</i>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SkillsTab;
|
||||
@ -1,173 +0,0 @@
|
||||
import React, { useState, useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import set from 'lodash/set';
|
||||
|
||||
import TextField from '../../../shared/TextField';
|
||||
import TextArea from '../../../shared/TextArea';
|
||||
import AppContext from '../../../context/AppContext';
|
||||
import Checkbox from '../../../shared/Checkbox';
|
||||
import { addItem } from '../../../utils';
|
||||
import ItemActions from '../../../shared/ItemActions';
|
||||
import AddItemButton from '../../../shared/AddItemButton';
|
||||
import ItemHeading from '../../../shared/ItemHeading';
|
||||
|
||||
const WorkTab = ({ data, onChange }) => {
|
||||
const context = useContext(AppContext);
|
||||
const { dispatch } = context;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-6 grid grid-cols-6 items-center">
|
||||
<div className="col-span-1">
|
||||
<Checkbox checked={data.work.enable} onChange={v => onChange('data.work.enable', v)} />
|
||||
</div>
|
||||
<div className="col-span-5">
|
||||
<TextField
|
||||
placeholder="Heading"
|
||||
value={data.work.heading}
|
||||
onChange={v => onChange('data.work.heading', v)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="my-6" />
|
||||
|
||||
{data.work.items.map((x, index) => (
|
||||
<Item
|
||||
dispatch={dispatch}
|
||||
first={index === 0}
|
||||
index={index}
|
||||
item={x}
|
||||
key={x.id}
|
||||
last={index === data.work.items.length - 1}
|
||||
onChange={onChange}
|
||||
/>
|
||||
))}
|
||||
|
||||
<AddItem heading={data.work.heading} dispatch={dispatch} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Form = ({ item, onChange, identifier = '' }) => {
|
||||
const { t } = useTranslation(['leftSidebar', 'app']);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('work.name.label')}
|
||||
placeholder="Amazon"
|
||||
value={item.title}
|
||||
onChange={v => onChange(`${identifier}title`, v)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('work.role.label')}
|
||||
placeholder="Full-Stack Web Developer"
|
||||
value={item.role}
|
||||
onChange={v => onChange(`${identifier}role`, v)}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-2 col-gap-4">
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('app:item.startDate.label')}
|
||||
placeholder="March 2018"
|
||||
value={item.start}
|
||||
onChange={v => onChange(`${identifier}start`, v)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className="mb-6"
|
||||
label={t('app:item.endDate.label')}
|
||||
placeholder="June 2022"
|
||||
value={item.end}
|
||||
onChange={v => onChange(`${identifier}end`, v)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TextArea
|
||||
rows="5"
|
||||
className="mb-6"
|
||||
label={t('app:item.description.label')}
|
||||
value={item.description}
|
||||
onChange={v => onChange(`${identifier}description`, v)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AddItem = ({ heading, dispatch }) => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const [item, setItem] = useState({
|
||||
id: uuidv4(),
|
||||
enable: true,
|
||||
title: '',
|
||||
role: '',
|
||||
start: '',
|
||||
end: '',
|
||||
description: '',
|
||||
});
|
||||
|
||||
const onChange = (key, value) => setItem(set({ ...item }, key, value));
|
||||
|
||||
const onSubmit = () => {
|
||||
if (item.title === '' || item.role === '') return;
|
||||
|
||||
addItem(dispatch, 'work', item);
|
||||
|
||||
setItem({
|
||||
id: uuidv4(),
|
||||
enable: true,
|
||||
title: '',
|
||||
role: '',
|
||||
start: '',
|
||||
end: '',
|
||||
description: '',
|
||||
});
|
||||
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="my-4 border border-gray-200 rounded p-5">
|
||||
<ItemHeading heading={heading} setOpen={setOpen} isOpen={isOpen} />
|
||||
|
||||
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
|
||||
<Form item={item} onChange={onChange} />
|
||||
|
||||
<AddItemButton onSubmit={onSubmit} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Item = ({ item, index, onChange, dispatch, first, last }) => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const identifier = `data.work.items[${index}].`;
|
||||
|
||||
return (
|
||||
<div className="my-4 border border-gray-200 rounded p-5">
|
||||
<ItemHeading title={item.title} setOpen={setOpen} isOpen={isOpen} />
|
||||
|
||||
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
|
||||
<Form item={item} onChange={onChange} identifier={identifier} />
|
||||
|
||||
<ItemActions
|
||||
dispatch={dispatch}
|
||||
first={first}
|
||||
identifier={identifier}
|
||||
item={item}
|
||||
last={last}
|
||||
onChange={onChange}
|
||||
type="work"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WorkTab;
|
||||
@ -1,90 +0,0 @@
|
||||
import React, { useState, useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import AppContext from '../../context/AppContext';
|
||||
import TabBar from '../../shared/TabBar';
|
||||
import TemplatesTab from './tabs/Templates';
|
||||
import ColorsTab from './tabs/Colors';
|
||||
import FontsTab from './tabs/Fonts';
|
||||
import ActionsTab from './tabs/Actions';
|
||||
import AboutTab from './tabs/About';
|
||||
import SettingsTab from './tabs/Settings';
|
||||
|
||||
const RightSidebar = () => {
|
||||
const { t } = useTranslation('rightSidebar');
|
||||
|
||||
const context = useContext(AppContext);
|
||||
const { state, dispatch } = context;
|
||||
const { data, theme, settings } = state;
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
key: 'templates',
|
||||
name: t('templates.title'),
|
||||
},
|
||||
{
|
||||
key: 'colors',
|
||||
name: t('colors.title'),
|
||||
},
|
||||
{
|
||||
key: 'fonts',
|
||||
name: t('fonts.title'),
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
name: t('actions.title'),
|
||||
},
|
||||
{
|
||||
key: 'settings',
|
||||
name: t('settings.title'),
|
||||
},
|
||||
{
|
||||
key: 'about',
|
||||
name: t('about.title'),
|
||||
},
|
||||
];
|
||||
const [currentTab, setCurrentTab] = useState(tabs[0].key);
|
||||
|
||||
const onChange = (key, value) => {
|
||||
dispatch({
|
||||
type: 'on_input',
|
||||
payload: {
|
||||
key,
|
||||
value,
|
||||
},
|
||||
});
|
||||
|
||||
dispatch({ type: 'save_data' });
|
||||
};
|
||||
|
||||
const renderTabs = () => {
|
||||
switch (currentTab) {
|
||||
case tabs[0].key:
|
||||
return <TemplatesTab theme={theme} onChange={onChange} />;
|
||||
case tabs[1].key:
|
||||
return <ColorsTab theme={theme} onChange={onChange} />;
|
||||
case tabs[2].key:
|
||||
return <FontsTab theme={theme} onChange={onChange} />;
|
||||
case tabs[3].key:
|
||||
return <ActionsTab data={data} theme={theme} dispatch={dispatch} />;
|
||||
case tabs[4].key:
|
||||
return <SettingsTab settings={settings} onChange={onChange} />;
|
||||
case tabs[5].key:
|
||||
return <AboutTab />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
id="rightSidebar"
|
||||
className="animated slideInRight z-10 py-6 h-screen bg-white col-span-1 shadow-2xl overflow-y-scroll"
|
||||
>
|
||||
<TabBar tabs={tabs} currentTab={currentTab} setCurrentTab={setCurrentTab} />
|
||||
<div className="px-6">{renderTabs()}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RightSidebar;
|
||||
@ -1,121 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
const AboutTab = () => {
|
||||
const { t } = useTranslation('rightSidebar');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="shadow text-center p-5">
|
||||
<h6 className="font-bold text-sm mb-2">{t('about.documentation.heading')}</h6>
|
||||
|
||||
<div className="text-sm">{t('about.documentation.body')}</div>
|
||||
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://docs.rxresu.me/"
|
||||
className="flex justify-center items-center mt-4 bg-gray-600 hover:bg-gray-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">description</i>
|
||||
<span className="text-sm">{t('about.documentation.buttons.documentation')}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<hr className="my-5" />
|
||||
|
||||
<div className="shadow text-center p-5">
|
||||
<h6 className="font-bold text-sm mb-2">{t('about.bugOrFeatureRequest.heading')}</h6>
|
||||
|
||||
<div className="text-sm">{t('about.bugOrFeatureRequest.body')}</div>
|
||||
|
||||
<div className="grid grid-cols-1">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://github.com/AmruthPillai/Reactive-Resume/issues/new"
|
||||
className="mt-4 bg-red-600 hover:bg-red-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">bug_report</i>
|
||||
<span className="text-sm">{t('about.bugOrFeatureRequest.buttons.raiseIssue')}</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="mailto:im.amruth@gmail.com?subject=Feature Request/Reporting a Bug in Reactive Resume: "
|
||||
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">email</i>
|
||||
<span className="text-sm">{t('about.bugOrFeatureRequest.buttons.sendEmail')}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="my-5" />
|
||||
|
||||
<div className="shadow text-center p-5">
|
||||
<h6 className="font-bold text-sm mb-2">{t('about.sourceCode.heading')}</h6>
|
||||
|
||||
<div className="text-sm">{t('about.sourceCode.body')}</div>
|
||||
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://github.com/AmruthPillai/Reactive-Resume"
|
||||
className="flex justify-center items-center mt-4 bg-green-600 hover:bg-green-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">code</i>
|
||||
<span className="text-sm">{t('about.sourceCode.buttons.githubRepo')}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<hr className="my-5" />
|
||||
|
||||
<div className="shadow text-center p-5">
|
||||
<h6 className="font-bold text-sm mb-2">{t('about.license.heading')}</h6>
|
||||
|
||||
<div className="text-sm">{t('about.license.body')}</div>
|
||||
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://github.com/AmruthPillai/Reactive-Resume/blob/master/LICENSE"
|
||||
className="flex justify-center items-center mt-4 bg-gray-600 hover:bg-gray-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">gavel</i>
|
||||
<span className="text-sm">{t('about.license.buttons.mitLicense')}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="mt-5">
|
||||
<p className="text-xs font-gray-600 text-center">
|
||||
<Trans t={t} i18nKey="about.footer.credit">
|
||||
Made with Love by
|
||||
<a
|
||||
className="font-bold hover:underline"
|
||||
href="https://www.amruthpillai.com/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Amruth Pillai
|
||||
</a>
|
||||
</Trans>
|
||||
</p>
|
||||
<p className="text-xs font-gray-600 text-center">{t('about.footer.thanks')}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutTab;
|
||||
@ -1,139 +0,0 @@
|
||||
/* eslint-disable new-cap */
|
||||
/* eslint-disable jsx-a11y/anchor-has-content */
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
|
||||
import React, { useRef, useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import PageContext from '../../../context/PageContext';
|
||||
import { importJson } from '../../../utils';
|
||||
|
||||
const ActionsTab = ({ data, theme, dispatch }) => {
|
||||
const pageContext = useContext(PageContext);
|
||||
const { setPrintDialogOpen } = pageContext;
|
||||
const { t } = useTranslation('rightSidebar');
|
||||
const fileInputRef = useRef(null);
|
||||
|
||||
const exportToJson = () => {
|
||||
const backupObj = { data, theme };
|
||||
const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(JSON.stringify(backupObj))}`;
|
||||
const dlAnchor = document.getElementById('downloadAnchor');
|
||||
dlAnchor.setAttribute('href', dataStr);
|
||||
dlAnchor.setAttribute('download', `RxResumeBackup_${Date.now()}.json`);
|
||||
dlAnchor.click();
|
||||
};
|
||||
|
||||
const loadDemoData = () => {
|
||||
dispatch({ type: 'load_demo_data' });
|
||||
dispatch({ type: 'save_data' });
|
||||
};
|
||||
|
||||
const resetEverything = () => {
|
||||
dispatch({ type: 'reset' });
|
||||
dispatch({ type: 'save_data' });
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="shadow text-center text-sm p-5">{t('actions.disclaimer')}</div>
|
||||
|
||||
<hr className="my-6" />
|
||||
|
||||
<div className="shadow text-center p-5">
|
||||
<h6 className="font-bold text-sm mb-2">{t('actions.importExport.heading')}</h6>
|
||||
|
||||
<p className="text-sm">{t('actions.importExport.body')}</p>
|
||||
|
||||
<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">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => fileInputRef.current.click()}
|
||||
className="bg-gray-600 hover:bg-gray-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">publish</i>
|
||||
<span className="text-sm">{t('actions.importExport.buttons.import')}</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={exportToJson}
|
||||
className="bg-gray-600 hover:bg-gray-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">get_app</i>
|
||||
<span className="text-sm">{t('actions.importExport.buttons.export')}</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="my-6" />
|
||||
|
||||
<div className="shadow text-center p-5">
|
||||
<h6 className="font-bold text-sm mb-2">{t('actions.downloadResume.heading')}</h6>
|
||||
<div className="text-sm">{t('actions.downloadResume.body')}</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPrintDialogOpen(true)}
|
||||
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" />
|
||||
|
||||
<div className="shadow text-center p-5">
|
||||
<h6 className="font-bold text-sm mb-2">{t('actions.loadDemoData.heading')}</h6>
|
||||
|
||||
<div className="text-sm">{t('actions.loadDemoData.body')}</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={loadDemoData}
|
||||
className="mt-4 bg-green-600 hover:bg-green-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">flight_takeoff</i>
|
||||
<span className="text-sm">{t('actions.loadDemoData.buttons.loadData')}</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<hr className="my-6" />
|
||||
|
||||
<div className="shadow text-center p-5">
|
||||
<h6 className="font-bold text-sm mb-2">{t('actions.reset.heading')}</h6>
|
||||
|
||||
<div className="text-sm">{t('actions.reset.body')}</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={resetEverything}
|
||||
className="mt-4 bg-red-600 hover:bg-red-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">refresh</i>
|
||||
<span className="text-sm">{t('actions.reset.buttons.reset')}</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActionsTab;
|
||||
@ -1,95 +0,0 @@
|
||||
import React from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import TextField from '../../../shared/TextField';
|
||||
import { copyToClipboard } from '../../../utils';
|
||||
|
||||
const colorOptions = [
|
||||
'#f44336',
|
||||
'#E91E63',
|
||||
'#9C27B0',
|
||||
'#673AB7',
|
||||
'#3F51B5',
|
||||
'#2196F3',
|
||||
'#03A9F4',
|
||||
'#00BCD4',
|
||||
'#009688',
|
||||
'#4CAF50',
|
||||
'#8BC34A',
|
||||
'#CDDC39',
|
||||
'#FFEB3B',
|
||||
'#FFC107',
|
||||
'#FF9800',
|
||||
'#FF5722',
|
||||
'#795548',
|
||||
'#9E9E9E',
|
||||
'#607D8B',
|
||||
'#FAFAFA',
|
||||
'#212121',
|
||||
'#263238',
|
||||
];
|
||||
|
||||
const ColorsTab = ({ theme, onChange }) => {
|
||||
const { t } = useTranslation('rightSidebar');
|
||||
|
||||
const copyColorToClipboard = color => {
|
||||
copyToClipboard(color);
|
||||
toast(t('colors.clipboardCopyAction', { color }), {
|
||||
bodyClassName: 'text-center text-gray-800 py-2',
|
||||
});
|
||||
onChange('theme.colors.accent', color);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="uppercase tracking-wide text-gray-600 text-xs font-semibold mb-4">
|
||||
{t('colors.colorOptions')}
|
||||
</div>
|
||||
<div className="mb-6 grid grid-cols-8 col-gap-2 row-gap-3">
|
||||
{colorOptions.map(color => (
|
||||
<div
|
||||
key={color}
|
||||
className="cursor-pointer rounded-full border border-gray-200 h-6 w-6 hover:opacity-75"
|
||||
style={{ backgroundColor: color }}
|
||||
onClick={() => copyColorToClipboard(color)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<hr className="my-6" />
|
||||
|
||||
<div className="my-6 grid grid-cols-6 items-end">
|
||||
<div
|
||||
className="rounded-full w-8 h-8 mb-2 border-2"
|
||||
style={{ backgroundColor: theme.colors.primary }}
|
||||
/>
|
||||
<div className="col-span-5">
|
||||
<TextField
|
||||
label={t('colors.primaryColor')}
|
||||
placeholder="#FFFFFF"
|
||||
value={theme.colors.primary}
|
||||
onChange={v => onChange('theme.colors.primary', v)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="my-6 grid grid-cols-6 items-end">
|
||||
<div
|
||||
className="rounded-full w-8 h-8 mb-2 border-2"
|
||||
style={{ backgroundColor: theme.colors.accent }}
|
||||
/>
|
||||
<div className="col-span-5">
|
||||
<TextField
|
||||
label={t('colors.accentColor')}
|
||||
placeholder="#FFFFFF"
|
||||
value={theme.colors.accent}
|
||||
onChange={v => onChange('theme.colors.accent', v)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColorsTab;
|
||||
@ -1,51 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import TextField from '../../../shared/TextField';
|
||||
|
||||
const fontOptions = [
|
||||
'Lato',
|
||||
'Montserrat',
|
||||
'Nunito',
|
||||
'Open Sans',
|
||||
'Raleway',
|
||||
'Rubik',
|
||||
'Source Sans Pro',
|
||||
'Titillium Web',
|
||||
'Ubuntu',
|
||||
];
|
||||
|
||||
const FontsTab = ({ theme, onChange }) => {
|
||||
const { t } = useTranslation('rightSidebar');
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-6">
|
||||
{fontOptions.map(x => (
|
||||
<div
|
||||
key={x}
|
||||
style={{ fontFamily: x }}
|
||||
onClick={() => onChange('theme.font.family', x)}
|
||||
className={`w-full rounded border py-4 shadow text-xl text-center ${
|
||||
theme.font.family === x ? 'border-gray-500' : 'border-transparent'
|
||||
} hover:border-gray-400 cursor-pointer`}
|
||||
>
|
||||
{x}
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div>
|
||||
<TextField
|
||||
className="mb-3"
|
||||
label={t('fonts.fontFamily.label')}
|
||||
placeholder="Avenir Next"
|
||||
value={theme.font.family}
|
||||
onChange={v => onChange('theme.font.family', v)}
|
||||
/>
|
||||
|
||||
<p className="text-gray-800 text-xs">{t('fonts.fontFamily.helpText')}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FontsTab;
|
||||
@ -1,42 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation, Trans } from 'react-i18next';
|
||||
|
||||
import { languages } from '../../../i18n';
|
||||
import Dropdown from '../../../shared/Dropdown';
|
||||
|
||||
const SettingsTab = ({ settings, onChange }) => {
|
||||
const { t } = useTranslation('rightSidebar');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Dropdown
|
||||
label={t('settings.language.label')}
|
||||
value={settings.language}
|
||||
onChange={x => onChange('settings.language', x)}
|
||||
options={languages}
|
||||
optionItem={x => (
|
||||
<option key={x.code} value={x.code}>
|
||||
{x.name}
|
||||
</option>
|
||||
)}
|
||||
/>
|
||||
|
||||
<p className="text-gray-800 text-xs">
|
||||
<Trans t={t} i18nKey="settings.language.helpText">
|
||||
If you would like to help translate the app into your own language, please refer the
|
||||
<a
|
||||
className="text-blue-600 hover:underline"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://docs.rxresu.me/translation/"
|
||||
>
|
||||
Translation Documentation
|
||||
</a>
|
||||
.
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsTab;
|
||||
@ -1,26 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import templates from '../../../templates';
|
||||
|
||||
const TemplatesTab = ({ theme, onChange }) => {
|
||||
return (
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
{templates.map(x => (
|
||||
<div key={x.key} className="text-center" onClick={() => onChange('theme.layout', x.key)}>
|
||||
<img
|
||||
className={`rounded cursor-pointer object-cover border shadow hover:shadow-md ${
|
||||
theme.layout.toLowerCase() === x.key
|
||||
? 'border-gray-600 hover:border-gray-600'
|
||||
: 'border-transparent '
|
||||
} hover:border-gray-500 cursor-pointer`}
|
||||
src={x.preview}
|
||||
alt={x.name}
|
||||
/>
|
||||
<p className="mt-1 text-sm font-medium">{x.name}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TemplatesTab;
|
||||
Reference in New Issue
Block a user