mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-10 04:22:27 +10:00
Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d342c0a9af | |||
| 63084eebb4 | |||
| 3b4ea00db8 | |||
| c8f7bffe7e | |||
| 3ff56f89d9 | |||
| 7fb9f27837 | |||
| c9685d4ce7 | |||
| 4dc987e27d | |||
| f7af06ae9a | |||
| a5c337faa3 | |||
| fc4704f0a6 | |||
| d968334ada | |||
| fea6d23178 | |||
| 3fefc95572 | |||
| b07e7d1213 | |||
| d47b8bfb03 | |||
| 5bf7fbdae1 | |||
| fca766b382 | |||
| feadfb1b67 | |||
| e69000f221 | |||
| 6b4a54465a | |||
| 878659999f | |||
| 1868c47e30 | |||
| 51442efc23 | |||
| 556e962ec5 | |||
| b5ce67f863 | |||
| c3ce89dc3a | |||
| e87930c758 | |||
| 815a693e58 | |||
| 8287fcae96 | |||
| cd7fe6c404 | |||
| d47d5dd819 | |||
| 1919d79e43 | |||
| ab08cd9e34 | |||
| 2522bdd0a2 | |||
| f9b6aefffe | |||
| 2ba6658a0b | |||
| dbc46f27a3 | |||
| f21e1caed1 | |||
| 4ffe2a6330 | |||
| 1bc0438872 | |||
| 57fb9fdaea | |||
| 58ce641f18 | |||
| 5f4e7802e4 | |||
| 42d3109ae1 | |||
| f7ca7b97fa | |||
| f5d8a54134 | |||
| eaec14dc62 | |||
| c93b3264cd | |||
| bf41aa9c6c | |||
| 8af6bfd5ae | |||
| ab08c10874 | |||
| 9af9a0284e |
13
.env.example
13
.env.example
@ -1,12 +1,3 @@
|
||||
# Turbo Cache (Optional)
|
||||
TURBO_TEAM=
|
||||
TURBO_TOKEN=
|
||||
|
||||
# Sentry Error Logging (Optional)
|
||||
SENTRY_AUTH_TOKEN=
|
||||
SERVER_SENTRY_DSN=
|
||||
PUBLIC_CLIENT_SENTRY_DSN=
|
||||
|
||||
# Server + Client
|
||||
TZ=UTC
|
||||
PUBLIC_URL=http://localhost:3000
|
||||
@ -20,7 +11,7 @@ POSTGRES_PASSWORD=postgres
|
||||
|
||||
# Server
|
||||
SECRET_KEY=
|
||||
POSTGRES_HOST=postgres
|
||||
POSTGRES_HOST=localhost
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_SSL_CERT=
|
||||
JWT_SECRET=
|
||||
@ -41,5 +32,5 @@ STORAGE_ACCESS_KEY=
|
||||
STORAGE_SECRET_KEY=
|
||||
PDF_DELETION_TIME=345600000
|
||||
|
||||
# Flags (Client)
|
||||
# Client
|
||||
PUBLIC_FLAG_DISABLE_SIGNUPS=false
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
// TypeScript ESLint
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/interface-name-prefix": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off"
|
||||
|
||||
2
.github/workflows/digitalocean-deploy.yml
vendored
2
.github/workflows/digitalocean-deploy.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
steps:
|
||||
- name: Install DigitalOcean CLI
|
||||
uses: digitalocean/action-doctl@v2.2.0
|
||||
uses: digitalocean/action-doctl@v2.3.0
|
||||
with:
|
||||
token: ${{ secrets.DIGITALOCEAN_TOKEN }}
|
||||
|
||||
|
||||
21
.github/workflows/docker-build-push.yml
vendored
21
.github/workflows/docker-build-push.yml
vendored
@ -10,18 +10,13 @@ jobs:
|
||||
name: Build and Push Docker Image
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
image: [client, server]
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v3.2.0
|
||||
|
||||
- id: version
|
||||
name: App Version
|
||||
@ -56,22 +51,8 @@ jobs:
|
||||
push: true
|
||||
platforms: linux/amd64
|
||||
file: ${{ matrix.image }}/Dockerfile
|
||||
build-args: |
|
||||
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
|
||||
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
tags: |
|
||||
amruthpillai/reactive-resume:${{ matrix.image }}-latest
|
||||
amruthpillai/reactive-resume:${{ matrix.image }}-${{ steps.version.outputs.current-version }}
|
||||
ghcr.io/amruthpillai/reactive-resume:${{ matrix.image }}-latest
|
||||
ghcr.io/amruthpillai/reactive-resume:${{ matrix.image }}-${{ steps.version.outputs.current-version }}
|
||||
|
||||
- name: Create Sentry Release
|
||||
uses: getsentry/action-release@v1.2.1
|
||||
env:
|
||||
SENTRY_ORG: reactive-resume
|
||||
SENTRY_PROJECT: ${{ matrix.image }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
with:
|
||||
environment: production
|
||||
version: ${{ steps.version.outputs.current-version }}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
# Environment Variables
|
||||
.env
|
||||
.env.*
|
||||
!.env.gitpod
|
||||
!.env.example
|
||||
|
||||
# Project Dependencies
|
||||
|
||||
41
.gitpod.yml
Normal file
41
.gitpod.yml
Normal file
@ -0,0 +1,41 @@
|
||||
tasks:
|
||||
- name: Run PostgreSQL Database
|
||||
command: docker run --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=postgres -d postgres
|
||||
|
||||
- name: Install Project Dependencies
|
||||
command: |
|
||||
pnpm install
|
||||
pnpm dlx playwright install --with-deps chromium
|
||||
gp sync-done deps
|
||||
|
||||
- name: Generate Environment Variables
|
||||
init: gp sync-await deps
|
||||
command: |
|
||||
if [ -f .env ]; then
|
||||
echo "Found .env in workspace, skipping generation"
|
||||
else
|
||||
pnpm generate-env
|
||||
fi
|
||||
gp sync-done env
|
||||
|
||||
- name: Build and Run Project
|
||||
init: gp sync-await env
|
||||
command: |
|
||||
pnpm build
|
||||
pnpm start
|
||||
|
||||
ports:
|
||||
# PostgreSQL
|
||||
- port: 5432
|
||||
onOpen: ignore
|
||||
visibility: private
|
||||
|
||||
# Client
|
||||
- port: 3100
|
||||
onOpen: ignore
|
||||
visibility: public
|
||||
|
||||
# Client
|
||||
- port: 3000
|
||||
onOpen: open-browser
|
||||
visibility: public
|
||||
@ -6,7 +6,8 @@
|
||||
[](https://github.com/AmruthPillai/Reactive-Resume/blob/main/LICENSE)
|
||||
[](https://translate.rxresu.me)
|
||||
[](https://hub.docker.com/r/amruthpillai/reactive-resume)
|
||||

|
||||

|
||||
[](https://gitpod.io/#https://github.com/AmruthPillai/Reactive-Resume)
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2FAmruthPillai%2FReactive-Resume?ref=badge_shield)
|
||||
|
||||
## [Go to App](https://rxresu.me) | [Docs](https://docs.rxresu.me)
|
||||
@ -18,7 +19,7 @@ You have complete control over what goes into your resume, how it looks, what co
|
||||
## Table of Contents
|
||||
|
||||
- [Reactive Resume](#reactive-resume)
|
||||
- [Go to App Docs](https://docs.rxresu.me)
|
||||
- [Go to App | Docs](#go-to-app--docs)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Features](#features)
|
||||
- [Languages](#languages)
|
||||
@ -104,6 +105,10 @@ The docs include an extensive [Tutorial](https://docs.rxresu.me/tutorial) sectio
|
||||
|
||||
## Build from Source
|
||||
|
||||
[](https://gitpod.io/#https://github.com/AmruthPillai/Reactive-Resume)
|
||||
|
||||
Initially building the image and project on Gitpod will take at least ~10 minutes, so please be patient on first launch.
|
||||
|
||||
For extensive information on how to build the app on your local machine, head over to the docs [Source Code](https://docs.rxresu.me/source-code) section.
|
||||
|
||||
## Contributing
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application' version '7.1.2' apply false
|
||||
id 'com.android.library' version '7.1.2' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.7.21' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
|
||||
3
client/.gitignore
vendored
3
client/.gitignore
vendored
@ -40,6 +40,3 @@ __ENV.js
|
||||
|
||||
# next-sitemap
|
||||
sitemap*.xml
|
||||
|
||||
# sentry
|
||||
.sentryclirc
|
||||
@ -21,14 +21,6 @@ COPY --from=dependencies /app/node_modules ./node_modules
|
||||
COPY --from=dependencies /app/schema/node_modules ./schema/node_modules
|
||||
COPY --from=dependencies /app/client/node_modules ./client/node_modules
|
||||
|
||||
ARG TURBO_TEAM
|
||||
ARG TURBO_TOKEN
|
||||
ARG SENTRY_AUTH_TOKEN
|
||||
|
||||
ENV TURBO_TEAM $TURBO_TEAM
|
||||
ENV TURBO_TOKEN $TURBO_TOKEN
|
||||
ENV SENTRY_AUTH_TOKEN $SENTRY_AUTH_TOKEN
|
||||
|
||||
RUN pnpm run build --filter client
|
||||
|
||||
FROM base as production
|
||||
|
||||
@ -108,7 +108,7 @@ const LeftSidebar = () => {
|
||||
variant={isDesktop ? 'persistent' : 'temporary'}
|
||||
>
|
||||
<div className={styles.container}>
|
||||
<nav className="overflow-y-scroll">
|
||||
<nav className="overflow-y-auto">
|
||||
<div>
|
||||
<Link href="/dashboard">
|
||||
<Logo size={40} />
|
||||
|
||||
@ -43,7 +43,7 @@ const RightSidebar = () => {
|
||||
variant={isDesktop ? 'persistent' : 'temporary'}
|
||||
>
|
||||
<div className={styles.container}>
|
||||
<nav className="overflow-y-scroll">
|
||||
<nav className="overflow-y-auto">
|
||||
<div>
|
||||
<Avatar size={40} />
|
||||
<Divider />
|
||||
|
||||
@ -3,7 +3,7 @@ import { Button } from '@mui/material';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import Heading from '@/components/shared/Heading';
|
||||
import { DOCS_URL, DONATION_URL, GITHUB_ISSUES_URL, GITHUB_URL } from '@/constants/index';
|
||||
import { DOCS_URL, DONATION_URL, GITHUB_ISSUES_URL, GITHUB_URL, REDDIT_URL } from '@/constants/index';
|
||||
|
||||
import styles from './Links.module.scss';
|
||||
|
||||
@ -50,6 +50,12 @@ const Links = () => {
|
||||
</Button>
|
||||
</a>
|
||||
|
||||
<a href={REDDIT_URL} target="_blank" rel="noreferrer">
|
||||
<Button variant="text" startIcon={<Link />}>
|
||||
{t<string>('builder.rightSidebar.sections.links.reddit')}
|
||||
</Button>
|
||||
</a>
|
||||
|
||||
<a href={DOCS_URL} target="_blank" rel="noreferrer">
|
||||
<Button variant="text" startIcon={<Link />}>
|
||||
{t<string>('builder.rightSidebar.sections.links.docs')}
|
||||
|
||||
@ -51,7 +51,7 @@ const Settings = () => {
|
||||
const pageConfig: PageConfig | undefined = useMemo(() => get(resume, 'metadata.page'), [resume]);
|
||||
|
||||
const isDarkMode = useMemo(() => theme === 'dark', [theme]);
|
||||
const exampleDateString = useMemo(() => `Eg. ${dayjs().utc().format(dateConfig.format)}`, [dateConfig.format]);
|
||||
const exampleDateString = useMemo(() => `Eg. ${dayjs().format(dateConfig.format)}`, [dateConfig.format]);
|
||||
const themeString = useMemo(() => (isDarkMode ? 'Matte Black Everything' : 'As bright as your future'), [isDarkMode]);
|
||||
|
||||
const { mutateAsync: loadSampleDataMutation } = useMutation<Resume, ServerError, LoadSampleDataParams>(
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import clsx from 'clsx';
|
||||
import { isEmpty } from 'lodash';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import rehypeKatex from 'rehype-katex';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import remarkMath from 'remark-math';
|
||||
|
||||
type Props = {
|
||||
children?: string;
|
||||
@ -11,7 +14,11 @@ const Markdown: React.FC<Props> = ({ className, children }) => {
|
||||
if (!children || isEmpty(children)) return null;
|
||||
|
||||
return (
|
||||
<ReactMarkdown remarkPlugins={[]} className={clsx('markdown', className)}>
|
||||
<ReactMarkdown
|
||||
className={clsx('markdown', className)}
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[rehypeKatex]}
|
||||
>
|
||||
{children}
|
||||
</ReactMarkdown>
|
||||
);
|
||||
|
||||
@ -13,6 +13,7 @@ export const DOCS_URL = 'https://docs.rxresu.me';
|
||||
export const DONATION_URL = 'https://paypal.me/RajaRajanA';
|
||||
export const TRANSLATE_URL = 'https://translate.rxresu.me/';
|
||||
export const DIGITALOCEAN_URL = 'https://pillai.xyz/digitalocean';
|
||||
export const REDDIT_URL = 'https://www.reddit.com/r/reactiveresume/';
|
||||
export const GITHUB_URL = 'https://github.com/AmruthPillai/Reactive-Resume';
|
||||
export const PRODUCT_HUNT_URL = 'https://www.producthunt.com/posts/reactive-resume-v3';
|
||||
export const GITHUB_ISSUES_URL = 'https://github.com/AmruthPillai/Reactive-Resume/issues/new/choose';
|
||||
|
||||
@ -134,7 +134,7 @@ const AwardModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
|
||||
@ -134,7 +134,7 @@ const CertificateModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
|
||||
@ -151,7 +151,7 @@ const CustomModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
@ -175,7 +175,7 @@ const CustomModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
|
||||
@ -173,7 +173,7 @@ const EducationModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
@ -197,7 +197,7 @@ const EducationModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
|
||||
@ -143,7 +143,7 @@ const ProjectModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
@ -167,7 +167,7 @@ const ProjectModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
|
||||
@ -134,7 +134,7 @@ const PublicationModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
|
||||
@ -140,7 +140,7 @@ const VolunteerModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
@ -164,7 +164,7 @@ const VolunteerModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
|
||||
@ -140,7 +140,7 @@ const WorkModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
@ -164,7 +164,7 @@ const WorkModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
const { version } = require('../package.json');
|
||||
const { i18n } = require('./next-i18next.config');
|
||||
const { withSentryConfig } = require('@sentry/nextjs');
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
@ -16,11 +15,6 @@ const nextConfig = {
|
||||
domains: ['cdn.rxresu.me', 'www.gravatar.com'],
|
||||
},
|
||||
|
||||
sentry: {
|
||||
hideSourceMaps: true,
|
||||
widenClientFileUpload: true,
|
||||
},
|
||||
|
||||
// Hack to make Tailwind darkMode 'class' strategy with CSS Modules
|
||||
// Ref: https://github.com/tailwindlabs/tailwindcss/issues/3258#issuecomment-968368156
|
||||
webpack: (config) => {
|
||||
@ -53,11 +47,4 @@ const nextConfig = {
|
||||
},
|
||||
};
|
||||
|
||||
/** @type {import('@sentry/nextjs').SentryWebpackPluginOptions} */
|
||||
const sentryConfig = {
|
||||
silent: true,
|
||||
project: 'client',
|
||||
dryRun: process.env.NODE_ENV !== 'production',
|
||||
};
|
||||
|
||||
module.exports = withSentryConfig(nextConfig, sentryConfig);
|
||||
module.exports = nextConfig;
|
||||
|
||||
@ -13,72 +13,73 @@
|
||||
"@emotion/css": "^11.10.5",
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@hello-pangea/dnd": "^16.0.1",
|
||||
"@hello-pangea/dnd": "^16.2.0",
|
||||
"@hookform/resolvers": "2.9.10",
|
||||
"@monaco-editor/react": "^4.4.6",
|
||||
"@mui/icons-material": "^5.10.15",
|
||||
"@mui/lab": "^5.0.0-alpha.109",
|
||||
"@mui/material": "^5.10.15",
|
||||
"@mui/system": "^5.10.15",
|
||||
"@mui/x-date-pickers": "5.0.8",
|
||||
"@next/env": "^13.0.5",
|
||||
"@react-oauth/google": "^0.5.0",
|
||||
"@reduxjs/toolkit": "^1.9.0",
|
||||
"@sentry/nextjs": "^7.21.1",
|
||||
"axios": "^1.2.0",
|
||||
"@mui/icons-material": "^5.11.0",
|
||||
"@mui/lab": "^5.0.0-alpha.114",
|
||||
"@mui/material": "^5.11.3",
|
||||
"@mui/system": "^5.11.2",
|
||||
"@mui/x-date-pickers": "5.0.12",
|
||||
"@next/env": "^13.1.1",
|
||||
"@react-oauth/google": "^0.5.1",
|
||||
"@reduxjs/toolkit": "^1.9.1",
|
||||
"axios": "^1.2.2",
|
||||
"clsx": "^1.2.1",
|
||||
"dayjs": "^1.11.6",
|
||||
"dayjs": "^1.11.7",
|
||||
"downloadjs": "^1.4.7",
|
||||
"joi": "^17.7.0",
|
||||
"lodash": "^4.17.21",
|
||||
"md5-hex": "^4.0.0",
|
||||
"monaco-editor": "^0.34.1",
|
||||
"nanoid": "^3.3.4",
|
||||
"next": "13.0.5",
|
||||
"next-i18next": "^13.0.0",
|
||||
"nanoid": "3.3.4",
|
||||
"next": "13.1.1",
|
||||
"next-i18next": "^13.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-dnd": "16.0.1",
|
||||
"react-dnd-html5-backend": "16.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.39.5",
|
||||
"react-hook-form": "^7.41.3",
|
||||
"react-hot-toast": "2.4.0",
|
||||
"react-icons": "^4.6.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-icons": "^4.7.1",
|
||||
"react-markdown": "^8.0.4",
|
||||
"react-query": "^3.39.2",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-zoom-pan-pinch": "^2.1.3",
|
||||
"redux": "^4.2.0",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-saga": "^1.2.1",
|
||||
"redux-saga": "^1.2.2",
|
||||
"redux-undo": "^1.0.1",
|
||||
"rehype-katex": "^6.0.2",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"sharp": "^0.31.2",
|
||||
"remark-math": "^5.1.1",
|
||||
"sharp": "^0.31.3",
|
||||
"uuid": "^9.0.0",
|
||||
"webfontloader": "^1.6.28"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.2",
|
||||
"@babel/core": "^7.20.7",
|
||||
"@reactive-resume/schema": "workspace:*",
|
||||
"@tailwindcss/typography": "^0.5.8",
|
||||
"@types/downloadjs": "^1.4.3",
|
||||
"@types/lodash": "^4.14.190",
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/react": "^18.0.25",
|
||||
"@types/react-dom": "^18.0.9",
|
||||
"@types/react-redux": "^7.1.24",
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/react": "^18.0.26",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"@types/react-redux": "^7.1.25",
|
||||
"@types/tailwindcss": "^3.0.11",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"@types/webfontloader": "^1.6.35",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"csstype": "^3.1.1",
|
||||
"eslint-config-next": "^13.0.5",
|
||||
"eslint-plugin-tailwindcss": "^3.7.0",
|
||||
"eslint-config-next": "^13.1.1",
|
||||
"eslint-plugin-tailwindcss": "^3.8.0",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"next-sitemap": "^3.1.32",
|
||||
"postcss": "^8.4.19",
|
||||
"sass": "^1.56.1",
|
||||
"next-sitemap": "^3.1.44",
|
||||
"postcss": "^8.4.20",
|
||||
"sass": "^1.57.1",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"typescript": "^4.9.3"
|
||||
"typescript": "^4.9.4"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
import type { NextPage } from 'next';
|
||||
import type { ErrorProps } from 'next/error';
|
||||
import NextErrorComponent from 'next/error';
|
||||
|
||||
const CustomErrorComponent: NextPage<ErrorProps> = (props) => <NextErrorComponent statusCode={props.statusCode} />;
|
||||
|
||||
CustomErrorComponent.getInitialProps = async (contextData) => {
|
||||
await Sentry.captureUnderscoreErrorException(contextData);
|
||||
|
||||
return NextErrorComponent.getInitialProps(contextData);
|
||||
};
|
||||
|
||||
export default CustomErrorComponent;
|
||||
@ -20,7 +20,7 @@ import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||
import { setModalState } from '@/store/modal/modalSlice';
|
||||
import styles from '@/styles/pages/Home.module.scss';
|
||||
|
||||
import { DIGITALOCEAN_URL, DOCS_URL, DONATION_URL, GITHUB_URL } from '../constants';
|
||||
import { DIGITALOCEAN_URL, DOCS_URL, DONATION_URL, GITHUB_URL, REDDIT_URL } from '../constants';
|
||||
|
||||
export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => ({
|
||||
props: {
|
||||
@ -177,6 +177,12 @@ const Home: NextPage = () => {
|
||||
</Button>
|
||||
</a>
|
||||
|
||||
<a href={REDDIT_URL} target="_blank" rel="noreferrer">
|
||||
<Button variant="text" startIcon={<LinkIcon />}>
|
||||
{t<string>('landing.links.links.reddit')}
|
||||
</Button>
|
||||
</a>
|
||||
|
||||
<a href={DONATION_URL} target="_blank" rel="noreferrer">
|
||||
<Button variant="text" startIcon={<LinkIcon />}>
|
||||
{t<string>('landing.links.links.donate')}
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
"present": "Heute"
|
||||
},
|
||||
"subtitle": "Ein kostenloser Open Source Lebenslauf-Baukasten.",
|
||||
"title": "Reactive Resume",
|
||||
"title": "Reaktives Lebenslauf",
|
||||
"toast": {
|
||||
"error": {
|
||||
"upload-file-size": "Bitte laden Sie nur Dateien unter 2 Megabytes hoch.",
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
"actions": {
|
||||
"add": "Add New {{token}}",
|
||||
"delete": "Delete {{token}}",
|
||||
"edit": "Edit {{token}}",
|
||||
"duplicate": "Duplicate Section"
|
||||
"duplicate": "Duplicate Section",
|
||||
"edit": "Edit {{token}}"
|
||||
},
|
||||
"columns": {
|
||||
"heading": "Columns",
|
||||
@ -81,13 +81,13 @@
|
||||
"center-artboard": "Center Artboard",
|
||||
"copy-link": "Copy Link to Resume",
|
||||
"export-pdf": "Export PDF",
|
||||
"redo": "Redo",
|
||||
"toggle-orientation": "Toggle Page Orientation",
|
||||
"toggle-page-break-line": "Toggle Page Break Line",
|
||||
"toggle-sidebars": "Toggle Sidebars",
|
||||
"zoom-in": "Zoom In",
|
||||
"zoom-out": "Zoom Out",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo"
|
||||
"zoom-in": "Zoom In",
|
||||
"zoom-out": "Zoom Out"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
@ -115,6 +115,9 @@
|
||||
"actions": {
|
||||
"photo-filters": "Photo Filters"
|
||||
},
|
||||
"birthdate": {
|
||||
"label": "Date of Birth"
|
||||
},
|
||||
"heading": "Basics",
|
||||
"headline": {
|
||||
"label": "Headline"
|
||||
@ -122,9 +125,6 @@
|
||||
"name": {
|
||||
"label": "Full Name"
|
||||
},
|
||||
"birthdate": {
|
||||
"label": "Date of Birth"
|
||||
},
|
||||
"photo-filters": {
|
||||
"effects": {
|
||||
"border": {
|
||||
@ -265,14 +265,15 @@
|
||||
"button": "GitHub Issues",
|
||||
"heading": "Bugs? Feature Requests?"
|
||||
},
|
||||
"docs": "Documentation",
|
||||
"donate": {
|
||||
"body": "If you liked using Reactive Resume, please consider donating as much as you can to the cause of keeping the app up and running, without ads and free forever.",
|
||||
"button": "Buy me a coffee",
|
||||
"heading": "Donate to Reactive Resume"
|
||||
},
|
||||
"github": "Source Code",
|
||||
"docs": "Documentation",
|
||||
"heading": "Links"
|
||||
"heading": "Links",
|
||||
"reddit": "Reddit"
|
||||
},
|
||||
"settings": {
|
||||
"global": {
|
||||
@ -291,14 +292,14 @@
|
||||
},
|
||||
"heading": "Settings",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "Paper Size",
|
||||
"secondary": "Determines the dimensions of your resume pages"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "Break Line",
|
||||
"secondary": "Show a line on all pages to mark the height of an A4 page"
|
||||
},
|
||||
"format": {
|
||||
"primary": "Paper Size",
|
||||
"secondary": "Determines the dimensions of your resume pages"
|
||||
},
|
||||
"heading": "Page",
|
||||
"orientation": {
|
||||
"disabled": "Has no effect when there is only one page",
|
||||
|
||||
@ -20,23 +20,24 @@
|
||||
"links": {
|
||||
"heading": "Links",
|
||||
"links": {
|
||||
"docs": "Documentation",
|
||||
"donate": "Donate",
|
||||
"github": "Source Code",
|
||||
"docs": "Documentation",
|
||||
"privacy": "Privacy Policy",
|
||||
"reddit": "Reddit",
|
||||
"service": "Terms of Service"
|
||||
}
|
||||
},
|
||||
"screenshots": {
|
||||
"heading": "Screenshots"
|
||||
},
|
||||
"testimonials": {
|
||||
"heading": "Testimonials",
|
||||
"body": "Good or bad, I would love to hear your opinion on Reactive Resume and how the experience has been for you.<br/>Here are some of the messages sent in by users across the world.",
|
||||
"contact": "You can reach out to me through <1>my email</1> or through the contact form on <3>my website</3>."
|
||||
},
|
||||
"summary": {
|
||||
"body": "Reactive Resume is a free and open source resume builder that's built to make the mundane tasks of creating, updating and sharing your resume as easy as 1, 2, 3. With this app, you can create multiple resumes, share them with recruiters or friends through a unique link and print it as a PDF, all for free, no ads, no tracking, without losing the integrity and privacy of your data.",
|
||||
"heading": "Summary"
|
||||
},
|
||||
"testimonials": {
|
||||
"body": "Good or bad, I would love to hear your opinion on Reactive Resume and how the experience has been for you.<br/>Here are some of the messages sent in by users across the world.",
|
||||
"contact": "You can reach out to me through <1>my email</1> or through the contact form on <3>my website</3>.",
|
||||
"heading": "Testimonials"
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
"actions": {
|
||||
"add": "{{token}} جدید اضافه کنید",
|
||||
"delete": "حذف {{token}}",
|
||||
"duplicate": "بخش تکراری",
|
||||
"edit": "ویرایش {{token}}"
|
||||
},
|
||||
"columns": {
|
||||
@ -80,13 +81,13 @@
|
||||
"center-artboard": "قرار دادن صفحه در مرکز",
|
||||
"copy-link": "کپی کردن لینک رزومه",
|
||||
"export-pdf": "خروجی PDF",
|
||||
"redo": "دوباره انجام دهید",
|
||||
"toggle-orientation": "تغییر وضعیت جهتگیری صفحه",
|
||||
"toggle-page-break-line": "تغییر وضعیت خط شکست صفحه",
|
||||
"toggle-sidebars": "باز/بسته کردن نوار کنار صفحه",
|
||||
"zoom-in": "بزرگنمایی",
|
||||
"zoom-out": "کوچکنمایی",
|
||||
"undo": "واگرد",
|
||||
"redo": "دوباره انجام دهید"
|
||||
"zoom-in": "بزرگنمایی",
|
||||
"zoom-out": "کوچکنمایی"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
@ -114,6 +115,9 @@
|
||||
"actions": {
|
||||
"photo-filters": "فیلترهای تصویر"
|
||||
},
|
||||
"birthdate": {
|
||||
"label": "تاریخ تولد"
|
||||
},
|
||||
"heading": "موارد پایه",
|
||||
"headline": {
|
||||
"label": "سرصفحه"
|
||||
@ -121,9 +125,6 @@
|
||||
"name": {
|
||||
"label": "نام کامل"
|
||||
},
|
||||
"birthdate": {
|
||||
"label": "تاریخ تولد"
|
||||
},
|
||||
"photo-filters": {
|
||||
"effects": {
|
||||
"border": {
|
||||
@ -264,14 +265,15 @@
|
||||
"button": "GitHub Issues",
|
||||
"heading": "باگها؟ درخواست ویژگی جدید؟"
|
||||
},
|
||||
"docs": "مستندات",
|
||||
"donate": {
|
||||
"body": "اگر استفاده از Reactive Resume را دوست داشتید، لطفاً تا جایی که می توانید کمک مالی کنید تا برنامه را بدون تبلیغات و برای همیشه رایگان نگه دارید.",
|
||||
"button": "برای من یک قهوه بخر",
|
||||
"heading": "کمک مالی به Reactive Resume"
|
||||
},
|
||||
"github": "کد منبع",
|
||||
"docs": "مستندات",
|
||||
"heading": "لینکها"
|
||||
"heading": "لینکها",
|
||||
"reddit": "ردیت"
|
||||
},
|
||||
"settings": {
|
||||
"global": {
|
||||
@ -290,14 +292,14 @@
|
||||
},
|
||||
"heading": "تنظیمات",
|
||||
"page": {
|
||||
"format": {
|
||||
"primary": "اندازه کاغذ",
|
||||
"secondary": "ابعاد صفحات رزومه شما را تعیین می کند"
|
||||
},
|
||||
"break-line": {
|
||||
"primary": "خط شکست",
|
||||
"secondary": "برای مشخص کردن ارتفاع صفحه A4 یک خط در همه صفحات نشان داده شود"
|
||||
},
|
||||
"format": {
|
||||
"primary": "اندازه کاغذ",
|
||||
"secondary": "ابعاد صفحات رزومه شما را تعیین می کند"
|
||||
},
|
||||
"heading": "صفحه",
|
||||
"orientation": {
|
||||
"disabled": "زمانی که تنها یک صفحه وجود دارد، تاثیری ندارد",
|
||||
|
||||
@ -20,23 +20,24 @@
|
||||
"links": {
|
||||
"heading": "لینکها",
|
||||
"links": {
|
||||
"docs": "مستندات",
|
||||
"donate": "حمایت مالی",
|
||||
"github": "کد منبع",
|
||||
"docs": "مستندات",
|
||||
"privacy": "حریم خصوصی",
|
||||
"reddit": "ردیت",
|
||||
"service": "شرایط سرویسدهی"
|
||||
}
|
||||
},
|
||||
"screenshots": {
|
||||
"heading": "اسکرینشاتها"
|
||||
},
|
||||
"testimonials": {
|
||||
"heading": "نظرات کاربران",
|
||||
"body": "خوب یا بد، من دوست دارم نظر شما را در مورد Reactive Resume و اینکه تجربه کار با آن برای شما چگونه بوده است را بدانم.<br/>تعدادی از پیام های ارسال شده توسط کاربران در سراسر جهان را اینجا میبینید.",
|
||||
"contact": "میتوانید از طریق <1>ایمیل من</1> یا فرم تماس در <3>وبسایت من</3> با من در ارتباط باشید."
|
||||
},
|
||||
"summary": {
|
||||
"body": "Reactive Resume یک رزومه ساز رایگان و متنباز است که برای ایجاد، به روز رسانی و به اشتراک گذاری رزومه شما به آسانی شمردن ۱، ۲، ۳ ساخته شده است. با این برنامه، می توانید چندین رزومه ایجاد کنید و آنها را با کارفرماها یا دوستان از طریق یک لینک منحصر به فرد و چاپ آن به صورت PDF، همه به صورت رایگان، بدون تبلیغات، بدون ردیابی، بدون از دست دادن امنیت و حریم خصوصی داده های شما، به اشتراک بگذارید.",
|
||||
"heading": "درباره من"
|
||||
},
|
||||
"testimonials": {
|
||||
"body": "خوب یا بد، من دوست دارم نظر شما را در مورد Reactive Resume و اینکه تجربه کار با آن برای شما چگونه بوده است را بدانم.<br/>تعدادی از پیام های ارسال شده توسط کاربران در سراسر جهان را اینجا میبینید.",
|
||||
"contact": "میتوانید از طریق <1>ایمیل من</1> یا فرم تماس در <3>وبسایت من</3> با من در ارتباط باشید.",
|
||||
"heading": "نظرات کاربران"
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"avatar": {
|
||||
"menu": {
|
||||
"greeting": "Hallo",
|
||||
"logout": "Afmelden"
|
||||
"logout": "Uitloggen"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
@ -10,7 +10,7 @@
|
||||
"license": "Door de gemeenschap, voor de gemeenschap."
|
||||
},
|
||||
"markdown": {
|
||||
"help-text": "Deze sectie ondersteunt <1>markdown</1> opmaak."
|
||||
"help-text": "Deze sectie ondersteunt <1>html</1> opmaak."
|
||||
},
|
||||
"date": {
|
||||
"present": "Heden"
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
import env from '@beam-australia/react-env';
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
|
||||
import packageJSON from '../package.json';
|
||||
|
||||
const SENTRY_DSN = env('CLIENT_SENTRY_DSN');
|
||||
|
||||
if (SENTRY_DSN) {
|
||||
Sentry.init({
|
||||
dsn: SENTRY_DSN,
|
||||
tracesSampleRate: 1.0,
|
||||
release: packageJSON.version,
|
||||
enabled: process.env.NODE_ENV === 'production',
|
||||
});
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
defaults.url=https://sentry.io/
|
||||
defaults.org=reactive-resume
|
||||
defaults.project=client
|
||||
@ -1,15 +0,0 @@
|
||||
import env from '@beam-australia/react-env';
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
|
||||
import packageJSON from '../package.json';
|
||||
|
||||
const SENTRY_DSN = env('CLIENT_SENTRY_DSN');
|
||||
|
||||
if (SENTRY_DSN) {
|
||||
Sentry.init({
|
||||
dsn: SENTRY_DSN,
|
||||
tracesSampleRate: 1.0,
|
||||
release: packageJSON.version,
|
||||
enabled: process.env.NODE_ENV === 'production',
|
||||
});
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import env from '@beam-australia/react-env';
|
||||
import _axios from 'axios';
|
||||
import _axios, { RawAxiosRequestHeaders } from 'axios';
|
||||
import Router from 'next/router';
|
||||
|
||||
import { logout } from '@/store/auth/authSlice';
|
||||
@ -20,7 +20,7 @@ axios.interceptors.request.use((config) => {
|
||||
const { accessToken } = store.getState().auth;
|
||||
|
||||
config.headers = {
|
||||
...config.headers,
|
||||
...(config.headers as RawAxiosRequestHeaders),
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
};
|
||||
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
@import url('https://fonts.googleapis.com/icon?family=Material+Icons');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
||||
|
||||
// KaTeX (for remark-math)
|
||||
@import url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css');
|
||||
|
||||
// Tailwind CSS
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@ -35,6 +38,10 @@
|
||||
ul li {
|
||||
@apply ml-4 list-outside;
|
||||
}
|
||||
|
||||
.footnotes p {
|
||||
@apply inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -75,7 +75,7 @@ const Section: React.FC<SectionProps> = ({
|
||||
{Array.from(Array(8).keys()).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="mr-1 h-2 w-4 rounded-sm border"
|
||||
className="mr-1 h-2 w-full max-w-[1rem] rounded-sm border"
|
||||
style={{
|
||||
borderColor: primaryColor,
|
||||
backgroundColor: levelNum / (10 / 8) > index ? primaryColor : '',
|
||||
|
||||
@ -10,7 +10,7 @@ export const dateFormatOptions: string[] = [
|
||||
'DD.MM.YYYY',
|
||||
'DD/MM/YYYY',
|
||||
'MM.DD.YYYY',
|
||||
'M.D.YYYY',
|
||||
'M.D.YYYY',
|
||||
'MM/DD/YYYY',
|
||||
'YYYY.MM.DD',
|
||||
'YYYY/MM/DD',
|
||||
@ -20,7 +20,7 @@ export const dateFormatOptions: string[] = [
|
||||
'YYYY',
|
||||
];
|
||||
|
||||
export const getRelativeTime = (timestamp: dayjs.ConfigType): string => dayjs(timestamp).utc().toNow(true);
|
||||
export const getRelativeTime = (timestamp: dayjs.ConfigType): string => dayjs(timestamp).toNow(true);
|
||||
|
||||
export const formatDateString = (date: string | DateRange, formatStr: string): string | null => {
|
||||
const presentString = i18n?.t<string>('common.date.present') ?? '';
|
||||
@ -31,7 +31,7 @@ export const formatDateString = (date: string | DateRange, formatStr: string): s
|
||||
if (isString(date)) {
|
||||
if (!dayjs(date).isValid()) return null;
|
||||
|
||||
return dayjs(date).format(formatStr);
|
||||
return dayjs.utc(date).local().format(formatStr);
|
||||
}
|
||||
|
||||
// If `date` is a DateRange
|
||||
@ -40,12 +40,12 @@ export const formatDateString = (date: string | DateRange, formatStr: string): s
|
||||
if (!dayjs(date.start).isValid()) return null;
|
||||
|
||||
if (dayjs(date.start).isSame(date.end)) {
|
||||
return dayjs(date.start).format(formatStr);
|
||||
return dayjs.utc(date.start).local().format(formatStr);
|
||||
}
|
||||
|
||||
if (!isEmpty(date.end) && dayjs(date.end).isValid()) {
|
||||
return `${dayjs(date.start).format(formatStr)} - ${dayjs(date.end).format(formatStr)}`;
|
||||
return `${dayjs.utc(date.start).local().format(formatStr)} - ${dayjs.utc(date.end).local().format(formatStr)}`;
|
||||
}
|
||||
|
||||
return `${dayjs(date.start).format(formatStr)} - ${presentString}`;
|
||||
return `${dayjs.utc(date.start).local().format(formatStr)} - ${presentString}`;
|
||||
};
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
FaHackerrank,
|
||||
FaInstagram,
|
||||
FaLinkedinIn,
|
||||
FaMastodon,
|
||||
FaMedium,
|
||||
FaSkype,
|
||||
FaSoundcloud,
|
||||
@ -18,10 +19,11 @@ import {
|
||||
FaXing,
|
||||
FaYoutube,
|
||||
} from 'react-icons/fa';
|
||||
import { SiCodechef, SiCodeforces } from 'react-icons/si';
|
||||
import { SiCodeberg, SiCodechef, SiCodeforces } from 'react-icons/si';
|
||||
|
||||
const profileIconMap: Record<string, JSX.Element> = {
|
||||
behance: <FaBehance />,
|
||||
codeberg: <SiCodeberg />,
|
||||
codechef: <SiCodechef />,
|
||||
codeforces: <SiCodeforces />,
|
||||
dribbble: <FaDribbble />,
|
||||
@ -31,6 +33,7 @@ const profileIconMap: Record<string, JSX.Element> = {
|
||||
hackerrank: <FaHackerrank />,
|
||||
instagram: <FaInstagram />,
|
||||
linkedin: <FaLinkedinIn />,
|
||||
mastodon: <FaMastodon />,
|
||||
medium: <FaMedium />,
|
||||
skype: <FaSkype />,
|
||||
soundcloud: <FaSoundcloud />,
|
||||
|
||||
@ -13,9 +13,6 @@ const DateWrapper: React.FC<React.PropsWithChildren<unknown>> = ({ children }) =
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
// Set Default Timezone to UTC
|
||||
dayjs.tz.setDefault('UTC');
|
||||
|
||||
// Locales
|
||||
require('dayjs/locale/am');
|
||||
require('dayjs/locale/ar');
|
||||
|
||||
@ -2,16 +2,14 @@ import DateWrapper from './DateWrapper';
|
||||
import FontWrapper from './FontWrapper';
|
||||
import ThemeWrapper from './ThemeWrapper';
|
||||
|
||||
const WrapperRegistry: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||
return (
|
||||
<ThemeWrapper>
|
||||
<FontWrapper>
|
||||
<DateWrapper>
|
||||
<>{children}</>
|
||||
</DateWrapper>
|
||||
</FontWrapper>
|
||||
</ThemeWrapper>
|
||||
);
|
||||
};
|
||||
const WrapperRegistry: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => (
|
||||
<ThemeWrapper>
|
||||
<FontWrapper>
|
||||
<DateWrapper>
|
||||
<>{children}</>
|
||||
</DateWrapper>
|
||||
</FontWrapper>
|
||||
</ThemeWrapper>
|
||||
);
|
||||
|
||||
export default WrapperRegistry;
|
||||
|
||||
24
package.json
24
package.json
@ -1,13 +1,15 @@
|
||||
{
|
||||
"name": "reactive-resume",
|
||||
"version": "3.6.15",
|
||||
"version": "3.6.17",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "env-cmd --silent cross-var cross-env VERSION=$npm_package_version turbo run dev",
|
||||
"lint": "turbo run lint",
|
||||
"build": "env-cmd --silent cross-var cross-env VERSION=$npm_package_version turbo run build",
|
||||
"start": "env-cmd --silent cross-var cross-env VERSION=$npm_package_version turbo run start",
|
||||
"format": "prettier --write ."
|
||||
"update-deps": "ncu -x nanoid,class-validator --deep -u && pnpm install",
|
||||
"generate-env": "ts-node ./scripts/generate-env.ts",
|
||||
"format": "prettier --write .",
|
||||
"lint": "turbo run lint"
|
||||
},
|
||||
"workspaces": [
|
||||
"schema",
|
||||
@ -18,16 +20,20 @@
|
||||
"cross-env": "^7.0.3",
|
||||
"cross-var": "^1.1.0",
|
||||
"env-cmd": "^10.1.0",
|
||||
"turbo": "^1.6.3"
|
||||
"turbo": "^1.6.3",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^5.44.0",
|
||||
"@typescript-eslint/parser": "^5.44.0",
|
||||
"eslint": "^8.28.0",
|
||||
"@types/node": "^18.11.18",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.0",
|
||||
"@typescript-eslint/parser": "^5.48.0",
|
||||
"eslint": "^8.31.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-simple-import-sort": "^8.0.0",
|
||||
"prettier": "^2.8.0",
|
||||
"typescript": "^4.9.3"
|
||||
"npm-check-updates": "^16.6.2",
|
||||
"prettier": "^2.8.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "17.0.2",
|
||||
|
||||
4153
pnpm-lock.yaml
generated
4153
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,7 @@
|
||||
"build": "tsc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.28.0",
|
||||
"typescript": "^4.9.3"
|
||||
"eslint": "^8.31.0",
|
||||
"typescript": "^4.9.4"
|
||||
}
|
||||
}
|
||||
|
||||
66
scripts/generate-env.ts
Normal file
66
scripts/generate-env.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { randomBytes } from 'crypto';
|
||||
import fs from 'fs/promises';
|
||||
|
||||
const envMap = {
|
||||
TZ: 'UTC',
|
||||
PUBLIC_URL: '',
|
||||
PUBLIC_SERVER_URL: '',
|
||||
PUBLIC_GOOGLE_CLIENT_ID: '',
|
||||
POSTGRES_DB: 'postgres',
|
||||
POSTGRES_USER: 'postgres',
|
||||
POSTGRES_PASSWORD: 'postgres',
|
||||
SECRET_KEY: '',
|
||||
POSTGRES_HOST: 'localhost',
|
||||
POSTGRES_PORT: '5432',
|
||||
POSTGRES_SSL_CERT: '',
|
||||
JWT_SECRET: '',
|
||||
JWT_EXPIRY_TIME: '604800',
|
||||
GOOGLE_CLIENT_SECRET: '',
|
||||
GOOGLE_API_KEY: '',
|
||||
MAIL_FROM_NAME: '',
|
||||
MAIL_FROM_EMAIL: '',
|
||||
MAIL_HOST: '',
|
||||
MAIL_PORT: '',
|
||||
MAIL_USERNAME: '',
|
||||
MAIL_PASSWORD: '',
|
||||
STORAGE_BUCKET: '',
|
||||
STORAGE_REGION: '',
|
||||
STORAGE_ENDPOINT: '',
|
||||
STORAGE_URL_PREFIX: '',
|
||||
STORAGE_ACCESS_KEY: '',
|
||||
STORAGE_SECRET_KEY: '',
|
||||
PDF_DELETION_TIME: '345600000',
|
||||
PUBLIC_FLAG_DISABLE_SIGNUPS: 'false',
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
// URLs
|
||||
// If running in a Gitpod environment, auto generated the URLs
|
||||
if (process.env.GITPOD_WORKSPACE_URL) {
|
||||
const baseUrl = new URL(process.env.GITPOD_WORKSPACE_URL!).host;
|
||||
|
||||
envMap['PUBLIC_SERVER_URL'] = `https://3100-${baseUrl}`;
|
||||
envMap['PUBLIC_URL'] = `https://3000-${baseUrl}`;
|
||||
}
|
||||
// Otherwise, fallback to localhost
|
||||
else {
|
||||
envMap['PUBLIC_SERVER_URL'] = 'https://localhost:3100';
|
||||
envMap['PUBLIC_URL'] = 'https://localhost:3000';
|
||||
}
|
||||
|
||||
// Secret Key
|
||||
envMap['SECRET_KEY'] = randomBytes(20).toString('hex');
|
||||
envMap['JWT_SECRET'] = randomBytes(40).toString('hex');
|
||||
|
||||
const envFile = Object.entries(envMap)
|
||||
.reduce((acc, [key, value]) => {
|
||||
acc.push(`${key}=${value}`);
|
||||
|
||||
return acc;
|
||||
}, [] as string[])
|
||||
.join('\n');
|
||||
|
||||
await fs.writeFile('.env', envFile);
|
||||
};
|
||||
|
||||
main();
|
||||
@ -21,17 +21,9 @@ COPY --from=dependencies /app/node_modules ./node_modules
|
||||
COPY --from=dependencies /app/schema/node_modules ./schema/node_modules
|
||||
COPY --from=dependencies /app/server/node_modules ./server/node_modules
|
||||
|
||||
ARG TURBO_TEAM
|
||||
ARG TURBO_TOKEN
|
||||
ARG SENTRY_AUTH_TOKEN
|
||||
|
||||
ENV TURBO_TEAM $TURBO_TEAM
|
||||
ENV TURBO_TOKEN $TURBO_TOKEN
|
||||
ENV SENTRY_AUTH_TOKEN $SENTRY_AUTH_TOKEN
|
||||
|
||||
RUN pnpm run build --filter server
|
||||
|
||||
FROM mcr.microsoft.com/playwright:v1.28.0-focal as production
|
||||
FROM mcr.microsoft.com/playwright:v1.29.1-focal as production
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@ -8,67 +8,66 @@
|
||||
"start": "node dist/main"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.216.0",
|
||||
"@nestjs/axios": "^1.0.0",
|
||||
"@nestjs/common": "^9.2.0",
|
||||
"@aws-sdk/client-s3": "^3.241.0",
|
||||
"@nestjs/axios": "^1.0.1",
|
||||
"@nestjs/common": "^9.2.1",
|
||||
"@nestjs/config": "^2.2.0",
|
||||
"@nestjs/core": "^9.2.0",
|
||||
"@nestjs/jwt": "^9.0.0",
|
||||
"@nestjs/core": "^9.2.1",
|
||||
"@nestjs/jwt": "^10.0.1",
|
||||
"@nestjs/mapped-types": "^1.2.0",
|
||||
"@nestjs/passport": "^9.0.0",
|
||||
"@nestjs/platform-express": "^9.2.0",
|
||||
"@nestjs/platform-express": "^9.2.1",
|
||||
"@nestjs/schedule": "^2.1.0",
|
||||
"@nestjs/serve-static": "^3.0.0",
|
||||
"@nestjs/terminus": "^9.1.3",
|
||||
"@nestjs/terminus": "^9.1.4",
|
||||
"@nestjs/typeorm": "^9.0.1",
|
||||
"@sentry/node": "^7.21.1",
|
||||
"@sentry/tracing": "^7.21.1",
|
||||
"@types/passport": "^1.0.11",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"cache-manager": "^5.1.3",
|
||||
"cache-manager": "^5.1.4",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.13.2",
|
||||
"class-validator": "^0.13.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"csvtojson": "^2.0.10",
|
||||
"dayjs": "^1.11.6",
|
||||
"dayjs": "^1.11.7",
|
||||
"google-auth-library": "^8.7.0",
|
||||
"joi": "^17.7.0",
|
||||
"lodash": "^4.17.21",
|
||||
"multer": "^1.4.4",
|
||||
"nanoid": "^3.3.4",
|
||||
"nanoid": "3.3.4",
|
||||
"node-stream-zip": "^1.15.0",
|
||||
"nodemailer": "^6.8.0",
|
||||
"passport": "^0.6.0",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pg": "^8.8.0",
|
||||
"playwright-chromium": "^1.28.1",
|
||||
"playwright-chromium": "^1.29.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.5.7",
|
||||
"typeorm": "0.3.10",
|
||||
"rxjs": "^7.8.0",
|
||||
"typeorm": "0.3.11",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^9.1.5",
|
||||
"@nestjs/schematics": "^9.0.3",
|
||||
"@nestjs/cli": "^9.1.8",
|
||||
"@nestjs/schematics": "^9.0.4",
|
||||
"@reactive-resume/schema": "workspace:*",
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/express": "^4.17.14",
|
||||
"@types/lodash": "^4.14.190",
|
||||
"@types/express": "^4.17.15",
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/nodemailer": "^6.4.6",
|
||||
"@types/passport-jwt": "^3.0.7",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/nodemailer": "^6.4.7",
|
||||
"@types/passport-jwt": "^3.0.8",
|
||||
"@types/passport-local": "^1.0.34",
|
||||
"prettier": "^2.8.0",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"prettier": "^2.8.1",
|
||||
"source-map-support": "^0.5.21",
|
||||
"ts-loader": "^9.4.1",
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "^4.1.0",
|
||||
"typescript": "^4.9.3",
|
||||
"tsconfig-paths": "^4.1.2",
|
||||
"typescript": "^4.9.4",
|
||||
"webpack": "^5.75.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
defaults.url=https://sentry.io/
|
||||
defaults.org=reactive-resume
|
||||
defaults.project=server
|
||||
@ -11,7 +11,6 @@ import { HttpExceptionFilter } from './filters/http-exception.filter';
|
||||
import { FontsModule } from './fonts/fonts.module';
|
||||
import { HealthModule } from './health/health.module';
|
||||
import { IntegrationsModule } from './integrations/integrations.module';
|
||||
import { SentryInterceptor } from './interceptors/sentry.interceptor';
|
||||
import { MailModule } from './mail/mail.module';
|
||||
import { PrinterModule } from './printer/printer.module';
|
||||
import { ResumeModule } from './resume/resume.module';
|
||||
@ -41,10 +40,6 @@ import { UsersModule } from './users/users.module';
|
||||
provide: APP_INTERCEPTOR,
|
||||
useClass: ClassSerializerInterceptor,
|
||||
},
|
||||
{
|
||||
provide: APP_INTERCEPTOR,
|
||||
useClass: SentryInterceptor,
|
||||
},
|
||||
{
|
||||
provide: APP_FILTER,
|
||||
useClass: HttpExceptionFilter,
|
||||
|
||||
@ -7,7 +7,6 @@ import authConfig from './auth.config';
|
||||
import cacheConfig from './cache.config';
|
||||
import databaseConfig from './database.config';
|
||||
import googleConfig from './google.config';
|
||||
import loggingConfig from './logging.config';
|
||||
import mailConfig from './mail.config';
|
||||
import storageConfig from './storage.config';
|
||||
|
||||
@ -60,24 +59,12 @@ const validationSchema = Joi.object({
|
||||
PDF_DELETION_TIME: Joi.number()
|
||||
.default(4 * 24 * 60 * 60 * 1000) // 4 days
|
||||
.allow(''),
|
||||
|
||||
// Logging
|
||||
SERVER_SENTRY_DSN: Joi.string().allow(''),
|
||||
});
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
NestConfigModule.forRoot({
|
||||
load: [
|
||||
appConfig,
|
||||
authConfig,
|
||||
cacheConfig,
|
||||
databaseConfig,
|
||||
googleConfig,
|
||||
loggingConfig,
|
||||
mailConfig,
|
||||
storageConfig,
|
||||
],
|
||||
load: [appConfig, authConfig, cacheConfig, databaseConfig, googleConfig, mailConfig, storageConfig],
|
||||
validationSchema: validationSchema,
|
||||
}),
|
||||
],
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs('logging', () => ({
|
||||
sentryDSN: process.env.SERVER_SENTRY_DSN,
|
||||
}));
|
||||
@ -15,7 +15,6 @@ import {
|
||||
} from '@reactive-resume/schema';
|
||||
import csv from 'csvtojson';
|
||||
import dayjs from 'dayjs';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import { readFile, unlink } from 'fs/promises';
|
||||
import { cloneDeep, get, isEmpty, merge } from 'lodash';
|
||||
import StreamZip from 'node-stream-zip';
|
||||
@ -29,9 +28,7 @@ import { ResumeService } from '@/resume/resume.service';
|
||||
|
||||
@Injectable()
|
||||
export class IntegrationsService {
|
||||
constructor(private resumeService: ResumeService) {
|
||||
dayjs.extend(utc);
|
||||
}
|
||||
constructor(private resumeService: ResumeService) {}
|
||||
|
||||
async linkedIn(userId: number, path: string): Promise<ResumeEntity> {
|
||||
let archive: StreamZip.StreamZipAsync;
|
||||
@ -43,7 +40,7 @@ export class IntegrationsService {
|
||||
const resume: Partial<Resume> = cloneDeep(defaultState);
|
||||
|
||||
// Basics
|
||||
const timestamp = dayjs().utc().format(FILENAME_TIMESTAMP);
|
||||
const timestamp = dayjs().format(FILENAME_TIMESTAMP);
|
||||
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
|
||||
name: `Imported from LinkedIn (${timestamp})`,
|
||||
slug: `imported-from-linkedin-${timestamp}`,
|
||||
@ -276,7 +273,7 @@ export class IntegrationsService {
|
||||
const resume: Partial<Resume> = cloneDeep(defaultState);
|
||||
|
||||
// Metadata
|
||||
const timestamp = dayjs().utc().format(FILENAME_TIMESTAMP);
|
||||
const timestamp = dayjs().format(FILENAME_TIMESTAMP);
|
||||
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
|
||||
name: `Imported from JSON Resume (${timestamp})`,
|
||||
slug: `imported-from-json-resume-${timestamp}`,
|
||||
@ -611,7 +608,7 @@ export class IntegrationsService {
|
||||
const resume: Partial<Resume> = cloneDeep(jsonResume);
|
||||
|
||||
// Metadata
|
||||
const timestamp = dayjs().utc().format(FILENAME_TIMESTAMP);
|
||||
const timestamp = dayjs().format(FILENAME_TIMESTAMP);
|
||||
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
|
||||
name: `Imported from Reactive Resume (${timestamp})`,
|
||||
slug: `imported-from-reactive-resume-${timestamp}`,
|
||||
@ -632,7 +629,7 @@ export class IntegrationsService {
|
||||
const resume: Partial<Resume> = cloneDeep(defaultState);
|
||||
|
||||
// Metadata
|
||||
const timestamp = dayjs().utc().format(FILENAME_TIMESTAMP);
|
||||
const timestamp = dayjs().format(FILENAME_TIMESTAMP);
|
||||
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
|
||||
name: `Imported from Reactive Resume V2 (${timestamp})`,
|
||||
slug: `imported-from-reactive-resume-v2-${timestamp}`,
|
||||
@ -958,6 +955,6 @@ export class IntegrationsService {
|
||||
}
|
||||
|
||||
private parseDate = (date: string): string => {
|
||||
return isEmpty(date) ? '' : dayjs(date).utc().toISOString();
|
||||
return isEmpty(date) ? '' : dayjs(date).toISOString();
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { Observable } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class SentryInterceptor implements NestInterceptor {
|
||||
intercept(_context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
return next.handle().pipe(
|
||||
tap(null, (exception) => {
|
||||
Sentry.captureException(exception);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,7 @@
|
||||
import '@sentry/tracing';
|
||||
|
||||
import { Logger, ValidationPipe } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import cookieParser from 'cookie-parser';
|
||||
|
||||
import { AppModule } from './app.module';
|
||||
@ -13,27 +10,16 @@ const bootstrap = async () => {
|
||||
const app = await NestFactory.create<NestExpressApplication>(AppModule);
|
||||
const configService = app.get(ConfigService);
|
||||
|
||||
const appUrl = configService.get<string>('app.url');
|
||||
|
||||
// Middleware
|
||||
app.enableCors({ credentials: true });
|
||||
app.enableCors({ origin: [appUrl], credentials: true });
|
||||
app.enableShutdownHooks();
|
||||
app.use(cookieParser());
|
||||
|
||||
// Pipes
|
||||
app.useGlobalPipes(new ValidationPipe({ transform: true }));
|
||||
|
||||
// Sentry Error Logging
|
||||
const sentryDSN = configService.get<string>('logging.sentryDSN');
|
||||
const version = configService.get<string>('app.version');
|
||||
|
||||
if (sentryDSN) {
|
||||
Sentry.init({
|
||||
dsn: sentryDSN,
|
||||
release: version,
|
||||
tracesSampleRate: 1.0,
|
||||
enabled: process.env.NODE_ENV === 'production',
|
||||
});
|
||||
}
|
||||
|
||||
// Server Port
|
||||
const port = configService.get<number>('app.port');
|
||||
await app.listen(port);
|
||||
|
||||
@ -31,10 +31,8 @@ export class PrinterService implements OnModuleInit, OnModuleDestroy {
|
||||
const publicUrl = `${serverUrl}/assets/exports/${filename}`;
|
||||
|
||||
try {
|
||||
// check if file already exists
|
||||
await access(join(directory, filename));
|
||||
} catch {
|
||||
// delete old files and scheduler jobs
|
||||
const activeSchedulerTimeouts = this.schedulerRegistry.getTimeouts();
|
||||
await readdir(directory).then(async (files) => {
|
||||
await Promise.all(
|
||||
@ -49,7 +47,6 @@ export class PrinterService implements OnModuleInit, OnModuleDestroy {
|
||||
);
|
||||
});
|
||||
|
||||
// create file as it doesn't exist
|
||||
const url = this.configService.get<string>('app.url');
|
||||
const secretKey = this.configService.get<string>('app.secretKey');
|
||||
const pdfDeletionTime = this.configService.get<number>('cache.pdfDeletionTime');
|
||||
@ -57,6 +54,7 @@ export class PrinterService implements OnModuleInit, OnModuleDestroy {
|
||||
const page = await this.browser.newPage();
|
||||
|
||||
await page.goto(`${url}/${username}/${slug}/printer?secretKey=${secretKey}`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForSelector('html.wf-active');
|
||||
|
||||
const pageFormat: PageConfig['format'] = await page.$$eval(
|
||||
|
||||
@ -5,9 +5,9 @@ const sampleData: Partial<Resume> = {
|
||||
name: 'Alexis Jones',
|
||||
email: 'alexis.jones@gmail.com',
|
||||
phone: '+1 800 1200 3820',
|
||||
birthdate: '1995-08-06T00:00:00.000Z',
|
||||
birthdate: '1995-08-06',
|
||||
photo: {
|
||||
url: `/images/sample-photo.jpg`,
|
||||
url: `https://i.imgur.com/40gTnCx.jpg`,
|
||||
filters: {
|
||||
size: 128,
|
||||
shape: 'rounded-square',
|
||||
@ -53,7 +53,7 @@ const sampleData: Partial<Resume> = {
|
||||
url: 'https://www.espritcam.com',
|
||||
date: {
|
||||
end: '',
|
||||
start: '2015-09-01T16:34:27.000Z',
|
||||
start: '2015-09-01',
|
||||
},
|
||||
name: 'DP Technology Corp.',
|
||||
summary:
|
||||
@ -64,8 +64,8 @@ const sampleData: Partial<Resume> = {
|
||||
id: '285d78f8-df56-4569-ba6b-cff5ebe5381e',
|
||||
url: 'https://www.vokophone.com',
|
||||
date: {
|
||||
end: '2015-07-31T22:00:00.000Z',
|
||||
start: '2011-05-31T22:00:00.000Z',
|
||||
end: '2015-07-31',
|
||||
start: '2011-05-31',
|
||||
},
|
||||
name: 'Voko Communications',
|
||||
summary:
|
||||
@ -84,7 +84,7 @@ const sampleData: Partial<Resume> = {
|
||||
{
|
||||
title: 'Blitz Hackathon',
|
||||
awarder: '2nd Place',
|
||||
date: '2018-03-31T22:00:00.000Z',
|
||||
date: '2018-03-31',
|
||||
url: '',
|
||||
summary: '',
|
||||
id: '657cadb0-c07d-4a35-8351-9079598c7ac0',
|
||||
@ -92,7 +92,7 @@ const sampleData: Partial<Resume> = {
|
||||
{
|
||||
title: 'Carl-Zeiss Hackathon',
|
||||
awarder: '2nd Place',
|
||||
date: '2017-05-09T22:00:00.000Z',
|
||||
date: '2017-05-09',
|
||||
url: '',
|
||||
summary: '',
|
||||
id: 'db3bc5cb-483e-4221-9867-9c28ee5f2051',
|
||||
|
||||
16
turbo.json
16
turbo.json
@ -1,19 +1,13 @@
|
||||
{
|
||||
"$schema": "https://turborepo.org/schema.json",
|
||||
"globalDependencies": [".env"],
|
||||
"pipeline": {
|
||||
"dev": {
|
||||
"cache": false
|
||||
},
|
||||
"lint": {
|
||||
"cache": false
|
||||
},
|
||||
"start": {
|
||||
"cache": false
|
||||
},
|
||||
"dev": {},
|
||||
"lint": {},
|
||||
"start": {},
|
||||
"build": {
|
||||
"outputs": ["dist/**", ".next/**"],
|
||||
"dependsOn": ["^build"]
|
||||
}
|
||||
},
|
||||
"globalDependencies": [".env"]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user