Compare commits

...

48 Commits

Author SHA1 Message Date
b24da90ba7 bump up version to 3.6.18 2023-01-19 07:55:12 +01:00
2aa7dbd3ad Merge pull request #1188 from AmruthPillai/feat/implement-self-serve-account-deletion
[Feature] Implement Self-Serve Account Deletion
2023-01-19 07:53:49 +01:00
9f8f2c4b8b remove console.log 2023-01-19 07:52:59 +01:00
5331ecccc1 i18n-ize the user profile modal 2023-01-19 07:51:43 +01:00
f2ec86940c Merge branch 'main' into feat/implement-self-serve-account-deletion 2023-01-19 07:34:11 +01:00
cd74e707ba update dependencies 2023-01-19 07:33:15 +01:00
ff101dbfac [Feature] Implement Self-Serve Account Deletion 2023-01-19 00:11:15 +01:00
5024c19f87 fix error messages not displaying toasts sometimes, add axios error interceptors 2023-01-18 21:36:05 +01:00
c9850b5815 Merge pull request #1183 from AmruthPillai/i18n_main
New Crowdin updates
2023-01-18 20:37:54 +01:00
6fe4e7d7e1 Merge pull request #1184 from AmruthPillai/dependabot/github_actions/docker/build-push-action-3.3.0
Bump docker/build-push-action from 3.2.0 to 3.3.0
2023-01-16 10:53:29 +01:00
a5b8b91e82 Bump docker/build-push-action from 3.2.0 to 3.3.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3.2.0 to 3.3.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v3.2.0...v3.3.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-16 02:33:23 +00:00
cc7095adc3 New translations landing.json (Spanish) 2023-01-16 01:53:40 +01:00
e2703f55aa New translations builder.json (Spanish) 2023-01-16 01:53:37 +01:00
8c5849c988 Merge pull request #1172 from AmruthPillai/dependabot/github_actions/actions/checkout-3.3.0
Bump actions/checkout from 3.2.0 to 3.3.0
2023-01-09 10:02:35 +01:00
5322ab2420 Merge pull request #1173 from AmruthPillai/dependabot/docker/server/playwright-v1.29.2-focal
Bump playwright from v1.29.1-focal to v1.29.2-focal in /server
2023-01-09 10:02:27 +01:00
b84e6bcfb1 Bump playwright from v1.29.1-focal to v1.29.2-focal in /server
Bumps playwright from v1.29.1-focal to v1.29.2-focal.

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-09 02:06:20 +00:00
a4ab0174c7 Bump actions/checkout from 3.2.0 to 3.3.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.2.0 to 3.3.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3.2.0...v3.3.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-09 02:05:40 +00:00
7e93b5a757 update dependencies 2023-01-04 22:15:13 +01:00
044820fa71 Update README.md 2023-01-04 16:05:30 +01:00
322178e8a4 Merge pull request #1165 from JadinHeaston/main
Fixing unconditional ```http://``` prefix.
2023-01-03 23:22:17 +01:00
6358fbad30 Adding check for ``addHttp()` to see if `mailto:` or `tel:`` is present.Signed-off-by: Jadin Heaston <jadin.heaston@como.gov> 2023-01-03 13:57:07 -06:00
d342c0a9af fix axios type issue 2023-01-03 17:17:35 +01:00
63084eebb4 feat(dependencies): ⬆️ update dependencies, fix date display issue, add more profile icons 2023-01-03 17:06:30 +01:00
3b4ea00db8 Merge pull request #1162 from AmruthPillai/dependabot/gradle/app/org.jetbrains.kotlin.android-1.8.0
Bump org.jetbrains.kotlin.android from 1.7.22 to 1.8.0 in /app
2023-01-03 16:51:17 +01:00
c8f7bffe7e Merge pull request #1160 from coolswood/main
fix: Gengar two cols bug
2023-01-03 16:51:09 +01:00
3ff56f89d9 Merge pull request #1158 from AmruthPillai/i18n_main
New Crowdin updates
2023-01-03 16:50:48 +01:00
7fb9f27837 Bump org.jetbrains.kotlin.android from 1.7.22 to 1.8.0 in /app
Bumps [org.jetbrains.kotlin.android](https://github.com/JetBrains/kotlin) from 1.7.22 to 1.8.0.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.7.22...v1.8.0)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin.android
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-02 02:04:34 +00:00
c9685d4ce7 fix: Gengar two cols bug 2022-12-31 10:01:54 +02:00
4dc987e27d New translations landing.json (Persian) 2022-12-30 19:09:41 +01:00
f7af06ae9a New translations builder.json (Persian) 2022-12-30 18:12:15 +01:00
a5c337faa3 Merge pull request #1151 from AmruthPillai/dependabot/github_actions/digitalocean/action-doctl-2.3.0
Bump digitalocean/action-doctl from 2.2.0 to 2.3.0
2022-12-30 11:48:34 +01:00
fc4704f0a6 Merge pull request #1150 from AmruthPillai/dependabot/docker/server/playwright-v1.29.1-focal
Bump playwright from v1.29.0-focal to v1.29.1-focal in /server
2022-12-30 11:48:25 +01:00
d968334ada Merge pull request #1155 from coolswood/main
fix: overflow-y-auto
2022-12-29 21:37:29 +01:00
fea6d23178 fix: overflow-y-auto 2022-12-29 19:37:47 +02:00
3fefc95572 Bump digitalocean/action-doctl from 2.2.0 to 2.3.0
Bumps [digitalocean/action-doctl](https://github.com/digitalocean/action-doctl) from 2.2.0 to 2.3.0.
- [Release notes](https://github.com/digitalocean/action-doctl/releases)
- [Commits](https://github.com/digitalocean/action-doctl/compare/v2.2.0...v2.3.0)

---
updated-dependencies:
- dependency-name: digitalocean/action-doctl
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-26 02:04:49 +00:00
b07e7d1213 Bump playwright from v1.29.0-focal to v1.29.1-focal in /server
Bumps playwright from v1.29.0-focal to v1.29.1-focal.

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-26 02:04:31 +00:00
d47b8bfb03 Merge pull request #1139 from AmruthPillai/dependabot/github_actions/actions/checkout-3.2.0
Bump actions/checkout from 3.1.0 to 3.2.0
2022-12-19 10:22:44 +01:00
5bf7fbdae1 Merge pull request #1140 from AmruthPillai/dependabot/docker/server/playwright-v1.29.0-focal
Bump playwright from v1.28.1-focal to v1.29.0-focal in /server
2022-12-19 10:22:34 +01:00
fca766b382 Bump playwright from v1.28.1-focal to v1.29.0-focal in /server
Bumps playwright from v1.28.1-focal to v1.29.0-focal.

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-19 02:06:27 +00:00
feadfb1b67 Bump actions/checkout from 3.1.0 to 3.2.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.1.0 to 3.2.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3.1.0...v3.2.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-19 02:06:19 +00:00
e69000f221 Merge pull request #1137 from AmruthPillai/feat/add-remark-gfm-math-plugins
feat(client):  add github flavored syntax and math equations to markdown support
2022-12-17 10:27:19 +01:00
6b4a54465a feat(client): add github flavored syntax and math equations to markdown support 2022-12-17 10:19:25 +01:00
878659999f Merge pull request #1135 from AmruthPillai/i18n_main
New Crowdin updates
2022-12-17 09:53:13 +01:00
1868c47e30 New translations common.json (Dutch) 2022-12-17 09:48:12 +01:00
51442efc23 New translations common.json (German) 2022-12-17 09:48:07 +01:00
556e962ec5 refactor(client): 📝 add link to subreddit 2022-12-16 22:56:23 +01:00
b5ce67f863 Update README.md 2022-12-16 18:06:46 +01:00
c3ce89dc3a Update README.md 2022-12-16 18:04:22 +01:00
63 changed files with 2904 additions and 1132 deletions

View File

@ -13,7 +13,7 @@ jobs:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: Install DigitalOcean CLI
uses: digitalocean/action-doctl@v2.2.0
uses: digitalocean/action-doctl@v2.3.0
with:
token: ${{ secrets.DIGITALOCEAN_TOKEN }}

View File

@ -16,7 +16,7 @@ jobs:
steps:
- name: Checkout the repository
uses: actions/checkout@v3.1.0
uses: actions/checkout@v3.3.0
- id: version
name: App Version
@ -45,7 +45,7 @@ jobs:
password: ${{ secrets.GH_TOKEN }}
- name: Build and Push Docker Image
uses: docker/build-push-action@v3.2.0
uses: docker/build-push-action@v3.3.0
with:
context: .
push: true

View File

@ -6,7 +6,8 @@
[![Project License](https://img.shields.io/github/license/AmruthPillai/Reactive-Resume?style=flat-square)](https://github.com/AmruthPillai/Reactive-Resume/blob/main/LICENSE)
[![Crowdin](https://badges.crowdin.net/reactive-resume/localized.svg)](https://translate.rxresu.me)
[![Docker Pulls](https://img.shields.io/docker/pulls/amruthpillai/reactive-resume?style=flat-square)](https://hub.docker.com/r/amruthpillai/reactive-resume)
![GitHub Workflow Status](https://img.shields.io/github/workflow/status/AmruthPillai/Reactive-Resume/Build%20and%20Push%20Docker%20Image?label=docker%20build&style=flat-square)
![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/AmruthPillai/Reactive-Resume/docker-build-push.yml?branch=main&label=docker%20build&style=flat-square)
[![Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod)](https://gitpod.io/#https://github.com/AmruthPillai/Reactive-Resume)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FAmruthPillai%2FReactive-Resume.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FAmruthPillai%2FReactive-Resume?ref=badge_shield)
## [Go to App](https://rxresu.me) | [Docs](https://docs.rxresu.me)
@ -133,6 +134,10 @@ Reactive Resume would be nothing without the folks who supported me and kept the
### [GitHub Sponsor](https://github.com/sponsors/AmruthPillai)
### [PayPal](https://paypal.me/RajaRajanA)
## GitHub Star History
[![Star History Chart](https://api.star-history.com/svg?repos=AmruthPillai/Reactive-Resume&type=Date)](https://star-history.com/#AmruthPillai/Reactive-Resume&Date)
## Infrastructure
- [Next.js](https://nextjs.org/), frontend

View File

@ -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.22' apply false
id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
}
task clean(type: Delete) {

View File

@ -53,13 +53,12 @@ const Header = () => {
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
const { mutateAsync: duplicateMutation } = useMutation<Resume, ServerError, DuplicateResumeParams>(duplicateResume);
const { mutateAsync: deleteMutation } = useMutation<void, ServerError, DeleteResumeParams>(deleteResume);
const resume = useAppSelector((state) => state.resume.present);
const { left, right } = useAppSelector((state) => state.build.sidebar);
const { mutateAsync: deleteMutation } = useMutation<void, ServerError, DeleteResumeParams>(deleteResume);
const { mutateAsync: duplicateMutation } = useMutation<Resume, ServerError, DuplicateResumeParams>(duplicateResume);
const name = useMemo(() => get(resume, 'name'), [resume]);
useEffect(() => {

View File

@ -108,7 +108,7 @@ const LeftSidebar = () => {
variant={isDesktop ? 'persistent' : 'temporary'}
>
<div className={styles.container}>
<nav className="overflow-y-scroll">
<nav className="overflow-y-auto">
<div>
<Link href="/dashboard">
<Logo size={40} />

View File

@ -43,7 +43,7 @@ const RightSidebar = () => {
variant={isDesktop ? 'persistent' : 'temporary'}
>
<div className={styles.container}>
<nav className="overflow-y-scroll">
<nav className="overflow-y-auto">
<div>
<Avatar size={40} />
<Divider />

View File

@ -3,7 +3,7 @@ import { Button } from '@mui/material';
import { useTranslation } from 'next-i18next';
import Heading from '@/components/shared/Heading';
import { DOCS_URL, DONATION_URL, GITHUB_ISSUES_URL, GITHUB_URL } from '@/constants/index';
import { DOCS_URL, DONATION_URL, GITHUB_ISSUES_URL, GITHUB_URL, REDDIT_URL } from '@/constants/index';
import styles from './Links.module.scss';
@ -50,6 +50,12 @@ const Links = () => {
</Button>
</a>
<a href={REDDIT_URL} target="_blank" rel="noreferrer">
<Button variant="text" startIcon={<Link />}>
{t<string>('builder.rightSidebar.sections.links.reddit')}
</Button>
</a>
<a href={DOCS_URL} target="_blank" rel="noreferrer">
<Button variant="text" startIcon={<Link />}>
{t<string>('builder.rightSidebar.sections.links.docs')}

View File

@ -51,7 +51,7 @@ const Settings = () => {
const pageConfig: PageConfig | undefined = useMemo(() => get(resume, 'metadata.page'), [resume]);
const isDarkMode = useMemo(() => theme === 'dark', [theme]);
const exampleDateString = useMemo(() => `Eg. ${dayjs().utc().format(dateConfig.format)}`, [dateConfig.format]);
const exampleDateString = useMemo(() => `Eg. ${dayjs().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>(

View File

@ -6,15 +6,17 @@ import { useState } from 'react';
import { logout } from '@/store/auth/authSlice';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice';
import getGravatarUrl from '@/utils/getGravatarUrl';
import styles from './Avatar.module.scss';
type Props = {
size?: number;
interactive?: boolean;
};
const Avatar: React.FC<Props> = ({ size = 64 }) => {
const Avatar: React.FC<Props> = ({ size = 64, interactive = true }) => {
const router = useRouter();
const { t } = useTranslation();
@ -34,6 +36,11 @@ const Avatar: React.FC<Props> = ({ size = 64 }) => {
setAnchorEl(null);
};
const handleOpenProfile = () => {
dispatch(setModalState({ modal: 'auth.profile', state: { open: true } }));
handleClose();
};
const handleLogout = () => {
dispatch(logout());
handleClose();
@ -43,7 +50,7 @@ const Avatar: React.FC<Props> = ({ size = 64 }) => {
return (
<>
<IconButton onClick={handleOpen}>
<IconButton onClick={handleOpen} disabled={!interactive}>
<Image
width={size}
height={size}
@ -54,9 +61,9 @@ const Avatar: React.FC<Props> = ({ size = 64 }) => {
</IconButton>
<Menu anchorEl={anchorEl} onClose={handleClose} open={Boolean(anchorEl)}>
<MenuItem>
<MenuItem onClick={handleOpenProfile}>
<div>
<span className="text-xs opacity-50">{t<string>('common.avatar.menu.greeting')}</span>
<span className="text-xs opacity-50">{t<string>('common.avatar.menu.greeting')},</span>
<p>{user?.name}</p>
</div>
</MenuItem>

View File

@ -1,6 +1,9 @@
import clsx from 'clsx';
import { isEmpty } from 'lodash';
import ReactMarkdown from 'react-markdown';
import rehypeKatex from 'rehype-katex';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
type Props = {
children?: string;
@ -11,7 +14,11 @@ const Markdown: React.FC<Props> = ({ className, children }) => {
if (!children || isEmpty(children)) return null;
return (
<ReactMarkdown remarkPlugins={[]} className={clsx('markdown', className)}>
<ReactMarkdown
className={clsx('markdown', className)}
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeKatex]}
>
{children}
</ReactMarkdown>
);

View File

@ -13,6 +13,11 @@ export const DOCS_URL = 'https://docs.rxresu.me';
export const DONATION_URL = 'https://paypal.me/RajaRajanA';
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/';
export const GITHUB_URL = 'https://github.com/AmruthPillai/Reactive-Resume';
export const PRODUCT_HUNT_URL = 'https://www.producthunt.com/posts/reactive-resume-v3';
export const GITHUB_ISSUES_URL = 'https://github.com/AmruthPillai/Reactive-Resume/issues/new/choose';
// Default Error Message
export const DEFAULT_ERROR_MESSAGE =
'Something went wrong while performing this action, please report this issue on GitHub.';

View File

@ -62,14 +62,7 @@ const LoginModal: React.FC = () => {
};
const onSubmit = async ({ identifier, password }: FormData) => {
await loginMutation(
{ identifier, password },
{
onError: (error) => {
toast.error(error.message);
},
}
);
await loginMutation({ identifier, password });
handleClose();
};
@ -86,14 +79,14 @@ const LoginModal: React.FC = () => {
const handleLoginWithGoogle = async (response: CredentialResponse) => {
if (response.credential) {
await loginWithGoogleMutation({ credential: response.credential }, { onError: handleLoginWithGoogleError });
await loginWithGoogleMutation({ credential: response.credential }, { onError: handleGoogleLoginError });
handleClose();
}
};
const handleLoginWithGoogleError = () => {
toast("Please try logging in using email/password, or use another browser that supports Google's One Tap API.");
const handleGoogleLoginError = () => {
toast.error("Google doesn't seem to be responding, please try logging in using email/password instead.");
};
const PasswordVisibility = (): React.ReactElement => {
@ -117,7 +110,7 @@ const LoginModal: React.FC = () => {
footerChildren={
<div className="flex gap-4">
{!isEmpty(env('GOOGLE_CLIENT_ID')) && (
<GoogleLogin onSuccess={handleLoginWithGoogle} onError={handleLoginWithGoogleError} />
<GoogleLogin onSuccess={handleLoginWithGoogle} onError={handleGoogleLoginError} />
)}
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}>

View File

@ -81,14 +81,14 @@ const RegisterModal: React.FC = () => {
const handleLoginWithGoogle = async (response: CredentialResponse) => {
if (response.credential) {
await loginWithGoogleMutation({ credential: response.credential }, { onError: handleLoginWithGoogleError });
await loginWithGoogleMutation({ credential: response.credential }, { onError: handleGoogleLoginError });
handleClose();
}
};
const handleLoginWithGoogleError = () => {
toast("Please try logging in using email/password, or use another browser that supports Google's One Tap API.");
const handleGoogleLoginError = () => {
toast("Google doesn't seem to be responding, please try logging in using email/password instead.");
};
return (
@ -100,7 +100,7 @@ const RegisterModal: React.FC = () => {
footerChildren={
<div className="flex gap-4">
{!isEmpty(env('GOOGLE_CLIENT_ID')) && (
<GoogleLogin onSuccess={handleLoginWithGoogle} onError={handleLoginWithGoogleError} />
<GoogleLogin onSuccess={handleLoginWithGoogle} onError={handleGoogleLoginError} />
)}
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}>

View File

@ -0,0 +1,159 @@
import { joiResolver } from '@hookform/resolvers/joi';
import { CrisisAlert, ManageAccounts } from '@mui/icons-material';
import { Button, Divider, TextField } from '@mui/material';
import Joi from 'joi';
import { useRouter } from 'next/router';
import { Trans, useTranslation } from 'next-i18next';
import { useEffect, useMemo, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useMutation } from 'react-query';
import Avatar from '@/components/shared/Avatar';
import BaseModal from '@/components/shared/BaseModal';
import { deleteAccount, updateProfile, UpdateProfileParams } from '@/services/auth';
import { ServerError } from '@/services/axios';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice';
type FormData = {
name: string;
email: string;
};
const defaultState: FormData = {
name: '',
email: '',
};
const schema = Joi.object({
name: Joi.string().required(),
email: Joi.string()
.email({ tlds: { allow: false } })
.required(),
});
const UserProfileModal = () => {
const router = useRouter();
const { t } = useTranslation();
const dispatch = useAppDispatch();
const [deleteText, setDeleteText] = useState<string>('');
const isDeleteTextValid = useMemo(() => deleteText.toLowerCase() === 'delete', [deleteText]);
const user = useAppSelector((state) => state.auth.user);
const { open: isOpen } = useAppSelector((state) => state.modal['auth.profile']);
const { mutateAsync: deleteAccountMutation } = useMutation<void, ServerError>(deleteAccount);
const { mutateAsync: updateProfileMutation } = useMutation<void, ServerError, UpdateProfileParams>(updateProfile);
const { reset, getFieldState, control, handleSubmit } = useForm<FormData>({
defaultValues: defaultState,
resolver: joiResolver(schema),
});
useEffect(() => {
if (user && !getFieldState('name').isTouched && !getFieldState('email').isTouched) {
reset({ name: user.name, email: user.email });
}
}, [user]);
const handleClose = () => {
dispatch(setModalState({ modal: 'auth.profile', state: { open: false } }));
};
const handleUpdate = handleSubmit(async (data) => {
handleClose();
await updateProfileMutation({ name: data.name });
});
const handleDelete = async () => {
await deleteAccountMutation();
handleClose();
router.push('/');
};
return (
<BaseModal isOpen={isOpen} handleClose={handleClose} heading="Your Account" icon={<ManageAccounts />}>
<div className="grid gap-4">
<form className="grid gap-4 xl:w-2/3">
<div className="flex items-center gap-4">
<Avatar interactive={false} />
<div className="grid flex-1 gap-1.5">
<Controller
name="name"
control={control}
render={({ field, fieldState }) => (
<TextField
autoFocus
label={t('modals.auth.profile.form.name.label')}
error={!!fieldState.error}
helperText={fieldState.error?.message}
{...field}
/>
)}
/>
<p className="pl-4 text-[10.25px] opacity-50">
<Trans t={t} i18nKey="modals.auth.profile.form.avatar.help-text">
You can update your profile picture on{' '}
<a href="https://gravatar.com/" target="_blank" rel="noreferrer">
Gravatar
</a>
</Trans>
</p>
</div>
</div>
<Controller
name="email"
control={control}
render={({ field, fieldState }) => (
<TextField
disabled
label={t('modals.auth.profile.form.email.label')}
error={!!fieldState.error}
helperText={t('modals.auth.profile.form.email.help-text')}
{...field}
/>
)}
/>
<div>
<Button onClick={handleUpdate}>{t('modals.auth.profile.actions.save')}</Button>
</div>
</form>
<div className="my-2">
<Divider />
</div>
<div className="flex items-center gap-2">
<CrisisAlert />
<h5 className="font-medium">{t('modals.auth.profile.delete-account.heading')}</h5>
</div>
<p className="text-xs opacity-75">{t('modals.auth.profile.delete-account.body', { keyword: 'delete' })}</p>
<div className="flex max-w-xs flex-col gap-4">
<TextField
value={deleteText}
placeholder="Type 'delete' to confirm"
onChange={(e) => setDeleteText(e.target.value)}
/>
<div>
<Button variant="contained" color="error" disabled={!isDeleteTextValid} onClick={handleDelete}>
{t('modals.auth.profile.delete-account.actions.delete')}
</Button>
</div>
</div>
</div>
</BaseModal>
);
};
export default UserProfileModal;

View File

@ -134,7 +134,7 @@ const AwardModal: React.FC = () => {
views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange('');
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
}}
renderInput={(params) => (
<TextField

View File

@ -134,7 +134,7 @@ const CertificateModal: React.FC = () => {
views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange('');
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
}}
renderInput={(params) => (
<TextField

View File

@ -151,7 +151,7 @@ const CustomModal: React.FC = () => {
views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange('');
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
}}
renderInput={(params) => (
<TextField
@ -175,7 +175,7 @@ const CustomModal: React.FC = () => {
views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange('');
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
}}
renderInput={(params) => (
<TextField

View File

@ -173,7 +173,7 @@ const EducationModal: React.FC = () => {
views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange('');
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
}}
renderInput={(params) => (
<TextField
@ -197,7 +197,7 @@ const EducationModal: React.FC = () => {
views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange('');
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
}}
renderInput={(params) => (
<TextField

View File

@ -143,7 +143,7 @@ const ProjectModal: React.FC = () => {
views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange('');
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
}}
renderInput={(params) => (
<TextField
@ -167,7 +167,7 @@ const ProjectModal: React.FC = () => {
views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange('');
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
}}
renderInput={(params) => (
<TextField

View File

@ -134,7 +134,7 @@ const PublicationModal: React.FC = () => {
views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange('');
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
}}
renderInput={(params) => (
<TextField

View File

@ -140,7 +140,7 @@ const VolunteerModal: React.FC = () => {
views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange('');
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
}}
renderInput={(params) => (
<TextField
@ -164,7 +164,7 @@ const VolunteerModal: React.FC = () => {
views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange('');
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
}}
renderInput={(params) => (
<TextField

View File

@ -140,7 +140,7 @@ const WorkModal: React.FC = () => {
views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange('');
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
}}
renderInput={(params) => (
<TextField
@ -164,7 +164,7 @@ const WorkModal: React.FC = () => {
views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange('');
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
}}
renderInput={(params) => (
<TextField

View File

@ -6,7 +6,6 @@ import Joi from 'joi';
import { useTranslation } from 'next-i18next';
import { useEffect } from 'react';
import { Controller, useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
import { useMutation } from 'react-query';
import BaseModal from '@/components/shared/BaseModal';
@ -66,15 +65,10 @@ const CreateResumeModal: React.FC = () => {
}, [name, setValue]);
const onSubmit = async ({ name, slug, isPublic }: FormData) => {
try {
await mutateAsync({ name, slug, public: isPublic });
await mutateAsync({ name, slug, public: isPublic });
await queryClient.invalidateQueries(RESUMES_QUERY);
await queryClient.invalidateQueries(RESUMES_QUERY);
handleClose();
} catch (error: any) {
toast.error(error.message);
}
handleClose();
};
const handleClose = () => {

View File

@ -68,8 +68,8 @@ const ImportExternalModal: React.FC = () => {
}
await mutateAsync({ integration, file });
queryClient.invalidateQueries(RESUMES_QUERY);
handleClose();
}
};

View File

@ -8,6 +8,7 @@ import ForgotPasswordModal from './auth/ForgotPasswordModal';
import LoginModal from './auth/LoginModal';
import RegisterModal from './auth/RegisterModal';
import ResetPasswordModal from './auth/ResetPasswordModal';
import UserProfileModal from './auth/UserProfileModal';
import AwardModal from './builder/sections/AwardModal';
import CertificateModal from './builder/sections/CertificateModal';
import CustomModal from './builder/sections/CustomModal';
@ -49,6 +50,7 @@ const ModalWrapper: React.FC = () => {
<RegisterModal />
<ForgotPasswordModal />
<ResetPasswordModal />
<UserProfileModal />
{/* Dashboard */}
<CreateResumeModal />

View File

@ -17,14 +17,13 @@
"@hookform/resolvers": "2.9.10",
"@monaco-editor/react": "^4.4.6",
"@mui/icons-material": "^5.11.0",
"@mui/lab": "^5.0.0-alpha.112",
"@mui/material": "^5.11.0",
"@mui/system": "^5.11.0",
"@mui/x-date-pickers": "5.0.11",
"@next/env": "^13.0.7",
"@react-oauth/google": "^0.5.1",
"@mui/lab": "^5.0.0-alpha.116",
"@mui/material": "^5.11.5",
"@mui/system": "^5.11.5",
"@mui/x-date-pickers": "5.0.14",
"@react-oauth/google": "^0.6.0",
"@reduxjs/toolkit": "^1.9.1",
"axios": "^1.2.1",
"axios": "^1.2.3",
"clsx": "^1.2.1",
"dayjs": "^1.11.7",
"downloadjs": "^1.4.7",
@ -32,51 +31,52 @@
"lodash": "^4.17.21",
"md5-hex": "^4.0.0",
"monaco-editor": "^0.34.1",
"nanoid": "^3.3.4",
"next": "13.0.7",
"next-i18next": "^13.0.2",
"nanoid": "3.3.4",
"next": "13.1.2",
"next-i18next": "^13.0.3",
"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.40.0",
"react-hook-form": "^7.42.1",
"react-hot-toast": "2.4.0",
"react-icons": "^4.7.1",
"react-markdown": "^8.0.4",
"react-markdown": "^8.0.5",
"react-query": "^3.39.2",
"react-redux": "^8.0.5",
"react-zoom-pan-pinch": "^2.1.3",
"react-zoom-pan-pinch": "^2.2.0",
"redux": "^4.2.0",
"redux-persist": "^6.0.0",
"redux-saga": "^1.2.2",
"redux-undo": "^1.0.1",
"rehype-katex": "^6.0.2",
"remark-gfm": "^3.0.1",
"sharp": "^0.31.2",
"remark-math": "^5.1.1",
"sharp": "^0.31.3",
"uuid": "^9.0.0",
"webfontloader": "^1.6.28"
},
"devDependencies": {
"@babel/core": "^7.20.5",
"@babel/core": "^7.20.12",
"@reactive-resume/schema": "workspace:*",
"@tailwindcss/typography": "^0.5.8",
"@tailwindcss/typography": "^0.5.9",
"@types/downloadjs": "^1.4.3",
"@types/lodash": "^4.14.191",
"@types/node": "^18.11.15",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@types/react-redux": "^7.1.24",
"@types/tailwindcss": "^3.0.11",
"@types/node": "^18.11.18",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@types/react-redux": "^7.1.25",
"@types/uuid": "^9.0.0",
"@types/webfontloader": "^1.6.35",
"autoprefixer": "^10.4.13",
"csstype": "^3.1.1",
"eslint-config-next": "^13.0.7",
"eslint-plugin-tailwindcss": "^3.7.1",
"eslint-config-next": "^13.1.2",
"eslint-plugin-tailwindcss": "^3.8.0",
"eslint-plugin-unused-imports": "^2.0.0",
"next-sitemap": "^3.1.42",
"postcss": "^8.4.20",
"sass": "^1.56.2",
"next-sitemap": "^3.1.45",
"postcss": "^8.4.21",
"sass": "^1.57.1",
"tailwindcss": "^3.2.4",
"typescript": "^4.9.4"
}

View File

@ -15,6 +15,7 @@ import toast from 'react-hot-toast';
import { useMutation, useQuery } from 'react-query';
import Page from '@/components/build/Center/Page';
import { DEFAULT_ERROR_MESSAGE } from '@/constants/index';
import { ServerError } from '@/services/axios';
import { printResumeAsPdf, PrintResumeAsPdfParams } from '@/services/printer';
import { fetchResumeByIdentifier } from '@/services/resume';
@ -105,7 +106,7 @@ const Preview: NextPage<Props> = ({ username, slug, resume: initialData }) => {
download(url);
} catch {
toast.error('Something went wrong, please try again later.');
toast.error(DEFAULT_ERROR_MESSAGE);
}
};

View File

@ -20,7 +20,7 @@ import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice';
import styles from '@/styles/pages/Home.module.scss';
import { DIGITALOCEAN_URL, DOCS_URL, DONATION_URL, GITHUB_URL } from '../constants';
import { DIGITALOCEAN_URL, DOCS_URL, DONATION_URL, GITHUB_URL, REDDIT_URL } from '../constants';
export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => ({
props: {
@ -177,6 +177,12 @@ const Home: NextPage = () => {
</Button>
</a>
<a href={REDDIT_URL} target="_blank" rel="noreferrer">
<Button variant="text" startIcon={<LinkIcon />}>
{t<string>('landing.links.links.reddit')}
</Button>
</a>
<a href={DONATION_URL} target="_blank" rel="noreferrer">
<Button variant="text" startIcon={<LinkIcon />}>
{t<string>('landing.links.links.donate')}

View File

@ -11,7 +11,6 @@ import Link from 'next/link';
import { useRouter } from 'next/router';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { useEffect } from 'react';
import toast from 'react-hot-toast';
import { useMutation, useQuery } from 'react-query';
import Page from '@/components/build/Center/Page';
@ -69,17 +68,13 @@ const Preview: NextPage<Props> = ({ shortId }) => {
const layout: string[][][] = get(resume, 'metadata.layout', []);
const handleDownload = async () => {
try {
const url = await mutateAsync({
username: resume.user.username,
slug: resume.slug,
lastUpdated: dayjs(resume.updatedAt).unix().toString(),
});
const url = await mutateAsync({
username: resume.user.username,
slug: resume.slug,
lastUpdated: dayjs(resume.updatedAt).unix().toString(),
});
download(url);
} catch {
toast.error('Something went wrong, please try again later.');
}
download(url);
};
return (

View File

@ -16,7 +16,7 @@
"present": "Heute"
},
"subtitle": "Ein kostenloser Open Source Lebenslauf-Baukasten.",
"title": "Reactive Resume",
"title": "Reaktives Lebenslauf",
"toast": {
"error": {
"upload-file-size": "Bitte laden Sie nur Dateien unter 2 Megabytes hoch.",

View File

@ -3,8 +3,8 @@
"actions": {
"add": "Add New {{token}}",
"delete": "Delete {{token}}",
"edit": "Edit {{token}}",
"duplicate": "Duplicate Section"
"duplicate": "Duplicate Section",
"edit": "Edit {{token}}"
},
"columns": {
"heading": "Columns",
@ -81,13 +81,13 @@
"center-artboard": "Center Artboard",
"copy-link": "Copy Link to Resume",
"export-pdf": "Export PDF",
"redo": "Redo",
"toggle-orientation": "Toggle Page Orientation",
"toggle-page-break-line": "Toggle Page Break Line",
"toggle-sidebars": "Toggle Sidebars",
"zoom-in": "Zoom In",
"zoom-out": "Zoom Out",
"undo": "Undo",
"redo": "Redo"
"zoom-in": "Zoom In",
"zoom-out": "Zoom Out"
}
},
"header": {
@ -115,6 +115,9 @@
"actions": {
"photo-filters": "Photo Filters"
},
"birthdate": {
"label": "Date of Birth"
},
"heading": "Basics",
"headline": {
"label": "Headline"
@ -122,9 +125,6 @@
"name": {
"label": "Full Name"
},
"birthdate": {
"label": "Date of Birth"
},
"photo-filters": {
"effects": {
"border": {
@ -265,14 +265,15 @@
"button": "GitHub Issues",
"heading": "Bugs? Feature Requests?"
},
"docs": "Documentation",
"donate": {
"body": "If you liked using Reactive Resume, please consider donating as much as you can to the cause of keeping the app up and running, without ads and free forever.",
"button": "Buy me a coffee",
"heading": "Donate to Reactive Resume"
},
"github": "Source Code",
"docs": "Documentation",
"heading": "Links"
"heading": "Links",
"reddit": "Reddit"
},
"settings": {
"global": {
@ -291,14 +292,14 @@
},
"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"
},
"format": {
"primary": "Paper Size",
"secondary": "Determines the dimensions of your resume pages"
},
"heading": "Page",
"orientation": {
"disabled": "Has no effect when there is only one page",

View File

@ -20,23 +20,24 @@
"links": {
"heading": "Links",
"links": {
"docs": "Documentation",
"donate": "Donate",
"github": "Source Code",
"docs": "Documentation",
"privacy": "Privacy Policy",
"reddit": "Reddit",
"service": "Terms of Service"
}
},
"screenshots": {
"heading": "Screenshots"
},
"testimonials": {
"heading": "Testimonials",
"body": "Good or bad, I would love to hear your opinion on Reactive Resume and how the experience has been for you.<br/>Here are some of the messages sent in by users across the world.",
"contact": "You can reach out to me through <1>my email</1> or through the contact form on <3>my website</3>."
},
"summary": {
"body": "Reactive Resume is a free and open source resume builder that's built to make the mundane tasks of creating, updating and sharing your resume as easy as 1, 2, 3. With this app, you can create multiple resumes, share them with recruiters or friends through a unique link and print it as a PDF, all for free, no ads, no tracking, without losing the integrity and privacy of your data.",
"heading": "Summary"
},
"testimonials": {
"body": "Good or bad, I would love to hear your opinion on Reactive Resume and how the experience has been for you.<br/>Here are some of the messages sent in by users across the world.",
"contact": "You can reach out to me through <1>my email</1> or through the contact form on <3>my website</3>.",
"heading": "Testimonials"
}
}

View File

@ -71,6 +71,31 @@
}
},
"heading": "Reset your password"
},
"profile": {
"heading": "Your Account",
"form": {
"avatar": {
"help-text": "You can update your profile picture on <1>Gravatar</1>"
},
"name": {
"label": "Full Name"
},
"email": {
"label": "Email Address",
"help-text": "It is not possible to update your email address at the moment, please create a new account instead."
}
},
"delete-account": {
"heading": "Delete Account and Data",
"body": "To delete your account, your data and all your resumes, type \"{{keyword}}\" into the textbox and click on the button. Please note that this is an irreversible action and your data cannot be retrieved again.",
"actions": {
"delete": "Delete Account"
}
},
"actions": {
"save": "Save Changes"
}
}
},
"dashboard": {

View File

@ -3,6 +3,7 @@
"actions": {
"add": "Agregar nuevo {{token}}",
"delete": "Eliminar {{token}}",
"duplicate": "Sección duplicada",
"edit": "Editar {{token}}"
},
"columns": {
@ -80,13 +81,13 @@
"center-artboard": "Centrar Tablero",
"copy-link": "Copiar enlace al currículum",
"export-pdf": "Exportar PDF",
"redo": "Rehacer",
"toggle-orientation": "Cambiar la orientación de la página",
"toggle-page-break-line": "Alternar línea de salto de página",
"toggle-sidebars": "Ocultar/mostrar barra lateral",
"zoom-in": "Acercar",
"zoom-out": "Alejar",
"undo": "Deshacer",
"redo": "Rehacer"
"zoom-in": "Acercar",
"zoom-out": "Alejar"
}
},
"header": {
@ -114,6 +115,9 @@
"actions": {
"photo-filters": "Filtro de fotos"
},
"birthdate": {
"label": "Fecha de cumpleaños"
},
"heading": "Información básica",
"headline": {
"label": "Titular"
@ -121,9 +125,6 @@
"name": {
"label": "Nombre Completo"
},
"birthdate": {
"label": "Fecha de cumpleaños"
},
"photo-filters": {
"effects": {
"border": {
@ -264,14 +265,15 @@
"button": "Propuesta de GitHub",
"heading": "¿Errores? ¿Solicitud de características?"
},
"docs": "Documentación",
"donate": {
"body": "Si le gustó usar Reactive Resume, considere donar lo que pueda a la causa de mantener la aplicación en funcionamiento, sin anuncios y gratis para siempre.",
"button": "Invítame a un café",
"heading": "Donar a Reactive Resume"
},
"github": "Código Fuente",
"docs": "Documentación",
"heading": "Enlaces"
"heading": "Enlaces",
"reddit": "Reddit"
},
"settings": {
"global": {
@ -290,14 +292,14 @@
},
"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"
},
"format": {
"primary": "Tamaño de papel",
"secondary": "Determina las dimensiones de las páginas de tu currículum."
},
"heading": "Página",
"orientation": {
"disabled": "No tiene efecto cuando solo hay una página",

View File

@ -20,23 +20,24 @@
"links": {
"heading": "Enlaces",
"links": {
"docs": "Documentación",
"donate": "Donar",
"github": "Código fuente",
"docs": "Documentación",
"privacy": "Política de Privacidad",
"reddit": "Reddit",
"service": "Términos de Servicio"
}
},
"screenshots": {
"heading": "Capturas de pantalla"
},
"testimonials": {
"heading": "Opiniones",
"body": "Bueno o malo, me encantaría saber tu opinión sobre Reactive Resume y cómo ha sido la experiencia para ti.<br/>Estos son algunos de los mensajes enviados por usuarios de todo el mundo.",
"contact": "Puedes comunicarte conmigo a través de <1>mi correo electrónico</1> o a través del formulario de contacto en <3>mi sitio web</3>."
},
"summary": {
"body": "Reactive Resume es un generador de currículums gratuito y de código abierto que está diseñado para hacer que las tareas mundanas de crear, actualizar y compartir su currículum sean tan fáciles como 1, 2, 3. Con esta aplicación, puede crear múltiples currículums, compartirlos con reclutadores o amigos a través de un enlace único e imprímalo como PDF, todo gratis, sin anuncios, sin seguimiento, sin perder la integridad y privacidad de sus datos.",
"heading": "Resumen"
},
"testimonials": {
"body": "Bueno o malo, me encantaría saber tu opinión sobre Reactive Resume y cómo ha sido la experiencia para ti.<br/>Estos son algunos de los mensajes enviados por usuarios de todo el mundo.",
"contact": "Puedes comunicarte conmigo a través de <1>mi correo electrónico</1> o a través del formulario de contacto en <3>mi sitio web</3>.",
"heading": "Opiniones"
}
}

View File

@ -3,6 +3,7 @@
"actions": {
"add": "{{token}} جدید اضافه کنید",
"delete": "حذف {{token}}",
"duplicate": "بخش تکراری",
"edit": "ویرایش {{token}}"
},
"columns": {
@ -80,13 +81,13 @@
"center-artboard": "قرار دادن صفحه در مرکز",
"copy-link": "کپی کردن لینک رزومه",
"export-pdf": "خروجی PDF",
"redo": "دوباره انجام دهید",
"toggle-orientation": "تغییر وضعیت جهت‌گیری صفحه",
"toggle-page-break-line": "تغییر وضعیت خط شکست صفحه",
"toggle-sidebars": "باز/بسته کردن نوار کنار صفحه",
"zoom-in": "بزرگ‌نمایی",
"zoom-out": "کوچک‌نمایی",
"undo": "واگرد",
"redo": "دوباره انجام دهید"
"zoom-in": "بزرگ‌نمایی",
"zoom-out": "کوچک‌نمایی"
}
},
"header": {
@ -114,6 +115,9 @@
"actions": {
"photo-filters": "فیلترهای تصویر"
},
"birthdate": {
"label": "تاریخ تولد"
},
"heading": "موارد پایه",
"headline": {
"label": "سرصفحه"
@ -121,9 +125,6 @@
"name": {
"label": "نام کامل"
},
"birthdate": {
"label": "تاریخ تولد"
},
"photo-filters": {
"effects": {
"border": {
@ -264,14 +265,15 @@
"button": "GitHub Issues",
"heading": "باگ‌ها؟ درخواست ویژگی جدید؟"
},
"docs": "مستندات",
"donate": {
"body": "اگر استفاده از Reactive Resume را دوست داشتید، لطفاً تا جایی که می توانید کمک مالی کنید تا برنامه را بدون تبلیغات و برای همیشه رایگان نگه دارید.",
"button": "برای من یک قهوه بخر",
"heading": "کمک مالی به Reactive Resume"
},
"github": "کد منبع",
"docs": "مستندات",
"heading": "لینک‌ها"
"heading": "لینک‌ها",
"reddit": "ردیت"
},
"settings": {
"global": {
@ -290,14 +292,14 @@
},
"heading": "تنظیمات",
"page": {
"format": {
"primary": "اندازه کاغذ",
"secondary": "ابعاد صفحات رزومه شما را تعیین می کند"
},
"break-line": {
"primary": "خط شکست",
"secondary": "برای مشخص کردن ارتفاع صفحه A4 یک خط در همه صفحات نشان داده شود"
},
"format": {
"primary": "اندازه کاغذ",
"secondary": "ابعاد صفحات رزومه شما را تعیین می کند"
},
"heading": "صفحه",
"orientation": {
"disabled": "زمانی که تنها یک صفحه وجود دارد، تاثیری ندارد",

View File

@ -20,23 +20,24 @@
"links": {
"heading": "لینک‌ها",
"links": {
"docs": "مستندات",
"donate": "حمایت مالی",
"github": "کد منبع",
"docs": "مستندات",
"privacy": "حریم خصوصی",
"reddit": "ردیت",
"service": "شرایط سرویس‌دهی"
}
},
"screenshots": {
"heading": "اسکرین‌شات‌ها"
},
"testimonials": {
"heading": "نظرات کاربران",
"body": "خوب یا بد، من دوست دارم نظر شما را در مورد Reactive Resume و اینکه تجربه کار با آن برای شما چگونه بوده است را بدانم.<br/>تعدادی از پیام های ارسال شده توسط کاربران در سراسر جهان را اینجا می‌بینید.",
"contact": "می‌توانید از طریق <1>ایمیل من</1> یا فرم تماس در <3>وب‌سایت من</3> با من در ارتباط باشید."
},
"summary": {
"body": "Reactive Resume یک رزومه ساز رایگان و متن‌باز است که برای ایجاد، به روز رسانی و به اشتراک گذاری رزومه شما به آسانی شمردن ۱، ۲، ۳ ساخته شده است. با این برنامه، می توانید چندین رزومه ایجاد کنید و آنها را با کارفرماها یا دوستان از طریق یک لینک منحصر به فرد و چاپ آن به صورت PDF، همه به صورت رایگان، بدون تبلیغات، بدون ردیابی، بدون از دست دادن امنیت و حریم خصوصی داده های شما، به اشتراک بگذارید.",
"heading": "درباره من"
},
"testimonials": {
"body": "خوب یا بد، من دوست دارم نظر شما را در مورد Reactive Resume و اینکه تجربه کار با آن برای شما چگونه بوده است را بدانم.<br/>تعدادی از پیام های ارسال شده توسط کاربران در سراسر جهان را اینجا می‌بینید.",
"contact": "می‌توانید از طریق <1>ایمیل من</1> یا فرم تماس در <3>وب‌سایت من</3> با من در ارتباط باشید.",
"heading": "نظرات کاربران"
}
}

View File

@ -2,7 +2,7 @@
"avatar": {
"menu": {
"greeting": "Hallo",
"logout": "Afmelden"
"logout": "Uitloggen"
}
},
"footer": {
@ -10,7 +10,7 @@
"license": "Door de gemeenschap, voor de gemeenschap."
},
"markdown": {
"help-text": "Deze sectie ondersteunt <1>markdown</1> opmaak."
"help-text": "Deze sectie ondersteunt <1>html</1> opmaak."
},
"date": {
"present": "Heden"

View File

@ -1,6 +1,7 @@
# *
User-agent: *
Allow: /
Disallow: /*/*
# Host
Host: https://rxresu.me

View File

@ -2,7 +2,7 @@ import { User } from '@reactive-resume/schema';
import { AxiosResponse } from 'axios';
import toast from 'react-hot-toast';
import { setAccessToken, setUser } from '@/store/auth/authSlice';
import { logout, setAccessToken, setUser } from '@/store/auth/authSlice';
import store from '../store';
import axios from './axios';
@ -37,6 +37,10 @@ export type ResetPasswordParams = {
password: string;
};
export type UpdateProfileParams = {
name: string;
};
export const login = async (loginParams: LoginParams) => {
const {
data: { user, accessToken },
@ -75,3 +79,23 @@ export const resetPassword = async (resetPasswordParams: ResetPasswordParams) =>
toast.success('Your password has been changed successfully, please login again.');
};
export const updateProfile = async (updateProfileParams: UpdateProfileParams) => {
const { data: user } = await axios.patch<User, AxiosResponse<User>, UpdateProfileParams>(
'/auth/update-profile',
updateProfileParams
);
store.dispatch(setUser(user));
toast.success('Your profile has been successfully updated.');
};
export const deleteAccount = async () => {
await axios.delete('/resume/all');
await axios.delete('/auth');
store.dispatch(logout());
toast.success('Your account has been deleted, hope to see you again soon.');
};

View File

@ -1,6 +1,7 @@
import env from '@beam-australia/react-env';
import _axios from 'axios';
import _axios, { AxiosError } from 'axios';
import Router from 'next/router';
import { toast } from 'react-hot-toast';
import { logout } from '@/store/auth/authSlice';
@ -19,22 +20,22 @@ const axios = _axios.create({ baseURL });
axios.interceptors.request.use((config) => {
const { accessToken } = store.getState().auth;
config.headers = {
...config.headers,
Authorization: `Bearer ${accessToken}`,
};
config.headers.set('Authorization', `Bearer ${accessToken}`);
return config;
});
axios.interceptors.response.use(
(response) => response,
(error) => {
(error: AxiosError<ServerError>) => {
const { response } = error;
if (response) {
const errorObject: ServerError = response.data;
const errorObject = response.data;
const code = errorObject.statusCode;
const message = errorObject.message;
toast.error(message);
if (code === 401 || code === 404) {
store.dispatch(logout());

View File

@ -39,7 +39,7 @@ export const buildSlice = createSlice({
name: 'build',
initialState,
reducers: {
setTheme: (state, action: PayloadAction<SetThemePayload>) => {
setTheme: (state: BuildState, action: PayloadAction<SetThemePayload>) => {
const { theme } = action.payload;
state.theme = theme;

View File

@ -5,6 +5,7 @@ export type ModalName =
| 'auth.register'
| 'auth.forgot'
| 'auth.reset'
| 'auth.profile'
| 'dashboard.create-resume'
| 'dashboard.import-external'
| 'dashboard.rename-resume'
@ -24,6 +25,7 @@ const initialState: Record<ModalName, ModalState> = {
'auth.register': { open: false },
'auth.forgot': { open: false },
'auth.reset': { open: false },
'auth.profile': { open: false },
'dashboard.create-resume': { open: false },
'dashboard.import-external': { open: false },
'dashboard.rename-resume': { open: false },

View File

@ -2,6 +2,9 @@
@import url('https://fonts.googleapis.com/icon?family=Material+Icons');
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
// KaTeX (for remark-math)
@import url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css');
// Tailwind CSS
@tailwind base;
@tailwind components;
@ -35,6 +38,10 @@
ul li {
@apply ml-4 list-outside;
}
.footnotes p {
@apply inline;
}
}
}

View File

@ -75,7 +75,7 @@ const Section: React.FC<SectionProps> = ({
{Array.from(Array(8).keys()).map((_, index) => (
<div
key={index}
className="mr-1 h-2 w-4 rounded-sm border"
className="mr-1 h-2 w-full max-w-[1rem] rounded-sm border"
style={{
borderColor: primaryColor,
backgroundColor: levelNum / (10 / 8) > index ? primaryColor : '',

View File

@ -10,7 +10,7 @@ export const dateFormatOptions: string[] = [
'DD.MM.YYYY',
'DD/MM/YYYY',
'MM.DD.YYYY',
'M.D.YYYY',
'M.D.YYYY',
'MM/DD/YYYY',
'YYYY.MM.DD',
'YYYY/MM/DD',
@ -20,7 +20,7 @@ export const dateFormatOptions: string[] = [
'YYYY',
];
export const getRelativeTime = (timestamp: dayjs.ConfigType): string => dayjs(timestamp).utc().toNow(true);
export const getRelativeTime = (timestamp: dayjs.ConfigType): string => dayjs(timestamp).toNow(true);
export const formatDateString = (date: string | DateRange, formatStr: string): string | null => {
const presentString = i18n?.t<string>('common.date.present') ?? '';
@ -31,7 +31,7 @@ export const formatDateString = (date: string | DateRange, formatStr: string): s
if (isString(date)) {
if (!dayjs(date).isValid()) return null;
return dayjs(date).format(formatStr);
return dayjs.utc(date).local().format(formatStr);
}
// If `date` is a DateRange
@ -40,12 +40,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(date.start).format(formatStr);
return dayjs.utc(date.start).local().format(formatStr);
}
if (!isEmpty(date.end) && dayjs(date.end).isValid()) {
return `${dayjs(date.start).format(formatStr)} - ${dayjs(date.end).format(formatStr)}`;
return `${dayjs.utc(date.start).local().format(formatStr)} - ${dayjs.utc(date.end).local().format(formatStr)}`;
}
return `${dayjs(date.start).format(formatStr)} - ${presentString}`;
return `${dayjs.utc(date.start).local().format(formatStr)} - ${presentString}`;
};

View File

@ -9,6 +9,7 @@ import {
FaHackerrank,
FaInstagram,
FaLinkedinIn,
FaMastodon,
FaMedium,
FaSkype,
FaSoundcloud,
@ -18,10 +19,11 @@ import {
FaXing,
FaYoutube,
} from 'react-icons/fa';
import { SiCodechef, SiCodeforces } from 'react-icons/si';
import { SiCodeberg, SiCodechef, SiCodeforces } from 'react-icons/si';
const profileIconMap: Record<string, JSX.Element> = {
behance: <FaBehance />,
codeberg: <SiCodeberg />,
codechef: <SiCodechef />,
codeforces: <SiCodeforces />,
dribbble: <FaDribbble />,
@ -31,6 +33,7 @@ const profileIconMap: Record<string, JSX.Element> = {
hackerrank: <FaHackerrank />,
instagram: <FaInstagram />,
linkedin: <FaLinkedinIn />,
mastodon: <FaMastodon />,
medium: <FaMedium />,
skype: <FaSkype />,
soundcloud: <FaSoundcloud />,

View File

@ -20,7 +20,7 @@ export const formatLocation = (location?: Location): string => {
};
export const addHttp = (url: string) => {
if (url.search(/^http[s]?:\/\//) == -1) {
if (url.search(/^http[s]?:\/\//) == -1 && url.search(/^mailto:/) == -1 && url.search(/^tel:/) == -1) {
url = 'http://' + url;
}

View File

@ -13,9 +13,6 @@ const DateWrapper: React.FC<React.PropsWithChildren<unknown>> = ({ children }) =
dayjs.extend(timezone);
dayjs.extend(relativeTime);
// Set Default Timezone to UTC
dayjs.tz.setDefault('UTC');
// Locales
require('dayjs/locale/am');
require('dayjs/locale/ar');

View File

@ -1,11 +1,12 @@
{
"name": "reactive-resume",
"version": "3.6.16",
"version": "3.6.18",
"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",
"update-deps": "ncu -x nanoid,class-validator --deep -u && pnpm install",
"generate-env": "ts-node ./scripts/generate-env.ts",
"format": "prettier --write .",
"lint": "turbo run lint"
@ -19,17 +20,18 @@
"cross-env": "^7.0.3",
"cross-var": "^1.1.0",
"env-cmd": "^10.1.0",
"turbo": "^1.6.3",
"turbo": "^1.7.0",
"uuid": "^9.0.0"
},
"devDependencies": {
"@types/node": "^18.11.15",
"@typescript-eslint/eslint-plugin": "^5.46.1",
"@typescript-eslint/parser": "^5.46.1",
"eslint": "^8.29.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-simple-import-sort": "^8.0.0",
"prettier": "^2.8.1",
"@types/node": "^18.11.18",
"@typescript-eslint/eslint-plugin": "^5.48.2",
"@typescript-eslint/parser": "^5.48.2",
"eslint": "^8.32.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-simple-import-sort": "^9.0.0",
"npm-check-updates": "^16.6.2",
"prettier": "^2.8.3",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
},

3309
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.29.0",
"eslint": "^8.32.0",
"typescript": "^4.9.4"
}
}

View File

@ -23,7 +23,7 @@ COPY --from=dependencies /app/server/node_modules ./server/node_modules
RUN pnpm run build --filter server
FROM mcr.microsoft.com/playwright:v1.28.1-focal as production
FROM mcr.microsoft.com/playwright:v1.29.2-focal as production
WORKDIR /app

View File

@ -8,12 +8,12 @@
"start": "node dist/main"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.231.0",
"@nestjs/axios": "^1.0.0",
"@aws-sdk/client-s3": "^3.252.0",
"@nestjs/axios": "^1.0.1",
"@nestjs/common": "^9.2.1",
"@nestjs/config": "^2.2.0",
"@nestjs/core": "^9.2.1",
"@nestjs/jwt": "^9.0.0",
"@nestjs/jwt": "^10.0.1",
"@nestjs/mapped-types": "^1.2.0",
"@nestjs/passport": "^9.0.0",
"@nestjs/platform-express": "^9.2.1",
@ -32,40 +32,41 @@
"google-auth-library": "^8.7.0",
"joi": "^17.7.0",
"lodash": "^4.17.21",
"multer": "^1.4.4",
"nanoid": "^3.3.4",
"multer": "^1.4.5-lts.1",
"nanoid": "3.3.4",
"node-stream-zip": "^1.15.0",
"nodemailer": "^6.8.0",
"nodemailer": "^6.9.0",
"passport": "^0.6.0",
"passport-jwt": "^4.0.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"pdf-lib": "^1.17.1",
"pg": "^8.8.0",
"playwright-chromium": "^1.28.1",
"playwright-chromium": "^1.29.2",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rimraf": "^4.1.1",
"rxjs": "^7.8.0",
"typeorm": "0.3.11",
"uuid": "^9.0.0"
},
"devDependencies": {
"@nestjs/cli": "^9.1.5",
"@nestjs/schematics": "^9.0.3",
"@nestjs/cli": "^9.1.8",
"@nestjs/schematics": "^9.0.4",
"@reactive-resume/schema": "workspace:*",
"@types/bcryptjs": "^2.4.2",
"@types/cookie-parser": "^1.4.3",
"@types/express": "^4.17.15",
"@types/lodash": "^4.14.191",
"@types/multer": "^1.4.7",
"@types/node": "^18.11.15",
"@types/nodemailer": "^6.4.6",
"@types/node": "^18.11.18",
"@types/nodemailer": "^6.4.7",
"@types/passport-jwt": "^3.0.8",
"@types/passport-local": "^1.0.34",
"prettier": "^2.8.1",
"@types/passport-local": "^1.0.35",
"@types/uuid": "^9.0.0",
"prettier": "^2.8.3",
"source-map-support": "^0.5.21",
"ts-loader": "^9.4.2",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.1.1",
"tsconfig-paths": "^4.1.2",
"typescript": "^4.9.4",
"webpack": "^5.75.0"
}

View File

@ -1,4 +1,4 @@
import { Body, Controller, Delete, Get, HttpCode, Post, UseGuards } from '@nestjs/common';
import { Body, Controller, Delete, Get, HttpCode, Patch, Post, UseGuards } from '@nestjs/common';
import { User } from '@/decorators/user.decorator';
import { User as UserEntity } from '@/users/entities/user.entity';
@ -7,6 +7,7 @@ import { AuthService } from './auth.service';
import { ForgotPasswordDto } from './dto/forgot-password.dto';
import { RegisterDto } from './dto/register.dto';
import { ResetPasswordDto } from './dto/reset-password.dto';
import { UpdateProfileDto } from './dto/update-profile.dto';
import { JwtAuthGuard } from './guards/jwt.guard';
import { LocalAuthGuard } from './guards/local.guard';
@ -57,6 +58,13 @@ export class AuthController {
return this.authService.resetPassword(resetPasswordDto);
}
@HttpCode(200)
@UseGuards(JwtAuthGuard)
@Patch('update-profile')
updateProfile(@User('id') userId: number, @Body() updateProfileDto: UpdateProfileDto) {
return this.authService.updateProfile(userId, updateProfileDto);
}
@HttpCode(200)
@UseGuards(JwtAuthGuard)
@Delete()

View File

@ -6,12 +6,14 @@ import { compareSync, hashSync } from 'bcryptjs';
import { OAuth2Client } from 'google-auth-library';
import { PostgresErrorCode } from '@/database/errorCodes.enum';
import { ResumeService } from '@/resume/resume.service';
import { CreateGoogleUserDto } from '@/users/dto/create-google-user.dto';
import { User } from '@/users/entities/user.entity';
import { UsersService } from '@/users/users.service';
import { RegisterDto } from './dto/register.dto';
import { ResetPasswordDto } from './dto/reset-password.dto';
import { UpdateProfileDto } from './dto/update-profile.dto';
@Injectable()
export class AuthService {
@ -68,6 +70,10 @@ export class AuthService {
}
}
updateProfile(id: number, newData: UpdateProfileDto) {
return this.usersService.update(id, { name: newData.name });
}
forgotPassword(email: string) {
return this.usersService.generateResetToken(email);
}

View File

@ -0,0 +1,7 @@
import { IsNotEmpty, IsString, MinLength } from 'class-validator';
export class UpdateProfileDto {
@IsString()
@IsNotEmpty()
name: string;
}

View File

@ -15,7 +15,6 @@ import {
} from '@reactive-resume/schema';
import csv from 'csvtojson';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import { readFile, unlink } from 'fs/promises';
import { cloneDeep, get, isEmpty, merge } from 'lodash';
import StreamZip from 'node-stream-zip';
@ -29,9 +28,7 @@ import { ResumeService } from '@/resume/resume.service';
@Injectable()
export class IntegrationsService {
constructor(private resumeService: ResumeService) {
dayjs.extend(utc);
}
constructor(private resumeService: ResumeService) {}
async linkedIn(userId: number, path: string): Promise<ResumeEntity> {
let archive: StreamZip.StreamZipAsync;
@ -43,7 +40,7 @@ export class IntegrationsService {
const resume: Partial<Resume> = cloneDeep(defaultState);
// Basics
const timestamp = dayjs().utc().format(FILENAME_TIMESTAMP);
const timestamp = dayjs().format(FILENAME_TIMESTAMP);
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
name: `Imported from LinkedIn (${timestamp})`,
slug: `imported-from-linkedin-${timestamp}`,
@ -276,7 +273,7 @@ export class IntegrationsService {
const resume: Partial<Resume> = cloneDeep(defaultState);
// Metadata
const timestamp = dayjs().utc().format(FILENAME_TIMESTAMP);
const timestamp = dayjs().format(FILENAME_TIMESTAMP);
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
name: `Imported from JSON Resume (${timestamp})`,
slug: `imported-from-json-resume-${timestamp}`,
@ -611,7 +608,7 @@ export class IntegrationsService {
const resume: Partial<Resume> = cloneDeep(jsonResume);
// Metadata
const timestamp = dayjs().utc().format(FILENAME_TIMESTAMP);
const timestamp = dayjs().format(FILENAME_TIMESTAMP);
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
name: `Imported from Reactive Resume (${timestamp})`,
slug: `imported-from-reactive-resume-${timestamp}`,
@ -632,7 +629,7 @@ export class IntegrationsService {
const resume: Partial<Resume> = cloneDeep(defaultState);
// Metadata
const timestamp = dayjs().utc().format(FILENAME_TIMESTAMP);
const timestamp = dayjs().format(FILENAME_TIMESTAMP);
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
name: `Imported from Reactive Resume V2 (${timestamp})`,
slug: `imported-from-reactive-resume-v2-${timestamp}`,
@ -958,6 +955,6 @@ export class IntegrationsService {
}
private parseDate = (date: string): string => {
return isEmpty(date) ? '' : dayjs(date).utc().toISOString();
return isEmpty(date) ? '' : dayjs(date).toISOString();
};
}

View File

@ -31,10 +31,8 @@ export class PrinterService implements OnModuleInit, OnModuleDestroy {
const publicUrl = `${serverUrl}/assets/exports/${filename}`;
try {
// check if file already exists
await access(join(directory, filename));
} catch {
// delete old files and scheduler jobs
const activeSchedulerTimeouts = this.schedulerRegistry.getTimeouts();
await readdir(directory).then(async (files) => {
await Promise.all(
@ -49,7 +47,6 @@ export class PrinterService implements OnModuleInit, OnModuleDestroy {
);
});
// create file as it doesn't exist
const url = this.configService.get<string>('app.url');
const secretKey = this.configService.get<string>('app.secretKey');
const pdfDeletionTime = this.configService.get<number>('cache.pdfDeletionTime');
@ -57,8 +54,8 @@ export class PrinterService implements OnModuleInit, OnModuleDestroy {
const page = await this.browser.newPage();
await page.goto(`${url}/${username}/${slug}/printer?secretKey=${secretKey}`);
await page.waitForLoadState('networkidle');
await page.waitForSelector('html.wf-active');
await page.waitForLoadState("networkidle")
const pageFormat: PageConfig['format'] = await page.$$eval(
'[data-page]',

View File

@ -5,9 +5,9 @@ const sampleData: Partial<Resume> = {
name: 'Alexis Jones',
email: 'alexis.jones@gmail.com',
phone: '+1 800 1200 3820',
birthdate: '1995-08-06T00:00:00.000Z',
birthdate: '1995-08-06',
photo: {
url: `/images/sample-photo.jpg`,
url: `https://i.imgur.com/O7iT9ke.jpg`,
filters: {
size: 128,
shape: 'rounded-square',
@ -53,7 +53,7 @@ const sampleData: Partial<Resume> = {
url: 'https://www.espritcam.com',
date: {
end: '',
start: '2015-09-01T16:34:27.000Z',
start: '2015-09-01',
},
name: 'DP Technology Corp.',
summary:
@ -64,8 +64,8 @@ const sampleData: Partial<Resume> = {
id: '285d78f8-df56-4569-ba6b-cff5ebe5381e',
url: 'https://www.vokophone.com',
date: {
end: '2015-07-31T22:00:00.000Z',
start: '2011-05-31T22:00:00.000Z',
end: '2015-07-31',
start: '2011-05-31',
},
name: 'Voko Communications',
summary:
@ -84,7 +84,7 @@ const sampleData: Partial<Resume> = {
{
title: 'Blitz Hackathon',
awarder: '2nd Place',
date: '2018-03-31T22:00:00.000Z',
date: '2018-03-31',
url: '',
summary: '',
id: '657cadb0-c07d-4a35-8351-9079598c7ac0',
@ -92,7 +92,7 @@ const sampleData: Partial<Resume> = {
{
title: 'Carl-Zeiss Hackathon',
awarder: '2nd Place',
date: '2017-05-09T22:00:00.000Z',
date: '2017-05-09',
url: '',
summary: '',
id: 'db3bc5cb-483e-4221-9867-9c28ee5f2051',

View File

@ -71,6 +71,12 @@ export class ResumeController {
return this.resumeService.update(+id, updateResumeDto, userId);
}
@UseGuards(JwtAuthGuard)
@Delete('/all')
removeAllByUser(@User('id') userId: number) {
return this.resumeService.removeAllByUser(userId);
}
@UseGuards(JwtAuthGuard)
@Delete(':id')
remove(@Param('id') id: string, @User('id') userId: number) {

View File

@ -176,8 +176,12 @@ export class ResumeService {
return this.resumeRepository.save<Resume>(updatedResume);
}
async remove(id: number, userId: number) {
await this.resumeRepository.delete({ id, user: { id: userId } });
remove(id: number, userId: number) {
return this.resumeRepository.delete({ id, user: { id: userId } });
}
removeAllByUser(userId: number) {
return this.resumeRepository.delete({ user: { id: userId } });
}
async duplicate(id: number, userId: number) {