clear everything

This commit is contained in:
Amruth Pillai
2022-02-28 20:23:10 +01:00
parent ff892e3ea5
commit 2175256310
301 changed files with 0 additions and 84972 deletions
-47
View File
@@ -1,47 +0,0 @@
/* eslint-disable react/no-danger */
import { Helmet } from 'react-helmet';
import { graphql } from 'gatsby';
import React from 'react';
import Hero from './landing/Hero';
import * as styles from './Blog.module.css';
import Wrapper from './shared/Wrapper';
export default function Template({ data }) {
const { markdownRemark } = data;
const { frontmatter, html } = markdownRemark;
return (
<Wrapper>
<div className="my-24 container">
<Hero />
<Helmet>
<title>{frontmatter.title} | Reactive Resume</title>
<link
rel="canonical"
href={`https://rxresu.me/blog/${frontmatter.slug}`}
/>
</Helmet>
<h1 className="mt-16 text-3xl">{frontmatter.title}</h1>
<h2>{frontmatter.date}</h2>
<div
className={styles.container}
dangerouslySetInnerHTML={{ __html: html }}
/>
</div>
</Wrapper>
);
}
export const pageQuery = graphql`
query ($slug: String!) {
markdownRemark(frontmatter: { slug: { eq: $slug } }) {
html
frontmatter {
date(formatString: "MMMM DD, YYYY")
slug
title
}
}
}
`;
-11
View File
@@ -1,11 +0,0 @@
.container {
@apply leading-loose my-8;
}
.container p {
@apply my-4;
}
.container h4 {
@apply font-medium text-xl;
}
-40
View File
@@ -1,40 +0,0 @@
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
import React, { memo } from 'react';
import * as styles from './Artboard.module.css';
import { useSelector } from '../../../contexts/ResumeContext';
import Castform from '../../../templates/Castform';
import Celebi from '../../../templates/Celebi';
import Gengar from '../../../templates/Gengar';
import Glalie from '../../../templates/Glalie';
import Onyx from '../../../templates/Onyx';
import Pikachu from '../../../templates/Pikachu';
const Artboard = () => {
const state = useSelector();
const { t } = useTranslation();
const { id, name, metadata } = state;
const { template } = metadata;
return (
<>
<Helmet>
<title>
{name} | {t('shared.appName')}
</title>
<link rel="canonical" href={`https://rxresu.me/app/builder/${id}`} />
</Helmet>
<div id="page" className={styles.container}>
{template === 'onyx' && <Onyx data={state} />}
{template === 'pikachu' && <Pikachu data={state} />}
{template === 'gengar' && <Gengar data={state} />}
{template === 'castform' && <Castform data={state} />}
{template === 'glalie' && <Glalie data={state} />}
{template === 'celebi' && <Celebi data={state} />}
</div>
</>
);
};
export default memo(Artboard);
@@ -1,11 +0,0 @@
@media screen {
.container {
width: 210mm;
height: 297mm;
overflow: scroll;
margin: 2rem auto;
box-shadow: var(--shadow);
@apply bg-white rounded;
}
}
-44
View File
@@ -1,44 +0,0 @@
import { Link } from 'gatsby';
import { Tooltip } from '@material-ui/core';
import { useTranslation } from 'react-i18next';
import React, { memo } from 'react';
import Avatar from '../../shared/Avatar';
import Logo from '../../shared/Logo';
import * as styles from './LeftNavbar.module.css';
import SectionIcon from '../../shared/SectionIcon';
import sections from '../../../data/leftSections';
const LeftNavbar = () => {
const { t } = useTranslation();
return (
<div className={styles.container}>
<Tooltip title={t('builder.tooltips.backToDashboard')} placement="right">
<div>
<Link to="/app/dashboard">
<Logo size="40px" />
</Link>
</div>
</Tooltip>
<hr className="my-6" />
<div className="grid grid-cols-1 gap-4 text-primary-500">
{sections.map((x) => (
<SectionIcon
key={x.id}
section={x}
containerId="LeftSidebar"
tooltipPlacement="right"
/>
))}
</div>
<hr className="mt-auto my-6" />
<Avatar />
</div>
);
};
export default memo(LeftNavbar);
@@ -1,7 +0,0 @@
.container {
width: 75px;
z-index: 20;
box-shadow: var(--left-shadow);
@apply px-4 py-6 h-screen flex flex-col items-center;
}
@@ -1,77 +0,0 @@
import { Element } from 'react-scroll';
import React, { Fragment, memo, useContext } from 'react';
import * as styles from './LeftSidebar.module.css';
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 LeftNavbar from './LeftNavbar';
import Objective from './sections/Objective';
import Profile from './sections/Profile';
import Projects from './sections/Projects';
import References from './sections/References';
import Skills from './sections/Skills';
import Social from './sections/Social';
import Work from './sections/Work';
import sections from '../../../data/leftSections';
import SettingsContext from '../../../contexts/SettingsContext';
const getComponent = (id) => {
switch (id) {
case 'profile':
return Profile;
case 'social':
return Social;
case 'objective':
return Objective;
case 'work':
return Work;
case 'education':
return Education;
case 'projects':
return Projects;
case 'awards':
return Awards;
case 'certifications':
return Certifications;
case 'skills':
return Skills;
case 'hobbies':
return Hobbies;
case 'languages':
return Languages;
case 'references':
return References;
default:
throw new Error();
}
};
const SidebarSection = ({ id, event }) => {
const Component = getComponent(id);
return (
<Fragment key={id}>
<Element name={id}>
<Component id={id} event={event} />
</Element>
<hr />
</Fragment>
);
};
const LeftSidebar = () => {
const { isSideBarOpen } = useContext(SettingsContext);
return (
<div className="flex">
<LeftNavbar />
{isSideBarOpen && (
<div id="LeftSidebar" className={styles.container}>
{sections.map(SidebarSection)}
</div>
)}
</div>
);
};
export default memo(LeftSidebar);
@@ -1,6 +0,0 @@
.container {
z-index: 10;
@apply w-full h-screen overflow-scroll p-8;
@apply grid gap-8;
}
@@ -1,32 +0,0 @@
import { useTranslation } from 'react-i18next';
import React, { memo } from 'react';
import Heading from '../../../shared/Heading';
import Input from '../../../shared/Input';
import List from '../../lists/List';
const Awards = ({ id, event }) => {
const path = `${id}.items`;
const { t } = useTranslation();
return (
<section>
<Heading id={id} />
<Input
name="heading"
label={t('builder.sections.heading')}
path={`${id}.heading`}
/>
<List
path={path}
event={event}
titlePath="title"
subtitlePath="awarder"
textPath="summary"
/>
</section>
);
};
export default memo(Awards);
@@ -1,32 +0,0 @@
import { useTranslation } from 'react-i18next';
import React, { memo } from 'react';
import Heading from '../../../shared/Heading';
import Input from '../../../shared/Input';
import List from '../../lists/List';
const Certifications = ({ id, event }) => {
const path = `${id}.items`;
const { t } = useTranslation();
return (
<section>
<Heading id={id} />
<Input
name="heading"
label={t('builder.sections.heading')}
path={`${id}.heading`}
/>
<List
path={path}
event={event}
titlePath="title"
subtitlePath="issuer"
textPath="summary"
/>
</section>
);
};
export default memo(Certifications);
@@ -1,32 +0,0 @@
import { useTranslation } from 'react-i18next';
import React, { memo } from 'react';
import Heading from '../../../shared/Heading';
import Input from '../../../shared/Input';
import List from '../../lists/List';
const Education = ({ id, event }) => {
const path = `${id}.items`;
const { t } = useTranslation();
return (
<section>
<Heading id={id} />
<Input
name="heading"
label={t('builder.sections.heading')}
path={`${id}.heading`}
/>
<List
hasDate
path={path}
event={event}
titlePath="institution"
textPath="field"
/>
</section>
);
};
export default memo(Education);
@@ -1,26 +0,0 @@
import { useTranslation } from 'react-i18next';
import React, { memo } from 'react';
import Heading from '../../../shared/Heading';
import Input from '../../../shared/Input';
import List from '../../lists/List';
const Hobbies = ({ id, event }) => {
const path = `${id}.items`;
const { t } = useTranslation();
return (
<section>
<Heading id={id} />
<Input
name="heading"
label={t('builder.sections.heading')}
path={`${id}.heading`}
/>
<List path={path} event={event} titlePath="name" />
</section>
);
};
export default memo(Hobbies);
@@ -1,26 +0,0 @@
import { useTranslation } from 'react-i18next';
import React, { memo } from 'react';
import Heading from '../../../shared/Heading';
import Input from '../../../shared/Input';
import List from '../../lists/List';
const Languages = ({ id, event }) => {
const path = `${id}.items`;
const { t } = useTranslation();
return (
<section>
<Heading id={id} />
<Input
name="heading"
label={t('builder.sections.heading')}
path={`${id}.heading`}
/>
<List path={path} event={event} titlePath="name" subtitlePath="fluency" />
</section>
);
};
export default memo(Languages);
@@ -1,28 +0,0 @@
import { useTranslation } from 'react-i18next';
import React, { memo } from 'react';
import Heading from '../../../shared/Heading';
import Input from '../../../shared/Input';
const Objective = ({ id }) => {
const { t } = useTranslation();
return (
<section>
<Heading id={id} />
<Input
name="heading"
label={t('builder.sections.heading')}
path={`${id}.heading`}
/>
<Input
type="textarea"
label={t('shared.forms.summary')}
path="objective.body"
/>
</section>
);
};
export default memo(Objective);
@@ -1,95 +0,0 @@
import { useTranslation } from 'react-i18next';
import React, { memo } from 'react';
import Heading from '../../../shared/Heading';
import Input from '../../../shared/Input';
import PhotoUpload from '../../../shared/PhotoUpload';
const Profile = ({ id }) => {
const { t } = useTranslation();
return (
<section>
<Heading id={id} />
<Input
name="heading"
label={t('builder.sections.heading')}
path={`${id}.heading`}
/>
<PhotoUpload />
<div className="grid grid-cols-2 gap-6">
<Input
name="firstName"
label={t('builder.profile.firstName')}
path="profile.firstName"
/>
<Input
name="lastName"
label={t('builder.profile.lastName')}
path="profile.lastName"
/>
</div>
<Input
name="subtitle"
label={t('shared.forms.subtitle')}
path="profile.subtitle"
/>
<Input
type="date"
name="birthDate"
label={t('builder.profile.birthDate')}
path="profile.birthDate"
/>
<hr />
<Input
name="addressLine1"
label={t('builder.profile.address.line1')}
path="profile.address.line1"
/>
<Input
name="addressLine2"
label={t('builder.profile.address.line2')}
path="profile.address.line2"
/>
<div className="grid grid-cols-2 gap-6">
<Input
name="city"
label={t('builder.profile.address.city')}
path="profile.address.city"
/>
<Input
name="pincode"
label={t('builder.profile.address.pincode')}
path="profile.address.pincode"
/>
</div>
<hr />
<Input
name="phone"
label={t('shared.forms.phone')}
path="profile.phone"
/>
<Input
name="website"
label={t('shared.forms.website')}
path="profile.website"
/>
<Input
name="email"
label={t('shared.forms.email')}
path="profile.email"
/>
</section>
);
};
export default memo(Profile);
@@ -1,32 +0,0 @@
import { useTranslation } from 'react-i18next';
import React, { memo } from 'react';
import Heading from '../../../shared/Heading';
import Input from '../../../shared/Input';
import List from '../../lists/List';
const Projects = ({ id, event }) => {
const path = `${id}.items`;
const { t } = useTranslation();
return (
<section>
<Heading id={id} />
<Input
name="heading"
label={t('builder.sections.heading')}
path={`${id}.heading`}
/>
<List
path={path}
event={event}
titlePath="title"
subtitlePath="link"
textPath="summary"
/>
</section>
);
};
export default memo(Projects);
@@ -1,32 +0,0 @@
import { useTranslation } from 'react-i18next';
import React, { memo } from 'react';
import Heading from '../../../shared/Heading';
import Input from '../../../shared/Input';
import List from '../../lists/List';
const References = ({ id, event }) => {
const path = `${id}.items`;
const { t } = useTranslation();
return (
<section>
<Heading id={id} />
<Input
name="heading"
label={t('builder.sections.heading')}
path={`${id}.heading`}
/>
<List
path={path}
event={event}
titlePath="name"
subtitlePath="position"
textPath="summary"
/>
</section>
);
};
export default memo(References);
@@ -1,26 +0,0 @@
import { useTranslation } from 'react-i18next';
import React, { memo } from 'react';
import Heading from '../../../shared/Heading';
import Input from '../../../shared/Input';
import List from '../../lists/List';
const Skills = ({ id, event }) => {
const path = `${id}.items`;
const { t } = useTranslation();
return (
<section>
<Heading id={id} />
<Input
name="heading"
label={t('builder.sections.heading')}
path={`${id}.heading`}
/>
<List path={path} event={event} titlePath="name" subtitlePath="level" />
</section>
);
};
export default memo(Skills);
@@ -1,31 +0,0 @@
import { useTranslation } from 'react-i18next';
import React, { memo } from 'react';
import Heading from '../../../shared/Heading';
import Input from '../../../shared/Input';
import List from '../../lists/List';
const Social = ({ id, event }) => {
const path = `${id}.items`;
const { t } = useTranslation();
return (
<section>
<Heading id={id} />
<Input
name="heading"
label={t('builder.sections.heading')}
path={`${id}.heading`}
/>
<List
path={path}
event={event}
titlePath="network"
subtitlePath="username"
/>
</section>
);
};
export default memo(Social);
@@ -1,32 +0,0 @@
import { useTranslation } from 'react-i18next';
import React, { memo } from 'react';
import Heading from '../../../shared/Heading';
import Input from '../../../shared/Input';
import List from '../../lists/List';
const Work = ({ id, event }) => {
const path = `${id}.items`;
const { t } = useTranslation();
return (
<section>
<Heading id={id} />
<Input
name="heading"
label={t('builder.sections.heading')}
path={`${id}.heading`}
/>
<List
hasDate
path={path}
event={event}
titlePath="company"
textPath="summary"
/>
</section>
);
};
export default memo(Work);
-12
View File
@@ -1,12 +0,0 @@
import { useTranslation } from 'react-i18next';
import React, { memo } from 'react';
const EmptyList = () => {
const { t } = useTranslation();
return (
<div className="py-6 opacity-75 text-center">{t('builder.emptyList')}</div>
);
};
export default memo(EmptyList);
-115
View File
@@ -1,115 +0,0 @@
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
import { MdAdd } from 'react-icons/md';
import { get, isEmpty } from 'lodash';
import { useTranslation } from 'react-i18next';
import React, { memo, useContext } from 'react';
import * as styles from './List.module.css';
import { formatDateRange, reorder } from '../../../utils';
import { useDispatch, useSelector } from '../../../contexts/ResumeContext';
import Button from '../../shared/Button';
import EmptyList from './EmptyList';
import ListItem from './ListItem';
import ModalContext from '../../../contexts/ModalContext';
const List = ({
path,
title,
titlePath,
subtitle,
subtitlePath,
text,
textPath,
hasDate,
event,
}) => {
const { t, i18n } = useTranslation();
const items = useSelector(path, []);
const { emitter } = useContext(ModalContext);
const dispatch = useDispatch();
const handleAdd = () => emitter.emit(event);
const handleEdit = (data) => emitter.emit(event, data);
const onDragEnd = (result) => {
const { source, destination } = result;
if (!destination) {
return;
}
const sourceDroppableId = source.droppableId;
const destinationDroppableId = destination.droppableId;
if (sourceDroppableId !== destinationDroppableId) {
return;
}
if (source.index === destination.index) {
return;
}
const itemsReordered = reorder(items, source.index, destination.index);
dispatch({
type: 'on_input',
payload: {
path,
value: itemsReordered,
},
});
};
return (
<div className="flex flex-col">
<div className={styles.list}>
{isEmpty(items) ? (
<EmptyList />
) : (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId={path}>
{(dropProvided) => (
<div
ref={dropProvided.innerRef}
{...dropProvided.droppableProps}
>
{items.map((x, i) => (
<ListItem
key={x.id}
data={x}
path={path}
title={title || get(x, titlePath, '')}
subtitle={
subtitle ||
get(x, subtitlePath, '') ||
(hasDate &&
formatDateRange(
{
startDate: x.startDate,
endDate: x.endDate,
language: i18n.language,
},
t,
))
}
text={text || get(x, textPath, '')}
onEdit={() => handleEdit(x)}
isFirst={i === 0}
isLast={i === items.length - 1}
index={i}
/>
))}
{dropProvided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
)}
</div>
<Button outline icon={MdAdd} onClick={handleAdd} className="mt-8 ml-auto">
{t('shared.buttons.add')}
</Button>
</div>
);
};
export default memo(List);
@@ -1,3 +0,0 @@
.list {
@apply flex flex-col border border-primary-200 rounded;
}
-173
View File
@@ -1,173 +0,0 @@
import { Draggable } from 'react-beautiful-dnd';
import { IoIosArrowDown, IoIosArrowUp } from 'react-icons/io';
import { MdMoreVert } from 'react-icons/md';
import { Menu, MenuItem } from '@material-ui/core';
import { useTranslation } from 'react-i18next';
import React, { memo, useState } from 'react';
import Switch from '@material-ui/core/Switch';
import { useDispatch } from '../../../contexts/ResumeContext';
import * as styles from './ListItem.module.css';
const dataTestIdPrefix = 'list-item-';
const ListItem = ({
title,
subtitle,
text,
path,
data,
isFirst,
isLast,
onEdit,
index,
}) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const [anchorEl, setAnchorEl] = useState(null);
const [deleteClickCount, setDeleteClickCount] = useState(0);
const handleClick = (event) => setAnchorEl(event.currentTarget);
const toggleSwitchState = 'isVisible' in data ? data.isVisible : true;
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();
};
const handleToggleUse = () => {
dispatch({
type: 'on_toggle_use_item',
payload: {
path,
value: data,
},
});
};
const checkConfirmationAndDelte = () => {
if (deleteClickCount === 1) {
handleDelete();
} else if (deleteClickCount < 1) {
setDeleteClickCount(deleteClickCount + 1);
}
};
return (
<Draggable draggableId={data.id} index={index}>
{(dragProvided) => (
<div
ref={dragProvided.innerRef}
className={styles.listItem}
data-testid={`${dataTestIdPrefix}${path}`}
{...dragProvided.draggableProps}
{...dragProvided.dragHandleProps}
>
<div className="grid">
<span className="font-medium truncate">{title}</span>
{subtitle && (
<span className="mt-1 text-sm opacity-75 truncate">
{subtitle}
</span>
)}
{text && (
<span className="w-4/5 mt-5 text-sm opacity-75 truncate">
{text}
</span>
)}
</div>
<div className={styles.toggleButton}>
<Switch
checked={toggleSwitchState}
onChange={handleToggleUse}
color="primary"
name="toggleSwitch"
className="toggle-button"
inputProps={{ 'aria-label': 'primary checkbox' }}
/>
<div className={styles.menu}>
<MdMoreVert
size="18px"
aria-haspopup="true"
onClick={handleClick}
className="cursor-context-menu"
/>
<Menu
keepMounted
anchorEl={anchorEl}
onClose={() => {
setDeleteClickCount(0);
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}>
{t('shared.buttons.edit')}
</MenuItem>
<MenuItem onClick={checkConfirmationAndDelte}>
<span className="text-red-600 font-medium">
{deleteClickCount
? t('shared.buttons.confirmation')
: t('shared.buttons.delete')}
</span>
</MenuItem>
</Menu>
</div>
</div>
</div>
)}
</Draggable>
);
};
export default memo(ListItem);
export { dataTestIdPrefix };
@@ -1,21 +0,0 @@
.list-item {
@apply flex items-center justify-between border-t border-primary-200 px-6 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;
}
.toggle-button {
@apply flex items-center justify-center
}
@@ -1,39 +0,0 @@
import React, { memo, useContext } from 'react';
import { BsBoxArrowRight, BsBoxArrowLeft } from 'react-icons/bs';
import * as styles from './RightNavbar.module.css';
import SectionIcon from '../../shared/SectionIcon';
import SyncIndicator from './SyncIndicator';
import sections from '../../../data/rightSections';
import SettingsContext from '../../../contexts/SettingsContext';
const SideBarToggleIcon = ({ className }) => {
const { isSideBarOpen, setSideBarToggle } = useContext(SettingsContext);
const ToggleIcon = isSideBarOpen ? BsBoxArrowRight : BsBoxArrowLeft;
return (
<button
className={className}
onClick={() => setSideBarToggle(!isSideBarOpen)}
>
<ToggleIcon size="18px" />
</button>
);
};
const RightNavbar = () => (
<div className={styles.container}>
<div className="grid grid-cols-1 gap-4 text-primary-500">
{sections.map((x) => (
<SectionIcon
key={x.id}
section={x}
containerId="RightSidebar"
tooltipPlacement="left"
/>
))}
</div>
<SideBarToggleIcon className="mt-auto" />
<hr className=" my-6" />
<SyncIndicator />
</div>
);
export default memo(RightNavbar);
@@ -1,7 +0,0 @@
.container {
width: 75px;
z-index: 20;
box-shadow: var(--right-shadow);
@apply px-4 py-6 h-screen flex flex-col items-center;
}
@@ -1,65 +0,0 @@
import { Element } from 'react-scroll';
import React, { Fragment, memo, useContext } from 'react';
import * as styles from './RightSidebar.module.css';
import About from './sections/About';
import Actions from './sections/Actions';
import Colors from './sections/Colors';
import FontSize from './sections/FontSize';
import Fonts from './sections/Fonts';
import Layout from './sections/Layout';
import RightNavbar from './RightNavbar';
import Settings from './sections/Settings';
import Templates from './sections/Templates';
import sections from '../../../data/rightSections';
import SettingsContext from '../../../contexts/SettingsContext';
const getComponent = (id) => {
switch (id) {
case 'templates':
return Templates;
case 'layout':
return Layout;
case 'colors':
return Colors;
case 'fonts':
return Fonts;
case 'actions':
return Actions;
case 'settings':
return Settings;
case 'about':
return About;
case 'font-size':
return FontSize;
default:
throw new Error();
}
};
const SidebarSection = ({ id, event }) => {
const Component = getComponent(id);
return (
<Fragment key={id}>
<Element name={id}>
<Component id={id} event={event} />
</Element>
<hr />
</Fragment>
);
};
const RightSidebar = () => {
const { isSideBarOpen } = useContext(SettingsContext);
return (
<div className="flex">
{isSideBarOpen && (
<div id="RightSidebar" className={styles.container}>
{sections.map(SidebarSection)}
</div>
)}
<RightNavbar />
</div>
);
};
export default memo(RightSidebar);
@@ -1,6 +0,0 @@
.container {
z-index: 10;
@apply w-full h-screen overflow-scroll p-8;
@apply grid gap-8;
}
@@ -1,16 +0,0 @@
import { MdSync } from 'react-icons/md';
import React, { memo, useContext } from 'react';
import cx from 'classnames';
import DatabaseContext from '../../../contexts/DatabaseContext';
const SyncIndicator = () => {
const { isUpdating } = useContext(DatabaseContext);
return (
<div className="text-4xl">
<MdSync className={cx({ spin: isUpdating })} />
</div>
);
};
export default memo(SyncIndicator);
@@ -1,107 +0,0 @@
import { FaBug, FaCoffee, FaExternalLinkAlt } from 'react-icons/fa';
import { MdCode } from 'react-icons/md';
import { Trans, useTranslation } from 'react-i18next';
import { graphql, useStaticQuery } from 'gatsby';
import React, { memo } from 'react';
import Button from '../../../shared/Button';
import Heading from '../../../shared/Heading';
import * as styles from './About.module.css';
const About = ({ id }) => {
const { t } = useTranslation();
const { site } = useStaticQuery(graphql`
query {
site {
siteMetadata {
version
}
}
}
`);
return (
<section>
<Heading id={id} />
<div className={styles.container}>
<h5>{t('builder.about.donate.heading')}</h5>
<p className="leading-loose">
<Trans t={t} i18nKey="builder.about.donate.text">
A<span className="font-bold">B</span>C
</Trans>
</p>
<div className="mt-4 flex">
<a
href="https://www.buymeacoffee.com/AmruthPillai"
rel="noreferrer"
target="_blank"
>
<Button icon={FaCoffee}>{t('builder.about.donate.button')}</Button>
</a>
</div>
</div>
<div className={styles.container}>
<h5>{t('builder.about.bugFeature.heading')}</h5>
<p className="leading-loose">{t('builder.about.bugFeature.text')}</p>
<div className="mt-4 flex">
<a
href="https://github.com/AmruthPillai/Reactive-Resume/issues/new"
rel="noreferrer"
target="_blank"
>
<Button icon={FaBug}>{t('builder.about.bugFeature.button')}</Button>
</a>
</div>
</div>
<div className={styles.container}>
<h5>{t('builder.about.appreciate.heading')}</h5>
<p className="leading-loose">{t('builder.about.appreciate.text')}</p>
<div className="mt-4 flex">
<a href="https://amruthpillai.com" rel="noreferrer" target="_blank">
<Button icon={FaExternalLinkAlt}>amruthpillai.com</Button>
</a>
</div>
</div>
<div className={styles.container}>
<h5>{t('builder.about.sourceCode.heading')}</h5>
<p className="leading-loose">{t('builder.about.sourceCode.text')}</p>
<div className="mt-4 flex">
<a
href="https://github.com/AmruthPillai/Reactive-Resume"
rel="noreferrer"
target="_blank"
>
<Button icon={MdCode}>
{t('builder.about.sourceCode.button')}
</Button>
</a>
</div>
</div>
<div className="my-4 text-center opacity-50 text-sm flex flex-col items-center justify-center">
<Trans t={t} i18nKey="builder.about.footer">
A
<a href="https://amruthpillai.com" rel="noreferrer" target="_blank">
B
</a>
</Trans>
<span className="mt-2 font-medium">v{site.siteMetadata.version}</span>
</div>
</section>
);
};
export default memo(About);
@@ -1,11 +0,0 @@
.container {
@apply bg-primary-100 rounded grid gap-5 p-8;
}
.container h5 {
@apply text-lg font-semibold;
}
.container p {
@apply text-sm font-medium;
}
@@ -1,129 +0,0 @@
import { FaFileExport, FaFileImport } from 'react-icons/fa';
import { useTranslation } from 'react-i18next';
import React, { memo, useContext, useState } from 'react';
import * as styles from './Actions.module.css';
import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
import Button from '../../../shared/Button';
import Heading from '../../../shared/Heading';
import Input from '../../../shared/Input';
import ModalContext from '../../../../contexts/ModalContext';
const Actions = ({ id }) => {
const { t } = useTranslation();
const [loadDemoText, setLoadDemoText] = useState(
t('builder.actions.loadDemoData.button'),
);
const [resetText, setResetText] = useState(
t('builder.actions.resetEverything.button'),
);
const state = useSelector();
const dispatch = useDispatch();
const { emitter, events } = useContext(ModalContext);
const handleImport = () => emitter.emit(events.IMPORT_MODAL);
const handleExport = () => emitter.emit(events.EXPORT_MODAL);
const getSharableUrl = () => {
const shareId = state.id;
return `https://rxresu.me/r/${shareId}`;
};
const handleOpenLink = () => {
if (typeof window !== `undefined`) {
window && window.open(getSharableUrl());
}
};
const handleLoadDemo = () => {
if (loadDemoText === t('builder.actions.loadDemoData.button')) {
setLoadDemoText(t('shared.buttons.confirmation'));
return;
}
dispatch({ type: 'load_demo_data' });
setLoadDemoText(t('builder.actions.loadDemoData.button'));
};
const handleReset = () => {
if (resetText === t('builder.actions.resetEverything.button')) {
setResetText(t('shared.buttons.confirmation'));
return;
}
setResetText(t('builder.actions.resetEverything.button'));
dispatch({ type: 'reset_data' });
};
return (
<section>
<Heading id={id} />
<div className={styles.container}>
<h5>{t('builder.actions.import.heading')}</h5>
<p className="leading-loose">{t('builder.actions.import.text')}</p>
<div className="mt-4 flex">
<Button icon={FaFileImport} onClick={handleImport}>
{t('builder.actions.import.button')}
</Button>
</div>
</div>
<div className={styles.container}>
<h5>{t('builder.actions.export.heading')}</h5>
<p className="leading-loose">{t('builder.actions.export.text')}</p>
<div className="mt-4 flex">
<Button icon={FaFileExport} onClick={handleExport}>
{t('builder.actions.export.button')}
</Button>
</div>
</div>
<div className={styles.container}>
<h5>{t('builder.actions.share.heading')}</h5>
<p className="leading-loose">{t('builder.actions.share.text')}</p>
<div>
<Input
type="action"
value={getSharableUrl()}
onClick={handleOpenLink}
/>
</div>
</div>
<div className={styles.container}>
<h5>{t('builder.actions.loadDemoData.button')}</h5>
<p className="leading-loose">
{t('builder.actions.loadDemoData.text')}
</p>
<div className="mt-4 flex">
<Button onClick={handleLoadDemo}>{loadDemoText}</Button>
</div>
</div>
<div className={styles.container}>
<h5>{t('builder.actions.resetEverything.button')}</h5>
<p className="leading-loose">
{t('builder.actions.resetEverything.text')}
</p>
<div className="mt-4 flex">
<Button onClick={handleReset}>{resetText}</Button>
</div>
</div>
</section>
);
};
export default memo(Actions);
@@ -1,11 +0,0 @@
.container {
@apply bg-primary-100 rounded grid gap-5 p-8;
}
.container h5 {
@apply text-lg font-semibold;
}
.container p {
@apply text-sm font-medium;
}
@@ -1,70 +0,0 @@
/* eslint-disable jsx-a11y/control-has-associated-label */
import { useTranslation } from 'react-i18next';
import React, { memo } from 'react';
import * as styles from './Colors.module.css';
import { handleKeyUp } from '../../../../utils';
import { useDispatch } from '../../../../contexts/ResumeContext';
import Heading from '../../../shared/Heading';
import Input from '../../../shared/Input';
import colorOptions from '../../../../data/colorOptions';
const Colors = ({ id }) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const handleClick = (value) => {
dispatch({
type: 'on_input',
payload: {
path: 'metadata.colors.primary',
value,
},
});
};
return (
<section>
<Heading id={id} />
<div className="mb-6 grid grid-cols-8 gap-x-2 gap-y-6">
{colorOptions.map((color) => (
<div
key={color}
tabIndex="0"
role="button"
className={styles.circle}
style={{ backgroundColor: color }}
onKeyUp={(e) => handleKeyUp(e, () => handleClick(color))}
onClick={() => handleClick(color)}
/>
))}
</div>
<Input
type="color"
name="primary"
label={t('builder.colors.primary')}
placeholder="#FF4081"
path="metadata.colors.primary"
/>
<Input
type="color"
name="text"
label={t('builder.colors.text')}
placeholder="#444444"
path="metadata.colors.text"
/>
<Input
type="color"
name="background"
label={t('builder.colors.background')}
placeholder="#FFFFFF"
path="metadata.colors.background"
/>
</section>
);
};
export default memo(Colors);
@@ -1,11 +0,0 @@
.circle {
@apply cursor-pointer rounded-full h-6 w-6;
}
.circle:focus {
@apply outline-none;
}
.circle:hover {
@apply opacity-75;
}
@@ -1,57 +0,0 @@
/* eslint-disable jsx-a11y/control-has-associated-label */
import React, { memo, useEffect, useState } from 'react';
import { scaler } from '../../../../utils';
import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
import Heading from '../../../shared/Heading';
import fontSizeOptions from '../../../../data/fontSizeOptions';
const FontSizes = ({ id }) => {
const steps = 20;
const min = 0;
const max = min + steps - 1;
const dispatch = useDispatch();
const fontSize = useSelector('metadata.fontSize');
const [scale, setScale] = useState(fontSize || 7);
useEffect(() => {
for (const [key, sizeDefault] of Object.entries(fontSizeOptions)) {
document.documentElement.style.setProperty(
key,
`${scaler(scale) * sizeDefault}rem`,
);
}
}, [scale]);
const onChange = (event) => {
const { value } = event.target;
setScale(value);
dispatch({
type: 'on_input',
payload: {
path: 'metadata.fontSize',
value,
},
});
};
return (
<section>
<Heading id={id} />
<input
step={1}
min={min}
max={max}
type="range"
onChange={onChange}
defaultValue={scale}
className="rounded-lg overflow-hidden appearance-none bg-gray-400 h-4 w-full"
/>
</section>
);
};
export default memo(FontSizes);
@@ -1,46 +0,0 @@
import React, { memo } from 'react';
import cx from 'classnames';
import * as styles from './Fonts.module.css';
import { handleKeyUp } from '../../../../utils';
import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
import Heading from '../../../shared/Heading';
import fontOptions from '../../../../data/fontOptions';
const Fonts = ({ id }) => {
const dispatch = useDispatch();
const font = useSelector('metadata.font');
const handleClick = (value) => {
dispatch({
type: 'on_input',
payload: {
path: 'metadata.font',
value,
},
});
};
return (
<section>
<Heading id={id} />
<div className="grid grid-cols-2 gap-8">
{fontOptions.map((x) => (
<div
key={x}
tabIndex="0"
role="button"
style={{ fontFamily: x }}
className={cx(styles.font, { [styles.selected]: font === x })}
onKeyUp={(e) => handleKeyUp(e, () => handleClick(x))}
onClick={() => handleClick(x)}
>
{x}
</div>
))}
</div>
</section>
);
};
export default memo(Fonts);
@@ -1,15 +0,0 @@
.font {
@apply px-6 py-6 text-xl font-medium truncate text-center border border-transparent bg-primary-100 rounded;
}
.font:focus {
@apply outline-none border border-primary-400;
}
.font:hover {
@apply border border-primary-400;
}
.font.selected {
@apply outline-none border border-primary-600;
}
@@ -1,117 +0,0 @@
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
import { useTranslation } from 'react-i18next';
import React, { memo, useState } from 'react';
import * as styles from './Layout.module.css';
import { move, reorder } from '../../../../utils';
import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
import Button from '../../../shared/Button';
import Heading from '../../../shared/Heading';
const Layout = ({ id }) => {
const { t } = useTranslation();
const [resetLayoutText, setResetLayoutText] = useState(
t('builder.layout.reset'),
);
const template = useSelector('metadata.template');
const blocks = useSelector(`metadata.layout.${template}`, [[]]);
const dispatch = useDispatch();
const onDragEnd = (result) => {
const { source, destination } = result;
if (!destination) {
return;
}
const sInd = +source.droppableId;
const dInd = +destination.droppableId;
if (sInd === dInd) {
const items = reorder(blocks[sInd], source.index, destination.index);
const newState = [...blocks];
newState[sInd] = items;
dispatch({
type: 'on_input',
payload: {
path: `metadata.layout.${template}`,
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: 'on_input',
payload: {
path: `metadata.layout.${template}`,
value: newState,
},
});
}
};
const handleResetLayout = () => {
if (resetLayoutText === t('builder.layout.reset')) {
setResetLayoutText(t('shared.buttons.confirmation'));
return;
}
dispatch({ type: 'reset_layout' });
setResetLayoutText(t('builder.layout.reset'));
};
return (
<section>
<Heading id={id} />
<p className="leading-loose">
{t('builder.layout.text', { count: blocks.length })}
</p>
<div className={`grid gap-8 grid-cols-${blocks.length}`}>
<DragDropContext onDragEnd={onDragEnd}>
{blocks.map((el, ind) => (
<Droppable key={ind} droppableId={`${ind}`}>
{(dropProvided) => (
<div
ref={dropProvided.innerRef}
className={styles.droppable}
{...dropProvided.droppableProps}
>
<div className="grid gap-3">
<span className="uppercase font-semibold text-xs">
{t('builder.layout.block')} {ind + 1}
</span>
{el.map((item, index) => (
<Draggable key={item} index={index} draggableId={item}>
{(dragProvided) => (
<div
ref={dragProvided.innerRef}
className={styles.draggable}
{...dragProvided.draggableProps}
{...dragProvided.dragHandleProps}
>
{t(`builder.sections.${item}`)}
</div>
)}
</Draggable>
))}
</div>
{dropProvided.placeholder}
</div>
)}
</Droppable>
))}
</DragDropContext>
</div>
<div className="flex">
<Button onClick={handleResetLayout}>{resetLayoutText}</Button>
</div>
</section>
);
};
export default memo(Layout);
@@ -1,7 +0,0 @@
.droppable {
@apply p-6 bg-primary-100 col-span-1 rounded;
}
.draggable {
@apply px-4 py-2 font-medium rounded bg-primary-200;
}
@@ -1,119 +0,0 @@
import { FaAngleDown } from 'react-icons/fa';
import { Trans, useTranslation } from 'react-i18next';
import { toast } from 'react-toastify';
import React, { memo, useContext, useState } from 'react';
import * as styles from './Settings.module.css';
import { languages } from '../../../../i18n';
import { useDispatch } from '../../../../contexts/ResumeContext';
import Button from '../../../shared/Button';
import Heading from '../../../shared/Heading';
import Input from '../../../shared/Input';
import SettingsContext from '../../../../contexts/SettingsContext';
import UserContext from '../../../../contexts/UserContext';
import themeConfig from '../../../../data/themeConfig';
const Settings = ({ id }) => {
const { t } = useTranslation();
const [deleteText, setDeleteText] = useState(
t('builder.settings.dangerZone.button'),
);
const [isDeleteAccountInProgress, setDeleteAccountInProgress] =
useState(false);
const dispatch = useDispatch();
const { deleteAccount } = useContext(UserContext);
const { theme, setTheme, language, setLanguage } =
useContext(SettingsContext);
const handleChangeTheme = (e) => {
setTheme(e.target.value);
};
const handleChangeLanguage = (e) => {
const lang = e.target.value;
setLanguage(lang);
dispatch({ type: 'change_language', payload: lang });
};
const handleDeleteAccount = async () => {
if (deleteText === t('builder.settings.dangerZone.button')) {
setDeleteText(t('shared.buttons.confirmation'));
return;
}
setDeleteAccountInProgress(true);
try {
await deleteAccount();
} catch (error) {
toast.error('An error occurred deleting your account.');
setDeleteAccountInProgress(false);
setDeleteText(t('builder.settings.dangerZone.button'));
}
};
return (
<section>
<Heading id={id} />
<Input
label={t('builder.settings.theme')}
type="dropdown"
options={Object.keys(themeConfig)}
value={theme}
onChange={handleChangeTheme}
/>
<label>
<span>{t('builder.settings.language')}</span>
<div className="relative grid items-center">
<select onChange={handleChangeLanguage} value={language}>
{languages.map((x) => (
<option key={x.code} value={x.code}>
{x.name}
</option>
))}
</select>
<FaAngleDown
size="16px"
className="absolute right-0 opacity-50 hover:opacity-75 mx-4"
/>
</div>
</label>
<p className="text-sm leading-loose">
<Trans t={t} i18nKey="builder.settings.translate">
A
<a
href="https://github.com/AmruthPillai/Reactive-Resume#translation"
rel="noreferrer"
target="_blank"
>
B
</a>
C
</Trans>
</p>
<div className={styles.container}>
<h5>{t('builder.settings.dangerZone.heading')}</h5>
<p className="leading-loose">{t('builder.settings.dangerZone.text')}</p>
<div className="mt-4 flex">
<Button
isDelete
onClick={handleDeleteAccount}
isLoading={isDeleteAccountInProgress}
>
{deleteText}
</Button>
</div>
</div>
</section>
);
};
export default memo(Settings);
@@ -1,11 +0,0 @@
.container {
@apply bg-primary-100 rounded grid gap-5 p-8;
}
.container h5 {
@apply text-lg font-semibold;
}
.container p {
@apply text-sm font-medium;
}
@@ -1,88 +0,0 @@
import { graphql, useStaticQuery } from 'gatsby';
import { GatsbyImage } from 'gatsby-plugin-image';
import React, { memo } from 'react';
import cx from 'classnames';
import * as styles from './Templates.module.css';
import { handleKeyUp } from '../../../../utils';
import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
import Heading from '../../../shared/Heading';
import templateOptions from '../../../../data/templateOptions';
const Templates = ({ id }) => {
const dispatch = useDispatch();
const template = useSelector('metadata.template');
const previews = useStaticQuery(graphql`
query {
onyx: file(relativePath: { eq: "templates/onyx.png" }) {
childImageSharp {
gatsbyImageData(layout: FIXED, height: 240)
}
}
pikachu: file(relativePath: { eq: "templates/pikachu.png" }) {
childImageSharp {
gatsbyImageData(layout: FIXED, height: 240)
}
}
gengar: file(relativePath: { eq: "templates/gengar.png" }) {
childImageSharp {
gatsbyImageData(layout: FIXED, height: 240)
}
}
castform: file(relativePath: { eq: "templates/castform.png" }) {
childImageSharp {
gatsbyImageData(layout: FIXED, height: 240)
}
}
glalie: file(relativePath: { eq: "templates/glalie.png" }) {
childImageSharp {
gatsbyImageData(layout: FIXED, height: 240)
}
}
celebi: file(relativePath: { eq: "templates/celebi.png" }) {
childImageSharp {
gatsbyImageData(layout: FIXED, height: 240)
}
}
}
`);
const handleClick = (value) => {
dispatch({
type: 'on_input',
payload: {
path: 'metadata.template',
value,
},
});
};
return (
<section>
<Heading id={id} />
<div className="grid grid-cols-2 gap-8">
{templateOptions.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,
})}
>
<GatsbyImage
alt={x.name}
image={previews[x.id].childImageSharp.gatsbyImageData}
/>
<span>{x.name}</span>
</div>
))}
</div>
</section>
);
};
export default memo(Templates);
@@ -1,27 +0,0 @@
.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 border-transparent rounded;
@apply transition-opacity duration-200 ease-in-out;
}
.template.selected img {
@apply border-primary-600;
}
.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;
}
-40
View File
@@ -1,40 +0,0 @@
import { MdAdd } from 'react-icons/md';
import { useTranslation } from 'react-i18next';
import React, { memo, useContext } from 'react';
import * as styles from './CreateResume.module.css';
import { handleKeyUp } from '../../utils';
import ModalContext from '../../contexts/ModalContext';
const createResumeButtonDataTestId = 'create-resume-button';
const CreateResume = () => {
const { t } = useTranslation();
const { emitter, events } = useContext(ModalContext);
const handleClick = () => emitter.emit(events.CREATE_RESUME_MODAL);
return (
<div className={styles.resume}>
<div className={styles.backdrop}>
<MdAdd size="48" />
</div>
<div
data-testid={createResumeButtonDataTestId}
tabIndex="0"
role="button"
className={styles.page}
onClick={handleClick}
onKeyUp={(e) => handleKeyUp(e, handleClick)}
>
<MdAdd size="48" />
</div>
<div className={styles.meta}>
<p>{t('dashboard.createResume')}</p>
</div>
</div>
);
};
export default memo(CreateResume);
export { createResumeButtonDataTestId };
@@ -1,34 +0,0 @@
.resume {
@apply relative flex flex-col items-center;
}
.resume > .backdrop {
max-width: 184px;
height: 260px;
@apply rounded absolute w-full bg-black text-white shadow;
@apply absolute flex justify-center items-center;
}
.resume > .page {
max-width: 184px;
height: 260px;
@apply rounded absolute w-full bg-primary-100 text-primary-400;
@apply transition-opacity duration-200 ease-in-out;
@apply cursor-pointer absolute flex justify-center items-center;
}
.resume > .page:hover {
@apply transition-opacity duration-200 ease-in-out opacity-25;
}
.resume > .meta {
margin-top: 260px;
@apply text-center;
}
.resume > .meta p {
@apply mt-3 font-medium leading-normal;
}
-103
View File
@@ -1,103 +0,0 @@
import { MdMoreHoriz, MdOpenInNew } from 'react-icons/md';
import { Menu, MenuItem } from '@material-ui/core';
import { navigate } from 'gatsby';
import { toast } from 'react-toastify';
import { useTranslation } from 'react-i18next';
import React, { useContext, useState } from 'react';
import dayjs from 'dayjs';
import DatabaseContext from '../../contexts/DatabaseContext';
import ModalContext from '../../contexts/ModalContext';
import * as styles from './ResumePreview.module.css';
const menuToggleDataTestIdPrefix = 'resume-preview-menu-toggle-';
const ResumePreview = ({ resume }) => {
const { t, i18n } = useTranslation();
const [anchorEl, setAnchorEl] = useState(null);
const { emitter, events } = useContext(ModalContext);
const { duplicateResume, deleteResume } = useContext(DatabaseContext);
const handleOpen = () => navigate(`/app/builder/${resume.id}`);
const handleMenuClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleDuplicate = () => {
duplicateResume(resume);
setAnchorEl(null);
};
const handleRename = () => {
emitter.emit(events.CREATE_RESUME_MODAL, resume);
setAnchorEl(null);
};
const handleDelete = () => {
deleteResume(resume.id);
toast(t('dashboard.toasts.deleted', { name: resume.name }));
setAnchorEl(null);
};
const handleMenuClose = () => {
setAnchorEl(null);
};
return (
<div className={styles.resume}>
<div className={styles.backdrop}>
<img src={resume.preview} alt={resume.name} />
</div>
<div className={styles.page}>
<MdOpenInNew
color="#fff"
size="48"
className="cursor-pointer"
onClick={handleOpen}
/>
<MdMoreHoriz
data-testid={`${menuToggleDataTestIdPrefix}${resume.id}`}
color="#fff"
size="48"
className="cursor-pointer"
aria-haspopup="true"
onClick={handleMenuClick}
/>
<Menu
keepMounted
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleMenuClose}
>
<MenuItem onClick={handleDuplicate}>
{t('dashboard.buttons.duplicate')}
</MenuItem>
<MenuItem onClick={handleRename}>
{t('dashboard.buttons.rename')}
</MenuItem>
<MenuItem onClick={handleDelete}>
<span className="text-red-600 font-medium">
{t('shared.buttons.delete')}
</span>
</MenuItem>
</Menu>
</div>
<div className={styles.meta}>
<span>{resume.name}</span>
{resume.updatedAt && (
<span>
{t('dashboard.lastUpdated', {
timestamp: dayjs(resume.updatedAt)
.locale(i18n.language.substr(0, 2))
.fromNow(),
})}
</span>
)}
</div>
</div>
);
};
export default ResumePreview;
export { menuToggleDataTestIdPrefix };
@@ -1,44 +0,0 @@
.resume {
@apply relative flex flex-col items-center;
}
.resume > .backdrop {
max-width: 184px;
height: 260px;
@apply rounded absolute w-full bg-black shadow;
}
.resume > .backdrop img {
max-width: 184px;
height: 260px;
@apply w-full object-cover rounded;
}
.resume > .page {
max-width: 184px;
height: 260px;
@apply rounded absolute w-full bg-black;
@apply opacity-0 transition-opacity duration-200 ease-in-out;
@apply absolute text-primary-500 flex flex-col justify-evenly items-center;
}
.resume > .page:hover {
@apply opacity-75 transition-opacity duration-200 ease-in-out;
}
.resume > .meta {
margin-top: 260px;
@apply flex flex-col text-center items-center;
}
.resume > .meta span:first-child {
@apply mt-3 font-medium leading-normal;
}
.resume > .meta span:last-child {
font-size: 10px;
}
-19
View File
@@ -1,19 +0,0 @@
import { Link } from 'gatsby';
import React, { memo } from 'react';
import * as styles from './TopNavbar.module.css';
import Avatar from '../shared/Avatar';
import Logo from '../shared/Logo';
const TopNavbar = () => (
<div className={styles.navbar}>
<div className="container">
<Link to="/">
<Logo size="40px" />
</Link>
<Avatar className="ml-8" />
</div>
</div>
);
export default memo(TopNavbar);
@@ -1,10 +0,0 @@
.navbar {
height: 65px;
box-shadow: var(--bottom-shadow);
@apply w-full;
}
.navbar > div {
@apply h-full flex items-center justify-between;
}
-56
View File
@@ -1,56 +0,0 @@
import { Link } from '@reach/router';
import { navigate } from 'gatsby';
import { useTranslation } from 'react-i18next';
import React, { memo, useContext } from 'react';
import Button from '../shared/Button';
import Logo from '../shared/Logo';
import ModalContext from '../../contexts/ModalContext';
import UserContext from '../../contexts/UserContext';
const Hero = () => {
const { t } = useTranslation();
const { emitter, events } = useContext(ModalContext);
const { user, loading } = useContext(UserContext);
const handleLogin = () => emitter.emit(events.AUTH_MODAL);
const handleGotoApp = () => navigate('/app/dashboard');
return (
<div className="flex flex-col md:flex-row items-center">
<Link to="/">
<span className="sr-only">Reactive Resume</span>
<Logo className="shadow-lg" size="256px" />
</Link>
<div className="mt-12 md:mt-0 md:ml-12">
<h1 className="text-5xl font-bold">{t('shared.appName')}</h1>
<h2 className="mt-1 text-3xl text-primary-500">
{t('shared.shortDescription')}
</h2>
<div className="mt-12">
{user ? (
<Button
onClick={handleGotoApp}
isLoading={loading}
className="mx-auto md:mx-0"
>
{t('landing.hero.goToApp')}
</Button>
) : (
<Button
onClick={handleLogin}
isLoading={loading}
className="mx-auto md:mx-0"
>
{t('shared.buttons.login')}
</Button>
)}
</div>
</div>
</div>
);
};
export default memo(Hero);
-77
View File
@@ -1,77 +0,0 @@
import { graphql, useStaticQuery } from 'gatsby';
import { GatsbyImage } from 'gatsby-plugin-image';
import React from 'react';
import * as styles from './Screenshots.module.css';
const Screenshots = () => {
const screenshots = useStaticQuery(graphql`
query {
screen1: file(relativePath: { eq: "screenshots/screen-1.png" }) {
childImageSharp {
original {
src
}
gatsbyImageData(layout: FIXED, width: 400)
}
}
screen2: file(relativePath: { eq: "screenshots/screen-2.png" }) {
childImageSharp {
original {
src
}
gatsbyImageData(layout: FIXED, width: 400)
}
}
screen3: file(relativePath: { eq: "screenshots/screen-3.png" }) {
childImageSharp {
original {
src
}
gatsbyImageData(layout: FIXED, width: 400)
}
}
screen4: file(relativePath: { eq: "screenshots/screen-4.png" }) {
childImageSharp {
original {
src
}
gatsbyImageData(layout: FIXED, width: 400)
}
}
screen5: file(relativePath: { eq: "screenshots/screen-5.png" }) {
childImageSharp {
original {
src
}
gatsbyImageData(layout: FIXED, width: 400)
}
}
}
`);
return (
<div className="mt-24 mb-4">
<div className="text-xl uppercase font-bold mb-8">Screenshots</div>
<div className="flex w-full overflow-x-scroll">
{Object.keys(screenshots).map((x) => (
<a
target="_blank"
rel="noreferrer"
className={styles.screenshot}
key={screenshots[x].childImageSharp.original.src}
href={screenshots[x].childImageSharp.original.src}
>
<span className="sr-only">Reactive Resume Screenshot</span>
<GatsbyImage
image={screenshots[x].childImageSharp.gatsbyImageData}
alt="Reactive Resume Screenshot"
/>
</a>
))}
</div>
</div>
);
};
export default Screenshots;
@@ -1,16 +0,0 @@
.screenshot {
filter: grayscale(100%);
@apply shadow-xl rounded border-2 border-primary-100 ml-10;
@apply transition-all duration-200 ease-in-out;
}
.screenshot:hover {
filter: grayscale(0%);
@apply transition-all duration-200 ease-in-out;
}
.screenshot:first-child {
@apply ml-0;
}
-22
View File
@@ -1,22 +0,0 @@
import { Fade, Modal } from '@material-ui/core';
import React, { memo } from 'react';
import { getRandomTip } from '../../data/tips';
import Logo from '../shared/Logo';
const dataTestId = 'loading-screen';
const LoadingScreen = () => (
<Modal data-testid={dataTestId} open hideBackdrop>
<Fade in>
<div className="w-screen h-screen flex justify-center items-center outline-none">
<div className="flex flex-col items-center">
<Logo size="48px" className="mb-4" />
<span className="font-medium opacity-75">{getRandomTip()}</span>
</div>
</div>
</Fade>
</Modal>
);
export default memo(LoadingScreen);
export { dataTestId };
-21
View File
@@ -1,21 +0,0 @@
import { navigate } from 'gatsby';
import React, { memo, useContext } from 'react';
import LoadingScreen from './LoadingScreen';
import UserContext from '../../contexts/UserContext';
const PrivateRoute = ({ component: Component, ...props }) => {
const { user, loading } = useContext(UserContext);
if (loading) {
return <LoadingScreen />;
}
if (!user) {
navigate('/');
return null;
}
return <Component user={user} {...props} />;
};
export default memo(PrivateRoute);
-55
View File
@@ -1,55 +0,0 @@
import { Menu, MenuItem } from '@material-ui/core';
import { toUrl } from 'gatsby-source-gravatar';
import { useTranslation } from 'react-i18next';
import React, { memo, useContext, useMemo, useState } from 'react';
import cx from 'classnames';
import UserContext from '../../contexts/UserContext';
import { handleKeyUp } from '../../utils';
import * as styles from './Avatar.module.css';
const Avatar = ({ className }) => {
const { t } = useTranslation();
const { user, logout } = useContext(UserContext);
const [anchorEl, setAnchorEl] = useState(null);
const handleClick = (event) => setAnchorEl(event.currentTarget);
const handleClose = () => setAnchorEl(null);
const handleLogout = () => {
logout();
handleClose();
};
const photoURL = useMemo(
() => toUrl(user.email || '', 'size=128&d=retro'),
[user.email],
);
return (
<div>
<div
tabIndex="0"
role="button"
className="flex focus:outline-none"
onClick={handleClick}
onKeyUp={(e) => handleKeyUp(e, handleClick)}
>
<img
src={photoURL}
alt={user.displayName || 'Anonymous User'}
className={cx(styles.container, className)}
/>
</div>
<Menu
keepMounted
anchorEl={anchorEl}
onClose={handleClose}
open={Boolean(anchorEl)}
>
<MenuItem onClick={handleLogout}>{t('shared.buttons.logout')}</MenuItem>
</Menu>
</div>
);
};
export default memo(Avatar);
-3
View File
@@ -1,3 +0,0 @@
.container {
@apply w-12 h-12 rounded-full;
}
-34
View File
@@ -1,34 +0,0 @@
import { useTranslation } from 'react-i18next';
import React, { memo } from 'react';
import cx from 'classnames';
import { handleKeyUp } from '../../utils';
import * as styles from './Button.module.css';
const Button = ({
icon,
onClick,
outline,
children,
className,
isLoading,
isDelete,
}) => {
const { t } = useTranslation();
const Icon = icon;
return (
<button
onKeyUp={(e) => handleKeyUp(e, onClick)}
onClick={isLoading ? undefined : onClick}
className={cx(styles.container, className, {
[styles.outline]: outline,
[styles.remove]: isDelete,
})}
>
{icon && <Icon size="14" className="mr-3" />}
{isLoading ? t('shared.buttons.loading') : children}
</button>
);
};
export default memo(Button);
-44
View File
@@ -1,44 +0,0 @@
.container {
font-size: 11px;
padding: 6px 18px;
@apply relative flex items-center cursor-pointer rounded font-semibold bg-primary-900 border border-primary-900 text-primary-50;
@apply transition-colors duration-200 ease-in-out;
}
.container:hover {
@apply bg-primary-700 border-primary-700;
@apply transition-colors duration-200 ease-in-out;
}
.container:active {
top: 1px;
}
.container:focus {
@apply outline-none;
}
.container.outline {
@apply border border-primary-900 bg-transparent text-primary-900;
}
.container.outline:hover {
@apply bg-primary-200;
}
.container.outline:focus {
@apply outline-none;
}
.container.remove {
@apply bg-red-600 border-red-600 text-white;
}
.container.remove:hover {
@apply bg-red-700 border-red-700;
}
.container.remove:focus {
@apply outline-none;
}
-12
View File
@@ -1,12 +0,0 @@
import { useTranslation } from 'react-i18next';
import React, { memo } from 'react';
import { useSelector } from '../../contexts/ResumeContext';
const Heading = ({ id }) => {
const { t } = useTranslation();
const heading = useSelector(`${id}.heading`, t(`builder.sections.${id}`));
return <h2 className="text-4xl focus:outline-none">{heading}</h2>;
};
export default memo(Heading);
-164
View File
@@ -1,164 +0,0 @@
import { FaAngleDown } from 'react-icons/fa';
import { MdOpenInNew } from 'react-icons/md';
import { Trans, useTranslation } from 'react-i18next';
import { isFunction } from 'lodash';
import { v4 as uuidv4 } from 'uuid';
import React, { memo, useEffect, useState } from 'react';
import cx from 'classnames';
import { useDispatch, useSelector } from '../../contexts/ResumeContext';
import { handleKeyUp } from '../../utils';
import * as styles from './Input.module.css';
const Input = ({
name,
path,
label,
error,
value,
onBlur,
options,
touched,
onClick,
onChange,
className,
isRequired,
placeholder,
type = 'text',
}) => {
const { t } = useTranslation();
const [uuid, setUuid] = useState(null);
const stateValue = useSelector(path, '');
const dispatch = useDispatch();
useEffect(() => {
setUuid(uuidv4());
}, []);
value = path ? stateValue : value;
onChange = isFunction(onChange)
? onChange
: (e) => {
dispatch({
type: 'on_input',
payload: {
path,
value: e.target.value,
},
});
};
return (
<div className={className}>
<label htmlFor={uuid}>
<span>
{label}{' '}
{isRequired && (
<span className="opacity-75 font-normal lowercase">
({t('shared.forms.required')})
</span>
)}
</span>
{(type === 'text' || type === 'date') && (
<div className="relative grid items-center">
<input
id={uuid}
name={name}
type={type}
value={value}
onBlur={onBlur}
onChange={onChange}
placeholder={placeholder}
/>
</div>
)}
{type === 'textarea' && (
<div className="flex flex-col">
<textarea
id={uuid}
rows="4"
name={name}
value={value}
onBlur={onBlur}
onChange={onChange}
placeholder={placeholder}
/>
<p className="mt-2 text-sm opacity-75">
<Trans t={t} i18nKey="shared.forms.markdown">
A
<a
href="https://www.markdownguide.org/basic-syntax/"
className="text-blue-600"
target="blank"
>
B
</a>
C
</Trans>
</p>
</div>
)}
{type === 'dropdown' && (
<div className="relative grid items-center">
<select
id={uuid}
name={name}
value={value}
onBlur={onBlur}
onChange={onChange}
>
{options.map((x) => (
<option key={x} value={x}>
{x}
</option>
))}
</select>
<FaAngleDown
size="16px"
className="absolute right-0 opacity-50 hover:opacity-75 mx-4"
/>
</div>
)}
{type === 'color' && (
<div className="relative grid items-center">
<div className={styles.circle} style={{ backgroundColor: value }} />
<input
id={uuid}
name={name}
type="text"
value={value}
onBlur={onBlur}
onChange={onChange}
placeholder={placeholder}
/>
</div>
)}
{type === 'action' && (
<div className={cx('relative grid items-center', styles.readOnly)}>
<input readOnly id={uuid} name={name} type="text" value={value} />
<div
tabIndex="0"
role="button"
onClick={onClick}
onKeyUp={(e) => handleKeyUp(e, onClick)}
>
<MdOpenInNew size="16px" />
</div>
</div>
)}
{error && touched && <p>{error}</p>}
</label>
</div>
);
};
export default memo(Input);
-30
View File
@@ -1,30 +0,0 @@
.circle {
left: 14px;
@apply absolute bg-primary-900 rounded-full w-6 h-6;
}
div.circle + input {
padding-left: 44px;
}
.read-only input:hover {
cursor: default;
border-color: transparent !important;
}
.read-only input:focus {
border-color: transparent !important;
}
.read-only div {
@apply absolute rounded-r top-0 right-0 bottom-0 w-20 flex justify-center items-center bg-primary-50;
}
.read-only div:hover {
@apply bg-primary-100;
}
.read-only div:focus {
@apply outline-none;
}
-29
View File
@@ -1,29 +0,0 @@
import { graphql, useStaticQuery } from 'gatsby';
import { GatsbyImage } from 'gatsby-plugin-image';
import React, { memo } from 'react';
import cx from 'classnames';
import * as styles from './Logo.module.css';
const Logo = ({ size = '256px', className }) => {
const { file } = useStaticQuery(graphql`
query {
file(relativePath: { eq: "logo.png" }) {
childImageSharp {
gatsbyImageData(layout: FIXED)
}
}
}
`);
return (
<GatsbyImage
loading="eager"
alt="Reactive Resume"
className={cx(styles.logo, className)}
style={{ width: size, height: size }}
image={file.childImageSharp.gatsbyImageData}
/>
);
};
export default memo(Logo);
-9
View File
@@ -1,9 +0,0 @@
.logo {
box-shadow: var(--shadow);
@apply rounded;
}
.logo:hover {
box-shadow: var(--shadow-strong);
}
-16
View File
@@ -1,16 +0,0 @@
import React, { memo } from 'react';
import ReactMarkdown from 'react-markdown';
import gfm from 'remark-gfm';
import cx from 'classnames';
const Markdown = ({ children, className, ...props }) => (
<ReactMarkdown
className={cx('markdown', className)}
remarkPlugins={[gfm]}
{...props}
>
{children}
</ReactMarkdown>
);
export default memo(Markdown);
-58
View File
@@ -1,58 +0,0 @@
import { MdFileUpload } from 'react-icons/md';
import { Tooltip } from '@material-ui/core';
import { useTranslation } from 'react-i18next';
import React, { memo, useContext, useRef } from 'react';
import { handleKeyUp } from '../../utils';
import Input from './Input';
import * as styles from './PhotoUpload.module.css';
import StorageContext from '../../contexts/StorageContext';
const PhotoUpload = () => {
const fileInputRef = useRef(null);
const { t } = useTranslation();
const { uploadPhotograph } = useContext(StorageContext);
const handleIconClick = () => {
fileInputRef.current.click();
};
const handleImageUpload = (e) => {
const file = e.target.files[0];
uploadPhotograph(file);
};
return (
<div className="flex items-center">
<Tooltip
title={t('builder.tooltips.uploadPhotograph')}
placement="right-start"
>
<div
role="button"
tabIndex="0"
className={styles.circle}
onClick={handleIconClick}
onKeyUp={(e) => handleKeyUp(e, handleIconClick)}
>
<MdFileUpload size="22px" />
<input
name="file"
type="file"
ref={fileInputRef}
className="hidden"
onChange={handleImageUpload}
/>
</div>
</Tooltip>
<Input
name="photograph"
label={t('builder.profile.photograph')}
className="pl-6 w-full"
path="profile.photograph"
/>
</div>
);
};
export default memo(PhotoUpload);
@@ -1,17 +0,0 @@
.circle {
width: 60px;
height: 60px;
flex: 0 0 60px;
@apply flex items-center justify-center cursor-pointer bg-primary-200 text-primary-500 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;
}
-33
View File
@@ -1,33 +0,0 @@
import { Link } from 'react-scroll';
import { Tooltip } from '@material-ui/core';
import { useTranslation } from 'react-i18next';
import React, { memo } from 'react';
import * as styles from './SectionIcon.module.css';
const SectionIcon = ({ section, containerId, tooltipPlacement }) => {
const { t } = useTranslation();
const { id, icon: Icon } = section;
return (
<Tooltip
title={t(`builder.sections.${id}`)}
placement={tooltipPlacement}
arrow
>
<Link
spy
smooth
to={id}
offset={-18}
duration={500}
containerId={containerId}
activeClass="text-primary-900"
className={styles.icon}
>
<Icon size="18px" />
</Link>
</Tooltip>
);
};
export default memo(SectionIcon);
@@ -1,11 +0,0 @@
.icon {
@apply py-2 cursor-pointer;
}
.icon:focus {
@apply outline-none text-primary-900;
}
.icon:hover {
@apply text-primary-900;
}
-44
View File
@@ -1,44 +0,0 @@
import { Helmet } from 'react-helmet';
import { Slide, toast } from 'react-toastify';
import React, { memo, useEffect } from 'react';
import ModalRegistrar from '../../modals/ModalRegistrar';
const Wrapper = ({ children }) => {
useEffect(() => {
toast.configure({
role: 'alert',
hideProgressBar: true,
transition: Slide,
closeButton: false,
position: 'bottom-right',
pauseOnFocusLoss: false,
});
}, []);
return (
<>
<Helmet>
<html lang="en" />
<title>Reactive Resume</title>
<meta
name="description"
content="A free and open source resume builder thats built to make the mundane tasks of creating, updating and sharing your resume as easy as 1, 2, 3."
/>
<link rel="canonical" href="https://rxresu.me" />
<meta property="og:url" content="https://rxresu.me" />
<meta property="og:type" content="website" />
<meta
property="og:description"
content="A free and open source resume builder thats built to make the mundane tasks of creating, updating and sharing your resume as easy as 1, 2, 3."
/>
<meta property="og:image" content="http://rxresu.me/images/share.png" />
</Helmet>
{children}
<ModalRegistrar />
</>
);
};
export default memo(Wrapper);