mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-10 04:22:27 +10:00
Compare commits
240 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7902f67f4f | |||
| 57dd110187 | |||
| 829375e87a | |||
| 0a15b4ebc9 | |||
| 2bff3fc20b | |||
| 1e997fe67c | |||
| dbf06455e4 | |||
| 42c7c9ade1 | |||
| 36c19bac3f | |||
| 44a9300aff | |||
| 610b5ba9d4 | |||
| 769e8811cd | |||
| 676fbcafe7 | |||
| 3935ae1e04 | |||
| ef6b765266 | |||
| 647dd6e682 | |||
| 43841e9962 | |||
| e2236c3207 | |||
| 7389d33ee5 | |||
| 4b21eabec9 | |||
| 1815b0fa21 | |||
| 6c4d3cbd56 | |||
| 8c2f3c8504 | |||
| 3aa7a98d9d | |||
| 5519ec898d | |||
| 1cd4c5d733 | |||
| 73d11c323f | |||
| 38812fcf25 | |||
| c22de12f12 | |||
| c94c971599 | |||
| c9a71a5917 | |||
| 8a29387470 | |||
| 592511b090 | |||
| af63fd38d4 | |||
| bf38b1b254 | |||
| 4a1c0079db | |||
| 5b6f6b7621 | |||
| 02587255fe | |||
| 9ef2a84ac2 | |||
| 77b1c5b536 | |||
| bf956fe18c | |||
| 4114f1e1dd | |||
| 668d39fa87 | |||
| 0d88a18757 | |||
| 0630369087 | |||
| 73af4a6859 | |||
| 99ddeb25a9 | |||
| 685aa06778 | |||
| 460abc6f1d | |||
| 04f02157ac | |||
| 828a4a8715 | |||
| 5b3141cd49 | |||
| 779d22101f | |||
| ef240b2110 | |||
| 32bb7354a4 | |||
| 0dcbad1f8a | |||
| a74921b27a | |||
| d4f47423c9 | |||
| 03f9a6543c | |||
| eb89cfcf5d | |||
| c52ef9ecb7 | |||
| c499abbb88 | |||
| 1a7ee88ecd | |||
| 16d19eb70f | |||
| 331346b99c | |||
| 95d265f672 | |||
| 315c7d6328 | |||
| 490e174564 | |||
| b5cde79f8b | |||
| d50f14bb78 | |||
| c13a751c1a | |||
| 5c37fc55d5 | |||
| 48a0f90597 | |||
| 05d3f1f06f | |||
| 4d43f6a642 | |||
| f7363ccdd7 | |||
| 07c91e9ac2 | |||
| cbe08f1d2c | |||
| c2617a8277 | |||
| fe72d2de41 | |||
| 23667e218f | |||
| 977fa72dde | |||
| 5197f954c0 | |||
| 58341e4cd2 | |||
| 1559703567 | |||
| 0a1fd50d07 | |||
| 1c19062c63 | |||
| 25cf594eb9 | |||
| 1c3beee6cd | |||
| 95c3d4c315 | |||
| 85df339e56 | |||
| d61ad44ebc | |||
| ccb1eff749 | |||
| bfb48e3aa7 | |||
| e2e08ad390 | |||
| f0dda06af3 | |||
| 4c4e77e21d | |||
| f364ae8929 | |||
| b52f292d89 | |||
| 8cac7f907c | |||
| a18a60679f | |||
| 5cc6a81b8c | |||
| 6ff212b698 | |||
| 56bcec5196 | |||
| 12019f90e9 | |||
| 7e6e69ed49 | |||
| a09a945e17 | |||
| df714dc8de | |||
| 28b63ef0c7 | |||
| 1b594dac61 | |||
| dd34a30ee0 | |||
| 0af398ceed | |||
| 04abd2cacc | |||
| a037a091e7 | |||
| f3a4c17cb4 | |||
| f06f7ad2e5 | |||
| aab2e5c8a9 | |||
| 4318dbe762 | |||
| ae3ff274ee | |||
| 164403c495 | |||
| 8595c92fb7 | |||
| 8f75f32f88 | |||
| 0d44189a5f | |||
| cd16a6d360 | |||
| 7b795bfaa4 | |||
| 8f78d47661 | |||
| 0b5e5a2ece | |||
| 9eade9514c | |||
| d744e06e96 | |||
| 9657c199d2 | |||
| 2dbe737b73 | |||
| f624699efa | |||
| e46f473754 | |||
| 767f4bf4bc | |||
| 1c5d025c15 | |||
| 8de8d89290 | |||
| 83662122a5 | |||
| 126482a760 | |||
| b04c22a27b | |||
| 63f88a3d1c | |||
| bd519db14f | |||
| a49aa42176 | |||
| 1a382db4d9 | |||
| c68f75dc8c | |||
| c12de0c013 | |||
| 4cafaf306a | |||
| 0238cf18a5 | |||
| 2f6072a7ba | |||
| 55dd2c5925 | |||
| a3e25f87fa | |||
| 9e82ea11c3 | |||
| 62fd63e41f | |||
| b91c175352 | |||
| 898e2314fc | |||
| bca2aa2fe5 | |||
| 427fdb717a | |||
| ee5b0187e2 | |||
| 94d05f33b4 | |||
| 35fe4e2774 | |||
| 317901a4d2 | |||
| 350ffcbc43 | |||
| 2c074a96c8 | |||
| 79f140b2d0 | |||
| 649c655ad5 | |||
| d5284a90d1 | |||
| bd18c53ab8 | |||
| 704c1ab7d4 | |||
| 1dbd7f221e | |||
| e1a47ffbe2 | |||
| 2add629970 | |||
| a48fcd9c97 | |||
| df7b00cb2c | |||
| 27fc939101 | |||
| 7c574d17e4 | |||
| 86a105f5a5 | |||
| 327bcc2b32 | |||
| a6cbd85010 | |||
| 371b820923 | |||
| 1d47fd0267 | |||
| 276fc95bb0 | |||
| 34c8861321 | |||
| 780b782579 | |||
| 9daa99fd5b | |||
| 76b3aa29cf | |||
| 25d4913fab | |||
| 0efeff3a4f | |||
| f56089925e | |||
| 5afae08f20 | |||
| 4bf114dfd6 | |||
| 23a3c2e624 | |||
| 71862f4354 | |||
| 6861c0f0fa | |||
| 9a18e74b90 | |||
| 4dd1b70079 | |||
| f9580fe716 | |||
| 3545f7939f | |||
| 9caad3bc0b | |||
| 5bdb92b1cf | |||
| 87d381fe8e | |||
| ccfb4d3cb0 | |||
| 763074a86c | |||
| 0f46895711 | |||
| aa736af0f5 | |||
| 1d9056f935 | |||
| 9cadd603f3 | |||
| b7b62d7bd0 | |||
| 820e6c90d3 | |||
| ea642d1b60 | |||
| ec006779a8 | |||
| 515be23c44 | |||
| c11aec8b44 | |||
| 3c2147e72c | |||
| 15a35e6243 | |||
| d53a5a492c | |||
| 0810e5ae6a | |||
| 881b183db5 | |||
| 15cea02872 | |||
| c195561df0 | |||
| fc725cfc0c | |||
| 9f54516e8c | |||
| 68a4cd9635 | |||
| ff01802f2f | |||
| bb900bc2e1 | |||
| 459f82b66b | |||
| 4b382243e4 | |||
| af074085d1 | |||
| 0c8c872668 | |||
| ddb29bb40d | |||
| aecb627ab7 | |||
| b8cd53cb59 | |||
| e61f6153c3 | |||
| 386e8ab902 | |||
| 5e8f02e3ca | |||
| f219562e72 | |||
| 29d94dfc14 | |||
| 622f5fc28c | |||
| 647f01e25c | |||
| 2a4c298572 | |||
| 447d9b3ca1 | |||
| 86e66eb6a0 |
@ -1,4 +1,4 @@
|
||||
ARG VARIANT="16-bullseye"
|
||||
ARG VARIANT="lts-bullseye"
|
||||
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
|
||||
|
||||
|
||||
@ -1,13 +1,19 @@
|
||||
# Build Artifacts
|
||||
dist
|
||||
.next
|
||||
.turbo
|
||||
|
||||
# IDEs
|
||||
.vscode
|
||||
|
||||
# Project Metadata
|
||||
.crowdin.yml
|
||||
|
||||
# Documentation
|
||||
README.md
|
||||
SECURITY.md
|
||||
CHANGELOG.md
|
||||
CODE_OF_CONDUCT.md
|
||||
|
||||
# Project Dependencies
|
||||
node_modules
|
||||
|
||||
25
.env.example
25
.env.example
@ -1,10 +1,14 @@
|
||||
# Shared
|
||||
# Turbo Cache (Optional)
|
||||
TURBO_TEAM=
|
||||
TURBO_TOKEN=
|
||||
|
||||
# Server + Client
|
||||
TZ=UTC
|
||||
PUBLIC_URL=http://localhost
|
||||
PUBLIC_SERVER_URL=http://localhost/api
|
||||
PUBLIC_URL=http://client:3000
|
||||
PUBLIC_SERVER_URL=http://server:3100
|
||||
PUBLIC_GOOGLE_CLIENT_ID=
|
||||
|
||||
# Database
|
||||
# Server + Database
|
||||
POSTGRES_DB=postgres
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=postgres
|
||||
@ -16,13 +20,14 @@ POSTGRES_PORT=5432
|
||||
POSTGRES_SSL_CERT=
|
||||
JWT_SECRET=
|
||||
JWT_EXPIRY_TIME=604800
|
||||
PUBLIC_GOOGLE_CLIENT_ID=
|
||||
GOOGLE_CLIENT_SECRET=
|
||||
GOOGLE_API_KEY=
|
||||
SENDGRID_API_KEY=
|
||||
SENDGRID_FORGOT_PASSWORD_TEMPLATE_ID=
|
||||
SENDGRID_FROM_NAME=
|
||||
SENDGRID_FROM_EMAIL=
|
||||
MAIL_FROM_NAME=
|
||||
MAIL_FROM_EMAIL=
|
||||
MAIL_HOST=
|
||||
MAIL_PORT=
|
||||
MAIL_USERNAME=
|
||||
MAIL_PASSWORD=
|
||||
STORAGE_BUCKET=
|
||||
STORAGE_REGION=
|
||||
STORAGE_ENDPOINT=
|
||||
@ -30,5 +35,5 @@ STORAGE_URL_PREFIX=
|
||||
STORAGE_ACCESS_KEY=
|
||||
STORAGE_SECRET_KEY=
|
||||
|
||||
# Flags
|
||||
# Flags (Client)
|
||||
PUBLIC_FLAG_DISABLE_SIGNUPS=false
|
||||
@ -1,31 +1,34 @@
|
||||
{
|
||||
"root": true,
|
||||
"ignorePatterns": ["/app"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"extends": ["plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"],
|
||||
"extends": ["plugin:@typescript-eslint/recommended"],
|
||||
"plugins": ["@typescript-eslint/eslint-plugin", "simple-import-sort", "unused-imports"],
|
||||
"rules": {
|
||||
// TypeScript ESLint
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/interface-name-prefix": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
// Simple Import Sort
|
||||
"simple-import-sort/imports": "error",
|
||||
"simple-import-sort/exports": "error",
|
||||
// Unused Imports
|
||||
// ESLint
|
||||
"no-unused-vars": "off",
|
||||
|
||||
// Unused Imports
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
"unused-imports/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"vars": "all",
|
||||
"varsIgnorePattern": "^_",
|
||||
"args": "none",
|
||||
"varsIgnorePattern": "^_",
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
]
|
||||
],
|
||||
|
||||
// Simple Import Sort
|
||||
"simple-import-sort/imports": "error",
|
||||
"simple-import-sort/exports": "error",
|
||||
|
||||
// TypeScript ESLint
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/interface-name-prefix": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
|
||||
17
.github/ISSUE_TEMPLATE/bug-report.md
vendored
17
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@ -1,36 +1,43 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Create a report to help improve
|
||||
title: "[BUG] "
|
||||
title: '[BUG] '
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**Product Flavor**
|
||||
|
||||
- [ ] Managed (https://rxresu.me)
|
||||
- [ ] Self Hosted
|
||||
|
||||
**To Reproduce**
|
||||
|
||||
<!-- Steps to reproduce the behavior: -->
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
**Screenshots**
|
||||
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: <!--[e.g. iOS]-->
|
||||
- Browser <!--[e.g. chrome, safari]-->
|
||||
- Version <!--[e.g. 22]-->
|
||||
|
||||
- OS: <!--[e.g. iOS]-->
|
||||
- Browser <!--[e.g. chrome, safari]-->
|
||||
- Version <!--[e.g. 22]-->
|
||||
|
||||
**Additional context**
|
||||
|
||||
<!-- Add any other context about the problem here. -->
|
||||
|
||||
7
.github/ISSUE_TEMPLATE/feature-request.md
vendored
7
.github/ISSUE_TEMPLATE/feature-request.md
vendored
@ -1,20 +1,23 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FEATURE] "
|
||||
title: '[FEATURE] '
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
**Describe the solution you'd like**
|
||||
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
**Additional context**
|
||||
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
|
||||
16
.github/workflows/close-stale.yml
vendored
16
.github/workflows/close-stale.yml
vendored
@ -1,16 +0,0 @@
|
||||
name: 'Close stale issues and PRs'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v5.0.0
|
||||
with:
|
||||
stale-pr-message: 'This PR is stale because it has been open for 30 days with no activity. Remove the stale label or comment on this PR, otherwise it would be closed in 5 days.'
|
||||
stale-issue-message: 'This issue is stale because it has been open for 30 days with no activity. Remove the stale label or comment on this issue, otherwise it would be closed in 5 days.'
|
||||
days-before-stale: 30
|
||||
days-before-close: 5
|
||||
136
.github/workflows/docker-build-push.yml
vendored
136
.github/workflows/docker-build-push.yml
vendored
@ -5,144 +5,88 @@ on:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
docker_client:
|
||||
name: Docker (Client)
|
||||
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.0
|
||||
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@v1.2.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1.6.0
|
||||
|
||||
- name: Login to Docker
|
||||
uses: docker/login-action@v1.14.1
|
||||
- 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 Client Image
|
||||
uses: docker/build-push-action@v2.10.0
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
file: client/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
amruthpillai/reactive-resume:client-latest
|
||||
amruthpillai/reactive-resume:client-${{ steps.version.outputs.tag }}
|
||||
ghcr.io/amruthpillai/reactive-resume:client-latest
|
||||
ghcr.io/amruthpillai/reactive-resume:client-${{ steps.version.outputs.tag }}
|
||||
|
||||
docker_server:
|
||||
name: Docker (Server)
|
||||
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.0
|
||||
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@v1.2.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1.6.0
|
||||
|
||||
- name: Login to Docker
|
||||
uses: docker/login-action@v1.14.1
|
||||
- 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@v2.10.0
|
||||
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 }}
|
||||
|
||||
github_client:
|
||||
name: GitHub (Client)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.0.0
|
||||
|
||||
- id: version
|
||||
name: Get Version
|
||||
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1.2.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1.6.0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1.14.1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: $GITHUB_REPOSITORY_OWNER
|
||||
password: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Build and Push Client Image
|
||||
uses: docker/build-push-action@v2.10.0
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
file: client/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
ghcr.io/amruthpillai/reactive-resume:client-latest
|
||||
ghcr.io/amruthpillai/reactive-resume:client-${{ steps.version.outputs.tag }}
|
||||
|
||||
github_server:
|
||||
name: GitHub (Server)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.0.0
|
||||
|
||||
- id: version
|
||||
name: Get Version
|
||||
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1.2.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1.6.0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1.14.1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: $GITHUB_REPOSITORY_OWNER
|
||||
password: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Build and Push Server Image
|
||||
uses: docker/build-push-action@v2.10.0
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
file: server/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
ghcr.io/amruthpillai/reactive-resume:server-latest
|
||||
ghcr.io/amruthpillai/reactive-resume:server-${{ steps.version.outputs.tag }}
|
||||
|
||||
88
.github/workflows/docker-build.yml
vendored
Normal file
88
.github/workflows/docker-build.yml
vendored
Normal file
@ -0,0 +1,88 @@
|
||||
name: Build Docker Image
|
||||
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
client:
|
||||
name: Client
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.0.2
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- id: vars
|
||||
name: Get Short SHA
|
||||
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.0.0
|
||||
|
||||
- id: buildx
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
with:
|
||||
install: true
|
||||
|
||||
- id: version
|
||||
name: Get Version
|
||||
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
|
||||
|
||||
- name: Build Client Image
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
file: client/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
amruthpillai/reactive-resume:client-latest
|
||||
amruthpillai/reactive-resume:client-${{ steps.vars.outputs.sha_short }}
|
||||
ghcr.io/amruthpillai/reactive-resume:client-latest
|
||||
ghcr.io/amruthpillai/reactive-resume:client-${{ steps.vars.outputs.sha_short }}
|
||||
|
||||
server:
|
||||
name: Server
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.0.2
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.0.0
|
||||
|
||||
- id: buildx
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
with:
|
||||
install: true
|
||||
|
||||
- id: version
|
||||
name: Get Version
|
||||
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
|
||||
|
||||
- name: Build Server Image
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
file: server/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
amruthpillai/reactive-resume:server-latest
|
||||
amruthpillai/reactive-resume:server-${{ steps.vars.outputs.sha_short }}
|
||||
ghcr.io/amruthpillai/reactive-resume:server-latest
|
||||
ghcr.io/amruthpillai/reactive-resume:server-${{ steps.vars.outputs.sha_short }}
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -8,3 +8,6 @@ node_modules
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Turbo
|
||||
.turbo
|
||||
@ -1,6 +0,0 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
pnpm install
|
||||
pnpm run lint
|
||||
pnpm run format
|
||||
2
.npmrc
Normal file
2
.npmrc
Normal file
@ -0,0 +1,2 @@
|
||||
auto-install-peers=true
|
||||
strict-peer-dependencies=false
|
||||
@ -18,6 +18,7 @@ CHANGELOG.md
|
||||
|
||||
# Project Dependencies
|
||||
node_modules
|
||||
pnpm-lock.yaml
|
||||
|
||||
# Docker
|
||||
Dockerfile
|
||||
|
||||
6
.vscode/extensions.json
vendored
6
.vscode/extensions.json
vendored
@ -1,3 +1,7 @@
|
||||
{
|
||||
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "lokalise.i18n-ally"]
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"lokalise.i18n-ally"
|
||||
]
|
||||
}
|
||||
10
.vscode/launch.json
vendored
10
.vscode/launch.json
vendored
@ -3,12 +3,12 @@
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "Debug: Server",
|
||||
"port": 9229,
|
||||
"request": "attach",
|
||||
"restart": true,
|
||||
"stopOnEntry": false,
|
||||
"protocol": "inspector"
|
||||
"protocol": "inspector",
|
||||
"stopOnEntry": false
|
||||
},
|
||||
{
|
||||
"name": "Debug: Client",
|
||||
@ -17,9 +17,9 @@
|
||||
"command": "pnpm run dev:client",
|
||||
"console": "integratedTerminal",
|
||||
"serverReadyAction": {
|
||||
"action": "debugWithChrome",
|
||||
"pattern": "started server on .+, url: (https?://.+)",
|
||||
"uriFormat": "%s",
|
||||
"action": "debugWithChrome"
|
||||
"uriFormat": "%s"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
25
.vscode/settings.json
vendored
25
.vscode/settings.json
vendored
@ -1,25 +1,22 @@
|
||||
{
|
||||
"css.validate": false,
|
||||
"scss.validate": false,
|
||||
"editor.wordWrap": "on",
|
||||
"npm.packageManager": "pnpm",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.wordWrap": "on",
|
||||
"eslint.workingDirectories": [
|
||||
"schema",
|
||||
"client",
|
||||
"server"
|
||||
],
|
||||
"i18n-ally.enabledFrameworks": [
|
||||
"react"
|
||||
],
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.localesPaths": [
|
||||
"client/public/locales"
|
||||
],
|
||||
"i18n-ally.namespace": true,
|
||||
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
|
||||
"i18n-ally.sortKeys": true,
|
||||
"scss.validate": false
|
||||
"conventionalCommits.scopes": [
|
||||
"client",
|
||||
"server",
|
||||
"docker",
|
||||
"dependencies"
|
||||
]
|
||||
}
|
||||
113
CHANGELOG.md
113
CHANGELOG.md
@ -2,6 +2,119 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [3.6.2](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.5.3...v3.6.2) (2022-08-25)
|
||||
|
||||
### Features
|
||||
|
||||
- Implement Undo/Redo functionality across the resume builder section
|
||||
|
||||
### Improvements
|
||||
|
||||
- Update dependencies to the latest version
|
||||
- Cleanup ESLint configuration, add tailwindCSS formatting
|
||||
|
||||
### [3.5.3](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.5.2...v3.5.3) (2022-08-11)
|
||||
|
||||
### [3.5.2](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.5.1...v3.5.2) (2022-08-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **client:** :dizzy: add Finnish language support ([16d19eb](https://github.com/AmruthPillai/Reactive-Resume/commit/16d19eb70f64f768304f352d0f87102d328b57c1))
|
||||
|
||||
### [3.5.1](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.5.0...v3.5.1) (2022-07-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **client:** :sparkles: ask for confirmation when resetting a resume ([4d43f6a](https://github.com/AmruthPillai/Reactive-Resume/commit/4d43f6a6427198e62e9fcb995f1a28c0ee4de71e))
|
||||
* **docker:** :zap: remove ports from postgres docker instance ([07c91e9](https://github.com/AmruthPillai/Reactive-Resume/commit/07c91e9ac21e8ef120d08ab92363d8e48a55aaba))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **server:** :zap: don't initialize sendgrid if the apikey is empty ([05d3f1f](https://github.com/AmruthPillai/Reactive-Resume/commit/05d3f1f06fbffd899269a5c4dea3c52cf408125f))
|
||||
|
||||
## [3.5.0](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.4.8...v3.5.0) (2022-07-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **client:** :bug: attempt to fix the one-off date issue ([5197f95](https://github.com/AmruthPillai/Reactive-Resume/commit/5197f954c0baed3daf1c7e2c79b607354ef42024))
|
||||
* **client:** :bug: fix mui rendering of utc dates ([977fa72](https://github.com/AmruthPillai/Reactive-Resume/commit/977fa72ddeeeebf7463d43a820e85f783489a4dc))
|
||||
|
||||
### [3.4.8](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.4.7...v3.4.8) (2022-07-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **google:** add toast to display error message from google ([25cf594](https://github.com/AmruthPillai/Reactive-Resume/commit/25cf594eb948e1c2d6157028ee1fff2799df5f92))
|
||||
|
||||
### [3.4.7](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.4.6...v3.4.7) (2022-06-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **mui:** update mui datepickers to newer package ([bfb48e3](https://github.com/AmruthPillai/Reactive-Resume/commit/bfb48e3aa7e0575922841522edc1d38544d1884f))
|
||||
|
||||
### [3.4.6](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.4.5...v3.4.6) (2022-06-19)
|
||||
|
||||
## [3.6.0](https://github.com/dvd741-a/Reactive-Resume/compare/v3.3.4...v3.6.0) (2022-06-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **all:** upgrade to v3.4.0 ([87d381f](https://github.com/dvd741-a/Reactive-Resume/commit/87d381fe8eab9ca4624df5de6e8b9ab18a072b67))
|
||||
* **i18n:** add Hungrarian (Magyar) language ([35fe4e2](https://github.com/dvd741-a/Reactive-Resume/commit/35fe4e27744b6f7325b25db2cf3b626ed8598623))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **i18n:** fix language mismatch in exported pdf ([62fd63e](https://github.com/dvd741-a/Reactive-Resume/commit/62fd63e41fe10fba843a40fb08191f5944f2b2fc))
|
||||
* **typeorm:** update typeorm to latest 0.2.x for secpatch ([5bdb92b](https://github.com/dvd741-a/Reactive-Resume/commit/5bdb92b1cff9e56879f9bbf31801d6554a00a8d5))
|
||||
|
||||
## [3.5.0](https://github.com/dvd741-a/Reactive-Resume/compare/v3.3.4...v3.5.0) (2022-06-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **all:** upgrade to v3.4.0 ([87d381f](https://github.com/dvd741-a/Reactive-Resume/commit/87d381fe8eab9ca4624df5de6e8b9ab18a072b67))
|
||||
* **i18n:** add Hungrarian (Magyar) language ([35fe4e2](https://github.com/dvd741-a/Reactive-Resume/commit/35fe4e27744b6f7325b25db2cf3b626ed8598623))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **i18n:** fix language mismatch in exported pdf ([62fd63e](https://github.com/dvd741-a/Reactive-Resume/commit/62fd63e41fe10fba843a40fb08191f5944f2b2fc))
|
||||
* **typeorm:** update typeorm to latest 0.2.x for secpatch ([5bdb92b](https://github.com/dvd741-a/Reactive-Resume/commit/5bdb92b1cff9e56879f9bbf31801d6554a00a8d5))
|
||||
|
||||
### [3.4.5](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.4.4...v3.4.5) (2022-05-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **i18n:** fix language mismatch in exported pdf ([62fd63e](https://github.com/AmruthPillai/Reactive-Resume/commit/62fd63e41fe10fba843a40fb08191f5944f2b2fc))
|
||||
|
||||
## [3.4.4](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.4.3...v3.4.4) (2022-05-02)
|
||||
|
||||
### Features
|
||||
|
||||
* **i18n:** add Hungrarian (Magyar) language ([35fe4e2](https://github.com/AmruthPillai/Reactive-Resume/commit/35fe4e27744b6f7325b25db2cf3b626ed8598623))
|
||||
|
||||
### [3.4.3](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.4.2...v3.4.3) (2022-05-01)
|
||||
|
||||
### [3.4.2](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.4.1...v3.4.2) (2022-04-30)
|
||||
|
||||
### [3.4.1](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.4.0...v3.4.1) (2022-04-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **typeorm:** update typeorm to latest 0.2.x for secpatch ([5bdb92b](https://github.com/AmruthPillai/Reactive-Resume/commit/5bdb92b1cff9e56879f9bbf31801d6554a00a8d5))
|
||||
|
||||
### [3.4.0](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.3.3...v3.4.0) (2022-04-30)
|
||||
|
||||
### [3.3.4](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.3.3...v3.3.4) (2022-04-09)
|
||||
|
||||
### [3.3.3](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.3.2...v3.3.3) (2022-04-09)
|
||||
|
||||
|
||||
|
||||
@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
- Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
- The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
|
||||
29
README.md
29
README.md
@ -15,6 +15,23 @@ Reactive Resume is a free and open source resume builder that’s built to make
|
||||
|
||||
You have complete control over what goes into your resume, how it looks, what colors, what templates, even the layout in which sections placed. Want a dark mode resume? It’s as easy as editing 3 values and you’re done. You don’t need to wait to see your changes either. Everything you type, everything you change, appears immediately on your resume and gets updated in real time.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Reactive Resume](#reactive-resume)
|
||||
- [Go to App | [Docs](https://docs.rxresu.me)](#go-to-app--docs)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Features](#features)
|
||||
- [Languages](#languages)
|
||||
- [Tutorial](#tutorial)
|
||||
- [Build from Source](#build-from-source)
|
||||
- [Contributing](#contributing)
|
||||
- [Report Bugs and Feature Requests](#report-bugs-and-feature-requests)
|
||||
- [Donations](#donations)
|
||||
- [💸 PayPal](#-paypal)
|
||||
- [Infrastructure](#infrastructure)
|
||||
- [Contributors Wall](#contributors-wall)
|
||||
- [License](#license)
|
||||
|
||||
## Features
|
||||
|
||||
- Free, forever
|
||||
@ -39,20 +56,30 @@ You have complete control over what goes into your resume, how it looks, what co
|
||||
|
||||
- Arabic (اَلْعَرَبِيَّةُ)
|
||||
- Bengali (বাংলা)
|
||||
- Bulgarian (български)
|
||||
- Chinese (中文)
|
||||
- Czech (čeština)
|
||||
- Danish (Dansk)
|
||||
- Dutch (Nederlands)
|
||||
- English
|
||||
- Finnish (Suomi)
|
||||
- French (Français)
|
||||
- German (Deutsch)
|
||||
- Greek (Ελληνικά)
|
||||
- Hebrew (Ivrit)
|
||||
- Hindi (हिन्दी)
|
||||
- Hungarian (Magyar)
|
||||
- Indonesian (Bahasa Indonesia)
|
||||
- Italian (Italiano)
|
||||
- Kannada (ಕನ್ನಡ)
|
||||
- Malayalam (മലയാളം)
|
||||
- Odia (ଓଡ଼ିଆ)
|
||||
- Persian (Farsi)
|
||||
- Polish (Polski)
|
||||
- Portuguese (Português)
|
||||
- Russian (русский)
|
||||
- Spanish (Español)
|
||||
- Swedish (Svenska)
|
||||
- Tamil (தமிழ்)
|
||||
- Turkish (Türkçe)
|
||||
- Vietnamese (Tiếng Việt)
|
||||
@ -79,7 +106,7 @@ This project makes use of [conventional commits](https://www.conventionalcommits
|
||||
|
||||
NOTE: Be sure to merge the latest from `main` before making a pull request!
|
||||
|
||||
## Bugs? Feature Requests?
|
||||
## Report Bugs and Feature Requests
|
||||
|
||||
Use the [GitHub Issues](https://github.com/AmruthPillai/Reactive-Resume/issues/new/choose) platform to notify me about bugs or new features that you would like to see in Reactive Resume. Please check before creating new issues as there might already be one.
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application' version '7.1.2' apply false
|
||||
id 'com.android.library' version '7.1.2' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.6.20' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
|
||||
@ -1,8 +1,20 @@
|
||||
{
|
||||
"extends": ["../.eslintrc.json", "next/core-web-vitals"],
|
||||
"extends": ["../.eslintrc.json", "next/core-web-vitals", "plugin:tailwindcss/recommended"],
|
||||
"ignorePatterns": [".next", "__ENV.js"],
|
||||
"settings": {
|
||||
"next": {
|
||||
"rootDir": "client"
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
// Next.js
|
||||
"@next/next/no-img-element": "off",
|
||||
"@next/next/no-sync-scripts": "off"
|
||||
"@next/next/no-sync-scripts": "off",
|
||||
|
||||
// React Hooks
|
||||
"react-hooks/exhaustive-deps": "off",
|
||||
|
||||
// Tailwind CSS
|
||||
"tailwindcss/no-custom-classname": ["warn", { "whitelist": ["preview-mode", "printer-mode", "markdown"] }]
|
||||
}
|
||||
}
|
||||
|
||||
3
client/.gitignore
vendored
3
client/.gitignore
vendored
@ -37,3 +37,6 @@ yarn-error.log*
|
||||
|
||||
# react-env
|
||||
__ENV.js
|
||||
|
||||
# next-sitemap
|
||||
sitemap*.xml
|
||||
@ -1,22 +1,19 @@
|
||||
FROM node:17-alpine as dependencies
|
||||
|
||||
RUN apk add --no-cache curl g++ make python3 \
|
||||
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
||||
FROM node:lts-alpine AS base
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json pnpm-*.yaml ./
|
||||
RUN apk add --no-cache g++ git curl make python3 \
|
||||
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
||||
|
||||
FROM base as dependencies
|
||||
|
||||
COPY package.json pnpm-*.yaml turbo.json ./
|
||||
COPY ./schema/package.json ./schema/package.json
|
||||
COPY ./client/package.json ./client/package.json
|
||||
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
FROM node:17-alpine as builder
|
||||
|
||||
RUN apk add --no-cache curl g++ make python3 \
|
||||
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
||||
|
||||
WORKDIR /app
|
||||
FROM base as builder
|
||||
|
||||
COPY . .
|
||||
|
||||
@ -24,21 +21,20 @@ COPY --from=dependencies /app/node_modules ./node_modules
|
||||
COPY --from=dependencies /app/schema/node_modules ./schema/node_modules
|
||||
COPY --from=dependencies /app/client/node_modules ./client/node_modules
|
||||
|
||||
RUN pnpm run build:schema
|
||||
RUN pnpm run build:client
|
||||
ARG TURBO_TEAM
|
||||
ARG TURBO_TOKEN
|
||||
|
||||
FROM node:17-alpine as production
|
||||
ENV TURBO_TEAM $TURBO_TEAM
|
||||
ENV TURBO_TOKEN $TURBO_TOKEN
|
||||
|
||||
WORKDIR /app
|
||||
RUN pnpm run build --filter client
|
||||
|
||||
RUN apk add --no-cache curl \
|
||||
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
||||
FROM base as production
|
||||
|
||||
COPY --from=builder /app/pnpm-*.yaml ./
|
||||
COPY --from=builder /app/package.json ./
|
||||
COPY --from=builder /app/package.json /app/pnpm-*.yaml /app/turbo.json ./
|
||||
COPY --from=builder /app/client/package.json ./client/package.json
|
||||
|
||||
RUN pnpm install -F client --frozen-lockfile --prod
|
||||
RUN pnpm install --filter client --prod --frozen-lockfile --workspace-root
|
||||
|
||||
COPY --from=builder /app/client/.next ./client/.next
|
||||
COPY --from=builder /app/client/public ./client/public
|
||||
@ -52,4 +48,4 @@ ENV PORT 3000
|
||||
HEALTHCHECK --interval=30s --timeout=20s --retries=3 --start-period=15s \
|
||||
CMD curl -fSs 127.0.0.1:3000 || exit 1
|
||||
|
||||
CMD [ "pnpm", "run", "start:client" ]
|
||||
CMD [ "pnpm", "run", "start", "--filter", "client" ]
|
||||
@ -5,6 +5,8 @@ import {
|
||||
FilterCenterFocus,
|
||||
InsertPageBreak,
|
||||
Link,
|
||||
RedoOutlined,
|
||||
UndoOutlined,
|
||||
ViewSidebar,
|
||||
ZoomIn,
|
||||
ZoomOut,
|
||||
@ -16,6 +18,7 @@ import { useTranslation } from 'next-i18next';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useMutation } from 'react-query';
|
||||
import { ReactZoomPanPinchRef } from 'react-zoom-pan-pinch';
|
||||
import { ActionCreators } from 'redux-undo';
|
||||
|
||||
import { ServerError } from '@/services/axios';
|
||||
import { printResumeAsPdf, PrintResumeAsPdfParams } from '@/services/printer';
|
||||
@ -31,14 +34,18 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
|
||||
const theme = useTheme();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
const isDesktop = useMediaQuery(theme.breakpoints.up('sm'));
|
||||
const pages = useAppSelector((state) => state.resume.metadata.layout);
|
||||
|
||||
const { past, present: resume, future } = useAppSelector((state) => state.resume);
|
||||
const pages = get(resume, 'metadata.layout');
|
||||
const { left, right } = useAppSelector((state) => state.build.sidebar);
|
||||
const orientation = useAppSelector((state) => state.build.page.orientation);
|
||||
|
||||
const { mutateAsync, isLoading } = useMutation<string, ServerError, PrintResumeAsPdfParams>(printResumeAsPdf);
|
||||
|
||||
const handleUndo = () => dispatch(ActionCreators.undo());
|
||||
const handleRedo = () => dispatch(ActionCreators.redo());
|
||||
|
||||
const handleTogglePageBreakLine = () => dispatch(togglePageBreakLine());
|
||||
|
||||
const handleTogglePageOrientation = () => dispatch(togglePageOrientation());
|
||||
@ -75,6 +82,20 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
|
||||
})}
|
||||
>
|
||||
<div className={styles.controller}>
|
||||
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.undo')}>
|
||||
<ButtonBase onClick={handleUndo} className={clsx({ 'pointer-events-none opacity-50': past.length < 2 })}>
|
||||
<UndoOutlined fontSize="medium" />
|
||||
</ButtonBase>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.redo')}>
|
||||
<ButtonBase onClick={handleRedo} className={clsx({ 'pointer-events-none opacity-50': future.length === 0 })}>
|
||||
<RedoOutlined fontSize="medium" />
|
||||
</ButtonBase>
|
||||
</Tooltip>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.zoom-in')}>
|
||||
<ButtonBase onClick={() => zoomIn(0.25)}>
|
||||
<ZoomIn fontSize="medium" />
|
||||
@ -97,17 +118,18 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
|
||||
|
||||
{isDesktop && (
|
||||
<>
|
||||
{pages.length > 1 && (
|
||||
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.toggle-orientation')}>
|
||||
<ButtonBase onClick={handleTogglePageOrientation}>
|
||||
{orientation === 'vertical' ? (
|
||||
<AlignHorizontalCenter fontSize="medium" />
|
||||
) : (
|
||||
<AlignVerticalCenter fontSize="medium" />
|
||||
)}
|
||||
</ButtonBase>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.toggle-orientation')}>
|
||||
<ButtonBase
|
||||
onClick={handleTogglePageOrientation}
|
||||
className={clsx({ 'pointer-events-none opacity-50': pages.length === 1 })}
|
||||
>
|
||||
{orientation === 'vertical' ? (
|
||||
<AlignHorizontalCenter fontSize="medium" />
|
||||
) : (
|
||||
<AlignVerticalCenter fontSize="medium" />
|
||||
)}
|
||||
</ButtonBase>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.toggle-page-break-line')}>
|
||||
<ButtonBase onClick={handleTogglePageBreakLine}>
|
||||
|
||||
@ -13,7 +13,7 @@ import Page from './Page';
|
||||
const Center = () => {
|
||||
const orientation = useAppSelector((state) => state.build.page.orientation);
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
const resume = useAppSelector((state) => state.resume.present);
|
||||
const layout: string[][][] = get(resume, 'metadata.layout');
|
||||
|
||||
if (isEmpty(resume)) return null;
|
||||
|
||||
@ -57,7 +57,7 @@ const Header = () => {
|
||||
|
||||
const { mutateAsync: deleteMutation } = useMutation<void, ServerError, DeleteResumeParams>(deleteResume);
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
const resume = useAppSelector((state) => state.resume.present);
|
||||
const { left, right } = useAppSelector((state) => state.build.sidebar);
|
||||
|
||||
const name = useMemo(() => get(resume, 'name'), [resume]);
|
||||
|
||||
@ -20,7 +20,7 @@ type Props = PageProps & {
|
||||
const Page: React.FC<Props> = ({ page, showPageNumbers = false }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
const resume = useAppSelector((state) => state.resume.present);
|
||||
const breakLine: boolean = useAppSelector((state) => state.build.page.breakLine);
|
||||
|
||||
const theme: Theme = get(resume, 'metadata.theme');
|
||||
@ -48,7 +48,7 @@ const Page: React.FC<Props> = ({ page, showPageNumbers = false }) => {
|
||||
</div>
|
||||
|
||||
{showPageNumbers && (
|
||||
<h4 className={styles.pageNumber}>{`${t<string>('builder.common.glossary.page')} {page + 1}`}</h4>
|
||||
<h4 className={styles.pageNumber}>{`${t<string>('builder.common.glossary.page')} ${page + 1}`}</h4>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -25,7 +25,7 @@ const LeftSidebar = () => {
|
||||
|
||||
const isDesktop = useMediaQuery(theme.breakpoints.up('lg'));
|
||||
|
||||
const sections = useAppSelector((state) => state.resume.sections);
|
||||
const sections = useAppSelector((state) => state.resume.present.sections);
|
||||
const { open } = useAppSelector((state) => state.build.sidebar.left);
|
||||
|
||||
const customSections = useMemo(() => getCustomSections(sections), [sections]);
|
||||
@ -81,14 +81,14 @@ const LeftSidebar = () => {
|
||||
arrow
|
||||
key={id}
|
||||
placement="right"
|
||||
title={get(sections, `${id}.name`, t<string>(`builder.leftSidebar.sections.${id}.heading`))}
|
||||
title={get(sections, `${id}.name`, t<string>(`builder.leftSidebar.sections.${id}.heading`)) as string}
|
||||
>
|
||||
<IconButton onClick={() => handleClick(id)}>{icon}</IconButton>
|
||||
</Tooltip>
|
||||
))}
|
||||
|
||||
{customSections.map(({ id }) => (
|
||||
<Tooltip key={id} title={get(sections, `${id}.name`, '')} placement="right" arrow>
|
||||
<Tooltip key={id} title={get(sections, `${id}.name`, '') as string} placement="right" arrow>
|
||||
<IconButton onClick={() => handleClick(id)}>
|
||||
<Star />
|
||||
</IconButton>
|
||||
|
||||
@ -32,7 +32,7 @@ const Basics = () => {
|
||||
<PhotoUpload />
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2 w-full sm:col-span-2">
|
||||
<div className="grid w-full gap-2 sm:col-span-2">
|
||||
<ResumeInput label={t<string>('builder.leftSidebar.sections.basics.name.label')} path="basics.name" />
|
||||
|
||||
<Button variant="outlined" startIcon={<PhotoFilter />} onClick={handleClick}>
|
||||
@ -57,6 +57,12 @@ const Basics = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ResumeInput
|
||||
type="date"
|
||||
label={t<string>('builder.leftSidebar.sections.basics.birthdate.label')}
|
||||
path="basics.birthdate"
|
||||
className="sm:col-span-2"
|
||||
/>
|
||||
<ResumeInput
|
||||
label={t<string>('builder.common.form.email.label')}
|
||||
path="basics.email"
|
||||
|
||||
@ -12,7 +12,7 @@ const PhotoFilters = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const photo: Photo = useAppSelector((state) => get(state.resume, 'basics.photo'));
|
||||
const photo: Photo = useAppSelector((state) => get(state.resume.present, 'basics.photo'));
|
||||
const size: number = get(photo, 'filters.size', 128);
|
||||
const shape: PhotoShape = get(photo, 'filters.shape', 'square');
|
||||
const grayscale: boolean = get(photo, 'filters.grayscale', false);
|
||||
|
||||
@ -21,8 +21,8 @@ const PhotoUpload: React.FC = () => {
|
||||
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const id: number = useAppSelector((state) => get(state.resume, 'id'));
|
||||
const photo: Photo = useAppSelector((state) => get(state.resume, 'basics.photo'));
|
||||
const id: number = useAppSelector((state) => get(state.resume.present, 'id'));
|
||||
const photo: Photo = useAppSelector((state) => get(state.resume.present, 'basics.photo'));
|
||||
|
||||
const { mutateAsync: uploadMutation, isLoading } = useMutation<Resume, ServerError, UploadPhotoParams>(uploadPhoto);
|
||||
|
||||
|
||||
@ -37,8 +37,8 @@ const Section: React.FC<Props> = ({
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector<string>((state) => get(state.resume, `${path}.name`, name));
|
||||
const visibility = useAppSelector<boolean>((state) => get(state.resume, `${path}.visible`, true));
|
||||
const heading = useAppSelector<string>((state) => get(state.resume.present, `${path}.name`, name));
|
||||
const visibility = useAppSelector<boolean>((state) => get(state.resume.present, `${path}.visible`, true));
|
||||
|
||||
const handleAdd = () => {
|
||||
const id = path.split('.')[1];
|
||||
|
||||
@ -18,7 +18,7 @@ const SectionSettings: React.FC<Props> = ({ path }) => {
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
|
||||
|
||||
const columns = useAppSelector<number>((state) => get(state.resume, `${path}.columns`, 2));
|
||||
const columns = useAppSelector<number>((state) => get(state.resume.present, `${path}.columns`, 2));
|
||||
|
||||
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
|
||||
@ -17,7 +17,7 @@ const CustomCSS = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const customCSS: CustomCSSType = useAppSelector((state) => get(state.resume, 'metadata.css', {}));
|
||||
const customCSS: CustomCSSType = useAppSelector((state) => get(state.resume.present, 'metadata.css', {}));
|
||||
|
||||
const handleChange = (value: string | undefined) => {
|
||||
dispatch(setResumeState({ path: 'metadata.css.value', value }));
|
||||
|
||||
@ -13,7 +13,7 @@ import { useAppSelector } from '@/store/hooks';
|
||||
const Export = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
const resume = useAppSelector((state) => state.resume.present);
|
||||
|
||||
const { mutateAsync, isLoading } = useMutation<string, ServerError, PrintResumeAsPdfParams>(printResumeAsPdf);
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { DragDropContext, Draggable, DraggableLocation, Droppable, DropResult } from '@hello-pangea/dnd';
|
||||
import { Add, Close, Restore } from '@mui/icons-material';
|
||||
import { Button, IconButton, Tooltip } from '@mui/material';
|
||||
import clsx from 'clsx';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import get from 'lodash/get';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { DragDropContext, Draggable, DraggableLocation, Droppable, DropResult } from 'react-beautiful-dnd';
|
||||
|
||||
import Heading from '@/components/shared/Heading';
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||
@ -23,8 +23,8 @@ const Layout = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const layout = useAppSelector((state) => state.resume.metadata.layout);
|
||||
const resumeSections = useAppSelector((state) => state.resume.sections);
|
||||
const layout = useAppSelector((state) => state.resume.present.metadata.layout);
|
||||
const resumeSections = useAppSelector((state) => state.resume.present.sections);
|
||||
|
||||
const onDragEnd = (dropResult: DropResult) => {
|
||||
const { source: srcLoc, destination: destLoc } = dropResult;
|
||||
@ -117,7 +117,7 @@ const Layout = () => {
|
||||
[styles.disabled]: !get(resumeSections, `${sectionId}.visible`, true),
|
||||
})}
|
||||
>
|
||||
{get(resumeSections, `${sectionId}.name`, '')}
|
||||
{get(resumeSections, `${sectionId}.name`, '') as string}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -15,7 +15,7 @@ import dayjs from 'dayjs';
|
||||
import get from 'lodash/get';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useMemo } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useMutation } from 'react-query';
|
||||
|
||||
import Heading from '@/components/shared/Heading';
|
||||
@ -36,9 +36,11 @@ const Settings = () => {
|
||||
|
||||
const { locale, ...router } = useRouter();
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
const [confirmReset, setConfirmReset] = useState(false);
|
||||
|
||||
const resume = useAppSelector((state) => state.resume.present);
|
||||
const theme = useAppSelector((state) => state.build.theme);
|
||||
const pages = useAppSelector((state) => state.resume.metadata.layout);
|
||||
const pages = useAppSelector((state) => state.resume.present.metadata.layout);
|
||||
const breakLine = useAppSelector((state) => state.build.page.breakLine);
|
||||
const orientation = useAppSelector((state) => state.build.page.orientation);
|
||||
|
||||
@ -48,7 +50,7 @@ const Settings = () => {
|
||||
const dateConfig: DateConfig = useMemo(() => get(resume, 'metadata.date'), [resume]);
|
||||
|
||||
const isDarkMode = useMemo(() => theme === 'dark', [theme]);
|
||||
const exampleString = useMemo(() => `Eg. ${dayjs().format(dateConfig.format)}`, [dateConfig.format]);
|
||||
const exampleString = useMemo(() => `Eg. ${dayjs().utc().format(dateConfig.format)}`, [dateConfig.format]);
|
||||
const themeString = useMemo(() => (isDarkMode ? 'Matte Black Everything' : 'As bright as your future'), [isDarkMode]);
|
||||
|
||||
const { mutateAsync: loadSampleDataMutation } = useMutation<Resume, ServerError, LoadSampleDataParams>(
|
||||
@ -78,9 +80,14 @@ const Settings = () => {
|
||||
};
|
||||
|
||||
const handleResetResume = async () => {
|
||||
await resetResumeMutation({ id });
|
||||
if (!confirmReset) {
|
||||
return setConfirmReset(true);
|
||||
}
|
||||
|
||||
queryClient.invalidateQueries(`resume/${username}/${slug}`);
|
||||
await resetResumeMutation({ id });
|
||||
await queryClient.invalidateQueries(`resume/${username}/${slug}`);
|
||||
|
||||
setConfirmReset(false);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -90,7 +97,7 @@ const Settings = () => {
|
||||
<List sx={{ padding: 0 }}>
|
||||
{/* Global Settings */}
|
||||
<>
|
||||
<ListSubheader className="rounded">
|
||||
<ListSubheader disableSticky className="rounded">
|
||||
{t<string>('builder.rightSidebar.sections.settings.global.heading')}
|
||||
</ListSubheader>
|
||||
|
||||
@ -148,7 +155,7 @@ const Settings = () => {
|
||||
|
||||
{/* Page Settings */}
|
||||
<>
|
||||
<ListSubheader className="rounded">
|
||||
<ListSubheader disableSticky className="rounded">
|
||||
{t<string>('builder.rightSidebar.sections.settings.page.heading')}
|
||||
</ListSubheader>
|
||||
|
||||
@ -180,7 +187,7 @@ const Settings = () => {
|
||||
|
||||
{/* Resume Settings */}
|
||||
<>
|
||||
<ListSubheader className="rounded">
|
||||
<ListSubheader disableSticky className="rounded">
|
||||
{t<string>('builder.rightSidebar.sections.settings.resume.heading')}
|
||||
</ListSubheader>
|
||||
|
||||
@ -202,7 +209,11 @@ const Settings = () => {
|
||||
<DeleteForever />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t<string>('builder.rightSidebar.sections.settings.resume.reset.primary')}
|
||||
primary={
|
||||
confirmReset
|
||||
? 'Are you sure?'
|
||||
: t<string>('builder.rightSidebar.sections.settings.resume.reset.primary')
|
||||
}
|
||||
secondary={t<string>('builder.rightSidebar.sections.settings.resume.reset.secondary')}
|
||||
/>
|
||||
</ListItemButton>
|
||||
|
||||
@ -17,7 +17,7 @@ const Sharing = () => {
|
||||
|
||||
const [showShortUrl, setShowShortUrl] = useState(false);
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
const resume = useAppSelector((state) => state.resume.present);
|
||||
const isPublic = useMemo(() => get(resume, 'public'), [resume]);
|
||||
const url = useMemo(() => getResumeUrl(resume, { withHost: true }), [resume]);
|
||||
const shortUrl = useMemo(() => getResumeUrl(resume, { withHost: true, shortUrl: true }), [resume]);
|
||||
|
||||
@ -16,7 +16,7 @@ const Templates = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const currentTemplate: string = useAppSelector((state) => get(state.resume, 'metadata.template'));
|
||||
const currentTemplate: string = useAppSelector((state) => get(state.resume.present, 'metadata.template'));
|
||||
|
||||
const handleChange = (template: TemplateMeta) => {
|
||||
dispatch(setResumeState({ path: 'metadata.template', value: template.id }));
|
||||
@ -31,7 +31,7 @@ const Templates = () => {
|
||||
<div key={template.id} className={styles.template}>
|
||||
<div className={clsx(styles.preview, { [styles.selected]: template.id === currentTemplate })}>
|
||||
<ButtonBase onClick={() => handleChange(template)}>
|
||||
<Image src={template.preview} alt={template.name} className="rounded-sm" layout="fill" />
|
||||
<Image src={template.preview} alt={template.name} className="rounded-sm" layout="fill" priority />
|
||||
</ButtonBase>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
.container {
|
||||
@apply grid sm:grid-cols-2 gap-4;
|
||||
@apply grid gap-4 sm:grid-cols-2;
|
||||
}
|
||||
|
||||
.colorOptions {
|
||||
@apply col-span-2 mb-4;
|
||||
@apply grid grid-cols-8 gap-y-2 justify-items-center;
|
||||
@apply grid grid-cols-8 justify-items-center gap-y-2;
|
||||
}
|
||||
|
||||
@ -16,7 +16,9 @@ const Theme = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { background, text, primary } = useAppSelector<ThemeType>((state) => get(state.resume, 'metadata.theme'));
|
||||
const { background, text, primary } = useAppSelector<ThemeType>((state) =>
|
||||
get(state.resume.present, 'metadata.theme')
|
||||
);
|
||||
|
||||
const handleChange = (property: string, color: string) => {
|
||||
dispatch(setResumeState({ path: `metadata.theme.${property}`, value: color[0] !== '#' ? `#${color}` : color }));
|
||||
|
||||
@ -33,7 +33,7 @@ const Widgets: React.FC<WidgetProps> = ({ label, category }) => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { family, size } = useAppSelector<TypographyType>((state) => get(state.resume, 'metadata.typography'));
|
||||
const { family, size } = useAppSelector<TypographyType>((state) => get(state.resume.present, 'metadata.typography'));
|
||||
|
||||
const { data: fonts } = useQuery(FONTS_QUERY, fetchFonts, {
|
||||
select: (fonts) => fonts.sort((a, b) => a.category.localeCompare(b.category)),
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
.testimony {
|
||||
@apply grid gap-2;
|
||||
@apply border-2 rounded p-4 dark:border-neutral-800;
|
||||
@apply rounded border-2 p-4 dark:border-neutral-800;
|
||||
|
||||
blockquote {
|
||||
@apply text-xs leading-normal text-justify opacity-90;
|
||||
@apply text-justify text-xs leading-normal opacity-90;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
}
|
||||
|
||||
.header {
|
||||
@apply sticky top-0 left-0 right-0 z-50 pt-6 bg-neutral-50 dark:bg-neutral-900;
|
||||
@apply sticky top-0 left-0 right-0 z-50 bg-neutral-50 pt-6 dark:bg-neutral-900;
|
||||
@apply flex items-center justify-between;
|
||||
@apply w-full border-b pb-5 dark:border-white/10;
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
}
|
||||
|
||||
.footer {
|
||||
@apply sticky bottom-0 left-0 right-0 z-50 pb-6 bg-neutral-50 dark:bg-neutral-900;
|
||||
@apply sticky bottom-0 left-0 right-0 z-50 bg-neutral-50 pb-6 dark:bg-neutral-900;
|
||||
@apply flex items-center justify-end gap-x-4;
|
||||
@apply w-full border-t pt-5 dark:border-white/10;
|
||||
}
|
||||
|
||||
@ -5,11 +5,12 @@ import { useRouter } from 'next/router';
|
||||
import styles from './BaseModal.module.scss';
|
||||
|
||||
type Props = {
|
||||
icon?: React.ReactNode;
|
||||
isOpen: boolean;
|
||||
heading: string;
|
||||
handleClose: () => void;
|
||||
icon?: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
footerChildren?: React.ReactNode;
|
||||
handleClose: () => void;
|
||||
};
|
||||
|
||||
const BaseModal: React.FC<Props> = ({ icon, isOpen, heading, children, handleClose, footerChildren }) => {
|
||||
|
||||
@ -32,8 +32,8 @@ const Heading: React.FC<Props> = ({
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`, name));
|
||||
const visibility = useAppSelector((state) => get(state.resume, `${path}.visible`, true));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`, name));
|
||||
const visibility = useAppSelector((state) => get(state.resume.present, `${path}.visible`, true));
|
||||
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
|
||||
|
||||
@ -9,5 +9,5 @@
|
||||
}
|
||||
|
||||
.language {
|
||||
@apply py-2 px-4 cursor-pointer text-center hover:underline;
|
||||
@apply cursor-pointer py-2 px-4 text-center hover:underline;
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ const List: React.FC<Props> = ({
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const list: Array<ListItemType> = useAppSelector((state) => get(state.resume, path, []));
|
||||
const list: Array<ListItemType> = useAppSelector((state) => get(state.resume.present, path, []));
|
||||
|
||||
const handleEdit = (item: ListItemType) => {
|
||||
isFunction(onEdit) && onEdit(item);
|
||||
@ -76,6 +76,7 @@ const List: React.FC<Props> = ({
|
||||
return (
|
||||
<ListItem
|
||||
key={item.id}
|
||||
path={path}
|
||||
item={item}
|
||||
index={index}
|
||||
title={title}
|
||||
|
||||
@ -17,6 +17,7 @@ interface DragItem {
|
||||
|
||||
type Props = {
|
||||
item: ListItemType;
|
||||
path: string;
|
||||
index: number;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
@ -26,14 +27,14 @@ type Props = {
|
||||
onDuplicate?: (item: ListItemType) => void;
|
||||
};
|
||||
|
||||
const ListItem: React.FC<Props> = ({ item, index, title, subtitle, onMove, onEdit, onDelete, onDuplicate }) => {
|
||||
const ListItem: React.FC<Props> = ({ item, path, index, title, subtitle, onMove, onEdit, onDelete, onDuplicate }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [anchorEl, setAnchorEl] = useState<Element | null>(null);
|
||||
|
||||
const [{ handlerId }, drop] = useDrop<DragItem, any, any>({
|
||||
accept: 'ListItem',
|
||||
accept: path,
|
||||
collect(monitor) {
|
||||
return { handlerId: monitor.getHandlerId() };
|
||||
},
|
||||
@ -68,7 +69,7 @@ const ListItem: React.FC<Props> = ({ item, index, title, subtitle, onMove, onEdi
|
||||
});
|
||||
|
||||
const [{ isDragging }, drag] = useDrag({
|
||||
type: 'ListItem',
|
||||
type: path,
|
||||
item: () => {
|
||||
return { id: item.id, index };
|
||||
},
|
||||
|
||||
@ -5,6 +5,7 @@ import styles from './Loading.module.scss';
|
||||
|
||||
const Loading: React.FC = () => {
|
||||
const { isReady } = useRouter();
|
||||
|
||||
const isFetching = useIsFetching();
|
||||
const isMutating = useIsMutating();
|
||||
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const NoSSR: React.FC = ({ children }) => <>{children}</>;
|
||||
|
||||
export default dynamic(() => Promise.resolve(NoSSR), { ssr: false });
|
||||
@ -1,4 +1,7 @@
|
||||
import { TextField } from '@mui/material';
|
||||
import { DatePicker } from '@mui/x-date-pickers';
|
||||
import dayjs from 'dayjs';
|
||||
import { isEmpty } from 'lodash';
|
||||
import get from 'lodash/get';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
@ -8,7 +11,7 @@ import { setResumeState } from '@/store/resume/resumeSlice';
|
||||
import MarkdownSupported from './MarkdownSupported';
|
||||
|
||||
interface Props {
|
||||
type?: 'text' | 'textarea';
|
||||
type?: 'text' | 'textarea' | 'date';
|
||||
label: string;
|
||||
path: string;
|
||||
className?: string;
|
||||
@ -18,7 +21,7 @@ interface Props {
|
||||
const ResumeInput: React.FC<Props> = ({ type = 'text', label, path, className, markdownSupported = false }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const stateValue = useAppSelector((state) => get(state.resume, path, ''));
|
||||
const stateValue = useAppSelector((state) => get(state.resume.present, path, ''));
|
||||
|
||||
useEffect(() => {
|
||||
setValue(stateValue);
|
||||
@ -31,6 +34,11 @@ const ResumeInput: React.FC<Props> = ({ type = 'text', label, path, className, m
|
||||
dispatch(setResumeState({ path, value: event.target.value }));
|
||||
};
|
||||
|
||||
const onChangeValue = (value: string) => {
|
||||
setValue(value);
|
||||
dispatch(setResumeState({ path, value }));
|
||||
};
|
||||
|
||||
if (type === 'textarea') {
|
||||
return (
|
||||
<TextField
|
||||
@ -45,6 +53,22 @@ const ResumeInput: React.FC<Props> = ({ type = 'text', label, path, className, m
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 'date') {
|
||||
return (
|
||||
<DatePicker
|
||||
openTo="year"
|
||||
label={label}
|
||||
value={value}
|
||||
views={['year', 'month', 'day']}
|
||||
renderInput={(params) => <TextField {...params} error={false} className={className} />}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && onChangeValue('');
|
||||
date && dayjs(date).utc().isValid() && onChangeValue(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <TextField type={type} label={label} value={value} onChange={onChange} className={className} />;
|
||||
};
|
||||
|
||||
|
||||
@ -6,20 +6,30 @@ export type Language = {
|
||||
|
||||
export const languages: Language[] = [
|
||||
{ code: 'ar', name: 'Arabic', localName: 'اَلْعَرَبِيَّةُ' },
|
||||
{ code: 'bg', name: 'Bulgarian', localName: 'български' },
|
||||
{ code: 'bn', name: 'Bengali', localName: 'বাংলা' },
|
||||
{ code: 'cs', name: 'Czech', localName: 'čeština' },
|
||||
{ code: 'da', name: 'Danish', localName: 'Dansk' },
|
||||
{ code: 'de', name: 'German', localName: 'Deutsch' },
|
||||
{ code: 'el', name: 'Greek', localName: 'Ελληνικά' },
|
||||
{ code: 'en', name: 'English' },
|
||||
{ code: 'es', name: 'Spanish', localName: 'Español' },
|
||||
{ code: 'fi', name: 'Finnish', localName: 'Suomi' },
|
||||
{ code: 'fr', name: 'French', localName: 'Français' },
|
||||
{ code: 'he', name: 'Hebrew', localName: 'Ivrit' },
|
||||
{ code: 'hi', name: 'Hindi', localName: 'हिन्दी' },
|
||||
{ code: 'hu', name: 'Hungarian', localName: 'Magyar' },
|
||||
{ code: 'id', name: 'Indonesian', localName: 'Bahasa Indonesia' },
|
||||
{ code: 'it', name: 'Italian', localName: 'Italiano' },
|
||||
{ code: 'kn', name: 'Kannada', localName: 'ಕನ್ನಡ' },
|
||||
{ code: 'ml', name: 'Malayalam', localName: 'മലയാളം' },
|
||||
{ code: 'nl', name: 'Dutch', localName: 'Nederlands' },
|
||||
{ code: 'or', name: 'Odia', localName: 'ଓଡ଼ିଆ' },
|
||||
{ code: 'fa', name: 'Persian', localName: 'Farsi' },
|
||||
{ code: 'pl', name: 'Polish', localName: 'Polski' },
|
||||
{ code: 'pt', name: 'Portuguese', localName: 'Português' },
|
||||
{ code: 'ru', name: 'Russian', localName: 'русский' },
|
||||
{ code: 'sv', name: 'Swedish', localName: 'Svenska' },
|
||||
{ code: 'ta', name: 'Tamil', localName: 'தமிழ்' },
|
||||
{ code: 'tr', name: 'Turkish', localName: 'Türkçe' },
|
||||
{ code: 'vi', name: 'Vietnamese', localName: 'Tiếng Việt' },
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { createTheme } from '@mui/material';
|
||||
import { createTheme, ThemeOptions } from '@mui/material/styles';
|
||||
|
||||
const theme = createTheme({
|
||||
const theme: ThemeOptions = {
|
||||
typography: {
|
||||
fontSize: 12,
|
||||
fontFamily: 'Inter, sans-serif',
|
||||
@ -49,7 +49,7 @@ const theme = createTheme({
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const lightTheme = createTheme({
|
||||
...theme,
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import env from '@beam-australia/react-env';
|
||||
import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Google, Login, Visibility, VisibilityOff } from '@mui/icons-material';
|
||||
import { Login, Visibility, VisibilityOff } from '@mui/icons-material';
|
||||
import { Button, IconButton, InputAdornment, TextField } from '@mui/material';
|
||||
import { CredentialResponse, GoogleLogin } from '@react-oauth/google';
|
||||
import Joi from 'joi';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Trans, useTranslation } from 'next-i18next';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { GoogleLoginResponse, GoogleLoginResponseOffline, useGoogleLogin } from 'react-google-login';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useIsMutating, useMutation } from 'react-query';
|
||||
@ -56,15 +56,6 @@ const LoginModal: React.FC = () => {
|
||||
loginWithGoogle
|
||||
);
|
||||
|
||||
const { signIn } = useGoogleLogin({
|
||||
clientId: env('GOOGLE_CLIENT_ID'),
|
||||
onSuccess: async (response: GoogleLoginResponse | GoogleLoginResponseOffline) => {
|
||||
await loginWithGoogleMutation({ accessToken: (response as GoogleLoginResponse).accessToken });
|
||||
|
||||
handleClose();
|
||||
},
|
||||
});
|
||||
|
||||
const handleClose = () => {
|
||||
dispatch(setModalState({ modal: 'auth.login', state: { open: false } }));
|
||||
reset();
|
||||
@ -93,8 +84,16 @@ const LoginModal: React.FC = () => {
|
||||
dispatch(setModalState({ modal: 'auth.forgot', state: { open: true } }));
|
||||
};
|
||||
|
||||
const handleLoginWithGoogle = () => {
|
||||
signIn();
|
||||
const handleLoginWithGoogle = async (response: CredentialResponse) => {
|
||||
if (response.credential) {
|
||||
await loginWithGoogleMutation({ credential: response.credential }, { onError: handleLoginWithGoogleError });
|
||||
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoginWithGoogleError = () => {
|
||||
toast("Please try logging in using email/password, or use another browser that supports Google's One Tap API.");
|
||||
};
|
||||
|
||||
const PasswordVisibility = (): React.ReactElement => {
|
||||
@ -118,15 +117,7 @@ const LoginModal: React.FC = () => {
|
||||
footerChildren={
|
||||
<div className="flex gap-4">
|
||||
{!isEmpty(env('GOOGLE_CLIENT_ID')) && (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outlined"
|
||||
disabled={isLoading}
|
||||
startIcon={<Google />}
|
||||
onClick={handleLoginWithGoogle}
|
||||
>
|
||||
{t<string>('modals.auth.login.actions.google')}
|
||||
</Button>
|
||||
<GoogleLogin onSuccess={handleLoginWithGoogle} onError={handleLoginWithGoogleError} />
|
||||
)}
|
||||
|
||||
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}>
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import env from '@beam-australia/react-env';
|
||||
import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Google, HowToReg } from '@mui/icons-material';
|
||||
import { HowToReg } from '@mui/icons-material';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { CredentialResponse, GoogleLogin } from '@react-oauth/google';
|
||||
import Joi from 'joi';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Trans, useTranslation } from 'next-i18next';
|
||||
import { GoogleLoginResponse, GoogleLoginResponseOffline, useGoogleLogin } from 'react-google-login';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useMutation } from 'react-query';
|
||||
|
||||
import BaseModal from '@/components/shared/BaseModal';
|
||||
@ -63,15 +64,6 @@ const RegisterModal: React.FC = () => {
|
||||
loginWithGoogle
|
||||
);
|
||||
|
||||
const { signIn } = useGoogleLogin({
|
||||
clientId: env('GOOGLE_CLIENT_ID'),
|
||||
onSuccess: async (response: GoogleLoginResponse | GoogleLoginResponseOffline) => {
|
||||
await loginWithGoogleMutation({ accessToken: (response as GoogleLoginResponse).accessToken });
|
||||
|
||||
handleClose();
|
||||
},
|
||||
});
|
||||
|
||||
const handleClose = () => {
|
||||
dispatch(setModalState({ modal: 'auth.register', state: { open: false } }));
|
||||
reset();
|
||||
@ -87,8 +79,16 @@ const RegisterModal: React.FC = () => {
|
||||
dispatch(setModalState({ modal: 'auth.login', state: { open: true } }));
|
||||
};
|
||||
|
||||
const handleLoginWithGoogle = () => {
|
||||
signIn();
|
||||
const handleLoginWithGoogle = async (response: CredentialResponse) => {
|
||||
if (response.credential) {
|
||||
await loginWithGoogleMutation({ credential: response.credential }, { onError: handleLoginWithGoogleError });
|
||||
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoginWithGoogleError = () => {
|
||||
toast("Please try logging in using email/password, or use another browser that supports Google's One Tap API.");
|
||||
};
|
||||
|
||||
return (
|
||||
@ -100,15 +100,7 @@ const RegisterModal: React.FC = () => {
|
||||
footerChildren={
|
||||
<div className="flex gap-4">
|
||||
{!isEmpty(env('GOOGLE_CLIENT_ID')) && (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outlined"
|
||||
disabled={isLoading}
|
||||
startIcon={<Google />}
|
||||
onClick={handleLoginWithGoogle}
|
||||
>
|
||||
{t<string>('modals.auth.register.actions.google')}
|
||||
</Button>
|
||||
<GoogleLogin onSuccess={handleLoginWithGoogle} onError={handleLoginWithGoogleError} />
|
||||
)}
|
||||
|
||||
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||
import DatePicker from '@mui/lab/DatePicker';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { DatePicker } from '@mui/x-date-pickers';
|
||||
import { Award, SectionPath } from '@reactive-resume/schema';
|
||||
import dayjs from 'dayjs';
|
||||
import Joi from 'joi';
|
||||
@ -44,7 +44,7 @@ const AwardModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
@ -134,7 +134,7 @@ const AwardModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||
import DatePicker from '@mui/lab/DatePicker';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { DatePicker } from '@mui/x-date-pickers';
|
||||
import { Certificate, SectionPath } from '@reactive-resume/schema';
|
||||
import dayjs from 'dayjs';
|
||||
import Joi from 'joi';
|
||||
@ -44,7 +44,7 @@ const CertificateModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
|
||||
@ -134,7 +134,7 @@ const CertificateModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||
import DatePicker from '@mui/lab/DatePicker';
|
||||
import { Button, Slider, TextField } from '@mui/material';
|
||||
import { DatePicker } from '@mui/x-date-pickers';
|
||||
import { Custom } from '@reactive-resume/schema';
|
||||
import dayjs from 'dayjs';
|
||||
import Joi from 'joi';
|
||||
@ -60,7 +60,7 @@ const CustomModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal['builder.sections.custom']);
|
||||
|
||||
const path: string = get(payload, 'path', '');
|
||||
@ -150,7 +150,7 @@ const CustomModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
@ -174,7 +174,7 @@ const CustomModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||
import DatePicker from '@mui/lab/DatePicker';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { DatePicker } from '@mui/x-date-pickers';
|
||||
import { Education, SectionPath } from '@reactive-resume/schema';
|
||||
import dayjs from 'dayjs';
|
||||
import Joi from 'joi';
|
||||
@ -57,7 +57,7 @@ const EducationModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
@ -173,7 +173,7 @@ const EducationModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
@ -197,7 +197,7 @@ const EducationModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
|
||||
@ -35,7 +35,7 @@ const InterestModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
|
||||
@ -36,7 +36,7 @@ const LanguageModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||
import DatePicker from '@mui/lab/DatePicker';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { DatePicker } from '@mui/x-date-pickers';
|
||||
import { Project, SectionPath } from '@reactive-resume/schema';
|
||||
import dayjs from 'dayjs';
|
||||
import Joi from 'joi';
|
||||
@ -53,7 +53,7 @@ const ProjectModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
@ -143,7 +143,7 @@ const ProjectModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
@ -167,7 +167,7 @@ const ProjectModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||
import DatePicker from '@mui/lab/DatePicker';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { DatePicker } from '@mui/x-date-pickers';
|
||||
import { Publication, SectionPath } from '@reactive-resume/schema';
|
||||
import dayjs from 'dayjs';
|
||||
import Joi from 'joi';
|
||||
@ -44,7 +44,7 @@ const PublicationModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
@ -134,7 +134,7 @@ const PublicationModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
|
||||
@ -41,7 +41,7 @@ const ReferenceModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
|
||||
@ -39,7 +39,7 @@ const SkillModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||
import DatePicker from '@mui/lab/DatePicker';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { DatePicker } from '@mui/x-date-pickers';
|
||||
import { SectionPath, Volunteer } from '@reactive-resume/schema';
|
||||
import dayjs from 'dayjs';
|
||||
import Joi from 'joi';
|
||||
@ -50,7 +50,7 @@ const VolunteerModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
@ -140,7 +140,7 @@ const VolunteerModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
@ -164,7 +164,7 @@ const VolunteerModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||
import DatePicker from '@mui/lab/DatePicker';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { DatePicker } from '@mui/x-date-pickers';
|
||||
import { SectionPath, WorkExperience } from '@reactive-resume/schema';
|
||||
import dayjs from 'dayjs';
|
||||
import Joi from 'joi';
|
||||
@ -50,7 +50,7 @@ const WorkModal: React.FC = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
|
||||
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
@ -140,7 +140,7 @@ const WorkModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
@ -164,7 +164,7 @@ const WorkModal: React.FC = () => {
|
||||
views={['year', 'month', 'day']}
|
||||
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
|
||||
isEmpty(keyboardInputValue) && field.onChange('');
|
||||
date && dayjs(date).isValid() && field.onChange(date.toISOString());
|
||||
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
|
||||
@ -69,7 +69,8 @@ const CreateResumeModal: React.FC = () => {
|
||||
try {
|
||||
await mutateAsync({ name, slug, public: isPublic });
|
||||
|
||||
queryClient.invalidateQueries(RESUMES_QUERY);
|
||||
await queryClient.invalidateQueries(RESUMES_QUERY);
|
||||
|
||||
handleClose();
|
||||
} catch (error: any) {
|
||||
toast.error(error.message);
|
||||
|
||||
@ -5,20 +5,30 @@ const i18nConfig = {
|
||||
defaultLocale: 'en',
|
||||
locales: [
|
||||
'ar',
|
||||
'bg',
|
||||
'bn',
|
||||
'cs',
|
||||
'da',
|
||||
'de',
|
||||
'el',
|
||||
'en',
|
||||
'es',
|
||||
'fa',
|
||||
'fi',
|
||||
'fr',
|
||||
'he',
|
||||
'hi',
|
||||
'hu',
|
||||
'id',
|
||||
'it',
|
||||
'kn',
|
||||
'ml',
|
||||
'nl',
|
||||
'or',
|
||||
'pl',
|
||||
'pt',
|
||||
'ru',
|
||||
'sv',
|
||||
'ta',
|
||||
'tr',
|
||||
'vi',
|
||||
|
||||
@ -2,78 +2,82 @@
|
||||
"name": "@reactive-resume/client",
|
||||
"scripts": {
|
||||
"dev": "react-env --prefix PUBLIC -- next dev",
|
||||
"lint": "next lint --fix",
|
||||
"build": "next build && npm run sitemap",
|
||||
"start": "react-env --prefix PUBLIC -- next start",
|
||||
"lint": "next lint --fix",
|
||||
"sitemap": "next-sitemap --config next-sitemap.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@beam-australia/react-env": "^3.1.1",
|
||||
"@emotion/css": "^11.9.0",
|
||||
"@emotion/react": "^11.9.0",
|
||||
"@emotion/styled": "^11.8.1",
|
||||
"@hookform/resolvers": "2.8.8",
|
||||
"@monaco-editor/react": "^4.4.1",
|
||||
"@mui/icons-material": "^5.6.0",
|
||||
"@mui/lab": "^5.0.0-alpha.76",
|
||||
"@mui/material": "^5.6.0",
|
||||
"@reduxjs/toolkit": "^1.8.1",
|
||||
"axios": "^0.26.1",
|
||||
"clsx": "^1.1.1",
|
||||
"dayjs": "^1.11.0",
|
||||
"@date-io/dayjs": "^2.15.0",
|
||||
"@emotion/css": "^11.10.0",
|
||||
"@emotion/react": "^11.10.0",
|
||||
"@emotion/styled": "^11.10.0",
|
||||
"@hello-pangea/dnd": "^16.0.0",
|
||||
"@hookform/resolvers": "2.9.7",
|
||||
"@monaco-editor/react": "^4.4.5",
|
||||
"@mui/icons-material": "^5.10.2",
|
||||
"@mui/lab": "^5.0.0-alpha.96",
|
||||
"@mui/material": "^5.10.2",
|
||||
"@mui/system": "^5.10.2",
|
||||
"@mui/x-date-pickers": "5.0.0-beta.7",
|
||||
"@next/env": "^12.2.5",
|
||||
"@react-oauth/google": "^0.2.6",
|
||||
"@reduxjs/toolkit": "^1.8.5",
|
||||
"axios": "^0.27.2",
|
||||
"clsx": "^1.2.1",
|
||||
"dayjs": "^1.11.5",
|
||||
"downloadjs": "^1.4.7",
|
||||
"joi": "^17.6.0",
|
||||
"lodash": "^4.17.21",
|
||||
"md5-hex": "^4.0.0",
|
||||
"monaco-editor": "^0.33.0",
|
||||
"nanoid": "^3.3.2",
|
||||
"next": "12.1.4",
|
||||
"next-i18next": "^11.0.0",
|
||||
"react": "<18",
|
||||
"react-beautiful-dnd": "^13.1.0",
|
||||
"react-colorful": "^5.5.1",
|
||||
"react-dnd": "^15.1.2",
|
||||
"react-dnd-html5-backend": "^15.1.2",
|
||||
"react-dom": "<18",
|
||||
"react-google-login": "^5.2.2",
|
||||
"react-hook-form": "^7.29.0",
|
||||
"react-hot-toast": "2.2.0",
|
||||
"react-hotkeys-hook": "^3.4.4",
|
||||
"react-icons": "^4.3.1",
|
||||
"react-markdown": "^8.0.2",
|
||||
"react-query": "^3.34.19",
|
||||
"react-redux": "^7.2.8",
|
||||
"monaco-editor": "^0.34.0",
|
||||
"nanoid": "^3.3.4",
|
||||
"next": "12.2.5",
|
||||
"next-i18next": "^12.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-dnd": "16.0.1",
|
||||
"react-dnd-html5-backend": "16.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.34.2",
|
||||
"react-hot-toast": "2.3.0",
|
||||
"react-hotkeys-hook": "^3.4.7",
|
||||
"react-icons": "^4.4.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-query": "^3.39.2",
|
||||
"react-redux": "^8.0.2",
|
||||
"react-zoom-pan-pinch": "^2.1.3",
|
||||
"redux": "^4.1.2",
|
||||
"redux": "^4.2.0",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-saga": "^1.1.3",
|
||||
"redux-saga": "^1.2.1",
|
||||
"redux-undo": "^1.0.1",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"sharp": "^0.30.3",
|
||||
"sharp": "^0.30.7",
|
||||
"uuid": "^8.3.2",
|
||||
"webfontloader": "^1.6.28"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.17.9",
|
||||
"@babel/core": "^7.18.13",
|
||||
"@reactive-resume/schema": "workspace:*",
|
||||
"@tailwindcss/typography": "^0.5.2",
|
||||
"@tailwindcss/typography": "^0.5.4",
|
||||
"@types/downloadjs": "^1.4.3",
|
||||
"@types/lodash": "^4.14.181",
|
||||
"@types/node": "17.0.23",
|
||||
"@types/react": "<18",
|
||||
"@types/react-beautiful-dnd": "^13.1.2",
|
||||
"@types/react-redux": "^7.1.23",
|
||||
"@types/tailwindcss": "^3.0.10",
|
||||
"@types/lodash": "^4.14.184",
|
||||
"@types/node": "^18.7.13",
|
||||
"@types/react": "^18.0.17",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-redux": "^7.1.24",
|
||||
"@types/tailwindcss": "^3.0.11",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/webfontloader": "^1.6.34",
|
||||
"autoprefixer": "^10.4.4",
|
||||
"csstype": "^3.0.11",
|
||||
"eslint": "^8.12.0",
|
||||
"eslint-config-next": "12.1.4",
|
||||
"next-sitemap": "^2.5.19",
|
||||
"postcss": "^8.4.12",
|
||||
"prettier": "^2.6.2",
|
||||
"sass": "^1.50.0",
|
||||
"tailwindcss": "^3.0.23",
|
||||
"typescript": "<4.6.0"
|
||||
"autoprefixer": "^10.4.8",
|
||||
"csstype": "^3.1.0",
|
||||
"eslint-config-next": "^12.2.5",
|
||||
"eslint-plugin-tailwindcss": "^3.6.0",
|
||||
"next-sitemap": "^3.1.21",
|
||||
"postcss": "^8.4.16",
|
||||
"sass": "^1.54.5",
|
||||
"tailwindcss": "^3.1.8",
|
||||
"typescript": "^4.7.4"
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ const Preview: NextPage<Props> = ({ username, slug, resume: initialData }) => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
const resume = useAppSelector((state) => state.resume.present);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialData && !isEmpty(initialData)) {
|
||||
@ -59,6 +59,14 @@ const Preview: NextPage<Props> = ({ username, slug, resume: initialData }) => {
|
||||
}
|
||||
}, [dispatch, initialData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEmpty(resume) && router.locale !== resume.metadata.locale) {
|
||||
const { pathname, asPath, query } = router;
|
||||
|
||||
router.push({ pathname, query }, asPath, { locale: resume.metadata.locale });
|
||||
}
|
||||
}, [resume, router]);
|
||||
|
||||
useQuery<Resume>(`resume/${username}/${slug}`, () => fetchResumeByIdentifier({ username, slug }), {
|
||||
initialData,
|
||||
retry: false,
|
||||
|
||||
@ -3,7 +3,6 @@ import clsx from 'clsx';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { GetServerSideProps, NextPage } from 'next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
@ -55,22 +54,14 @@ export const getServerSideProps: GetServerSideProps<Props | Promise<Props>, Quer
|
||||
};
|
||||
|
||||
const Printer: NextPage<Props> = ({ resume: initialData, locale }) => {
|
||||
const router = useRouter();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
const resume = useAppSelector((state) => state.resume.present);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialData) dispatch(setResume(initialData));
|
||||
}, [dispatch, initialData]);
|
||||
|
||||
useEffect(() => {
|
||||
const { pathname, asPath, query } = router;
|
||||
|
||||
router.push({ pathname, query }, asPath, { locale });
|
||||
}, [router, locale]);
|
||||
|
||||
if (!resume || isEmpty(resume)) return null;
|
||||
|
||||
const layout: string[][][] = get(resume, 'metadata.layout', []);
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import '@/styles/globals.scss';
|
||||
|
||||
import DateAdapter from '@mui/lab/AdapterDayjs';
|
||||
import LocalizationProvider from '@mui/lab/LocalizationProvider';
|
||||
import env from '@beam-australia/react-env';
|
||||
import DayjsAdapter from '@date-io/dayjs';
|
||||
import { LocalizationProvider } from '@mui/x-date-pickers';
|
||||
import { GoogleOAuthProvider } from '@react-oauth/google';
|
||||
import type { AppProps } from 'next/app';
|
||||
import Head from 'next/head';
|
||||
import { appWithTranslation } from 'next-i18next';
|
||||
@ -31,24 +33,26 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
|
||||
</Head>
|
||||
|
||||
<ReduxProvider store={store}>
|
||||
<LocalizationProvider dateAdapter={DateAdapter}>
|
||||
<LocalizationProvider dateAdapter={DayjsAdapter}>
|
||||
<PersistGate loading={null} persistor={persistor}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<WrapperRegistry>
|
||||
<Loading />
|
||||
<GoogleOAuthProvider clientId={env('GOOGLE_CLIENT_ID')}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<WrapperRegistry>
|
||||
<Loading />
|
||||
|
||||
<Component {...pageProps} />
|
||||
<Component {...pageProps} />
|
||||
|
||||
<ModalWrapper />
|
||||
<Toaster
|
||||
position="bottom-right"
|
||||
toastOptions={{
|
||||
duration: 4000,
|
||||
className: 'toast',
|
||||
}}
|
||||
/>
|
||||
</WrapperRegistry>
|
||||
</QueryClientProvider>
|
||||
<ModalWrapper />
|
||||
<Toaster
|
||||
position="bottom-right"
|
||||
toastOptions={{
|
||||
duration: 4000,
|
||||
className: 'toast',
|
||||
}}
|
||||
/>
|
||||
</WrapperRegistry>
|
||||
</QueryClientProvider>
|
||||
</GoogleOAuthProvider>
|
||||
</PersistGate>
|
||||
</LocalizationProvider>
|
||||
</ReduxProvider>
|
||||
|
||||
@ -4,7 +4,9 @@ import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||
import { useEffect } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { ActionCreators } from 'redux-undo';
|
||||
|
||||
import ResumeCard from '@/components/dashboard/ResumeCard';
|
||||
import ResumePreview from '@/components/dashboard/ResumePreview';
|
||||
@ -12,6 +14,7 @@ import Avatar from '@/components/shared/Avatar';
|
||||
import Logo from '@/components/shared/Logo';
|
||||
import { RESUMES_QUERY } from '@/constants/index';
|
||||
import { fetchResumes } from '@/services/resume';
|
||||
import { useAppDispatch } from '@/store/hooks';
|
||||
import styles from '@/styles/pages/Dashboard.module.scss';
|
||||
|
||||
export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => {
|
||||
@ -25,8 +28,14 @@ export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => {
|
||||
const Dashboard: NextPage = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { data } = useQuery(RESUMES_QUERY, fetchResumes);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(ActionCreators.clearHistory());
|
||||
}, []);
|
||||
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { DarkMode, LightMode, Link as LinkIcon } from '@mui/icons-material';
|
||||
import { Masonry } from '@mui/lab';
|
||||
import { Button, IconButton } from '@mui/material';
|
||||
import { Button, IconButton, NoSsr } from '@mui/material';
|
||||
import type { GetStaticProps, NextPage } from 'next';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
@ -11,7 +11,6 @@ import Testimony from '@/components/landing/Testimony';
|
||||
import Footer from '@/components/shared/Footer';
|
||||
import LanguageSwitcher from '@/components/shared/LanguageSwitcher';
|
||||
import Logo from '@/components/shared/Logo';
|
||||
import NoSSR from '@/components/shared/NoSSR';
|
||||
import { screenshots } from '@/config/screenshots';
|
||||
import { FLAG_DISABLE_SIGNUPS } from '@/constants/flags';
|
||||
import testimonials from '@/data/testimonials';
|
||||
@ -59,7 +58,7 @@ const Home: NextPage = () => {
|
||||
|
||||
<h2>{t<string>('common.subtitle')}</h2>
|
||||
|
||||
<NoSSR>
|
||||
<NoSsr>
|
||||
<div className={styles.buttonWrapper}>
|
||||
{isLoggedIn ? (
|
||||
<>
|
||||
@ -81,7 +80,7 @@ const Home: NextPage = () => {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</NoSSR>
|
||||
</NoSsr>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { NextPage } from 'next';
|
||||
|
||||
const PrivacyPolicy: NextPage = () => (
|
||||
<div className="mx-auto my-12 prose dark:prose-invert">
|
||||
<div className="prose mx-auto my-12 dark:prose-invert">
|
||||
<h1>Privacy Policy</h1>
|
||||
|
||||
<p>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { NextPage } from 'next';
|
||||
|
||||
const TermsOfService: NextPage = () => (
|
||||
<div className="mx-auto my-12 prose dark:prose-invert">
|
||||
<div className="prose mx-auto my-12 dark:prose-invert">
|
||||
<h1>Terms of Service</h1>
|
||||
|
||||
<p>
|
||||
|
||||
@ -7,6 +7,7 @@ import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { GetServerSideProps, NextPage } from 'next';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||
import { useEffect } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
@ -35,6 +36,8 @@ export const getServerSideProps: GetServerSideProps<Props> = async ({ query, loc
|
||||
};
|
||||
|
||||
const Preview: NextPage<Props> = ({ shortId }) => {
|
||||
const router = useRouter();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { data: resume } = useQuery<Resume>(`resume/${shortId}`, () => fetchResumeByShortId({ shortId }), {
|
||||
@ -52,6 +55,14 @@ const Preview: NextPage<Props> = ({ shortId }) => {
|
||||
if (resume) dispatch(setResume(resume));
|
||||
}, [resume, dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (resume && !isEmpty(resume) && router.locale !== resume.metadata.locale) {
|
||||
const { pathname, asPath, query } = router;
|
||||
|
||||
router.push({ pathname, query }, asPath, { locale: resume.metadata.locale });
|
||||
}
|
||||
}, [resume, router]);
|
||||
|
||||
if (!resume || isEmpty(resume)) return null;
|
||||
|
||||
const layout: string[][][] = get(resume, 'metadata.layout', []);
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"common": {
|
||||
"actions": {
|
||||
"add": "إضافة {{فقرة}} جديدة",
|
||||
"delete": "حذة {{الفقرة}}",
|
||||
"edit": "تحرير {{الفقرة}}"
|
||||
"add": "إضافة {{token}} جديدة",
|
||||
"delete": "حذف {{token}}",
|
||||
"edit": "تحرير {{token}}"
|
||||
},
|
||||
"columns": {
|
||||
"heading": "الأعمدة",
|
||||
@ -24,13 +24,13 @@
|
||||
"label": "تاريخ الانتهاء"
|
||||
},
|
||||
"keywords": {
|
||||
"label": "الكلمات الرئيسية"
|
||||
"label": "الكلمات الدالة"
|
||||
},
|
||||
"level": {
|
||||
"label": "مستوى"
|
||||
},
|
||||
"levelNum": {
|
||||
"label": "المستوى (Number)"
|
||||
"label": "المستوى (العدد)"
|
||||
},
|
||||
"name": {
|
||||
"label": "الاسم"
|
||||
@ -42,7 +42,7 @@
|
||||
"label": "المنصب"
|
||||
},
|
||||
"start-date": {
|
||||
"label": "تاريخ البداية"
|
||||
"label": "تاريخ البدء"
|
||||
},
|
||||
"subtitle": {
|
||||
"label": "العنوان الفرعي"
|
||||
@ -119,6 +119,9 @@
|
||||
"name": {
|
||||
"label": "الاسم الكامل"
|
||||
},
|
||||
"birthdate": {
|
||||
"label": "تاريخ الميلاد"
|
||||
},
|
||||
"photo-filters": {
|
||||
"effects": {
|
||||
"border": {
|
||||
|
||||
@ -15,8 +15,7 @@
|
||||
},
|
||||
"login": {
|
||||
"actions": {
|
||||
"login": "تسجيل الدخول",
|
||||
"google": "تسجيل الدخول باستخدام حساب جوجل"
|
||||
"login": "تسجيل الدخول"
|
||||
},
|
||||
"body": "يرجى إدخال اسم المستخدم وكلمة المرور المرتبطين بحسابك لتسجيل الدخول والوصول إلى السير الذاتية وإدارتها ومشاركتها.",
|
||||
"form": {
|
||||
|
||||
361
client/public/locales/bg/builder.json
Normal file
361
client/public/locales/bg/builder.json
Normal file
@ -0,0 +1,361 @@
|
||||
{
|
||||
"common": {
|
||||
"actions": {
|
||||
"add": "Добави нов {{token}}",
|
||||
"delete": "Изтрий {{token}}",
|
||||
"edit": "Редакция на {{token}}"
|
||||
},
|
||||
"columns": {
|
||||
"heading": "Колони",
|
||||
"tooltip": "Променете броя на колоните"
|
||||
},
|
||||
"form": {
|
||||
"date": {
|
||||
"label": "Дата"
|
||||
},
|
||||
"description": {
|
||||
"label": "Описание"
|
||||
},
|
||||
"email": {
|
||||
"label": "Имейл адрес"
|
||||
},
|
||||
"end-date": {
|
||||
"help-text": "Оставете това поле празно, ако все още е налице",
|
||||
"label": "Крайна Дата"
|
||||
},
|
||||
"keywords": {
|
||||
"label": "Ключови думи"
|
||||
},
|
||||
"level": {
|
||||
"label": "Ниво"
|
||||
},
|
||||
"levelNum": {
|
||||
"label": "Ниво (номер)"
|
||||
},
|
||||
"name": {
|
||||
"label": "Име"
|
||||
},
|
||||
"phone": {
|
||||
"label": "Телефон"
|
||||
},
|
||||
"position": {
|
||||
"label": "Длъжност"
|
||||
},
|
||||
"start-date": {
|
||||
"label": "Начална дата"
|
||||
},
|
||||
"subtitle": {
|
||||
"label": "Подзаглавие"
|
||||
},
|
||||
"summary": {
|
||||
"label": "Резюме"
|
||||
},
|
||||
"title": {
|
||||
"label": "Заглавие"
|
||||
},
|
||||
"url": {
|
||||
"label": "Уебсайт"
|
||||
}
|
||||
},
|
||||
"glossary": {
|
||||
"page": "Страница"
|
||||
},
|
||||
"list": {
|
||||
"actions": {
|
||||
"delete": "Изтрии",
|
||||
"duplicate": "Дубликирай",
|
||||
"edit": "Редактирай"
|
||||
},
|
||||
"empty-text": "Този списък е празен."
|
||||
},
|
||||
"tooltip": {
|
||||
"delete-item": "Наистина ли искате да изтриете този запис? Това действие не може да бъде отменено.",
|
||||
"delete-section": "Изтриване на раздел",
|
||||
"rename-section": "Преименуване на раздел",
|
||||
"toggle-visibility": "Видим/Невидим"
|
||||
}
|
||||
},
|
||||
"controller": {
|
||||
"tooltip": {
|
||||
"center-artboard": "Централна табла",
|
||||
"copy-link": "Копирай линка в резюмето",
|
||||
"export-pdf": "Експорт в PDF",
|
||||
"toggle-orientation": "Превключване на ориентацията на страницата",
|
||||
"toggle-page-break-line": "Линия за прекъсване на страницата",
|
||||
"toggle-sidebars": "Включване на страничната лента",
|
||||
"zoom-in": "Увеличи",
|
||||
"zoom-out": "Намали"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"menu": {
|
||||
"delete": "Изтрии",
|
||||
"duplicate": "Дубликирай",
|
||||
"rename": "Преименувай",
|
||||
"share-link": "Споделяне на връзка",
|
||||
"tooltips": {
|
||||
"delete": "Наистина ли искате да изтриете това CV? Това действие не може да бъде отменено.",
|
||||
"share-link": "Трябва да промените видимостта на CV-то си на публична, за да я направите видима за другите."
|
||||
}
|
||||
}
|
||||
},
|
||||
"leftSidebar": {
|
||||
"sections": {
|
||||
"awards": {
|
||||
"form": {
|
||||
"awarder": {
|
||||
"label": "Награждаващ"
|
||||
}
|
||||
}
|
||||
},
|
||||
"basics": {
|
||||
"actions": {
|
||||
"photo-filters": "Филтри за снимата"
|
||||
},
|
||||
"heading": "Основни",
|
||||
"headline": {
|
||||
"label": "Заглавие"
|
||||
},
|
||||
"name": {
|
||||
"label": "Пълно име"
|
||||
},
|
||||
"birthdate": {
|
||||
"label": "Дата на раждане"
|
||||
},
|
||||
"photo-filters": {
|
||||
"effects": {
|
||||
"border": {
|
||||
"label": "Рамка"
|
||||
},
|
||||
"grayscale": {
|
||||
"label": "Нива на сивото"
|
||||
},
|
||||
"heading": "Ефекти"
|
||||
},
|
||||
"shape": {
|
||||
"heading": "Форма"
|
||||
},
|
||||
"size": {
|
||||
"heading": "Размер (в px)"
|
||||
}
|
||||
},
|
||||
"photo-upload": {
|
||||
"tooltip": {
|
||||
"remove": "Премахване на снимка",
|
||||
"upload": "Качи снимка"
|
||||
}
|
||||
}
|
||||
},
|
||||
"certifications": {
|
||||
"form": {
|
||||
"issuer": {
|
||||
"label": "Издател"
|
||||
}
|
||||
}
|
||||
},
|
||||
"education": {
|
||||
"form": {
|
||||
"area-study": {
|
||||
"label": "Специалност"
|
||||
},
|
||||
"courses": {
|
||||
"label": "Курсове"
|
||||
},
|
||||
"degree": {
|
||||
"label": "Степен"
|
||||
},
|
||||
"grade": {
|
||||
"label": "Клас"
|
||||
},
|
||||
"institution": {
|
||||
"label": "Институция"
|
||||
}
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"address": {
|
||||
"label": "Адрес"
|
||||
},
|
||||
"city": {
|
||||
"label": "Град"
|
||||
},
|
||||
"country": {
|
||||
"label": "Държава"
|
||||
},
|
||||
"heading": "Местоположение",
|
||||
"postal-code": {
|
||||
"label": "Пощенски код"
|
||||
},
|
||||
"region": {
|
||||
"label": "Регион"
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"form": {
|
||||
"network": {
|
||||
"label": "Социална мрежа"
|
||||
},
|
||||
"username": {
|
||||
"label": "Потребителско име"
|
||||
}
|
||||
},
|
||||
"heading": "Профили",
|
||||
"heading_one": "Профил"
|
||||
},
|
||||
"publications": {
|
||||
"form": {
|
||||
"publisher": {
|
||||
"label": "Издател"
|
||||
}
|
||||
}
|
||||
},
|
||||
"references": {
|
||||
"form": {
|
||||
"relationship": {
|
||||
"label": "Връзка"
|
||||
}
|
||||
}
|
||||
},
|
||||
"section": {
|
||||
"heading": "Раздел"
|
||||
},
|
||||
"volunteer": {
|
||||
"form": {
|
||||
"organization": {
|
||||
"label": "Организация"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"rightSidebar": {
|
||||
"sections": {
|
||||
"css": {
|
||||
"heading": "Персонализиран CSS"
|
||||
},
|
||||
"export": {
|
||||
"heading": "Експортиране",
|
||||
"json": {
|
||||
"primary": "JSON",
|
||||
"secondary": "Изтеглете JSON версия на вашата автобиография, която може да бъде импортирана обратно в Reactive Resume."
|
||||
},
|
||||
"pdf": {
|
||||
"loading": {
|
||||
"primary": "Генериране на PDF",
|
||||
"secondary": "Моля, изчакайте, докато вашият PDF се генерира, това може да отнеме до 15 секунди."
|
||||
},
|
||||
"normal": {
|
||||
"primary": "РDF",
|
||||
"secondary": "Изтеглете PDF файл на вашата автобиография, който можете да отпечатате и изпратите до мечтаната работа. Този файл не може да бъде импортиран обратно за по-нататъшно редактиране."
|
||||
}
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
"heading": "Оформление",
|
||||
"tooltip": {
|
||||
"reset-layout": "Рестартирай оформлението"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"bugs-features": {
|
||||
"body": "Нещо ви пречи да си направите автобиография? Или имате невероятна идея, която да добавите? Повдигнете въпрос в GitHub, за да започнете.",
|
||||
"button": "GitHub общност",
|
||||
"heading": "Бъгове? Искания за функции?"
|
||||
},
|
||||
"donate": {
|
||||
"body": "Ако ви е харесало да използвате Reactive Resume, моля, помислете дали да не дарите колкото можете повече за поддръжка на приложението, без реклами и безплатно завинаги.",
|
||||
"button": "Почерпете ме с кафе",
|
||||
"heading": "Направи дарение и подкрепи Reactive Resume"
|
||||
},
|
||||
"github": "Програмен код",
|
||||
"heading": "Връзки"
|
||||
},
|
||||
"settings": {
|
||||
"global": {
|
||||
"date": {
|
||||
"primary": "Дата",
|
||||
"secondary": "Формат на датата, който да се използва в приложението"
|
||||
},
|
||||
"heading": "Глобално",
|
||||
"language": {
|
||||
"primary": "Език",
|
||||
"secondary": "Език за показване, който да се използва в приложението"
|
||||
},
|
||||
"theme": {
|
||||
"primary": "Тема"
|
||||
}
|
||||
},
|
||||
"heading": "Настройки",
|
||||
"page": {
|
||||
"break-line": {
|
||||
"primary": "Линия на прекъсване",
|
||||
"secondary": "Показване на линия на всички страници за обозначаване на височината на страница A4"
|
||||
},
|
||||
"heading": "Страница",
|
||||
"orientation": {
|
||||
"disabled": "Няма ефект, когато има само една страница",
|
||||
"primary": "Ориентация",
|
||||
"secondary": "Дали страниците да се показват хоризонтално или вертикално"
|
||||
}
|
||||
},
|
||||
"resume": {
|
||||
"heading": "Възобновяване",
|
||||
"reset": {
|
||||
"primary": "Нулирайте всичко",
|
||||
"secondary": "Направихте твърде много грешки? Щракнете тук, за да нулирате всички промени и да започнете от нулата. Внимавайте, това действие не може да бъде отменено."
|
||||
},
|
||||
"sample": {
|
||||
"primary": "Зареждане на примерни данни",
|
||||
"secondary": "Не сте сигурни откъде да започнете? Щракнете тук, за да заредите някои примерни данни и да видите как изглежда една пълна автобиография."
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"heading": "Споделяне",
|
||||
"short-url": {
|
||||
"label": "Предпочитам кратък URL адрес"
|
||||
},
|
||||
"visibility": {
|
||||
"subtitle": "Позволете на всеки с връзка да види това CV",
|
||||
"title": "Публичен"
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"heading": "Шаблони"
|
||||
},
|
||||
"theme": {
|
||||
"form": {
|
||||
"background": {
|
||||
"label": "Фон"
|
||||
},
|
||||
"primary": {
|
||||
"label": "Основен"
|
||||
},
|
||||
"text": {
|
||||
"label": "Текст"
|
||||
}
|
||||
},
|
||||
"heading": "Тема"
|
||||
},
|
||||
"typography": {
|
||||
"form": {
|
||||
"font-family": {
|
||||
"label": "Шрифтово семейство"
|
||||
},
|
||||
"font-size": {
|
||||
"label": "Размер на шрифта"
|
||||
}
|
||||
},
|
||||
"heading": "Типография",
|
||||
"widgets": {
|
||||
"body": {
|
||||
"label": "Тяло"
|
||||
},
|
||||
"headings": {
|
||||
"label": "Заглавия"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
client/public/locales/bg/common.json
Normal file
29
client/public/locales/bg/common.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"avatar": {
|
||||
"menu": {
|
||||
"greeting": "Здравейте",
|
||||
"logout": "Изход"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"credit": "Проект от <1>Амрут Пилай</1>",
|
||||
"license": "От общността, за общността."
|
||||
},
|
||||
"markdown": {
|
||||
"help-text": "Този раздел поддържа <1>markdown</1> форматиране."
|
||||
},
|
||||
"date": {
|
||||
"present": "Настояще"
|
||||
},
|
||||
"subtitle": "Безплатен инструмент за създаване на автобиография с отворен код.",
|
||||
"title": "Reactive Resume",
|
||||
"toast": {
|
||||
"error": {
|
||||
"upload-file-size": "Моля, качвайте само файлове с размер под 2 мегабайта.",
|
||||
"upload-photo-size": "Моля, качвайте само снимки под 2 мегабайта, за предпочитане квадратни."
|
||||
},
|
||||
"success": {
|
||||
"resume-link-copied": "Връзката към автобиографията ви е копирана в клипборда."
|
||||
}
|
||||
}
|
||||
}
|
||||
25
client/public/locales/bg/dashboard.json
Normal file
25
client/public/locales/bg/dashboard.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"create-resume": {
|
||||
"subtitle": "Започване отначало",
|
||||
"title": "Създаване на ново CV"
|
||||
},
|
||||
"import-external": {
|
||||
"subtitle": "LinkedIn, JSON резюме, Reactive Resume",
|
||||
"title": "Импортиране от външни източници"
|
||||
},
|
||||
"resume": {
|
||||
"menu": {
|
||||
"delete": "Изтрии",
|
||||
"duplicate": "Дубликирай",
|
||||
"open": "Отвори",
|
||||
"rename": "Преименувай",
|
||||
"share-link": "Споделяне на връзка",
|
||||
"tooltips": {
|
||||
"delete": "Наистина ли искате да изтриете това CV? Това действие не може да бъде отменено.",
|
||||
"share-link": "Трябва да промените видимостта на CV-то си на публична, за да я направите видима за другите."
|
||||
}
|
||||
},
|
||||
"timestamp": "Последната промяна е преди {{timestamp}}"
|
||||
},
|
||||
"title": "Контролен панел"
|
||||
}
|
||||
41
client/public/locales/bg/landing.json
Normal file
41
client/public/locales/bg/landing.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"actions": {
|
||||
"app": "Към приложението",
|
||||
"login": "Вход",
|
||||
"logout": "Изход",
|
||||
"register": "Регистрация"
|
||||
},
|
||||
"features": {
|
||||
"heading": "Функции",
|
||||
"list": {
|
||||
"ads": "Без реклами.",
|
||||
"export": "Експортирайте автобиографията си в JSON или PDF формат",
|
||||
"free": "Безплатно завинаги",
|
||||
"import": "Импортиране на данни от LinkedIn, JSON резюме",
|
||||
"languages": "Достъпно на множество езици",
|
||||
"more": "И още много вълнуващи функции, <1>прочетете всичко за тях тук</1>",
|
||||
"tracking": "Без проследяване на потребители"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"heading": "Връзки",
|
||||
"links": {
|
||||
"donate": "Дарение",
|
||||
"github": "Програмен код",
|
||||
"privacy": "Политика за поверителност",
|
||||
"service": "Условия на ползване"
|
||||
}
|
||||
},
|
||||
"screenshots": {
|
||||
"heading": "Екранни снимки"
|
||||
},
|
||||
"testimonials": {
|
||||
"heading": "Препоръки",
|
||||
"body": "Позитивно или негативно, ще се радвам да чуя мнението ви за Reactive Resume и какъв е вашия опитът.<br/>Ето някои от съобщенията, изпратени от потребители по целия свят.",
|
||||
"contact": "Можете да се свържете с мен чрез <1>моя имейл</1> или чрез формата за контакт на <3>моя уебсайт</3> ."
|
||||
},
|
||||
"summary": {
|
||||
"body": "Reactive Resume е безплатен инструмент за създаване на автобиография/CV с отворен код, който е създаден, за да улесни обикновените задачи за създаване, актуализиране и споделяне на вашата автобиография като 1, 2, 3. С това приложение можете да създавате множество автобиографии, да ги споделяте директно със специалистите по подбор на персонал или приятели чрез уникална връзка, както и ги отпечатате като PDF. Всичко е безплатно, без реклами, без проследяване, без да губите целостта и поверителността на вашите данни.",
|
||||
"heading": "Обобщение"
|
||||
}
|
||||
}
|
||||
135
client/public/locales/bg/modals.json
Normal file
135
client/public/locales/bg/modals.json
Normal file
@ -0,0 +1,135 @@
|
||||
{
|
||||
"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": "Регистрация с Google"
|
||||
},
|
||||
"body": "Моля, въведете вашата лична информация, за да създадете акаунт.",
|
||||
"form": {
|
||||
"confirm-password": {
|
||||
"label": "Потвърждение на парола"
|
||||
},
|
||||
"email": {
|
||||
"label": "Имейл адрес"
|
||||
},
|
||||
"name": {
|
||||
"label": "Пълно име"
|
||||
},
|
||||
"password": {
|
||||
"label": "Парола"
|
||||
},
|
||||
"username": {
|
||||
"label": "Потребителско име"
|
||||
}
|
||||
},
|
||||
"heading": "Създаване на профил",
|
||||
"loginText": "Ако вече имате акаунт, можете <1>да влезете от тук</1> ."
|
||||
},
|
||||
"reset-password": {
|
||||
"actions": {
|
||||
"set-password": "Задайте нова парола"
|
||||
},
|
||||
"body": "Въведете нова парола за вашия акаунт.",
|
||||
"form": {
|
||||
"confirm-password": {
|
||||
"label": "Потвърждение на парола"
|
||||
},
|
||||
"password": {
|
||||
"label": "Парола"
|
||||
}
|
||||
},
|
||||
"heading": "Нулиране на паролата"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"create-resume": {
|
||||
"actions": {
|
||||
"create-resume": "Създай CV/Резюме"
|
||||
},
|
||||
"body": "Започнете да създавате автобиографията си, като й дадете име. Може да е във връзка с позицията, за която кандидатствате, или просто любимата ви закуска.",
|
||||
"form": {
|
||||
"name": {
|
||||
"label": "Име"
|
||||
},
|
||||
"public": {
|
||||
"label": "Публично достъпна ли е?"
|
||||
},
|
||||
"slug": {
|
||||
"label": "Слъг"
|
||||
}
|
||||
},
|
||||
"heading": "Създаване на ново CV"
|
||||
},
|
||||
"import-external": {
|
||||
"heading": "Импортиране от външни източници",
|
||||
"json-resume": {
|
||||
"actions": {
|
||||
"upload-json": "Качване на JSON"
|
||||
},
|
||||
"body": "Ако имате готова <1>валидирана JSON автобиография</1>, можете да я използвате, за да стартирате бързо в Reactive Resume. Щракнете върху бутона по-долу и качете валиден JSON файл, за да започнете.",
|
||||
"heading": "Качване на JSON Resume"
|
||||
},
|
||||
"linkedin": {
|
||||
"actions": {
|
||||
"upload-archive": "Качете ZIP архив"
|
||||
},
|
||||
"body": "Можете да спестите време, като експортирате данните си от LinkedIn и ги използвате за автоматично попълване на полета в Reactive Resume. Отидете в раздел <1>Поверителност на данните</1> в LinkedIn и поискайте архив на вашите данни. След като е наличен, качете ZIP файла по-долу.",
|
||||
"heading": "Импортиране от LinkedIn"
|
||||
},
|
||||
"reactive-resume": {
|
||||
"actions": {
|
||||
"upload-json": "Качване на JSON",
|
||||
"upload-json-v2": "Качете JSON от v2"
|
||||
},
|
||||
"body": "Ако имате JSON, който е бил експортиран с текущата версия на Reactive Resume, можете да го импортирате обратно тук, за да получите отново редактируема версия.",
|
||||
"heading": "Импортиране от Reactive Resume"
|
||||
}
|
||||
},
|
||||
"rename-resume": {
|
||||
"actions": {
|
||||
"rename-resume": "Преименуване на резюмето"
|
||||
},
|
||||
"form": {
|
||||
"name": {
|
||||
"label": "Име"
|
||||
},
|
||||
"slug": {
|
||||
"label": "Слъг"
|
||||
}
|
||||
},
|
||||
"heading": "Преименувайте автобиографията си"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -119,6 +119,9 @@
|
||||
"name": {
|
||||
"label": "পূর্ণ নাম"
|
||||
},
|
||||
"birthdate": {
|
||||
"label": "জন্ম তারিখ"
|
||||
},
|
||||
"photo-filters": {
|
||||
"effects": {
|
||||
"border": {
|
||||
|
||||
@ -15,8 +15,7 @@
|
||||
},
|
||||
"login": {
|
||||
"actions": {
|
||||
"login": "লগইন",
|
||||
"google": "গুগল দিয়ে লগইন করুন"
|
||||
"login": "লগইন"
|
||||
},
|
||||
"body": "লগইন এবং অ্যাক্সেস, পরিচালনা এবং আপনার জীবনবৃত্তান্ত শেয়ার করতে আপনার অ্যাকাউন্টের সাথে যুক্ত আপনার ব্যবহারকারীর নাম এবং পাসওয়ার্ড লিখুন।",
|
||||
"form": {
|
||||
|
||||
@ -119,6 +119,9 @@
|
||||
"name": {
|
||||
"label": "Celé jméno"
|
||||
},
|
||||
"birthdate": {
|
||||
"label": "Datum narození"
|
||||
},
|
||||
"photo-filters": {
|
||||
"effects": {
|
||||
"border": {
|
||||
|
||||
29
client/public/locales/cs/common.json
Normal file
29
client/public/locales/cs/common.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"avatar": {
|
||||
"menu": {
|
||||
"greeting": "Dobrý den",
|
||||
"logout": "Odhlásit se"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"credit": "Vášnivý projekt <1>Amrutha Pillaie</1>",
|
||||
"license": "Od komunity, pro komunitu."
|
||||
},
|
||||
"markdown": {
|
||||
"help-text": "Tato sekce podporuje <1>markdown</1> formátování."
|
||||
},
|
||||
"date": {
|
||||
"present": "Současnost"
|
||||
},
|
||||
"subtitle": "Bezplatný a open source tvůrce životopisů.",
|
||||
"title": "Reactive Resume",
|
||||
"toast": {
|
||||
"error": {
|
||||
"upload-file-size": "Prosím nahrajte pouze soubory pod 2 megabajty.",
|
||||
"upload-photo-size": "Nahrávejte prosím pouze fotografie o velikosti do 2 megabajtů, nejlépe čtvercové."
|
||||
},
|
||||
"success": {
|
||||
"resume-link-copied": "Odkaz na váš životopis byl zkopírován do schránky."
|
||||
}
|
||||
}
|
||||
}
|
||||
25
client/public/locales/cs/dashboard.json
Normal file
25
client/public/locales/cs/dashboard.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"create-resume": {
|
||||
"subtitle": "Začít od začátku",
|
||||
"title": "Vytvořit nový životopis"
|
||||
},
|
||||
"import-external": {
|
||||
"subtitle": "LinkedIn, JSON Resume, Reactive Resume",
|
||||
"title": "Importovat z externích zdrojů"
|
||||
},
|
||||
"resume": {
|
||||
"menu": {
|
||||
"delete": "Smazat",
|
||||
"duplicate": "Duplikovat",
|
||||
"open": "Otevřít",
|
||||
"rename": "Přejmenovat",
|
||||
"share-link": "Sdílet odkaz",
|
||||
"tooltips": {
|
||||
"delete": "Opravdu chcete smazat tento životopis? Toto je nevratná akce.",
|
||||
"share-link": "Musíte změnit viditelnost svého životopisu na veřejnou, aby byl viditelný pro ostatní."
|
||||
}
|
||||
},
|
||||
"timestamp": "Naposledy aktualizováno před {{timestamp}}"
|
||||
},
|
||||
"title": "Přístrojová deska"
|
||||
}
|
||||
41
client/public/locales/cs/landing.json
Normal file
41
client/public/locales/cs/landing.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"actions": {
|
||||
"app": "Přejděte do aplikace",
|
||||
"login": "Přihlásit se",
|
||||
"logout": "Odhlásit se",
|
||||
"register": "Registrovat"
|
||||
},
|
||||
"features": {
|
||||
"heading": "Funkce",
|
||||
"list": {
|
||||
"ads": "Žádná reklama",
|
||||
"export": "Exportujte svůj životopis do formátu JSON nebo PDF",
|
||||
"free": "Zdarma, navždy",
|
||||
"import": "Import dat z LinkedIn, JSON Resume",
|
||||
"languages": "Dostupné ve více jazycích",
|
||||
"more": "A mnohem více vzrušujících funkcí, <1>o tom si vše přečtěte zde</1>",
|
||||
"tracking": "Žádné sledování uživatelů"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"heading": "Odkazy",
|
||||
"links": {
|
||||
"donate": "Darovat",
|
||||
"github": "Zdrojový kód",
|
||||
"privacy": "Zásady ochrany osobních údajů",
|
||||
"service": "Podmínky služby"
|
||||
}
|
||||
},
|
||||
"screenshots": {
|
||||
"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": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
135
client/public/locales/cs/modals.json
Normal file
135
client/public/locales/cs/modals.json
Normal file
@ -0,0 +1,135 @@
|
||||
{
|
||||
"auth": {
|
||||
"forgot-password": {
|
||||
"actions": {
|
||||
"send-email": "Odeslat e-mail pro obnovení hesla"
|
||||
},
|
||||
"body": "Stačí zadat e-mailovou adresu spojenou s účtem, který chcete obnovit.",
|
||||
"form": {
|
||||
"email": {
|
||||
"label": "Emailová adresa"
|
||||
}
|
||||
},
|
||||
"heading": "Zapomněli jste heslo?",
|
||||
"help-text": "Pokud účet existuje, obdržíte e-mail s odkazem na resetování hesla."
|
||||
},
|
||||
"login": {
|
||||
"actions": {
|
||||
"login": "Přihlásit se"
|
||||
},
|
||||
"body": "Zadejte prosím své uživatelské jméno a heslo spojené s vaším účtem, abyste se mohli přihlásit a získat přístup, spravovat a sdílet své životopisy.",
|
||||
"form": {
|
||||
"password": {
|
||||
"label": "Heslo"
|
||||
},
|
||||
"username": {
|
||||
"help-text": "Můžete také zadat svou e-mailovou adresu",
|
||||
"label": "uživatelské jméno"
|
||||
}
|
||||
},
|
||||
"heading": "Přihlaste se ke svému účtu",
|
||||
"recover-text": "V případě, že jste zapomněli své heslo, můžete <1>obnovit svůj účet</1> tady.",
|
||||
"register-text": "Pokud jej nemáte, můžete si <1>vytvořit účet</1> tady."
|
||||
},
|
||||
"register": {
|
||||
"actions": {
|
||||
"register": "Registrovat",
|
||||
"google": "Zaregistrujte se u Google"
|
||||
},
|
||||
"body": "Chcete-li vytvořit účet, zadejte své osobní údaje.",
|
||||
"form": {
|
||||
"confirm-password": {
|
||||
"label": "Potvrďte heslo"
|
||||
},
|
||||
"email": {
|
||||
"label": "Emailová adresa"
|
||||
},
|
||||
"name": {
|
||||
"label": "Celé jméno"
|
||||
},
|
||||
"password": {
|
||||
"label": "Heslo"
|
||||
},
|
||||
"username": {
|
||||
"label": "uživatelské jméno"
|
||||
}
|
||||
},
|
||||
"heading": "Vytvořit účet",
|
||||
"loginText": "Pokud již máte účet, můžete se <1>přihlásit zde</1> ."
|
||||
},
|
||||
"reset-password": {
|
||||
"actions": {
|
||||
"set-password": "Nastavit nové heslo"
|
||||
},
|
||||
"body": "Zadejte nové heslo ke svému účtu.",
|
||||
"form": {
|
||||
"confirm-password": {
|
||||
"label": "Potvrďte heslo"
|
||||
},
|
||||
"password": {
|
||||
"label": "Heslo"
|
||||
}
|
||||
},
|
||||
"heading": "Obnovit heslo"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"create-resume": {
|
||||
"actions": {
|
||||
"create-resume": "Vytvořit životopis"
|
||||
},
|
||||
"body": "Začněte budovat svůj životopis tím, že mu dáte jméno. Může to být odkaz na roli, o kterou se ucházíte, nebo jen na vaši oblíbenou svačinu.",
|
||||
"form": {
|
||||
"name": {
|
||||
"label": "název"
|
||||
},
|
||||
"public": {
|
||||
"label": "Je veřejně přístupný?"
|
||||
},
|
||||
"slug": {
|
||||
"label": "Slimák"
|
||||
}
|
||||
},
|
||||
"heading": "Vytvořte nový životopis"
|
||||
},
|
||||
"import-external": {
|
||||
"heading": "Import z externích zdrojů",
|
||||
"json-resume": {
|
||||
"actions": {
|
||||
"upload-json": "Nahrajte JSON"
|
||||
},
|
||||
"body": "Pokud máte <1>ověřený životopis JSON</1> připraven k použití, můžete jej použít k rychlému sledování svého vývoje na Reactive Resume. Začněte kliknutím na tlačítko níže a nahráním platného souboru JSON.",
|
||||
"heading": "Import z obnovení JSON"
|
||||
},
|
||||
"linkedin": {
|
||||
"actions": {
|
||||
"upload-archive": "Nahrát archiv ZIP"
|
||||
},
|
||||
"body": "Můžete ušetřit čas tím, že exportujete svá data z LinkedIn a použijete je k automatickému vyplňování polí na Reactive Resume. Přejděte na <1>Ochrana osobních údajů</1> sekce na LinkedIn a vyžádejte si archiv vašich dat. Jakmile bude k dispozici, nahrajte níže uvedený soubor ZIP.",
|
||||
"heading": "Import z LinkedIn"
|
||||
},
|
||||
"reactive-resume": {
|
||||
"actions": {
|
||||
"upload-json": "Nahrajte JSON",
|
||||
"upload-json-v2": "Nahrajte JSON z v2"
|
||||
},
|
||||
"body": "Pokud máte JSON, který byl exportován s aktuální verzí Reactive Resume, můžete jej sem importovat zpět a znovu získat upravitelnou verzi.",
|
||||
"heading": "Import z reaktivního obnovení"
|
||||
}
|
||||
},
|
||||
"rename-resume": {
|
||||
"actions": {
|
||||
"rename-resume": "Přejmenovat Resume"
|
||||
},
|
||||
"form": {
|
||||
"name": {
|
||||
"label": "název"
|
||||
},
|
||||
"slug": {
|
||||
"label": "Slimák"
|
||||
}
|
||||
},
|
||||
"heading": "Přejmenujte svůj životopis"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -119,6 +119,9 @@
|
||||
"name": {
|
||||
"label": "Fulde navn"
|
||||
},
|
||||
"birthdate": {
|
||||
"label": "Fødselsdato"
|
||||
},
|
||||
"photo-filters": {
|
||||
"effects": {
|
||||
"border": {
|
||||
|
||||
@ -15,8 +15,7 @@
|
||||
},
|
||||
"login": {
|
||||
"actions": {
|
||||
"login": "Log ind",
|
||||
"google": "Log ind med Google"
|
||||
"login": "Log ind"
|
||||
},
|
||||
"body": "Indtast venligst dit brugernavn og din adgangskode knyttet til din konto for at logge ind og få adgang til, administrere og dele dine CV'er.",
|
||||
"form": {
|
||||
|
||||
@ -119,6 +119,9 @@
|
||||
"name": {
|
||||
"label": "Voller Name"
|
||||
},
|
||||
"birthdate": {
|
||||
"label": "Geburtsdatum"
|
||||
},
|
||||
"photo-filters": {
|
||||
"effects": {
|
||||
"border": {
|
||||
|
||||
@ -15,8 +15,7 @@
|
||||
},
|
||||
"login": {
|
||||
"actions": {
|
||||
"login": "Anmeldung",
|
||||
"google": "Mit Google anmelden"
|
||||
"login": "Anmeldung"
|
||||
},
|
||||
"body": "Bitte geben Sie Ihren Benutzernamen und Ihr Passwort ein, um sich anzumelden und zuzugreifen, Ihre Bewerbungen zu verwalten und weiterzugeben.",
|
||||
"form": {
|
||||
|
||||
361
client/public/locales/el/builder.json
Normal file
361
client/public/locales/el/builder.json
Normal file
@ -0,0 +1,361 @@
|
||||
{
|
||||
"common": {
|
||||
"actions": {
|
||||
"add": "Προσθήκη νέου {{token}}",
|
||||
"delete": "Διαγραφή {{token}}",
|
||||
"edit": "Επεξεργασία {{token}}"
|
||||
},
|
||||
"columns": {
|
||||
"heading": "Στήλες",
|
||||
"tooltip": "Αλλαγή αριθμού στηλών"
|
||||
},
|
||||
"form": {
|
||||
"date": {
|
||||
"label": "Ημερομηνία"
|
||||
},
|
||||
"description": {
|
||||
"label": "Περιγραφή"
|
||||
},
|
||||
"email": {
|
||||
"label": "Διεύθυνση Email"
|
||||
},
|
||||
"end-date": {
|
||||
"help-text": "Αφήστε αυτό το πεδίο κενό, εάν εξακολουθεί να υπάρχει",
|
||||
"label": "Ημερομηνία λήξης"
|
||||
},
|
||||
"keywords": {
|
||||
"label": "Λέξεις κλειδιά"
|
||||
},
|
||||
"level": {
|
||||
"label": "Επίπεδο"
|
||||
},
|
||||
"levelNum": {
|
||||
"label": "Επίπεδο (αριθμός)"
|
||||
},
|
||||
"name": {
|
||||
"label": "Όνομα"
|
||||
},
|
||||
"phone": {
|
||||
"label": "Αριθμός τηλεφώνου"
|
||||
},
|
||||
"position": {
|
||||
"label": "Θέση"
|
||||
},
|
||||
"start-date": {
|
||||
"label": "Ημερομηνία έναρξης"
|
||||
},
|
||||
"subtitle": {
|
||||
"label": "Υπότιτλος"
|
||||
},
|
||||
"summary": {
|
||||
"label": "Σύνοψη"
|
||||
},
|
||||
"title": {
|
||||
"label": "Τίτλος"
|
||||
},
|
||||
"url": {
|
||||
"label": "Ιστοσελίδα"
|
||||
}
|
||||
},
|
||||
"glossary": {
|
||||
"page": "Σελίδα"
|
||||
},
|
||||
"list": {
|
||||
"actions": {
|
||||
"delete": "Διαγραφή",
|
||||
"duplicate": "Διπλότυπο",
|
||||
"edit": "Επεξεργασία"
|
||||
},
|
||||
"empty-text": "Αυτή η λίστα είναι κενή."
|
||||
},
|
||||
"tooltip": {
|
||||
"delete-item": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το στοιχείο; Αυτή είναι μια μη αναστρέψιμη ενέργεια.",
|
||||
"delete-section": "Διαγραφή ενότητας",
|
||||
"rename-section": "Μετονομασία ενότητας",
|
||||
"toggle-visibility": "Εναλλαγή ορατότητας"
|
||||
}
|
||||
},
|
||||
"controller": {
|
||||
"tooltip": {
|
||||
"center-artboard": "Κεντράρισμα πίνακα",
|
||||
"copy-link": "Αντιγραφή συνδέσμου στο βιογραφικό",
|
||||
"export-pdf": "Εξαγωγή σε PDF",
|
||||
"toggle-orientation": "Εναλλαγή προσανατολισμού σελίδας",
|
||||
"toggle-page-break-line": "Εναλλαγή γραμμής αλλαγής σελίδας",
|
||||
"toggle-sidebars": "Εναλλαγή πλευρικών γραμμών",
|
||||
"zoom-in": "Μεγέθυνση",
|
||||
"zoom-out": "Σμίκρυνση"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"menu": {
|
||||
"delete": "Διαγραφή",
|
||||
"duplicate": "Διπλότυπο",
|
||||
"rename": "Μετονομασία",
|
||||
"share-link": "Κοινοποίηση συνδέσμου",
|
||||
"tooltips": {
|
||||
"delete": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το βιογραφικό; Αυτή είναι μια μη αναστρέψιμη ενέργεια.",
|
||||
"share-link": "Πρέπει να αλλάξετε την ορατότητα του βιογραφικού σας σε δημόσιο για να το κάνετε ορατό σε άλλους."
|
||||
}
|
||||
}
|
||||
},
|
||||
"leftSidebar": {
|
||||
"sections": {
|
||||
"awards": {
|
||||
"form": {
|
||||
"awarder": {
|
||||
"label": "Βραβευόμενος"
|
||||
}
|
||||
}
|
||||
},
|
||||
"basics": {
|
||||
"actions": {
|
||||
"photo-filters": "Φίλτρα φωτογραφιών"
|
||||
},
|
||||
"heading": "Βασικά",
|
||||
"headline": {
|
||||
"label": "Επικεφαλίδα"
|
||||
},
|
||||
"name": {
|
||||
"label": "Ονοματεπώνυμο"
|
||||
},
|
||||
"birthdate": {
|
||||
"label": "Ημερομηνια γεννησης"
|
||||
},
|
||||
"photo-filters": {
|
||||
"effects": {
|
||||
"border": {
|
||||
"label": "Περίγραμμα"
|
||||
},
|
||||
"grayscale": {
|
||||
"label": "Κλίμακα του γκρι"
|
||||
},
|
||||
"heading": "Εφέ"
|
||||
},
|
||||
"shape": {
|
||||
"heading": "Σχήμα"
|
||||
},
|
||||
"size": {
|
||||
"heading": "Μέγεθος (σε px)"
|
||||
}
|
||||
},
|
||||
"photo-upload": {
|
||||
"tooltip": {
|
||||
"remove": "Αφαίρεση φωτογραφίας",
|
||||
"upload": "Ανέβασμα φωτογραφίας"
|
||||
}
|
||||
}
|
||||
},
|
||||
"certifications": {
|
||||
"form": {
|
||||
"issuer": {
|
||||
"label": "Εκδότης"
|
||||
}
|
||||
}
|
||||
},
|
||||
"education": {
|
||||
"form": {
|
||||
"area-study": {
|
||||
"label": "Τομέας σπουδών"
|
||||
},
|
||||
"courses": {
|
||||
"label": "Σεμινάρια"
|
||||
},
|
||||
"degree": {
|
||||
"label": "Πτυχίο"
|
||||
},
|
||||
"grade": {
|
||||
"label": "Βαθμός"
|
||||
},
|
||||
"institution": {
|
||||
"label": "Ίδρυμα"
|
||||
}
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"address": {
|
||||
"label": "Διεύθυνση"
|
||||
},
|
||||
"city": {
|
||||
"label": "Πόλη"
|
||||
},
|
||||
"country": {
|
||||
"label": "Χώρα"
|
||||
},
|
||||
"heading": "Τοποθεσία",
|
||||
"postal-code": {
|
||||
"label": "Ταχυδρομικός κώδικας"
|
||||
},
|
||||
"region": {
|
||||
"label": "Περιοχή"
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"form": {
|
||||
"network": {
|
||||
"label": "Δίκτυο"
|
||||
},
|
||||
"username": {
|
||||
"label": "Όνομα χρήστη"
|
||||
}
|
||||
},
|
||||
"heading": "Προφίλ",
|
||||
"heading_one": "Προφίλ"
|
||||
},
|
||||
"publications": {
|
||||
"form": {
|
||||
"publisher": {
|
||||
"label": "Εκδότης"
|
||||
}
|
||||
}
|
||||
},
|
||||
"references": {
|
||||
"form": {
|
||||
"relationship": {
|
||||
"label": "Σχέση"
|
||||
}
|
||||
}
|
||||
},
|
||||
"section": {
|
||||
"heading": "Ενότητα"
|
||||
},
|
||||
"volunteer": {
|
||||
"form": {
|
||||
"organization": {
|
||||
"label": "Οργανισμός"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"rightSidebar": {
|
||||
"sections": {
|
||||
"css": {
|
||||
"heading": "Προσαρμοσμένο CSS"
|
||||
},
|
||||
"export": {
|
||||
"heading": "Εξαγωγή",
|
||||
"json": {
|
||||
"primary": "JSON",
|
||||
"secondary": "Κατεβάστε μια έκδοση JSON του βιογραφικού σας που μπορεί να εισαχθεί ξανά στο Reactive Resume."
|
||||
},
|
||||
"pdf": {
|
||||
"loading": {
|
||||
"primary": "Δημιουργία PDF",
|
||||
"secondary": "Περιμένετε μέχρι να δημιουργηθεί το PDF σας, αυτό μπορεί να διαρκέσει έως και 15 δευτερόλεπτα."
|
||||
},
|
||||
"normal": {
|
||||
"primary": "PDF",
|
||||
"secondary": "Κατεβάστε ένα PDF του βιογραφικού σας που μπορείτε να εκτυπώσετε και να στείλετε στην εργασία των ονείρων σας. Αυτό το αρχείο δεν μπορεί να εισαχθεί ξανά για περαιτέρω επεξεργασία."
|
||||
}
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
"heading": "Διάταξη",
|
||||
"tooltip": {
|
||||
"reset-layout": "Επαναφορά διάταξης"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"bugs-features": {
|
||||
"body": "Υπάρχει κάτι που σας εμποδίζει να φτιάξετε ένα βιογραφικό σημείωμα; Ή μήπως έχετε μια καταπληκτική ιδέα να προσθέσετε; Θέστε το ως σφάλμα στο GitHub για να ξεκινήσετε.",
|
||||
"button": "Σφάλματα GitHub",
|
||||
"heading": "Σφάλματα; Αιτήματα δυνατοτήτων;"
|
||||
},
|
||||
"donate": {
|
||||
"body": "Αν σας άρεσε να χρησιμοποιείτε το Reactive Resume, σκεφτείτε το ενδεχόμενο να δωρίσετε όσο το δυνατόν περισσότερα για τον σκοπό της διατήρησης και λειτουργίας της εφαρμογής, χωρίς διαφημίσεις και δωρεάν για πάντα.",
|
||||
"button": "Κεράστε με ένα καφεδάκι",
|
||||
"heading": "Κάντε δωρεά στο Reactive βιογραφικό"
|
||||
},
|
||||
"github": "Πηγαίος κώδικας",
|
||||
"heading": "Σύνδεσμοι"
|
||||
},
|
||||
"settings": {
|
||||
"global": {
|
||||
"date": {
|
||||
"primary": "Ημερομηνία",
|
||||
"secondary": "Μορφή ημερομηνίας για χρήση στην εφαρμογή"
|
||||
},
|
||||
"heading": "Καθολικό",
|
||||
"language": {
|
||||
"primary": "Γλώσσα",
|
||||
"secondary": "Εμφάνιση γλώσσας για χρήση στην εφαρμογή"
|
||||
},
|
||||
"theme": {
|
||||
"primary": "Θέμα"
|
||||
}
|
||||
},
|
||||
"heading": "Ρυθμίσεις",
|
||||
"page": {
|
||||
"break-line": {
|
||||
"primary": "Γραμμή διακοπής",
|
||||
"secondary": "Εμφάνιση μιας γραμμής σε όλες τις σελίδες για να επισημάνετε το ύψος μιας σελίδας Α4"
|
||||
},
|
||||
"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/el/common.json
Normal file
29
client/public/locales/el/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 megabytes.",
|
||||
"upload-photo-size": "Παρακαλούμε ανεβάστε μόνο φωτογραφίες κάτω των 2 megabytes, κατά προτίμηση τετράγωνες."
|
||||
},
|
||||
"success": {
|
||||
"resume-link-copied": "Ένας σύνδεσμος για το βιογραφικό σας αντιγράφηκε."
|
||||
}
|
||||
}
|
||||
}
|
||||
25
client/public/locales/el/dashboard.json
Normal file
25
client/public/locales/el/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": "Πίνακας ελέγχου"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user