mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-10 04:22:27 +10:00
Compare commits
98 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f9ef4d0a64 | |||
| c4b4e6013f | |||
| 24bbc46c32 | |||
| 85bc9ef124 | |||
| 33755a8573 | |||
| ab45321889 | |||
| 940b310f64 | |||
| 8026241b6c | |||
| 89b35392bd | |||
| 62eb239ec4 | |||
| 7fdf8c1f0c | |||
| 538697238a | |||
| 7bc4a998fe | |||
| e33df485ab | |||
| 36ae54fe17 | |||
| 50958fd6df | |||
| e9e595f0d0 | |||
| 43ddfba777 | |||
| 78a32961d7 | |||
| 9b1f3eda05 | |||
| 1154621e5c | |||
| e7aeee77a7 | |||
| fab3988a36 | |||
| 354cad88d3 | |||
| 876f930f30 | |||
| 5b3ea46f0f | |||
| 37a2563c11 | |||
| cb977a146b | |||
| 72b2551b6d | |||
| c94633e616 | |||
| 7fee2d670f | |||
| 837b06eb38 | |||
| 2b8860b21c | |||
| 3a7b98d30e | |||
| 284a39aa77 | |||
| c14c9955dd | |||
| 4de787157a | |||
| 6bc6425a01 | |||
| 6051305908 | |||
| 5e13253454 | |||
| c1fd2b40e3 | |||
| fccf7a7b56 | |||
| 5098b094db | |||
| 7c1eb74aca | |||
| 7f9ede8ff0 | |||
| 172b23e429 | |||
| f287ca6183 | |||
| 7548e36aaf | |||
| 2f754616b4 | |||
| dc51f6f9b2 | |||
| 0cdac1d657 | |||
| c3cfe8ae7b | |||
| 08435c173b | |||
| 62cc2d6eac | |||
| 5a6f6e2b6c | |||
| 2dfa8c04a1 | |||
| 63e3f94d2d | |||
| 7f45a8cb7f | |||
| 4377ebb811 | |||
| ed3af6975b | |||
| 7904905a8b | |||
| bd2e6d2bf2 | |||
| ae4e9e688e | |||
| 78c45b7019 | |||
| 9a2fbbec4e | |||
| 93d751d9be | |||
| 9926ed2262 | |||
| 62220d20e7 | |||
| 2f6108cd29 | |||
| 7aeed37869 | |||
| ed99659b7b | |||
| 5e33d00910 | |||
| c00d0341e6 | |||
| 511ae036c2 | |||
| 1642ec9ba2 | |||
| 1115bc2b69 | |||
| 27e5c7811c | |||
| 3b739f0bb7 | |||
| d937ba2056 | |||
| de110d7de1 | |||
| b03229b5e0 | |||
| 280fc73c7b | |||
| 677ad2a115 | |||
| f394b26d18 | |||
| aab4e2e941 | |||
| f0f552a635 | |||
| 136e143e12 | |||
| 857e4b8670 | |||
| ff03d41d97 | |||
| 2bad37aaf3 | |||
| 3a40fbf78b | |||
| 49c638fb18 | |||
| 50e8d60773 | |||
| bf157a8d1a | |||
| c4f5955fcd | |||
| 86d33b0f21 | |||
| 56bca30639 | |||
| fc0b69796f |
@ -34,6 +34,7 @@ STORAGE_ENDPOINT=
|
||||
STORAGE_URL_PREFIX=
|
||||
STORAGE_ACCESS_KEY=
|
||||
STORAGE_SECRET_KEY=
|
||||
PDF_DELETION_TIME=
|
||||
|
||||
# Flags (Client)
|
||||
PUBLIC_FLAG_DISABLE_SIGNUPS=false
|
||||
2
.github/workflows/digitalocean-deploy.yml
vendored
2
.github/workflows/digitalocean-deploy.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install DigitalOcean CLI
|
||||
uses: digitalocean/action-doctl@v2.1.1
|
||||
uses: digitalocean/action-doctl@v2.2.0
|
||||
with:
|
||||
token: ${{ secrets.DIGITALOCEAN_TOKEN }}
|
||||
|
||||
|
||||
36
.github/workflows/docker-build-push.yml
vendored
36
.github/workflows/docker-build-push.yml
vendored
@ -15,38 +15,38 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.0.2
|
||||
uses: actions/checkout@v3.1.0
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- id: version
|
||||
name: Get Version
|
||||
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
|
||||
run: echo "version=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.0.0
|
||||
uses: docker/setup-qemu-action@v2.1.0
|
||||
|
||||
- id: buildx
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
uses: docker/setup-buildx-action@v2.2.1
|
||||
with:
|
||||
install: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2.0.0
|
||||
uses: docker/login-action@v2.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2.0.0
|
||||
uses: docker/login-action@v2.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: $GITHUB_REPOSITORY_OWNER
|
||||
password: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Build and Push Client Image
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
uses: docker/build-push-action@v3.2.0
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
@ -54,9 +54,9 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
amruthpillai/reactive-resume:client-latest
|
||||
amruthpillai/reactive-resume:client-${{ steps.version.outputs.tag }}
|
||||
amruthpillai/reactive-resume:client-${{ env.version }}
|
||||
ghcr.io/amruthpillai/reactive-resume:client-latest
|
||||
ghcr.io/amruthpillai/reactive-resume:client-${{ steps.version.outputs.tag }}
|
||||
ghcr.io/amruthpillai/reactive-resume:client-${{ env.version }}
|
||||
|
||||
server:
|
||||
name: Server
|
||||
@ -68,38 +68,38 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.0.2
|
||||
uses: actions/checkout@v3.1.0
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- id: version
|
||||
name: Get Version
|
||||
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
|
||||
run: echo "version=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.0.0
|
||||
uses: docker/setup-qemu-action@v2.1.0
|
||||
|
||||
- id: buildx
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
uses: docker/setup-buildx-action@v2.2.1
|
||||
with:
|
||||
install: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2.0.0
|
||||
uses: docker/login-action@v2.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2.0.0
|
||||
uses: docker/login-action@v2.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: $GITHUB_REPOSITORY_OWNER
|
||||
password: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Build and Push Server Image
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
uses: docker/build-push-action@v3.2.0
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
@ -107,6 +107,6 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
amruthpillai/reactive-resume:server-latest
|
||||
amruthpillai/reactive-resume:server-${{ steps.version.outputs.tag }}
|
||||
amruthpillai/reactive-resume:server-${{ env.version }}
|
||||
ghcr.io/amruthpillai/reactive-resume:server-latest
|
||||
ghcr.io/amruthpillai/reactive-resume:server-${{ steps.version.outputs.tag }}
|
||||
ghcr.io/amruthpillai/reactive-resume:server-${{ env.version }}
|
||||
|
||||
84
.github/workflows/docker-build.yml
vendored
84
.github/workflows/docker-build.yml
vendored
@ -1,84 +0,0 @@
|
||||
name: Build Docker Image
|
||||
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
client:
|
||||
name: Client
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.0.2
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.0.0
|
||||
|
||||
- id: buildx
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
with:
|
||||
install: true
|
||||
|
||||
- id: variables
|
||||
name: Get Short SHA
|
||||
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
|
||||
|
||||
- name: Build Client Image
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
file: client/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
amruthpillai/reactive-resume:client-latest
|
||||
amruthpillai/reactive-resume:client-${{ steps.variables.outputs.sha_short }}
|
||||
ghcr.io/amruthpillai/reactive-resume:client-latest
|
||||
ghcr.io/amruthpillai/reactive-resume:client-${{ steps.variables.outputs.sha_short }}
|
||||
|
||||
server:
|
||||
name: Server
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.0.2
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.0.0
|
||||
|
||||
- id: buildx
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
with:
|
||||
install: true
|
||||
|
||||
- id: variables
|
||||
name: Get Short SHA
|
||||
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
|
||||
|
||||
- name: Build Server Image
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
file: server/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
amruthpillai/reactive-resume:server-latest
|
||||
amruthpillai/reactive-resume:server-${{ steps.variables.outputs.sha_short }}
|
||||
ghcr.io/amruthpillai/reactive-resume:server-latest
|
||||
ghcr.io/amruthpillai/reactive-resume:server-${{ steps.variables.outputs.sha_short }}
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@ -10,4 +10,7 @@ node_modules
|
||||
.DS_Store
|
||||
|
||||
# Turbo
|
||||
.turbo
|
||||
.turbo
|
||||
|
||||
# Intellij
|
||||
.idea
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application' version '7.1.2' apply false
|
||||
id 'com.android.library' version '7.1.2' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.7.21' apply false
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
|
||||
@ -2,7 +2,7 @@ FROM node:lts-alpine AS base
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache g++ git curl make python3 \
|
||||
RUN apk add --no-cache g++ git curl make python3 libc6-compat \
|
||||
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
||||
|
||||
FROM base as dependencies
|
||||
@ -46,6 +46,6 @@ EXPOSE 3000
|
||||
ENV PORT 3000
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=20s --retries=3 --start-period=15s \
|
||||
CMD curl -fSs 127.0.0.1:3000 || exit 1
|
||||
CMD curl -fSs localhost:3000 || exit 1
|
||||
|
||||
CMD [ "pnpm", "run", "start", "--filter", "client" ]
|
||||
@ -13,6 +13,7 @@ import {
|
||||
} from '@mui/icons-material';
|
||||
import { ButtonBase, Divider, Tooltip, useMediaQuery, useTheme } from '@mui/material';
|
||||
import clsx from 'clsx';
|
||||
import dayjs from 'dayjs';
|
||||
import { get } from 'lodash';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import toast from 'react-hot-toast';
|
||||
@ -67,8 +68,9 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
|
||||
|
||||
const slug = get(resume, 'slug');
|
||||
const username = get(resume, 'user.username');
|
||||
const updatedAt = get(resume, 'updatedAt');
|
||||
|
||||
const url = await mutateAsync({ username, slug });
|
||||
const url = await mutateAsync({ username, slug, lastUpdated: dayjs(updatedAt).unix().toString() });
|
||||
|
||||
download(url);
|
||||
};
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
@apply h-full w-full #{!important};
|
||||
@apply h-full w-full overflow-visible #{!important};
|
||||
}
|
||||
|
||||
.artboard {
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
}
|
||||
|
||||
&.break::after {
|
||||
content: 'A4 Page Break';
|
||||
content: 'Page Break';
|
||||
top: calc(297mm - 19px);
|
||||
|
||||
@apply absolute w-full border-b border-dashed border-neutral-800/75;
|
||||
@ -28,10 +28,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.markdown {
|
||||
ul {
|
||||
padding-left: 1.5em;
|
||||
text-indent: -1.5em;
|
||||
&.format-letter {
|
||||
width: 216mm;
|
||||
min-height: 279mm;
|
||||
|
||||
&.break::after {
|
||||
top: calc(279mm - 19px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { CustomCSS, Theme, Typography } from '@reactive-resume/schema';
|
||||
import { CustomCSS, PageConfig, ThemeConfig, Typography } from '@reactive-resume/schema';
|
||||
import clsx from 'clsx';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
@ -23,22 +23,24 @@ const Page: React.FC<Props> = ({ page, showPageNumbers = false }) => {
|
||||
const resume = useAppSelector((state) => state.resume.present);
|
||||
const breakLine: boolean = useAppSelector((state) => state.build.page.breakLine);
|
||||
|
||||
const theme: Theme = get(resume, 'metadata.theme');
|
||||
const theme: ThemeConfig = get(resume, 'metadata.theme');
|
||||
const customCSS: CustomCSS = get(resume, 'metadata.css');
|
||||
const template: string = get(resume, 'metadata.template');
|
||||
const typography: Typography = get(resume, 'metadata.typography');
|
||||
const pageConfig: PageConfig = get(resume, 'metadata.page', {} as PageConfig);
|
||||
|
||||
const themeCSS = useMemo(() => !isEmpty(theme) && generateThemeStyles(theme), [theme]);
|
||||
const typographyCSS = useMemo(() => !isEmpty(typography) && generateTypographyStyles(typography), [typography]);
|
||||
const TemplatePage: React.FC<PageProps> | null = useMemo(() => templateMap[template].component, [template]);
|
||||
|
||||
return (
|
||||
<div data-page={page + 1} className={styles.container}>
|
||||
<div className={styles.container} data-page={page + 1} data-format={pageConfig?.format || 'A4'}>
|
||||
<div
|
||||
className={clsx({
|
||||
reset: true,
|
||||
[styles.page]: true,
|
||||
[styles.break]: breakLine,
|
||||
[styles['format-letter']]: pageConfig?.format === 'Letter',
|
||||
[css(themeCSS)]: true,
|
||||
[css(typographyCSS)]: true,
|
||||
[css(customCSS.value)]: customCSS.visible,
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
import { Add, Star } from '@mui/icons-material';
|
||||
import { Button, Divider, IconButton, SwipeableDrawer, Tooltip, useMediaQuery, useTheme } from '@mui/material';
|
||||
import { Section as SectionRecord } from '@reactive-resume/schema';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import get from 'lodash/get';
|
||||
import Link from 'next/link';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useMemo } from 'react';
|
||||
import React, { ReactComponentElement, useMemo } from 'react';
|
||||
import { validate } from 'uuid';
|
||||
|
||||
import Logo from '@/components/shared/Logo';
|
||||
import { getCustomSections, left } from '@/config/sections';
|
||||
import { getCustomSections, getSectionsByType, left } from '@/config/sections';
|
||||
import { setSidebarState } from '@/store/build/buildSlice';
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||
import { addSection } from '@/store/resume/resumeSlice';
|
||||
@ -52,7 +53,49 @@ const LeftSidebar = () => {
|
||||
items: [],
|
||||
};
|
||||
|
||||
dispatch(addSection({ value: newSection }));
|
||||
dispatch(addSection({ value: newSection, type: 'custom' }));
|
||||
};
|
||||
|
||||
const sectionsList = () => {
|
||||
const sectionsComponents: Array<ReactComponentElement<any>> = [];
|
||||
|
||||
for (const item of left) {
|
||||
const id = (item as any).id;
|
||||
const component = (item as any).component;
|
||||
const type = component.props.type || 'basic';
|
||||
const addMore = !!component.props.addMore;
|
||||
|
||||
sectionsComponents.push(
|
||||
<section key={id} id={id}>
|
||||
{component}
|
||||
</section>
|
||||
);
|
||||
|
||||
if (addMore) {
|
||||
const additionalSections = getSectionsByType(sections, type);
|
||||
const elements = [];
|
||||
for (const element of additionalSections) {
|
||||
const newId = element.id;
|
||||
|
||||
const props = cloneDeep(component.props);
|
||||
props.path = 'sections.' + newId;
|
||||
props.name = element.name;
|
||||
props.isDeletable = true;
|
||||
props.addMore = false;
|
||||
props.isDuplicated = true;
|
||||
const newComponent = React.cloneElement(component, props);
|
||||
|
||||
elements.push(
|
||||
<section key={newId} id={`section-${newId}`}>
|
||||
{newComponent}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
sectionsComponents.push(...elements);
|
||||
}
|
||||
}
|
||||
|
||||
return sectionsComponents;
|
||||
};
|
||||
|
||||
return (
|
||||
@ -68,9 +111,7 @@ const LeftSidebar = () => {
|
||||
<nav className="overflow-y-scroll">
|
||||
<div>
|
||||
<Link href="/dashboard">
|
||||
<a className="inline-flex">
|
||||
<Logo size={40} />
|
||||
</a>
|
||||
<Logo size={40} />
|
||||
</Link>
|
||||
<Divider />
|
||||
</div>
|
||||
@ -89,7 +130,7 @@ const LeftSidebar = () => {
|
||||
|
||||
{customSections.map(({ id }) => (
|
||||
<Tooltip key={id} title={get(sections, `${id}.name`, '') as string} placement="right" arrow>
|
||||
<IconButton onClick={() => handleClick(id)}>
|
||||
<IconButton onClick={() => id && handleClick(id)}>
|
||||
<Star />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
@ -100,12 +141,7 @@ const LeftSidebar = () => {
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
{left.map(({ id, component }) => (
|
||||
<section key={id} id={id}>
|
||||
{component}
|
||||
</section>
|
||||
))}
|
||||
|
||||
{sectionsList()}
|
||||
{customSections.map(({ id }) => (
|
||||
<section key={id} id={`section-${id}`}>
|
||||
<Section path={`sections.${id}`} isEditable isHideable isDeletable />
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Add } from '@mui/icons-material';
|
||||
import { Button } from '@mui/material';
|
||||
import { ListItem } from '@reactive-resume/schema';
|
||||
import { ListItem, Section as SectionRecord, SectionType } from '@reactive-resume/schema';
|
||||
import clsx from 'clsx';
|
||||
import get from 'lodash/get';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
@ -10,28 +10,34 @@ import Heading from '@/components/shared/Heading';
|
||||
import List from '@/components/shared/List';
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||
import { ModalName, setModalState } from '@/store/modal/modalSlice';
|
||||
import { duplicateItem } from '@/store/resume/resumeSlice';
|
||||
import { duplicateItem, duplicateSection } from '@/store/resume/resumeSlice';
|
||||
|
||||
import SectionSettings from './SectionSettings';
|
||||
|
||||
type Props = {
|
||||
path: `sections.${string}`;
|
||||
type?: SectionType;
|
||||
name?: string;
|
||||
titleKey?: string;
|
||||
subtitleKey?: string;
|
||||
isEditable?: boolean;
|
||||
isHideable?: boolean;
|
||||
isDeletable?: boolean;
|
||||
addMore?: boolean;
|
||||
isDuplicated?: boolean;
|
||||
};
|
||||
|
||||
const Section: React.FC<Props> = ({
|
||||
path,
|
||||
name = 'Section Name',
|
||||
type = 'basic',
|
||||
titleKey = 'title',
|
||||
subtitleKey = 'subtitle',
|
||||
isEditable = false,
|
||||
isHideable = false,
|
||||
isDeletable = false,
|
||||
addMore = false,
|
||||
isDuplicated = false,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -42,21 +48,43 @@ const Section: React.FC<Props> = ({
|
||||
|
||||
const handleAdd = () => {
|
||||
const id = path.split('.')[1];
|
||||
const modal: ModalName = validate(id) ? 'builder.sections.custom' : `builder.${path}`;
|
||||
let modal: ModalName = validate(id) ? 'builder.sections.custom' : `builder.${path}`;
|
||||
|
||||
if (type) {
|
||||
modal = `builder.sections.${type}`;
|
||||
}
|
||||
dispatch(setModalState({ modal, state: { open: true, payload: { path } } }));
|
||||
};
|
||||
|
||||
const handleEdit = (item: ListItem) => {
|
||||
const id = path.split('.')[1];
|
||||
const modal: ModalName = validate(id) ? 'builder.sections.custom' : `builder.${path}`;
|
||||
let modal: ModalName = validate(id) ? 'builder.sections.custom' : `builder.${path}`;
|
||||
|
||||
const payload = validate(id) ? { path, item } : { item };
|
||||
|
||||
if (isDuplicated) {
|
||||
modal = `builder.sections.${type}`;
|
||||
payload.path = path;
|
||||
}
|
||||
|
||||
dispatch(setModalState({ modal, state: { open: true, payload } }));
|
||||
};
|
||||
|
||||
const handleDuplicate = (item: ListItem) => dispatch(duplicateItem({ path: `${path}.items`, value: item }));
|
||||
|
||||
const handleDuplicateSection = () => {
|
||||
const newSection: SectionRecord = {
|
||||
name: `${heading}`,
|
||||
type: type,
|
||||
visible: true,
|
||||
columns: 2,
|
||||
items: [],
|
||||
isDuplicated: true,
|
||||
};
|
||||
|
||||
dispatch(duplicateSection({ value: newSection, type }));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Heading path={path} name={name} isEditable={isEditable} isHideable={isHideable} isDeletable={isDeletable} />
|
||||
@ -77,6 +105,16 @@ const Section: React.FC<Props> = ({
|
||||
{t<string>('builder.common.actions.add', { token: heading })}
|
||||
</Button>
|
||||
</footer>
|
||||
|
||||
{addMore ? (
|
||||
<div className="py-6 text-right">
|
||||
<Button fullWidth variant="outlined" startIcon={<Add />} onClick={handleDuplicateSection}>
|
||||
{t<string>('builder.common.actions.duplicate')}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -17,7 +17,9 @@ const CustomCSS = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const customCSS: CustomCSSType = useAppSelector((state) => get(state.resume.present, 'metadata.css', {}));
|
||||
const customCSS: CustomCSSType = useAppSelector((state) =>
|
||||
get(state.resume.present, 'metadata.css', {} as CustomCSSType)
|
||||
);
|
||||
|
||||
const handleChange = (value: string | undefined) => {
|
||||
dispatch(setResumeState({ path: 'metadata.css.value', value }));
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { PictureAsPdf, Schema } from '@mui/icons-material';
|
||||
import { List, ListItem, ListItemButton, ListItemText } from '@mui/material';
|
||||
import dayjs from 'dayjs';
|
||||
import get from 'lodash/get';
|
||||
import pick from 'lodash/pick';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
@ -45,8 +46,9 @@ const Export = () => {
|
||||
|
||||
const slug = get(resume, 'slug');
|
||||
const username = get(resume, 'user.username');
|
||||
const updatedAt = get(resume, 'updatedAt');
|
||||
|
||||
const url = await mutateAsync({ username, slug });
|
||||
const url = await mutateAsync({ username, slug, lastUpdated: dayjs(updatedAt).unix().toString() });
|
||||
|
||||
download(url);
|
||||
};
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
Switch,
|
||||
TextField,
|
||||
} from '@mui/material';
|
||||
import { DateConfig, Resume } from '@reactive-resume/schema';
|
||||
import { DateConfig, PageConfig, Resume } from '@reactive-resume/schema';
|
||||
import dayjs from 'dayjs';
|
||||
import get from 'lodash/get';
|
||||
import { useRouter } from 'next/router';
|
||||
@ -48,9 +48,10 @@ const Settings = () => {
|
||||
const slug: string = useMemo(() => get(resume, 'slug'), [resume]);
|
||||
const username: string = useMemo(() => get(resume, 'user.username'), [resume]);
|
||||
const dateConfig: DateConfig = useMemo(() => get(resume, 'metadata.date'), [resume]);
|
||||
const pageConfig: PageConfig | undefined = useMemo(() => get(resume, 'metadata.page'), [resume]);
|
||||
|
||||
const isDarkMode = useMemo(() => theme === 'dark', [theme]);
|
||||
const exampleString = useMemo(() => `Eg. ${dayjs().utc().format(dateConfig.format)}`, [dateConfig.format]);
|
||||
const exampleDateString = useMemo(() => `Eg. ${dayjs().utc().format(dateConfig.format)}`, [dateConfig.format]);
|
||||
const themeString = useMemo(() => (isDarkMode ? 'Matte Black Everything' : 'As bright as your future'), [isDarkMode]);
|
||||
|
||||
const { mutateAsync: loadSampleDataMutation } = useMutation<Resume, ServerError, LoadSampleDataParams>(
|
||||
@ -60,6 +61,9 @@ const Settings = () => {
|
||||
|
||||
const handleSetTheme = (value: boolean) => dispatch(setTheme({ theme: value ? 'dark' : 'light' }));
|
||||
|
||||
const handleChangePageFormat = (value: PageConfig['format'] | null) =>
|
||||
dispatch(setResumeState({ path: 'metadata.page.format', value }));
|
||||
|
||||
const handleChangeDateFormat = (value: string | null) =>
|
||||
dispatch(setResumeState({ path: 'metadata.date.format', value }));
|
||||
|
||||
@ -94,7 +98,7 @@ const Settings = () => {
|
||||
<>
|
||||
<Heading path="metadata.settings" name={t<string>('builder.rightSidebar.sections.settings.heading')} />
|
||||
|
||||
<List sx={{ padding: 0 }}>
|
||||
<List disablePadding>
|
||||
{/* Global Settings */}
|
||||
<>
|
||||
<ListSubheader disableSticky className="rounded">
|
||||
@ -118,13 +122,13 @@ const Settings = () => {
|
||||
primary={t<string>('builder.rightSidebar.sections.settings.global.date.primary')}
|
||||
secondary={t<string>('builder.rightSidebar.sections.settings.global.date.secondary')}
|
||||
/>
|
||||
<Autocomplete<string, false, boolean, false>
|
||||
<Autocomplete<string, false, true, false>
|
||||
disableClearable
|
||||
className="my-2 w-full"
|
||||
options={dateFormatOptions}
|
||||
value={dateConfig.format}
|
||||
onChange={(_, value) => handleChangeDateFormat(value)}
|
||||
renderInput={(params) => <TextField {...params} helperText={exampleString} />}
|
||||
renderInput={(params) => <TextField {...params} helperText={exampleDateString} />}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
@ -134,7 +138,7 @@ const Settings = () => {
|
||||
primary={t<string>('builder.rightSidebar.sections.settings.global.language.primary')}
|
||||
secondary={t<string>('builder.rightSidebar.sections.settings.global.language.secondary')}
|
||||
/>
|
||||
<Autocomplete<Language, false, boolean, false>
|
||||
<Autocomplete<Language, false, true, false>
|
||||
disableClearable
|
||||
className="my-2 w-full"
|
||||
options={languages}
|
||||
@ -159,6 +163,23 @@ const Settings = () => {
|
||||
{t<string>('builder.rightSidebar.sections.settings.page.heading')}
|
||||
</ListSubheader>
|
||||
|
||||
<ListItem className="flex-col">
|
||||
<ListItemText
|
||||
className="w-full"
|
||||
primary={t<string>('builder.rightSidebar.sections.settings.page.format.primary')}
|
||||
secondary={t<string>('builder.rightSidebar.sections.settings.page.format.secondary')}
|
||||
/>
|
||||
<Autocomplete<PageConfig['format'], false, true, false>
|
||||
disableClearable
|
||||
defaultValue="A4"
|
||||
className="my-2 w-full"
|
||||
options={['A4', 'Letter']}
|
||||
value={pageConfig?.format || 'A4'}
|
||||
renderInput={(params) => <TextField {...params} />}
|
||||
onChange={(_, value) => handleChangePageFormat(value)}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t<string>('builder.rightSidebar.sections.settings.page.orientation.primary')}
|
||||
@ -191,7 +212,7 @@ const Settings = () => {
|
||||
{t<string>('builder.rightSidebar.sections.settings.resume.heading')}
|
||||
</ListSubheader>
|
||||
|
||||
<ListItem>
|
||||
<ListItem disableGutters>
|
||||
<ListItemButton onClick={handleLoadSampleData}>
|
||||
<ListItemIcon>
|
||||
<Anchor />
|
||||
@ -203,7 +224,7 @@ const Settings = () => {
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<ListItem disableGutters>
|
||||
<ListItemButton onClick={handleResetResume}>
|
||||
<ListItemIcon>
|
||||
<DeleteForever />
|
||||
|
||||
@ -31,7 +31,14 @@ 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" priority />
|
||||
<Image
|
||||
fill
|
||||
priority
|
||||
alt={template.name}
|
||||
src={template.preview}
|
||||
className="rounded-sm"
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||
/>
|
||||
</ButtonBase>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Theme as ThemeType } from '@reactive-resume/schema';
|
||||
import { ThemeConfig } from '@reactive-resume/schema';
|
||||
import get from 'lodash/get';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
@ -16,7 +16,7 @@ const Theme = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { background, text, primary } = useAppSelector<ThemeType>((state) =>
|
||||
const { background, text, primary } = useAppSelector<ThemeConfig>((state) =>
|
||||
get(state.resume.present, 'metadata.theme')
|
||||
);
|
||||
|
||||
|
||||
@ -16,9 +16,7 @@ type Props = {
|
||||
const ResumeCard: React.FC<Props> = ({ modal, icon: Icon, title, subtitle }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleClick = () => {
|
||||
dispatch(setModalState({ modal, state: { open: true } }));
|
||||
};
|
||||
const handleClick = () => dispatch(setModalState({ modal, state: { open: true } }));
|
||||
|
||||
return (
|
||||
<section className={styles.resume}>
|
||||
|
||||
@ -115,9 +115,7 @@ const ResumePreview: React.FC<Props> = ({ resume }) => {
|
||||
}}
|
||||
>
|
||||
<ButtonBase className={styles.preview}>
|
||||
{resume.image ? (
|
||||
<Image src={resume.image} alt={resume.name} objectFit="cover" layout="fill" priority />
|
||||
) : null}
|
||||
{resume.image ? <Image src={resume.image} alt={resume.name} priority width={400} height={0} /> : null}
|
||||
</ButtonBase>
|
||||
</Link>
|
||||
|
||||
|
||||
@ -47,9 +47,9 @@ const Avatar: React.FC<Props> = ({ size = 64 }) => {
|
||||
<Image
|
||||
width={size}
|
||||
height={size}
|
||||
alt={user?.name}
|
||||
className={styles.avatar}
|
||||
src={getGravatarUrl(email, size)}
|
||||
alt={user?.name ?? 'User Avatar'}
|
||||
/>
|
||||
</IconButton>
|
||||
|
||||
|
||||
@ -4,8 +4,8 @@ type Props = {
|
||||
size?: 256 | 64 | 48 | 40 | 32;
|
||||
};
|
||||
|
||||
const Logo: React.FC<Props> = ({ size = 64 }) => {
|
||||
return <Image alt="Reactive Resume" src="/images/logos/logo.svg" className="rounded" width={size} height={size} />;
|
||||
};
|
||||
const Logo: React.FC<Props> = ({ size = 64 }) => (
|
||||
<Image alt="Reactive Resume" src="/images/logos/logo.svg" className="rounded" width={size} height={size} priority />
|
||||
);
|
||||
|
||||
export default Logo;
|
||||
|
||||
@ -23,7 +23,7 @@ import {
|
||||
VolunteerActivism,
|
||||
Work,
|
||||
} from '@mui/icons-material';
|
||||
import { Section as SectionRecord } from '@reactive-resume/schema';
|
||||
import { Section as SectionRecord, SectionType } from '@reactive-resume/schema';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
|
||||
import Basics from '@/components/build/LeftSidebar/sections/Basics';
|
||||
@ -60,59 +60,136 @@ export const left: SidebarSection[] = [
|
||||
{
|
||||
id: 'work',
|
||||
icon: <Work />,
|
||||
component: <Section path="sections.work" titleKey="name" subtitleKey="position" isEditable isHideable />,
|
||||
component: (
|
||||
<Section
|
||||
type={'work'}
|
||||
addMore={true}
|
||||
path="sections.work"
|
||||
titleKey="name"
|
||||
subtitleKey="position"
|
||||
isEditable
|
||||
isHideable
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'education',
|
||||
icon: <School />,
|
||||
component: <Section path="sections.education" titleKey="institution" subtitleKey="area" isEditable isHideable />,
|
||||
component: (
|
||||
<Section
|
||||
type={'education'}
|
||||
path="sections.education"
|
||||
titleKey="institution"
|
||||
subtitleKey="area"
|
||||
isEditable
|
||||
isHideable
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'awards',
|
||||
icon: <EmojiEvents />,
|
||||
component: <Section path="sections.awards" titleKey="title" subtitleKey="awarder" isEditable isHideable />,
|
||||
component: (
|
||||
<Section type={'awards'} path="sections.awards" titleKey="title" subtitleKey="awarder" isEditable isHideable />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'certifications',
|
||||
icon: <CardGiftcard />,
|
||||
component: <Section path="sections.certifications" titleKey="name" subtitleKey="issuer" isEditable isHideable />,
|
||||
component: (
|
||||
<Section
|
||||
type={'certifications'}
|
||||
path="sections.certifications"
|
||||
titleKey="name"
|
||||
subtitleKey="issuer"
|
||||
isEditable
|
||||
isHideable
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'publications',
|
||||
icon: <MenuBook />,
|
||||
component: <Section path="sections.publications" titleKey="name" subtitleKey="publisher" isEditable isHideable />,
|
||||
component: (
|
||||
<Section
|
||||
type={'publications'}
|
||||
path="sections.publications"
|
||||
titleKey="name"
|
||||
subtitleKey="publisher"
|
||||
isEditable
|
||||
isHideable
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'skills',
|
||||
icon: <Architecture />,
|
||||
component: <Section path="sections.skills" titleKey="name" subtitleKey="level" isEditable isHideable />,
|
||||
component: (
|
||||
<Section type={'skills'} path="sections.skills" titleKey="name" subtitleKey="level" isEditable isHideable />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'languages',
|
||||
icon: <Language />,
|
||||
component: <Section path="sections.languages" titleKey="name" subtitleKey="level" isEditable isHideable />,
|
||||
component: (
|
||||
<Section type={'languages'} path="sections.languages" titleKey="name" subtitleKey="level" isEditable isHideable />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'interests',
|
||||
icon: <Sailing />,
|
||||
component: <Section path="sections.interests" titleKey="name" subtitleKey="keywords" isEditable isHideable />,
|
||||
component: (
|
||||
<Section
|
||||
type={'interests'}
|
||||
path="sections.interests"
|
||||
titleKey="name"
|
||||
subtitleKey="keywords"
|
||||
isEditable
|
||||
isHideable
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'volunteer',
|
||||
icon: <VolunteerActivism />,
|
||||
component: (
|
||||
<Section path="sections.volunteer" titleKey="organization" subtitleKey="position" isEditable isHideable />
|
||||
<Section
|
||||
type={'volunteer'}
|
||||
path="sections.volunteer"
|
||||
titleKey="organization"
|
||||
subtitleKey="position"
|
||||
isEditable
|
||||
isHideable
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'projects',
|
||||
icon: <Coffee />,
|
||||
component: <Section path="sections.projects" titleKey="name" subtitleKey="description" isEditable isHideable />,
|
||||
component: (
|
||||
<Section
|
||||
type={'projects'}
|
||||
path="sections.projects"
|
||||
titleKey="name"
|
||||
subtitleKey="description"
|
||||
isEditable
|
||||
isHideable
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'references',
|
||||
icon: <Groups />,
|
||||
component: <Section path="sections.references" titleKey="name" subtitleKey="relationship" isEditable isHideable />,
|
||||
component: (
|
||||
<Section
|
||||
type={'references'}
|
||||
path="sections.references"
|
||||
titleKey="name"
|
||||
subtitleKey="relationship"
|
||||
isEditable
|
||||
isHideable
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
@ -164,7 +241,19 @@ export const right: SidebarSection[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const getCustomSections = (sections: Record<string, SectionRecord>): Array<Required<SectionRecord>> => {
|
||||
export const getSectionsByType = (sections: Record<string, SectionRecord>, type: SectionType): SectionRecord[] => {
|
||||
if (isEmpty(sections)) return [];
|
||||
|
||||
return Object.entries(sections).reduce((acc, [id, section]) => {
|
||||
if (section.type.startsWith(type) && section.isDuplicated) {
|
||||
return [...acc, { ...section, id }];
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, [] as SectionRecord[]);
|
||||
};
|
||||
|
||||
export const getCustomSections = (sections: Record<string, SectionRecord>): SectionRecord[] => {
|
||||
if (isEmpty(sections)) return [];
|
||||
|
||||
return Object.entries(sections).reduce((acc, [id, section]) => {
|
||||
@ -173,7 +262,7 @@ export const getCustomSections = (sections: Record<string, SectionRecord>): Arra
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, [] as Array<Required<SectionRecord>>);
|
||||
}, [] as SectionRecord[]);
|
||||
};
|
||||
|
||||
const sections = [...left, ...right];
|
||||
|
||||
@ -162,15 +162,15 @@ const LoginModal: React.FC = () => {
|
||||
{!FLAG_DISABLE_SIGNUPS && (
|
||||
<p className="text-xs">
|
||||
<Trans t={t} i18nKey="modals.auth.login.register-text">
|
||||
If you don't have one, you can <a onClick={handleCreateAccount}>create an account</a> here.
|
||||
If you don't have one, you can <a onClick={handleCreateAccount}>create an account here.</a>
|
||||
</Trans>
|
||||
</p>
|
||||
)}
|
||||
|
||||
<p className="text-xs">
|
||||
<Trans t={t} i18nKey="modals.auth.login.recover-text">
|
||||
In case you have forgotten your password, you can <a onClick={handleRecoverAccount}>recover your account</a>
|
||||
here.
|
||||
In case you have forgotten your password, you can{' '}
|
||||
<a onClick={handleRecoverAccount}>recover your account here.</a>
|
||||
</Trans>
|
||||
</p>
|
||||
</BaseModal>
|
||||
|
||||
@ -93,7 +93,7 @@ const AwardModal: React.FC = () => {
|
||||
heading={isEditMode ? editText : addText}
|
||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||
>
|
||||
<form className="my-2 grid grid-cols-2 gap-4">
|
||||
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||
<Controller
|
||||
name="title"
|
||||
control={control}
|
||||
@ -177,6 +177,7 @@ const AwardModal: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<input type="submit" style={{ display: 'none' }} />
|
||||
</form>
|
||||
</BaseModal>
|
||||
);
|
||||
|
||||
@ -93,7 +93,7 @@ const CertificateModal: React.FC = () => {
|
||||
heading={isEditMode ? editText : addText}
|
||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||
>
|
||||
<form className="my-2 grid grid-cols-2 gap-4">
|
||||
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||
<Controller
|
||||
name="name"
|
||||
control={control}
|
||||
@ -177,6 +177,7 @@ const CertificateModal: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<input type="submit" style={{ display: 'none' }} />
|
||||
</form>
|
||||
</BaseModal>
|
||||
);
|
||||
|
||||
@ -60,13 +60,14 @@ const CustomModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
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', '');
|
||||
const path: string = get(payload, 'path', 'sections.custom');
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
const isEditMode = useMemo(() => !!item, [item]);
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
|
||||
const addText = useMemo(() => t<string>('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t<string>('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
|
||||
@ -110,7 +111,7 @@ const CustomModal: React.FC = () => {
|
||||
heading={isEditMode ? editText : addText}
|
||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||
>
|
||||
<form className="my-2 grid grid-cols-2 gap-4">
|
||||
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||
<Controller
|
||||
name="title"
|
||||
control={control}
|
||||
@ -260,9 +261,9 @@ const CustomModal: React.FC = () => {
|
||||
multiline
|
||||
minRows={3}
|
||||
maxRows={6}
|
||||
label={t<string>('builder.common.form.summary.label')}
|
||||
className="col-span-2"
|
||||
error={!!fieldState.error}
|
||||
label={t<string>('builder.common.form.summary.label')}
|
||||
helperText={fieldState.error?.message || <MarkdownSupported />}
|
||||
{...field}
|
||||
/>
|
||||
@ -282,6 +283,7 @@ const CustomModal: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<input type="submit" style={{ display: 'none' }} />
|
||||
</form>
|
||||
</BaseModal>
|
||||
);
|
||||
|
||||
@ -106,7 +106,7 @@ const EducationModal: React.FC = () => {
|
||||
heading={isEditMode ? editText : addText}
|
||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||
>
|
||||
<form className="my-2 grid grid-cols-2 gap-4">
|
||||
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||
<Controller
|
||||
name="institution"
|
||||
control={control}
|
||||
@ -255,6 +255,7 @@ const EducationModal: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<input type="submit" style={{ display: 'none' }} />
|
||||
</form>
|
||||
</BaseModal>
|
||||
);
|
||||
|
||||
@ -84,7 +84,7 @@ const InterestModal: React.FC = () => {
|
||||
heading={isEditMode ? editText : addText}
|
||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||
>
|
||||
<form className="my-2 grid grid-cols-2 gap-4">
|
||||
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||
<Controller
|
||||
name="name"
|
||||
control={control}
|
||||
@ -114,6 +114,7 @@ const InterestModal: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<input type="submit" style={{ display: 'none' }} />
|
||||
</form>
|
||||
</BaseModal>
|
||||
);
|
||||
|
||||
@ -85,7 +85,7 @@ const LanguageModal: React.FC = () => {
|
||||
heading={isEditMode ? editText : addText}
|
||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||
>
|
||||
<form className="my-2 grid grid-cols-2 gap-4">
|
||||
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||
<Controller
|
||||
name="name"
|
||||
control={control}
|
||||
@ -150,6 +150,7 @@ const LanguageModal: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<input type="submit" style={{ display: 'none' }} />
|
||||
</form>
|
||||
</BaseModal>
|
||||
);
|
||||
|
||||
@ -89,7 +89,7 @@ const ProfileModal: React.FC = () => {
|
||||
handleClose={handleClose}
|
||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||
>
|
||||
<form className="my-2 grid grid-cols-2 gap-4">
|
||||
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||
<Controller
|
||||
name="network"
|
||||
control={control}
|
||||
@ -136,6 +136,7 @@ const ProfileModal: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<input type="submit" style={{ display: 'none' }} />
|
||||
</form>
|
||||
</BaseModal>
|
||||
);
|
||||
|
||||
@ -102,7 +102,7 @@ const ProjectModal: React.FC = () => {
|
||||
heading={isEditMode ? editText : addText}
|
||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||
>
|
||||
<form className="my-2 grid grid-cols-2 gap-4">
|
||||
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||
<Controller
|
||||
name="name"
|
||||
control={control}
|
||||
@ -225,6 +225,7 @@ const ProjectModal: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<input type="submit" style={{ display: 'none' }} />
|
||||
</form>
|
||||
</BaseModal>
|
||||
);
|
||||
|
||||
@ -93,7 +93,7 @@ const PublicationModal: React.FC = () => {
|
||||
heading={isEditMode ? editText : addText}
|
||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||
>
|
||||
<form className="my-2 grid grid-cols-2 gap-4">
|
||||
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||
<Controller
|
||||
name="name"
|
||||
control={control}
|
||||
@ -177,6 +177,7 @@ const PublicationModal: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<input type="submit" style={{ display: 'none' }} />
|
||||
</form>
|
||||
</BaseModal>
|
||||
);
|
||||
|
||||
@ -90,7 +90,7 @@ const ReferenceModal: React.FC = () => {
|
||||
heading={isEditMode ? editText : addText}
|
||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||
>
|
||||
<form className="my-2 grid grid-cols-2 gap-4">
|
||||
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||
<Controller
|
||||
name="name"
|
||||
control={control}
|
||||
@ -162,6 +162,7 @@ const ReferenceModal: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<input type="submit" style={{ display: 'none' }} />
|
||||
</form>
|
||||
</BaseModal>
|
||||
);
|
||||
|
||||
@ -88,7 +88,7 @@ const SkillModal: React.FC = () => {
|
||||
heading={isEditMode ? editText : addText}
|
||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||
>
|
||||
<form className="my-2 grid grid-cols-2 gap-4">
|
||||
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||
<Controller
|
||||
name="name"
|
||||
control={control}
|
||||
@ -166,6 +166,8 @@ const SkillModal: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<input type="submit" style={{ display: 'none' }} />
|
||||
</form>
|
||||
</BaseModal>
|
||||
);
|
||||
|
||||
@ -99,7 +99,7 @@ const VolunteerModal: React.FC = () => {
|
||||
heading={isEditMode ? editText : addText}
|
||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||
>
|
||||
<form className="my-2 grid grid-cols-2 gap-4">
|
||||
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||
<Controller
|
||||
name="organization"
|
||||
control={control}
|
||||
@ -208,6 +208,7 @@ const VolunteerModal: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<input type="submit" style={{ display: 'none' }} />
|
||||
</form>
|
||||
</BaseModal>
|
||||
);
|
||||
|
||||
@ -2,7 +2,7 @@ import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { DatePicker } from '@mui/x-date-pickers';
|
||||
import { SectionPath, WorkExperience } from '@reactive-resume/schema';
|
||||
import { WorkExperience } from '@reactive-resume/schema';
|
||||
import dayjs from 'dayjs';
|
||||
import Joi from 'joi';
|
||||
import get from 'lodash/get';
|
||||
@ -20,8 +20,6 @@ import { addItem, editItem } from '@/store/resume/resumeSlice';
|
||||
|
||||
type FormData = WorkExperience;
|
||||
|
||||
const path: SectionPath = 'sections.work';
|
||||
|
||||
const defaultState: FormData = {
|
||||
name: '',
|
||||
position: '',
|
||||
@ -50,10 +48,12 @@ const WorkModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal['builder.sections.work']);
|
||||
const path: string = get(payload, 'path', 'sections.work');
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
|
||||
const isEditMode = useMemo(() => !!item, [item]);
|
||||
|
||||
const addText = useMemo(() => t<string>('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
@ -77,7 +77,7 @@ const WorkModal: React.FC = () => {
|
||||
const handleClose = () => {
|
||||
dispatch(
|
||||
setModalState({
|
||||
modal: `builder.${path}`,
|
||||
modal: 'builder.sections.work',
|
||||
state: { open: false },
|
||||
})
|
||||
);
|
||||
@ -99,7 +99,7 @@ const WorkModal: React.FC = () => {
|
||||
heading={isEditMode ? editText : addText}
|
||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||
>
|
||||
<form className="my-2 grid grid-cols-2 gap-4">
|
||||
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||
<Controller
|
||||
name="name"
|
||||
control={control}
|
||||
@ -208,6 +208,7 @@ const WorkModal: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<input type="submit" style={{ display: 'none' }} />
|
||||
</form>
|
||||
</BaseModal>
|
||||
);
|
||||
|
||||
@ -10,75 +10,74 @@
|
||||
"dependencies": {
|
||||
"@beam-australia/react-env": "^3.1.1",
|
||||
"@date-io/dayjs": "^2.16.0",
|
||||
"@emotion/css": "^11.10.0",
|
||||
"@emotion/react": "^11.10.4",
|
||||
"@emotion/styled": "^11.10.4",
|
||||
"@hello-pangea/dnd": "^16.0.0",
|
||||
"@hookform/resolvers": "2.9.8",
|
||||
"@emotion/css": "^11.10.5",
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@hello-pangea/dnd": "^16.0.1",
|
||||
"@hookform/resolvers": "2.9.10",
|
||||
"@monaco-editor/react": "^4.4.6",
|
||||
"@mui/icons-material": "^5.10.6",
|
||||
"@mui/lab": "^5.0.0-alpha.102",
|
||||
"@mui/material": "^5.10.8",
|
||||
"@mui/system": "^5.10.8",
|
||||
"@mui/x-date-pickers": "5.0.4",
|
||||
"@next/env": "^12.3.1",
|
||||
"@react-oauth/google": "^0.2.8",
|
||||
"@reduxjs/toolkit": "^1.8.5",
|
||||
"axios": "^1.1.0",
|
||||
"@mui/icons-material": "^5.10.9",
|
||||
"@mui/lab": "^5.0.0-alpha.107",
|
||||
"@mui/material": "^5.10.13",
|
||||
"@mui/system": "^5.10.13",
|
||||
"@mui/x-date-pickers": "5.0.8",
|
||||
"@next/env": "^13.0.3",
|
||||
"@react-oauth/google": "^0.4.0",
|
||||
"@reduxjs/toolkit": "^1.9.0",
|
||||
"axios": "^1.1.3",
|
||||
"clsx": "^1.2.1",
|
||||
"dayjs": "^1.11.5",
|
||||
"dayjs": "^1.11.6",
|
||||
"downloadjs": "^1.4.7",
|
||||
"joi": "^17.6.2",
|
||||
"joi": "^17.7.0",
|
||||
"lodash": "^4.17.21",
|
||||
"md5-hex": "^4.0.0",
|
||||
"monaco-editor": "^0.34.0",
|
||||
"monaco-editor": "^0.34.1",
|
||||
"nanoid": "^3.3.4",
|
||||
"next": "12.3.1",
|
||||
"next": "13.0.3",
|
||||
"next-i18next": "^12.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.2.0",
|
||||
"react-hook-form": "^7.37.0",
|
||||
"react-hook-form": "^7.39.3",
|
||||
"react-hot-toast": "2.4.0",
|
||||
"react-hotkeys-hook": "^3.4.7",
|
||||
"react-icons": "^4.4.0",
|
||||
"react-icons": "^4.6.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-query": "^3.39.2",
|
||||
"react-redux": "^8.0.4",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-zoom-pan-pinch": "^2.1.3",
|
||||
"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.31.1",
|
||||
"sharp": "^0.31.2",
|
||||
"uuid": "^9.0.0",
|
||||
"webfontloader": "^1.6.28"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19.3",
|
||||
"@babel/core": "^7.20.2",
|
||||
"@reactive-resume/schema": "workspace:*",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"@tailwindcss/typography": "^0.5.7",
|
||||
"@tailwindcss/typography": "^0.5.8",
|
||||
"@types/downloadjs": "^1.4.3",
|
||||
"@types/lodash": "^4.14.186",
|
||||
"@types/node": "^18.8.3",
|
||||
"@types/react": "^18.0.21",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/lodash": "^4.14.188",
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/react": "^18.0.25",
|
||||
"@types/react-dom": "^18.0.8",
|
||||
"@types/react-redux": "^7.1.24",
|
||||
"@types/tailwindcss": "^3.0.11",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/webfontloader": "^1.6.35",
|
||||
"autoprefixer": "^10.4.12",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"csstype": "^3.1.1",
|
||||
"eslint-config-next": "^12.3.1",
|
||||
"eslint-config-next": "^13.0.3",
|
||||
"eslint-plugin-tailwindcss": "^3.6.2",
|
||||
"next-sitemap": "^3.1.23",
|
||||
"postcss": "^8.4.17",
|
||||
"sass": "^1.55.0",
|
||||
"tailwindcss": "^3.1.8",
|
||||
"next-sitemap": "^3.1.31",
|
||||
"postcss": "^8.4.19",
|
||||
"sass": "^1.56.1",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"typescript": "^4.8.4"
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import { Download, Downloading } from '@mui/icons-material';
|
||||
import { ButtonBase } from '@mui/material';
|
||||
import { Resume } from '@reactive-resume/schema';
|
||||
import clsx from 'clsx';
|
||||
import dayjs from 'dayjs';
|
||||
import download from 'downloadjs';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
@ -96,7 +97,9 @@ const Preview: NextPage<Props> = ({ username, slug, resume: initialData }) => {
|
||||
|
||||
const handleDownload = async () => {
|
||||
try {
|
||||
const url = await mutateAsync({ username, slug });
|
||||
const updatedAt = get(resume, 'updatedAt');
|
||||
|
||||
const url = await mutateAsync({ username, slug, lastUpdated: dayjs(updatedAt).unix().toString() });
|
||||
|
||||
download(url);
|
||||
} catch {
|
||||
|
||||
@ -17,13 +17,11 @@ 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' }) => {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, ['common', 'modals', 'dashboard'])),
|
||||
},
|
||||
};
|
||||
};
|
||||
export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => ({
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, ['common', 'modals', 'dashboard'])),
|
||||
},
|
||||
});
|
||||
|
||||
const Dashboard: NextPage = () => {
|
||||
const { t } = useTranslation();
|
||||
@ -48,9 +46,7 @@ const Dashboard: NextPage = () => {
|
||||
|
||||
<header>
|
||||
<Link href="/">
|
||||
<a>
|
||||
<Logo size={40} />
|
||||
</a>
|
||||
<Logo size={40} />
|
||||
</Link>
|
||||
|
||||
<Avatar size={40} />
|
||||
@ -58,15 +54,15 @@ const Dashboard: NextPage = () => {
|
||||
|
||||
<main className={styles.resumes}>
|
||||
<ResumeCard
|
||||
modal="dashboard.create-resume"
|
||||
icon={Add}
|
||||
modal="dashboard.create-resume"
|
||||
title={t<string>('dashboard.create-resume.title')}
|
||||
subtitle={t<string>('dashboard.create-resume.subtitle')}
|
||||
/>
|
||||
|
||||
<ResumeCard
|
||||
modal="dashboard.import-external"
|
||||
icon={ImportExport}
|
||||
modal="dashboard.import-external"
|
||||
title={t<string>('dashboard.import-external.title')}
|
||||
subtitle={t<string>('dashboard.import-external.subtitle')}
|
||||
/>
|
||||
|
||||
@ -22,13 +22,11 @@ import styles from '@/styles/pages/Home.module.scss';
|
||||
|
||||
import { DIGITALOCEAN_URL, DOCS_URL, DONATION_URL, GITHUB_URL } from '../constants';
|
||||
|
||||
export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, ['common', 'modals', 'landing'])),
|
||||
},
|
||||
};
|
||||
};
|
||||
export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => ({
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, ['common', 'modals', 'landing'])),
|
||||
},
|
||||
});
|
||||
|
||||
const Home: NextPage = () => {
|
||||
const { t } = useTranslation();
|
||||
@ -39,11 +37,8 @@ const Home: NextPage = () => {
|
||||
const isLoggedIn = useAppSelector((state) => state.auth.isLoggedIn);
|
||||
|
||||
const handleLogin = () => dispatch(setModalState({ modal: 'auth.login', state: { open: true } }));
|
||||
|
||||
const handleRegister = () => dispatch(setModalState({ modal: 'auth.register', state: { open: true } }));
|
||||
|
||||
const handleToggle = () => dispatch(setTheme({ theme: theme === 'light' ? 'dark' : 'light' }));
|
||||
|
||||
const handleLogout = () => dispatch(logout());
|
||||
|
||||
return (
|
||||
@ -117,7 +112,13 @@ const Home: NextPage = () => {
|
||||
<div className={styles.screenshots}>
|
||||
{screenshots.map(({ src, alt }) => (
|
||||
<a key={src} href={src} className={styles.image} target="_blank" rel="noreferrer">
|
||||
<Image src={src} alt={alt} layout="fill" objectFit="cover" />
|
||||
<Image
|
||||
fill
|
||||
src={src}
|
||||
alt={alt}
|
||||
className="object-cover"
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||
/>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
@ -186,7 +187,13 @@ const Home: NextPage = () => {
|
||||
|
||||
<section className={styles.section}>
|
||||
<a href={DIGITALOCEAN_URL} target="_blank" rel="noreferrer">
|
||||
<Image src={`/images/sponsors/${theme=="dark"?"digitalocean":"digitaloceanLight"}.svg`} alt="Powered By DigitalOcean" width={200} height={40} />
|
||||
<Image
|
||||
src={`/images/sponsors/${theme == 'dark' ? 'digitalocean' : 'digitaloceanLight'}.svg`}
|
||||
style={{ width: 200, height: 40, objectFit: 'contain' }}
|
||||
alt="Powered By DigitalOcean"
|
||||
width={200}
|
||||
height={40}
|
||||
/>
|
||||
</a>
|
||||
</section>
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import { Download, Downloading } from '@mui/icons-material';
|
||||
import { ButtonBase } from '@mui/material';
|
||||
import { Resume } from '@reactive-resume/schema';
|
||||
import clsx from 'clsx';
|
||||
import dayjs from 'dayjs';
|
||||
import download from 'downloadjs';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
@ -69,7 +70,11 @@ const Preview: NextPage<Props> = ({ shortId }) => {
|
||||
|
||||
const handleDownload = async () => {
|
||||
try {
|
||||
const url = await mutateAsync({ username: resume.user.username, slug: resume.slug });
|
||||
const url = await mutateAsync({
|
||||
username: resume.user.username,
|
||||
slug: resume.slug,
|
||||
lastUpdated: dayjs(resume.updatedAt).unix().toString(),
|
||||
});
|
||||
|
||||
download(url);
|
||||
} catch {
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "ቅንብሮች",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "የወረቀት መጠን",
|
||||
"secondary": "ከቆመበት ቀጥል ገጾችዎ ልኬቶችን ይወስናል"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "መስመር መቁረጫ",
|
||||
"secondary": "የA4 ገጽ ቁመትን ለመለየት በሁሉም ገጾች ላይ መስመር አሳይ"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "الإعدادات",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "حجم الورق",
|
||||
"secondary": "تحدد أبعاد صفحات سيرتك الذاتية"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "خط فاصل",
|
||||
"secondary": "اعرض خط في كل الصفحات لتحديد ارتفاع صفحة A4"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Настройки",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Размер на хартията",
|
||||
"secondary": "Определя размерите на вашите страници с автобиография"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Линия на прекъсване",
|
||||
"secondary": "Показване на линия на всички страници за обозначаване на височината на страница A4"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "সেটিংস",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "কাগজের আকার",
|
||||
"secondary": "আপনার জীবনবৃত্তান্ত পৃষ্ঠাগুলির মাত্রা নির্ধারণ করে"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "লাইন ভেঙ্গে ফেলুন",
|
||||
"secondary": "একটি A4 পৃষ্ঠার উচ্চতা চিহ্নিত করতে সমস্ত পৃষ্ঠায় একটি লাইন দেখান৷"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Configuració",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Mida del paper",
|
||||
"secondary": "Determina les dimensions de les pàgines del vostre currículum"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Línia de trencament",
|
||||
"secondary": "Mostra una línia a totes les pàgines per marcar l'alçada d'una pàgina A4"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Nastavení",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Velikost papíru",
|
||||
"secondary": "Určuje rozměry stránek vašeho životopisu"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Nový řádek",
|
||||
"secondary": "Zobrazit čáru na všech stránkách pro označení výšky stránky A4"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Indstillinger",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Papirstørrelse",
|
||||
"secondary": "Bestemmer dimensionerne på dine CV-sider"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Brudlinje",
|
||||
"secondary": "Vis en streg på alle sider for at markere højden på en A4-side"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Einstellungen",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Papier größe",
|
||||
"secondary": "Legt die Abmessungen Ihrer Lebenslaufseiten fest"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Linie anhalten",
|
||||
"secondary": "Zeile auf allen Seiten anzeigen, um die Höhe einer A4-Seite zu markieren"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Ρυθμίσεις",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Μέγεθος χαρτιού",
|
||||
"secondary": "Καθορίζει τις διαστάσεις των σελίδων του βιογραφικού σας"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Γραμμή διακοπής",
|
||||
"secondary": "Εμφάνιση μιας γραμμής σε όλες τις σελίδες για να επισημάνετε το ύψος μιας σελίδας Α4"
|
||||
|
||||
@ -3,7 +3,8 @@
|
||||
"actions": {
|
||||
"add": "Add New {{token}}",
|
||||
"delete": "Delete {{token}}",
|
||||
"edit": "Edit {{token}}"
|
||||
"edit": "Edit {{token}}",
|
||||
"duplicate": "Duplicate Section"
|
||||
},
|
||||
"columns": {
|
||||
"heading": "Columns",
|
||||
@ -244,7 +245,7 @@
|
||||
"pdf": {
|
||||
"loading": {
|
||||
"primary": "Generating PDF",
|
||||
"secondary": "Please wait as your PDF gets generated, this may take upto 15 seconds."
|
||||
"secondary": "Please wait as your PDF gets generated, this may take up to 15 seconds."
|
||||
},
|
||||
"normal": {
|
||||
"primary": "PDF",
|
||||
@ -290,6 +291,10 @@
|
||||
},
|
||||
"heading": "Settings",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Paper Size",
|
||||
"secondary": "Determines the dimensions of your resume pages"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Break Line",
|
||||
"secondary": "Show a line on all pages to mark the height of an A4 page"
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
"toast": {
|
||||
"error": {
|
||||
"upload-file-size": "Please upload only files under 2 megabytes.",
|
||||
"upload-photo-size": "Please upload only photos under 2 megabytes, preferrably square."
|
||||
"upload-photo-size": "Please upload only photos under 2 megabytes, preferably square."
|
||||
},
|
||||
"success": {
|
||||
"resume-link-copied": "A link to your resume has been copied to your clipboard."
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Preferencias",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Tamaño de papel",
|
||||
"secondary": "Determina las dimensiones de las páginas de tu currículum."
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Linea de separación",
|
||||
"secondary": "Mostrar una línea en todas las páginas para marcar la altura de una página A4"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "تنظیمات",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "اندازه کاغذ",
|
||||
"secondary": "ابعاد صفحات رزومه شما را تعیین می کند"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "خط شکست",
|
||||
"secondary": "برای مشخص کردن ارتفاع صفحه A4 یک خط در همه صفحات نشان داده شود"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Asetukset",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Paperikoko",
|
||||
"secondary": "Määrittää ansioluettelosi sivujen mitat"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Katkoviiva",
|
||||
"secondary": "Näytä viiva kaikilla sivuilla A4-sivun korkeuden merkitsemiseksi"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Paramètres",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Taille de papier",
|
||||
"secondary": "Détermine les dimensions de vos pages de CV"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Indicateur de changement de page",
|
||||
"secondary": "Afficher une ligne sur toutes les pages pour marquer la hauteur d'une feuille A4"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "הגדרות",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "גודל נייר",
|
||||
"secondary": "קובע את הממדים של דפי קורות החיים שלך"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "קו עצירה",
|
||||
"secondary": "הצג קו בכל הדפים כדי לסמן את גובהו של דף A4"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "समायोजन",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "काग़ज़ का आकार",
|
||||
"secondary": "आपके रेज़्यूमे पृष्ठों के आयाम निर्धारित करता है"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "अंतराल वाली लकीर",
|
||||
"secondary": "A4 पृष्ठ की ऊंचाई को चिह्नित करने के लिए सभी पृष्ठों पर एक पंक्ति दिखाएं"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"common": {
|
||||
"actions": {
|
||||
"add": "Új hozzáadása {{token}}",
|
||||
"add": "Új {{token}} hozzáadása",
|
||||
"delete": "{{token}} törlése",
|
||||
"edit": "{{token}} szerkesztése"
|
||||
},
|
||||
@ -20,7 +20,7 @@
|
||||
"label": "E-mail cím"
|
||||
},
|
||||
"end-date": {
|
||||
"help-text": "Hagyja üresen ezt a mezőt, ha még mindig tart van",
|
||||
"help-text": "Hagyja üresen ezt a mezőt, ha még folyamatban van",
|
||||
"label": "Befejezés dátuma"
|
||||
},
|
||||
"keywords": {
|
||||
@ -51,7 +51,7 @@
|
||||
"label": "Összegzés"
|
||||
},
|
||||
"title": {
|
||||
"label": "Cím"
|
||||
"label": "Titulus"
|
||||
},
|
||||
"url": {
|
||||
"label": "Honlap"
|
||||
@ -63,30 +63,30 @@
|
||||
"list": {
|
||||
"actions": {
|
||||
"delete": "Törlés",
|
||||
"duplicate": "Duplikálás",
|
||||
"duplicate": "Másolás",
|
||||
"edit": "Szerkesztés"
|
||||
},
|
||||
"empty-text": "Ez a lista üres."
|
||||
},
|
||||
"tooltip": {
|
||||
"delete-item": "Biztosan törli ezt az elemet? Ez egy visszafordíthatatlan művelet.",
|
||||
"delete-item": "Biztosan törli ezt az elemet? Ez a művelet nem visszavonható.",
|
||||
"delete-section": "Szakasz törlése",
|
||||
"rename-section": "Szakasz átnevezése",
|
||||
"toggle-visibility": "Láthatóság váltása"
|
||||
"toggle-visibility": "Láthatóság ki/be"
|
||||
}
|
||||
},
|
||||
"controller": {
|
||||
"tooltip": {
|
||||
"center-artboard": "Központi rajztábla",
|
||||
"copy-link": "Link másolása az önéletrajzba",
|
||||
"copy-link": "Önéletrajz link másolása",
|
||||
"export-pdf": "Exportálás PDF-be",
|
||||
"toggle-orientation": "Oldaltájolás váltása",
|
||||
"toggle-page-break-line": "Oldaltörés vonal váltása",
|
||||
"toggle-sidebars": "Az oldalsávok váltása",
|
||||
"toggle-orientation": "Oldaltájolás",
|
||||
"toggle-page-break-line": "Oldaltörés vonal ki/be",
|
||||
"toggle-sidebars": "Oldalsávok ki/be",
|
||||
"zoom-in": "Nagyítás",
|
||||
"zoom-out": "Kicsinyítés",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo"
|
||||
"undo": "Visszavonás",
|
||||
"redo": "Mégis"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
@ -96,7 +96,7 @@
|
||||
"rename": "Átnevezés",
|
||||
"share-link": "Link megosztása",
|
||||
"tooltips": {
|
||||
"delete": "Biztos, hogy törölni szeretné ezt az önéletrajzot? Ez egy visszafordíthatatlan művelet.",
|
||||
"delete": "Biztos, hogy törölni szeretné ezt az önéletrajzot? Ez a művelet nem visszavonható.",
|
||||
"share-link": "Az önéletrajz láthatóságát nyilvánosra kell változtatnia, hogy mások számára is látható legyen."
|
||||
}
|
||||
}
|
||||
@ -138,7 +138,7 @@
|
||||
"heading": "Alak"
|
||||
},
|
||||
"size": {
|
||||
"heading": "Méret (px-ben)"
|
||||
"heading": "Méret (pixel)"
|
||||
}
|
||||
},
|
||||
"photo-upload": {
|
||||
@ -189,7 +189,7 @@
|
||||
"label": "Irányítószám"
|
||||
},
|
||||
"region": {
|
||||
"label": "Vidék"
|
||||
"label": "Régió"
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
@ -248,7 +248,7 @@
|
||||
},
|
||||
"normal": {
|
||||
"primary": "PDF",
|
||||
"secondary": "Töltse le önéletrajzának PDF formátumát, amelyet kinyomtathat és elküldhet álmai munkahelyére. Ez a fájl nem importálható vissza további szerkesztéshez."
|
||||
"secondary": "Töltse le önéletrajzát PDF formátumban, amelyet kinyomtathat és elküldhet álmai munkahelyére. Ez a fájl nem importálható vissza további szerkesztéshez."
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -265,7 +265,7 @@
|
||||
"heading": "Hibák? Funkciókérés?"
|
||||
},
|
||||
"donate": {
|
||||
"body": "Ha tetszett a Reactive Resume, kérjük, fontolja meg, hogy amennyit csak tud, adományozzon arra, hogy az alkalmazás folyamatosan működjön, hirdetések nélkül és örökké ingyenesen.",
|
||||
"body": "Ha elégedett a Reactive Resume alkalmazással, kérjük, fontolja meg, hogy tetszőleges összeggel támogassa munkánkat, hogy továbbra is ingyenes és hirdetésmentes lehessen.",
|
||||
"button": "Vegyél nekem egy kávét",
|
||||
"heading": "Adományozzon a Reactive Resume-nak"
|
||||
},
|
||||
@ -277,12 +277,12 @@
|
||||
"global": {
|
||||
"date": {
|
||||
"primary": "Dátum",
|
||||
"secondary": "Az alkalmazásban használható dátumformátum"
|
||||
"secondary": "Az alkalmazásban használt dátumformátum"
|
||||
},
|
||||
"heading": "Globális",
|
||||
"language": {
|
||||
"primary": "Nyelv",
|
||||
"secondary": "Az alkalmazásban használható megjelenítési nyelv"
|
||||
"secondary": "Az alkalmazásban használt megjelenítési nyelv"
|
||||
},
|
||||
"theme": {
|
||||
"primary": "Téma"
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Beállítások",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Papírméret",
|
||||
"secondary": "Meghatározza az önéletrajz oldalméreteit"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Törésvonal",
|
||||
"secondary": "Mutasson egy vonalat az összes oldalon, hogy megjelölje egy A4-es oldal magasságát"
|
||||
@ -297,7 +301,7 @@
|
||||
"heading": "oldal",
|
||||
"orientation": {
|
||||
"disabled": "Nincs hatása, ha csak egy oldal van",
|
||||
"primary": "Irányultság",
|
||||
"primary": "Tájolás",
|
||||
"secondary": "Az oldalak vízszintes vagy függőleges megjelenítése"
|
||||
}
|
||||
},
|
||||
@ -316,7 +320,7 @@
|
||||
"sharing": {
|
||||
"heading": "Megosztás",
|
||||
"short-url": {
|
||||
"label": "Rövid URL-t részesítsen előnyben"
|
||||
"label": "Rövid URL előnyben részesítése"
|
||||
},
|
||||
"visibility": {
|
||||
"subtitle": "A link birtokában bárki megtekintheti önéletrajzát",
|
||||
@ -332,7 +336,7 @@
|
||||
"label": "Háttér"
|
||||
},
|
||||
"primary": {
|
||||
"label": "Elsődleges"
|
||||
"label": "Elsődleges "
|
||||
},
|
||||
"text": {
|
||||
"label": "Szöveg"
|
||||
@ -352,7 +356,7 @@
|
||||
"heading": "Tipográfia",
|
||||
"widgets": {
|
||||
"body": {
|
||||
"label": "Test"
|
||||
"label": "Szövegtörzs"
|
||||
},
|
||||
"headings": {
|
||||
"label": "Címsorok"
|
||||
|
||||
@ -6,14 +6,14 @@
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"credit": "<1>Amruth Pillai szenvedélyes projektje</1>",
|
||||
"credit": "<1>Amruth Pillai hobbi projektje</1>",
|
||||
"license": "A közösség által, a közösségért."
|
||||
},
|
||||
"markdown": {
|
||||
"help-text": "Ez a szakasz támogatja a <1>markdown</1> formázást."
|
||||
},
|
||||
"date": {
|
||||
"present": "Ajándék"
|
||||
"present": "Jelenleg is"
|
||||
},
|
||||
"subtitle": "Ingyenes és nyílt forráskódú önéletrajzkészítő.",
|
||||
"title": "Reactive Resume",
|
||||
|
||||
@ -4,18 +4,18 @@
|
||||
"title": "Új önéletrajz létrehozása"
|
||||
},
|
||||
"import-external": {
|
||||
"subtitle": "LinkedIn, JSON önéletrajz, reaktív önéletrajz",
|
||||
"subtitle": "LinkedIn, JSON önéletrajz, Reactive Resume",
|
||||
"title": "Importálás külső forrásokból"
|
||||
},
|
||||
"resume": {
|
||||
"menu": {
|
||||
"delete": "Törlés",
|
||||
"duplicate": "Másolat",
|
||||
"open": "Nyisd ki",
|
||||
"open": "Megnyitás",
|
||||
"rename": "Átnevezés",
|
||||
"share-link": "Link megosztása",
|
||||
"tooltips": {
|
||||
"delete": "Biztos, hogy törölni szeretné ezt az önéletrajzot? Ez egy visszafordíthatatlan művelet.",
|
||||
"delete": "Biztos, hogy törölni szeretné ezt az önéletrajzot? Ez a művelet nem visszavonható.",
|
||||
"share-link": "Az önéletrajz láthatóságát nyilvánosra kell változtatnia, hogy mások számára is látható legyen."
|
||||
}
|
||||
},
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"actions": {
|
||||
"app": "Lépjen az App",
|
||||
"app": "Alkalmazás indítása",
|
||||
"login": "Belépés",
|
||||
"logout": "Kijelentkezés",
|
||||
"register": "Regisztráció"
|
||||
@ -8,7 +8,7 @@
|
||||
"features": {
|
||||
"heading": "Jellemzők",
|
||||
"list": {
|
||||
"ads": "Nincs reklám",
|
||||
"ads": "Reklámmentes",
|
||||
"export": "Exportálja önéletrajzát JSON vagy PDF formátumba",
|
||||
"free": "Ingyenes, örökre",
|
||||
"import": "Adatok importálása a LinkedInből, JSON Resume",
|
||||
@ -20,11 +20,11 @@
|
||||
"links": {
|
||||
"heading": "Linkek",
|
||||
"links": {
|
||||
"donate": "Adományoz",
|
||||
"donate": "Adományozás",
|
||||
"github": "Forráskód",
|
||||
"docs": "Dokumentáció",
|
||||
"privacy": "Adatvédelmi irányelvek",
|
||||
"service": "Szolgáltatási feltételek"
|
||||
"service": "Felhasználói feltételek"
|
||||
}
|
||||
},
|
||||
"screenshots": {
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"auth": {
|
||||
"forgot-password": {
|
||||
"actions": {
|
||||
"send-email": "Jelszó visszaállítása e-mail küldése"
|
||||
"send-email": "Jelszó helyreállító e-mail küldése"
|
||||
},
|
||||
"body": "Csak adja meg a helyreállítani kívánt fiókhoz társított e-mail címet.",
|
||||
"form": {
|
||||
@ -27,14 +27,14 @@
|
||||
"label": "Felhasználónév"
|
||||
}
|
||||
},
|
||||
"heading": "Jelentkezz be a fiókodba",
|
||||
"heading": "Jelentkezzen be a fiókjába",
|
||||
"recover-text": "Ha elfelejtette jelszavát, <1>visszaállíthatja fiókját</1> itt.",
|
||||
"register-text": "Ha nem rendelkezik fiókkal, <1>létrehozhat egy fiókot</1> itt."
|
||||
},
|
||||
"register": {
|
||||
"actions": {
|
||||
"register": "Regisztráció",
|
||||
"google": "Regisztráljon a Google-nál"
|
||||
"google": "Regisztráljon Google fiókkal"
|
||||
},
|
||||
"body": "Kérjük, adja meg személyes adatait fiók létrehozásához.",
|
||||
"form": {
|
||||
@ -87,7 +87,7 @@
|
||||
"label": "Nyilvánosan elérhető?"
|
||||
},
|
||||
"slug": {
|
||||
"label": "Meztelen csiga"
|
||||
"label": "Saját URL"
|
||||
}
|
||||
},
|
||||
"heading": "Új önéletrajz létrehozása"
|
||||
@ -126,7 +126,7 @@
|
||||
"label": "Név"
|
||||
},
|
||||
"slug": {
|
||||
"label": "Meztelen csiga"
|
||||
"label": "Saját URL"
|
||||
}
|
||||
},
|
||||
"heading": "Nevezze át önéletrajzát"
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
"label": "Tanggal"
|
||||
},
|
||||
"description": {
|
||||
"label": "Keterangan"
|
||||
"label": "Deskripsi"
|
||||
},
|
||||
"email": {
|
||||
"label": "Alamat Email"
|
||||
@ -130,7 +130,7 @@
|
||||
"label": "Batas"
|
||||
},
|
||||
"grayscale": {
|
||||
"label": "Grayscale"
|
||||
"label": "Tingkat keabuan"
|
||||
},
|
||||
"heading": "Efek"
|
||||
},
|
||||
@ -138,7 +138,7 @@
|
||||
"heading": "Bentuk"
|
||||
},
|
||||
"size": {
|
||||
"heading": "Besar (dalam px)"
|
||||
"heading": "Ukuran (dalam px)"
|
||||
}
|
||||
},
|
||||
"photo-upload": {
|
||||
@ -158,7 +158,7 @@
|
||||
"education": {
|
||||
"form": {
|
||||
"area-study": {
|
||||
"label": "Area belajar"
|
||||
"label": "Bidang Studi"
|
||||
},
|
||||
"courses": {
|
||||
"label": "Kursus"
|
||||
@ -170,7 +170,7 @@
|
||||
"label": "Tingkatan"
|
||||
},
|
||||
"institution": {
|
||||
"label": "Lembaga"
|
||||
"label": "Institusi"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Pengaturan",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Ukuran kertas",
|
||||
"secondary": "Menentukan dimensi halaman resume Anda"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Break Line",
|
||||
"secondary": "Tampilkan garis di semua halaman untuk menandai ketinggian halaman A4"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Impostazioni",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Dimensioni del foglio",
|
||||
"secondary": "Determina le dimensioni delle pagine del tuo curriculum"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Linea di interruzione",
|
||||
"secondary": "Mostra su tutte le pagine una linea che indica l'altezza di un foglio A4"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "設定",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "用紙サイズ",
|
||||
"secondary": "履歴書ページのサイズを決定します"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "ブレイクライン",
|
||||
"secondary": "A4ページの高さを示す線を全ページに表示する。"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "ការកំណត់",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "ទំហំក្រដាស",
|
||||
"secondary": "កំណត់វិមាត្រនៃទំព័រប្រវត្តិរូបរបស់អ្នក។"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "បំបែកបន្ទាត់",
|
||||
"secondary": "បង្ហាញបន្ទាត់នៅលើទំព័រទាំងអស់ដើម្បីសម្គាល់កម្ពស់នៃទំព័រ A4"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "ಅಳವಡಿಕೆಗಳು",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "ಕಾಗದದ ಗಾತ್ರ",
|
||||
"secondary": "ನಿಮ್ಮ ರೆಸ್ಯೂಮ್ ಪುಟಗಳ ಆಯಾಮಗಳನ್ನು ನಿರ್ಧರಿಸುತ್ತದೆ"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "ಬ್ರೇಕ್ ಲೈನ್",
|
||||
"secondary": "A4 ಪುಟದ ಎತ್ತರವನ್ನು ಗುರುತಿಸಲು ಎಲ್ಲಾ ಪುಟಗಳಲ್ಲಿ ಒಂದು ಸಾಲನ್ನು ತೋರಿಸಿ"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "설정",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "용지 크기",
|
||||
"secondary": "이력서 페이지의 크기를 결정합니다."
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "브레이크 라인",
|
||||
"secondary": "A4 페이지의 높이를 표시하기 위해 모든 페이지에 선 표시"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "ക്രമീകരണങ്ങള്",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "പേപ്പർ വലിപ്പം",
|
||||
"secondary": "നിങ്ങളുടെ റെസ്യൂമെ പേജുകളുടെ അളവുകൾ നിർണ്ണയിക്കുന്നു"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "ബ്രേക്ക് ലൈൻ",
|
||||
"secondary": "A4 പേജിന്റെ ഉയരം അടയാളപ്പെടുത്താൻ എല്ലാ പേജുകളിലും ഒരു വരി കാണിക്കുക"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "सेटिंग्ज",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "कागदाचा आकार",
|
||||
"secondary": "तुमच्या रेझ्युमे पेजेसची परिमाणे निर्धारित करते"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "ब्रेक लाइन",
|
||||
"secondary": "A4 पृष्ठाची उंची चिन्हांकित करण्यासाठी सर्व पृष्ठांवर एक ओळ दर्शवा"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "सेटिङहरू",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "कागज आकार",
|
||||
"secondary": "तपाईंको पुन: सुरुवात पृष्ठहरूको आयामहरू निर्धारण गर्दछ"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "ब्रेक लाइन",
|
||||
"secondary": "A4 पृष्ठको उचाइ चिन्ह लगाउन सबै पृष्ठहरूमा रेखा देखाउनुहोस्"
|
||||
|
||||
@ -267,7 +267,7 @@
|
||||
"donate": {
|
||||
"body": "Als Je Reactive Resume graag gebruikt, kun je overwegen zoveel mogelijk te doneren om de app in de lucht te houden, zonder advertenties en voor altijd gratis.",
|
||||
"button": "Betaal me een koffie",
|
||||
"heading": "Doneer aan Reactiv Resume"
|
||||
"heading": "Doneer aan Reactive Resume"
|
||||
},
|
||||
"github": "Broncode",
|
||||
"docs": "Documentatie",
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Instellingen",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Papiergrootte",
|
||||
"secondary": "Bepaalt de afmetingen van uw cv-pagina's"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Breek Lijn",
|
||||
"secondary": "Toon een lijn op alle pagina's om de hoogte van een A4-pagina te markeren"
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
"license": "Door de gemeenschap, voor de gemeenschap."
|
||||
},
|
||||
"markdown": {
|
||||
"help-text": "Deze sectie ondersteunt <1>html</1> opmaak."
|
||||
"help-text": "Deze sectie ondersteunt <1>markdown</1> opmaak."
|
||||
},
|
||||
"date": {
|
||||
"present": "Heden"
|
||||
@ -20,7 +20,7 @@
|
||||
"toast": {
|
||||
"error": {
|
||||
"upload-file-size": "Upload alleen bestanden onder de 2 megabytes.",
|
||||
"upload-photo-size": "Upload alleen foto's onder de 2 megabytes, bij voorkeur vierkante."
|
||||
"upload-photo-size": "Upload alleen foto's onder de 2 megabytes, bij voorkeur vierkant."
|
||||
},
|
||||
"success": {
|
||||
"resume-link-copied": "Een link naar jouw CV is naar het klembord gekopieerd."
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Innstillinger",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Papir størrelse",
|
||||
"secondary": "Bestemmer dimensjonene til CV-sidene dine"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Break Line",
|
||||
"secondary": "Vis en linje på alle sider for å markere høyden på en A4-side"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "ସେଟିଂସ୍",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "କାଗଜ ଆକାର |",
|
||||
"secondary": "ତୁମର ରିଜ୍ୟୁମ୍ ପୃଷ୍ଠାଗୁଡ଼ିକର ପରିମାଣ ନିର୍ଣ୍ଣୟ କରେ |"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "ବ୍ରେକ୍ ଲାଇନ୍",
|
||||
"secondary": "A4 ପୃଷ୍ଠାର ଉଚ୍ଚତା ଚିହ୍ନିତ କରିବାକୁ ସମସ୍ତ ପୃଷ୍ଠାରେ ଏକ ରେଖା ଦେଖାନ୍ତୁ ।"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Ustawienia",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Rozmiar papieru",
|
||||
"secondary": "Określa wymiary twoich stron CV"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Inteligentne dopasowanie linii",
|
||||
"secondary": "Pokaż linię na wszystkich stronach, aby oznaczyć wysokość strony A4"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Configurações",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Tamanho do papel",
|
||||
"secondary": "Determina as dimensões das suas páginas de currículo"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Linha de quebra",
|
||||
"secondary": "Mostrar uma linha em todas as páginas para marcar a altura de uma página A4"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Setări",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Dimensiunea hartiei",
|
||||
"secondary": "Determină dimensiunile paginilor de CV"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Linia de pauză",
|
||||
"secondary": "Afișați o linie pe toate paginile pentru a marca înălțimea unei pagini A4"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Настройки",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Размер бумаги",
|
||||
"secondary": "Определяет размеры страниц вашего резюме"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Линия разрыва",
|
||||
"secondary": "Показывать линию, обозначающую высоту страницы A4"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Podešavanja",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Величина папира",
|
||||
"secondary": "Одређује димензије страница вашег животописа"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Prelomi Redova",
|
||||
"secondary": "Pokaži liniju na svakoj stranici kako bi se obeležila visina A4 stranice"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Inställningar",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Pappersformat",
|
||||
"secondary": "Bestämmer måtten på dina CV-sidor"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Radbrytning",
|
||||
"secondary": "Visa en linje på alla sidor för att markera höjden på en A4-sida"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "அமைப்புகள்",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "காகித அளவு",
|
||||
"secondary": "உங்கள் ரெஸ்யூம் பக்கங்களின் பரிமாணங்களைத் தீர்மானிக்கிறது"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "பிரேக் லைன்",
|
||||
"secondary": "A4 பக்கத்தின் உயரத்தைக் குறிக்க அனைத்துப் பக்கங்களிலும் ஒரு வரியைக் காட்டு"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Ayarlar",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Kağıt boyutu",
|
||||
"secondary": "Özgeçmiş sayfalarınızın boyutlarını belirler"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Ara Hat",
|
||||
"secondary": "A4 sayfasının yüksekliğini işaretlemek için tüm sayfalarda bir çizgi göster"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Налаштування",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Розмір паперу",
|
||||
"secondary": "Визначає розміри сторінок вашого резюме"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Розривати рядки",
|
||||
"secondary": "Показати лінію на всіх сторінках, для позначення розміру сторінки А4"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "Cài đặt",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Khổ giấy",
|
||||
"secondary": "Xác định kích thước của các trang sơ yếu lý lịch của bạn"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Ngắt dòng",
|
||||
"secondary": "Hiển thị một dòng chữ trên tất cả các trang để đánh dấu chiều cao của trang A4"
|
||||
|
||||
@ -290,6 +290,10 @@
|
||||
},
|
||||
"heading": "设置",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "纸张尺寸",
|
||||
"secondary": "确定简历页面的尺寸"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "换行",
|
||||
"secondary": "在所有页面上显示一条线以标记 A4 页面的高度"
|
||||
|
||||
@ -18,8 +18,8 @@ export type LoginWithGoogleParams = {
|
||||
|
||||
export type RegisterParams = {
|
||||
name: string;
|
||||
username: string;
|
||||
email: string;
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
|
||||
@ -3,7 +3,12 @@ import axios from './axios';
|
||||
export type PrintResumeAsPdfParams = {
|
||||
username: string;
|
||||
slug: string;
|
||||
lastUpdated: string;
|
||||
};
|
||||
|
||||
export const printResumeAsPdf = (printResumeAsPdfParams: PrintResumeAsPdfParams): Promise<string> =>
|
||||
axios.get(`/printer/${printResumeAsPdfParams.username}/${printResumeAsPdfParams.slug}`).then((res) => res.data);
|
||||
axios
|
||||
.get(
|
||||
`/printer/${printResumeAsPdfParams.username}/${printResumeAsPdfParams.slug}?lastUpdated=${printResumeAsPdfParams.lastUpdated}`
|
||||
)
|
||||
.then((res) => res.data);
|
||||
|
||||
@ -34,7 +34,7 @@ const store = configureStore({
|
||||
},
|
||||
});
|
||||
|
||||
sagaMiddleware.run(syncSaga);
|
||||
sagaMiddleware.run(() => syncSaga(store.dispatch));
|
||||
|
||||
export const persistor = persistStore(store);
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ export type ModalName =
|
||||
| 'dashboard.import-external'
|
||||
| 'dashboard.rename-resume'
|
||||
| 'builder.sections.profile'
|
||||
| 'builder.sections.work'
|
||||
| `builder.sections.${string}`;
|
||||
|
||||
export type ModalState = {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ListItem, Profile, Resume, Section } from '@reactive-resume/schema';
|
||||
import { ListItem, Profile, Resume, Section, SectionType } from '@reactive-resume/schema';
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import get from 'lodash/get';
|
||||
@ -7,6 +7,8 @@ import pick from 'lodash/pick';
|
||||
import set from 'lodash/set';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { getSectionsByType } from '@/config/sections';
|
||||
|
||||
type SetResumeStatePayload = { path: string; value: unknown };
|
||||
|
||||
type AddItemPayload = { path: string; value: ListItem };
|
||||
@ -17,7 +19,7 @@ type DuplicateItemPayload = { path: string; value: ListItem };
|
||||
|
||||
type DeleteItemPayload = { path: string; value: ListItem };
|
||||
|
||||
type AddSectionPayload = { value: Section };
|
||||
type AddSectionPayload = { value: Section; type: SectionType };
|
||||
|
||||
type DeleteSectionPayload = { path: string };
|
||||
|
||||
@ -38,7 +40,7 @@ export const resumeSlice = createSlice({
|
||||
addItem: (state: Resume, action: PayloadAction<AddItemPayload>) => {
|
||||
const { path, value } = action.payload;
|
||||
const id = uuidv4();
|
||||
const list = get(state, path, []);
|
||||
const list: ListItem[] = get(state, path, []);
|
||||
const item = merge(value, { id });
|
||||
|
||||
list.push(item);
|
||||
@ -80,6 +82,15 @@ export const resumeSlice = createSlice({
|
||||
state.sections[id] = value;
|
||||
state.metadata.layout[0][0].push(id);
|
||||
},
|
||||
duplicateSection: (state: Resume, action: PayloadAction<AddSectionPayload>) => {
|
||||
const { value, type } = action.payload;
|
||||
|
||||
const id = getSectionsByType(state.sections, type).length + 1;
|
||||
value.name = value.name + '-' + id;
|
||||
|
||||
state.sections[`${type}-${id}`] = value;
|
||||
state.metadata.layout[0][0].push(`${type}-${id}`);
|
||||
},
|
||||
deleteSection: (state: Resume, action: PayloadAction<DeleteSectionPayload>) => {
|
||||
const { path } = action.payload;
|
||||
const id = path.split('.')[1];
|
||||
@ -119,6 +130,7 @@ export const {
|
||||
duplicateItem,
|
||||
deleteItem,
|
||||
addSection,
|
||||
duplicateSection,
|
||||
deleteSection,
|
||||
addPage,
|
||||
deletePage,
|
||||
|
||||
@ -3,7 +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 { AppDispatch, RootState } from '@/store/index';
|
||||
|
||||
import {
|
||||
addItem,
|
||||
@ -12,23 +12,26 @@ import {
|
||||
deleteSection,
|
||||
duplicateItem,
|
||||
editItem,
|
||||
setResume,
|
||||
setResumeState,
|
||||
} from '../resume/resumeSlice';
|
||||
|
||||
const DEBOUNCE_WAIT = 1000;
|
||||
|
||||
const debouncedSync = debounce((resume: Resume) => updateResume(resume), DEBOUNCE_WAIT);
|
||||
const debouncedSync = debounce(
|
||||
(resume: Resume, dispatch: AppDispatch) => updateResume(resume).then((resume) => dispatch(setResume(resume))),
|
||||
DEBOUNCE_WAIT
|
||||
);
|
||||
|
||||
function* handleSync() {
|
||||
function* handleSync(dispatch: AppDispatch) {
|
||||
const resume: Resume = yield select((state: RootState) => state.resume.present);
|
||||
|
||||
debouncedSync(resume);
|
||||
debouncedSync(resume, dispatch);
|
||||
}
|
||||
|
||||
function* syncSaga() {
|
||||
yield takeLatest(
|
||||
[setResumeState, addItem, editItem, duplicateItem, deleteItem, addSection, deleteSection],
|
||||
handleSync
|
||||
function* syncSaga(dispatch: AppDispatch) {
|
||||
yield takeLatest([setResumeState, addItem, editItem, duplicateItem, deleteItem, addSection, deleteSection], () =>
|
||||
handleSync(dispatch)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -22,11 +22,19 @@
|
||||
}
|
||||
|
||||
p {
|
||||
@apply leading-relaxed;
|
||||
@apply leading-normal;
|
||||
}
|
||||
|
||||
a {
|
||||
@apply cursor-pointer font-medium hover:underline;
|
||||
@apply cursor-pointer font-medium;
|
||||
}
|
||||
|
||||
.markdown {
|
||||
@apply prose prose-sm leading-normal max-w-none prose-ul:p-0 prose-ul:my-0 prose-p:my-0;
|
||||
|
||||
ul li {
|
||||
@apply ml-4 list-outside;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -52,7 +52,7 @@
|
||||
@apply grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-6;
|
||||
|
||||
.image {
|
||||
@apply relative h-64 rounded hover:opacity-75;
|
||||
@apply relative h-48 rounded hover:opacity-75;
|
||||
@apply border-2 dark:border-neutral-700;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { Theme } from '@reactive-resume/schema';
|
||||
import { ThemeConfig } from '@reactive-resume/schema';
|
||||
import clsx from 'clsx';
|
||||
import get from 'lodash/get';
|
||||
import { useMemo } from 'react';
|
||||
@ -17,7 +17,7 @@ const Castform: React.FC<PageProps> = ({ page }) => {
|
||||
const isFirstPage = useMemo(() => page === 0, [page]);
|
||||
|
||||
const layout: string[][] = useAppSelector((state) => state.resume.present.metadata.layout[page]);
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {} as ThemeConfig));
|
||||
|
||||
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
||||
const color = useMemo(() => (contrast === 'dark' ? theme.text : theme.background), [theme, contrast]);
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { darken } from '@mui/material';
|
||||
import { Theme } from '@reactive-resume/schema';
|
||||
import { ThemeConfig } from '@reactive-resume/schema';
|
||||
import get from 'lodash/get';
|
||||
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.present, 'metadata.theme', {}));
|
||||
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {} as ThemeConfig));
|
||||
const darkerPrimary = useMemo(() => darken(theme.primary, 0.2), [theme.primary]);
|
||||
|
||||
return (
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
|
||||
import { Theme } from '@reactive-resume/schema';
|
||||
import { ThemeConfig } from '@reactive-resume/schema';
|
||||
import clsx from 'clsx';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
@ -19,7 +19,7 @@ export const MastheadSidebar: React.FC = () => {
|
||||
const { name, headline, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
||||
(state) => state.resume.present.basics
|
||||
);
|
||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {} as ThemeConfig));
|
||||
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
||||
const color = useMemo(() => (contrast === 'dark' ? theme.text : theme.background), [theme, contrast]);
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ const Section: React.FC<SectionProps> = ({
|
||||
headlinePath = 'headline',
|
||||
keywordsPath = 'keywords',
|
||||
}) => {
|
||||
const section: SectionType = useAppSelector((state) => get(state.resume.present, path, {}));
|
||||
const section: SectionType = useAppSelector((state) => get(state.resume.present, path, {} as SectionType));
|
||||
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
||||
|
||||
const sectionId = useMemo(() => section.id || path.replace('sections.', ''), [path, section]);
|
||||
@ -44,13 +44,13 @@ const Section: React.FC<SectionProps> = ({
|
||||
subtitle = parseListItemPath(item, subtitlePath),
|
||||
headline = parseListItemPath(item, headlinePath),
|
||||
keywords: string[] = get(item, keywordsPath),
|
||||
url: string = get(item, 'url'),
|
||||
summary: string = get(item, 'summary'),
|
||||
level: string = get(item, 'level'),
|
||||
levelNum: number = get(item, 'levelNum'),
|
||||
phone: string = get(item, 'phone'),
|
||||
email: string = get(item, 'email'),
|
||||
date = formatDateString(get(item, 'date'), dateFormat);
|
||||
url: string = get(item, 'url', ''),
|
||||
level: string = get(item, 'level', ''),
|
||||
phone: string = get(item, 'phone', ''),
|
||||
email: string = get(item, 'email', ''),
|
||||
summary: string = get(item, 'summary', ''),
|
||||
levelNum: number = get(item, 'levelNum', 0),
|
||||
date = formatDateString(get(item, 'date', ''), dateFormat);
|
||||
|
||||
return (
|
||||
<div key={id} id={id} className="grid gap-1">
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user