Compare commits

...

69 Commits

Author SHA1 Message Date
d67272cf9e bump up version to 3.8.3 2023-07-27 20:09:26 +02:00
f937a88b9d attempt to fix leafish template issues 2023-07-27 20:08:47 +02:00
7465a7ec78 Merge pull request #1407 from AmruthPillai/fix/id-null-when-section-duplicated-cloned
Fix issue when a section is duplicated/cloned, ID is null
2023-07-27 19:51:01 +02:00
b53d8854df bump up version to 3.8.2 2023-07-27 19:43:19 +02:00
bffa0be909 fix dev dotenv script 2023-07-27 19:42:48 +02:00
06fee1696e update dependencies, fix birthDate error outline 2023-07-27 19:42:25 +02:00
f9a11092a6 Fix issue when a section is duplicated/cloned, ID is null 2023-07-27 18:35:19 +02:00
9c76999945 add start pipeline command to turbo 2023-07-25 15:37:10 +02:00
d23d1a615e Merge pull request #1403 from Jubair70/fix_education_modal_datepicker
fix: Education Date picker modal UI component
2023-07-25 14:50:46 +02:00
a5701a37a6 Merge pull request #1400 from rcwell/main
Fixed Date entered on any <Date> field is one day earlier
2023-07-25 14:50:39 +02:00
a739b25f42 update all dependencies 2023-07-25 14:50:10 +02:00
145aa14ba0 fix: Education Date picker modal 2023-07-24 18:27:39 +06:00
94358bf61c don't convert date to local date 2023-07-23 07:46:19 +14:00
ce50df61a5 Merge pull request #1395 from AmruthPillai/i18n_main
New Crowdin updates
2023-07-21 22:14:17 +02:00
f18da54dfa Merge branch 'main' into i18n_main 2023-07-21 22:14:11 +02:00
14c5e36fae Merge pull request #1396 from Jubair70/Jubair70/fixes-date-selection
Fixed date selection by selecting month after year
2023-07-21 22:13:06 +02:00
1483f9b4f2 Merge branch 'main' into Jubair70/fixes-date-selection 2023-07-21 15:04:53 +06:00
f7d8e4ebb4 slots removed 2023-07-21 15:03:58 +06:00
7c42d6e607 New translations modals.json (Portuguese, Brazilian) 2023-07-20 23:34:56 +02:00
08dea8ad8b New translations landing.json (Portuguese, Brazilian) 2023-07-20 23:34:41 +02:00
950d7ea4e7 New translations common.json (Portuguese, Brazilian) 2023-07-20 23:34:15 +02:00
ebc12042a9 New translations builder.json (Nepali) 2023-07-20 23:34:05 +02:00
d8168d2a9d New translations builder.json (Amharic) 2023-07-20 23:34:04 +02:00
7cfda3c83d New translations builder.json (Odia) 2023-07-20 23:34:03 +02:00
8fcfbdd69d New translations builder.json (Kannada) 2023-07-20 23:34:03 +02:00
1eb52261f2 New translations builder.json (Malayalam) 2023-07-20 23:34:02 +02:00
88400b769d New translations builder.json (Hindi) 2023-07-20 23:34:01 +02:00
b6831fc532 New translations builder.json (Marathi) 2023-07-20 23:34:00 +02:00
b231b60b5a New translations builder.json (Tamil) 2023-07-20 23:33:59 +02:00
2679c9ebc2 New translations builder.json (Khmer) 2023-07-20 23:33:58 +02:00
278253b809 New translations builder.json (Persian) 2023-07-20 23:33:57 +02:00
8a933de0bd New translations builder.json (Indonesian) 2023-07-20 23:33:57 +02:00
704cba06f4 New translations builder.json (Portuguese, Brazilian) 2023-07-20 23:33:56 +02:00
b946098bd0 New translations builder.json (Vietnamese) 2023-07-20 23:33:55 +02:00
f7b95f7679 New translations builder.json (Ukrainian) 2023-07-20 23:33:54 +02:00
e38967874e New translations builder.json (Turkish) 2023-07-20 23:33:53 +02:00
8368c4e183 New translations builder.json (Serbian (Cyrillic)) 2023-07-20 23:33:52 +02:00
951f14ef69 New translations builder.json (Russian) 2023-07-20 23:33:51 +02:00
4a75be95ef New translations builder.json (Portuguese) 2023-07-20 23:33:51 +02:00
1125557fbc New translations builder.json (Polish) 2023-07-20 23:33:50 +02:00
db63138307 New translations builder.json (Korean) 2023-07-20 23:33:48 +02:00
a52feac93b New translations builder.json (Italian) 2023-07-20 23:33:47 +02:00
ad7b6ad2c6 New translations builder.json (Hungarian) 2023-07-20 23:33:46 +02:00
33e3850bb7 New translations builder.json (Hebrew) 2023-07-20 23:33:45 +02:00
c29605dbd0 New translations builder.json (Finnish) 2023-07-20 23:33:44 +02:00
14b2ba4f73 New translations builder.json (Greek) 2023-07-20 23:33:43 +02:00
8868684125 New translations builder.json (Danish) 2023-07-20 23:33:39 +02:00
c1ceb0cd50 New translations builder.json (Czech) 2023-07-20 23:33:38 +02:00
1105f672a5 New translations builder.json (Catalan) 2023-07-20 23:33:37 +02:00
67cc49c068 New translations builder.json (Bulgarian) 2023-07-20 23:33:36 +02:00
505406508b New translations builder.json (Arabic) 2023-07-20 23:33:35 +02:00
bfd37951df New translations builder.json (Spanish) 2023-07-20 23:33:34 +02:00
339cae05f1 New translations builder.json (French) 2023-07-20 23:33:34 +02:00
48069c10a4 New translations builder.json (Romanian) 2023-07-20 23:33:33 +02:00
51317b2901 New translations builder.json (Japanese) 2023-07-20 22:32:27 +02:00
e5ce53b2aa Merge pull request #1399 from thomasmazon/thomas-improve-pt-br-translation
Thomas: fixing some Portuguese Brazilian labels
2023-07-20 22:32:00 +02:00
2bc7c93174 Thomas: fixing some Portuguese Brazilian labels 2023-07-20 11:56:54 -03:00
1d97f01942 Jubair70:fix/date-selection 2023-07-19 17:58:13 +06:00
5ad517f1d3 Jubair70:fix/date-selection 2023-07-19 17:53:23 +06:00
8088c70038 New translations builder.json (Chinese Simplified) 2023-07-19 13:52:09 +02:00
e36df82ba9 Merge pull request #1393 from AmruthPillai/dependabot/docker/server/playwright-v1.36.1-focal
Bump playwright from v1.35.1-focal to v1.36.1-focal in /server
2023-07-19 08:45:18 +02:00
de513a12da Merge pull request #1392 from AmruthPillai/dependabot/github_actions/docker/setup-buildx-action-2.9.1
Bump docker/setup-buildx-action from 2.9.0 to 2.9.1
2023-07-19 08:45:11 +02:00
06f1a813ce Merge pull request #1387 from AmruthPillai/i18n_main
New Crowdin updates
2023-07-19 08:45:01 +02:00
1de9195f20 Merge branch 'main' into i18n_main 2023-07-19 08:44:54 +02:00
eaa21ead3e Update build-deploy.yml 2023-07-19 08:44:47 +02:00
3ea4a9b000 Bump playwright from v1.35.1-focal to v1.36.1-focal in /server
Bumps playwright from v1.35.1-focal to v1.36.1-focal.

