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