- implement work experience

- implement education
- show dynamic names in layout
This commit is contained in:
Amruth Pillai
2020-07-08 16:49:26 +05:30
parent bee6a40e9f
commit 922db70107
33 changed files with 822 additions and 169 deletions
+2 -2
View File
@@ -12,9 +12,9 @@ const LeftSidebar = () => {
<LeftNavbar />
<div className={styles.container}>
{sections.map(({ id, component: Component }) => (
{sections.map(({ id, name, event, component: Component }) => (
<Fragment key={id}>
<Component state={state} />
<Component id={id} name={name} event={event} state={state} />
<hr />
</Fragment>
))}
@@ -1,6 +1,12 @@
.container {
z-index: 10;
box-shadow: var(--left-shadow);
-ms-overflow-style: none;
scrollbar-width: none;
@apply w-full h-screen overflow-scroll p-8;
@apply grid gap-6;
}
.container::-webkit-scrollbar {
display: none;
}
+1 -3
View File
@@ -1,9 +1,7 @@
import React from "react";
const EmptyList = () => (
<div className="rounded border border-secondary py-6 text-center">
This list is empty.
</div>
<div className="py-6 opacity-75 text-center">This list is empty.</div>
);
export default EmptyList;
+51 -6
View File
@@ -1,22 +1,67 @@
import { isEmpty } from "lodash";
import React from "react";
import { get, isEmpty } from "lodash";
import moment from "moment";
import React, { useContext } from "react";
import { MdAdd } from "react-icons/md";
import ModalContext from "../../../contexts/ModalContext";
import Button from "../../shared/Button";
import EmptyList from "./EmptyList";
import styles from "./List.module.css";
const List = ({ items, onAdd, children }) => {
const List = ({
path,
items,
title,
titlePath,
subtitle,
subtitlePath,
text,
textPath,
event,
listItemComponent: ListItemComponent,
}) => {
const { emitter } = useContext(ModalContext);
const handleAdd = () => emitter.emit(event);
const handleEdit = (data) => emitter.emit(event, data);
const formatDateRange = (x) =>
`${moment(x.startDate).format("MMMM Y")}${
moment(x.endDate).isValid()
? moment(x.endDate).format("MMMM Y")
: "Present"
}`;
return (
<div className="flex flex-col">
<div className={styles.container}>
{isEmpty(items) ? <EmptyList /> : children}
<div className={styles.list}>
{isEmpty(items) ? (
<EmptyList />
) : (
items.map((x, i) => (
<ListItemComponent
key={x.id}
data={x}
path={path}
title={title || get(x, titlePath, "")}
subtitle={
subtitle || get(x, subtitlePath, "") || formatDateRange(x)
}
text={text || get(x, textPath, "")}
className={styles.listItem}
onEdit={() => handleEdit(x)}
isFirst={i === 0}
isLast={i === items.length - 1}
/>
))
)}
</div>
<Button
outline
icon={MdAdd}
title="Add New"
onClick={onAdd}
onClick={handleAdd}
className="mt-8 ml-auto"
/>
</div>
+20 -2
View File
@@ -1,3 +1,21 @@
.container {
.list {
@apply flex flex-col border border-secondary rounded;
}
}
.list-item {
@apply flex items-center justify-between border-t border-secondary px-8 py-5;
}
.list-item:first-child {
@apply border-t-0;
}
.menu {
@apply opacity-0;
@apply transition-opacity duration-200 ease-in-out;
}
.list-item:hover .menu {
@apply opacity-100;
@apply transition-opacity duration-200 ease-in-out;
}
@@ -3,9 +3,9 @@ import React, { useContext, useState } from "react";
import { IoIosArrowDown, IoIosArrowUp } from "react-icons/io";
import { MdMoreVert } from "react-icons/md";
import ResumeContext from "../../../../contexts/ResumeContext";
import styles from "./SmallListItem.module.css";
import styles from "./DoubleFieldListItem.module.css";
const SmallListItem = ({
const DoubleFieldListItem = ({
title,
subtitle,
path,
@@ -100,4 +100,4 @@ const SmallListItem = ({
);
};
export default SmallListItem;
export default DoubleFieldListItem;
@@ -0,0 +1,106 @@
import { Menu, MenuItem } from "@material-ui/core";
import React, { useContext, useState } from "react";
import { IoIosArrowDown, IoIosArrowUp } from "react-icons/io";
import { MdMoreVert } from "react-icons/md";
import ResumeContext from "../../../../contexts/ResumeContext";
import styles from "./TripleFieldListItem.module.css";
const TripleFieldListItem = ({
title,
subtitle,
text,
path,
data,
isFirst,
isLast,
onEdit,
}) => {
const [anchorEl, setAnchorEl] = useState(null);
const { dispatch } = useContext(ResumeContext);
const handleClick = (event) => setAnchorEl(event.currentTarget);
const handleClose = () => setAnchorEl(null);
const handleEdit = () => {
onEdit();
handleClose();
};
const handleMoveUp = () => {
dispatch({
type: "on_move_item_up",
payload: {
path,
value: data,
},
});
handleClose();
};
const handleMoveDown = () => {
dispatch({
type: "on_move_item_down",
payload: {
path,
value: data,
},
});
handleClose();
};
const handleDelete = () => {
dispatch({
type: "on_delete_item",
payload: {
path,
value: data,
},
});
handleClose();
};
return (
<div className={styles.container}>
<div className="grid">
<span className="font-medium">{title}</span>
<span className="mt-1 text-sm opacity-75">{subtitle}</span>
<span className="w-4/5 mt-6 text-sm opacity-75 truncate">{text}</span>
</div>
<div className={styles.menu}>
<MdMoreVert
size="18px"
aria-haspopup="true"
onClick={handleClick}
className="cursor-pointer"
/>
<Menu
keepMounted
anchorEl={anchorEl}
onClose={handleClose}
open={Boolean(anchorEl)}
>
<div className="flex items-center space-around">
<MenuItem disabled={isFirst} onClick={handleMoveUp}>
<IoIosArrowUp size="18px" />
</MenuItem>
<MenuItem disabled={isLast} onClick={handleMoveDown}>
<IoIosArrowDown size="18px" />
</MenuItem>
</div>
<MenuItem onClick={handleEdit}>Edit</MenuItem>
<MenuItem onClick={handleDelete}>
<span className="text-red-600 font-medium">Delete</span>
</MenuItem>
</Menu>
</div>
</div>
);
};
export default TripleFieldListItem;
@@ -0,0 +1,17 @@
.container {
@apply flex items-center justify-between border-t border-secondary px-8 py-5;
}
.container:first-child {
@apply border-t-0;
}
.menu {
@apply opacity-0;
@apply transition-opacity duration-200 ease-in-out;
}
.container:hover .menu {
@apply opacity-100;
@apply transition-opacity duration-200 ease-in-out;
}
@@ -8,6 +8,7 @@ const RightSidebar = () => {
<div className="flex">
<div className={styles.container}>
<Layout />
<hr />
</div>
<RightNavbar />
@@ -1,5 +1,12 @@
.container {
z-index: 10;
box-shadow: var(--right-shadow);
@apply w-full h-screen p-8;
-ms-overflow-style: none;
scrollbar-width: none;
@apply w-full h-screen overflow-scroll p-8;
@apply grid gap-6;
}
.container::-webkit-scrollbar {
display: none;
}
@@ -0,0 +1,27 @@
import { get } from "lodash";
import React from "react";
import Heading from "../../shared/Heading";
import List from "../lists/List";
import TripleFieldListItem from "../lists/triple/TripleFieldListItem";
const Education = ({ id, name, event, state }) => {
const path = `${id}.items`;
const items = get(state, path, []);
return (
<section>
<Heading>{name}</Heading>
<List
path={path}
items={items}
event={event}
titlePath="institution"
textPath="field"
listItemComponent={TripleFieldListItem}
/>
</section>
);
};
export default Education;
+6 -2
View File
@@ -31,7 +31,11 @@ const Layout = () => {
Block {ind + 1}
</span>
{el.map((item, index) => (
<Draggable key={item} draggableId={item} index={index}>
<Draggable
key={item.id}
draggableId={item.id}
index={index}
>
{(provided) => (
<div
ref={provided.innerRef}
@@ -39,7 +43,7 @@ const Layout = () => {
{...provided.draggableProps}
{...provided.dragHandleProps}
>
{item}
{item.name}
</div>
)}
</Draggable>
@@ -0,0 +1,15 @@
import React from "react";
import Heading from "../../shared/Heading";
import Input from "../../shared/Input";
const Objective = () => {
return (
<section>
<Heading>Objective</Heading>
<Input type="textarea" label="Objective" path="objective" />
</section>
);
};
export default Objective;
+13 -26
View File
@@ -1,38 +1,25 @@
import { get } from "lodash";
import React, { useContext } from "react";
import ModalContext from "../../../contexts/ModalContext";
import React from "react";
import Heading from "../../shared/Heading";
import DoubleFieldListItem from "../lists/double/DoubleFieldListItem";
import List from "../lists/List";
import SmallListItem from "../lists/small/SmallListItem";
const Social = ({ state }) => {
const { emitter, events } = useContext(ModalContext);
const path = "social.items";
const Social = ({ id, name, event, state }) => {
const path = `${id}.items`;
const items = get(state, path, []);
const handleAdd = () => emitter.emit(events.SOCIAL_MODAL);
const handleEdit = (data) => emitter.emit(events.SOCIAL_MODAL, data);
return (
<section>
<Heading>Social Network</Heading>
<Heading>{name}</Heading>
<List items={items} onAdd={handleAdd}>
{items.map((x, i) => (
<SmallListItem
key={x.id}
data={x}
path={path}
title={x.network}
isFirst={i === 0}
subtitle={x.username}
onEdit={() => handleEdit(x)}
isLast={i === items.length - 1}
/>
))}
</List>
<List
path={path}
items={items}
event={event}
titlePath="network"
subtitlePath="username"
listItemComponent={DoubleFieldListItem}
/>
</section>
);
};
+27
View File
@@ -0,0 +1,27 @@
import { get } from "lodash";
import React from "react";
import Heading from "../../shared/Heading";
import List from "../lists/List";
import TripleFieldListItem from "../lists/triple/TripleFieldListItem";
const Work = ({ id, name, event, state }) => {
const path = `${id}.items`;
const items = get(state, path, []);
return (
<section>
<Heading>{name}</Heading>
<List
path={path}
items={items}
event={event}
titlePath="company"
textPath="summary"
listItemComponent={TripleFieldListItem}
/>
</section>
);
};
export default Work;
-4
View File
@@ -7,10 +7,6 @@
@apply bg-primary-dark;
}
.container:focus {
@apply outline-none;
}
.container.outline {
@apply border border-primary bg-inverse text-primary;
}
+58 -13
View File
@@ -1,8 +1,10 @@
import cx from "classnames";
import { get } from "lodash";
import { get, isFunction } from "lodash";
import React, { useContext } from "react";
import { MdClose } from "react-icons/md";
import { v4 as uuidv4 } from "uuid";
import ResumeContext from "../../contexts/ResumeContext";
import { handleKeyDown } from "../../utils";
import styles from "./Input.module.css";
const Input = ({
@@ -17,6 +19,8 @@ const Input = ({
onChange,
className,
placeholder,
onDeleteItem,
showDeleteItemButton,
type = "text",
}) => {
const uuid = uuidv4();
@@ -39,18 +43,59 @@ const Input = ({
<div className={cx(styles.container, className)}>
<label htmlFor={uuid}>
<span>{label}</span>
<input
id={uuid}
name={name}
type={type}
value={value}
onBlur={onBlur}
checked={checked}
onChange={onChange}
placeholder={placeholder}
{...(path && inputProps(path))}
/>
{touched && <p>{error}</p>}
{type === "textarea" ? (
<div className="flex flex-col">
<textarea
id={uuid}
rows="4"
name={name}
type={type}
value={value}
onBlur={onBlur}
checked={checked}
onChange={onChange}
placeholder={placeholder}
{...(path && inputProps(path))}
/>
<p className="mt-2 text-sm opacity-75">
This text block supports{" "}
<a
href="https://www.markdownguide.org/basic-syntax/"
className="text-blue-600"
target="blank"
>
markdown
</a>
.
</p>
</div>
) : (
<div className="relative grid items-center">
<input
id={uuid}
name={name}
type={type}
value={value}
onBlur={onBlur}
checked={checked}
onChange={onChange}
placeholder={placeholder}
{...(path && inputProps(path))}
/>
{showDeleteItemButton && isFunction(onDeleteItem) && (
<MdClose
size="16px"
tabIndex="0"
onClick={onDeleteItem}
onKeyDown={(e) => handleKeyDown(e, onDeleteItem)}
className="absolute right-0 cursor-pointer opacity-50 hover:opacity-75 mx-4"
/>
)}
</div>
)}
{error && touched && <p>{error}</p>}
</label>
</div>
);
+7 -12
View File
@@ -2,26 +2,21 @@
@apply w-full;
}
.container > label {
@apply flex flex-col;
}
.container > label > span {
@apply mb-1 text-secondary-dark font-semibold tracking-wide text-xs uppercase;
}
.container > label > input {
.container label input,
.container label textarea {
@apply py-3 px-4 rounded bg-secondary text-primary border border-secondary;
}
.container > label > input::placeholder {
.container label input::placeholder,
.container label textarea::placeholder {
@apply text-primary opacity-50;
}
.container > label > input:focus {
.container label input:focus,
.container label textarea:focus {
@apply outline-none border-gray-500;
}
.container > label > p {
.container label > p {
@apply mt-1 text-red-600 text-xs;
}
+5 -3
View File
@@ -1,15 +1,17 @@
import { createNanoEvents } from "nanoevents";
import React, { createContext } from "react";
const events = {
const MODAL_EVENTS = {
AUTH_MODAL: "auth_modal",
CREATE_RESUME_MODAL: "create_resume_modal",
SOCIAL_MODAL: "social_modal",
WORK_MODAL: "work_modal",
EDUCATION_MODAL: "education_modal",
};
const emitter = createNanoEvents();
const defaultState = { events, emitter };
const defaultState = { events: MODAL_EVENTS, emitter };
const ModalContext = createContext(defaultState);
@@ -23,4 +25,4 @@ const ModalProvider = ({ children }) => {
export default ModalContext;
export { ModalProvider };
export { ModalProvider, MODAL_EVENTS };
+2
View File
@@ -21,6 +21,7 @@ const ResumeProvider = ({ children }) => {
switch (type) {
case "on_add_item":
delete payload.value.__temp;
items = get(state, payload.path, []);
newState = setWith(
clone(state),
@@ -32,6 +33,7 @@ const ResumeProvider = ({ children }) => {
return newState;
case "on_edit_item":
delete payload.value.__temp;
items = get(state, payload.path);
index = findIndex(items, ["id", payload.value.id]);
newState = setWith(
+1 -1
View File
@@ -10,7 +10,7 @@ const defaultState = {
primaryColor: "#f44336",
backgroundColor: "#FFFFFF",
},
blocks: [leftSections.map((x) => x.id)],
blocks: [leftSections],
setBlocks: () => {},
setFixedBlocks: () => {},
setSupportedBlocks: () => {},
+27 -1
View File
@@ -1,7 +1,12 @@
import { AiOutlineTwitter } from "react-icons/ai";
import { MdPerson } from "react-icons/md";
import { IoMdBriefcase, IoMdDocument } from "react-icons/io";
import { MdPerson, MdSchool } from "react-icons/md";
import Education from "../components/builder/sections/Education";
import Objective from "../components/builder/sections/Objective";
import Profile from "../components/builder/sections/Profile";
import Social from "../components/builder/sections/Social";
import Work from "../components/builder/sections/Work";
import { MODAL_EVENTS } from "../contexts/ModalContext";
export default [
{
@@ -15,5 +20,26 @@ export default [
name: "Social Network",
icon: AiOutlineTwitter,
component: Social,
event: MODAL_EVENTS.SOCIAL_MODAL,
},
{
id: "objective",
name: "Objective",
icon: IoMdDocument,
component: Objective,
},
{
id: "work",
name: "Work Experience",
icon: IoMdBriefcase,
component: Work,
event: MODAL_EVENTS.WORK_MODAL,
},
{
id: "education",
name: "Education",
icon: MdSchool,
component: Education,
event: MODAL_EVENTS.EDUCATION_MODAL,
},
];
+7 -1
View File
@@ -5,6 +5,7 @@ import { isFunction } from "lodash";
import React, { forwardRef, useImperativeHandle } from "react";
import { MdClose } from "react-icons/md";
import Button from "../components/shared/Button";
import { handleKeyDown } from "../utils";
import styles from "./BaseModal.module.css";
const BaseModal = forwardRef(
@@ -33,7 +34,12 @@ const BaseModal = forwardRef(
<div className={styles.modal}>
<div className={styles.title}>
<h2>{title}</h2>
<MdClose size="18" onClick={handleClose} />
<MdClose
size="18"
tabIndex="0"
onClick={handleClose}
onKeyDown={(e) => handleKeyDown(e, handleClose)}
/>
</div>
<div className={styles.body}>{children}</div>
+6 -9
View File
@@ -1,3 +1,4 @@
import { useFormikContext } from "formik";
import { isEmpty, isFunction } from "lodash";
import React, { useContext, useEffect, useRef, useState } from "react";
import { v4 as uuidv4 } from "uuid";
@@ -12,7 +13,6 @@ const DataModal = ({
path,
event,
title,
formik,
onEdit,
onCreate,
children,
@@ -25,6 +25,7 @@ const DataModal = ({
const { emitter } = useContext(ModalContext);
const { dispatch } = useContext(ResumeContext);
const { values, setValues, resetForm, validateForm } = useFormikContext();
useEffect(() => {
const unbind = emitter.on(event, (data) => {
@@ -36,12 +37,12 @@ const DataModal = ({
}, [emitter, event]);
useEffect(() => {
data && formik.setValues(data) && setEditMode(true);
data && setValues(data) && setEditMode(true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data]);
const onSubmit = async (newData) => {
if (isEmpty(await formik.validateForm(newData))) {
if (isEmpty(await validateForm(newData))) {
if (isEditMode) {
if (data !== newData) {
isFunction(onEdit)
@@ -79,15 +80,11 @@ const DataModal = ({
: title.create;
const submitAction = (
<Button
type="submit"
title={getTitle}
onClick={() => onSubmit(formik.values)}
/>
<Button type="submit" title={getTitle} onClick={() => onSubmit(values)} />
);
const onDestroy = () => {
formik.resetForm();
resetForm();
setEditMode(false);
setData(null);
};
+4
View File
@@ -1,7 +1,9 @@
import React, { Fragment } from "react";
import AuthModal from "./AuthModal";
import ResumeModal from "./ResumeModal";
import EducationModal from "./sections/EducationModal";
import SocialModal from "./sections/SocialModal";
import WorkModal from "./sections/WorkModal";
const ModalRegistrar = () => {
return (
@@ -9,6 +11,8 @@ const ModalRegistrar = () => {
<AuthModal />
<ResumeModal />
<SocialModal />
<WorkModal />
<EducationModal />
</Fragment>
);
};
+35 -33
View File
@@ -1,4 +1,5 @@
import { useFormik } from "formik";
import { Formik } from "formik";
import { get } from "lodash";
import React, { useContext } from "react";
import * as Yup from "yup";
import Input from "../components/shared/Input";
@@ -20,44 +21,45 @@ const ResumeModal = () => {
const { events } = useContext(ModalContext);
const { createResume, updateResume } = useContext(DatabaseContext);
const formik = useFormik({
initialValues,
validationSchema,
});
const getFieldProps = (name) => ({
const getFieldProps = (formik, name) => ({
touched: get(formik, `touched.${name}`, false),
error: get(formik, `errors.${name}`, ""),
...formik.getFieldProps(name),
error: formik.errors[name],
});
return (
<DataModal
name="Resume"
formik={formik}
title={{
create: "Create Resume",
edit: "Edit Resume",
}}
onEdit={updateResume}
onCreate={createResume}
event={events.CREATE_RESUME_MODAL}
<Formik
validateOnBlur
initialValues={initialValues}
validationSchema={validationSchema}
>
<Input
type="text"
name="name"
label="Name"
className="mb-8"
placeholder="Full Stack Web Developer"
{...getFieldProps("name")}
/>
{(formik) => (
<DataModal
name="Resume"
title={{
create: "Create Resume",
edit: "Edit Resume",
}}
onEdit={updateResume}
onCreate={createResume}
event={events.CREATE_RESUME_MODAL}
>
<Input
label="Name"
className="mb-8"
placeholder="Full Stack Web Developer"
{...getFieldProps(formik, "name")}
/>
<p>
You are going to be creating a new resume from scratch, but first, let's
give it a name. This can be the name of the role you want to apply for,
or if you're making a resume for a friend, you could call it Alex's
Resume.
</p>
</DataModal>
<p>
You are going to be creating a new resume from scratch, but first,
let's give it a name. This can be the name of the role you want to
apply for, or if you're making a resume for a friend, you could call
it Alex's Resume.
</p>
</DataModal>
)}
</Formik>
);
};
+154
View File
@@ -0,0 +1,154 @@
import { Field, FieldArray, Formik } from "formik";
import { get } from "lodash";
import React, { useContext } from "react";
import { MdAdd } from "react-icons/md";
import * as Yup from "yup";
import Input from "../../components/shared/Input";
import ModalContext from "../../contexts/ModalContext";
import { handleKeyDown } from "../../utils";
import DataModal from "../DataModal";
const initialValues = {
institution: "",
field: "",
degree: "",
gpa: "",
startDate: "",
endDate: "",
courses: [],
__temp: "",
};
const validationSchema = Yup.object().shape({
institution: Yup.string().required("This is a required field."),
field: Yup.string().required("This is a required field."),
degree: Yup.string(),
gpa: Yup.string(),
startDate: Yup.date().required("This is a required field."),
endDate: Yup.date().when(
"startDate",
(startDate, schema) =>
startDate &&
schema.min(startDate, "End Date must be later than Start Date")
),
courses: Yup.array().of(Yup.string().required("This is a required field.")),
__temp: Yup.string().ensure(),
});
const EducationModal = () => {
const { events } = useContext(ModalContext);
const getFieldProps = (formik, name) => ({
touched: get(formik, `touched.${name}`, false),
error: get(formik, `errors.${name}`, ""),
...formik.getFieldProps(name),
});
return (
<Formik
validateOnBlur
initialValues={initialValues}
validationSchema={validationSchema}
>
{(formik) => (
<DataModal
path="education.items"
name="Education"
event={events.EDUCATION_MODAL}
>
<div className="grid grid-cols-2 gap-8">
<Input
label="Institution"
className="col-span-2"
placeholder="Dayananda Sagar College of Engineering"
{...getFieldProps(formik, "institution")}
/>
<Input
label="Field of Study"
className="col-span-2"
placeholder="Computer Science &amp; Engineering"
{...getFieldProps(formik, "field")}
/>
<Input
label="Degree Type"
placeholder="Bachelor's Degree"
{...getFieldProps(formik, "degree")}
/>
<Input
label="GPA"
placeholder="8.8"
{...getFieldProps(formik, "gpa")}
/>
<Input
type="date"
label="Start Date"
placeholder="6th August 208"
{...getFieldProps(formik, "startDate")}
/>
<Input
type="date"
label="End Date"
placeholder="6th August 208"
{...getFieldProps(formik, "endDate")}
/>
<FieldArray
name="courses"
render={(arrayHelpers) => {
const handleClickAdd = () => {
formik.values.__temp &&
arrayHelpers.push(formik.values.__temp);
formik.setFieldValue("__temp", "");
};
return (
<div className="col-span-2">
<label>
<span>Courses</span>
{formik.values.courses &&
formik.values.courses.map((x, i) => (
<Field key={i} name={`courses.${i}`}>
{({ field, meta }) => (
<Input
className="my-1"
showDeleteItemButton
onDeleteItem={() => arrayHelpers.remove(i)}
{...field}
{...meta}
/>
)}
</Field>
))}
<div className="flex items-center">
<Input
placeholder="Algorithms &amp; Data Structures"
{...getFieldProps(formik, "__temp")}
/>
<MdAdd
size="18px"
tabIndex="0"
className="mx-4 cursor-pointer opacity-50 hover:opacity-75"
onKeyDown={(e) => handleKeyDown(e, handleClickAdd)}
onClick={handleClickAdd}
/>
</div>
</label>
</div>
);
}}
/>
</div>
</DataModal>
)}
</Formik>
);
};
export default EducationModal;
+36 -40
View File
@@ -1,4 +1,5 @@
import { useFormik } from "formik";
import { Formik } from "formik";
import { get } from "lodash";
import React, { useContext } from "react";
import * as Yup from "yup";
import Input from "../../components/shared/Input";
@@ -25,52 +26,47 @@ const validationSchema = Yup.object().shape({
const SocialModal = () => {
const { events } = useContext(ModalContext);
const formik = useFormik({
initialValues,
validationSchema,
validateOnBlur: true,
});
const getFieldProps = (name) => ({
const getFieldProps = (formik, name) => ({
touched: get(formik, `touched.${name}`, false),
error: get(formik, `errors.${name}`, ""),
...formik.getFieldProps(name),
touched: formik.touched[name],
error: formik.errors[name],
});
return (
<DataModal
formik={formik}
path="social.items"
name="Social Network"
event={events.SOCIAL_MODAL}
<Formik
validateOnBlur
initialValues={initialValues}
validationSchema={validationSchema}
>
<div className="grid grid-cols-2 gap-8">
<Input
type="text"
name="network"
label="Network"
placeholder="Twitter"
{...getFieldProps("network")}
/>
{(formik) => (
<DataModal
path="social.items"
name="Social Network"
event={events.SOCIAL_MODAL}
>
<div className="grid grid-cols-2 gap-8">
<Input
label="Network"
placeholder="Twitter"
{...getFieldProps(formik, "network")}
/>
<Input
type="text"
name="username"
label="Username"
placeholder="KingOKings"
{...getFieldProps("username")}
/>
<Input
label="Username"
placeholder="KingOKings"
{...getFieldProps(formik, "username")}
/>
<Input
type="text"
name="url"
label="URL"
className="col-span-2"
placeholder="https://twitter.com/KingOKings"
{...getFieldProps("url")}
/>
</div>
</DataModal>
<Input
label="URL"
className="col-span-2"
placeholder="https://twitter.com/KingOKings"
{...getFieldProps(formik, "url")}
/>
</div>
</DataModal>
)}
</Formik>
);
};
+156
View File
@@ -0,0 +1,156 @@
import { Field, FieldArray, Formik } from "formik";
import { get } from "lodash";
import React, { useContext } from "react";
import { MdAdd } from "react-icons/md";
import * as Yup from "yup";
import Input from "../../components/shared/Input";
import ModalContext from "../../contexts/ModalContext";
import { handleKeyDown } from "../../utils";
import DataModal from "../DataModal";
const initialValues = {
company: "",
position: "",
website: "https://",
startDate: "",
endDate: "",
summary: "",
highlights: [],
__temp: "",
};
const validationSchema = Yup.object().shape({
company: Yup.string().required("This is a required field."),
position: Yup.string().required("This is a required field."),
website: Yup.string().url("Must be a valid URL"),
startDate: Yup.date().required("This is a required field."),
endDate: Yup.date().when(
"startDate",
(startDate, schema) =>
startDate &&
schema.min(startDate, "End Date must be later than Start Date")
),
summary: Yup.string().min(10, "Please enter at least 10 characters."),
highlights: Yup.array().of(
Yup.string().required("This is a required field.")
),
__temp: Yup.string().ensure(),
});
const WorkModal = () => {
const { events } = useContext(ModalContext);
const getFieldProps = (formik, name) => ({
touched: get(formik, `touched.${name}`, false),
error: get(formik, `errors.${name}`, ""),
...formik.getFieldProps(name),
});
return (
<Formik
validateOnBlur
initialValues={initialValues}
validationSchema={validationSchema}
>
{(formik) => (
<DataModal
path="work.items"
name="Work Experience"
event={events.WORK_MODAL}
>
<div className="grid grid-cols-2 gap-8">
<Input
label="Company"
className="col-span-2"
placeholder="Postdot Technologies Pvt. Ltd."
{...getFieldProps(formik, "company")}
/>
<Input
label="Position"
placeholder="Full Stack Web Developer"
{...getFieldProps(formik, "position")}
/>
<Input
label="Website"
placeholder="https://example.com/"
{...getFieldProps(formik, "website")}
/>
<Input
type="date"
label="Start Date"
placeholder="6th August 208"
{...getFieldProps(formik, "startDate")}
/>
<Input
type="date"
label="End Date"
placeholder="6th August 208"
{...getFieldProps(formik, "endDate")}
/>
<Input
type="textarea"
label="Summary"
className="col-span-2"
{...getFieldProps(formik, "summary")}
/>
<FieldArray
name="highlights"
render={(arrayHelpers) => {
const handleClickAdd = () => {
formik.values.__temp &&
arrayHelpers.push(formik.values.__temp);
formik.setFieldValue("__temp", "");
};
return (
<div className="col-span-2">
<label>
<span>Highlights</span>
{formik.values.highlights &&
formik.values.highlights.map((x, i) => (
<Field key={i} name={`highlights.${i}`}>
{({ field, meta }) => (
<Input
className="my-1"
showDeleteItemButton
onDeleteItem={() => arrayHelpers.remove(i)}
{...field}
{...meta}
/>
)}
</Field>
))}
<div className="flex items-center">
<Input
placeholder="Worked passionately in customer service in a high volume restaurant."
{...getFieldProps(formik, "__temp")}
/>
<MdAdd
size="18px"
tabIndex="0"
className="mx-4 cursor-pointer opacity-50 hover:opacity-75"
onKeyDown={(e) => handleKeyDown(e, handleClickAdd)}
onClick={handleClickAdd}
/>
</div>
</label>
</div>
);
}}
/>
</div>
</DataModal>
)}
</Formik>
);
};
export default WorkModal;
+12 -6
View File
@@ -33,12 +33,18 @@ const Home = () => {
<Feature title="Kickstarting your career shouldnt come at a cost.">
There are brilliant alternatives to this app like{" "}
<a href="/">Novoresume</a> and <a href="/">Zety</a>, but they come
at a cost, mainly because of the time the developers and the
marketing they had to incur to make the product. This app might not
be better than them, but it does cater to people who are just not in
a position to pay hundreds of dollars to create a resume to
bootstrap their career.
<a href="/" target="blank">
Novoresume
</a>{" "}
and{" "}
<a href="/" target="blank">
Zety
</a>
, but they come at a cost, mainly because of the time the developers
and the marketing they had to incur to make the product. This app
might not be better than them, but it does cater to people who are
just not in a position to pay hundreds of dollars to create a resume
to bootstrap their career.
</Feature>
</div>
+8
View File
@@ -29,6 +29,14 @@ section {
@apply grid grid-cols-1 gap-8;
}
label {
@apply flex flex-col;
}
label > span:first-child {
@apply mb-1 text-secondary-dark font-semibold tracking-wide text-xs uppercase;
}
#artboard {
color: #444;
}
+1 -1
View File
@@ -9,7 +9,7 @@ export const transformCollectionSnapshot = (snapshot, setData) => {
};
export const handleKeyDown = (event, action) => {
event.which === 13 && action();
(event.which === 13 || event.which === 32) && action();
};
export const isFileImage = (file) => {