mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-16 17:51:43 +10:00
- refactor sections
- combine resume and metadata contexts
This commit is contained in:
@ -1,14 +1,13 @@
|
||||
import React, { memo } from 'react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { useSelector as useMetadataSelector } from '../../../contexts/MetadataContext';
|
||||
import { useSelector as useResumeSelector } from '../../../contexts/ResumeContext';
|
||||
import { useSelector } from '../../../contexts/ResumeContext';
|
||||
import Onyx from '../../../templates/Onyx';
|
||||
import styles from './Artboard.module.css';
|
||||
|
||||
const Artboard = () => {
|
||||
const { template, layout, colors } = useMetadataSelector((store) => store);
|
||||
const state = useResumeSelector((store) => store);
|
||||
const { id, name } = state;
|
||||
const state = useSelector();
|
||||
const { id, name, metadata } = state;
|
||||
const { template } = metadata;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -18,9 +17,7 @@ const Artboard = () => {
|
||||
</Helmet>
|
||||
|
||||
<div id="artboard" className={styles.container}>
|
||||
{template === 'Onyx' && (
|
||||
<Onyx data={state} layout={layout} colors={colors} />
|
||||
)}
|
||||
{template === 'onyx' && <Onyx data={state} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -14,7 +14,7 @@ const LeftNavbar = () => (
|
||||
|
||||
<hr className="my-6" />
|
||||
|
||||
<div className="grid grid-cols-1 gap-5 text-secondary-dark">
|
||||
<div className="grid grid-cols-1 gap-4 text-secondary-dark">
|
||||
{sections.map((x) => (
|
||||
<SectionIcon
|
||||
key={x.id}
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
import React, { Fragment, memo } from 'react';
|
||||
import { Element } from 'react-scroll';
|
||||
import sections from '../../../data/leftSections';
|
||||
import Awards from '../sections/Awards';
|
||||
import Certifications from '../sections/Certifications';
|
||||
import Education from '../sections/Education';
|
||||
import Hobbies from '../sections/Hobbies';
|
||||
import Languages from '../sections/Languages';
|
||||
import Objective from '../sections/Objective';
|
||||
import Profile from '../sections/Profile';
|
||||
import References from '../sections/References';
|
||||
import Skills from '../sections/Skills';
|
||||
import Social from '../sections/Social';
|
||||
import Work from '../sections/Work';
|
||||
import Awards from './sections/Awards';
|
||||
import Certifications from './sections/Certifications';
|
||||
import Education from './sections/Education';
|
||||
import Hobbies from './sections/Hobbies';
|
||||
import Languages from './sections/Languages';
|
||||
import Objective from './sections/Objective';
|
||||
import Profile from './sections/Profile';
|
||||
import References from './sections/References';
|
||||
import Skills from './sections/Skills';
|
||||
import Social from './sections/Social';
|
||||
import Work from './sections/Work';
|
||||
import LeftNavbar from './LeftNavbar';
|
||||
import styles from './LeftSidebar.module.css';
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../shared/Heading';
|
||||
import List from '../lists/List';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import List from '../../lists/List';
|
||||
|
||||
const Awards = ({ id, name, event }) => {
|
||||
const path = `${id}.items`;
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../shared/Heading';
|
||||
import List from '../lists/List';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import List from '../../lists/List';
|
||||
|
||||
const Certifications = ({ id, name, event }) => {
|
||||
const path = `${id}.items`;
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../shared/Heading';
|
||||
import List from '../lists/List';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import List from '../../lists/List';
|
||||
|
||||
const Education = ({ id, name, event }) => {
|
||||
const path = `${id}.items`;
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../shared/Heading';
|
||||
import List from '../lists/List';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import List from '../../lists/List';
|
||||
|
||||
const Hobbies = ({ id, name, event }) => {
|
||||
const path = `${id}.items`;
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../shared/Heading';
|
||||
import List from '../lists/List';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import List from '../../lists/List';
|
||||
|
||||
const Languages = ({ id, name, event }) => {
|
||||
const path = `${id}.items`;
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../shared/Heading';
|
||||
import Input from '../../shared/Input';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import Input from '../../../shared/Input';
|
||||
|
||||
const Objective = () => {
|
||||
return (
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../shared/Heading';
|
||||
import Input from '../../shared/Input';
|
||||
import PhotoUpload from '../../shared/PhotoUpload';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import Input from '../../../shared/Input';
|
||||
import PhotoUpload from '../../../shared/PhotoUpload';
|
||||
|
||||
const Profile = () => {
|
||||
return (
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../shared/Heading';
|
||||
import List from '../lists/List';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import List from '../../lists/List';
|
||||
|
||||
const References = ({ id, name, event }) => {
|
||||
const path = `${id}.items`;
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../shared/Heading';
|
||||
import List from '../lists/List';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import List from '../../lists/List';
|
||||
|
||||
const Skills = ({ id, name, event }) => {
|
||||
const path = `${id}.items`;
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../shared/Heading';
|
||||
import List from '../lists/List';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import List from '../../lists/List';
|
||||
|
||||
const Social = ({ id, name, event }) => {
|
||||
const path = `${id}.items`;
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../shared/Heading';
|
||||
import List from '../lists/List';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import List from '../../lists/List';
|
||||
|
||||
const Work = ({ id, name, event }) => {
|
||||
const path = `${id}.items`;
|
||||
@ -20,7 +20,7 @@ const List = ({
|
||||
hasDate,
|
||||
event,
|
||||
}) => {
|
||||
const items = useSelector((state) => get(state, path, []));
|
||||
const items = useSelector(path, []);
|
||||
const { emitter } = useContext(ModalContext);
|
||||
|
||||
const handleAdd = () => emitter.emit(event);
|
||||
|
||||
@ -82,7 +82,7 @@ const ListItem = ({
|
||||
size="18px"
|
||||
aria-haspopup="true"
|
||||
onClick={handleClick}
|
||||
className="cursor-pointer"
|
||||
className="cursor-context-menu"
|
||||
/>
|
||||
<Menu
|
||||
keepMounted
|
||||
|
||||
@ -7,7 +7,7 @@ import SyncIndicator from './SyncIndicator';
|
||||
const RightNavbar = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className="grid grid-cols-1 gap-6 text-secondary-dark">
|
||||
<div className="grid grid-cols-1 gap-4 text-secondary-dark">
|
||||
{sections.map((x) => (
|
||||
<SectionIcon
|
||||
key={x.id}
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
import React, { Fragment, memo } from 'react';
|
||||
import { Element } from 'react-scroll';
|
||||
import sections from '../../../data/rightSections';
|
||||
import Layout from '../sections/Layout';
|
||||
import RightNavbar from './RightNavbar';
|
||||
import styles from './RightSidebar.module.css';
|
||||
import Layout from './sections/Layout';
|
||||
import Templates from './sections/Templates';
|
||||
|
||||
const getComponent = (id) => {
|
||||
switch (id) {
|
||||
case 'templates':
|
||||
return Templates;
|
||||
case 'layout':
|
||||
return Layout;
|
||||
default:
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
@apply w-full h-screen overflow-scroll p-8;
|
||||
@apply grid gap-6;
|
||||
@apply grid gap-8;
|
||||
}
|
||||
|
||||
.container::-webkit-scrollbar {
|
||||
|
||||
47
src/components/builder/right/sections/Colors.js
Normal file
47
src/components/builder/right/sections/Colors.js
Normal file
@ -0,0 +1,47 @@
|
||||
import React, { memo } from 'react';
|
||||
import { useDispatch } from '../../../../contexts/ResumeContext';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import Input from '../../../shared/Input';
|
||||
|
||||
const Colors = () => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleClick = (value) => {
|
||||
dispatch({
|
||||
type: 'on_input',
|
||||
payload: {
|
||||
path: 'metadata.colors.primary',
|
||||
value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading>Colors</Heading>
|
||||
|
||||
<Input
|
||||
name="primary"
|
||||
label="Primary Color"
|
||||
placeholder="#FF4081"
|
||||
path="metadata.colors.primary"
|
||||
/>
|
||||
|
||||
<Input
|
||||
name="text"
|
||||
label="Text Color"
|
||||
placeholder="#444444"
|
||||
path="metadata.colors.text"
|
||||
/>
|
||||
|
||||
<Input
|
||||
name="background"
|
||||
label="Background Color"
|
||||
placeholder="#FFFFFF"
|
||||
path="metadata.colors.background"
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Colors);
|
||||
@ -1,12 +1,12 @@
|
||||
import React, { memo } from 'react';
|
||||
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
|
||||
import { useDispatch, useSelector } from '../../../contexts/MetadataContext';
|
||||
import { move, reorder } from '../../../utils';
|
||||
import Heading from '../../shared/Heading';
|
||||
import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
|
||||
import { move, reorder } from '../../../../utils';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import styles from './Layout.module.css';
|
||||
|
||||
const Layout = () => {
|
||||
const blocks = useSelector((state) => state.layout);
|
||||
const blocks = useSelector('metadata.layout');
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onDragEnd = (result) => {
|
||||
@ -22,13 +22,25 @@ const Layout = () => {
|
||||
const items = reorder(blocks[sInd], source.index, destination.index);
|
||||
const newState = [...blocks];
|
||||
newState[sInd] = items;
|
||||
dispatch({ type: 'set_layout', payload: newState });
|
||||
dispatch({
|
||||
type: 'on_input',
|
||||
payload: {
|
||||
path: 'metadata.layout',
|
||||
value: newState,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
const newResult = move(blocks[sInd], blocks[dInd], source, destination);
|
||||
const newState = [...blocks];
|
||||
newState[sInd] = newResult[sInd];
|
||||
newState[dInd] = newResult[dInd];
|
||||
dispatch({ type: 'set_layout', payload: newState });
|
||||
dispatch({
|
||||
type: 'on_input',
|
||||
payload: {
|
||||
path: 'layout',
|
||||
value: newState,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -58,8 +70,8 @@ const Layout = () => {
|
||||
{el.map((item, index) => (
|
||||
<Draggable
|
||||
key={item.id}
|
||||
draggableId={item.id}
|
||||
index={index}
|
||||
draggableId={item.id}
|
||||
>
|
||||
{(dragProvided) => (
|
||||
<div
|
||||
48
src/components/builder/right/sections/Templates.js
Normal file
48
src/components/builder/right/sections/Templates.js
Normal file
@ -0,0 +1,48 @@
|
||||
import cx from 'classnames';
|
||||
import React, { memo } from 'react';
|
||||
import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
|
||||
import templates from '../../../../data/templates';
|
||||
import { handleKeyUp } from '../../../../utils';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import styles from './Templates.module.css';
|
||||
|
||||
const Templates = () => {
|
||||
const dispatch = useDispatch();
|
||||
const template = useSelector('metadata.template');
|
||||
|
||||
const handleClick = (value) => {
|
||||
dispatch({
|
||||
type: 'on_input',
|
||||
payload: {
|
||||
path: 'metadata.template',
|
||||
value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading>Templates</Heading>
|
||||
|
||||
<div className="grid grid-cols-2 gap-8">
|
||||
{templates.map((x) => (
|
||||
<div
|
||||
key={x.id}
|
||||
tabIndex="0"
|
||||
role="button"
|
||||
onKeyUp={(e) => handleKeyUp(e, () => handleClick(x.id))}
|
||||
onClick={() => handleClick(x.id)}
|
||||
className={cx(styles.template, {
|
||||
[styles.selected]: template === x.id,
|
||||
})}
|
||||
>
|
||||
<img loading="lazy" height="240px" src={x.preview} alt={x.name} />
|
||||
<span>{x.name}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Templates);
|
||||
26
src/components/builder/right/sections/Templates.module.css
Normal file
26
src/components/builder/right/sections/Templates.module.css
Normal file
@ -0,0 +1,26 @@
|
||||
.template {
|
||||
@apply w-full rounded flex flex-col items-center;
|
||||
}
|
||||
|
||||
.template:focus {
|
||||
@apply outline-none;
|
||||
}
|
||||
|
||||
.template img {
|
||||
height: 240px;
|
||||
@apply w-full object-cover border-2 border-transparent rounded;
|
||||
@apply transition-opacity duration-200 ease-in-out;
|
||||
}
|
||||
|
||||
.template.selected img {
|
||||
@apply border-2 border-primary;
|
||||
}
|
||||
|
||||
.template:hover img {
|
||||
@apply cursor-pointer opacity-75;
|
||||
@apply transition-opacity duration-200 ease-in-out;
|
||||
}
|
||||
|
||||
.template span {
|
||||
@apply mt-1 text-center text-sm font-semibold;
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import cx from 'classnames';
|
||||
import { get, isFunction } from 'lodash';
|
||||
import { isFunction } from 'lodash';
|
||||
import React, { memo, useEffect, useState } from 'react';
|
||||
import { FaAngleDown } from 'react-icons/fa';
|
||||
import { MdClose } from 'react-icons/md';
|
||||
@ -26,14 +26,14 @@ const Input = ({
|
||||
type = 'text',
|
||||
}) => {
|
||||
const [uuid, setUuid] = useState(null);
|
||||
const stateValue = useSelector((state) => get(state, path));
|
||||
const stateValue = useSelector(path, '');
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
setUuid(uuidv4());
|
||||
}, []);
|
||||
|
||||
value = isFunction(onChange) ? value : stateValue;
|
||||
value = path ? stateValue : value;
|
||||
onChange = isFunction(onChange)
|
||||
? onChange
|
||||
: (e) => {
|
||||
|
||||
@ -14,10 +14,16 @@
|
||||
@apply text-primary opacity-50;
|
||||
}
|
||||
|
||||
.container label input:hover,
|
||||
.container label textarea:hover,
|
||||
.container label select:hover {
|
||||
@apply outline-none border-secondary-dark;
|
||||
}
|
||||
|
||||
.container label input:focus,
|
||||
.container label textarea:focus,
|
||||
.container label select:focus {
|
||||
@apply outline-none border-gray-500;
|
||||
@apply outline-none border-primary;
|
||||
}
|
||||
|
||||
.container label > p {
|
||||
|
||||
@ -3,4 +3,14 @@
|
||||
height: 60px;
|
||||
flex: 0 0 60px;
|
||||
@apply flex items-center justify-center cursor-pointer bg-secondary text-secondary-dark rounded-full;
|
||||
@apply transition-opacity duration-200 ease-in-out;
|
||||
}
|
||||
|
||||
.circle:focus {
|
||||
@apply outline-none;
|
||||
}
|
||||
|
||||
.circle:hover {
|
||||
@apply opacity-75;
|
||||
@apply transition-opacity duration-200 ease-in-out;
|
||||
}
|
||||
|
||||
@ -19,9 +19,9 @@ const SectionIcon = ({ section, containerId, tooltipPlacement }) => {
|
||||
duration={500}
|
||||
containerId={containerId}
|
||||
activeClass="text-primary"
|
||||
className="py-2 cursor-pointer focus:outline-none focus:text-primary hover:text-primary"
|
||||
className="py-2 cursor-pointer focus:outline-none focus:text-primary hover:text-primary animate__animated animate__fadeIn"
|
||||
>
|
||||
<Icon size="16px" />
|
||||
<Icon size="18px" />
|
||||
</Link>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user