---
updated-dependencies:
- dependency-name: playwright
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-17 02:36:05 +00:00
f0484c1c28 Bump docker/setup-buildx-action from 2.9.0 to 2.9.1
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.9.0 to 2.9.1.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.9.0...v2.9.1)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-17 02:11:08 +00:00
7ebda09a5f New translations builder.json (German) 2023-07-14 22:35:05 +02:00
161ca0ee28 run prettier formatting on all files 2023-07-13 08:54:37 +02:00
92 changed files with 1495 additions and 2383 deletions

2
.github/FUNDING.yml vendored
View File

@ -1,2 +1,2 @@
github: AmruthPillai
custom: https://paypal.me/RajaRajanA
custom: https://paypal.me/amruthde

View File

@ -1,9 +1,9 @@
name: 🐞 Bug Report
description: Create a bug report to help improve Reactive Resume.
title: "[Bug] <title>"
title: '[Bug] <title>'
labels: [Bug, Needs Triage]
assignees: "AmruthPillai"
assignees: 'AmruthPillai'
body:
- type: checkboxes

View File

@ -1,9 +1,9 @@
name: ✨ Feature Request
description: Suggest an feature or idea that you would like to see in Reactive Resume.
title: "[Feature] <title>"
title: '[Feature] <title>'
labels: [Feature, Needs Triage]
assignees: "AmruthPillai"
assignees: 'AmruthPillai'
body:
- type: checkboxes

View File

