mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-11 13:02:25 +10:00
Compare commits
391 Commits
v3.6.8-pat
...
v3.7.4
| Author | SHA1 | Date | |
|---|---|---|---|
| 30fd283898 | |||
| 726ea7312b | |||
| f3a7180d4b | |||
| 0173ce32c3 | |||
| d4b6c16bf9 | |||
| c571f201d3 | |||
| e4ecf50ed4 | |||
| 5ee99cfdab | |||
| 72e610b50d | |||
| ba34787333 | |||
| e11b0e6224 | |||
| c78ee18e05 | |||
| 5f5b484243 | |||
| bcc451a6a1 | |||
| 55a7f6a556 | |||
| e9b6265c60 | |||
| 2e2f3271c9 | |||
| fa3e92d643 | |||
| 1f9b52eda6 | |||
| 7074b6fc76 | |||
| b4c4fb94f7 | |||
| 22bdb64fa9 | |||
| af02158d05 | |||
| 6a8db92fc4 | |||
| 6f219ef17e | |||
| 667e51abdc | |||
| 7b98277c32 | |||
| 77ed7ed8be | |||
| ce584d9326 | |||
| 5685352375 | |||
| 036b2917a6 | |||
| e972320722 | |||
| 4ac1e9db35 | |||
| 9fe4403b40 | |||
| 4f4084ab45 | |||
| 72227dc9ab | |||
| d44795a421 | |||
| e9584144e4 | |||
| bbedfa3b75 | |||
| 03f7d74096 | |||
| a62693d611 | |||
| 421f195e1e | |||
| b22dff523f | |||
| 58d0c6e315 | |||
| 36178cac22 | |||
| 376786fa25 | |||
| efceda1c55 | |||
| 047e317c51 | |||
| 36ad63adb9 | |||
| 45c88caf58 | |||
| ca11a9217a | |||
| fd6fbbba77 | |||
| e2fb83bda9 | |||
| 40567e8f61 | |||
| 64c899b159 | |||
| b267cc4097 | |||
| f4657b6592 | |||
| 6a2f512638 | |||
| 499005c21f | |||
| 0e18d3fc48 | |||
| 3b831c4eb4 | |||
| 40564944ef | |||
| fdbb6d2e5b | |||
| 398cd63082 | |||
| efd4af14e5 | |||
| 889697fc31 | |||
| 3aedf6618d | |||
| abf42e13af | |||
| 40bcbebadd | |||
| 364f2e6d49 | |||
| 7e5dfd75f9 | |||
| b94d10c614 | |||
| 8c40b417ec | |||
| 1f17dfe6ea | |||
| be6ea1a224 | |||
| 583e9effae | |||
| 619b2757c8 | |||
| 9e27eee029 | |||
| c2d3c611e1 | |||
| 735f589e54 | |||
| 1e3d6fbb77 | |||
| 3995e7159a | |||
| 6662acf0b0 | |||
| feb8abca95 | |||
| 75c83bd91d | |||
| f6d5897ed3 | |||
| ed356763a1 | |||
| 4847246d84 | |||
| a0ae6cb77e | |||
| 2aa2550be0 | |||
| df39913d49 | |||
| 2225505d48 | |||
| afe20e61ee | |||
| 794e9c6511 | |||
| e7e423bf29 | |||
| 2173297207 | |||
| b091cfa474 | |||
| 057bb3a414 | |||
| c1442c9acc | |||
| 977f1beafd | |||
| 39ee710e97 | |||
| 1d1841c8db | |||
| 3e44774ed4 | |||
| 9e2fa01896 | |||
| 7811f9840c | |||
| 34425c6200 | |||
| 46f9fc549a | |||
| 237abf359b | |||
| c5e8739009 | |||
| 0ea8040977 | |||
| 1f10e8efe3 | |||
| 8c2688670e | |||
| bc5d49b568 | |||
| 27ea84e720 | |||
| 0becb66bfd | |||
| 11f88492e9 | |||
| ae3e01466f | |||
| 5d04dd8a83 | |||
| 52c15a8151 | |||
| f6104e7051 | |||
| ed710f6fe5 | |||
| 7e6e239d7f | |||
| b4381a22f3 | |||
| ba6ca4d220 | |||
| 5486906b05 | |||
| 7348b295cb | |||
| 025762fdf6 | |||
| 96411cdb90 | |||
| 835f453384 | |||
| cc475ae1e9 | |||
| a5249ec646 | |||
| d0e3090421 | |||
| 14f68c8937 | |||
| 9c0c6076b3 | |||
| 36bf729161 | |||
| 3a430ad98c | |||
| 90a8610dd7 | |||
| d62ddab140 | |||
| ca0186bb67 | |||
| 88ac8389ea | |||
| 2f3864fff2 | |||
| 7878e52cc4 | |||
| 08b1967a4e | |||
| b870ca8297 | |||
| 1507c54671 | |||
| 0984ca4daf | |||
| 438798f8de | |||
| 27aadb8948 | |||
| 244a4118cf | |||
| cde320ce46 | |||
| 4772df7618 | |||
| ecb95b35f3 | |||
| 24b09af563 | |||
| 9471fb4169 | |||
| 2f296d6f08 | |||
| cd3d3caa15 | |||
| 440eefe46e | |||
| 5787e2badb | |||
| c235e5ab16 | |||
| f584c70f27 | |||
| 3aa0279519 | |||
| 6830aec2f9 | |||
| 9a3d7af325 | |||
| 52f7e8557f | |||
| 12c17d1c7c | |||
| 67918187a1 | |||
| dcbc0c2b45 | |||
| 26b6a741c2 | |||
| d7064129e8 | |||
| 279dd36a13 | |||
| 49e47b28de | |||
| a782343e0a | |||
| 1e7821a46d | |||
| f7bea5a218 | |||
| 05fa4f3192 | |||
| ed357f0ebc | |||
| 1774832a58 | |||
| 2837befd52 | |||
| 38d866c0c2 | |||
| 87c7acf4f1 | |||
| 1bd68118ce | |||
| 5c34b28c80 | |||
| c550183720 | |||
| 3605579b1b | |||
| fa2e28688f | |||
| 20f1031e28 | |||
| 292cb6d0ed | |||
| 45f2dc1cfc | |||
| e319dd3e3d | |||
| 9678f7a6e5 | |||
| 0cca4e21fb | |||
| f6758f191d | |||
| 983662f877 | |||
| c7fc28a5c5 | |||
| 1f7c33e805 | |||
| 437cc331a8 | |||
| aef51375b8 | |||
| bdd65968e5 | |||
| 061a789c18 | |||
| 68507d0501 | |||
| 1e28c5adfa | |||
| 3b09550ebd | |||
| 16aef9cbec | |||
| b24da90ba7 | |||
| 2aa7dbd3ad | |||
| 9f8f2c4b8b | |||
| 5331ecccc1 | |||
| f2ec86940c | |||
| cd74e707ba | |||
| ff101dbfac | |||
| 5024c19f87 | |||
| c9850b5815 | |||
| 6fe4e7d7e1 | |||
| a5b8b91e82 | |||
| cc7095adc3 | |||
| e2703f55aa | |||
| 8c5849c988 | |||
| 5322ab2420 | |||
| b84e6bcfb1 | |||
| a4ab0174c7 | |||
| 7e93b5a757 | |||
| 044820fa71 | |||
| 322178e8a4 | |||
| 6358fbad30 | |||
| d342c0a9af | |||
| 63084eebb4 | |||
| 3b4ea00db8 | |||
| c8f7bffe7e | |||
| 3ff56f89d9 | |||
| 7fb9f27837 | |||
| c9685d4ce7 | |||
| 4dc987e27d | |||
| f7af06ae9a | |||
| a5c337faa3 | |||
| fc4704f0a6 | |||
| d968334ada | |||
| fea6d23178 | |||
| 3fefc95572 | |||
| b07e7d1213 | |||
| d47b8bfb03 | |||
| 5bf7fbdae1 | |||
| fca766b382 | |||
| feadfb1b67 | |||
| e69000f221 | |||
| 6b4a54465a | |||
| 878659999f | |||
| 1868c47e30 | |||
| 51442efc23 | |||
| 556e962ec5 | |||
| b5ce67f863 | |||
| c3ce89dc3a | |||
| e87930c758 | |||
| 815a693e58 | |||
| 8287fcae96 | |||
| cd7fe6c404 | |||
| d47d5dd819 | |||
| 1919d79e43 | |||
| ab08cd9e34 | |||
| 2522bdd0a2 | |||
| f9b6aefffe | |||
| 2ba6658a0b | |||
| dbc46f27a3 | |||
| f21e1caed1 | |||
| 4ffe2a6330 | |||
| 1bc0438872 | |||
| 57fb9fdaea | |||
| 58ce641f18 | |||
| 5f4e7802e4 | |||
| 42d3109ae1 | |||
| f7ca7b97fa | |||
| f5d8a54134 | |||
| eaec14dc62 | |||
| c93b3264cd | |||
| bf41aa9c6c | |||
| 8af6bfd5ae | |||
| ab08c10874 | |||
| 9af9a0284e | |||
| 716a05032d | |||
| 43e43e7d76 | |||
| c91af3668d | |||
| 52f41f0b3b | |||
| 3b709d606b | |||
| 2e5fafac62 | |||
| ea2aee2d25 | |||
| e36fbb5f64 | |||
| 5221ef707b | |||
| f0df806f01 | |||
| 9d01d6a833 | |||
| 1914ebb9ae | |||
| 686dba90c9 | |||
| 95dc3bf571 | |||
| 1c8fdbf848 | |||
| d8357c9959 | |||
| 90e994377b | |||
| 82c6ee6d5d | |||
| 7b615e73c3 | |||
| 268e4a87fe | |||
| 73f8eb84c9 | |||
| a31ef89996 | |||
| d6bca7ebab | |||
| e0a42fd928 | |||
| deb4e0a0de | |||
| a687062866 | |||
| 700439c8a8 | |||
| fb09283e53 | |||
| 88ac365e03 | |||
| aec78cf875 | |||
| 77c587681b | |||
| 7ac8b906d9 | |||
| e9a5f86a6a | |||
| 7238a3b50e | |||
| ebe13fa82e | |||
| 6ee290a625 | |||
| 69f2b7070f | |||
| 11bea1c7c4 | |||
| 68a1dc65c1 | |||
| 4b1ce539d5 | |||
| a6fbb8191d | |||
| 552ff281b8 | |||
| 54fad2f6d8 | |||
| 78edcd7d0e | |||
| a8034b21d5 | |||
| f0e95905d2 | |||
| 69a5276614 | |||
| 2e62eea351 | |||
| 13d972b8f3 | |||
| 03cb198e95 | |||
| 67ee55b502 | |||
| b5998d7f3a | |||
| f71cf99b77 | |||
| a2092a6a39 | |||
| 43c09666a0 | |||
| 0da23f95fd | |||
| e8f44e2142 | |||
| fbb237e982 | |||
| 7f7c1d7b87 | |||
| be0b7f20f9 | |||
| 0672988fff | |||
| 75dad60cb5 | |||
| 0140e3fce0 | |||
| 42d0e14b98 | |||
| 9a42d684fb | |||
| ab6ad65445 | |||
| b613764ccc | |||
| ac44d0489f | |||
| c57e6fbbb8 | |||
| 6c6da215c8 | |||
| be700c7629 | |||
| b697f73492 | |||
| 3106f94989 | |||
| 50f41f73d5 | |||
| 83e3f59e68 | |||
| 056c61e985 | |||
| d1a1b68302 | |||
| 6bd7b9a50f | |||
| e6967aab88 | |||
| 47e96803e3 | |||
| f9ef4d0a64 | |||
| c4b4e6013f | |||
| 24bbc46c32 | |||
| 85bc9ef124 | |||
| 33755a8573 | |||
| ab45321889 | |||
| 940b310f64 | |||
| 8026241b6c | |||
| 89b35392bd | |||
| 62eb239ec4 | |||
| 7fdf8c1f0c | |||
| 538697238a | |||
| 7bc4a998fe | |||
| e33df485ab | |||
| 36ae54fe17 | |||
| 50958fd6df | |||
| e9e595f0d0 | |||
| 43ddfba777 | |||
| 78a32961d7 | |||
| 9b1f3eda05 | |||
| 1154621e5c | |||
| e7aeee77a7 | |||
| fab3988a36 | |||
| 354cad88d3 | |||
| 876f930f30 | |||
| 5b3ea46f0f | |||
| 37a2563c11 | |||
| cb977a146b | |||
| 72b2551b6d | |||
| c94633e616 | |||
| 7fee2d670f | |||
| 837b06eb38 | |||
| 2b8860b21c | |||
| fc0b69796f |
@ -2,9 +2,9 @@
|
|||||||
/app
|
/app
|
||||||
|
|
||||||
# Build Artifacts
|
# Build Artifacts
|
||||||
dist
|
/schema/dist
|
||||||
.next
|
/server/dist
|
||||||
.turbo
|
/client/.next
|
||||||
|
|
||||||
# IDEs
|
# IDEs
|
||||||
.vscode
|
.vscode
|
||||||
@ -19,7 +19,7 @@ CHANGELOG.md
|
|||||||
CODE_OF_CONDUCT.md
|
CODE_OF_CONDUCT.md
|
||||||
|
|
||||||
# Project Dependencies
|
# Project Dependencies
|
||||||
node_modules
|
**/node_modules
|
||||||
|
|
||||||
# Docker
|
# Docker
|
||||||
Dockerfile
|
Dockerfile
|
||||||
|
|||||||
11
.env.example
11
.env.example
@ -1,7 +1,3 @@
|
|||||||
# Turbo Cache (Optional)
|
|
||||||
TURBO_TEAM=
|
|
||||||
TURBO_TOKEN=
|
|
||||||
|
|
||||||
# Server + Client
|
# Server + Client
|
||||||
TZ=UTC
|
TZ=UTC
|
||||||
PUBLIC_URL=http://localhost:3000
|
PUBLIC_URL=http://localhost:3000
|
||||||
@ -15,7 +11,7 @@ POSTGRES_PASSWORD=postgres
|
|||||||
|
|
||||||
# Server
|
# Server
|
||||||
SECRET_KEY=
|
SECRET_KEY=
|
||||||
POSTGRES_HOST=postgres
|
POSTGRES_HOST=localhost
|
||||||
POSTGRES_PORT=5432
|
POSTGRES_PORT=5432
|
||||||
POSTGRES_SSL_CERT=
|
POSTGRES_SSL_CERT=
|
||||||
JWT_SECRET=
|
JWT_SECRET=
|
||||||
@ -34,6 +30,7 @@ STORAGE_ENDPOINT=
|
|||||||
STORAGE_URL_PREFIX=
|
STORAGE_URL_PREFIX=
|
||||||
STORAGE_ACCESS_KEY=
|
STORAGE_ACCESS_KEY=
|
||||||
STORAGE_SECRET_KEY=
|
STORAGE_SECRET_KEY=
|
||||||
|
PDF_DELETION_TIME=345600000
|
||||||
|
|
||||||
# Flags (Client)
|
# Client
|
||||||
PUBLIC_FLAG_DISABLE_SIGNUPS=false
|
PUBLIC_FLAG_DISABLE_SIGNUPS=false
|
||||||
|
|||||||
@ -1,12 +1,23 @@
|
|||||||
{
|
{
|
||||||
"ignorePatterns": ["/app"],
|
|
||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
"extends": ["plugin:@typescript-eslint/recommended"],
|
"extends": ["plugin:@typescript-eslint/recommended"],
|
||||||
"plugins": ["@typescript-eslint/eslint-plugin", "simple-import-sort"],
|
"plugins": ["@typescript-eslint/eslint-plugin", "unused-imports", "simple-import-sort"],
|
||||||
"rules": {
|
"rules": {
|
||||||
// ESLint
|
// ESLint
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
|
|
||||||
|
// Unused Imports
|
||||||
|
"unused-imports/no-unused-imports": "error",
|
||||||
|
"unused-imports/no-unused-vars": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"vars": "all",
|
||||||
|
"args": "none",
|
||||||
|
"varsIgnorePattern": "^_",
|
||||||
|
"argsIgnorePattern": "^_"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
// Simple Import Sort
|
// Simple Import Sort
|
||||||
"simple-import-sort/imports": "error",
|
"simple-import-sort/imports": "error",
|
||||||
"simple-import-sort/exports": "error",
|
"simple-import-sort/exports": "error",
|
||||||
@ -14,6 +25,7 @@
|
|||||||
// TypeScript ESLint
|
// TypeScript ESLint
|
||||||
"@typescript-eslint/no-unused-vars": "off",
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "off",
|
||||||
"@typescript-eslint/interface-name-prefix": "off",
|
"@typescript-eslint/interface-name-prefix": "off",
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "off"
|
"@typescript-eslint/explicit-module-boundary-types": "off"
|
||||||
|
|||||||
13
.github/workflows/digitalocean-deploy.yml
vendored
13
.github/workflows/digitalocean-deploy.yml
vendored
@ -8,14 +8,21 @@ on:
|
|||||||
- completed
|
- completed
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
on-success:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||||
steps:
|
steps:
|
||||||
- name: Install DigitalOcean CLI
|
- name: Install DigitalOcean CLI
|
||||||
uses: digitalocean/action-doctl@v2.1.1
|
uses: digitalocean/action-doctl@v2.3.0
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.DIGITALOCEAN_TOKEN }}
|
token: ${{ secrets.DIGITALOCEAN_TOKEN }}
|
||||||
|
|
||||||
- name: Create Deployment with Latest Version
|
- name: Create Deployment with Latest Version
|
||||||
run: doctl apps create-deployment ${{ secrets.DIGITALOCEAN_APP_ID }} --wait --force-rebuild
|
run: doctl apps create-deployment ${{ secrets.DIGITALOCEAN_APP_ID }} --wait --force-rebuild
|
||||||
|
|
||||||
|
on-failure:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
|
||||||
|
steps:
|
||||||
|
- name: Abruptly end the worklfow
|
||||||
|
run: exit 1
|
||||||
|
|||||||
103
.github/workflows/docker-build-push.yml
vendored
103
.github/workflows/docker-build-push.yml
vendored
@ -1,112 +1,61 @@
|
|||||||
name: Build and Push Docker Image
|
name: Build and Push Docker Image
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
client:
|
build_matrix:
|
||||||
name: Client
|
name: Build and Push Docker Image
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
env:
|
strategy:
|
||||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
matrix:
|
||||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
image: [client, server]
|
||||||
|
arch: [linux/amd64, linux/arm64]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v3.1.0
|
uses: actions/checkout@v3.5.2
|
||||||
with:
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
- id: version
|
- id: version
|
||||||
name: Get Version
|
name: App Version
|
||||||
run: echo "version=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
uses: martinbeentjes/npm-get-version-action@v1.3.1
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2.0.0
|
uses: docker/setup-qemu-action@v2.1.0
|
||||||
|
with:
|
||||||
|
platforms: ${{ matrix.arch }}
|
||||||
|
|
||||||
- id: buildx
|
- id: buildx
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2.0.0
|
uses: docker/setup-buildx-action@v2.5.0
|
||||||
with:
|
|
||||||
install: true
|
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v2.0.0
|
uses: docker/login-action@v2.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v2.0.0
|
uses: docker/login-action@v2.1.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: $GITHUB_REPOSITORY_OWNER
|
username: $GITHUB_REPOSITORY_OWNER
|
||||||
password: ${{ secrets.GH_TOKEN }}
|
password: ${{ secrets.GH_TOKEN }}
|
||||||
|
|
||||||
- name: Build and Push Client Image
|
- name: Build and Push Docker Image
|
||||||
uses: docker/build-push-action@v3.1.1
|
uses: docker/build-push-action@v4.0.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
file: client/Dockerfile
|
platforms: ${{ matrix.arch }}
|
||||||
platforms: linux/amd64,linux/arm64
|
file: ${{ matrix.image }}/Dockerfile
|
||||||
|
build-args: |
|
||||||
|
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
|
||||||
tags: |
|
tags: |
|
||||||
amruthpillai/reactive-resume:client-latest
|
amruthpillai/reactive-resume:${{ matrix.image }}-latest
|
||||||
amruthpillai/reactive-resume:client-${{ env.version }}
|
amruthpillai/reactive-resume:${{ matrix.image }}-${{ steps.version.outputs.current-version }}
|
||||||
ghcr.io/amruthpillai/reactive-resume:client-latest
|
ghcr.io/amruthpillai/reactive-resume:${{ matrix.image }}-latest
|
||||||
ghcr.io/amruthpillai/reactive-resume:client-${{ env.version }}
|
ghcr.io/amruthpillai/reactive-resume:${{ matrix.image }}-${{ steps.version.outputs.current-version }}
|
||||||
|
|
||||||
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.1.0
|
|
||||||
with:
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
- id: version
|
|
||||||
name: Get Version
|
|
||||||
run: echo "version=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- 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
|
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v2.0.0
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
|
||||||
uses: docker/login-action@v2.0.0
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: $GITHUB_REPOSITORY_OWNER
|
|
||||||
password: ${{ secrets.GH_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build and Push Server Image
|
|
||||||
uses: docker/build-push-action@v3.1.1
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
file: server/Dockerfile
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
tags: |
|
|
||||||
amruthpillai/reactive-resume:server-latest
|
|
||||||
amruthpillai/reactive-resume:server-${{ env.version }}
|
|
||||||
ghcr.io/amruthpillai/reactive-resume:server-latest
|
|
||||||
ghcr.io/amruthpillai/reactive-resume:server-${{ env.version }}
|
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,6 +1,8 @@
|
|||||||
# Environment Variables
|
# Environment Variables
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
|
*.env
|
||||||
|
!.env.gitpod
|
||||||
!.env.example
|
!.env.example
|
||||||
|
|
||||||
# Project Dependencies
|
# Project Dependencies
|
||||||
@ -9,5 +11,8 @@ node_modules
|
|||||||
# macOS
|
# macOS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
# Turbo
|
# Intellij
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Turborepo
|
||||||
.turbo
|
.turbo
|
||||||
41
.gitpod.yml
Normal file
41
.gitpod.yml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
tasks:
|
||||||
|
- name: Run PostgreSQL Database
|
||||||
|
command: docker run --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=postgres -d postgres
|
||||||
|
|
||||||
|
- name: Install Project Dependencies
|
||||||
|
command: |
|
||||||
|
pnpm install
|
||||||
|
pnpm dlx playwright install --with-deps chromium
|
||||||
|
gp sync-done deps
|
||||||
|
|
||||||
|
- name: Generate Environment Variables
|
||||||
|
init: gp sync-await deps
|
||||||
|
command: |
|
||||||
|
if [ -f .env ]; then
|
||||||
|
echo "Found .env in workspace, skipping generation"
|
||||||
|
else
|
||||||
|
pnpm generate-env
|
||||||
|
fi
|
||||||
|
gp sync-done env
|
||||||
|
|
||||||
|
- name: Build and Run Project
|
||||||
|
init: gp sync-await env
|
||||||
|
command: |
|
||||||
|
pnpm build
|
||||||
|
pnpm start
|
||||||
|
|
||||||
|
ports:
|
||||||
|
# PostgreSQL
|
||||||
|
- port: 5432
|
||||||
|
onOpen: ignore
|
||||||
|
visibility: private
|
||||||
|
|
||||||
|
# Client
|
||||||
|
- port: 3100
|
||||||
|
onOpen: ignore
|
||||||
|
visibility: public
|
||||||
|
|
||||||
|
# Client
|
||||||
|
- port: 3000
|
||||||
|
onOpen: open-browser
|
||||||
|
visibility: public
|
||||||
17
README.md
17
README.md
@ -6,7 +6,8 @@
|
|||||||
[](https://github.com/AmruthPillai/Reactive-Resume/blob/main/LICENSE)
|
[](https://github.com/AmruthPillai/Reactive-Resume/blob/main/LICENSE)
|
||||||
[](https://translate.rxresu.me)
|
[](https://translate.rxresu.me)
|
||||||
[](https://hub.docker.com/r/amruthpillai/reactive-resume)
|
[](https://hub.docker.com/r/amruthpillai/reactive-resume)
|
||||||

|

|
||||||
|
[](https://gitpod.io/#https://github.com/AmruthPillai/Reactive-Resume)
|
||||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2FAmruthPillai%2FReactive-Resume?ref=badge_shield)
|
[](https://app.fossa.com/projects/git%2Bgithub.com%2FAmruthPillai%2FReactive-Resume?ref=badge_shield)
|
||||||
|
|
||||||
## [Go to App](https://rxresu.me) | [Docs](https://docs.rxresu.me)
|
## [Go to App](https://rxresu.me) | [Docs](https://docs.rxresu.me)
|
||||||
@ -18,7 +19,7 @@ You have complete control over what goes into your resume, how it looks, what co
|
|||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [Reactive Resume](#reactive-resume)
|
- [Reactive Resume](#reactive-resume)
|
||||||
- [Go to App | [Docs](https://docs.rxresu.me)](#go-to-app--docs)
|
- [Go to App | Docs](#go-to-app--docs)
|
||||||
- [Table of Contents](#table-of-contents)
|
- [Table of Contents](#table-of-contents)
|
||||||
- [Features](#features)
|
- [Features](#features)
|
||||||
- [Languages](#languages)
|
- [Languages](#languages)
|
||||||
@ -93,7 +94,7 @@ You have complete control over what goes into your resume, how it looks, what co
|
|||||||
- Swedish (Svenska)
|
- Swedish (Svenska)
|
||||||
- Tamil (தமிழ்)
|
- Tamil (தமிழ்)
|
||||||
- Turkish (Türkçe)
|
- Turkish (Türkçe)
|
||||||
- Ukranian (Українська мова)
|
- Ukrainian (Українська мова)
|
||||||
- Vietnamese (Tiếng Việt)
|
- Vietnamese (Tiếng Việt)
|
||||||
|
|
||||||
Help by [translating Reactive Resume](https://translate.rxresu.me) to your language!
|
Help by [translating Reactive Resume](https://translate.rxresu.me) to your language!
|
||||||
@ -104,7 +105,11 @@ The docs include an extensive [Tutorial](https://docs.rxresu.me/tutorial) sectio
|
|||||||
|
|
||||||
## Build from Source
|
## Build from Source
|
||||||
|
|
||||||
For extensive information on how to build the app on your local machine, head over to the docs's [Source Code](https://docs.rxresu.me/source-code) section.
|
[](https://gitpod.io/#https://github.com/AmruthPillai/Reactive-Resume)
|
||||||
|
|
||||||
|
Initially building the image and project on Gitpod will take at least ~10 minutes, so please be patient on first launch.
|
||||||
|
|
||||||
|
For extensive information on how to build the app on your local machine, head over to the docs [Source Code](https://docs.rxresu.me/source-code) section.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
@ -129,6 +134,10 @@ Reactive Resume would be nothing without the folks who supported me and kept the
|
|||||||
### [GitHub Sponsor](https://github.com/sponsors/AmruthPillai)
|
### [GitHub Sponsor](https://github.com/sponsors/AmruthPillai)
|
||||||
### [PayPal](https://paypal.me/RajaRajanA)
|
### [PayPal](https://paypal.me/RajaRajanA)
|
||||||
|
|
||||||
|
## GitHub Star History
|
||||||
|
|
||||||
|
[](https://star-history.com/#AmruthPillai/Reactive-Resume&Date)
|
||||||
|
|
||||||
## Infrastructure
|
## Infrastructure
|
||||||
|
|
||||||
- [Next.js](https://nextjs.org/), frontend
|
- [Next.js](https://nextjs.org/), frontend
|
||||||
|
|||||||
@ -12,8 +12,8 @@ android {
|
|||||||
targetSdk 32
|
targetSdk 32
|
||||||
versionCode 3
|
versionCode 3
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
|
resConfigs 'en'
|
||||||
|
|
||||||
resConfigs "en"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@ -38,6 +38,7 @@ android {
|
|||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding true
|
viewBinding true
|
||||||
}
|
}
|
||||||
|
namespace 'me.rxresu.app'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest
|
<manifest
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
package="me.rxresu.app">
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import android.webkit.WebView
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private lateinit var webView: WebView
|
private lateinit var webView: WebView
|
||||||
|
|
||||||
private var url = "https://rxresu.me"
|
private var url = "https://rxresu.me"
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application' version '7.1.2' apply false
|
id 'com.android.application' version '7.4.2' apply false
|
||||||
id 'com.android.library' version '7.1.2' apply false
|
id 'com.android.library' version '7.4.2' apply false
|
||||||
id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
|
id 'org.jetbrains.kotlin.android' version '1.8.21' apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
task clean(type: Delete) {
|
task clean(type: Delete) {
|
||||||
|
|||||||
2
app/gradle/wrapper/gradle-wrapper.properties
vendored
2
app/gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
#Wed Mar 09 21:34:49 CET 2022
|
#Wed Mar 09 21:34:49 CET 2022
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
2
client/.gitignore
vendored
2
client/.gitignore
vendored
@ -39,4 +39,4 @@ yarn-error.log*
|
|||||||
__ENV.js
|
__ENV.js
|
||||||
|
|
||||||
# next-sitemap
|
# next-sitemap
|
||||||
sitemap*.xml
|
sitemap*.xml
|
||||||
|
|||||||
@ -2,12 +2,12 @@ FROM node:lts-alpine AS base
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN apk add --no-cache g++ git curl make python3 libc6-compat \
|
RUN apk add --no-cache g++ git make curl python3 libc6-compat \
|
||||||
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
&& corepack enable && corepack prepare pnpm@latest --activate
|
||||||
|
|
||||||
FROM base as dependencies
|
FROM base as dependencies
|
||||||
|
|
||||||
COPY package.json pnpm-*.yaml turbo.json ./
|
COPY package.json pnpm-*.yaml ./
|
||||||
COPY ./schema/package.json ./schema/package.json
|
COPY ./schema/package.json ./schema/package.json
|
||||||
COPY ./client/package.json ./client/package.json
|
COPY ./client/package.json ./client/package.json
|
||||||
|
|
||||||
@ -21,17 +21,14 @@ COPY --from=dependencies /app/node_modules ./node_modules
|
|||||||
COPY --from=dependencies /app/schema/node_modules ./schema/node_modules
|
COPY --from=dependencies /app/schema/node_modules ./schema/node_modules
|
||||||
COPY --from=dependencies /app/client/node_modules ./client/node_modules
|
COPY --from=dependencies /app/client/node_modules ./client/node_modules
|
||||||
|
|
||||||
ARG TURBO_TEAM
|
|
||||||
ARG TURBO_TOKEN
|
ARG TURBO_TOKEN
|
||||||
|
ENV TURBO_TOKEN=$TURBO_TOKEN
|
||||||
|
|
||||||
ENV TURBO_TEAM $TURBO_TEAM
|
RUN pnpm exec turbo --filter client build
|
||||||
ENV TURBO_TOKEN $TURBO_TOKEN
|
|
||||||
|
|
||||||
RUN pnpm run build --filter client
|
|
||||||
|
|
||||||
FROM base as production
|
FROM base as production
|
||||||
|
|
||||||
COPY --from=builder /app/package.json /app/pnpm-*.yaml /app/turbo.json ./
|
COPY --from=builder /app/package.json /app/pnpm-*.yaml ./
|
||||||
COPY --from=builder /app/client/package.json ./client/package.json
|
COPY --from=builder /app/client/package.json ./client/package.json
|
||||||
|
|
||||||
RUN pnpm install --filter client --prod --frozen-lockfile --workspace-root
|
RUN pnpm install --filter client --prod --frozen-lockfile --workspace-root
|
||||||
@ -45,7 +42,4 @@ EXPOSE 3000
|
|||||||
|
|
||||||
ENV PORT 3000
|
ENV PORT 3000
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=20s --retries=3 --start-period=15s \
|
CMD [ "pnpm", "run", "--filter", "client", "start" ]
|
||||||
CMD curl -fSs 127.0.0.1:3000 || exit 1
|
|
||||||
|
|
||||||
CMD [ "pnpm", "run", "start", "--filter", "client" ]
|
|
||||||
43
client/Dockerfile.standalone
Normal file
43
client/Dockerfile.standalone
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
FROM node:18-alpine AS base
|
||||||
|
|
||||||
|
FROM base AS deps
|
||||||
|
RUN apk add --no-cache libc6-compat
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
|
||||||
|
RUN \
|
||||||
|
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
|
||||||
|
elif [ -f package-lock.json ]; then npm ci; \
|
||||||
|
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
|
||||||
|
else echo "Lockfile not found." && exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
FROM base AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ENV NEXT_TELEMETRY_DISABLED 1
|
||||||
|
|
||||||
|
RUN yarn global add pnpm && pnpm build
|
||||||
|
|
||||||
|
FROM base AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV NODE_ENV production
|
||||||
|
ENV NEXT_TELEMETRY_DISABLED 1
|
||||||
|
|
||||||
|
RUN addgroup --system --gid 1001 nodejs
|
||||||
|
RUN adduser --system --uid 1001 nextjs
|
||||||
|
|
||||||
|
COPY --from=builder /app/public ./public
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||||
|
|
||||||
|
USER nextjs
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
ENV PORT 3000
|
||||||
|
|
||||||
|
CMD ["node", "client/server.js"]
|
||||||
@ -13,11 +13,12 @@ import {
|
|||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import { ButtonBase, Divider, Tooltip, useMediaQuery, useTheme } from '@mui/material';
|
import { ButtonBase, Divider, Tooltip, useMediaQuery, useTheme } from '@mui/material';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { get } from 'lodash';
|
import dayjs from 'dayjs';
|
||||||
|
import get from 'lodash/get';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { useMutation } from 'react-query';
|
import { useMutation } from 'react-query';
|
||||||
import { ReactZoomPanPinchRef } from 'react-zoom-pan-pinch';
|
import { ReactZoomPanPinchHandlers } from 'react-zoom-pan-pinch';
|
||||||
import { ActionCreators } from 'redux-undo';
|
import { ActionCreators } from 'redux-undo';
|
||||||
|
|
||||||
import { ServerError } from '@/services/axios';
|
import { ServerError } from '@/services/axios';
|
||||||
@ -28,7 +29,7 @@ import getResumeUrl from '@/utils/getResumeUrl';
|
|||||||
|
|
||||||
import styles from './ArtboardController.module.scss';
|
import styles from './ArtboardController.module.scss';
|
||||||
|
|
||||||
const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, centerView }) => {
|
const ArtboardController: React.FC<ReactZoomPanPinchHandlers> = ({ zoomIn, zoomOut, centerView }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@ -67,8 +68,9 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
|
|||||||
|
|
||||||
const slug = get(resume, 'slug');
|
const slug = get(resume, 'slug');
|
||||||
const username = get(resume, 'user.username');
|
const username = get(resume, 'user.username');
|
||||||
|
const updatedAt = get(resume, 'updatedAt');
|
||||||
|
|
||||||
const url = await mutateAsync({ username, slug });
|
const url = await mutateAsync({ username, slug, lastUpdated: dayjs(updatedAt).unix().toString() });
|
||||||
|
|
||||||
download(url);
|
download(url);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
@apply h-full w-full #{!important};
|
@apply h-full w-full overflow-visible #{!important};
|
||||||
}
|
}
|
||||||
|
|
||||||
.artboard {
|
.artboard {
|
||||||
|
|||||||
@ -20,7 +20,6 @@ import {
|
|||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Resume } from '@reactive-resume/schema';
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
@ -28,6 +27,7 @@ import { useTranslation } from 'next-i18next';
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { useMutation } from 'react-query';
|
import { useMutation } from 'react-query';
|
||||||
|
import { Resume } from 'schema';
|
||||||
|
|
||||||
import { RESUMES_QUERY } from '@/constants/index';
|
import { RESUMES_QUERY } from '@/constants/index';
|
||||||
import { ServerError } from '@/services/axios';
|
import { ServerError } from '@/services/axios';
|
||||||
@ -53,13 +53,12 @@ const Header = () => {
|
|||||||
|
|
||||||
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
|
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
|
||||||
|
|
||||||
const { mutateAsync: duplicateMutation } = useMutation<Resume, ServerError, DuplicateResumeParams>(duplicateResume);
|
|
||||||
|
|
||||||
const { mutateAsync: deleteMutation } = useMutation<void, ServerError, DeleteResumeParams>(deleteResume);
|
|
||||||
|
|
||||||
const resume = useAppSelector((state) => state.resume.present);
|
const resume = useAppSelector((state) => state.resume.present);
|
||||||
const { left, right } = useAppSelector((state) => state.build.sidebar);
|
const { left, right } = useAppSelector((state) => state.build.sidebar);
|
||||||
|
|
||||||
|
const { mutateAsync: deleteMutation } = useMutation<void, ServerError, DeleteResumeParams>(deleteResume);
|
||||||
|
const { mutateAsync: duplicateMutation } = useMutation<Resume, ServerError, DuplicateResumeParams>(duplicateResume);
|
||||||
|
|
||||||
const name = useMemo(() => get(resume, 'name'), [resume]);
|
const name = useMemo(() => get(resume, 'name'), [resume]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -36,13 +36,6 @@
|
|||||||
top: calc(279mm - 19px);
|
top: calc(279mm - 19px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown {
|
|
||||||
ul {
|
|
||||||
padding-left: 1.5em;
|
|
||||||
text-indent: -1.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.pageNumber {
|
.pageNumber {
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { CustomCSS, PageConfig, ThemeConfig, Typography } from '@reactive-resume/schema';
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { CustomCSS, PageConfig, ThemeConfig, Typography } from 'schema';
|
||||||
|
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
import templateMap from '@/templates/templateMap';
|
import templateMap from '@/templates/templateMap';
|
||||||
@ -26,8 +26,8 @@ const Page: React.FC<Props> = ({ page, showPageNumbers = false }) => {
|
|||||||
const theme: ThemeConfig = get(resume, 'metadata.theme');
|
const theme: ThemeConfig = get(resume, 'metadata.theme');
|
||||||
const customCSS: CustomCSS = get(resume, 'metadata.css');
|
const customCSS: CustomCSS = get(resume, 'metadata.css');
|
||||||
const template: string = get(resume, 'metadata.template');
|
const template: string = get(resume, 'metadata.template');
|
||||||
const pageConfig: PageConfig = get(resume, 'metadata.page');
|
|
||||||
const typography: Typography = get(resume, 'metadata.typography');
|
const typography: Typography = get(resume, 'metadata.typography');
|
||||||
|
const pageConfig: PageConfig = get(resume, 'metadata.page', {} as PageConfig);
|
||||||
|
|
||||||
const themeCSS = useMemo(() => !isEmpty(theme) && generateThemeStyles(theme), [theme]);
|
const themeCSS = useMemo(() => !isEmpty(theme) && generateThemeStyles(theme), [theme]);
|
||||||
const typographyCSS = useMemo(() => !isEmpty(typography) && generateTypographyStyles(typography), [typography]);
|
const typographyCSS = useMemo(() => !isEmpty(typography) && generateTypographyStyles(typography), [typography]);
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
import { Add, Star } from '@mui/icons-material';
|
import { Add, Star } from '@mui/icons-material';
|
||||||
import { Button, Divider, IconButton, SwipeableDrawer, Tooltip, useMediaQuery, useTheme } from '@mui/material';
|
import { Button, Divider, IconButton, SwipeableDrawer, Tooltip, useMediaQuery, useTheme } from '@mui/material';
|
||||||
import { Section as SectionRecord } from '@reactive-resume/schema';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useMemo } from 'react';
|
import React, { ReactComponentElement, useMemo } from 'react';
|
||||||
|
import { Section as SectionRecord } from 'schema';
|
||||||
import { validate } from 'uuid';
|
import { validate } from 'uuid';
|
||||||
|
|
||||||
import Logo from '@/components/shared/Logo';
|
import Logo from '@/components/shared/Logo';
|
||||||
import { getCustomSections, left } from '@/config/sections';
|
import { getCustomSections, getSectionsByType, left } from '@/config/sections';
|
||||||
import { setSidebarState } from '@/store/build/buildSlice';
|
import { setSidebarState } from '@/store/build/buildSlice';
|
||||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||||
import { addSection } from '@/store/resume/resumeSlice';
|
import { addSection } from '@/store/resume/resumeSlice';
|
||||||
@ -26,6 +27,7 @@ const LeftSidebar = () => {
|
|||||||
const isDesktop = useMediaQuery(theme.breakpoints.up('lg'));
|
const isDesktop = useMediaQuery(theme.breakpoints.up('lg'));
|
||||||
|
|
||||||
const sections = useAppSelector((state) => state.resume.present.sections);
|
const sections = useAppSelector((state) => state.resume.present.sections);
|
||||||
|
|
||||||
const { open } = useAppSelector((state) => state.build.sidebar.left);
|
const { open } = useAppSelector((state) => state.build.sidebar.left);
|
||||||
|
|
||||||
const customSections = useMemo(() => getCustomSections(sections), [sections]);
|
const customSections = useMemo(() => getCustomSections(sections), [sections]);
|
||||||
@ -52,7 +54,49 @@ const LeftSidebar = () => {
|
|||||||
items: [],
|
items: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
dispatch(addSection({ value: newSection }));
|
dispatch(addSection({ value: newSection, type: 'custom' }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const sectionsList = () => {
|
||||||
|
const sectionsComponents: Array<ReactComponentElement<any>> = [];
|
||||||
|
|
||||||
|
for (const item of left) {
|
||||||
|
const id = (item as any).id;
|
||||||
|
const component = (item as any).component;
|
||||||
|
const type = component.props.type;
|
||||||
|
const addMore = !!component.props.addMore;
|
||||||
|
|
||||||
|
sectionsComponents.push(
|
||||||
|
<section key={id} id={id}>
|
||||||
|
{component}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (addMore) {
|
||||||
|
const additionalSections = getSectionsByType(sections, type);
|
||||||
|
const elements = [];
|
||||||
|
for (const element of additionalSections) {
|
||||||
|
const newId = element.id;
|
||||||
|
|
||||||
|
const props = cloneDeep(component.props);
|
||||||
|
props.path = 'sections.' + newId;
|
||||||
|
props.name = element.name;
|
||||||
|
props.isDeletable = true;
|
||||||
|
props.addMore = false;
|
||||||
|
props.isDuplicated = true;
|
||||||
|
const newComponent = React.cloneElement(component, props);
|
||||||
|
|
||||||
|
elements.push(
|
||||||
|
<section key={newId} id={`section-${newId}`}>
|
||||||
|
{newComponent}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
sectionsComponents.push(...elements);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sectionsComponents;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -65,12 +109,10 @@ const LeftSidebar = () => {
|
|||||||
variant={isDesktop ? 'persistent' : 'temporary'}
|
variant={isDesktop ? 'persistent' : 'temporary'}
|
||||||
>
|
>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<nav className="overflow-y-scroll">
|
<nav className="overflow-y-auto">
|
||||||
<div>
|
<div>
|
||||||
<Link href="/dashboard">
|
<Link href="/dashboard">
|
||||||
<a className="inline-flex">
|
<Logo size={40} />
|
||||||
<Logo size={40} />
|
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
<Divider />
|
<Divider />
|
||||||
</div>
|
</div>
|
||||||
@ -81,15 +123,20 @@ const LeftSidebar = () => {
|
|||||||
arrow
|
arrow
|
||||||
key={id}
|
key={id}
|
||||||
placement="right"
|
placement="right"
|
||||||
title={get(sections, `${id}.name`, t<string>(`builder.leftSidebar.sections.${id}.heading`)) as string}
|
title={t<string>(`builder.leftSidebar.sections.${id}.heading`, get(sections, `${id}.name`))}
|
||||||
>
|
>
|
||||||
<IconButton onClick={() => handleClick(id)}>{icon}</IconButton>
|
<IconButton onClick={() => handleClick(id)}>{icon}</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{customSections.map(({ id }) => (
|
{customSections.map(({ id }) => (
|
||||||
<Tooltip key={id} title={get(sections, `${id}.name`, '') as string} placement="right" arrow>
|
<Tooltip
|
||||||
<IconButton onClick={() => handleClick(id)}>
|
key={id}
|
||||||
|
title={t<string>(`builder.leftSidebar.sections.${id}.heading`, get(sections, `${id}.name`))}
|
||||||
|
placement="right"
|
||||||
|
arrow
|
||||||
|
>
|
||||||
|
<IconButton onClick={() => id && handleClick(id)}>
|
||||||
<Star />
|
<Star />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -100,15 +147,11 @@ const LeftSidebar = () => {
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
{left.map(({ id, component }) => (
|
{sectionsList()}
|
||||||
<section key={id} id={id}>
|
|
||||||
{component}
|
|
||||||
</section>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{customSections.map(({ id }) => (
|
{customSections.map(({ id }) => (
|
||||||
<section key={id} id={`section-${id}`}>
|
<section key={id} id={`section-${id}`}>
|
||||||
<Section path={`sections.${id}`} isEditable isHideable isDeletable />
|
<Section path={`sections.${id}`} type="custom" isEditable isHideable isDeletable />
|
||||||
</section>
|
</section>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { Circle, Square, SquareRounded } from '@mui/icons-material';
|
import { Circle, Square, SquareRounded } from '@mui/icons-material';
|
||||||
import { Checkbox, Divider, FormControlLabel, Slider, ToggleButton, ToggleButtonGroup } from '@mui/material';
|
import { Checkbox, Divider, FormControlLabel, Slider, ToggleButton, ToggleButtonGroup } from '@mui/material';
|
||||||
import { Photo, PhotoShape } from '@reactive-resume/schema';
|
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import { Photo, PhotoShape } from 'schema';
|
||||||
|
|
||||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||||
import { setResumeState } from '@/store/resume/resumeSlice';
|
import { setResumeState } from '@/store/resume/resumeSlice';
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { Avatar, IconButton, Skeleton, Tooltip } from '@mui/material';
|
import { Avatar, IconButton, Skeleton, Tooltip } from '@mui/material';
|
||||||
import { Photo, Resume } from '@reactive-resume/schema';
|
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import React, { useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { useMutation } from 'react-query';
|
import { useMutation } from 'react-query';
|
||||||
|
import { Photo, Resume } from 'schema';
|
||||||
|
|
||||||
import { ServerError } from '@/services/axios';
|
import { ServerError } from '@/services/axios';
|
||||||
import { deletePhoto, DeletePhotoParams, uploadPhoto, UploadPhotoParams } from '@/services/resume';
|
import { deletePhoto, DeletePhotoParams, uploadPhoto, UploadPhotoParams } from '@/services/resume';
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Add } from '@mui/icons-material';
|
import { Add } from '@mui/icons-material';
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
import { ListItem } from '@reactive-resume/schema';
|
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import { ListItem } from 'schema';
|
||||||
|
|
||||||
import Heading from '@/components/shared/Heading';
|
import Heading from '@/components/shared/Heading';
|
||||||
import List from '@/components/shared/List';
|
import List from '@/components/shared/List';
|
||||||
|
|||||||
@ -1,37 +1,43 @@
|
|||||||
import { Add } from '@mui/icons-material';
|
import { Add } from '@mui/icons-material';
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
import { ListItem } from '@reactive-resume/schema';
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import { ListItem, Section as SectionRecord, SectionType } from 'schema';
|
||||||
import { validate } from 'uuid';
|
import { validate } from 'uuid';
|
||||||
|
|
||||||
import Heading from '@/components/shared/Heading';
|
import Heading from '@/components/shared/Heading';
|
||||||
import List from '@/components/shared/List';
|
import List from '@/components/shared/List';
|
||||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||||
import { ModalName, setModalState } from '@/store/modal/modalSlice';
|
import { ModalName, setModalState } from '@/store/modal/modalSlice';
|
||||||
import { duplicateItem } from '@/store/resume/resumeSlice';
|
import { duplicateItem, duplicateSection } from '@/store/resume/resumeSlice';
|
||||||
|
|
||||||
import SectionSettings from './SectionSettings';
|
import SectionSettings from './SectionSettings';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
path: `sections.${string}`;
|
path: `sections.${string}`;
|
||||||
|
type?: SectionType;
|
||||||
name?: string;
|
name?: string;
|
||||||
titleKey?: string;
|
titleKey?: string;
|
||||||
subtitleKey?: string;
|
subtitleKey?: string;
|
||||||
isEditable?: boolean;
|
isEditable?: boolean;
|
||||||
isHideable?: boolean;
|
isHideable?: boolean;
|
||||||
isDeletable?: boolean;
|
isDeletable?: boolean;
|
||||||
|
addMore?: boolean;
|
||||||
|
isDuplicated?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Section: React.FC<Props> = ({
|
const Section: React.FC<Props> = ({
|
||||||
path,
|
path,
|
||||||
name = 'Section Name',
|
name = 'Section Name',
|
||||||
|
type = 'basic',
|
||||||
titleKey = 'title',
|
titleKey = 'title',
|
||||||
subtitleKey = 'subtitle',
|
subtitleKey = 'subtitle',
|
||||||
isEditable = false,
|
isEditable = false,
|
||||||
isHideable = false,
|
isHideable = false,
|
||||||
isDeletable = false,
|
isDeletable = false,
|
||||||
|
addMore = false,
|
||||||
|
isDuplicated = false,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -41,22 +47,40 @@ const Section: React.FC<Props> = ({
|
|||||||
const visibility = useAppSelector<boolean>((state) => get(state.resume.present, `${path}.visible`, true));
|
const visibility = useAppSelector<boolean>((state) => get(state.resume.present, `${path}.visible`, true));
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
const id = path.split('.')[1];
|
const modal: ModalName = `builder.sections.${type}`;
|
||||||
const modal: ModalName = validate(id) ? 'builder.sections.custom' : `builder.${path}`;
|
|
||||||
|
|
||||||
dispatch(setModalState({ modal, state: { open: true, payload: { path } } }));
|
dispatch(setModalState({ modal, state: { open: true, payload: { path } } }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = (item: ListItem) => {
|
const handleEdit = (item: ListItem) => {
|
||||||
const id = path.split('.')[1];
|
const id = path.split('.')[1];
|
||||||
const modal: ModalName = validate(id) ? 'builder.sections.custom' : `builder.${path}`;
|
let modal: ModalName = validate(id) ? 'builder.sections.custom' : `builder.${path}`;
|
||||||
|
|
||||||
const payload = validate(id) ? { path, item } : { item };
|
const payload = validate(id) ? { path, item } : { item };
|
||||||
|
|
||||||
|
if (isDuplicated) {
|
||||||
|
modal = `builder.sections.${type}`;
|
||||||
|
payload.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(setModalState({ modal, state: { open: true, payload } }));
|
dispatch(setModalState({ modal, state: { open: true, payload } }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDuplicate = (item: ListItem) => dispatch(duplicateItem({ path: `${path}.items`, value: item }));
|
const handleDuplicate = (item: ListItem) => dispatch(duplicateItem({ path: `${path}.items`, value: item }));
|
||||||
|
|
||||||
|
const handleDuplicateSection = () => {
|
||||||
|
const newSection: SectionRecord = {
|
||||||
|
name: `${heading}`,
|
||||||
|
type: type,
|
||||||
|
visible: true,
|
||||||
|
columns: 2,
|
||||||
|
items: [],
|
||||||
|
isDuplicated: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
dispatch(duplicateSection({ value: newSection, type }));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Heading path={path} name={name} isEditable={isEditable} isHideable={isHideable} isDeletable={isDeletable} />
|
<Heading path={path} name={name} isEditable={isEditable} isHideable={isHideable} isDeletable={isDeletable} />
|
||||||
@ -74,9 +98,21 @@ const Section: React.FC<Props> = ({
|
|||||||
<SectionSettings path={path} />
|
<SectionSettings path={path} />
|
||||||
|
|
||||||
<Button variant="outlined" startIcon={<Add />} onClick={handleAdd}>
|
<Button variant="outlined" startIcon={<Add />} onClick={handleAdd}>
|
||||||
{t<string>('builder.common.actions.add', { token: heading })}
|
{t<string>('builder.common.actions.add', {
|
||||||
|
token: t<string>(`builder.leftSidebar.${path}.heading`, { defaultValue: heading }),
|
||||||
|
})}
|
||||||
</Button>
|
</Button>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
{addMore ? (
|
||||||
|
<div className="py-6 text-right">
|
||||||
|
<Button fullWidth variant="outlined" startIcon={<Add />} onClick={handleDuplicateSection}>
|
||||||
|
{t<string>('builder.common.actions.duplicate')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Divider, IconButton, SwipeableDrawer, Tooltip, useMediaQuery, useTheme } from '@mui/material';
|
import { Divider, IconButton, SwipeableDrawer, Tooltip, useMediaQuery, useTheme } from '@mui/material';
|
||||||
import { capitalize } from 'lodash';
|
import capitalize from 'lodash/capitalize';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
import Avatar from '@/components/shared/Avatar';
|
import Avatar from '@/components/shared/Avatar';
|
||||||
@ -43,7 +43,7 @@ const RightSidebar = () => {
|
|||||||
variant={isDesktop ? 'persistent' : 'temporary'}
|
variant={isDesktop ? 'persistent' : 'temporary'}
|
||||||
>
|
>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<nav className="overflow-y-scroll">
|
<nav className="overflow-y-auto">
|
||||||
<div>
|
<div>
|
||||||
<Avatar size={40} />
|
<Avatar size={40} />
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import Editor from '@monaco-editor/react';
|
import Editor from '@monaco-editor/react';
|
||||||
import { useTheme } from '@mui/material';
|
import { useTheme } from '@mui/material';
|
||||||
import { CustomCSS as CustomCSSType } from '@reactive-resume/schema';
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { CustomCSS as CustomCSSType } from 'schema';
|
||||||
|
|
||||||
import Heading from '@/components/shared/Heading';
|
import Heading from '@/components/shared/Heading';
|
||||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||||
@ -17,7 +17,9 @@ const CustomCSS = () => {
|
|||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const customCSS: CustomCSSType = useAppSelector((state) => get(state.resume.present, 'metadata.css', {}));
|
const customCSS: CustomCSSType = useAppSelector((state) =>
|
||||||
|
get(state.resume.present, 'metadata.css', {} as CustomCSSType)
|
||||||
|
);
|
||||||
|
|
||||||
const handleChange = (value: string | undefined) => {
|
const handleChange = (value: string | undefined) => {
|
||||||
dispatch(setResumeState({ path: 'metadata.css.value', value }));
|
dispatch(setResumeState({ path: 'metadata.css.value', value }));
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { PictureAsPdf, Schema } from '@mui/icons-material';
|
import { PictureAsPdf, Schema } from '@mui/icons-material';
|
||||||
import { List, ListItem, ListItemButton, ListItemText } from '@mui/material';
|
import { List, ListItem, ListItemButton, ListItemText } from '@mui/material';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import pick from 'lodash/pick';
|
import pick from 'lodash/pick';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
@ -45,8 +46,9 @@ const Export = () => {
|
|||||||
|
|
||||||
const slug = get(resume, 'slug');
|
const slug = get(resume, 'slug');
|
||||||
const username = get(resume, 'user.username');
|
const username = get(resume, 'user.username');
|
||||||
|
const updatedAt = get(resume, 'updatedAt');
|
||||||
|
|
||||||
const url = await mutateAsync({ username, slug });
|
const url = await mutateAsync({ username, slug, lastUpdated: dayjs(updatedAt).unix().toString() });
|
||||||
|
|
||||||
download(url);
|
download(url);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { Button } from '@mui/material';
|
|||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
import Heading from '@/components/shared/Heading';
|
import Heading from '@/components/shared/Heading';
|
||||||
import { DOCS_URL, DONATION_URL, GITHUB_ISSUES_URL, GITHUB_URL } from '@/constants/index';
|
import { DOCS_URL, DONATION_URL, GITHUB_ISSUES_URL, GITHUB_URL, REDDIT_URL } from '@/constants/index';
|
||||||
|
|
||||||
import styles from './Links.module.scss';
|
import styles from './Links.module.scss';
|
||||||
|
|
||||||
@ -50,6 +50,12 @@ const Links = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a href={REDDIT_URL} target="_blank" rel="noreferrer">
|
||||||
|
<Button variant="text" startIcon={<Link />}>
|
||||||
|
{t<string>('builder.rightSidebar.sections.links.reddit')}
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
|
||||||
<a href={DOCS_URL} target="_blank" rel="noreferrer">
|
<a href={DOCS_URL} target="_blank" rel="noreferrer">
|
||||||
<Button variant="text" startIcon={<Link />}>
|
<Button variant="text" startIcon={<Link />}>
|
||||||
{t<string>('builder.rightSidebar.sections.links.docs')}
|
{t<string>('builder.rightSidebar.sections.links.docs')}
|
||||||
|
|||||||
@ -10,13 +10,13 @@ import {
|
|||||||
Switch,
|
Switch,
|
||||||
TextField,
|
TextField,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { DateConfig, PageConfig, Resume } from '@reactive-resume/schema';
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { useMutation } from 'react-query';
|
import { useMutation } from 'react-query';
|
||||||
|
import { DateConfig, PageConfig, Resume } from 'schema';
|
||||||
|
|
||||||
import Heading from '@/components/shared/Heading';
|
import Heading from '@/components/shared/Heading';
|
||||||
import ThemeSwitch from '@/components/shared/ThemeSwitch';
|
import ThemeSwitch from '@/components/shared/ThemeSwitch';
|
||||||
@ -47,11 +47,11 @@ const Settings = () => {
|
|||||||
const id: number = useMemo(() => get(resume, 'id'), [resume]);
|
const id: number = useMemo(() => get(resume, 'id'), [resume]);
|
||||||
const slug: string = useMemo(() => get(resume, 'slug'), [resume]);
|
const slug: string = useMemo(() => get(resume, 'slug'), [resume]);
|
||||||
const username: string = useMemo(() => get(resume, 'user.username'), [resume]);
|
const username: string = useMemo(() => get(resume, 'user.username'), [resume]);
|
||||||
const pageConfig: PageConfig = useMemo(() => get(resume, 'metadata.page'), [resume]);
|
|
||||||
const dateConfig: DateConfig = useMemo(() => get(resume, 'metadata.date'), [resume]);
|
const dateConfig: DateConfig = useMemo(() => get(resume, 'metadata.date'), [resume]);
|
||||||
|
const pageConfig: PageConfig | undefined = useMemo(() => get(resume, 'metadata.page'), [resume]);
|
||||||
|
|
||||||
const isDarkMode = useMemo(() => theme === 'dark', [theme]);
|
const isDarkMode = useMemo(() => theme === 'dark', [theme]);
|
||||||
const exampleDateString = useMemo(() => `Eg. ${dayjs().utc().format(dateConfig.format)}`, [dateConfig.format]);
|
const exampleDateString = useMemo(() => `Eg. ${dayjs().format(dateConfig.format)}`, [dateConfig.format]);
|
||||||
const themeString = useMemo(() => (isDarkMode ? 'Matte Black Everything' : 'As bright as your future'), [isDarkMode]);
|
const themeString = useMemo(() => (isDarkMode ? 'Matte Black Everything' : 'As bright as your future'), [isDarkMode]);
|
||||||
|
|
||||||
const { mutateAsync: loadSampleDataMutation } = useMutation<Resume, ServerError, LoadSampleDataParams>(
|
const { mutateAsync: loadSampleDataMutation } = useMutation<Resume, ServerError, LoadSampleDataParams>(
|
||||||
@ -98,7 +98,7 @@ const Settings = () => {
|
|||||||
<>
|
<>
|
||||||
<Heading path="metadata.settings" name={t<string>('builder.rightSidebar.sections.settings.heading')} />
|
<Heading path="metadata.settings" name={t<string>('builder.rightSidebar.sections.settings.heading')} />
|
||||||
|
|
||||||
<List sx={{ padding: 0 }}>
|
<List disablePadding>
|
||||||
{/* Global Settings */}
|
{/* Global Settings */}
|
||||||
<>
|
<>
|
||||||
<ListSubheader disableSticky className="rounded">
|
<ListSubheader disableSticky className="rounded">
|
||||||
@ -212,7 +212,7 @@ const Settings = () => {
|
|||||||
{t<string>('builder.rightSidebar.sections.settings.resume.heading')}
|
{t<string>('builder.rightSidebar.sections.settings.resume.heading')}
|
||||||
</ListSubheader>
|
</ListSubheader>
|
||||||
|
|
||||||
<ListItem>
|
<ListItem disableGutters>
|
||||||
<ListItemButton onClick={handleLoadSampleData}>
|
<ListItemButton onClick={handleLoadSampleData}>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<Anchor />
|
<Anchor />
|
||||||
@ -224,7 +224,7 @@ const Settings = () => {
|
|||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem>
|
<ListItem disableGutters>
|
||||||
<ListItemButton onClick={handleResetResume}>
|
<ListItemButton onClick={handleResetResume}>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<DeleteForever />
|
<DeleteForever />
|
||||||
|
|||||||
@ -31,7 +31,14 @@ const Templates = () => {
|
|||||||
<div key={template.id} className={styles.template}>
|
<div key={template.id} className={styles.template}>
|
||||||
<div className={clsx(styles.preview, { [styles.selected]: template.id === currentTemplate })}>
|
<div className={clsx(styles.preview, { [styles.selected]: template.id === currentTemplate })}>
|
||||||
<ButtonBase onClick={() => handleChange(template)}>
|
<ButtonBase onClick={() => handleChange(template)}>
|
||||||
<Image src={template.preview} alt={template.name} className="rounded-sm" layout="fill" priority />
|
<Image
|
||||||
|
fill
|
||||||
|
priority
|
||||||
|
alt={template.name}
|
||||||
|
src={template.preview}
|
||||||
|
className="rounded-sm"
|
||||||
|
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||||
|
/>
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { ThemeConfig } from '@reactive-resume/schema';
|
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import { ThemeConfig } from 'schema';
|
||||||
|
|
||||||
import ColorAvatar from '@/components/shared/ColorAvatar';
|
import ColorAvatar from '@/components/shared/ColorAvatar';
|
||||||
import ColorPicker from '@/components/shared/ColorPicker';
|
import ColorPicker from '@/components/shared/ColorPicker';
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { Autocomplete, Skeleton, Slider, TextField } from '@mui/material';
|
import { Autocomplete, Skeleton, Slider, TextField } from '@mui/material';
|
||||||
import { Font, TypeCategory, TypeProperty, Typography as TypographyType } from '@reactive-resume/schema';
|
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
|
import { Font, TypeCategory, TypeProperty, Typography as TypographyType } from 'schema';
|
||||||
|
|
||||||
import Heading from '@/components/shared/Heading';
|
import Heading from '@/components/shared/Heading';
|
||||||
import { FONTS_QUERY } from '@/constants/index';
|
import { FONTS_QUERY } from '@/constants/index';
|
||||||
|
|||||||
@ -16,9 +16,7 @@ type Props = {
|
|||||||
const ResumeCard: React.FC<Props> = ({ modal, icon: Icon, title, subtitle }) => {
|
const ResumeCard: React.FC<Props> = ({ modal, icon: Icon, title, subtitle }) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => dispatch(setModalState({ modal, state: { open: true } }));
|
||||||
dispatch(setModalState({ modal, state: { open: true } }));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.resume}>
|
<section className={styles.resume}>
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import {
|
|||||||
OpenInNew,
|
OpenInNew,
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import { ButtonBase, ListItemIcon, ListItemText, Menu, MenuItem, Tooltip } from '@mui/material';
|
import { ButtonBase, ListItemIcon, ListItemText, Menu, MenuItem, Tooltip } from '@mui/material';
|
||||||
import { Resume } from '@reactive-resume/schema';
|
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
@ -15,6 +14,7 @@ import { useTranslation } from 'next-i18next';
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { useMutation } from 'react-query';
|
import { useMutation } from 'react-query';
|
||||||
|
import { Resume } from 'schema';
|
||||||
|
|
||||||
import { RESUMES_QUERY } from '@/constants/index';
|
import { RESUMES_QUERY } from '@/constants/index';
|
||||||
import { ServerError } from '@/services/axios';
|
import { ServerError } from '@/services/axios';
|
||||||
@ -115,9 +115,7 @@ const ResumePreview: React.FC<Props> = ({ resume }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ButtonBase className={styles.preview}>
|
<ButtonBase className={styles.preview}>
|
||||||
{resume.image ? (
|
{resume.image ? <Image src={resume.image} alt={resume.name} priority width={400} height={0} /> : null}
|
||||||
<Image src={resume.image} alt={resume.name} objectFit="cover" layout="fill" priority />
|
|
||||||
) : null}
|
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
|||||||
@ -6,15 +6,17 @@ import { useState } from 'react';
|
|||||||
|
|
||||||
import { logout } from '@/store/auth/authSlice';
|
import { logout } from '@/store/auth/authSlice';
|
||||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||||
|
import { setModalState } from '@/store/modal/modalSlice';
|
||||||
import getGravatarUrl from '@/utils/getGravatarUrl';
|
import getGravatarUrl from '@/utils/getGravatarUrl';
|
||||||
|
|
||||||
import styles from './Avatar.module.scss';
|
import styles from './Avatar.module.scss';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
size?: number;
|
size?: number;
|
||||||
|
interactive?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Avatar: React.FC<Props> = ({ size = 64 }) => {
|
const Avatar: React.FC<Props> = ({ size = 64, interactive = true }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -34,6 +36,11 @@ const Avatar: React.FC<Props> = ({ size = 64 }) => {
|
|||||||
setAnchorEl(null);
|
setAnchorEl(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenProfile = () => {
|
||||||
|
dispatch(setModalState({ modal: 'auth.profile', state: { open: true } }));
|
||||||
|
handleClose();
|
||||||
|
};
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
dispatch(logout());
|
dispatch(logout());
|
||||||
handleClose();
|
handleClose();
|
||||||
@ -43,20 +50,20 @@ const Avatar: React.FC<Props> = ({ size = 64 }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IconButton onClick={handleOpen}>
|
<IconButton onClick={handleOpen} disabled={!interactive}>
|
||||||
<Image
|
<Image
|
||||||
width={size}
|
width={size}
|
||||||
height={size}
|
height={size}
|
||||||
alt={user?.name}
|
|
||||||
className={styles.avatar}
|
className={styles.avatar}
|
||||||
src={getGravatarUrl(email, size)}
|
src={getGravatarUrl(email, size)}
|
||||||
|
alt={user?.name ?? 'User Avatar'}
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<Menu anchorEl={anchorEl} onClose={handleClose} open={Boolean(anchorEl)}>
|
<Menu anchorEl={anchorEl} onClose={handleClose} open={Boolean(anchorEl)}>
|
||||||
<MenuItem>
|
<MenuItem onClick={handleOpenProfile}>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-xs opacity-50">{t<string>('common.avatar.menu.greeting')}</span>
|
<span className="text-xs opacity-50">{t<string>('common.avatar.menu.greeting')},</span>
|
||||||
<p>{user?.name}</p>
|
<p>{user?.name}</p>
|
||||||
</div>
|
</div>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|||||||
@ -62,7 +62,7 @@ const Heading: React.FC<Props> = ({
|
|||||||
{editMode ? (
|
{editMode ? (
|
||||||
<TextField size="small" value={heading} className="w-3/4" onChange={handleChange} />
|
<TextField size="small" value={heading} className="w-3/4" onChange={handleChange} />
|
||||||
) : (
|
) : (
|
||||||
<h1>{heading}</h1>
|
<h1>{t<string>(`builder.leftSidebar.${path}.heading`, { defaultValue: heading })}</h1>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { ListItem as ListItemType } from '@reactive-resume/schema';
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isArray from 'lodash/isArray';
|
import isArray from 'lodash/isArray';
|
||||||
@ -8,6 +7,7 @@ import { useTranslation } from 'next-i18next';
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { DndProvider } from 'react-dnd';
|
import { DndProvider } from 'react-dnd';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
|
import { ListItem as ListItemType } from 'schema';
|
||||||
|
|
||||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||||
import { deleteItem, setResumeState } from '@/store/resume/resumeSlice';
|
import { deleteItem, setResumeState } from '@/store/resume/resumeSlice';
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { DeleteOutline, DriveFileRenameOutline, FileCopy, MoreVert } from '@mui/icons-material';
|
import { DeleteOutline, DriveFileRenameOutline, FileCopy, MoreVert } from '@mui/icons-material';
|
||||||
import { Divider, IconButton, ListItemIcon, ListItemText, Menu, MenuItem, Tooltip } from '@mui/material';
|
import { Divider, IconButton, ListItemIcon, ListItemText, Menu, MenuItem, Tooltip } from '@mui/material';
|
||||||
import { ListItem as ListItemType } from '@reactive-resume/schema';
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import isFunction from 'lodash/isFunction';
|
import isFunction from 'lodash/isFunction';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
import { DropTargetMonitor, useDrag, useDrop, XYCoord } from 'react-dnd';
|
import { DropTargetMonitor, useDrag, useDrop, XYCoord } from 'react-dnd';
|
||||||
|
import { ListItem as ListItemType } from 'schema';
|
||||||
|
|
||||||
import styles from './ListItem.module.scss';
|
import styles from './ListItem.module.scss';
|
||||||
|
|
||||||
|
|||||||
@ -4,8 +4,8 @@ type Props = {
|
|||||||
size?: 256 | 64 | 48 | 40 | 32;
|
size?: 256 | 64 | 48 | 40 | 32;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Logo: React.FC<Props> = ({ size = 64 }) => {
|
const Logo: React.FC<Props> = ({ size = 64 }) => (
|
||||||
return <Image alt="Reactive Resume" src="/images/logos/logo.svg" className="rounded" width={size} height={size} />;
|
<Image alt="Reactive Resume" src="/images/logos/logo.svg" className="rounded" width={size} height={size} priority />
|
||||||
};
|
);
|
||||||
|
|
||||||
export default Logo;
|
export default Logo;
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { isEmpty } from 'lodash';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
|
import rehypeKatex from 'rehype-katex';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
|
import remarkMath from 'remark-math';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children?: string;
|
children?: string;
|
||||||
@ -12,7 +14,11 @@ const Markdown: React.FC<Props> = ({ className, children }) => {
|
|||||||
if (!children || isEmpty(children)) return null;
|
if (!children || isEmpty(children)) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactMarkdown remarkPlugins={[remarkGfm]} className={clsx('markdown', className)}>
|
<ReactMarkdown
|
||||||
|
className={clsx('markdown', className)}
|
||||||
|
remarkPlugins={[remarkGfm, remarkMath]}
|
||||||
|
rehypePlugins={[rehypeKatex]}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { TextField } from '@mui/material';
|
import { TextField } from '@mui/material';
|
||||||
import { DatePicker } from '@mui/x-date-pickers';
|
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
@ -58,12 +57,11 @@ const ResumeInput: React.FC<Props> = ({ type = 'text', label, path, className, m
|
|||||||
<DatePicker
|
<DatePicker
|
||||||
openTo="year"
|
openTo="year"
|
||||||
label={label}
|
label={label}
|
||||||
value={value}
|
value={dayjs(value)}
|
||||||
views={['year', 'month', 'day']}
|
slots={{ textField: (params) => <TextField {...params} error={false} className={className} /> }}
|
||||||
renderInput={(params) => <TextField {...params} error={false} className={className} />}
|
onChange={(date: dayjs.Dayjs | null) => {
|
||||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
if (!date) return onChangeValue('');
|
||||||
isEmpty(keyboardInputValue) && onChangeValue('');
|
if (dayjs(date).isValid()) return onChangeValue(dayjs(date).format('YYYY-MM-DD'));
|
||||||
date && dayjs(date).utc().isValid() && onChangeValue(dayjs(date).utc().toISOString());
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -13,7 +13,7 @@ export const languages: Language[] = [
|
|||||||
{ code: 'ca', name: 'Catalan', localName: 'Valencian' },
|
{ code: 'ca', name: 'Catalan', localName: 'Valencian' },
|
||||||
{ code: 'cs', name: 'Czech', localName: 'čeština' },
|
{ code: 'cs', name: 'Czech', localName: 'čeština' },
|
||||||
{ code: 'da', name: 'Danish', localName: 'Dansk' },
|
{ code: 'da', name: 'Danish', localName: 'Dansk' },
|
||||||
{ code: 'de', name: 'German', localName: 'Deutsch' },
|
{ code: 'de', name: 'German', localName: 'Deutsch Formell / Sie' },
|
||||||
{ code: 'el', name: 'Greek', localName: 'Ελληνικά' },
|
{ code: 'el', name: 'Greek', localName: 'Ελληνικά' },
|
||||||
{ code: 'en', name: 'English' },
|
{ code: 'en', name: 'English' },
|
||||||
{ code: 'es', name: 'Spanish', localName: 'Español' },
|
{ code: 'es', name: 'Spanish', localName: 'Español' },
|
||||||
@ -37,6 +37,7 @@ export const languages: Language[] = [
|
|||||||
{ code: 'or', name: 'Odia', localName: 'ଓଡ଼ିଆ' },
|
{ code: 'or', name: 'Odia', localName: 'ଓଡ଼ିଆ' },
|
||||||
{ code: 'pl', name: 'Polish', localName: 'Polski' },
|
{ code: 'pl', name: 'Polish', localName: 'Polski' },
|
||||||
{ code: 'pt', name: 'Portuguese', localName: 'Português' },
|
{ code: 'pt', name: 'Portuguese', localName: 'Português' },
|
||||||
|
{ code: 'pt-BR', name: 'Brazilian Portuguese', localName: 'Brasil' },
|
||||||
{ code: 'ro', name: 'Romanian', localName: 'limba română' },
|
{ code: 'ro', name: 'Romanian', localName: 'limba română' },
|
||||||
{ code: 'ru', name: 'Russian', localName: 'русский' },
|
{ code: 'ru', name: 'Russian', localName: 'русский' },
|
||||||
{ code: 'sr', name: 'Serbian', localName: 'српски језик' },
|
{ code: 'sr', name: 'Serbian', localName: 'српски језик' },
|
||||||
|
|||||||
@ -23,8 +23,8 @@ import {
|
|||||||
VolunteerActivism,
|
VolunteerActivism,
|
||||||
Work,
|
Work,
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import { Section as SectionRecord } from '@reactive-resume/schema';
|
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
import { Section as SectionRecord, SectionType } from 'schema';
|
||||||
|
|
||||||
import Basics from '@/components/build/LeftSidebar/sections/Basics';
|
import Basics from '@/components/build/LeftSidebar/sections/Basics';
|
||||||
import Location from '@/components/build/LeftSidebar/sections/Location';
|
import Location from '@/components/build/LeftSidebar/sections/Location';
|
||||||
@ -60,59 +60,136 @@ export const left: SidebarSection[] = [
|
|||||||
{
|
{
|
||||||
id: 'work',
|
id: 'work',
|
||||||
icon: <Work />,
|
icon: <Work />,
|
||||||
component: <Section path="sections.work" titleKey="name" subtitleKey="position" isEditable isHideable />,
|
component: (
|
||||||
|
<Section
|
||||||
|
type={'work'}
|
||||||
|
addMore={true}
|
||||||
|
path="sections.work"
|
||||||
|
titleKey="name"
|
||||||
|
subtitleKey="position"
|
||||||
|
isEditable
|
||||||
|
isHideable
|
||||||
|
/>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'education',
|
id: 'education',
|
||||||
icon: <School />,
|
icon: <School />,
|
||||||
component: <Section path="sections.education" titleKey="institution" subtitleKey="area" isEditable isHideable />,
|
component: (
|
||||||
|
<Section
|
||||||
|
type={'education'}
|
||||||
|
path="sections.education"
|
||||||
|
titleKey="institution"
|
||||||
|
subtitleKey="area"
|
||||||
|
isEditable
|
||||||
|
isHideable
|
||||||
|
/>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'awards',
|
id: 'awards',
|
||||||
icon: <EmojiEvents />,
|
icon: <EmojiEvents />,
|
||||||
component: <Section path="sections.awards" titleKey="title" subtitleKey="awarder" isEditable isHideable />,
|
component: (
|
||||||
|
<Section type={'awards'} path="sections.awards" titleKey="title" subtitleKey="awarder" isEditable isHideable />
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'certifications',
|
id: 'certifications',
|
||||||
icon: <CardGiftcard />,
|
icon: <CardGiftcard />,
|
||||||
component: <Section path="sections.certifications" titleKey="name" subtitleKey="issuer" isEditable isHideable />,
|
component: (
|
||||||
|
<Section
|
||||||
|
type={'certifications'}
|
||||||
|
path="sections.certifications"
|
||||||
|
titleKey="name"
|
||||||
|
subtitleKey="issuer"
|
||||||
|
isEditable
|
||||||
|
isHideable
|
||||||
|
/>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'publications',
|
id: 'publications',
|
||||||
icon: <MenuBook />,
|
icon: <MenuBook />,
|
||||||
component: <Section path="sections.publications" titleKey="name" subtitleKey="publisher" isEditable isHideable />,
|
component: (
|
||||||
|
<Section
|
||||||
|
type={'publications'}
|
||||||
|
path="sections.publications"
|
||||||
|
titleKey="name"
|
||||||
|
subtitleKey="publisher"
|
||||||
|
isEditable
|
||||||
|
isHideable
|
||||||
|
/>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'skills',
|
id: 'skills',
|
||||||
icon: <Architecture />,
|
icon: <Architecture />,
|
||||||
component: <Section path="sections.skills" titleKey="name" subtitleKey="level" isEditable isHideable />,
|
component: (
|
||||||
|
<Section type={'skills'} path="sections.skills" titleKey="name" subtitleKey="level" isEditable isHideable />
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'languages',
|
id: 'languages',
|
||||||
icon: <Language />,
|
icon: <Language />,
|
||||||
component: <Section path="sections.languages" titleKey="name" subtitleKey="level" isEditable isHideable />,
|
component: (
|
||||||
|
<Section type={'languages'} path="sections.languages" titleKey="name" subtitleKey="level" isEditable isHideable />
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'interests',
|
id: 'interests',
|
||||||
icon: <Sailing />,
|
icon: <Sailing />,
|
||||||
component: <Section path="sections.interests" titleKey="name" subtitleKey="keywords" isEditable isHideable />,
|
component: (
|
||||||
|
<Section
|
||||||
|
type={'interests'}
|
||||||
|
path="sections.interests"
|
||||||
|
titleKey="name"
|
||||||
|
subtitleKey="keywords"
|
||||||
|
isEditable
|
||||||
|
isHideable
|
||||||
|
/>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'volunteer',
|
id: 'volunteer',
|
||||||
icon: <VolunteerActivism />,
|
icon: <VolunteerActivism />,
|
||||||
component: (
|
component: (
|
||||||
<Section path="sections.volunteer" titleKey="organization" subtitleKey="position" isEditable isHideable />
|
<Section
|
||||||
|
type={'volunteer'}
|
||||||
|
path="sections.volunteer"
|
||||||
|
titleKey="organization"
|
||||||
|
subtitleKey="position"
|
||||||
|
isEditable
|
||||||
|
isHideable
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'projects',
|
id: 'projects',
|
||||||
icon: <Coffee />,
|
icon: <Coffee />,
|
||||||
component: <Section path="sections.projects" titleKey="name" subtitleKey="description" isEditable isHideable />,
|
component: (
|
||||||
|
<Section
|
||||||
|
type={'projects'}
|
||||||
|
path="sections.projects"
|
||||||
|
titleKey="name"
|
||||||
|
subtitleKey="description"
|
||||||
|
isEditable
|
||||||
|
isHideable
|
||||||
|
/>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'references',
|
id: 'references',
|
||||||
icon: <Groups />,
|
icon: <Groups />,
|
||||||
component: <Section path="sections.references" titleKey="name" subtitleKey="relationship" isEditable isHideable />,
|
component: (
|
||||||
|
<Section
|
||||||
|
type={'references'}
|
||||||
|
path="sections.references"
|
||||||
|
titleKey="name"
|
||||||
|
subtitleKey="relationship"
|
||||||
|
isEditable
|
||||||
|
isHideable
|
||||||
|
/>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -164,7 +241,19 @@ export const right: SidebarSection[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const getCustomSections = (sections: Record<string, SectionRecord>): Array<Required<SectionRecord>> => {
|
export const getSectionsByType = (sections: Record<string, SectionRecord>, type: SectionType): SectionRecord[] => {
|
||||||
|
if (isEmpty(sections)) return [];
|
||||||
|
|
||||||
|
return Object.entries(sections).reduce((acc, [id, section]) => {
|
||||||
|
if (section.type.startsWith(type) && section.isDuplicated) {
|
||||||
|
return [...acc, { ...section, id }];
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, [] as SectionRecord[]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCustomSections = (sections: Record<string, SectionRecord>): SectionRecord[] => {
|
||||||
if (isEmpty(sections)) return [];
|
if (isEmpty(sections)) return [];
|
||||||
|
|
||||||
return Object.entries(sections).reduce((acc, [id, section]) => {
|
return Object.entries(sections).reduce((acc, [id, section]) => {
|
||||||
@ -173,7 +262,7 @@ export const getCustomSections = (sections: Record<string, SectionRecord>): Arra
|
|||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, [] as Array<Required<SectionRecord>>);
|
}, [] as SectionRecord[]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const sections = [...left, ...right];
|
const sections = [...left, ...right];
|
||||||
|
|||||||
@ -13,6 +13,11 @@ export const DOCS_URL = 'https://docs.rxresu.me';
|
|||||||
export const DONATION_URL = 'https://paypal.me/RajaRajanA';
|
export const DONATION_URL = 'https://paypal.me/RajaRajanA';
|
||||||
export const TRANSLATE_URL = 'https://translate.rxresu.me/';
|
export const TRANSLATE_URL = 'https://translate.rxresu.me/';
|
||||||
export const DIGITALOCEAN_URL = 'https://pillai.xyz/digitalocean';
|
export const DIGITALOCEAN_URL = 'https://pillai.xyz/digitalocean';
|
||||||
|
export const REDDIT_URL = 'https://www.reddit.com/r/reactiveresume/';
|
||||||
export const GITHUB_URL = 'https://github.com/AmruthPillai/Reactive-Resume';
|
export const GITHUB_URL = 'https://github.com/AmruthPillai/Reactive-Resume';
|
||||||
export const PRODUCT_HUNT_URL = 'https://www.producthunt.com/posts/reactive-resume-v3';
|
export const PRODUCT_HUNT_URL = 'https://www.producthunt.com/posts/reactive-resume-v3';
|
||||||
export const GITHUB_ISSUES_URL = 'https://github.com/AmruthPillai/Reactive-Resume/issues/new/choose';
|
export const GITHUB_ISSUES_URL = 'https://github.com/AmruthPillai/Reactive-Resume/issues/new/choose';
|
||||||
|
|
||||||
|
// Default Error Message
|
||||||
|
export const DEFAULT_ERROR_MESSAGE =
|
||||||
|
'Something went wrong while performing this action, please report this issue on GitHub.';
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { Login, Visibility, VisibilityOff } from '@mui/icons-material';
|
|||||||
import { Button, IconButton, InputAdornment, TextField } from '@mui/material';
|
import { Button, IconButton, InputAdornment, TextField } from '@mui/material';
|
||||||
import { CredentialResponse, GoogleLogin } from '@react-oauth/google';
|
import { CredentialResponse, GoogleLogin } from '@react-oauth/google';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import { isEmpty } from 'lodash';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import { Trans, useTranslation } from 'next-i18next';
|
import { Trans, useTranslation } from 'next-i18next';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
@ -62,14 +62,7 @@ const LoginModal: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async ({ identifier, password }: FormData) => {
|
const onSubmit = async ({ identifier, password }: FormData) => {
|
||||||
await loginMutation(
|
await loginMutation({ identifier, password });
|
||||||
{ identifier, password },
|
|
||||||
{
|
|
||||||
onError: (error) => {
|
|
||||||
toast.error(error.message);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
handleClose();
|
handleClose();
|
||||||
};
|
};
|
||||||
@ -86,14 +79,14 @@ const LoginModal: React.FC = () => {
|
|||||||
|
|
||||||
const handleLoginWithGoogle = async (response: CredentialResponse) => {
|
const handleLoginWithGoogle = async (response: CredentialResponse) => {
|
||||||
if (response.credential) {
|
if (response.credential) {
|
||||||
await loginWithGoogleMutation({ credential: response.credential }, { onError: handleLoginWithGoogleError });
|
await loginWithGoogleMutation({ credential: response.credential }, { onError: handleGoogleLoginError });
|
||||||
|
|
||||||
handleClose();
|
handleClose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLoginWithGoogleError = () => {
|
const handleGoogleLoginError = () => {
|
||||||
toast("Please try logging in using email/password, or use another browser that supports Google's One Tap API.");
|
toast.error("Google doesn't seem to be responding, please try logging in using email/password instead.");
|
||||||
};
|
};
|
||||||
|
|
||||||
const PasswordVisibility = (): React.ReactElement => {
|
const PasswordVisibility = (): React.ReactElement => {
|
||||||
@ -117,7 +110,7 @@ const LoginModal: React.FC = () => {
|
|||||||
footerChildren={
|
footerChildren={
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
{!isEmpty(env('GOOGLE_CLIENT_ID')) && (
|
{!isEmpty(env('GOOGLE_CLIENT_ID')) && (
|
||||||
<GoogleLogin onSuccess={handleLoginWithGoogle} onError={handleLoginWithGoogleError} />
|
<GoogleLogin onSuccess={handleLoginWithGoogle} onError={handleGoogleLoginError} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}>
|
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}>
|
||||||
@ -169,7 +162,8 @@ const LoginModal: React.FC = () => {
|
|||||||
|
|
||||||
<p className="text-xs">
|
<p className="text-xs">
|
||||||
<Trans t={t} i18nKey="modals.auth.login.recover-text">
|
<Trans t={t} i18nKey="modals.auth.login.recover-text">
|
||||||
In case you have forgotten your password, you can <a onClick={handleRecoverAccount}>recover your account here.</a>
|
In case you have forgotten your password, you can
|
||||||
|
<a onClick={handleRecoverAccount}>recover your account here.</a>
|
||||||
</Trans>
|
</Trans>
|
||||||
</p>
|
</p>
|
||||||
</BaseModal>
|
</BaseModal>
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { HowToReg } from '@mui/icons-material';
|
|||||||
import { Button, TextField } from '@mui/material';
|
import { Button, TextField } from '@mui/material';
|
||||||
import { CredentialResponse, GoogleLogin } from '@react-oauth/google';
|
import { CredentialResponse, GoogleLogin } from '@react-oauth/google';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import { isEmpty } from 'lodash';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import { Trans, useTranslation } from 'next-i18next';
|
import { Trans, useTranslation } from 'next-i18next';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
@ -81,14 +81,14 @@ const RegisterModal: React.FC = () => {
|
|||||||
|
|
||||||
const handleLoginWithGoogle = async (response: CredentialResponse) => {
|
const handleLoginWithGoogle = async (response: CredentialResponse) => {
|
||||||
if (response.credential) {
|
if (response.credential) {
|
||||||
await loginWithGoogleMutation({ credential: response.credential }, { onError: handleLoginWithGoogleError });
|
await loginWithGoogleMutation({ credential: response.credential }, { onError: handleGoogleLoginError });
|
||||||
|
|
||||||
handleClose();
|
handleClose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLoginWithGoogleError = () => {
|
const handleGoogleLoginError = () => {
|
||||||
toast("Please try logging in using email/password, or use another browser that supports Google's One Tap API.");
|
toast("Google doesn't seem to be responding, please try logging in using email/password instead.");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -100,7 +100,7 @@ const RegisterModal: React.FC = () => {
|
|||||||
footerChildren={
|
footerChildren={
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
{!isEmpty(env('GOOGLE_CLIENT_ID')) && (
|
{!isEmpty(env('GOOGLE_CLIENT_ID')) && (
|
||||||
<GoogleLogin onSuccess={handleLoginWithGoogle} onError={handleLoginWithGoogleError} />
|
<GoogleLogin onSuccess={handleLoginWithGoogle} onError={handleGoogleLoginError} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}>
|
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}>
|
||||||
|
|||||||
161
client/modals/auth/UserProfileModal.tsx
Normal file
161
client/modals/auth/UserProfileModal.tsx
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
import { joiResolver } from '@hookform/resolvers/joi';
|
||||||
|
import { CrisisAlert, ManageAccounts } from '@mui/icons-material';
|
||||||
|
import { Button, Divider, TextField } from '@mui/material';
|
||||||
|
import Joi from 'joi';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { Trans, useTranslation } from 'next-i18next';
|
||||||
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
|
import { useMutation } from 'react-query';
|
||||||
|
|
||||||
|
import Avatar from '@/components/shared/Avatar';
|
||||||
|
import BaseModal from '@/components/shared/BaseModal';
|
||||||
|
import { deleteAccount, updateProfile, UpdateProfileParams } from '@/services/auth';
|
||||||
|
import { ServerError } from '@/services/axios';
|
||||||
|
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||||
|
import { setModalState } from '@/store/modal/modalSlice';
|
||||||
|
|
||||||
|
type FormData = {
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultState: FormData = {
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const schema = Joi.object({
|
||||||
|
name: Joi.string().required(),
|
||||||
|
email: Joi.string()
|
||||||
|
.email({ tlds: { allow: false } })
|
||||||
|
.required(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const UserProfileModal = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const [deleteText, setDeleteText] = useState<string>('');
|
||||||
|
const isDeleteTextValid = useMemo(() => deleteText.toLowerCase() === 'delete', [deleteText]);
|
||||||
|
|
||||||
|
const user = useAppSelector((state) => state.auth.user);
|
||||||
|
const { open: isOpen } = useAppSelector((state) => state.modal['auth.profile']);
|
||||||
|
|
||||||
|
const { mutateAsync: deleteAccountMutation } = useMutation<void, ServerError>(deleteAccount);
|
||||||
|
const { mutateAsync: updateProfileMutation } = useMutation<void, ServerError, UpdateProfileParams>(updateProfile);
|
||||||
|
|
||||||
|
const { reset, getFieldState, control, handleSubmit } = useForm<FormData>({
|
||||||
|
defaultValues: defaultState,
|
||||||
|
resolver: joiResolver(schema),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (user && !getFieldState('name').isTouched && !getFieldState('email').isTouched) {
|
||||||
|
reset({ name: user.name, email: user.email });
|
||||||
|
}
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
dispatch(setModalState({ modal: 'auth.profile', state: { open: false } }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdate = handleSubmit(async (data) => {
|
||||||
|
handleClose();
|
||||||
|
await updateProfileMutation({ name: data.name });
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
await deleteAccountMutation();
|
||||||
|
handleClose();
|
||||||
|
|
||||||
|
router.push('/');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseModal isOpen={isOpen} handleClose={handleClose} heading="Your Account" icon={<ManageAccounts />}>
|
||||||
|
<div className="grid gap-4">
|
||||||
|
<form className="grid gap-4 xl:w-2/3">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<Avatar interactive={false} />
|
||||||
|
|
||||||
|
<div className="grid flex-1 gap-1.5">
|
||||||
|
<Controller
|
||||||
|
name="name"
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState }) => (
|
||||||
|
<TextField
|
||||||
|
autoFocus
|
||||||
|
label={t<string>('modals.auth.profile.form.name.label')}
|
||||||
|
error={!!fieldState.error}
|
||||||
|
helperText={fieldState.error?.message}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<p className="pl-4 text-[10.25px] opacity-50">
|
||||||
|
<Trans t={t} i18nKey="modals.auth.profile.form.avatar.help-text">
|
||||||
|
You can update your profile picture on{' '}
|
||||||
|
<a href="https://gravatar.com/" target="_blank" rel="noreferrer">
|
||||||
|
Gravatar
|
||||||
|
</a>
|
||||||
|
</Trans>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
name="email"
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState }) => (
|
||||||
|
<TextField
|
||||||
|
disabled
|
||||||
|
label={t<string>('modals.auth.profile.form.email.label')}
|
||||||
|
error={!!fieldState.error}
|
||||||
|
helperText={t<string>('modals.auth.profile.form.email.help-text')}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Button onClick={handleUpdate}>{t<string>('modals.auth.profile.actions.save')}</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="my-2">
|
||||||
|
<Divider />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<CrisisAlert />
|
||||||
|
<h5 className="font-medium">{t<string>('modals.auth.profile.delete-account.heading')}</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-xs opacity-75">
|
||||||
|
{t<string>('modals.auth.profile.delete-account.body', { keyword: 'delete' })}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex max-w-xs flex-col gap-4">
|
||||||
|
<TextField
|
||||||
|
value={deleteText}
|
||||||
|
placeholder="Type 'delete' to confirm"
|
||||||
|
onChange={(e) => setDeleteText(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Button variant="contained" color="error" disabled={!isDeleteTextValid} onClick={handleDelete}>
|
||||||
|
{t<string>('modals.auth.profile.delete-account.actions.delete')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</BaseModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserProfileModal;
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import { joiResolver } from '@hookform/resolvers/joi';
|
import { joiResolver } from '@hookform/resolvers/joi';
|
||||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||||
import { Button, TextField } from '@mui/material';
|
import { Button, TextField } from '@mui/material';
|
||||||
import { DatePicker } from '@mui/x-date-pickers';
|
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||||
import { Award, SectionPath } from '@reactive-resume/schema';
|
import { Award, SectionPath } from 'schema';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
@ -128,21 +128,23 @@ const AwardModal: React.FC = () => {
|
|||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState }) => (
|
render={({ field, fieldState }) => (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...field}
|
|
||||||
openTo="year"
|
openTo="year"
|
||||||
|
inputRef={field.ref}
|
||||||
label={t<string>('builder.common.form.date.label')}
|
label={t<string>('builder.common.form.date.label')}
|
||||||
|
value={dayjs(field.value)}
|
||||||
views={['year', 'month', 'day']}
|
views={['year', 'month', 'day']}
|
||||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
slots={{
|
||||||
isEmpty(keyboardInputValue) && field.onChange('');
|
textField: (params) => (
|
||||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
<TextField
|
||||||
|
{...params}
|
||||||
|
error={!!fieldState.error}
|
||||||
|
helperText={fieldState.error?.message || params.inputProps?.placeholder}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
onChange={(date: dayjs.Dayjs | null) => {
|
||||||
|
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => (
|
|
||||||
<TextField
|
|
||||||
{...params}
|
|
||||||
error={!!fieldState.error}
|
|
||||||
helperText={fieldState.error?.message || params.inputProps?.placeholder}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { joiResolver } from '@hookform/resolvers/joi';
|
import { joiResolver } from '@hookform/resolvers/joi';
|
||||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||||
import { Button, TextField } from '@mui/material';
|
import { Button, TextField } from '@mui/material';
|
||||||
import { DatePicker } from '@mui/x-date-pickers';
|
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||||
import { Certificate, SectionPath } from '@reactive-resume/schema';
|
import { Certificate, SectionPath } from 'schema';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
@ -128,21 +128,23 @@ const CertificateModal: React.FC = () => {
|
|||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState }) => (
|
render={({ field, fieldState }) => (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...field}
|
|
||||||
openTo="year"
|
openTo="year"
|
||||||
|
inputRef={field.ref}
|
||||||
label={t<string>('builder.common.form.date.label')}
|
label={t<string>('builder.common.form.date.label')}
|
||||||
|
value={dayjs(field.value)}
|
||||||
views={['year', 'month', 'day']}
|
views={['year', 'month', 'day']}
|
||||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
slots={{
|
||||||
isEmpty(keyboardInputValue) && field.onChange('');
|
textField: (params) => (
|
||||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
<TextField
|
||||||
|
{...params}
|
||||||
|
error={!!fieldState.error}
|
||||||
|
helperText={fieldState.error?.message || params.inputProps?.placeholder}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
onChange={(date: dayjs.Dayjs | null) => {
|
||||||
|
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => (
|
|
||||||
<TextField
|
|
||||||
{...params}
|
|
||||||
error={!!fieldState.error}
|
|
||||||
helperText={fieldState.error?.message || params.inputProps?.placeholder}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { joiResolver } from '@hookform/resolvers/joi';
|
import { joiResolver } from '@hookform/resolvers/joi';
|
||||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||||
import { Button, Slider, TextField } from '@mui/material';
|
import { Button, Slider, TextField } from '@mui/material';
|
||||||
import { DatePicker } from '@mui/x-date-pickers';
|
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||||
import { Custom } from '@reactive-resume/schema';
|
import { Custom } from 'schema';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
@ -60,13 +60,14 @@ const CustomModal: React.FC = () => {
|
|||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
|
||||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal['builder.sections.custom']);
|
const { open: isOpen, payload } = useAppSelector((state) => state.modal['builder.sections.custom']);
|
||||||
|
|
||||||
const path: string = get(payload, 'path', '');
|
const path: string = get(payload, 'path', 'sections.custom');
|
||||||
const item: FormData = get(payload, 'item', null);
|
const item: FormData = get(payload, 'item', null);
|
||||||
const isEditMode = useMemo(() => !!item, [item]);
|
const isEditMode = useMemo(() => !!item, [item]);
|
||||||
|
|
||||||
|
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||||
|
|
||||||
const addText = useMemo(() => t<string>('builder.common.actions.add', { token: heading }), [t, heading]);
|
const addText = useMemo(() => t<string>('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||||
const editText = useMemo(() => t<string>('builder.common.actions.edit', { token: heading }), [t, heading]);
|
const editText = useMemo(() => t<string>('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||||
|
|
||||||
@ -144,21 +145,23 @@ const CustomModal: React.FC = () => {
|
|||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState }) => (
|
render={({ field, fieldState }) => (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...field}
|
|
||||||
openTo="year"
|
openTo="year"
|
||||||
|
inputRef={field.ref}
|
||||||
label={t<string>('builder.common.form.start-date.label')}
|
label={t<string>('builder.common.form.start-date.label')}
|
||||||
|
value={dayjs(field.value)}
|
||||||
views={['year', 'month', 'day']}
|
views={['year', 'month', 'day']}
|
||||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
slots={{
|
||||||
isEmpty(keyboardInputValue) && field.onChange('');
|
textField: (params) => (
|
||||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
<TextField
|
||||||
|
{...params}
|
||||||
|
error={!!fieldState.error}
|
||||||
|
helperText={fieldState.error?.message || params.inputProps?.placeholder}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
onChange={(date: dayjs.Dayjs | null) => {
|
||||||
|
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => (
|
|
||||||
<TextField
|
|
||||||
{...params}
|
|
||||||
error={!!fieldState.error}
|
|
||||||
helperText={fieldState.error?.message || params.inputProps?.placeholder}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -168,21 +171,23 @@ const CustomModal: React.FC = () => {
|
|||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState }) => (
|
render={({ field, fieldState }) => (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...field}
|
|
||||||
openTo="year"
|
openTo="year"
|
||||||
|
inputRef={field.ref}
|
||||||
label={t<string>('builder.common.form.end-date.label')}
|
label={t<string>('builder.common.form.end-date.label')}
|
||||||
|
value={dayjs(field.value)}
|
||||||
views={['year', 'month', 'day']}
|
views={['year', 'month', 'day']}
|
||||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
slots={{
|
||||||
isEmpty(keyboardInputValue) && field.onChange('');
|
textField: (params) => (
|
||||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
<TextField
|
||||||
|
{...params}
|
||||||
|
error={!!fieldState.error}
|
||||||
|
helperText={fieldState.error?.message || t<string>('builder.common.form.end-date.help-text')}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
onChange={(date: dayjs.Dayjs | null) => {
|
||||||
|
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => (
|
|
||||||
<TextField
|
|
||||||
{...params}
|
|
||||||
error={!!fieldState.error}
|
|
||||||
helperText={fieldState.error?.message || 'Leave this field blank, if still present'}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -260,9 +265,9 @@ const CustomModal: React.FC = () => {
|
|||||||
multiline
|
multiline
|
||||||
minRows={3}
|
minRows={3}
|
||||||
maxRows={6}
|
maxRows={6}
|
||||||
label={t<string>('builder.common.form.summary.label')}
|
|
||||||
className="col-span-2"
|
className="col-span-2"
|
||||||
error={!!fieldState.error}
|
error={!!fieldState.error}
|
||||||
|
label={t<string>('builder.common.form.summary.label')}
|
||||||
helperText={fieldState.error?.message || <MarkdownSupported />}
|
helperText={fieldState.error?.message || <MarkdownSupported />}
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { joiResolver } from '@hookform/resolvers/joi';
|
import { joiResolver } from '@hookform/resolvers/joi';
|
||||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||||
import { Button, TextField } from '@mui/material';
|
import { Button, TextField } from '@mui/material';
|
||||||
import { DatePicker } from '@mui/x-date-pickers';
|
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||||
import { Education, SectionPath } from '@reactive-resume/schema';
|
import { Education, SectionPath } from 'schema';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
@ -167,21 +167,23 @@ const EducationModal: React.FC = () => {
|
|||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState }) => (
|
render={({ field, fieldState }) => (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...field}
|
|
||||||
openTo="year"
|
openTo="year"
|
||||||
|
inputRef={field.ref}
|
||||||
label={t<string>('builder.common.form.start-date.label')}
|
label={t<string>('builder.common.form.start-date.label')}
|
||||||
|
value={dayjs(field.value)}
|
||||||
views={['year', 'month', 'day']}
|
views={['year', 'month', 'day']}
|
||||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
slots={{
|
||||||
isEmpty(keyboardInputValue) && field.onChange('');
|
textField: (params) => (
|
||||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
<TextField
|
||||||
|
{...params}
|
||||||
|
error={!!fieldState.error}
|
||||||
|
helperText={fieldState.error?.message || params.inputProps?.placeholder}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
onChange={(date: dayjs.Dayjs | null) => {
|
||||||
|
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => (
|
|
||||||
<TextField
|
|
||||||
{...params}
|
|
||||||
error={!!fieldState.error}
|
|
||||||
helperText={fieldState.error?.message || params.inputProps?.placeholder}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -191,21 +193,23 @@ const EducationModal: React.FC = () => {
|
|||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState }) => (
|
render={({ field, fieldState }) => (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...field}
|
|
||||||
openTo="year"
|
openTo="year"
|
||||||
|
inputRef={field.ref}
|
||||||
label={t<string>('builder.common.form.end-date.label')}
|
label={t<string>('builder.common.form.end-date.label')}
|
||||||
|
value={dayjs(field.value)}
|
||||||
views={['year', 'month', 'day']}
|
views={['year', 'month', 'day']}
|
||||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
slots={{
|
||||||
isEmpty(keyboardInputValue) && field.onChange('');
|
textField: (params) => (
|
||||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
<TextField
|
||||||
|
{...params}
|
||||||
|
error={!!fieldState.error}
|
||||||
|
helperText={fieldState.error?.message || t<string>('builder.common.form.end-date.help-text')}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
onChange={(date: dayjs.Dayjs | null) => {
|
||||||
|
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => (
|
|
||||||
<TextField
|
|
||||||
{...params}
|
|
||||||
error={!!fieldState.error}
|
|
||||||
helperText={fieldState.error?.message || t<string>('builder.common.form.end-date.help-text')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { joiResolver } from '@hookform/resolvers/joi';
|
import { joiResolver } from '@hookform/resolvers/joi';
|
||||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||||
import { Button, TextField } from '@mui/material';
|
import { Button, TextField } from '@mui/material';
|
||||||
import { Interest, SectionPath } from '@reactive-resume/schema';
|
import { Interest, SectionPath } from 'schema';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { joiResolver } from '@hookform/resolvers/joi';
|
import { joiResolver } from '@hookform/resolvers/joi';
|
||||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||||
import { Button, Slider, TextField } from '@mui/material';
|
import { Button, Slider, TextField } from '@mui/material';
|
||||||
import { Language, SectionPath } from '@reactive-resume/schema';
|
import { Language, SectionPath } from 'schema';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { joiResolver } from '@hookform/resolvers/joi';
|
import { joiResolver } from '@hookform/resolvers/joi';
|
||||||
import { Add, AlternateEmail, DriveFileRenameOutline } from '@mui/icons-material';
|
import { Add, AlternateEmail, DriveFileRenameOutline } from '@mui/icons-material';
|
||||||
import { Button, TextField } from '@mui/material';
|
import { Button, TextField } from '@mui/material';
|
||||||
import { Profile } from '@reactive-resume/schema';
|
import { Profile } from 'schema';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { joiResolver } from '@hookform/resolvers/joi';
|
import { joiResolver } from '@hookform/resolvers/joi';
|
||||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||||
import { Button, TextField } from '@mui/material';
|
import { Button, TextField } from '@mui/material';
|
||||||
import { DatePicker } from '@mui/x-date-pickers';
|
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||||
import { Project, SectionPath } from '@reactive-resume/schema';
|
import { Project, SectionPath } from 'schema';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
@ -137,21 +137,23 @@ const ProjectModal: React.FC = () => {
|
|||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState }) => (
|
render={({ field, fieldState }) => (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...field}
|
|
||||||
openTo="year"
|
openTo="year"
|
||||||
|
inputRef={field.ref}
|
||||||
label={t<string>('builder.common.form.start-date.label')}
|
label={t<string>('builder.common.form.start-date.label')}
|
||||||
|
value={dayjs(field.value)}
|
||||||
views={['year', 'month', 'day']}
|
views={['year', 'month', 'day']}
|
||||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
slots={{
|
||||||
isEmpty(keyboardInputValue) && field.onChange('');
|
textField: (params) => (
|
||||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
<TextField
|
||||||
|
{...params}
|
||||||
|
error={!!fieldState.error}
|
||||||
|
helperText={fieldState.error?.message || params.inputProps?.placeholder}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
onChange={(date: dayjs.Dayjs | null) => {
|
||||||
|
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => (
|
|
||||||
<TextField
|
|
||||||
{...params}
|
|
||||||
error={!!fieldState.error}
|
|
||||||
helperText={fieldState.error?.message || params.inputProps?.placeholder}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -161,21 +163,23 @@ const ProjectModal: React.FC = () => {
|
|||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState }) => (
|
render={({ field, fieldState }) => (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...field}
|
|
||||||
openTo="year"
|
openTo="year"
|
||||||
|
inputRef={field.ref}
|
||||||
label={t<string>('builder.common.form.end-date.label')}
|
label={t<string>('builder.common.form.end-date.label')}
|
||||||
|
value={dayjs(field.value)}
|
||||||
views={['year', 'month', 'day']}
|
views={['year', 'month', 'day']}
|
||||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
slots={{
|
||||||
isEmpty(keyboardInputValue) && field.onChange('');
|
textField: (params) => (
|
||||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
<TextField
|
||||||
|
{...params}
|
||||||
|
error={!!fieldState.error}
|
||||||
|
helperText={fieldState.error?.message || t<string>('builder.common.form.end-date.help-text')}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
onChange={(date: dayjs.Dayjs | null) => {
|
||||||
|
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => (
|
|
||||||
<TextField
|
|
||||||
{...params}
|
|
||||||
error={!!fieldState.error}
|
|
||||||
helperText={fieldState.error?.message || 'Leave this field blank, if still present'}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { joiResolver } from '@hookform/resolvers/joi';
|
import { joiResolver } from '@hookform/resolvers/joi';
|
||||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||||
import { Button, TextField } from '@mui/material';
|
import { Button, TextField } from '@mui/material';
|
||||||
import { DatePicker } from '@mui/x-date-pickers';
|
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||||
import { Publication, SectionPath } from '@reactive-resume/schema';
|
import { Publication, SectionPath } from 'schema';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
@ -128,21 +128,23 @@ const PublicationModal: React.FC = () => {
|
|||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState }) => (
|
render={({ field, fieldState }) => (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...field}
|
|
||||||
openTo="year"
|
openTo="year"
|
||||||
|
inputRef={field.ref}
|
||||||
label={t<string>('builder.common.form.date.label')}
|
label={t<string>('builder.common.form.date.label')}
|
||||||
|
value={dayjs(field.value)}
|
||||||
views={['year', 'month', 'day']}
|
views={['year', 'month', 'day']}
|
||||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
slots={{
|
||||||
isEmpty(keyboardInputValue) && field.onChange('');
|
textField: (params) => (
|
||||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
<TextField
|
||||||
|
{...params}
|
||||||
|
error={!!fieldState.error}
|
||||||
|
helperText={fieldState.error?.message || params.inputProps?.placeholder}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
onChange={(date: dayjs.Dayjs | null) => {
|
||||||
|
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => (
|
|
||||||
<TextField
|
|
||||||
{...params}
|
|
||||||
error={!!fieldState.error}
|
|
||||||
helperText={fieldState.error?.message || params.inputProps?.placeholder}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { joiResolver } from '@hookform/resolvers/joi';
|
import { joiResolver } from '@hookform/resolvers/joi';
|
||||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||||
import { Button, TextField } from '@mui/material';
|
import { Button, TextField } from '@mui/material';
|
||||||
import { Reference, SectionPath } from '@reactive-resume/schema';
|
import { Reference, SectionPath } from 'schema';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { joiResolver } from '@hookform/resolvers/joi';
|
import { joiResolver } from '@hookform/resolvers/joi';
|
||||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||||
import { Button, Slider, TextField } from '@mui/material';
|
import { Button, Slider, TextField } from '@mui/material';
|
||||||
import { SectionPath, Skill } from '@reactive-resume/schema';
|
import { SectionPath, Skill } from 'schema';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { joiResolver } from '@hookform/resolvers/joi';
|
import { joiResolver } from '@hookform/resolvers/joi';
|
||||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||||
import { Button, TextField } from '@mui/material';
|
import { Button, TextField } from '@mui/material';
|
||||||
import { DatePicker } from '@mui/x-date-pickers';
|
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||||
import { SectionPath, Volunteer } from '@reactive-resume/schema';
|
import { SectionPath, Volunteer } from 'schema';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
@ -134,21 +134,23 @@ const VolunteerModal: React.FC = () => {
|
|||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState }) => (
|
render={({ field, fieldState }) => (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...field}
|
|
||||||
openTo="year"
|
openTo="year"
|
||||||
|
inputRef={field.ref}
|
||||||
label={t<string>('builder.common.form.start-date.label')}
|
label={t<string>('builder.common.form.start-date.label')}
|
||||||
|
value={dayjs(field.value)}
|
||||||
views={['year', 'month', 'day']}
|
views={['year', 'month', 'day']}
|
||||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
slots={{
|
||||||
isEmpty(keyboardInputValue) && field.onChange('');
|
textField: (params) => (
|
||||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
<TextField
|
||||||
|
{...params}
|
||||||
|
error={!!fieldState.error}
|
||||||
|
helperText={fieldState.error?.message || params.inputProps?.placeholder}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
onChange={(date: dayjs.Dayjs | null) => {
|
||||||
|
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => (
|
|
||||||
<TextField
|
|
||||||
{...params}
|
|
||||||
error={!!fieldState.error}
|
|
||||||
helperText={fieldState.error?.message || params.inputProps?.placeholder}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -158,21 +160,23 @@ const VolunteerModal: React.FC = () => {
|
|||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState }) => (
|
render={({ field, fieldState }) => (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...field}
|
|
||||||
openTo="year"
|
openTo="year"
|
||||||
|
inputRef={field.ref}
|
||||||
label={t<string>('builder.common.form.end-date.label')}
|
label={t<string>('builder.common.form.end-date.label')}
|
||||||
|
value={dayjs(field.value)}
|
||||||
views={['year', 'month', 'day']}
|
views={['year', 'month', 'day']}
|
||||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
slots={{
|
||||||
isEmpty(keyboardInputValue) && field.onChange('');
|
textField: (params) => (
|
||||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
<TextField
|
||||||
|
{...params}
|
||||||
|
error={!!fieldState.error}
|
||||||
|
helperText={fieldState.error?.message || t<string>('builder.common.form.end-date.help-text')}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
onChange={(date: dayjs.Dayjs | null) => {
|
||||||
|
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => (
|
|
||||||
<TextField
|
|
||||||
{...params}
|
|
||||||
error={!!fieldState.error}
|
|
||||||
helperText={fieldState.error?.message || 'Leave this field blank, if still present'}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { joiResolver } from '@hookform/resolvers/joi';
|
import { joiResolver } from '@hookform/resolvers/joi';
|
||||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||||
import { Button, TextField } from '@mui/material';
|
import { Button, TextField } from '@mui/material';
|
||||||
import { DatePicker } from '@mui/x-date-pickers';
|
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||||
import { SectionPath, WorkExperience } from '@reactive-resume/schema';
|
import { WorkExperience } from 'schema';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
@ -20,8 +20,6 @@ import { addItem, editItem } from '@/store/resume/resumeSlice';
|
|||||||
|
|
||||||
type FormData = WorkExperience;
|
type FormData = WorkExperience;
|
||||||
|
|
||||||
const path: SectionPath = 'sections.work';
|
|
||||||
|
|
||||||
const defaultState: FormData = {
|
const defaultState: FormData = {
|
||||||
name: '',
|
name: '',
|
||||||
position: '',
|
position: '',
|
||||||
@ -50,14 +48,28 @@ const WorkModal: React.FC = () => {
|
|||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
const { open: isOpen, payload } = useAppSelector((state) => state.modal['builder.sections.work']);
|
||||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
const path: string = get(payload, 'path', 'sections.work');
|
||||||
|
|
||||||
const item: FormData = get(payload, 'item', null);
|
const item: FormData = get(payload, 'item', null);
|
||||||
|
|
||||||
|
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||||
|
|
||||||
const isEditMode = useMemo(() => !!item, [item]);
|
const isEditMode = useMemo(() => !!item, [item]);
|
||||||
|
|
||||||
const addText = useMemo(() => t<string>('builder.common.actions.add', { token: heading }), [t, heading]);
|
const addText = useMemo(
|
||||||
const editText = useMemo(() => t<string>('builder.common.actions.edit', { token: heading }), [t, heading]);
|
() =>
|
||||||
|
t<string>('builder.common.actions.add', {
|
||||||
|
token: t<string>(`builder.leftSidebar.${path}.heading`, { defaultValue: heading }),
|
||||||
|
}),
|
||||||
|
[t, heading]
|
||||||
|
);
|
||||||
|
const editText = useMemo(
|
||||||
|
() =>
|
||||||
|
t<string>('builder.common.actions.edit', {
|
||||||
|
token: t<string>(`builder.leftSidebar.${path}.heading`, { defaultValue: heading }),
|
||||||
|
}),
|
||||||
|
[t, heading]
|
||||||
|
);
|
||||||
|
|
||||||
const { reset, control, handleSubmit } = useForm<FormData>({
|
const { reset, control, handleSubmit } = useForm<FormData>({
|
||||||
defaultValues: defaultState,
|
defaultValues: defaultState,
|
||||||
@ -77,7 +89,7 @@ const WorkModal: React.FC = () => {
|
|||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
dispatch(
|
dispatch(
|
||||||
setModalState({
|
setModalState({
|
||||||
modal: `builder.${path}`,
|
modal: 'builder.sections.work',
|
||||||
state: { open: false },
|
state: { open: false },
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -107,7 +119,7 @@ const WorkModal: React.FC = () => {
|
|||||||
<TextField
|
<TextField
|
||||||
required
|
required
|
||||||
autoFocus
|
autoFocus
|
||||||
label={t<string>('builder.common.form.name.label')}
|
label={t<string>('builder.leftSidebar.sections.experience.form.name.label')}
|
||||||
error={!!fieldState.error}
|
error={!!fieldState.error}
|
||||||
helperText={fieldState.error?.message}
|
helperText={fieldState.error?.message}
|
||||||
{...field}
|
{...field}
|
||||||
@ -134,21 +146,23 @@ const WorkModal: React.FC = () => {
|
|||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState }) => (
|
render={({ field, fieldState }) => (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...field}
|
|
||||||
openTo="year"
|
openTo="year"
|
||||||
|
inputRef={field.ref}
|
||||||
label={t<string>('builder.common.form.start-date.label')}
|
label={t<string>('builder.common.form.start-date.label')}
|
||||||
|
value={dayjs(field.value)}
|
||||||
views={['year', 'month', 'day']}
|
views={['year', 'month', 'day']}
|
||||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
slots={{
|
||||||
isEmpty(keyboardInputValue) && field.onChange('');
|
textField: (params) => (
|
||||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
<TextField
|
||||||
|
{...params}
|
||||||
|
error={!!fieldState.error}
|
||||||
|
helperText={fieldState.error?.message || params.inputProps?.placeholder}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
onChange={(date: dayjs.Dayjs | null) => {
|
||||||
|
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => (
|
|
||||||
<TextField
|
|
||||||
{...params}
|
|
||||||
error={!!fieldState.error}
|
|
||||||
helperText={fieldState.error?.message || params.inputProps?.placeholder}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -158,21 +172,23 @@ const WorkModal: React.FC = () => {
|
|||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState }) => (
|
render={({ field, fieldState }) => (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...field}
|
|
||||||
openTo="year"
|
openTo="year"
|
||||||
|
inputRef={field.ref}
|
||||||
label={t<string>('builder.common.form.end-date.label')}
|
label={t<string>('builder.common.form.end-date.label')}
|
||||||
|
value={dayjs(field.value)}
|
||||||
views={['year', 'month', 'day']}
|
views={['year', 'month', 'day']}
|
||||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
slots={{
|
||||||
isEmpty(keyboardInputValue) && field.onChange('');
|
textField: (params) => (
|
||||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
<TextField
|
||||||
|
{...params}
|
||||||
|
error={!!fieldState.error}
|
||||||
|
helperText={fieldState.error?.message || t<string>('builder.common.form.end-date.help-text')}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
onChange={(date: dayjs.Dayjs | null) => {
|
||||||
|
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => (
|
|
||||||
<TextField
|
|
||||||
{...params}
|
|
||||||
error={!!fieldState.error}
|
|
||||||
helperText={fieldState.error?.message || t<string>('builder.common.form.end-date.help-text')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
import { joiResolver } from '@hookform/resolvers/joi';
|
import { joiResolver } from '@hookform/resolvers/joi';
|
||||||
import { Add } from '@mui/icons-material';
|
import { Add } from '@mui/icons-material';
|
||||||
import { Button, FormControlLabel, FormGroup, Switch, TextField } from '@mui/material';
|
import { Button, FormControlLabel, FormGroup, Switch, TextField } from '@mui/material';
|
||||||
import { Resume } from '@reactive-resume/schema';
|
import { Resume } from 'schema';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
import { useMutation } from 'react-query';
|
import { useMutation } from 'react-query';
|
||||||
|
|
||||||
import BaseModal from '@/components/shared/BaseModal';
|
import BaseModal from '@/components/shared/BaseModal';
|
||||||
@ -66,15 +65,10 @@ const CreateResumeModal: React.FC = () => {
|
|||||||
}, [name, setValue]);
|
}, [name, setValue]);
|
||||||
|
|
||||||
const onSubmit = async ({ name, slug, isPublic }: FormData) => {
|
const onSubmit = async ({ name, slug, isPublic }: FormData) => {
|
||||||
try {
|
await mutateAsync({ name, slug, public: isPublic });
|
||||||
await mutateAsync({ name, slug, public: isPublic });
|
await queryClient.invalidateQueries(RESUMES_QUERY);
|
||||||
|
|
||||||
await queryClient.invalidateQueries(RESUMES_QUERY);
|
handleClose();
|
||||||
|
|
||||||
handleClose();
|
|
||||||
} catch (error: any) {
|
|
||||||
toast.error(error.message);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Code, ImportExport, LinkedIn, TrackChanges, UploadFile } from '@mui/icons-material';
|
import { Code, ImportExport, LinkedIn, TrackChanges, UploadFile } from '@mui/icons-material';
|
||||||
import { Button, Divider } from '@mui/material';
|
import { Button, Divider } from '@mui/material';
|
||||||
import { Integration, Resume } from '@reactive-resume/schema';
|
import { Integration, Resume } from 'schema';
|
||||||
import { Trans, useTranslation } from 'next-i18next';
|
import { Trans, useTranslation } from 'next-i18next';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
@ -68,8 +68,8 @@ const ImportExternalModal: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await mutateAsync({ integration, file });
|
await mutateAsync({ integration, file });
|
||||||
|
|
||||||
queryClient.invalidateQueries(RESUMES_QUERY);
|
queryClient.invalidateQueries(RESUMES_QUERY);
|
||||||
|
|
||||||
handleClose();
|
handleClose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { joiResolver } from '@hookform/resolvers/joi';
|
import { joiResolver } from '@hookform/resolvers/joi';
|
||||||
import { DriveFileRenameOutline } from '@mui/icons-material';
|
import { DriveFileRenameOutline } from '@mui/icons-material';
|
||||||
import { Button, TextField } from '@mui/material';
|
import { Button, TextField } from '@mui/material';
|
||||||
import { Resume } from '@reactive-resume/schema';
|
import { Resume } from 'schema';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import noop from 'lodash/noop';
|
import noop from 'lodash/noop';
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import ForgotPasswordModal from './auth/ForgotPasswordModal';
|
|||||||
import LoginModal from './auth/LoginModal';
|
import LoginModal from './auth/LoginModal';
|
||||||
import RegisterModal from './auth/RegisterModal';
|
import RegisterModal from './auth/RegisterModal';
|
||||||
import ResetPasswordModal from './auth/ResetPasswordModal';
|
import ResetPasswordModal from './auth/ResetPasswordModal';
|
||||||
|
import UserProfileModal from './auth/UserProfileModal';
|
||||||
import AwardModal from './builder/sections/AwardModal';
|
import AwardModal from './builder/sections/AwardModal';
|
||||||
import CertificateModal from './builder/sections/CertificateModal';
|
import CertificateModal from './builder/sections/CertificateModal';
|
||||||
import CustomModal from './builder/sections/CustomModal';
|
import CustomModal from './builder/sections/CustomModal';
|
||||||
@ -49,6 +50,7 @@ const ModalWrapper: React.FC = () => {
|
|||||||
<RegisterModal />
|
<RegisterModal />
|
||||||
<ForgotPasswordModal />
|
<ForgotPasswordModal />
|
||||||
<ResetPasswordModal />
|
<ResetPasswordModal />
|
||||||
|
<UserProfileModal />
|
||||||
|
|
||||||
{/* Dashboard */}
|
{/* Dashboard */}
|
||||||
<CreateResumeModal />
|
<CreateResumeModal />
|
||||||
|
|||||||
@ -35,6 +35,7 @@ const i18nConfig = {
|
|||||||
'or',
|
'or',
|
||||||
'pl',
|
'pl',
|
||||||
'pt',
|
'pt',
|
||||||
|
'pt-BR',
|
||||||
'ro',
|
'ro',
|
||||||
'ru',
|
'ru',
|
||||||
'sr',
|
'sr',
|
||||||
@ -47,6 +48,7 @@ const i18nConfig = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
nsSeparator: '.',
|
nsSeparator: '.',
|
||||||
|
returnNull: false,
|
||||||
localePath: path.resolve('./public/locales'),
|
localePath: path.resolve('./public/locales'),
|
||||||
ns: ['common', 'modals', 'landing', 'dashboard', 'builder'],
|
ns: ['common', 'modals', 'landing', 'dashboard', 'builder'],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,5 +2,5 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
siteUrl: 'https://rxresu.me',
|
siteUrl: 'https://rxresu.me',
|
||||||
changefreq: 'monthly',
|
changefreq: 'monthly',
|
||||||
generateRobotsTxt: true,
|
generateIndexSitemap: false,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@reactive-resume/client",
|
"name": "client",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "react-env --prefix PUBLIC -- next dev",
|
"dev": "react-env --prefix PUBLIC -- next dev",
|
||||||
"lint": "next lint --fix",
|
"lint": "next lint --fix",
|
||||||
@ -9,76 +9,74 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@beam-australia/react-env": "^3.1.1",
|
"@beam-australia/react-env": "^3.1.1",
|
||||||
"@date-io/dayjs": "^2.16.0",
|
"@emotion/css": "^11.11.0",
|
||||||
"@emotion/css": "^11.10.0",
|
"@emotion/react": "^11.11.1",
|
||||||
"@emotion/react": "^11.10.4",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@emotion/styled": "^11.10.4",
|
"@hello-pangea/dnd": "^16.2.0",
|
||||||
"@hello-pangea/dnd": "^16.0.1",
|
"@hookform/resolvers": "3.1.0",
|
||||||
"@hookform/resolvers": "2.9.9",
|
"@monaco-editor/react": "^4.5.1",
|
||||||
"@monaco-editor/react": "^4.4.6",
|
"@mui/icons-material": "^5.11.16",
|
||||||
"@mui/icons-material": "^5.10.9",
|
"@mui/lab": "^5.0.0-alpha.133",
|
||||||
"@mui/lab": "^5.0.0-alpha.103",
|
"@mui/material": "^5.13.4",
|
||||||
"@mui/material": "^5.10.9",
|
"@mui/system": "^5.13.2",
|
||||||
"@mui/system": "^5.10.9",
|
"@mui/x-date-pickers": "6.6.0",
|
||||||
"@mui/x-date-pickers": "5.0.4",
|
"@react-oauth/google": "^0.11.0",
|
||||||
"@next/env": "^12.3.1",
|
"@reduxjs/toolkit": "^1.9.5",
|
||||||
"@react-oauth/google": "^0.2.8",
|
"axios": "^1.4.0",
|
||||||
"@reduxjs/toolkit": "^1.8.6",
|
|
||||||
"axios": "^1.1.2",
|
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"dayjs": "^1.11.5",
|
"dayjs": "^1.11.8",
|
||||||
"downloadjs": "^1.4.7",
|
"downloadjs": "^1.4.7",
|
||||||
"joi": "^17.6.3",
|
"joi": "^17.9.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"md5-hex": "^4.0.0",
|
"md5-hex": "^4.0.0",
|
||||||
"monaco-editor": "^0.34.0",
|
"monaco-editor": "^0.39.0",
|
||||||
"nanoid": "^3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"next": "12.3.1",
|
"next": "13.4.4",
|
||||||
"next-i18next": "^12.1.0",
|
"next-i18next": "^13.3.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-colorful": "^5.6.1",
|
"react-colorful": "^5.6.1",
|
||||||
"react-dnd": "16.0.1",
|
"react-dnd": "16.0.1",
|
||||||
"react-dnd-html5-backend": "16.0.1",
|
"react-dnd-html5-backend": "16.0.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hook-form": "^7.37.0",
|
"react-hook-form": "^7.44.3",
|
||||||
"react-hot-toast": "2.4.0",
|
"react-hot-toast": "2.4.1",
|
||||||
"react-hotkeys-hook": "^3.4.7",
|
"react-icons": "^4.9.0",
|
||||||
"react-icons": "^4.6.0",
|
"react-markdown": "^8.0.7",
|
||||||
"react-markdown": "^8.0.3",
|
"react-query": "^3.39.3",
|
||||||
"react-query": "^3.39.2",
|
"react-redux": "^8.0.7",
|
||||||
"react-redux": "^8.0.4",
|
"react-zoom-pan-pinch": "^3.0.8",
|
||||||
"react-zoom-pan-pinch": "^2.1.3",
|
"redux": "^4.2.1",
|
||||||
"redux": "^4.2.0",
|
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"redux-saga": "^1.2.1",
|
"redux-saga": "^1.2.3",
|
||||||
"redux-undo": "^1.0.1",
|
"redux-undo": "^1.0.1",
|
||||||
|
"rehype-katex": "^6.0.3",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
"sharp": "^0.31.1",
|
"remark-math": "^5.1.1",
|
||||||
|
"sharp": "^0.32.1",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"webfontloader": "^1.6.28"
|
"webfontloader": "^1.6.28"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.19.3",
|
"@babel/core": "^7.22.1",
|
||||||
"@reactive-resume/schema": "workspace:*",
|
"schema": "workspace:*",
|
||||||
"eslint-plugin-unused-imports": "^2.0.0",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"@tailwindcss/typography": "^0.5.7",
|
|
||||||
"@types/downloadjs": "^1.4.3",
|
"@types/downloadjs": "^1.4.3",
|
||||||
"@types/lodash": "^4.14.186",
|
"@types/lodash": "^4.14.195",
|
||||||
"@types/node": "^18.11.0",
|
"@types/node": "^20.2.5",
|
||||||
"@types/react": "^18.0.21",
|
"@types/react": "^18.2.8",
|
||||||
"@types/react-dom": "^18.0.6",
|
"@types/react-dom": "^18.2.4",
|
||||||
"@types/react-redux": "^7.1.24",
|
"@types/react-redux": "^7.1.25",
|
||||||
"@types/tailwindcss": "^3.0.11",
|
"@types/uuid": "^9.0.1",
|
||||||
"@types/uuid": "^8.3.4",
|
|
||||||
"@types/webfontloader": "^1.6.35",
|
"@types/webfontloader": "^1.6.35",
|
||||||
"autoprefixer": "^10.4.12",
|
"autoprefixer": "^10.4.14",
|
||||||
"csstype": "^3.1.1",
|
"csstype": "^3.1.2",
|
||||||
"eslint-config-next": "^12.3.1",
|
"eslint-config-next": "^13.4.4",
|
||||||
"eslint-plugin-tailwindcss": "^3.6.2",
|
"eslint-plugin-tailwindcss": "^3.12.1",
|
||||||
"next-sitemap": "^3.1.25",
|
"eslint-plugin-unused-imports": "^2.0.0",
|
||||||
"postcss": "^8.4.18",
|
"next-sitemap": "^4.1.3",
|
||||||
"sass": "^1.55.0",
|
"postcss": "^8.4.24",
|
||||||
"tailwindcss": "^3.1.8",
|
"sass": "^1.62.1",
|
||||||
"typescript": "^4.8.4"
|
"tailwindcss": "^3.3.2",
|
||||||
|
"typescript": "^5.1.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { Resume } from '@reactive-resume/schema';
|
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import { GetServerSideProps, NextPage } from 'next';
|
import { GetServerSideProps, NextPage } from 'next';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
@ -6,6 +5,7 @@ import { useTranslation } from 'next-i18next';
|
|||||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
|
import { Resume } from 'schema';
|
||||||
|
|
||||||
import Center from '@/components/build/Center/Center';
|
import Center from '@/components/build/Center/Center';
|
||||||
import LeftSidebar from '@/components/build/LeftSidebar/LeftSidebar';
|
import LeftSidebar from '@/components/build/LeftSidebar/LeftSidebar';
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Download, Downloading } from '@mui/icons-material';
|
import { Download, Downloading } from '@mui/icons-material';
|
||||||
import { ButtonBase } from '@mui/material';
|
import { ButtonBase } from '@mui/material';
|
||||||
import { Resume } from '@reactive-resume/schema';
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import download from 'downloadjs';
|
import download from 'downloadjs';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
@ -12,8 +12,10 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { useMutation, useQuery } from 'react-query';
|
import { useMutation, useQuery } from 'react-query';
|
||||||
|
import { Resume } from 'schema';
|
||||||
|
|
||||||
import Page from '@/components/build/Center/Page';
|
import Page from '@/components/build/Center/Page';
|
||||||
|
import { DEFAULT_ERROR_MESSAGE } from '@/constants/index';
|
||||||
import { ServerError } from '@/services/axios';
|
import { ServerError } from '@/services/axios';
|
||||||
import { printResumeAsPdf, PrintResumeAsPdfParams } from '@/services/printer';
|
import { printResumeAsPdf, PrintResumeAsPdfParams } from '@/services/printer';
|
||||||
import { fetchResumeByIdentifier } from '@/services/resume';
|
import { fetchResumeByIdentifier } from '@/services/resume';
|
||||||
@ -60,10 +62,12 @@ const Preview: NextPage<Props> = ({ username, slug, resume: initialData }) => {
|
|||||||
}, [dispatch, initialData]);
|
}, [dispatch, initialData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isEmpty(resume) && router.locale !== resume.metadata.locale) {
|
const locale = get(resume, 'metadata.locale', 'en');
|
||||||
|
|
||||||
|
if (!isEmpty(resume) && router.locale !== locale) {
|
||||||
const { pathname, asPath, query } = router;
|
const { pathname, asPath, query } = router;
|
||||||
|
|
||||||
router.push({ pathname, query }, asPath, { locale: resume.metadata.locale });
|
router.push({ pathname, query }, asPath, { locale });
|
||||||
}
|
}
|
||||||
}, [resume, router]);
|
}, [resume, router]);
|
||||||
|
|
||||||
@ -96,11 +100,13 @@ const Preview: NextPage<Props> = ({ username, slug, resume: initialData }) => {
|
|||||||
|
|
||||||
const handleDownload = async () => {
|
const handleDownload = async () => {
|
||||||
try {
|
try {
|
||||||
const url = await mutateAsync({ username, slug });
|
const updatedAt = get(resume, 'updatedAt');
|
||||||
|
|
||||||
|
const url = await mutateAsync({ username, slug, lastUpdated: dayjs(updatedAt).unix().toString() });
|
||||||
|
|
||||||
download(url);
|
download(url);
|
||||||
} catch {
|
} catch {
|
||||||
toast.error('Something went wrong, please try again later.');
|
toast.error(DEFAULT_ERROR_MESSAGE);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { Resume } from '@reactive-resume/schema';
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
@ -6,6 +5,7 @@ import { GetServerSideProps, NextPage } from 'next';
|
|||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
import { Resume } from 'schema';
|
||||||
|
|
||||||
import Page from '@/components/build/Center/Page';
|
import Page from '@/components/build/Center/Page';
|
||||||
import { fetchResumeByIdentifier } from '@/services/resume';
|
import { fetchResumeByIdentifier } from '@/services/resume';
|
||||||
@ -27,7 +27,7 @@ type Props = {
|
|||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps<Props | Promise<Props>, QueryParams> = async ({
|
export const getServerSideProps: GetServerSideProps<Props | Promise<Props>, QueryParams> = async ({
|
||||||
query,
|
query,
|
||||||
locale,
|
locale = 'en',
|
||||||
}) => {
|
}) => {
|
||||||
const { username, slug, secretKey } = query as QueryParams;
|
const { username, slug, secretKey } = query as QueryParams;
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ export const getServerSideProps: GetServerSideProps<Props | Promise<Props>, Quer
|
|||||||
if (isEmpty(secretKey)) throw new Error('There is no secret key!');
|
if (isEmpty(secretKey)) throw new Error('There is no secret key!');
|
||||||
|
|
||||||
const resume = await fetchResumeByIdentifier({ username, slug, options: { secretKey } });
|
const resume = await fetchResumeByIdentifier({ username, slug, options: { secretKey } });
|
||||||
const displayLocale = resume.metadata.locale || locale || 'en';
|
const displayLocale = get(resume, 'metadata.locale') ?? locale;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import '@/styles/globals.scss';
|
import '@/styles/globals.scss';
|
||||||
|
|
||||||
import env from '@beam-australia/react-env';
|
import env from '@beam-australia/react-env';
|
||||||
import DayjsAdapter from '@date-io/dayjs';
|
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
||||||
import { LocalizationProvider } from '@mui/x-date-pickers';
|
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
||||||
import { GoogleOAuthProvider } from '@react-oauth/google';
|
import { GoogleOAuthProvider } from '@react-oauth/google';
|
||||||
import type { AppProps } from 'next/app';
|
import type { AppProps } from 'next/app';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
@ -21,7 +21,7 @@ import WrapperRegistry from '@/wrappers/index';
|
|||||||
const App = ({ Component, pageProps }: AppProps): JSX.Element => (
|
const App = ({ Component, pageProps }: AppProps): JSX.Element => (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>Reactive Resume</title>
|
<title>Reactive Resume | A free and open source resume builder</title>
|
||||||
|
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
@ -32,7 +32,7 @@ const App = ({ Component, pageProps }: AppProps): JSX.Element => (
|
|||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<ReduxProvider store={store}>
|
<ReduxProvider store={store}>
|
||||||
<LocalizationProvider dateAdapter={DayjsAdapter}>
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
<PersistGate loading={null} persistor={persistor}>
|
<PersistGate loading={null} persistor={persistor}>
|
||||||
<GoogleOAuthProvider clientId={env('GOOGLE_CLIENT_ID')}>
|
<GoogleOAuthProvider clientId={env('GOOGLE_CLIENT_ID')}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
|
|||||||
@ -1,15 +1,69 @@
|
|||||||
import { NextPage } from 'next';
|
import { NextPage } from 'next';
|
||||||
import NextDocument, { DocumentContext, Head, Html, Main, NextScript } from 'next/document';
|
import NextDocument, { DocumentContext, Head, Html, Main, NextScript } from 'next/document';
|
||||||
|
import Script from 'next/script';
|
||||||
|
|
||||||
const Document: NextPage = () => (
|
const Document: NextPage = () => (
|
||||||
<Html>
|
<Html lang="en">
|
||||||
<Head />
|
<Head>
|
||||||
|
<Script
|
||||||
|
id="google-tag-manager"
|
||||||
|
type="text/javascript"
|
||||||
|
strategy="afterInteractive"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','GTM-M9DK4S4');`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Script
|
||||||
|
id="logo-schema"
|
||||||
|
type="application/ld+json"
|
||||||
|
strategy="afterInteractive"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "Organization",
|
||||||
|
"url": "https://rxresu.me",
|
||||||
|
"logo": "https://rxresu.me/images/logos/logo.svg"
|
||||||
|
}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Script
|
||||||
|
id="software-application-schema"
|
||||||
|
type="application/ld+json"
|
||||||
|
strategy="afterInteractive"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "SoftwareApplication",
|
||||||
|
"name": "Reactive Resume",
|
||||||
|
"operatingSystem": "Windows, macOS, Linux, Android, iOS",
|
||||||
|
"applicationCategory": "BrowserApplication",
|
||||||
|
"aggregateRating": {
|
||||||
|
"@type": "AggregateRating",
|
||||||
|
"ratingValue": "5",
|
||||||
|
"ratingCount": "11394"
|
||||||
|
},
|
||||||
|
"offers": {
|
||||||
|
"@type": "Offer",
|
||||||
|
"price": "0"
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<Main />
|
<Main />
|
||||||
<NextScript />
|
<NextScript />
|
||||||
|
|
||||||
<script src="/__ENV.js" />
|
<script src="/__ENV.js" />
|
||||||
|
|
||||||
|
<noscript
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `<iframe src="https://www.googletagmanager.com/ns.html?id=GTM-M9DK4S4" height="0" width="0" style="display:none;visibility:hidden"></iframe>`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</body>
|
</body>
|
||||||
</Html>
|
</Html>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -17,13 +17,11 @@ import { fetchResumes } from '@/services/resume';
|
|||||||
import { useAppDispatch } from '@/store/hooks';
|
import { useAppDispatch } from '@/store/hooks';
|
||||||
import styles from '@/styles/pages/Dashboard.module.scss';
|
import styles from '@/styles/pages/Dashboard.module.scss';
|
||||||
|
|
||||||
export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => {
|
export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => ({
|
||||||
return {
|
props: {
|
||||||
props: {
|
...(await serverSideTranslations(locale, ['common', 'modals', 'dashboard'])),
|
||||||
...(await serverSideTranslations(locale, ['common', 'modals', 'dashboard'])),
|
},
|
||||||
},
|
});
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const Dashboard: NextPage = () => {
|
const Dashboard: NextPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -48,9 +46,7 @@ const Dashboard: NextPage = () => {
|
|||||||
|
|
||||||
<header>
|
<header>
|
||||||
<Link href="/">
|
<Link href="/">
|
||||||
<a>
|
<Logo size={40} />
|
||||||
<Logo size={40} />
|
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Avatar size={40} />
|
<Avatar size={40} />
|
||||||
@ -58,15 +54,15 @@ const Dashboard: NextPage = () => {
|
|||||||
|
|
||||||
<main className={styles.resumes}>
|
<main className={styles.resumes}>
|
||||||
<ResumeCard
|
<ResumeCard
|
||||||
modal="dashboard.create-resume"
|
|
||||||
icon={Add}
|
icon={Add}
|
||||||
|
modal="dashboard.create-resume"
|
||||||
title={t<string>('dashboard.create-resume.title')}
|
title={t<string>('dashboard.create-resume.title')}
|
||||||
subtitle={t<string>('dashboard.create-resume.subtitle')}
|
subtitle={t<string>('dashboard.create-resume.subtitle')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ResumeCard
|
<ResumeCard
|
||||||
modal="dashboard.import-external"
|
|
||||||
icon={ImportExport}
|
icon={ImportExport}
|
||||||
|
modal="dashboard.import-external"
|
||||||
title={t<string>('dashboard.import-external.title')}
|
title={t<string>('dashboard.import-external.title')}
|
||||||
subtitle={t<string>('dashboard.import-external.subtitle')}
|
subtitle={t<string>('dashboard.import-external.subtitle')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -20,15 +20,13 @@ import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
|||||||
import { setModalState } from '@/store/modal/modalSlice';
|
import { setModalState } from '@/store/modal/modalSlice';
|
||||||
import styles from '@/styles/pages/Home.module.scss';
|
import styles from '@/styles/pages/Home.module.scss';
|
||||||
|
|
||||||
import { DIGITALOCEAN_URL, DOCS_URL, DONATION_URL, GITHUB_URL } from '../constants';
|
import { DIGITALOCEAN_URL, DOCS_URL, DONATION_URL, GITHUB_URL, REDDIT_URL } from '../constants';
|
||||||
|
|
||||||
export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => {
|
export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => ({
|
||||||
return {
|
props: {
|
||||||
props: {
|
...(await serverSideTranslations(locale, ['common', 'modals', 'landing'])),
|
||||||
...(await serverSideTranslations(locale, ['common', 'modals', 'landing'])),
|
},
|
||||||
},
|
});
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const Home: NextPage = () => {
|
const Home: NextPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -39,11 +37,8 @@ const Home: NextPage = () => {
|
|||||||
const isLoggedIn = useAppSelector((state) => state.auth.isLoggedIn);
|
const isLoggedIn = useAppSelector((state) => state.auth.isLoggedIn);
|
||||||
|
|
||||||
const handleLogin = () => dispatch(setModalState({ modal: 'auth.login', state: { open: true } }));
|
const handleLogin = () => dispatch(setModalState({ modal: 'auth.login', state: { open: true } }));
|
||||||
|
|
||||||
const handleRegister = () => dispatch(setModalState({ modal: 'auth.register', state: { open: true } }));
|
const handleRegister = () => dispatch(setModalState({ modal: 'auth.register', state: { open: true } }));
|
||||||
|
|
||||||
const handleToggle = () => dispatch(setTheme({ theme: theme === 'light' ? 'dark' : 'light' }));
|
const handleToggle = () => dispatch(setTheme({ theme: theme === 'light' ? 'dark' : 'light' }));
|
||||||
|
|
||||||
const handleLogout = () => dispatch(logout());
|
const handleLogout = () => dispatch(logout());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -117,7 +112,13 @@ const Home: NextPage = () => {
|
|||||||
<div className={styles.screenshots}>
|
<div className={styles.screenshots}>
|
||||||
{screenshots.map(({ src, alt }) => (
|
{screenshots.map(({ src, alt }) => (
|
||||||
<a key={src} href={src} className={styles.image} target="_blank" rel="noreferrer">
|
<a key={src} href={src} className={styles.image} target="_blank" rel="noreferrer">
|
||||||
<Image src={src} alt={alt} layout="fill" objectFit="cover" />
|
<Image
|
||||||
|
fill
|
||||||
|
src={src}
|
||||||
|
alt={alt}
|
||||||
|
className="object-cover"
|
||||||
|
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -176,6 +177,12 @@ const Home: NextPage = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a href={REDDIT_URL} target="_blank" rel="noreferrer">
|
||||||
|
<Button variant="text" startIcon={<LinkIcon />}>
|
||||||
|
{t<string>('landing.links.links.reddit')}
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
|
||||||
<a href={DONATION_URL} target="_blank" rel="noreferrer">
|
<a href={DONATION_URL} target="_blank" rel="noreferrer">
|
||||||
<Button variant="text" startIcon={<LinkIcon />}>
|
<Button variant="text" startIcon={<LinkIcon />}>
|
||||||
{t<string>('landing.links.links.donate')}
|
{t<string>('landing.links.links.donate')}
|
||||||
@ -186,7 +193,13 @@ const Home: NextPage = () => {
|
|||||||
|
|
||||||
<section className={styles.section}>
|
<section className={styles.section}>
|
||||||
<a href={DIGITALOCEAN_URL} target="_blank" rel="noreferrer">
|
<a href={DIGITALOCEAN_URL} target="_blank" rel="noreferrer">
|
||||||
<Image src={`/images/sponsors/${theme=="dark"?"digitalocean":"digitaloceanLight"}.svg`} alt="Powered By DigitalOcean" width={200} height={40} />
|
<Image
|
||||||
|
src={`/images/sponsors/${theme == 'dark' ? 'digitalocean' : 'digitaloceanLight'}.svg`}
|
||||||
|
style={{ width: 200, height: 40, objectFit: 'contain' }}
|
||||||
|
alt="Powered By DigitalOcean"
|
||||||
|
width={200}
|
||||||
|
height={40}
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Download, Downloading } from '@mui/icons-material';
|
import { Download, Downloading } from '@mui/icons-material';
|
||||||
import { ButtonBase } from '@mui/material';
|
import { ButtonBase } from '@mui/material';
|
||||||
import { Resume } from '@reactive-resume/schema';
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import download from 'downloadjs';
|
import download from 'downloadjs';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
@ -10,8 +10,8 @@ import Link from 'next/link';
|
|||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
import { useMutation, useQuery } from 'react-query';
|
import { useMutation, useQuery } from 'react-query';
|
||||||
|
import { Resume } from 'schema';
|
||||||
|
|
||||||
import Page from '@/components/build/Center/Page';
|
import Page from '@/components/build/Center/Page';
|
||||||
import { ServerError } from '@/services/axios';
|
import { ServerError } from '@/services/axios';
|
||||||
@ -68,13 +68,13 @@ const Preview: NextPage<Props> = ({ shortId }) => {
|
|||||||
const layout: string[][][] = get(resume, 'metadata.layout', []);
|
const layout: string[][][] = get(resume, 'metadata.layout', []);
|
||||||
|
|
||||||
const handleDownload = async () => {
|
const handleDownload = async () => {
|
||||||
try {
|
const url = await mutateAsync({
|
||||||
const url = await mutateAsync({ username: resume.user.username, slug: resume.slug });
|
username: resume.user.username,
|
||||||
|
slug: resume.slug,
|
||||||
|
lastUpdated: dayjs(resume.updatedAt).unix().toString(),
|
||||||
|
});
|
||||||
|
|
||||||
download(url);
|
download(url);
|
||||||
} catch {
|
|
||||||
toast.error('Something went wrong, please try again later.');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"add": "አዲስ {{token}} ጨምር",
|
"add": "አዲስ {{token}} ጨምር",
|
||||||
"delete": "{{token}} አጥፋ",
|
"delete": "{{token}} አጥፋ",
|
||||||
|
"duplicate": "የተባዛ ክፍል",
|
||||||
"edit": "{{token}} አዘምን"
|
"edit": "{{token}} አዘምን"
|
||||||
},
|
},
|
||||||
"columns": {
|
"columns": {
|
||||||
@ -80,13 +81,13 @@
|
|||||||
"center-artboard": "መሃል የሰዕል ማሳያ",
|
"center-artboard": "መሃል የሰዕል ማሳያ",
|
||||||
"copy-link": "የስራ ልምድ ሰነዱን ሊንክ ቅዳ",
|
"copy-link": "የስራ ልምድ ሰነዱን ሊንክ ቅዳ",
|
||||||
"export-pdf": "PDF አውጣ",
|
"export-pdf": "PDF አውጣ",
|
||||||
|
"redo": "ድገም",
|
||||||
"toggle-orientation": "የገጽ አቀማመጥን ቀያይር",
|
"toggle-orientation": "የገጽ አቀማመጥን ቀያይር",
|
||||||
"toggle-page-break-line": "የገጽ መግቻ መስመርን ቀያይር",
|
"toggle-page-break-line": "የገጽ መግቻ መስመርን ቀያይር",
|
||||||
"toggle-sidebars": "የጎን ክፍሎችን ቀይር",
|
"toggle-sidebars": "የጎን ክፍሎችን ቀይር",
|
||||||
"zoom-in": "አቅርብ",
|
|
||||||
"zoom-out": "አሳንስ",
|
|
||||||
"undo": "ቀልብስ",
|
"undo": "ቀልብስ",
|
||||||
"redo": "ድገም"
|
"zoom-in": "አቅርብ",
|
||||||
|
"zoom-out": "አሳንስ"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
@ -114,6 +115,9 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"photo-filters": "የፎቶ ማጣሪያዎች"
|
"photo-filters": "የፎቶ ማጣሪያዎች"
|
||||||
},
|
},
|
||||||
|
"birthdate": {
|
||||||
|
"label": "የትውልድ ቀን"
|
||||||
|
},
|
||||||
"heading": "መሰረታዊ ነገሮች",
|
"heading": "መሰረታዊ ነገሮች",
|
||||||
"headline": {
|
"headline": {
|
||||||
"label": "ርዕስ"
|
"label": "ርዕስ"
|
||||||
@ -121,9 +125,6 @@
|
|||||||
"name": {
|
"name": {
|
||||||
"label": "ሙሉ ስም"
|
"label": "ሙሉ ስም"
|
||||||
},
|
},
|
||||||
"birthdate": {
|
|
||||||
"label": "የትውልድ ቀን"
|
|
||||||
},
|
|
||||||
"photo-filters": {
|
"photo-filters": {
|
||||||
"effects": {
|
"effects": {
|
||||||
"border": {
|
"border": {
|
||||||
@ -174,6 +175,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"experience": {
|
||||||
|
"form": {
|
||||||
|
"name": {
|
||||||
|
"label": "Company Name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"location": {
|
"location": {
|
||||||
"address": {
|
"address": {
|
||||||
"label": "አድራሻ"
|
"label": "አድራሻ"
|
||||||
@ -264,14 +272,15 @@
|
|||||||
"button": "GitHub ጉዳዮች",
|
"button": "GitHub ጉዳዮች",
|
||||||
"heading": "ችግሮች? የባህሪ ጥያቄዎች?"
|
"heading": "ችግሮች? የባህሪ ጥያቄዎች?"
|
||||||
},
|
},
|
||||||
|
"docs": "ሰነዶች",
|
||||||
"donate": {
|
"donate": {
|
||||||
"body": "Reactive Resumeን መጠቀም ከወደዱ እባክዎን መተግበሪያው እንዲሰራ እና ያለ ማስታወቂያ ሁሌም በነፃ እንዲቀጥል በተቻለዎት መጠን ለመለገስ ያስቡበት።",
|
"body": "Reactive Resumeን መጠቀም ከወደዱ እባክዎን መተግበሪያው እንዲሰራ እና ያለ ማስታወቂያ ሁሌም በነፃ እንዲቀጥል በተቻለዎት መጠን ለመለገስ ያስቡበት።",
|
||||||
"button": "ቡና ይጋብዙኝ",
|
"button": "ቡና ይጋብዙኝ",
|
||||||
"heading": "ለ Reactive Resume ይለግሱ"
|
"heading": "ለ Reactive Resume ይለግሱ"
|
||||||
},
|
},
|
||||||
"github": "የምንጭ ኮድ",
|
"github": "የምንጭ ኮድ",
|
||||||
"docs": "ሰነዶች",
|
"heading": "አገናኞች",
|
||||||
"heading": "አገናኞች"
|
"reddit": "Reddit"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"global": {
|
"global": {
|
||||||
@ -290,14 +299,14 @@
|
|||||||
},
|
},
|
||||||
"heading": "ቅንብሮች",
|
"heading": "ቅንብሮች",
|
||||||
"page": {
|
"page": {
|
||||||
"format": {
|
|
||||||
"primary": "የወረቀት መጠን",
|
|
||||||
"secondary": "ከቆመበት ቀጥል ገጾችዎ ልኬቶችን ይወስናል"
|
|
||||||
},
|
|
||||||
"break-line": {
|
"break-line": {
|
||||||
"primary": "መስመር መቁረጫ",
|
"primary": "መስመር መቁረጫ",
|
||||||
"secondary": "የA4 ገጽ ቁመትን ለመለየት በሁሉም ገጾች ላይ መስመር አሳይ"
|
"secondary": "የA4 ገጽ ቁመትን ለመለየት በሁሉም ገጾች ላይ መስመር አሳይ"
|
||||||
},
|
},
|
||||||
|
"format": {
|
||||||
|
"primary": "የወረቀት መጠን",
|
||||||
|
"secondary": "ከቆመበት ቀጥል ገጾችዎ ልኬቶችን ይወስናል"
|
||||||
|
},
|
||||||
"heading": "ገጽ",
|
"heading": "ገጽ",
|
||||||
"orientation": {
|
"orientation": {
|
||||||
"disabled": "አንድ ገጽ ብቻ ሲኖር ምንም ተጽእኖ የለውም",
|
"disabled": "አንድ ገጽ ብቻ ሲኖር ምንም ተጽእኖ የለውም",
|
||||||
|
|||||||
@ -20,23 +20,24 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"heading": "አገናኞች",
|
"heading": "አገናኞች",
|
||||||
"links": {
|
"links": {
|
||||||
|
"docs": "ሰነዶች",
|
||||||
"donate": "ይለግሱ",
|
"donate": "ይለግሱ",
|
||||||
"github": "የምንጭ ኮድ",
|
"github": "የምንጭ ኮድ",
|
||||||
"docs": "ሰነዶች",
|
|
||||||
"privacy": "የግላዊነት መመሪያ",
|
"privacy": "የግላዊነት መመሪያ",
|
||||||
|
"reddit": "Reddit",
|
||||||
"service": "የአገልግሎት ውሎች"
|
"service": "የአገልግሎት ውሎች"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"screenshots": {
|
"screenshots": {
|
||||||
"heading": "የገጽ እይታዎች"
|
"heading": "የገጽ እይታዎች"
|
||||||
},
|
},
|
||||||
"testimonials": {
|
|
||||||
"heading": "ምስክሮች",
|
|
||||||
"body": "ጥሩም ይሁን መጥፎ፣ ስለ Reactive Resume እና ለእርስዎ እንዴት እንደነበረ አስተያየትዎን መስማት እፈልጋለሁ።<br/>በአለም ዙሪያ በተጠቃሚዎች የተላኩ አንዳንድ መልዕክቶች እነዚሁና",
|
|
||||||
"contact": "በዚህ በኩል ልታገኙኝ <1>ኢሜል</1> ትችላላችሁ ወይም <3>በድረ-ገጽ</3> ላይ ባለው የእውቂያ ቅጽ ማግኘት ይችላሉ።"
|
|
||||||
},
|
|
||||||
"summary": {
|
"summary": {
|
||||||
"body": "Reactive Resume የእርስዎን የስራ ታሪክ የመፍጠር፣ የማዘመን እና የማጋራት መደበኛ ተግባራትን እንደ 1 2 3 ቀላል ለማድረግ የተሰራ ነፃ እና በነጻ የሚገኝ የስራ ልምድ ሰነድ መገንቢያ ነው። በዚህ መገልገያ የተለያዩ የስራ ልምድ ሰነዶችን በመስራት፣ ከቀጣሪዎች ወይም ከጓደኞች ጋር በማስፈንጠሪያ ማጋራት እና እንደ PDF ማተም ፣ ሁሉንም በነጻ ፣ ምንም ማስታወቂያ ሳይኖር ፣ ምንም ክትትል ሳይደረግ ፣ የመረጃዎን ትክክለኛነት እና ግላዊነት ተጠብቆ ማከናወን ይችላሉ።",
|
"body": "Reactive Resume የእርስዎን የስራ ታሪክ የመፍጠር፣ የማዘመን እና የማጋራት መደበኛ ተግባራትን እንደ 1 2 3 ቀላል ለማድረግ የተሰራ ነፃ እና በነጻ የሚገኝ የስራ ልምድ ሰነድ መገንቢያ ነው። በዚህ መገልገያ የተለያዩ የስራ ልምድ ሰነዶችን በመስራት፣ ከቀጣሪዎች ወይም ከጓደኞች ጋር በማስፈንጠሪያ ማጋራት እና እንደ PDF ማተም ፣ ሁሉንም በነጻ ፣ ምንም ማስታወቂያ ሳይኖር ፣ ምንም ክትትል ሳይደረግ ፣ የመረጃዎን ትክክለኛነት እና ግላዊነት ተጠብቆ ማከናወን ይችላሉ።",
|
||||||
"heading": "ማጠቃለያ"
|
"heading": "ማጠቃለያ"
|
||||||
|
},
|
||||||
|
"testimonials": {
|
||||||
|
"body": "ጥሩም ይሁን መጥፎ፣ ስለ Reactive Resume እና ለእርስዎ እንዴት እንደነበረ አስተያየትዎን መስማት እፈልጋለሁ።<br/>በአለም ዙሪያ በተጠቃሚዎች የተላኩ አንዳንድ መልዕክቶች እነዚሁና",
|
||||||
|
"contact": "በዚህ በኩል ልታገኙኝ <1>ኢሜል</1> ትችላላችሁ ወይም <3>በድረ-ገጽ</3> ላይ ባለው የእውቂያ ቅጽ ማግኘት ይችላሉ።",
|
||||||
|
"heading": "ምስክሮች"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,6 +71,31 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"heading": "የይለፍ ቃልዎን ዳግም ያስጀምሩ"
|
"heading": "የይለፍ ቃልዎን ዳግም ያስጀምሩ"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"heading": "መለያህ",
|
||||||
|
"form": {
|
||||||
|
"avatar": {
|
||||||
|
"help-text": "የመገለጫ ሥዕልህን በ<1>ግራቫታር ማዘመን ትችላለህ</1>"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"label": "ሙሉ ስም"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"label": "የ ኢሜል አድራሻ",
|
||||||
|
"help-text": "በአሁኑ ጊዜ የኢሜል አድራሻዎን ማዘመን አይቻልም፣ እባክዎ ይልቁንስ አዲስ መለያ ይፍጠሩ።"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete-account": {
|
||||||
|
"heading": "መለያ እና ውሂብ ሰርዝ",
|
||||||
|
"body": "መለያህን፣ ዳታህን እና ሁሉንም ከቆመበት ቀጥል ለመሰረዝ በጽሑፍ ሳጥኑ ውስጥ \"{{keyword}}\" ተይብ እና ቁልፉን ተጫን። ይህ የማይቀለበስ እርምጃ መሆኑን እና ውሂብዎን እንደገና ማምጣት እንደማይቻል እባክዎ ልብ ይበሉ።",
|
||||||
|
"actions": {
|
||||||
|
"delete": "መለያ ሰርዝ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"save": "ለውጦችን አስቀምጥ"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"add": "إضافة {{token}} جديدة",
|
"add": "إضافة {{token}} جديدة",
|
||||||
"delete": "حذف {{token}}",
|
"delete": "حذف {{token}}",
|
||||||
|
"duplicate": "قسم مكرر",
|
||||||
"edit": "تحرير {{token}}"
|
"edit": "تحرير {{token}}"
|
||||||
},
|
},
|
||||||
"columns": {
|
"columns": {
|
||||||
@ -80,13 +81,13 @@
|
|||||||
"center-artboard": "لوحة الوسط",
|
"center-artboard": "لوحة الوسط",
|
||||||
"copy-link": "انسخ الرابط للسيرة الذاتية",
|
"copy-link": "انسخ الرابط للسيرة الذاتية",
|
||||||
"export-pdf": "تصدير PDF",
|
"export-pdf": "تصدير PDF",
|
||||||
|
"redo": "إعادة",
|
||||||
"toggle-orientation": "تبديل اتجاه الصفحة",
|
"toggle-orientation": "تبديل اتجاه الصفحة",
|
||||||
"toggle-page-break-line": "تبديل سطر الصفحة",
|
"toggle-page-break-line": "تبديل سطر الصفحة",
|
||||||
"toggle-sidebars": "تبديل الشريط الجانبي",
|
"toggle-sidebars": "تبديل الشريط الجانبي",
|
||||||
"zoom-in": "تكبير",
|
|
||||||
"zoom-out": "تصغير",
|
|
||||||
"undo": "الغاء التحميل",
|
"undo": "الغاء التحميل",
|
||||||
"redo": "إعادة"
|
"zoom-in": "تكبير",
|
||||||
|
"zoom-out": "تصغير"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
@ -114,6 +115,9 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"photo-filters": "فلاتر الصور"
|
"photo-filters": "فلاتر الصور"
|
||||||
},
|
},
|
||||||
|
"birthdate": {
|
||||||
|
"label": "تاريخ الميلاد"
|
||||||
|
},
|
||||||
"heading": "الأساسيات",
|
"heading": "الأساسيات",
|
||||||
"headline": {
|
"headline": {
|
||||||
"label": "العنوان الرئيسي"
|
"label": "العنوان الرئيسي"
|
||||||
@ -121,9 +125,6 @@
|
|||||||
"name": {
|
"name": {
|
||||||
"label": "الاسم الكامل"
|
"label": "الاسم الكامل"
|
||||||
},
|
},
|
||||||
"birthdate": {
|
|
||||||
"label": "تاريخ الميلاد"
|
|
||||||
},
|
|
||||||
"photo-filters": {
|
"photo-filters": {
|
||||||
"effects": {
|
"effects": {
|
||||||
"border": {
|
"border": {
|
||||||
@ -174,6 +175,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"experience": {
|
||||||
|
"form": {
|
||||||
|
"name": {
|
||||||
|
"label": "Company Name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"location": {
|
"location": {
|
||||||
"address": {
|
"address": {
|
||||||
"label": "العنوان"
|
"label": "العنوان"
|
||||||
@ -264,14 +272,15 @@
|
|||||||
"button": "صفحة المشاكل على \"GitHub\"",
|
"button": "صفحة المشاكل على \"GitHub\"",
|
||||||
"heading": "خلل برمجي؟ طلب وظائف ومميزات مخصصة؟"
|
"heading": "خلل برمجي؟ طلب وظائف ومميزات مخصصة؟"
|
||||||
},
|
},
|
||||||
|
"docs": "توثيق",
|
||||||
"donate": {
|
"donate": {
|
||||||
"body": "إذا أعجبك استخدام Resctive Resume ، فالرجاء التفكير في التبرع بأكبر قدر ممكن من أجل الحفاظ على استمرار التطبيق وتشغيله ، بدون إعلانات وبشكل مجاني إلى الأبد.",
|
"body": "إذا أعجبك استخدام Resctive Resume ، فالرجاء التفكير في التبرع بأكبر قدر ممكن من أجل الحفاظ على استمرار التطبيق وتشغيله ، بدون إعلانات وبشكل مجاني إلى الأبد.",
|
||||||
"button": "إشتر لي قهوة",
|
"button": "إشتر لي قهوة",
|
||||||
"heading": "تبرع الى Reactive Resume"
|
"heading": "تبرع الى Reactive Resume"
|
||||||
},
|
},
|
||||||
"github": "الشفرة المصدرية",
|
"github": "الشفرة المصدرية",
|
||||||
"docs": "توثيق",
|
"heading": "الروابط",
|
||||||
"heading": "الروابط"
|
"reddit": "رديت"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"global": {
|
"global": {
|
||||||
@ -290,14 +299,14 @@
|
|||||||
},
|
},
|
||||||
"heading": "الإعدادات",
|
"heading": "الإعدادات",
|
||||||
"page": {
|
"page": {
|
||||||
"format": {
|
|
||||||
"primary": "حجم الورق",
|
|
||||||
"secondary": "تحدد أبعاد صفحات سيرتك الذاتية"
|
|
||||||
},
|
|
||||||
"break-line": {
|
"break-line": {
|
||||||
"primary": "خط فاصل",
|
"primary": "خط فاصل",
|
||||||
"secondary": "اعرض خط في كل الصفحات لتحديد ارتفاع صفحة A4"
|
"secondary": "اعرض خط في كل الصفحات لتحديد ارتفاع صفحة A4"
|
||||||
},
|
},
|
||||||
|
"format": {
|
||||||
|
"primary": "حجم الورق",
|
||||||
|
"secondary": "تحدد أبعاد صفحات سيرتك الذاتية"
|
||||||
|
},
|
||||||
"heading": "صفحة",
|
"heading": "صفحة",
|
||||||
"orientation": {
|
"orientation": {
|
||||||
"disabled": "ليس له تأثير عندما تكون هناك صفحة واحدة فقط",
|
"disabled": "ليس له تأثير عندما تكون هناك صفحة واحدة فقط",
|
||||||
|
|||||||
@ -20,23 +20,24 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"heading": "الروابط",
|
"heading": "الروابط",
|
||||||
"links": {
|
"links": {
|
||||||
|
"docs": "توثيق",
|
||||||
"donate": "تبرّع",
|
"donate": "تبرّع",
|
||||||
"github": "الشفرة المصدرية",
|
"github": "الشفرة المصدرية",
|
||||||
"docs": "توثيق",
|
|
||||||
"privacy": "سياسة الخصوصية",
|
"privacy": "سياسة الخصوصية",
|
||||||
|
"reddit": "رديت",
|
||||||
"service": "شروط الإستخدام"
|
"service": "شروط الإستخدام"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"screenshots": {
|
"screenshots": {
|
||||||
"heading": "لقطات الشاشة"
|
"heading": "لقطات الشاشة"
|
||||||
},
|
},
|
||||||
"testimonials": {
|
|
||||||
"heading": "الآراء والتقييمات",
|
|
||||||
"body": "سواء أكان ذلك جيدًا أم سيئًا ، أود أن أسمع رأيك في \"السيرة الذاتية التفاعلية\" وكيف كانت التجربة بالنسبة لك.<br/>فيما يلي بعض الرسائل المرسلة بواسطة مستخدمين من جميع أنحاء العالم.",
|
|
||||||
"contact": "يمكنك التواصل معي من خلال <1> بريدي الإلكتروني</1> أو من خلال نموذج الاتصال الموجود على <3> موقع الويب الخاص بي</3>."
|
|
||||||
},
|
|
||||||
"summary": {
|
"summary": {
|
||||||
"body": "Reactive Resume هو منشئ سيرة ذاتية مجاني ومفتوح المصدر تم إنشاؤه لجعل المهام الروتينية لإنشاء وتحديث ومشاركة سيرتك الذاتية سهلة مثل 1 و 2 و 3. باستخدام هذا التطبيق ، يمكنك إنشاء سيرة ذاتية متعددة ومشاركتها مع جهات التوظيف أو الأصدقاء من خلال رابط فريد وطباعته كملف PDF ، كل ذلك مجانًا ، بدون إعلانات ، بدون تتبع ، دون فقدان سلامة وخصوصية بياناتك.",
|
"body": "Reactive Resume هو منشئ سيرة ذاتية مجاني ومفتوح المصدر تم إنشاؤه لجعل المهام الروتينية لإنشاء وتحديث ومشاركة سيرتك الذاتية سهلة مثل 1 و 2 و 3. باستخدام هذا التطبيق ، يمكنك إنشاء سيرة ذاتية متعددة ومشاركتها مع جهات التوظيف أو الأصدقاء من خلال رابط فريد وطباعته كملف PDF ، كل ذلك مجانًا ، بدون إعلانات ، بدون تتبع ، دون فقدان سلامة وخصوصية بياناتك.",
|
||||||
"heading": "الملخص"
|
"heading": "الملخص"
|
||||||
|
},
|
||||||
|
"testimonials": {
|
||||||
|
"body": "سواء أكان ذلك جيدًا أم سيئًا ، أود أن أسمع رأيك في \"السيرة الذاتية التفاعلية\" وكيف كانت التجربة بالنسبة لك.<br/>فيما يلي بعض الرسائل المرسلة بواسطة مستخدمين من جميع أنحاء العالم.",
|
||||||
|
"contact": "يمكنك التواصل معي من خلال <1> بريدي الإلكتروني</1> أو من خلال نموذج الاتصال الموجود على <3> موقع الويب الخاص بي</3>.",
|
||||||
|
"heading": "الآراء والتقييمات"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,6 +71,31 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"heading": "إعادة تعيين كلمة المرور الخاصة بك"
|
"heading": "إعادة تعيين كلمة المرور الخاصة بك"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"heading": "الحساب الخاص بك",
|
||||||
|
"form": {
|
||||||
|
"avatar": {
|
||||||
|
"help-text": "يمكنك تحديث صورة ملفك الشخصي على <1> Gravatar</1>"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"label": "الاسم الكامل"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"label": "عنوان البريد الإلكتروني",
|
||||||
|
"help-text": "لا يمكن تحديث عنوان بريدك الإلكتروني في الوقت الحالي ، يرجى إنشاء حساب جديد بدلاً من ذلك."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete-account": {
|
||||||
|
"heading": "حذف الحساب والبيانات",
|
||||||
|
"body": "لحذف حسابك وبياناتك وجميع سيرتك الذاتية ، اكتب \"{{keyword}}\" في مربع النص وانقر على الزر. يرجى ملاحظة أن هذا إجراء لا رجوع فيه ولا يمكن استرداد بياناتك مرة أخرى.",
|
||||||
|
"actions": {
|
||||||
|
"delete": "حذف الحساب"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"save": "حفظ التغييرات"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"add": "Добави нов {{token}}",
|
"add": "Добави нов {{token}}",
|
||||||
"delete": "Изтрий {{token}}",
|
"delete": "Изтрий {{token}}",
|
||||||
|
"duplicate": "Дублиран раздел",
|
||||||
"edit": "Редакция на {{token}}"
|
"edit": "Редакция на {{token}}"
|
||||||
},
|
},
|
||||||
"columns": {
|
"columns": {
|
||||||
@ -80,13 +81,13 @@
|
|||||||
"center-artboard": "Централна табла",
|
"center-artboard": "Централна табла",
|
||||||
"copy-link": "Копирай линка в резюмето",
|
"copy-link": "Копирай линка в резюмето",
|
||||||
"export-pdf": "Експорт в PDF",
|
"export-pdf": "Експорт в PDF",
|
||||||
|
"redo": "Redo",
|
||||||
"toggle-orientation": "Превключване на ориентацията на страницата",
|
"toggle-orientation": "Превключване на ориентацията на страницата",
|
||||||
"toggle-page-break-line": "Линия за прекъсване на страницата",
|
"toggle-page-break-line": "Линия за прекъсване на страницата",
|
||||||
"toggle-sidebars": "Включване на страничната лента",
|
"toggle-sidebars": "Включване на страничната лента",
|
||||||
"zoom-in": "Увеличи",
|
|
||||||
"zoom-out": "Намали",
|
|
||||||
"undo": "Отмяна на",
|
"undo": "Отмяна на",
|
||||||
"redo": "Redo"
|
"zoom-in": "Увеличи",
|
||||||
|
"zoom-out": "Намали"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
@ -114,6 +115,9 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"photo-filters": "Филтри за снимата"
|
"photo-filters": "Филтри за снимата"
|
||||||
},
|
},
|
||||||
|
"birthdate": {
|
||||||
|
"label": "Дата на раждане"
|
||||||
|
},
|
||||||
"heading": "Основни",
|
"heading": "Основни",
|
||||||
"headline": {
|
"headline": {
|
||||||
"label": "Заглавие"
|
"label": "Заглавие"
|
||||||
@ -121,9 +125,6 @@
|
|||||||
"name": {
|
"name": {
|
||||||
"label": "Пълно име"
|
"label": "Пълно име"
|
||||||
},
|
},
|
||||||
"birthdate": {
|
|
||||||
"label": "Дата на раждане"
|
|
||||||
},
|
|
||||||
"photo-filters": {
|
"photo-filters": {
|
||||||
"effects": {
|
"effects": {
|
||||||
"border": {
|
"border": {
|
||||||
@ -174,6 +175,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"experience": {
|
||||||
|
"form": {
|
||||||
|
"name": {
|
||||||
|
"label": "Company Name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"location": {
|
"location": {
|
||||||
"address": {
|
"address": {
|
||||||
"label": "Адрес"
|
"label": "Адрес"
|
||||||
@ -264,14 +272,15 @@
|
|||||||
"button": "GitHub общност",
|
"button": "GitHub общност",
|
||||||
"heading": "Бъгове? Искания за функции?"
|
"heading": "Бъгове? Искания за функции?"
|
||||||
},
|
},
|
||||||
|
"docs": "Документация",
|
||||||
"donate": {
|
"donate": {
|
||||||
"body": "Ако ви е харесало да използвате Reactive Resume, моля, помислете дали да не дарите колкото можете повече за поддръжка на приложението, без реклами и безплатно завинаги.",
|
"body": "Ако ви е харесало да използвате Reactive Resume, моля, помислете дали да не дарите колкото можете повече за поддръжка на приложението, без реклами и безплатно завинаги.",
|
||||||
"button": "Почерпете ме с кафе",
|
"button": "Почерпете ме с кафе",
|
||||||
"heading": "Направи дарение и подкрепи Reactive Resume"
|
"heading": "Направи дарение и подкрепи Reactive Resume"
|
||||||
},
|
},
|
||||||
"github": "Програмен код",
|
"github": "Програмен код",
|
||||||
"docs": "Документация",
|
"heading": "Връзки",
|
||||||
"heading": "Връзки"
|
"reddit": "Reddit"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"global": {
|
"global": {
|
||||||
@ -290,14 +299,14 @@
|
|||||||
},
|
},
|
||||||
"heading": "Настройки",
|
"heading": "Настройки",
|
||||||
"page": {
|
"page": {
|
||||||
"format": {
|
|
||||||
"primary": "Размер на хартията",
|
|
||||||
"secondary": "Определя размерите на вашите страници с автобиография"
|
|
||||||
},
|
|
||||||
"break-line": {
|
"break-line": {
|
||||||
"primary": "Линия на прекъсване",
|
"primary": "Линия на прекъсване",
|
||||||
"secondary": "Показване на линия на всички страници за обозначаване на височината на страница A4"
|
"secondary": "Показване на линия на всички страници за обозначаване на височината на страница A4"
|
||||||
},
|
},
|
||||||
|
"format": {
|
||||||
|
"primary": "Размер на хартията",
|
||||||
|
"secondary": "Определя размерите на вашите страници с автобиография"
|
||||||
|
},
|
||||||
"heading": "Страница",
|
"heading": "Страница",
|
||||||
"orientation": {
|
"orientation": {
|
||||||
"disabled": "Няма ефект, когато има само една страница",
|
"disabled": "Няма ефект, когато има само една страница",
|
||||||
|
|||||||
@ -20,23 +20,24 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"heading": "Връзки",
|
"heading": "Връзки",
|
||||||
"links": {
|
"links": {
|
||||||
|
"docs": "Документация",
|
||||||
"donate": "Дарение",
|
"donate": "Дарение",
|
||||||
"github": "Програмен код",
|
"github": "Програмен код",
|
||||||
"docs": "Документация",
|
|
||||||
"privacy": "Политика за поверителност",
|
"privacy": "Политика за поверителност",
|
||||||
|
"reddit": "Reddit",
|
||||||
"service": "Условия на ползване"
|
"service": "Условия на ползване"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"screenshots": {
|
"screenshots": {
|
||||||
"heading": "Екранни снимки"
|
"heading": "Екранни снимки"
|
||||||
},
|
},
|
||||||
"testimonials": {
|
|
||||||
"heading": "Препоръки",
|
|
||||||
"body": "Позитивно или негативно, ще се радвам да чуя мнението ви за Reactive Resume и какъв е вашия опитът.<br/>Ето някои от съобщенията, изпратени от потребители по целия свят.",
|
|
||||||
"contact": "Можете да се свържете с мен чрез <1>моя имейл</1> или чрез формата за контакт на <3>моя уебсайт</3> ."
|
|
||||||
},
|
|
||||||
"summary": {
|
"summary": {
|
||||||
"body": "Reactive Resume е безплатен инструмент за създаване на автобиография/CV с отворен код, който е създаден, за да улесни обикновените задачи за създаване, актуализиране и споделяне на вашата автобиография като 1, 2, 3. С това приложение можете да създавате множество автобиографии, да ги споделяте директно със специалистите по подбор на персонал или приятели чрез уникална връзка, както и ги отпечатате като PDF. Всичко е безплатно, без реклами, без проследяване, без да губите целостта и поверителността на вашите данни.",
|
"body": "Reactive Resume е безплатен инструмент за създаване на автобиография/CV с отворен код, който е създаден, за да улесни обикновените задачи за създаване, актуализиране и споделяне на вашата автобиография като 1, 2, 3. С това приложение можете да създавате множество автобиографии, да ги споделяте директно със специалистите по подбор на персонал или приятели чрез уникална връзка, както и ги отпечатате като PDF. Всичко е безплатно, без реклами, без проследяване, без да губите целостта и поверителността на вашите данни.",
|
||||||
"heading": "Обобщение"
|
"heading": "Обобщение"
|
||||||
|
},
|
||||||
|
"testimonials": {
|
||||||
|
"body": "Позитивно или негативно, ще се радвам да чуя мнението ви за Reactive Resume и какъв е вашия опитът.<br/>Ето някои от съобщенията, изпратени от потребители по целия свят.",
|
||||||
|
"contact": "Можете да се свържете с мен чрез <1>моя имейл</1> или чрез формата за контакт на <3>моя уебсайт</3> .",
|
||||||
|
"heading": "Препоръки"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,6 +71,31 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"heading": "Нулиране на паролата"
|
"heading": "Нулиране на паролата"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"heading": "Вашата сметка",
|
||||||
|
"form": {
|
||||||
|
"avatar": {
|
||||||
|
"help-text": "Можете да актуализирате профилната си снимка в <1>Gravatar</1>"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"label": "Пълно име"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"label": "Имейл адрес",
|
||||||
|
"help-text": "В момента не е възможно да актуализирате своя имейл адрес, моля, създайте нов акаунт вместо това."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete-account": {
|
||||||
|
"heading": "Изтриване на акаунт и данни",
|
||||||
|
"body": "За да изтриете своя акаунт, вашите данни и всички ваши автобиографии, въведете \"{{keyword}}\" в текстовото поле и щракнете върху бутона. Моля, имайте предвид, че това е необратимо действие и вашите данни не могат да бъдат извлечени отново.",
|
||||||
|
"actions": {
|
||||||
|
"delete": "Изтриване на акаунт"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"save": "Запазите промените"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"add": "নতুন {{token}} যোগ করুন",
|
"add": "নতুন {{token}} যোগ করুন",
|
||||||
"delete": "{{token}} মুছুন৷",
|
"delete": "{{token}} মুছুন৷",
|
||||||
|
"duplicate": "সদৃশ বিভাগ",
|
||||||
"edit": "{{token}} সম্পাদনা করুন"
|
"edit": "{{token}} সম্পাদনা করুন"
|
||||||
},
|
},
|
||||||
"columns": {
|
"columns": {
|
||||||
@ -80,13 +81,13 @@
|
|||||||
"center-artboard": "কেন্দ্র আর্টবোর্ড",
|
"center-artboard": "কেন্দ্র আর্টবোর্ড",
|
||||||
"copy-link": "রিজিউমে লিঙ্ক কপি করুন",
|
"copy-link": "রিজিউমে লিঙ্ক কপি করুন",
|
||||||
"export-pdf": "পিডিএফ আকারে পাঠান",
|
"export-pdf": "পিডিএফ আকারে পাঠান",
|
||||||
|
"redo": "আবার করুন",
|
||||||
"toggle-orientation": "পৃষ্ঠা ওরিয়েন্টেশন টগল করুন",
|
"toggle-orientation": "পৃষ্ঠা ওরিয়েন্টেশন টগল করুন",
|
||||||
"toggle-page-break-line": "পৃষ্ঠা বিরতি লাইন টগল করুন",
|
"toggle-page-break-line": "পৃষ্ঠা বিরতি লাইন টগল করুন",
|
||||||
"toggle-sidebars": "সাইডবার টগল করুন",
|
"toggle-sidebars": "সাইডবার টগল করুন",
|
||||||
"zoom-in": "বড় কর",
|
|
||||||
"zoom-out": "ছোট করা",
|
|
||||||
"undo": "পূর্বাবস্থায় ফেরান",
|
"undo": "পূর্বাবস্থায় ফেরান",
|
||||||
"redo": "আবার করুন"
|
"zoom-in": "বড় কর",
|
||||||
|
"zoom-out": "ছোট করা"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
@ -114,6 +115,9 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"photo-filters": "ফটো ফিল্টার"
|
"photo-filters": "ফটো ফিল্টার"
|
||||||
},
|
},
|
||||||
|
"birthdate": {
|
||||||
|
"label": "জন্ম তারিখ"
|
||||||
|
},
|
||||||
"heading": "মৌলিক",
|
"heading": "মৌলিক",
|
||||||
"headline": {
|
"headline": {
|
||||||
"label": "শিরোনাম"
|
"label": "শিরোনাম"
|
||||||
@ -121,9 +125,6 @@
|
|||||||
"name": {
|
"name": {
|
||||||
"label": "পূর্ণ নাম"
|
"label": "পূর্ণ নাম"
|
||||||
},
|
},
|
||||||
"birthdate": {
|
|
||||||
"label": "জন্ম তারিখ"
|
|
||||||
},
|
|
||||||
"photo-filters": {
|
"photo-filters": {
|
||||||
"effects": {
|
"effects": {
|
||||||
"border": {
|
"border": {
|
||||||
@ -174,6 +175,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"experience": {
|
||||||
|
"form": {
|
||||||
|
"name": {
|
||||||
|
"label": "কোম্পানির নাম"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"location": {
|
"location": {
|
||||||
"address": {
|
"address": {
|
||||||
"label": "ঠিকানা"
|
"label": "ঠিকানা"
|
||||||
@ -264,14 +272,15 @@
|
|||||||
"button": "Github ইস্যু তালিকা",
|
"button": "Github ইস্যু তালিকা",
|
||||||
"heading": "বাগ? বৈশিষ্ট্য অনুরোধ?"
|
"heading": "বাগ? বৈশিষ্ট্য অনুরোধ?"
|
||||||
},
|
},
|
||||||
|
"docs": "ডকুমেন্টেশন",
|
||||||
"donate": {
|
"donate": {
|
||||||
"body": "আপনি যদি প্রতিক্রিয়াশীল জীবনবৃত্তান্ত ব্যবহার করতে পছন্দ করেন, অনুগ্রহ করে বিজ্ঞাপন ছাড়া এবং চিরতরে বিনামূল্যে অ্যাপটিকে চালু ও চালু রাখার জন্য যতটা সম্ভব দান করার কথা বিবেচনা করুন।",
|
"body": "আপনি যদি প্রতিক্রিয়াশীল জীবনবৃত্তান্ত ব্যবহার করতে পছন্দ করেন, অনুগ্রহ করে বিজ্ঞাপন ছাড়া এবং চিরতরে বিনামূল্যে অ্যাপটিকে চালু ও চালু রাখার জন্য যতটা সম্ভব দান করার কথা বিবেচনা করুন।",
|
||||||
"button": "আমাকে একটা কফি কিনে দাও",
|
"button": "আমাকে একটা কফি কিনে দাও",
|
||||||
"heading": "Reactive Resume -তে দান করুন"
|
"heading": "Reactive Resume -তে দান করুন"
|
||||||
},
|
},
|
||||||
"github": "সোর্স কোড",
|
"github": "সোর্স কোড",
|
||||||
"docs": "ডকুমেন্টেশন",
|
"heading": "লিঙ্ক",
|
||||||
"heading": "লিঙ্ক"
|
"reddit": "রেডডিট"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"global": {
|
"global": {
|
||||||
@ -290,14 +299,14 @@
|
|||||||
},
|
},
|
||||||
"heading": "সেটিংস",
|
"heading": "সেটিংস",
|
||||||
"page": {
|
"page": {
|
||||||
"format": {
|
|
||||||
"primary": "কাগজের আকার",
|
|
||||||
"secondary": "আপনার জীবনবৃত্তান্ত পৃষ্ঠাগুলির মাত্রা নির্ধারণ করে"
|
|
||||||
},
|
|
||||||
"break-line": {
|
"break-line": {
|
||||||
"primary": "লাইন ভেঙ্গে ফেলুন",
|
"primary": "লাইন ভেঙ্গে ফেলুন",
|
||||||
"secondary": "একটি A4 পৃষ্ঠার উচ্চতা চিহ্নিত করতে সমস্ত পৃষ্ঠায় একটি লাইন দেখান৷"
|
"secondary": "একটি A4 পৃষ্ঠার উচ্চতা চিহ্নিত করতে সমস্ত পৃষ্ঠায় একটি লাইন দেখান৷"
|
||||||
},
|
},
|
||||||
|
"format": {
|
||||||
|
"primary": "কাগজের আকার",
|
||||||
|
"secondary": "আপনার জীবনবৃত্তান্ত পৃষ্ঠাগুলির মাত্রা নির্ধারণ করে"
|
||||||
|
},
|
||||||
"heading": "পাতা",
|
"heading": "পাতা",
|
||||||
"orientation": {
|
"orientation": {
|
||||||
"disabled": "শুধুমাত্র একটি পৃষ্ঠা থাকলে কোন প্রভাব নেই",
|
"disabled": "শুধুমাত্র একটি পৃষ্ঠা থাকলে কোন প্রভাব নেই",
|
||||||
|
|||||||
@ -20,23 +20,24 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"heading": "লিঙ্ক",
|
"heading": "লিঙ্ক",
|
||||||
"links": {
|
"links": {
|
||||||
|
"docs": "ডকুমেন্টেশন",
|
||||||
"donate": "দান করুন",
|
"donate": "দান করুন",
|
||||||
"github": "সোর্স কোড",
|
"github": "সোর্স কোড",
|
||||||
"docs": "ডকুমেন্টেশন",
|
|
||||||
"privacy": "গোপনীয়তা নীতি",
|
"privacy": "গোপনীয়তা নীতি",
|
||||||
|
"reddit": "রেডডিট",
|
||||||
"service": "সেবা পাবার শর্ত"
|
"service": "সেবা পাবার শর্ত"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"screenshots": {
|
"screenshots": {
|
||||||
"heading": "স্ক্রিনশট"
|
"heading": "স্ক্রিনশট"
|
||||||
},
|
},
|
||||||
"testimonials": {
|
|
||||||
"heading": "প্রশংসাপত্র",
|
|
||||||
"body": "ভাল বা খারাপ, আমি প্রতিক্রিয়াশীল জীবনবৃত্তান্ত সম্পর্কে আপনার মতামত এবং আপনার জন্য অভিজ্ঞতা কেমন হয়েছে তা জানতে চাই।<br/>এখানে বিশ্বজুড়ে ব্যবহারকারীদের পাঠানো কিছু বার্তা রয়েছে৷",
|
|
||||||
"contact": "আপনি <1>আমার ইমেল</1> বা <3>আমার ওয়েবসাইট</3>-এ যোগাযোগ ফর্মের মাধ্যমে আমার সাথে যোগাযোগ করতে পারেন।"
|
|
||||||
},
|
|
||||||
"summary": {
|
"summary": {
|
||||||
"body": "Reactive Resume হল একটি বিনামূল্যের এবং ওপেন সোর্স জীবনবৃত্তান্ত নির্মাতা যা আপনার জীবনবৃত্তান্ত তৈরি, আপডেট এবং শেয়ার করার জাগতিক কাজগুলিকে 1, 2, 3 এর মতো সহজ করে তুলতে তৈরি করা হয়েছে৷ এই অ্যাপটির মাধ্যমে, আপনি একাধিক জীবনবৃত্তান্ত তৈরি করতে পারেন, নিয়োগকারীদের বা বন্ধুদের সাথে শেয়ার করতে পারেন৷ একটি অনন্য লিঙ্কের মাধ্যমে এবং আপনার ডেটার অখণ্ডতা এবং গোপনীয়তা হারানো ছাড়াই বিনামূল্যে, কোনও বিজ্ঞাপন, কোনও ট্র্যাকিং ছাড়াই একটি পিডিএফ হিসাবে মুদ্রণ করুন।",
|
"body": "Reactive Resume হল একটি বিনামূল্যের এবং ওপেন সোর্স জীবনবৃত্তান্ত নির্মাতা যা আপনার জীবনবৃত্তান্ত তৈরি, আপডেট এবং শেয়ার করার জাগতিক কাজগুলিকে 1, 2, 3 এর মতো সহজ করে তুলতে তৈরি করা হয়েছে৷ এই অ্যাপটির মাধ্যমে, আপনি একাধিক জীবনবৃত্তান্ত তৈরি করতে পারেন, নিয়োগকারীদের বা বন্ধুদের সাথে শেয়ার করতে পারেন৷ একটি অনন্য লিঙ্কের মাধ্যমে এবং আপনার ডেটার অখণ্ডতা এবং গোপনীয়তা হারানো ছাড়াই বিনামূল্যে, কোনও বিজ্ঞাপন, কোনও ট্র্যাকিং ছাড়াই একটি পিডিএফ হিসাবে মুদ্রণ করুন।",
|
||||||
"heading": "সারসংক্ষেপ"
|
"heading": "সারসংক্ষেপ"
|
||||||
|
},
|
||||||
|
"testimonials": {
|
||||||
|
"body": "ভাল বা খারাপ, আমি প্রতিক্রিয়াশীল জীবনবৃত্তান্ত সম্পর্কে আপনার মতামত এবং আপনার জন্য অভিজ্ঞতা কেমন হয়েছে তা জানতে চাই।<br/>এখানে বিশ্বজুড়ে ব্যবহারকারীদের পাঠানো কিছু বার্তা রয়েছে৷",
|
||||||
|
"contact": "আপনি <1>আমার ইমেল</1> বা <3>আমার ওয়েবসাইট</3>-এ যোগাযোগ ফর্মের মাধ্যমে আমার সাথে যোগাযোগ করতে পারেন।",
|
||||||
|
"heading": "প্রশংসাপত্র"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,6 +71,31 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"heading": "আপনার পাসওয়ার্ডটি রিসেট করুন"
|
"heading": "আপনার পাসওয়ার্ডটি রিসেট করুন"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"heading": "আপনার অ্যাকাউন্ট",
|
||||||
|
"form": {
|
||||||
|
"avatar": {
|
||||||
|
"help-text": "আপনি <1>Gravatar-এ আপনার প্রোফাইল ছবি আপডেট করতে পারেন</1>"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"label": "পুরো নাম"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"label": "ইমেইল ঠিকানা",
|
||||||
|
"help-text": "এই মুহূর্তে আপনার ইমেল ঠিকানা আপডেট করা সম্ভব নয়, এর পরিবর্তে একটি নতুন অ্যাকাউন্ট তৈরি করুন।"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete-account": {
|
||||||
|
"heading": "অ্যাকাউন্ট এবং ডেটা মুছুন",
|
||||||
|
"body": "আপনার অ্যাকাউন্ট, আপনার ডেটা এবং আপনার সমস্ত জীবনবৃত্তান্ত মুছে ফেলতে, পাঠ্যবক্সে \"{{keyword}}\" টাইপ করুন এবং বোতামে ক্লিক করুন৷ দয়া করে মনে রাখবেন এটি একটি অপরিবর্তনীয় ক্রিয়া এবং আপনার ডেটা আবার পুনরুদ্ধার করা যাবে না।",
|
||||||
|
"actions": {
|
||||||
|
"delete": "হিসাব মুছে ফেলা"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"save": "পরিবর্তনগুলোর সংরক্ষন"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"add": "Afegeix nou {{token}}",
|
"add": "Afegeix nou {{token}}",
|
||||||
"delete": "Suprimeix {{token}}",
|
"delete": "Suprimeix {{token}}",
|
||||||
|
"duplicate": "Secció duplicada",
|
||||||
"edit": "Edita {{token}}"
|
"edit": "Edita {{token}}"
|
||||||
},
|
},
|
||||||
"columns": {
|
"columns": {
|
||||||
@ -80,13 +81,13 @@
|
|||||||
"center-artboard": "Centrar tauler de dibuix",
|
"center-artboard": "Centrar tauler de dibuix",
|
||||||
"copy-link": "Copia l'enllaç al currículum",
|
"copy-link": "Copia l'enllaç al currículum",
|
||||||
"export-pdf": "Exporta com a PDF",
|
"export-pdf": "Exporta com a PDF",
|
||||||
|
"redo": "Refer",
|
||||||
"toggle-orientation": "Commuta l'orientació de la pàgina",
|
"toggle-orientation": "Commuta l'orientació de la pàgina",
|
||||||
"toggle-page-break-line": "Commuta la línia de salt de pàgina",
|
"toggle-page-break-line": "Commuta la línia de salt de pàgina",
|
||||||
"toggle-sidebars": "Mostra o oculta la barra lateral",
|
"toggle-sidebars": "Mostra o oculta la barra lateral",
|
||||||
"zoom-in": "Amplia",
|
|
||||||
"zoom-out": "Allunya",
|
|
||||||
"undo": "Desfer",
|
"undo": "Desfer",
|
||||||
"redo": "Refer"
|
"zoom-in": "Amplia",
|
||||||
|
"zoom-out": "Allunya"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
@ -114,6 +115,9 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"photo-filters": "Filtres fotogràfics"
|
"photo-filters": "Filtres fotogràfics"
|
||||||
},
|
},
|
||||||
|
"birthdate": {
|
||||||
|
"label": "Data de naixement"
|
||||||
|
},
|
||||||
"heading": "Bàsics",
|
"heading": "Bàsics",
|
||||||
"headline": {
|
"headline": {
|
||||||
"label": "Títol"
|
"label": "Títol"
|
||||||
@ -121,9 +125,6 @@
|
|||||||
"name": {
|
"name": {
|
||||||
"label": "Nom complet"
|
"label": "Nom complet"
|
||||||
},
|
},
|
||||||
"birthdate": {
|
|
||||||
"label": "Data de naixement"
|
|
||||||
},
|
|
||||||
"photo-filters": {
|
"photo-filters": {
|
||||||
"effects": {
|
"effects": {
|
||||||
"border": {
|
"border": {
|
||||||
@ -174,6 +175,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"experience": {
|
||||||
|
"form": {
|
||||||
|
"name": {
|
||||||
|
"label": "Company Name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"location": {
|
"location": {
|
||||||
"address": {
|
"address": {
|
||||||
"label": "Adreça"
|
"label": "Adreça"
|
||||||
@ -264,14 +272,15 @@
|
|||||||
"button": "Problemes de GitHub",
|
"button": "Problemes de GitHub",
|
||||||
"heading": "Errors? Sol·licituds de funcions?"
|
"heading": "Errors? Sol·licituds de funcions?"
|
||||||
},
|
},
|
||||||
|
"docs": "Documentació",
|
||||||
"donate": {
|
"donate": {
|
||||||
"body": "Si us ha agradat utilitzar Reactive Resume, considereu donar tant com pugueu per mantenir l'aplicació en funcionament, sense anuncis i gratuïta per sempre.",
|
"body": "Si us ha agradat utilitzar Reactive Resume, considereu donar tant com pugueu per mantenir l'aplicació en funcionament, sense anuncis i gratuïta per sempre.",
|
||||||
"button": "Compra'm un cafè",
|
"button": "Compra'm un cafè",
|
||||||
"heading": "Dona a Reactive Curriculum vitae"
|
"heading": "Dona a Reactive Curriculum vitae"
|
||||||
},
|
},
|
||||||
"github": "Codi font",
|
"github": "Codi font",
|
||||||
"docs": "Documentació",
|
"heading": "Enllaços",
|
||||||
"heading": "Enllaços"
|
"reddit": "Reddit"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"global": {
|
"global": {
|
||||||
@ -290,14 +299,14 @@
|
|||||||
},
|
},
|
||||||
"heading": "Configuració",
|
"heading": "Configuració",
|
||||||
"page": {
|
"page": {
|
||||||
"format": {
|
|
||||||
"primary": "Mida del paper",
|
|
||||||
"secondary": "Determina les dimensions de les pàgines del vostre currículum"
|
|
||||||
},
|
|
||||||
"break-line": {
|
"break-line": {
|
||||||
"primary": "Línia de trencament",
|
"primary": "Línia de trencament",
|
||||||
"secondary": "Mostra una línia a totes les pàgines per marcar l'alçada d'una pàgina A4"
|
"secondary": "Mostra una línia a totes les pàgines per marcar l'alçada d'una pàgina A4"
|
||||||
},
|
},
|
||||||
|
"format": {
|
||||||
|
"primary": "Mida del paper",
|
||||||
|
"secondary": "Determina les dimensions de les pàgines del vostre currículum"
|
||||||
|
},
|
||||||
"heading": "Pàgina",
|
"heading": "Pàgina",
|
||||||
"orientation": {
|
"orientation": {
|
||||||
"disabled": "No té efecte quan només hi ha una pàgina",
|
"disabled": "No té efecte quan només hi ha una pàgina",
|
||||||
|
|||||||
@ -20,23 +20,24 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"heading": "Enllaços",
|
"heading": "Enllaços",
|
||||||
"links": {
|
"links": {
|
||||||
|
"docs": "Documentació",
|
||||||
"donate": "Donar",
|
"donate": "Donar",
|
||||||
"github": "Codi font",
|
"github": "Codi font",
|
||||||
"docs": "Documentació",
|
|
||||||
"privacy": "Política de privacitat",
|
"privacy": "Política de privacitat",
|
||||||
|
"reddit": "Reddit",
|
||||||
"service": "Termes del servei"
|
"service": "Termes del servei"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"screenshots": {
|
"screenshots": {
|
||||||
"heading": "Captures de pantalla"
|
"heading": "Captures de pantalla"
|
||||||
},
|
},
|
||||||
"testimonials": {
|
|
||||||
"heading": "Testimonis",
|
|
||||||
"body": "Bo o dolent, m'encantaria saber la teva opinió sobre Reactive Curriculum vitae i com ha estat l'experiència per a tu.<br/>Aquests són alguns dels missatges enviats pels usuaris de tot el món.",
|
|
||||||
"contact": "Podeu posar-vos en contacte amb mi mitjançant <1>el meu correu electrònic</1> o mitjançant el formulari de contacte al <3>el meu lloc web</3> ."
|
|
||||||
},
|
|
||||||
"summary": {
|
"summary": {
|
||||||
"body": "Reactive Resume és un creador de currículums gratuït i de codi obert que s'ha creat per fer que les tasques mundanes de crear, actualitzar i compartir el vostre currículum siguin tan fàcils com 1, 2, 3. Amb aquesta aplicació, podeu crear diversos currículums, compartir-los amb reclutadors o amics. mitjançant un enllaç únic i imprimiu-lo com a PDF, tot de franc, sense anuncis, sense seguiment, sense perdre la integritat i la privadesa de les vostres dades.",
|
"body": "Reactive Resume és un creador de currículums gratuït i de codi obert que s'ha creat per fer que les tasques mundanes de crear, actualitzar i compartir el vostre currículum siguin tan fàcils com 1, 2, 3. Amb aquesta aplicació, podeu crear diversos currículums, compartir-los amb reclutadors o amics. mitjançant un enllaç únic i imprimiu-lo com a PDF, tot de franc, sense anuncis, sense seguiment, sense perdre la integritat i la privadesa de les vostres dades.",
|
||||||
"heading": "Resum"
|
"heading": "Resum"
|
||||||
|
},
|
||||||
|
"testimonials": {
|
||||||
|
"body": "Bo o dolent, m'encantaria saber la teva opinió sobre Reactive Curriculum vitae i com ha estat l'experiència per a tu.<br/>Aquests són alguns dels missatges enviats pels usuaris de tot el món.",
|
||||||
|
"contact": "Podeu posar-vos en contacte amb mi mitjançant <1>el meu correu electrònic</1> o mitjançant el formulari de contacte al <3>el meu lloc web</3> .",
|
||||||
|
"heading": "Testimonis"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,6 +71,31 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"heading": "Restablir la contrasenya"
|
"heading": "Restablir la contrasenya"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"heading": "El teu compte",
|
||||||
|
"form": {
|
||||||
|
"avatar": {
|
||||||
|
"help-text": "Pots actualitzar la teva foto de perfil a <1>Gravatar</1>"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"label": "Nom complet"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"label": "Correu electrònic",
|
||||||
|
"help-text": "No és possible actualitzar la vostra adreça de correu electrònic en aquest moment, si us plau, creeu un compte nou."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete-account": {
|
||||||
|
"heading": "Suprimeix el compte i les dades",
|
||||||
|
"body": "Per eliminar el vostre compte, les vostres dades i tots els vostres currículums, escriviu \"{{keyword}}\" al quadre de text i feu clic al botó. Tingueu en compte que aquesta és una acció irreversible i que les vostres dades no es poden recuperar de nou.",
|
||||||
|
"actions": {
|
||||||
|
"delete": "Esborrar compte"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"save": "Guardar canvis"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"add": "Přidat nový {{token}}",
|
"add": "Přidat nový {{token}}",
|
||||||
"delete": "Odstranit {{token}}",
|
"delete": "Odstranit {{token}}",
|
||||||
|
"duplicate": "Duplicitní sekce",
|
||||||
"edit": "Upravit {{token}}"
|
"edit": "Upravit {{token}}"
|
||||||
},
|
},
|
||||||
"columns": {
|
"columns": {
|
||||||
@ -80,13 +81,13 @@
|
|||||||
"center-artboard": "Vycentrovat Artboard",
|
"center-artboard": "Vycentrovat Artboard",
|
||||||
"copy-link": "Kopírovat odkaz na životopis",
|
"copy-link": "Kopírovat odkaz na životopis",
|
||||||
"export-pdf": "Exportovat PDF",
|
"export-pdf": "Exportovat PDF",
|
||||||
|
"redo": "Přepracovat",
|
||||||
"toggle-orientation": "Přepnout orientaci stránky",
|
"toggle-orientation": "Přepnout orientaci stránky",
|
||||||
"toggle-page-break-line": "Přepnout řádek zalomení stránky",
|
"toggle-page-break-line": "Přepnout řádek zalomení stránky",
|
||||||
"toggle-sidebars": "Přepnout boční panely",
|
"toggle-sidebars": "Přepnout boční panely",
|
||||||
"zoom-in": "Přiblížit",
|
|
||||||
"zoom-out": "Oddálit",
|
|
||||||
"undo": "Zrušit",
|
"undo": "Zrušit",
|
||||||
"redo": "Přepracovat"
|
"zoom-in": "Přiblížit",
|
||||||
|
"zoom-out": "Oddálit"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
@ -114,6 +115,9 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"photo-filters": "Foto filtry"
|
"photo-filters": "Foto filtry"
|
||||||
},
|
},
|
||||||
|
"birthdate": {
|
||||||
|
"label": "Datum narození"
|
||||||
|
},
|
||||||
"heading": "Základy",
|
"heading": "Základy",
|
||||||
"headline": {
|
"headline": {
|
||||||
"label": "Titulek"
|
"label": "Titulek"
|
||||||
@ -121,9 +125,6 @@
|
|||||||
"name": {
|
"name": {
|
||||||
"label": "Celé jméno"
|
"label": "Celé jméno"
|
||||||
},
|
},
|
||||||
"birthdate": {
|
|
||||||
"label": "Datum narození"
|
|
||||||
},
|
|
||||||
"photo-filters": {
|
"photo-filters": {
|
||||||
"effects": {
|
"effects": {
|
||||||
"border": {
|
"border": {
|
||||||
@ -174,6 +175,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"experience": {
|
||||||
|
"form": {
|
||||||
|
"name": {
|
||||||
|
"label": "Company Name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"location": {
|
"location": {
|
||||||
"address": {
|
"address": {
|
||||||
"label": "Adresa"
|
"label": "Adresa"
|
||||||
@ -264,14 +272,15 @@
|
|||||||
"button": "Hlášení chyb GitHub",
|
"button": "Hlášení chyb GitHub",
|
||||||
"heading": "Chyby? Požadavky na funkce?"
|
"heading": "Chyby? Požadavky na funkce?"
|
||||||
},
|
},
|
||||||
|
"docs": "Dokumentace",
|
||||||
"donate": {
|
"donate": {
|
||||||
"body": "Pokud se vám líbilo používání Reactive Resume, zvažte prosím darování co největší částky na udržení aplikace v provozu, bez reklam a navždy zdarma.",
|
"body": "Pokud se vám líbilo používání Reactive Resume, zvažte prosím darování co největší částky na udržení aplikace v provozu, bez reklam a navždy zdarma.",
|
||||||
"button": "Kupte mi kávu",
|
"button": "Kupte mi kávu",
|
||||||
"heading": "Přispějte na Reactive Resume"
|
"heading": "Přispějte na Reactive Resume"
|
||||||
},
|
},
|
||||||
"github": "Zdrojový kód",
|
"github": "Zdrojový kód",
|
||||||
"docs": "Dokumentace",
|
"heading": "Odkazy",
|
||||||
"heading": "Odkazy"
|
"reddit": "Reddit"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"global": {
|
"global": {
|
||||||
@ -290,14 +299,14 @@
|
|||||||
},
|
},
|
||||||
"heading": "Nastavení",
|
"heading": "Nastavení",
|
||||||
"page": {
|
"page": {
|
||||||
"format": {
|
|
||||||
"primary": "Velikost papíru",
|
|
||||||
"secondary": "Určuje rozměry stránek vašeho životopisu"
|
|
||||||
},
|
|
||||||
"break-line": {
|
"break-line": {
|
||||||
"primary": "Nový řádek",
|
"primary": "Nový řádek",
|
||||||
"secondary": "Zobrazit čáru na všech stránkách pro označení výšky stránky A4"
|
"secondary": "Zobrazit čáru na všech stránkách pro označení výšky stránky A4"
|
||||||
},
|
},
|
||||||
|
"format": {
|
||||||
|
"primary": "Velikost papíru",
|
||||||
|
"secondary": "Určuje rozměry stránek vašeho životopisu"
|
||||||
|
},
|
||||||
"heading": "Stránka",
|
"heading": "Stránka",
|
||||||
"orientation": {
|
"orientation": {
|
||||||
"disabled": "Nemá žádný efekt, pokud existuje pouze jedna stránka",
|
"disabled": "Nemá žádný efekt, pokud existuje pouze jedna stránka",
|
||||||
|
|||||||
@ -20,23 +20,24 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"heading": "Odkazy",
|
"heading": "Odkazy",
|
||||||
"links": {
|
"links": {
|
||||||
|
"docs": "Dokumentace",
|
||||||
"donate": "Darovat",
|
"donate": "Darovat",
|
||||||
"github": "Zdrojový kód",
|
"github": "Zdrojový kód",
|
||||||
"docs": "Dokumentace",
|
|
||||||
"privacy": "Zásady ochrany osobních údajů",
|
"privacy": "Zásady ochrany osobních údajů",
|
||||||
|
"reddit": "Reddit",
|
||||||
"service": "Podmínky služby"
|
"service": "Podmínky služby"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"screenshots": {
|
"screenshots": {
|
||||||
"heading": "Snímky obrazovky"
|
"heading": "Snímky obrazovky"
|
||||||
},
|
},
|
||||||
"testimonials": {
|
|
||||||
"heading": "Posudky",
|
|
||||||
"body": "Ať je to dobře nebo špatně, rád bych slyšel váš názor na Reactive Resume a jaké to bylo pro vás.<br/>Zde jsou některé zprávy zaslané uživateli z celého světa.",
|
|
||||||
"contact": "Můžete mě kontaktovat prostřednictvím <1>mého e-mailu</1> nebo prostřednictvím kontaktního formuláře na <3>mých webových stránkách</3> ."
|
|
||||||
},
|
|
||||||
"summary": {
|
"summary": {
|
||||||
"body": "Reactive Resume je bezplatný a open source tvůrce životopisů, který je vytvořen tak, aby zjednodušil každodenní úkoly vytváření, aktualizace a sdílení vašeho životopisu jako 1, 2, 3. Pomocí této aplikace můžete vytvořit více životopisů a sdílet je s náboráři nebo přáteli. prostřednictvím jedinečného odkazu a vytiskněte si jej jako PDF, vše zdarma, bez reklam, bez sledování, bez ztráty integrity a soukromí vašich dat.",
|
"body": "Reactive Resume je bezplatný a open source tvůrce životopisů, který je vytvořen tak, aby zjednodušil každodenní úkoly vytváření, aktualizace a sdílení vašeho životopisu jako 1, 2, 3. Pomocí této aplikace můžete vytvořit více životopisů a sdílet je s náboráři nebo přáteli. prostřednictvím jedinečného odkazu a vytiskněte si jej jako PDF, vše zdarma, bez reklam, bez sledování, bez ztráty integrity a soukromí vašich dat.",
|
||||||
"heading": "souhrn"
|
"heading": "souhrn"
|
||||||
|
},
|
||||||
|
"testimonials": {
|
||||||
|
"body": "Ať je to dobře nebo špatně, rád bych slyšel váš názor na Reactive Resume a jaké to bylo pro vás.<br/>Zde jsou některé zprávy zaslané uživateli z celého světa.",
|
||||||
|
"contact": "Můžete mě kontaktovat prostřednictvím <1>mého e-mailu</1> nebo prostřednictvím kontaktního formuláře na <3>mých webových stránkách</3> .",
|
||||||
|
"heading": "Posudky"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,6 +71,31 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"heading": "Obnovit heslo"
|
"heading": "Obnovit heslo"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"heading": "Váš účet",
|
||||||
|
"form": {
|
||||||
|
"avatar": {
|
||||||
|
"help-text": "Svůj profilový obrázek si můžete aktualizovat na <1>Gravatar</1>"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"label": "Celé jméno"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"label": "Emailová adresa",
|
||||||
|
"help-text": "V tuto chvíli není možné aktualizovat vaši e-mailovou adresu, vytvořte si místo toho nový účet."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete-account": {
|
||||||
|
"heading": "Smazat účet a data",
|
||||||
|
"body": "Chcete-li smazat svůj účet, data a všechny své životopisy, zadejte do textového pole „{{keyword}}“ a klikněte na tlačítko. Upozorňujeme, že se jedná o nevratnou akci a vaše data nelze znovu získat.",
|
||||||
|
"actions": {
|
||||||
|
"delete": "Smazat účet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"save": "Uložit změny"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"add": "Tilføj ny {{token}}",
|
"add": "Tilføj ny {{token}}",
|
||||||
"delete": "Slet {{token}}",
|
"delete": "Slet {{token}}",
|
||||||
|
"duplicate": "Dublet afsnit",
|
||||||
"edit": "Rediger {{token}}"
|
"edit": "Rediger {{token}}"
|
||||||
},
|
},
|
||||||
"columns": {
|
"columns": {
|
||||||
@ -80,13 +81,13 @@
|
|||||||
"center-artboard": "Centrer tegnebræt",
|
"center-artboard": "Centrer tegnebræt",
|
||||||
"copy-link": "Kopier link til CV",
|
"copy-link": "Kopier link til CV",
|
||||||
"export-pdf": "Eksporter PDF",
|
"export-pdf": "Eksporter PDF",
|
||||||
|
"redo": "Redo",
|
||||||
"toggle-orientation": "Skift sideorientering",
|
"toggle-orientation": "Skift sideorientering",
|
||||||
"toggle-page-break-line": "Skift sideskiftlinje",
|
"toggle-page-break-line": "Skift sideskiftlinje",
|
||||||
"toggle-sidebars": "Sidebjælke til/fra",
|
"toggle-sidebars": "Sidebjælke til/fra",
|
||||||
"zoom-in": "Zoom ind",
|
|
||||||
"zoom-out": "Zoom ud",
|
|
||||||
"undo": "Fortryd",
|
"undo": "Fortryd",
|
||||||
"redo": "Redo"
|
"zoom-in": "Zoom ind",
|
||||||
|
"zoom-out": "Zoom ud"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
@ -114,6 +115,9 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"photo-filters": "Fotofiltre"
|
"photo-filters": "Fotofiltre"
|
||||||
},
|
},
|
||||||
|
"birthdate": {
|
||||||
|
"label": "Fødselsdato"
|
||||||
|
},
|
||||||
"heading": "Grundlæggende",
|
"heading": "Grundlæggende",
|
||||||
"headline": {
|
"headline": {
|
||||||
"label": "Overskrift"
|
"label": "Overskrift"
|
||||||
@ -121,9 +125,6 @@
|
|||||||
"name": {
|
"name": {
|
||||||
"label": "Fulde navn"
|
"label": "Fulde navn"
|
||||||
},
|
},
|
||||||
"birthdate": {
|
|
||||||
"label": "Fødselsdato"
|
|
||||||
},
|
|
||||||
"photo-filters": {
|
"photo-filters": {
|
||||||
"effects": {
|
"effects": {
|
||||||
"border": {
|
"border": {
|
||||||
@ -174,6 +175,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"experience": {
|
||||||
|
"form": {
|
||||||
|
"name": {
|
||||||
|
"label": "Company Name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"location": {
|
"location": {
|
||||||
"address": {
|
"address": {
|
||||||
"label": "Adresse"
|
"label": "Adresse"
|
||||||
@ -264,14 +272,15 @@
|
|||||||
"button": "GitHub-problemer",
|
"button": "GitHub-problemer",
|
||||||
"heading": "Fejl? Ønsker til en ny funktion?"
|
"heading": "Fejl? Ønsker til en ny funktion?"
|
||||||
},
|
},
|
||||||
|
"docs": "Dokumentation",
|
||||||
"donate": {
|
"donate": {
|
||||||
"body": "Hvis du kunne lide at bruge Reactive Resume, bedes du overveje at donere så meget som muligt til formålet med at holde appen oppe og køre, uden annoncer og gratis for evigt.",
|
"body": "Hvis du kunne lide at bruge Reactive Resume, bedes du overveje at donere så meget som muligt til formålet med at holde appen oppe og køre, uden annoncer og gratis for evigt.",
|
||||||
"button": "Købe mig en kop kaffe",
|
"button": "Købe mig en kop kaffe",
|
||||||
"heading": "Donér til Reactive Resume"
|
"heading": "Donér til Reactive Resume"
|
||||||
},
|
},
|
||||||
"github": "Kildekode",
|
"github": "Kildekode",
|
||||||
"docs": "Dokumentation",
|
"heading": "Links",
|
||||||
"heading": "Links"
|
"reddit": "Reddit"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"global": {
|
"global": {
|
||||||
@ -290,14 +299,14 @@
|
|||||||
},
|
},
|
||||||
"heading": "Indstillinger",
|
"heading": "Indstillinger",
|
||||||
"page": {
|
"page": {
|
||||||
"format": {
|
|
||||||
"primary": "Papirstørrelse",
|
|
||||||
"secondary": "Bestemmer dimensionerne på dine CV-sider"
|
|
||||||
},
|
|
||||||
"break-line": {
|
"break-line": {
|
||||||
"primary": "Brudlinje",
|
"primary": "Brudlinje",
|
||||||
"secondary": "Vis en streg på alle sider for at markere højden på en A4-side"
|
"secondary": "Vis en streg på alle sider for at markere højden på en A4-side"
|
||||||
},
|
},
|
||||||
|
"format": {
|
||||||
|
"primary": "Papirstørrelse",
|
||||||
|
"secondary": "Bestemmer dimensionerne på dine CV-sider"
|
||||||
|
},
|
||||||
"heading": "Side",
|
"heading": "Side",
|
||||||
"orientation": {
|
"orientation": {
|
||||||
"disabled": "Har ingen effekt, når der kun er én side",
|
"disabled": "Har ingen effekt, når der kun er én side",
|
||||||
|
|||||||
@ -8,35 +8,36 @@
|
|||||||
"features": {
|
"features": {
|
||||||
"heading": "Funktioner",
|
"heading": "Funktioner",
|
||||||
"list": {
|
"list": {
|
||||||
"ads": "Ingen reklamer.",
|
"ads": "Ingen reklamer",
|
||||||
"export": "Eksporter dit CV til JSON- eller PDF-format",
|
"export": "Eksporter dit CV til JSON- eller PDF-format",
|
||||||
"free": "Gratis for evigt",
|
"free": "Gratis for evigt",
|
||||||
"import": "Importer data fra LinkedIn, JSON CV",
|
"import": "Importer data fra LinkedIn, JSON CV",
|
||||||
"languages": "Tilgængelig på flere sprog",
|
"languages": "Tilgængelig på flere sprog",
|
||||||
"more": "Og meget flere spændende funktioner, <1>læs alt om det her</1>",
|
"more": "Og mange flere spændende funktioner, <1>læs alt om det her</1>",
|
||||||
"tracking": "Ingen brugersporing"
|
"tracking": "Ingen brugersporing"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
"heading": "Links",
|
"heading": "Links",
|
||||||
"links": {
|
"links": {
|
||||||
|
"docs": "Dokumentation",
|
||||||
"donate": "Donér",
|
"donate": "Donér",
|
||||||
"github": "Kildekode",
|
"github": "Kildekode",
|
||||||
"docs": "Dokumentation",
|
|
||||||
"privacy": "Fortrolighedspolitik",
|
"privacy": "Fortrolighedspolitik",
|
||||||
|
"reddit": "Reddit",
|
||||||
"service": "Servicevilkår"
|
"service": "Servicevilkår"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"screenshots": {
|
"screenshots": {
|
||||||
"heading": "Skærmbilleder"
|
"heading": "Skærmbilleder"
|
||||||
},
|
},
|
||||||
"testimonials": {
|
|
||||||
"heading": "Udtalelser",
|
|
||||||
"body": "Godt eller dårligt, jeg ville elske at høre din mening om Reactive Resume og hvordan oplevelsen har været for dig.<br/>Her er nogle af de beskeder, der er sendt af brugere over hele verden.",
|
|
||||||
"contact": "Du kan kontakte mig via <1>min e-mail</1> eller gennem kontaktformularen på <3>min hjemmeside</3> ."
|
|
||||||
},
|
|
||||||
"summary": {
|
"summary": {
|
||||||
"body": "Reactive Resume er en gratis og open source CV-bygger, der er bygget til at gøre de hverdagsagtige opgaver med at oprette, opdatere og dele dit CV så lette som 1, 2, 3. Med denne app kan du oprette flere CV'er, dele dem med rekrutterere eller venner gennem et unikt link og udskriv det som en PDF, helt gratis, ingen annoncer, ingen sporing, uden at miste integriteten og fortroligheden af dine data.",
|
"body": "Reactive Resume er en gratis og open source CV-bygger, der er bygget til at gøre de hverdagsagtige opgaver med at oprette, opdatere og dele dit CV så lette som 1, 2, 3. Med denne app kan du oprette flere CV'er, dele dem med rekrutterere eller venner gennem et unikt link og udskriv det som en PDF, helt gratis, ingen annoncer, ingen sporing, uden at miste integriteten og fortroligheden af dine data.",
|
||||||
"heading": "Opsummering"
|
"heading": "Opsummering"
|
||||||
|
},
|
||||||
|
"testimonials": {
|
||||||
|
"body": "Godt eller dårligt, jeg ville elske at høre din mening om Reactive Resume og hvordan oplevelsen har været for dig.<br/>Her er nogle af de beskeder, der er sendt af brugere over hele verden.",
|
||||||
|
"contact": "Du kan kontakte mig via <1>min e-mail</1> eller gennem kontaktformularen på <3>min hjemmeside</3> .",
|
||||||
|
"heading": "Udtalelser"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,6 +71,31 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"heading": "Nulstil din adgangskode"
|
"heading": "Nulstil din adgangskode"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"heading": "Din konto",
|
||||||
|
"form": {
|
||||||
|
"avatar": {
|
||||||
|
"help-text": "Du kan opdatere dit profilbillede på <1>Gravatar</1>"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"label": "Fulde navn"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"label": "Email adresse",
|
||||||
|
"help-text": "Det er ikke muligt at opdatere din e-mailadresse i øjeblikket, opret en ny konto i stedet for."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete-account": {
|
||||||
|
"heading": "Slet konto og data",
|
||||||
|
"body": "For at slette din konto, dine data og alle dine CV'er skal du skrive \"{{keyword}}\" i tekstboksen og klikke på knappen. Bemærk venligst, at dette er en uigenkaldelig handling, og dine data kan ikke genfindes.",
|
||||||
|
"actions": {
|
||||||
|
"delete": "Slet konto"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"save": "Gem ændringer"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
{
|
{
|
||||||
"common": {
|
"common": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"add": "Neue {{token}} hinzufügen",
|
"add": "{{token}} hinzufügen",
|
||||||
"delete": "Löschen {{token}}",
|
"delete": "{{token}} löschen",
|
||||||
"edit": "Bearbeiten {{token}}"
|
"duplicate": "Abschnitt duplizieren",
|
||||||
|
"edit": "{{token}} bearbeiten"
|
||||||
},
|
},
|
||||||
"columns": {
|
"columns": {
|
||||||
"heading": "Spalten",
|
"heading": "Spalten",
|
||||||
@ -17,10 +18,10 @@
|
|||||||
"label": "Beschreibung"
|
"label": "Beschreibung"
|
||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"label": "E-Mail Adresse"
|
"label": "E-Mail-Adresse"
|
||||||
},
|
},
|
||||||
"end-date": {
|
"end-date": {
|
||||||
"help-text": "Dieses Feld leer lassen, wenn noch vorhanden",
|
"help-text": "Dieses Feld leer lassen, wenn dieser Eintrag noch kein Enddatum hat.",
|
||||||
"label": "Enddatum"
|
"label": "Enddatum"
|
||||||
},
|
},
|
||||||
"keywords": {
|
"keywords": {
|
||||||
@ -69,7 +70,7 @@
|
|||||||
"empty-text": "Diese Liste ist leer."
|
"empty-text": "Diese Liste ist leer."
|
||||||
},
|
},
|
||||||
"tooltip": {
|
"tooltip": {
|
||||||
"delete-item": "Sind Sie sicher, dass Sie dieses Element löschen möchten? Dies ist eine unumkehrbare Aktion.",
|
"delete-item": "Sind Sie sicher, dass Sie dieses Element löschen möchten? Dies lässt sich nicht rückgängig machen.",
|
||||||
"delete-section": "Abschnitt löschen",
|
"delete-section": "Abschnitt löschen",
|
||||||
"rename-section": "Abschnitt umbenennen",
|
"rename-section": "Abschnitt umbenennen",
|
||||||
"toggle-visibility": "Sichtbarkeit umschalten"
|
"toggle-visibility": "Sichtbarkeit umschalten"
|
||||||
@ -80,13 +81,13 @@
|
|||||||
"center-artboard": "Artboard zentrieren",
|
"center-artboard": "Artboard zentrieren",
|
||||||
"copy-link": "Link zum Lebenslauf kopieren",
|
"copy-link": "Link zum Lebenslauf kopieren",
|
||||||
"export-pdf": "PDF exportieren",
|
"export-pdf": "PDF exportieren",
|
||||||
|
"redo": "Wiederholen",
|
||||||
"toggle-orientation": "Seitenausrichtung umschalten",
|
"toggle-orientation": "Seitenausrichtung umschalten",
|
||||||
"toggle-page-break-line": "Pausenzeile umschalten",
|
"toggle-page-break-line": "Pausenzeile umschalten",
|
||||||
"toggle-sidebars": "Seitenleisten umschalten",
|
"toggle-sidebars": "Seitenleisten umschalten",
|
||||||
"zoom-in": "Vergrößern",
|
|
||||||
"zoom-out": "Verkleinern",
|
|
||||||
"undo": "Rückgängig machen",
|
"undo": "Rückgängig machen",
|
||||||
"redo": "Redo"
|
"zoom-in": "Vergrößern",
|
||||||
|
"zoom-out": "Verkleinern"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
@ -96,8 +97,8 @@
|
|||||||
"rename": "Umbenennen",
|
"rename": "Umbenennen",
|
||||||
"share-link": "Link teilen",
|
"share-link": "Link teilen",
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"delete": "Sind Sie sicher, dass Sie diesen Lebenslauf löschen möchten? Dies ist eine unumkehrbare Aktion.",
|
"delete": "Sind Sie sicher, dass Sie diesen Lebenslauf löschen möchten? Dies lässt sich nicht rückgängig machen.",
|
||||||
"share-link": "Du musst die Sichtbarkeit deines Lebenslaufs in die Öffentlichkeit ändern, um ihn für andere sichtbar zu machen."
|
"share-link": "Sie müssen die Sichtbarkeit Ihres Lebenslaufs in Öffentlich ändern, um ihn für andere sichtbar zu machen."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -106,7 +107,7 @@
|
|||||||
"awards": {
|
"awards": {
|
||||||
"form": {
|
"form": {
|
||||||
"awarder": {
|
"awarder": {
|
||||||
"label": "Auszeichnung"
|
"label": "Auszeichner"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -114,20 +115,20 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"photo-filters": "Fotofilter"
|
"photo-filters": "Fotofilter"
|
||||||
},
|
},
|
||||||
|
"birthdate": {
|
||||||
|
"label": "Geburtsdatum"
|
||||||
|
},
|
||||||
"heading": "Grundlagen",
|
"heading": "Grundlagen",
|
||||||
"headline": {
|
"headline": {
|
||||||
"label": "Überschrift"
|
"label": "Überschrift"
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"label": "Voller Name"
|
"label": "Vollständiger Name"
|
||||||
},
|
|
||||||
"birthdate": {
|
|
||||||
"label": "Geburtsdatum"
|
|
||||||
},
|
},
|
||||||
"photo-filters": {
|
"photo-filters": {
|
||||||
"effects": {
|
"effects": {
|
||||||
"border": {
|
"border": {
|
||||||
"label": "Grenze"
|
"label": "Rahmen"
|
||||||
},
|
},
|
||||||
"grayscale": {
|
"grayscale": {
|
||||||
"label": "Graustufen"
|
"label": "Graustufen"
|
||||||
@ -158,13 +159,13 @@
|
|||||||
"education": {
|
"education": {
|
||||||
"form": {
|
"form": {
|
||||||
"area-study": {
|
"area-study": {
|
||||||
"label": "Studienbereich"
|
"label": "Studienfach"
|
||||||
},
|
},
|
||||||
"courses": {
|
"courses": {
|
||||||
"label": "Kurse"
|
"label": "Kurse"
|
||||||
},
|
},
|
||||||
"degree": {
|
"degree": {
|
||||||
"label": "Grad"
|
"label": "Abschluss"
|
||||||
},
|
},
|
||||||
"grade": {
|
"grade": {
|
||||||
"label": "Note"
|
"label": "Note"
|
||||||
@ -174,9 +175,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"experience": {
|
||||||
|
"form": {
|
||||||
|
"name": {
|
||||||
|
"label": "Company Name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"location": {
|
"location": {
|
||||||
"address": {
|
"address": {
|
||||||
"label": "Adresse"
|
"label": "Straße"
|
||||||
},
|
},
|
||||||
"city": {
|
"city": {
|
||||||
"label": "Stadt"
|
"label": "Stadt"
|
||||||
@ -184,12 +192,12 @@
|
|||||||
"country": {
|
"country": {
|
||||||
"label": "Land"
|
"label": "Land"
|
||||||
},
|
},
|
||||||
"heading": "Standort",
|
"heading": "Anschrift",
|
||||||
"postal-code": {
|
"postal-code": {
|
||||||
"label": "Postleitzahl"
|
"label": "Postleitzahl"
|
||||||
},
|
},
|
||||||
"region": {
|
"region": {
|
||||||
"label": "Region"
|
"label": "Bundesland"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"profiles": {
|
"profiles": {
|
||||||
@ -201,7 +209,7 @@
|
|||||||
"label": "Benutzername"
|
"label": "Benutzername"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"heading": "Profiles",
|
"heading": "Soziale Netzwerke",
|
||||||
"heading_one": "Profil"
|
"heading_one": "Profil"
|
||||||
},
|
},
|
||||||
"publications": {
|
"publications": {
|
||||||
@ -239,16 +247,16 @@
|
|||||||
"heading": "Exportieren",
|
"heading": "Exportieren",
|
||||||
"json": {
|
"json": {
|
||||||
"primary": "JSON",
|
"primary": "JSON",
|
||||||
"secondary": "Laden Sie eine JSON-Version Ihres Lebenslaufs herunter, die Sie wieder in Reaktives Lebenslauf importieren können."
|
"secondary": "Laden Sie eine JSON-Version Ihres Lebenslaufs herunter, die Sie wieder in Reactive Resume importieren können."
|
||||||
},
|
},
|
||||||
"pdf": {
|
"pdf": {
|
||||||
"loading": {
|
"loading": {
|
||||||
"primary": "PDF wird erstellt",
|
"primary": "PDF wird erstellt",
|
||||||
"secondary": "Bitte warten Sie, wenn Ihr PDF generiert wird, dies kann bis zu 15 Sekunden dauern."
|
"secondary": "Bitte warten Sie, während Ihr PDF generiert wird. Dies kann bis zu 15 Sekunden dauern."
|
||||||
},
|
},
|
||||||
"normal": {
|
"normal": {
|
||||||
"primary": "PDF",
|
"primary": "PDF",
|
||||||
"secondary": "Laden Sie sich ein PDF Ihres Lebenslaufs herunter, das Sie ausdrucken und an Ihren Traumjob senden können. Diese Datei kann nicht zur weiteren Bearbeitung importiert werden."
|
"secondary": "Laden Sie sich ein PDF Ihres Lebenslaufs herunter, dass Sie ausdrucken oder an Ihren Traumarbeitgeber senden können. Diese Datei kann nicht zur weiteren Bearbeitung importiert werden."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -260,60 +268,61 @@
|
|||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
"bugs-features": {
|
"bugs-features": {
|
||||||
"body": "Hält Sie etwas davon ab, einen Lebenslauf zu erstellen? Oder haben Sie eine tolle Idee, die Sie hinzufügen möchten? Erhöhen Sie einen Eintrag auf GitHub, um loszulegen.",
|
"body": "Sind Sie bei der Erstellung Ihres Lebenslaufs auf ein Problem gestoßen? Oder haben Sie eine tolle Idee, die Sie hinzufügen möchten? Erstellen Sie ein Ticket auf GitHub.",
|
||||||
"button": "GitHub Themen",
|
"button": "GitHub Themen",
|
||||||
"heading": "Fehler? Feature-Anfragen?"
|
"heading": "Fehler? Verbesserungsvorschläge?"
|
||||||
},
|
},
|
||||||
|
"docs": "Dokumentation",
|
||||||
"donate": {
|
"donate": {
|
||||||
"body": "Wenn Ihnen Reactive Resume gefallen hat, denken Sie bitte darüber nach, so viel wie möglich zu spenden, damit die App für immer kostenlos und werbefrei bleibt.",
|
"body": "Sollte Ihnen Reactive Resume gefallen, möchte ich Sie bitten, etwas zu spenden, damit die App für immer kostenlos und werbefrei bleibt.",
|
||||||
"button": "Kaufe mir einen Kaffee",
|
"button": "Kaufe mir einen Kaffee",
|
||||||
"heading": "Spenden an Reaktives Lebenslauf"
|
"heading": "Spenden Sie an Reactive Resume."
|
||||||
},
|
},
|
||||||
"github": "Quellcode",
|
"github": "Quellcode",
|
||||||
"docs": "Dokumentation",
|
"heading": "Links",
|
||||||
"heading": "Links"
|
"reddit": "Reddit"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"global": {
|
"global": {
|
||||||
"date": {
|
"date": {
|
||||||
"primary": "Datum",
|
"primary": "Datum",
|
||||||
"secondary": "Datumsformat für die gesamte App"
|
"secondary": "Datumsformat für die gesamte App."
|
||||||
},
|
},
|
||||||
"heading": "Globale",
|
"heading": "Globale",
|
||||||
"language": {
|
"language": {
|
||||||
"primary": "Sprache",
|
"primary": "Sprache",
|
||||||
"secondary": "Sprache anzeigen, die in der gesamten App verwendet wird"
|
"secondary": "Anzeigesprache, die in der gesamten App verwendet wird."
|
||||||
},
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"primary": "Thema"
|
"primary": "App Design"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"heading": "Einstellungen",
|
"heading": "Einstellungen",
|
||||||
"page": {
|
"page": {
|
||||||
"format": {
|
|
||||||
"primary": "Papier größe",
|
|
||||||
"secondary": "Legt die Abmessungen Ihrer Lebenslaufseiten fest"
|
|
||||||
},
|
|
||||||
"break-line": {
|
"break-line": {
|
||||||
"primary": "Linie anhalten",
|
"primary": "Seitenumbruch anzeigen",
|
||||||
"secondary": "Zeile auf allen Seiten anzeigen, um die Höhe einer A4-Seite zu markieren"
|
"secondary": "Zeigt den Seitenumbruch als Linie auf allen Seiten an."
|
||||||
|
},
|
||||||
|
"format": {
|
||||||
|
"primary": "Papiergröße",
|
||||||
|
"secondary": "Legt die Seitenabmessungen Ihres Lebenslaufs fest."
|
||||||
},
|
},
|
||||||
"heading": "Seite",
|
"heading": "Seite",
|
||||||
"orientation": {
|
"orientation": {
|
||||||
"disabled": "Hat keine Auswirkung, wenn nur eine Seite vorhanden ist",
|
"disabled": "Hat keine Auswirkung, wenn nur eine Seite vorhanden ist.",
|
||||||
"primary": "Ausrichtung",
|
"primary": "Ausrichtung",
|
||||||
"secondary": "Ob Seiten horizontal oder vertikal angezeigt werden sollen"
|
"secondary": "Legt fest, ob Seiten horizontal oder vertikal angezeigt werden sollen."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"resume": {
|
"resume": {
|
||||||
"heading": "Lebenslauf",
|
"heading": "Lebenslauf",
|
||||||
"reset": {
|
"reset": {
|
||||||
"primary": "Alles zurücksetzen",
|
"primary": "Alles zurücksetzen",
|
||||||
"secondary": "Zu viele Fehler gemacht? Klicken Sie hier, um alle Änderungen zurückzusetzen und bei Null zu beginnen. Sei vorsichtig, diese Aktion kann nicht rückgängig gemacht werden."
|
"secondary": "Zu viele Fehler gemacht? Klicken Sie hier, um alle Änderungen zurückzusetzen und von vorne zu beginnen. Vorsicht! Diese Aktion kann nicht rückgängig gemacht werden."
|
||||||
},
|
},
|
||||||
"sample": {
|
"sample": {
|
||||||
"primary": "Beispieldaten laden",
|
"primary": "Beispieldaten laden",
|
||||||
"secondary": "Nicht sicher, wo man anfangen soll? Klicken Sie hier, um ein paar Beispieldaten zu laden, um zu sehen, wie ein vollständiger Lebenslauf aussieht."
|
"secondary": "Sie sind nicht sicher, wo Sie anfangen sollen? Klicken Sie hier, um Beispieldaten zu laden. So können Sie sich ansehen, wie ein vollständiger Lebenslauf aussieht."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -323,8 +332,8 @@
|
|||||||
"label": "Kurze URL bevorzugen"
|
"label": "Kurze URL bevorzugen"
|
||||||
},
|
},
|
||||||
"visibility": {
|
"visibility": {
|
||||||
"subtitle": "Erlaube jemandem mit einem Link deinen Lebenslauf anzusehen",
|
"subtitle": "Erlaubt jedem, dem Sie diesen Link schicken, Ihren Lebenslauf anzusehen.",
|
||||||
"title": "Öffentlich"
|
"title": "Öffentlich zugänglich"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"templates": {
|
"templates": {
|
||||||
@ -333,16 +342,16 @@
|
|||||||
"theme": {
|
"theme": {
|
||||||
"form": {
|
"form": {
|
||||||
"background": {
|
"background": {
|
||||||
"label": "Hintergrund"
|
"label": "Hintergrundfarbe"
|
||||||
},
|
},
|
||||||
"primary": {
|
"primary": {
|
||||||
"label": "Primär"
|
"label": "Primärfarbe"
|
||||||
},
|
},
|
||||||
"text": {
|
"text": {
|
||||||
"label": "Text"
|
"label": "Textfarbe"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"heading": "Thema"
|
"heading": "Lebenslauf Design"
|
||||||
},
|
},
|
||||||
"typography": {
|
"typography": {
|
||||||
"form": {
|
"form": {
|
||||||
|
|||||||
@ -13,14 +13,14 @@
|
|||||||
"help-text": "Dieser Abschnitt unterstützt <1>Markdown</1> Formatierung."
|
"help-text": "Dieser Abschnitt unterstützt <1>Markdown</1> Formatierung."
|
||||||
},
|
},
|
||||||
"date": {
|
"date": {
|
||||||
"present": "Gegenwärtig"
|
"present": "Heute"
|
||||||
},
|
},
|
||||||
"subtitle": "Ein freier und Open-Source-Lebenslauf-Builder.",
|
"subtitle": "Ein kostenloser Open Source Lebenslauf-Baukasten.",
|
||||||
"title": "Reaktives Lebenslauf",
|
"title": "Reactive Resume",
|
||||||
"toast": {
|
"toast": {
|
||||||
"error": {
|
"error": {
|
||||||
"upload-file-size": "Bitte laden Sie nur Dateien unter 2 Megabytes hoch.",
|
"upload-file-size": "Bitte laden Sie nur Dateien unter 2 Megabytes hoch.",
|
||||||
"upload-photo-size": "Bitte laden Sie nur Fotos unter 2 Megabytes hoch, vorzugsweise quadratisch."
|
"upload-photo-size": "Bitte laden Sie nur Fotos unter 2 Megabytes hoch, am besten in einem quadratischen Format."
|
||||||
},
|
},
|
||||||
"success": {
|
"success": {
|
||||||
"resume-link-copied": "Ein Link zu deinem Lebenslauf wurde in deine Zwischenablage kopiert."
|
"resume-link-copied": "Ein Link zu deinem Lebenslauf wurde in deine Zwischenablage kopiert."
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user