Skip to content

Commit

Permalink
Merge pull request #1060 from sashokbg/feat/multiple_work_sections
Browse files Browse the repository at this point in the history
feat: additional work sections
  • Loading branch information
AmruthPillai authored Nov 13, 2022
2 parents 62eb239 + 7bc4a99 commit 89b3539
Show file tree
Hide file tree
Showing 13 changed files with 184 additions and 38 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ node_modules
.DS_Store

# Turbo
.turbo
.turbo

# Intellij
.idea
56 changes: 47 additions & 9 deletions client/components/build/LeftSidebar/LeftSidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Add, Star } from '@mui/icons-material';
import { Button, Divider, IconButton, SwipeableDrawer, Tooltip, useMediaQuery, useTheme } from '@mui/material';
import { Section as SectionRecord } from '@reactive-resume/schema';
import cloneDeep from 'lodash/cloneDeep';
import get from 'lodash/get';
import Link from 'next/link';
import { useTranslation } from 'next-i18next';
import { useMemo } from 'react';
import React, { ReactComponentElement, useMemo } from 'react';
import { validate } from 'uuid';

import Logo from '@/components/shared/Logo';
import { getCustomSections, left } from '@/config/sections';
import { getCustomSections, getSectionsByType, left } from '@/config/sections';
import { setSidebarState } from '@/store/build/buildSlice';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { addSection } from '@/store/resume/resumeSlice';
Expand Down Expand Up @@ -52,7 +53,49 @@ const LeftSidebar = () => {
items: [],
};

dispatch(addSection({ value: newSection }));
dispatch(addSection({ value: newSection, type: 'custom' }));
};

const sectionsList = () => {
const sectionsComponents: Array<ReactComponentElement<any>> = [];

for (const item of left) {
const id = (item as any).id;
const component = (item as any).component;
const type = component.props.type || 'basic';
const addMore = !!component.props.addMore;

sectionsComponents.push(
<section key={id} id={id}>
{component}
</section>
);

if (addMore) {
const additionalSections = getSectionsByType(sections, type);
const elements = [];
for (const element of additionalSections) {
const newId = element.id;

const props = cloneDeep(component.props);
props.path = 'sections.' + newId;
props.name = element.name;
props.isDeletable = true;
props.addMore = false;
props.isDuplicated = true;
const newComponent = React.cloneElement(component, props);

elements.push(
<section key={newId} id={`section-${newId}`}>
{newComponent}
</section>
);
}
sectionsComponents.push(...elements);
}
}

return sectionsComponents;
};

