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
dist
.next
.turbo
# IDEs
.vscode
# Project Metadata
.crowdin.yml
# Documentation
README.md
SECURITY.md
CHANGELOG.md
CODE_OF_CONDUCT.md
# Project Dependencies
node_modules
@ -16,6 +25,3 @@ node_modules
Dockerfile
.dockerignore
docker-compose.yml
# Android App
/app

View File

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

View File

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

1
.github/FUNDING.yml vendored
View File

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

View File

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

View File

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

View File

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

View File

@ -9,72 +9,104 @@ jobs:
name: Client
runs-on: ubuntu-latest
env:
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
steps:
- name: Checkout the repository
uses: actions/checkout@v3.0.2
uses: actions/checkout@v3.1.0
with:
fetch-depth: 2
- id: 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
uses: docker/login-action@v2.0.0
uses: docker/login-action@v2.1.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2.0.0
uses: docker/login-action@v2.1.0
with:
registry: ghcr.io
username: $GITHUB_REPOSITORY_OWNER
password: ${{ secrets.GH_TOKEN }}
- name: Build and Push Client Image
uses: docker/build-push-action@v3.0.0
uses: docker/build-push-action@v3.2.0
with:
context: .
push: true
file: client/Dockerfile
platforms: linux/amd64,linux/arm64
tags: |
amruthpillai/reactive-resume:client-latest
amruthpillai/reactive-resume:client-${{ steps.version.outputs.tag }}
amruthpillai/reactive-resume:client-${{ env.version }}
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:
name: Server
runs-on: ubuntu-latest
env:
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
steps:
- name: Checkout the repository
uses: actions/checkout@v3.0.2
uses: actions/checkout@v3.1.0
with:
fetch-depth: 2
- id: 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
uses: docker/login-action@v2.0.0
uses: docker/login-action@v2.1.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2.0.0
uses: docker/login-action@v2.1.0
with:
registry: ghcr.io
username: $GITHUB_REPOSITORY_OWNER
password: ${{ secrets.GH_TOKEN }}
- name: Build and Push Server Image
uses: docker/build-push-action@v3.0.0
uses: docker/build-push-action@v3.2.0
with:
context: .
push: true
file: server/Dockerfile
platforms: linux/amd64,linux/arm64
tags: |
amruthpillai/reactive-resume:server-latest
amruthpillai/reactive-resume:server-${{ steps.version.outputs.tag }}
amruthpillai/reactive-resume:server-${{ env.version }}
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
.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/dist
@ -18,15 +21,9 @@ CHANGELOG.md
# Project Dependencies
node_modules
pnpm-lock.yaml
# Docker
Dockerfile
.dockerignore
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,
"scss.validate": false,
"editor.wordWrap": "on",
"npm.packageManager": "pnpm",
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.wordWrap": "on",
"eslint.workingDirectories": [
"schema",
"client",
"server"
],
"i18n-ally.enabledFrameworks": [
"react"
],
"i18n-ally.keystyle": "nested",
"i18n-ally.localesPaths": [
"client/public/locales"
],
"i18n-ally.namespace": true,
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
"i18n-ally.sortKeys": true,
"scss.validate": false
"conventionalCommits.scopes": [
"client",
"server",
"docker",
"dependencies"
]
}

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
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities

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
@ -27,7 +27,8 @@ You have complete control over what goes into your resume, how it looks, what co
- [Contributing](#contributing)
- [Report Bugs and Feature Requests](#report-bugs-and-feature-requests)
- [Donations](#donations)
- [💸 PayPal](#-paypal)
- [GitHub Sponsor](#github-sponsor)
- [PayPal](#paypal)
- [Infrastructure](#infrastructure)
- [Contributors Wall](#contributors-wall)
- [License](#license)
@ -54,13 +55,17 @@ You have complete control over what goes into your resume, how it looks, what co
## Languages
- Amharic (አማርኛ)
- Arabic (اَلْعَرَبِيَّةُ)
- Bengali (বাংলা)
- Bulgarian (български)
- Catalan (Valencian)
- Chinese (中文)
- Czech (čeština)
- Danish (Dansk)
- Dutch (Nederlands)
- English
- Finnish (Suomi)
- French (Français)
- German (Deutsch)
- Greek (Ελληνικά)
@ -69,17 +74,26 @@ You have complete control over what goes into your resume, how it looks, what co
- Hungarian (Magyar)
- Indonesian (Bahasa Indonesia)
- Italian (Italiano)
- Japanese (日本語)
- Kannada (ಕನ್ನಡ)
- Khmer (ភាសាខ្មែរ)
- Korean (한국어)
- Malayalam (മലയാളം)
- Marathi (मराठी)
- Nepali (नेपाली)
- Norwegian (Norsk)
- Odia (ଓଡ଼ିଆ)
- Persian (Farsi)
- Persian (فارسی)
- Polish (Polski)
- Portuguese (Português)
- Romanian (limba română)
- Russian (русский)
- Serbian (српски језик)
- Spanish (Español)
- Swedish (Svenska)
- Tamil (தமிழ்)
- Turkish (Türkçe)
- Ukranian (Українська мова)
- Vietnamese (Tiếng Việt)
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.
### [💸 PayPal](https://paypal.me/RajaRajanA)
### [GitHub Sponsor](https://github.com/sponsors/AmruthPillai)
### [PayPal](https://paypal.me/RajaRajanA)
## Infrastructure

View File

@ -1,7 +1,7 @@
plugins {
id 'com.android.application' version '7.1.2' apply false
id 'com.android.library' version '7.1.2' apply false
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
}
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"],
"settings": {
"next": {
"rootDir": "client"
}
},
"rules": {
// Next.js
"@next/next/no-img-element": "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
RUN apk add --no-cache curl g++ make python3 \
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
FROM node:lts-alpine AS base
WORKDIR /app
COPY package.json pnpm-*.yaml ./
RUN apk add --no-cache g++ git curl make python3 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 ./client/package.json ./client/package.json
RUN pnpm install --frozen-lockfile
FROM node:lts-alpine as builder
RUN apk add --no-cache curl g++ make python3 \
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
WORKDIR /app
FROM base as builder
COPY . .
@ -24,21 +21,20 @@ COPY --from=dependencies /app/node_modules ./node_modules
COPY --from=dependencies /app/schema/node_modules ./schema/node_modules
COPY --from=dependencies /app/client/node_modules ./client/node_modules
RUN pnpm run build:schema
RUN pnpm run build:client
ARG TURBO_TEAM
ARG TURBO_TOKEN
FROM node:lts-alpine as production
ENV TURBO_TEAM $TURBO_TEAM
ENV TURBO_TOKEN $TURBO_TOKEN
WORKDIR /app
RUN pnpm run build --filter client
RUN apk add --no-cache curl \
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
FROM base as production
COPY --from=builder /app/pnpm-*.yaml ./
COPY --from=builder /app/package.json ./
COPY --from=builder /app/package.json /app/pnpm-*.yaml /app/turbo.json ./
COPY --from=builder /app/client/package.json ./client/package.json
RUN pnpm install -F client --frozen-lockfile --prod
RUN pnpm install --filter client --prod --frozen-lockfile --workspace-root
COPY --from=builder /app/client/.next ./client/.next
COPY --from=builder /app/client/public ./client/public
@ -52,4 +48,4 @@ ENV PORT 3000
HEALTHCHECK --interval=30s --timeout=20s --retries=3 --start-period=15s \
CMD curl -fSs 127.0.0.1:3000 || exit 1
CMD [ "pnpm", "run", "start:client" ]
CMD [ "pnpm", "run", "start", "--filter", "client" ]

View File

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

View File

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

View File

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

View File

@ -15,7 +15,7 @@
}
&.break::after {
content: 'A4 Page Break';
content: 'Page Break';
top: calc(297mm - 19px);
@apply absolute w-full border-b border-dashed border-neutral-800/75;
@ -27,6 +27,22 @@
@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 {

View File

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

View File

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

View File

@ -32,7 +32,7 @@ const Basics = () => {
<PhotoUpload />
</div>
<div className="grid gap-2 w-full sm:col-span-2">
<div className="grid w-full gap-2 sm:col-span-2">
<ResumeInput label={t<string>('builder.leftSidebar.sections.basics.name.label')} path="basics.name" />
<Button variant="outlined" startIcon={<PhotoFilter />} onClick={handleClick}>

View File

@ -12,7 +12,7 @@ const PhotoFilters = () => {
const dispatch = useAppDispatch();
const photo: Photo = useAppSelector((state) => get(state.resume, 'basics.photo'));
const photo: Photo = useAppSelector((state) => get(state.resume.present, 'basics.photo'));
const size: number = get(photo, 'filters.size', 128);
const shape: PhotoShape = get(photo, 'filters.shape', 'square');
const grayscale: boolean = get(photo, 'filters.grayscale', false);

View File

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

View File

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

View File

@ -18,7 +18,7 @@ const SectionSettings: React.FC<Props> = ({ path }) => {
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
const columns = useAppSelector<number>((state) => get(state.resume, `${path}.columns`, 2));
const columns = useAppSelector<number>((state) => get(state.resume.present, `${path}.columns`, 2));
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);

View File

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

View File

@ -17,7 +17,7 @@ const CustomCSS = () => {
const dispatch = useAppDispatch();
const customCSS: CustomCSSType = useAppSelector((state) => get(state.resume, 'metadata.css', {}));
const customCSS: CustomCSSType = useAppSelector((state) => get(state.resume.present, 'metadata.css', {}));
const handleChange = (value: string | undefined) => {
dispatch(setResumeState({ path: 'metadata.css.value', value }));

View File

@ -13,7 +13,7 @@ import { useAppSelector } from '@/store/hooks';
const Export = () => {
const { t } = useTranslation();
const resume = useAppSelector((state) => state.resume);
const resume = useAppSelector((state) => state.resume.present);
const { mutateAsync, isLoading } = useMutation<string, ServerError, PrintResumeAsPdfParams>(printResumeAsPdf);
@ -48,7 +48,7 @@ const Export = () => {
const url = await mutateAsync({ username, slug });
download(`/api${url}`);
download(url);
};
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 { Button, IconButton, Tooltip } from '@mui/material';
import clsx from 'clsx';
import cloneDeep from 'lodash/cloneDeep';
import get from 'lodash/get';
import { useTranslation } from 'next-i18next';
import { DragDropContext, Draggable, DraggableLocation, Droppable, DropResult } from 'react-beautiful-dnd';
import Heading from '@/components/shared/Heading';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
@ -23,8 +23,8 @@ const Layout = () => {
const dispatch = useAppDispatch();
const layout = useAppSelector((state) => state.resume.metadata.layout);
const resumeSections = useAppSelector((state) => state.resume.sections);
const layout = useAppSelector((state) => state.resume.present.metadata.layout);
const resumeSections = useAppSelector((state) => state.resume.present.sections);
const onDragEnd = (dropResult: DropResult) => {
const { source: srcLoc, destination: destLoc } = dropResult;

View File

@ -3,7 +3,7 @@ import { Button } from '@mui/material';
import { useTranslation } from 'next-i18next';
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';
@ -49,6 +49,12 @@ const Links = () => {
{t<string>('builder.rightSidebar.sections.links.github')}
</Button>
</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>
</>

View File

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

View File

@ -17,7 +17,7 @@ const Sharing = () => {
const [showShortUrl, setShowShortUrl] = useState(false);
const resume = useAppSelector((state) => state.resume);
const resume = useAppSelector((state) => state.resume.present);
const isPublic = useMemo(() => get(resume, 'public'), [resume]);
const url = useMemo(() => getResumeUrl(resume, { withHost: true }), [resume]);
const shortUrl = useMemo(() => getResumeUrl(resume, { withHost: true, shortUrl: true }), [resume]);

View File

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

View File

@ -1,8 +1,8 @@
.container {
@apply grid sm:grid-cols-2 gap-4;
@apply grid gap-4 sm:grid-cols-2;
}
.colorOptions {
@apply col-span-2 mb-4;
@apply grid grid-cols-8 gap-y-2 justify-items-center;
@apply grid grid-cols-8 justify-items-center gap-y-2;
}

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 { useTranslation } from 'next-i18next';
@ -16,7 +16,9 @@ const Theme = () => {
const dispatch = useAppDispatch();
const { background, text, primary } = useAppSelector<ThemeType>((state) => get(state.resume, 'metadata.theme'));
const { background, text, primary } = useAppSelector<ThemeConfig>((state) =>
get(state.resume.present, 'metadata.theme')
);
const handleChange = (property: string, color: string) => {
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 { family, size } = useAppSelector<TypographyType>((state) => get(state.resume, 'metadata.typography'));
const { family, size } = useAppSelector<TypographyType>((state) => get(state.resume.present, 'metadata.typography'));
const { data: fonts } = useQuery(FONTS_QUERY, fetchFonts, {
select: (fonts) => fonts.sort((a, b) => a.category.localeCompare(b.category)),

View File

@ -1,9 +1,9 @@
.testimony {
@apply grid gap-2;
@apply border-2 rounded p-4 dark:border-neutral-800;
@apply rounded border-2 p-4 dark:border-neutral-800;
blockquote {
@apply text-xs leading-normal text-justify opacity-90;
@apply text-justify text-xs leading-normal opacity-90;
}
figcaption {

View File

@ -10,7 +10,7 @@
}
.header {
@apply sticky top-0 left-0 right-0 z-50 pt-6 bg-neutral-50 dark:bg-neutral-900;
@apply sticky top-0 left-0 right-0 z-50 bg-neutral-50 pt-6 dark:bg-neutral-900;
@apply flex items-center justify-between;
@apply w-full border-b pb-5 dark:border-white/10;
@ -33,7 +33,7 @@
}
.footer {
@apply sticky bottom-0 left-0 right-0 z-50 pb-6 bg-neutral-50 dark:bg-neutral-900;
@apply sticky bottom-0 left-0 right-0 z-50 bg-neutral-50 pb-6 dark:bg-neutral-900;
@apply flex items-center justify-end gap-x-4;
@apply w-full border-t pt-5 dark:border-white/10;
}

View File

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

View File

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

View File

@ -36,7 +36,7 @@ const List: React.FC<Props> = ({
const dispatch = useAppDispatch();
const list: Array<ListItemType> = useAppSelector((state) => get(state.resume, path, []));
const list: Array<ListItemType> = useAppSelector((state) => get(state.resume.present, path, []));
const handleEdit = (item: ListItemType) => {
isFunction(onEdit) && onEdit(item);

View File

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

View File

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

View File

@ -2,34 +2,48 @@ export type Language = {
code: string;
name: string;
localName?: string;
isRTL?: boolean;
};
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: 'ca', name: 'Catalan', localName: 'Valencian' },
{ code: 'cs', name: 'Czech', localName: 'čeština' },
{ code: 'da', name: 'Danish', localName: 'Dansk' },
{ code: 'de', name: 'German', localName: 'Deutsch' },
{ code: 'el', name: 'Greek', localName: 'Ελληνικά' },
{ code: 'en', name: 'English' },
{ code: 'es', name: 'Spanish', localName: 'Español' },
{ code: 'fa', name: 'Persian', localName: 'فارسی', isRTL: true },
{ code: 'fi', name: 'Finnish', localName: 'Suomi' },
{ 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: 'hu', name: 'Hungarian', localName: 'Magyar' },
{ code: 'id', name: 'Indonesian', localName: 'Bahasa Indonesia' },
{ code: 'it', name: 'Italian', localName: 'Italiano' },
{ code: 'ja', name: 'Japanese', localName: '日本語' },
{ code: 'km', name: 'Khmer', localName: 'ភាសាខ្មែរ' },
{ code: 'kn', name: 'Kannada', localName: 'ಕನ್ನಡ' },
{ code: 'ko', name: 'Korean', localName: '한국어' },
{ code: 'ml', name: 'Malayalam', localName: 'മലയാളം' },
{ code: 'mr', name: 'Marathi', localName: 'मराठी' },
{ code: 'ne', name: 'Nepali', localName: 'नेपाली' },
{ code: 'nl', name: 'Dutch', localName: 'Nederlands' },
{ code: 'no', name: 'Norwegian', localName: 'Norsk' },
{ code: 'or', name: 'Odia', localName: 'ଓଡ଼ିଆ' },
{ code: 'fa', name: 'Persian', localName: 'Farsi' },
{ code: 'pl', name: 'Polish', localName: 'Polski' },
{ code: 'pt', name: 'Portuguese', localName: 'Português' },
{ code: 'ro', name: 'Romanian', localName: 'limba română' },
{ code: 'ru', name: 'Russian', localName: 'русский' },
{ code: 'sr', name: 'Serbian', localName: 'српски језик' },
{ code: 'sv', name: 'Swedish', localName: 'Svenska' },
{ code: 'ta', name: 'Tamil', localName: 'தமிழ்' },
{ code: 'tr', name: 'Turkish', localName: 'Türkçe' },
{ code: 'uk', name: 'Ukranian', localName: 'Українська мова' },
{ code: 'vi', name: 'Vietnamese', localName: 'Tiếng Việt' },
{ code: 'zh', name: 'Chinese', localName: '中文' },
].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: {
fontSize: 12,
fontFamily: 'Inter, sans-serif',
@ -49,7 +49,7 @@ const theme = createTheme({
},
},
},
});
};
export const lightTheme = createTheme({
...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';
// Links
export const DOCS_URL = 'https://docs.rxresu.me';
export const DONATION_URL = 'https://paypal.me/RajaRajanA';
export const TRANSLATE_URL = 'https://translate.rxresu.me/';
export const DIGITALOCEAN_URL = 'https://pillai.xyz/digitalocean';

View File

@ -1,7 +1,8 @@
import env from '@beam-australia/react-env';
import { joiResolver } from '@hookform/resolvers/joi';
import { Google, Login, Visibility, VisibilityOff } from '@mui/icons-material';
import { Login, Visibility, VisibilityOff } from '@mui/icons-material';
import { Button, IconButton, InputAdornment, TextField } from '@mui/material';
import { CredentialResponse, GoogleLogin } from '@react-oauth/google';
import Joi from 'joi';
import { isEmpty } from 'lodash';
import { Trans, useTranslation } from 'next-i18next';
@ -17,8 +18,6 @@ import { ServerError } from '@/services/axios';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice';
declare const google: any;
type FormData = {
identifier: string;
password: string;
@ -85,26 +84,16 @@ const LoginModal: React.FC = () => {
dispatch(setModalState({ modal: 'auth.forgot', state: { open: true } }));
};
const handleLoginWithGoogle = async () => {
google.accounts.id.initialize({
auto_select: true,
itp_support: true,
client_id: env('GOOGLE_CLIENT_ID'),
callback: async (response: any) => {
await loginWithGoogleMutation({ credential: response.credential });
const handleLoginWithGoogle = async (response: CredentialResponse) => {
if (response.credential) {
await loginWithGoogleMutation({ credential: response.credential }, { onError: handleLoginWithGoogleError });
handleClose();
},
});
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 => {
@ -128,15 +117,7 @@ const LoginModal: React.FC = () => {
footerChildren={
<div className="flex gap-4">
{!isEmpty(env('GOOGLE_CLIENT_ID')) && (
<Button
type="submit"
variant="outlined"
disabled={isLoading}
startIcon={<Google />}
onClick={handleLoginWithGoogle}
>
{t<string>('modals.auth.login.actions.google')}
</Button>
<GoogleLogin onSuccess={handleLoginWithGoogle} onError={handleLoginWithGoogleError} />
)}
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}>
@ -181,15 +162,14 @@ const LoginModal: React.FC = () => {
{!FLAG_DISABLE_SIGNUPS && (
<p className="text-xs">
<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>
</p>
)}
<p className="text-xs">
<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>
here.
In case you have forgotten your password, you can <a onClick={handleRecoverAccount}>recover your account here.</a>
</Trans>
</p>
</BaseModal>

View File

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

View File

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

View File

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

View File

@ -60,7 +60,7 @@ const CustomModal: React.FC = () => {
const dispatch = useAppDispatch();
const heading = useAppSelector((state) => get(state.resume, `${path}.name`));
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
const { open: isOpen, payload } = useAppSelector((state) => state.modal['builder.sections.custom']);
const path: string = get(payload, 'path', '');
@ -110,7 +110,7 @@ const CustomModal: React.FC = () => {
heading={isEditMode ? editText : addText}
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
name="title"
control={control}
@ -150,7 +150,7 @@ const CustomModal: React.FC = () => {
views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange('');
date && dayjs(date).isValid() && field.onChange(date.toISOString());
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
}}
renderInput={(params) => (
<TextField
@ -174,7 +174,7 @@ const CustomModal: React.FC = () => {
views={['year', 'month', 'day']}
onChange={(date: Date | null, keyboardInputValue: string | undefined) => {
isEmpty(keyboardInputValue) && field.onChange('');
date && dayjs(date).isValid() && field.onChange(date.toISOString());
date && dayjs(date).utc().isValid() && field.onChange(dayjs(date).utc().toISOString());
}}
renderInput={(params) => (
<TextField
@ -282,6 +282,7 @@ const CustomModal: React.FC = () => {
/>
)}
/>
<input type="submit" style={{ display: 'none' }} />
</form>
</BaseModal>
);

View File

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

View File

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

View File

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

View File

@ -89,7 +89,7 @@ const ProfileModal: React.FC = () => {
handleClose={handleClose}
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
name="network"
control={control}
@ -136,6 +136,7 @@ const ProfileModal: React.FC = () => {
/>
)}
/>
<input type="submit" style={{ display: 'none' }} />
</form>
</BaseModal>
);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,19 +15,6 @@ const nextConfig = {
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
// Ref: https://github.com/tailwindlabs/tailwindcss/issues/3258#issuecomment-968368156
webpack: (config) => {

View File

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

View File

@ -51,7 +51,7 @@ const Preview: NextPage<Props> = ({ username, slug, resume: initialData }) => {
const dispatch = useAppDispatch();
const resume = useAppSelector((state) => state.resume);
const resume = useAppSelector((state) => state.resume.present);
useEffect(() => {
if (initialData && !isEmpty(initialData)) {
@ -98,7 +98,7 @@ const Preview: NextPage<Props> = ({ username, slug, resume: initialData }) => {
try {
const url = await mutateAsync({ username, slug });
download(`/api${url}`);
download(url);
} catch {
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 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(() => {
if (initialData) dispatch(setResume(initialData));
}, [dispatch, initialData]);
useEffect(() => {
const { pathname, asPath, query } = router;
router.push({ pathname, query }, asPath, { locale });
}, [router, locale]);
if (!resume || isEmpty(resume)) return null;
const layout: string[][][] = get(resume, 'metadata.layout', []);

View File

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

View File

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

View File

@ -20,7 +20,7 @@ import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice';
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' }) => {
return {
@ -170,6 +170,12 @@ const Home: NextPage = () => {
</Button>
</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">
<Button variant="text" startIcon={<LinkIcon />}>
{t<string>('landing.links.links.donate')}
@ -180,7 +186,7 @@ const Home: NextPage = () => {
<section className={styles.section}>
<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>
</section>

View File

@ -1,7 +1,7 @@
import { NextPage } from 'next';
const PrivacyPolicy: NextPage = () => (
<div className="mx-auto my-12 prose dark:prose-invert">
<div className="prose mx-auto my-12 dark:prose-invert">
<h1>Privacy Policy</h1>
<p>

View File

@ -1,7 +1,7 @@
import { NextPage } from 'next';
const TermsOfService: NextPage = () => (
<div className="mx-auto my-12 prose dark:prose-invert">
<div className="prose mx-auto my-12 dark:prose-invert">
<h1>Terms of Service</h1>
<p>

View File

@ -71,7 +71,7 @@ const Preview: NextPage<Props> = ({ shortId }) => {
try {
const url = await mutateAsync({ username: resume.user.username, slug: resume.slug });
download(`/api${url}`);
download(url);
} catch {
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-sidebars": "تبديل الشريط الجانبي",
"zoom-in": "تكبير",
"zoom-out": "تصغير"
"zoom-out": "تصغير",
"undo": "الغاء التحميل",
"redo": "إعادة"
}
},
"header": {
@ -268,6 +270,7 @@
"heading": "تبرع الى Reactive Resume"
},
"github": "الشفرة المصدرية",
"docs": "توثيق",
"heading": "الروابط"
},
"settings": {
@ -287,6 +290,10 @@
},
"heading": "الإعدادات",
"page": {
"format": {
"primary": "حجم الورق",
"secondary": "تحدد أبعاد صفحات سيرتك الذاتية"
},
"break-line": {
"primary": "خط فاصل",
"secondary": "اعرض خط في كل الصفحات لتحديد ارتفاع صفحة A4"

View File

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

View File

@ -15,8 +15,7 @@
},
"login": {
"actions": {
"login": "تسجيل الدخول",
"google": "تسجيل الدخول باستخدام حساب جوجل"
"login": "تسجيل الدخول"
},
"body": "يرجى إدخال اسم المستخدم وكلمة المرور المرتبطين بحسابك لتسجيل الدخول والوصول إلى السير الذاتية وإدارتها ومشاركتها.",
"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-sidebars": "সাইডবার টগল করুন",
"zoom-in": "বড় কর",
"zoom-out": "ছোট করা"
"zoom-out": "ছোট করা",
"undo": "পূর্বাবস্থায় ফেরান",
"redo": "আবার করুন"
}
},
"header": {
@ -268,6 +270,7 @@
"heading": "Reactive Resume -তে দান করুন"
},
"github": "সোর্স কোড",
"docs": "ডকুমেন্টেশন",
"heading": "লিঙ্ক"
},
"settings": {
@ -287,6 +290,10 @@
},
"heading": "সেটিংস",
"page": {
"format": {
"primary": "কাগজের আকার",
"secondary": "আপনার জীবনবৃত্তান্ত পৃষ্ঠাগুলির মাত্রা নির্ধারণ করে"
},
"break-line": {
"primary": "লাইন ভেঙ্গে ফেলুন",
"secondary": "একটি A4 পৃষ্ঠার উচ্চতা চিহ্নিত করতে সমস্ত পৃষ্ঠায় একটি লাইন দেখান৷"

View File

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

View File

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