Merge branch 'main' into patch-3

This commit is contained in:
Amruth Pillai
2022-11-02 23:36:13 +01:00
committed by GitHub
324 changed files with 11478 additions and 61005 deletions

View File

@ -1,5 +0,0 @@
ARG VARIANT="lts-bullseye"
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm

View File

@ -1,27 +0,0 @@
{
"name": "Node.js",
"build": {
"dockerfile": "Dockerfile",
"args": { "VARIANT": "16-bullseye" }
},
// Set *default* container specific settings.json values on container create.
"settings": {},
// Add the IDs of extensions you want installed when the container is created.
"extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "lokalise.i18n-ally"],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [80, 5432],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "pnpm install",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "node",
"features": {
"docker-from-docker": "latest"
}
}

View File

@ -1,13 +1,22 @@
# Android App
/app
# Build Artifacts # Build Artifacts
dist dist
.next .next
.turbo
# IDEs # IDEs
.vscode .vscode
# Project Metadata # Project Metadata
.crowdin.yml
# Documentation
README.md README.md
SECURITY.md
CHANGELOG.md CHANGELOG.md
CODE_OF_CONDUCT.md
# Project Dependencies # Project Dependencies
node_modules node_modules
@ -16,6 +25,3 @@ node_modules
Dockerfile Dockerfile
.dockerignore .dockerignore
docker-compose.yml docker-compose.yml
# Android App
/app

View File

@ -1,7 +1,11 @@
# Turbo Cache (Optional)
TURBO_TEAM=
TURBO_TOKEN=
# Server + Client # Server + Client
TZ=UTC TZ=UTC
PUBLIC_URL=http://localhost:3000 PUBLIC_URL=http://localhost:3000
PUBLIC_SERVER_URL=http://localhost:3000/api PUBLIC_SERVER_URL=http://localhost:3100
PUBLIC_GOOGLE_CLIENT_ID= PUBLIC_GOOGLE_CLIENT_ID=
# Server + Database # Server + Database
@ -18,10 +22,12 @@ JWT_SECRET=
JWT_EXPIRY_TIME=604800 JWT_EXPIRY_TIME=604800
GOOGLE_CLIENT_SECRET= GOOGLE_CLIENT_SECRET=
GOOGLE_API_KEY= GOOGLE_API_KEY=
SENDGRID_API_KEY= MAIL_FROM_NAME=
SENDGRID_FORGOT_PASSWORD_TEMPLATE_ID= MAIL_FROM_EMAIL=
SENDGRID_FROM_NAME= MAIL_HOST=
SENDGRID_FROM_EMAIL= MAIL_PORT=
MAIL_USERNAME=
MAIL_PASSWORD=
STORAGE_BUCKET= STORAGE_BUCKET=
STORAGE_REGION= STORAGE_REGION=
STORAGE_ENDPOINT= STORAGE_ENDPOINT=

View File

@ -1,31 +1,22 @@
{ {
"root": true,
"ignorePatterns": ["/app"], "ignorePatterns": ["/app"],
"parser": "@typescript-eslint/parser", "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"], "plugins": ["@typescript-eslint/eslint-plugin", "simple-import-sort"],
"rules": { "rules": {
// ESLint
"no-unused-vars": "off",
// Simple Import Sort
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
// TypeScript ESLint // TypeScript ESLint
"@typescript-eslint/no-unused-vars": "off", "@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/interface-name-prefix": "off", "@typescript-eslint/interface-name-prefix": "off",
"@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/explicit-module-boundary-types": "off"
// Simple Import Sort
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
// Unused Imports
"no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{
"vars": "all",
"varsIgnorePattern": "^_",
"args": "none",
"argsIgnorePattern": "^_"
}
]
}, },
"overrides": [ "overrides": [
{ {

1
.github/FUNDING.yml vendored
View File

@ -1 +1,2 @@
github: AmruthPillai
custom: https://paypal.me/RajaRajanA custom: https://paypal.me/RajaRajanA

View File

@ -1,36 +1,43 @@
--- ---
name: Bug Report name: Bug Report
about: Create a report to help improve about: Create a report to help improve
title: "[BUG] " title: '[BUG] '
labels: bug labels: bug
assignees: '' assignees: ''
--- ---
**Describe the bug** **Describe the bug**
<!-- A clear and concise description of what the bug is. --> <!-- A clear and concise description of what the bug is. -->
**Product Flavor** **Product Flavor**
- [ ] Managed (https://rxresu.me) - [ ] Managed (https://rxresu.me)
- [ ] Self Hosted - [ ] Self Hosted
**To Reproduce** **To Reproduce**
<!-- Steps to reproduce the behavior: --> <!-- Steps to reproduce the behavior: -->
1. Go to '...' 1. Go to '...'
2. Click on '....' 2. Click on '....'
3. Scroll down to '....' 3. Scroll down to '....'
4. See error 4. See error
**Expected behavior** **Expected behavior**
<!-- A clear and concise description of what you expected to happen. --> <!-- A clear and concise description of what you expected to happen. -->
**Screenshots** **Screenshots**
<!-- If applicable, add screenshots to help explain your problem. --> <!-- If applicable, add screenshots to help explain your problem. -->
**Desktop (please complete the following information):** **Desktop (please complete the following information):**
- OS: <!--[e.g. iOS]--> - OS: <!--[e.g. iOS]-->
- Browser <!--[e.g. chrome, safari]--> - Browser <!--[e.g. chrome, safari]-->
- Version <!--[e.g. 22]--> - Version <!--[e.g. 22]-->
**Additional context** **Additional context**
<!-- Add any other context about the problem here. --> <!-- Add any other context about the problem here. -->

View File

@ -1,20 +1,23 @@
--- ---
name: Feature Request name: Feature Request
about: Suggest an idea for this project about: Suggest an idea for this project
title: "[FEATURE] " title: '[FEATURE] '
labels: enhancement labels: enhancement
assignees: '' assignees: ''
--- ---
**Is your feature request related to a problem? Please describe.** **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 [...] --> <!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
**Describe the solution you'd like** **Describe the solution you'd like**
<!-- A clear and concise description of what you want to happen. --> <!-- A clear and concise description of what you want to happen. -->
**Describe alternatives you've considered** **Describe alternatives you've considered**
<!-- A clear and concise description of any alternative solutions or features you've considered. --> <!-- A clear and concise description of any alternative solutions or features you've considered. -->
**Additional context** **Additional context**
<!-- Add any other context or screenshots about the feature request here. --> <!-- Add any other context or screenshots about the feature request here. -->

View File

@ -13,7 +13,7 @@ jobs:
steps: steps:
- name: Install DigitalOcean CLI - name: Install DigitalOcean CLI
uses: digitalocean/action-doctl@v2.1.1 uses: digitalocean/action-doctl@v2.2.0
with: with:
token: ${{ secrets.DIGITALOCEAN_TOKEN }} token: ${{ secrets.DIGITALOCEAN_TOKEN }}

View File

@ -9,72 +9,104 @@ jobs:
name: Client name: Client
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.1.0
with:
fetch-depth: 2
- id: version - id: version
name: Get Version name: Get Version
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/} run: echo "version=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.1.0
- id: buildx
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.2.1
with:
install: true
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v2.0.0 uses: docker/login-action@v2.1.0
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v2.0.0 uses: docker/login-action@v2.1.0
with: with:
registry: ghcr.io registry: ghcr.io
username: $GITHUB_REPOSITORY_OWNER username: $GITHUB_REPOSITORY_OWNER
password: ${{ secrets.GH_TOKEN }} password: ${{ secrets.GH_TOKEN }}
- name: Build and Push Client Image - name: Build and Push Client Image
uses: docker/build-push-action@v3.0.0 uses: docker/build-push-action@v3.2.0
with: with:
context: . context: .
push: true push: true
file: client/Dockerfile file: client/Dockerfile
platforms: linux/amd64,linux/arm64
tags: | tags: |
amruthpillai/reactive-resume:client-latest amruthpillai/reactive-resume:client-latest
amruthpillai/reactive-resume:client-${{ steps.version.outputs.tag }} amruthpillai/reactive-resume:client-${{ env.version }}
ghcr.io/amruthpillai/reactive-resume:client-latest ghcr.io/amruthpillai/reactive-resume:client-latest
ghcr.io/amruthpillai/reactive-resume:client-${{ steps.version.outputs.tag }} ghcr.io/amruthpillai/reactive-resume:client-${{ env.version }}
server: server:
name: Server name: Server
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.1.0
with:
fetch-depth: 2
- id: version - id: version
name: Get Version name: Get Version
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/} run: echo "version=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.1.0
- id: buildx
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.2.1
with:
install: true
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v2.0.0 uses: docker/login-action@v2.1.0
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v2.0.0 uses: docker/login-action@v2.1.0
with: with:
registry: ghcr.io registry: ghcr.io
username: $GITHUB_REPOSITORY_OWNER username: $GITHUB_REPOSITORY_OWNER
password: ${{ secrets.GH_TOKEN }} password: ${{ secrets.GH_TOKEN }}
- name: Build and Push Server Image - name: Build and Push Server Image
uses: docker/build-push-action@v3.0.0 uses: docker/build-push-action@v3.2.0
with: with:
context: . context: .
push: true push: true
file: server/Dockerfile file: server/Dockerfile
platforms: linux/amd64,linux/arm64
tags: | tags: |
amruthpillai/reactive-resume:server-latest amruthpillai/reactive-resume:server-latest
amruthpillai/reactive-resume:server-${{ steps.version.outputs.tag }} amruthpillai/reactive-resume:server-${{ env.version }}
ghcr.io/amruthpillai/reactive-resume:server-latest ghcr.io/amruthpillai/reactive-resume:server-latest
ghcr.io/amruthpillai/reactive-resume:server-${{ steps.version.outputs.tag }} ghcr.io/amruthpillai/reactive-resume:server-${{ env.version }}

3
.gitignore vendored
View File

@ -8,3 +8,6 @@ node_modules
# macOS # macOS
.DS_Store .DS_Store
# Turbo
.turbo

View File

@ -1,6 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
pnpm install
pnpm run lint
pnpm run format

View File

@ -1,3 +1,6 @@
# Android App
/app
# Schema # Schema
schema/dist schema/dist
@ -18,15 +21,9 @@ CHANGELOG.md
# Project Dependencies # Project Dependencies
node_modules node_modules
pnpm-lock.yaml
# Docker # Docker
Dockerfile Dockerfile
.dockerignore .dockerignore
docker-compose.yml docker-compose.yml
# Android App
/app
# Docs
docs/build
docs/.docusaurus

View File

@ -1,3 +1,7 @@
{ {
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "lokalise.i18n-ally"] "recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"lokalise.i18n-ally"
]
} }

26
.vscode/launch.json vendored
View File

@ -1,26 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Debug: Server",
"port": 9229,
"restart": true,
"stopOnEntry": false,
"protocol": "inspector"
},
{
"name": "Debug: Client",
"type": "node-terminal",
"request": "launch",
"command": "pnpm run dev:client",
"console": "integratedTerminal",
"serverReadyAction": {
"pattern": "started server on .+, url: (https?://.+)",
"uriFormat": "%s",
"action": "debugWithChrome"
}
}
]
}

25
.vscode/settings.json vendored
View File

@ -1,25 +1,22 @@
{ {
"css.validate": false, "css.validate": false,
"scss.validate": false,
"editor.wordWrap": "on",
"npm.packageManager": "pnpm",
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true "source.fixAll.eslint": true
}, },
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.wordWrap": "on",
"eslint.workingDirectories": [ "eslint.workingDirectories": [
"schema", "schema",
"client", "client",
"server" "server"
], ],
"i18n-ally.enabledFrameworks": [ "conventionalCommits.scopes": [
"react" "client",
], "server",
"i18n-ally.keystyle": "nested", "docker",
"i18n-ally.localesPaths": [ "dependencies"
"client/public/locales" ]
],
"i18n-ally.namespace": true,
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
"i18n-ally.sortKeys": true,
"scss.validate": false
} }

View File