@ -38,7 +38,7 @@ jobs:
uses: docker/setup-qemu-action@v2.2.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.9.0
uses: docker/setup-buildx-action@v2.9.1
- name: Login to Docker Hub
uses: docker/login-action@v2.2.0
@ -91,14 +91,14 @@ jobs:
with:
images: amruthpillai/reactive-resume
tags: |
type=raw,value=${{ matrix.image }}-latest
type=raw,value=${{ matrix.image }}-${{ steps.version.outputs.current-version }}
type=raw,value=${{ matrix.image }}-arm64-latest
type=raw,value=${{ matrix.image }}-arm64-${{ steps.version.outputs.current-version }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.2.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.9.0
uses: docker/setup-buildx-action@v2.9.1
- name: Login to Docker Hub
uses: docker/login-action@v2.2.0

View File

@ -30,7 +30,7 @@ ports:
onOpen: ignore
visibility: private
# Client
# Server
- port: 3100
onOpen: ignore
visibility: public

View File

@ -30,7 +30,7 @@ const Export = () => {
};
const handleExportJSON = async () => {
const { nanoid } = await import('nanoid');
const nanoid = (await import('nanoid')).nanoid;
const download = (await import('downloadjs')).default;
const redactedResume = pick(resume, ['basics', 'sections', 'metadata', 'public']);

View File

@ -0,0 +1,50 @@
import Tilt from 'react-parallax-tilt';
import { defaultTiltProps } from '@/constants/tilt';
import HomeActions from '../Actions';
import HeroBackground from '../Background';
import HeroPattern from '../Pattern';
const HeroSection = () => (
<section className="relative">
<HeroPattern />
<HeroBackground />
<div className="mx-auto max-w-7xl px-6 pb-24 pt-10 sm:pb-32 lg:flex lg:px-8 lg:py-52">
<div className="mx-auto max-w-2xl shrink-0 lg:mx-0 lg:max-w-xl lg:pt-12">
<div className="mt-10 space-y-2">
<h6 className="text-base font-bold tracking-wide">Finally,</h6>
<h1 className="text-4xl font-bold !leading-[1.15] tracking-tight sm:text-6xl">
A free and open-source resume builder
</h1>
</div>
<p className="prose prose-base prose-zinc mt-6 text-lg leading-8 dark:prose-invert">
Reactive Resume is a free and open-source resume builder that simplifies the tasks of creating, updating, and
sharing your resume.
</p>
<div className="mt-12 space-x-4">
<HomeActions />
</div>
</div>
<div className="mx-auto mt-16 flex max-w-2xl sm:mt-24 lg:ml-10 lg:mr-0 lg:mt-0 lg:max-w-none lg:flex-none xl:ml-32">
<div className="max-w-3xl flex-none sm:max-w-5xl lg:max-w-none">
<Tilt {...defaultTiltProps}>
<img
width={2432}
height={1442}
src="/images/screenshots/builder.png"
alt="Reactive Resume Screenshot - Builder Screen"
className="w-[76rem] rounded-lg bg-zinc-50/5 shadow-2xl ring-1 ring-zinc-950/10 dark:bg-zinc-950/5 dark:ring-zinc-50/10"
/>
</Tilt>
</div>
</div>
</div>
</section>
);
export default HeroSection;

View File

@ -1,5 +1,5 @@
.content {
@apply rounded px-6 text-sm shadow lg:w-1/2 xl:w-2/5;
@apply rounded px-6 text-sm shadow lg:w-2/3 xl:w-1/2;
@apply absolute inset-4 sm:inset-x-4 sm:inset-y-auto lg:inset-auto;
@apply overflow-scroll bg-zinc-100 dark:bg-zinc-900 lg:overflow-auto;
@apply max-h-[90vh] min-h-fit;

View File

@ -8,7 +8,7 @@ export const Copyright = ({ className }: Props) => (
<div
className={clsx('prose prose-sm prose-zinc flex flex-col gap-y-1 text-xs opacity-40 dark:prose-invert', className)}
>
<span className="font-medium">v4.0.0</span>
<span className="font-medium">v{process.env.appVersion}</span>
<span>
Licensed under <a href="https://github.com/AmruthPillai/Reactive-Resume/blob/main/LICENSE">MIT</a>
</span>

View File

@ -15,9 +15,9 @@ const Markdown: React.FC<Props> = ({ className, children }) => {
return (
<ReactMarkdown
rehypePlugins={[rehypeKatex]}
className={clsx('markdown', className)}
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeKatex]}
>
{children}
</ReactMarkdown>

View File

@ -2,6 +2,7 @@ import { TextField } from '@mui/material';
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import dayjs from 'dayjs';
import get from 'lodash/get';
import isEmpty from 'lodash/isEmpty';
import { useEffect, useState } from 'react';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
@ -57,8 +58,9 @@ const ResumeInput: React.FC<Props> = ({ type = 'text', label, path, className, m
<DatePicker
openTo="year"
label={label}
value={dayjs(value)}
slots={{ textField: (params) => <TextField {...params} error={false} className={className} /> }}
className={className}
views={['year', 'month', 'day']}
value={isEmpty(value) ? null : dayjs(value)}
onChange={(date: dayjs.Dayjs | null) => {
if (!date) return onChangeValue('');
if (dayjs(date).isValid()) return onChangeValue(dayjs(date).format('YYYY-MM-DD'));

View File

@ -13,7 +13,7 @@ const ThemeSwitch = styled(Switch)(({ theme }) => ({
transform: 'translateX(22px)',
'& .MuiSwitch-thumb:before': {
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent(
'#fff'
'#fff',
)}" d="M4.2 2.5l-.7 1.8-1.8.7 1.8.7.7 1.8.6-1.8L6.7 5l-1.9-.7-.6-1.8zm15 8.3a6.7 6.7 0 11-6.6-6.6 5.8 5.8 0 006.6 6.6z"/></svg>')`,
},
'& + .MuiSwitch-track': {
@ -36,7 +36,7 @@ const ThemeSwitch = styled(Switch)(({ theme }) => ({
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent(
'#fff'
'#fff',
)}" d="M9.305 1.667V3.75h1.389V1.667h-1.39zm-4.707 1.95l-.982.982L5.09 6.072l.982-.982-1.473-1.473zm10.802 0L13.927 5.09l.982.982 1.473-1.473-.982-.982zM10 5.139a4.872 4.872 0 00-4.862 4.86A4.872 4.872 0 0010 14.862 4.872 4.872 0 0014.86 10 4.872 4.872 0 0010 5.139zm0 1.389A3.462 3.462 0 0113.471 10a3.462 3.462 0 01-3.473 3.472A3.462 3.462 0 016.527 10 3.462 3.462 0 0110 6.528zM1.665 9.305v1.39h2.083v-1.39H1.666zm14.583 0v1.39h2.084v-1.39h-2.084zM5.09 13.928L3.616 15.4l.982.982 1.473-1.473-.982-.982zm9.82 0l-.982.982 1.473 1.473.982-.982-1.473-1.473zM9.305 16.25v2.083h1.389V16.25h-1.39z"/></svg>')`,
},
},

View File

@ -54,5 +54,5 @@ export const languageMap: Record<string, Language> = languages.reduce(
...acc,
[lang.code]: lang,
}),
{}
{},
);

View File

@ -24,6 +24,13 @@ const theme: ThemeOptions = {
variant: 'outlined',
},
},
MuiInputBase: {
styleOverrides: {
input: {
boxShadow: 'none !important',
},
},
},
MuiAppBar: {
styleOverrides: {
root: {

View File

@ -10,7 +10,7 @@ export const FILENAME_TIMESTAMP = 'DDMMYYYYHHmmss';
// Links
export const DOCS_URL = 'https://docs.rxresu.me';
export const DONATION_URL = 'https://paypal.me/RajaRajanA';
export const DONATION_URL = 'https://paypal.me/amruthde';
export const TRANSLATE_URL = 'https://translate.rxresu.me/';
export const DIGITALOCEAN_URL = 'https://pillai.xyz/digitalocean';
export const REDDIT_URL = 'https://www.reddit.com/r/reactiveresume/';

View File

@ -121,7 +121,7 @@ const LoginModal: React.FC = () => {
>
<p>{t('modals.auth.login.body')}</p>
<form className="grid gap-4 xl:w-2/3">
<form className="grid gap-4">
<Controller
name="identifier"
control={control}

View File

@ -172,14 +172,11 @@ const EducationModal: React.FC = () => {
label={t('builder.common.form.start-date.label')}
value={dayjs(field.value)}
views={['year', 'month', 'day']}
slots={{
textField: (params) => (
<TextField
{...params}
error={!!fieldState.error}
helperText={fieldState.error?.message || params.inputProps?.placeholder}
/>
),
slotProps={{
textField: {
error: !!fieldState.error,
helperText: fieldState.error?.message || t('builder.common.form.start-date.help-text')
},
}}
onChange={(date: dayjs.Dayjs | null) => {
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
@ -198,14 +195,11 @@ const EducationModal: React.FC = () => {
label={t('builder.common.form.end-date.label')}
value={dayjs(field.value)}
views={['year', 'month', 'day']}
slots={{
textField: (params) => (
<TextField
{...params}
error={!!fieldState.error}
helperText={fieldState.error?.message || t('builder.common.form.end-date.help-text')}
/>
),
slotProps={{
textField: {
error: !!fieldState.error,
helperText: fieldState.error?.message || t('builder.common.form.end-date.help-text')
},
}}
onChange={(date: dayjs.Dayjs | null) => {
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));

View File

@ -16,16 +16,16 @@
"@hello-pangea/dnd": "^16.3.0",
"@hookform/resolvers": "3.1.1",
"@monaco-editor/react": "^4.5.1",
"@mui/icons-material": "^5.14.0",
"@mui/lab": "^5.0.0-alpha.136",
"@mui/material": "^5.14.0",
"@mui/system": "^5.14.0",
"@mui/x-date-pickers": "6.9.2",
"@mui/icons-material": "^5.14.1",
"@mui/lab": "^5.0.0-alpha.137",
"@mui/material": "^5.14.2",
"@mui/system": "^5.14.1",
"@mui/x-date-pickers": "6.10.2",
"@radix-ui/react-separator": "^1.0.3",
"@react-oauth/google": "^0.11.0",
"@react-oauth/google": "^0.11.1",
"@reduxjs/toolkit": "^1.9.5",
"axios": "^1.4.0",
"clsx": "^1.2.1",
"clsx": "^2.0.0",
"dayjs": "^1.11.9",
"downloadjs": "^1.4.7",
"joi": "^17.9.2",
@ -33,7 +33,7 @@
"md5-hex": "^4.0.0",
"monaco-editor": "^0.40.0",
"nanoid": "^3.3.6",
"next": "13.4.9",
"next": "13.4.12",
"next-i18next": "^14.0.0",
"react": "^18.2.0",
"react-colorful": "^5.6.1",
@ -41,49 +41,49 @@
"react-dnd-html5-backend": "16.0.1",
"react-dom": "^18.2.0",
"react-github-btn": "^1.4.0",
"react-hook-form": "^7.45.1",
"react-hook-form": "^7.45.2",
"react-hot-toast": "2.4.1",
"react-icons": "^4.10.1",
"react-markdown": "^8.0.7",
"react-parallax-tilt": "^1.7.152",
"react-parallax-tilt": "^1.7.154",
"react-query": "^3.39.3",
"react-redux": "^8.1.1",
"react-zoom-pan-pinch": "^3.1.0",
"redux": "^4.2.1",
"redux-persist": "^6.0.0",
"redux-saga": "^1.2.3",
"redux-undo": "^1.0.1",
"redux-undo": "^1.1.0",
"rehype-katex": "^6.0.3",
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
"sharp": "^0.32.2",
"tailwind-merge": "^1.13.2",
"sharp": "^0.32.4",
"tailwind-merge": "^1.14.0",
"uuid": "^9.0.0",
"webfontloader": "^1.6.28"
},
"devDependencies": {
"@babel/core": "^7.22.8",
"@babel/core": "^7.22.9",
"@fontsource/ibm-plex-sans": "^5.0.5",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/forms": "^0.5.4",
"@tailwindcss/typography": "^0.5.9",
"@types/downloadjs": "^1.4.3",
"@types/lodash": "^4.14.195",
"@types/node": "^20.4.1",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@types/lodash": "^4.14.196",
"@types/node": "^20.4.5",
"@types/react": "^18.2.17",
"@types/react-dom": "^18.2.7",
"@types/react-redux": "^7.1.25",
"@types/uuid": "^9.0.2",
"@types/webfontloader": "^1.6.35",
"autoprefixer": "^10.4.14",
"csstype": "^3.1.2",
"eslint-config-next": "^13.4.9",
"eslint-config-next": "^13.4.12",
"eslint-plugin-tailwindcss": "^3.13.0",
"eslint-plugin-unused-imports": "^3.0.0",
"next-sitemap": "^4.1.8",
"postcss": "^8.4.25",
"sass": "^1.63.6",
"postcss": "^8.4.27",
"sass": "^1.64.1",
"schema": "workspace:*",
"tailwindcss": "^3.3.2",
"tailwindcss": "^3.3.3",
"typescript": "^5.1.6"
}
}

View File

@ -1,14 +1,11 @@
import type { GetStaticProps, NextPage } from 'next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import Tilt from 'react-parallax-tilt';
import HeroBackground from '@/components/home/Background';
import Footer from '@/components/home/Footer';
import Header from '@/components/home/Header';
import HeroPattern from '@/components/home/Pattern';
import HeroSection from '@/components/home/sections/Hero';
import LogoSection from '@/components/home/sections/Logo';
import StatsSection from '@/components/home/sections/Stats';
import { defaultTiltProps } from '@/constants/tilt';
export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => ({
props: {
@ -21,43 +18,13 @@ const Home: NextPage = () => (
<Header />
<main className="relative isolate mb-[450px] overflow-hidden bg-zinc-50 dark:bg-zinc-950">
<section className="relative">
<HeroPattern />
<HeroBackground />
<div className="mx-auto max-w-7xl px-6 pb-24 pt-10 sm:pb-32 lg:flex lg:px-8 lg:py-52">
<div className="mx-auto max-w-2xl shrink-0 lg:mx-0 lg:max-w-xl lg:pt-12">
<div className="mt-10 space-y-2">
<h6 className="text-base font-bold tracking-wide">Finally,</h6>
<h1 className="text-4xl font-bold !leading-[1.15] tracking-tight sm:text-6xl">
A free and open-source resume builder
</h1>
</div>
<p className="prose prose-base prose-zinc mt-6 text-lg leading-8 dark:prose-invert">
Reactive Resume is a free and open-source resume builder that simplifies the tasks of creating, updating,
and sharing your resume.
</p>
</div>
<div className="mx-auto mt-16 flex max-w-2xl sm:mt-24 lg:ml-10 lg:mr-0 lg:mt-0 lg:max-w-none lg:flex-none xl:ml-32">
<div className="max-w-3xl flex-none sm:max-w-5xl lg:max-w-none">
<Tilt {...defaultTiltProps}>
<img
width={2432}
height={1442}
src="/images/screenshots/builder.png"
alt="Reactive Resume Screenshot - Builder Screen"
className="w-[76rem] rounded-lg bg-zinc-50/5 shadow-2xl ring-1 ring-zinc-950/10 dark:bg-zinc-950/5 dark:ring-zinc-50/10"
/>
</Tilt>
</div>
</div>
</div>
</section>
{/* Hero */}
<HeroSection />
{/* Logo Cloud */}
<LogoSection />
{/* Statistics */}
<StatsSection />
</main>

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "የድርጅት ስም"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "اسم الشركة"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "Име на фирмата"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "Nom de la companyia"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "Jméno společnosti"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "firmanavn"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "Firmenname"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "Όνομα εταιρείας"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "nombre de empresa"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "نام شرکت"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "Yrityksen nimi"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "Nom de l'entreprise"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "שם החברה"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "कंपनी का नाम"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "Cégnév"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "Nama perusahaan"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "Nome della ditta"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "会社名"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "ឈ្មោះ​ក្រុម​ហ៊ុន"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "ಸಂಸ್ಥೆಯ ಹೆಸರು"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "회사 이름"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "കമ്പനി പേര്"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "कंपनीचे नाव"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "कम्पनीको नाम"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "କମ୍ପାନି ନାଁ"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "Nazwa firmy"
}
}
},

View File

@ -3,12 +3,12 @@
"actions": {
"add": "Adicionar {{token}}",
"delete": "Remover {{token}}",
"edit": "Editar {{token}}",
"duplicate": "Duplicar sessão"
"duplicate": "Duplicar sessão",
"edit": "Editar {{token}}"
},
"columns": {
"heading": "Colunas",
"tooltip": "Alterar o número de colunas"
"tooltip": "Alterar número de colunas"
},
"form": {
"date": {
@ -21,7 +21,7 @@
"label": "Endereço de e-mail"
},
"end-date": {
"help-text": "Deixe este campo em branco se for até o presente",
"help-text": "Deixe este campo em branco se for emprego atual",
"label": "Data de Término"
},
"keywords": {
@ -49,7 +49,7 @@
"label": "Subtítulo"
},
"summary": {
"label": "Resumo"
"label": "Descritivo"
},
"title": {
"label": "Título"
@ -67,7 +67,7 @@
"duplicate": "Duplicar",
"edit": "Editar"
},
"empty-text": "Essa lista está vazia."
"empty-text": "Lista está vazia."
},
"tooltip": {
"delete-item": "Tem certeza de que deseja excluir este item? Esta ação é irreversível.",
@ -81,13 +81,13 @@
"center-artboard": "Prancheta central",
"copy-link": "Copiar link do currículo",
"export-pdf": "Exportar PDF",
"redo": "Refazer",
"toggle-orientation": "Alternar orientação da página",
"toggle-page-break-line": "Alternar linha de quebra de página",
"toggle-sidebars": "Alternar barra lateral",
"zoom-in": "Mais Zoom",
"zoom-out": "Menos Zoom",
"undo": "Desfazer",
"redo": "Redo"
"zoom-in": "Mais Zoom",
"zoom-out": "Menos Zoom"
}
},
"header": {
@ -109,18 +109,15 @@
"awarder": {
"label": "Concedente"
}
},
"heading": "Títulos",
"heading_one": "Título"
},
"work": {
"heading": "Experiências de Trabalho",
"heading_one": "Experiência de Trabalho"
}
},
"basics": {
"actions": {
"photo-filters": "Filtros da foto"
},
"birthdate": {
"label": "Data de nascimento"
},
"heading": "Informações básicas",
"headline": {
"label": "Título"
@ -128,9 +125,6 @@
"name": {
"label": "Nome Completo"
},
"birthdate": {
"label": "Data de nascimento"
},
"photo-filters": {
"effects": {
"border": {
@ -160,9 +154,7 @@
"issuer": {
"label": "Emissor"
}
},
"heading": "Certificações",
"heading_one": "Certificação"
}
},
"education": {
"form": {
@ -182,13 +174,13 @@
"label": "Instituição"
}
},
"heading": "Formação Acadêmica",
"heading_one": "Formação Acadêmica"
"heading": "Escolaridade",
"heading_one": "Escolaridade"
},
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "Nome da Empresa"
}
}
},
@ -204,10 +196,10 @@
},
"heading": "Localização",
"postal-code": {
"label": "Código Postal"
"label": "CEP"
},
"region": {
"label": "Região"
"label": "Estado"
}
},
"profiles": {
@ -227,34 +219,14 @@
"publisher": {
"label": "Editor"
}
},
"heading": "Publicações",
"heading_one": "Publicação"
}
},
"references": {
"form": {
"relationship": {
"label": "Relação"
}
},
"heading": "Referências",
"heading_one": "Referência"
},
"skills": {
"heading": "Habilidades",
"heading_one": "Habilidade"
},
"languages": {
"heading": "Idiomas",
"heading_one": "Idioma"
},
"interests": {
"heading": "Interesses",
"heading_one": "Interesse"
},
"projects": {
"heading": "Projetos",
"heading_one": "Projeto"
}
},
"section": {
"heading": "Seção"
@ -264,9 +236,7 @@
"organization": {
"label": "Organização"
}
},
"heading": "Trabalhos Voluntários",
"heading_one": "Trabalho Voluntário"
}
}
}
},
@ -293,7 +263,7 @@
}
},
"layout": {
"heading": "Layout",
"heading": "Disposição",
"tooltip": {
"reset-layout": "Redefinir Layout"
}
@ -301,17 +271,18 @@
"links": {
"bugs-features": {
"body": "Alguma coisa te impede de fazer um currículo? Ou você tem uma ideia incrível para adicionar? Crie uma issue no GitHub para começar.",
"button": "GitHub Issues",
"button": "Problemas do GitHub",
"heading": "Bugs? Sugestões de recursos?"
},
"docs": "Documentação",
"donate": {
"body": "Se você gostou de usar o Reactive Resume, considere doar o máximo possível para manter o aplicativo em funcionamento, sem anúncios e gratuito para sempre.",
"button": "Pague-me um café",
"heading": "Faça uma doação ao Reactive Resume"
},
"github": "Código Fonte",
"docs": "Documentação",
"heading": "Links"
"heading": "links",
"reddit": "Reddit"
},
"settings": {
"global": {
@ -330,14 +301,14 @@
},
"heading": "Configurações",
"page": {
"break-line": {
"primary": "Quebra de Linha",
"secondary": "Mostrar uma linha em todas as páginas para marcar a altura de uma página A4"
},
"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"
},
"heading": "Página",
"orientation": {
"disabled": "Não tem efeito quando há somente uma página",

View File

@ -16,7 +16,7 @@
"present": "Presente"
},
"subtitle": "Gerador de currículos gratuito e de código aberto.",
"title": "Reactive Resume",
"title": "Currículo reativo",
"toast": {
"error": {
"upload-file-size": "Carregue apenas arquivos com menos de 2 megabytes.",

View File

@ -18,25 +18,26 @@
}
},
"links": {
"heading": "Links",
"heading": "links",
"links": {
"docs": "Documentação",
"donate": "Fazer doação",
"github": "Código Fonte",
"docs": "Documentação",
"privacy": "Política de privacidade",
"reddit": "Reddit",
"service": "Termos de serviço"
}
},
"screenshots": {
"heading": "Imagens"
},
"testimonials": {
"heading": "Depoimentos",
"body": "Considero importante a sua opinião, positiva ou negativa, a respeito do Reactive Resume, bem como a sua experiência ao usá-lo.<br/>Confira algumas das mensagens enviadas por pessoas de todo o mundo.",
"contact": "Envie a sua mensagem por <1>e-mail</1> ou pelo formulário disponível <3>aqui</3>."
},
"summary": {
"body": "O Reactive Resume é um gerador de currículos, gratuito e de código aberto, desenvolvido para facilitar as tarefas tediosas de criação, atualização e divulgação de seu currículo. Este aplicativo possibilita a criação de múltiplos currículos, que podem ser compartilhados com recrutadores ou amigos com um link exclusivo ou impressos como PDF. Tudo isso de graça, sem anúncios, sem rastreamento, mantendo a integridade e privacidade dos seus dados.",
"heading": "Resumo"
},
"testimonials": {
"body": "Considero importante a sua opinião, positiva ou negativa, a respeito do Reactive Resume, bem como a sua experiência ao usá-lo.<br/>Confira algumas das mensagens enviadas por pessoas de todo o mundo.",
"contact": "Envie a sua mensagem por <1>e-mail</1> ou pelo formulário disponível <3>aqui</3>.",
"heading": "Depoimentos"
}
}

View File

@ -71,6 +71,31 @@
}
},
"heading": "Redefinir sua senha"
},
"profile": {
"heading": "Sua conta",
"form": {
"avatar": {
"help-text": "Você pode atualizar sua foto de perfil no <1>Gravatar</1>"
},
"name": {
"label": "Nome completo"
},
"email": {
"label": "Endereço de email",
"help-text": "Não é possível atualizar seu endereço de e-mail no momento, crie uma nova conta."
}
},
"delete-account": {
"heading": "Excluir conta e dados",
"body": "Para excluir sua conta, seus dados e todos os seus currículos, digite \"{{keyword}}\" na caixa de texto e clique no botão. Observe que esta é uma ação irreversível e seus dados não podem ser recuperados novamente.",
"actions": {
"delete": "Deletar conta"
}
},
"actions": {
"save": "Salvar alterações"
}
}
},
"dashboard": {

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "nome da empresa"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "Numele companiei"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "Название компании"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "Име компаније"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "நிறுவனத்தின் பெயர்"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "Firma Adı"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "Назва компанії"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "Tên công ty"
}
}
},

