mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-11 21:12:42 +10:00
Compare commits
467 Commits
| 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 | |||
| 3a7b98d30e | |||
| 284a39aa77 | |||
| c14c9955dd | |||
| 4de787157a | |||
| 6bc6425a01 | |||
| 6051305908 | |||
| 5e13253454 | |||
| c1fd2b40e3 | |||
| fccf7a7b56 | |||
| 5098b094db | |||
| 7c1eb74aca | |||
| 7f9ede8ff0 | |||
| 172b23e429 | |||
| f287ca6183 | |||
| 7548e36aaf | |||
| 2f754616b4 | |||
| dc51f6f9b2 | |||
| 0cdac1d657 | |||
| c3cfe8ae7b | |||
| 08435c173b | |||
| 62cc2d6eac | |||
| 5a6f6e2b6c | |||
| 2dfa8c04a1 | |||
| 63e3f94d2d | |||
| 7f45a8cb7f | |||
| 4377ebb811 | |||
| ed3af6975b | |||
| 7904905a8b | |||
| bd2e6d2bf2 | |||
| ae4e9e688e | |||
| 78c45b7019 | |||
| 9a2fbbec4e | |||
| 93d751d9be | |||
| 9926ed2262 | |||
| 62220d20e7 | |||
| 2f6108cd29 | |||
| 7aeed37869 | |||
| ed99659b7b | |||
| 5e33d00910 | |||
| c00d0341e6 | |||
| 511ae036c2 | |||
| 1642ec9ba2 | |||
| 1115bc2b69 | |||
| 27e5c7811c | |||
| 3b739f0bb7 | |||
| d937ba2056 | |||
| de110d7de1 | |||
| b03229b5e0 | |||
| 280fc73c7b | |||
| 677ad2a115 | |||
| f394b26d18 | |||
| aab4e2e941 | |||
| f0f552a635 | |||
| 136e143e12 | |||
| 857e4b8670 | |||
| ff03d41d97 | |||
| 2bad37aaf3 | |||
| 3a40fbf78b | |||
| 49c638fb18 | |||
| 50e8d60773 | |||
| bf157a8d1a | |||
| c4f5955fcd | |||
| 86d33b0f21 | |||
| 56bca30639 | |||
| eed3b76959 | |||
| 615eb3ad5d | |||
| b505199319 | |||
| 91e55e642c | |||
| f549d8749a | |||
| f31123659e | |||
| 93633c9415 | |||
| 19b9fa4857 | |||
| a5c84214f9 | |||
| 65bb8b5ceb | |||
| 06a11a1f2a | |||
| 53eedc8500 | |||
| 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.0.2
|
uses: actions/checkout@v3.5.2
|
||||||
with:
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
- id: version
|
- id: version
|
||||||
name: Get Version
|
name: App Version
|
||||||
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
|
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-${{ steps.version.outputs.tag }}
|
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-${{ steps.version.outputs.tag }}
|
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.0.2
|
|
||||||
with:
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
- id: version
|
|
||||||
name: Get Version
|
|
||||||
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
|
|
||||||
|
|
||||||
- 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-${{ steps.version.outputs.tag }}
|
|
||||||
ghcr.io/amruthpillai/reactive-resume:server-latest
|
|
||||||
ghcr.io/amruthpillai/reactive-resume:server-${{ steps.version.outputs.tag }}
|
|
||||||
|
|||||||
84
.github/workflows/docker-build.yml
vendored
84
.github/workflows/docker-build.yml
vendored
@ -1,84 +0,0 @@
|
|||||||
name: Build Docker Image
|
|
||||||
|
|
||||||
on: pull_request
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
client:
|
|
||||||
name: Client
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
env:
|
|
||||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
|
||||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout the repository
|
|
||||||
uses: actions/checkout@v3.0.2
|
|
||||||
with:
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2.0.0
|
|
||||||
|
|
||||||
- id: buildx
|
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2.0.0
|
|
||||||
with:
|
|
||||||
install: true
|
|
||||||
|
|
||||||
- id: variables
|
|
||||||
name: Get Short SHA
|
|
||||||
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
|
|
||||||
|
|
||||||
- name: Build Client Image
|
|
||||||
uses: docker/build-push-action@v3.1.1
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: false
|
|
||||||
file: client/Dockerfile
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
tags: |
|
|
||||||
amruthpillai/reactive-resume:client-latest
|
|
||||||
amruthpillai/reactive-resume:client-${{ steps.variables.outputs.sha_short }}
|
|
||||||
ghcr.io/amruthpillai/reactive-resume:client-latest
|
|
||||||
ghcr.io/amruthpillai/reactive-resume:client-${{ steps.variables.outputs.sha_short }}
|
|
||||||
|
|
||||||
server:
|
|
||||||
name: Server
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
env:
|
|
||||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
|
||||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout the repository
|
|
||||||
uses: actions/checkout@v3.0.2
|
|
||||||
with:
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2.0.0
|
|
||||||
|
|
||||||
- id: buildx
|
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2.0.0
|
|
||||||
with:
|
|
||||||
install: true
|
|
||||||
|
|
||||||
- id: variables
|
|
||||||
name: Get Short SHA
|
|
||||||
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
|
|
||||||
|
|
||||||
- name: Build Server Image
|
|
||||||
uses: docker/build-push-action@v3.1.1
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: false
|
|
||||||
file: server/Dockerfile
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
tags: |
|
|
||||||
amruthpillai/reactive-resume:server-latest
|
|
||||||
amruthpillai/reactive-resume:server-${{ steps.variables.outputs.sha_short }}
|
|
||||||
ghcr.io/amruthpillai/reactive-resume:server-latest
|
|
||||||
ghcr.io/amruthpillai/reactive-resume:server-${{ steps.variables.outputs.sha_short }}
|
|
||||||
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
|
||||||
19
README.md
19
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,6 +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](#go-to-app--docs)
|
||||||
- [Table of Contents](#table-of-contents)
|
- [Table of Contents](#table-of-contents)
|
||||||
- [Features](#features)
|
- [Features](#features)
|
||||||
- [Languages](#languages)
|
- [Languages](#languages)
|
||||||
@ -26,6 +28,8 @@ You have complete control over what goes into your resume, how it looks, what co
|
|||||||
- [Contributing](#contributing)
|
- [Contributing](#contributing)
|
||||||
- [Report Bugs and Feature Requests](#report-bugs-and-feature-requests)
|
- [Report Bugs and Feature Requests](#report-bugs-and-feature-requests)
|
||||||
- [Donations](#donations)
|
- [Donations](#donations)
|
||||||
|
- [GitHub Sponsor](#github-sponsor)
|
||||||
|
- [PayPal](#paypal)
|
||||||
- [Infrastructure](#infrastructure)
|
- [Infrastructure](#infrastructure)
|
||||||
- [Contributors Wall](#contributors-wall)
|
- [Contributors Wall](#contributors-wall)
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
@ -52,6 +56,7 @@ You have complete control over what goes into your resume, how it looks, what co
|
|||||||
|
|
||||||
## Languages
|
## Languages
|
||||||
|
|
||||||
|
- Amharic (አማርኛ)
|
||||||
- Arabic (اَلْعَرَبِيَّةُ)
|
- Arabic (اَلْعَرَبِيَّةُ)
|
||||||
- Bengali (বাংলা)
|
- Bengali (বাংলা)
|
||||||
- Bulgarian (български)
|
- Bulgarian (български)
|
||||||
@ -89,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!
|
||||||
@ -100,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
|
||||||
|
|
||||||
@ -125,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.10' 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 \
|
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(() => {
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.break::after {
|
&.break::after {
|
||||||
content: 'A4 Page Break';
|
content: 'Page Break';
|
||||||
top: calc(297mm - 19px);
|
top: calc(297mm - 19px);
|
||||||
|
|
||||||
@apply absolute w-full border-b border-dashed border-neutral-800/75;
|
@apply absolute w-full border-b border-dashed border-neutral-800/75;
|
||||||
@ -28,10 +28,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown {
|
&.format-letter {
|
||||||
ul {
|
width: 216mm;
|
||||||
padding-left: 1.5em;
|
min-height: 279mm;
|
||||||
text-indent: -1.5em;
|
|
||||||
|
&.break::after {
|
||||||
|
top: calc(279mm - 19px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { CustomCSS, Theme, 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';
|
||||||
@ -23,22 +23,24 @@ const Page: React.FC<Props> = ({ page, showPageNumbers = false }) => {
|
|||||||
const resume = useAppSelector((state) => state.resume.present);
|
const resume = useAppSelector((state) => state.resume.present);
|
||||||
const breakLine: boolean = useAppSelector((state) => state.build.page.breakLine);
|
const breakLine: boolean = useAppSelector((state) => state.build.page.breakLine);
|
||||||
|
|
||||||
const theme: Theme = 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 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]);
|
||||||
const TemplatePage: React.FC<PageProps> | null = useMemo(() => templateMap[template].component, [template]);
|
const TemplatePage: React.FC<PageProps> | null = useMemo(() => templateMap[template].component, [template]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-page={page + 1} className={styles.container}>
|
<div className={styles.container} data-page={page + 1} data-format={pageConfig?.format || 'A4'}>
|
||||||
<div
|
<div
|
||||||
className={clsx({
|
className={clsx({
|
||||||
reset: true,
|
reset: true,
|
||||||
[styles.page]: true,
|
[styles.page]: true,
|
||||||
[styles.break]: breakLine,
|
[styles.break]: breakLine,
|
||||||
|
[styles['format-letter']]: pageConfig?.format === 'Letter',
|
||||||
[css(themeCSS)]: true,
|
[css(themeCSS)]: true,
|
||||||
[css(typographyCSS)]: true,
|
[css(typographyCSS)]: true,
|
||||||
[css(customCSS.value)]: customCSS.visible,
|
[css(customCSS.value)]: customCSS.visible,
|
||||||
|
|||||||
@ -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, 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';
|
||||||
@ -48,9 +48,10 @@ const Settings = () => {
|
|||||||
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 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 exampleString = 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>(
|
||||||
@ -60,6 +61,9 @@ const Settings = () => {
|
|||||||
|
|
||||||
const handleSetTheme = (value: boolean) => dispatch(setTheme({ theme: value ? 'dark' : 'light' }));
|
const handleSetTheme = (value: boolean) => dispatch(setTheme({ theme: value ? 'dark' : 'light' }));
|
||||||
|
|
||||||
|
const handleChangePageFormat = (value: PageConfig['format'] | null) =>
|
||||||
|
dispatch(setResumeState({ path: 'metadata.page.format', value }));
|
||||||
|
|
||||||
const handleChangeDateFormat = (value: string | null) =>
|
const handleChangeDateFormat = (value: string | null) =>
|
||||||
dispatch(setResumeState({ path: 'metadata.date.format', value }));
|
dispatch(setResumeState({ path: 'metadata.date.format', value }));
|
||||||
|
|
||||||
@ -94,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">
|
||||||
@ -118,13 +122,13 @@ const Settings = () => {
|
|||||||
primary={t<string>('builder.rightSidebar.sections.settings.global.date.primary')}
|
primary={t<string>('builder.rightSidebar.sections.settings.global.date.primary')}
|
||||||
secondary={t<string>('builder.rightSidebar.sections.settings.global.date.secondary')}
|
secondary={t<string>('builder.rightSidebar.sections.settings.global.date.secondary')}
|
||||||
/>
|
/>
|
||||||
<Autocomplete<string, false, boolean, false>
|
<Autocomplete<string, false, true, false>
|
||||||
disableClearable
|
disableClearable
|
||||||
className="my-2 w-full"
|
className="my-2 w-full"
|
||||||
options={dateFormatOptions}
|
options={dateFormatOptions}
|
||||||
value={dateConfig.format}
|
value={dateConfig.format}
|
||||||
onChange={(_, value) => handleChangeDateFormat(value)}
|
onChange={(_, value) => handleChangeDateFormat(value)}
|
||||||
renderInput={(params) => <TextField {...params} helperText={exampleString} />}
|
renderInput={(params) => <TextField {...params} helperText={exampleDateString} />}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
@ -134,7 +138,7 @@ const Settings = () => {
|
|||||||
primary={t<string>('builder.rightSidebar.sections.settings.global.language.primary')}
|
primary={t<string>('builder.rightSidebar.sections.settings.global.language.primary')}
|
||||||
secondary={t<string>('builder.rightSidebar.sections.settings.global.language.secondary')}
|
secondary={t<string>('builder.rightSidebar.sections.settings.global.language.secondary')}
|
||||||
/>
|
/>
|
||||||
<Autocomplete<Language, false, boolean, false>
|
<Autocomplete<Language, false, true, false>
|
||||||
disableClearable
|
disableClearable
|
||||||
className="my-2 w-full"
|
className="my-2 w-full"
|
||||||
options={languages}
|
options={languages}
|
||||||
@ -159,6 +163,23 @@ const Settings = () => {
|
|||||||
{t<string>('builder.rightSidebar.sections.settings.page.heading')}
|
{t<string>('builder.rightSidebar.sections.settings.page.heading')}
|
||||||
</ListSubheader>
|
</ListSubheader>
|
||||||
|
|
||||||
|
<ListItem className="flex-col">
|
||||||
|
<ListItemText
|
||||||
|
className="w-full"
|
||||||
|
primary={t<string>('builder.rightSidebar.sections.settings.page.format.primary')}
|
||||||
|
secondary={t<string>('builder.rightSidebar.sections.settings.page.format.secondary')}
|
||||||
|
/>
|
||||||
|
<Autocomplete<PageConfig['format'], false, true, false>
|
||||||
|
disableClearable
|
||||||
|
defaultValue="A4"
|
||||||
|
className="my-2 w-full"
|
||||||
|
options={['A4', 'Letter']}
|
||||||
|
value={pageConfig?.format || 'A4'}
|
||||||
|
renderInput={(params) => <TextField {...params} />}
|
||||||
|
onChange={(_, value) => handleChangePageFormat(value)}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t<string>('builder.rightSidebar.sections.settings.page.orientation.primary')}
|
primary={t<string>('builder.rightSidebar.sections.settings.page.orientation.primary')}
|
||||||
@ -191,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 />
|
||||||
@ -203,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 { Theme as ThemeType } 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';
|
||||||
@ -16,7 +16,7 @@ const Theme = () => {
|
|||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const { background, text, primary } = useAppSelector<ThemeType>((state) =>
|
const { background, text, primary } = useAppSelector<ThemeConfig>((state) =>
|
||||||
get(state.resume.present, 'metadata.theme')
|
get(state.resume.present, 'metadata.theme')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -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());
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,23 +2,25 @@ export type Language = {
|
|||||||
code: string;
|
code: string;
|
||||||
name: string;
|
name: string;
|
||||||
localName?: string;
|
localName?: string;
|
||||||
|
isRTL?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const languages: Language[] = [
|
export const languages: Language[] = [
|
||||||
{ code: 'ar', name: 'Arabic', localName: 'اَلْعَرَبِيَّةُ' },
|
{ code: 'am', name: 'Amharic', localName: 'አማርኛ' },
|
||||||
|
{ code: 'ar', name: 'Arabic', localName: 'اَلْعَرَبِيَّةُ', isRTL: true },
|
||||||
{ code: 'bg', name: 'Bulgarian', localName: 'български' },
|
{ code: 'bg', name: 'Bulgarian', localName: 'български' },
|
||||||
{ code: 'bn', name: 'Bengali', localName: 'বাংলা' },
|
{ code: 'bn', name: 'Bengali', localName: 'বাংলা' },
|
||||||
{ 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' },
|
||||||
{ code: 'fa', name: 'Persian', localName: 'فارسی' },
|
{ code: 'fa', name: 'Persian', localName: 'فارسی', isRTL: true },
|
||||||
{ code: 'fi', name: 'Finnish', localName: 'Suomi' },
|
{ code: 'fi', name: 'Finnish', localName: 'Suomi' },
|
||||||
{ code: 'fr', name: 'French', localName: 'Français' },
|
{ code: 'fr', name: 'French', localName: 'Français' },
|
||||||
{ code: 'he', name: 'Hebrew', localName: 'Ivrit' },
|
{ code: 'he', name: 'Hebrew', localName: 'Ivrit', isRTL: true },
|
||||||
{ code: 'hi', name: 'Hindi', localName: 'हिन्दी' },
|
{ code: 'hi', name: 'Hindi', localName: 'हिन्दी' },
|
||||||
{ code: 'hu', name: 'Hungarian', localName: 'Magyar' },
|
{ code: 'hu', name: 'Hungarian', localName: 'Magyar' },
|
||||||
{ code: 'id', name: 'Indonesian', localName: 'Bahasa Indonesia' },
|
{ code: 'id', name: 'Indonesian', localName: 'Bahasa Indonesia' },
|
||||||
@ -35,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}>
|
||||||
@ -162,15 +155,15 @@ const LoginModal: React.FC = () => {
|
|||||||
{!FLAG_DISABLE_SIGNUPS && (
|
{!FLAG_DISABLE_SIGNUPS && (
|
||||||
<p className="text-xs">
|
<p className="text-xs">
|
||||||
<Trans t={t} i18nKey="modals.auth.login.register-text">
|
<Trans t={t} i18nKey="modals.auth.login.register-text">
|
||||||
If you don't have one, you can <a onClick={handleCreateAccount}>create an account</a> here.
|
If you don't have one, you can <a onClick={handleCreateAccount}>create an account here.</a>
|
||||||
</Trans>
|
</Trans>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<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</a>
|
In case you have forgotten your password, you can
|
||||||
here.
|
<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';
|
||||||
@ -93,7 +93,7 @@ const AwardModal: React.FC = () => {
|
|||||||
heading={isEditMode ? editText : addText}
|
heading={isEditMode ? editText : addText}
|
||||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||||
>
|
>
|
||||||
<form className="my-2 grid grid-cols-2 gap-4">
|
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Controller
|
<Controller
|
||||||
name="title"
|
name="title"
|
||||||
control={control}
|
control={control}
|
||||||
@ -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}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -177,6 +179,7 @@ const AwardModal: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<input type="submit" style={{ display: 'none' }} />
|
||||||
</form>
|
</form>
|
||||||
</BaseModal>
|
</BaseModal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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';
|
||||||
@ -93,7 +93,7 @@ const CertificateModal: React.FC = () => {
|
|||||||
heading={isEditMode ? editText : addText}
|
heading={isEditMode ? editText : addText}
|
||||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||||
>
|
>
|
||||||
<form className="my-2 grid grid-cols-2 gap-4">
|
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Controller
|
<Controller
|
||||||
name="name"
|
name="name"
|
||||||
control={control}
|
control={control}
|
||||||
@ -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}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -177,6 +179,7 @@ const CertificateModal: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<input type="submit" style={{ display: 'none' }} />
|
||||||
</form>
|
</form>
|
||||||
</BaseModal>
|
</BaseModal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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]);
|
||||||
|
|
||||||
@ -110,7 +111,7 @@ const CustomModal: React.FC = () => {
|
|||||||
heading={isEditMode ? editText : addText}
|
heading={isEditMode ? editText : addText}
|
||||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||||
>
|
>
|
||||||
<form className="my-2 grid grid-cols-2 gap-4">
|
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Controller
|
<Controller
|
||||||
name="title"
|
name="title"
|
||||||
control={control}
|
control={control}
|
||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
@ -282,6 +287,7 @@ const CustomModal: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<input type="submit" style={{ display: 'none' }} />
|
||||||
</form>
|
</form>
|
||||||
</BaseModal>
|
</BaseModal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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';
|
||||||
@ -106,7 +106,7 @@ const EducationModal: React.FC = () => {
|
|||||||
heading={isEditMode ? editText : addText}
|
heading={isEditMode ? editText : addText}
|
||||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||||
>
|
>
|
||||||
<form className="my-2 grid grid-cols-2 gap-4">
|
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Controller
|
<Controller
|
||||||
name="institution"
|
name="institution"
|
||||||
control={control}
|
control={control}
|
||||||
@ -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')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -255,6 +259,7 @@ const EducationModal: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<input type="submit" style={{ display: 'none' }} />
|
||||||
</form>
|
</form>
|
||||||
</BaseModal>
|
</BaseModal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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';
|
||||||
@ -84,7 +84,7 @@ const InterestModal: React.FC = () => {
|
|||||||
heading={isEditMode ? editText : addText}
|
heading={isEditMode ? editText : addText}
|
||||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||||
>
|
>
|
||||||
<form className="my-2 grid grid-cols-2 gap-4">
|
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Controller
|
<Controller
|
||||||
name="name"
|
name="name"
|
||||||
control={control}
|
control={control}
|
||||||
@ -114,6 +114,7 @@ const InterestModal: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<input type="submit" style={{ display: 'none' }} />
|
||||||
</form>
|
</form>
|
||||||
</BaseModal>
|
</BaseModal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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';
|
||||||
@ -85,7 +85,7 @@ const LanguageModal: React.FC = () => {
|
|||||||
heading={isEditMode ? editText : addText}
|
heading={isEditMode ? editText : addText}
|
||||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||||
>
|
>
|
||||||
<form className="my-2 grid grid-cols-2 gap-4">
|
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Controller
|
<Controller
|
||||||
name="name"
|
name="name"
|
||||||
control={control}
|
control={control}
|
||||||
@ -150,6 +150,7 @@ const LanguageModal: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<input type="submit" style={{ display: 'none' }} />
|
||||||
</form>
|
</form>
|
||||||
</BaseModal>
|
</BaseModal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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';
|
||||||
@ -89,7 +89,7 @@ const ProfileModal: React.FC = () => {
|
|||||||
handleClose={handleClose}
|
handleClose={handleClose}
|
||||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||||
>
|
>
|
||||||
<form className="my-2 grid grid-cols-2 gap-4">
|
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Controller
|
<Controller
|
||||||
name="network"
|
name="network"
|
||||||
control={control}
|
control={control}
|
||||||
@ -136,6 +136,7 @@ const ProfileModal: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<input type="submit" style={{ display: 'none' }} />
|
||||||
</form>
|
</form>
|
||||||
</BaseModal>
|
</BaseModal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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';
|
||||||
@ -102,7 +102,7 @@ const ProjectModal: React.FC = () => {
|
|||||||
heading={isEditMode ? editText : addText}
|
heading={isEditMode ? editText : addText}
|
||||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||||
>
|
>
|
||||||
<form className="my-2 grid grid-cols-2 gap-4">
|
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Controller
|
<Controller
|
||||||
name="name"
|
name="name"
|
||||||
control={control}
|
control={control}
|
||||||
@ -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'}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -225,6 +229,7 @@ const ProjectModal: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<input type="submit" style={{ display: 'none' }} />
|
||||||
</form>
|
</form>
|
||||||
</BaseModal>
|
</BaseModal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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';
|
||||||
@ -93,7 +93,7 @@ const PublicationModal: React.FC = () => {
|
|||||||
heading={isEditMode ? editText : addText}
|
heading={isEditMode ? editText : addText}
|
||||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||||
>
|
>
|
||||||
<form className="my-2 grid grid-cols-2 gap-4">
|
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Controller
|
<Controller
|
||||||
name="name"
|
name="name"
|
||||||
control={control}
|
control={control}
|
||||||
@ -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}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -177,6 +179,7 @@ const PublicationModal: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<input type="submit" style={{ display: 'none' }} />
|
||||||
</form>
|
</form>
|
||||||
</BaseModal>
|
</BaseModal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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';
|
||||||
@ -90,7 +90,7 @@ const ReferenceModal: React.FC = () => {
|
|||||||
heading={isEditMode ? editText : addText}
|
heading={isEditMode ? editText : addText}
|
||||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||||
>
|
>
|
||||||
<form className="my-2 grid grid-cols-2 gap-4">
|
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Controller
|
<Controller
|
||||||
name="name"
|
name="name"
|
||||||
control={control}
|
control={control}
|
||||||
@ -162,6 +162,7 @@ const ReferenceModal: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<input type="submit" style={{ display: 'none' }} />
|
||||||
</form>
|
</form>
|
||||||
</BaseModal>
|
</BaseModal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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';
|
||||||
@ -88,7 +88,7 @@ const SkillModal: React.FC = () => {
|
|||||||
heading={isEditMode ? editText : addText}
|
heading={isEditMode ? editText : addText}
|
||||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||||
>
|
>
|
||||||
<form className="my-2 grid grid-cols-2 gap-4">
|
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Controller
|
<Controller
|
||||||
name="name"
|
name="name"
|
||||||
control={control}
|
control={control}
|
||||||
@ -166,6 +166,8 @@ const SkillModal: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<input type="submit" style={{ display: 'none' }} />
|
||||||
</form>
|
</form>
|
||||||
</BaseModal>
|
</BaseModal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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';
|
||||||
@ -99,7 +99,7 @@ const VolunteerModal: React.FC = () => {
|
|||||||
heading={isEditMode ? editText : addText}
|
heading={isEditMode ? editText : addText}
|
||||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||||
>
|
>
|
||||||
<form className="my-2 grid grid-cols-2 gap-4">
|
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Controller
|
<Controller
|
||||||
name="organization"
|
name="organization"
|
||||||
control={control}
|
control={control}
|
||||||
@ -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'}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -208,6 +212,7 @@ const VolunteerModal: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<input type="submit" style={{ display: 'none' }} />
|
||||||
</form>
|
</form>
|
||||||
</BaseModal>
|
</BaseModal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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 },
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -99,7 +111,7 @@ const WorkModal: React.FC = () => {
|
|||||||
heading={isEditMode ? editText : addText}
|
heading={isEditMode ? editText : addText}
|
||||||
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
|
||||||
>
|
>
|
||||||
<form className="my-2 grid grid-cols-2 gap-4">
|
<form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Controller
|
<Controller
|
||||||
name="name"
|
name="name"
|
||||||
control={control}
|
control={control}
|
||||||
@ -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')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -208,6 +224,7 @@ const WorkModal: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<input type="submit" style={{ display: 'none' }} />
|
||||||
</form>
|
</form>
|
||||||
</BaseModal>
|
</BaseModal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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 />
|
||||||
|
|||||||
@ -4,6 +4,7 @@ const i18nConfig = {
|
|||||||
i18n: {
|
i18n: {
|
||||||
defaultLocale: 'en',
|
defaultLocale: 'en',
|
||||||
locales: [
|
locales: [
|
||||||
|
'am',
|
||||||
'ar',
|
'ar',
|
||||||
'bg',
|
'bg',
|
||||||
'bn',
|
'bn',
|
||||||
@ -34,6 +35,7 @@ const i18nConfig = {
|
|||||||
'or',
|
'or',
|
||||||
'pl',
|
'pl',
|
||||||
'pt',
|
'pt',
|
||||||
|
'pt-BR',
|
||||||
'ro',
|
'ro',
|
||||||
'ru',
|
'ru',
|
||||||
'sr',
|
'sr',
|
||||||
@ -46,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.15.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.0",
|
"@hookform/resolvers": "3.1.0",
|
||||||
"@hookform/resolvers": "2.9.8",
|
"@monaco-editor/react": "^4.5.1",
|
||||||
"@monaco-editor/react": "^4.4.5",
|
"@mui/icons-material": "^5.11.16",
|
||||||
"@mui/icons-material": "^5.10.3",
|
"@mui/lab": "^5.0.0-alpha.133",
|
||||||
"@mui/lab": "^5.0.0-alpha.99",
|
"@mui/material": "^5.13.4",
|
||||||
"@mui/material": "^5.10.5",
|
"@mui/system": "^5.13.2",
|
||||||
"@mui/system": "^5.10.5",
|
"@mui/x-date-pickers": "6.6.0",
|
||||||
"@mui/x-date-pickers": "5.0.1",
|
"@react-oauth/google": "^0.11.0",
|
||||||
"@next/env": "^12.3.0",
|
"@reduxjs/toolkit": "^1.9.5",
|
||||||
"@react-oauth/google": "^0.2.6",
|
"axios": "^1.4.0",
|
||||||
"@reduxjs/toolkit": "^1.8.5",
|
|
||||||
"axios": "^0.27.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.0",
|
"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.0",
|
"next": "13.4.4",
|
||||||
"next-i18next": "^12.0.1",
|
"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.35.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.4.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.2",
|
"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.0",
|
"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.1",
|
"@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.185",
|
"@types/lodash": "^4.14.195",
|
||||||
"@types/node": "^18.7.18",
|
"@types/node": "^20.2.5",
|
||||||
"@types/react": "^18.0.20",
|
"@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.34",
|
"autoprefixer": "^10.4.14",
|
||||||
"autoprefixer": "^10.4.11",
|
"csstype": "^3.1.2",
|
||||||
"csstype": "^3.1.1",
|
"eslint-config-next": "^13.4.4",
|
||||||
"eslint-config-next": "^12.3.0",
|
"eslint-plugin-tailwindcss": "^3.12.1",
|
||||||
"eslint-plugin-tailwindcss": "^3.6.1",
|
"eslint-plugin-unused-imports": "^2.0.0",
|
||||||
"next-sitemap": "^3.1.22",
|
"next-sitemap": "^4.1.3",
|
||||||
"postcss": "^8.4.16",
|
"postcss": "^8.4.24",
|
||||||
"sass": "^1.54.9",
|
"sass": "^1.62.1",
|
||||||
"tailwindcss": "^3.1.8",
|
"tailwindcss": "^3.3.2",
|
||||||
"typescript": "^4.8.3"
|
"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/digitalocean.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 (
|
||||||
|
|||||||
101
client/public/images/sponsors/digitaloceanLight.svg
Normal file
101
client/public/images/sponsors/digitaloceanLight.svg
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 604 129" style="enable-background:new 0 0 604 129;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#0069ff;}
|
||||||
|
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#0069ff;}
|
||||||
|
</style>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M174.3,3c4.9,0,8.7,2.9,8.7,8.6c0,5.6-3.8,8.5-8.7,8.5h-7.6v11.1h-3.5V3H174.3z M166.7,17.1h7.2
|
||||||
|
c3,0,5.6-1.8,5.6-5.5c0-3.8-2.5-5.5-5.6-5.5h-7.2V17.1z"/>
|
||||||
|
<path class="st0" d="M208.8,21.7c0,6.1-4.3,10-9.9,10c-5.6,0-9.9-3.9-9.9-10c0-6.1,4.3-10,9.9-10
|
||||||
|
C204.5,11.7,208.8,15.6,208.8,21.7z M192.3,21.7c0,4.5,2.9,7.2,6.6,7.2c3.7,0,6.6-2.7,6.6-7.2c0-4.5-2.9-7.1-6.6-7.1
|
||||||
|
C195.2,14.5,192.3,17.2,192.3,21.7z"/>
|
||||||
|
<path class="st0" d="M234.4,31.3l-5.2-13.8L224,31.3h-2.6L214.1,12h3.6l5.2,14l5.2-14h2.3l5.3,14l5.2-14h3.5L237,31.3H234.4z"/>
|
||||||
|
<path class="st0" d="M253,22.9c0.2,3.7,2.6,5.9,6,5.9c2.8,0,4.8-1.3,5.4-3.4l3.2,0.2c-0.8,3.5-4.1,6.1-8.6,6.1
|
||||||
|
c-5.5,0-9.6-3.7-9.6-10c0-6.3,4-10,9.5-10c5.5,0,8.8,3.7,8.8,9.4v1.8H253z M253,20.3h11.6c-0.1-3.4-2-5.7-5.6-5.7
|
||||||
|
C255.6,14.5,253.2,16.5,253,20.3z"/>
|
||||||
|
<path class="st0" d="M285.4,14.9c-3.4,0-5.6,2.3-5.6,5.3v11.1h-3.2V12h3.2v2.9c0.7-1.6,2.5-3.1,5.7-3.1V14.9z"/>
|
||||||
|
<path class="st0" d="M294.7,22.9c0.2,3.7,2.6,5.9,6,5.9c2.8,0,4.8-1.3,5.4-3.4l3.2,0.2c-0.8,3.5-4.1,6.1-8.6,6.1
|
||||||
|
c-5.5,0-9.6-3.7-9.6-10c0-6.3,4-10,9.5-10c5.5,0,8.8,3.7,8.8,9.4v1.8H294.7z M294.7,20.3h11.6c-0.1-3.4-2-5.7-5.6-5.7
|
||||||
|
C297.4,14.5,294.9,16.5,294.7,20.3z"/>
|
||||||
|
<path class="st0" d="M333.1,31.3v-3.1c-1.1,2-3.6,3.5-6.8,3.5c-5.3,0-9.3-3.8-9.3-10c0-6.2,4-10,9.3-10c3.2,0,5.6,1.4,6.6,3.2V2
|
||||||
|
h3.2v29.4H333.1z M320.3,21.7c0,4.6,2.8,7.2,6.5,7.2c3.6,0,6.2-2.2,6.2-6.6v-1.1c0-4.3-2.6-6.6-6.2-6.6
|
||||||
|
C323.1,14.5,320.3,17.1,320.3,21.7z"/>
|
||||||
|
<path class="st0" d="M361.8,14.9c1.1-1.9,3.4-3.2,6.7-3.2c5.3,0,9.3,3.8,9.3,10c0,6.2-4,10-9.3,10c-3.3,0-5.7-1.5-6.8-3.5v3.1
|
||||||
|
h-3.1V2h3.2V14.9z M361.9,21.1v1.1c0,4.4,2.6,6.6,6.2,6.6c3.7,0,6.5-2.5,6.5-7.2c0-4.6-2.8-7.1-6.5-7.1
|
||||||
|
C364.5,14.5,361.9,16.8,361.9,21.1z"/>
|
||||||
|
<path class="st0" d="M386.3,40.9l4.6-10.7L383.2,12h3.6l5.8,14.5l5.8-14.5h3.6l-12.2,28.9H386.3z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g id="XMLID_2369_">
|
||||||
|
<g>
|
||||||
|
<g id="XMLID_281_">
|
||||||
|
<g id="XMLID_282_">
|
||||||
|
<g>
|
||||||
|
<g id="XMLID_283_">
|
||||||
|
<g id="XMLID_287_">
|
||||||
|
<path id="XMLID_288_" class="st0" d="M64.4,127l0-24.2c25.6,0,45.5-25.4,35.7-52.3c-3.6-10-11.6-17.9-21.6-21.6
|
||||||
|
c-27-9.8-52.3,10-52.3,35.7c0,0,0,0,0,0L2,64.7C2,23.8,41.5-8,84.3,5.4c18.7,5.8,33.6,20.7,39.4,39.4
|
||||||
|
C137,87.6,105.2,127,64.4,127z"/>
|
||||||
|
</g>
|
||||||
|
<polygon id="XMLID_286_" class="st1" points="64.4,102.9 40.4,102.9 40.4,78.9 40.4,78.9 64.4,78.9 64.4,78.9 "/>
|
||||||
|
<polygon id="XMLID_285_" class="st1" points="40.3,121.5 21.8,121.5 21.8,121.5 21.8,102.9 40.4,102.9 40.4,121.5 "/>
|
||||||
|
<path id="XMLID_284_" class="st1" d="M21.9,102.9H6.3c0,0,0,0,0,0V87.4c0,0,0,0,0,0h15.5c0,0,0,0,0,0V102.9z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g id="XMLID_254_">
|
||||||
|
<path id="XMLID_278_" class="st0" d="M200.9,52.4c-5.5-3.8-12.4-5.8-20.5-5.8h-17.5v55.5h17.5c8,0,14.9-2.1,20.5-6.1
|
||||||
|
c3-2.1,5.4-5.1,7.1-8.9c1.7-3.7,2.5-8.2,2.5-13.1c0-4.9-0.8-9.3-2.5-13C206.3,57.4,203.9,54.4,200.9,52.4z M173.1,56h5.5
|
||||||
|
c6.1,0,11.1,1.2,15,3.6c4.2,2.6,6.4,7.4,6.4,14.4c0,7.2-2.2,12.3-6.4,15.1h0c-3.7,2.4-8.7,3.6-14.9,3.6h-5.6V56z"/>
|
||||||
|
<path id="XMLID_277_" class="st0" d="M222.6,45.9c-1.7,0-3.1,0.6-4.3,1.8c-1.2,1.1-1.8,2.6-1.8,4.2c0,1.7,0.6,3.1,1.8,4.3
|
||||||
|
c1.2,1.2,2.6,1.8,4.3,1.8c1.7,0,3.1-0.6,4.3-1.8c1.2-1.2,1.8-2.6,1.8-4.3c0-1.7-0.6-3.1-1.8-4.2
|
||||||
|
C225.7,46.5,224.3,45.9,222.6,45.9z"/>
|
||||||
|
<rect id="XMLID_276_" x="217.6" y="63" class="st0" width="9.8" height="39.1"/>
|
||||||
|
<path id="XMLID_273_" class="st0" d="M263.2,66.3c-3-2.6-6.3-4.2-9.9-4.2c-5.4,0-9.9,1.9-13.4,5.6c-3.5,3.7-5.3,8.4-5.3,14.1
|
||||||
|
c0,5.5,1.8,10.2,5.2,14c3.5,3.7,8,5.5,13.5,5.5c3.8,0,7.1-1.1,9.7-3.1V99c0,3.2-0.9,5.8-2.6,7.5c-1.7,1.7-4.1,2.6-7.1,2.6
|
||||||
|
c-4.5,0-7.4-1.8-10.9-6.5l-6.7,6.4l0.2,0.3c1.4,2,3.7,4,6.6,5.9c2.9,1.9,6.6,2.8,10.9,2.8c5.8,0,10.6-1.8,14.1-5.4
|
||||||
|
c3.5-3.6,5.3-8.4,5.3-14.2V63h-9.7V66.3z M260.6,89.4c-1.7,2-3.9,2.9-6.8,2.9c-2.8,0-5-0.9-6.7-2.9c-1.7-1.9-2.5-4.5-2.5-7.7
|
||||||
|
c0-3.2,0.9-5.8,2.5-7.7c1.7-1.9,3.9-2.9,6.7-2.9c2.8,0,5,1,6.8,2.9c1.7,2,2.6,4.6,2.6,7.7C263.2,84.9,262.3,87.5,260.6,89.4z"/>
|
||||||
|
<rect id="XMLID_272_" x="281.3" y="63" class="st0" width="9.8" height="39.1"/>
|
||||||
|
<path id="XMLID_271_" class="st0" d="M286.3,45.9c-1.7,0-3.1,0.6-4.3,1.8c-1.2,1.1-1.8,2.6-1.8,4.2c0,1.7,0.6,3.1,1.8,4.3
|
||||||
|
c1.2,1.2,2.6,1.8,4.3,1.8c1.7,0,3.1-0.6,4.3-1.8c1.2-1.2,1.8-2.6,1.8-4.3c0-1.7-0.6-3.1-1.8-4.2C289.4,46.5,288,45.9,286.3,45.9
|
||||||
|
z"/>
|
||||||
|
<path id="XMLID_270_" class="st0" d="M312.7,52.5H303V63h-5.6v9h5.6v16.2c0,5.1,1,8.7,3,10.8c2,2.1,5.6,3.2,10.6,3.2
|
||||||
|
c1.6,0,3.2-0.1,4.8-0.2l0.4,0v-9l-3.4,0.2c-2.3,0-3.9-0.4-4.7-1.2c-0.8-0.8-1.1-2.6-1.1-5.2V72h9.2v-9h-9.2V52.5z"/>
|
||||||
|
<rect id="XMLID_269_" x="368" y="46.6" class="st0" width="9.8" height="55.5"/>
|
||||||
|
<path id="XMLID_268_" class="st0" d="M477.3,88.2c-1.8,2-3.6,3.7-4.9,4.6v0c-1.4,0.9-3.1,1.3-5.1,1.3c-2.9,0-5.2-1.1-7.1-3.2
|
||||||
|
c-1.9-2.2-2.8-4.9-2.8-8.3s0.9-6.1,2.8-8.2c1.9-2.2,4.2-3.2,7.1-3.2c3.2,0,6.5,2,9.4,5.4l6.5-6.2l0,0c-4.2-5.5-9.7-8.1-16.1-8.1
|
||||||
|
c-5.4,0-10.1,2-13.9,5.8c-3.8,3.9-5.7,8.8-5.7,14.6s1.9,10.7,5.7,14.6c3.8,3.9,8.5,5.9,13.9,5.9c7.1,0,12.9-3.1,16.8-8.7
|
||||||
|
L477.3,88.2z"/>
|
||||||
|
<path id="XMLID_265_" class="st0" d="M517.7,68.5c-1.4-1.9-3.3-3.5-5.7-4.7c-2.3-1.1-5.1-1.7-8.1-1.7c-5.5,0-10,2-13.4,6
|
||||||
|
c-3.3,4-4.9,8.9-4.9,14.7c0,5.9,1.8,10.8,5.4,14.6c3.6,3.7,8.4,5.6,14.2,5.6c6.6,0,12.1-2.7,16.2-8l0.2-0.3l-6.4-6.2l0,0
|
||||||
|
c-0.6,0.7-1.4,1.5-2.2,2.3c-1,0.9-1.9,1.6-2.9,2.1c-1.5,0.7-3.1,1.1-5,1.1c-2.7,0-5-0.8-6.7-2.4c-1.6-1.5-2.6-3.5-2.8-5.9h26.1
|
||||||
|
l0.1-3.6c0-2.5-0.3-5-1-7.3C520.1,72.6,519.1,70.4,517.7,68.5z M496.2,77.7c0.5-1.9,1.3-3.4,2.6-4.6c1.3-1.3,3.1-2,5.2-2
|
||||||
|
c2.4,0,4.2,0.7,5.5,2c1.2,1.2,1.8,2.8,2,4.6H496.2z"/>
|
||||||
|
<path id="XMLID_262_" class="st0" d="M555.5,66L555.5,66c-3-2.5-7.1-3.8-12.3-3.8c-3.3,0-6.3,0.7-9.1,2.1
|
||||||
|
c-2.6,1.3-5.1,3.5-6.7,6.3l0.1,0.1l6.3,6c2.6-4.1,5.5-5.6,9.3-5.6c2.1,0,3.8,0.6,5.1,1.6c1.3,1.1,1.9,2.5,1.9,4.2v1.9
|
||||||
|
c-2.4-0.7-4.9-1.1-7.2-1.1c-4.9,0-8.9,1.2-11.8,3.4c-3,2.3-4.5,5.6-4.5,9.8c0,3.7,1.3,6.7,3.8,8.9c2.6,2.1,5.8,3.2,9.5,3.2
|
||||||
|
c3.7,0,7.3-1.5,10.4-4.1v3.2h9.7V77C560,72.2,558.5,68.5,555.5,66z M538,87.2c1.1-0.8,2.7-1.2,4.7-1.2c2.4,0,4.9,0.5,7.5,1.4
|
||||||
|
v3.8c-2.1,2-5,3-8.5,3c-1.7,0-3-0.4-3.9-1.1c-0.9-0.7-1.3-1.7-1.3-2.8C536.4,89,536.9,88,538,87.2z"/>
|
||||||
|
<path id="XMLID_261_" class="st0" d="M597.9,66.7c-2.7-3.1-6.6-4.6-11.5-4.6c-3.9,0-7.1,1.1-9.4,3.3V63h-9.7v39.1h9.8V80.6
|
||||||
|
c0-3,0.7-5.3,2.1-7c1.4-1.7,3.3-2.5,5.8-2.5c2.2,0,3.9,0.7,5.2,2.2c1.3,1.5,1.9,3.6,1.9,6.2v22.7h9.8V79.5
|
||||||
|
C602,74.1,600.6,69.8,597.9,66.7z"/>
|
||||||
|
<path id="XMLID_258_" class="st0" d="M355.6,66L355.6,66c-3-2.5-7.1-3.8-12.3-3.8c-3.3,0-6.3,0.7-9.1,2.1
|
||||||
|
c-2.6,1.3-5.1,3.5-6.7,6.3l0.1,0.1l6.3,6c2.6-4.1,5.5-5.6,9.3-5.6c2.1,0,3.8,0.6,5.1,1.6c1.3,1.1,1.9,2.5,1.9,4.2v1.9
|
||||||
|
c-2.4-0.7-4.9-1.1-7.2-1.1c-4.9,0-8.9,1.2-11.8,3.4c-3,2.3-4.5,5.6-4.5,9.8c0,3.7,1.3,6.7,3.8,8.9c2.6,2.1,5.8,3.2,9.5,3.2
|
||||||
|
c3.7,0,7.3-1.5,10.4-4.1v3.2h9.7V77C360.2,72.2,358.7,68.5,355.6,66z M338.2,87.2c1.1-0.8,2.7-1.2,4.7-1.2
|
||||||
|
c2.4,0,4.9,0.5,7.5,1.4v3.8c-2.1,2-5,3-8.5,3c-1.7,0-3-0.4-3.9-1.1c-0.9-0.7-1.3-1.7-1.3-2.8C336.6,89,337.1,88,338.2,87.2z"/>
|
||||||
|
<path id="XMLID_255_" class="st0" d="M413.6,103c-15.8,0-28.6-12.8-28.6-28.6s12.8-28.6,28.6-28.6s28.6,12.8,28.6,28.6
|
||||||
|
S429.4,103,413.6,103z M413.6,55.8c-10.2,0-18.5,8.3-18.5,18.5s8.3,18.5,18.5,18.5s18.5-8.3,18.5-18.5S423.8,55.8,413.6,55.8z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 7.7 KiB |
377
client/public/locales/am/builder.json
Normal file
377
client/public/locales/am/builder.json
Normal file
@ -0,0 +1,377 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"actions": {
|
||||||
|
"add": "አዲስ {{token}} ጨምር",
|
||||||
|
"delete": "{{token}} አጥፋ",
|
||||||
|
"duplicate": "የተባዛ ክፍል",
|
||||||
|
"edit": "{{token}} አዘምን"
|
||||||
|
},
|
||||||
|
"columns": {
|
||||||
|
"heading": "አምዶች",
|
||||||
|
"tooltip": "የአምዶች ብዛት ይቀይሩ"
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"date": {
|
||||||
|
"label": "ቀን"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"label": "መግለጫ"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"label": "የኢሜል አድራሻ"
|
||||||
|
},
|
||||||
|
"end-date": {
|
||||||
|
"help-text": "አሁንም ካለ፤ ይህንን ቦታ ክፍት ይተውት።",
|
||||||
|
"label": "የመጨረሻ ቀን"
|
||||||
|
},
|
||||||
|
"keywords": {
|
||||||
|
"label": "ቁልፍ ቃላት"
|
||||||
|
},
|
||||||
|
"level": {
|
||||||
|
"label": "ደረጃ"
|
||||||
|
},
|
||||||
|
"levelNum": {
|
||||||
|
"label": "ደረጃ (ቁጥር)"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"label": "ስም"
|
||||||
|
},
|
||||||
|
"phone": {
|
||||||
|
"label": "የስልክ ቁጥር"
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"label": "የስራ ሚና"
|
||||||
|
},
|
||||||
|
"start-date": {
|
||||||
|
"label": "የመጀመሪያ ቀን"
|
||||||
|
},
|
||||||
|
"subtitle": {
|
||||||
|
"label": "ንዑስ ርዕስ"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"label": "ማጠቃለያ"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"label": "ርዕስ"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"label": "ድህረገፅ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"glossary": {
|
||||||
|
"page": "ገጽ"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"actions": {
|
||||||
|
"delete": "አጥፋ",
|
||||||
|
"duplicate": "አባዛ",
|
||||||
|
"edit": "አዘምን"
|
||||||
|
},
|
||||||
|
"empty-text": "ይህ ዝርዝር ባዶ ነው።"
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"delete-item": "እርግጠኛ ነዎት ይህንn ማጥፋት ይፈልጋሉ? ይህ የማይመለስ ተግባር ነው።",
|
||||||
|
"delete-section": "ክፍሉን አጥፋ",
|
||||||
|
"rename-section": "ክፍሉን እንደገና ይሰይሙ",
|
||||||
|
"toggle-visibility": "ዕይታውን ቀያይር"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"controller": {
|
||||||
|
"tooltip": {
|
||||||
|
"center-artboard": "መሃል የሰዕል ማሳያ",
|
||||||
|
"copy-link": "የስራ ልምድ ሰነዱን ሊንክ ቅዳ",
|
||||||
|
"export-pdf": "PDF አውጣ",
|
||||||
|
"redo": "ድገም",
|
||||||
|
"toggle-orientation": "የገጽ አቀማመጥን ቀያይር",
|
||||||
|
"toggle-page-break-line": "የገጽ መግቻ መስመርን ቀያይር",
|
||||||
|
"toggle-sidebars": "የጎን ክፍሎችን ቀይር",
|
||||||
|
"undo": "ቀልብስ",
|
||||||
|
"zoom-in": "አቅርብ",
|
||||||
|
"zoom-out": "አሳንስ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"menu": {
|
||||||
|
"delete": "አጥፋ",
|
||||||
|
"duplicate": "አብዛ",
|
||||||
|
"rename": "ዳግም ሰይም",
|
||||||
|
"share-link": "ሊንክ አጋራ",
|
||||||
|
"tooltips": {
|
||||||
|
"delete": "እርግጠኛ ነዎት ይህን የስራ ልምድ ሰነድ ማጥፋት ይፈልጋሉ? ይህ የማይመለስ ተግባር ነው።",
|
||||||
|
"share-link": "የሥራ ልምድዎን ለሌሎች እንዲታይ ለማድረግ ዕይታውን ወደ ይፋዊ መለወጥ ያስፈልግዎታል።"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"leftSidebar": {
|
||||||
|
"sections": {
|
||||||
|
"awards": {
|
||||||
|
"form": {
|
||||||
|
"awarder": {
|
||||||
|
"label": "ሸላሚ"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"basics": {
|
||||||
|
"actions": {
|
||||||
|
"photo-filters": "የፎቶ ማጣሪያዎች"
|
||||||
|
},
|
||||||
|
"birthdate": {
|
||||||
|
"label": "የትውልድ ቀን"
|
||||||
|
},
|
||||||
|
"heading": "መሰረታዊ ነገሮች",
|
||||||
|
"headline": {
|
||||||
|
"label": "ርዕስ"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"label": "ሙሉ ስም"
|
||||||
|
},
|
||||||
|
"photo-filters": {
|
||||||
|
"effects": {
|
||||||
|
"border": {
|
||||||
|
"label": "ድንበር"
|
||||||
|
},
|
||||||
|
"grayscale": {
|
||||||
|
"label": "ግራጫ ልኬት"
|
||||||
|
},
|
||||||
|
"heading": "ተፅዕኖዎች"
|
||||||
|
},
|
||||||
|
"shape": {
|
||||||
|
"heading": "ቅርጽ"
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"heading": "መጠን (በፒክስል)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"photo-upload": {
|
||||||
|
"tooltip": {
|
||||||
|
"remove": "ፎቶ አስወግድ",
|
||||||
|
"upload": "ፎቶ ስቀል"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"certifications": {
|
||||||
|
"form": {
|
||||||
|
"issuer": {
|
||||||
|
"label": "ሰጪ አካል"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"education": {
|
||||||
|
"form": {
|
||||||
|
"area-study": {
|
||||||
|
"label": "የጥናት ዙሪያ"
|
||||||
|
},
|
||||||
|
"courses": {
|
||||||
|
"label": "ትምህርቶች"
|
||||||
|
},
|
||||||
|
"degree": {
|
||||||
|
"label": "ዲግሪ"
|
||||||
|
},
|
||||||
|
"grade": {
|
||||||
|
"label": "ውጤት"
|
||||||
|
},
|
||||||
|
"institution": {
|
||||||
|
"label": "ተቋም"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"experience": {
|
||||||
|
"form": {
|
||||||
|
"name": {
|
||||||
|
"label": "Company Name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"address": {
|
||||||
|
"label": "አድራሻ"
|
||||||
|
},
|
||||||
|
"city": {
|
||||||
|
"label": "ከተማ"
|
||||||
|
},
|
||||||
|
"country": {
|
||||||
|
"label": "ሀገር"
|
||||||
|
},
|
||||||
|
"heading": "አካባቢ",
|
||||||
|
"postal-code": {
|
||||||
|
"label": "የፖስታ ኮድ"
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"label": "ክልል"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"form": {
|
||||||
|
"network": {
|
||||||
|
"label": "አውታረ መረብ"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"label": "የተጠቃሚ ስም"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"heading": "መለያዎች",
|
||||||
|
"heading_one": "መለያ"
|
||||||
|
},
|
||||||
|
"publications": {
|
||||||
|
"form": {
|
||||||
|
"publisher": {
|
||||||
|
"label": "አታሚ"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"references": {
|
||||||
|
"form": {
|
||||||
|
"relationship": {
|
||||||
|
"label": "ዝምድና"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"section": {
|
||||||
|
"heading": "ክፍል"
|
||||||
|
},
|
||||||
|
"volunteer": {
|
||||||
|
"form": {
|
||||||
|
"organization": {
|
||||||
|
"label": "ድርጅት"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rightSidebar": {
|
||||||
|
"sections": {
|
||||||
|
"css": {
|
||||||
|
"heading": "የራስ CSS"
|
||||||
|
},
|
||||||
|
"export": {
|
||||||
|
"heading": "አውጣ",
|
||||||
|
"json": {
|
||||||
|
"primary": "JSON",
|
||||||
|
"secondary": "ወደ Reactive Resume ተመልሶ ሊመጣ የሚችል የስራ ልምድ ሰነድ የJSON ቅጂዎትን ያውርዱ።"
|
||||||
|
},
|
||||||
|
"pdf": {
|
||||||
|
"loading": {
|
||||||
|
"primary": "PDF በመስራት ላይ",
|
||||||
|
"secondary": "እባክዎ PDF እስኪሰራ ይጠብቁ፣ ይህ እስከ 15 ሰከንድ ሊወስድ ይችላል።"
|
||||||
|
},
|
||||||
|
"normal": {
|
||||||
|
"primary": "PDF",
|
||||||
|
"secondary": "ማተም እና መላክ የሚችሉትን የስራ ልምድ ሰነድዎን በPDF ያውርዱ። ይህ ፋይል ለተጨማሪ አርትዖት ተመልሶ ሊመጣ አይችልም።"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"layout": {
|
||||||
|
"heading": "አቀማመጥ",
|
||||||
|
"tooltip": {
|
||||||
|
"reset-layout": "አቀማመጡን መልስ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"links": {
|
||||||
|
"bugs-features": {
|
||||||
|
"body": "የስራ ልምድ ሰነድዎን ለመስራት የሚከለክሎት ነገር አለ? ወይም የሚጨምሩት አስደናቂ ሀሳብ አለዎት? በ GitHub ላይ ጉዳይዎን ያቅርቡ።",
|
||||||
|
"button": "GitHub ጉዳዮች",
|
||||||
|
"heading": "ችግሮች? የባህሪ ጥያቄዎች?"
|
||||||
|
},
|
||||||
|
"docs": "ሰነዶች",
|
||||||
|
"donate": {
|
||||||
|
"body": "Reactive Resumeን መጠቀም ከወደዱ እባክዎን መተግበሪያው እንዲሰራ እና ያለ ማስታወቂያ ሁሌም በነፃ እንዲቀጥል በተቻለዎት መጠን ለመለገስ ያስቡበት።",
|
||||||
|
"button": "ቡና ይጋብዙኝ",
|
||||||
|
"heading": "ለ Reactive Resume ይለግሱ"
|
||||||
|
},
|
||||||
|
"github": "የምንጭ ኮድ",
|
||||||
|
"heading": "አገናኞች",
|
||||||
|
"reddit": "Reddit"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"global": {
|
||||||
|
"date": {
|
||||||
|
"primary": "ቀን",
|
||||||
|
"secondary": "በመላው መተግበሪያ ላይ የሚጠቀሙበት የቀን አይነት"
|
||||||
|
},
|
||||||
|
"heading": "ዓለም አቀፍ",
|
||||||
|
"language": {
|
||||||
|
"primary": "ቋንቋ",
|
||||||
|
"secondary": "በመላው መተግበሪያ ላይ የሚጠቀሙበት ቋንቋ"
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"primary": "ገጽታ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"heading": "ቅንብሮች",
|
||||||
|
"page": {
|
||||||
|
"break-line": {
|
||||||
|
"primary": "መስመር መቁረጫ",
|
||||||
|
"secondary": "የA4 ገጽ ቁመትን ለመለየት በሁሉም ገጾች ላይ መስመር አሳይ"
|
||||||
|
},
|
||||||
|
"format": {
|
||||||
|
"primary": "የወረቀት መጠን",
|
||||||
|
"secondary": "ከቆመበት ቀጥል ገጾችዎ ልኬቶችን ይወስናል"
|
||||||
|
},
|
||||||
|
"heading": "ገጽ",
|
||||||
|
"orientation": {
|
||||||
|
"disabled": "አንድ ገጽ ብቻ ሲኖር ምንም ተጽእኖ የለውም",
|
||||||
|
"primary": "አቅጣጫ",
|
||||||
|
"secondary": "ገጾችን በአግድም ሆነ በቋሚ ለማሳየት"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume": {
|
||||||
|
"heading": "የስራልምድ ሰነድ",
|
||||||
|
"reset": {
|
||||||
|
"primary": "ሁሉንም ነገር ዳግም አስጀምር",
|
||||||
|
"secondary": "በጣም ብዙ ስህተቶችን ሰርተዋል? ሁሉንም ለውጦች ዳግም ለማስጀመር እና ከባዶ ለመጀመር እዚህ ይንኩ። ይጠንቀቁ፤ ይህ እርምጃ ወደ ኋላ መመለስ አይቻልም።"
|
||||||
|
},
|
||||||
|
"sample": {
|
||||||
|
"primary": "የናሙና መረጃን ጫን",
|
||||||
|
"secondary": "የት መጀመር እንዳለብዎት እርግጠኛ አይደሉም? የተሟላ የስራ ልምድ ሰነድ እንዴት እንደሚመስል ለማየት አንዳንድ ናሙና መረጃ ለመጫን እዚህ ይንኩ።"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sharing": {
|
||||||
|
"heading": "ማጋራት",
|
||||||
|
"short-url": {
|
||||||
|
"label": "አጭር ማስፈንጠሪያ ይመርጣሉ።"
|
||||||
|
},
|
||||||
|
"visibility": {
|
||||||
|
"subtitle": "ማስፈንጥሪያ ያለው ማንኛውም ሰው የእርስዎን የስራ ልምድ እንዲመለከት ይፍቀዱለት",
|
||||||
|
"title": "ይፋዊ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"heading": "ምሳሌዎች"
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"form": {
|
||||||
|
"background": {
|
||||||
|
"label": "ዳራ"
|
||||||
|
},
|
||||||
|
"primary": {
|
||||||
|
"label": "ዋና"
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"label": "ጽሑፍ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"heading": "ገጽታ"
|
||||||
|
},
|
||||||
|
"typography": {
|
||||||
|
"form": {
|
||||||
|
"font-family": {
|
||||||
|
"label": "የጽሁፍ ቅርጽ ቤተሰብ"
|
||||||
|
},
|
||||||
|
"font-size": {
|
||||||
|
"label": "የጽሁፍ ቅርጽ መጠን"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"heading": "የፊደል አጻጻፍ",
|
||||||
|
"widgets": {
|
||||||
|
"body": {
|
||||||
|
"label": "ሐተታ"
|
||||||
|
},
|
||||||
|
"headings": {
|
||||||
|
"label": "ርዕሶች"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
client/public/locales/am/common.json
Normal file
29
client/public/locales/am/common.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"avatar": {
|
||||||
|
"menu": {
|
||||||
|
"greeting": "ሰላም",
|
||||||
|
"logout": "ውጣ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"credit": "ተወዳጅ ፕሮጀክት በ<1>Amruth Pillai</1>",
|
||||||
|
"license": "በማህበረሰቡ ፣ ለማህበረሰቡ።"
|
||||||
|
},
|
||||||
|
"markdown": {
|
||||||
|
"help-text": "ይህ ክፍል ይደግፋል <1>markdown</1> አፃፃፍ ይደግፋል።"
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"present": "አሁን"
|
||||||
|
},
|
||||||
|
"subtitle": "ነፃ እና ክፍት የስራ ልምድ ሰነድ መገንቢያ",
|
||||||
|
"title": "Reactive Resume",
|
||||||
|
"toast": {
|
||||||
|
"error": {
|
||||||
|
"upload-file-size": "እባክዎ ከ2 ሜጋባይት በታች የሆኑ ፋይሎችን ብቻ ይስቀሉ።",
|
||||||
|
"upload-photo-size": "እባክዎትን ከ2 ሜጋባይት በታች የሆኑ ፎቶዎችን ብቻ ይስቀሉ፣ ቢቻል ካሬ።"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"resume-link-copied": "ወደ የስራ ታሪክዎ የሚወስድ አገናኝ በሰሌዳዎ ተይዟል።"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
client/public/locales/am/dashboard.json
Normal file
25
client/public/locales/am/dashboard.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"create-resume": {
|
||||||
|
"subtitle": "ከባዶ ጀምር",
|
||||||
|
"title": "አዲስ የሥራ ታሪክ ፍጠር"
|
||||||
|
},
|
||||||
|
"import-external": {
|
||||||
|
"subtitle": "LinkedIn, JSON Resume, Reactive Resume",
|
||||||
|
"title": "ከውጭ ምንጮች አስገባ"
|
||||||
|
},
|
||||||
|
"resume": {
|
||||||
|
"menu": {
|
||||||
|
"delete": "አጥፋ",
|
||||||
|
"duplicate": "አብዛ",
|
||||||
|
"open": "ክፈት",
|
||||||
|
"rename": "እንደገና ይሰይሙ",
|
||||||
|
"share-link": "ሊንክ አጋራ",
|
||||||
|
"tooltips": {
|
||||||
|
"delete": "እርግጠኛ ነዎት ይህን የሥራ ታሪክ ማጥፋት ይፈልጋሉ? ይህ የማይመለስ ተግባር ነው።",
|
||||||
|
"share-link": "የስራ ልምድዎን ለሌሎች እንዲታይ ለማድረግ ዕይታውን ወደ ይፋዊ መቀየር አለብዎት።"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timestamp": "መጨረሻ የተሻሻለው {{timestamp}} በፊት"
|
||||||
|
},
|
||||||
|
"title": "ዳሽቦርድ"
|
||||||
|
}
|
||||||
43
client/public/locales/am/landing.json
Normal file
43
client/public/locales/am/landing.json
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"actions": {
|
||||||
|
"app": "ወደ መተግበሪያ ይሂዱ",
|
||||||
|
"login": "ግባ",
|
||||||
|
"logout": "ውጣ",
|
||||||
|
"register": "ይመዝገቡ"
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"heading": "መገለጫዎች",
|
||||||
|
"list": {
|
||||||
|
"ads": "ምንም ማስታወቂያ የለም",
|
||||||
|
"export": "የስራ ልምድዎን ወደ JSON ወይም PDF ቅርጸት ይላኩ።",
|
||||||
|
"free": "ሁሌም ነጻ",
|
||||||
|
"import": "መረጃ ከ LinkedIn, JSON Resume ማምጣት",
|
||||||
|
"languages": "በተለያዩ ቋንቋዎች ተደራሽ",
|
||||||
|
"more": "እና ብዙ ተጨማሪ አስደሳች መገለጫዎች፤ <1>ሁሉንም እዚህ ያንብቡ</1>",
|
||||||
|
"tracking": "ምንም የተጠቃሚ መከታተያ የለም።"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"links": {
|
||||||
|
"heading": "አገናኞች",
|
||||||
|
"links": {
|
||||||
|
"docs": "ሰነዶች",
|
||||||
|
"donate": "ይለግሱ",
|
||||||
|
"github": "የምንጭ ኮድ",
|
||||||
|
"privacy": "የግላዊነት መመሪያ",
|
||||||
|
"reddit": "Reddit",
|
||||||
|
"service": "የአገልግሎት ውሎች"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"screenshots": {
|
||||||
|
"heading": "የገጽ እይታዎች"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"body": "Reactive Resume የእርስዎን የስራ ታሪክ የመፍጠር፣ የማዘመን እና የማጋራት መደበኛ ተግባራትን እንደ 1 2 3 ቀላል ለማድረግ የተሰራ ነፃ እና በነጻ የሚገኝ የስራ ልምድ ሰነድ መገንቢያ ነው። በዚህ መገልገያ የተለያዩ የስራ ልምድ ሰነዶችን በመስራት፣ ከቀጣሪዎች ወይም ከጓደኞች ጋር በማስፈንጠሪያ ማጋራት እና እንደ PDF ማተም ፣ ሁሉንም በነጻ ፣ ምንም ማስታወቂያ ሳይኖር ፣ ምንም ክትትል ሳይደረግ ፣ የመረጃዎን ትክክለኛነት እና ግላዊነት ተጠብቆ ማከናወን ይችላሉ።",
|
||||||
|
"heading": "ማጠቃለያ"
|
||||||
|
},
|
||||||
|
"testimonials": {
|
||||||
|
"body": "ጥሩም ይሁን መጥፎ፣ ስለ Reactive Resume እና ለእርስዎ እንዴት እንደነበረ አስተያየትዎን መስማት እፈልጋለሁ።<br/>በአለም ዙሪያ በተጠቃሚዎች የተላኩ አንዳንድ መልዕክቶች እነዚሁና",
|
||||||
|
"contact": "በዚህ በኩል ልታገኙኝ <1>ኢሜል</1> ትችላላችሁ ወይም <3>በድረ-ገጽ</3> ላይ ባለው የእውቂያ ቅጽ ማግኘት ይችላሉ።",
|
||||||
|
"heading": "ምስክሮች"
|
||||||
|
}
|
||||||
|
}
|
||||||
160
client/public/locales/am/modals.json
Normal file
160
client/public/locales/am/modals.json
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
{
|
||||||
|
"auth": {
|
||||||
|
"forgot-password": {
|
||||||
|
"actions": {
|
||||||
|
"send-email": "የይለፍ ቃል ዳግም ማስጀመሪያ ኢሜይል ላክ"
|
||||||
|
},
|
||||||
|
"body": "መልሰው ማግኘት ከሚፈልጉት መለያ ጋር የተያያዘውን የኢሜይል አድራሻ ያስገቡ።",
|
||||||
|
"form": {
|
||||||
|
"email": {
|
||||||
|
"label": "የኢሜል አድራሻ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"heading": "የይለፍ ቃልዎን ረሱ?",
|
||||||
|
"help-text": "መለያው ካለ የይለፍ ቃልዎን እንደገና ለማስጀመር የሚያስችል ኢሜል ይደርስዎታል።"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"actions": {
|
||||||
|
"login": "ግባ"
|
||||||
|
},
|
||||||
|
"body": "እባክዎ የስራ ልምድ ሰነድዎ ወዳለበት ለመግባት፣ ለማግኘት፣ ለማስተዳደር እና ለማጋራት ከመለያዎ ጋር የተያያዘውን የተጠቃሚ ስምዎን እና የይለፍ ቃልዎን ያስገቡ።",
|
||||||
|
"form": {
|
||||||
|
"password": {
|
||||||
|
"label": "የይለፍ ቃል"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"help-text": "እንዲሁም የኢሜል አድራሻዎን ማስገባት ይችላሉ",
|
||||||
|
"label": "የተጠቃሚ ስም"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"heading": "ወደ መለያዎ ይግቡ",
|
||||||
|
"recover-text": "የይለፍ ቃልዎን ከረሱት፣ እዚህ <1>መለያዎን መልሰው ማግኘት</1> ይችላሉ።",
|
||||||
|
"register-text": "ከሌለዎት እዚህ <1>መለያ መፍጠር</1> ይችላሉ።"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"actions": {
|
||||||
|
"register": "ይመዝገቡ",
|
||||||
|
"google": "በጉግል ይመዝገቡ"
|
||||||
|
},
|
||||||
|
"body": "እባክዎ መለያ ለመፍጠር የእርስዎን የግል መረጃ ያስገቡ።",
|
||||||
|
"form": {
|
||||||
|
"confirm-password": {
|
||||||
|
"label": "የይለፍ ቃልዎን አረጋግጥ"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"label": "የኢሜል አድራሻ"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"label": "ሙሉ ስም"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"label": "የይለፍ ቃል"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"label": "የተጠቃሚ ስም"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"heading": "መለያ ፍጠር",
|
||||||
|
"loginText": "መለያ ካለዎት <1>እዚህ መግባት</1> ይችላሉ።"
|
||||||
|
},
|
||||||
|
"reset-password": {
|
||||||
|
"actions": {
|
||||||
|
"set-password": "አዲስ የይለፍ ቃል ያዘጋጁ"
|
||||||
|
},
|
||||||
|
"body": "ለመለያዎ አዲስ የይለፍ ቃል ያስገቡ።",
|
||||||
|
"form": {
|
||||||
|
"confirm-password": {
|
||||||
|
"label": "የይለፍ ቃልዎን አረጋግጡ"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"label": "የይለፍ ቃል"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"heading": "የይለፍ ቃልዎን ዳግም ያስጀምሩ"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"heading": "መለያህ",
|
||||||
|
"form": {
|
||||||
|
"avatar": {
|
||||||
|
"help-text": "የመገለጫ ሥዕልህን በ<1>ግራቫታር ማዘመን ትችላለህ</1>"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"label": "ሙሉ ስም"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"label": "የ ኢሜል አድራሻ",
|
||||||
|
"help-text": "በአሁኑ ጊዜ የኢሜል አድራሻዎን ማዘመን አይቻልም፣ እባክዎ ይልቁንስ አዲስ መለያ ይፍጠሩ።"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete-account": {
|
||||||
|
"heading": "መለያ እና ውሂብ ሰርዝ",
|
||||||
|
"body": "መለያህን፣ ዳታህን እና ሁሉንም ከቆመበት ቀጥል ለመሰረዝ በጽሑፍ ሳጥኑ ውስጥ \"{{keyword}}\" ተይብ እና ቁልፉን ተጫን። ይህ የማይቀለበስ እርምጃ መሆኑን እና ውሂብዎን እንደገና ማምጣት እንደማይቻል እባክዎ ልብ ይበሉ።",
|
||||||
|
"actions": {
|
||||||
|
"delete": "መለያ ሰርዝ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"save": "ለውጦችን አስቀምጥ"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"create-resume": {
|
||||||
|
"actions": {
|
||||||
|
"create-resume": "የስራ ታሪክ ሰነድ ይፍጠሩ"
|
||||||
|
},
|
||||||
|
"body": "ስም በመስጠት የስራ ልምድዎ ሰነድዎን መገንባት ይጀምሩ። መጠሪያው ለሚያመለክቱበት የስራ ሚና ወይም የሚወዱት ምግብ ሊሆን ይችላል።",
|
||||||
|
"form": {
|
||||||
|
"name": {
|
||||||
|
"label": "ስም"
|
||||||
|
},
|
||||||
|
"public": {
|
||||||
|
"label": "በይፋ ተደራሽ ነው?"
|
||||||
|
},
|
||||||
|
"slug": {
|
||||||
|
"label": "ማስፈንጠሪያ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"heading": "አዲስ የስራ ታሪክ ሰነድ ይፍጠሩ"
|
||||||
|
},
|
||||||
|
"import-external": {
|
||||||
|
"heading": "ከውጭ ምንጮች አስገባ",
|
||||||
|
"json-resume": {
|
||||||
|
"actions": {
|
||||||
|
"upload-json": "JSON ስቀል"
|
||||||
|
},
|
||||||
|
"body": "ለመቀጠል ዝግጁ የሆነ <1>የተረጋገጠ JSON ሰነድ</1> ካለዎት በReactive Resume ላይ ስራዎን ለማፋጠን ሊጠቀሙበት ይችላሉ። ከታች ያለውን አዝራር በመጫን የሚሰራ JSON ፋይል ይስቀሉ።",
|
||||||
|
"heading": "ከ JSON ሰነድ"
|
||||||
|
},
|
||||||
|
"linkedin": {
|
||||||
|
"actions": {
|
||||||
|
"upload-archive": "የ ZIP ማህደር ስቀል"
|
||||||
|
},
|
||||||
|
"body": "መረጃዎን ከ LinkedIn ወደ በመላክ እና Reactive Resume ላይ በራስ-ሙላ መስኮችን በመጠቀም ጊዜ መቆጠብ ይችላሉ። በ LinkedIn ላይ ወደ <1>የመረጃ ግላዊነት</1> ክፍል ይሂዱ እና የመረጃ ማህደርዎን ይጠይቁ። ከተገኘ በኋላ፣ ከታች በሚገኘው የ ZIP ፋይሉን ይስቀሉ።",
|
||||||
|
"heading": "ከ LinkedIn ስቀል"
|
||||||
|
},
|
||||||
|
"reactive-resume": {
|
||||||
|
"actions": {
|
||||||
|
"upload-json": "JSON ስቀል",
|
||||||
|
"upload-json-v2": "JSON v2 ይስቀሉ።"
|
||||||
|
},
|
||||||
|
"body": "አሁን ካለው Reactive Resume ስሪት ጋር ወደ ውጭ የተላከ JSON ካለዎት፣ እንደገና ሊስተካከል የሚችል ስሪት ለማግኘት ወደዚህ መልሰው ማስገባት ይችላሉ።",
|
||||||
|
"heading": "ከ Reactive Resume ስቀል"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rename-resume": {
|
||||||
|
"actions": {
|
||||||
|
"rename-resume": "የስራ ታሪክ ሰነዱን ደግመው ይሰይሙ"
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"name": {
|
||||||
|
"label": "ስም"
|
||||||
|
},
|
||||||
|
"slug": {
|
||||||
|
"label": "ማስፈንጥሪያ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"heading": "የስራ ታሪክ ሰነዱን ደግመው ይሰይሙ"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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": {
|
||||||
@ -294,6 +303,10 @@
|
|||||||
"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": {
|
||||||
@ -294,6 +303,10 @@
|
|||||||
"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": {
|
||||||
@ -294,6 +303,10 @@
|
|||||||
"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": {
|
||||||
@ -294,6 +303,10 @@
|
|||||||
"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": {
|
||||||
@ -294,6 +303,10 @@
|
|||||||
"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": {
|
||||||
@ -294,6 +303,10 @@
|
|||||||
"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",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user