@ -1,293 +0,0 @@
# Changelog
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.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)
### Features
* **profile:** add XING profile icon ([1e59f73](https://github.com/AmruthPillai/Reactive-Resume/commit/1e59f73f79a91d0264c0d2108906ee89d4eb27f8)), closes [#821](https://github.com/AmruthPillai/Reactive-Resume/issues/821)
* **s3:** implement non-ephemeral storage through S3/DO Spaces ([feb911a](https://github.com/AmruthPillai/Reactive-Resume/commit/feb911aea06bacf58ea933d2803a2a89fe36e57b))
### [3.3.2](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.3.1...v3.3.2) (2022-04-08)
### Bug Fixes
* **types/react:** downgrade to <18 ([fc77b54](https://github.com/AmruthPillai/Reactive-Resume/commit/fc77b548d8d61530b2d158ff83f088bed12d5080))
### [3.3.1](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.3.0...v3.3.1) (2022-04-08)
## [3.3.0](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.2.11...v3.3.0) (2022-04-08)
### Features
* **upgrade:** changes to code to support new template ([1df7810](https://github.com/AmruthPillai/Reactive-Resume/commit/1df78100ca0667ce9b7834cf2c25384eb21c67c2))
### What's Changed
* New Crowdin updates by @AmruthPillai in https://github.com/AmruthPillai/Reactive-Resume/pull/791
* Bump org.jetbrains.kotlin.android from 1.6.10 to 1.6.20 in /app by @dependabot in https://github.com/AmruthPillai/Reactive-Resume/pull/812
* New Crowdin updates by @AmruthPillai in https://github.com/AmruthPillai/Reactive-Resume/pull/806
* A new template - Leafish by @klejejs in https://github.com/AmruthPillai/Reactive-Resume/pull/811
* Automatic multi-platform Docker image build by @schklom in https://github.com/AmruthPillai/Reactive-Resume/pull/817
### New Contributors
* @klejejs made their first contribution in https://github.com/AmruthPillai/Reactive-Resume/pull/811
* @schklom made their first contribution in https://github.com/AmruthPillai/Reactive-Resume/pull/817
### [3.2.11](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.2.10...v3.2.11) (2022-03-28)
### [3.2.10](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.2.9...v3.2.10) (2022-03-24)
### Features
* **i18n:** add portuguese (pt) language to i18n locales ([7f1c82c](https://github.com/AmruthPillai/Reactive-Resume/commit/7f1c82cd9185ebb44486a16132eb44d5c2fb747a))
### [3.2.9](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.2.8...v3.2.9) (2022-03-21)
### Features
* **i18n:** add nl and ru i18n locales to app ([03cbf22](https://github.com/AmruthPillai/Reactive-Resume/commit/03cbf22c9bee96cac8f228830b67b44529b7ecee))
### [3.2.8](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.2.7...v3.2.8) (2022-03-18)
### Features
* **client/theme:** add theme switcher to landing page ([8f5632c](https://github.com/AmruthPillai/Reactive-Resume/commit/8f5632c5ad0bc8a4b3028c2806365717fedd78c9))
* **flags:** introduce flags, disable_user_signups ([b6267d0](https://github.com/AmruthPillai/Reactive-Resume/commit/b6267d07ba2dcaed0da3946d136a0a9a01c441d5)), closes [#698](https://github.com/AmruthPillai/Reactive-Resume/issues/698)
* **i18n:** add Vietnamese language to i18n locales ([4390bcc](https://github.com/AmruthPillai/Reactive-Resume/commit/4390bccfb9764f2d2730ec3a124b7befb6792e9a))
### Bug Fixes
* **client/create-rename-slug:** fix slug accepting apostrophes and other special characters ([1facd2a](https://github.com/AmruthPillai/Reactive-Resume/commit/1facd2ad111cd9d990c808b3956d3915e8711acd)), closes [#706](https://github.com/AmruthPillai/Reactive-Resume/issues/706)
* **disable_user_signups:** hide create account link under flag ([80acfe9](https://github.com/AmruthPillai/Reactive-Resume/commit/80acfe97c74bfa05b719285b19144144f3f7c5ba))
### [3.2.7](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.2.6...v3.2.7) (2022-03-18)
### Features
* **i18n:** add Malayalam (മലയ) language to i18n locales ([3a2e62b](https://github.com/AmruthPillai/Reactive-Resume/commit/3a2e62be4c9acc14f17277c060cc9ea2c417a478))
### Bug Fixes
* **printer/i18n:** fix dates not showing up in resume language when printing ([90321e1](https://github.com/AmruthPillai/Reactive-Resume/commit/90321e1284409ab9442883c04a9b4c591d36f95d)), closes [#729](https://github.com/AmruthPillai/Reactive-Resume/issues/729)
### [3.2.6](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.2.5...v3.2.6) (2022-03-17)
### Features
* **client/auth/google:** disable google login/registration if GOOGLE_CLIENT_ID is not in ENV ([7f0ee40](https://github.com/AmruthPillai/Reactive-Resume/commit/7f0ee40af4acc7eb41514406ecee3218ace9e891)), closes [#724](https://github.com/AmruthPillai/Reactive-Resume/issues/724)
* **i18n:** add arabic language to i18n locale ([39fa6da](https://github.com/AmruthPillai/Reactive-Resume/commit/39fa6da5dd77ce2e12e81530fa18c2eac722c1f2))
### Bug Fixes
* **i18n:** add missing languages to dayjs date wrapper locales ([9e6dafc](https://github.com/AmruthPillai/Reactive-Resume/commit/9e6dafc8cada5c01559894905996b81004bedaec)), closes [#719](https://github.com/AmruthPillai/Reactive-Resume/issues/719)
* **json-export:** add mimeType and charset to JSON export ([b3ff780](https://github.com/AmruthPillai/Reactive-Resume/commit/b3ff7805cd856a52900d9acef0554867d8ce0b01)), closes [#726](https://github.com/AmruthPillai/Reactive-Resume/issues/726)
* **linkedin:** fix skill modal crashing when importing from linkedin ([a02b85b](https://github.com/AmruthPillai/Reactive-Resume/commit/a02b85b4bb1c4a1499aacddeac7bc59bcb1f7adb)), closes [#718](https://github.com/AmruthPillai/Reactive-Resume/issues/718)
### [3.2.5](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.2.4...v3.2.5) (2022-03-16)
### Features
* **i18n:** add danish, polish and turkish locales to i18n ([97e9432](https://github.com/AmruthPillai/Reactive-Resume/commit/97e9432d6bd887e666a3443fbfde9a92cef53965))
### Bug Fixes
* **client/templates:** fix text veering off of artboard in most templates ([b2f1fb3](https://github.com/AmruthPillai/Reactive-Resume/commit/b2f1fb3a5502988a49c5cd3e496d9d165f5c1792)), closes [#702](https://github.com/AmruthPillai/Reactive-Resume/issues/702)
### [3.2.4](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.2.3...v3.2.4) (2022-03-14)
### [3.2.3](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.2.2...v3.2.3) (2022-03-14)
### Features
* **client/import:** implement import json from reactive resume v2 ([42408ce](https://github.com/AmruthPillai/Reactive-Resume/commit/42408ce8c5ce55904854f9f6e0481889a01edfb8))
### [3.2.2](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.2.1...v3.2.2) (2022-03-14)
### Bug Fixes
* **client/skills:** make skill level optional ([02e396b](https://github.com/AmruthPillai/Reactive-Resume/commit/02e396bfdbf07ae75661f1e7e4e55060cacee7d0))
### [3.2.1](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.2.0...v3.2.1) (2022-03-14)
### Features
* **i18n:** add Chinese (Simplified) language to locales ([549363b](https://github.com/AmruthPillai/Reactive-Resume/commit/549363bbe5bdd781699dea9506bd4baedf5740d1))
### Bug Fixes
* **client/basics:** fix issue with overlapping photo filters on safari/webkit/iOS ([e6bda68](https://github.com/AmruthPillai/Reactive-Resume/commit/e6bda688ac3ba1c04e82721add92e755ea5386c3))
* **docker:** fix docker-compose for production grade deployments ([57f7edc](https://github.com/AmruthPillai/Reactive-Resume/commit/57f7edc13432a038c907afc6cb74b5182a9b2333))
## [3.2.0](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.1.4...v3.2.0) (2022-03-14)
### Features
* **i18n:** add Bengali, Italian and other languages ([21931bc](https://github.com/AmruthPillai/Reactive-Resume/commit/21931bc324b5e2440baaaaa2e52a93b4f2c766f8))
### Bug Fixes
* **app:** fix issue with external link redirection in android app ([b18120b](https://github.com/AmruthPillai/Reactive-Resume/commit/b18120b3f7223981e28c0441a6b7725787186edb))
* **client:** fix issue with react-query cache ([ed75a85](https://github.com/AmruthPillai/Reactive-Resume/commit/ed75a858279047dfd43152e041c1a09a625417f5))
### [3.1.4](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.1.3...v3.1.4) (2022-03-12)
### Bug Fixes
* **client:** exported pdf did not contain "Present" keyword with translations ([cf670af](https://github.com/AmruthPillai/Reactive-Resume/commit/cf670af4035dc9b462cf5b1aad06ca089cf1d40c))
* **client:** fix issues raised through lgtm alerts ([dfccb31](https://github.com/AmruthPillai/Reactive-Resume/commit/dfccb3130f889934d31196226be3d33e772f323b))
### [3.1.3](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.1.2...v3.1.3) (2022-03-12)
### Bug Fixes
* **server:** reform url for pdf generation and download ([6d55f91](https://github.com/AmruthPillai/Reactive-Resume/commit/6d55f917eab3cb2f5f3a90c5a18f03b625d60021)), closes [#661](https://github.com/AmruthPillai/Reactive-Resume/issues/661)
### [3.1.2](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.1.1...v3.1.2) (2022-03-12)
### CI
* **docker:**: include traefik routing and proxy to ensure server connections pass in local ([11cb066](https://github.com/AmruthPillai/Reactive-Resume/commit/11cb066573c6917857b79c028b97fcda1acaf90a))
### [3.1.1](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.1.0...v3.1.1) (2022-03-12)
### Features
* **client:** add product hunt announcement banner ([b515fc3](https://github.com/AmruthPillai/Reactive-Resume/commit/b515fc36e7f282db92e8eb509b6c5004a944fa95))
## [3.1.0](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.0.0...v3.1.0) (2022-03-12)
### Features
* **client:** add "spanish (es)" language to i18n locales ([bf167f8](https://github.com/AmruthPillai/Reactive-Resume/commit/bf167f81a3659677dada55856f5eaf0fc469e697))
* **client:** add mm/yyyy date option to date format options ([82bf44d](https://github.com/AmruthPillai/Reactive-Resume/commit/82bf44daa24422156779e9b38d3dc695176eaa09)), closes [#656](https://github.com/AmruthPillai/Reactive-Resume/issues/656)
* **client:** add sitemap generation to next app ([2cbc582](https://github.com/AmruthPillai/Reactive-Resume/commit/2cbc582a12b72b3012246022d4b518ed657d4c08))
* **client:** disable "Toggle Page Orientation" when there's only one page on the artboard ([01da1a0](https://github.com/AmruthPillai/Reactive-Resume/commit/01da1a06b802f1063a41d7a9a682e76b1daf9461)), closes [#655](https://github.com/AmruthPillai/Reactive-Resume/issues/655)
### Bug Fixes
* **client:** remove hard-coded "keywords:" in certain templates ([dda42b4](https://github.com/AmruthPillai/Reactive-Resume/commit/dda42b4c6b3bc359ac4f2bb91ca8118ddc84ec07)), closes [#650](https://github.com/AmruthPillai/Reactive-Resume/issues/650)
* **client:** show "present" string if end date is not entered, also add to i18n locales ([b5cd6c4](https://github.com/AmruthPillai/Reactive-Resume/commit/b5cd6c412b5b6b6ca7bb43c3801762de451f06b4)), closes [#653](https://github.com/AmruthPillai/Reactive-Resume/issues/653)
* **server:** photo uploads not working, fix save location and returned url ([799f208](https://github.com/AmruthPillai/Reactive-Resume/commit/799f20823e6d97a1ff0ba2c45c61d56304d0fa58)), closes [#658](https://github.com/AmruthPillai/Reactive-Resume/issues/658)
## [3.0.0](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.0.0-beta.6...v3.0.0) (2022-03-11)
### Features
* **lang**: add German, Kannada and Tamil languages to the app ([3a524f9](https://github.com/AmruthPillai/Reactive-Resume/commit/3a524f9c9c7a0e446491265b2242ad3dfeae188c))
* **docs:** add docusaurus workspace, initial setup of docs ([dc4aa0b](https://github.com/AmruthPillai/Reactive-Resume/commit/dc4aa0b496096bd59c45426bfcea6ba7db5f5c01))
## [3.0.0-beta.6](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.0.0-beta.5...v3.0.0-beta.6) (2022-03-11)
### Features
* **lang:** add language switcher on the landing page, in the footer ([8bc7d25](https://github.com/AmruthPillai/Reactive-Resume/commit/8bc7d2599ef6af7a07bfbe886c43844152b0d9f7))
### Bug Fixes
* **i18n:** add missing translation keys, update lang/locale logic ([7d8828a](https://github.com/AmruthPillai/Reactive-Resume/commit/7d8828a358d653bb162877a64c75028eb82678cd))
* **webkit:** fix issue with webkit not supporting .at() ([2654cba](https://github.com/AmruthPillai/Reactive-Resume/commit/2654cba039eb73d33257c36fa90a52cabc9fda96))
## [3.0.0-beta.5](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.0.0-beta.4...v3.0.0-beta.5) (2022-03-10)
### Bug Fixes
* **app:** fix issue with using swipelayout ([972e8b1](https://github.com/AmruthPillai/Reactive-Resume/commit/972e8b1bcf9ad44d8915bf23d189711672937bc0))

View File

@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our Examples of behavior that contributes to a positive environment for our
community include: community include:
* Demonstrating empathy and kindness toward other people - Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences - Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback - Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, - Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience 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 overall community
Examples of unacceptable behavior include: 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 advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks - Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment - Public or private harassment
* Publishing others' private information, such as a physical or email - Publishing others' private information, such as a physical or email
address, without their explicit permission 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 professional setting
## Enforcement Responsibilities ## Enforcement Responsibilities

View File

@ -1,4 +1,4 @@
<img src="https://github.com/AmruthPillai/Reactive-Resume/blob/main/docs/static/logo.svg" alt="Reactive Resume" width="256px" height="256px" /> <img src="https://rxresu.me/images/logos/logo.png" alt="Reactive Resume" width="256px" height="256px" />
# Reactive Resume # Reactive Resume
@ -27,7 +27,8 @@ You have complete control over what goes into your resume, how it looks, what co
- [Contributing](#contributing) - [Contributing](#contributing)
- [Report Bugs and Feature Requests](#report-bugs-and-feature-requests) - [Report Bugs and Feature Requests](#report-bugs-and-feature-requests)
- [Donations](#donations) - [Donations](#donations)
- [💸 PayPal](#-paypal) - [GitHub Sponsor](#github-sponsor)
- [PayPal](#paypal)
- [Infrastructure](#infrastructure) - [Infrastructure](#infrastructure)
- [Contributors Wall](#contributors-wall) - [Contributors Wall](#contributors-wall)
- [License](#license) - [License](#license)
@ -54,13 +55,17 @@ You have complete control over what goes into your resume, how it looks, what co
## Languages ## Languages
- Amharic (አማርኛ)
- Arabic (اَلْعَرَبِيَّةُ) - Arabic (اَلْعَرَبِيَّةُ)
- Bengali (বাংলা) - Bengali (বাংলা)
- Bulgarian (български)
- Catalan (Valencian)
- Chinese (中文) - Chinese (中文)
- Czech (čeština) - Czech (čeština)
- Danish (Dansk) - Danish (Dansk)
- Dutch (Nederlands) - Dutch (Nederlands)
- English - English
- Finnish (Suomi)
- French (Français) - French (Français)
- German (Deutsch) - German (Deutsch)
- Greek (Ελληνικά) - Greek (Ελληνικά)
@ -69,17 +74,26 @@ You have complete control over what goes into your resume, how it looks, what co
- Hungarian (Magyar) - Hungarian (Magyar)
- Indonesian (Bahasa Indonesia) - Indonesian (Bahasa Indonesia)
- Italian (Italiano) - Italian (Italiano)
- Japanese (日本語)
- Kannada (ಕನ್ನಡ) - Kannada (ಕನ್ನಡ)
- Khmer (ភាសាខ្មែរ)
- Korean (한국어)
- Malayalam (മലയാളം) - Malayalam (മലയാളം)
- Marathi (मराठी)
- Nepali (नेपाली)
- Norwegian (Norsk)
- Odia (ଓଡ଼ିଆ) - Odia (ଓଡ଼ିଆ)
- Persian (Farsi) - Persian (فارسی)
- Polish (Polski) - Polish (Polski)
- Portuguese (Português) - Portuguese (Português)
- Romanian (limba română)
- Russian (русский) - Russian (русский)
- Serbian (српски језик)
- Spanish (Español) - Spanish (Español)
- Swedish (Svenska) - Swedish (Svenska)
- Tamil (தமிழ்) - Tamil (தமிழ்)
- Turkish (Türkçe) - Turkish (Türkçe)
- Ukranian (Українська мова)
- Vietnamese (Tiếng Việt) - Vietnamese (Tiếng Việt)
Help by [translating Reactive Resume](https://translate.rxresu.me) to your language! Help by [translating Reactive Resume](https://translate.rxresu.me) to your language!
@ -112,7 +126,8 @@ Use the [GitHub Issues](https://github.com/AmruthPillai/Reactive-Resume/issues/n
Reactive Resume would be nothing without the folks who supported me and kept the project alive in the beginning, and your continued support is what keeps me going. If you found Reactive Resume to be useful, helpful or just insightful and appreciate the effort I took to make the project, please consider donating as little or as much as you can. Reactive Resume would be nothing without the folks who supported me and kept the project alive in the beginning, and your continued support is what keeps me going. If you found Reactive Resume to be useful, helpful or just insightful and appreciate the effort I took to make the project, please consider donating as little or as much as you can.
### [💸 PayPal](https://paypal.me/RajaRajanA) ### [GitHub Sponsor](https://github.com/sponsors/AmruthPillai)
### [PayPal](https://paypal.me/RajaRajanA)
## Infrastructure ## Infrastructure

View File

@ -1,7 +1,7 @@
plugins { plugins {
id 'com.android.application' version '7.1.2' apply false id 'com.android.application' version '7.1.2' apply false
id 'com.android.library' version '7.1.2' apply false id 'com.android.library' version '7.1.2' apply false
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
} }
task clean(type: Delete) { task clean(type: Delete) {

View File

@ -1,9 +1,33 @@
{ {
"extends": ["../.eslintrc.json", "next/core-web-vitals"], "extends": ["../.eslintrc.json", "next/core-web-vitals", "plugin:tailwindcss/recommended"],
"plugins": ["unused-imports"],
"ignorePatterns": [".next", "__ENV.js"], "ignorePatterns": [".next", "__ENV.js"],
"settings": {
"next": {
"rootDir": "client"
}
},
"rules": { "rules": {
// Next.js
"@next/next/no-img-element": "off", "@next/next/no-img-element": "off",
"@next/next/no-sync-scripts": "off", "@next/next/no-sync-scripts": "off",
"@next/next/no-html-link-for-pages": ["error", "pages"]
// React Hooks
"react-hooks/exhaustive-deps": "off",
// Unused Imports
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{
"vars": "all",
"args": "none",
"varsIgnorePattern": "^_",
"argsIgnorePattern": "^_"
}
],
// Tailwind CSS
"tailwindcss/no-custom-classname": ["warn", { "whitelist": ["preview-mode", "printer-mode", "markdown"] }]
} }
} }

View File

@ -1,22 +1,19 @@
FROM node:lts-alpine as dependencies FROM node:lts-alpine AS base
RUN apk add --no-cache curl g++ make python3 \
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
WORKDIR /app WORKDIR /app
COPY package.json pnpm-*.yaml ./ RUN apk add --no-cache g++ git curl make python3 libc6-compat \
&& 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 ./schema/package.json ./schema/package.json
COPY ./client/package.json ./client/package.json COPY ./client/package.json ./client/package.json
RUN pnpm install --frozen-lockfile RUN pnpm install --frozen-lockfile
FROM node:lts-alpine as builder FROM base 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
COPY . . 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/schema/node_modules ./schema/node_modules
COPY --from=dependencies /app/client/node_modules ./client/node_modules COPY --from=dependencies /app/client/node_modules ./client/node_modules
RUN pnpm run build:schema ARG TURBO_TEAM
RUN pnpm run build:client ARG TURBO_TOKEN
FROM node:lts-alpine as production ENV TURBO_TEAM $TURBO_TEAM
ENV TURBO_TOKEN $TURBO_TOKEN
WORKDIR /app RUN pnpm run build --filter client
RUN apk add --no-cache curl \ FROM base as production
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
COPY --from=builder /app/pnpm-*.yaml ./ COPY --from=builder /app/package.json /app/pnpm-*.yaml /app/turbo.json ./
COPY --from=builder /app/package.json ./
COPY --from=builder /app/client/package.json ./client/package.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/.next ./client/.next
COPY --from=builder /app/client/public ./client/public 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 \ HEALTHCHECK --interval=30s --timeout=20s --retries=3 --start-period=15s \
CMD curl -fSs 127.0.0.1:3000 || exit 1 CMD curl -fSs 127.0.0.1:3000 || exit 1
CMD [ "pnpm", "run", "start:client" ] CMD [ "pnpm", "run", "start", "--filter", "client" ]

View File

@ -5,6 +5,8 @@ import {
FilterCenterFocus, FilterCenterFocus,
InsertPageBreak, InsertPageBreak,
Link, Link,
RedoOutlined,
UndoOutlined,
ViewSidebar, ViewSidebar,
ZoomIn, ZoomIn,
ZoomOut, ZoomOut,
@ -16,6 +18,7 @@ import { useTranslation } from 'next-i18next';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { useMutation } from 'react-query'; import { useMutation } from 'react-query';
import { ReactZoomPanPinchRef } from 'react-zoom-pan-pinch'; import { ReactZoomPanPinchRef } from 'react-zoom-pan-pinch';
import { ActionCreators } from 'redux-undo';
import { ServerError } from '@/services/axios'; import { ServerError } from '@/services/axios';
import { printResumeAsPdf, PrintResumeAsPdfParams } from '@/services/printer'; import { printResumeAsPdf, PrintResumeAsPdfParams } from '@/services/printer';
@ -31,14 +34,18 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
const theme = useTheme(); const theme = useTheme();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const resume = useAppSelector((state) => state.resume);
const isDesktop = useMediaQuery(theme.breakpoints.up('sm')); 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 { left, right } = useAppSelector((state) => state.build.sidebar);
const orientation = useAppSelector((state) => state.build.page.orientation); const orientation = useAppSelector((state) => state.build.page.orientation);
const { mutateAsync, isLoading } = useMutation<string, ServerError, PrintResumeAsPdfParams>(printResumeAsPdf); const { mutateAsync, isLoading } = useMutation<string, ServerError, PrintResumeAsPdfParams>(printResumeAsPdf);
const handleUndo = () => dispatch(ActionCreators.undo());
const handleRedo = () => dispatch(ActionCreators.redo());
const handleTogglePageBreakLine = () => dispatch(togglePageBreakLine()); const handleTogglePageBreakLine = () => dispatch(togglePageBreakLine());
const handleTogglePageOrientation = () => dispatch(togglePageOrientation()); const handleTogglePageOrientation = () => dispatch(togglePageOrientation());
@ -63,7 +70,7 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
const url = await mutateAsync({ username, slug }); const url = await mutateAsync({ username, slug });
download(`/api${url}`); download(url);
}; };
return ( return (
@ -75,6 +82,20 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
})} })}
> >
<div className={styles.controller}> <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')}> <Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.zoom-in')}>
<ButtonBase onClick={() => zoomIn(0.25)}> <ButtonBase onClick={() => zoomIn(0.25)}>
<ZoomIn fontSize="medium" /> <ZoomIn fontSize="medium" />
@ -97,9 +118,11 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
{isDesktop && ( {isDesktop && (
<> <>
{pages.length > 1 && (
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.toggle-orientation')}> <Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.toggle-orientation')}>
<ButtonBase onClick={handleTogglePageOrientation}> <ButtonBase
onClick={handleTogglePageOrientation}
className={clsx({ 'pointer-events-none opacity-50': pages.length === 1 })}
>
{orientation === 'vertical' ? ( {orientation === 'vertical' ? (
<AlignHorizontalCenter fontSize="medium" /> <AlignHorizontalCenter fontSize="medium" />
) : ( ) : (
@ -107,7 +130,6 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
)} )}
</ButtonBase> </ButtonBase>
</Tooltip> </Tooltip>
)}
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.toggle-page-break-line')}> <Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.toggle-page-break-line')}>
<ButtonBase onClick={handleTogglePageBreakLine}> <ButtonBase onClick={handleTogglePageBreakLine}>

View File

@ -13,7 +13,7 @@ import Page from './Page';
const Center = () => { const Center = () => {
const orientation = useAppSelector((state) => state.build.page.orientation); 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'); const layout: string[][][] = get(resume, 'metadata.layout');
if (isEmpty(resume)) return null; if (isEmpty(resume)) return null;

View File

@ -57,7 +57,7 @@ const Header = () => {
const { mutateAsync: deleteMutation } = useMutation<void, ServerError, DeleteResumeParams>(deleteResume); 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 { left, right } = useAppSelector((state) => state.build.sidebar);
const name = useMemo(() => get(resume, 'name'), [resume]); const name = useMemo(() => get(resume, 'name'), [resume]);

View File

@ -15,7 +15,7 @@
} }
&.break::after { &.break::after {
content: 'A4 Page Break'; content: 'Page Break';
top: calc(297mm - 19px); top: calc(297mm - 19px);
@apply absolute w-full border-b border-dashed border-neutral-800/75; @apply absolute w-full border-b border-dashed border-neutral-800/75;
@ -27,6 +27,22 @@
@apply hidden; @apply hidden;
} }
} }
&.format-letter {
width: 216mm;
min-height: 279mm;
&.break::after {
top: calc(279mm - 19px);
}
}
.markdown {
ul {
padding-left: 1.5em;
text-indent: -1.5em;
}
}
} }
.pageNumber { .pageNumber {

View File

@ -1,5 +1,5 @@
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { CustomCSS, Theme, Typography } from '@reactive-resume/schema'; import { CustomCSS, PageConfig, ThemeConfig, Typography } from '@reactive-resume/schema';
import clsx from 'clsx'; import clsx from 'clsx';
import get from 'lodash/get'; import get from 'lodash/get';
import isEmpty from 'lodash/isEmpty'; import isEmpty from 'lodash/isEmpty';
@ -20,12 +20,13 @@ type Props = PageProps & {
const Page: React.FC<Props> = ({ page, showPageNumbers = false }) => { const Page: React.FC<Props> = ({ page, showPageNumbers = false }) => {
const { t } = useTranslation(); 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 breakLine: boolean = useAppSelector((state) => state.build.page.breakLine);
const theme: Theme = get(resume, 'metadata.theme'); const theme: ThemeConfig = get(resume, 'metadata.theme');
const customCSS: CustomCSS = get(resume, 'metadata.css'); const customCSS: CustomCSS = get(resume, 'metadata.css');
const template: string = get(resume, 'metadata.template'); const template: string = get(resume, 'metadata.template');
const pageConfig: PageConfig = get(resume, 'metadata.page');
const typography: Typography = get(resume, 'metadata.typography'); const typography: Typography = get(resume, 'metadata.typography');
const themeCSS = useMemo(() => !isEmpty(theme) && generateThemeStyles(theme), [theme]); const themeCSS = useMemo(() => !isEmpty(theme) && generateThemeStyles(theme), [theme]);
@ -33,12 +34,13 @@ const Page: React.FC<Props> = ({ page, showPageNumbers = false }) => {
const TemplatePage: React.FC<PageProps> | null = useMemo(() => templateMap[template].component, [template]); const TemplatePage: React.FC<PageProps> | null = useMemo(() => templateMap[template].component, [template]);
return ( return (
<div data-page={page + 1} className={styles.container}> <div className={styles.container} data-page={page + 1} data-format={pageConfig?.format || 'A4'}>
<div <div
className={clsx({ className={clsx({
reset: true, reset: true,
[styles.page]: true, [styles.page]: true,
[styles.break]: breakLine, [styles.break]: breakLine,
[styles['format-letter']]: pageConfig?.format === 'Letter',
[css(themeCSS)]: true, [css(themeCSS)]: true,
[css(typographyCSS)]: true, [css(typographyCSS)]: true,
[css(customCSS.value)]: customCSS.visible, [css(customCSS.value)]: customCSS.visible,

View File

@ -25,7 +25,7 @@ const LeftSidebar = () => {
const isDesktop = useMediaQuery(theme.breakpoints.up('lg')); 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 { open } = useAppSelector((state) => state.build.sidebar.left);
const customSections = useMemo(() => getCustomSections(sections), [sections]); const customSections = useMemo(() => getCustomSections(sections), [sections]);
@ -65,7 +65,7 @@ const LeftSidebar = () => {
variant={isDesktop ? 'persistent' : 'temporary'} variant={isDesktop ? 'persistent' : 'temporary'}
> >
<div className={styles.container}> <div className={styles.container}>
<nav> <nav className="overflow-y-scroll">
<div> <div>
<Link href="/dashboard"> <Link href="/dashboard">
<a className="inline-flex"> <a className="inline-flex">

View File

@ -32,7 +32,7 @@ const Basics = () => {
<PhotoUpload /> <PhotoUpload />
</div> </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" /> <ResumeInput label={t<string>('builder.leftSidebar.sections.basics.name.label')} path="basics.name" />
<Button variant="outlined" startIcon={<PhotoFilter />} onClick={handleClick}> <Button variant="outlined" startIcon={<PhotoFilter />} onClick={handleClick}>

View File

@ -12,7 +12,7 @@ const PhotoFilters = () => {
const dispatch = useAppDispatch(); 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 size: number = get(photo, 'filters.size', 128);
const shape: PhotoShape = get(photo, 'filters.shape', 'square'); const shape: PhotoShape = get(photo, 'filters.shape', 'square');
const grayscale: boolean = get(photo, 'filters.grayscale', false); const grayscale: boolean = get(photo, 'filters.grayscale', false);

View File

@ -21,8 +21,8 @@ const PhotoUpload: React.FC = () => {
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
const id: number = useAppSelector((state) => get(state.resume, 'id')); const id: number = useAppSelector((state) => get(state.resume.present, 'id'));
const photo: Photo = useAppSelector((state) => get(state.resume, 'basics.photo')); const photo: Photo = useAppSelector((state) => get(state.resume.present, 'basics.photo'));
const { mutateAsync: uploadMutation, isLoading } = useMutation<Resume, ServerError, UploadPhotoParams>(uploadPhoto); const { mutateAsync: uploadMutation, isLoading } = useMutation<Resume, ServerError, UploadPhotoParams>(uploadPhoto);

View File

@ -37,8 +37,8 @@ const Section: React.FC<Props> = ({
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const heading = useAppSelector<string>((state) => get(state.resume, `${path}.name`, name)); const heading = useAppSelector<string>((state) => get(state.resume.present, `${path}.name`, name));
const visibility = useAppSelector<boolean>((state) => get(state.resume, `${path}.visible`, true)); const visibility = useAppSelector<boolean>((state) => get(state.resume.present, `${path}.visible`, true));
const handleAdd = () => { const handleAdd = () => {
const id = path.split('.')[1]; const id = path.split('.')[1];

View File

@ -18,7 +18,7 @@ const SectionSettings: React.FC<Props> = ({ path }) => {
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null); 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>) => { const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget); setAnchorEl(event.currentTarget);

View File

@ -43,7 +43,7 @@ const RightSidebar = () => {
variant={isDesktop ? 'persistent' : 'temporary'} variant={isDesktop ? 'persistent' : 'temporary'}
> >
<div className={styles.container}> <div className={styles.container}>
<nav> <nav className="overflow-y-scroll">
<div> <div>
<Avatar size={40} /> <Avatar size={40} />
<Divider /> <Divider />

View File

@ -17,7 +17,7 @@ const CustomCSS = () => {
const dispatch = useAppDispatch(); 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) => { const handleChange = (value: string | undefined) => {
dispatch(setResumeState({ path: 'metadata.css.value', value })); dispatch(setResumeState({ path: 'metadata.css.value', value }));

View File

@ -13,7 +13,7 @@ import { useAppSelector } from '@/store/hooks';
const Export = () => { const Export = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const resume = useAppSelector((state) => state.resume); const resume = useAppSelector((state) => state.resume.present);
const { mutateAsync, isLoading } = useMutation<string, ServerError, PrintResumeAsPdfParams>(printResumeAsPdf); const { mutateAsync, isLoading } = useMutation<string, ServerError, PrintResumeAsPdfParams>(printResumeAsPdf);
@ -48,7 +48,7 @@ const Export = () => {
const url = await mutateAsync({ username, slug }); const url = await mutateAsync({ username, slug });
download(`/api${url}`); download(url);
}; };
return ( return (

View File

@ -1,10 +1,10 @@
import { DragDropContext, Draggable, DraggableLocation, Droppable, DropResult } from '@hello-pangea/dnd';
import { Add, Close, Restore } from '@mui/icons-material'; import { Add, Close, Restore } from '@mui/icons-material';
import { Button, IconButton, Tooltip } from '@mui/material'; import { Button, IconButton, Tooltip } from '@mui/material';
import clsx from 'clsx'; import clsx from 'clsx';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import get from 'lodash/get'; import get from 'lodash/get';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { DragDropContext, Draggable, DraggableLocation, Droppable, DropResult } from 'react-beautiful-dnd';
import Heading from '@/components/shared/Heading'; import Heading from '@/components/shared/Heading';
import { useAppDispatch, useAppSelector } from '@/store/hooks'; import { useAppDispatch, useAppSelector } from '@/store/hooks';
@ -23,8 +23,8 @@ const Layout = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const layout = useAppSelector((state) => state.resume.metadata.layout); const layout = useAppSelector((state) => state.resume.present.metadata.layout);
const resumeSections = useAppSelector((state) => state.resume.sections); const resumeSections = useAppSelector((state) => state.resume.present.sections);
const onDragEnd = (dropResult: DropResult) => { const onDragEnd = (dropResult: DropResult) => {
const { source: srcLoc, destination: destLoc } = dropResult; const { source: srcLoc, destination: destLoc } = dropResult;

View File

@ -3,7 +3,7 @@ import { Button } from '@mui/material';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import Heading from '@/components/shared/Heading'; import Heading from '@/components/shared/Heading';
import { DONATION_URL, GITHUB_ISSUES_URL, GITHUB_URL } from '@/constants/index'; import { DOCS_URL, DONATION_URL, GITHUB_ISSUES_URL, GITHUB_URL } from '@/constants/index';
import styles from './Links.module.scss'; import styles from './Links.module.scss';
@ -49,6 +49,12 @@ const Links = () => {
{t<string>('builder.rightSidebar.sections.links.github')} {t<string>('builder.rightSidebar.sections.links.github')}
</Button> </Button>
</a> </a>
<a href={DOCS_URL} target="_blank" rel="noreferrer">
<Button variant="text" startIcon={<Link />}>
{t<string>('builder.rightSidebar.sections.links.docs')}
</Button>
</a>
</div> </div>
</div> </div>
</> </>

View File

@ -10,12 +10,12 @@ import {
Switch, Switch,
TextField, TextField,
} from '@mui/material'; } from '@mui/material';
import { DateConfig, Resume } from '@reactive-resume/schema'; import { DateConfig, PageConfig, Resume } from '@reactive-resume/schema';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import get from 'lodash/get'; import get from 'lodash/get';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useMemo } from 'react'; import { useMemo, useState } from 'react';
import { useMutation } from 'react-query'; import { useMutation } from 'react-query';
import Heading from '@/components/shared/Heading'; import Heading from '@/components/shared/Heading';
@ -36,19 +36,22 @@ const Settings = () => {
const { locale, ...router } = useRouter(); 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 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 breakLine = useAppSelector((state) => state.build.page.breakLine);
const orientation = useAppSelector((state) => state.build.page.orientation); const orientation = useAppSelector((state) => state.build.page.orientation);
const id: number = useMemo(() => get(resume, 'id'), [resume]); const id: number = useMemo(() => get(resume, 'id'), [resume]);
const slug: string = useMemo(() => get(resume, 'slug'), [resume]); const slug: string = useMemo(() => get(resume, 'slug'), [resume]);
const username: string = useMemo(() => get(resume, 'user.username'), [resume]); const username: string = useMemo(() => get(resume, 'user.username'), [resume]);
const pageConfig: PageConfig = useMemo(() => get(resume, 'metadata.page'), [resume]);
const dateConfig: DateConfig = useMemo(() => get(resume, 'metadata.date'), [resume]); const dateConfig: DateConfig = useMemo(() => get(resume, 'metadata.date'), [resume]);
const isDarkMode = useMemo(() => theme === 'dark', [theme]); const isDarkMode = useMemo(() => theme === 'dark', [theme]);
const exampleString = useMemo(() => `Eg. ${dayjs().format(dateConfig.format)}`, [dateConfig.format]); const exampleDateString = useMemo(() => `Eg. ${dayjs().utc().format(dateConfig.format)}`, [dateConfig.format]);
const themeString = useMemo(() => (isDarkMode ? 'Matte Black Everything' : 'As bright as your future'), [isDarkMode]); const themeString = useMemo(() => (isDarkMode ? 'Matte Black Everything' : 'As bright as your future'), [isDarkMode]);
const { mutateAsync: loadSampleDataMutation } = useMutation<Resume, ServerError, LoadSampleDataParams>( const { mutateAsync: loadSampleDataMutation } = useMutation<Resume, ServerError, LoadSampleDataParams>(
@ -58,6 +61,9 @@ const Settings = () => {
const handleSetTheme = (value: boolean) => dispatch(setTheme({ theme: value ? 'dark' : 'light' })); const handleSetTheme = (value: boolean) => dispatch(setTheme({ theme: value ? 'dark' : 'light' }));
const handleChangePageFormat = (value: PageConfig['format'] | null) =>
dispatch(setResumeState({ path: 'metadata.page.format', value }));
const handleChangeDateFormat = (value: string | null) => const handleChangeDateFormat = (value: string | null) =>
dispatch(setResumeState({ path: 'metadata.date.format', value })); dispatch(setResumeState({ path: 'metadata.date.format', value }));
@ -78,9 +84,14 @@ const Settings = () => {
}; };
const handleResetResume = async () => { 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 ( return (
@ -90,7 +101,7 @@ const Settings = () => {
<List sx={{ padding: 0 }}> <List sx={{ padding: 0 }}>
{/* Global Settings */} {/* Global Settings */}
<> <>
<ListSubheader className="rounded"> <ListSubheader disableSticky className="rounded">
{t<string>('builder.rightSidebar.sections.settings.global.heading')} {t<string>('builder.rightSidebar.sections.settings.global.heading')}
</ListSubheader> </ListSubheader>
@ -111,13 +122,13 @@ const Settings = () => {
primary={t<string>('builder.rightSidebar.sections.settings.global.date.primary')} primary={t<string>('builder.rightSidebar.sections.settings.global.date.primary')}
secondary={t<string>('builder.rightSidebar.sections.settings.global.date.secondary')} secondary={t<string>('builder.rightSidebar.sections.settings.global.date.secondary')}
/> />
<Autocomplete<string, false, boolean, false> <Autocomplete<string, false, true, false>
disableClearable disableClearable
className="my-2 w-full" className="my-2 w-full"
options={dateFormatOptions} options={dateFormatOptions}
value={dateConfig.format} value={dateConfig.format}
onChange={(_, value) => handleChangeDateFormat(value)} onChange={(_, value) => handleChangeDateFormat(value)}
renderInput={(params) => <TextField {...params} helperText={exampleString} />} renderInput={(params) => <TextField {...params} helperText={exampleDateString} />}
/> />
</ListItem> </ListItem>
@ -127,7 +138,7 @@ const Settings = () => {
primary={t<string>('builder.rightSidebar.sections.settings.global.language.primary')} primary={t<string>('builder.rightSidebar.sections.settings.global.language.primary')}
secondary={t<string>('builder.rightSidebar.sections.settings.global.language.secondary')} secondary={t<string>('builder.rightSidebar.sections.settings.global.language.secondary')}
/> />
<Autocomplete<Language, false, boolean, false> <Autocomplete<Language, false, true, false>
disableClearable disableClearable
className="my-2 w-full" className="my-2 w-full"
options={languages} options={languages}
@ -148,10 +159,27 @@ const Settings = () => {
{/* Page Settings */} {/* Page Settings */}
<> <>
<ListSubheader className="rounded"> <ListSubheader disableSticky className="rounded">
{t<string>('builder.rightSidebar.sections.settings.page.heading')} {t<string>('builder.rightSidebar.sections.settings.page.heading')}
</ListSubheader> </ListSubheader>
<ListItem className="flex-col">
<ListItemText
className="w-full"
primary={t<string>('builder.rightSidebar.sections.settings.page.format.primary')}
secondary={t<string>('builder.rightSidebar.sections.settings.page.format.secondary')}
/>
<Autocomplete<PageConfig['format'], false, true, false>
disableClearable
defaultValue="A4"
className="my-2 w-full"
options={['A4', 'Letter']}
value={pageConfig?.format || 'A4'}
renderInput={(params) => <TextField {...params} />}
onChange={(_, value) => handleChangePageFormat(value)}
/>
</ListItem>
<ListItem> <ListItem>
<ListItemText <ListItemText
primary={t<string>('builder.rightSidebar.sections.settings.page.orientation.primary')} primary={t<string>('builder.rightSidebar.sections.settings.page.orientation.primary')}
@ -180,7 +208,7 @@ const Settings = () => {
{/* Resume Settings */} {/* Resume Settings */}
<> <>
<ListSubheader className="rounded"> <ListSubheader disableSticky className="rounded">
{t<string>('builder.rightSidebar.sections.settings.resume.heading')} {t<string>('builder.rightSidebar.sections.settings.resume.heading')}
</ListSubheader> </ListSubheader>
@ -202,7 +230,11 @@ const Settings = () => {
<DeleteForever /> <DeleteForever />
</ListItemIcon> </ListItemIcon>
<ListItemText <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')} secondary={t<string>('builder.rightSidebar.sections.settings.resume.reset.secondary')}
/> />
</ListItemButton> </ListItemButton>

View File

@ -17,7 +17,7 @@ const Sharing = () => {
const [showShortUrl, setShowShortUrl] = useState(false); 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 isPublic = useMemo(() => get(resume, 'public'), [resume]);
const url = useMemo(() => getResumeUrl(resume, { withHost: true }), [resume]); const url = useMemo(() => getResumeUrl(resume, { withHost: true }), [resume]);
const shortUrl = useMemo(() => getResumeUrl(resume, { withHost: true, shortUrl: true }), [resume]); const shortUrl = useMemo(() => getResumeUrl(resume, { withHost: true, shortUrl: true }), [resume]);

View File

@ -16,7 +16,7 @@ const Templates = () => {
const dispatch = useAppDispatch(); 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) => { const handleChange = (template: TemplateMeta) => {
dispatch(setResumeState({ path: 'metadata.template', value: template.id })); dispatch(setResumeState({ path: 'metadata.template', value: template.id }));
@ -31,7 +31,7 @@ const Templates = () => {
<div key={template.id} className={styles.template}> <div key={template.id} className={styles.template}>
<div className={clsx(styles.preview, { [styles.selected]: template.id === currentTemplate })}> <div className={clsx(styles.preview, { [styles.selected]: template.id === currentTemplate })}>
<ButtonBase onClick={() => handleChange(template)}> <ButtonBase onClick={() => handleChange(template)}>
<Image src={template.preview} alt={template.name} className="rounded-sm" layout="fill" /> <Image src={template.preview} alt={template.name} className="rounded-sm" layout="fill" priority />
</ButtonBase> </ButtonBase>
</div> </div>

View File

@ -1,8 +1,8 @@
.container { .container {
@apply grid sm:grid-cols-2 gap-4; @apply grid gap-4 sm:grid-cols-2;
} }
.colorOptions { .colorOptions {
@apply col-span-2 mb-4; @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;
} }

View File

@ -1,4 +1,4 @@
import { Theme as ThemeType } from '@reactive-resume/schema'; import { ThemeConfig } from '@reactive-resume/schema';
import get from 'lodash/get'; import get from 'lodash/get';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
@ -16,7 +16,9 @@ const Theme = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { background, text, primary } = useAppSelector<ThemeType>((state) => get(state.resume, 'metadata.theme')); const { background, text, primary } = useAppSelector<ThemeConfig>((state) =>
get(state.resume.present, 'metadata.theme')
);
const handleChange = (property: string, color: string) => { const handleChange = (property: string, color: string) => {
dispatch(setResumeState({ path: `metadata.theme.${property}`, value: color[0] !== '#' ? `#${color}` : color })); dispatch(setResumeState({ path: `metadata.theme.${property}`, value: color[0] !== '#' ? `#${color}` : color }));

View File

@ -33,7 +33,7 @@ const Widgets: React.FC<WidgetProps> = ({ label, category }) => {
const dispatch = useAppDispatch(); 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, { const { data: fonts } = useQuery(FONTS_QUERY, fetchFonts, {
select: (fonts) => fonts.sort((a, b) => a.category.localeCompare(b.category)), select: (fonts) => fonts.sort((a, b) => a.category.localeCompare(b.category)),

View File

@ -1,9 +1,9 @@
.testimony { .testimony {
@apply grid gap-2; @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 { blockquote {
@apply text-xs leading-normal text-justify opacity-90; @apply text-justify text-xs leading-normal opacity-90;
} }
figcaption { figcaption {

View File

@ -10,7 +10,7 @@
} }
.header { .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 flex items-center justify-between;
@apply w-full border-b pb-5 dark:border-white/10; @apply w-full border-b pb-5 dark:border-white/10;
@ -33,7 +33,7 @@
} }
.footer { .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 flex items-center justify-end gap-x-4;
@apply w-full border-t pt-5 dark:border-white/10; @apply w-full border-t pt-5 dark:border-white/10;
} }

View File

@ -32,8 +32,8 @@ const Heading: React.FC<Props> = ({
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const heading = useAppSelector((state) => get(state.resume, `${path}.name`, name)); const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`, name));
const visibility = useAppSelector((state) => get(state.resume, `${path}.visible`, true)); const visibility = useAppSelector((state) => get(state.resume.present, `${path}.visible`, true));
const [editMode, setEditMode] = useState(false); const [editMode, setEditMode] = useState(false);

View File

@ -9,5 +9,5 @@
} }
.language { .language {
@apply py-2 px-4 cursor-pointer text-center hover:underline; @apply cursor-pointer py-2 px-4 text-center hover:underline;
} }

View File

@ -36,7 +36,7 @@ const List: React.FC<Props> = ({
const dispatch = useAppDispatch(); 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) => { const handleEdit = (item: ListItemType) => {
isFunction(onEdit) && onEdit(item); isFunction(onEdit) && onEdit(item);

View File

@ -5,6 +5,7 @@ import styles from './Loading.module.scss';
const Loading: React.FC = () => { const Loading: React.FC = () => {
const { isReady } = useRouter(); const { isReady } = useRouter();
const isFetching = useIsFetching(); const isFetching = useIsFetching();
const isMutating = useIsMutating(); const isMutating = useIsMutating();

View File

@ -21,7 +21,7 @@ interface Props {
const ResumeInput: React.FC<Props> = ({ type = 'text', label, path, className, markdownSupported = false }) => { const ResumeInput: React.FC<Props> = ({ type = 'text', label, path, className, markdownSupported = false }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const stateValue = useAppSelector((state) => get(state.resume, path, '')); const stateValue = useAppSelector((state) => get(state.resume.present, path, ''));
useEffect(() => { useEffect(() => {
setValue(stateValue); setValue(stateValue);
@ -63,7 +63,7 @@ const ResumeInput: React.FC<Props> = ({ type = 'text', label, path, className, m
renderInput={(params) => <TextField {...params} error={false} className={className} />} renderInput={(params) => <TextField {...params} error={false} className={className} />}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => { onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && onChangeValue(''); isEmpty(keyboardInputValue) && onChangeValue('');
date && dayjs(date).isValid() && onChangeValue(date.toISOString()); date && dayjs(date).utc().isValid() && onChangeValue(dayjs(date).utc().toISOString());
}} }}
/> />
); );

View File

@ -2,34 +2,48 @@ export type Language = {
code: string; code: string;
name: string; name: string;
localName?: string; localName?: string;
isRTL?: boolean;
}; };
export const languages: Language[] = [ export const languages: Language[] = [
{ code: 'ar', name: 'Arabic', localName: 'اَلْعَرَبِيَّةُ' }, { code: 'am', name: 'Amharic', localName: 'አማርኛ' },
{ code: 'ar', name: 'Arabic', localName: 'اَلْعَرَبِيَّةُ', isRTL: true },
{ code: 'bg', name: 'Bulgarian', localName: 'български' },
{ code: 'bn', name: 'Bengali', localName: 'বাংলা' }, { code: 'bn', name: 'Bengali', localName: 'বাংলা' },
{ code: 'ca', name: 'Catalan', localName: 'Valencian' },
{ code: 'cs', name: 'Czech', localName: 'čeština' }, { code: 'cs', name: 'Czech', localName: 'čeština' },
{ code: 'da', name: 'Danish', localName: 'Dansk' }, { code: 'da', name: 'Danish', localName: 'Dansk' },
{ code: 'de', name: 'German', localName: 'Deutsch' }, { code: 'de', name: 'German', localName: 'Deutsch' },
{ code: 'el', name: 'Greek', localName: 'Ελληνικά' }, { code: 'el', name: 'Greek', localName: 'Ελληνικά' },
{ code: 'en', name: 'English' }, { code: 'en', name: 'English' },
{ code: 'es', name: 'Spanish', localName: 'Español' }, { code: 'es', name: 'Spanish', localName: 'Español' },
{ code: 'fa', name: 'Persian', localName: 'فارسی', isRTL: true },
{ code: 'fi', name: 'Finnish', localName: 'Suomi' },
{ code: 'fr', name: 'French', localName: 'Français' }, { code: 'fr', name: 'French', localName: 'Français' },
{ code: 'he', name: 'Hebrew', localName: 'Ivrit' }, { code: 'he', name: 'Hebrew', localName: 'Ivrit', isRTL: true },
{ code: 'hi', name: 'Hindi', localName: 'हिन्दी' }, { code: 'hi', name: 'Hindi', localName: 'हिन्दी' },
{ code: 'hu', name: 'Hungarian', localName: 'Magyar' }, { code: 'hu', name: 'Hungarian', localName: 'Magyar' },
{ code: 'id', name: 'Indonesian', localName: 'Bahasa Indonesia' }, { code: 'id', name: 'Indonesian', localName: 'Bahasa Indonesia' },
{ code: 'it', name: 'Italian', localName: 'Italiano' }, { code: 'it', name: 'Italian', localName: 'Italiano' },
{ code: 'ja', name: 'Japanese', localName: '日本語' },
{ code: 'km', name: 'Khmer', localName: 'ភាសាខ្មែរ' },
{ code: 'kn', name: 'Kannada', localName: 'ಕನ್ನಡ' }, { code: 'kn', name: 'Kannada', localName: 'ಕನ್ನಡ' },
{ code: 'ko', name: 'Korean', localName: '한국어' },
{ code: 'ml', name: 'Malayalam', localName: 'മലയാളം' }, { code: 'ml', name: 'Malayalam', localName: 'മലയാളം' },
{ code: 'mr', name: 'Marathi', localName: 'मराठी' },
{ code: 'ne', name: 'Nepali', localName: 'नेपाली' },
{ code: 'nl', name: 'Dutch', localName: 'Nederlands' }, { code: 'nl', name: 'Dutch', localName: 'Nederlands' },
{ code: 'no', name: 'Norwegian', localName: 'Norsk' },
{ code: 'or', name: 'Odia', localName: 'ଓଡ଼ିଆ' }, { code: 'or', name: 'Odia', localName: 'ଓଡ଼ିଆ' },
{ code: 'fa', name: 'Persian', localName: 'Farsi' },
{ code: 'pl', name: 'Polish', localName: 'Polski' }, { code: 'pl', name: 'Polish', localName: 'Polski' },
{ code: 'pt', name: 'Portuguese', localName: 'Português' }, { code: 'pt', name: 'Portuguese', localName: 'Português' },
{ code: 'ro', name: 'Romanian', localName: 'limba română' },
{ code: 'ru', name: 'Russian', localName: 'русский' }, { code: 'ru', name: 'Russian', localName: 'русский' },
{ code: 'sr', name: 'Serbian', localName: 'српски језик' },
{ code: 'sv', name: 'Swedish', localName: 'Svenska' }, { code: 'sv', name: 'Swedish', localName: 'Svenska' },
{ code: 'ta', name: 'Tamil', localName: 'தமிழ்' }, { code: 'ta', name: 'Tamil', localName: 'தமிழ்' },
{ code: 'tr', name: 'Turkish', localName: 'Türkçe' }, { code: 'tr', name: 'Turkish', localName: 'Türkçe' },
{ code: 'uk', name: 'Ukranian', localName: 'Українська мова' },
{ code: 'vi', name: 'Vietnamese', localName: 'Tiếng Việt' }, { code: 'vi', name: 'Vietnamese', localName: 'Tiếng Việt' },
{ code: 'zh', name: 'Chinese', localName: '中文' }, { code: 'zh', name: 'Chinese', localName: '中文' },
].sort((a, b) => a.name.localeCompare(b.name)); ].sort((a, b) => a.name.localeCompare(b.name));

View File

@ -1,6 +1,6 @@
import { createTheme } from '@mui/material'; import { createTheme, ThemeOptions } from '@mui/material/styles';
const theme = createTheme({ const theme: ThemeOptions = {
typography: { typography: {
fontSize: 12, fontSize: 12,
fontFamily: 'Inter, sans-serif', fontFamily: 'Inter, sans-serif',
@ -49,7 +49,7 @@ const theme = createTheme({
}, },
}, },
}, },
}); };
export const lightTheme = createTheme({ export const lightTheme = createTheme({
...theme, ...theme,

View File

@ -9,6 +9,7 @@ export const VALID_URL_REGEX = /[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}
export const FILENAME_TIMESTAMP = 'DDMMYYYYHHmmss'; export const FILENAME_TIMESTAMP = 'DDMMYYYYHHmmss';
// Links // Links
export const DOCS_URL = 'https://docs.rxresu.me';
export const DONATION_URL = 'https://paypal.me/RajaRajanA'; export const DONATION_URL = 'https://paypal.me/RajaRajanA';
export const TRANSLATE_URL = 'https://translate.rxresu.me/'; export const TRANSLATE_URL = 'https://translate.rxresu.me/';
export const DIGITALOCEAN_URL = 'https://pillai.xyz/digitalocean'; export const DIGITALOCEAN_URL = 'https://pillai.xyz/digitalocean';

View File

@ -1,7 +1,8 @@
import env from '@beam-australia/react-env'; import env from '@beam-australia/react-env';
import { joiResolver } from '@hookform/resolvers/joi'; 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 { Button, IconButton, InputAdornment, TextField } from '@mui/material';
import { CredentialResponse, GoogleLogin } from '@react-oauth/google';
import Joi from 'joi'; import Joi from 'joi';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { Trans, useTranslation } from 'next-i18next'; import { Trans, useTranslation } from 'next-i18next';
@ -17,8 +18,6 @@ import { ServerError } from '@/services/axios';
import { useAppDispatch, useAppSelector } from '@/store/hooks'; import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice'; import { setModalState } from '@/store/modal/modalSlice';
declare const google: any;
type FormData = { type FormData = {
identifier: string; identifier: string;
password: string; password: string;
@ -85,26 +84,16 @@ const LoginModal: React.FC = () => {
dispatch(setModalState({ modal: 'auth.forgot', state: { open: true } })); dispatch(setModalState({ modal: 'auth.forgot', state: { open: true } }));
}; };
const handleLoginWithGoogle = async () => { const handleLoginWithGoogle = async (response: CredentialResponse) => {
google.accounts.id.initialize({ if (response.credential) {
auto_select: true, await loginWithGoogleMutation({ credential: response.credential }, { onError: handleLoginWithGoogleError });
itp_support: true,
client_id: env('GOOGLE_CLIENT_ID'),
callback: async (response: any) => {
await loginWithGoogleMutation({ credential: response.credential });
handleClose(); handleClose();
},
});
google.accounts.id.prompt((notification: any) => {
if (notification.isNotDisplayed() || notification.isSkippedMoment()) {
const reason = notification.getNotDisplayedReason() || notification.getSkippedReason();
toast.error(`Google returned an error while trying to sign in: ${reason}.`);
toast("Please try logging in using email/password, or use another browser that supports Google's One Tap API.");
} }
}); };
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 => { const PasswordVisibility = (): React.ReactElement => {
@ -128,15 +117,7 @@ const LoginModal: React.FC = () => {
footerChildren={ footerChildren={
<div className="flex gap-4"> <div className="flex gap-4">
{!isEmpty(env('GOOGLE_CLIENT_ID')) && ( {!isEmpty(env('GOOGLE_CLIENT_ID')) && (
<Button <GoogleLogin onSuccess={handleLoginWithGoogle} onError={handleLoginWithGoogleError} />
type="submit"
variant="outlined"
disabled={isLoading}
startIcon={<Google />}
onClick={handleLoginWithGoogle}
>
{t<string>('modals.auth.login.actions.google')}
</Button>
)} )}
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}> <Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}>
@ -181,15 +162,14 @@ const LoginModal: React.FC = () => {
{!FLAG_DISABLE_SIGNUPS && ( {!FLAG_DISABLE_SIGNUPS && (
<p className="text-xs"> <p className="text-xs">
<Trans t={t} i18nKey="modals.auth.login.register-text"> <Trans t={t} i18nKey="modals.auth.login.register-text">
If you don&apos;t have one, you can <a onClick={handleCreateAccount}>create an account</a> here. If you don&apos;t have one, you can <a onClick={handleCreateAccount}>create an account here.</a>
</Trans> </Trans>
</p> </p>
)} )}
<p className="text-xs"> <p className="text-xs">
<Trans t={t} i18nKey="modals.auth.login.recover-text"> <Trans t={t} i18nKey="modals.auth.login.recover-text">
In case you have forgotten your password, you can <a onClick={handleRecoverAccount}>recover your account</a> In case you have forgotten your password, you can <a onClick={handleRecoverAccount}>recover your account here.</a>
here.
</Trans> </Trans>
</p> </p>
</BaseModal> </BaseModal>

View File

@ -1,11 +1,13 @@
import env from '@beam-australia/react-env'; import env from '@beam-australia/react-env';
import { joiResolver } from '@hookform/resolvers/joi'; 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 { Button, TextField } from '@mui/material';
import { CredentialResponse, GoogleLogin } from '@react-oauth/google';
import Joi from 'joi'; import Joi from 'joi';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { Trans, useTranslation } from 'next-i18next'; import { Trans, useTranslation } from 'next-i18next';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
import { useMutation } from 'react-query'; import { useMutation } from 'react-query';
import BaseModal from '@/components/shared/BaseModal'; import BaseModal from '@/components/shared/BaseModal';
@ -14,8 +16,6 @@ import { ServerError } from '@/services/axios';
import { useAppDispatch, useAppSelector } from '@/store/hooks'; import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice'; import { setModalState } from '@/store/modal/modalSlice';
declare const google: any;
type FormData = { type FormData = {
name: string; name: string;
username: string; username: string;
@ -79,18 +79,16 @@ const RegisterModal: React.FC = () => {
dispatch(setModalState({ modal: 'auth.login', state: { open: true } })); dispatch(setModalState({ modal: 'auth.login', state: { open: true } }));
}; };
const handleLoginWithGoogle = async () => { const handleLoginWithGoogle = async (response: CredentialResponse) => {
google.accounts.id.initialize({ if (response.credential) {
client_id: env('GOOGLE_CLIENT_ID'), await loginWithGoogleMutation({ credential: response.credential }, { onError: handleLoginWithGoogleError });
callback: async (response: any) => {
await loginWithGoogleMutation({ credential: response.credential });
handleClose(); handleClose();
}, }
auto_select: false, };
});
google.accounts.id.prompt(); const handleLoginWithGoogleError = () => {
toast("Please try logging in using email/password, or use another browser that supports Google's One Tap API.");
}; };
return ( return (
@ -102,15 +100,7 @@ const RegisterModal: React.FC = () => {
footerChildren={ footerChildren={
<div className="flex gap-4"> <div className="flex gap-4">
{!isEmpty(env('GOOGLE_CLIENT_ID')) && ( {!isEmpty(env('GOOGLE_CLIENT_ID')) && (
<Button <GoogleLogin onSuccess={handleLoginWithGoogle} onError={handleLoginWithGoogleError} />
type="submit"
variant="outlined"
disabled={isLoading}
startIcon={<Google />}
onClick={handleLoginWithGoogle}
>
{t<string>('modals.auth.register.actions.google')}
</Button>
)} )}
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}> <Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}>

View File

@ -44,7 +44,7 @@ const AwardModal: React.FC = () => {
const dispatch = useAppDispatch(); 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 { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
const item: FormData = get(payload, 'item', null); const item: FormData = get(payload, 'item', null);
@ -93,7 +93,7 @@ const AwardModal: React.FC = () => {
heading={isEditMode ? editText : addText} heading={isEditMode ? editText : addText}
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
> >
<form className="my-2 grid grid-cols-2 gap-4"> <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
<Controller <Controller
name="title" name="title"
control={control} control={control}
@ -134,7 +134,7 @@ const AwardModal: React.FC = () => {
views={['year', 'month', 'day']} views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => { onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange(''); 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) => ( renderInput={(params) => (
<TextField <TextField
@ -177,6 +177,7 @@ const AwardModal: React.FC = () => {
/> />
)} )}
/> />
<input type="submit" style={{ display: 'none' }} />
</form> </form>
</BaseModal> </BaseModal>
); );

View File

@ -44,7 +44,7 @@ const CertificateModal: React.FC = () => {
const dispatch = useAppDispatch(); 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 { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
const item: FormData = get(payload, 'item', null); const item: FormData = get(payload, 'item', null);
@ -93,7 +93,7 @@ const CertificateModal: React.FC = () => {
heading={isEditMode ? editText : addText} heading={isEditMode ? editText : addText}
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
> >
<form className="my-2 grid grid-cols-2 gap-4"> <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
<Controller <Controller
name="name" name="name"
control={control} control={control}
@ -134,7 +134,7 @@ const CertificateModal: React.FC = () => {
views={['year', 'month', 'day']} views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => { onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange(''); 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) => ( renderInput={(params) => (
<TextField <TextField
@ -177,6 +177,7 @@ const CertificateModal: React.FC = () => {
/> />
)} )}
/> />
<input type="submit" style={{ display: 'none' }} />
</form> </form>
</BaseModal> </BaseModal>
); );

View File

@ -60,7 +60,7 @@ const CustomModal: React.FC = () => {
const dispatch = useAppDispatch(); 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 { open: isOpen, payload } = useAppSelector((state) => state.modal['builder.sections.custom']);
const path: string = get(payload, 'path', ''); const path: string = get(payload, 'path', '');
@ -110,7 +110,7 @@ const CustomModal: React.FC = () => {
heading={isEditMode ? editText : addText} heading={isEditMode ? editText : addText}
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
> >
<form className="my-2 grid grid-cols-2 gap-4"> <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
<Controller <Controller
name="title" name="title"
control={control} control={control}
@ -150,7 +150,7 @@ const CustomModal: React.FC = () => {
views={['year', 'month', 'day']} views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => { onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange(''); 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) => ( renderInput={(params) => (
<TextField <TextField
@ -174,7 +174,7 @@ const CustomModal: React.FC = () => {
views={['year', 'month', 'day']} views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => { onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange(''); 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) => ( renderInput={(params) => (
<TextField <TextField
@ -282,6 +282,7 @@ const CustomModal: React.FC = () => {
/> />
)} )}
/> />
<input type="submit" style={{ display: 'none' }} />
</form> </form>
</BaseModal> </BaseModal>
); );

View File

@ -57,7 +57,7 @@ const EducationModal: React.FC = () => {
const dispatch = useAppDispatch(); 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 { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
const item: FormData = get(payload, 'item', null); const item: FormData = get(payload, 'item', null);
@ -106,7 +106,7 @@ const EducationModal: React.FC = () => {
heading={isEditMode ? editText : addText} heading={isEditMode ? editText : addText}
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
> >
<form className="my-2 grid grid-cols-2 gap-4"> <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
<Controller <Controller
name="institution" name="institution"
control={control} control={control}
@ -173,7 +173,7 @@ const EducationModal: React.FC = () => {
views={['year', 'month', 'day']} views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => { onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange(''); 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) => ( renderInput={(params) => (
<TextField <TextField
@ -197,7 +197,7 @@ const EducationModal: React.FC = () => {
views={['year', 'month', 'day']} views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => { onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange(''); 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) => ( renderInput={(params) => (
<TextField <TextField
@ -255,6 +255,7 @@ const EducationModal: React.FC = () => {
/> />
)} )}
/> />
<input type="submit" style={{ display: 'none' }} />
</form> </form>
</BaseModal> </BaseModal>
); );

View File

@ -35,7 +35,7 @@ const InterestModal: React.FC = () => {
const dispatch = useAppDispatch(); 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 { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
const item: FormData = get(payload, 'item', null); const item: FormData = get(payload, 'item', null);
@ -84,7 +84,7 @@ const InterestModal: React.FC = () => {
heading={isEditMode ? editText : addText} heading={isEditMode ? editText : addText}
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
> >
<form className="my-2 grid grid-cols-2 gap-4"> <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
<Controller <Controller
name="name" name="name"
control={control} control={control}
@ -114,6 +114,7 @@ const InterestModal: React.FC = () => {
/> />
)} )}
/> />
<input type="submit" style={{ display: 'none' }} />
</form> </form>
</BaseModal> </BaseModal>
); );

View File

@ -36,7 +36,7 @@ const LanguageModal: React.FC = () => {
const dispatch = useAppDispatch(); 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 { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
const item: FormData = get(payload, 'item', null); const item: FormData = get(payload, 'item', null);
@ -85,7 +85,7 @@ const LanguageModal: React.FC = () => {
heading={isEditMode ? editText : addText} heading={isEditMode ? editText : addText}
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
> >
<form className="my-2 grid grid-cols-2 gap-4"> <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
<Controller <Controller
name="name" name="name"
control={control} control={control}
@ -150,6 +150,7 @@ const LanguageModal: React.FC = () => {
</div> </div>
)} )}
/> />
<input type="submit" style={{ display: 'none' }} />
</form> </form>
</BaseModal> </BaseModal>
); );

View File

@ -89,7 +89,7 @@ const ProfileModal: React.FC = () => {
handleClose={handleClose} handleClose={handleClose}
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
> >
<form className="my-2 grid grid-cols-2 gap-4"> <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
<Controller <Controller
name="network" name="network"
control={control} control={control}
@ -136,6 +136,7 @@ const ProfileModal: React.FC = () => {
/> />
)} )}
/> />
<input type="submit" style={{ display: 'none' }} />
</form> </form>
</BaseModal> </BaseModal>
); );

View File

@ -53,7 +53,7 @@ const ProjectModal: React.FC = () => {
const dispatch = useAppDispatch(); 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 { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
const item: FormData = get(payload, 'item', null); const item: FormData = get(payload, 'item', null);
@ -102,7 +102,7 @@ const ProjectModal: React.FC = () => {
heading={isEditMode ? editText : addText} heading={isEditMode ? editText : addText}
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
> >
<form className="my-2 grid grid-cols-2 gap-4"> <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
<Controller <Controller
name="name" name="name"
control={control} control={control}
@ -143,7 +143,7 @@ const ProjectModal: React.FC = () => {
views={['year', 'month', 'day']} views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => { onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange(''); 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) => ( renderInput={(params) => (
<TextField <TextField
@ -167,7 +167,7 @@ const ProjectModal: React.FC = () => {
views={['year', 'month', 'day']} views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => { onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange(''); 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) => ( renderInput={(params) => (
<TextField <TextField
@ -225,6 +225,7 @@ const ProjectModal: React.FC = () => {
/> />
)} )}
/> />
<input type="submit" style={{ display: 'none' }} />
</form> </form>
</BaseModal> </BaseModal>
); );

View File

@ -44,7 +44,7 @@ const PublicationModal: React.FC = () => {
const dispatch = useAppDispatch(); 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 { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
const item: FormData = get(payload, 'item', null); const item: FormData = get(payload, 'item', null);
@ -93,7 +93,7 @@ const PublicationModal: React.FC = () => {
heading={isEditMode ? editText : addText} heading={isEditMode ? editText : addText}
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
> >
<form className="my-2 grid grid-cols-2 gap-4"> <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
<Controller <Controller
name="name" name="name"
control={control} control={control}
@ -134,7 +134,7 @@ const PublicationModal: React.FC = () => {
views={['year', 'month', 'day']} views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => { onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange(''); 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) => ( renderInput={(params) => (
<TextField <TextField
@ -177,6 +177,7 @@ const PublicationModal: React.FC = () => {
/> />
)} )}
/> />
<input type="submit" style={{ display: 'none' }} />
</form> </form>
</BaseModal> </BaseModal>
); );

View File

@ -41,7 +41,7 @@ const ReferenceModal: React.FC = () => {
const dispatch = useAppDispatch(); 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 { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
const item: FormData = get(payload, 'item', null); const item: FormData = get(payload, 'item', null);
@ -90,7 +90,7 @@ const ReferenceModal: React.FC = () => {
heading={isEditMode ? editText : addText} heading={isEditMode ? editText : addText}
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
> >
<form className="my-2 grid grid-cols-2 gap-4"> <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
<Controller <Controller
name="name" name="name"
control={control} control={control}
@ -162,6 +162,7 @@ const ReferenceModal: React.FC = () => {
/> />
)} )}
/> />
<input type="submit" style={{ display: 'none' }} />
</form> </form>
</BaseModal> </BaseModal>
); );

View File

@ -39,7 +39,7 @@ const SkillModal: React.FC = () => {
const dispatch = useAppDispatch(); 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 { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
const item: FormData = get(payload, 'item', null); const item: FormData = get(payload, 'item', null);
@ -88,7 +88,7 @@ const SkillModal: React.FC = () => {
heading={isEditMode ? editText : addText} heading={isEditMode ? editText : addText}
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
> >
<form className="my-2 grid grid-cols-2 gap-4"> <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
<Controller <Controller
name="name" name="name"
control={control} control={control}
@ -166,6 +166,8 @@ const SkillModal: React.FC = () => {
/> />
)} )}
/> />
<input type="submit" style={{ display: 'none' }} />
</form> </form>
</BaseModal> </BaseModal>
); );

View File

@ -50,7 +50,7 @@ const VolunteerModal: React.FC = () => {
const dispatch = useAppDispatch(); 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 { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
const item: FormData = get(payload, 'item', null); const item: FormData = get(payload, 'item', null);
@ -99,7 +99,7 @@ const VolunteerModal: React.FC = () => {
heading={isEditMode ? editText : addText} heading={isEditMode ? editText : addText}
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
> >
<form className="my-2 grid grid-cols-2 gap-4"> <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
<Controller <Controller
name="organization" name="organization"
control={control} control={control}
@ -140,7 +140,7 @@ const VolunteerModal: React.FC = () => {
views={['year', 'month', 'day']} views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => { onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange(''); 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) => ( renderInput={(params) => (
<TextField <TextField
@ -164,7 +164,7 @@ const VolunteerModal: React.FC = () => {
views={['year', 'month', 'day']} views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => { onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange(''); 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) => ( renderInput={(params) => (
<TextField <TextField
@ -208,6 +208,7 @@ const VolunteerModal: React.FC = () => {
/> />
)} )}
/> />
<input type="submit" style={{ display: 'none' }} />
</form> </form>
</BaseModal> </BaseModal>
); );

View File

@ -50,7 +50,7 @@ const WorkModal: React.FC = () => {
const dispatch = useAppDispatch(); 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 { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]);
const item: FormData = get(payload, 'item', null); const item: FormData = get(payload, 'item', null);
@ -99,7 +99,7 @@ const WorkModal: React.FC = () => {
heading={isEditMode ? editText : addText} heading={isEditMode ? editText : addText}
footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>}
> >
<form className="my-2 grid grid-cols-2 gap-4"> <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}>
<Controller <Controller
name="name" name="name"
control={control} control={control}
@ -140,7 +140,7 @@ const WorkModal: React.FC = () => {
views={['year', 'month', 'day']} views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => { onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange(''); 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) => ( renderInput={(params) => (
<TextField <TextField
@ -164,7 +164,7 @@ const WorkModal: React.FC = () => {
views={['year', 'month', 'day']} views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => { onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange(''); 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) => ( renderInput={(params) => (
<TextField <TextField
@ -208,6 +208,7 @@ const WorkModal: React.FC = () => {
/> />
)} )}
/> />
<input type="submit" style={{ display: 'none' }} />
</form> </form>
</BaseModal> </BaseModal>
); );

View File

@ -69,7 +69,8 @@ const CreateResumeModal: React.FC = () => {
try { try {
await mutateAsync({ name, slug, public: isPublic }); await mutateAsync({ name, slug, public: isPublic });
queryClient.invalidateQueries(RESUMES_QUERY); await queryClient.invalidateQueries(RESUMES_QUERY);
handleClose(); handleClose();
} catch (error: any) { } catch (error: any) {
toast.error(error.message); toast.error(error.message);

View File

@ -4,8 +4,11 @@ const i18nConfig = {
i18n: { i18n: {
defaultLocale: 'en', defaultLocale: 'en',
locales: [ locales: [
'am',
'ar', 'ar',
'bg',
'bn', 'bn',
'ca',
'cs', 'cs',
'da', 'da',
'de', 'de',
@ -13,22 +16,32 @@ const i18nConfig = {
'en', 'en',
'es', 'es',
'fa', 'fa',
'fi',
'fr', 'fr',
'he', 'he',
'hi', 'hi',
'hu', 'hu',
'id', 'id',
'it', 'it',
'ja',
'km',
'kn', 'kn',
'ko',
'ml', 'ml',
'mr',
'ne',
'nl', 'nl',
'no',
'or', 'or',
'pl', 'pl',
'pt', 'pt',
'ro',
'ru', 'ru',
'sr',
'sv', 'sv',
'ta', 'ta',
'tr', 'tr',
'uk',
'vi', 'vi',
'zh', 'zh',
], ],

View File

@ -15,19 +15,6 @@ const nextConfig = {
domains: ['cdn.rxresu.me', 'www.gravatar.com'], domains: ['cdn.rxresu.me', 'www.gravatar.com'],
}, },
async rewrites() {
if (process.env.NODE_ENV === 'development') {
return [
{
source: '/api/:path*',
destination: 'http://localhost:3100/:path*',
},
];
}
return [];
},
// Hack to make Tailwind darkMode 'class' strategy with CSS Modules // Hack to make Tailwind darkMode 'class' strategy with CSS Modules
// Ref: https://github.com/tailwindlabs/tailwindcss/issues/3258#issuecomment-968368156 // Ref: https://github.com/tailwindlabs/tailwindcss/issues/3258#issuecomment-968368156
webpack: (config) => { webpack: (config) => {

View File

@ -2,81 +2,83 @@
"name": "@reactive-resume/client", "name": "@reactive-resume/client",
"scripts": { "scripts": {
"dev": "react-env --prefix PUBLIC -- next dev", "dev": "react-env --prefix PUBLIC -- next dev",
"lint": "next lint --fix",
"build": "next build && npm run sitemap", "build": "next build && npm run sitemap",
"start": "react-env --prefix PUBLIC -- next start", "start": "react-env --prefix PUBLIC -- next start",
"lint": "next lint --fix",
"sitemap": "next-sitemap --config next-sitemap.config.js" "sitemap": "next-sitemap --config next-sitemap.config.js"
}, },
"dependencies": { "dependencies": {
"@beam-australia/react-env": "^3.1.1", "@beam-australia/react-env": "^3.1.1",
"@date-io/dayjs": "^2.14.0", "@date-io/dayjs": "^2.16.0",
"@emotion/css": "^11.9.0", "@emotion/css": "^11.10.0",
"@emotion/react": "^11.9.3", "@emotion/react": "^11.10.4",
"@emotion/styled": "^11.9.3", "@emotion/styled": "^11.10.4",
"@hookform/resolvers": "2.9.5", "@hello-pangea/dnd": "^16.0.1",
"@monaco-editor/react": "^4.4.5", "@hookform/resolvers": "2.9.9",
"@mui/icons-material": "^5.8.4", "@monaco-editor/react": "^4.4.6",
"@mui/lab": "^5.0.0-alpha.90", "@mui/icons-material": "^5.10.9",
"@mui/material": "^5.9.0", "@mui/lab": "^5.0.0-alpha.103",
"@mui/system": "^5.9.0", "@mui/material": "^5.10.9",
"@mui/x-date-pickers": "5.0.0-beta.0", "@mui/system": "^5.10.9",
"@next/env": "^12.2.2", "@mui/x-date-pickers": "5.0.4",
"@reduxjs/toolkit": "^1.8.3", "@next/env": "^12.3.1",
"axios": "^0.27.2", "@react-oauth/google": "^0.2.8",
"@reduxjs/toolkit": "^1.8.6",
"axios": "^1.1.2",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"dayjs": "^1.11.3", "dayjs": "^1.11.5",
"downloadjs": "^1.4.7", "downloadjs": "^1.4.7",
"joi": "^17.6.0", "joi": "^17.6.3",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"md5-hex": "^4.0.0", "md5-hex": "^4.0.0",
"monaco-editor": "^0.33.0", "monaco-editor": "^0.34.0",
"nanoid": "^3.3.4", "nanoid": "^3.3.4",
"next": "12.2.2", "next": "12.3.1",
"next-i18next": "^11.0.0", "next-i18next": "^12.1.0",
"react": "18.2.0", "react": "^18.2.0",
"react-beautiful-dnd": "^13.1.0", "react-colorful": "^5.6.1",
"react-colorful": "^5.5.1",
"react-dnd": "16.0.1", "react-dnd": "16.0.1",
"react-dnd-html5-backend": "16.0.1", "react-dnd-html5-backend": "16.0.1",
"react-dom": "18.2.0", "react-dom": "^18.2.0",
"react-hook-form": "^7.33.1", "react-hook-form": "^7.37.0",
"react-hot-toast": "2.2.0", "react-hot-toast": "2.4.0",
"react-hotkeys-hook": "^3.4.6", "react-hotkeys-hook": "^3.4.7",
"react-icons": "^4.4.0", "react-icons": "^4.6.0",
"react-markdown": "^8.0.3", "react-markdown": "^8.0.3",
"react-query": "^3.39.1", "react-query": "^3.39.2",
"react-redux": "^8.0.2", "react-redux": "^8.0.4",
"react-zoom-pan-pinch": "^2.1.3", "react-zoom-pan-pinch": "^2.1.3",
"redux": "^4.2.0", "redux": "^4.2.0",
"redux-persist": "^6.0.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", "remark-gfm": "^3.0.1",
"sharp": "^0.30.7", "sharp": "^0.31.1",
"uuid": "^8.3.2", "uuid": "^9.0.0",
"webfontloader": "^1.6.28" "webfontloader": "^1.6.28"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.18.6", "@babel/core": "^7.19.3",
"@reactive-resume/schema": "workspace:*", "@reactive-resume/schema": "workspace:*",
"@tailwindcss/typography": "^0.5.3", "eslint-plugin-unused-imports": "^2.0.0",
"@tailwindcss/typography": "^0.5.7",
"@types/downloadjs": "^1.4.3", "@types/downloadjs": "^1.4.3",
"@types/lodash": "^4.14.182", "@types/lodash": "^4.14.186",
"@types/node": "18.0.3", "@types/node": "^18.11.0",
"@types/react": "18.0.15", "@types/react": "^18.0.21",
"@types/react-beautiful-dnd": "^13.1.2", "@types/react-dom": "^18.0.6",
"@types/react-redux": "^7.1.24", "@types/react-redux": "^7.1.24",
"@types/tailwindcss": "^3.0.11", "@types/tailwindcss": "^3.0.11",
"@types/uuid": "^8.3.4", "@types/uuid": "^8.3.4",
"@types/webfontloader": "^1.6.34", "@types/webfontloader": "^1.6.35",
"autoprefixer": "^10.4.7", "autoprefixer": "^10.4.12",
"csstype": "^3.1.0", "csstype": "^3.1.1",
"eslint": "^8.19.0", "eslint-config-next": "^12.3.1",
"eslint-config-next": "12.2.2", "eslint-plugin-tailwindcss": "^3.6.2",
"next-sitemap": "^3.1.10", "next-sitemap": "^3.1.25",
"postcss": "^8.4.14", "postcss": "^8.4.18",
"prettier": "^2.7.1", "sass": "^1.55.0",
"sass": "^1.53.0", "tailwindcss": "^3.1.8",
"tailwindcss": "^3.1.6", "typescript": "^4.8.4"
"typescript": "^4.7.4"
} }
} }

View File

@ -51,7 +51,7 @@ const Preview: NextPage<Props> = ({ username, slug, resume: initialData }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const resume = useAppSelector((state) => state.resume); const resume = useAppSelector((state) => state.resume.present);
useEffect(() => { useEffect(() => {
if (initialData && !isEmpty(initialData)) { if (initialData && !isEmpty(initialData)) {
@ -98,7 +98,7 @@ const Preview: NextPage<Props> = ({ username, slug, resume: initialData }) => {
try { try {
const url = await mutateAsync({ username, slug }); const url = await mutateAsync({ username, slug });
download(`/api${url}`); download(url);
} catch { } catch {
toast.error('Something went wrong, please try again later.'); toast.error('Something went wrong, please try again later.');
} }

View File

@ -59,18 +59,20 @@ const Printer: NextPage<Props> = ({ resume: initialData, locale }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const resume = useAppSelector((state) => state.resume); const resume = useAppSelector((state) => state.resume.present);
useEffect(() => {
if (router.locale !== locale) {
const { pathname, asPath, query } = router;
router.push({ pathname, query }, asPath, { locale });
}
}, [router, locale]);
useEffect(() => { useEffect(() => {
if (initialData) dispatch(setResume(initialData)); if (initialData) dispatch(setResume(initialData));
}, [dispatch, initialData]); }, [dispatch, initialData]);
useEffect(() => {
const { pathname, asPath, query } = router;
router.push({ pathname, query }, asPath, { locale });
}, [router, locale]);
if (!resume || isEmpty(resume)) return null; if (!resume || isEmpty(resume)) return null;
const layout: string[][][] = get(resume, 'metadata.layout', []); const layout: string[][][] = get(resume, 'metadata.layout', []);

View File

@ -1,10 +1,11 @@
import '@/styles/globals.scss'; import '@/styles/globals.scss';
import env from '@beam-australia/react-env';
import DayjsAdapter from '@date-io/dayjs'; import DayjsAdapter from '@date-io/dayjs';
import { LocalizationProvider } from '@mui/x-date-pickers'; import { LocalizationProvider } from '@mui/x-date-pickers';
import { GoogleOAuthProvider } from '@react-oauth/google';
import type { AppProps } from 'next/app'; import type { AppProps } from 'next/app';
import Head from 'next/head'; import Head from 'next/head';
import Script from 'next/script';
import { appWithTranslation } from 'next-i18next'; import { appWithTranslation } from 'next-i18next';
import { Toaster } from 'react-hot-toast'; import { Toaster } from 'react-hot-toast';
import { QueryClientProvider } from 'react-query'; import { QueryClientProvider } from 'react-query';
@ -17,8 +18,7 @@ import queryClient from '@/services/react-query';
import store, { persistor } from '@/store/index'; import store, { persistor } from '@/store/index';
import WrapperRegistry from '@/wrappers/index'; import WrapperRegistry from '@/wrappers/index';
const App: React.FC<AppProps> = ({ Component, pageProps }) => { const App = ({ Component, pageProps }: AppProps): JSX.Element => (
return (
<> <>
<Head> <Head>
<title>Reactive Resume</title> <title>Reactive Resume</title>
@ -34,6 +34,7 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
<ReduxProvider store={store}> <ReduxProvider store={store}>
<LocalizationProvider dateAdapter={DayjsAdapter}> <LocalizationProvider dateAdapter={DayjsAdapter}>
<PersistGate loading={null} persistor={persistor}> <PersistGate loading={null} persistor={persistor}>
<GoogleOAuthProvider clientId={env('GOOGLE_CLIENT_ID')}>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<WrapperRegistry> <WrapperRegistry>
<Loading /> <Loading />
@ -50,13 +51,11 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
/> />
</WrapperRegistry> </WrapperRegistry>
</QueryClientProvider> </QueryClientProvider>
</GoogleOAuthProvider>
</PersistGate> </PersistGate>
</LocalizationProvider> </LocalizationProvider>
</ReduxProvider> </ReduxProvider>
<Script src="https://accounts.google.com/gsi/client" async defer />
</> </>
); );
};
export default appWithTranslation(App); export default appWithTranslation(App);

View File

@ -4,7 +4,9 @@ import Head from 'next/head';
import Link from 'next/link'; import Link from 'next/link';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { useEffect } from 'react';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
import { ActionCreators } from 'redux-undo';
import ResumeCard from '@/components/dashboard/ResumeCard'; import ResumeCard from '@/components/dashboard/ResumeCard';
import ResumePreview from '@/components/dashboard/ResumePreview'; import ResumePreview from '@/components/dashboard/ResumePreview';
@ -12,6 +14,7 @@ import Avatar from '@/components/shared/Avatar';
import Logo from '@/components/shared/Logo'; import Logo from '@/components/shared/Logo';
import { RESUMES_QUERY } from '@/constants/index'; import { RESUMES_QUERY } from '@/constants/index';
import { fetchResumes } from '@/services/resume'; import { fetchResumes } from '@/services/resume';
import { useAppDispatch } from '@/store/hooks';
import styles from '@/styles/pages/Dashboard.module.scss'; import styles from '@/styles/pages/Dashboard.module.scss';
export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => { export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => {
@ -25,8 +28,14 @@ export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => {
const Dashboard: NextPage = () => { const Dashboard: NextPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch();
const { data } = useQuery(RESUMES_QUERY, fetchResumes); const { data } = useQuery(RESUMES_QUERY, fetchResumes);
useEffect(() => {
dispatch(ActionCreators.clearHistory());
}, []);
if (!data) return null; if (!data) return null;
return ( return (

View File

@ -20,7 +20,7 @@ import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice'; import { setModalState } from '@/store/modal/modalSlice';
import styles from '@/styles/pages/Home.module.scss'; import styles from '@/styles/pages/Home.module.scss';
import { DIGITALOCEAN_URL, DONATION_URL, GITHUB_URL } from '../constants'; import { DIGITALOCEAN_URL, DOCS_URL, DONATION_URL, GITHUB_URL } from '../constants';
export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => { export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => {
return { return {
@ -170,6 +170,12 @@ const Home: NextPage = () => {
</Button> </Button>
</a> </a>
<a href={DOCS_URL} target="_blank" rel="noreferrer">
<Button variant="text" startIcon={<LinkIcon />}>
{t<string>('landing.links.links.docs')}
</Button>
</a>
<a href={DONATION_URL} target="_blank" rel="noreferrer"> <a href={DONATION_URL} target="_blank" rel="noreferrer">
<Button variant="text" startIcon={<LinkIcon />}> <Button variant="text" startIcon={<LinkIcon />}>
{t<string>('landing.links.links.donate')} {t<string>('landing.links.links.donate')}
@ -180,7 +186,7 @@ const Home: NextPage = () => {
<section className={styles.section}> <section className={styles.section}>
<a href={DIGITALOCEAN_URL} target="_blank" rel="noreferrer"> <a href={DIGITALOCEAN_URL} target="_blank" rel="noreferrer">
<Image src="/images/sponsors/digitalocean.svg" alt="Powered By DigitalOcean" width={200} height={40} /> <Image src={`/images/sponsors/${theme=="dark"?"digitalocean":"digitaloceanLight"}.svg`} alt="Powered By DigitalOcean" width={200} height={40} />
</a> </a>
</section> </section>

View File

@ -1,7 +1,7 @@
import { NextPage } from 'next'; import { NextPage } from 'next';
const PrivacyPolicy: NextPage = () => ( 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> <h1>Privacy Policy</h1>
<p> <p>

View File

@ -1,7 +1,7 @@
import { NextPage } from 'next'; import { NextPage } from 'next';
const TermsOfService: NextPage = () => ( 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> <h1>Terms of Service</h1>
<p> <p>

View File

@ -71,7 +71,7 @@ const Preview: NextPage<Props> = ({ shortId }) => {
try { try {
const url = await mutateAsync({ username: resume.user.username, slug: resume.slug }); const url = await mutateAsync({ username: resume.user.username, slug: resume.slug });
download(`/api${url}`); download(url);
} catch { } catch {
toast.error('Something went wrong, please try again later.'); toast.error('Something went wrong, please try again later.');
} }

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 604 129" style="enable-background:new 0 0 604 129;" xml:space="preserve">
<style type="text/css">
.st0{fill:#0069ff;}
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#0069ff;}
</style>
<g>
<g>
<g>
<path class="st0" d="M174.3,3c4.9,0,8.7,2.9,8.7,8.6c0,5.6-3.8,8.5-8.7,8.5h-7.6v11.1h-3.5V3H174.3z M166.7,17.1h7.2
c3,0,5.6-1.8,5.6-5.5c0-3.8-2.5-5.5-5.6-5.5h-7.2V17.1z"/>
<path class="st0" d="M208.8,21.7c0,6.1-4.3,10-9.9,10c-5.6,0-9.9-3.9-9.9-10c0-6.1,4.3-10,9.9-10
C204.5,11.7,208.8,15.6,208.8,21.7z M192.3,21.7c0,4.5,2.9,7.2,6.6,7.2c3.7,0,6.6-2.7,6.6-7.2c0-4.5-2.9-7.1-6.6-7.1
C195.2,14.5,192.3,17.2,192.3,21.7z"/>
<path class="st0" d="M234.4,31.3l-5.2-13.8L224,31.3h-2.6L214.1,12h3.6l5.2,14l5.2-14h2.3l5.3,14l5.2-14h3.5L237,31.3H234.4z"/>
<path class="st0" d="M253,22.9c0.2,3.7,2.6,5.9,6,5.9c2.8,0,4.8-1.3,5.4-3.4l3.2,0.2c-0.8,3.5-4.1,6.1-8.6,6.1
c-5.5,0-9.6-3.7-9.6-10c0-6.3,4-10,9.5-10c5.5,0,8.8,3.7,8.8,9.4v1.8H253z M253,20.3h11.6c-0.1-3.4-2-5.7-5.6-5.7
C255.6,14.5,253.2,16.5,253,20.3z"/>
<path class="st0" d="M285.4,14.9c-3.4,0-5.6,2.3-5.6,5.3v11.1h-3.2V12h3.2v2.9c0.7-1.6,2.5-3.1,5.7-3.1V14.9z"/>
<path class="st0" d="M294.7,22.9c0.2,3.7,2.6,5.9,6,5.9c2.8,0,4.8-1.3,5.4-3.4l3.2,0.2c-0.8,3.5-4.1,6.1-8.6,6.1
c-5.5,0-9.6-3.7-9.6-10c0-6.3,4-10,9.5-10c5.5,0,8.8,3.7,8.8,9.4v1.8H294.7z M294.7,20.3h11.6c-0.1-3.4-2-5.7-5.6-5.7
C297.4,14.5,294.9,16.5,294.7,20.3z"/>
<path class="st0" d="M333.1,31.3v-3.1c-1.1,2-3.6,3.5-6.8,3.5c-5.3,0-9.3-3.8-9.3-10c0-6.2,4-10,9.3-10c3.2,0,5.6,1.4,6.6,3.2V2
h3.2v29.4H333.1z M320.3,21.7c0,4.6,2.8,7.2,6.5,7.2c3.6,0,6.2-2.2,6.2-6.6v-1.1c0-4.3-2.6-6.6-6.2-6.6
C323.1,14.5,320.3,17.1,320.3,21.7z"/>
<path class="st0" d="M361.8,14.9c1.1-1.9,3.4-3.2,6.7-3.2c5.3,0,9.3,3.8,9.3,10c0,6.2-4,10-9.3,10c-3.3,0-5.7-1.5-6.8-3.5v3.1
h-3.1V2h3.2V14.9z M361.9,21.1v1.1c0,4.4,2.6,6.6,6.2,6.6c3.7,0,6.5-2.5,6.5-7.2c0-4.6-2.8-7.1-6.5-7.1
C364.5,14.5,361.9,16.8,361.9,21.1z"/>
<path class="st0" d="M386.3,40.9l4.6-10.7L383.2,12h3.6l5.8,14.5l5.8-14.5h3.6l-12.2,28.9H386.3z"/>
</g>
</g>
<g id="XMLID_2369_">
<g>
<g id="XMLID_281_">
<g id="XMLID_282_">
<g>
<g id="XMLID_283_">
<g id="XMLID_287_">
<path id="XMLID_288_" class="st0" d="M64.4,127l0-24.2c25.6,0,45.5-25.4,35.7-52.3c-3.6-10-11.6-17.9-21.6-21.6
c-27-9.8-52.3,10-52.3,35.7c0,0,0,0,0,0L2,64.7C2,23.8,41.5-8,84.3,5.4c18.7,5.8,33.6,20.7,39.4,39.4
C137,87.6,105.2,127,64.4,127z"/>
</g>
<polygon id="XMLID_286_" class="st1" points="64.4,102.9 40.4,102.9 40.4,78.9 40.4,78.9 64.4,78.9 64.4,78.9 "/>
<polygon id="XMLID_285_" class="st1" points="40.3,121.5 21.8,121.5 21.8,121.5 21.8,102.9 40.4,102.9 40.4,121.5 "/>
<path id="XMLID_284_" class="st1" d="M21.9,102.9H6.3c0,0,0,0,0,0V87.4c0,0,0,0,0,0h15.5c0,0,0,0,0,0V102.9z"/>
</g>
</g>
</g>
</g>
<g id="XMLID_254_">
<path id="XMLID_278_" class="st0" d="M200.9,52.4c-5.5-3.8-12.4-5.8-20.5-5.8h-17.5v55.5h17.5c8,0,14.9-2.1,20.5-6.1
c3-2.1,5.4-5.1,7.1-8.9c1.7-3.7,2.5-8.2,2.5-13.1c0-4.9-0.8-9.3-2.5-13C206.3,57.4,203.9,54.4,200.9,52.4z M173.1,56h5.5
c6.1,0,11.1,1.2,15,3.6c4.2,2.6,6.4,7.4,6.4,14.4c0,7.2-2.2,12.3-6.4,15.1h0c-3.7,2.4-8.7,3.6-14.9,3.6h-5.6V56z"/>
<path id="XMLID_277_" class="st0" d="M222.6,45.9c-1.7,0-3.1,0.6-4.3,1.8c-1.2,1.1-1.8,2.6-1.8,4.2c0,1.7,0.6,3.1,1.8,4.3
c1.2,1.2,2.6,1.8,4.3,1.8c1.7,0,3.1-0.6,4.3-1.8c1.2-1.2,1.8-2.6,1.8-4.3c0-1.7-0.6-3.1-1.8-4.2
C225.7,46.5,224.3,45.9,222.6,45.9z"/>
<rect id="XMLID_276_" x="217.6" y="63" class="st0" width="9.8" height="39.1"/>
<path id="XMLID_273_" class="st0" d="M263.2,66.3c-3-2.6-6.3-4.2-9.9-4.2c-5.4,0-9.9,1.9-13.4,5.6c-3.5,3.7-5.3,8.4-5.3,14.1
c0,5.5,1.8,10.2,5.2,14c3.5,3.7,8,5.5,13.5,5.5c3.8,0,7.1-1.1,9.7-3.1V99c0,3.2-0.9,5.8-2.6,7.5c-1.7,1.7-4.1,2.6-7.1,2.6
c-4.5,0-7.4-1.8-10.9-6.5l-6.7,6.4l0.2,0.3c1.4,2,3.7,4,6.6,5.9c2.9,1.9,6.6,2.8,10.9,2.8c5.8,0,10.6-1.8,14.1-5.4
c3.5-3.6,5.3-8.4,5.3-14.2V63h-9.7V66.3z M260.6,89.4c-1.7,2-3.9,2.9-6.8,2.9c-2.8,0-5-0.9-6.7-2.9c-1.7-1.9-2.5-4.5-2.5-7.7
c0-3.2,0.9-5.8,2.5-7.7c1.7-1.9,3.9-2.9,6.7-2.9c2.8,0,5,1,6.8,2.9c1.7,2,2.6,4.6,2.6,7.7C263.2,84.9,262.3,87.5,260.6,89.4z"/>
<rect id="XMLID_272_" x="281.3" y="63" class="st0" width="9.8" height="39.1"/>
<path id="XMLID_271_" class="st0" d="M286.3,45.9c-1.7,0-3.1,0.6-4.3,1.8c-1.2,1.1-1.8,2.6-1.8,4.2c0,1.7,0.6,3.1,1.8,4.3
c1.2,1.2,2.6,1.8,4.3,1.8c1.7,0,3.1-0.6,4.3-1.8c1.2-1.2,1.8-2.6,1.8-4.3c0-1.7-0.6-3.1-1.8-4.2C289.4,46.5,288,45.9,286.3,45.9
z"/>
<path id="XMLID_270_" class="st0" d="M312.7,52.5H303V63h-5.6v9h5.6v16.2c0,5.1,1,8.7,3,10.8c2,2.1,5.6,3.2,10.6,3.2
c1.6,0,3.2-0.1,4.8-0.2l0.4,0v-9l-3.4,0.2c-2.3,0-3.9-0.4-4.7-1.2c-0.8-0.8-1.1-2.6-1.1-5.2V72h9.2v-9h-9.2V52.5z"/>
<rect id="XMLID_269_" x="368" y="46.6" class="st0" width="9.8" height="55.5"/>
<path id="XMLID_268_" class="st0" d="M477.3,88.2c-1.8,2-3.6,3.7-4.9,4.6v0c-1.4,0.9-3.1,1.3-5.1,1.3c-2.9,0-5.2-1.1-7.1-3.2
c-1.9-2.2-2.8-4.9-2.8-8.3s0.9-6.1,2.8-8.2c1.9-2.2,4.2-3.2,7.1-3.2c3.2,0,6.5,2,9.4,5.4l6.5-6.2l0,0c-4.2-5.5-9.7-8.1-16.1-8.1
c-5.4,0-10.1,2-13.9,5.8c-3.8,3.9-5.7,8.8-5.7,14.6s1.9,10.7,5.7,14.6c3.8,3.9,8.5,5.9,13.9,5.9c7.1,0,12.9-3.1,16.8-8.7
L477.3,88.2z"/>
<path id="XMLID_265_" class="st0" d="M517.7,68.5c-1.4-1.9-3.3-3.5-5.7-4.7c-2.3-1.1-5.1-1.7-8.1-1.7c-5.5,0-10,2-13.4,6
c-3.3,4-4.9,8.9-4.9,14.7c0,5.9,1.8,10.8,5.4,14.6c3.6,3.7,8.4,5.6,14.2,5.6c6.6,0,12.1-2.7,16.2-8l0.2-0.3l-6.4-6.2l0,0
c-0.6,0.7-1.4,1.5-2.2,2.3c-1,0.9-1.9,1.6-2.9,2.1c-1.5,0.7-3.1,1.1-5,1.1c-2.7,0-5-0.8-6.7-2.4c-1.6-1.5-2.6-3.5-2.8-5.9h26.1
l0.1-3.6c0-2.5-0.3-5-1-7.3C520.1,72.6,519.1,70.4,517.7,68.5z M496.2,77.7c0.5-1.9,1.3-3.4,2.6-4.6c1.3-1.3,3.1-2,5.2-2
c2.4,0,4.2,0.7,5.5,2c1.2,1.2,1.8,2.8,2,4.6H496.2z"/>
<path id="XMLID_262_" class="st0" d="M555.5,66L555.5,66c-3-2.5-7.1-3.8-12.3-3.8c-3.3,0-6.3,0.7-9.1,2.1
c-2.6,1.3-5.1,3.5-6.7,6.3l0.1,0.1l6.3,6c2.6-4.1,5.5-5.6,9.3-5.6c2.1,0,3.8,0.6,5.1,1.6c1.3,1.1,1.9,2.5,1.9,4.2v1.9
c-2.4-0.7-4.9-1.1-7.2-1.1c-4.9,0-8.9,1.2-11.8,3.4c-3,2.3-4.5,5.6-4.5,9.8c0,3.7,1.3,6.7,3.8,8.9c2.6,2.1,5.8,3.2,9.5,3.2
c3.7,0,7.3-1.5,10.4-4.1v3.2h9.7V77C560,72.2,558.5,68.5,555.5,66z M538,87.2c1.1-0.8,2.7-1.2,4.7-1.2c2.4,0,4.9,0.5,7.5,1.4
v3.8c-2.1,2-5,3-8.5,3c-1.7,0-3-0.4-3.9-1.1c-0.9-0.7-1.3-1.7-1.3-2.8C536.4,89,536.9,88,538,87.2z"/>
<path id="XMLID_261_" class="st0" d="M597.9,66.7c-2.7-3.1-6.6-4.6-11.5-4.6c-3.9,0-7.1,1.1-9.4,3.3V63h-9.7v39.1h9.8V80.6
c0-3,0.7-5.3,2.1-7c1.4-1.7,3.3-2.5,5.8-2.5c2.2,0,3.9,0.7,5.2,2.2c1.3,1.5,1.9,3.6,1.9,6.2v22.7h9.8V79.5
C602,74.1,600.6,69.8,597.9,66.7z"/>
<path id="XMLID_258_" class="st0" d="M355.6,66L355.6,66c-3-2.5-7.1-3.8-12.3-3.8c-3.3,0-6.3,0.7-9.1,2.1
c-2.6,1.3-5.1,3.5-6.7,6.3l0.1,0.1l6.3,6c2.6-4.1,5.5-5.6,9.3-5.6c2.1,0,3.8,0.6,5.1,1.6c1.3,1.1,1.9,2.5,1.9,4.2v1.9
c-2.4-0.7-4.9-1.1-7.2-1.1c-4.9,0-8.9,1.2-11.8,3.4c-3,2.3-4.5,5.6-4.5,9.8c0,3.7,1.3,6.7,3.8,8.9c2.6,2.1,5.8,3.2,9.5,3.2
c3.7,0,7.3-1.5,10.4-4.1v3.2h9.7V77C360.2,72.2,358.7,68.5,355.6,66z M338.2,87.2c1.1-0.8,2.7-1.2,4.7-1.2
c2.4,0,4.9,0.5,7.5,1.4v3.8c-2.1,2-5,3-8.5,3c-1.7,0-3-0.4-3.9-1.1c-0.9-0.7-1.3-1.7-1.3-2.8C336.6,89,337.1,88,338.2,87.2z"/>
<path id="XMLID_255_" class="st0" d="M413.6,103c-15.8,0-28.6-12.8-28.6-28.6s12.8-28.6,28.6-28.6s28.6,12.8,28.6,28.6
S429.4,103,413.6,103z M413.6,55.8c-10.2,0-18.5,8.3-18.5,18.5s8.3,18.5,18.5,18.5s18.5-8.3,18.5-18.5S423.8,55.8,413.6,55.8z"
/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@ -0,0 +1,368 @@
{
"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": "እርግጠኛ ነዎት ይህንn ማጥፋት ይፈልጋሉ? ይህ የማይመለስ ተግባር ነው።",
"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": "አሳንስ",
"undo": "ቀልብስ",
"redo": "ድገም"
}
},
"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": "መጠን (በፒክስል)"
}
},
"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": "ወደ Reactive Resume ተመልሶ ሊመጣ የሚችል የስራ ልምድ ሰነድ የJSON ቅጂዎትን ያውርዱ።"
},
"pdf": {
"loading": {
"primary": "PDF በመስራት ላይ",
"secondary": "እባክዎ PDF እስኪሰራ ይጠብቁ፣ ይህ እስከ 15 ሰከንድ ሊወስድ ይችላል።"
},
"normal": {
"primary": "PDF",
"secondary": "ማተም እና መላክ የሚችሉትን የስራ ልምድ ሰነድዎን በPDF ያውርዱ። ይህ ፋይል ለተጨማሪ አርትዖት ተመልሶ ሊመጣ አይችልም።"
}
}
},
"layout": {
"heading": "አቀማመጥ",
"tooltip": {
"reset-layout": "አቀማመጡን መልስ"
}
},
"links": {
"bugs-features": {
"body": "የስራ ልምድ ሰነድዎን ለመስራት የሚከለክሎት ነገር አለ? ወይም የሚጨምሩት አስደናቂ ሀሳብ አለዎት? በ GitHub ላይ ጉዳይዎን ያቅርቡ።",
"button": "GitHub ጉዳዮች",
"heading": "ችግሮች? የባህሪ ጥያቄዎች?"
},
"donate": {
"body": "Reactive Resumeን መጠቀም ከወደዱ እባክዎን መተግበሪያው እንዲሰራ እና ያለ ማስታወቂያ ሁሌም በነፃ እንዲቀጥል በተቻለዎት መጠን ለመለገስ ያስቡበት።",
"button": "ቡና ይጋብዙኝ",
"heading": "ለ Reactive Resume ይለግሱ"
},
"github": "የምንጭ ኮድ",
"docs": "ሰነዶች",
"heading": "አገናኞች"
},
"settings": {
"global": {
"date": {
"primary": "ቀን",
"secondary": "በመላው መተግበሪያ ላይ የሚጠቀሙበት የቀን አይነት"
},
"heading": "ዓለም አቀፍ",
"language": {
"primary": "ቋንቋ",
"secondary": "በመላው መተግበሪያ ላይ የሚጠቀሙበት ቋንቋ"
},
"theme": {
"primary": "ገጽታ"
}
},
"heading": "ቅንብሮች",
"page": {
"format": {
"primary": "የወረቀት መጠን",
"secondary": "ከቆመበት ቀጥል ገጾችዎ ልኬቶችን ይወስናል"
},
"break-line": {
"primary": "መስመር መቁረጫ",
"secondary": "የA4 ገጽ ቁመትን ለመለየት በሁሉም ገጾች ላይ መስመር አሳይ"
},
"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": "ርዕሶች"
}
}
}
}
}
}

View File

@ -0,0 +1,29 @@
{
"avatar": {
"menu": {
"greeting": "ሰላም",
"logout": "ውጣ"
}
},
"footer": {
"credit": "ተወዳጅ ፕሮጀክት በ<1>Amruth Pillai</1>",
"license": "በማህበረሰቡ ፣ ለማህበረሰቡ።"
},
"markdown": {
"help-text": "ይህ ክፍል ይደግፋል <1>markdown</1> አፃፃፍ ይደግፋል።"
},
"date": {
"present": "አሁን"
},
"subtitle": "ነፃ እና ክፍት የስራ ልምድ ሰነድ መገንቢያ",
"title": "Reactive Resume",
"toast": {
"error": {
"upload-file-size": "እባክዎ ከ2 ሜጋባይት በታች የሆኑ ፋይሎችን ብቻ ይስቀሉ።",
"upload-photo-size": "እባክዎትን ከ2 ሜጋባይት በታች የሆኑ ፎቶዎችን ብቻ ይስቀሉ፣ ቢቻል ካሬ።"
},
"success": {
"resume-link-copied": "ወደ የስራ ታሪክዎ የሚወስድ አገናኝ በሰሌዳዎ ተይዟል።"
}
}
}

View 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": "ዳሽቦርድ"
}

View File

@ -0,0 +1,42 @@
{
"actions": {
"app": "ወደ መተግበሪያ ይሂዱ",
"login": "ግባ",
"logout": "ውጣ",
"register": "ይመዝገቡ"
},
"features": {
"heading": "መገለጫዎች",
"list": {
"ads": "ምንም ማስታወቂያ የለም",
"export": "የስራ ልምድዎን ወደ JSON ወይም PDF ቅርጸት ይላኩ።",
"free": "ሁሌም ነጻ",
"import": "መረጃ ከ LinkedIn, JSON Resume ማምጣት",
"languages": "በተለያዩ ቋንቋዎች ተደራሽ",
"more": "እና ብዙ ተጨማሪ አስደሳች መገለጫዎች፤ <1>ሁሉንም እዚህ ያንብቡ</1>",
"tracking": "ምንም የተጠቃሚ መከታተያ የለም።"
}
},
"links": {
"heading": "አገናኞች",
"links": {
"donate": "ይለግሱ",
"github": "የምንጭ ኮድ",
"docs": "ሰነዶች",
"privacy": "የግላዊነት መመሪያ",
"service": "የአገልግሎት ውሎች"
}
},
"screenshots": {
"heading": "የገጽ እይታዎች"
},
"testimonials": {
"heading": "ምስክሮች",
"body": "ጥሩም ይሁን መጥፎ፣ ስለ Reactive Resume እና ለእርስዎ እንዴት እንደነበረ አስተያየትዎን መስማት እፈልጋለሁ።<br/>በአለም ዙሪያ በተጠቃሚዎች የተላኩ አንዳንድ መልዕክቶች እነዚሁና",
"contact": "በዚህ በኩል ልታገኙኝ <1>ኢሜል</1> ትችላላችሁ ወይም <3>በድረ-ገጽ</3> ላይ ባለው የእውቂያ ቅጽ ማግኘት ይችላሉ።"
},
"summary": {
"body": "Reactive Resume የእርስዎን የስራ ታሪክ የመፍጠር፣ የማዘመን እና የማጋራት መደበኛ ተግባራትን እንደ 1 2 3 ቀላል ለማድረግ የተሰራ ነፃ እና በነጻ የሚገኝ የስራ ልምድ ሰነድ መገንቢያ ነው። በዚህ መገልገያ የተለያዩ የስራ ልምድ ሰነዶችን በመስራት፣ ከቀጣሪዎች ወይም ከጓደኞች ጋር በማስፈንጠሪያ ማጋራት እና እንደ PDF ማተም ፣ ሁሉንም በነጻ ፣ ምንም ማስታወቂያ ሳይኖር ፣ ምንም ክትትል ሳይደረግ ፣ የመረጃዎን ትክክለኛነት እና ግላዊነት ተጠብቆ ማከናወን ይችላሉ።",
"heading": "ማጠቃለያ"
}
}

View 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": "በጉግል ይመዝገቡ"
},
"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": "የስራ ታሪክ ሰነድ ይፍጠሩ"
},
"body": "ስም በመስጠት የስራ ልምድዎ ሰነድዎን መገንባት ይጀምሩ። መጠሪያው ለሚያመለክቱበት የስራ ሚና ወይም የሚወዱት ምግብ ሊሆን ይችላል።",
"form": {
"name": {
"label": "ስም"
},
"public": {
"label": "በይፋ ተደራሽ ነው?"
},
"slug": {
"label": "ማስፈንጠሪያ"
}
},
"heading": "አዲስ የስራ ታሪክ ሰነድ ይፍጠሩ"
},
"import-external": {
"heading": "ከውጭ ምንጮች አስገባ",
"json-resume": {
"actions": {
"upload-json": "JSON ስቀል"
},
"body": "ለመቀጠል ዝግጁ የሆነ <1>የተረጋገጠ JSON ሰነድ</1> ካለዎት በReactive Resume ላይ ስራዎን ለማፋጠን ሊጠቀሙበት ይችላሉ። ከታች ያለውን አዝራር በመጫን የሚሰራ JSON ፋይል ይስቀሉ።",
"heading": "ከ JSON ሰነድ"
},
"linkedin": {
"actions": {
"upload-archive": "የ ZIP ማህደር ስቀል"
},
"body": "መረጃዎን ከ LinkedIn ወደ በመላክ እና Reactive Resume ላይ በራስ-ሙላ መስኮችን በመጠቀም ጊዜ መቆጠብ ይችላሉ። በ LinkedIn ላይ ወደ <1>የመረጃ ግላዊነት</1> ክፍል ይሂዱ እና የመረጃ ማህደርዎን ይጠይቁ። ከተገኘ በኋላ፣ ከታች በሚገኘው የ ZIP ፋይሉን ይስቀሉ።",
"heading": "ከ LinkedIn ስቀል"
},
"reactive-resume": {
"actions": {
"upload-json": "JSON ስቀል",
"upload-json-v2": "JSON v2 ይስቀሉ።"
},
"body": "አሁን ካለው Reactive Resume ስሪት ጋር ወደ ውጭ የተላከ JSON ካለዎት፣ እንደገና ሊስተካከል የሚችል ስሪት ለማግኘት ወደዚህ መልሰው ማስገባት ይችላሉ።",
"heading": "ከ Reactive Resume ስቀል"
}
},
"rename-resume": {
"actions": {
"rename-resume": "የስራ ታሪክ ሰነዱን ደግመው ይሰይሙ"
},
"form": {
"name": {
"label": "ስም"
},
"slug": {
"label": "ማስፈንጥሪያ"
}
},
"heading": "የስራ ታሪክ ሰነዱን ደግመው ይሰይሙ"
}
}
}

View File

@ -84,7 +84,9 @@
"toggle-page-break-line": "تبديل سطر الصفحة", "toggle-page-break-line": "تبديل سطر الصفحة",
"toggle-sidebars": "تبديل الشريط الجانبي", "toggle-sidebars": "تبديل الشريط الجانبي",
"zoom-in": "تكبير", "zoom-in": "تكبير",
"zoom-out": "تصغير" "zoom-out": "تصغير",
"undo": "الغاء التحميل",
"redo": "إعادة"
} }
}, },
"header": { "header": {
@ -268,6 +270,7 @@
"heading": "تبرع الى Reactive Resume" "heading": "تبرع الى Reactive Resume"
}, },
"github": "الشفرة المصدرية", "github": "الشفرة المصدرية",
"docs": "توثيق",
"heading": "الروابط" "heading": "الروابط"
}, },
"settings": { "settings": {
@ -287,6 +290,10 @@
}, },
"heading": "الإعدادات", "heading": "الإعدادات",
"page": { "page": {
"format": {
"primary": "حجم الورق",
"secondary": "تحدد أبعاد صفحات سيرتك الذاتية"
},
"break-line": { "break-line": {
"primary": "خط فاصل", "primary": "خط فاصل",
"secondary": "اعرض خط في كل الصفحات لتحديد ارتفاع صفحة A4" "secondary": "اعرض خط في كل الصفحات لتحديد ارتفاع صفحة A4"

View File

@ -22,6 +22,7 @@
"links": { "links": {
"donate": "تبرّع", "donate": "تبرّع",
"github": "الشفرة المصدرية", "github": "الشفرة المصدرية",
"docs": "توثيق",
"privacy": "سياسة الخصوصية", "privacy": "سياسة الخصوصية",
"service": "شروط الإستخدام" "service": "شروط الإستخدام"
} }

View File

@ -15,8 +15,7 @@
}, },
"login": { "login": {
"actions": { "actions": {
"login": "تسجيل الدخول", "login": "تسجيل الدخول"
"google": "تسجيل الدخول باستخدام حساب جوجل"
}, },
"body": "يرجى إدخال اسم المستخدم وكلمة المرور المرتبطين بحسابك لتسجيل الدخول والوصول إلى السير الذاتية وإدارتها ومشاركتها.", "body": "يرجى إدخال اسم المستخدم وكلمة المرور المرتبطين بحسابك لتسجيل الدخول والوصول إلى السير الذاتية وإدارتها ومشاركتها.",
"form": { "form": {

View File

@ -0,0 +1,368 @@
{
"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": "Намали",
"undo": "Отмяна на",
"redo": "Redo"
}
},
"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": "Програмен код",
"docs": "Документация",
"heading": "Връзки"
},
"settings": {
"global": {
"date": {
"primary": "Дата",
"secondary": "Формат на датата, който да се използва в приложението"
},
"heading": "Глобално",
"language": {
"primary": "Език",
"secondary": "Език за показване, който да се използва в приложението"
},
"theme": {
"primary": "Тема"
}
},
"heading": "Настройки",
"page": {
"format": {
"primary": "Размер на хартията",
"secondary": "Определя размерите на вашите страници с автобиография"
},
"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": "Заглавия"
}
}
}
}
}
}

View 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": "Връзката към автобиографията ви е копирана в клипборда."
}
}
}

View 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": "Контролен панел"
}

View File

@ -0,0 +1,42 @@
{
"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": "Програмен код",
"docs": "Документация",
"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": "Обобщение"
}
}

View 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": "Преименувайте автобиографията си"
}
}
}

View File

@ -84,7 +84,9 @@
"toggle-page-break-line": "পৃষ্ঠা বিরতি লাইন টগল করুন", "toggle-page-break-line": "পৃষ্ঠা বিরতি লাইন টগল করুন",
"toggle-sidebars": "সাইডবার টগল করুন", "toggle-sidebars": "সাইডবার টগল করুন",
"zoom-in": "বড় কর", "zoom-in": "বড় কর",
"zoom-out": "ছোট করা" "zoom-out": "ছোট করা",
"undo": "পূর্বাবস্থায় ফেরান",
"redo": "আবার করুন"
} }
}, },
"header": { "header": {
@ -268,6 +270,7 @@
"heading": "Reactive Resume -তে দান করুন" "heading": "Reactive Resume -তে দান করুন"
}, },
"github": "সোর্স কোড", "github": "সোর্স কোড",
"docs": "ডকুমেন্টেশন",
"heading": "লিঙ্ক" "heading": "লিঙ্ক"
}, },
"settings": { "settings": {
@ -287,6 +290,10 @@
}, },
"heading": "সেটিংস", "heading": "সেটিংস",
"page": { "page": {
"format": {
"primary": "কাগজের আকার",
"secondary": "আপনার জীবনবৃত্তান্ত পৃষ্ঠাগুলির মাত্রা নির্ধারণ করে"
},
"break-line": { "break-line": {
"primary": "লাইন ভেঙ্গে ফেলুন", "primary": "লাইন ভেঙ্গে ফেলুন",
"secondary": "একটি A4 পৃষ্ঠার উচ্চতা চিহ্নিত করতে সমস্ত পৃষ্ঠায় একটি লাইন দেখান৷" "secondary": "একটি A4 পৃষ্ঠার উচ্চতা চিহ্নিত করতে সমস্ত পৃষ্ঠায় একটি লাইন দেখান৷"

View File

@ -22,6 +22,7 @@
"links": { "links": {
"donate": "দান করুন", "donate": "দান করুন",
"github": "সোর্স কোড", "github": "সোর্স কোড",
"docs": "ডকুমেন্টেশন",
"privacy": "গোপনীয়তা নীতি", "privacy": "গোপনীয়তা নীতি",
"service": "সেবা পাবার শর্ত" "service": "সেবা পাবার শর্ত"
} }

View File

@ -15,8 +15,7 @@
}, },
"login": { "login": {
"actions": { "actions": {
"login": "লগইন", "login": "লগইন"
"google": "গুগল দিয়ে লগইন করুন"
}, },
"body": "লগইন এবং অ্যাক্সেস, পরিচালনা এবং আপনার জীবনবৃত্তান্ত শেয়ার করতে আপনার অ্যাকাউন্টের সাথে যুক্ত আপনার ব্যবহারকারীর নাম এবং পাসওয়ার্ড লিখুন।", "body": "লগইন এবং অ্যাক্সেস, পরিচালনা এবং আপনার জীবনবৃত্তান্ত শেয়ার করতে আপনার অ্যাকাউন্টের সাথে যুক্ত আপনার ব্যবহারকারীর নাম এবং পাসওয়ার্ড লিখুন।",
"form": { "form": {

View File

@ -0,0 +1,368 @@
{
"common": {
"actions": {
"add": "Afegeix nou {{token}}",
"delete": "Suprimeix {{token}}",
"edit": "Edita {{token}}"
},
"columns": {
"heading": "Columnes",
"tooltip": "Canvia el nombre de columnes"
},
"form": {
"date": {
"label": "Data"
},
"description": {
"label": "Descripció"
},
"email": {
"label": "Correu electrònic"
},
"end-date": {
"help-text": "Deixeu aquest camp en blanc, si encara està present",
"label": "Data de finalització"
},
"keywords": {
"label": "Paraules clau"
},
"level": {
"label": "Nivell"
},
"levelNum": {
"label": "Nivell (nombre)"
},
"name": {
"label": "Nom"
},
"phone": {
"label": "Número de telèfon"
},
"position": {
"label": "Posició"
},
"start-date": {
"label": "Data d'inici"
},
"subtitle": {
"label": "Subtítol"
},
"summary": {
"label": "Resum"
},
"title": {
"label": "Títol"
},
"url": {
"label": "Pàgina web"
}
},
"glossary": {
"page": "Pàgina"
},
"list": {
"actions": {
"delete": "Elimina",
"duplicate": "Duplica",
"edit": "Edita"
},
"empty-text": "Aquesta llista està buida."
},
"tooltip": {
"delete-item": "Estàs segur que vols esborrar aquest element? Aquesta acció és irreversible.",
"delete-section": "Suprimeix una secció",
"rename-section": "Canvia el nom de la secció",
"toggle-visibility": "Commuta la visibilitat"
}
},
"controller": {
"tooltip": {
"center-artboard": "Centrar tauler de dibuix",
"copy-link": "Copia l'enllaç al currículum",
"export-pdf": "Exporta com a PDF",
"toggle-orientation": "Commuta l'orientació de la pàgina",
"toggle-page-break-line": "Commuta la línia de salt de pàgina",
"toggle-sidebars": "Mostra o oculta la barra lateral",
"zoom-in": "Amplia",
"zoom-out": "Allunya",
"undo": "Desfer",
"redo": "Refer"
}
},
"header": {
"menu": {
"delete": "Elimina",
"duplicate": "Duplica",
"rename": "Canvia el nom",
"share-link": "Comparteix l'enllaç",
"tooltips": {
"delete": "Estàs segur que vols esborrar aquest currículum? Aquesta acció és irreversible.",
"share-link": "Heu de canviar la visibilitat del vostre currículum a pública per fer-lo visible per als altres."
}
}
},
"leftSidebar": {
"sections": {
"awards": {
"form": {
"awarder": {
"label": "Otorgat per"
}
}
},
"basics": {
"actions": {
"photo-filters": "Filtres fotogràfics"
},
"heading": "Bàsics",
"headline": {
"label": "Títol"
},
"name": {
"label": "Nom complet"
},
"birthdate": {
"label": "Data de naixement"
},
"photo-filters": {
"effects": {
"border": {
"label": "Vora"
},
"grayscale": {
"label": "Escala de grisos"
},
"heading": "Efectes"
},
"shape": {
"heading": "Forma"
},
"size": {
"heading": "Mida (en px)"
}
},
"photo-upload": {
"tooltip": {
"remove": "Esborrar foto",
"upload": "Pugeu la foto"
}
}
},
"certifications": {
"form": {
"issuer": {
"label": "Emissor"
}
}
},
"education": {
"form": {
"area-study": {
"label": "Àrea d'estudi"
},
"courses": {
"label": "Cursos"
},
"degree": {
"label": "Graus"
},
"grade": {
"label": "Qualificació"
},
"institution": {
"label": "Institució"
}
}
},
"location": {
"address": {
"label": "Adreça"
},
"city": {
"label": "Ciutat"
},
"country": {
"label": "País"
},
"heading": "Ubicació",
"postal-code": {
"label": "Codi Postal"
},
"region": {
"label": "Regió"
}
},
"profiles": {
"form": {
"network": {
"label": "Xarxa"
},
"username": {
"label": "Nom d'usuari"
}
},
"heading": "Perfils",
"heading_one": "Perfil"
},
"publications": {
"form": {
"publisher": {
"label": "Editor"
}
}
},
"references": {
"form": {
"relationship": {
"label": "Relació"
}
}
},
"section": {
"heading": "Secció"
},
"volunteer": {
"form": {
"organization": {
"label": "Organització"
}
}
}
}
},
"rightSidebar": {
"sections": {
"css": {
"heading": "CSS personalitzat"
},
"export": {
"heading": "Exporta",
"json": {
"primary": "JSON",
"secondary": "Baixeu una versió JSON del vostre currículum que es pot tornar a importar a Reactive Resume."
},
"pdf": {
"loading": {
"primary": "Generació de PDF",
"secondary": "Si us plau, espereu mentre es generi el vostre PDF; això pot trigar fins a 15 segons."
},
"normal": {
"primary": "PDF",
"secondary": "Descarrega un PDF del teu currículum que pots imprimir i enviar a la feina dels teus somnis. Aquest fitxer no es pot tornar a importar per editar-lo més."
}
}
},
"layout": {
"heading": "Disseny",
"tooltip": {
"reset-layout": "Restableix el disseny"
}
},
"links": {
"bugs-features": {
"body": "Alguna cosa t'impedeix fer un currículum? O tens una idea sorprenent per afegir? Planteja un problema a GitHub per començar.",
"button": "Problemes de GitHub",
"heading": "Errors? Sol·licituds de funcions?"
},
"donate": {
"body": "Si us ha agradat utilitzar Reactive Resume, considereu donar tant com pugueu per mantenir l'aplicació en funcionament, sense anuncis i gratuïta per sempre.",
"button": "Compra'm un cafè",
"heading": "Dona a Reactive Curriculum vitae"
},
"github": "Codi font",
"docs": "Documentació",
"heading": "Enllaços"
},
"settings": {
"global": {
"date": {
"primary": "Data",
"secondary": "Format de data per utilitzar a l'aplicació"
},
"heading": "Global",
"language": {
"primary": "Llenguatge",
"secondary": "Mostra l'idioma per utilitzar a l'aplicació"
},
"theme": {
"primary": "Tema"
}
},
"heading": "Configuració",
"page": {
"format": {
"primary": "Mida del paper",
"secondary": "Determina les dimensions de les pàgines del vostre currículum"
},
"break-line": {
"primary": "Línia de trencament",
"secondary": "Mostra una línia a totes les pàgines per marcar l'alçada d'una pàgina A4"
},
"heading": "Pàgina",
"orientation": {
"disabled": "No té efecte quan només hi ha una pàgina",
"primary": "Orientació",
"secondary": "Si voleu mostrar les pàgines horitzontalment o verticalment"
}
},
"resume": {
"heading": "Resum",
"reset": {
"primary": "Restablir-ho tot",
"secondary": "S'ha equivocat massa? Feu clic aquí per restablir tots els canvis i començar des de zero. Aneu amb compte, aquesta acció no es pot revertir."
},
"sample": {
"primary": "Carregueu dades de mostra",
"secondary": "No saps per on començar? Feu clic aquí per carregar algunes dades de mostra per veure com es veu un currículum complet."
}
}
},
"sharing": {
"heading": "Compartint",
"short-url": {
"label": "Prefereix URL curt"
},
"visibility": {
"subtitle": "Permet que qualsevol persona amb un enllaç vegi el teu currículum",
"title": "Públic"
}
},
"templates": {
"heading": "Plantilles"
},
"theme": {
"form": {
"background": {
"label": "Fons"
},
"primary": {
"label": "primària"
},
"text": {
"label": "Text"
}
},
"heading": "Tema"
},
"typography": {
"form": {
"font-family": {
"label": "Família de lletres"
},
"font-size": {
"label": "Mida de la font"
}
},
"heading": "Tipografia",
"widgets": {
"body": {
"label": "Cos"
},
"headings": {
"label": "Encapçalaments"
}
}
}
}
}
}

View File

@ -0,0 +1,29 @@
{
"avatar": {
"menu": {
"greeting": "Hola",
"logout": "Tancar sessió"
}
},
"footer": {
"credit": "Un projecte de passió d'<1>Amruth Pillai</1>",
"license": "Per la comunitat, per a la comunitat."
},
"markdown": {
"help-text": "Aquesta secció admet <1>reducció</1> formatació."
},
"date": {
"present": "Present"
},
"subtitle": "Un creador de currículums gratuït i de codi obert.",
"title": "Currículum reactiu",
"toast": {
"error": {
"upload-file-size": "Pengeu només fitxers de menys de 2 megabytes.",
"upload-photo-size": "Pengeu només fotos de menys de 2 megabytes, preferiblement quadrades."
},
"success": {
"resume-link-copied": "S'ha copiat un enllaç al vostre currículum al porta-retalls."
}
}
}

Some files were not shown because too many files have changed in this diff Show More