mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-10 04:22:27 +10:00
feat: ⚡ update app version to 3.6.2
This commit is contained in:
@ -1,15 +1,14 @@
|
||||
{
|
||||
"root": true,
|
||||
"ignorePatterns": ["/app"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"extends": ["plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"],
|
||||
"extends": ["plugin:@typescript-eslint/recommended"],
|
||||
"plugins": ["@typescript-eslint/eslint-plugin", "simple-import-sort", "unused-imports"],
|
||||
"rules": {
|
||||
// Unused Imports
|
||||
// ESLint
|
||||
"no-unused-vars": "off",
|
||||
// Simple Import Sort
|
||||
"simple-import-sort/imports": "error",
|
||||
"simple-import-sort/exports": "error",
|
||||
|
||||
// Unused Imports
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
"unused-imports/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
@ -19,7 +18,11 @@
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
|
||||
// Simple Import Sort
|
||||
"simple-import-sort/imports": "error",
|
||||
"simple-import-sort/exports": "error",
|
||||
|
||||
// TypeScript ESLint
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
|
||||
11
CHANGELOG.md
11
CHANGELOG.md
@ -2,6 +2,17 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [3.6.2](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.5.3...v3.6.2) (2022-08-25)
|
||||
|
||||
### Features
|
||||
|
||||
- Implement Undo/Redo functionality across the resume builder section
|
||||
|
||||
### Improvements
|
||||
|
||||
- Update dependencies to the latest version
|
||||
- Cleanup ESLint configuration, add tailwindCSS formatting
|
||||
|
||||
### [3.5.3](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.5.2...v3.5.3) (2022-08-11)
|
||||
|
||||
### [3.5.2](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.5.1...v3.5.2) (2022-08-04)
|
||||
|
||||
@ -18,7 +18,7 @@ You have complete control over what goes into your resume, how it looks, what co
|
||||
## Table of Contents
|
||||
|
||||
- [Reactive Resume](#reactive-resume)
|
||||
- [Go to App](https://rxresu.me) | [Docs](https://docs.rxresu.me)
|
||||
- [Go to App | [Docs](https://docs.rxresu.me)](#go-to-app--docs)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Features](#features)
|
||||
- [Languages](#languages)
|
||||
|
||||
@ -1,9 +1,20 @@
|
||||
{
|
||||
"extends": ["../.eslintrc.json", "next/core-web-vitals"],
|
||||
"extends": ["../.eslintrc.json", "next/core-web-vitals", "plugin:tailwindcss/recommended"],
|
||||
"ignorePatterns": [".next", "__ENV.js"],
|
||||
"settings": {
|
||||
"next": {
|
||||
"rootDir": "client"
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
// Next.js
|
||||
"@next/next/no-img-element": "off",
|
||||
"@next/next/no-sync-scripts": "off",
|
||||
"@next/next/no-html-link-for-pages": ["error", "pages"]
|
||||
|
||||
// React Hooks
|
||||
"react-hooks/exhaustive-deps": "off",
|
||||
|
||||
// Tailwind CSS
|
||||
"tailwindcss/no-custom-classname": ["warn", { "whitelist": ["preview-mode", "printer-mode", "markdown"] }]
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,8 @@ import {
|
||||
FilterCenterFocus,
|
||||
InsertPageBreak,
|
||||
Link,
|
||||
RedoOutlined,
|
||||
UndoOutlined,
|
||||
ViewSidebar,
|
||||
ZoomIn,
|
||||
ZoomOut,
|
||||
@ -16,6 +18,7 @@ import { useTranslation } from 'next-i18next';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useMutation } from 'react-query';
|
||||
import { ReactZoomPanPinchRef } from 'react-zoom-pan-pinch';
|
||||
import { ActionCreators } from 'redux-undo';
|
||||
|
||||
import { ServerError } from '@/services/axios';
|
||||
import { printResumeAsPdf, PrintResumeAsPdfParams } from '@/services/printer';
|
||||
@ -31,14 +34,18 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
|
||||
const theme = useTheme();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
const isDesktop = useMediaQuery(theme.breakpoints.up('sm'));
|
||||
const pages = useAppSelector((state) => state.resume.metadata.layout);
|
||||
|
||||
const { past, present: resume, future } = useAppSelector((state) => state.resume);
|
||||
const pages = get(resume, 'metadata.layout');
|
||||
const { left, right } = useAppSelector((state) => state.build.sidebar);
|
||||
const orientation = useAppSelector((state) => state.build.page.orientation);
|
||||
|
||||
const { mutateAsync, isLoading } = useMutation<string, ServerError, PrintResumeAsPdfParams>(printResumeAsPdf);
|
||||
|
||||
const handleUndo = () => dispatch(ActionCreators.undo());
|
||||
const handleRedo = () => dispatch(ActionCreators.redo());
|
||||
|
||||
const handleTogglePageBreakLine = () => dispatch(togglePageBreakLine());
|
||||
|
||||
const handleTogglePageOrientation = () => dispatch(togglePageOrientation());
|
||||
@ -75,6 +82,20 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
|
||||
})}
|
||||
>
|
||||
<div className={styles.controller}>
|
||||
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.undo')}>
|
||||
<ButtonBase onClick={handleUndo} className={clsx({ 'pointer-events-none opacity-50': past.length < 2 })}>
|
||||
<UndoOutlined fontSize="medium" />
|
||||
</ButtonBase>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.redo')}>
|
||||
<ButtonBase onClick={handleRedo} className={clsx({ 'pointer-events-none opacity-50': future.length === 0 })}>
|
||||
<RedoOutlined fontSize="medium" />
|
||||
</ButtonBase>
|
||||
</Tooltip>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.zoom-in')}>
|
||||
<ButtonBase onClick={() => zoomIn(0.25)}>
|
||||
<ZoomIn fontSize="medium" />
|
||||
@ -97,17 +118,18 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
|
||||
|
||||
{isDesktop && (
|
||||
<>
|
||||
{pages.length > 1 && (
|
||||
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.toggle-orientation')}>
|
||||
<ButtonBase onClick={handleTogglePageOrientation}>
|
||||
{orientation === 'vertical' ? (
|
||||
<AlignHorizontalCenter fontSize="medium" />
|
||||
) : (
|
||||
<AlignVerticalCenter fontSize="medium" />
|
||||
)}
|
||||
</ButtonBase>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.toggle-orientation')}>
|
||||
<ButtonBase
|
||||
onClick={handleTogglePageOrientation}
|
||||
className={clsx({ 'pointer-events-none opacity-50': pages.length === 1 })}
|
||||
>
|
||||
{orientation === 'vertical' ? (
|
||||
<AlignHorizontalCenter fontSize="medium" />
|
||||
) : (
|
||||
<AlignVerticalCenter fontSize="medium" />
|
||||
)}
|
||||
</ButtonBase>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.toggle-page-break-line')}>
|
||||
<ButtonBase onClick={handleTogglePageBreakLine}>
|
||||
|
||||
@ -13,7 +13,7 @@ import Page from './Page';
|
||||
const Center = () => {
|
||||
const orientation = useAppSelector((state) => state.build.page.orientation);
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
const resume = useAppSelector((state) => state.resume.present);
|
||||
const layout: string[][][] = get(resume, 'metadata.layout');
|
||||
|
||||
if (isEmpty(resume)) return null;
|
||||
|
||||
@ -57,7 +57,7 @@ const Header = () => {
|
||||
|
||||
const { mutateAsync: deleteMutation } = useMutation<void, ServerError, DeleteResumeParams>(deleteResume);
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
const resume = useAppSelector((state) => state.resume.present);
|
||||
const { left, right } = useAppSelector((state) => state.build.sidebar);
|
||||
|
||||
const name = useMemo(() => get(resume, 'name'), [resume]);
|
||||
|
||||
@ -20,7 +20,7 @@ type Props = PageProps & {
|
||||
const Page: React.FC<Props> = ({ page, showPageNumbers = false }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
const resume = useAppSelector((state) => state.resume.present);
|
||||
const breakLine: boolean = useAppSelector((state) => state.build.page.breakLine);
|
||||
|
||||
const theme: Theme = get(resume, 'metadata.theme');
|
||||
|
||||
@ -25,7 +25,7 @@ const LeftSidebar = () => {
|
||||
|
||||
const isDesktop = useMediaQuery(theme.breakpoints.up('lg'));
|
||||
|
||||
const sections = useAppSelector((state) => state.resume.sections);
|
||||
const sections = useAppSelector((state) => state.resume.present.sections);
|
||||
const { open } = useAppSelector((state) => state.build.sidebar.left);
|
||||
|
||||
const customSections = useMemo(() => getCustomSections(sections), [sections]);
|
||||
|
||||
@ -32,7 +32,7 @@ const Basics = () => {
|
||||
<PhotoUpload />
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2 w-full sm:col-span-2">
|
||||
<div className="grid w-full gap-2 sm:col-span-2">
|
||||
<ResumeInput label={t<string>('builder.leftSidebar.sections.basics.name.label')} path="basics.name" />
|
||||
|
||||
<Button variant="outlined" startIcon={<PhotoFilter />} onClick={handleClick}>
|
||||
|
||||
@ -12,7 +12,7 @@ const PhotoFilters = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const photo: Photo = useAppSelector((state) => get(state.resume, 'basics.photo'));
|
||||
const photo: Photo = useAppSelector((state) => get(state.resume.present, 'basics.photo'));
|
||||
const size: number = get(photo, 'filters.size', 128);
|
||||
const shape: PhotoShape = get(photo, 'filters.shape', 'square');
|
||||
const grayscale: boolean = get(photo, 'filters.grayscale', false);
|
||||
|
||||
@ -21,8 +21,8 @@ const PhotoUpload: React.FC = () => {
|
||||
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const id: number = useAppSelector((state) => get(state.resume, 'id'));
|
||||
const photo: Photo = useAppSelector((state) => get(state.resume, 'basics.photo'));
|
||||
const id: number = useAppSelector((state) => get(state.resume.present, 'id'));
|
||||
const photo: Photo = useAppSelector((state) => get(state.resume.present, 'basics.photo'));
|
||||
|
||||
const { mutateAsync: uploadMutation, isLoading } = useMutation<Resume, ServerError, UploadPhotoParams>(uploadPhoto);
|
||||
|
||||
|
||||
@ -37,8 +37,8 @@ const Section: React.FC<Props> = ({
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector<string>((state) => get(state.resume, `${path}.name`, name));
|
||||
const visibility = useAppSelector<boolean>((state) => get(state.resume, `${path}.visible`, true));
|
||||
const heading = useAppSelector<string>((state) => get(state.resume.present, `${path}.name`, name));
|
||||
const visibility = useAppSelector<boolean>((state) => get(state.resume.present, `${path}.visible`, true));
|
||||
|
||||
const handleAdd = () => {
|
||||
const id = path.split('.')[1];
|
||||
|
||||
@ -18,7 +18,7 @@ const SectionSettings: React.FC<Props> = ({ path }) => {
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
|
||||
|
||||
const columns = useAppSelector<number>((state) => get(state.resume, `${path}.columns`, 2));
|
||||
const columns = useAppSelector<number>((state) => get(state.resume.present, `${path}.columns`, 2));
|
||||
|
||||
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
|
||||
@ -17,7 +17,7 @@ const CustomCSS = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const customCSS: CustomCSSType = useAppSelector((state) => get(state.resume, 'metadata.css', {}));
|
||||
const customCSS: CustomCSSType = useAppSelector((state) => get(state.resume.present, 'metadata.css', {}));
|
||||
|
||||
const handleChange = (value: string | undefined) => {
|
||||
dispatch(setResumeState({ path: 'metadata.css.value', value }));
|
||||
|
||||
@ -13,7 +13,7 @@ import { useAppSelector } from '@/store/hooks';
|
||||
const Export = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
const resume = useAppSelector((state) => state.resume.present);
|
||||
|
||||
const { mutateAsync, isLoading } = useMutation<string, ServerError, PrintResumeAsPdfParams>(printResumeAsPdf);
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { DragDropContext, Draggable, DraggableLocation, Droppable, DropResult } from '@hello-pangea/dnd';
|
||||
import { Add, Close, Restore } from '@mui/icons-material';
|
||||
import { Button, IconButton, Tooltip } from '@mui/material';
|
||||
import clsx from 'clsx';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import get from 'lodash/get';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { DragDropContext, Draggable, DraggableLocation, Droppable, DropResult } from 'react-beautiful-dnd';
|
||||
|
||||
import Heading from '@/components/shared/Heading';
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||
@ -23,8 +23,8 @@ const Layout = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const layout = useAppSelector((state) => state.resume.metadata.layout);
|
||||
const resumeSections = useAppSelector((state) => state.resume.sections);
|
||||
const layout = useAppSelector((state) => state.resume.present.metadata.layout);
|
||||
const resumeSections = useAppSelector((state) => state.resume.present.sections);
|
||||
|
||||
const onDragEnd = (dropResult: DropResult) => {
|
||||
const { source: srcLoc, destination: destLoc } = dropResult;
|
||||
|
||||
@ -38,9 +38,9 @@ const Settings = () => {
|
||||
|
||||
const [confirmReset, setConfirmReset] = useState(false);
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
const resume = useAppSelector((state) => state.resume.present);
|
||||
const theme = useAppSelector((state) => state.build.theme);
|
||||
const pages = useAppSelector((state) => state.resume.metadata.layout);
|
||||
const pages = useAppSelector((state) => state.resume.present.metadata.layout);
|
||||
const breakLine = useAppSelector((state) => state.build.page.breakLine);
|
||||
const orientation = useAppSelector((state) => state.build.page.orientation);
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ const Sharing = () => {
|
||||
|
||||
const [showShortUrl, setShowShortUrl] = useState(false);
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
const resume = useAppSelector((state) => state.resume.present);
|
||||
const isPublic = useMemo(() => get(resume, 'public'), [resume]);
|
||||
const url = useMemo(() => getResumeUrl(resume, { withHost: true }), [resume]);
|
||||
const shortUrl = useMemo(() => getResumeUrl(resume, { withHost: true, shortUrl: true }), [resume]);
|
||||
|
||||
@ -16,7 +16,7 @@ const Templates = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const currentTemplate: string = useAppSelector((state) => get(state.resume, 'metadata.template'));
|
||||
const currentTemplate: string = useAppSelector((state) => get(state.resume.present, 'metadata.template'));
|
||||
|
||||
const handleChange = (template: TemplateMeta) => {
|
||||
dispatch(setResumeState({ path: 'metadata.template', value: template.id }));
|
||||
@ -31,7 +31,7 @@ const Templates = () => {
|
||||
<div key={template.id} className={styles.template}>
|
||||
<div className={clsx(styles.preview, { [styles.selected]: template.id === currentTemplate })}>
|
||||
<ButtonBase onClick={() => handleChange(template)}>
|
||||
<Image src={template.preview} alt={template.name} className="rounded-sm" layout="fill" />
|
||||
<Image src={template.preview} alt={template.name} className="rounded-sm" layout="fill" priority />
|
||||
</ButtonBase>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
.container {
|
||||
@apply grid sm:grid-cols-2 gap-4;
|
||||
@apply grid gap-4 sm:grid-cols-2;
|
||||
}
|
||||
|
||||
.colorOptions {
|
||||
@apply col-span-2 mb-4;
|
||||
@apply grid grid-cols-8 gap-y-2 justify-items-center;
|
||||
@apply grid grid-cols-8 justify-items-center gap-y-2;
|
||||
}
|
||||
|
||||
@ -16,7 +16,9 @@ const Theme = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { background, text, primary } = useAppSelector<ThemeType>((state) => get(state.resume, 'metadata.theme'));
|
||||
const { background, text, primary } = useAppSelector<ThemeType>((state) =>
|
||||
get(state.resume.present, 'metadata.theme')
|
||||
);
|
||||
|
||||
const handleChange = (property: string, color: string) => {
|
||||
dispatch(setResumeState({ path: `metadata.theme.${property}`, value: color[0] !== '#' ? `#${color}` : color }));
|
||||
|
||||
@ -33,7 +33,7 @@ const Widgets: React.FC<WidgetProps> = ({ label, category }) => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { family, size } = useAppSelector<TypographyType>((state) => get(state.resume, 'metadata.typography'));
|
||||
const { family, size } = useAppSelector<TypographyType>((state) => get(state.resume.present, 'metadata.typography'));
|
||||
|
||||
const { data: fonts } = useQuery(FONTS_QUERY, fetchFonts, {
|
||||
select: (fonts) => fonts.sort((a, b) => a.category.localeCompare(b.category)),
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
.testimony {
|
||||
@apply grid gap-2;
|
||||
@apply border-2 rounded p-4 dark:border-neutral-800;
|
||||
@apply rounded border-2 p-4 dark:border-neutral-800;
|
||||
|
||||
blockquote {
|
||||
@apply text-xs leading-normal text-justify opacity-90;
|
||||
@apply text-justify text-xs leading-normal opacity-90;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
}
|
||||
|
||||
.header {
|
||||
@apply sticky top-0 left-0 right-0 z-50 pt-6 bg-neutral-50 dark:bg-neutral-900;
|
||||
@apply sticky top-0 left-0 right-0 z-50 bg-neutral-50 pt-6 dark:bg-neutral-900;
|
||||
@apply flex items-center justify-between;
|
||||
@apply w-full border-b pb-5 dark:border-white/10;
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
}
|
||||
|
||||
.footer {
|
||||
@apply sticky bottom-0 left-0 right-0 z-50 pb-6 bg-neutral-50 dark:bg-neutral-900;
|
||||
@apply sticky bottom-0 left-0 right-0 z-50 bg-neutral-50 pb-6 dark:bg-neutral-900;
|
||||
@apply flex items-center justify-end gap-x-4;
|
||||
@apply w-full border-t pt-5 dark:border-white/10;
|
||||
}
|
||||
|
||||
@ -32,8 +32,8 @@ const Heading: React.FC<Props> = ({
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`, name));
|
||||
const visibility = useAppSelector((state) => get(state.resume, `${path}.visible`, true));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`, name));
|
||||
const visibility = useAppSelector((state) => get(state.resume.present, `${path}.visible`, true));
|
||||
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
|
||||
|
||||
@ -9,5 +9,5 @@
|
||||
}
|
||||
|
||||
.language {
|
||||
@apply py-2 px-4 cursor-pointer text-center hover:underline;
|
||||
@apply cursor-pointer py-2 px-4 text-center hover:underline;
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ const List: React.FC<Props> = ({
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const list: Array<ListItemType> = useAppSelector((state) => get(state.resume, path, []));
|
||||
const list: Array<ListItemType> = useAppSelector((state) => get(state.resume.present, path, []));
|
||||
|
||||
const handleEdit = (item: ListItemType) => {
|
||||
isFunction(onEdit) && onEdit(item);
|
||||
|
||||
@ -5,6 +5,7 @@ import styles from './Loading.module.scss';
|
||||
|
||||
const Loading: React.FC = () => {
|
||||
const { isReady } = useRouter();
|
||||
|
||||
const isFetching = useIsFetching();
|
||||
const isMutating = useIsMutating();
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ interface Props {
|
||||
const ResumeInput: React.FC<Props> = ({ type = 'text', label, path, className, markdownSupported = false }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const stateValue = useAppSelector((state) => get(state.resume, path, ''));
|
||||
const stateValue = useAppSelector((state) => get(state.resume.present, path, ''));
|
||||
|
||||
useEffect(() => {
|
||||
setValue(stateValue);
|
||||
|
||||
@ -44,7 +44,7 @@ const AwardModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
|
||||
@ -44,7 +44,7 @@ const CertificateModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
|
||||
|
||||
@ -60,7 +60,7 @@ const CustomModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal['builder.sections.custom']);
|
||||
|
||||
const path: string = get(payload, 'path', '');
|
||||
|
||||
@ -57,7 +57,7 @@ const EducationModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
|
||||
@ -35,7 +35,7 @@ const InterestModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
|
||||
@ -36,7 +36,7 @@ const LanguageModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
|
||||
@ -53,7 +53,7 @@ const ProjectModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
|
||||
@ -44,7 +44,7 @@ const PublicationModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
|
||||
@ -41,7 +41,7 @@ const ReferenceModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
|
||||
@ -39,7 +39,7 @@ const SkillModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
|
||||
@ -50,7 +50,7 @@ const VolunteerModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
|
||||
@ -50,7 +50,7 @@ const WorkModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
|
||||
@ -69,7 +69,8 @@ const CreateResumeModal: React.FC = () => {
|
||||
try {
|
||||
await mutateAsync({ name, slug, public: isPublic });
|
||||
|
||||
queryClient.invalidateQueries(RESUMES_QUERY);
|
||||
await queryClient.invalidateQueries(RESUMES_QUERY);
|
||||
|
||||
handleClose();
|
||||
} catch (error: any) {
|
||||
toast.error(error.message);
|
||||
|
||||
@ -13,13 +13,14 @@
|
||||
"@emotion/css": "^11.10.0",
|
||||
"@emotion/react": "^11.10.0",
|
||||
"@emotion/styled": "^11.10.0",
|
||||
"@hello-pangea/dnd": "^16.0.0",
|
||||
"@hookform/resolvers": "2.9.7",
|
||||
"@monaco-editor/react": "^4.4.5",
|
||||
"@mui/icons-material": "^5.8.4",
|
||||
"@mui/lab": "^5.0.0-alpha.95",
|
||||
"@mui/material": "^5.10.1",
|
||||
"@mui/system": "^5.10.1",
|
||||
"@mui/x-date-pickers": "5.0.0-beta.6",
|
||||
"@mui/icons-material": "^5.10.2",
|
||||
"@mui/lab": "^5.0.0-alpha.96",
|
||||
"@mui/material": "^5.10.2",
|
||||
"@mui/system": "^5.10.2",
|
||||
"@mui/x-date-pickers": "5.0.0-beta.7",
|
||||
"@next/env": "^12.2.5",
|
||||
"@react-oauth/google": "^0.2.6",
|
||||
"@reduxjs/toolkit": "^1.8.5",
|
||||
@ -34,12 +35,11 @@
|
||||
"nanoid": "^3.3.4",
|
||||
"next": "12.2.5",
|
||||
"next-i18next": "^12.0.0",
|
||||
"react": "18",
|
||||
"react-beautiful-dnd": "^13.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-dnd": "16.0.1",
|
||||
"react-dnd-html5-backend": "16.0.1",
|
||||
"react-dom": "18",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.34.2",
|
||||
"react-hot-toast": "2.3.0",
|
||||
"react-hotkeys-hook": "^3.4.7",
|
||||
@ -51,32 +51,29 @@
|
||||
"redux": "^4.2.0",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-saga": "^1.2.1",
|
||||
"redux-undo": "^1.0.1",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"sharp": "^0.30.7",
|
||||
"uuid": "^8.3.2",
|
||||
"webfontloader": "^1.6.28"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "18",
|
||||
"@types/react-dom": "18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.10",
|
||||
"@babel/core": "^7.18.13",
|
||||
"@reactive-resume/schema": "workspace:*",
|
||||
"@tailwindcss/typography": "^0.5.4",
|
||||
"@types/downloadjs": "^1.4.3",
|
||||
"@types/lodash": "^4.14.184",
|
||||
"@types/node": "18.7.9",
|
||||
"@types/react": "18",
|
||||
"@types/react-beautiful-dnd": "^13.1.2",
|
||||
"@types/react-dom": "18",
|
||||
"@types/node": "^18.7.13",
|
||||
"@types/react": "^18.0.17",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-redux": "^7.1.24",
|
||||
"@types/tailwindcss": "^3.0.11",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/webfontloader": "^1.6.34",
|
||||
"autoprefixer": "^10.4.8",
|
||||
"csstype": "^3.1.0",
|
||||
"eslint-config-next": "12.2.5",
|
||||
"eslint-config-next": "^12.2.5",
|
||||
"eslint-plugin-tailwindcss": "^3.6.0",
|
||||
"next-sitemap": "^3.1.21",
|
||||
"postcss": "^8.4.16",
|
||||
"sass": "^1.54.5",
|
||||
|
||||
@ -51,7 +51,7 @@ const Preview: NextPage<Props> = ({ username, slug, resume: initialData }) => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
const resume = useAppSelector((state) => state.resume.present);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialData && !isEmpty(initialData)) {
|
||||
|
||||
@ -56,7 +56,7 @@ export const getServerSideProps: GetServerSideProps<Props | Promise<Props>, Quer
|
||||
const Printer: NextPage<Props> = ({ resume: initialData, locale }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
const resume = useAppSelector((state) => state.resume.present);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialData) dispatch(setResume(initialData));
|
||||
|
||||
@ -4,7 +4,9 @@ import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||
import { useEffect } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { ActionCreators } from 'redux-undo';
|
||||
|
||||
import ResumeCard from '@/components/dashboard/ResumeCard';
|
||||
import ResumePreview from '@/components/dashboard/ResumePreview';
|
||||
@ -12,6 +14,7 @@ import Avatar from '@/components/shared/Avatar';
|
||||
import Logo from '@/components/shared/Logo';
|
||||
import { RESUMES_QUERY } from '@/constants/index';
|
||||
import { fetchResumes } from '@/services/resume';
|
||||
import { useAppDispatch } from '@/store/hooks';
|
||||
import styles from '@/styles/pages/Dashboard.module.scss';
|
||||
|
||||
export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => {
|
||||
@ -25,8 +28,14 @@ export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => {
|
||||
const Dashboard: NextPage = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { data } = useQuery(RESUMES_QUERY, fetchResumes);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(ActionCreators.clearHistory());
|
||||
}, []);
|
||||
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { NextPage } from 'next';
|
||||
|
||||
const PrivacyPolicy: NextPage = () => (
|
||||
<div className="mx-auto my-12 prose dark:prose-invert">
|
||||
<div className="prose mx-auto my-12 dark:prose-invert">
|
||||
<h1>Privacy Policy</h1>
|
||||
|
||||
<p>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { NextPage } from 'next';
|
||||
|
||||
const TermsOfService: NextPage = () => (
|
||||
<div className="mx-auto my-12 prose dark:prose-invert">
|
||||
<div className="prose mx-auto my-12 dark:prose-invert">
|
||||
<h1>Terms of Service</h1>
|
||||
|
||||
<p>
|
||||
|
||||
@ -84,7 +84,9 @@
|
||||
"toggle-page-break-line": "Toggle Page Break Line",
|
||||
"toggle-sidebars": "Toggle Sidebars",
|
||||
"zoom-in": "Zoom In",
|
||||
"zoom-out": "Zoom Out"
|
||||
"zoom-out": "Zoom Out",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { combineReducers, configureStore } from '@reduxjs/toolkit';
|
||||
import { persistReducer, persistStore } from 'redux-persist';
|
||||
import createSagaMiddleware from 'redux-saga';
|
||||
import undoable from 'redux-undo';
|
||||
|
||||
import authReducer from '@/store/auth/authSlice';
|
||||
import buildReducer from '@/store/build/buildSlice';
|
||||
@ -16,7 +17,7 @@ const reducers = combineReducers({
|
||||
auth: authReducer,
|
||||
modal: modalReducer,
|
||||
build: buildReducer,
|
||||
resume: resumeReducer,
|
||||
resume: undoable(resumeReducer),
|
||||
});
|
||||
|
||||
const persistedReducers = persistReducer({ key: 'root', storage, whitelist: ['auth', 'build'] }, reducers);
|
||||
|
||||
@ -3,6 +3,7 @@ import debounce from 'lodash/debounce';
|
||||
import { select, takeLatest } from 'redux-saga/effects';
|
||||
|
||||
import { updateResume } from '@/services/resume';
|
||||
import { RootState } from '@/store/index';
|
||||
|
||||
import {
|
||||
addItem,
|
||||
@ -19,7 +20,7 @@ const DEBOUNCE_WAIT = 1000;
|
||||
const debouncedSync = debounce((resume: Resume) => updateResume(resume), DEBOUNCE_WAIT);
|
||||
|
||||
function* handleSync() {
|
||||
const resume: Resume = yield select((state) => state.resume);
|
||||
const resume: Resume = yield select((state: RootState) => state.resume.present);
|
||||
|
||||
debouncedSync(resume);
|
||||
}
|
||||
|
||||
@ -2,14 +2,14 @@
|
||||
@apply m-6 grid gap-8 text-center md:m-8 md:text-left;
|
||||
|
||||
footer {
|
||||
@apply flex flex-col gap-8 items-center sm:items-end justify-between sm:flex-row sm:gap-0;
|
||||
@apply flex flex-col items-center justify-between gap-8 sm:flex-row sm:items-end sm:gap-0;
|
||||
|
||||
.actions {
|
||||
@apply flex gap-2;
|
||||
}
|
||||
|
||||
.version > div {
|
||||
@apply text-xs font-medium opacity-50 mt-3;
|
||||
@apply mt-3 text-xs font-medium opacity-50;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -49,7 +49,7 @@
|
||||
}
|
||||
|
||||
.screenshots {
|
||||
@apply grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-4;
|
||||
@apply grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-6;
|
||||
|
||||
.image {
|
||||
@apply relative h-64 rounded hover:opacity-75;
|
||||
|
||||
@ -16,8 +16,8 @@ import Section from './widgets/Section';
|
||||
const Castform: React.FC<PageProps> = ({ page }) => {
|
||||
const isFirstPage = useMemo(() => page === 0, [page]);
|
||||
|
||||
const layout: string[][] = useAppSelector((state) => state.resume.metadata.layout[page]);
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||
const layout: string[][] = useAppSelector((state) => state.resume.present.metadata.layout[page]);
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||
|
||||
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
||||
const color = useMemo(() => (contrast === 'dark' ? theme.text : theme.background), [theme, contrast]);
|
||||
|
||||
@ -6,7 +6,7 @@ import { useMemo } from 'react';
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
|
||||
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||
const darkerPrimary = useMemo(() => darken(theme.primary, 0.2), [theme.primary]);
|
||||
|
||||
return (
|
||||
|
||||
@ -15,11 +15,11 @@ import { getContrastColor } from '@/utils/styles';
|
||||
import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
||||
|
||||
export const MastheadSidebar: React.FC = () => {
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume, 'metadata.date.format'));
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
||||
const { name, headline, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
||||
(state) => state.resume.basics
|
||||
(state) => state.resume.present.basics
|
||||
);
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
||||
const color = useMemo(() => (contrast === 'dark' ? theme.text : theme.background), [theme, contrast]);
|
||||
|
||||
@ -72,7 +72,7 @@ export const MastheadSidebar: React.FC = () => {
|
||||
};
|
||||
|
||||
export const MastheadMain: React.FC = () => {
|
||||
const { summary } = useAppSelector((state) => state.resume.basics);
|
||||
const { summary } = useAppSelector((state) => state.resume.present.basics);
|
||||
|
||||
return (
|
||||
<div className="px-4 pt-4">
|
||||
|
||||
@ -3,6 +3,7 @@ import { ListItem, Section as SectionType } from '@reactive-resume/schema';
|
||||
import get from 'lodash/get';
|
||||
import isArray from 'lodash/isArray';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import Markdown from '@/components/shared/Markdown';
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
@ -20,15 +21,17 @@ const Section: React.FC<SectionProps> = ({
|
||||
headlinePath = 'headline',
|
||||
keywordsPath = 'keywords',
|
||||
}) => {
|
||||
const section: SectionType = useAppSelector((state) => get(state.resume, path, {}));
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume, 'metadata.date.format'));
|
||||
const section: SectionType = useAppSelector((state) => get(state.resume.present, path, {}));
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
||||
|
||||
const sectionId = useMemo(() => section.id || path.replace('sections.', ''), [path, section]);
|
||||
|
||||
if (!section.visible) return null;
|
||||
|
||||
if (isArray(section.items) && isEmpty(section.items)) return null;
|
||||
|
||||
return (
|
||||
<section>
|
||||
<section id={`Castform_${sectionId}`}>
|
||||
<Heading>{section.name}</Heading>
|
||||
|
||||
<div
|
||||
@ -67,7 +70,7 @@ const Section: React.FC<SectionProps> = ({
|
||||
<div className="grid gap-1">
|
||||
{level && <span className="opacity-75">{level}</span>}
|
||||
{levelNum > 0 && (
|
||||
<div className="level flex">
|
||||
<div className="flex">
|
||||
{Array.from(Array(5).keys()).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
|
||||
@ -17,8 +17,8 @@ import Section from './widgets/Section';
|
||||
const Gengar: React.FC<PageProps> = ({ page }) => {
|
||||
const isFirstPage = useMemo(() => page === 0, [page]);
|
||||
|
||||
const layout: string[][] = useAppSelector((state) => state.resume.metadata.layout[page]);
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||
const layout: string[][] = useAppSelector((state) => state.resume.present.metadata.layout[page]);
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
||||
const backgroundColor: string = useMemo(() => alpha(theme.primary, 0.15), [theme.primary]);
|
||||
const color = useMemo(() => (contrast === 'dark' ? theme.text : theme.background), [theme, contrast]);
|
||||
|
||||
@ -4,7 +4,7 @@ import get from 'lodash/get';
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
|
||||
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||
|
||||
return (
|
||||
<h3
|
||||
|
||||
@ -16,11 +16,11 @@ import { getContrastColor } from '@/utils/styles';
|
||||
import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
||||
|
||||
export const MastheadSidebar: React.FC = () => {
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume, 'metadata.date.format'));
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
||||
const { name, headline, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
||||
(state) => state.resume.basics
|
||||
(state) => state.resume.present.basics
|
||||
);
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
||||
const iconColor = useMemo(() => (contrast === 'dark' ? theme.text : theme.background), [theme, contrast]);
|
||||
|
||||
@ -73,10 +73,10 @@ export const MastheadSidebar: React.FC = () => {
|
||||
};
|
||||
|
||||
export const MastheadMain: React.FC = () => {
|
||||
const primaryColor: string = useAppSelector((state) => get(state.resume, 'metadata.theme.primary'));
|
||||
const primaryColor: string = useAppSelector((state) => get(state.resume.present, 'metadata.theme.primary'));
|
||||
const backgroundColor: string = useMemo(() => alpha(primaryColor, 0.15), [primaryColor]);
|
||||
|
||||
const { summary } = useAppSelector((state) => state.resume.basics);
|
||||
const { summary } = useAppSelector((state) => state.resume.present.basics);
|
||||
|
||||
return (
|
||||
<div className="grid gap-2 p-4" style={{ backgroundColor }}>
|
||||
|
||||
@ -3,6 +3,7 @@ import { ListItem, Section as SectionType } from '@reactive-resume/schema';
|
||||
import get from 'lodash/get';
|
||||
import isArray from 'lodash/isArray';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import Markdown from '@/components/shared/Markdown';
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
@ -20,16 +21,18 @@ const Section: React.FC<SectionProps> = ({
|
||||
headlinePath = 'headline',
|
||||
keywordsPath = 'keywords',
|
||||
}) => {
|
||||
const section: SectionType = useAppSelector((state) => get(state.resume, path, {}));
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume, 'metadata.date.format'));
|
||||
const primaryColor: string = useAppSelector((state) => get(state.resume, 'metadata.theme.primary'));
|
||||
const section: SectionType = useAppSelector((state) => get(state.resume.present, path, {}));
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
||||
const primaryColor: string = useAppSelector((state) => get(state.resume.present, 'metadata.theme.primary'));
|
||||
|
||||
const sectionId = useMemo(() => section.id || path.replace('sections.', ''), [path, section]);
|
||||
|
||||
if (!section.visible) return null;
|
||||
|
||||
if (isArray(section.items) && isEmpty(section.items)) return null;
|
||||
|
||||
return (
|
||||
<section>
|
||||
<section id={`Gengar_${sectionId}`}>
|
||||
<Heading>{section.name}</Heading>
|
||||
|
||||
<div
|
||||
|
||||
@ -13,8 +13,8 @@ import Section from './widgets/Section';
|
||||
const Glalie: React.FC<PageProps> = ({ page }) => {
|
||||
const isFirstPage = useMemo(() => page === 0, [page]);
|
||||
|
||||
const layout: string[][] = useAppSelector((state) => state.resume.metadata.layout[page]);
|
||||
const primaryColor: string = useAppSelector((state) => get(state.resume, 'metadata.theme.primary'));
|
||||
const layout: string[][] = useAppSelector((state) => state.resume.present.metadata.layout[page]);
|
||||
const primaryColor: string = useAppSelector((state) => get(state.resume.present, 'metadata.theme.primary'));
|
||||
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
|
||||
@ -13,7 +13,7 @@ type Props = {
|
||||
};
|
||||
|
||||
const BadgeDisplay: React.FC<Props> = ({ items }) => {
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
||||
|
||||
if (!isArray(items) || isEmpty(items)) return null;
|
||||
|
||||
@ -4,7 +4,7 @@ import get from 'lodash/get';
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
|
||||
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||
|
||||
return (
|
||||
<h3
|
||||
|
||||
@ -10,10 +10,10 @@ import getProfileIcon from '@/utils/getProfileIcon';
|
||||
import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
||||
|
||||
export const MastheadSidebar: React.FC = () => {
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume, 'metadata.date.format'));
|
||||
const primaryColor: string = useAppSelector((state) => get(state.resume, 'metadata.theme.primary'));
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
||||
const primaryColor: string = useAppSelector((state) => get(state.resume.present, 'metadata.theme.primary'));
|
||||
const { name, headline, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
||||
(state) => state.resume.basics
|
||||
(state) => state.resume.present.basics
|
||||
);
|
||||
|
||||
return (
|
||||
@ -65,7 +65,7 @@ export const MastheadSidebar: React.FC = () => {
|
||||
};
|
||||
|
||||
export const MastheadMain: React.FC = () => {
|
||||
const { summary } = useAppSelector((state) => state.resume.basics);
|
||||
const { summary } = useAppSelector((state) => state.resume.present.basics);
|
||||
|
||||
return <Markdown>{summary}</Markdown>;
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@ import { ListItem, Section as SectionType } from '@reactive-resume/schema';
|
||||
import get from 'lodash/get';
|
||||
import isArray from 'lodash/isArray';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import Markdown from '@/components/shared/Markdown';
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
@ -21,16 +22,18 @@ const Section: React.FC<SectionProps> = ({
|
||||
headlinePath = 'headline',
|
||||
keywordsPath = 'keywords',
|
||||
}) => {
|
||||
const section: SectionType = useAppSelector((state) => get(state.resume, path, {}));
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume, 'metadata.date.format'));
|
||||
const primaryColor: string = useAppSelector((state) => get(state.resume, 'metadata.theme.primary'));
|
||||
const section: SectionType = useAppSelector((state) => get(state.resume.present, path, {}));
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
||||
const primaryColor: string = useAppSelector((state) => get(state.resume.present, 'metadata.theme.primary'));
|
||||
|
||||
const sectionId = useMemo(() => section.id || path.replace('sections.', ''), [path, section]);
|
||||
|
||||
if (!section.visible) return null;
|
||||
|
||||
if (isArray(section.items) && isEmpty(section.items)) return null;
|
||||
|
||||
return (
|
||||
<section>
|
||||
<section id={`Glalie_${sectionId}`}>
|
||||
<Heading>{section.name}</Heading>
|
||||
|
||||
<div
|
||||
|
||||
@ -12,8 +12,8 @@ import Section from './widgets/Section';
|
||||
const Kakuna: React.FC<PageProps> = ({ page }) => {
|
||||
const isFirstPage = useMemo(() => page === 0, [page]);
|
||||
|
||||
const { summary } = useAppSelector((state) => state.resume.basics);
|
||||
const layout: string[][] = useAppSelector((state) => state.resume.metadata.layout[page]);
|
||||
const { summary } = useAppSelector((state) => state.resume.present.basics);
|
||||
const layout: string[][] = useAppSelector((state) => state.resume.present.metadata.layout[page]);
|
||||
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
|
||||
@ -12,7 +12,7 @@ type Props = {
|
||||
};
|
||||
|
||||
const BadgeDisplay: React.FC<Props> = ({ items }) => {
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
||||
|
||||
if (!isArray(items) || isEmpty(items)) return null;
|
||||
|
||||
@ -10,13 +10,13 @@ import getProfileIcon from '@/utils/getProfileIcon';
|
||||
import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
||||
|
||||
const Masthead = () => {
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume, 'metadata.date.format'));
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
||||
const { name, photo, email, phone, website, birthdate, headline, location, profiles } = useAppSelector(
|
||||
(state) => state.resume.basics
|
||||
(state) => state.resume.present.basics
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="grid gap-3 justify-center mb-4 border-b pb-4 text-center">
|
||||
<div className="mb-4 grid justify-center gap-3 border-b pb-4 text-center">
|
||||
<div className="mx-auto">
|
||||
{photo.visible && !isEmpty(photo.url) && (
|
||||
<img
|
||||
|
||||
@ -3,6 +3,7 @@ import { ListItem, Section as SectionType } from '@reactive-resume/schema';
|
||||
import get from 'lodash/get';
|
||||
import isArray from 'lodash/isArray';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import Markdown from '@/components/shared/Markdown';
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
@ -20,16 +21,18 @@ const Section: React.FC<SectionProps> = ({
|
||||
headlinePath = 'headline',
|
||||
keywordsPath = 'keywords',
|
||||
}) => {
|
||||
const section: SectionType = useAppSelector((state) => get(state.resume, path, {}));
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume, 'metadata.date.format'));
|
||||
const primaryColor: string = useAppSelector((state) => get(state.resume, 'metadata.theme.primary'));
|
||||
const section: SectionType = useAppSelector((state) => get(state.resume.present, path, {}));
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
||||
const primaryColor: string = useAppSelector((state) => get(state.resume.present, 'metadata.theme.primary'));
|
||||
|
||||
const sectionId = useMemo(() => section.id || path.replace('sections.', ''), [path, section]);
|
||||
|
||||
if (!section.visible) return null;
|
||||
|
||||
if (isArray(section.items) && isEmpty(section.items)) return null;
|
||||
|
||||
return (
|
||||
<section>
|
||||
<section id={`Kakuna_${sectionId}`}>
|
||||
<Heading>{section.name}</Heading>
|
||||
|
||||
<div
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
.container {
|
||||
@apply grid grid-cols-2 gap-8 px-6 py-4;
|
||||
@apply grid grid-cols-2 gap-4 px-6 py-4;
|
||||
|
||||
.column {
|
||||
@apply col-span-1 flex flex-col;
|
||||
.main {
|
||||
@apply grid gap-4;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
@apply grid gap-4;
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,15 +11,15 @@ import Section from './widgets/Section';
|
||||
const Leafish: React.FC<PageProps> = ({ page }) => {
|
||||
const isFirstPage = useMemo(() => page === 0, [page]);
|
||||
|
||||
const layout: string[][] = useAppSelector((state) => state.resume.metadata.layout[page]);
|
||||
const layout: string[][] = useAppSelector((state) => state.resume.present.metadata.layout[page]);
|
||||
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
{isFirstPage && <Masthead />}
|
||||
|
||||
<div className={styles.container}>
|
||||
<div className={styles.column}>{layout[0].map((key) => getSectionById(key, Section))}</div>
|
||||
<div className={styles.column}>{layout[1].map((key) => getSectionById(key, Section))}</div>
|
||||
<div className={styles.main}>{layout[0].map((key) => getSectionById(key, Section))}</div>
|
||||
<div className={styles.sidebar}>{layout[1].map((key) => getSectionById(key, Section))}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -4,11 +4,11 @@ import get from 'lodash/get';
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
|
||||
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||
|
||||
return (
|
||||
<h2
|
||||
className="pb-1 mb-2 font-bold uppercase opacity-75"
|
||||
className="mb-2 pb-1 font-bold uppercase opacity-75"
|
||||
style={{ borderBottomWidth: '3px', borderColor: theme.primary, color: theme.primary, display: 'inline-block' }}
|
||||
>
|
||||
{children}
|
||||
|
||||
@ -11,27 +11,19 @@ import getProfileIcon from '@/utils/getProfileIcon';
|
||||
import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
||||
|
||||
const Masthead: React.FC = () => {
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume, 'metadata.date.format'));
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
||||
const { name, photo, headline, summary, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
||||
(state) => state.resume.basics
|
||||
(state) => state.resume.present.basics
|
||||
);
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className="flex items-center gap-4 p-6"
|
||||
id="Masterhead_main"
|
||||
style={{ backgroundColor: alpha(theme.primary, 0.2) }}
|
||||
>
|
||||
<div className="flex items-center gap-4 p-6" style={{ backgroundColor: alpha(theme.primary, 0.2) }}>
|
||||
<div className="grid flex-1 gap-1">
|
||||
<h1 id="Masterhead_name">{name}</h1>
|
||||
<p style={{ color: theme.primary }} id="Masterhead_headline">
|
||||
{headline}
|
||||
</p>
|
||||
<p className="opacity-75" id="Masterhead_summary">
|
||||
{summary}
|
||||
</p>
|
||||
<h1>{name}</h1>
|
||||
<p style={{ color: theme.primary }}>{headline}</p>
|
||||
<p className="opacity-75">{summary}</p>
|
||||
</div>
|
||||
|
||||
{photo.visible && !isEmpty(photo.url) && (
|
||||
@ -41,13 +33,11 @@ const Masthead: React.FC = () => {
|
||||
width={photo.filters.size}
|
||||
height={photo.filters.size}
|
||||
className={getPhotoClassNames(photo.filters)}
|
||||
id="Masterhead_photo"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="grid gap-y-2 px-8 py-4"
|
||||
id="Masterhead_data"
|
||||
style={{ backgroundColor: alpha(theme.primary, 0.4), gridTemplateColumns: `repeat(2, minmax(0, 1fr))` }}
|
||||
>
|
||||
<DataDisplay icon={<Room />} className="col-span-2">
|
||||
|
||||
@ -3,6 +3,7 @@ import { ListItem, Section as SectionType } from '@reactive-resume/schema';
|
||||
import get from 'lodash/get';
|
||||
import isArray from 'lodash/isArray';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import Markdown from '@/components/shared/Markdown';
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
@ -20,22 +21,23 @@ const Section: React.FC<SectionProps> = ({
|
||||
headlinePath = 'headline',
|
||||
keywordsPath = 'keywords',
|
||||
}) => {
|
||||
const section: SectionType = useAppSelector((state) => get(state.resume, path, {}));
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume, 'metadata.date.format'));
|
||||
const primaryColor: string = useAppSelector((state) => get(state.resume, 'metadata.theme.primary'));
|
||||
const section: SectionType = useAppSelector((state) => get(state.resume.present, path, {}));
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
||||
const primaryColor: string = useAppSelector((state) => get(state.resume.present, 'metadata.theme.primary'));
|
||||
|
||||
const sectionId = useMemo(() => section.id || path.replace('sections.', ''), [path, section]);
|
||||
|
||||
if (!section.visible) return null;
|
||||
|
||||
if (isArray(section.items) && isEmpty(section.items)) return null;
|
||||
|
||||
return (
|
||||
<section className="mb-4">
|
||||
<section id={`Leafish_${sectionId}`}>
|
||||
<Heading>{section.name}</Heading>
|
||||
|
||||
<div
|
||||
className="grid items-start gap-4"
|
||||
style={{ gridTemplateColumns: `repeat(${section.columns}, minmax(0, 1fr))` }}
|
||||
id={`Section_${section.id}`}
|
||||
>
|
||||
{section.items.map((item: ListItem) => {
|
||||
const id = item.id,
|
||||
@ -52,16 +54,16 @@ const Section: React.FC<SectionProps> = ({
|
||||
date = formatDateString(get(item, 'date'), dateFormat);
|
||||
|
||||
return (
|
||||
<div key={id} id={id} className={`grid gap-1 mb-2 Section_${section.id}_inner`}>
|
||||
<div>
|
||||
{title && <div className="font-bold Section_title">{title}</div>}
|
||||
{subtitle && <div className="Section_subtitle">{subtitle}</div>}
|
||||
<div key={id} className="mb-2 grid gap-1">
|
||||
<div className="grid gap-1">
|
||||
{title && <div className="font-bold">{title}</div>}
|
||||
{subtitle && <div>{subtitle}</div>}
|
||||
{date && (
|
||||
<div className="italic text-xs Section_date" style={{ color: primaryColor }}>
|
||||
<div className="text-xs" style={{ color: primaryColor }}>
|
||||
({date})
|
||||
</div>
|
||||
)}
|
||||
{headline && <div className="opacity-50 Section_headline">{headline}</div>}
|
||||
{headline && <div className="opacity-50">{headline}</div>}
|
||||
</div>
|
||||
|
||||
{(level || levelNum > 0) && (
|
||||
@ -84,14 +86,7 @@ const Section: React.FC<SectionProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{summary && (
|
||||
<div>
|
||||
<div className="italic text-xs" style={{ color: primaryColor }}>
|
||||
Overview
|
||||
</div>
|
||||
<Markdown className={`marker:text-[${primaryColor}]`}>{summary}</Markdown>
|
||||
</div>
|
||||
)}
|
||||
{summary && <Markdown>{summary}</Markdown>}
|
||||
|
||||
{url && (
|
||||
<DataDisplay icon={<Link />} link={url} className="text-xs">
|
||||
|
||||
@ -12,8 +12,8 @@ import Section from './widgets/Section';
|
||||
const Onyx: React.FC<PageProps> = ({ page }) => {
|
||||
const isFirstPage = useMemo(() => page === 0, [page]);
|
||||
|
||||
const { summary } = useAppSelector((state) => state.resume.basics);
|
||||
const layout: string[][] = useAppSelector((state) => state.resume.metadata.layout[page]);
|
||||
const { summary } = useAppSelector((state) => state.resume.present.basics);
|
||||
const layout: string[][] = useAppSelector((state) => state.resume.present.metadata.layout[page]);
|
||||
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
|
||||
@ -4,7 +4,7 @@ import get from 'lodash/get';
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
|
||||
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||
|
||||
return (
|
||||
<h4 className="mb-2 font-bold uppercase" style={{ color: theme.primary }}>
|
||||
|
||||
@ -9,9 +9,9 @@ import getProfileIcon from '@/utils/getProfileIcon';
|
||||
import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
||||
|
||||
const Masthead: React.FC = () => {
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume, 'metadata.date.format'));
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
||||
const { name, photo, email, phone, website, birthdate, headline, location, profiles } = useAppSelector(
|
||||
(state) => state.resume.basics
|
||||
(state) => state.resume.present.basics
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@ -3,6 +3,7 @@ import { ListItem, Section as SectionType } from '@reactive-resume/schema';
|
||||
import get from 'lodash/get';
|
||||
import isArray from 'lodash/isArray';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import Markdown from '@/components/shared/Markdown';
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
@ -20,16 +21,18 @@ const Section: React.FC<SectionProps> = ({
|
||||
headlinePath = 'headline',
|
||||
keywordsPath = 'keywords',
|
||||
}) => {
|
||||
const section: SectionType = useAppSelector((state) => get(state.resume, path, {}));
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume, 'metadata.date.format'));
|
||||
const primaryColor: string = useAppSelector((state) => get(state.resume, 'metadata.theme.primary'));
|
||||
const section: SectionType = useAppSelector((state) => get(state.resume.present, path, {}));
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
||||
const primaryColor: string = useAppSelector((state) => get(state.resume.present, 'metadata.theme.primary'));
|
||||
|
||||
const sectionId = useMemo(() => section.id || path.replace('sections.', ''), [path, section]);
|
||||
|
||||
if (!section.visible) return null;
|
||||
|
||||
if (isArray(section.items) && isEmpty(section.items)) return null;
|
||||
|
||||
return (
|
||||
<section>
|
||||
<section id={`Onyx_${sectionId}`}>
|
||||
<Heading>{section.name}</Heading>
|
||||
|
||||
<div
|
||||
|
||||
@ -10,7 +10,7 @@ import Section from './widgets/Section';
|
||||
|
||||
const Pikachu: React.FC<PageProps> = ({ page }) => {
|
||||
const isFirstPage = useMemo(() => page === 0, [page]);
|
||||
const layout: string[][] = useAppSelector((state) => state.resume.metadata.layout[page]);
|
||||
const layout: string[][] = useAppSelector((state) => state.resume.present.metadata.layout[page]);
|
||||
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
|
||||
@ -4,7 +4,7 @@ import get from 'lodash/get';
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
|
||||
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||
|
||||
return (
|
||||
<h3
|
||||
|
||||
@ -13,13 +13,13 @@ import { getContrastColor } from '@/utils/styles';
|
||||
import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
||||
|
||||
export const MastheadSidebar: React.FC = () => {
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume, 'metadata.date.format'));
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
||||
const { name, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
||||
(state) => state.resume.basics
|
||||
(state) => state.resume.present.basics
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="col-span-2 grid justify-items-left gap-4">
|
||||
<div className="col-span-2 grid justify-items-start gap-4">
|
||||
{photo.visible && !isEmpty(photo.url) && (
|
||||
<img
|
||||
alt={name}
|
||||
@ -62,10 +62,10 @@ export const MastheadSidebar: React.FC = () => {
|
||||
};
|
||||
|
||||
export const MastheadMain: React.FC = () => {
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
||||
|
||||
const { name, summary, headline } = useAppSelector((state) => state.resume.basics);
|
||||
const { name, summary, headline } = useAppSelector((state) => state.resume.present.basics);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@ -3,6 +3,7 @@ import { ListItem, Section as SectionType } from '@reactive-resume/schema';
|
||||
import get from 'lodash/get';
|
||||
import isArray from 'lodash/isArray';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import Markdown from '@/components/shared/Markdown';
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
@ -20,16 +21,18 @@ const Section: React.FC<SectionProps> = ({
|
||||
headlinePath = 'headline',
|
||||
keywordsPath = 'keywords',
|
||||
}) => {
|
||||
const section: SectionType = useAppSelector((state) => get(state.resume, path, {}));
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume, 'metadata.date.format'));
|
||||
const primaryColor: string = useAppSelector((state) => get(state.resume, 'metadata.theme.primary'));
|
||||
const section: SectionType = useAppSelector((state) => get(state.resume.present, path, {}));
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
||||
const primaryColor: string = useAppSelector((state) => get(state.resume.present, 'metadata.theme.primary'));
|
||||
|
||||
const sectionId = useMemo(() => section.id || path.replace('sections.', ''), [path, section]);
|
||||
|
||||
if (!section.visible) return null;
|
||||
|
||||
if (isArray(section.items) && isEmpty(section.items)) return null;
|
||||
|
||||
return (
|
||||
<section>
|
||||
<section id={`Pikachu_${sectionId}`}>
|
||||
<Heading>{section.name}</Heading>
|
||||
|
||||
<div
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
@ -9,8 +10,12 @@ const DateWrapper: React.FC<React.PropsWithChildren<unknown>> = ({ children }) =
|
||||
|
||||
useEffect(() => {
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
// Set Default Timezone to UTC
|
||||
dayjs.tz.setDefault('UTC');
|
||||
|
||||
// Locales
|
||||
require('dayjs/locale/ar');
|
||||
require('dayjs/locale/bg');
|
||||
|
||||
@ -5,7 +5,7 @@ import { useCallback, useEffect } from 'react';
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
|
||||
const FontWrapper: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||
const typography = useAppSelector((state) => get(state.resume, 'metadata.typography'));
|
||||
const typography = useAppSelector((state) => get(state.resume.present, 'metadata.typography'));
|
||||
|
||||
const loadFonts = useCallback(async () => {
|
||||
const WebFont = (await import('webfontloader')).default;
|
||||
|
||||
26
package.json
26
package.json
@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "reactive-resume",
|
||||
"version": "3.6.1",
|
||||
"version": "3.6.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "env-cmd --silent turbo run dev",
|
||||
"lint": "eslint --fix .",
|
||||
"lint": "turbo run lint",
|
||||
"build": "env-cmd --silent turbo run build",
|
||||
"start": "env-cmd --silent turbo run start",
|
||||
"format": "prettier --write .",
|
||||
@ -17,20 +17,22 @@
|
||||
"docs"
|
||||
],
|
||||
"dependencies": {
|
||||
"turbo": "^1.4.3",
|
||||
"env-cmd": "^10.1.0"
|
||||
"env-cmd": "^10.1.0",
|
||||
"turbo": "^1.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^5.35.1",
|
||||
"@typescript-eslint/parser": "^5.35.1",
|
||||
"eslint": "^8.22.0",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.7.4",
|
||||
"standard-version": "^9.5.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"@typescript-eslint/parser": "^5.33.1",
|
||||
"eslint-plugin-simple-import-sort": "^7.0.0",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.1",
|
||||
"eslint-plugin-simple-import-sort": "^7.0.0"
|
||||
"prettier": "^2.7.1",
|
||||
"standard-version": "^9.5.0",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "17.0.2",
|
||||
"@types/react-dom": "17.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
3277
pnpm-lock.yaml
generated
3277
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,7 @@
|
||||
"main": "./dist/index.js",
|
||||
"typings": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"lint": "eslint --fix src",
|
||||
"dev": "tsc -w",
|
||||
"build": "tsc"
|
||||
},
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
{
|
||||
"name": "@reactive-resume/server",
|
||||
"scripts": {
|
||||
"lint": "eslint --fix src",
|
||||
"dev": "nest start --watch",
|
||||
"build": "rimraf dist && nest build",
|
||||
"debug": "nest start --debug --watch",
|
||||
"start": "node dist/main"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.154.0",
|
||||
"@aws-sdk/client-s3": "^3.157.0",
|
||||
"@nestjs/axios": "^0.1.0",
|
||||
"@nestjs/common": "^9.0.11",
|
||||
"@nestjs/config": "^2.2.0",
|
||||
@ -28,7 +29,7 @@
|
||||
"cookie-parser": "^1.4.6",
|
||||
"csvtojson": "^2.0.10",
|
||||
"dayjs": "^1.11.5",
|
||||
"google-auth-library": "^8.3.0",
|
||||
"google-auth-library": "^8.4.0",
|
||||
"joi": "^17.6.0",
|
||||
"lodash": "^4.17.21",
|
||||
"multer": "^1.4.4",
|
||||
@ -39,8 +40,8 @@
|
||||
"passport-jwt": "^4.0.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pg": "^8.7.3",
|
||||
"playwright-chromium": "^1.25.0",
|
||||
"pg": "^8.8.0",
|
||||
"playwright-chromium": "^1.25.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.5.6",
|
||||
@ -56,7 +57,7 @@
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/lodash": "^4.14.184",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/node": "^18.7.9",
|
||||
"@types/node": "^18.7.13",
|
||||
"@types/nodemailer": "^6.4.5",
|
||||
"@types/passport-jwt": "^3.0.6",
|
||||
"@types/passport-local": "^1.0.34",
|
||||
|
||||
@ -4,6 +4,9 @@
|
||||
"dev": {
|
||||
"cache": false
|
||||
},
|
||||
"lint": {
|
||||
"cache": false
|
||||
},
|
||||
"start": {
|
||||
"cache": false
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user