feat(all): upgrade to v3.4.0
This commit is contained in:
parent
ccfb4d3cb0
commit
87d381fe8e
|
@ -1,4 +1,4 @@
|
||||||
ARG VARIANT="16-bullseye"
|
ARG VARIANT="lts-bullseye"
|
||||||
|
|
||||||
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
|
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||||
|
|
||||||
|
### [3.4.0](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.3.3...v3.4.0) (2022-04-30)
|
||||||
|
|
||||||
### [3.3.4](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.3.3...v3.3.4) (2022-04-09)
|
### [3.3.4](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.3.3...v3.3.4) (2022-04-09)
|
||||||
|
|
||||||
### [3.3.3](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.3.2...v3.3.3) (2022-04-09)
|
### [3.3.3](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.3.2...v3.3.3) (2022-04-09)
|
||||||
|
|
28
README.md
28
README.md
|
@ -17,16 +17,20 @@ You have complete control over what goes into your resume, how it looks, what co
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [Features](#features)
|
- [Reactive Resume](#reactive-resume)
|
||||||
- [Languages](#languages)
|
- [Go to App | [Docs](https://docs.rxresu.me)](#go-to-app--docs)
|
||||||
- [Tutorial](#tutorial)
|
- [Table of Contents](#table-of-contents)
|
||||||
- [Build from Source](#build-from-source)
|
- [Features](#features)
|
||||||
- [Contributing](#contributing)
|
- [Languages](#languages)
|
||||||
- [Report Bugs and Feature Requests](#report-bugs-and-feature-requests)
|
- [Tutorial](#tutorial)
|
||||||
- [Donations](#donations)
|
- [Build from Source](#build-from-source)
|
||||||
- [Infrastructure](#infrastructure)
|
- [Contributing](#contributing)
|
||||||
- [Contributors Wall](#contributors-wall)
|
- [Report Bugs and Feature Requests](#report-bugs-and-feature-requests)
|
||||||
- [License](#license)
|
- [Donations](#donations)
|
||||||
|
- [💸 PayPal](#-paypal)
|
||||||
|
- [Infrastructure](#infrastructure)
|
||||||
|
- [Contributors Wall](#contributors-wall)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
@ -53,19 +57,23 @@ You have complete control over what goes into your resume, how it looks, what co
|
||||||
- Arabic (اَلْعَرَبِيَّةُ)
|
- Arabic (اَلْعَرَبِيَّةُ)
|
||||||
- Bengali (বাংলা)
|
- Bengali (বাংলা)
|
||||||
- Chinese (中文)
|
- Chinese (中文)
|
||||||
|
- Czech (čeština)
|
||||||
- Danish (Dansk)
|
- Danish (Dansk)
|
||||||
- Dutch (Nederlands)
|
- Dutch (Nederlands)
|
||||||
- English
|
- English
|
||||||
- French (Français)
|
- French (Français)
|
||||||
- German (Deutsch)
|
- German (Deutsch)
|
||||||
|
- Greek (Ελληνικά)
|
||||||
- Hindi (हिन्दी)
|
- Hindi (हिन्दी)
|
||||||
- Italian (Italiano)
|
- Italian (Italiano)
|
||||||
- Kannada (ಕನ್ನಡ)
|
- Kannada (ಕನ್ನಡ)
|
||||||
- Malayalam (മലയാളം)
|
- Malayalam (മലയാളം)
|
||||||
|
- Odia (ଓଡ଼ିଆ)
|
||||||
- Polish (Polski)
|
- Polish (Polski)
|
||||||
- Portuguese (Português)
|
- Portuguese (Português)
|
||||||
- Russian (русский)
|
- Russian (русский)
|
||||||
- Spanish (Español)
|
- Spanish (Español)
|
||||||
|
- Swedish (Svenska)
|
||||||
- Tamil (தமிழ்)
|
- Tamil (தமிழ்)
|
||||||
- Turkish (Türkçe)
|
- Turkish (Türkçe)
|
||||||
- Vietnamese (Tiếng Việt)
|
- Vietnamese (Tiếng Việt)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM node:17-alpine as dependencies
|
FROM node:lts-alpine as dependencies
|
||||||
|
|
||||||
RUN apk add --no-cache curl g++ make python3 \
|
RUN apk add --no-cache curl g++ make python3 \
|
||||||
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
||||||
|
@ -11,7 +11,7 @@ COPY ./client/package.json ./client/package.json
|
||||||
|
|
||||||
RUN pnpm install --frozen-lockfile
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
FROM node:17-alpine as builder
|
FROM node:lts-alpine as builder
|
||||||
|
|
||||||
RUN apk add --no-cache curl g++ make python3 \
|
RUN apk add --no-cache curl g++ make python3 \
|
||||||
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
||||||
|
@ -27,7 +27,7 @@ COPY --from=dependencies /app/client/node_modules ./client/node_modules
|
||||||
RUN pnpm run build:schema
|
RUN pnpm run build:schema
|
||||||
RUN pnpm run build:client
|
RUN pnpm run build:client
|
||||||
|
|
||||||
FROM node:17-alpine as production
|
FROM node:lts-alpine as production
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
|
@ -81,14 +81,14 @@ const LeftSidebar = () => {
|
||||||
arrow
|
arrow
|
||||||
key={id}
|
key={id}
|
||||||
placement="right"
|
placement="right"
|
||||||
title={get(sections, `${id}.name`, t<string>(`builder.leftSidebar.sections.${id}.heading`))}
|
title={get(sections, `${id}.name`, t<string>(`builder.leftSidebar.sections.${id}.heading`)) as string}
|
||||||
>
|
>
|
||||||
<IconButton onClick={() => handleClick(id)}>{icon}</IconButton>
|
<IconButton onClick={() => handleClick(id)}>{icon}</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{customSections.map(({ id }) => (
|
{customSections.map(({ id }) => (
|
||||||
<Tooltip key={id} title={get(sections, `${id}.name`, '')} placement="right" arrow>
|
<Tooltip key={id} title={get(sections, `${id}.name`, '') as string} placement="right" arrow>
|
||||||
<IconButton onClick={() => handleClick(id)}>
|
<IconButton onClick={() => handleClick(id)}>
|
||||||
<Star />
|
<Star />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
|
@ -57,6 +57,12 @@ const Basics = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ResumeInput
|
||||||
|
type="date"
|
||||||
|
label={t<string>('builder.leftSidebar.sections.basics.birthdate.label')}
|
||||||
|
path="basics.birthdate"
|
||||||
|
className="sm:col-span-2"
|
||||||
|
/>
|
||||||
<ResumeInput
|
<ResumeInput
|
||||||
label={t<string>('builder.common.form.email.label')}
|
label={t<string>('builder.common.form.email.label')}
|
||||||
path="basics.email"
|
path="basics.email"
|
||||||
|
|
|
@ -117,7 +117,7 @@ const Layout = () => {
|
||||||
[styles.disabled]: !get(resumeSections, `${sectionId}.visible`, true),
|
[styles.disabled]: !get(resumeSections, `${sectionId}.visible`, true),
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{get(resumeSections, `${sectionId}.name`, '')}
|
{get(resumeSections, `${sectionId}.name`, '') as string}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -5,11 +5,12 @@ import { useRouter } from 'next/router';
|
||||||
import styles from './BaseModal.module.scss';
|
import styles from './BaseModal.module.scss';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
icon?: React.ReactNode;
|
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
heading: string;
|
heading: string;
|
||||||
handleClose: () => void;
|
icon?: React.ReactNode;
|
||||||
|
children?: React.ReactNode;
|
||||||
footerChildren?: React.ReactNode;
|
footerChildren?: React.ReactNode;
|
||||||
|
handleClose: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const BaseModal: React.FC<Props> = ({ icon, isOpen, heading, children, handleClose, footerChildren }) => {
|
const BaseModal: React.FC<Props> = ({ icon, isOpen, heading, children, handleClose, footerChildren }) => {
|
||||||
|
|
|
@ -76,6 +76,7 @@ const List: React.FC<Props> = ({
|
||||||
return (
|
return (
|
||||||
<ListItem
|
<ListItem
|
||||||
key={item.id}
|
key={item.id}
|
||||||
|
path={path}
|
||||||
item={item}
|
item={item}
|
||||||
index={index}
|
index={index}
|
||||||
title={title}
|
title={title}
|
||||||
|
|
|
@ -17,6 +17,7 @@ interface DragItem {
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
item: ListItemType;
|
item: ListItemType;
|
||||||
|
path: string;
|
||||||
index: number;
|
index: number;
|
||||||
title: string;
|
title: string;
|
||||||
subtitle?: string;
|
subtitle?: string;
|
||||||
|
@ -26,14 +27,14 @@ type Props = {
|
||||||
onDuplicate?: (item: ListItemType) => void;
|
onDuplicate?: (item: ListItemType) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ListItem: React.FC<Props> = ({ item, index, title, subtitle, onMove, onEdit, onDelete, onDuplicate }) => {
|
const ListItem: React.FC<Props> = ({ item, path, index, title, subtitle, onMove, onEdit, onDelete, onDuplicate }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const [anchorEl, setAnchorEl] = useState<Element | null>(null);
|
const [anchorEl, setAnchorEl] = useState<Element | null>(null);
|
||||||
|
|
||||||
const [{ handlerId }, drop] = useDrop<DragItem, any, any>({
|
const [{ handlerId }, drop] = useDrop<DragItem, any, any>({
|
||||||
accept: 'ListItem',
|
accept: path,
|
||||||
collect(monitor) {
|
collect(monitor) {
|
||||||
return { handlerId: monitor.getHandlerId() };
|
return { handlerId: monitor.getHandlerId() };
|
||||||
},
|
},
|
||||||
|
@ -68,7 +69,7 @@ const ListItem: React.FC<Props> = ({ item, index, title, subtitle, onMove, onEdi
|
||||||
});
|
});
|
||||||
|
|
||||||
const [{ isDragging }, drag] = useDrag({
|
const [{ isDragging }, drag] = useDrag({
|
||||||
type: 'ListItem',
|
type: path,
|
||||||
item: () => {
|
item: () => {
|
||||||
return { id: item.id, index };
|
return { id: item.id, index };
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
import dynamic from 'next/dynamic';
|
|
||||||
|
|
||||||
const NoSSR: React.FC = ({ children }) => <>{children}</>;
|
|
||||||
|
|
||||||
export default dynamic(() => Promise.resolve(NoSSR), { ssr: false });
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
import { DatePicker } from '@mui/lab';
|
||||||
import { TextField } from '@mui/material';
|
import { TextField } from '@mui/material';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
@ -8,7 +11,7 @@ import { setResumeState } from '@/store/resume/resumeSlice';
|
||||||
import MarkdownSupported from './MarkdownSupported';
|
import MarkdownSupported from './MarkdownSupported';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
type?: 'text' | 'textarea';
|
type?: 'text' | 'textarea' | 'date';
|
||||||
label: string;
|
label: string;
|
||||||
path: string;
|
path: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -31,6 +34,11 @@ const ResumeInput: React.FC<Props> = ({ type = 'text', label, path, className, m
|
||||||
dispatch(setResumeState({ path, value: event.target.value }));
|
dispatch(setResumeState({ path, value: event.target.value }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onChangeValue = (value: string) => {
|
||||||
|
setValue(value);
|
||||||
|
dispatch(setResumeState({ path, value }));
|
||||||
|
};
|
||||||
|
|
||||||
if (type === 'textarea') {
|
if (type === 'textarea') {
|
||||||
return (
|
return (
|
||||||
<TextField
|
<TextField
|
||||||
|
@ -45,6 +53,22 @@ const ResumeInput: React.FC<Props> = ({ type = 'text', label, path, className, m
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === 'date') {
|
||||||
|
return (
|
||||||
|
<DatePicker
|
||||||
|
openTo="year"
|
||||||
|
label={label}
|
||||||
|
value={value}
|
||||||
|
views={['year', 'month', 'day']}
|
||||||
|
renderInput={(params) => <TextField {...params} error={false} className={className} />}
|
||||||
|
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||||
|
isEmpty(keyboardInputValue) && onChangeValue('');
|
||||||
|
date && dayjs(date).isValid() && onChangeValue(date.toISOString());
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return <TextField type={type} label={label} value={value} onChange={onChange} className={className} />;
|
return <TextField type={type} label={label} value={value} onChange={onChange} className={className} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,10 @@ export type Language = {
|
||||||
export const languages: Language[] = [
|
export const languages: Language[] = [
|
||||||
{ code: 'ar', name: 'Arabic', localName: 'اَلْعَرَبِيَّةُ' },
|
{ code: 'ar', name: 'Arabic', localName: 'اَلْعَرَبِيَّةُ' },
|
||||||
{ code: 'bn', name: 'Bengali', localName: 'বাংলা' },
|
{ code: 'bn', name: 'Bengali', localName: 'বাংলা' },
|
||||||
|
{ code: 'cs', name: 'Czech', localName: 'čeština' },
|
||||||
{ code: 'da', name: 'Danish', localName: 'Dansk' },
|
{ code: 'da', name: 'Danish', localName: 'Dansk' },
|
||||||
{ code: 'de', name: 'German', localName: 'Deutsch' },
|
{ code: 'de', name: 'German', localName: 'Deutsch' },
|
||||||
|
{ code: 'el', name: 'Greek', localName: 'Ελληνικά' },
|
||||||
{ code: 'en', name: 'English' },
|
{ code: 'en', name: 'English' },
|
||||||
{ code: 'es', name: 'Spanish', localName: 'Español' },
|
{ code: 'es', name: 'Spanish', localName: 'Español' },
|
||||||
{ code: 'fr', name: 'French', localName: 'Français' },
|
{ code: 'fr', name: 'French', localName: 'Français' },
|
||||||
|
@ -17,9 +19,11 @@ export const languages: Language[] = [
|
||||||
{ code: 'kn', name: 'Kannada', localName: 'ಕನ್ನಡ' },
|
{ code: 'kn', name: 'Kannada', localName: 'ಕನ್ನಡ' },
|
||||||
{ code: 'ml', name: 'Malayalam', localName: 'മലയാളം' },
|
{ code: 'ml', name: 'Malayalam', localName: 'മലയാളം' },
|
||||||
{ code: 'nl', name: 'Dutch', localName: 'Nederlands' },
|
{ code: 'nl', name: 'Dutch', localName: 'Nederlands' },
|
||||||
|
{ code: 'or', name: 'Odia', localName: 'ଓଡ଼ିଆ' },
|
||||||
{ code: 'pl', name: 'Polish', localName: 'Polski' },
|
{ code: 'pl', name: 'Polish', localName: 'Polski' },
|
||||||
{ code: 'pt', name: 'Portuguese', localName: 'Português' },
|
{ code: 'pt', name: 'Portuguese', localName: 'Português' },
|
||||||
{ code: 'ru', name: 'Russian', localName: 'русский' },
|
{ code: 'ru', name: 'Russian', localName: 'русский' },
|
||||||
|
{ code: 'sv', name: 'Swedish', localName: 'Svenska' },
|
||||||
{ code: 'ta', name: 'Tamil', localName: 'தமிழ்' },
|
{ code: 'ta', name: 'Tamil', localName: 'தமிழ்' },
|
||||||
{ code: 'tr', name: 'Turkish', localName: 'Türkçe' },
|
{ code: 'tr', name: 'Turkish', localName: 'Türkçe' },
|
||||||
{ code: 'vi', name: 'Vietnamese', localName: 'Tiếng Việt' },
|
{ code: 'vi', name: 'Vietnamese', localName: 'Tiếng Việt' },
|
||||||
|
|
|
@ -6,7 +6,6 @@ import Joi from 'joi';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import { Trans, useTranslation } from 'next-i18next';
|
import { Trans, useTranslation } from 'next-i18next';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { GoogleLoginResponse, GoogleLoginResponseOffline, useGoogleLogin } from 'react-google-login';
|
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { useIsMutating, useMutation } from 'react-query';
|
import { useIsMutating, useMutation } from 'react-query';
|
||||||
|
@ -18,6 +17,8 @@ import { ServerError } from '@/services/axios';
|
||||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||||
import { setModalState } from '@/store/modal/modalSlice';
|
import { setModalState } from '@/store/modal/modalSlice';
|
||||||
|
|
||||||
|
declare const google: any;
|
||||||
|
|
||||||
type FormData = {
|
type FormData = {
|
||||||
identifier: string;
|
identifier: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
@ -56,15 +57,6 @@ const LoginModal: React.FC = () => {
|
||||||
loginWithGoogle
|
loginWithGoogle
|
||||||
);
|
);
|
||||||
|
|
||||||
const { signIn } = useGoogleLogin({
|
|
||||||
clientId: env('GOOGLE_CLIENT_ID'),
|
|
||||||
onSuccess: async (response: GoogleLoginResponse | GoogleLoginResponseOffline) => {
|
|
||||||
await loginWithGoogleMutation({ accessToken: (response as GoogleLoginResponse).accessToken });
|
|
||||||
|
|
||||||
handleClose();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
dispatch(setModalState({ modal: 'auth.login', state: { open: false } }));
|
dispatch(setModalState({ modal: 'auth.login', state: { open: false } }));
|
||||||
reset();
|
reset();
|
||||||
|
@ -93,8 +85,18 @@ const LoginModal: React.FC = () => {
|
||||||
dispatch(setModalState({ modal: 'auth.forgot', state: { open: true } }));
|
dispatch(setModalState({ modal: 'auth.forgot', state: { open: true } }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLoginWithGoogle = () => {
|
const handleLoginWithGoogle = async () => {
|
||||||
signIn();
|
google.accounts.id.initialize({
|
||||||
|
client_id: env('GOOGLE_CLIENT_ID'),
|
||||||
|
callback: async (response: any) => {
|
||||||
|
await loginWithGoogleMutation({ credential: response.credential });
|
||||||
|
|
||||||
|
handleClose();
|
||||||
|
},
|
||||||
|
auto_select: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
google.accounts.id.prompt();
|
||||||
};
|
};
|
||||||
|
|
||||||
const PasswordVisibility = (): React.ReactElement => {
|
const PasswordVisibility = (): React.ReactElement => {
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { Button, TextField } from '@mui/material';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import { Trans, useTranslation } from 'next-i18next';
|
import { Trans, useTranslation } from 'next-i18next';
|
||||||
import { GoogleLoginResponse, GoogleLoginResponseOffline, useGoogleLogin } from 'react-google-login';
|
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import { useMutation } from 'react-query';
|
import { useMutation } from 'react-query';
|
||||||
|
|
||||||
|
@ -15,6 +14,8 @@ import { ServerError } from '@/services/axios';
|
||||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||||
import { setModalState } from '@/store/modal/modalSlice';
|
import { setModalState } from '@/store/modal/modalSlice';
|
||||||
|
|
||||||
|
declare const google: any;
|
||||||
|
|
||||||
type FormData = {
|
type FormData = {
|
||||||
name: string;
|
name: string;
|
||||||
username: string;
|
username: string;
|
||||||
|
@ -63,15 +64,6 @@ const RegisterModal: React.FC = () => {
|
||||||
loginWithGoogle
|
loginWithGoogle
|
||||||
);
|
);
|
||||||
|
|
||||||
const { signIn } = useGoogleLogin({
|
|
||||||
clientId: env('GOOGLE_CLIENT_ID'),
|
|
||||||
onSuccess: async (response: GoogleLoginResponse | GoogleLoginResponseOffline) => {
|
|
||||||
await loginWithGoogleMutation({ accessToken: (response as GoogleLoginResponse).accessToken });
|
|
||||||
|
|
||||||
handleClose();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
dispatch(setModalState({ modal: 'auth.register', state: { open: false } }));
|
dispatch(setModalState({ modal: 'auth.register', state: { open: false } }));
|
||||||
reset();
|
reset();
|
||||||
|
@ -87,8 +79,18 @@ const RegisterModal: React.FC = () => {
|
||||||
dispatch(setModalState({ modal: 'auth.login', state: { open: true } }));
|
dispatch(setModalState({ modal: 'auth.login', state: { open: true } }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLoginWithGoogle = () => {
|
const handleLoginWithGoogle = async () => {
|
||||||
signIn();
|
google.accounts.id.initialize({
|
||||||
|
client_id: env('GOOGLE_CLIENT_ID'),
|
||||||
|
callback: async (response: any) => {
|
||||||
|
await loginWithGoogleMutation({ credential: response.credential });
|
||||||
|
|
||||||
|
handleClose();
|
||||||
|
},
|
||||||
|
auto_select: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
google.accounts.id.prompt();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -13,67 +13,66 @@
|
||||||
"@emotion/react": "^11.9.0",
|
"@emotion/react": "^11.9.0",
|
||||||
"@emotion/styled": "^11.8.1",
|
"@emotion/styled": "^11.8.1",
|
||||||
"@hookform/resolvers": "2.8.8",
|
"@hookform/resolvers": "2.8.8",
|
||||||
"@monaco-editor/react": "^4.4.1",
|
"@monaco-editor/react": "^4.4.4",
|
||||||
"@mui/icons-material": "^5.6.0",
|
"@mui/icons-material": "^5.6.2",
|
||||||
"@mui/lab": "^5.0.0-alpha.76",
|
"@mui/lab": "^5.0.0-alpha.79",
|
||||||
"@mui/material": "^5.6.0",
|
"@mui/material": "^5.6.3",
|
||||||
"@reduxjs/toolkit": "^1.8.1",
|
"@reduxjs/toolkit": "^1.8.1",
|
||||||
"axios": "^0.26.1",
|
"axios": "^0.27.2",
|
||||||
"clsx": "^1.1.1",
|
"clsx": "^1.1.1",
|
||||||
"dayjs": "^1.11.0",
|
"dayjs": "^1.11.1",
|
||||||
"downloadjs": "^1.4.7",
|
"downloadjs": "^1.4.7",
|
||||||
"joi": "^17.6.0",
|
"joi": "^17.6.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"md5-hex": "^4.0.0",
|
"md5-hex": "^4.0.0",
|
||||||
"monaco-editor": "^0.33.0",
|
"monaco-editor": "^0.33.0",
|
||||||
"nanoid": "^3.3.2",
|
"nanoid": "^3.3.3",
|
||||||
"next": "12.1.4",
|
"next": "12.1.5",
|
||||||
"next-i18next": "^11.0.0",
|
"next-i18next": "^11.0.0",
|
||||||
"react": "<18",
|
"react": "^18",
|
||||||
"react-beautiful-dnd": "^13.1.0",
|
"react-beautiful-dnd": "^13.1.0",
|
||||||
"react-colorful": "^5.5.1",
|
"react-colorful": "^5.5.1",
|
||||||
"react-dnd": "^15.1.2",
|
"react-dnd": "^16.0.1",
|
||||||
"react-dnd-html5-backend": "^15.1.2",
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-dom": "<18",
|
"react-dom": "^18",
|
||||||
"react-google-login": "^5.2.2",
|
"react-hook-form": "^7.30.0",
|
||||||
"react-hook-form": "^7.29.0",
|
|
||||||
"react-hot-toast": "2.2.0",
|
"react-hot-toast": "2.2.0",
|
||||||
"react-hotkeys-hook": "^3.4.4",
|
"react-hotkeys-hook": "^3.4.4",
|
||||||
"react-icons": "^4.3.1",
|
"react-icons": "^4.3.1",
|
||||||
"react-markdown": "^8.0.2",
|
"react-markdown": "^8.0.3",
|
||||||
"react-query": "^3.34.19",
|
"react-query": "^3.38.0",
|
||||||
"react-redux": "^7.2.8",
|
"react-redux": "^8.0.1",
|
||||||
"react-zoom-pan-pinch": "^2.1.3",
|
"react-zoom-pan-pinch": "^2.1.3",
|
||||||
"redux": "^4.1.2",
|
"redux": "^4.2.0",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"redux-saga": "^1.1.3",
|
"redux-saga": "^1.1.3",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
"sharp": "^0.30.3",
|
"sharp": "^0.30.4",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"webfontloader": "^1.6.28"
|
"webfontloader": "^1.6.28"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.17.9",
|
"@babel/core": "^7.17.10",
|
||||||
"@reactive-resume/schema": "workspace:*",
|
"@reactive-resume/schema": "workspace:*",
|
||||||
"@tailwindcss/typography": "^0.5.2",
|
"@tailwindcss/typography": "^0.5.2",
|
||||||
"@types/downloadjs": "^1.4.3",
|
"@types/downloadjs": "^1.4.3",
|
||||||
"@types/lodash": "^4.14.181",
|
"@types/lodash": "^4.14.182",
|
||||||
"@types/node": "17.0.23",
|
"@types/node": "17.0.30",
|
||||||
"@types/react": "<18",
|
"@types/react": "^18",
|
||||||
"@types/react-beautiful-dnd": "^13.1.2",
|
"@types/react-beautiful-dnd": "^13.1.2",
|
||||||
"@types/react-redux": "^7.1.23",
|
"@types/react-redux": "^7.1.24",
|
||||||
"@types/tailwindcss": "^3.0.10",
|
"@types/tailwindcss": "^3.0.10",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"@types/webfontloader": "^1.6.34",
|
"@types/webfontloader": "^1.6.34",
|
||||||
"autoprefixer": "^10.4.4",
|
"autoprefixer": "^10.4.5",
|
||||||
"csstype": "^3.0.11",
|
"csstype": "^3.0.11",
|
||||||
"eslint": "^8.12.0",
|
"eslint": "^8.14.0",
|
||||||
"eslint-config-next": "12.1.4",
|
"eslint-config-next": "12.1.5",
|
||||||
"next-sitemap": "^2.5.19",
|
"next-sitemap": "^2.5.20",
|
||||||
"postcss": "^8.4.12",
|
"postcss": "^8.4.13",
|
||||||
"prettier": "^2.6.2",
|
"prettier": "^2.6.2",
|
||||||
"sass": "^1.50.0",
|
"sass": "^1.51.0",
|
||||||
"tailwindcss": "^3.0.23",
|
"tailwindcss": "^3.0.24",
|
||||||
"typescript": "<4.6.0"
|
"typescript": "^4.6.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import DateAdapter from '@mui/lab/AdapterDayjs';
|
||||||
import LocalizationProvider from '@mui/lab/LocalizationProvider';
|
import LocalizationProvider from '@mui/lab/LocalizationProvider';
|
||||||
import type { AppProps } from 'next/app';
|
import type { AppProps } from 'next/app';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
|
import Script from 'next/script';
|
||||||
import { appWithTranslation } from 'next-i18next';
|
import { appWithTranslation } from 'next-i18next';
|
||||||
import { Toaster } from 'react-hot-toast';
|
import { Toaster } from 'react-hot-toast';
|
||||||
import { QueryClientProvider } from 'react-query';
|
import { QueryClientProvider } from 'react-query';
|
||||||
|
@ -52,6 +53,8 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
|
||||||
</PersistGate>
|
</PersistGate>
|
||||||
</LocalizationProvider>
|
</LocalizationProvider>
|
||||||
</ReduxProvider>
|
</ReduxProvider>
|
||||||
|
|
||||||
|
<Script src="https://accounts.google.com/gsi/client" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { DarkMode, LightMode, Link as LinkIcon } from '@mui/icons-material';
|
import { DarkMode, LightMode, Link as LinkIcon } from '@mui/icons-material';
|
||||||
import { Masonry } from '@mui/lab';
|
import { Masonry } from '@mui/lab';
|
||||||
import { Button, IconButton } from '@mui/material';
|
import { Button, IconButton, NoSsr } from '@mui/material';
|
||||||
import type { GetStaticProps, NextPage } from 'next';
|
import type { GetStaticProps, NextPage } from 'next';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
@ -11,7 +11,6 @@ import Testimony from '@/components/landing/Testimony';
|
||||||
import Footer from '@/components/shared/Footer';
|
import Footer from '@/components/shared/Footer';
|
||||||
import LanguageSwitcher from '@/components/shared/LanguageSwitcher';
|
import LanguageSwitcher from '@/components/shared/LanguageSwitcher';
|
||||||
import Logo from '@/components/shared/Logo';
|
import Logo from '@/components/shared/Logo';
|
||||||
import NoSSR from '@/components/shared/NoSSR';
|
|
||||||
import { screenshots } from '@/config/screenshots';
|
import { screenshots } from '@/config/screenshots';
|
||||||
import { FLAG_DISABLE_SIGNUPS } from '@/constants/flags';
|
import { FLAG_DISABLE_SIGNUPS } from '@/constants/flags';
|
||||||
import testimonials from '@/data/testimonials';
|
import testimonials from '@/data/testimonials';
|
||||||
|
@ -59,7 +58,7 @@ const Home: NextPage = () => {
|
||||||
|
|
||||||
<h2>{t<string>('common.subtitle')}</h2>
|
<h2>{t<string>('common.subtitle')}</h2>
|
||||||
|
|
||||||
<NoSSR>
|
<NoSsr>
|
||||||
<div className={styles.buttonWrapper}>
|
<div className={styles.buttonWrapper}>
|
||||||
{isLoggedIn ? (
|
{isLoggedIn ? (
|
||||||
<>
|
<>
|
||||||
|
@ -81,7 +80,7 @@ const Home: NextPage = () => {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</NoSSR>
|
</NoSsr>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -119,6 +119,9 @@
|
||||||
"name": {
|
"name": {
|
||||||
"label": "Full Name"
|
"label": "Full Name"
|
||||||
},
|
},
|
||||||
|
"birthdate": {
|
||||||
|
"label": "Date of Birth"
|
||||||
|
},
|
||||||
"photo-filters": {
|
"photo-filters": {
|
||||||
"effects": {
|
"effects": {
|
||||||
"border": {
|
"border": {
|
||||||
|
|
|
@ -1,43 +1,43 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
|
||||||
<url><loc>https://rxresu.me</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.972Z</lastmod></url>
|
<url><loc>https://rxresu.me</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.972Z</lastmod></url>
|
<url><loc>https://rxresu.me/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/meta/privacy</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.972Z</lastmod></url>
|
<url><loc>https://rxresu.me/meta/privacy</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/meta/service</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.972Z</lastmod></url>
|
<url><loc>https://rxresu.me/meta/service</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/ar/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.972Z</lastmod></url>
|
<url><loc>https://rxresu.me/ar/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/bn/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.972Z</lastmod></url>
|
<url><loc>https://rxresu.me/bn/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/da/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.972Z</lastmod></url>
|
<url><loc>https://rxresu.me/da/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/de/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.972Z</lastmod></url>
|
<url><loc>https://rxresu.me/de/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/es/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.972Z</lastmod></url>
|
<url><loc>https://rxresu.me/es/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/fr/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.972Z</lastmod></url>
|
<url><loc>https://rxresu.me/fr/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/hi/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.972Z</lastmod></url>
|
<url><loc>https://rxresu.me/hi/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/it/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.972Z</lastmod></url>
|
<url><loc>https://rxresu.me/it/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/kn/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.972Z</lastmod></url>
|
<url><loc>https://rxresu.me/kn/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/ml/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/ml/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/nl/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/nl/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/pl/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/pl/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/pt/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/pt/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/ru/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/ru/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/ta/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/ta/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/tr/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/tr/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/vi/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/vi/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/zh/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/zh/dashboard</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/ar</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/ar</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/bn</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/bn</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/da</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/da</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/de</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/de</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/es</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/es</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/fr</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/fr</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/hi</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/hi</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/it</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/it</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/kn</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/kn</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/ml</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/ml</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/nl</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/nl</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/pl</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/pl</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/pt</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/pt</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/ru</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/ru</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/ta</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/ta</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/tr</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/tr</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/vi</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/vi</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
<url><loc>https://rxresu.me/zh</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-08T08:32:21.973Z</lastmod></url>
|
<url><loc>https://rxresu.me/zh</loc><changefreq>monthly</changefreq><priority>0.7</priority><lastmod>2022-04-30T10:56:16.825Z</lastmod></url>
|
||||||
</urlset>
|
</urlset>
|
|
@ -13,7 +13,7 @@ export type LoginParams = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LoginWithGoogleParams = {
|
export type LoginWithGoogleParams = {
|
||||||
accessToken: string;
|
credential: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RegisterParams = {
|
export type RegisterParams = {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import _axios, { AxiosError } from 'axios';
|
import _axios from 'axios';
|
||||||
import Router from 'next/router';
|
import Router from 'next/router';
|
||||||
|
|
||||||
import { logout } from '@/store/auth/authSlice';
|
import { logout } from '@/store/auth/authSlice';
|
||||||
|
@ -27,7 +27,7 @@ axios.interceptors.request.use((config) => {
|
||||||
|
|
||||||
axios.interceptors.response.use(
|
axios.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
(error: AxiosError) => {
|
(error) => {
|
||||||
const { response } = error;
|
const { response } = error;
|
||||||
|
|
||||||
if (response) {
|
if (response) {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { useMemo } from 'react';
|
||||||
|
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
|
|
||||||
const Heading: React.FC = ({ children }) => {
|
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||||
const darkerPrimary = useMemo(() => darken(theme.primary, 0.2), [theme.primary]);
|
const darkerPrimary = useMemo(() => darken(theme.primary, 0.2), [theme.primary]);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { Email, Phone, Public, Room } from '@mui/icons-material';
|
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
|
||||||
import { Theme } from '@reactive-resume/schema';
|
import { Theme } from '@reactive-resume/schema';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
|
@ -9,12 +9,14 @@ import { useMemo } from 'react';
|
||||||
import Markdown from '@/components/shared/Markdown';
|
import Markdown from '@/components/shared/Markdown';
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
import DataDisplay from '@/templates/shared/DataDisplay';
|
import DataDisplay from '@/templates/shared/DataDisplay';
|
||||||
|
import { formatDateString } from '@/utils/date';
|
||||||
import getProfileIcon from '@/utils/getProfileIcon';
|
import getProfileIcon from '@/utils/getProfileIcon';
|
||||||
import { getContrastColor } from '@/utils/styles';
|
import { getContrastColor } from '@/utils/styles';
|
||||||
import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
||||||
|
|
||||||
export const MastheadSidebar: React.FC = () => {
|
export const MastheadSidebar: React.FC = () => {
|
||||||
const { name, headline, photo, email, phone, website, location, profiles } = useAppSelector(
|
const dateFormat: string = useAppSelector((state) => get(state.resume, 'metadata.date.format'));
|
||||||
|
const { name, headline, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
||||||
(state) => state.resume.basics
|
(state) => state.resume.basics
|
||||||
);
|
);
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||||
|
@ -43,6 +45,10 @@ export const MastheadSidebar: React.FC = () => {
|
||||||
{formatLocation(location)}
|
{formatLocation(location)}
|
||||||
</DataDisplay>
|
</DataDisplay>
|
||||||
|
|
||||||
|
<DataDisplay icon={<Cake />} className="!gap-2 text-xs">
|
||||||
|
{formatDateString(birthdate, dateFormat)}
|
||||||
|
</DataDisplay>
|
||||||
|
|
||||||
<DataDisplay icon={<Email />} className="!gap-2 text-xs" link={`mailto:${email}`}>
|
<DataDisplay icon={<Email />} className="!gap-2 text-xs" link={`mailto:${email}`}>
|
||||||
{email}
|
{email}
|
||||||
</DataDisplay>
|
</DataDisplay>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import get from 'lodash/get';
|
||||||
|
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
|
|
||||||
const Heading: React.FC = ({ children }) => {
|
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { Email, Phone, Public, Room } from '@mui/icons-material';
|
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
|
||||||
import { alpha } from '@mui/material';
|
import { alpha } from '@mui/material';
|
||||||
import { Theme } from '@reactive-resume/schema';
|
import { Theme } from '@reactive-resume/schema';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
@ -10,12 +10,14 @@ import { useMemo } from 'react';
|
||||||
import Markdown from '@/components/shared/Markdown';
|
import Markdown from '@/components/shared/Markdown';
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
import DataDisplay from '@/templates/shared/DataDisplay';
|
import DataDisplay from '@/templates/shared/DataDisplay';
|
||||||
|
import { formatDateString } from '@/utils/date';
|
||||||
import getProfileIcon from '@/utils/getProfileIcon';
|
import getProfileIcon from '@/utils/getProfileIcon';
|
||||||
import { getContrastColor } from '@/utils/styles';
|
import { getContrastColor } from '@/utils/styles';
|
||||||
import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
||||||
|
|
||||||
export const MastheadSidebar: React.FC = () => {
|
export const MastheadSidebar: React.FC = () => {
|
||||||
const { name, headline, photo, email, phone, website, location, profiles } = useAppSelector(
|
const dateFormat: string = useAppSelector((state) => get(state.resume, 'metadata.date.format'));
|
||||||
|
const { name, headline, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
||||||
(state) => state.resume.basics
|
(state) => state.resume.basics
|
||||||
);
|
);
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||||
|
@ -44,6 +46,10 @@ export const MastheadSidebar: React.FC = () => {
|
||||||
{formatLocation(location)}
|
{formatLocation(location)}
|
||||||
</DataDisplay>
|
</DataDisplay>
|
||||||
|
|
||||||
|
<DataDisplay icon={<Cake />} className="!gap-2 text-xs">
|
||||||
|
{formatDateString(birthdate, dateFormat)}
|
||||||
|
</DataDisplay>
|
||||||
|
|
||||||
<DataDisplay icon={<Email />} className="!gap-2 text-xs" link={`mailto:${email}`}>
|
<DataDisplay icon={<Email />} className="!gap-2 text-xs" link={`mailto:${email}`}>
|
||||||
{email}
|
{email}
|
||||||
</DataDisplay>
|
</DataDisplay>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import get from 'lodash/get';
|
||||||
|
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
|
|
||||||
const Heading: React.FC = ({ children }) => {
|
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
import { Email, Phone, Public, Room } from '@mui/icons-material';
|
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
|
||||||
import Markdown from '@/components/shared/Markdown';
|
import Markdown from '@/components/shared/Markdown';
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
import DataDisplay from '@/templates/shared/DataDisplay';
|
import DataDisplay from '@/templates/shared/DataDisplay';
|
||||||
|
import { formatDateString } from '@/utils/date';
|
||||||
import getProfileIcon from '@/utils/getProfileIcon';
|
import getProfileIcon from '@/utils/getProfileIcon';
|
||||||
import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
||||||
|
|
||||||
export const MastheadSidebar: React.FC = () => {
|
export const MastheadSidebar: React.FC = () => {
|
||||||
|
const dateFormat: string = useAppSelector((state) => get(state.resume, 'metadata.date.format'));
|
||||||
const primaryColor: string = useAppSelector((state) => get(state.resume, 'metadata.theme.primary'));
|
const primaryColor: string = useAppSelector((state) => get(state.resume, 'metadata.theme.primary'));
|
||||||
const { name, headline, photo, email, phone, website, location, profiles } = useAppSelector(
|
const { name, headline, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
||||||
(state) => state.resume.basics
|
(state) => state.resume.basics
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -36,6 +38,10 @@ export const MastheadSidebar: React.FC = () => {
|
||||||
{formatLocation(location)}
|
{formatLocation(location)}
|
||||||
</DataDisplay>
|
</DataDisplay>
|
||||||
|
|
||||||
|
<DataDisplay icon={<Cake />} className="text-xs">
|
||||||
|
{formatDateString(birthdate, dateFormat)}
|
||||||
|
</DataDisplay>
|
||||||
|
|
||||||
<DataDisplay icon={<Email />} className="text-xs" link={`mailto:${email}`}>
|
<DataDisplay icon={<Email />} className="text-xs" link={`mailto:${email}`}>
|
||||||
{email}
|
{email}
|
||||||
</DataDisplay>
|
</DataDisplay>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const Heading: React.FC = ({ children }) => {
|
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
return <h3 className="my-2 inline-block border-b px-5 pb-2">{children}</h3>;
|
return <h3 className="my-2 inline-block border-b px-5 pb-2">{children}</h3>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
import { Email, Phone, Public, Room } from '@mui/icons-material';
|
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
|
||||||
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
import DataDisplay from '@/templates/shared/DataDisplay';
|
import DataDisplay from '@/templates/shared/DataDisplay';
|
||||||
|
import { formatDateString } from '@/utils/date';
|
||||||
import getProfileIcon from '@/utils/getProfileIcon';
|
import getProfileIcon from '@/utils/getProfileIcon';
|
||||||
import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
||||||
|
|
||||||
const Masthead = () => {
|
const Masthead = () => {
|
||||||
const { name, photo, email, phone, website, headline, location, profiles } = useAppSelector(
|
const dateFormat: string = useAppSelector((state) => get(state.resume, 'metadata.date.format'));
|
||||||
|
const { name, photo, email, phone, website, birthdate, headline, location, profiles } = useAppSelector(
|
||||||
(state) => state.resume.basics
|
(state) => state.resume.basics
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -32,6 +35,8 @@ const Masthead = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center gap-3">
|
<div className="flex flex-wrap justify-center gap-3">
|
||||||
|
<DataDisplay icon={<Cake />}>{formatDateString(birthdate, dateFormat)}</DataDisplay>
|
||||||
|
|
||||||
<DataDisplay icon={<Email />} link={`mailto:${email}`}>
|
<DataDisplay icon={<Email />} link={`mailto:${email}`}>
|
||||||
{email}
|
{email}
|
||||||
</DataDisplay>
|
</DataDisplay>
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
.page {}
|
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
@apply grid grid-cols-2 gap-8 px-6 py-4;
|
@apply grid grid-cols-2 gap-8 px-6 py-4;
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import get from 'lodash/get';
|
||||||
|
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
|
|
||||||
const Heading: React.FC = ({ children }) => {
|
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Email, Phone, Public, Room } from '@mui/icons-material';
|
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
|
||||||
import { alpha } from '@mui/material';
|
import { alpha } from '@mui/material';
|
||||||
import { Theme } from '@reactive-resume/schema';
|
import { Theme } from '@reactive-resume/schema';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
|
@ -6,11 +6,13 @@ import isEmpty from 'lodash/isEmpty';
|
||||||
|
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
import DataDisplay from '@/templates/shared/DataDisplay';
|
import DataDisplay from '@/templates/shared/DataDisplay';
|
||||||
|
import { formatDateString } from '@/utils/date';
|
||||||
import getProfileIcon from '@/utils/getProfileIcon';
|
import getProfileIcon from '@/utils/getProfileIcon';
|
||||||
import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
||||||
|
|
||||||
const Masthead: React.FC = () => {
|
const Masthead: React.FC = () => {
|
||||||
const { name, photo, headline, summary, email, phone, website, location, profiles } = useAppSelector(
|
const dateFormat: string = useAppSelector((state) => get(state.resume, 'metadata.date.format'));
|
||||||
|
const { name, photo, headline, summary, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
||||||
(state) => state.resume.basics
|
(state) => state.resume.basics
|
||||||
);
|
);
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||||
|
@ -48,6 +50,12 @@ const Masthead: React.FC = () => {
|
||||||
id="Masterhead_data"
|
id="Masterhead_data"
|
||||||
style={{ backgroundColor: alpha(theme.primary, 0.4), gridTemplateColumns: `repeat(2, minmax(0, 1fr))` }}
|
style={{ backgroundColor: alpha(theme.primary, 0.4), gridTemplateColumns: `repeat(2, minmax(0, 1fr))` }}
|
||||||
>
|
>
|
||||||
|
<DataDisplay icon={<Room />} className="col-span-2">
|
||||||
|
{formatLocation(location)}
|
||||||
|
</DataDisplay>
|
||||||
|
|
||||||
|
<DataDisplay icon={<Cake />}>{formatDateString(birthdate, dateFormat)}</DataDisplay>
|
||||||
|
|
||||||
<DataDisplay icon={<Email />} link={`mailto:${email}`}>
|
<DataDisplay icon={<Email />} link={`mailto:${email}`}>
|
||||||
{email}
|
{email}
|
||||||
</DataDisplay>
|
</DataDisplay>
|
||||||
|
@ -60,8 +68,6 @@ const Masthead: React.FC = () => {
|
||||||
{website}
|
{website}
|
||||||
</DataDisplay>
|
</DataDisplay>
|
||||||
|
|
||||||
<DataDisplay icon={<Room />}>{formatLocation(location)}</DataDisplay>
|
|
||||||
|
|
||||||
{profiles.map(({ id, username, network, url }) => (
|
{profiles.map(({ id, username, network, url }) => (
|
||||||
<DataDisplay key={id} icon={getProfileIcon(network)} link={url && addHttp(url)}>
|
<DataDisplay key={id} icon={getProfileIcon(network)} link={url && addHttp(url)}>
|
||||||
{username}
|
{username}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import get from 'lodash/get';
|
||||||
|
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
|
|
||||||
const Heading: React.FC = ({ children }) => {
|
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
import { Email, Phone, Public, Room } from '@mui/icons-material';
|
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
|
||||||
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
import DataDisplay from '@/templates/shared/DataDisplay';
|
import DataDisplay from '@/templates/shared/DataDisplay';
|
||||||
|
import { formatDateString } from '@/utils/date';
|
||||||
import getProfileIcon from '@/utils/getProfileIcon';
|
import getProfileIcon from '@/utils/getProfileIcon';
|
||||||
import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
||||||
|
|
||||||
const Masthead: React.FC = () => {
|
const Masthead: React.FC = () => {
|
||||||
const { name, photo, email, phone, website, headline, location, profiles } = useAppSelector(
|
const dateFormat: string = useAppSelector((state) => get(state.resume, 'metadata.date.format'));
|
||||||
|
const { name, photo, email, phone, website, birthdate, headline, location, profiles } = useAppSelector(
|
||||||
(state) => state.resume.basics
|
(state) => state.resume.basics
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -33,6 +36,10 @@ const Masthead: React.FC = () => {
|
||||||
</DataDisplay>
|
</DataDisplay>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
<DataDisplay icon={<Cake />} className="text-xs">
|
||||||
|
{formatDateString(birthdate, dateFormat)}
|
||||||
|
</DataDisplay>
|
||||||
|
|
||||||
<DataDisplay icon={<Email />} className="text-xs" link={`mailto:${email}`}>
|
<DataDisplay icon={<Email />} className="text-xs" link={`mailto:${email}`}>
|
||||||
{email}
|
{email}
|
||||||
</DataDisplay>
|
</DataDisplay>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import get from 'lodash/get';
|
||||||
|
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
|
|
||||||
const Heading: React.FC = ({ children }) => {
|
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
const theme: Theme = useAppSelector((state) => get(state.resume, 'metadata.theme', {}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Email, Phone, Public, Room } from '@mui/icons-material';
|
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
|
||||||
import { Theme } from '@reactive-resume/schema';
|
import { Theme } from '@reactive-resume/schema';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
@ -7,12 +7,16 @@ import { useMemo } from 'react';
|
||||||
import Markdown from '@/components/shared/Markdown';
|
import Markdown from '@/components/shared/Markdown';
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
import DataDisplay from '@/templates/shared/DataDisplay';
|
import DataDisplay from '@/templates/shared/DataDisplay';
|
||||||
|
import { formatDateString } from '@/utils/date';
|
||||||
import getProfileIcon from '@/utils/getProfileIcon';
|
import getProfileIcon from '@/utils/getProfileIcon';
|
||||||
import { getContrastColor } from '@/utils/styles';
|
import { getContrastColor } from '@/utils/styles';
|
||||||
import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
||||||
|
|
||||||
export const MastheadSidebar: React.FC = () => {
|
export const MastheadSidebar: React.FC = () => {
|
||||||
const { name, photo, email, phone, website, location, profiles } = useAppSelector((state) => state.resume.basics);
|
const dateFormat: string = useAppSelector((state) => get(state.resume, 'metadata.date.format'));
|
||||||
|
const { name, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
||||||
|
(state) => state.resume.basics
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="col-span-2 grid justify-items-left gap-4">
|
<div className="col-span-2 grid justify-items-left gap-4">
|
||||||
|
@ -31,6 +35,10 @@ export const MastheadSidebar: React.FC = () => {
|
||||||
{formatLocation(location)}
|
{formatLocation(location)}
|
||||||
</DataDisplay>
|
</DataDisplay>
|
||||||
|
|
||||||
|
<DataDisplay icon={<Cake />} className="text-xs">
|
||||||
|
{formatDateString(birthdate, dateFormat)}
|
||||||
|
</DataDisplay>
|
||||||
|
|
||||||
<DataDisplay icon={<Email />} className="text-xs" link={`mailto:${email}`}>
|
<DataDisplay icon={<Email />} className="text-xs" link={`mailto:${email}`}>
|
||||||
{email}
|
{email}
|
||||||
</DataDisplay>
|
</DataDisplay>
|
||||||
|
|
|
@ -7,7 +7,7 @@ type Props = {
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DataDisplay: React.FC<Props> = ({ icon, link, className, children }) => {
|
const DataDisplay: React.FC<React.PropsWithChildren<Props>> = ({ icon, link, className, children }) => {
|
||||||
if (isEmpty(children)) return null;
|
if (isEmpty(children)) return null;
|
||||||
|
|
||||||
if (!isEmpty(link)) {
|
if (!isEmpty(link)) {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
FaFacebookF,
|
FaFacebookF,
|
||||||
FaGithub,
|
FaGithub,
|
||||||
FaGitlab,
|
FaGitlab,
|
||||||
|
FaHackerrank,
|
||||||
FaInstagram,
|
FaInstagram,
|
||||||
FaLinkedinIn,
|
FaLinkedinIn,
|
||||||
FaSkype,
|
FaSkype,
|
||||||
|
@ -16,20 +17,24 @@ import {
|
||||||
FaXing,
|
FaXing,
|
||||||
FaYoutube,
|
FaYoutube,
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
|
import { SiCodechef, SiCodeforces } from 'react-icons/si';
|
||||||
|
|
||||||
const profileIconMap: Record<string, JSX.Element> = {
|
const profileIconMap: Record<string, JSX.Element> = {
|
||||||
facebook: <FaFacebookF />,
|
|
||||||
twitter: <FaTwitter />,
|
|
||||||
linkedin: <FaLinkedinIn />,
|
|
||||||
dribbble: <FaDribbble />,
|
|
||||||
soundcloud: <FaSoundcloud />,
|
|
||||||
github: <FaGithub />,
|
|
||||||
instagram: <FaInstagram />,
|
|
||||||
stackoverflow: <FaStackOverflow />,
|
|
||||||
behance: <FaBehance />,
|
behance: <FaBehance />,
|
||||||
|
codechef: <SiCodechef />,
|
||||||
|
codeforces: <SiCodeforces />,
|
||||||
|
dribbble: <FaDribbble />,
|
||||||
|
facebook: <FaFacebookF />,
|
||||||
|
github: <FaGithub />,
|
||||||
gitlab: <FaGitlab />,
|
gitlab: <FaGitlab />,
|
||||||
telegram: <FaTelegram />,
|
hackerrank: <FaHackerrank />,
|
||||||
|
instagram: <FaInstagram />,
|
||||||
|
linkedin: <FaLinkedinIn />,
|
||||||
skype: <FaSkype />,
|
skype: <FaSkype />,
|
||||||
|
soundcloud: <FaSoundcloud />,
|
||||||
|
stackoverflow: <FaStackOverflow />,
|
||||||
|
telegram: <FaTelegram />,
|
||||||
|
twitter: <FaTwitter />,
|
||||||
xing: <FaXing />,
|
xing: <FaXing />,
|
||||||
youtube: <FaYoutube />,
|
youtube: <FaYoutube />,
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,7 +3,7 @@ import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
const DateWrapper: React.FC = ({ children }) => {
|
const DateWrapper: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
const { locale } = useRouter();
|
const { locale } = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -12,8 +12,10 @@ const DateWrapper: React.FC = ({ children }) => {
|
||||||
// Locales
|
// Locales
|
||||||
require('dayjs/locale/ar');
|
require('dayjs/locale/ar');
|
||||||
require('dayjs/locale/bn');
|
require('dayjs/locale/bn');
|
||||||
|
require('dayjs/locale/cs');
|
||||||
require('dayjs/locale/da');
|
require('dayjs/locale/da');
|
||||||
require('dayjs/locale/de');
|
require('dayjs/locale/de');
|
||||||
|
require('dayjs/locale/el');
|
||||||
require('dayjs/locale/en');
|
require('dayjs/locale/en');
|
||||||
require('dayjs/locale/es');
|
require('dayjs/locale/es');
|
||||||
require('dayjs/locale/fr');
|
require('dayjs/locale/fr');
|
||||||
|
@ -25,6 +27,7 @@ const DateWrapper: React.FC = ({ children }) => {
|
||||||
require('dayjs/locale/pl');
|
require('dayjs/locale/pl');
|
||||||
require('dayjs/locale/pt');
|
require('dayjs/locale/pt');
|
||||||
require('dayjs/locale/ru');
|
require('dayjs/locale/ru');
|
||||||
|
require('dayjs/locale/sv');
|
||||||
require('dayjs/locale/ta');
|
require('dayjs/locale/ta');
|
||||||
require('dayjs/locale/tr');
|
require('dayjs/locale/tr');
|
||||||
require('dayjs/locale/vi');
|
require('dayjs/locale/vi');
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
|
|
||||||
const FontWrapper: React.FC = ({ children }) => {
|
const FontWrapper: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
const typography = useAppSelector((state) => get(state.resume, 'metadata.typography'));
|
const typography = useAppSelector((state) => get(state.resume, 'metadata.typography'));
|
||||||
|
|
||||||
const loadFonts = useCallback(async () => {
|
const loadFonts = useCallback(async () => {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { toggleSidebar } from '@/store/build/buildSlice';
|
import { toggleSidebar } from '@/store/build/buildSlice';
|
||||||
import { useAppDispatch } from '@/store/hooks';
|
import { useAppDispatch } from '@/store/hooks';
|
||||||
|
|
||||||
const HotkeysWrapper: React.FC = ({ children }) => {
|
const HotkeysWrapper: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
useHotkeys('ctrl+/, cmd+/', () => {
|
useHotkeys('ctrl+/, cmd+/', () => {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { darkTheme, lightTheme } from '@/config/theme';
|
||||||
import { setTheme } from '@/store/build/buildSlice';
|
import { setTheme } from '@/store/build/buildSlice';
|
||||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||||
|
|
||||||
const ThemeWrapper: React.FC = ({ children }) => {
|
const ThemeWrapper: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const theme = useAppSelector((state) => state.build.theme);
|
const theme = useAppSelector((state) => state.build.theme);
|
||||||
|
|
|
@ -3,7 +3,7 @@ import FontWrapper from './FontWrapper';
|
||||||
import HotkeysWrapper from './HotkeysWrapper';
|
import HotkeysWrapper from './HotkeysWrapper';
|
||||||
import ThemeWrapper from './ThemeWrapper';
|
import ThemeWrapper from './ThemeWrapper';
|
||||||
|
|
||||||
const WrapperRegistry: React.FC = ({ children }) => {
|
const WrapperRegistry: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
return (
|
return (
|
||||||
<ThemeWrapper>
|
<ThemeWrapper>
|
||||||
<FontWrapper>
|
<FontWrapper>
|
||||||
|
|
|
@ -47,19 +47,23 @@ You have complete control over what goes into your resume, how it looks, what co
|
||||||
- Arabic (اَلْعَرَبِيَّةُ)
|
- Arabic (اَلْعَرَبِيَّةُ)
|
||||||
- Bengali (বাংলা)
|
- Bengali (বাংলা)
|
||||||
- Chinese (中文)
|
- Chinese (中文)
|
||||||
|
- Czech (čeština)
|
||||||
- Danish (Dansk)
|
- Danish (Dansk)
|
||||||
- Dutch (Nederlands)
|
- Dutch (Nederlands)
|
||||||
- English
|
- English
|
||||||
- French (Français)
|
- French (Français)
|
||||||
- German (Deutsch)
|
- German (Deutsch)
|
||||||
|
- Greek (Ελληνικά)
|
||||||
- Hindi (हिन्दी)
|
- Hindi (हिन्दी)
|
||||||
- Italian (Italiano)
|
- Italian (Italiano)
|
||||||
- Kannada (ಕನ್ನಡ)
|
- Kannada (ಕನ್ನಡ)
|
||||||
- Malayalam (മലയാളം)
|
- Malayalam (മലയാളം)
|
||||||
|
- Odia (ଓଡ଼ିଆ)
|
||||||
- Polish (Polski)
|
- Polish (Polski)
|
||||||
- Portuguese (Português)
|
- Portuguese (Português)
|
||||||
- Russian (русский)
|
- Russian (русский)
|
||||||
- Spanish (Español)
|
- Spanish (Español)
|
||||||
|
- Swedish (Svenska)
|
||||||
- Tamil (தமிழ்)
|
- Tamil (தமிழ்)
|
||||||
- Turkish (Türkçe)
|
- Turkish (Türkçe)
|
||||||
- Vietnamese (Tiếng Việt)
|
- Vietnamese (Tiếng Việt)
|
||||||
|
|
|
@ -127,13 +127,15 @@ This field is only required if the Google Login functionality is important to yo
|
||||||
|
|
||||||
### `GOOGLE_API_KEY`
|
### `GOOGLE_API_KEY`
|
||||||
|
|
||||||
**Required**: `yes`
|
**Required**: `no`
|
||||||
**Description:** Google API Key used for fetching Google Fonts
|
**Description:** Google API Key used for fetching Google Fonts
|
||||||
|
|
||||||
Within the resume builder, there's a section where you can pick any font from the Google Fonts Library. To fetch the names and IDs of these fonts, we depend on the Google Fonts API. It does not cost any payment, or the need to enter credit card information to create or use this API.
|
Within the resume builder, there's a section where you can pick any font from the Google Fonts Library. To fetch the names and IDs of these fonts, we depend on the Google Fonts API. It does not cost any payment, or the need to enter credit card information to create or use this API.
|
||||||
|
|
||||||
You can get your own key here: https://developers.google.com/fonts/docs/developer_api
|
You can get your own key here: https://developers.google.com/fonts/docs/developer_api
|
||||||
|
|
||||||
|
If you do not have a Google API Key, it was make use of the cached response JSON that's stored within the project source. Please note that this cache is not updated and may not have all the latest fonts that Google Fonts has to offer.
|
||||||
|
|
||||||
## SendGrid
|
## SendGrid
|
||||||
|
|
||||||
The server makes use of SendGrid to send the password reset email to those who have forgotten their password. **This section is completely optional for those who do not require this functionality.**
|
The server makes use of SendGrid to send the password reset email to those who have forgotten their password. **This section is completely optional for those who do not require this functionality.**
|
||||||
|
|
|
@ -17,11 +17,11 @@
|
||||||
"@algolia/client-search": "^4.13.0",
|
"@algolia/client-search": "^4.13.0",
|
||||||
"@docusaurus/core": "2.0.0-beta.18",
|
"@docusaurus/core": "2.0.0-beta.18",
|
||||||
"@docusaurus/preset-classic": "2.0.0-beta.18",
|
"@docusaurus/preset-classic": "2.0.0-beta.18",
|
||||||
"@mdx-js/react": "^2.1.1",
|
"@mdx-js/react": "1.6.22",
|
||||||
"clsx": "^1.1.1",
|
"clsx": "^1.1.1",
|
||||||
"prism-react-renderer": "^1.3.1",
|
"prism-react-renderer": "^1.3.1",
|
||||||
"react": "<18",
|
"react": "17.0.2",
|
||||||
"react-dom": "<18"
|
"react-dom": "17.0.2"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
@ -36,6 +36,6 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "<18"
|
"@types/react": "17.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
package.json
10
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "reactive-resume",
|
"name": "reactive-resume",
|
||||||
"version": "3.3.4",
|
"version": "3.4.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"schema",
|
"schema",
|
||||||
|
@ -38,10 +38,10 @@
|
||||||
"env-cmd": "^10.1.0"
|
"env-cmd": "^10.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "^5.18.0",
|
"@typescript-eslint/eslint-plugin": "^5.21.0",
|
||||||
"@typescript-eslint/parser": "^5.18.0",
|
"@typescript-eslint/parser": "^5.21.0",
|
||||||
"cz-conventional-changelog": "^3.3.0",
|
"cz-conventional-changelog": "^3.3.0",
|
||||||
"eslint": "^8.12.0",
|
"eslint": "^8.14.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
|
@ -50,6 +50,6 @@
|
||||||
"husky": "^7.0.4",
|
"husky": "^7.0.4",
|
||||||
"prettier": "^2.6.2",
|
"prettier": "^2.6.2",
|
||||||
"standard-version": "^9.3.2",
|
"standard-version": "^9.3.2",
|
||||||
"typescript": "<4.6.0"
|
"typescript": "^4.6.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2389
pnpm-lock.yaml
2389
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -9,7 +9,7 @@
|
||||||
"lint": "eslint --fix --ext .ts ./src"
|
"lint": "eslint --fix --ext .ts ./src"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^8.12.0",
|
"eslint": "^8.14.0",
|
||||||
"typescript": "<4.6.0"
|
"typescript": "^4.6.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ export type Basics = {
|
||||||
phone: string;
|
phone: string;
|
||||||
website: string;
|
website: string;
|
||||||
headline: string;
|
headline: string;
|
||||||
|
birthdate: string;
|
||||||
summary: string;
|
summary: string;
|
||||||
location: Location;
|
location: Location;
|
||||||
profiles: Profile[];
|
profiles: Profile[];
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM node:17-alpine as dependencies
|
FROM node:lts-alpine as dependencies
|
||||||
|
|
||||||
RUN apk add --no-cache g++ curl make python3 \
|
RUN apk add --no-cache g++ curl make python3 \
|
||||||
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
||||||
|
@ -11,7 +11,7 @@ COPY ./server/package.json ./server/package.json
|
||||||
|
|
||||||
RUN pnpm install --frozen-lockfile
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
FROM node:17-alpine as builder
|
FROM node:lts-alpine as builder
|
||||||
|
|
||||||
RUN apk add --no-cache g++ curl make python3 \
|
RUN apk add --no-cache g++ curl make python3 \
|
||||||
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
"lint": "eslint --fix --ext .ts ./src"
|
"lint": "eslint --fix --ext .ts ./src"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.67.0",
|
"@aws-sdk/client-s3": "^3.81.0",
|
||||||
"@nestjs/axios": "^0.0.7",
|
"@nestjs/axios": "^0.0.7",
|
||||||
"@nestjs/common": "^8.4.4",
|
"@nestjs/common": "^8.4.4",
|
||||||
"@nestjs/config": "^2.0.0",
|
"@nestjs/config": "^2.0.0",
|
||||||
|
@ -31,23 +31,23 @@
|
||||||
"class-validator": "^0.13.2",
|
"class-validator": "^0.13.2",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"csvtojson": "^2.0.10",
|
"csvtojson": "^2.0.10",
|
||||||
"dayjs": "^1.11.0",
|
"dayjs": "^1.11.1",
|
||||||
"googleapis": "^100.0.0",
|
"google-auth-library": "^8.0.2",
|
||||||
"joi": "^17.6.0",
|
"joi": "^17.6.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"multer": "^1.4.4",
|
"multer": "^1.4.4",
|
||||||
"nanoid": "^3.3.2",
|
"nanoid": "^3.3.3",
|
||||||
"node-stream-zip": "^1.15.0",
|
"node-stream-zip": "^1.15.0",
|
||||||
"passport": "^0.5.2",
|
"passport": "^0.5.2",
|
||||||
"passport-jwt": "^4.0.0",
|
"passport-jwt": "^4.0.0",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pg": "^8.7.3",
|
"pg": "^8.7.3",
|
||||||
"playwright-chromium": "^1.20.2",
|
"playwright-chromium": "^1.21.1",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rxjs": "^7.5.5",
|
"rxjs": "^7.5.5",
|
||||||
"typeorm": "^0.2.34",
|
"typeorm": "0.2",
|
||||||
"uuid": "^8.3.2"
|
"uuid": "^8.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -55,17 +55,17 @@
|
||||||
"@nestjs/schematics": "^8.0.10",
|
"@nestjs/schematics": "^8.0.10",
|
||||||
"@reactive-resume/schema": "workspace:*",
|
"@reactive-resume/schema": "workspace:*",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/cookie-parser": "^1.4.2",
|
"@types/cookie-parser": "^1.4.3",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/multer": "^1.4.7",
|
"@types/multer": "^1.4.7",
|
||||||
"@types/node": "^17.0.23",
|
"@types/node": "^17.0.30",
|
||||||
"eslint": "^8.12.0",
|
"eslint": "^8.14.0",
|
||||||
"prettier": "^2.6.2",
|
"prettier": "^2.6.2",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"ts-loader": "^9.2.8",
|
"ts-loader": "^9.2.9",
|
||||||
"ts-node": "^10.7.0",
|
"ts-node": "^10.7.0",
|
||||||
"tsconfig-paths": "^3.14.1",
|
"tsconfig-paths": "^3.14.1",
|
||||||
"typescript": "<4.6.0",
|
"typescript": "^4.6.4",
|
||||||
"webpack": "^5.72.0"
|
"webpack": "^5.72.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,8 @@ export class AuthController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('google')
|
@Post('google')
|
||||||
async loginWithGoogle(@Body('accessToken') googleAccessToken: string) {
|
async loginWithGoogle(@Body('credential') credential: string) {
|
||||||
const user = await this.authService.authenticateWithGoogle(googleAccessToken);
|
const user = await this.authService.authenticateWithGoogle(credential);
|
||||||
const accessToken = this.authService.getAccessToken(user.id);
|
const accessToken = this.authService.getAccessToken(user.id);
|
||||||
|
|
||||||
return { user, accessToken };
|
return { user, accessToken };
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { JwtService } from '@nestjs/jwt';
|
||||||
import { SchedulerRegistry } from '@nestjs/schedule';
|
import { SchedulerRegistry } from '@nestjs/schedule';
|
||||||
import bcrypt from 'bcrypt';
|
import bcrypt from 'bcrypt';
|
||||||
import { randomInt } from 'crypto';
|
import { randomInt } from 'crypto';
|
||||||
import { google } from 'googleapis';
|
import { OAuth2Client } from 'google-auth-library';
|
||||||
|
|
||||||
import { PostgresErrorCode } from '@/database/errorCodes.enum';
|
import { PostgresErrorCode } from '@/database/errorCodes.enum';
|
||||||
import { CreateGoogleUserDto } from '@/users/dto/create-google-user.dto';
|
import { CreateGoogleUserDto } from '@/users/dto/create-google-user.dto';
|
||||||
|
@ -107,17 +107,16 @@ export class AuthService {
|
||||||
return this.usersService.findById(payload.id);
|
return this.usersService.findById(payload.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async authenticateWithGoogle(googleAccessToken: string) {
|
async authenticateWithGoogle(credential: string) {
|
||||||
const clientID = this.configService.get<string>('google.clientID');
|
const clientID = this.configService.get<string>('google.clientID');
|
||||||
const clientSecret = this.configService.get<string>('google.clientSecret');
|
const clientSecret = this.configService.get<string>('google.clientSecret');
|
||||||
|
|
||||||
const OAuthClient = new google.auth.OAuth2(clientID, clientSecret);
|
const OAuthClient = new OAuth2Client(clientID, clientSecret);
|
||||||
OAuthClient.setCredentials({ access_token: googleAccessToken });
|
const client = await OAuthClient.verifyIdToken({ idToken: credential });
|
||||||
|
const userPayload = client.getPayload();
|
||||||
const { email } = await OAuthClient.getTokenInfo(googleAccessToken);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user = await this.usersService.findByEmail(email);
|
const user = await this.usersService.findByEmail(userPayload.email);
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
@ -125,14 +124,12 @@ export class AuthService {
|
||||||
throw new HttpException(error, HttpStatus.BAD_GATEWAY);
|
throw new HttpException(error, HttpStatus.BAD_GATEWAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserInfoClient = google.oauth2('v2').userinfo;
|
const username = userPayload.email.split('@')[0];
|
||||||
const { data } = await UserInfoClient.get({ auth: OAuthClient });
|
|
||||||
const username = data.email.split('@')[0];
|
|
||||||
|
|
||||||
const createUserDto: CreateGoogleUserDto = {
|
const createUserDto: CreateGoogleUserDto = {
|
||||||
name: `${data.given_name} ${data.family_name}`,
|
name: `${userPayload.given_name} ${userPayload.family_name}`,
|
||||||
username,
|
username,
|
||||||
email: data.email,
|
email: userPayload.email,
|
||||||
provider: 'google',
|
provider: 'google',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,6 +5,8 @@ import { Font } from '@reactive-resume/schema';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
|
|
||||||
|
import cachedResponse from './assets/cachedResponse.json';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FontsService {
|
export class FontsService {
|
||||||
constructor(private configService: ConfigService, private httpService: HttpService) {}
|
constructor(private configService: ConfigService, private httpService: HttpService) {}
|
||||||
|
@ -13,8 +15,14 @@ export class FontsService {
|
||||||
const apiKey = this.configService.get<string>('google.apiKey');
|
const apiKey = this.configService.get<string>('google.apiKey');
|
||||||
const url = 'https://www.googleapis.com/webfonts/v1/webfonts?key=' + apiKey;
|
const url = 'https://www.googleapis.com/webfonts/v1/webfonts?key=' + apiKey;
|
||||||
|
|
||||||
const response = await firstValueFrom(this.httpService.get(url));
|
let data = [];
|
||||||
const data = get(response.data, 'items', []);
|
|
||||||
|
if (apiKey) {
|
||||||
|
const response = await firstValueFrom(this.httpService.get(url));
|
||||||
|
data = get(response.data, 'items', []);
|
||||||
|
} else {
|
||||||
|
data = cachedResponse;
|
||||||
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ const defaultState: Partial<Resume> = {
|
||||||
basics: {
|
basics: {
|
||||||
email: '',
|
email: '',
|
||||||
headline: '',
|
headline: '',
|
||||||
|
birthdate: '',
|
||||||
photo: {
|
photo: {
|
||||||
url: '',
|
url: '',
|
||||||
visible: true,
|
visible: true,
|
||||||
|
|
|
@ -5,6 +5,7 @@ const sampleData: Partial<Resume> = {
|
||||||
name: 'Alexis Jones',
|
name: 'Alexis Jones',
|
||||||
email: 'alexis.jones@gmail.com',
|
email: 'alexis.jones@gmail.com',
|
||||||
phone: '+1 800 1200 3820',
|
phone: '+1 800 1200 3820',
|
||||||
|
birthdate: '1995-08-06T00:00:00.000Z',
|
||||||
photo: {
|
photo: {
|
||||||
url: `/images/sample-photo.jpg`,
|
url: `/images/sample-photo.jpg`,
|
||||||
filters: {
|
filters: {
|
||||||
|
|
|
@ -35,6 +35,8 @@ export class ResumeController {
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@Get()
|
@Get()
|
||||||
async findAllByUser(@User('id') userId: number) {
|
async findAllByUser(@User('id') userId: number) {
|
||||||
|
console.log('findAllByUser', userId);
|
||||||
|
|
||||||
return this.resumeService.findAllByUser(userId);
|
return this.resumeService.findAllByUser(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strictNullChecks": false,
|
"strictNullChecks": false,
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
|
"resolveJsonModule": true,
|
||||||
"strictBindCallApply": false,
|
"strictBindCallApply": false,
|
||||||
"forceConsistentCasingInFileNames": false,
|
"forceConsistentCasingInFileNames": false,
|
||||||
"noFallthroughCasesInSwitch": false,
|
"noFallthroughCasesInSwitch": false,
|
||||||
|
|
Loading…
Reference in New Issue