mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-10 04:22:27 +10:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a1c0079db | |||
| 5b6f6b7621 | |||
| 02587255fe | |||
| 9ef2a84ac2 | |||
| 77b1c5b536 | |||
| bf956fe18c | |||
| 4114f1e1dd | |||
| 668d39fa87 | |||
| 0d88a18757 | |||
| 0630369087 | |||
| 73af4a6859 | |||
| 99ddeb25a9 | |||
| 685aa06778 | |||
| 460abc6f1d | |||
| 04f02157ac | |||
| 828a4a8715 | |||
| 5b3141cd49 | |||
| 779d22101f | |||
| ef240b2110 | |||
| 32bb7354a4 | |||
| 0dcbad1f8a | |||
| a74921b27a | |||
| d4f47423c9 | |||
| 03f9a6543c | |||
| eb89cfcf5d | |||
| c52ef9ecb7 | |||
| c499abbb88 | |||
| 1a7ee88ecd | |||
| 16d19eb70f | |||
| 331346b99c | |||
| 95d265f672 | |||
| 315c7d6328 | |||
| 490e174564 | |||
| b5cde79f8b | |||
| d50f14bb78 | |||
| c13a751c1a | |||
| 5c37fc55d5 | |||
| 48a0f90597 | |||
| 05d3f1f06f | |||
| 4d43f6a642 | |||
| f7363ccdd7 | |||
| 07c91e9ac2 | |||
| cbe08f1d2c | |||
| c2617a8277 | |||
| fe72d2de41 | |||
| 23667e218f | |||
| 977fa72dde | |||
| 5197f954c0 | |||
| 58341e4cd2 | |||
| 1559703567 | |||
| 0a1fd50d07 | |||
| 1c19062c63 | |||
| 25cf594eb9 | |||
| 1c3beee6cd | |||
| 95c3d4c315 | |||
| 85df339e56 |
@ -1,13 +1,19 @@
|
||||
# Build Artifacts
|
||||
dist
|
||||
.next
|
||||
.turbo
|
||||
|
||||
# IDEs
|
||||
.vscode
|
||||
|
||||
# Project Metadata
|
||||
.crowdin.yml
|
||||
|
||||
# Documentation
|
||||
README.md
|
||||
SECURITY.md
|
||||
CHANGELOG.md
|
||||
CODE_OF_CONDUCT.md
|
||||
|
||||
# Project Dependencies
|
||||
node_modules
|
||||
@ -18,4 +24,4 @@ Dockerfile
|
||||
docker-compose.yml
|
||||
|
||||
# Android App
|
||||
/app
|
||||
/app
|
||||
|
||||
18
.env.example
18
.env.example
@ -1,7 +1,11 @@
|
||||
# Turbo Cache (Optional)
|
||||
TURBO_TEAM=
|
||||
TURBO_TOKEN=
|
||||
|
||||
# Server + Client
|
||||
TZ=UTC
|
||||
PUBLIC_URL=http://localhost:3000
|
||||
PUBLIC_SERVER_URL=http://localhost:3000/api
|
||||
PUBLIC_URL=http://client:3000
|
||||
PUBLIC_SERVER_URL=http://server:3100
|
||||
PUBLIC_GOOGLE_CLIENT_ID=
|
||||
|
||||
# Server + Database
|
||||
@ -18,10 +22,12 @@ JWT_SECRET=
|
||||
JWT_EXPIRY_TIME=604800
|
||||
GOOGLE_CLIENT_SECRET=
|
||||
GOOGLE_API_KEY=
|
||||
SENDGRID_API_KEY=
|
||||
SENDGRID_FORGOT_PASSWORD_TEMPLATE_ID=
|
||||
SENDGRID_FROM_NAME=
|
||||
SENDGRID_FROM_EMAIL=
|
||||
MAIL_FROM_NAME=
|
||||
MAIL_FROM_EMAIL=
|
||||
MAIL_HOST=
|
||||
MAIL_PORT=
|
||||
MAIL_USERNAME=
|
||||
MAIL_PASSWORD=
|
||||
STORAGE_BUCKET=
|
||||
STORAGE_REGION=
|
||||
STORAGE_ENDPOINT=
|
||||
|
||||
@ -5,27 +5,27 @@
|
||||
"extends": ["plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"],
|
||||
"plugins": ["@typescript-eslint/eslint-plugin", "simple-import-sort", "unused-imports"],
|
||||
"rules": {
|
||||
// Unused Imports
|
||||
"no-unused-vars": "off",
|
||||
// Simple Import Sort
|
||||
"simple-import-sort/imports": "error",
|
||||
"simple-import-sort/exports": "error",
|
||||
"unused-imports/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"vars": "all",
|
||||
"args": "none",
|
||||
"varsIgnorePattern": "^_",
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
// TypeScript ESLint
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/interface-name-prefix": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
// Simple Import Sort
|
||||
"simple-import-sort/imports": "error",
|
||||
"simple-import-sort/exports": "error",
|
||||
// Unused Imports
|
||||
"no-unused-vars": "off",
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
"unused-imports/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"vars": "all",
|
||||
"varsIgnorePattern": "^_",
|
||||
"args": "none",
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
]
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
|
||||
17
.github/ISSUE_TEMPLATE/bug-report.md
vendored
17
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@ -1,36 +1,43 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Create a report to help improve
|
||||
title: "[BUG] "
|
||||
title: '[BUG] '
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**Product Flavor**
|
||||
|
||||
- [ ] Managed (https://rxresu.me)
|
||||
- [ ] Self Hosted
|
||||
|
||||
**To Reproduce**
|
||||
|
||||
<!-- Steps to reproduce the behavior: -->
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
**Screenshots**
|
||||
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: <!--[e.g. iOS]-->
|
||||
- Browser <!--[e.g. chrome, safari]-->
|
||||
- Version <!--[e.g. 22]-->
|
||||
|
||||
- OS: <!--[e.g. iOS]-->
|
||||
- Browser <!--[e.g. chrome, safari]-->
|
||||
- Version <!--[e.g. 22]-->
|
||||
|
||||
**Additional context**
|
||||
|
||||
<!-- Add any other context about the problem here. -->
|
||||
|
||||
7
.github/ISSUE_TEMPLATE/feature-request.md
vendored
7
.github/ISSUE_TEMPLATE/feature-request.md
vendored
@ -1,20 +1,23 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FEATURE] "
|
||||
title: '[FEATURE] '
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
**Describe the solution you'd like**
|
||||
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
**Additional context**
|
||||
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
|
||||
16
.github/workflows/docker-build-push.yml
vendored
16
.github/workflows/docker-build-push.yml
vendored
@ -9,9 +9,15 @@ jobs:
|
||||
name: Client
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.0.2
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- id: version
|
||||
name: Get Version
|
||||
@ -31,7 +37,7 @@ jobs:
|
||||
password: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Build and Push Client Image
|
||||
uses: docker/build-push-action@v3.0.0
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
@ -46,9 +52,15 @@ jobs:
|
||||
name: Server
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.0.2
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- id: version
|
||||
name: Get Version
|
||||
@ -68,7 +80,7 @@ jobs:
|
||||
password: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Build and Push Server Image
|
||||
uses: docker/build-push-action@v3.0.0
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
||||
88
.github/workflows/docker-build.yml
vendored
Normal file
88
.github/workflows/docker-build.yml
vendored
Normal file
@ -0,0 +1,88 @@
|
||||
name: Build Docker Image
|
||||
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
client:
|
||||
name: Client
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.0.2
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- id: vars
|
||||
name: Get Short SHA
|
||||
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.0.0
|
||||
|
||||
- id: buildx
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
with:
|
||||
install: true
|
||||
|
||||
- id: version
|
||||
name: Get Version
|
||||
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
|
||||
|
||||
- name: Build Client Image
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
file: client/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
amruthpillai/reactive-resume:client-latest
|
||||
amruthpillai/reactive-resume:client-${{ steps.vars.outputs.sha_short }}
|
||||
ghcr.io/amruthpillai/reactive-resume:client-latest
|
||||
ghcr.io/amruthpillai/reactive-resume:client-${{ steps.vars.outputs.sha_short }}
|
||||
|
||||
server:
|
||||
name: Server
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.0.2
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.0.0
|
||||
|
||||
- id: buildx
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
with:
|
||||
install: true
|
||||
|
||||
- id: version
|
||||
name: Get Version
|
||||
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
|
||||
|
||||
- name: Build Server Image
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
file: server/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
amruthpillai/reactive-resume:server-latest
|
||||
amruthpillai/reactive-resume:server-${{ steps.vars.outputs.sha_short }}
|
||||
ghcr.io/amruthpillai/reactive-resume:server-latest
|
||||
ghcr.io/amruthpillai/reactive-resume:server-${{ steps.vars.outputs.sha_short }}
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@ -7,4 +7,7 @@
|
||||
node_modules
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
|
||||
# Turbo
|
||||
.turbo
|
||||
@ -1,6 +0,0 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
pnpm install
|
||||
pnpm run lint
|
||||
pnpm run format
|
||||
@ -18,6 +18,7 @@ CHANGELOG.md
|
||||
|
||||
# Project Dependencies
|
||||
node_modules
|
||||
pnpm-lock.yaml
|
||||
|
||||
# Docker
|
||||
Dockerfile
|
||||
|
||||
8
.vscode/extensions.json
vendored
8
.vscode/extensions.json
vendored
@ -1,3 +1,7 @@
|
||||
{
|
||||
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "lokalise.i18n-ally"]
|
||||
}
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"lokalise.i18n-ally"
|
||||
]
|
||||
}
|
||||
12
.vscode/launch.json
vendored
12
.vscode/launch.json
vendored
@ -3,12 +3,12 @@
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "Debug: Server",
|
||||
"port": 9229,
|
||||
"request": "attach",
|
||||
"restart": true,
|
||||
"stopOnEntry": false,
|
||||
"protocol": "inspector"
|
||||
"protocol": "inspector",
|
||||
"stopOnEntry": false
|
||||
},
|
||||
{
|
||||
"name": "Debug: Client",
|
||||
@ -17,10 +17,10 @@
|
||||
"command": "pnpm run dev:client",
|
||||
"console": "integratedTerminal",
|
||||
"serverReadyAction": {
|
||||
"action": "debugWithChrome",
|
||||
"pattern": "started server on .+, url: (https?://.+)",
|
||||
"uriFormat": "%s",
|
||||
"action": "debugWithChrome"
|
||||
"uriFormat": "%s"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
25
.vscode/settings.json
vendored
25
.vscode/settings.json
vendored
@ -1,25 +1,22 @@
|
||||
{
|
||||
"css.validate": false,
|
||||
"scss.validate": false,
|
||||
"editor.wordWrap": "on",
|
||||
"npm.packageManager": "pnpm",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.wordWrap": "on",
|
||||
"eslint.workingDirectories": [
|
||||
"schema",
|
||||
"client",
|
||||
"server"
|
||||
],
|
||||
"i18n-ally.enabledFrameworks": [
|
||||
"react"
|
||||
],
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.localesPaths": [
|
||||
"client/public/locales"
|
||||
],
|
||||
"i18n-ally.namespace": true,
|
||||
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
|
||||
"i18n-ally.sortKeys": true,
|
||||
"scss.validate": false
|
||||
"conventionalCommits.scopes": [
|
||||
"client",
|
||||
"server",
|
||||
"docker",
|
||||
"dependencies"
|
||||
]
|
||||
}
|
||||
37
CHANGELOG.md
37
CHANGELOG.md
@ -2,6 +2,43 @@
|
||||
|
||||
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.5.3](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.5.2...v3.5.3) (2022-08-11)
|
||||
|
||||
### [3.5.2](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.5.1...v3.5.2) (2022-08-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **client:** :dizzy: add Finnish language support ([16d19eb](https://github.com/AmruthPillai/Reactive-Resume/commit/16d19eb70f64f768304f352d0f87102d328b57c1))
|
||||
|
||||
### [3.5.1](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.5.0...v3.5.1) (2022-07-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **client:** :sparkles: ask for confirmation when resetting a resume ([4d43f6a](https://github.com/AmruthPillai/Reactive-Resume/commit/4d43f6a6427198e62e9fcb995f1a28c0ee4de71e))
|
||||
* **docker:** :zap: remove ports from postgres docker instance ([07c91e9](https://github.com/AmruthPillai/Reactive-Resume/commit/07c91e9ac21e8ef120d08ab92363d8e48a55aaba))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **server:** :zap: don't initialize sendgrid if the apikey is empty ([05d3f1f](https://github.com/AmruthPillai/Reactive-Resume/commit/05d3f1f06fbffd899269a5c4dea3c52cf408125f))
|
||||
|
||||
## [3.5.0](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.4.8...v3.5.0) (2022-07-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **client:** :bug: attempt to fix the one-off date issue ([5197f95](https://github.com/AmruthPillai/Reactive-Resume/commit/5197f954c0baed3daf1c7e2c79b607354ef42024))
|
||||
* **client:** :bug: fix mui rendering of utc dates ([977fa72](https://github.com/AmruthPillai/Reactive-Resume/commit/977fa72ddeeeebf7463d43a820e85f783489a4dc))
|
||||
|
||||
### [3.4.8](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.4.7...v3.4.8) (2022-07-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **google:** add toast to display error message from google ([25cf594](https://github.com/AmruthPillai/Reactive-Resume/commit/25cf594eb948e1c2d6157028ee1fff2799df5f92))
|
||||
|
||||
### [3.4.7](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.4.6...v3.4.7) (2022-06-30)
|
||||
|
||||
|
||||
|
||||
@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
- Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
- The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
|
||||
@ -18,7 +18,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](https://rxresu.me) | [Docs](https://docs.rxresu.me)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Features](#features)
|
||||
- [Languages](#languages)
|
||||
@ -56,11 +56,13 @@ You have complete control over what goes into your resume, how it looks, what co
|
||||
|
||||
- Arabic (اَلْعَرَبِيَّةُ)
|
||||
- Bengali (বাংলা)
|
||||
- Bulgarian (български)
|
||||
- Chinese (中文)
|
||||
- Czech (čeština)
|
||||
- Danish (Dansk)
|
||||
- Dutch (Nederlands)
|
||||
- English
|
||||
- Finnish (Suomi)
|
||||
- French (Français)
|
||||
- German (Deutsch)
|
||||
- Greek (Ελληνικά)
|
||||
|
||||
@ -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.0' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
|
||||
@ -1,22 +1,19 @@
|
||||
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
|
||||
FROM node:lts-alpine AS base
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json pnpm-*.yaml ./
|
||||
RUN apk add --no-cache g++ git curl make python3 \
|
||||
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
||||
|
||||
FROM base as dependencies
|
||||
|
||||
COPY package.json pnpm-*.yaml turbo.json ./
|
||||
COPY ./schema/package.json ./schema/package.json
|
||||
COPY ./client/package.json ./client/package.json
|
||||
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
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
|
||||
|
||||
WORKDIR /app
|
||||
FROM base as builder
|
||||
|
||||
COPY . .
|
||||
|
||||
@ -24,21 +21,20 @@ 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
|
||||
|
||||
RUN pnpm run build:schema
|
||||
RUN pnpm run build:client
|
||||
ARG TURBO_TEAM
|
||||
ARG TURBO_TOKEN
|
||||
|
||||
FROM node:lts-alpine as production
|
||||
ENV TURBO_TEAM $TURBO_TEAM
|
||||
ENV TURBO_TOKEN $TURBO_TOKEN
|
||||
|
||||
WORKDIR /app
|
||||
RUN pnpm run build --filter client
|
||||
|
||||
RUN apk add --no-cache curl \
|
||||
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
||||
FROM base as production
|
||||
|
||||
COPY --from=builder /app/pnpm-*.yaml ./
|
||||
COPY --from=builder /app/package.json ./
|
||||
COPY --from=builder /app/package.json /app/pnpm-*.yaml /app/turbo.json ./
|
||||
COPY --from=builder /app/client/package.json ./client/package.json
|
||||
|
||||
RUN pnpm install -F client --frozen-lockfile --prod
|
||||
RUN pnpm install --filter client --prod --frozen-lockfile --workspace-root
|
||||
|
||||
COPY --from=builder /app/client/.next ./client/.next
|
||||
COPY --from=builder /app/client/public ./client/public
|
||||
@ -52,4 +48,4 @@ ENV PORT 3000
|
||||
HEALTHCHECK --interval=30s --timeout=20s --retries=3 --start-period=15s \
|
||||
CMD curl -fSs 127.0.0.1:3000 || exit 1
|
||||
|
||||
CMD [ "pnpm", "run", "start:client" ]
|
||||
CMD [ "pnpm", "run", "start", "--filter", "client" ]
|
||||
@ -15,7 +15,7 @@ import dayjs from 'dayjs';
|
||||
import get from 'lodash/get';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useMemo } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useMutation } from 'react-query';
|
||||
|
||||
import Heading from '@/components/shared/Heading';
|
||||
@ -36,6 +36,8 @@ const Settings = () => {
|
||||
|
||||
const { locale, ...router } = useRouter();
|
||||
|
||||
const [confirmReset, setConfirmReset] = useState(false);
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
const theme = useAppSelector((state) => state.build.theme);
|
||||
const pages = useAppSelector((state) => state.resume.metadata.layout);
|
||||
@ -48,7 +50,7 @@ const Settings = () => {
|
||||
const dateConfig: DateConfig = useMemo(() => get(resume, 'metadata.date'), [resume]);
|
||||
|
||||
const isDarkMode = useMemo(() => theme === 'dark', [theme]);
|
||||
const exampleString = useMemo(() => `Eg. ${dayjs().format(dateConfig.format)}`, [dateConfig.format]);
|
||||
const exampleString = useMemo(() => `Eg. ${dayjs().utc().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>(
|
||||
@ -78,9 +80,14 @@ const Settings = () => {
|
||||
};
|
||||
|
||||
const handleResetResume = async () => {
|
||||
await resetResumeMutation({ id });
|
||||
if (!confirmReset) {
|
||||
return setConfirmReset(true);
|
||||
}
|
||||
|
||||
queryClient.invalidateQueries(`resume/${username}/${slug}`);
|
||||
await resetResumeMutation({ id });
|
||||
await queryClient.invalidateQueries(`resume/${username}/${slug}`);
|
||||
|
||||
setConfirmReset(false);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -90,7 +97,7 @@ const Settings = () => {
|
||||
<List sx={{ padding: 0 }}>
|
||||
{/* Global Settings */}
|
||||
<>
|
||||
<ListSubheader className="rounded">
|
||||
<ListSubheader disableSticky className="rounded">
|
||||
{t<string>('builder.rightSidebar.sections.settings.global.heading')}
|
||||
</ListSubheader>
|
||||
|
||||
@ -148,7 +155,7 @@ const Settings = () => {
|
||||
|
||||
{/* Page Settings */}
|
||||
<>
|
||||
<ListSubheader className="rounded">
|
||||
<ListSubheader disableSticky className="rounded">
|
||||
{t<string>('builder.rightSidebar.sections.settings.page.heading')}
|
||||
</ListSubheader>
|
||||
|
||||
@ -180,7 +187,7 @@ const Settings = () => {
|
||||
|
||||
{/* Resume Settings */}
|
||||
<>
|
||||
<ListSubheader className="rounded">
|
||||
<ListSubheader disableSticky className="rounded">
|
||||
{t<string>('builder.rightSidebar.sections.settings.resume.heading')}
|
||||
</ListSubheader>
|
||||
|
||||
@ -202,7 +209,11 @@ const Settings = () => {
|
||||
<DeleteForever />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t<string>('builder.rightSidebar.sections.settings.resume.reset.primary')}
|
||||
primary={
|
||||
confirmReset
|
||||
? 'Are you sure?'
|
||||
: t<string>('builder.rightSidebar.sections.settings.resume.reset.primary')
|
||||
}
|
||||
secondary={t<string>('builder.rightSidebar.sections.settings.resume.reset.secondary')}
|
||||
/>
|
||||
</ListItemButton>
|
||||
|
||||
@ -63,7 +63,7 @@ const ResumeInput: React.FC<Props> = ({ type = 'text', label, path, className, m
|
||||
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());
|
||||
date && dayjs(date).utc().isValid() && onChangeValue(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -6,6 +6,7 @@ export type Language = {
|
||||
|
||||
export const languages: Language[] = [
|
||||
{ code: 'ar', name: 'Arabic', localName: 'اَلْعَرَبِيَّةُ' },
|
||||
{ code: 'bg', name: 'Bulgarian', localName: 'български' },
|
||||
{ code: 'bn', name: 'Bengali', localName: 'বাংলা' },
|
||||
{ code: 'cs', name: 'Czech', localName: 'čeština' },
|
||||
{ code: 'da', name: 'Danish', localName: 'Dansk' },
|
||||
@ -13,6 +14,7 @@ export const languages: Language[] = [
|
||||
{ code: 'el', name: 'Greek', localName: 'Ελληνικά' },
|
||||
{ code: 'en', name: 'English' },
|
||||
{ code: 'es', name: 'Spanish', localName: 'Español' },
|
||||
{ code: 'fi', name: 'Finnish', localName: 'Suomi' },
|
||||
{ code: 'fr', name: 'French', localName: 'Français' },
|
||||
{ code: 'he', name: 'Hebrew', localName: 'Ivrit' },
|
||||
{ code: 'hi', name: 'Hindi', localName: 'हिन्दी' },
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { createTheme } from '@mui/material';
|
||||
import { createTheme, ThemeOptions } from '@mui/material/styles';
|
||||
|
||||
const theme = createTheme({
|
||||
const theme: ThemeOptions = {
|
||||
typography: {
|
||||
fontSize: 12,
|
||||
fontFamily: 'Inter, sans-serif',
|
||||
@ -49,7 +49,7 @@ const theme = createTheme({
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const lightTheme = createTheme({
|
||||
...theme,
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import env from '@beam-australia/react-env';
|
||||
import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Google, Login, Visibility, VisibilityOff } from '@mui/icons-material';
|
||||
import { Login, Visibility, VisibilityOff } from '@mui/icons-material';
|
||||
import { Button, IconButton, InputAdornment, TextField } from '@mui/material';
|
||||
import { CredentialResponse, GoogleLogin } from '@react-oauth/google';
|
||||
import Joi from 'joi';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Trans, useTranslation } from 'next-i18next';
|
||||
@ -17,8 +18,6 @@ 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;
|
||||
@ -85,18 +84,16 @@ const LoginModal: React.FC = () => {
|
||||
dispatch(setModalState({ modal: 'auth.forgot', state: { open: true } }));
|
||||
};
|
||||
|
||||
const handleLoginWithGoogle = async () => {
|
||||
google.accounts.id.initialize({
|
||||
client_id: env('GOOGLE_CLIENT_ID'),
|
||||
callback: async (response: any) => {
|
||||
await loginWithGoogleMutation({ credential: response.credential });
|
||||
const handleLoginWithGoogle = async (response: CredentialResponse) => {
|
||||
if (response.credential) {
|
||||
await loginWithGoogleMutation({ credential: response.credential }, { onError: handleLoginWithGoogleError });
|
||||
|
||||
handleClose();
|
||||
},
|
||||
auto_select: false,
|
||||
});
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
google.accounts.id.prompt();
|
||||
const handleLoginWithGoogleError = () => {
|
||||
toast("Please try logging in using email/password, or use another browser that supports Google's One Tap API.");
|
||||
};
|
||||
|
||||
const PasswordVisibility = (): React.ReactElement => {
|
||||
@ -120,15 +117,7 @@ const LoginModal: React.FC = () => {
|
||||
footerChildren={
|
||||
<div className="flex gap-4">
|
||||
{!isEmpty(env('GOOGLE_CLIENT_ID')) && (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outlined"
|
||||
disabled={isLoading}
|
||||
startIcon={<Google />}
|
||||
onClick={handleLoginWithGoogle}
|
||||
>
|
||||
{t<string>('modals.auth.login.actions.google')}
|
||||
</Button>
|
||||
<GoogleLogin onSuccess={handleLoginWithGoogle} onError={handleLoginWithGoogleError} />
|
||||
)}
|
||||
|
||||
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}>
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import env from '@beam-australia/react-env';
|
||||
import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Google, HowToReg } from '@mui/icons-material';
|
||||
import { HowToReg } from '@mui/icons-material';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { CredentialResponse, GoogleLogin } from '@react-oauth/google';
|
||||
import Joi from 'joi';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Trans, useTranslation } from 'next-i18next';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useMutation } from 'react-query';
|
||||
|
||||
import BaseModal from '@/components/shared/BaseModal';
|
||||
@ -14,8 +16,6 @@ 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;
|
||||
@ -79,18 +79,16 @@ const RegisterModal: React.FC = () => {
|
||||
dispatch(setModalState({ modal: 'auth.login', state: { open: true } }));
|
||||
};
|
||||
|
||||
const handleLoginWithGoogle = async () => {
|
||||
google.accounts.id.initialize({
|
||||
client_id: env('GOOGLE_CLIENT_ID'),
|
||||
callback: async (response: any) => {
|
||||
await loginWithGoogleMutation({ credential: response.credential });
|
||||
const handleLoginWithGoogle = async (response: CredentialResponse) => {
|
||||
if (response.credential) {
|
||||
await loginWithGoogleMutation({ credential: response.credential }, { onError: handleLoginWithGoogleError });
|
||||
|
||||
handleClose();
|
||||
},
|
||||
auto_select: false,
|
||||
});
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
google.accounts.id.prompt();
|
||||
const handleLoginWithGoogleError = () => {
|
||||
toast("Please try logging in using email/password, or use another browser that supports Google's One Tap API.");
|
||||
};
|
||||
|
||||
return (
|
||||
@ -102,15 +100,7 @@ const RegisterModal: React.FC = () => {
|
||||
footerChildren={
|
||||
<div className="flex gap-4">
|
||||
{!isEmpty(env('GOOGLE_CLIENT_ID')) && (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outlined"
|
||||
disabled={isLoading}
|
||||
startIcon={<Google />}
|
||||
onClick={handleLoginWithGoogle}
|
||||
>
|
||||
{t<string>('modals.auth.register.actions.google')}
|
||||
</Button>
|
||||
<GoogleLogin onSuccess={handleLoginWithGoogle} onError={handleLoginWithGoogleError} />
|
||||
)}
|
||||
|
||||
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}>
|
||||
|
||||
@ -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).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
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).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
|
||||
@ -150,7 +150,7 @@ const CustomModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
@ -174,7 +174,7 @@ const CustomModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
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).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
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).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
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).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
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).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
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).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
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).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
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).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
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).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
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).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
|
||||
@ -5,6 +5,7 @@ const i18nConfig = {
|
||||
defaultLocale: 'en',
|
||||
locales: [
|
||||
'ar',
|
||||
'bg',
|
||||
'bn',
|
||||
'cs',
|
||||
'da',
|
||||
@ -13,6 +14,7 @@ const i18nConfig = {
|
||||
'en',
|
||||
'es',
|
||||
'fa',
|
||||
'fi',
|
||||
'fr',
|
||||
'he',
|
||||
'hi',
|
||||
|
||||
@ -2,81 +2,85 @@
|
||||
"name": "@reactive-resume/client",
|
||||
"scripts": {
|
||||
"dev": "react-env --prefix PUBLIC -- next dev",
|
||||
"lint": "next lint --fix",
|
||||
"build": "next build && npm run sitemap",
|
||||
"start": "react-env --prefix PUBLIC -- next start",
|
||||
"lint": "next lint --fix",
|
||||
"sitemap": "next-sitemap --config next-sitemap.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@beam-australia/react-env": "^3.1.1",
|
||||
"@date-io/dayjs": "^2.14.0",
|
||||
"@emotion/css": "^11.9.0",
|
||||
"@emotion/react": "^11.9.3",
|
||||
"@emotion/styled": "^11.9.3",
|
||||
"@hookform/resolvers": "2.9.3",
|
||||
"@date-io/dayjs": "^2.15.0",
|
||||
"@emotion/css": "^11.10.0",
|
||||
"@emotion/react": "^11.10.0",
|
||||
"@emotion/styled": "^11.10.0",
|
||||
"@hookform/resolvers": "2.9.7",
|
||||
"@monaco-editor/react": "^4.4.5",
|
||||
"@mui/icons-material": "^5.8.4",
|
||||
"@mui/lab": "^5.0.0-alpha.88",
|
||||
"@mui/material": "^5.8.6",
|
||||
"@mui/system": "^5.8.6",
|
||||
"@mui/x-date-pickers": "5.0.0-alpha.7",
|
||||
"@next/env": "^12.2.0",
|
||||
"@reduxjs/toolkit": "^1.8.3",
|
||||
"@mui/lab": "^5.0.0-alpha.95",
|
||||
"@mui/material": "^5.10.1",
|
||||
"@mui/system": "^5.10.1",
|
||||
"@mui/x-date-pickers": "5.0.0-beta.6",
|
||||
"@next/env": "^12.2.5",
|
||||
"@react-oauth/google": "^0.2.6",
|
||||
"@reduxjs/toolkit": "^1.8.5",
|
||||
"axios": "^0.27.2",
|
||||
"clsx": "^1.1.1",
|
||||
"dayjs": "^1.11.3",
|
||||
"clsx": "^1.2.1",
|
||||
"dayjs": "^1.11.5",
|
||||
"downloadjs": "^1.4.7",
|
||||
"joi": "^17.6.0",
|
||||
"lodash": "^4.17.21",
|
||||
"md5-hex": "^4.0.0",
|
||||
"monaco-editor": "^0.33.0",
|
||||
"nanoid": "^3.0.0",
|
||||
"next": "12.2.0",
|
||||
"next-i18next": "^11.0.0",
|
||||
"react": "18.2.0",
|
||||
"monaco-editor": "^0.34.0",
|
||||
"nanoid": "^3.3.4",
|
||||
"next": "12.2.5",
|
||||
"next-i18next": "^12.0.0",
|
||||
"react": "18",
|
||||
"react-beautiful-dnd": "^13.1.0",
|
||||
"react-colorful": "^5.5.1",
|
||||
"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.33.0",
|
||||
"react-hot-toast": "2.2.0",
|
||||
"react-hotkeys-hook": "^3.4.6",
|
||||
"react-dom": "18",
|
||||
"react-hook-form": "^7.34.2",
|
||||
"react-hot-toast": "2.3.0",
|
||||
"react-hotkeys-hook": "^3.4.7",
|
||||
"react-icons": "^4.4.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-query": "^3.39.1",
|
||||
"react-query": "^3.39.2",
|
||||
"react-redux": "^8.0.2",
|
||||
"react-zoom-pan-pinch": "^2.1.3",
|
||||
"redux": "^4.2.0",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-saga": "^1.1.3",
|
||||
"redux-saga": "^1.2.1",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"sharp": "^0.30.7",
|
||||
"uuid": "^8.3.2",
|
||||
"webfontloader": "^1.6.28"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "18",
|
||||
"@types/react-dom": "18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.6",
|
||||
"@babel/core": "^7.18.10",
|
||||
"@reactive-resume/schema": "workspace:*",
|
||||
"@tailwindcss/typography": "^0.5.2",
|
||||
"@tailwindcss/typography": "^0.5.4",
|
||||
"@types/downloadjs": "^1.4.3",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/node": "18.0.0",
|
||||
"@types/react": "18.0.14",
|
||||
"@types/lodash": "^4.14.184",
|
||||
"@types/node": "18.7.9",
|
||||
"@types/react": "18",
|
||||
"@types/react-beautiful-dnd": "^13.1.2",
|
||||
"@types/react-dom": "18",
|
||||
"@types/react-redux": "^7.1.24",
|
||||
"@types/tailwindcss": "^3.0.11",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/webfontloader": "^1.6.34",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"autoprefixer": "^10.4.8",
|
||||
"csstype": "^3.1.0",
|
||||
"eslint": "^8.18.0",
|
||||
"eslint-config-next": "12.2.0",
|
||||
"next-sitemap": "^3.1.7",
|
||||
"postcss": "^8.4.14",
|
||||
"prettier": "^2.7.1",
|
||||
"sass": "^1.53.0",
|
||||
"tailwindcss": "^3.1.4",
|
||||
"eslint-config-next": "12.2.5",
|
||||
"next-sitemap": "^3.1.21",
|
||||
"postcss": "^8.4.16",
|
||||
"sass": "^1.54.5",
|
||||
"tailwindcss": "^3.1.8",
|
||||
"typescript": "^4.7.4"
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ import clsx from 'clsx';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { GetServerSideProps, NextPage } from 'next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
@ -55,8 +54,6 @@ export const getServerSideProps: GetServerSideProps<Props | Promise<Props>, Quer
|
||||
};
|
||||
|
||||
const Printer: NextPage<Props> = ({ resume: initialData, locale }) => {
|
||||
const router = useRouter();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
@ -65,12 +62,6 @@ const Printer: NextPage<Props> = ({ resume: initialData, locale }) => {
|
||||
if (initialData) dispatch(setResume(initialData));
|
||||
}, [dispatch, initialData]);
|
||||
|
||||
useEffect(() => {
|
||||
const { pathname, asPath, query } = router;
|
||||
|
||||
router.push({ pathname, query }, asPath, { locale });
|
||||
}, [router, locale]);
|
||||
|
||||
if (!resume || isEmpty(resume)) return null;
|
||||
|
||||
const layout: string[][][] = get(resume, 'metadata.layout', []);
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import '@/styles/globals.scss';
|
||||
|
||||
import env from '@beam-australia/react-env';
|
||||
import DayjsAdapter from '@date-io/dayjs';
|
||||
import { LocalizationProvider } from '@mui/x-date-pickers';
|
||||
import { GoogleOAuthProvider } from '@react-oauth/google';
|
||||
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';
|
||||
@ -34,27 +35,27 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
|
||||
<ReduxProvider store={store}>
|
||||
<LocalizationProvider dateAdapter={DayjsAdapter}>
|
||||
<PersistGate loading={null} persistor={persistor}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<WrapperRegistry>
|
||||
<Loading />
|
||||
<GoogleOAuthProvider clientId={env('GOOGLE_CLIENT_ID')}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<WrapperRegistry>
|
||||
<Loading />
|
||||
|
||||
<Component {...pageProps} />
|
||||
<Component {...pageProps} />
|
||||
|
||||
<ModalWrapper />
|
||||
<Toaster
|
||||
position="bottom-right"
|
||||
toastOptions={{
|
||||
duration: 4000,
|
||||
className: 'toast',
|
||||
}}
|
||||
/>
|
||||
</WrapperRegistry>
|
||||
</QueryClientProvider>
|
||||
<ModalWrapper />
|
||||
<Toaster
|
||||
position="bottom-right"
|
||||
toastOptions={{
|
||||
duration: 4000,
|
||||
className: 'toast',
|
||||
}}
|
||||
/>
|
||||
</WrapperRegistry>
|
||||
</QueryClientProvider>
|
||||
</GoogleOAuthProvider>
|
||||
</PersistGate>
|
||||
</LocalizationProvider>
|
||||
</ReduxProvider>
|
||||
|
||||
<Script src="https://accounts.google.com/gsi/client" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
361
client/public/locales/bg/builder.json
Normal file
361
client/public/locales/bg/builder.json
Normal file
@ -0,0 +1,361 @@
|
||||
{
|
||||
"common": {
|
||||
"actions": {
|
||||
"add": "Добави нов {{token}}",
|
||||
"delete": "Изтрий {{token}}",
|
||||
"edit": "Редакция на {{token}}"
|
||||
},
|
||||
"columns": {
|
||||
"heading": "Колони",
|
||||
"tooltip": "Променете броя на колоните"
|
||||
},
|
||||
"form": {
|
||||
"date": {
|
||||
"label": "Дата"
|
||||
},
|
||||
"description": {
|
||||
"label": "Описание"
|
||||
},
|
||||
"email": {
|
||||
"label": "Имейл адрес"
|
||||
},
|
||||
"end-date": {
|
||||
"help-text": "Оставете това поле празно, ако все още е налице",
|
||||
"label": "Крайна Дата"
|
||||
},
|
||||
"keywords": {
|
||||
"label": "Ключови думи"
|
||||
},
|
||||
"level": {
|
||||
"label": "Ниво"
|
||||
},
|
||||
"levelNum": {
|
||||
"label": "Ниво (номер)"
|
||||
},
|
||||
"name": {
|
||||
"label": "Име"
|
||||
},
|
||||
"phone": {
|
||||
"label": "Телефон"
|
||||
},
|
||||
"position": {
|
||||
"label": "Длъжност"
|
||||
},
|
||||
"start-date": {
|
||||
"label": "Начална дата"
|
||||
},
|
||||
"subtitle": {
|
||||
"label": "Подзаглавие"
|
||||
},
|
||||
"summary": {
|
||||
"label": "Резюме"
|
||||
},
|
||||
"title": {
|
||||
"label": "Заглавие"
|
||||
},
|
||||
"url": {
|
||||
"label": "Уебсайт"
|
||||
}
|
||||
},
|
||||
"glossary": {
|
||||
"page": "Страница"
|
||||
},
|
||||
"list": {
|
||||
"actions": {
|
||||
"delete": "Изтрии",
|
||||
"duplicate": "Дубликирай",
|
||||
"edit": "Редактирай"
|
||||
},
|
||||
"empty-text": "Този списък е празен."
|
||||
},
|
||||
"tooltip": {
|
||||
"delete-item": "Наистина ли искате да изтриете този запис? Това действие не може да бъде отменено.",
|
||||
"delete-section": "Изтриване на раздел",
|
||||
"rename-section": "Преименуване на раздел",
|
||||
"toggle-visibility": "Видим/Невидим"
|
||||
}
|
||||
},
|
||||
"controller": {
|
||||
"tooltip": {
|
||||
"center-artboard": "Централна табла",
|
||||
"copy-link": "Копирай линка в резюмето",
|
||||
"export-pdf": "Експорт в PDF",
|
||||
"toggle-orientation": "Превключване на ориентацията на страницата",
|
||||
"toggle-page-break-line": "Линия за прекъсване на страницата",
|
||||
"toggle-sidebars": "Включване на страничната лента",
|
||||
"zoom-in": "Увеличи",
|
||||
"zoom-out": "Намали"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"menu": {
|
||||
"delete": "Изтрии",
|
||||
"duplicate": "Дубликирай",
|
||||
"rename": "Преименувай",
|
||||
"share-link": "Споделяне на връзка",
|
||||
"tooltips": {
|
||||
"delete": "Наистина ли искате да изтриете това CV? Това действие не може да бъде отменено.",
|
||||
"share-link": "Трябва да промените видимостта на CV-то си на публична, за да я направите видима за другите."
|
||||
}
|
||||
}
|
||||
},
|
||||
"leftSidebar": {
|
||||
"sections": {
|
||||
"awards": {
|
||||
"form": {
|
||||
"awarder": {
|
||||
"label": "Награждаващ"
|
||||
}
|
||||
}
|
||||
},
|
||||
"basics": {
|
||||
"actions": {
|
||||
"photo-filters": "Филтри за снимата"
|
||||
},
|
||||
"heading": "Основни",
|
||||
"headline": {
|
||||
"label": "Заглавие"
|
||||
},
|
||||
"name": {
|
||||
"label": "Пълно име"
|
||||
},
|
||||
"birthdate": {
|
||||
"label": "Дата на раждане"
|
||||
},
|
||||
"photo-filters": {
|
||||
"effects": {
|
||||
"border": {
|
||||
"label": "Рамка"
|
||||
},
|
||||
"grayscale": {
|
||||
"label": "Нива на сивото"
|
||||
},
|
||||
"heading": "Ефекти"
|
||||
},
|
||||
"shape": {
|
||||
"heading": "Форма"
|
||||
},
|
||||
"size": {
|
||||
"heading": "Размер (в px)"
|
||||
}
|
||||
},
|
||||
"photo-upload": {
|
||||
"tooltip": {
|
||||
"remove": "Премахване на снимка",
|
||||
"upload": "Качи снимка"
|
||||
}
|
||||
}
|
||||
},
|
||||
"certifications": {
|
||||
"form": {
|
||||
"issuer": {
|
||||
"label": "Издател"
|
||||
}
|
||||
}
|
||||
},
|
||||
"education": {
|
||||
"form": {
|
||||
"area-study": {
|
||||
"label": "Специалност"
|
||||
},
|
||||
"courses": {
|
||||
"label": "Курсове"
|
||||
},
|
||||
"degree": {
|
||||
"label": "Степен"
|
||||
},
|
||||
"grade": {
|
||||
"label": "Клас"
|
||||
},
|
||||
"institution": {
|
||||
"label": "Институция"
|
||||
}
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"address": {
|
||||
"label": "Адрес"
|
||||
},
|
||||
"city": {
|
||||
"label": "Град"
|
||||
},
|
||||
"country": {
|
||||
"label": "Държава"
|
||||
},
|
||||
"heading": "Местоположение",
|
||||
"postal-code": {
|
||||
"label": "Пощенски код"
|
||||
},
|
||||
"region": {
|
||||
"label": "Регион"
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"form": {
|
||||
"network": {
|
||||
"label": "Социална мрежа"
|
||||
},
|
||||
"username": {
|
||||
"label": "Потребителско име"
|
||||
}
|
||||
},
|
||||
"heading": "Профили",
|
||||
"heading_one": "Профил"
|
||||
},
|
||||
"publications": {
|
||||
"form": {
|
||||
"publisher": {
|
||||
"label": "Издател"
|
||||
}
|
||||
}
|
||||
},
|
||||
"references": {
|
||||
"form": {
|
||||
"relationship": {
|
||||
"label": "Връзка"
|
||||
}
|
||||
}
|
||||
},
|
||||
"section": {
|
||||
"heading": "Раздел"
|
||||
},
|
||||
"volunteer": {
|
||||
"form": {
|
||||
"organization": {
|
||||
"label": "Организация"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"rightSidebar": {
|
||||
"sections": {
|
||||
"css": {
|
||||
"heading": "Персонализиран CSS"
|
||||
},
|
||||
"export": {
|
||||
"heading": "Експортиране",
|
||||
"json": {
|
||||
"primary": "JSON",
|
||||
"secondary": "Изтеглете JSON версия на вашата автобиография, която може да бъде импортирана обратно в Reactive Resume."
|
||||
},
|
||||
"pdf": {
|
||||
"loading": {
|
||||
"primary": "Генериране на PDF",
|
||||
"secondary": "Моля, изчакайте, докато вашият PDF се генерира, това може да отнеме до 15 секунди."
|
||||
},
|
||||
"normal": {
|
||||
"primary": "РDF",
|
||||
"secondary": "Изтеглете PDF файл на вашата автобиография, който можете да отпечатате и изпратите до мечтаната работа. Този файл не може да бъде импортиран обратно за по-нататъшно редактиране."
|
||||
}
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
"heading": "Оформление",
|
||||
"tooltip": {
|
||||
"reset-layout": "Рестартирай оформлението"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"bugs-features": {
|
||||
"body": "Нещо ви пречи да си направите автобиография? Или имате невероятна идея, която да добавите? Повдигнете въпрос в GitHub, за да започнете.",
|
||||
"button": "GitHub общност",
|
||||
"heading": "Бъгове? Искания за функции?"
|
||||
},
|
||||
"donate": {
|
||||
"body": "Ако ви е харесало да използвате Reactive Resume, моля, помислете дали да не дарите колкото можете повече за поддръжка на приложението, без реклами и безплатно завинаги.",
|
||||
"button": "Почерпете ме с кафе",
|
||||
"heading": "Направи дарение и подкрепи Reactive Resume"
|
||||
},
|
||||
"github": "Програмен код",
|
||||
"heading": "Връзки"
|
||||
},
|
||||
"settings": {
|
||||
"global": {
|
||||
"date": {
|
||||
"primary": "Дата",
|
||||
"secondary": "Формат на датата, който да се използва в приложението"
|
||||
},
|
||||
"heading": "Глобално",
|
||||
"language": {
|
||||
"primary": "Език",
|
||||
"secondary": "Език за показване, който да се използва в приложението"
|
||||
},
|
||||
"theme": {
|
||||
"primary": "Тема"
|
||||
}
|
||||
},
|
||||
"heading": "Настройки",
|
||||
"page": {
|
||||
"break-line": {
|
||||
"primary": "Линия на прекъсване",
|
||||
"secondary": "Показване на линия на всички страници за обозначаване на височината на страница A4"
|
||||
},
|
||||
"heading": "Страница",
|
||||
"orientation": {
|
||||
"disabled": "Няма ефект, когато има само една страница",
|
||||
"primary": "Ориентация",
|
||||
"secondary": "Дали страниците да се показват хоризонтално или вертикално"
|
||||
}
|
||||
},
|
||||
"resume": {
|
||||
"heading": "Възобновяване",
|
||||
"reset": {
|
||||
"primary": "Нулирайте всичко",
|
||||
"secondary": "Направихте твърде много грешки? Щракнете тук, за да нулирате всички промени и да започнете от нулата. Внимавайте, това действие не може да бъде отменено."
|
||||
},
|
||||
"sample": {
|
||||
"primary": "Зареждане на примерни данни",
|
||||
"secondary": "Не сте сигурни откъде да започнете? Щракнете тук, за да заредите някои примерни данни и да видите как изглежда една пълна автобиография."
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"heading": "Споделяне",
|
||||
"short-url": {
|
||||
"label": "Предпочитам кратък URL адрес"
|
||||
},
|
||||
"visibility": {
|
||||
"subtitle": "Позволете на всеки с връзка да види това CV",
|
||||
"title": "Публичен"
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"heading": "Шаблони"
|
||||
},
|
||||
"theme": {
|
||||
"form": {
|
||||
"background": {
|
||||
"label": "Фон"
|
||||
},
|
||||
"primary": {
|
||||
"label": "Основен"
|
||||
},
|
||||
"text": {
|
||||
"label": "Текст"
|
||||
}
|
||||
},
|
||||
"heading": "Тема"
|
||||
},
|
||||
"typography": {
|
||||
"form": {
|
||||
"font-family": {
|
||||
"label": "Шрифтово семейство"
|
||||
},
|
||||
"font-size": {
|
||||
"label": "Размер на шрифта"
|
||||
}
|
||||
},
|
||||
"heading": "Типография",
|
||||
"widgets": {
|
||||
"body": {
|
||||
"label": "Тяло"
|
||||
},
|
||||
"headings": {
|
||||
"label": "Заглавия"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
client/public/locales/bg/common.json
Normal file
29
client/public/locales/bg/common.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"avatar": {
|
||||
"menu": {
|
||||
"greeting": "Здравейте",
|
||||
"logout": "Изход"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"credit": "Проект от <1>Амрут Пилай</1>",
|
||||
"license": "От общността, за общността."
|
||||
},
|
||||
"markdown": {
|
||||
"help-text": "Този раздел поддържа <1>markdown</1> форматиране."
|
||||
},
|
||||
"date": {
|
||||
"present": "Настояще"
|
||||
},
|
||||
"subtitle": "Безплатен инструмент за създаване на автобиография с отворен код.",
|
||||
"title": "Reactive Resume",
|
||||
"toast": {
|
||||
"error": {
|
||||
"upload-file-size": "Моля, качвайте само файлове с размер под 2 мегабайта.",
|
||||
"upload-photo-size": "Моля, качвайте само снимки под 2 мегабайта, за предпочитане квадратни."
|
||||
},
|
||||
"success": {
|
||||
"resume-link-copied": "Връзката към автобиографията ви е копирана в клипборда."
|
||||
}
|
||||
}
|
||||
}
|
||||
25
client/public/locales/bg/dashboard.json
Normal file
25
client/public/locales/bg/dashboard.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"create-resume": {
|
||||
"subtitle": "Започване отначало",
|
||||
"title": "Създаване на ново CV"
|
||||
},
|
||||
"import-external": {
|
||||
"subtitle": "LinkedIn, JSON резюме, Reactive Resume",
|
||||
"title": "Импортиране от външни източници"
|
||||
},
|
||||
"resume": {
|
||||
"menu": {
|
||||
"delete": "Изтрии",
|
||||
"duplicate": "Дубликирай",
|
||||
"open": "Отвори",
|
||||
"rename": "Преименувай",
|
||||
"share-link": "Споделяне на връзка",
|
||||
"tooltips": {
|
||||
"delete": "Наистина ли искате да изтриете това CV? Това действие не може да бъде отменено.",
|
||||
"share-link": "Трябва да промените видимостта на CV-то си на публична, за да я направите видима за другите."
|
||||
}
|
||||
},
|
||||
"timestamp": "Последната промяна е преди {{timestamp}}"
|
||||
},
|
||||
"title": "Контролен панел"
|
||||
}
|
||||
41
client/public/locales/bg/landing.json
Normal file
41
client/public/locales/bg/landing.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"actions": {
|
||||
"app": "Към приложението",
|
||||
"login": "Вход",
|
||||
"logout": "Изход",
|
||||
"register": "Регистрация"
|
||||
},
|
||||
"features": {
|
||||
"heading": "Функции",
|
||||
"list": {
|
||||
"ads": "Без реклами.",
|
||||
"export": "Експортирайте автобиографията си в JSON или PDF формат",
|
||||
"free": "Безплатно завинаги",
|
||||
"import": "Импортиране на данни от LinkedIn, JSON резюме",
|
||||
"languages": "Достъпно на множество езици",
|
||||
"more": "И още много вълнуващи функции, <1>прочетете всичко за тях тук</1>",
|
||||
"tracking": "Без проследяване на потребители"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"heading": "Връзки",
|
||||
"links": {
|
||||
"donate": "Дарение",
|
||||
"github": "Програмен код",
|
||||
"privacy": "Политика за поверителност",
|
||||
"service": "Условия на ползване"
|
||||
}
|
||||
},
|
||||
"screenshots": {
|
||||
"heading": "Екранни снимки"
|
||||
},
|
||||
"testimonials": {
|
||||
"heading": "Препоръки",
|
||||
"body": "Позитивно или негативно, ще се радвам да чуя мнението ви за Reactive Resume и какъв е вашия опитът.<br/>Ето някои от съобщенията, изпратени от потребители по целия свят.",
|
||||
"contact": "Можете да се свържете с мен чрез <1>моя имейл</1> или чрез формата за контакт на <3>моя уебсайт</3> ."
|
||||
},
|
||||
"summary": {
|
||||
"body": "Reactive Resume е безплатен инструмент за създаване на автобиография/CV с отворен код, който е създаден, за да улесни обикновените задачи за създаване, актуализиране и споделяне на вашата автобиография като 1, 2, 3. С това приложение можете да създавате множество автобиографии, да ги споделяте директно със специалистите по подбор на персонал или приятели чрез уникална връзка, както и ги отпечатате като PDF. Всичко е безплатно, без реклами, без проследяване, без да губите целостта и поверителността на вашите данни.",
|
||||
"heading": "Обобщение"
|
||||
}
|
||||
}
|
||||
136
client/public/locales/bg/modals.json
Normal file
136
client/public/locales/bg/modals.json
Normal file
@ -0,0 +1,136 @@
|
||||
{
|
||||
"auth": {
|
||||
"forgot-password": {
|
||||
"actions": {
|
||||
"send-email": "Възстановяване на парола"
|
||||
},
|
||||
"body": "Просто въведете имейл адреса, свързан с акаунта, който искате да възстановите.",
|
||||
"form": {
|
||||
"email": {
|
||||
"label": "Имейл адрес"
|
||||
}
|
||||
},
|
||||
"heading": "Забравена парола?",
|
||||
"help-text": "Ако профилът ви съществува, ще получите линк за възстановяване на паролата."
|
||||
},
|
||||
"login": {
|
||||
"actions": {
|
||||
"login": "Вход",
|
||||
"google": "Вход чрез Google"
|
||||
},
|
||||
"body": "Моля, въведете вашето потребителско име и парола, свързани с вашия акаунт, за да влезете и получите достъп, управлявате и споделяте вашите автобиографии.",
|
||||
"form": {
|
||||
"password": {
|
||||
"label": "Парола"
|
||||
},
|
||||
"username": {
|
||||
"help-text": "Можете също да въведете своя имейл адрес",
|
||||
"label": "Потребителско име"
|
||||
}
|
||||
},
|
||||
"heading": "Влезте във Вашият профил",
|
||||
"recover-text": "В случай, че сте забравили паролата си, можете <1>да възстановите акаунта си</1> тук.",
|
||||
"register-text": "Ако нямате такъв, можете <1>да си създадете акаунт</1> тук."
|
||||
},
|
||||
"register": {
|
||||
"actions": {
|
||||
"register": "Регистрация",
|
||||
"google": "Регистрация с Google"
|
||||
},
|
||||
"body": "Моля, въведете вашата лична информация, за да създадете акаунт.",
|
||||
"form": {
|
||||
"confirm-password": {
|
||||
"label": "Потвърждение на парола"
|
||||
},
|
||||
"email": {
|
||||
"label": "Имейл адрес"
|
||||
},
|
||||
"name": {
|
||||
"label": "Пълно име"
|
||||
},
|
||||
"password": {
|
||||
"label": "Парола"
|
||||
},
|
||||
"username": {
|
||||
"label": "Потребителско име"
|
||||
}
|
||||
},
|
||||
"heading": "Създаване на профил",
|
||||
"loginText": "Ако вече имате акаунт, можете <1>да влезете от тук</1> ."
|
||||
},
|
||||
"reset-password": {
|
||||
"actions": {
|
||||
"set-password": "Задайте нова парола"
|
||||
},
|
||||
"body": "Въведете нова парола за вашия акаунт.",
|
||||
"form": {
|
||||
"confirm-password": {
|
||||
"label": "Потвърждение на парола"
|
||||
},
|
||||
"password": {
|
||||
"label": "Парола"
|
||||
}
|
||||
},
|
||||
"heading": "Нулиране на паролата"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"create-resume": {
|
||||
"actions": {
|
||||
"create-resume": "Създай CV/Резюме"
|
||||
},
|
||||
"body": "Започнете да създавате автобиографията си, като й дадете име. Може да е във връзка с позицията, за която кандидатствате, или просто любимата ви закуска.",
|
||||
"form": {
|
||||
"name": {
|
||||
"label": "Име"
|
||||
},
|
||||
"public": {
|
||||
"label": "Публично достъпна ли е?"
|
||||
},
|
||||
"slug": {
|
||||
"label": "Слъг"
|
||||
}
|
||||
},
|
||||
"heading": "Създаване на ново CV"
|
||||
},
|
||||
"import-external": {
|
||||
"heading": "Импортиране от външни източници",
|
||||
"json-resume": {
|
||||
"actions": {
|
||||
"upload-json": "Качване на JSON"
|
||||
},
|
||||
"body": "Ако имате готова <1>валидирана JSON автобиография</1>, можете да я използвате, за да стартирате бързо в Reactive Resume. Щракнете върху бутона по-долу и качете валиден JSON файл, за да започнете.",
|
||||
"heading": "Качване на JSON Resume"
|
||||
},
|
||||
"linkedin": {
|
||||
"actions": {
|
||||
"upload-archive": "Качете ZIP архив"
|
||||
},
|
||||
"body": "Можете да спестите време, като експортирате данните си от LinkedIn и ги използвате за автоматично попълване на полета в Reactive Resume. Отидете в раздел <1>Поверителност на данните</1> в LinkedIn и поискайте архив на вашите данни. След като е наличен, качете ZIP файла по-долу.",
|
||||
"heading": "Импортиране от LinkedIn"
|
||||
},
|
||||
"reactive-resume": {
|
||||
"actions": {
|
||||
"upload-json": "Качване на JSON",
|
||||
"upload-json-v2": "Качете JSON от v2"
|
||||
},
|
||||
"body": "Ако имате JSON, който е бил експортиран с текущата версия на Reactive Resume, можете да го импортирате обратно тук, за да получите отново редактируема версия.",
|
||||
"heading": "Импортиране от Reactive Resume"
|
||||
}
|
||||
},
|
||||
"rename-resume": {
|
||||
"actions": {
|
||||
"rename-resume": "Преименуване на резюмето"
|
||||
},
|
||||
"form": {
|
||||
"name": {
|
||||
"label": "Име"
|
||||
},
|
||||
"slug": {
|
||||
"label": "Слъг"
|
||||
}
|
||||
},
|
||||
"heading": "Преименувайте автобиографията си"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -15,8 +15,7 @@
|
||||
},
|
||||
"login": {
|
||||
"actions": {
|
||||
"login": "Login",
|
||||
"google": "Login with Google"
|
||||
"login": "Login"
|
||||
},
|
||||
"body": "Please enter your username and password associated with your account to login and access, manage and share your resumes.",
|
||||
"form": {
|
||||
|
||||
361
client/public/locales/fi/builder.json
Normal file
361
client/public/locales/fi/builder.json
Normal file
@ -0,0 +1,361 @@
|
||||
{
|
||||
"common": {
|
||||
"actions": {
|
||||
"add": "Lisää uusi",
|
||||
"delete": "Poista",
|
||||
"edit": "Muokkaa"
|
||||
},
|
||||
"columns": {
|
||||
"heading": "Palstat",
|
||||
"tooltip": "Muut palstojen määrää"
|
||||
},
|
||||
"form": {
|
||||
"date": {
|
||||
"label": "Päivämäärä"
|
||||
},
|
||||
"description": {
|
||||
"label": "Kuvaus"
|
||||
},
|
||||
"email": {
|
||||
"label": "Sähköposti"
|
||||
},
|
||||
"end-date": {
|
||||
"help-text": "Jätä tämä kenttä tyhjäksi, jos se on vielä esillä",
|
||||
"label": "Päättymispäivä"
|
||||
},
|
||||
"keywords": {
|
||||
"label": "Avainsanat"
|
||||
},
|
||||
"level": {
|
||||
"label": "Taso"
|
||||
},
|
||||
"levelNum": {
|
||||
"label": "Taso (numero)"
|
||||
},
|
||||
"name": {
|
||||
"label": "Nimi"
|
||||
},
|
||||
"phone": {
|
||||
"label": "Puhelinnumero"
|
||||
},
|
||||
"position": {
|
||||
"label": "Sijainti"
|
||||
},
|
||||
"start-date": {
|
||||
"label": "Alkamispäivä"
|
||||
},
|
||||
"subtitle": {
|
||||
"label": "Alaotsikko"
|
||||
},
|
||||
"summary": {
|
||||
"label": "Yhteenveto"
|
||||
},
|
||||
"title": {
|
||||
"label": "Otsikko"
|
||||
},
|
||||
"url": {
|
||||
"label": "Verkkosivusto"
|
||||
}
|
||||
},
|
||||
"glossary": {
|
||||
"page": "Sivu"
|
||||
},
|
||||
"list": {
|
||||
"actions": {
|
||||
"delete": "Poista",
|
||||
"duplicate": "Monista",
|
||||
"edit": "Muokkaa"
|
||||
},
|
||||
"empty-text": "Tämä luettelo on tyhjä."
|
||||
},
|
||||
"tooltip": {
|
||||
"delete-item": "Haluatko varmasti poistaa tämän kohteen? Tämä on peruuttamaton toimenpide.",
|
||||
"delete-section": "Poista osio",
|
||||
"rename-section": "Nimeä osio uudelleen",
|
||||
"toggle-visibility": "Näkyvyys päälle/pois"
|
||||
}
|
||||
},
|
||||
"controller": {
|
||||
"tooltip": {
|
||||
"center-artboard": "Keskitä Kuvataulu",
|
||||
"copy-link": "Kopioi linkki ansioluetteloon",
|
||||
"export-pdf": "Vie PDF",
|
||||
"toggle-orientation": "Vaihda sivun suunta",
|
||||
"toggle-page-break-line": "Vaihda sivunvaihtoviivaa",
|
||||
"toggle-sidebars": "Vaihda Sivupalkkeja",
|
||||
"zoom-in": "Suurenna",
|
||||
"zoom-out": "Pienennä"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"menu": {
|
||||
"delete": "Poista",
|
||||
"duplicate": "Kopioi",
|
||||
"rename": "Nimeä uudelleen",
|
||||
"share-link": "Jaa linkki",
|
||||
"tooltips": {
|
||||
"delete": "Haluatko varmasti poistaa tämän ansioluettelon? Tämä on peruuttamaton toimenpide.",
|
||||
"share-link": "Sinun on muutettava ansioluettelosi näkyvyys julkiseksi, jotta se näkyisi muille."
|
||||
}
|
||||
}
|
||||
},
|
||||
"leftSidebar": {
|
||||
"sections": {
|
||||
"awards": {
|
||||
"form": {
|
||||
"awarder": {
|
||||
"label": "Tunnustuksen antaja"
|
||||
}
|
||||
}
|
||||
},
|
||||
"basics": {
|
||||
"actions": {
|
||||
"photo-filters": "Valokuvasuodattimet"
|
||||
},
|
||||
"heading": "Perustiedot",
|
||||
"headline": {
|
||||
"label": "Otsikko"
|
||||
},
|
||||
"name": {
|
||||
"label": "Koko nimi"
|
||||
},
|
||||
"birthdate": {
|
||||
"label": "Syntymäaika"
|
||||
},
|
||||
"photo-filters": {
|
||||
"effects": {
|
||||
"border": {
|
||||
"label": "Kehys"
|
||||
},
|
||||
"grayscale": {
|
||||
"label": "Harmaasävy"
|
||||
},
|
||||
"heading": "Efektit"
|
||||
},
|
||||
"shape": {
|
||||
"heading": "Muoto"
|
||||
},
|
||||
"size": {
|
||||
"heading": "Koko (px)"
|
||||
}
|
||||
},
|
||||
"photo-upload": {
|
||||
"tooltip": {
|
||||
"remove": "Poista kuva",
|
||||
"upload": "Lataa valokuva"
|
||||
}
|
||||
}
|
||||
},
|
||||
"certifications": {
|
||||
"form": {
|
||||
"issuer": {
|
||||
"label": "Myöntäjä"
|
||||
}
|
||||
}
|
||||
},
|
||||
"education": {
|
||||
"form": {
|
||||
"area-study": {
|
||||
"label": "Opintojen alue"
|
||||
},
|
||||
"courses": {
|
||||
"label": "Kurssit"
|
||||
},
|
||||
"degree": {
|
||||
"label": "Tutkinto"
|
||||
},
|
||||
"grade": {
|
||||
"label": "Arvosana"
|
||||
},
|
||||
"institution": {
|
||||
"label": "Oppilaitos"
|
||||
}
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"address": {
|
||||
"label": "Osoite"
|
||||
},
|
||||
"city": {
|
||||
"label": "Kaupunki"
|
||||
},
|
||||
"country": {
|
||||
"label": "Maa"
|
||||
},
|
||||
"heading": "Sijainti",
|
||||
"postal-code": {
|
||||
"label": "Postinumero"
|
||||
},
|
||||
"region": {
|
||||
"label": "Paikkakunta"
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"form": {
|
||||
"network": {
|
||||
"label": "Verkosto"
|
||||
},
|
||||
"username": {
|
||||
"label": "Käyttäjätunnus"
|
||||
}
|
||||
},
|
||||
"heading": "Profiilit",
|
||||
"heading_one": "Profiili"
|
||||
},
|
||||
"publications": {
|
||||
"form": {
|
||||
"publisher": {
|
||||
"label": "Julkaisija"
|
||||
}
|
||||
}
|
||||
},
|
||||
"references": {
|
||||
"form": {
|
||||
"relationship": {
|
||||
"label": "Suhde suosittelijaan"
|
||||
}
|
||||
}
|
||||
},
|
||||
"section": {
|
||||
"heading": "Osio"
|
||||
},
|
||||
"volunteer": {
|
||||
"form": {
|
||||
"organization": {
|
||||
"label": "Organisaatio"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"rightSidebar": {
|
||||
"sections": {
|
||||
"css": {
|
||||
"heading": "Mukautettu CSS"
|
||||
},
|
||||
"export": {
|
||||
"heading": "Vie",
|
||||
"json": {
|
||||
"primary": "JSON",
|
||||
"secondary": "Lataa JSON-versio ansioluettelostasi, jonka voit tuoda takaisin Reactive Resumeen."
|
||||
},
|
||||
"pdf": {
|
||||
"loading": {
|
||||
"primary": "Luodaan PDF-tiedostoa",
|
||||
"secondary": "Odota, että PDF-tiedostosi luodaan. Tämä voi kestää jopa 15 sekuntia."
|
||||
},
|
||||
"normal": {
|
||||
"primary": "PDF",
|
||||
"secondary": "Lataa PDF-tiedosto ansioluettelostasi, jonka voit tulostaa ja lähettää unelmiesi työpaikkaan. Tätä tiedostoa ei voi tuoda takaisin muokattavaksi."
|
||||
}
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
"heading": "Asettelu",
|
||||
"tooltip": {
|
||||
"reset-layout": "Nollaa asettelu"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"bugs-features": {
|
||||
"body": "Estääkö jokin sinua tekemästä ansioluetteloa? Vai onko sinulla mahtava idea lisättäväksi? Nosta ongelma GitHubissa päästäksesi alkuun.",
|
||||
"button": "GitHub Issues",
|
||||
"heading": "Bugeja? Ominaisuuspyyntöjä?"
|
||||
},
|
||||
"donate": {
|
||||
"body": "Jos pidit Reactive Resume -sovelluksen käytöstä, harkitse lahjoitusta niin paljon kuin voit, jotta sovellus pysyy toiminnassa ilman mainoksia ja ikuisesti ilmaisena.",
|
||||
"button": "Osta minulle kahvi",
|
||||
"heading": "Lahjoita Reaktiiviselle ansioluettelolle"
|
||||
},
|
||||
"github": "Lähdekoodi",
|
||||
"heading": "Linkit"
|
||||
},
|
||||
"settings": {
|
||||
"global": {
|
||||
"date": {
|
||||
"primary": "Päivämäärä",
|
||||
"secondary": "Sovelluksessa käytettävä päivämäärämuoto"
|
||||
},
|
||||
"heading": "Yleinen",
|
||||
"language": {
|
||||
"primary": "Kieli",
|
||||
"secondary": "Koko sovelluksessa käytettävä näyttökieli"
|
||||
},
|
||||
"theme": {
|
||||
"primary": "Teema"
|
||||
}
|
||||
},
|
||||
"heading": "Asetukset",
|
||||
"page": {
|
||||
"break-line": {
|
||||
"primary": "Katkoviiva",
|
||||
"secondary": "Näytä viiva kaikilla sivuilla A4-sivun korkeuden merkitsemiseksi"
|
||||
},
|
||||
"heading": "Sivu",
|
||||
"orientation": {
|
||||
"disabled": "Ei vaikuta, kun sivuja on vain yksi",
|
||||
"primary": "Suunta",
|
||||
"secondary": "Näytetäänkö sivut vaaka- vai pystysuunnassa"
|
||||
}
|
||||
},
|
||||
"resume": {
|
||||
"heading": "Ansioluettelo",
|
||||
"reset": {
|
||||
"primary": "Tyhjennä Kaikki",
|
||||
"secondary": "Oletko tehnyt liikaa virheitä? Napsauta tätä nollataksesi kaikki muutokset ja aloittaaksesi alusta. Ole varovainen, tätä toimintoa ei voi peruuttaa."
|
||||
},
|
||||
"sample": {
|
||||
"primary": "Lataa näytetiedot",
|
||||
"secondary": "Etkö ole varma mistä aloittaa? Napsauta tätä ladataksesi näytetietoja nähdäksesi, miltä täydellinen ansioluettelo näyttää."
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"heading": "Jakaminen",
|
||||
"short-url": {
|
||||
"label": "Valitse lyhyt URL-osoite"
|
||||
},
|
||||
"visibility": {
|
||||
"subtitle": "Salli kenelle tahansa linkin avulla mahdollisuus tarkastella ansioluetteloasi.",
|
||||
"title": "Julkinen"
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"heading": "Mallipohjat"
|
||||
},
|
||||
"theme": {
|
||||
"form": {
|
||||
"background": {
|
||||
"label": "Taustakuva"
|
||||
},
|
||||
"primary": {
|
||||
"label": "Ensisijainen"
|
||||
},
|
||||
"text": {
|
||||
"label": "Teksti"
|
||||
}
|
||||
},
|
||||
"heading": "Teema"
|
||||
},
|
||||
"typography": {
|
||||
"form": {
|
||||
"font-family": {
|
||||
"label": "Kirjasintyyli"
|
||||
},
|
||||
"font-size": {
|
||||
"label": "Fontin koko"
|
||||
}
|
||||
},
|
||||
"heading": "Painatus",
|
||||
"widgets": {
|
||||
"body": {
|
||||
"label": "Runko"
|
||||
},
|
||||
"headings": {
|
||||
"label": "Otsikot"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
client/public/locales/fi/common.json
Normal file
29
client/public/locales/fi/common.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"avatar": {
|
||||
"menu": {
|
||||
"greeting": "Hei",
|
||||
"logout": "Kirjaudu ulos"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"credit": "<1>Amruth Pillain</1> intohimoprojekti",
|
||||
"license": "Yhteisön toimesta yhteisöä varten."
|
||||
},
|
||||
"markdown": {
|
||||
"help-text": "Tämä osio tukee <1>markdown-muotoilua</1>."
|
||||
},
|
||||
"date": {
|
||||
"present": "Tänään"
|
||||
},
|
||||
"subtitle": "Ilmainen ja avoimeen lähdekoodiin pohjautuva ansioluettelo.",
|
||||
"title": "Reactive Resume",
|
||||
"toast": {
|
||||
"error": {
|
||||
"upload-file-size": "Lataa vain alle 2 megatavun tiedostoja.",
|
||||
"upload-photo-size": "Lataa vain alle 2 megatavun kuvia, mieluiten neliönmuotoisia."
|
||||
},
|
||||
"success": {
|
||||
"resume-link-copied": "Linkki ansioluetteloosi on kopioitu leikepöydällesi."
|
||||
}
|
||||
}
|
||||
}
|
||||
25
client/public/locales/fi/dashboard.json
Normal file
25
client/public/locales/fi/dashboard.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"create-resume": {
|
||||
"subtitle": "Aloita tyhjästä",
|
||||
"title": "Luo uusi ansioluettelo"
|
||||
},
|
||||
"import-external": {
|
||||
"subtitle": "LinkedIn, JSON Resume, Reactive Resume",
|
||||
"title": "Tuo ulkoisista lähteistä"
|
||||
},
|
||||
"resume": {
|
||||
"menu": {
|
||||
"delete": "Poista",
|
||||
"duplicate": "Monista",
|
||||
"open": "Avaa",
|
||||
"rename": "Nimeä uudelleen",
|
||||
"share-link": "Jaa linkki",
|
||||
"tooltips": {
|
||||
"delete": "Haluatko varmasti poistaa tämän ansioluettelon? Tämä on peruuttamaton toimenpide.",
|
||||
"share-link": "Sinun on muutettava ansioluettelosi näkyvyys julkiseksi, jotta se näkyisi muille."
|
||||
}
|
||||
},
|
||||
"timestamp": "Viimeksi päivitetty: {{timestamp}}"
|
||||
},
|
||||
"title": "Hallintapaneeli"
|
||||
}
|
||||
41
client/public/locales/fi/landing.json
Normal file
41
client/public/locales/fi/landing.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"actions": {
|
||||
"app": "Siirry sovellukseen",
|
||||
"login": "Kirjaudu sisään",
|
||||
"logout": "Kirjaudu ulos",
|
||||
"register": "Rekisteröidy"
|
||||
},
|
||||
"features": {
|
||||
"heading": "Ominaisuudet",
|
||||
"list": {
|
||||
"ads": "Ei mainoksia",
|
||||
"export": "Vie ansioluettelosi JSON- tai PDF-muotoon",
|
||||
"free": "Ilmainen ikuisesti",
|
||||
"import": "Tuo tietoja LinkedInistä tai JSON tiedostosta",
|
||||
"languages": "Käytettävissä useilla kielillä",
|
||||
"more": "Ja paljon muita jännittäviä ominaisuuksia, <1>lue kaikki tästä</1>",
|
||||
"tracking": "Ei käyttäjän seurantaa"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"heading": "Linkit",
|
||||
"links": {
|
||||
"donate": "Lahjoita",
|
||||
"github": "Lähdekoodi",
|
||||
"privacy": "Tietosuojakäytäntö",
|
||||
"service": "Käyttöehdot"
|
||||
}
|
||||
},
|
||||
"screenshots": {
|
||||
"heading": "Kuvakaappaukset"
|
||||
},
|
||||
"testimonials": {
|
||||
"heading": "Asiakaskokemuksia",
|
||||
"body": "Hyvä tai huono, haluaisin kuulla mielipiteesi Reactive Resume -ohjelmasta ja siitä, millainen kokemus sinulla on ollut.<br/>Seuraavassa on muutamia käyttäjien eri puolilta maailmaa lähettämiä viestejä.",
|
||||
"contact": "Voit ottaa minuun yhteyttä <1>sähköpostitse</1> tai <3>verkkosivustoni</3> yhteydenottolomakkeen kautta."
|
||||
},
|
||||
"summary": {
|
||||
"body": "Reactive Resume on ilmainen ja avoimen lähdekoodin ansioluettelon rakentaja, jonka tarkoituksena on tehdä ansioluettelon luomisesta, päivittämisestä ja jakamisesta helppoa kuin 1, 2, 3. Tämän sovelluksen avulla voit luoda useita ansioluetteloita, jakaa ne rekrytoijien tai ystävien kanssa yksilöllisen linkin kautta ja tulostaa ne PDF-muodossa, ja kaikki tämä ilmaiseksi, ilman mainoksia, ilman seurantaa ja menettämättä tietojesi eheyttä ja yksityisyyttä.",
|
||||
"heading": "Yhteenveto"
|
||||
}
|
||||
}
|
||||
136
client/public/locales/fi/modals.json
Normal file
136
client/public/locales/fi/modals.json
Normal file
@ -0,0 +1,136 @@
|
||||
{
|
||||
"auth": {
|
||||
"forgot-password": {
|
||||
"actions": {
|
||||
"send-email": "Lähetä salasanan palautussähköposti"
|
||||
},
|
||||
"body": "Kirjoita vain palautettavaan tiliin liitetty sähköpostiosoite.",
|
||||
"form": {
|
||||
"email": {
|
||||
"label": "Sähköpostiosoite"
|
||||
}
|
||||
},
|
||||
"heading": "Unohditko salasanasi?",
|
||||
"help-text": "Jos sähköpostiosoitteesi on olemassa, saat sähköpostiviestissä linkin salasanasi palautukseen."
|
||||
},
|
||||
"login": {
|
||||
"actions": {
|
||||
"login": "Kirjaudu sisään",
|
||||
"google": "Kirjaudu Google-tilillä"
|
||||
},
|
||||
"body": "Anna tiliisi liitetty käyttäjätunnuksesi ja salasanasi kirjautuaksesi sisään ja käyttääksesi, hallitaksesi ja jakaaksesi ansioluettelosi.",
|
||||
"form": {
|
||||
"password": {
|
||||
"label": "Salasana"
|
||||
},
|
||||
"username": {
|
||||
"help-text": "Voit myös syöttää sähköpostiosoitteesi",
|
||||
"label": "Käyttäjänimi"
|
||||
}
|
||||
},
|
||||
"heading": "Kirjaudu tilillesi",
|
||||
"recover-text": "Jos olet unohtanut salasanasi, voit <1>palauttaa tilisi</1> täällä.",
|
||||
"register-text": "Jos sinulla ei ole tiliä, voit <1>luoda tilin</1> täällä."
|
||||
},
|
||||
"register": {
|
||||
"actions": {
|
||||
"register": "Rekisteröidy",
|
||||
"google": "Rekisteröidy Google-tilillä"
|
||||
},
|
||||
"body": "Anna tietosi luodaksesi uuden käyttäjätilin.",
|
||||
"form": {
|
||||
"confirm-password": {
|
||||
"label": "Vahvista Salasana"
|
||||
},
|
||||
"email": {
|
||||
"label": "Sähköpostiosoite"
|
||||
},
|
||||
"name": {
|
||||
"label": "Koko nimi"
|
||||
},
|
||||
"password": {
|
||||
"label": "Salasana"
|
||||
},
|
||||
"username": {
|
||||
"label": "Käyttäjänimi"
|
||||
}
|
||||
},
|
||||
"heading": "Luo käyttäjätili",
|
||||
"loginText": "Jos sinulla on jo tili, voit <1>kirjautua sisään täällä</1>."
|
||||
},
|
||||
"reset-password": {
|
||||
"actions": {
|
||||
"set-password": "Aseta uusi salasana"
|
||||
},
|
||||
"body": "Anna uusi salasana tilillesi.",
|
||||
"form": {
|
||||
"confirm-password": {
|
||||
"label": "Vahvista Salasana"
|
||||
},
|
||||
"password": {
|
||||
"label": "Salasana"
|
||||
}
|
||||
},
|
||||
"heading": "Palauta salasanasi"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"create-resume": {
|
||||
"actions": {
|
||||
"create-resume": "Luo ansioluettelo"
|
||||
},
|
||||
"body": "Aloita ansioluettelosi rakentaminen antamalla sille nimi. Se voi viitata rooliin, johon haet, tai vaikka suosikkivälipalaasi.",
|
||||
"form": {
|
||||
"name": {
|
||||
"label": "Nimi"
|
||||
},
|
||||
"public": {
|
||||
"label": "Onko julkisesti saatavilla?"
|
||||
},
|
||||
"slug": {
|
||||
"label": "Tunniste"
|
||||
}
|
||||
},
|
||||
"heading": "Luo uusi ansioluettelo"
|
||||
},
|
||||
"import-external": {
|
||||
"heading": "Tuo ulkoisista lähteistä",
|
||||
"json-resume": {
|
||||
"actions": {
|
||||
"upload-json": "Lataa JSON"
|
||||
},
|
||||
"body": "Jos sinulla on valmiiksi <1>validoitu JSON-ansioluettelo</1>, voit käyttää sitä Reactive Resume -ansioluettelon laatimisen nopeuttamiseen. Napsauta alla olevaa painiketta ja lataa validoitu JSON-tiedosto aloittaaksesi.",
|
||||
"heading": "Tuo JSON-muotoisesta ansioluettelosta"
|
||||
},
|
||||
"linkedin": {
|
||||
"actions": {
|
||||
"upload-archive": "Lataa ZIP-tiedosto"
|
||||
},
|
||||
"body": "Voit säästää aikaa tuomalla tietosi LinkedInistä ja käyttämällä niitä Reactive Resumen kenttien automaattiseen täyttämiseen. Siirry LinkedInin <1>tietosuojaosioon</1> ja pyydä tietojesi arkistointia. Kun se on saatavilla, lataa alla oleva ZIP-tiedosto.",
|
||||
"heading": "Tuo LinkedInistä"
|
||||
},
|
||||
"reactive-resume": {
|
||||
"actions": {
|
||||
"upload-json": "Lataa JSON",
|
||||
"upload-json-v2": "Lataa JSON v2:sta"
|
||||
},
|
||||
"body": "Jos sinulla on JSON, joka on viety Reactive Resume -ohjelman nykyisellä versiolla, voit tuoda sen takaisin tänne saadaksesi muokattavan version uudelleen.",
|
||||
"heading": "Tuo Reactive Resume -ohjelmalla tehdystä"
|
||||
}
|
||||
},
|
||||
"rename-resume": {
|
||||
"actions": {
|
||||
"rename-resume": "Nimeä ansioluettelo uudelleen"
|
||||
},
|
||||
"form": {
|
||||
"name": {
|
||||
"label": "Nimi"
|
||||
},
|
||||
"slug": {
|
||||
"label": "Tunniste"
|
||||
}
|
||||
},
|
||||
"heading": "Nimeä ansioluettelosi uudelleen"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"common": {
|
||||
"actions": {
|
||||
"add": "Tambah Baru {{token}}",
|
||||
"add": "Tambahkan {{token}} Baru",
|
||||
"delete": "Hapus {{token}}",
|
||||
"edit": "Ubah {{token}}"
|
||||
},
|
||||
|
||||
25
client/public/locales/no/dashboard.json
Normal file
25
client/public/locales/no/dashboard.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"create-resume": {
|
||||
"subtitle": "Start fra begynnelsen",
|
||||
"title": "Opprett ny CV"
|
||||
},
|
||||
"import-external": {
|
||||
"subtitle": "LinkedIn, JSON Resume, Reactive Resume",
|
||||
"title": "Importer fra eksterne kilder"
|
||||
},
|
||||
"resume": {
|
||||
"menu": {
|
||||
"delete": "Slett",
|
||||
"duplicate": "Dupliser",
|
||||
"open": "Åpen",
|
||||
"rename": "Endre navn",
|
||||
"share-link": "Del lenke",
|
||||
"tooltips": {
|
||||
"delete": "Er du sikker på at du vil slette dette resymeet? Dette er en irreversibel handling.",
|
||||
"share-link": "Du må endre synligheten til resymeet ditt til offentlig for å gjøre det synlig for andre."
|
||||
}
|
||||
},
|
||||
"timestamp": "Sist oppdatert for {{timestamp}} siden"
|
||||
},
|
||||
"title": "Oversikt"
|
||||
}
|
||||
361
client/public/locales/sr/builder.json
Normal file
361
client/public/locales/sr/builder.json
Normal file
@ -0,0 +1,361 @@
|
||||
{
|
||||
"common": {
|
||||
"actions": {
|
||||
"add": "Dodaj Novi {{token}}",
|
||||
"delete": "Obriši {{token}}",
|
||||
"edit": "Ažuriraj {{token}}"
|
||||
},
|
||||
"columns": {
|
||||
"heading": "Kolone",
|
||||
"tooltip": "Promeni broj kolona"
|
||||
},
|
||||
"form": {
|
||||
"date": {
|
||||
"label": "Datum"
|
||||
},
|
||||
"description": {
|
||||
"label": "Opis"
|
||||
},
|
||||
"email": {
|
||||
"label": "Imejl Adresa"
|
||||
},
|
||||
"end-date": {
|
||||
"help-text": "Ostavi ovo polje prazno, ukoliko još postoji",
|
||||
"label": "Datum kraja"
|
||||
},
|
||||
"keywords": {
|
||||
"label": "Ključne reči"
|
||||
},
|
||||
"level": {
|
||||
"label": "Nivo"
|
||||
},
|
||||
"levelNum": {
|
||||
"label": "Nivo (Broj)"
|
||||
},
|
||||
"name": {
|
||||
"label": "Ime"
|
||||
},
|
||||
"phone": {
|
||||
"label": "Broj Telefona"
|
||||
},
|
||||
"position": {
|
||||
"label": "Pozicija"
|
||||
},
|
||||
"start-date": {
|
||||
"label": "Datum Početka"
|
||||
},
|
||||
"subtitle": {
|
||||
"label": "Podnaslov"
|
||||
},
|
||||
"summary": {
|
||||
"label": "Rezime"
|
||||
},
|
||||
"title": {
|
||||
"label": "Naslov"
|
||||
},
|
||||
"url": {
|
||||
"label": "Veb Sajt"
|
||||
}
|
||||
},
|
||||
"glossary": {
|
||||
"page": "Stranica"
|
||||
},
|
||||
"list": {
|
||||
"actions": {
|
||||
"delete": "Obriši",
|
||||
"duplicate": "Dupliraj",
|
||||
"edit": "Edituj"
|
||||
},
|
||||
"empty-text": "Ova lista je prazna."
|
||||
},
|
||||
"tooltip": {
|
||||
"delete-item": "Da li sigurno želiš da izbrišeš ovu stavku? Ovo je nepovratna akcija.",
|
||||
"delete-section": "Obriši Sekciju",
|
||||
"rename-section": "Preimenuj Sekciju",
|
||||
"toggle-visibility": "Prebaci Vidljivost"
|
||||
}
|
||||
},
|
||||
"controller": {
|
||||
"tooltip": {
|
||||
"center-artboard": "Centriraj Kanvas",
|
||||
"copy-link": "Kopiraj link u rezime",
|
||||
"export-pdf": "Eksportuj PDF",
|
||||
"toggle-orientation": "Uključi/Isključi Orijentaciju Stranice",
|
||||
"toggle-page-break-line": "Uključi/Isključi liniju preloma stranice",
|
||||
"toggle-sidebars": "Uključi/Isključi Sidebar",
|
||||
"zoom-in": "Zumiraj",
|
||||
"zoom-out": "Odzumiraj"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"menu": {
|
||||
"delete": "Obriši",
|
||||
"duplicate": "Dupliraj",
|
||||
"rename": "Preimenuj",
|
||||
"share-link": "Podeli Link",
|
||||
"tooltips": {
|
||||
"delete": "Da li sigurno želiš da obrišeš ovaj rezime? Ovo je nepovratna akcija.",
|
||||
"share-link": "Moraš da promeniš vidljivost rezimea na javno kako bi bio vidljiv drugima."
|
||||
}
|
||||
}
|
||||
},
|
||||
"leftSidebar": {
|
||||
"sections": {
|
||||
"awards": {
|
||||
"form": {
|
||||
"awarder": {
|
||||
"label": "Dodelio nagradu"
|
||||
}
|
||||
}
|
||||
},
|
||||
"basics": {
|
||||
"actions": {
|
||||
"photo-filters": "Filteri za Slike"
|
||||
},
|
||||
"heading": "Osnovno",
|
||||
"headline": {
|
||||
"label": "Naslov"
|
||||
},
|
||||
"name": {
|
||||
"label": "Puno Ime"
|
||||
},
|
||||
"birthdate": {
|
||||
"label": "Datum Rođenja"
|
||||
},
|
||||
"photo-filters": {
|
||||
"effects": {
|
||||
"border": {
|
||||
"label": "Okvir"
|
||||
},
|
||||
"grayscale": {
|
||||
"label": "Sivi Ton"
|
||||
},
|
||||
"heading": "Efekti"
|
||||
},
|
||||
"shape": {
|
||||
"heading": "Oblik"
|
||||
},
|
||||
"size": {
|
||||
"heading": "Veličina (u pikselima)"
|
||||
}
|
||||
},
|
||||
"photo-upload": {
|
||||
"tooltip": {
|
||||
"remove": "Obriši Sliku",
|
||||
"upload": "Učitaj Sliku"
|
||||
}
|
||||
}
|
||||
},
|
||||
"certifications": {
|
||||
"form": {
|
||||
"issuer": {
|
||||
"label": "Izdavač"
|
||||
}
|
||||
}
|
||||
},
|
||||
"education": {
|
||||
"form": {
|
||||
"area-study": {
|
||||
"label": "Oblast Edukacije"
|
||||
},
|
||||
"courses": {
|
||||
"label": "Kursevi"
|
||||
},
|
||||
"degree": {
|
||||
"label": "Stepen"
|
||||
},
|
||||
"grade": {
|
||||
"label": "Ocena"
|
||||
},
|
||||
"institution": {
|
||||
"label": "Institucija"
|
||||
}
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"address": {
|
||||
"label": "Adresa"
|
||||
},
|
||||
"city": {
|
||||
"label": "Grad"
|
||||
},
|
||||
"country": {
|
||||
"label": "Zemlja"
|
||||
},
|
||||
"heading": "Lokacija",
|
||||
"postal-code": {
|
||||
"label": "Poštanski Broj"
|
||||
},
|
||||
"region": {
|
||||
"label": "Region"
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"form": {
|
||||
"network": {
|
||||
"label": "Mreža"
|
||||
},
|
||||
"username": {
|
||||
"label": "Korisničko ime"
|
||||
}
|
||||
},
|
||||
"heading": "Profili",
|
||||
"heading_one": "Profil"
|
||||
},
|
||||
"publications": {
|
||||
"form": {
|
||||
"publisher": {
|
||||
"label": "Izdavač"
|
||||
}
|
||||
}
|
||||
},
|
||||
"references": {
|
||||
"form": {
|
||||
"relationship": {
|
||||
"label": "Veza"
|
||||
}
|
||||
}
|
||||
},
|
||||
"section": {
|
||||
"heading": "Sekcija"
|
||||
},
|
||||
"volunteer": {
|
||||
"form": {
|
||||
"organization": {
|
||||
"label": "Organizacija"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"rightSidebar": {
|
||||
"sections": {
|
||||
"css": {
|
||||
"heading": "Prilagodi CSS"
|
||||
},
|
||||
"export": {
|
||||
"heading": "Eksportuj",
|
||||
"json": {
|
||||
"primary": "JSON",
|
||||
"secondary": "Preuzmi JSON verziju tvog rezimea koja se može importovati nazad u Reactive Resume."
|
||||
},
|
||||
"pdf": {
|
||||
"loading": {
|
||||
"primary": "Generišem PDF",
|
||||
"secondary": "Sačekaj dok se tvoj PDF generiše, ovo može da potraje do 15 sekundi."
|
||||
},
|
||||
"normal": {
|
||||
"primary": "PDF",
|
||||
"secondary": "Preuzmi PDF verziju rezimea koju možeš da štampaš i pošalješ za svoj posao iz snova. Ovaj fajl se ne može importovati ponovo kako bi se ponovo ažurirao."
|
||||
}
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
"heading": "Izgled",
|
||||
"tooltip": {
|
||||
"reset-layout": "Resetuj izgled"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"bugs-features": {
|
||||
"body": "Nešto de sprečava da napraviš svoj rezime? Ili imaš neku super ideju kao predlog? Kreiraj task na GitHub-u za početak.",
|
||||
"button": "GitHub Problemi",
|
||||
"heading": "Bagovi? Zahtevi za novu funkcionalnost?"
|
||||
},
|
||||
"donate": {
|
||||
"body": "Ako ti se sviđa Reactive Resume, razmotri o donaciji kako bi aplikacija nastavila da funkcioniše i bude dostupna, bez reklama i besplatna zauvek.",
|
||||
"button": "Kupi mi kafu",
|
||||
"heading": "Doniraj za Reactive Resume"
|
||||
},
|
||||
"github": "Izvorni Kod",
|
||||
"heading": "Linkovi"
|
||||
},
|
||||
"settings": {
|
||||
"global": {
|
||||
"date": {
|
||||
"primary": "Datum",
|
||||
"secondary": "Format datuma koji se koristi u aplikaciji"
|
||||
},
|
||||
"heading": "Globalno",
|
||||
"language": {
|
||||
"primary": "Jezik",
|
||||
"secondary": "Jezika prikaza za korišćenje u celoj aplikaciji"
|
||||
},
|
||||
"theme": {
|
||||
"primary": "Tema"
|
||||
}
|
||||
},
|
||||
"heading": "Podešavanja",
|
||||
"page": {
|
||||
"break-line": {
|
||||
"primary": "Prelomi Redova",
|
||||
"secondary": "Pokaži liniju na svakoj stranici kako bi se obeležila visina A4 stranice"
|
||||
},
|
||||
"heading": "Strana",
|
||||
"orientation": {
|
||||
"disabled": "Nema efekat kada postoji samo jedna stranica",
|
||||
"primary": "Orijentacija",
|
||||
"secondary": "Da li prikazujem stranice horizontalno ili vertikalno"
|
||||
}
|
||||
},
|
||||
"resume": {
|
||||
"heading": "Rezime",
|
||||
"reset": {
|
||||
"primary": "Resetuje sve",
|
||||
"secondary": "Previše grešaka? Klikni ovde da resetuješ sve izmene i počneš izpočetka. Vodi računa, ova akcija se ne može promeniti."
|
||||
},
|
||||
"sample": {
|
||||
"primary": "Učitaj podatke kao primer",
|
||||
"secondary": "Ne znaš gde da počneš? Klikni ovde da učitaš podatke kao primer i vidiš kako ceo rezime može da izgleda."
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"heading": "Deljenje",
|
||||
"short-url": {
|
||||
"label": "Kao Kratak URL"
|
||||
},
|
||||
"visibility": {
|
||||
"subtitle": "Dozvoli svakom sa linkom da pogleda tvoj rezime",
|
||||
"title": "Javno"
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"heading": "Šabloni"
|
||||
},
|
||||
"theme": {
|
||||
"form": {
|
||||
"background": {
|
||||
"label": "Pozadina"
|
||||
},
|
||||
"primary": {
|
||||
"label": "Glavni"
|
||||
},
|
||||
"text": {
|
||||
"label": "Tekst"
|
||||
}
|
||||
},
|
||||
"heading": "Tema"
|
||||
},
|
||||
"typography": {
|
||||
"form": {
|
||||
"font-family": {
|
||||
"label": "Porodica Fontova"
|
||||
},
|
||||
"font-size": {
|
||||
"label": "Veličina Fonta"
|
||||
}
|
||||
},
|
||||
"heading": "Tipografija",
|
||||
"widgets": {
|
||||
"body": {
|
||||
"label": "Sadržaj"
|
||||
},
|
||||
"headings": {
|
||||
"label": "Naslovi"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -95,7 +95,7 @@
|
||||
"share-link": "Bağlantıyı Paylaş",
|
||||
"tooltips": {
|
||||
"delete": "Bu özgeçmişi silmek istediğinizden emin misiniz? Bu geri dönüşü olmayan bir eylemdir.",
|
||||
"share-link": "Başkalarına görünür kılmak için özgeçmişinizin görünürlüğünü herkese açık olarak değiştirmeniz gerekir."
|
||||
"share-link": "Özgeçmişinizin herkese açık bir şekilde görüntülenebilmesi için, görünürlük ayarını değiştirmeniz gerekmektedir."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -1,39 +1,39 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": [".next"],
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"jsx": "preserve",
|
||||
"target": "es5",
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"allowJs": true,
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"baseUrl": "./",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"paths": {
|
||||
"@/components/*": ["components/*"],
|
||||
"@/config/*": ["config/*"],
|
||||
"@/constants/*": ["constants/*"],
|
||||
"@/data/*": ["data/*"],
|
||||
"@/i18n/*": ["i18n/*"],
|
||||
"@/modals/*": ["modals/*"],
|
||||
"@/pages/*": ["pages/*"],
|
||||
"@/public/*": ["public/*"],
|
||||
"@/services/*": ["services/*"],
|
||||
"@/store/*": ["store/*"],
|
||||
"@/styles/*": ["styles/*"],
|
||||
"@/templates/*": ["templates/*"],
|
||||
"@/types/*": ["types/*"],
|
||||
"@/utils/*": ["utils/*"],
|
||||
"@/wrappers/*": ["wrappers/*"]
|
||||
"@/config/*": ["config/*"],
|
||||
"@/modals/*": ["modals/*"],
|
||||
"@/public/*": ["public/*"],
|
||||
"@/styles/*": ["styles/*"],
|
||||
"@/services/*": ["services/*"],
|
||||
"@/wrappers/*": ["wrappers/*"],
|
||||
"@/constants/*": ["constants/*"],
|
||||
"@/templates/*": ["templates/*"],
|
||||
"@/components/*": ["components/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": [".next"]
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ export const dateFormatOptions: string[] = [
|
||||
'YYYY',
|
||||
];
|
||||
|
||||
export const getRelativeTime = (timestamp: dayjs.ConfigType): string => dayjs(timestamp).toNow(true);
|
||||
export const getRelativeTime = (timestamp: dayjs.ConfigType): string => dayjs(timestamp).utc().toNow(true);
|
||||
|
||||
export const formatDateString = (date: string | DateRange, formatStr: string): string | null => {
|
||||
const presentString = i18n?.t<string>('common.date.present') ?? '';
|
||||
@ -28,9 +28,9 @@ export const formatDateString = (date: string | DateRange, formatStr: string): s
|
||||
|
||||
// If `date` is a string
|
||||
if (isString(date)) {
|
||||
if (!dayjs(date).isValid()) return null;
|
||||
if (!dayjs(date).utc().utc().isValid()) return null;
|
||||
|
||||
return dayjs(date).format(formatStr);
|
||||
return dayjs(date).utc().format(formatStr);
|
||||
}
|
||||
|
||||
// If `date` is a DateRange
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
@ -7,10 +8,12 @@ const DateWrapper: React.FC<React.PropsWithChildren<unknown>> = ({ children }) =
|
||||
const { locale } = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
// Locales
|
||||
require('dayjs/locale/ar');
|
||||
require('dayjs/locale/bg');
|
||||
require('dayjs/locale/bn');
|
||||
require('dayjs/locale/cs');
|
||||
require('dayjs/locale/da');
|
||||
@ -19,6 +22,7 @@ const DateWrapper: React.FC<React.PropsWithChildren<unknown>> = ({ children }) =
|
||||
require('dayjs/locale/en');
|
||||
require('dayjs/locale/es');
|
||||
require('dayjs/locale/fa');
|
||||
require('dayjs/locale/fi');
|
||||
require('dayjs/locale/fr');
|
||||
require('dayjs/locale/he');
|
||||
require('dayjs/locale/hi');
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
version: '3'
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:14.2-alpine
|
||||
image: postgres:alpine
|
||||
container_name: postgres
|
||||
environment:
|
||||
- POSTGRES_DB=postgres
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
env_file: .env.docker
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
@ -21,7 +18,7 @@ services:
|
||||
restart: always
|
||||
|
||||
traefik:
|
||||
image: traefik:rocamadour
|
||||
image: traefik:latest
|
||||
container_name: traefik
|
||||
command:
|
||||
- --providers.docker=true
|
||||
@ -33,37 +30,12 @@ services:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
|
||||
server:
|
||||
image: amruthpillai/reactive-resume:server-latest
|
||||
# build:
|
||||
# context: .
|
||||
# dockerfile: ./server/Dockerfile
|
||||
# image: amruthpillai/reactive-resume:server-latest
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./server/Dockerfile
|
||||
container_name: server
|
||||
environment:
|
||||
- TZ=UTC
|
||||
- PUBLIC_URL=http://localhost:3000
|
||||
- PUBLIC_SERVER_URL=http://localhost:3000/api
|
||||
- PUBLIC_GOOGLE_CLIENT_ID=
|
||||
- POSTGRES_DB=postgres
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
- SECRET_KEY=
|
||||
- POSTGRES_HOST=postgres
|
||||
- POSTGRES_PORT=5432
|
||||
- POSTGRES_SSL_CERT=
|
||||
- JWT_SECRET=
|
||||
- JWT_EXPIRY_TIME=604800
|
||||
- GOOGLE_CLIENT_SECRET=
|
||||
- GOOGLE_API_KEY=
|
||||
- SENDGRID_API_KEY=
|
||||
- SENDGRID_FORGOT_PASSWORD_TEMPLATE_ID=
|
||||
- SENDGRID_FROM_NAME=
|
||||
- SENDGRID_FROM_EMAIL=
|
||||
- STORAGE_BUCKET=
|
||||
- STORAGE_REGION=
|
||||
- STORAGE_ENDPOINT=
|
||||
- STORAGE_URL_PREFIX=
|
||||
- STORAGE_ACCESS_KEY=
|
||||
- STORAGE_SECRET_KEY=
|
||||
env_file: .env.docker
|
||||
depends_on:
|
||||
- traefik
|
||||
- postgres
|
||||
@ -77,17 +49,12 @@ services:
|
||||
restart: always
|
||||
|
||||
client:
|
||||
image: amruthpillai/reactive-resume:client-latest
|
||||
# build:
|
||||
# context: .
|
||||
# dockerfile: ./client/Dockerfile
|
||||
# image: amruthpillai/reactive-resume:client-latest
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./client/Dockerfile
|
||||
container_name: client
|
||||
environment:
|
||||
- TZ=UTC
|
||||
- PUBLIC_URL=http://localhost:3000
|
||||
- PUBLIC_SERVER_URL=http://localhost:3000/api
|
||||
- PUBLIC_GOOGLE_CLIENT_ID=
|
||||
- PUBLIC_FLAG_DISABLE_SIGNUPS=false
|
||||
env_file: .env.docker
|
||||
depends_on:
|
||||
- traefik
|
||||
- server
|
||||
|
||||
2
docs/.gitignore
vendored
2
docs/.gitignore
vendored
@ -2,7 +2,7 @@
|
||||
/node_modules
|
||||
|
||||
# Production
|
||||
/build
|
||||
build
|
||||
|
||||
# Generated files
|
||||
.docusaurus
|
||||
|
||||
@ -46,20 +46,25 @@ You have complete control over what goes into your resume, how it looks, what co
|
||||
|
||||
- Arabic (اَلْعَرَبِيَّةُ)
|
||||
- Bengali (বাংলা)
|
||||
- Bulgarian (български)
|
||||
- Chinese (中文)
|
||||
- Czech (čeština)
|
||||
- Danish (Dansk)
|
||||
- Dutch (Nederlands)
|
||||
- English
|
||||
- Finnish (Suomi)
|
||||
- French (Français)
|
||||
- German (Deutsch)
|
||||
- Greek (Ελληνικά)
|
||||
- Hebrew (Ivrit)
|
||||
- Hindi (हिन्दी)
|
||||
- Hungarian (Magyar)
|
||||
- Indonesian (Bahasa Indonesia)
|
||||
- Italian (Italiano)
|
||||
- Kannada (ಕನ್ನಡ)
|
||||
- Malayalam (മലയാളം)
|
||||
- Odia (ଓଡ଼ିଆ)
|
||||
- Persian (Farsi)
|
||||
- Polish (Polski)
|
||||
- Portuguese (Português)
|
||||
- Russian (русский)
|
||||
|
||||
@ -136,30 +136,16 @@ You can get your own key here: https://developers.google.com/fonts/docs/develope
|
||||
|
||||
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
|
||||
## Mail
|
||||
|
||||
The server makes use of SendGrid to send the password reset email to those who have forgotten their password. **This section is completely optional for those who do not require this functionality.**
|
||||
The server makes use of SMTP 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.**
|
||||
|
||||
### `SENDGRID_API_KEY`
|
||||
|
||||
**Required**: `no`
|
||||
**Description:** SendGrid API Key
|
||||
|
||||
Does not require any payment or credit card information to obtain an API key.
|
||||
|
||||
You can get your own key here: https://docs.sendgrid.com/ui/account-and-settings/api-keys
|
||||
|
||||
### `SENDGRID_FORGOT_PASSWORD_TEMPLATE_ID`
|
||||
|
||||
**Required**: `no`
|
||||
**Description:** Dynamic Template ID for Forgot Password
|
||||
|
||||
### `SENDGRID_FROM_NAME`
|
||||
### `MAIL_FROM_NAME`
|
||||
|
||||
**Required**: `no`
|
||||
**Description:** Sender's Name
|
||||
|
||||
### `SENDGRID_FROM_EMAIL`
|
||||
### `MAIL_FROM_EMAIL`
|
||||
|
||||
**Required**: `no`
|
||||
**Description:** Sender's Email Address
|
||||
|
||||
@ -4,7 +4,6 @@ sidebar_position: 2
|
||||
|
||||
# Create a new resume
|
||||
|
||||
|
||||
To create a new resume from scratch, click on the selected part.
|
||||
|
||||
<a href="https://rxresu.me/dashboard">
|
||||
@ -12,13 +11,27 @@ To create a new resume from scratch, click on the selected part.
|
||||
</a>
|
||||
|
||||
<p>
|
||||
After you click on it, enter name and select whether you want it publicly accessible
|
||||
<img align="center" src="https://res.cloudinary.com/dn2mupqo0/image/upload/v1655884469/on-button_a5kfbz.png" height='25' type="image"></img> or not
|
||||
<img align="center" src="https://res.cloudinary.com/dn2mupqo0/image/upload/v1655884949/off-button_rrkz3g.png" height='25' type="image"></img> . You can also change it later from the sharing <a href="https://res.cloudinary.com/dn2mupqo0/image/upload/v1655889311/toggle_jcmfix.gif">see where</a>.
|
||||
After you click on it, enter name and select whether you want it publicly accessible
|
||||
<img
|
||||
align="center"
|
||||
src="https://res.cloudinary.com/dn2mupqo0/image/upload/v1655884469/on-button_a5kfbz.png"
|
||||
height="25"
|
||||
type="image"
|
||||
></img> or not
|
||||
<img
|
||||
align="center"
|
||||
src="https://res.cloudinary.com/dn2mupqo0/image/upload/v1655884949/off-button_rrkz3g.png"
|
||||
height="25"
|
||||
type="image"
|
||||
></img> . You can also change it later from the sharing{' '}
|
||||
<a href="https://res.cloudinary.com/dn2mupqo0/image/upload/v1655889311/toggle_jcmfix.gif">see where</a>.
|
||||
</p>
|
||||
|
||||
|
||||
<h3> Now it's look like ⚡</h3>
|
||||
|
||||
<a href="https://rxresu.me/dashboard">
|
||||
<img src="https://res.cloudinary.com/dn2mupqo0/image/upload/v1655882355/Screenshot_2022-06-22_at_12.48.42_PM_dx6714.png" type="image"></img>
|
||||
<img
|
||||
src="https://res.cloudinary.com/dn2mupqo0/image/upload/v1655882355/Screenshot_2022-06-22_at_12.48.42_PM_dx6714.png"
|
||||
type="image"
|
||||
></img>
|
||||
</a>
|
||||
|
||||
@ -1,10 +1,6 @@
|
||||
// @ts-check
|
||||
// Note: type annotations allow type checking and IDEs autocompletion
|
||||
|
||||
const lightCodeTheme = require('prism-react-renderer/themes/github');
|
||||
const darkCodeTheme = require('prism-react-renderer/themes/dracula');
|
||||
|
||||
/** @type {import('@docusaurus/types').Config} */
|
||||
const config = {
|
||||
title: 'Reactive Resume',
|
||||
tagline: 'A free and open source resume builder.',
|
||||
@ -24,7 +20,6 @@ const config = {
|
||||
blog: false,
|
||||
docs: {
|
||||
routeBasePath: '/',
|
||||
sidebarPath: require.resolve('./sidebars.js'),
|
||||
editUrl: 'https://github.com/AmruthPillai/Reactive-Resume/tree/main/docs',
|
||||
},
|
||||
sitemap: {
|
||||
|
||||
@ -3,25 +3,17 @@
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
"start": "docusaurus start --no-open --port 3200",
|
||||
"dev": "docusaurus start --no-open --port 3200",
|
||||
"build": "docusaurus build",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
"deploy": "docusaurus deploy",
|
||||
"clear": "docusaurus clear",
|
||||
"serve": "docusaurus serve --port 3200",
|
||||
"write-translations": "docusaurus write-translations",
|
||||
"write-heading-ids": "docusaurus write-heading-ids"
|
||||
"start": "docusaurus serve --no-open --port 3200"
|
||||
},
|
||||
"dependencies": {
|
||||
"@algolia/client-search": "^4.13.1",
|
||||
"@docusaurus/core": "2.0.0-beta.21",
|
||||
"@docusaurus/preset-classic": "2.0.0-beta.21",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"clsx": "^1.1.1",
|
||||
"@docusaurus/core": "2.0.1",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"@docusaurus/preset-classic": "2.0.1"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
@ -36,7 +28,7 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.14",
|
||||
"typescript": "^4.7.4"
|
||||
"typescript": "^4.7.4",
|
||||
"@types/react": "^17.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Creating a sidebar enables you to:
|
||||
- create an ordered group of docs
|
||||
- render a sidebar for each doc of that group
|
||||
- provide next/previous navigation
|
||||
|
||||
The sidebars can be generated from the filesystem, or explicitly defined here.
|
||||
|
||||
Create as many sidebars as you want.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
|
||||
const sidebars = {
|
||||
// By default, Docusaurus generates a sidebar from the docs folder structure
|
||||
tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }],
|
||||
|
||||
// But you can create a sidebar manually
|
||||
/*
|
||||
tutorialSidebar: [
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Tutorial',
|
||||
items: ['hello'],
|
||||
},
|
||||
],
|
||||
*/
|
||||
};
|
||||
|
||||
module.exports = sidebars;
|
||||
49834
package-lock.json
generated
49834
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
60
package.json
60
package.json
@ -1,58 +1,36 @@
|
||||
{
|
||||
"name": "reactive-resume",
|
||||
"version": "3.4.7",
|
||||
"version": "3.6.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "env-cmd --silent turbo run dev",
|
||||
"lint": "eslint --fix .",
|
||||
"build": "env-cmd --silent turbo run build",
|
||||
"start": "env-cmd --silent turbo run start",
|
||||
"format": "prettier --write .",
|
||||
"release": "standard-version --release-as patch"
|
||||
},
|
||||
"workspaces": [
|
||||
"schema",
|
||||
"client",
|
||||
"server",
|
||||
"docs"
|
||||
],
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "cz-conventional-changelog"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "husky install",
|
||||
"release": "standard-version",
|
||||
"lint": "eslint --quiet --fix --ext .js,.ts,.tsx .",
|
||||
"format": "prettier --loglevel error --write \"./**/*.{js,ts,tsx,json}\"",
|
||||
"dev:schema": "pnpm -F schema dev",
|
||||
"dev:server": "pnpm -F server start:debug",
|
||||
"dev:client": "pnpm -F client dev",
|
||||
"dev:docs": "pnpm -F docs start",
|
||||
"dev": "env-cmd --silent concurrently --kill-others \"pnpm run dev:*\"",
|
||||
"build:schema": "pnpm -F schema build",
|
||||
"build:server": "pnpm -F server build",
|
||||
"build:client": "pnpm -F client build",
|
||||
"build:docs": "pnpm -F docs build",
|
||||
"build": "env-cmd --silent concurrently \"pnpm run build:*\"",
|
||||
"start:server": "pnpm -F server start:prod",
|
||||
"start:client": "pnpm -F client start",
|
||||
"start:docs": "pnpm -F docs serve",
|
||||
"start": "env-cmd --silent concurrently --kill-others \"pnpm run start:*\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.120.0",
|
||||
"@docusaurus/core": "^2.0.0-beta.21",
|
||||
"@docusaurus/preset-classic": "^2.0.0-beta.21",
|
||||
"concurrently": "^7.2.2",
|
||||
"turbo": "^1.4.3",
|
||||
"env-cmd": "^10.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.0",
|
||||
"@typescript-eslint/parser": "^5.30.0",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"eslint": "^8.18.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-simple-import-sort": "^7.0.0",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"husky": "^8.0.1",
|
||||
"eslint": "^8.22.0",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.7.4",
|
||||
"standard-version": "^9.5.0",
|
||||
"typescript": "^4.7.4"
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"@typescript-eslint/parser": "^5.33.1",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.1",
|
||||
"eslint-plugin-simple-import-sort": "^7.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
7959
pnpm-lock.yaml
generated
7959
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -4,12 +4,11 @@
|
||||
"main": "./dist/index.js",
|
||||
"typings": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc -w",
|
||||
"lint": "eslint --fix --ext .ts ./src"
|
||||
"build": "tsc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.18.0",
|
||||
"eslint": "^8.22.0",
|
||||
"typescript": "^4.7.4"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,22 +1,19 @@
|
||||
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
|
||||
FROM node:lts-alpine AS base
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json pnpm-*.yaml ./
|
||||
RUN apk add --no-cache g++ git curl make python3 \
|
||||
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
||||
|
||||
FROM base AS dependencies
|
||||
|
||||
COPY package.json pnpm-*.yaml turbo.json ./
|
||||
COPY ./schema/package.json ./schema/package.json
|
||||
COPY ./server/package.json ./server/package.json
|
||||
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
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
|
||||
|
||||
WORKDIR /app
|
||||
FROM base AS builder
|
||||
|
||||
COPY . .
|
||||
|
||||
@ -24,25 +21,31 @@ 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
|
||||
|
||||
RUN pnpm run build:schema
|
||||
RUN pnpm run build:server
|
||||
ARG TURBO_TEAM
|
||||
ARG TURBO_TOKEN
|
||||
|
||||
FROM mcr.microsoft.com/playwright:focal as production
|
||||
ENV TURBO_TEAM $TURBO_TEAM
|
||||
ENV TURBO_TOKEN $TURBO_TOKEN
|
||||
|
||||
RUN pnpm run build --filter server
|
||||
|
||||
FROM mcr.microsoft.com/playwright:next-jammy as production
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y curl \
|
||||
&& apt-get install -y curl build-essential \
|
||||
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
||||
|
||||
COPY --from=builder /app/pnpm-*.yaml ./
|
||||
COPY --from=builder /app/package.json ./
|
||||
COPY --from=builder /app/package.json /app/pnpm-*.yaml /app/turbo.json ./
|
||||
COPY --from=builder /app/server/package.json ./server/package.json
|
||||
|
||||
RUN pnpm install -F server --frozen-lockfile --prod
|
||||
RUN pnpm dlx playwright install --with-deps chromium
|
||||
RUN pnpm install --filter server --prod --frozen-lockfile --workspace-root
|
||||
|
||||
COPY --from=builder /app/server/dist ./server/dist
|
||||
|
||||
VOLUME /app/server/dist/assets/exports
|
||||
VOLUME /app/server/dist/assets/uploads
|
||||
|
||||
EXPOSE 3100
|
||||
@ -52,4 +55,4 @@ ENV PORT 3100
|
||||
HEALTHCHECK --interval=30s --timeout=20s --retries=3 --start-period=15s \
|
||||
CMD curl -fSs localhost:3100/health || exit 1
|
||||
|
||||
CMD [ "pnpm", "run", "start:server" ]
|
||||
CMD [ "pnpm", "run", "start", "--filter", "server" ]
|
||||
@ -1,72 +1,71 @@
|
||||
{
|
||||
"name": "@reactive-resume/server",
|
||||
"scripts": {
|
||||
"dev": "nest start --watch",
|
||||
"build": "rimraf dist && nest build",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"lint": "eslint --fix --ext .ts ./src"
|
||||
"debug": "nest start --debug --watch",
|
||||
"start": "node dist/main"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.120.0",
|
||||
"@nestjs/axios": "^0.0.8",
|
||||
"@nestjs/common": "^8.4.7",
|
||||
"@nestjs/config": "^2.1.0",
|
||||
"@nestjs/core": "^8.4.7",
|
||||
"@nestjs/jwt": "^8.0.1",
|
||||
"@nestjs/mapped-types": "^1.0.1",
|
||||
"@nestjs/passport": "^8.2.2",
|
||||
"@nestjs/platform-express": "^8.4.7",
|
||||
"@nestjs/schedule": "^2.0.1",
|
||||
"@nestjs/serve-static": "^2.2.2",
|
||||
"@nestjs/terminus": "^8.1.0",
|
||||
"@nestjs/typeorm": "^8.1.4",
|
||||
"@sendgrid/mail": "^7.7.0",
|
||||
"@types/passport": "^1.0.9",
|
||||
"bcrypt": "^5.0.1",
|
||||
"cache-manager": "^4.0.1",
|
||||
"@aws-sdk/client-s3": "^3.154.0",
|
||||
"@nestjs/axios": "^0.1.0",
|
||||
"@nestjs/common": "^9.0.11",
|
||||
"@nestjs/config": "^2.2.0",
|
||||
"@nestjs/core": "^9.0.11",
|
||||
"@nestjs/jwt": "^9.0.0",
|
||||
"@nestjs/mapped-types": "^1.1.0",
|
||||
"@nestjs/passport": "^9.0.0",
|
||||
"@nestjs/platform-express": "^9.0.11",
|
||||
"@nestjs/schedule": "^2.1.0",
|
||||
"@nestjs/serve-static": "^3.0.0",
|
||||
"@nestjs/terminus": "^9.1.1",
|
||||
"@nestjs/typeorm": "^9.0.1",
|
||||
"@types/passport": "^1.0.10",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"cache-manager": "^4.1.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.13.2",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"csvtojson": "^2.0.10",
|
||||
"dayjs": "^1.11.3",
|
||||
"google-auth-library": "^8.1.0",
|
||||
"dayjs": "^1.11.5",
|
||||
"google-auth-library": "^8.3.0",
|
||||
"joi": "^17.6.0",
|
||||
"lodash": "^4.17.21",
|
||||
"multer": "^1.4.4",
|
||||
"nanoid": "^3.0.0",
|
||||
"nanoid": "^3.3.4",
|
||||
"node-stream-zip": "^1.15.0",
|
||||
"nodemailer": "^6.7.8",
|
||||
"passport": "^0.6.0",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pg": "^8.7.3",
|
||||
"playwright-chromium": "^1.23.1",
|
||||
"playwright-chromium": "^1.25.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.5.5",
|
||||
"rxjs": "^7.5.6",
|
||||
"typeorm": "0.3.7",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^8.2.8",
|
||||
"@nestjs/schematics": "^8.0.11",
|
||||
"@nestjs/cli": "^9.0.0",
|
||||
"@nestjs/schematics": "^9.0.1",
|
||||
"@reactive-resume/schema": "workspace:*",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/lodash": "^4.14.184",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/node": "^18.0.0",
|
||||
"eslint": "^8.18.0",
|
||||
"@types/node": "^18.7.9",
|
||||
"@types/nodemailer": "^6.4.5",
|
||||
"@types/passport-jwt": "^3.0.6",
|
||||
"@types/passport-local": "^1.0.34",
|
||||
"prettier": "^2.7.1",
|
||||
"source-map-support": "^0.5.21",
|
||||
"ts-loader": "^9.3.1",
|
||||
"ts-node": "^10.8.1",
|
||||
"tsconfig-paths": "^4.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "^4.1.0",
|
||||
"typescript": "^4.7.4",
|
||||
"webpack": "^5.73.0"
|
||||
"webpack": "^5.74.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,8 +2,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { SchedulerRegistry } from '@nestjs/schedule';
|
||||
import bcrypt from 'bcrypt';
|
||||
import { randomInt } from 'crypto';
|
||||
import { compareSync, hashSync } from 'bcryptjs';
|
||||
import { OAuth2Client } from 'google-auth-library';
|
||||
|
||||
import { PostgresErrorCode } from '@/database/errorCodes.enum';
|
||||
@ -24,12 +23,12 @@ export class AuthService {
|
||||
) {}
|
||||
|
||||
async register(registerDto: RegisterDto) {
|
||||
const hashedPassword = await bcrypt.hash(registerDto.password, randomInt(8, 12));
|
||||
const password = hashSync(registerDto.password);
|
||||
|
||||
try {
|
||||
const createdUser = await this.usersService.create({
|
||||
...registerDto,
|
||||
password: hashedPassword,
|
||||
password,
|
||||
provider: 'email',
|
||||
});
|
||||
|
||||
@ -43,6 +42,17 @@ export class AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
async verifyPassword(password: string, hashedPassword: string) {
|
||||
const isPasswordMatching = compareSync(password, hashedPassword);
|
||||
|
||||
if (!isPasswordMatching) {
|
||||
throw new HttpException(
|
||||
'The username/email and password combination provided was incorrect.',
|
||||
HttpStatus.UNAUTHORIZED
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getUser(identifier: string, password: string) {
|
||||
try {
|
||||
const user = await this.usersService.findByIdentifier(identifier);
|
||||
@ -58,27 +68,17 @@ export class AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
async verifyPassword(password: string, hashedPassword: string) {
|
||||
const isPasswordMatching = await bcrypt.compare(password, hashedPassword);
|
||||
|
||||
if (!isPasswordMatching) {
|
||||
throw new HttpException(
|
||||
'The username/email and password combination provided was incorrect.',
|
||||
HttpStatus.UNAUTHORIZED
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
forgotPassword(email: string) {
|
||||
return this.usersService.generateResetToken(email);
|
||||
}
|
||||
|
||||
async resetPassword(resetPasswordDto: ResetPasswordDto) {
|
||||
const user = await this.usersService.findByResetToken(resetPasswordDto.resetToken);
|
||||
const hashedPassword = await bcrypt.hash(resetPasswordDto.password, randomInt(8, 12));
|
||||
|
||||
const password = hashSync(resetPasswordDto.password);
|
||||
|
||||
await this.usersService.update(user.id, {
|
||||
password: hashedPassword,
|
||||
password,
|
||||
resetToken: null,
|
||||
});
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import appConfig from './app.config';
|
||||
import authConfig from './auth.config';
|
||||
import databaseConfig from './database.config';
|
||||
import googleConfig from './google.config';
|
||||
import sendgridConfig from './sendgrid.config';
|
||||
import mailConfig from './mail.config';
|
||||
import storageConfig from './storage.config';
|
||||
|
||||
const validationSchema = Joi.object({
|
||||
@ -36,11 +36,13 @@ const validationSchema = Joi.object({
|
||||
GOOGLE_CLIENT_SECRET: Joi.string().allow(''),
|
||||
PUBLIC_GOOGLE_CLIENT_ID: Joi.string().allow(''),
|
||||
|
||||
// SendGrid
|
||||
SENDGRID_API_KEY: Joi.string().allow(''),
|
||||
SENDGRID_FORGOT_PASSWORD_TEMPLATE_ID: Joi.string().allow(''),
|
||||
SENDGRID_FROM_NAME: Joi.string().allow(''),
|
||||
SENDGRID_FROM_EMAIL: Joi.string().allow(''),
|
||||
// Mail
|
||||
MAIL_FROM_NAME: Joi.string().allow(''),
|
||||
MAIL_FROM_EMAIL: Joi.string().allow(''),
|
||||
MAIL_HOST: Joi.string().allow(''),
|
||||
MAIL_PORT: Joi.string().allow(''),
|
||||
MAIL_USERNAME: Joi.string().allow(''),
|
||||
MAIL_PASSWORD: Joi.string().allow(''),
|
||||
|
||||
// Storage
|
||||
STORAGE_BUCKET: Joi.string().allow(''),
|
||||
@ -54,7 +56,7 @@ const validationSchema = Joi.object({
|
||||
@Module({
|
||||
imports: [
|
||||
NestConfigModule.forRoot({
|
||||
load: [appConfig, authConfig, databaseConfig, googleConfig, sendgridConfig, storageConfig],
|
||||
load: [appConfig, authConfig, databaseConfig, googleConfig, mailConfig, storageConfig],
|
||||
validationSchema: validationSchema,
|
||||
}),
|
||||
],
|
||||
|
||||
12
server/src/config/mail.config.ts
Normal file
12
server/src/config/mail.config.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs('mail', () => ({
|
||||
from: {
|
||||
name: process.env.MAIL_FROM_NAME,
|
||||
email: process.env.MAIL_FROM_EMAIL,
|
||||
},
|
||||
host: process.env.MAIL_HOST,
|
||||
port: process.env.MAIL_PORT,
|
||||
username: process.env.MAIL_USERNAME,
|
||||
password: process.env.MAIL_PASSWORD,
|
||||
}));
|
||||
@ -1,8 +0,0 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs('sendgrid', () => ({
|
||||
apiKey: process.env.SENDGRID_API_KEY,
|
||||
forgotPasswordTemplateId: process.env.SENDGRID_FORGOT_PASSWORD_TEMPLATE_ID,
|
||||
fromName: process.env.SENDGRID_FROM_NAME,
|
||||
fromEmail: process.env.SENDGRID_FROM_EMAIL,
|
||||
}));
|
||||
@ -12,10 +12,6 @@ export class HealthController {
|
||||
@Get()
|
||||
@HealthCheck()
|
||||
check() {
|
||||
return this.health.check([
|
||||
() => this.db.pingCheck('database'),
|
||||
() => this.http.pingCheck('app', 'https://rxresu.me'),
|
||||
() => this.http.pingCheck('docs', 'https://docs.rxresu.me'),
|
||||
]);
|
||||
return this.health.check([() => this.db.pingCheck('database')]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ 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';
|
||||
@ -28,7 +29,9 @@ import { ResumeService } from '@/resume/resume.service';
|
||||
|
||||
@Injectable()
|
||||
export class IntegrationsService {
|
||||
constructor(private resumeService: ResumeService) {}
|
||||
constructor(private resumeService: ResumeService) {
|
||||
dayjs.extend(utc);
|
||||
}
|
||||
|
||||
async linkedIn(userId: number, path: string): Promise<ResumeEntity> {
|
||||
let archive: StreamZip.StreamZipAsync;
|
||||
@ -39,7 +42,7 @@ export class IntegrationsService {
|
||||
const resume: Partial<Resume> = cloneDeep(defaultState);
|
||||
|
||||
// Basics
|
||||
const timestamp = dayjs().format(FILENAME_TIMESTAMP);
|
||||
const timestamp = dayjs().utc().format(FILENAME_TIMESTAMP);
|
||||
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
|
||||
name: `Imported from LinkedIn (${timestamp})`,
|
||||
slug: `imported-from-linkedin-${timestamp}`,
|
||||
@ -269,7 +272,7 @@ export class IntegrationsService {
|
||||
const resume: Partial<Resume> = cloneDeep(defaultState);
|
||||
|
||||
// Metadata
|
||||
const timestamp = dayjs().format(FILENAME_TIMESTAMP);
|
||||
const timestamp = dayjs().utc().format(FILENAME_TIMESTAMP);
|
||||
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
|
||||
name: `Imported from JSON Resume (${timestamp})`,
|
||||
slug: `imported-from-json-resume-${timestamp}`,
|
||||
@ -604,7 +607,7 @@ export class IntegrationsService {
|
||||
const resume: Partial<Resume> = cloneDeep(jsonResume);
|
||||
|
||||
// Metadata
|
||||
const timestamp = dayjs().format(FILENAME_TIMESTAMP);
|
||||
const timestamp = dayjs().utc().format(FILENAME_TIMESTAMP);
|
||||
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
|
||||
name: `Imported from Reactive Resume (${timestamp})`,
|
||||
slug: `imported-from-reactive-resume-${timestamp}`,
|
||||
@ -625,7 +628,7 @@ export class IntegrationsService {
|
||||
const resume: Partial<Resume> = cloneDeep(defaultState);
|
||||
|
||||
// Metadata
|
||||
const timestamp = dayjs().format(FILENAME_TIMESTAMP);
|
||||
const timestamp = dayjs().utc().format(FILENAME_TIMESTAMP);
|
||||
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
|
||||
name: `Imported from Reactive Resume V2 (${timestamp})`,
|
||||
slug: `imported-from-reactive-resume-v2-${timestamp}`,
|
||||
@ -948,6 +951,6 @@ export class IntegrationsService {
|
||||
}
|
||||
|
||||
private parseDate = (date: string): string => {
|
||||
return isEmpty(date) ? '' : dayjs(date).toISOString();
|
||||
return isEmpty(date) ? '' : dayjs(date).utc().toISOString();
|
||||
};
|
||||
}
|
||||
|
||||
30
server/src/mail/dto/send-mail.dto.ts
Normal file
30
server/src/mail/dto/send-mail.dto.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsDefined, IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class MailRecipient {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
email: string;
|
||||
}
|
||||
|
||||
export class SendMailDto {
|
||||
@IsDefined()
|
||||
@Type(() => MailRecipient)
|
||||
from: MailRecipient;
|
||||
|
||||
@IsDefined()
|
||||
@Type(() => MailRecipient)
|
||||
to: MailRecipient;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
subject: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
message: string;
|
||||
}
|
||||
@ -1,36 +1,56 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import SendGrid from '@sendgrid/mail';
|
||||
import { createTransport, Transporter } from 'nodemailer';
|
||||
|
||||
import { User } from '@/users/entities/user.entity';
|
||||
|
||||
import { SendMailDto } from './dto/send-mail.dto';
|
||||
|
||||
@Injectable()
|
||||
export class MailService {
|
||||
transporter: Transporter;
|
||||
|
||||
constructor(private configService: ConfigService) {
|
||||
SendGrid.setApiKey(this.configService.get<string>('sendgrid.apiKey'));
|
||||
this.transporter = createTransport({
|
||||
host: this.configService.get<string>('mail.host'),
|
||||
port: this.configService.get<number>('mail.port'),
|
||||
pool: true,
|
||||
secure: false,
|
||||
tls: { ciphers: 'SSLv3' },
|
||||
auth: {
|
||||
user: this.configService.get<string>('mail.username'),
|
||||
pass: this.configService.get<string>('mail.password'),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async sendEmail(mail: SendGrid.MailDataRequired) {
|
||||
return SendGrid.send(mail);
|
||||
async sendEmail(sendMailDto: SendMailDto) {
|
||||
this.transporter.sendMail({
|
||||
from: `${sendMailDto.from.name} <${sendMailDto.from.email}>`,
|
||||
to: `${sendMailDto.to.name} <${sendMailDto.to.email}>`,
|
||||
subject: sendMailDto.subject,
|
||||
text: sendMailDto.message,
|
||||
html: sendMailDto.message,
|
||||
});
|
||||
}
|
||||
|
||||
async sendForgotPasswordEmail(user: User, resetToken: string): Promise<void> {
|
||||
const appUrl = this.configService.get<string>('app.url');
|
||||
const url = `${appUrl}?modal=auth.reset&resetToken=${resetToken}`;
|
||||
|
||||
const mailData: SendGrid.MailDataRequired = {
|
||||
const sendMailDto: SendMailDto = {
|
||||
from: {
|
||||
name: this.configService.get<string>('sendgrid.fromName'),
|
||||
email: this.configService.get<string>('sendgrid.fromEmail'),
|
||||
name: this.configService.get<string>('mail.from.name'),
|
||||
email: this.configService.get<string>('mail.from.email'),
|
||||
},
|
||||
to: user.email,
|
||||
hideWarnings: true,
|
||||
dynamicTemplateData: { url },
|
||||
templateId: this.configService.get<string>('sendgrid.forgotPasswordTemplateId'),
|
||||
to: {
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
},
|
||||
subject: 'Reset your Reactive Resume password',
|
||||
message: `<p>Hey ${user.name}!</p> <p>You can reset your password by visiting this link: <a href="${url}">${url}</a>.</p> <p>But hurry, because it will expire in 30 minutes.</p>`,
|
||||
};
|
||||
|
||||
await SendGrid.send(mailData);
|
||||
|
||||
return;
|
||||
await this.sendEmail(sendMailDto);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { SchedulerRegistry } from '@nestjs/schedule';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { randomBytes } from 'crypto';
|
||||
import { Connection, Repository } from 'typeorm';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import { MailService } from '@/mail/mail.service';
|
||||
|
||||
@ -19,7 +19,7 @@ export class UsersService {
|
||||
@InjectRepository(User) private userRepository: Repository<User>,
|
||||
private schedulerRegistry: SchedulerRegistry,
|
||||
private mailService: MailService,
|
||||
private connection: Connection
|
||||
private dataSource: DataSource
|
||||
) {}
|
||||
|
||||
async findById(id: number): Promise<User> {
|
||||
@ -93,7 +93,7 @@ export class UsersService {
|
||||
const user = await this.findByEmail(email);
|
||||
|
||||
const resetToken = randomBytes(32).toString('hex');
|
||||
const queryRunner = this.connection.createQueryRunner();
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
const timeout = setTimeout(async () => {
|
||||
await this.userRepository.update(user.id, { resetToken: null });
|
||||
|
||||
@ -3,33 +3,33 @@
|
||||
"exclude": ["dist"],
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "es2017",
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"baseUrl": ".",
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": false,
|
||||
"noImplicitAny": false,
|
||||
"removeComments": true,
|
||||
"strictNullChecks": false,
|
||||
"resolveJsonModule": true,
|
||||
"strictBindCallApply": false,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"noFallthroughCasesInSwitch": false,
|
||||
"baseUrl": ".",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"paths": {
|
||||
"@/auth/*": ["src/auth/*"],
|
||||
"@/config/*": ["src/config/*"],
|
||||
"@/constants/*": ["src/constants/*"],
|
||||
"@/database/*": ["src/database/*"],
|
||||
"@/decorators/*": ["src/decorators/*"],
|
||||
"@/filters/*": ["src/filters/*"],
|
||||
"@/mail/*": ["src/mail/*"],
|
||||
"@/users/*": ["src/users/*"],
|
||||
"@/config/*": ["src/config/*"],
|
||||
"@/resume/*": ["src/resume/*"],
|
||||
"@/users/*": ["src/users/*"]
|
||||
"@/filters/*": ["src/filters/*"],
|
||||
"@/database/*": ["src/database/*"],
|
||||
"@/constants/*": ["src/constants/*"],
|
||||
"@/decorators/*": ["src/decorators/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
||||
|
||||
16
turbo.json
Normal file
16
turbo.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://turborepo.org/schema.json",
|
||||
"pipeline": {
|
||||
"dev": {
|
||||
"cache": false
|
||||
},
|
||||
"start": {
|
||||
"cache": false
|
||||
},
|
||||
"build": {
|
||||
"outputs": ["dist/**", ".next/**"],
|
||||
"dependsOn": ["^build"]
|
||||
}
|
||||
},
|
||||
"globalDependencies": [".env"]
|
||||
}
|
||||
Reference in New Issue
Block a user