return (
Expand Down Expand Up @@ -100,12 +143,7 @@ const LeftSidebar = () => {
</nav>

<main>
{left.map(({ id, component }) => (
<section key={id} id={id}>
{component}
</section>
))}

{sectionsList()}
{customSections.map(({ id }) => (
<section key={id} id={`section-${id}`}>
<Section path={`sections.${id}`} isEditable isHideable isDeletable />
Expand Down
46 changes: 42 additions & 4 deletions client/components/build/LeftSidebar/sections/Section.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Add } from '@mui/icons-material';
import { Button } from '@mui/material';
import { ListItem } from '@reactive-resume/schema';
import { ListItem, Section as SectionRecord, SectionType } from '@reactive-resume/schema';
import clsx from 'clsx';
import get from 'lodash/get';
import { useTranslation } from 'next-i18next';
Expand All @@ -10,28 +10,34 @@ import Heading from '@/components/shared/Heading';
import List from '@/components/shared/List';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { ModalName, setModalState } from '@/store/modal/modalSlice';
import { duplicateItem } from '@/store/resume/resumeSlice';
import { duplicateItem, duplicateSection } from '@/store/resume/resumeSlice';

import SectionSettings from './SectionSettings';

type Props = {
path: `sections.${string}`;
type?: SectionType;
name?: string;
titleKey?: string;
subtitleKey?: string;
isEditable?: boolean;
isHideable?: boolean;
isDeletable?: boolean;
addMore?: boolean;
isDuplicated?: boolean;
};

const Section: React.FC<Props> = ({
path,
name = 'Section Name',
type = 'basic',
titleKey = 'title',
subtitleKey = 'subtitle',
isEditable = false,
isHideable = false,
isDeletable = false,
addMore = false,
isDuplicated = false,
}) => {
const { t } = useTranslation();

Expand All @@ -42,21 +48,43 @@ const Section: React.FC<Props> = ({

const handleAdd = () => {
const id = path.split('.')[1];
const modal: ModalName = validate(id) ? 'builder.sections.custom' : `builder.${path}`;
let modal: ModalName = validate(id) ? 'builder.sections.custom' : `builder.${path}`;

if (type) {
modal = `builder.sections.${type}`;
}
dispatch(setModalState({ modal, state: { open: true, payload: { path } } }));
};

const handleEdit = (item: ListItem) => {
const id = path.split('.')[1];
const modal: ModalName = validate(id) ? 'builder.sections.custom' : `builder.${path}`;
let modal: ModalName = validate(id) ? 'builder.sections.custom' : `builder.${path}`;

const payload = validate(id) ? { path, item } : { item };

if (isDuplicated) {
modal = `builder.sections.${type}`;
payload.path = path;
}

dispatch(setModalState({ modal, state: { open: true, payload } }));
};

const handleDuplicate = (item: ListItem) => dispatch(duplicateItem({ path: `${path}.items`, value: item }));

const handleDuplicateSection = () => {
const newSection: SectionRecord = {
name: `${heading}`,
type: type,
visible: true,
columns: 2,
items: [],
isDuplicated: true
};

dispatch(duplicateSection({ value: newSection, type }));
};

return (
<>
<Heading path={path} name={name} isEditable={isEditable} isHideable={isHideable} isDeletable={isDeletable} />
Expand All @@ -77,6 +105,16 @@ const Section: React.FC<Props> = ({
{t<string>('builder.common.actions.add', { token: heading })}
</Button>
</footer>

{addMore ? (
<div className="py-6 text-right">
<Button fullWidth variant="outlined" startIcon={<Add />} onClick={handleDuplicateSection}>
{t<string>('builder.common.actions.duplicate')}
</Button>
</div>
) : (
<></>
)}
</>
);
};
Expand Down
49 changes: 37 additions & 12 deletions client/config/sections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
VolunteerActivism,
Work,
} from '@mui/icons-material';
import { Section as SectionRecord } from '@reactive-resume/schema';
import { Section as SectionRecord, SectionType } from '@reactive-resume/schema';
import isEmpty from 'lodash/isEmpty';

import Basics from '@/components/build/LeftSidebar/sections/Basics';
Expand Down Expand Up @@ -60,59 +60,69 @@ export const left: SidebarSection[] = [
{
id: 'work',
icon: <Work />,
component: <Section path="sections.work" titleKey="name" subtitleKey="position" isEditable isHideable />,
component: (
<Section
type={'work'}
addMore={true}
path="sections.work"
titleKey="name"
subtitleKey="position"
isEditable
isHideable
/>
),
},
{
id: 'education',
icon: <School />,
component: <Section path="sections.education" titleKey="institution" subtitleKey="area" isEditable isHideable />,
component: <Section type={"education"} path="sections.education" titleKey="institution" subtitleKey="area" isEditable isHideable />,
},
{
id: 'awards',
icon: <EmojiEvents />,
component: <Section path="sections.awards" titleKey="title" subtitleKey="awarder" isEditable isHideable />,
component: <Section type={"awards"} path="sections.awards" titleKey="title" subtitleKey="awarder" isEditable isHideable />,
},
{
id: 'certifications',
icon: <CardGiftcard />,
component: <Section path="sections.certifications" titleKey="name" subtitleKey="issuer" isEditable isHideable />,
component: <Section type={"certifications"} path="sections.certifications" titleKey="name" subtitleKey="issuer" isEditable isHideable />,
},
{
id: 'publications',
icon: <MenuBook />,
component: <Section path="sections.publications" titleKey="name" subtitleKey="publisher" isEditable isHideable />,
component: <Section type={"publications"} path="sections.publications" titleKey="name" subtitleKey="publisher" isEditable isHideable />,
},
{
id: 'skills',
icon: <Architecture />,
component: <Section path="sections.skills" titleKey="name" subtitleKey="level" isEditable isHideable />,
component: <Section type={"skills"} path="sections.skills" titleKey="name" subtitleKey="level" isEditable isHideable />,
},
{
id: 'languages',
icon: <Language />,
component: <Section path="sections.languages" titleKey="name" subtitleKey="level" isEditable isHideable />,
component: <Section type={"languages"} path="sections.languages" titleKey="name" subtitleKey="level" isEditable isHideable />,
},
{
id: 'interests',
icon: <Sailing />,
component: <Section path="sections.interests" titleKey="name" subtitleKey="keywords" isEditable isHideable />,
component: <Section type={"interests"} path="sections.interests" titleKey="name" subtitleKey="keywords" isEditable isHideable />,
},
{
id: 'volunteer',
icon: <VolunteerActivism />,
component: (
<Section path="sections.volunteer" titleKey="organization" subtitleKey="position" isEditable isHideable />
<Section type={"volunteer"} path="sections.volunteer" titleKey="organization" subtitleKey="position" isEditable isHideable />
),
},
{
id: 'projects',
icon: <Coffee />,
component: <Section path="sections.projects" titleKey="name" subtitleKey="description" isEditable isHideable />,
component: <Section type={"projects"} path="sections.projects" titleKey="name" subtitleKey="description" isEditable isHideable />,
},
{
id: 'references',
icon: <Groups />,
component: <Section path="sections.references" titleKey="name" subtitleKey="relationship" isEditable isHideable />,
component: <Section type={"references"} path="sections.references" titleKey="name" subtitleKey="relationship" isEditable isHideable />,
},
];

Expand Down Expand Up @@ -164,6 +174,21 @@ export const right: SidebarSection[] = [
},
];

export const getSectionsByType = (
sections: Record<string, SectionRecord>,
type: SectionType
): Array<Required<SectionRecord>> => {
if (isEmpty(sections)) return [];

return Object.entries(sections).reduce((acc, [id, section]) => {
if (section.type.startsWith(type) && section.isDuplicated) {
return [...acc, { ...section, id }];
}

return acc;
}, [] as Array<Required<SectionRecord>>);
};

export const getCustomSections = (sections: Record<string, SectionRecord>): Array<Required<SectionRecord>> => {
if (isEmpty(sections)) return [];

Expand Down
3 changes: 2 additions & 1 deletion client/modals/auth/LoginModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@ const LoginModal: React.FC = () => {

<p className="text-xs">
<Trans t={t} i18nKey="modals.auth.login.recover-text">
In case you have forgotten your password, you can <a onClick={handleRecoverAccount}>recover your account here.</a>
In case you have forgotten your password, you can{' '}
<a onClick={handleRecoverAccount}>recover your account here.</a>
</Trans>
</p>
</BaseModal>
Expand Down
10 changes: 5 additions & 5 deletions client/modals/builder/sections/WorkModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { joiResolver } from '@hookform/resolvers/joi';
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
import { Button, TextField } from '@mui/material';
import { DatePicker } from '@mui/x-date-pickers';
import { SectionPath, WorkExperience } from '@reactive-resume/schema';
import { WorkExperience } from '@reactive-resume/schema';
import dayjs from 'dayjs';
import Joi from 'joi';
import get from 'lodash/get';
Expand All @@ -20,8 +20,6 @@ import { addItem, editItem } from '@/store/resume/resumeSlice';

type FormData = WorkExperience;

const path: SectionPath = 'sections.work';

const defaultState: FormData = {
name: '',
position: '',
Expand Down Expand Up @@ -51,9 +49,11 @@ const WorkModal: React.FC = () => {
const dispatch = useAppDispatch();

const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);

const { open: isOpen, payload } = useAppSelector((state) => state.modal['builder.sections.work']);
const path: string = get(payload, 'path', 'sections.work');
const item: FormData = get(payload, 'item', null);

const isEditMode = useMemo(() => !!item, [item]);

const addText = useMemo(() => t<string>('builder.common.actions.add', { token: heading }), [t, heading]);
Expand All @@ -77,7 +77,7 @@ const WorkModal: React.FC = () => {
const handleClose = () => {
dispatch(
setModalState({
modal: `builder.${path}`,
modal: 'builder.sections.work',
state: { open: false },
})
);
Expand Down
3 changes: 2 additions & 1 deletion client/public/locales/en/builder.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"actions": {
"add": "Add New {{token}}",
"delete": "Delete {{token}}",
"edit": "Edit {{token}}"
"edit": "Edit {{token}}",
"duplicate": "Duplicate Section"
},
"columns": {
"heading": "Columns",
Expand Down
1 change: 1 addition & 0 deletions client/store/modal/modalSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type ModalName =
| 'dashboard.import-external'
| 'dashboard.rename-resume'
| 'builder.sections.profile'
| 'builder.sections.work'
| `builder.sections.${string}`;

export type ModalState = {
Expand Down
Loading

0 comments on commit 89b3539

Please sign in to comment.