View File

@ -178,7 +178,7 @@
"experience": {
"form": {
"name": {
"label": "Company Name"
"label": "公司名称"
}
}
},

View File

@ -83,7 +83,7 @@ export const resetPassword = async (resetPasswordParams: ResetPasswordParams) =>
export const updateProfile = async (updateProfileParams: UpdateProfileParams) => {
const { data: user } = await axios.patch<User, AxiosResponse<User>, UpdateProfileParams>(
'/auth/update-profile',
updateProfileParams
updateProfileParams,
);
store.dispatch(setUser(user));

View File

@ -46,7 +46,7 @@ axios.interceptors.response.use(
}
throw error;
}
},
);
export default axios;

View File

@ -9,6 +9,6 @@ export type PrintResumeAsPdfParams = {
export const printResumeAsPdf = (printResumeAsPdfParams: PrintResumeAsPdfParams): Promise<string> =>
axios
.get(
`/printer/${printResumeAsPdfParams.username}/${printResumeAsPdfParams.slug}?lastUpdated=${printResumeAsPdfParams.lastUpdated}`
`/printer/${printResumeAsPdfParams.username}/${printResumeAsPdfParams.slug}?lastUpdated=${printResumeAsPdfParams.lastUpdated}`,
)
.then((res) => res.data);

View File

@ -20,7 +20,7 @@ const DEBOUNCE_WAIT = 1000;
const debouncedSync = debounce(
(resume: Resume, dispatch: AppDispatch) => updateResume(resume).then((resume) => dispatch(setResume(resume))),
DEBOUNCE_WAIT
DEBOUNCE_WAIT,
);
function* handleSync(dispatch: AppDispatch) {
@ -31,7 +31,7 @@ function* handleSync(dispatch: AppDispatch) {
function* syncSaga(dispatch: AppDispatch) {
yield takeLatest([setResumeState, addItem, editItem, duplicateItem, deleteItem, addSection, deleteSection], () =>
handleSync(dispatch)
handleSync(dispatch),
);
}

View File

@ -17,7 +17,7 @@ import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
export const MastheadSidebar: React.FC = () => {
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
const { name, headline, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
(state) => state.resume.present.basics
(state) => state.resume.present.basics,
);
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {} as ThemeConfig));
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);

View File

@ -18,7 +18,7 @@ import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
export const MastheadSidebar: React.FC = () => {
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
const { name, headline, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
(state) => state.resume.present.basics
(state) => state.resume.present.basics,
);
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {} as ThemeConfig));
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);

View File

@ -13,7 +13,7 @@ export const MastheadSidebar: React.FC = () => {
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
const primaryColor: string = useAppSelector((state) => get(state.resume.present, 'metadata.theme.primary'));
const { name, headline, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
(state) => state.resume.present.basics
(state) => state.resume.present.basics,
);
return (

View File

@ -12,7 +12,7 @@ import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
const Masthead = () => {
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
const { name, photo, email, phone, website, birthdate, headline, location, profiles } = useAppSelector(
(state) => state.resume.present.basics
(state) => state.resume.present.basics,
);
return (

View File

@ -1,5 +1,5 @@
.container {
@apply grid grid-cols-2 gap-4 px-6 py-4 items-start;
@apply grid grid-cols-2 gap-4 mx-6 my-4 items-start;
.main {
@apply grid gap-4;

View File

@ -1,5 +1,5 @@
import { ThemeConfig } from 'schema';
import get from 'lodash/get';
import { ThemeConfig } from 'schema';
import { useAppSelector } from '@/store/hooks';
@ -7,12 +7,12 @@ const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {} as ThemeConfig));
return (
<h2
className="mb-2 pb-1 font-bold uppercase opacity-75"
style={{ borderBottomWidth: '3px', borderColor: theme.primary, color: theme.primary, display: 'inline-block' }}
<h3
className="mb-2 inline-block border-b-2 pb-1 font-bold uppercase opacity-75"
style={{ borderColor: theme.primary, color: theme.primary, display: 'inline-block' }}
>
{children}
</h2>
</h3>
);
};

View File

@ -1,8 +1,8 @@
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
import { alpha } from '@mui/material';
import { ThemeConfig } from 'schema';
import get from 'lodash/get';
import isEmpty from 'lodash/isEmpty';
import { ThemeConfig } from 'schema';
import Markdown from '@/components/shared/Markdown';
import { useAppSelector } from '@/store/hooks';
@ -14,7 +14,7 @@ import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
const Masthead: React.FC = () => {
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
const { name, photo, headline, summary, email, phone, birthdate, website, location, profiles } = useAppSelector(
(state) => state.resume.present.basics
(state) => state.resume.present.basics,
);
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {} as ThemeConfig));

View File

@ -1,9 +1,9 @@
import { Email, Link, Phone } from '@mui/icons-material';
import { ListItem, Section as SectionType } from 'schema';
import get from 'lodash/get';
import isArray from 'lodash/isArray';
import isEmpty from 'lodash/isEmpty';
import { useMemo } from 'react';
import { ListItem, Section as SectionType } from 'schema';
import Markdown from '@/components/shared/Markdown';
import { useAppSelector } from '@/store/hooks';

View File

@ -11,7 +11,7 @@ import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
const Masthead: React.FC = () => {
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
const { name, photo, email, phone, website, birthdate, headline, location, profiles } = useAppSelector(
(state) => state.resume.present.basics
(state) => state.resume.present.basics,
);
return (

View File

@ -1,5 +1,5 @@
import { ThemeConfig } from 'schema';
import get from 'lodash/get';
import { ThemeConfig } from 'schema';
import { useAppSelector } from '@/store/hooks';

View File

@ -1,9 +1,9 @@
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
import { ThemeConfig } from 'schema';
import clsx from 'clsx';
import get from 'lodash/get';
import isEmpty from 'lodash/isEmpty';
import { useMemo } from 'react';
import { ThemeConfig } from 'schema';
import Markdown from '@/components/shared/Markdown';
import { useAppSelector } from '@/store/hooks';
@ -16,7 +16,7 @@ import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
export const MastheadSidebar: React.FC = () => {
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
const { name, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
(state) => state.resume.present.basics
(state) => state.resume.present.basics,
);
return (

View File

@ -44,11 +44,11 @@ const sectionMap = (Section: React.FC<SectionProps>): Record<string, JSX.Element
references: <Section key="references" path="sections.references" titlePath="name" subtitlePath="relationship" />,
});
export const getSectionById = (id: string, Section: React.FC<SectionProps>): JSX.Element => {
// Check if section id is a custom section (an uuid)
if (validate(id)) {
return <Section key={id} path={`sections.${id}`} />;
}
export const getSectionById = (id: string, Section: React.FC<SectionProps>): JSX.Element | null => {
if (!id) return null;
// Check if section id is a custom section (is a valid uuid)
if (validate(id)) return <Section key={id} path={`sections.${id}`} />;
// Check if section id is a predefined seciton in config
const predefinedSection = get(sectionMap(Section), id);
@ -57,9 +57,11 @@ export const getSectionById = (id: string, Section: React.FC<SectionProps>): JSX
return predefinedSection;
}
// Other ways section should be a cloned section
const section = find(sectionMap(Section), (element, key) => id.includes(key));
return React.cloneElement(section!, { path: `sections.${id}` });
// Otherwise, section must be a cloned section
const section = find(sectionMap(Section), (_element, key) => id.includes(key));
if (section) return React.cloneElement(section, { path: `sections.${id}` });
return null;
};
export default sectionMap;

View File

@ -41,12 +41,12 @@ export const formatDateString = (date: string | DateRange, formatStr: string): s
if (!dayjs(date.start).isValid()) return null;
if (dayjs(date.start).isSame(date.end)) {
return dayjs.utc(date.start).local().format(formatStr);
return dayjs.utc(date.start).format(formatStr);
}
if (!isEmpty(date.end) && dayjs(date.end).isValid()) {
return `${dayjs.utc(date.start).local().format(formatStr)} - ${dayjs.utc(date.end).local().format(formatStr)}`;
return `${dayjs.utc(date.start).format(formatStr)} - ${dayjs.utc(date.end).format(formatStr)}`;
}
return `${dayjs.utc(date.start).local().format(formatStr)} - ${presentString}`;
return `${dayjs.utc(date.start).format(formatStr)} - ${presentString}`;
};

View File

@ -1,8 +1,8 @@
import { ListItem, Location, PhotoFilters } from 'schema';
import clsx from 'clsx';
import get from 'lodash/get';
import isArray from 'lodash/isArray';
import isEmpty from 'lodash/isEmpty';
import { ListItem, Location, PhotoFilters } from 'schema';
export type Required<T, K extends keyof T> = T & { [P in K]-?: T[P] };
@ -55,7 +55,7 @@ export const parseListItemPath = (item: ListItem, path: string | string[], separ
export const getPhotoClassNames = (filters: PhotoFilters) =>
clsx({
'object-cover': true,
'object-cover aspect-square': true,
grayscale: filters.grayscale,
'!border-[4px] !border-solid': filters.border,
'rounded-lg': filters.shape === 'rounded-square',

View File

@ -11,7 +11,7 @@ const FontWrapper: React.FC<React.PropsWithChildren<unknown>> = ({ children }) =
const WebFont = (await import('webfontloader')).default;
const families = Object.values(typography.family).reduce(
(acc, family) => [...acc, `${family}:400,600,700`],
[] as string[]
[] as string[],
);
WebFont.load({ google: { families } });

View File

@ -1,13 +1,13 @@
{
"name": "reactive-resume",
"version": "3.8.1",
"version": "3.8.3",
"private": true,
"scripts": {
"dev": "env-cmd --silent cross-var cross-env VERSION=$npm_package_version turbo run dev",
"build": "env-cmd --silent cross-var cross-env VERSION=$npm_package_version turbo run build",
"start": "env-cmd --silent cross-var cross-env VERSION=$npm_package_version turbo run start",
"format": "prettier --write --loglevel silent --cache .",
"dev": "dotenv -- turbo run dev",
"build": "dotenv -- turbo run build",
"start": "dotenv -- turbo run start",
"lint": "turbo run lint",
"format": "prettier --write --loglevel silent --cache .",
"update-dependencies": "ncu -x nanoid --deep -u && pnpm install",
"generate-env": "ts-node ./scripts/generate-env.ts"
},
@ -17,23 +17,20 @@
"server"
],
"dependencies": {
"cross-env": "^7.0.3",
"cross-var": "^1.1.0",
"env-cmd": "^10.1.0",
"uuid": "^9.0.0"
"dotenv-cli": "^7.2.1"
},
"devDependencies": {
"@types/node": "^20.4.1",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"eslint": "^8.44.0",
"@types/node": "^20.4.5",
"@typescript-eslint/eslint-plugin": "^6.2.0",
"@typescript-eslint/parser": "^6.2.0",
"eslint": "^8.45.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-unused-imports": "^3.0.0",
"npm-check-updates": "^16.10.15",
"npm-check-updates": "^16.10.17",
"prettier": "^3.0.0",
"ts-node": "^10.9.1",
"turbo": "^1.10.7",
"turbo": "^1.10.12",
"typescript": "^5.1.6"
},
"resolutions": {

3283
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@
"build": "tsc"
},
"devDependencies": {
"eslint": "^8.44.0",
"eslint": "^8.45.0",
"typescript": "^5.1.6"
}
}

View File

@ -26,7 +26,7 @@ ENV TURBO_TOKEN=$TURBO_TOKEN
RUN pnpm exec turbo --filter server build
FROM mcr.microsoft.com/playwright:v1.35.1-focal as production
FROM mcr.microsoft.com/playwright:v1.36.1-focal as production
WORKDIR /app

View File

@ -7,16 +7,16 @@
"start": "node dist/main"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.369.0",
"@aws-sdk/client-s3": "^3.378.0",
"@nestjs/axios": "^3.0.0",
"@nestjs/cache-manager": "^2.0.1",
"@nestjs/common": "^10.0.5",
"@nestjs/cache-manager": "^2.1.0",
"@nestjs/common": "^10.1.2",
"@nestjs/config": "^3.0.0",
"@nestjs/core": "^10.0.5",
"@nestjs/core": "^10.1.2",
"@nestjs/jwt": "^10.1.0",
"@nestjs/mapped-types": "^2.0.2",
"@nestjs/passport": "^10.0.0",
"@nestjs/platform-express": "^10.0.5",
"@nestjs/platform-express": "^10.1.2",
"@nestjs/schedule": "^3.0.1",
"@nestjs/serve-static": "^4.0.0",
"@nestjs/terminus": "^10.0.1",
@ -30,34 +30,34 @@
"cookie-parser": "^1.4.6",
"csvtojson": "^2.0.10",
"dayjs": "^1.11.9",
"google-auth-library": "^8.9.0",
"google-auth-library": "^9.0.0",
"joi": "^17.9.2",
"lodash": "^4.17.21",
"multer": "^1.4.5-lts.1",
"nanoid": "^3.3.6",
"node-stream-zip": "^1.15.0",
"nodemailer": "^6.9.3",
"nodemailer": "^6.9.4",
"passport": "^0.6.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"pdf-lib": "^1.17.1",
"pg": "^8.11.1",
"playwright-chromium": "^1.36.0",
"playwright-chromium": "^1.36.2",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
"typeorm": "0.3.17",
"uuid": "^9.0.0"
},
"devDependencies": {
"@nestjs/cli": "^10.1.8",
"@nestjs/cli": "^10.1.10",
"@nestjs/schematics": "^10.0.1",
"@types/bcryptjs": "^2.4.2",
"@types/cookie-parser": "^1.4.3",
"@types/express": "^4.17.17",
"@types/lodash": "^4.14.195",
"@types/lodash": "^4.14.196",
"@types/multer": "^1.4.7",
"@types/node": "^20.4.1",
"@types/nodemailer": "^6.4.8",
"@types/node": "^20.4.5",
"@types/nodemailer": "^6.4.9",
"@types/passport-jwt": "^3.0.9",
"@types/passport-local": "^1.0.35",
"@types/uuid": "^9.0.2",
@ -70,6 +70,6 @@
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.1.6",
"webpack": "^5.88.1"
"webpack": "^5.88.2"
}
}

View File

@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />

View File

@ -20,7 +20,7 @@ export class AuthService {
private schedulerRegistry: SchedulerRegistry,
private configService: ConfigService,
private usersService: UsersService,
private jwtService: JwtService
private jwtService: JwtService,
) {}
async register(registerDto: RegisterDto) {
@ -49,7 +49,7 @@ export class AuthService {
if (!isPasswordMatching) {
throw new HttpException(
'The username/email and password combination provided was incorrect.',
HttpStatus.UNAUTHORIZED
HttpStatus.UNAUTHORIZED,
);
}
}
@ -64,7 +64,7 @@ export class AuthService {
} catch (error) {
throw new HttpException(
'The username/email and password combination provided was incorrect.',
HttpStatus.UNAUTHORIZED
HttpStatus.UNAUTHORIZED,
);
}
}

View File

@ -8,7 +8,10 @@ import { UsersService } from '@/users/users.service';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(configService: ConfigService, private readonly usersService: UsersService) {
constructor(
configService: ConfigService,
private readonly usersService: UsersService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: configService.get('auth.jwtSecret'),

View File

@ -9,7 +9,10 @@ import cachedResponse from './assets/cachedResponse.json';
@Injectable()
export class FontsService {
constructor(private configService: ConfigService, private httpService: HttpService) {}
constructor(
private configService: ConfigService,
private httpService: HttpService,
) {}
async getAll(): Promise<Font[]> {
const apiKey = this.configService.get('google.apiKey');

View File

@ -6,7 +6,7 @@ export class HealthController {
constructor(
private health: HealthCheckService,
private http: HttpHealthIndicator,
private db: TypeOrmHealthIndicator
private db: TypeOrmHealthIndicator,
) {}
@Get()

View File

@ -10,7 +10,7 @@ export class PrinterController {
async printAsPdf(
@Param('username') username: string,
@Param('slug') slug: string,
@Query('lastUpdated') lastUpdated: string
@Query('lastUpdated') lastUpdated: string,
): Promise<string> {
try {
return await this.printerService.printAsPdf(username, slug, lastUpdated);

View File

@ -49,7 +49,10 @@ const minimal_chromium_args = [
export class PrinterService implements OnModuleInit, OnModuleDestroy {
private browser: BrowserContext;
constructor(private readonly schedulerRegistry: SchedulerRegistry, private readonly configService: ConfigService) {}
constructor(
private readonly schedulerRegistry: SchedulerRegistry,
private readonly configService: ConfigService,
) {}
async onModuleInit() {
this.browser = await chromium.launchPersistentContext('.playwright', {
@ -84,7 +87,7 @@ export class PrinterService implements OnModuleInit, OnModuleDestroy {
this.schedulerRegistry.deleteTimeout(`delete-${file}`);
}
}
})
}),
);
});
@ -99,7 +102,7 @@ export class PrinterService implements OnModuleInit, OnModuleDestroy {
const pageFormat: PageConfig['format'] = await page.$$eval(
'[data-page]',
(pages) => pages[0].getAttribute('data-format') as PageConfig['format']
(pages) => pages[0].getAttribute('data-format') as PageConfig['format'],
);
const resumePages = await page.$$eval('[data-page]', (pages) =>
@ -107,7 +110,7 @@ export class PrinterService implements OnModuleInit, OnModuleDestroy {
pageNumber: index + 1,
innerHTML: page.innerHTML,
height: page.clientHeight,
}))
})),
);
const pdf = await PDFDocument.create();

View File

@ -43,7 +43,7 @@ export class ResumeController {
findOneByShortId(
@Param('shortId') shortId: string,
@User('id') userId?: number,
@Query('secretKey') secretKey?: string
@Query('secretKey') secretKey?: string,
) {
return this.resumeService.findOneByShortId(shortId, userId, secretKey);
}
@ -54,7 +54,7 @@ export class ResumeController {
@Param('username') username: string,
@Param('slug') slug: string,
@User('id') userId?: number,
@Query('secretKey') secretKey?: string
@Query('secretKey') secretKey?: string,
) {
return this.resumeService.findOneByIdentifier(username, slug, userId, secretKey);
}

View File

@ -19,7 +19,7 @@ export class UsersService {
@InjectRepository(User) private userRepository: Repository<User>,
private schedulerRegistry: SchedulerRegistry,
private mailService: MailService,
private dataSource: DataSource
private dataSource: DataSource,
) {}
async findById(id: number): Promise<User> {
@ -115,7 +115,7 @@ export class UsersService {
throw new HttpException(
'Please wait at least 30 minutes before resetting your password again.',
HttpStatus.TOO_MANY_REQUESTS
HttpStatus.TOO_MANY_REQUESTS,
);
} finally {
await queryRunner.release();

View File

@ -5,6 +5,10 @@
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**", "dist/**"]
},
"start": {
"cache": false,
"persistent": true
},
"lint": {},
"format": {
"dependsOn": ["^lint"]