mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-10 04:22:27 +10:00
Compare commits
252 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a293d209de | |||
| 5b9ea43090 | |||
| 01b36ee8d8 | |||
| af1c314c36 | |||
| d836e3d992 | |||
| fb2db6839f | |||
| 787d0af9d1 | |||
| 95de63f387 | |||
| 456b896310 | |||
| 974bf7e032 | |||
| 8fa5324a39 | |||
| 4ac9289344 | |||
| d67272cf9e | |||
| f937a88b9d | |||
| 7465a7ec78 | |||
| b53d8854df | |||
| bffa0be909 | |||
| 06fee1696e | |||
| f9a11092a6 | |||
| 9c76999945 | |||
| d23d1a615e | |||
| a5701a37a6 | |||
| a739b25f42 | |||
| 145aa14ba0 | |||
| 94358bf61c | |||
| ce50df61a5 | |||
| f18da54dfa | |||
| 14c5e36fae | |||
| 1483f9b4f2 | |||
| f7d8e4ebb4 | |||
| 7c42d6e607 | |||
| 08dea8ad8b | |||
| 950d7ea4e7 | |||
| ebc12042a9 | |||
| d8168d2a9d | |||
| 7cfda3c83d | |||
| 8fcfbdd69d | |||
| 1eb52261f2 | |||
| 88400b769d | |||
| b6831fc532 | |||
| b231b60b5a | |||
| 2679c9ebc2 | |||
| 278253b809 | |||
| 8a933de0bd | |||
| 704cba06f4 | |||
| b946098bd0 | |||
| f7b95f7679 | |||
| e38967874e | |||
| 8368c4e183 | |||
| 951f14ef69 | |||
| 4a75be95ef | |||
| 1125557fbc | |||
| db63138307 | |||
| a52feac93b | |||
| ad7b6ad2c6 | |||
| 33e3850bb7 | |||
| c29605dbd0 | |||
| 14b2ba4f73 | |||
| 8868684125 | |||
| c1ceb0cd50 | |||
| 1105f672a5 | |||
| 67cc49c068 | |||
| 505406508b | |||
| bfd37951df | |||
| 339cae05f1 | |||
| 48069c10a4 | |||
| 51317b2901 | |||
| e5ce53b2aa | |||
| 2bc7c93174 | |||
| 1d97f01942 | |||
| 5ad517f1d3 | |||
| 8088c70038 | |||
| e36df82ba9 | |||
| de513a12da | |||
| 06f1a813ce | |||
| 1de9195f20 | |||
| eaa21ead3e | |||
| 3ea4a9b000 | |||
| f0484c1c28 | |||
| 7ebda09a5f | |||
| 161ca0ee28 | |||
| 984078db76 | |||
| fbc0ae8918 | |||
| 3e93656f1f | |||
| 01bf17d7c8 | |||
| 651013fcf2 | |||
| 1c2d796c50 | |||
| 5ef4bfcb6b | |||
| a305b6419e | |||
| dcfdff2abe | |||
| de77a6039a | |||
| e44eab55c3 | |||
| c73ad9a627 | |||
| be3d4a4f7c | |||
| c86792901b | |||
| a2645a10f0 | |||
| 772d8a0d41 | |||
| 57348c13b2 | |||
| ffb92a967e | |||
| ed933f0452 | |||
| 26e67fe457 | |||
| 4507d2d032 | |||
| 97b43d2fc9 | |||
| 88916a54d3 | |||
| 3aa57ebce8 | |||
| 623d300da3 | |||
| 10d7562e7a | |||
| 807e747018 | |||
| 1301cdce12 | |||
| c5fcbf5982 | |||
| a9daaeba55 | |||
| dc33c35433 | |||
| 189605484a | |||
| bb3e93d976 | |||
| 44d692aad1 | |||
| bb0ca824b8 | |||
| a0b8de4ab4 | |||
| f73a80c684 | |||
| 0eddb7d5a3 | |||
| 6ff36cb1e4 | |||
| c513d68813 | |||
| 8d3f4e031c | |||
| 3aa8778a67 | |||
| d4a3cec3c2 | |||
| 96eca65ed0 | |||
| 30fd283898 | |||
| 726ea7312b | |||
| f3a7180d4b | |||
| 0173ce32c3 | |||
| d4b6c16bf9 | |||
| c571f201d3 | |||
| e4ecf50ed4 | |||
| 5ee99cfdab | |||
| 72e610b50d | |||
| ba34787333 | |||
| e11b0e6224 | |||
| c78ee18e05 | |||
| 5f5b484243 | |||
| bcc451a6a1 | |||
| 55a7f6a556 | |||
| e9b6265c60 | |||
| 2e2f3271c9 | |||
| fa3e92d643 | |||
| 1f9b52eda6 | |||
| 7074b6fc76 | |||
| b4c4fb94f7 | |||
| 22bdb64fa9 | |||
| af02158d05 | |||
| 6a8db92fc4 | |||
| 6f219ef17e | |||
| 667e51abdc | |||
| 7b98277c32 | |||
| 77ed7ed8be | |||
| ce584d9326 | |||
| 5685352375 | |||
| 036b2917a6 | |||
| e972320722 | |||
| 4ac1e9db35 | |||
| 9fe4403b40 | |||
| 4f4084ab45 | |||
| 72227dc9ab | |||
| d44795a421 | |||
| e9584144e4 | |||
| bbedfa3b75 | |||
| 03f7d74096 | |||
| a62693d611 | |||
| 421f195e1e | |||
| b22dff523f | |||
| 58d0c6e315 | |||
| 36178cac22 | |||
| 376786fa25 | |||
| efceda1c55 | |||
| 047e317c51 | |||
| 36ad63adb9 | |||
| 45c88caf58 | |||
| ca11a9217a | |||
| fd6fbbba77 | |||
| e2fb83bda9 | |||
| 40567e8f61 | |||
| 64c899b159 | |||
| b267cc4097 | |||
| f4657b6592 | |||
| 6a2f512638 | |||
| 499005c21f | |||
| 0e18d3fc48 | |||
| 3b831c4eb4 | |||
| 40564944ef | |||
| fdbb6d2e5b | |||
| 398cd63082 | |||
| efd4af14e5 | |||
| 889697fc31 | |||
| 3aedf6618d | |||
| abf42e13af | |||
| 40bcbebadd | |||
| 364f2e6d49 | |||
| 7e5dfd75f9 | |||
| b94d10c614 | |||
| 8c40b417ec | |||
| 1f17dfe6ea | |||
| be6ea1a224 | |||
| 583e9effae | |||
| 619b2757c8 | |||
| 9e27eee029 | |||
| c2d3c611e1 | |||
| 735f589e54 | |||
| 1e3d6fbb77 | |||
| 3995e7159a | |||
| 6662acf0b0 | |||
| feb8abca95 | |||
| 75c83bd91d | |||
| f6d5897ed3 | |||
| ed356763a1 | |||
| 4847246d84 | |||
| a0ae6cb77e | |||
| 2aa2550be0 | |||
| df39913d49 | |||
| 2225505d48 | |||
| afe20e61ee | |||
| 794e9c6511 | |||
| e7e423bf29 | |||
| 2173297207 | |||
| b091cfa474 | |||
| 057bb3a414 | |||
| c1442c9acc | |||
| 977f1beafd | |||
| 39ee710e97 | |||
| 1d1841c8db | |||
| 3e44774ed4 | |||
| 9e2fa01896 | |||
| 7811f9840c | |||
| 34425c6200 | |||
| 46f9fc549a | |||
| 237abf359b | |||
| c5e8739009 | |||
| 0ea8040977 | |||
| 1f10e8efe3 | |||
| 8c2688670e | |||
| bc5d49b568 | |||
| 27ea84e720 | |||
| 0becb66bfd | |||
| 11f88492e9 | |||
| ae3e01466f | |||
| 5d04dd8a83 | |||
| 52c15a8151 | |||
| f6104e7051 | |||
| ed710f6fe5 | |||
| 7e6e239d7f | |||
| 835f453384 | |||
| cc475ae1e9 | |||
| a5249ec646 | |||
| d0e3090421 | |||
| 14f68c8937 |
@ -2,6 +2,7 @@
|
||||
/app
|
||||
|
||||
# Build Artifacts
|
||||
/schema/dist
|
||||
/server/dist
|
||||
/client/.next
|
||||
|
||||
|
||||
@ -1,12 +1,23 @@
|
||||
{
|
||||
"ignorePatterns": ["/app"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"extends": ["plugin:@typescript-eslint/recommended"],
|
||||
"plugins": ["@typescript-eslint/eslint-plugin", "simple-import-sort"],
|
||||
"plugins": ["@typescript-eslint/eslint-plugin", "unused-imports", "simple-import-sort"],
|
||||
"rules": {
|
||||
// ESLint
|
||||
"no-unused-vars": "off",
|
||||
|
||||
// Unused Imports
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
"unused-imports/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"vars": "all",
|
||||
"args": "none",
|
||||
"varsIgnorePattern": "^_",
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
|
||||
// Simple Import Sort
|
||||
"simple-import-sort/imports": "error",
|
||||
"simple-import-sort/exports": "error",
|
||||
|
||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@ -1,2 +1,2 @@
|
||||
github: AmruthPillai
|
||||
custom: https://paypal.me/RajaRajanA
|
||||
custom: https://paypal.me/amruthde
|
||||
|
||||
43
.github/ISSUE_TEMPLATE/bug-report.md
vendored
43
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@ -1,43 +0,0 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Create a report to help improve
|
||||
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. -->
|
||||
75
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
Normal file
75
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
name: 🐞 Bug Report
|
||||
description: Create a bug report to help improve Reactive Resume.
|
||||
|
||||
title: '[Bug] <title>'
|
||||
labels: [Bug, Needs Triage]
|
||||
assignees: 'AmruthPillai'
|
||||
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue already exists for the bug you encountered.
|
||||
options:
|
||||
- label: Yes, I have searched the existing issues
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: variant
|
||||
attributes:
|
||||
label: Product Variant
|
||||
description: What variant of Reactive Resume are you using?
|
||||
options:
|
||||
- Cloud (http://rxresu.me)
|
||||
- Self-Hosted
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Current Behavior
|
||||
description: A concise description of what you're experiencing.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A concise description of what you expected to happen.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: Detailed steps to reproduce the behavior, so that it can be easily diagnosed.
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: dropdown
|
||||
id: browsers
|
||||
attributes:
|
||||
label: What browsers are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- Firefox
|
||||
- Chrome
|
||||
- Safari
|
||||
- Microsoft Edge
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: |
|
||||
Links? References? Anything that will give us more context about the issue you are encountering!
|
||||
|
||||
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||
validations:
|
||||
required: false
|
||||
23
.github/ISSUE_TEMPLATE/feature-request.md
vendored
23
.github/ISSUE_TEMPLATE/feature-request.md
vendored
@ -1,23 +0,0 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for this project
|
||||
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. -->
|
||||
22
.github/ISSUE_TEMPLATE/feature-request.yaml
vendored
Normal file
22
.github/ISSUE_TEMPLATE/feature-request.yaml
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
name: ✨ Feature Request
|
||||
description: Suggest an feature or idea that you would like to see in Reactive Resume.
|
||||
|
||||
title: '[Feature] <title>'
|
||||
labels: [Feature, Needs Triage]
|
||||
assignees: 'AmruthPillai'
|
||||
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue already exists for the feature you requested.
|
||||
options:
|
||||
- label: Yes, I have searched the existing issues and it doesn't exist
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Feature Description
|
||||
description: A concise description of what feature you would like to see in Reactive Resume.
|
||||
validations:
|
||||
required: true
|
||||
142
.github/workflows/build-deploy.yml
vendored
Normal file
142
.github/workflows/build-deploy.yml
vendored
Normal file
@ -0,0 +1,142 @@
|
||||
name: Build and Deploy
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build-amd64:
|
||||
name: Build (amd64)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image:
|
||||
- client
|
||||
- server
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Retrieve version from package.json
|
||||
id: version
|
||||
uses: martinbeentjes/npm-get-version-action@v1.3.1
|
||||
|
||||
- name: Docker Metadaata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4.6.0
|
||||
with:
|
||||
images: amruthpillai/reactive-resume
|
||||
tags: |
|
||||
type=raw,value=${{ matrix.image }}-latest
|
||||
type=raw,value=${{ matrix.image }}-${{ steps.version.outputs.current-version }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.2.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2.9.1
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: $GITHUB_REPOSITORY_OWNER
|
||||
password: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Build and Push
|
||||
id: build
|
||||
uses: docker/build-push-action@v4.1.1
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64
|
||||
file: ${{ matrix.image }}/Dockerfile
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
build-args: |
|
||||
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
build-arm64:
|
||||
name: Build (arm64)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image:
|
||||
- client
|
||||
- server
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Retrieve version from package.json
|
||||
id: version
|
||||
uses: martinbeentjes/npm-get-version-action@v1.3.1
|
||||
|
||||
- name: Docker Metadaata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4.6.0
|
||||
with:
|
||||
images: amruthpillai/reactive-resume
|
||||
tags: |
|
||||
type=raw,value=${{ matrix.image }}-arm64-latest
|
||||
type=raw,value=${{ matrix.image }}-arm64-${{ steps.version.outputs.current-version }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.2.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2.9.1
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: $GITHUB_REPOSITORY_OWNER
|
||||
password: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Build and Push
|
||||
id: build
|
||||
uses: docker/build-push-action@v4.1.1
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/arm64
|
||||
file: ${{ matrix.image }}/Dockerfile
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
build-args: |
|
||||
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
deploy:
|
||||
name: Deploy
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
needs: build-amd64
|
||||
|
||||
steps:
|
||||
- name: Install DigitalOcean CLI
|
||||
uses: digitalocean/action-doctl@v2.3.0
|
||||
with:
|
||||
token: ${{ secrets.DIGITALOCEAN_TOKEN }}
|
||||
|
||||
- name: Create Deployment with Latest Version
|
||||
run: doctl apps create-deployment ${{ secrets.DIGITALOCEAN_APP_ID }} --wait --force-rebuild
|
||||
28
.github/workflows/digitalocean-deploy.yml
vendored
28
.github/workflows/digitalocean-deploy.yml
vendored
@ -1,28 +0,0 @@
|
||||
name: Deploy Latest Version on DigitalOcean
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows:
|
||||
- Build and Push Docker Image
|
||||
types:
|
||||
- completed
|
||||
|
||||
jobs:
|
||||
on-success:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
steps:
|
||||
- name: Install DigitalOcean CLI
|
||||
uses: digitalocean/action-doctl@v2.3.0
|
||||
with:
|
||||
token: ${{ secrets.DIGITALOCEAN_TOKEN }}
|
||||
|
||||
- name: Create Deployment with Latest Version
|
||||
run: doctl apps create-deployment ${{ secrets.DIGITALOCEAN_APP_ID }} --wait --force-rebuild
|
||||
|
||||
on-failure:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
|
||||
steps:
|
||||
- name: Abruptly end the worklfow
|
||||
run: exit 1
|
||||
58
.github/workflows/docker-build-push.yml
vendored
58
.github/workflows/docker-build-push.yml
vendored
@ -1,58 +0,0 @@
|
||||
name: Build and Push Docker Image
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build_matrix:
|
||||
name: Build and Push Docker Image
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
image: [client, server]
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.0
|
||||
|
||||
- id: version
|
||||
name: App Version
|
||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.1.0
|
||||
with:
|
||||
platforms: amd64
|
||||
|
||||
- id: buildx
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2.5.0
|
||||
|
||||
- name: Login to Docker Hub
|
||||
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.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: $GITHUB_REPOSITORY_OWNER
|
||||
password: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Build and Push Docker Image
|
||||
uses: docker/build-push-action@v4.0.0
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64
|
||||
file: ${{ matrix.image }}/Dockerfile
|
||||
tags: |
|
||||
amruthpillai/reactive-resume:${{ matrix.image }}-latest
|
||||
amruthpillai/reactive-resume:${{ matrix.image }}-${{ steps.version.outputs.current-version }}
|
||||
ghcr.io/amruthpillai/reactive-resume:${{ matrix.image }}-latest
|
||||
ghcr.io/amruthpillai/reactive-resume:${{ matrix.image }}-${{ steps.version.outputs.current-version }}
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
# Environment Variables
|
||||
.env
|
||||
.env.*
|
||||
*.env
|
||||
!.env.gitpod
|
||||
!.env.example
|
||||
|
||||
@ -12,3 +13,6 @@ node_modules
|
||||
|
||||
# Intellij
|
||||
.idea
|
||||
|
||||
# Turborepo
|
||||
.turbo
|
||||
@ -30,7 +30,7 @@ ports:
|
||||
onOpen: ignore
|
||||
visibility: private
|
||||
|
||||
# Client
|
||||
# Server
|
||||
- port: 3100
|
||||
onOpen: ignore
|
||||
visibility: public
|
||||
|
||||
13
README.md
13
README.md
@ -1,4 +1,4 @@
|
||||
<img src="https://rxresu.me/images/logos/logo.png" alt="Reactive Resume" width="256px" height="256px" />
|
||||
<img src="/client/public/logo/dark.svg" alt="Reactive Resume" width="256px" height="256px" />
|
||||
|
||||
# Reactive Resume
|
||||
|
||||
@ -16,6 +16,16 @@ Reactive Resume is a free and open source resume builder that’s built to make
|
||||
|
||||
You have complete control over what goes into your resume, how it looks, what colors, what templates, even the layout in which sections placed. Want a dark mode resume? It’s as easy as editing 3 values and you’re done. You don’t need to wait to see your changes either. Everything you type, everything you change, appears immediately on your resume and gets updated in real time.
|
||||
|
||||
## ❗️ Important Notice
|
||||
|
||||
Due to increasing and recurring costs from Google Cloud, I would have to take down the Version 1 and Version 2 editions of the application that live on https://v1.rxresu.me/ and https://v2.rxresu.me/ respectively.
|
||||
|
||||
I plan to take down the servers on 1st September 2023, so if you have any data on the earlier versions, please migrate it over to the latest version on https://rxresu.me/ as soon as you can.
|
||||
|
||||
The current version will remain unchanged, and is still actively supported. It will have no effect on these changes.
|
||||
|
||||
Thank you to the 400,000+ people using the app! 🙏
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Reactive Resume](#reactive-resume)
|
||||
@ -30,6 +40,7 @@ You have complete control over what goes into your resume, how it looks, what co
|
||||
- [Donations](#donations)
|
||||
- [GitHub Sponsor](#github-sponsor)
|
||||
- [PayPal](#paypal)
|
||||
- [GitHub Star History](#github-star-history)
|
||||
- [Infrastructure](#infrastructure)
|
||||
- [Contributors Wall](#contributors-wall)
|
||||
- [License](#license)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application' version '7.4.2' apply false
|
||||
id 'com.android.library' version '7.4.2' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.8.20' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.9.0' apply false
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
|
||||
@ -12,6 +12,9 @@
|
||||
"@next/next/no-img-element": "off",
|
||||
"@next/next/no-sync-scripts": "off",
|
||||
|
||||
// React
|
||||
"react/no-unescaped-entities": "off",
|
||||
|
||||
// React Hooks
|
||||
"react-hooks/exhaustive-deps": "off",
|
||||
|
||||
|
||||
@ -21,8 +21,10 @@ 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 --filter schema build \
|
||||
&& pnpm run --filter client build
|
||||
ARG TURBO_TOKEN
|
||||
ENV TURBO_TOKEN=$TURBO_TOKEN
|
||||
|
||||
RUN pnpm exec turbo --filter client build
|
||||
|
||||
FROM base as production
|
||||
|
||||
@ -40,7 +42,4 @@ EXPOSE 3000
|
||||
|
||||
ENV PORT 3000
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=20s --retries=3 --start-period=15s \
|
||||
CMD curl -fSs localhost:3000 || exit 1
|
||||
|
||||
CMD [ "pnpm", "run", "--filter", "client", "start" ]
|
||||
43
client/Dockerfile.standalone
Normal file
43
client/Dockerfile.standalone
Normal file
@ -0,0 +1,43 @@
|
||||
FROM node:20-alpine AS base
|
||||
|
||||
FROM base AS deps
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
|
||||
RUN \
|
||||
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
|
||||
elif [ -f package-lock.json ]; then npm ci; \
|
||||
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
|
||||
else echo "Lockfile not found." && exit 1; \
|
||||
fi
|
||||
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
RUN yarn global add pnpm && pnpm build
|
||||
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV production
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT 3000
|
||||
|
||||
CMD ["node", "client/server.js"]
|
||||
@ -15,7 +15,7 @@
|
||||
.controller {
|
||||
@apply z-20 flex items-center justify-center shadow-lg;
|
||||
@apply flex rounded-l-full rounded-r-full px-4;
|
||||
@apply bg-neutral-50 dark:bg-neutral-800;
|
||||
@apply bg-zinc-50 dark:bg-zinc-900;
|
||||
@apply opacity-70 transition-opacity duration-200 hover:opacity-100;
|
||||
|
||||
> button {
|
||||
@ -23,6 +23,6 @@
|
||||
}
|
||||
|
||||
> hr {
|
||||
@apply mx-3 h-5 w-0.5 bg-neutral-900/40 dark:bg-neutral-50/20;
|
||||
@apply mx-3 h-5 w-0.5 bg-zinc-900/40 dark:bg-zinc-50/20;
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@ import {
|
||||
ZoomOut,
|
||||
} from '@mui/icons-material';
|
||||
import { ButtonBase, Divider, Tooltip, useMediaQuery, useTheme } from '@mui/material';
|
||||
import clsx from 'clsx';
|
||||
import dayjs from 'dayjs';
|
||||
import get from 'lodash/get';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
@ -26,6 +25,7 @@ import { printResumeAsPdf, PrintResumeAsPdfParams } from '@/services/printer';
|
||||
import { togglePageBreakLine, togglePageOrientation, toggleSidebar } from '@/store/build/buildSlice';
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||
import getResumeUrl from '@/utils/getResumeUrl';
|
||||
import { cn } from '@/utils/styles';
|
||||
|
||||
import styles from './ArtboardController.module.scss';
|
||||
|
||||
@ -60,7 +60,7 @@ const ArtboardController: React.FC<ReactZoomPanPinchHandlers> = ({ zoomIn, zoomO
|
||||
const url = getResumeUrl(resume, { withHost: true });
|
||||
await navigator.clipboard.writeText(url);
|
||||
|
||||
toast.success(t<string>('common.toast.success.resume-link-copied'));
|
||||
toast.success(t('common.toast.success.resume-link-copied'));
|
||||
};
|
||||
|
||||
const handleExportPDF = async () => {
|
||||
@ -77,40 +77,40 @@ const ArtboardController: React.FC<ReactZoomPanPinchHandlers> = ({ zoomIn, zoomO
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx({
|
||||
className={cn({
|
||||
[styles.container]: true,
|
||||
[styles.pushLeft]: left.open,
|
||||
[styles.pushRight]: right.open,
|
||||
})}
|
||||
>
|
||||
<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 })}>
|
||||
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.undo')}>
|
||||
<ButtonBase onClick={handleUndo} className={cn({ '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 })}>
|
||||
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.redo')}>
|
||||
<ButtonBase onClick={handleRedo} className={cn({ 'pointer-events-none opacity-50': future.length === 0 })}>
|
||||
<RedoOutlined fontSize="medium" />
|
||||
</ButtonBase>
|
||||
</Tooltip>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.zoom-in')}>
|
||||
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.zoom-in')}>
|
||||
<ButtonBase onClick={() => zoomIn(0.25)}>
|
||||
<ZoomIn fontSize="medium" />
|
||||
</ButtonBase>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.zoom-out')}>
|
||||
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.zoom-out')}>
|
||||
<ButtonBase onClick={() => zoomOut(0.25)}>
|
||||
<ZoomOut fontSize="medium" />
|
||||
</ButtonBase>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.center-artboard')}>
|
||||
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.center-artboard')}>
|
||||
<ButtonBase onClick={() => centerView(0.95)}>
|
||||
<FilterCenterFocus fontSize="medium" />
|
||||
</ButtonBase>
|
||||
@ -120,10 +120,10 @@ const ArtboardController: React.FC<ReactZoomPanPinchHandlers> = ({ zoomIn, zoomO
|
||||
|
||||
{isDesktop && (
|
||||
<>
|
||||
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.toggle-orientation')}>
|
||||
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.toggle-orientation')}>
|
||||
<ButtonBase
|
||||
onClick={handleTogglePageOrientation}
|
||||
className={clsx({ 'pointer-events-none opacity-50': pages.length === 1 })}
|
||||
className={cn({ 'pointer-events-none opacity-50': pages.length === 1 })}
|
||||
>
|
||||
{orientation === 'vertical' ? (
|
||||
<AlignHorizontalCenter fontSize="medium" />
|
||||
@ -133,13 +133,13 @@ const ArtboardController: React.FC<ReactZoomPanPinchHandlers> = ({ zoomIn, zoomO
|
||||
</ButtonBase>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.toggle-page-break-line')}>
|
||||
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.toggle-page-break-line')}>
|
||||
<ButtonBase onClick={handleTogglePageBreakLine}>
|
||||
<InsertPageBreak fontSize="medium" />
|
||||
</ButtonBase>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.toggle-sidebars')}>
|
||||
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.toggle-sidebars')}>
|
||||
<ButtonBase onClick={handleToggleSidebar}>
|
||||
<ViewSidebar fontSize="medium" />
|
||||
</ButtonBase>
|
||||
@ -149,13 +149,13 @@ const ArtboardController: React.FC<ReactZoomPanPinchHandlers> = ({ zoomIn, zoomO
|
||||
</>
|
||||
)}
|
||||
|
||||
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.copy-link')}>
|
||||
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.copy-link')}>
|
||||
<ButtonBase onClick={handleCopyLink}>
|
||||
<Link fontSize="medium" />
|
||||
</ButtonBase>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.export-pdf')}>
|
||||
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.export-pdf')}>
|
||||
<ButtonBase onClick={handleExportPDF} disabled={isLoading}>
|
||||
<Download fontSize="medium" />
|
||||
</ButtonBase>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
.center {
|
||||
@apply mx-0 flex flex-grow pt-12 lg:pt-16;
|
||||
@apply transition-[margin-left,margin-right] duration-200;
|
||||
@apply bg-neutral-200 dark:bg-neutral-900;
|
||||
@apply bg-zinc-100 dark:bg-zinc-900;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import clsx from 'clsx';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch';
|
||||
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
import { cn } from '@/utils/styles';
|
||||
|
||||
import ArtboardController from './ArtboardController';
|
||||
import styles from './Center.module.scss';
|
||||
@ -19,7 +19,7 @@ const Center = () => {
|
||||
if (isEmpty(resume)) return null;
|
||||
|
||||
return (
|
||||
<div className={clsx(styles.center)}>
|
||||
<div className={cn(styles.center)}>
|
||||
<Header />
|
||||
|
||||
<TransformWrapper
|
||||
@ -35,7 +35,7 @@ const Center = () => {
|
||||
<>
|
||||
<TransformComponent wrapperClass={styles.wrapper}>
|
||||
<div
|
||||
className={clsx({
|
||||
className={cn({
|
||||
[styles.artboard]: true,
|
||||
'flex-col': orientation === 'vertical',
|
||||
})}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
.header {
|
||||
@apply mx-0 flex justify-between shadow;
|
||||
@apply bg-neutral-800 text-neutral-100;
|
||||
@apply bg-zinc-900 text-zinc-100;
|
||||
@apply transition-[margin-left,margin-right] duration-200;
|
||||
|
||||
button > svg {
|
||||
@apply text-base text-neutral-100;
|
||||
@apply text-base text-zinc-100;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -20,14 +20,13 @@ import {
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import { Resume } from '@reactive-resume/schema';
|
||||
import clsx from 'clsx';
|
||||
import get from 'lodash/get';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useMutation } from 'react-query';
|
||||
import { Resume } from 'schema';
|
||||
|
||||
import { RESUMES_QUERY } from '@/constants/index';
|
||||
import { ServerError } from '@/services/axios';
|
||||
@ -37,6 +36,7 @@ import { setSidebarState, toggleSidebar } from '@/store/build/buildSlice';
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||
import { setModalState } from '@/store/modal/modalSlice';
|
||||
import getResumeUrl from '@/utils/getResumeUrl';
|
||||
import { cn } from '@/utils/styles';
|
||||
|
||||
import styles from './Header.module.scss';
|
||||
|
||||
@ -102,7 +102,7 @@ const Header = () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
@ -132,14 +132,14 @@ const Header = () => {
|
||||
const url = getResumeUrl(resume, { withHost: true });
|
||||
await navigator.clipboard.writeText(url);
|
||||
|
||||
toast.success(t<string>('common.toast.success.resume-link-copied'));
|
||||
toast.success(t('common.toast.success.resume-link-copied'));
|
||||
};
|
||||
|
||||
return (
|
||||
<AppBar elevation={0} position="fixed">
|
||||
<Toolbar
|
||||
variant="regular"
|
||||
className={clsx({
|
||||
className={cn({
|
||||
[styles.header]: true,
|
||||
[styles.pushLeft]: left.open,
|
||||
[styles.pushRight]: right.open,
|
||||
@ -165,14 +165,14 @@ const Header = () => {
|
||||
<ListItemIcon>
|
||||
<DriveFileRenameOutline className="scale-90" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>{t<string>('builder.header.menu.rename')}</ListItemText>
|
||||
<ListItemText>{t('builder.header.menu.rename')}</ListItemText>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem onClick={handleDuplicate}>
|
||||
<ListItemIcon>
|
||||
<CopyAll className="scale-90" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>{t<string>('builder.header.menu.duplicate')}</ListItemText>
|
||||
<ListItemText>{t('builder.header.menu.duplicate')}</ListItemText>
|
||||
</MenuItem>
|
||||
|
||||
{resume.public ? (
|
||||
@ -180,27 +180,27 @@ const Header = () => {
|
||||
<ListItemIcon>
|
||||
<LinkIcon className="scale-90" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>{t<string>('builder.header.menu.share-link')}</ListItemText>
|
||||
<ListItemText>{t('builder.header.menu.share-link')}</ListItemText>
|
||||
</MenuItem>
|
||||
) : (
|
||||
<Tooltip arrow placement="right" title={t<string>('builder.header.menu.tooltips.share-link')}>
|
||||
<Tooltip arrow placement="right" title={t('builder.header.menu.tooltips.share-link')}>
|
||||
<div>
|
||||
<MenuItem>
|
||||
<ListItemIcon>
|
||||
<LinkIcon className="scale-90" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>{t<string>('builder.header.menu.share-link')}</ListItemText>
|
||||
<ListItemText>{t('builder.header.menu.share-link')}</ListItemText>
|
||||
</MenuItem>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<Tooltip arrow placement="right" title={t<string>('builder.header.menu.tooltips.delete')}>
|
||||
<Tooltip arrow placement="right" title={t('builder.header.menu.tooltips.delete')}>
|
||||
<MenuItem onClick={handleDelete}>
|
||||
<ListItemIcon>
|
||||
<Delete className="scale-90" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>{t<string>('builder.header.menu.delete')}</ListItemText>
|
||||
<ListItemText>{t('builder.header.menu.delete')}</ListItemText>
|
||||
</MenuItem>
|
||||
</Tooltip>
|
||||
</Menu>
|
||||
|
||||
@ -18,8 +18,8 @@
|
||||
content: 'Page Break';
|
||||
top: calc(297mm - 19px);
|
||||
|
||||
@apply absolute w-full border-b border-dashed border-neutral-800/75;
|
||||
@apply flex items-end justify-end pr-2 pb-0.5 text-xs font-bold text-neutral-800/75;
|
||||
@apply absolute w-full border-b border-dashed border-zinc-900/75;
|
||||
@apply flex items-end justify-end pr-2 pb-0.5 text-xs font-bold text-zinc-900/75;
|
||||
@apply print:hidden;
|
||||
|
||||
:global(.preview-mode) &,
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { CustomCSS, PageConfig, ThemeConfig, Typography } from '@reactive-resume/schema';
|
||||
import clsx from 'clsx';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useMemo } from 'react';
|
||||
import { CustomCSS, PageConfig, ThemeConfig, Typography } from 'schema';
|
||||
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
import templateMap from '@/templates/templateMap';
|
||||
@ -49,9 +49,7 @@ const Page: React.FC<Props> = ({ page, showPageNumbers = false }) => {
|
||||
{TemplatePage && <TemplatePage page={page} />}
|
||||
</div>
|
||||
|
||||
{showPageNumbers && (
|
||||
<h4 className={styles.pageNumber}>{`${t<string>('builder.common.glossary.page')} ${page + 1}`}</h4>
|
||||
)}
|
||||
{showPageNumbers && <h4 className={styles.pageNumber}>{`${t('builder.common.glossary.page')} ${page + 1}`}</h4>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
.container {
|
||||
@apply h-screen w-[95vw] md:w-[70vw] lg:w-[50vw] xl:w-[30vw] 2xl:w-[28vw];
|
||||
@apply bg-neutral-50 text-neutral-900 dark:bg-neutral-900 dark:text-neutral-50;
|
||||
@apply relative flex border-r-2 border-neutral-50/10;
|
||||
@apply bg-zinc-100 text-zinc-900 dark:bg-zinc-900 dark:text-zinc-100;
|
||||
@apply relative flex border-r-2 border-zinc-100/10;
|
||||
|
||||
nav {
|
||||
@apply absolute inset-y-0 left-0;
|
||||
@apply w-14 py-4 md:w-16 md:px-2;
|
||||
@apply bg-neutral-100 shadow dark:bg-neutral-800;
|
||||
@apply bg-zinc-100 shadow dark:bg-zinc-900;
|
||||
@apply flex flex-col items-center justify-between;
|
||||
|
||||
hr {
|
||||
@ -29,7 +29,7 @@
|
||||
> section {
|
||||
@apply grid gap-4;
|
||||
@apply pt-5 pb-7 first:pt-0;
|
||||
@apply border-b border-neutral-900/10 last:border-b-0 dark:border-neutral-50/10;
|
||||
@apply border-b border-zinc-900/10 last:border-b-0 dark:border-zinc-100/10;
|
||||
|
||||
hr {
|
||||
@apply my-2;
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import { Add, Star } from '@mui/icons-material';
|
||||
import { Button, Divider, IconButton, SwipeableDrawer, Tooltip, useMediaQuery, useTheme } from '@mui/material';
|
||||
import { Section as SectionRecord } from '@reactive-resume/schema';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import get from 'lodash/get';
|
||||
import Link from 'next/link';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { ReactComponentElement, useMemo } from 'react';
|
||||
import { Section as SectionRecord } from 'schema';
|
||||
import { validate } from 'uuid';
|
||||
|
||||
import Logo from '@/components/shared/Logo';
|
||||
import Icon from '@/components/shared/Icon';
|
||||
import { getCustomSections, getSectionsByType, left } from '@/config/sections';
|
||||
import { setSidebarState } from '@/store/build/buildSlice';
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||
@ -69,7 +69,7 @@ const LeftSidebar = () => {
|
||||
sectionsComponents.push(
|
||||
<section key={id} id={id}>
|
||||
{component}
|
||||
</section>
|
||||
</section>,
|
||||
);
|
||||
|
||||
if (addMore) {
|
||||
@ -89,7 +89,7 @@ const LeftSidebar = () => {
|
||||
elements.push(
|
||||
<section key={newId} id={`section-${newId}`}>
|
||||
{newComponent}
|
||||
</section>
|
||||
</section>,
|
||||
);
|
||||
}
|
||||
sectionsComponents.push(...elements);
|
||||
@ -112,7 +112,9 @@ const LeftSidebar = () => {
|
||||
<nav className="overflow-y-auto">
|
||||
<div>
|
||||
<Link href="/dashboard">
|
||||
<Logo size={40} />
|
||||
<IconButton>
|
||||
<Icon size={24} />
|
||||
</IconButton>
|
||||
</Link>
|
||||
<Divider />
|
||||
</div>
|
||||
@ -123,7 +125,7 @@ const LeftSidebar = () => {
|
||||
arrow
|
||||
key={id}
|
||||
placement="right"
|
||||
title={t<string>(`builder.leftSidebar.sections.${id}.heading`, get(sections, `${id}.name`))}
|
||||
title={t(`builder.leftSidebar.sections.${id}.heading`, get(sections, `${id}.name`))}
|
||||
>
|
||||
<IconButton onClick={() => handleClick(id)}>{icon}</IconButton>
|
||||
</Tooltip>
|
||||
@ -132,7 +134,7 @@ const LeftSidebar = () => {
|
||||
{customSections.map(({ id }) => (
|
||||
<Tooltip
|
||||
key={id}
|
||||
title={t<string>(`builder.leftSidebar.sections.${id}.heading`, get(sections, `${id}.name`))}
|
||||
title={t(`builder.leftSidebar.sections.${id}.heading`, get(sections, `${id}.name`))}
|
||||
placement="right"
|
||||
arrow
|
||||
>
|
||||
@ -157,8 +159,8 @@ const LeftSidebar = () => {
|
||||
|
||||
<div className="py-6 text-right">
|
||||
<Button fullWidth variant="outlined" startIcon={<Add />} onClick={handleAddSection}>
|
||||
{t<string>('builder.common.actions.add', {
|
||||
token: t<string>('builder.leftSidebar.sections.section.heading'),
|
||||
{t('builder.common.actions.add', {
|
||||
token: t('builder.leftSidebar.sections.section.heading'),
|
||||
})}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -24,7 +24,7 @@ const Basics = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Heading path="sections.basics" name={t<string>('builder.leftSidebar.sections.basics.heading')} />
|
||||
<Heading path="sections.basics" name={t('builder.leftSidebar.sections.basics.heading')} />
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div className="grid items-center gap-4 sm:col-span-2 sm:grid-cols-3">
|
||||
@ -33,10 +33,10 @@ const Basics = () => {
|
||||
</div>
|
||||
|
||||
<div className="grid w-full gap-2 sm:col-span-2">
|
||||
<ResumeInput label={t<string>('builder.leftSidebar.sections.basics.name.label')} path="basics.name" />
|
||||
<ResumeInput label={t('builder.leftSidebar.sections.basics.name.label')} path="basics.name" />
|
||||
|
||||
<Button variant="outlined" startIcon={<PhotoFilter />} onClick={handleClick}>
|
||||
{t<string>('builder.leftSidebar.sections.basics.actions.photo-filters')}
|
||||
{t('builder.leftSidebar.sections.basics.actions.photo-filters')}
|
||||
</Button>
|
||||
|
||||
<Popover
|
||||
@ -59,28 +59,24 @@ const Basics = () => {
|
||||
|
||||
<ResumeInput
|
||||
type="date"
|
||||
label={t<string>('builder.leftSidebar.sections.basics.birthdate.label')}
|
||||
label={t('builder.leftSidebar.sections.basics.birthdate.label')}
|
||||
path="basics.birthdate"
|
||||
className="sm:col-span-2"
|
||||
/>
|
||||
<ResumeInput
|
||||
label={t<string>('builder.common.form.email.label')}
|
||||
path="basics.email"
|
||||
className="sm:col-span-2"
|
||||
/>
|
||||
<ResumeInput label={t<string>('builder.common.form.phone.label')} path="basics.phone" />
|
||||
<ResumeInput label={t<string>('builder.common.form.url.label')} path="basics.website" />
|
||||
<ResumeInput label={t('builder.common.form.email.label')} path="basics.email" className="sm:col-span-2" />
|
||||
<ResumeInput label={t('builder.common.form.phone.label')} path="basics.phone" />
|
||||
<ResumeInput label={t('builder.common.form.url.label')} path="basics.website" />
|
||||
|
||||
<Divider className="sm:col-span-2" />
|
||||
|
||||
<ResumeInput
|
||||
label={t<string>('builder.leftSidebar.sections.basics.headline.label')}
|
||||
label={t('builder.leftSidebar.sections.basics.headline.label')}
|
||||
path="basics.headline"
|
||||
className="sm:col-span-2"
|
||||
/>
|
||||
<ResumeInput
|
||||
type="textarea"
|
||||
label={t<string>('builder.common.form.summary.label')}
|
||||
label={t('builder.common.form.summary.label')}
|
||||
path="basics.summary"
|
||||
className="sm:col-span-2"
|
||||
markdownSupported
|
||||
|
||||
@ -8,28 +8,19 @@ const Location = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Heading path="sections.location" name={t<string>('builder.leftSidebar.sections.location.heading')} />
|
||||
<Heading path="sections.location" name={t('builder.leftSidebar.sections.location.heading')} />
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<ResumeInput
|
||||
label={t<string>('builder.leftSidebar.sections.location.address.label')}
|
||||
label={t('builder.leftSidebar.sections.location.address.label')}
|
||||
path="basics.location.address"
|
||||
className="sm:col-span-2"
|
||||
/>
|
||||
<ResumeInput label={t('builder.leftSidebar.sections.location.city.label')} path="basics.location.city" />
|
||||
<ResumeInput label={t('builder.leftSidebar.sections.location.region.label')} path="basics.location.region" />
|
||||
<ResumeInput label={t('builder.leftSidebar.sections.location.country.label')} path="basics.location.country" />
|
||||
<ResumeInput
|
||||
label={t<string>('builder.leftSidebar.sections.location.city.label')}
|
||||
path="basics.location.city"
|
||||
/>
|
||||
<ResumeInput
|
||||
label={t<string>('builder.leftSidebar.sections.location.region.label')}
|
||||
path="basics.location.region"
|
||||
/>
|
||||
<ResumeInput
|
||||
label={t<string>('builder.leftSidebar.sections.location.country.label')}
|
||||
path="basics.location.country"
|
||||
/>
|
||||
<ResumeInput
|
||||
label={t<string>('builder.leftSidebar.sections.location.postal-code.label')}
|
||||
label={t('builder.leftSidebar.sections.location.postal-code.label')}
|
||||
path="basics.location.postalCode"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { Circle, Square, SquareRounded } from '@mui/icons-material';
|
||||
import { Checkbox, Divider, FormControlLabel, Slider, ToggleButton, ToggleButtonGroup } from '@mui/material';
|
||||
import { Photo, PhotoShape } from '@reactive-resume/schema';
|
||||
import get from 'lodash/get';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Photo, PhotoShape } from 'schema';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||
import { setResumeState } from '@/store/resume/resumeSlice';
|
||||
@ -30,9 +30,9 @@ const PhotoFilters = () => {
|
||||
const handleSetBorder = (value: boolean) => dispatch(setResumeState({ path: 'basics.photo.filters.border', value }));
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 p-5 dark:bg-neutral-800">
|
||||
<div className="flex flex-col gap-2 p-5 dark:bg-zinc-900">
|
||||
<div>
|
||||
<h4 className="font-medium">{t<string>('builder.leftSidebar.sections.basics.photo-filters.size.heading')}</h4>
|
||||
<h4 className="font-medium">{t('builder.leftSidebar.sections.basics.photo-filters.size.heading')}</h4>
|
||||
|
||||
<div className="mx-2">
|
||||
<Slider
|
||||
@ -54,20 +54,18 @@ const PhotoFilters = () => {
|
||||
<Divider />
|
||||
|
||||
<div>
|
||||
<h4 className="font-medium">
|
||||
{t<string>('builder.leftSidebar.sections.basics.photo-filters.effects.heading')}
|
||||
</h4>
|
||||
<h4 className="font-medium">{t('builder.leftSidebar.sections.basics.photo-filters.effects.heading')}</h4>
|
||||
|
||||
<div className="flex items-center">
|
||||
<FormControlLabel
|
||||
label={t<string>('builder.leftSidebar.sections.basics.photo-filters.effects.grayscale.label')}
|
||||
label={t('builder.leftSidebar.sections.basics.photo-filters.effects.grayscale.label')}
|
||||
control={
|
||||
<Checkbox color="secondary" checked={grayscale} onChange={(_, value) => handleSetGrayscale(value)} />
|
||||
}
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
label={t<string>('builder.leftSidebar.sections.basics.photo-filters.effects.border.label')}
|
||||
label={t('builder.leftSidebar.sections.basics.photo-filters.effects.border.label')}
|
||||
control={<Checkbox color="secondary" checked={border} onChange={(_, value) => handleSetBorder(value)} />}
|
||||
/>
|
||||
</div>
|
||||
@ -76,7 +74,7 @@ const PhotoFilters = () => {
|
||||
<Divider />
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<h4 className="font-medium">{t<string>('builder.leftSidebar.sections.basics.photo-filters.shape.heading')}</h4>
|
||||
<h4 className="font-medium">{t('builder.leftSidebar.sections.basics.photo-filters.shape.heading')}</h4>
|
||||
|
||||
<ToggleButtonGroup exclusive value={shape} onChange={(_, value) => handleChangeShape(value)}>
|
||||
<ToggleButton size="small" value="square" className="w-14">
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { Avatar, IconButton, Skeleton, Tooltip } from '@mui/material';
|
||||
import { Photo, Resume } from '@reactive-resume/schema';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useRef } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useMutation } from 'react-query';
|
||||
import { Photo, Resume } from 'schema';
|
||||
|
||||
import { ServerError } from '@/services/axios';
|
||||
import { deletePhoto, DeletePhotoParams, uploadPhoto, UploadPhotoParams } from '@/services/resume';
|
||||
@ -49,7 +49,7 @@ const PhotoUpload: React.FC = () => {
|
||||
const file = event.target.files[0];
|
||||
|
||||
if (file.size > FILE_UPLOAD_MAX_SIZE) {
|
||||
toast.error(t<string>('common.toast.error.upload-photo-size'));
|
||||
toast.error(t('common.toast.error.upload-photo-size'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -67,8 +67,8 @@ const PhotoUpload: React.FC = () => {
|
||||
<Tooltip
|
||||
title={
|
||||
isEmpty(photo.url)
|
||||
? (t<string>('builder.leftSidebar.sections.basics.photo-upload.tooltip.upload') as string)
|
||||
: (t<string>('builder.leftSidebar.sections.basics.photo-upload.tooltip.remove') as string)
|
||||
? (t('builder.leftSidebar.sections.basics.photo-upload.tooltip.upload') as string)
|
||||
: (t('builder.leftSidebar.sections.basics.photo-upload.tooltip.remove') as string)
|
||||
}
|
||||
>
|
||||
<Avatar sx={{ width: 96, height: 96 }} src={photo.url} />
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Add } from '@mui/icons-material';
|
||||
import { Button } from '@mui/material';
|
||||
import { ListItem } from '@reactive-resume/schema';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ListItem } from 'schema';
|
||||
|
||||
import Heading from '@/components/shared/Heading';
|
||||
import List from '@/components/shared/List';
|
||||
@ -28,7 +28,7 @@ const Profiles = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Heading path="sections.profiles" name={t<string>('builder.leftSidebar.sections.profiles.heading')} />
|
||||
<Heading path="sections.profiles" name={t('builder.leftSidebar.sections.profiles.heading')} />
|
||||
|
||||
<List
|
||||
path="basics.profiles"
|
||||
@ -40,8 +40,8 @@ const Profiles = () => {
|
||||
|
||||
<footer className="flex justify-end">
|
||||
<Button variant="outlined" startIcon={<Add />} onClick={handleAdd}>
|
||||
{t<string>('builder.common.actions.add', {
|
||||
token: t<string>('builder.leftSidebar.sections.profiles.heading', { count: 1 }),
|
||||
{t('builder.common.actions.add', {
|
||||
token: t('builder.leftSidebar.sections.profiles.heading', { count: 1 }),
|
||||
})}
|
||||
</Button>
|
||||
</footer>
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { Add } from '@mui/icons-material';
|
||||
import { Button } from '@mui/material';
|
||||
import { ListItem, Section as SectionRecord, SectionType } from '@reactive-resume/schema';
|
||||
import clsx from 'clsx';
|
||||
import get from 'lodash/get';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ListItem, Section as SectionRecord, SectionType } from 'schema';
|
||||
import { validate } from 'uuid';
|
||||
|
||||
import Heading from '@/components/shared/Heading';
|
||||
@ -98,8 +98,8 @@ const Section: React.FC<Props> = ({
|
||||
<SectionSettings path={path} />
|
||||
|
||||
<Button variant="outlined" startIcon={<Add />} onClick={handleAdd}>
|
||||
{t<string>('builder.common.actions.add', {
|
||||
token: t<string>(`builder.leftSidebar.${path}.heading`, heading),
|
||||
{t('builder.common.actions.add', {
|
||||
token: t(`builder.leftSidebar.${path}.heading`, { defaultValue: heading }),
|
||||
})}
|
||||
</Button>
|
||||
</footer>
|
||||
@ -107,7 +107,7 @@ const Section: React.FC<Props> = ({
|
||||
{addMore ? (
|
||||
<div className="py-6 text-right">
|
||||
<Button fullWidth variant="outlined" startIcon={<Add />} onClick={handleDuplicateSection}>
|
||||
{t<string>('builder.common.actions.duplicate')}
|
||||
{t('builder.common.actions.duplicate')}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@ -32,7 +32,7 @@ const SectionSettings: React.FC<Props> = ({ path }) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Tooltip title={t<string>('builder.common.columns.tooltip')}>
|
||||
<Tooltip title={t('builder.common.columns.tooltip')}>
|
||||
<ButtonBase onClick={handleClick} sx={{ padding: 1, borderRadius: 1 }} className="opacity-50 hover:opacity-75">
|
||||
<ViewWeek /> <span className="ml-1.5 text-xs">{columns}</span>
|
||||
</ButtonBase>
|
||||
@ -47,8 +47,8 @@ const SectionSettings: React.FC<Props> = ({ path }) => {
|
||||
horizontal: 'left',
|
||||
}}
|
||||
>
|
||||
<div className="p-5 dark:bg-neutral-800">
|
||||
<h4 className="mb-2 font-medium">{t<string>('builder.common.columns.heading')}</h4>
|
||||
<div className="p-5 dark:bg-zinc-900">
|
||||
<h4 className="mb-2 font-medium">{t('builder.common.columns.heading')}</h4>
|
||||
|
||||
<ToggleButtonGroup exclusive value={columns} onChange={(_, value: number) => handleSetColumns(value)}>
|
||||
{[1, 2, 3, 4].map((index) => (
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
.container {
|
||||
@apply h-screen w-[95vw] md:w-[70vw] lg:w-[50vw] xl:w-[30vw] 2xl:w-[28vw];
|
||||
@apply bg-neutral-50 text-neutral-900 dark:bg-neutral-900 dark:text-neutral-50;
|
||||
@apply relative flex border-l-2 border-neutral-50/10;
|
||||
@apply bg-zinc-50 text-zinc-900 dark:bg-zinc-900 dark:text-zinc-50;
|
||||
@apply relative flex border-l-2 border-zinc-50/10;
|
||||
|
||||
nav {
|
||||
@apply absolute inset-y-0 right-0;
|
||||
@apply w-12 py-4 md:w-16 md:px-2;
|
||||
@apply bg-neutral-100 shadow dark:bg-neutral-800;
|
||||
@apply bg-zinc-100 shadow dark:bg-zinc-900;
|
||||
@apply flex flex-col items-center justify-between;
|
||||
|
||||
hr {
|
||||
@ -29,7 +29,7 @@
|
||||
> section {
|
||||
@apply grid gap-4;
|
||||
@apply pt-5 pb-7 first:pt-0;
|
||||
@apply border-b border-neutral-900/10 last:border-b-0 dark:border-neutral-50/10;
|
||||
@apply border-b border-zinc-900/10 last:border-b-0 dark:border-zinc-50/10;
|
||||
|
||||
hr {
|
||||
@apply my-2;
|
||||
|
||||
@ -45,7 +45,7 @@ const RightSidebar = () => {
|
||||
<div className={styles.container}>
|
||||
<nav className="overflow-y-auto">
|
||||
<div>
|
||||
<Avatar size={40} />
|
||||
<Avatar size={24} />
|
||||
<Divider />
|
||||
</div>
|
||||
|
||||
@ -55,7 +55,7 @@ const RightSidebar = () => {
|
||||
key={id}
|
||||
arrow
|
||||
placement="right"
|
||||
title={t<string>(`builder.rightSidebar.sections.${id}.heading`, { defaultValue: capitalize(id) })}
|
||||
title={t(`builder.rightSidebar.sections.${id}.heading`, { defaultValue: capitalize(id) })}
|
||||
>
|
||||
<IconButton onClick={() => handleClick(id)}>{icon}</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import Editor from '@monaco-editor/react';
|
||||
import { useTheme } from '@mui/material';
|
||||
import { CustomCSS as CustomCSSType } from '@reactive-resume/schema';
|
||||
import clsx from 'clsx';
|
||||
import get from 'lodash/get';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React from 'react';
|
||||
import { CustomCSS as CustomCSSType } from 'schema';
|
||||
|
||||
import Heading from '@/components/shared/Heading';
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||
@ -18,7 +18,7 @@ const CustomCSS = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const customCSS: CustomCSSType = useAppSelector((state) =>
|
||||
get(state.resume.present, 'metadata.css', {} as CustomCSSType)
|
||||
get(state.resume.present, 'metadata.css', {} as CustomCSSType),
|
||||
);
|
||||
|
||||
const handleChange = (value: string | undefined) => {
|
||||
@ -27,7 +27,7 @@ const CustomCSS = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Heading path="metadata.css" name={t<string>('builder.rightSidebar.sections.css.heading')} isHideable />
|
||||
<Heading path="metadata.css" name={t('builder.rightSidebar.sections.css.heading')} isHideable />
|
||||
|
||||
<Editor
|
||||
height="200px"
|
||||
|
||||
@ -20,17 +20,17 @@ const Export = () => {
|
||||
|
||||
const pdfListItemText = {
|
||||
normal: {
|
||||
primary: t<string>('builder.rightSidebar.sections.export.pdf.normal.primary'),
|
||||
secondary: t<string>('builder.rightSidebar.sections.export.pdf.normal.secondary'),
|
||||
primary: t('builder.rightSidebar.sections.export.pdf.normal.primary'),
|
||||
secondary: t('builder.rightSidebar.sections.export.pdf.normal.secondary'),
|
||||
},
|
||||
loading: {
|
||||
primary: t<string>('builder.rightSidebar.sections.export.pdf.loading.primary'),
|
||||
secondary: t<string>('builder.rightSidebar.sections.export.pdf.loading.secondary'),
|
||||
primary: t('builder.rightSidebar.sections.export.pdf.loading.primary'),
|
||||
secondary: t('builder.rightSidebar.sections.export.pdf.loading.secondary'),
|
||||
},
|
||||
};
|
||||
|
||||
const handleExportJSON = async () => {
|
||||
const { nanoid } = await import('nanoid');
|
||||
const nanoid = (await import('nanoid')).nanoid;
|
||||
const download = (await import('downloadjs')).default;
|
||||
|
||||
const redactedResume = pick(resume, ['basics', 'sections', 'metadata', 'public']);
|
||||
@ -55,7 +55,7 @@ const Export = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Heading path="metadata.export" name={t<string>('builder.rightSidebar.sections.export.heading')} />
|
||||
<Heading path="metadata.export" name={t('builder.rightSidebar.sections.export.heading')} />
|
||||
|
||||
<List sx={{ padding: 0 }}>
|
||||
<ListItem sx={{ padding: 0 }}>
|
||||
@ -63,8 +63,8 @@ const Export = () => {
|
||||
<Schema />
|
||||
|
||||
<ListItemText
|
||||
primary={t<string>('builder.rightSidebar.sections.export.json.primary')}
|
||||
secondary={t<string>('builder.rightSidebar.sections.export.json.secondary')}
|
||||
primary={t('builder.rightSidebar.sections.export.json.primary')}
|
||||
secondary={t('builder.rightSidebar.sections.export.json.secondary')}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
.page {
|
||||
@apply relative border pl-4 pb-4 dark:border-neutral-100/10;
|
||||
@apply rounded bg-neutral-100 dark:bg-neutral-800;
|
||||
@apply relative border pl-4 pb-4 dark:border-zinc-100/10;
|
||||
@apply rounded bg-zinc-100 dark:bg-zinc-900;
|
||||
|
||||
.delete {
|
||||
@apply opacity-50 hover:opacity-75;
|
||||
@ -28,14 +28,14 @@
|
||||
|
||||
.base {
|
||||
@apply absolute inset-0 w-4/5;
|
||||
@apply rounded bg-neutral-200 dark:bg-neutral-700;
|
||||
@apply rounded bg-zinc-200 dark:bg-zinc-800;
|
||||
}
|
||||
}
|
||||
|
||||
.section {
|
||||
@apply relative my-3 w-full px-4 py-2;
|
||||
@apply cursor-move break-all rounded text-xs capitalize;
|
||||
@apply bg-neutral-800/90 text-neutral-50 dark:bg-neutral-50/90 dark:text-neutral-800;
|
||||
@apply bg-zinc-900/90 text-zinc-50 dark:bg-zinc-50/90 dark:text-zinc-900;
|
||||
|
||||
&.disabled {
|
||||
@apply opacity-60;
|
||||
|
||||
@ -60,9 +60,9 @@ const Layout = () => {
|
||||
<>
|
||||
<Heading
|
||||
path="metadata.layout"
|
||||
name={t<string>('builder.rightSidebar.sections.layout.heading')}
|
||||
name={t('builder.rightSidebar.sections.layout.heading')}
|
||||
action={
|
||||
<Tooltip title={t<string>('builder.rightSidebar.sections.layout.tooltip.reset-layout')}>
|
||||
<Tooltip title={t('builder.rightSidebar.sections.layout.tooltip.reset-layout')}>
|
||||
<IconButton onClick={handleResetLayout}>
|
||||
<Restore />
|
||||
</IconButton>
|
||||
@ -76,14 +76,14 @@ const Layout = () => {
|
||||
<div key={pageIndex} className={styles.page}>
|
||||
<div className="flex items-center justify-between pr-3">
|
||||
<p className={styles.heading}>
|
||||
{t<string>('builder.common.glossary.page')} {pageIndex + 1}
|
||||
{t('builder.common.glossary.page')} {pageIndex + 1}
|
||||
</p>
|
||||
|
||||
<div className={clsx(styles.delete, { hidden: pageIndex === 0 })}>
|
||||
<Tooltip
|
||||
title={
|
||||
t<string>('builder.common.actions.delete', {
|
||||
token: t<string>('builder.common.glossary.page'),
|
||||
t('builder.common.actions.delete', {
|
||||
token: t('builder.common.glossary.page'),
|
||||
}) as string
|
||||
}
|
||||
>
|
||||
@ -136,7 +136,7 @@ const Layout = () => {
|
||||
|
||||
<div className="flex items-center justify-end">
|
||||
<Button variant="outlined" startIcon={<Add />} onClick={handleAddPage}>
|
||||
{t<string>('builder.common.actions.add', { token: t<string>('builder.common.glossary.page') })}
|
||||
{t('builder.common.actions.add', { token: t('builder.common.glossary.page') })}
|
||||
</Button>
|
||||
</div>
|
||||
</DragDropContext>
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
.section {
|
||||
@apply grid gap-2 rounded p-6;
|
||||
@apply bg-neutral-100 dark:bg-neutral-800;
|
||||
@apply bg-zinc-100 dark:bg-zinc-900;
|
||||
|
||||
h2 {
|
||||
@apply inline-flex items-center gap-2 text-base font-medium;
|
||||
|
||||
@ -12,53 +12,51 @@ const Links = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Heading path="metadata.links" name={t<string>('builder.rightSidebar.sections.links.heading')} />
|
||||
<Heading path="metadata.links" name={t('builder.rightSidebar.sections.links.heading')} />
|
||||
|
||||
<div className={styles.container}>
|
||||
<div className={styles.section}>
|
||||
<h2>
|
||||
<Savings fontSize="small" />
|
||||
{t<string>('builder.rightSidebar.sections.links.donate.heading')}
|
||||
{t('builder.rightSidebar.sections.links.donate.heading')}
|
||||
</h2>
|
||||
|
||||
<p>{t<string>('builder.rightSidebar.sections.links.donate.body')}</p>
|
||||
<p>{t('builder.rightSidebar.sections.links.donate.body')}</p>
|
||||
|
||||
<a href={DONATION_URL} target="_blank" rel="noreferrer">
|
||||
<Button startIcon={<Coffee />}>{t<string>('builder.rightSidebar.sections.links.donate.button')}</Button>
|
||||
<Button startIcon={<Coffee />}>{t('builder.rightSidebar.sections.links.donate.button')}</Button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className={styles.section}>
|
||||
<h2>
|
||||
<BugReport fontSize="small" />
|
||||
{t<string>('builder.rightSidebar.sections.links.bugs-features.heading')}
|
||||
{t('builder.rightSidebar.sections.links.bugs-features.heading')}
|
||||
</h2>
|
||||
|
||||
<p>{t<string>('builder.rightSidebar.sections.links.bugs-features.body')}</p>
|
||||
<p>{t('builder.rightSidebar.sections.links.bugs-features.body')}</p>
|
||||
|
||||
<a href={GITHUB_ISSUES_URL} target="_blank" rel="noreferrer">
|
||||
<Button startIcon={<GitHub />}>
|
||||
{t<string>('builder.rightSidebar.sections.links.bugs-features.button')}
|
||||
</Button>
|
||||
<Button startIcon={<GitHub />}>{t('builder.rightSidebar.sections.links.bugs-features.button')}</Button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a href={GITHUB_URL} target="_blank" rel="noreferrer">
|
||||
<Button variant="text" startIcon={<Link />}>
|
||||
{t<string>('builder.rightSidebar.sections.links.github')}
|
||||
{t('builder.rightSidebar.sections.links.github')}
|
||||
</Button>
|
||||
</a>
|
||||
|
||||
<a href={REDDIT_URL} target="_blank" rel="noreferrer">
|
||||
<Button variant="text" startIcon={<Link />}>
|
||||
{t<string>('builder.rightSidebar.sections.links.reddit')}
|
||||
{t('builder.rightSidebar.sections.links.reddit')}
|
||||
</Button>
|
||||
</a>
|
||||
|
||||
<a href={DOCS_URL} target="_blank" rel="noreferrer">
|
||||
<Button variant="text" startIcon={<Link />}>
|
||||
{t<string>('builder.rightSidebar.sections.links.docs')}
|
||||
{t('builder.rightSidebar.sections.links.docs')}
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@ -10,13 +10,13 @@ import {
|
||||
Switch,
|
||||
TextField,
|
||||
} from '@mui/material';
|
||||
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, useState } from 'react';
|
||||
import { useMutation } from 'react-query';
|
||||
import { DateConfig, PageConfig, Resume } from 'schema';
|
||||
|
||||
import Heading from '@/components/shared/Heading';
|
||||
import ThemeSwitch from '@/components/shared/ThemeSwitch';
|
||||
@ -55,7 +55,7 @@ const Settings = () => {
|
||||
const themeString = useMemo(() => (isDarkMode ? 'Matte Black Everything' : 'As bright as your future'), [isDarkMode]);
|
||||
|
||||
const { mutateAsync: loadSampleDataMutation } = useMutation<Resume, ServerError, LoadSampleDataParams>(
|
||||
loadSampleData
|
||||
loadSampleData,
|
||||
);
|
||||
const { mutateAsync: resetResumeMutation } = useMutation<Resume, ServerError, ResetResumeParams>(resetResume);
|
||||
|
||||
@ -96,13 +96,13 @@ const Settings = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Heading path="metadata.settings" name={t<string>('builder.rightSidebar.sections.settings.heading')} />
|
||||
<Heading path="metadata.settings" name={t('builder.rightSidebar.sections.settings.heading')} />
|
||||
|
||||
<List disablePadding>
|
||||
{/* Global Settings */}
|
||||
<>
|
||||
<ListSubheader disableSticky className="rounded">
|
||||
{t<string>('builder.rightSidebar.sections.settings.global.heading')}
|
||||
{t('builder.rightSidebar.sections.settings.global.heading')}
|
||||
</ListSubheader>
|
||||
|
||||
<ListItem>
|
||||
@ -110,7 +110,7 @@ const Settings = () => {
|
||||
<Palette />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t<string>('builder.rightSidebar.sections.settings.global.theme.primary')}
|
||||
primary={t('builder.rightSidebar.sections.settings.global.theme.primary')}
|
||||
secondary={themeString}
|
||||
/>
|
||||
<ThemeSwitch checked={isDarkMode} onChange={(_, value: boolean) => handleSetTheme(value)} />
|
||||
@ -119,8 +119,8 @@ const Settings = () => {
|
||||
<ListItem className="flex-col">
|
||||
<ListItemText
|
||||
className="w-full"
|
||||
primary={t<string>('builder.rightSidebar.sections.settings.global.date.primary')}
|
||||
secondary={t<string>('builder.rightSidebar.sections.settings.global.date.secondary')}
|
||||
primary={t('builder.rightSidebar.sections.settings.global.date.primary')}
|
||||
secondary={t('builder.rightSidebar.sections.settings.global.date.secondary')}
|
||||
/>
|
||||
<Autocomplete<string, false, true, false>
|
||||
disableClearable
|
||||
@ -135,8 +135,8 @@ const Settings = () => {
|
||||
<ListItem className="flex-col">
|
||||
<ListItemText
|
||||
className="w-full"
|
||||
primary={t<string>('builder.rightSidebar.sections.settings.global.language.primary')}
|
||||
secondary={t<string>('builder.rightSidebar.sections.settings.global.language.secondary')}
|
||||
primary={t('builder.rightSidebar.sections.settings.global.language.primary')}
|
||||
secondary={t('builder.rightSidebar.sections.settings.global.language.secondary')}
|
||||
/>
|
||||
<Autocomplete<Language, false, true, false>
|
||||
disableClearable
|
||||
@ -160,14 +160,14 @@ const Settings = () => {
|
||||
{/* Page Settings */}
|
||||
<>
|
||||
<ListSubheader disableSticky className="rounded">
|
||||
{t<string>('builder.rightSidebar.sections.settings.page.heading')}
|
||||
{t('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')}
|
||||
primary={t('builder.rightSidebar.sections.settings.page.format.primary')}
|
||||
secondary={t('builder.rightSidebar.sections.settings.page.format.secondary')}
|
||||
/>
|
||||
<Autocomplete<PageConfig['format'], false, true, false>
|
||||
disableClearable
|
||||
@ -182,11 +182,11 @@ const Settings = () => {
|
||||
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t<string>('builder.rightSidebar.sections.settings.page.orientation.primary')}
|
||||
primary={t('builder.rightSidebar.sections.settings.page.orientation.primary')}
|
||||
secondary={
|
||||
pages.length === 1
|
||||
? t<string>('builder.rightSidebar.sections.settings.page.orientation.disabled')
|
||||
: t<string>('builder.rightSidebar.sections.settings.page.orientation.secondary')
|
||||
? t('builder.rightSidebar.sections.settings.page.orientation.disabled')
|
||||
: t('builder.rightSidebar.sections.settings.page.orientation.secondary')
|
||||
}
|
||||
/>
|
||||
<Switch
|
||||
@ -199,8 +199,8 @@ const Settings = () => {
|
||||
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={t<string>('builder.rightSidebar.sections.settings.page.break-line.primary')}
|
||||
secondary={t<string>('builder.rightSidebar.sections.settings.page.break-line.secondary')}
|
||||
primary={t('builder.rightSidebar.sections.settings.page.break-line.primary')}
|
||||
secondary={t('builder.rightSidebar.sections.settings.page.break-line.secondary')}
|
||||
/>
|
||||
<Switch color="secondary" checked={breakLine} onChange={() => dispatch(togglePageBreakLine())} />
|
||||
</ListItem>
|
||||
@ -209,7 +209,7 @@ const Settings = () => {
|
||||
{/* Resume Settings */}
|
||||
<>
|
||||
<ListSubheader disableSticky className="rounded">
|
||||
{t<string>('builder.rightSidebar.sections.settings.resume.heading')}
|
||||
{t('builder.rightSidebar.sections.settings.resume.heading')}
|
||||
</ListSubheader>
|
||||
|
||||
<ListItem disableGutters>
|
||||
@ -218,8 +218,8 @@ const Settings = () => {
|
||||
<Anchor />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t<string>('builder.rightSidebar.sections.settings.resume.sample.primary')}
|
||||
secondary={t<string>('builder.rightSidebar.sections.settings.resume.sample.secondary')}
|
||||
primary={t('builder.rightSidebar.sections.settings.resume.sample.primary')}
|
||||
secondary={t('builder.rightSidebar.sections.settings.resume.sample.secondary')}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
@ -231,11 +231,9 @@ const Settings = () => {
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={
|
||||
confirmReset
|
||||
? 'Are you sure?'
|
||||
: t<string>('builder.rightSidebar.sections.settings.resume.reset.primary')
|
||||
confirmReset ? 'Are you sure?' : t('builder.rightSidebar.sections.settings.resume.reset.primary')
|
||||
}
|
||||
secondary={t<string>('builder.rightSidebar.sections.settings.resume.reset.secondary')}
|
||||
secondary={t('builder.rightSidebar.sections.settings.resume.reset.secondary')}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
|
||||
@ -29,19 +29,19 @@ const Sharing = () => {
|
||||
|
||||
await navigator.clipboard.writeText(text);
|
||||
|
||||
toast.success(t<string>('common.toast.success.resume-link-copied'));
|
||||
toast.success(t('common.toast.success.resume-link-copied'));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Heading path="metadata.sharing" name={t<string>('builder.rightSidebar.sections.sharing.heading')} />
|
||||
<Heading path="metadata.sharing" name={t('builder.rightSidebar.sections.sharing.heading')} />
|
||||
|
||||
<List sx={{ padding: 0 }}>
|
||||
<ListItem className="flex flex-col" sx={{ padding: 0 }}>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<ListItemText
|
||||
primary={t<string>('builder.rightSidebar.sections.sharing.visibility.title')}
|
||||
secondary={t<string>('builder.rightSidebar.sections.sharing.visibility.subtitle')}
|
||||
primary={t('builder.rightSidebar.sections.sharing.visibility.title')}
|
||||
secondary={t('builder.rightSidebar.sections.sharing.visibility.subtitle')}
|
||||
/>
|
||||
<Switch color="secondary" checked={isPublic} onChange={(_, value) => handleSetVisibility(value)} />
|
||||
</div>
|
||||
@ -63,7 +63,7 @@ const Sharing = () => {
|
||||
|
||||
<div className="mt-1 flex w-full">
|
||||
<FormControlLabel
|
||||
label={t<string>('builder.rightSidebar.sections.sharing.short-url.label')}
|
||||
label={t('builder.rightSidebar.sections.sharing.short-url.label')}
|
||||
control={
|
||||
<Checkbox className="mr-1" checked={showShortUrl} onChange={(_, value) => setShowShortUrl(value)} />
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ const Templates = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Heading path="metadata.templates" name={t<string>('builder.rightSidebar.sections.templates.heading')} />
|
||||
<Heading path="metadata.templates" name={t('builder.rightSidebar.sections.templates.heading')} />
|
||||
|
||||
<div className={styles.container}>
|
||||
{Object.values(templateMap).map((template) => (
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ThemeConfig } from '@reactive-resume/schema';
|
||||
import get from 'lodash/get';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ThemeConfig } from 'schema';
|
||||
|
||||
import ColorAvatar from '@/components/shared/ColorAvatar';
|
||||
import ColorPicker from '@/components/shared/ColorPicker';
|
||||
@ -17,7 +17,7 @@ const Theme = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { background, text, primary } = useAppSelector<ThemeConfig>((state) =>
|
||||
get(state.resume.present, 'metadata.theme')
|
||||
get(state.resume.present, 'metadata.theme'),
|
||||
);
|
||||
|
||||
const handleChange = (property: string, color: string) => {
|
||||
@ -26,7 +26,7 @@ const Theme = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Heading path="metadata.theme" name={t<string>('builder.rightSidebar.sections.theme.heading')} />
|
||||
<Heading path="metadata.theme" name={t('builder.rightSidebar.sections.theme.heading')} />
|
||||
|
||||
<div className={styles.container}>
|
||||
<div className={styles.colorOptions}>
|
||||
@ -36,18 +36,18 @@ const Theme = () => {
|
||||
</div>
|
||||
|
||||
<ColorPicker
|
||||
label={t<string>('builder.rightSidebar.sections.theme.form.primary.label')}
|
||||
label={t('builder.rightSidebar.sections.theme.form.primary.label')}
|
||||
color={primary}
|
||||
className="col-span-2"
|
||||
onChange={(color) => handleChange('primary', color)}
|
||||
/>
|
||||
<ColorPicker
|
||||
label={t<string>('builder.rightSidebar.sections.theme.form.background.label')}
|
||||
label={t('builder.rightSidebar.sections.theme.form.background.label')}
|
||||
color={background}
|
||||
onChange={(color) => handleChange('background', color)}
|
||||
/>
|
||||
<ColorPicker
|
||||
label={t<string>('builder.rightSidebar.sections.theme.form.text.label')}
|
||||
label={t('builder.rightSidebar.sections.theme.form.text.label')}
|
||||
color={text}
|
||||
onChange={(color) => handleChange('text', color)}
|
||||
/>
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { Autocomplete, Skeleton, Slider, TextField } from '@mui/material';
|
||||
import { Font, TypeCategory, TypeProperty, Typography as TypographyType } from '@reactive-resume/schema';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import { Font, TypeCategory, TypeProperty, Typography as TypographyType } from 'schema';
|
||||
|
||||
import Heading from '@/components/shared/Heading';
|
||||
import { FONTS_QUERY } from '@/constants/index';
|
||||
@ -46,7 +46,7 @@ const Widgets: React.FC<WidgetProps> = ({ label, category }) => {
|
||||
setResumeState({
|
||||
path: `metadata.typography.${property}.${category}`,
|
||||
value: property === 'family' ? (value as Font).family : value,
|
||||
})
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
@ -64,7 +64,7 @@ const Widgets: React.FC<WidgetProps> = ({ label, category }) => {
|
||||
step={1}
|
||||
marks={[
|
||||
{ value: 12, label: '12px' },
|
||||
{ value: 24, label: t<string>('builder.rightSidebar.sections.typography.form.font-size.label') },
|
||||
{ value: 24, label: t('builder.rightSidebar.sections.typography.form.font-size.label') },
|
||||
{ value: 36, label: '36px' },
|
||||
]}
|
||||
valueLabelDisplay="auto"
|
||||
@ -82,10 +82,7 @@ const Widgets: React.FC<WidgetProps> = ({ label, category }) => {
|
||||
value={fonts.find((font) => font.family === family[category])}
|
||||
onChange={(_, font: Font | null) => handleChange('family', font)}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label={t<string>('builder.rightSidebar.sections.typography.form.font-family.label')}
|
||||
/>
|
||||
<TextField {...params} label={t('builder.rightSidebar.sections.typography.form.font-family.label')} />
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@ -98,13 +95,10 @@ const Typography = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Heading path="metadata.typography" name={t<string>('builder.rightSidebar.sections.typography.heading')} />
|
||||
<Heading path="metadata.typography" name={t('builder.rightSidebar.sections.typography.heading')} />
|
||||
|
||||
<Widgets
|
||||
label={t<string>('builder.rightSidebar.sections.typography.widgets.headings.label')}
|
||||
category="heading"
|
||||
/>
|
||||
<Widgets label={t<string>('builder.rightSidebar.sections.typography.widgets.body.label')} category="body" />
|
||||
<Widgets label={t('builder.rightSidebar.sections.typography.widgets.headings.label')} category="heading" />
|
||||
<Widgets label={t('builder.rightSidebar.sections.typography.widgets.body.label')} category="body" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
aspect-ratio: 1 / 1.41;
|
||||
|
||||
@apply flex items-center justify-center shadow;
|
||||
@apply cursor-pointer rounded-sm bg-neutral-100 transition-opacity hover:opacity-80 dark:bg-neutral-800;
|
||||
@apply cursor-pointer rounded-sm bg-zinc-100 transition-opacity hover:opacity-80 dark:bg-zinc-900;
|
||||
}
|
||||
|
||||
footer {
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
aspect-ratio: 1 / 1.41;
|
||||
|
||||
@apply relative cursor-pointer rounded-sm shadow;
|
||||
@apply bg-neutral-100 transition-opacity hover:opacity-80 dark:bg-neutral-800;
|
||||
@apply bg-zinc-100 transition-opacity hover:opacity-80 dark:bg-zinc-900;
|
||||
}
|
||||
|
||||
footer {
|
||||
|
||||
@ -7,7 +7,6 @@ import {
|
||||
OpenInNew,
|
||||
} from '@mui/icons-material';
|
||||
import { ButtonBase, ListItemIcon, ListItemText, Menu, MenuItem, Tooltip } from '@mui/material';
|
||||
import { Resume } from '@reactive-resume/schema';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
@ -15,6 +14,7 @@ import { useTranslation } from 'next-i18next';
|
||||
import { useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useMutation } from 'react-query';
|
||||
import { Resume } from 'schema';
|
||||
|
||||
import { RESUMES_QUERY } from '@/constants/index';
|
||||
import { ServerError } from '@/services/axios';
|
||||
@ -76,7 +76,7 @@ const ResumePreview: React.FC<Props> = ({ resume }) => {
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
@ -94,7 +94,7 @@ const ResumePreview: React.FC<Props> = ({ resume }) => {
|
||||
const url = getResumeUrl(resume, { withHost: true });
|
||||
await navigator.clipboard.writeText(url);
|
||||
|
||||
toast.success(t<string>('common.toast.success.resume-link-copied'));
|
||||
toast.success(t('common.toast.success.resume-link-copied'));
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
@ -122,7 +122,7 @@ const ResumePreview: React.FC<Props> = ({ resume }) => {
|
||||
<footer>
|
||||
<div className={styles.meta}>
|
||||
<p>{resume.name}</p>
|
||||
<p>{t<string>('dashboard.resume.timestamp', { timestamp: getRelativeTime(resume.updatedAt) })}</p>
|
||||
<p>{t('dashboard.resume.timestamp', { timestamp: getRelativeTime(resume.updatedAt) })}</p>
|
||||
</div>
|
||||
|
||||
<ButtonBase className={styles.menu} onClick={handleOpenMenu}>
|
||||
@ -134,21 +134,21 @@ const ResumePreview: React.FC<Props> = ({ resume }) => {
|
||||
<ListItemIcon>
|
||||
<OpenInNew className="scale-90" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>{t<string>('dashboard.resume.menu.open')}</ListItemText>
|
||||
<ListItemText>{t('dashboard.resume.menu.open')}</ListItemText>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem onClick={handleRename}>
|
||||
<ListItemIcon>
|
||||
<DriveFileRenameOutline className="scale-90" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>{t<string>('dashboard.resume.menu.rename')}</ListItemText>
|
||||
<ListItemText>{t('dashboard.resume.menu.rename')}</ListItemText>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem onClick={handleDuplicate}>
|
||||
<ListItemIcon>
|
||||
<ContentCopy className="scale-90" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>{t<string>('dashboard.resume.menu.duplicate')}</ListItemText>
|
||||
<ListItemText>{t('dashboard.resume.menu.duplicate')}</ListItemText>
|
||||
</MenuItem>
|
||||
|
||||
{resume.public ? (
|
||||
@ -156,27 +156,27 @@ const ResumePreview: React.FC<Props> = ({ resume }) => {
|
||||
<ListItemIcon>
|
||||
<LinkIcon className="scale-90" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>{t<string>('dashboard.resume.menu.share-link')}</ListItemText>
|
||||
<ListItemText>{t('dashboard.resume.menu.share-link')}</ListItemText>
|
||||
</MenuItem>
|
||||
) : (
|
||||
<Tooltip arrow placement="right" title={t<string>('dashboard.resume.menu.tooltips.share-link')}>
|
||||
<Tooltip arrow placement="right" title={t('dashboard.resume.menu.tooltips.share-link')}>
|
||||
<div>
|
||||
<MenuItem>
|
||||
<ListItemIcon>
|
||||
<LinkIcon className="scale-90" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>{t<string>('dashboard.resume.menu.share-link')}</ListItemText>
|
||||
<ListItemText>{t('dashboard.resume.menu.share-link')}</ListItemText>
|
||||
</MenuItem>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<Tooltip arrow placement="right" title={t<string>('dashboard.resume.menu.tooltips.delete')}>
|
||||
<Tooltip arrow placement="right" title={t('dashboard.resume.menu.tooltips.delete')}>
|
||||
<MenuItem onClick={handleDelete}>
|
||||
<ListItemIcon>
|
||||
<DeleteOutline className="scale-90" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>{t<string>('dashboard.resume.menu.delete')}</ListItemText>
|
||||
<ListItemText>{t('dashboard.resume.menu.delete')}</ListItemText>
|
||||
</MenuItem>
|
||||
</Tooltip>
|
||||
</Menu>
|
||||
|
||||
44
client/components/home/Actions.tsx
Normal file
44
client/components/home/Actions.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import { Button } from '@mui/material';
|
||||
import Link from 'next/link';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import { FLAG_DISABLE_SIGNUPS } from '@/constants/flags';
|
||||
import { logout } from '@/store/auth/authSlice';
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||
import { setModalState } from '@/store/modal/modalSlice';
|
||||
|
||||
const HomeActions = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const isLoggedIn = useAppSelector((state) => state.auth.isLoggedIn);
|
||||
|
||||
const handleLogin = () => dispatch(setModalState({ modal: 'auth.login', state: { open: true } }));
|
||||
const handleRegister = () => dispatch(setModalState({ modal: 'auth.register', state: { open: true } }));
|
||||
const handleLogout = () => dispatch(logout());
|
||||
|
||||
return isLoggedIn ? (
|
||||
<>
|
||||
<Link href="/dashboard" passHref>
|
||||
<Button size="large">{t('landing.actions.app')}</Button>
|
||||
</Link>
|
||||
|
||||
<Button size="large" variant="outlined" onClick={handleLogout}>
|
||||
{t('landing.actions.logout')}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button size="large" onClick={handleLogin}>
|
||||
{t('landing.actions.login')}
|
||||
</Button>
|
||||
|
||||
<Button size="large" variant="outlined" onClick={handleRegister} disabled={FLAG_DISABLE_SIGNUPS}>
|
||||
{t('landing.actions.register')}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default HomeActions;
|
||||
16
client/components/home/Background.tsx
Normal file
16
client/components/home/Background.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
const HeroBackground = () => (
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="absolute left-[calc(50%-4rem)] top-10 -z-10 transform-gpu blur-3xl sm:left-[calc(50%-18rem)] lg:left-48 lg:top-[calc(50%-30rem)] xl:left-[calc(50%-24rem)]"
|
||||
>
|
||||
<div
|
||||
className="aspect-[1108/632] h-96 w-[69.25rem] bg-gradient-to-r from-[#6f8cbb] to-[#c93b37] opacity-40 dark:opacity-20"
|
||||
style={{
|
||||
clipPath:
|
||||
'polygon(73.6% 51.7%, 91.7% 11.8%, 100% 46.4%, 97.4% 82.2%, 92.5% 84.9%, 75.7% 64%, 55.3% 47.5%, 46.5% 49.4%, 45% 62.9%, 50.3% 87.2%, 21.3% 64.1%, 0.1% 100%, 5.4% 51.1%, 21.4% 63.9%, 58.9% 0.2%, 73.6% 51.7%)',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default HeroBackground;
|
||||
47
client/components/home/Footer.tsx
Normal file
47
client/components/home/Footer.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import GitHubButton from 'react-github-btn';
|
||||
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
|
||||
import { Copyright } from '../shared/Copyright';
|
||||
import Logo from '../shared/Logo';
|
||||
import { Separator } from '../ui/Separator';
|
||||
|
||||
const Footer = () => {
|
||||
const theme = useAppSelector((state) => state.build.theme);
|
||||
|
||||
return (
|
||||
<footer className="fixed inset-x-0 bottom-0 -z-50 h-[450px] bg-zinc-50 dark:bg-zinc-950">
|
||||
<Separator />
|
||||
|
||||
<div className="container grid py-12 sm:grid-cols-3 lg:grid-cols-4">
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<Logo size={96} className="-ml-2" />
|
||||
|
||||
<h2 className="text-xl font-medium">Reactive Resume</h2>
|
||||
|
||||
<p className="prose prose-sm prose-zinc leading-relaxed opacity-60 dark:prose-invert">
|
||||
A free and open-source resume builder that simplifies the tasks of creating, updating, and sharing your
|
||||
resume.
|
||||
</p>
|
||||
|
||||
<div className="mt-6">
|
||||
<GitHubButton
|
||||
data-size="large"
|
||||
data-show-count="true"
|
||||
data-icon="octicon-star"
|
||||
data-color-scheme={theme ? 'dark' : 'light'}
|
||||
href="https://github.com/AmruthPillai/Reactive-Resume"
|
||||
aria-label="Star AmruthPillai/Reactive-Resume on GitHub"
|
||||
>
|
||||
Star
|
||||
</GitHubButton>
|
||||
</div>
|
||||
|
||||
<Copyright className="mt-4" />
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
24
client/components/home/Header.tsx
Normal file
24
client/components/home/Header.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import Logo from '../shared/Logo';
|
||||
import HomeActions from './Actions';
|
||||
|
||||
const Header = () => (
|
||||
<header className="fixed inset-x-0 top-0 z-50">
|
||||
<nav className="bg-gradient-to-b from-zinc-50 to-transparent py-3 dark:from-zinc-950">
|
||||
<div className="container flex items-center justify-between">
|
||||
<div className="lg:flex-1">
|
||||
<Link href="/">
|
||||
<Logo size={48} />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="space-x-4">
|
||||
<HomeActions />
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
|
||||
export default Header;
|
||||
28
client/components/home/Pattern.tsx
Normal file
28
client/components/home/Pattern.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
const HeroPattern = () => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="absolute inset-0 -z-10 h-full w-full stroke-zinc-950/10 opacity-60 [mask-image:radial-gradient(100%_100%_at_top_right,white,transparent)] dark:stroke-zinc-50/10 dark:opacity-40"
|
||||
>
|
||||
<defs>
|
||||
<pattern
|
||||
id="983e3e4c-de6d-4c3f-8d64-b9761d1534cc"
|
||||
width={200}
|
||||
height={200}
|
||||
x="50%"
|
||||
y={-1}
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
<path d="M.5 200V.5H200" fill="none" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<svg x="50%" y={-1} className="overflow-visible fill-zinc-100/20 dark:fill-zinc-900/20">
|
||||
<path
|
||||
d="M-200 0h201v201h-201Z M600 0h201v201h-201Z M-400 600h201v201h-201Z M200 800h201v201h-201Z"
|
||||
strokeWidth={0}
|
||||
/>
|
||||
</svg>
|
||||
<rect width="100%" height="100%" strokeWidth={0} fill="url(#983e3e4c-de6d-4c3f-8d64-b9761d1534cc)" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default HeroPattern;
|
||||
@ -1,6 +1,6 @@
|
||||
.testimony {
|
||||
@apply grid gap-2;
|
||||
@apply rounded border-2 p-4 dark:border-neutral-800;
|
||||
@apply rounded border-2 p-4 dark:border-zinc-900;
|
||||
|
||||
blockquote {
|
||||
@apply text-justify text-xs leading-normal opacity-90;
|
||||
50
client/components/home/sections/Hero.tsx
Normal file
50
client/components/home/sections/Hero.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import Tilt from 'react-parallax-tilt';
|
||||
|
||||
import { defaultTiltProps } from '@/constants/tilt';
|
||||
|
||||
import HomeActions from '../Actions';
|
||||
import HeroBackground from '../Background';
|
||||
import HeroPattern from '../Pattern';
|
||||
|
||||
const HeroSection = () => (
|
||||
<section className="relative">
|
||||
<HeroPattern />
|
||||
<HeroBackground />
|
||||
|
||||
<div className="mx-auto max-w-7xl px-6 pb-24 pt-10 sm:pb-32 lg:flex lg:px-8 lg:py-52">
|
||||
<div className="mx-auto max-w-2xl shrink-0 lg:mx-0 lg:max-w-xl lg:pt-12">
|
||||
<div className="mt-10 space-y-2">
|
||||
<h6 className="text-base font-bold tracking-wide">Finally,</h6>
|
||||
<h1 className="text-4xl font-bold !leading-[1.15] tracking-tight sm:text-6xl">
|
||||
A free and open-source resume builder
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<p className="prose prose-base prose-zinc mt-6 text-lg leading-8 dark:prose-invert">
|
||||
Reactive Resume is a free and open-source resume builder that simplifies the tasks of creating, updating, and
|
||||
sharing your resume.
|
||||
</p>
|
||||
|
||||
<div className="mt-12 space-x-4">
|
||||
<HomeActions />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto mt-16 flex max-w-2xl sm:mt-24 lg:ml-10 lg:mr-0 lg:mt-0 lg:max-w-none lg:flex-none xl:ml-32">
|
||||
<div className="max-w-3xl flex-none sm:max-w-5xl lg:max-w-none">
|
||||
<Tilt {...defaultTiltProps}>
|
||||
<img
|
||||
width={2432}
|
||||
height={1442}
|
||||
src="/images/screenshots/builder.png"
|
||||
alt="Reactive Resume Screenshot - Builder Screen"
|
||||
className="w-[76rem] rounded-lg bg-zinc-50/5 shadow-2xl ring-1 ring-zinc-950/10 dark:bg-zinc-950/5 dark:ring-zinc-50/10"
|
||||
/>
|
||||
</Tilt>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
export default HeroSection;
|
||||
51
client/components/home/sections/Logo.tsx
Normal file
51
client/components/home/sections/Logo.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import { cn } from '@/utils/styles';
|
||||
|
||||
type LogoProps = { brand: string };
|
||||
|
||||
const Logo = ({ brand }: LogoProps) => (
|
||||
<div className={cn('col-span-2 col-start-2 sm:col-start-auto lg:col-span-1', brand === 'twilio' && 'sm:col-start-2')}>
|
||||
{/* Show on Light Theme */}
|
||||
<img
|
||||
className="block max-h-12 object-contain dark:hidden"
|
||||
src={`/images/brand-logos/dark/${brand}.svg`}
|
||||
alt={brand}
|
||||
width={212}
|
||||
height={48}
|
||||
/>
|
||||
{/* Show on Dark Theme */}
|
||||
<img
|
||||
className="hidden max-h-12 object-contain dark:block"
|
||||
src={`/images/brand-logos/light/${brand}.svg`}
|
||||
alt={brand}
|
||||
width={212}
|
||||
height={48}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const logoList: string[] = ['amazon', 'google', 'postman', 'twilio', 'zalando'];
|
||||
|
||||
const LogoSection = () => (
|
||||
<section className="relative py-24 sm:py-32">
|
||||
<div className="mx-auto max-w-7xl px-6 lg:px-8">
|
||||
<p className="text-center text-lg leading-relaxed">
|
||||
Reactive Resume has helped people land jobs at these great companies:
|
||||
</p>
|
||||
<div className="mx-auto mt-10 grid max-w-lg grid-cols-4 items-center gap-x-8 gap-y-10 sm:max-w-xl sm:grid-cols-6 sm:gap-x-10 lg:mx-0 lg:max-w-none lg:grid-cols-5">
|
||||
{logoList.map((brand) => (
|
||||
<Logo key={brand} brand={brand} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p className="mx-auto mt-8 max-w-sm text-center leading-relaxed">
|
||||
If this app has helped you with your job hunt, let me know by reaching out through{' '}
|
||||
<a href="https://www.amruthpillai.com/#contact" target="_blank" rel="noreferrer" className="hover:underline">
|
||||
this contact form
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
export default LogoSection;
|
||||
27
client/components/home/sections/Stats.tsx
Normal file
27
client/components/home/sections/Stats.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
type Statistic = {
|
||||
name: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
const stats: Statistic[] = [
|
||||
{ name: 'GitHub Stars', value: '11,800+' },
|
||||
{ name: 'Users Signed Up', value: '300,000+' },
|
||||
{ name: 'Resumes Generated', value: '400,000+' },
|
||||
];
|
||||
|
||||
const StatsSection = () => (
|
||||
<section className="relative py-24 sm:py-32">
|
||||
<div className="mx-auto max-w-7xl px-6 lg:px-8">
|
||||
<dl className="grid grid-cols-1 gap-x-8 gap-y-16 text-center lg:grid-cols-3">
|
||||
{stats.map((stat, index) => (
|
||||
<div key={index} className="mx-auto flex max-w-xs flex-col gap-y-4">
|
||||
<dt className="text-base leading-7 opacity-60">{stat.name}</dt>
|
||||
<dd className="order-first text-3xl font-semibold tracking-tight sm:text-5xl">{stat.value}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
export default StatsSection;
|
||||
@ -63,12 +63,12 @@ const Avatar: React.FC<Props> = ({ size = 64, interactive = true }) => {
|
||||
<Menu anchorEl={anchorEl} onClose={handleClose} open={Boolean(anchorEl)}>
|
||||
<MenuItem onClick={handleOpenProfile}>
|
||||
<div>
|
||||
<span className="text-xs opacity-50">{t<string>('common.avatar.menu.greeting')},</span>
|
||||
<span className="text-xs opacity-50">{t('common.avatar.menu.greeting')},</span>
|
||||
<p>{user?.name}</p>
|
||||
</div>
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem onClick={handleLogout}>{t<string>('common.avatar.menu.logout')}</MenuItem>
|
||||
<MenuItem onClick={handleLogout}>{t('common.avatar.menu.logout')}</MenuItem>
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
.content {
|
||||
@apply rounded px-6 text-sm shadow lg:w-1/2 xl:w-2/5;
|
||||
@apply rounded px-6 text-sm shadow lg:w-2/3 xl:w-1/2;
|
||||
@apply absolute inset-4 sm:inset-x-4 sm:inset-y-auto lg:inset-auto;
|
||||
@apply overflow-scroll bg-neutral-50 dark:bg-neutral-900 lg:overflow-auto;
|
||||
@apply overflow-scroll bg-zinc-100 dark:bg-zinc-900 lg:overflow-auto;
|
||||
@apply max-h-[90vh] min-h-fit;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
@ -10,7 +10,7 @@
|
||||
}
|
||||
|
||||
.header {
|
||||
@apply sticky top-0 left-0 right-0 z-50 bg-neutral-50 pt-6 dark:bg-neutral-900;
|
||||
@apply sticky top-0 left-0 right-0 z-50 bg-zinc-100 pt-6 dark:bg-zinc-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 bg-neutral-50 pb-6 dark:bg-neutral-900;
|
||||
@apply sticky bottom-0 left-0 right-0 z-50 bg-zinc-100 pb-6 dark:bg-zinc-900;
|
||||
@apply flex items-center justify-end gap-x-4;
|
||||
@apply w-full border-t pt-5 dark:border-white/10;
|
||||
}
|
||||
|
||||
19
client/components/shared/Copyright.tsx
Normal file
19
client/components/shared/Copyright.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import clsx from 'clsx';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const Copyright = ({ className }: Props) => (
|
||||
<div
|
||||
className={clsx('prose prose-sm prose-zinc flex flex-col gap-y-1 text-xs opacity-40 dark:prose-invert', className)}
|
||||
>
|
||||
<span className="font-medium">v{process.env.appVersion}</span>
|
||||
<span>
|
||||
Licensed under <a href="https://github.com/AmruthPillai/Reactive-Resume/blob/main/LICENSE">MIT</a>
|
||||
</span>
|
||||
<span>
|
||||
A passion project by <a href="https://www.amruthpillai.com/">Amruth Pillai</a>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
@ -10,7 +10,7 @@ const Footer: React.FC<Props> = ({ className }) => {
|
||||
|
||||
return (
|
||||
<div className={clsx('text-xs', className)}>
|
||||
<p>{t<string>('common.footer.license')}</p>
|
||||
<p>{t('common.footer.license')}</p>
|
||||
|
||||
<p>
|
||||
<Trans t={t} i18nKey="common.footer.credit">
|
||||
|
||||
@ -62,7 +62,7 @@ const Heading: React.FC<Props> = ({
|
||||
{editMode ? (
|
||||
<TextField size="small" value={heading} className="w-3/4" onChange={handleChange} />
|
||||
) : (
|
||||
<h1>{t<string>(`builder.leftSidebar.${path}.heading`, heading)}</h1>
|
||||
<h1>{t(`builder.leftSidebar.${path}.heading`, { defaultValue: heading })}</h1>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -72,19 +72,19 @@ const Heading: React.FC<Props> = ({
|
||||
})}
|
||||
>
|
||||
{isEditable && (
|
||||
<Tooltip title={t<string>('builder.common.tooltip.rename-section')}>
|
||||
<Tooltip title={t('builder.common.tooltip.rename-section')}>
|
||||
<IconButton onClick={toggleEditMode}>{editMode ? <Check /> : <DriveFileRenameOutline />}</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{isHideable && (
|
||||
<Tooltip title={t<string>('builder.common.tooltip.toggle-visibility')}>
|
||||
<Tooltip title={t('builder.common.tooltip.toggle-visibility')}>
|
||||
<IconButton onClick={toggleVisibility}>{visibility ? <Visibility /> : <VisibilityOff />}</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{isDeletable && (
|
||||
<Tooltip title={t<string>('builder.common.tooltip.delete-section')}>
|
||||
<Tooltip title={t('builder.common.tooltip.delete-section')}>
|
||||
<IconButton onClick={handleDelete}>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
|
||||
27
client/components/shared/Icon.tsx
Normal file
27
client/components/shared/Icon.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import clsx from 'clsx';
|
||||
import Image from 'next/image';
|
||||
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
size?: 256 | 96 | 64 | 48 | 40 | 32 | 24 | 16;
|
||||
};
|
||||
|
||||
const Icon: React.FC<Props> = ({ size = 64, className }) => {
|
||||
const theme = useAppSelector((state) => state.build.theme);
|
||||
const iconTheme = theme === 'light' ? 'dark' : 'light';
|
||||
|
||||
return (
|
||||
<Image
|
||||
alt="Reactive Resume"
|
||||
src={`/icon/${iconTheme}.svg`}
|
||||
className={clsx('rounded', className)}
|
||||
width={size}
|
||||
height={size}
|
||||
priority
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Icon;
|
||||
@ -1,5 +1,5 @@
|
||||
.container {
|
||||
@apply rounded-lg border dark:border-neutral-50/10;
|
||||
@apply rounded-lg border dark:border-zinc-50/10;
|
||||
|
||||
.empty {
|
||||
@apply py-8 text-center;
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { ListItem as ListItemType } from '@reactive-resume/schema';
|
||||
import clsx from 'clsx';
|
||||
import get from 'lodash/get';
|
||||
import isArray from 'lodash/isArray';
|
||||
@ -8,6 +7,7 @@ import { useTranslation } from 'next-i18next';
|
||||
import { useCallback } from 'react';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { ListItem as ListItemType } from 'schema';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||
import { deleteItem, setResumeState } from '@/store/resume/resumeSlice';
|
||||
@ -60,13 +60,13 @@ const List: React.FC<Props> = ({
|
||||
|
||||
dispatch(setResumeState({ path, value: newList }));
|
||||
},
|
||||
[list, dispatch, path]
|
||||
[list, dispatch, path],
|
||||
);
|
||||
|
||||
return (
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<div className={clsx(styles.container, className)}>
|
||||
{isEmpty(list) && <div className={styles.empty}>{t<string>('builder.common.list.empty-text')}</div>}
|
||||
{isEmpty(list) && <div className={styles.empty}>{t('builder.common.list.empty-text')}</div>}
|
||||
|
||||
{list.map((item, index) => {
|
||||
const title = get(item, titleKey, '');
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
.item {
|
||||
@apply flex items-center justify-between;
|
||||
@apply py-5 pl-5 pr-2;
|
||||
@apply border-b border-neutral-900/10 last:border-0 dark:border-neutral-50/10;
|
||||
@apply border-b border-zinc-900/10 last:border-0 dark:border-zinc-50/10;
|
||||
@apply cursor-move transition-opacity;
|
||||
|
||||
.meta {
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { DeleteOutline, DriveFileRenameOutline, FileCopy, MoreVert } from '@mui/icons-material';
|
||||
import { Divider, IconButton, ListItemIcon, ListItemText, Menu, MenuItem, Tooltip } from '@mui/material';
|
||||
import { ListItem as ListItemType } from '@reactive-resume/schema';
|
||||
import clsx from 'clsx';
|
||||
import isFunction from 'lodash/isFunction';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { DropTargetMonitor, useDrag, useDrop, XYCoord } from 'react-dnd';
|
||||
import { ListItem as ListItemType } from 'schema';
|
||||
|
||||
import styles from './ListItem.module.scss';
|
||||
|
||||
@ -126,25 +126,25 @@ const ListItem: React.FC<Props> = ({ item, path, index, title, subtitle, onMove,
|
||||
<ListItemIcon>
|
||||
<DriveFileRenameOutline className="scale-90" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>{t<string>('builder.common.list.actions.edit')}</ListItemText>
|
||||
<ListItemText>{t('builder.common.list.actions.edit')}</ListItemText>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem onClick={() => handleDuplicate(item)}>
|
||||
<ListItemIcon>
|
||||
<FileCopy className="scale-90" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>{t<string>('builder.common.list.actions.duplicate')}</ListItemText>
|
||||
<ListItemText>{t('builder.common.list.actions.duplicate')}</ListItemText>
|
||||
</MenuItem>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Tooltip arrow placement="right" title={t<string>('builder.common.tooltip.delete-item')}>
|
||||
<Tooltip arrow placement="right" title={t('builder.common.tooltip.delete-item')}>
|
||||
<div>
|
||||
<MenuItem onClick={() => handleDelete(item)}>
|
||||
<ListItemIcon>
|
||||
<DeleteOutline className="scale-90" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>{t<string>('builder.common.list.actions.delete')}</ListItemText>
|
||||
<ListItemText>{t('builder.common.list.actions.delete')}</ListItemText>
|
||||
</MenuItem>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
@ -1,11 +1,26 @@
|
||||
import clsx from 'clsx';
|
||||
import Image from 'next/image';
|
||||
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
|
||||
type Props = {
|
||||
size?: 256 | 64 | 48 | 40 | 32;
|
||||
className?: string;
|
||||
size?: 256 | 96 | 64 | 48 | 40 | 32 | 24 | 16;
|
||||
};
|
||||
|
||||
const Logo: React.FC<Props> = ({ size = 64 }) => (
|
||||
<Image alt="Reactive Resume" src="/images/logos/logo.svg" className="rounded" width={size} height={size} priority />
|
||||
);
|
||||
const Logo: React.FC<Props> = ({ size = 64, className }) => {
|
||||
const theme = useAppSelector((state) => state.build.theme);
|
||||
|
||||
return (
|
||||
<Image
|
||||
alt="Reactive Resume"
|
||||
src={`/logo/${theme}.svg`}
|
||||
className={clsx('rounded', className)}
|
||||
width={size}
|
||||
height={size}
|
||||
priority
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Logo;
|
||||
|
||||
@ -15,9 +15,9 @@ const Markdown: React.FC<Props> = ({ className, children }) => {
|
||||
|
||||
return (
|
||||
<ReactMarkdown
|
||||
rehypePlugins={[rehypeKatex]}
|
||||
className={clsx('markdown', className)}
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[rehypeKatex]}
|
||||
>
|
||||
{children}
|
||||
</ReactMarkdown>
|
||||
|
||||
@ -2,6 +2,7 @@ import { TextField } from '@mui/material';
|
||||
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||
import dayjs from 'dayjs';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||
@ -57,13 +58,12 @@ const ResumeInput: React.FC<Props> = ({ type = 'text', label, path, className, m
|
||||
<DatePicker
|
||||
openTo="year"
|
||||
label={label}
|
||||
value={dayjs(value)}
|
||||
className={className}
|
||||
views={['year', 'month', 'day']}
|
||||
slots={{
|
||||
textField: (params) => <TextField {...params} error={false} className={className} />,
|
||||
}}
|
||||
value={isEmpty(value) ? null : dayjs(value)}
|
||||
onChange={(date: dayjs.Dayjs | null) => {
|
||||
date && dayjs(date).isValid() && onChangeValue(dayjs(date).format('YYYY-MM-DD'));
|
||||
if (!date) return onChangeValue('');
|
||||
if (dayjs(date).isValid()) return onChangeValue(dayjs(date).format('YYYY-MM-DD'));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -13,7 +13,7 @@ const ThemeSwitch = styled(Switch)(({ theme }) => ({
|
||||
transform: 'translateX(22px)',
|
||||
'& .MuiSwitch-thumb:before': {
|
||||
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent(
|
||||
'#fff'
|
||||
'#fff',
|
||||
)}" d="M4.2 2.5l-.7 1.8-1.8.7 1.8.7.7 1.8.6-1.8L6.7 5l-1.9-.7-.6-1.8zm15 8.3a6.7 6.7 0 11-6.6-6.6 5.8 5.8 0 006.6 6.6z"/></svg>')`,
|
||||
},
|
||||
'& + .MuiSwitch-track': {
|
||||
@ -36,7 +36,7 @@ const ThemeSwitch = styled(Switch)(({ theme }) => ({
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center',
|
||||
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent(
|
||||
'#fff'
|
||||
'#fff',
|
||||
)}" d="M9.305 1.667V3.75h1.389V1.667h-1.39zm-4.707 1.95l-.982.982L5.09 6.072l.982-.982-1.473-1.473zm10.802 0L13.927 5.09l.982.982 1.473-1.473-.982-.982zM10 5.139a4.872 4.872 0 00-4.862 4.86A4.872 4.872 0 0010 14.862 4.872 4.872 0 0014.86 10 4.872 4.872 0 0010 5.139zm0 1.389A3.462 3.462 0 0113.471 10a3.462 3.462 0 01-3.473 3.472A3.462 3.462 0 016.527 10 3.462 3.462 0 0110 6.528zM1.665 9.305v1.39h2.083v-1.39H1.666zm14.583 0v1.39h2.084v-1.39h-2.084zM5.09 13.928L3.616 15.4l.982.982 1.473-1.473-.982-.982zm9.82 0l-.982.982 1.473 1.473.982-.982-1.473-1.473zM9.305 16.25v2.083h1.389V16.25h-1.39z"/></svg>')`,
|
||||
},
|
||||
},
|
||||
|
||||
25
client/components/ui/Separator.tsx
Normal file
25
client/components/ui/Separator.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '@/utils/styles';
|
||||
|
||||
const Separator = React.forwardRef<
|
||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||
>(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => (
|
||||
<SeparatorPrimitive.Root
|
||||
ref={ref}
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
'shrink-0 dark:bg-zinc-900 bg-zinc-100',
|
||||
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
||||
|
||||
export { Separator };
|
||||
@ -54,5 +54,5 @@ export const languageMap: Record<string, Language> = languages.reduce(
|
||||
...acc,
|
||||
[lang.code]: lang,
|
||||
}),
|
||||
{}
|
||||
{},
|
||||
);
|
||||
|
||||
@ -23,8 +23,8 @@ import {
|
||||
VolunteerActivism,
|
||||
Work,
|
||||
} from '@mui/icons-material';
|
||||
import { Section as SectionRecord, SectionType } from '@reactive-resume/schema';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { Section as SectionRecord, SectionType } from 'schema';
|
||||
|
||||
import Basics from '@/components/build/LeftSidebar/sections/Basics';
|
||||
import Location from '@/components/build/LeftSidebar/sections/Location';
|
||||
|
||||
@ -3,7 +3,7 @@ import { createTheme, ThemeOptions } from '@mui/material/styles';
|
||||
const theme: ThemeOptions = {
|
||||
typography: {
|
||||
fontSize: 12,
|
||||
fontFamily: 'Inter, sans-serif',
|
||||
fontFamily: '"IBM Plex Sans", sans-serif',
|
||||
},
|
||||
components: {
|
||||
MuiButton: {
|
||||
@ -24,6 +24,13 @@ const theme: ThemeOptions = {
|
||||
variant: 'outlined',
|
||||
},
|
||||
},
|
||||
MuiInputBase: {
|
||||
styleOverrides: {
|
||||
input: {
|
||||
boxShadow: 'none !important',
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiAppBar: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
@ -48,6 +55,15 @@ const theme: ThemeOptions = {
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiModal: {
|
||||
defaultProps: {
|
||||
componentsProps: {
|
||||
backdrop: {
|
||||
className: 'backdrop-blur-sm',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -55,8 +71,9 @@ export const lightTheme = createTheme({
|
||||
...theme,
|
||||
palette: {
|
||||
mode: 'light',
|
||||
primary: { main: '#404040' }, // neutral[700]
|
||||
secondary: { main: '#0d9488' }, // teal[600]
|
||||
background: { default: '#fafafa', paper: '#f4f4f5' },
|
||||
primary: { main: '#18181b' },
|
||||
secondary: { main: '#14b8a6' },
|
||||
},
|
||||
});
|
||||
|
||||
@ -64,7 +81,8 @@ export const darkTheme = createTheme({
|
||||
...theme,
|
||||
palette: {
|
||||
mode: 'dark',
|
||||
primary: { main: '#f5f5f5' }, // neutral[100]
|
||||
secondary: { main: '#2dd4bf' }, // teal[400]
|
||||
background: { default: '#09090b', paper: '#18181b' },
|
||||
primary: { main: '#f4f4f5' },
|
||||
secondary: { main: '#0d9488' },
|
||||
},
|
||||
});
|
||||
|
||||
@ -10,7 +10,7 @@ export const FILENAME_TIMESTAMP = 'DDMMYYYYHHmmss';
|
||||
|
||||
// Links
|
||||
export const DOCS_URL = 'https://docs.rxresu.me';
|
||||
export const DONATION_URL = 'https://paypal.me/RajaRajanA';
|
||||
export const DONATION_URL = 'https://paypal.me/amruthde';
|
||||
export const TRANSLATE_URL = 'https://translate.rxresu.me/';
|
||||
export const DIGITALOCEAN_URL = 'https://pillai.xyz/digitalocean';
|
||||
export const REDDIT_URL = 'https://www.reddit.com/r/reactiveresume/';
|
||||
|
||||
11
client/constants/tilt.ts
Normal file
11
client/constants/tilt.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { ReactParallaxTiltProps } from 'react-parallax-tilt';
|
||||
|
||||
export const defaultTiltProps: ReactParallaxTiltProps = {
|
||||
scale: 1.05,
|
||||
tiltMaxAngleX: 8,
|
||||
tiltMaxAngleY: 8,
|
||||
perspective: 1400,
|
||||
glareEnable: true,
|
||||
glareMaxOpacity: 0.1,
|
||||
glareColor: '#fafafa',
|
||||
};
|
||||
@ -54,16 +54,16 @@ const ForgotPasswordModal: React.FC = () => {
|
||||
<BaseModal
|
||||
icon={<Password />}
|
||||
isOpen={isOpen}
|
||||
heading={t<string>('modals.auth.forgot-password.heading')}
|
||||
heading={t('modals.auth.forgot-password.heading')}
|
||||
handleClose={handleClose}
|
||||
footerChildren={
|
||||
<Button type="submit" disabled={isLoading} onClick={handleSubmit(onSubmit)}>
|
||||
{t<string>('modals.auth.forgot-password.actions.send-email')}
|
||||
{t('modals.auth.forgot-password.actions.send-email')}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="grid gap-4">
|
||||
<p>{t<string>('modals.auth.forgot-password.body')}</p>
|
||||
<p>{t('modals.auth.forgot-password.body')}</p>
|
||||
|
||||
<form className="grid gap-4 xl:w-2/3">
|
||||
<Controller
|
||||
@ -72,7 +72,7 @@ const ForgotPasswordModal: React.FC = () => {
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
autoFocus
|
||||
label={t<string>('modals.auth.forgot-password.form.email.label')}
|
||||
label={t('modals.auth.forgot-password.form.email.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -81,7 +81,7 @@ const ForgotPasswordModal: React.FC = () => {
|
||||
/>
|
||||
</form>
|
||||
|
||||
<p className="text-xs">{t<string>('modals.auth.forgot-password.help-text')}</p>
|
||||
<p className="text-xs">{t('modals.auth.forgot-password.help-text')}</p>
|
||||
</div>
|
||||
</BaseModal>
|
||||
</>
|
||||
|
||||
@ -53,7 +53,7 @@ const LoginModal: React.FC = () => {
|
||||
const { mutateAsync: loginMutation } = useMutation<void, ServerError, LoginParams>(login);
|
||||
|
||||
const { mutateAsync: loginWithGoogleMutation } = useMutation<void, ServerError, LoginWithGoogleParams>(
|
||||
loginWithGoogle
|
||||
loginWithGoogle,
|
||||
);
|
||||
|
||||
const handleClose = () => {
|
||||
@ -105,7 +105,7 @@ const LoginModal: React.FC = () => {
|
||||
<BaseModal
|
||||
icon={<Login />}
|
||||
isOpen={isOpen}
|
||||
heading={t<string>('modals.auth.login.heading')}
|
||||
heading={t('modals.auth.login.heading')}
|
||||
handleClose={handleClose}
|
||||
footerChildren={
|
||||
<div className="flex gap-4">
|
||||
@ -114,23 +114,23 @@ const LoginModal: React.FC = () => {
|
||||
)}
|
||||
|
||||
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}>
|
||||
{t<string>('modals.auth.login.actions.login')}
|
||||
{t('modals.auth.login.actions.login')}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<p>{t<string>('modals.auth.login.body')}</p>
|
||||
<p>{t('modals.auth.login.body')}</p>
|
||||
|
||||
<form className="grid gap-4 xl:w-2/3">
|
||||
<form className="grid gap-4">
|
||||
<Controller
|
||||
name="identifier"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
autoFocus
|
||||
label={t<string>('modals.auth.login.form.username.label')}
|
||||
label={t('modals.auth.login.form.username.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message || t<string>('modals.auth.login.form.username.help-text')}
|
||||
helperText={fieldState.error?.message || t('modals.auth.login.form.username.help-text')}
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
@ -142,7 +142,7 @@ const LoginModal: React.FC = () => {
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
label={t<string>('modals.auth.login.form.password.label')}
|
||||
label={t('modals.auth.login.form.password.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
InputProps={{ endAdornment: <PasswordVisibility /> }}
|
||||
|
||||
@ -61,7 +61,7 @@ const RegisterModal: React.FC = () => {
|
||||
const { mutateAsync, isLoading } = useMutation<void, ServerError, RegisterParams>(registerUser);
|
||||
|
||||
const { mutateAsync: loginWithGoogleMutation } = useMutation<void, ServerError, LoginWithGoogleParams>(
|
||||
loginWithGoogle
|
||||
loginWithGoogle,
|
||||
);
|
||||
|
||||
const handleClose = () => {
|
||||
@ -95,7 +95,7 @@ const RegisterModal: React.FC = () => {
|
||||
<BaseModal
|
||||
icon={<HowToReg />}
|
||||
isOpen={isOpen}
|
||||
heading={t<string>('modals.auth.register.heading')}
|
||||
heading={t('modals.auth.register.heading')}
|
||||
handleClose={handleClose}
|
||||
footerChildren={
|
||||
<div className="flex gap-4">
|
||||
@ -104,12 +104,12 @@ const RegisterModal: React.FC = () => {
|
||||
)}
|
||||
|
||||
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}>
|
||||
{t<string>('modals.auth.register.actions.register')}
|
||||
{t('modals.auth.register.actions.register')}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<p>{t<string>('modals.auth.register.body')}</p>
|
||||
<p>{t('modals.auth.register.body')}</p>
|
||||
|
||||
<form className="grid gap-4 md:grid-cols-2">
|
||||
<Controller
|
||||
@ -118,7 +118,7 @@ const RegisterModal: React.FC = () => {
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
autoFocus
|
||||
label={t<string>('modals.auth.register.form.name.label')}
|
||||
label={t('modals.auth.register.form.name.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -131,7 +131,7 @@ const RegisterModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
label={t<string>('modals.auth.register.form.username.label')}
|
||||
label={t('modals.auth.register.form.username.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -145,7 +145,7 @@ const RegisterModal: React.FC = () => {
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
type="email"
|
||||
label={t<string>('modals.auth.register.form.email.label')}
|
||||
label={t('modals.auth.register.form.email.label')}
|
||||
className="col-span-2"
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
@ -160,7 +160,7 @@ const RegisterModal: React.FC = () => {
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
type="password"
|
||||
label={t<string>('modals.auth.register.form.password.label')}
|
||||
label={t('modals.auth.register.form.password.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -174,7 +174,7 @@ const RegisterModal: React.FC = () => {
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
type="password"
|
||||
label={t<string>('modals.auth.register.form.confirm-password.label')}
|
||||
label={t('modals.auth.register.form.confirm-password.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
|
||||
@ -65,15 +65,15 @@ const ResetPasswordModal: React.FC = () => {
|
||||
<BaseModal
|
||||
icon={<LockReset />}
|
||||
isOpen={isOpen}
|
||||
heading={t<string>('modals.auth.reset-password.heading')}
|
||||
heading={t('modals.auth.reset-password.heading')}
|
||||
handleClose={handleClose}
|
||||
footerChildren={
|
||||
<Button type="submit" disabled={isLoading} onClick={handleSubmit(onSubmit)}>
|
||||
{t<string>('modals.auth.reset-password.actions.set-password')}
|
||||
{t('modals.auth.reset-password.actions.set-password')}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<p>{t<string>('modals.auth.reset-password.body')}</p>
|
||||
<p>{t('modals.auth.reset-password.body')}</p>
|
||||
|
||||
<form className="grid gap-4 md:grid-cols-2">
|
||||
<Controller
|
||||
@ -83,7 +83,7 @@ const ResetPasswordModal: React.FC = () => {
|
||||
<TextField
|
||||
autoFocus
|
||||
type="password"
|
||||
label={t<string>('modals.auth.reset-password.form.password.label')}
|
||||
label={t('modals.auth.reset-password.form.password.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -97,7 +97,7 @@ const ResetPasswordModal: React.FC = () => {
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
type="password"
|
||||
label={t<string>('modals.auth.reset-password.form.confirm-password.label')}
|
||||
label={t('modals.auth.reset-password.form.confirm-password.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
|
||||
@ -2,7 +2,7 @@ import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||
import { Award, SectionPath } from '@reactive-resume/schema';
|
||||
import { Award, SectionPath } from 'schema';
|
||||
import dayjs from 'dayjs';
|
||||
import Joi from 'joi';
|
||||
import get from 'lodash/get';
|
||||
@ -50,8 +50,8 @@ const AwardModal: React.FC = () => {
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
const isEditMode = useMemo(() => !!item, [item]);
|
||||
|
||||
const addText = useMemo(() => t<string>('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t<string>('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
const addText = useMemo(() => t('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
|
||||
const { reset, control, handleSubmit } = useForm<FormData>({
|
||||
defaultValues: defaultState,
|
||||
@ -73,7 +73,7 @@ const AwardModal: React.FC = () => {
|
||||
setModalState({
|
||||
modal: `builder.${path}`,
|
||||
state: { open: false },
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
reset(defaultState);
|
||||
@ -101,7 +101,7 @@ const AwardModal: React.FC = () => {
|
||||
<TextField
|
||||
required
|
||||
autoFocus
|
||||
label={t<string>('builder.common.form.title.label')}
|
||||
label={t('builder.common.form.title.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -115,7 +115,7 @@ const AwardModal: React.FC = () => {
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
required
|
||||
label={t<string>('builder.leftSidebar.sections.awards.form.awarder.label')}
|
||||
label={t('builder.leftSidebar.sections.awards.form.awarder.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -130,7 +130,7 @@ const AwardModal: React.FC = () => {
|
||||
<DatePicker
|
||||
openTo="year"
|
||||
inputRef={field.ref}
|
||||
label={t<string>('builder.common.form.date.label')}
|
||||
label={t('builder.common.form.date.label')}
|
||||
value={dayjs(field.value)}
|
||||
views={['year', 'month', 'day']}
|
||||
slots={{
|
||||
@ -154,7 +154,7 @@ const AwardModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
label={t<string>('builder.common.form.url.label')}
|
||||
label={t('builder.common.form.url.label')}
|
||||
placeholder="https://"
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
@ -171,7 +171,7 @@ const AwardModal: React.FC = () => {
|
||||
multiline
|
||||
minRows={3}
|
||||
maxRows={6}
|
||||
label={t<string>('builder.common.form.summary.label')}
|
||||
label={t('builder.common.form.summary.label')}
|
||||
className="col-span-2"
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message || <MarkdownSupported />}
|
||||
|
||||
@ -2,7 +2,7 @@ import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||
import { Certificate, SectionPath } from '@reactive-resume/schema';
|
||||
import { Certificate, SectionPath } from 'schema';
|
||||
import dayjs from 'dayjs';
|
||||
import Joi from 'joi';
|
||||
import get from 'lodash/get';
|
||||
@ -50,8 +50,8 @@ const CertificateModal: React.FC = () => {
|
||||
|
||||
const isEditMode = useMemo(() => !!item, [item]);
|
||||
|
||||
const addText = useMemo(() => t<string>('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t<string>('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
const addText = useMemo(() => t('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
|
||||
const { reset, control, handleSubmit } = useForm<FormData>({
|
||||
defaultValues: defaultState,
|
||||
@ -73,7 +73,7 @@ const CertificateModal: React.FC = () => {
|
||||
setModalState({
|
||||
modal: `builder.${path}`,
|
||||
state: { open: false },
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
reset(defaultState);
|
||||
@ -101,7 +101,7 @@ const CertificateModal: React.FC = () => {
|
||||
<TextField
|
||||
required
|
||||
autoFocus
|
||||
label={t<string>('builder.common.form.name.label')}
|
||||
label={t('builder.common.form.name.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -115,7 +115,7 @@ const CertificateModal: React.FC = () => {
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
required
|
||||
label={t<string>('builder.leftSidebar.sections.certifications.form.issuer.label')}
|
||||
label={t('builder.leftSidebar.sections.certifications.form.issuer.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -130,7 +130,7 @@ const CertificateModal: React.FC = () => {
|
||||
<DatePicker
|
||||
openTo="year"
|
||||
inputRef={field.ref}
|
||||
label={t<string>('builder.common.form.date.label')}
|
||||
label={t('builder.common.form.date.label')}
|
||||
value={dayjs(field.value)}
|
||||
views={['year', 'month', 'day']}
|
||||
slots={{
|
||||
@ -154,7 +154,7 @@ const CertificateModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
label={t<string>('builder.common.form.url.label')}
|
||||
label={t('builder.common.form.url.label')}
|
||||
placeholder="https://"
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
@ -171,7 +171,7 @@ const CertificateModal: React.FC = () => {
|
||||
multiline
|
||||
minRows={3}
|
||||
maxRows={6}
|
||||
label={t<string>('builder.common.form.summary.label')}
|
||||
label={t('builder.common.form.summary.label')}
|
||||
className="col-span-2"
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message || <MarkdownSupported />}
|
||||
|
||||
@ -2,7 +2,6 @@ import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||
import { Button, Slider, TextField } from '@mui/material';
|
||||
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||
import { Custom } from '@reactive-resume/schema';
|
||||
import dayjs from 'dayjs';
|
||||
import Joi from 'joi';
|
||||
import get from 'lodash/get';
|
||||
@ -10,6 +9,7 @@ import isEmpty from 'lodash/isEmpty';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { Custom } from 'schema';
|
||||
|
||||
import ArrayInput from '@/components/shared/ArrayInput';
|
||||
import BaseModal from '@/components/shared/BaseModal';
|
||||
@ -68,8 +68,8 @@ const CustomModal: React.FC = () => {
|
||||
|
||||
const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`));
|
||||
|
||||
const addText = useMemo(() => t<string>('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t<string>('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
const addText = useMemo(() => t('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
|
||||
const { reset, control, handleSubmit } = useForm<FormData>({
|
||||
defaultValues: defaultState,
|
||||
@ -91,7 +91,7 @@ const CustomModal: React.FC = () => {
|
||||
setModalState({
|
||||
modal: 'builder.sections.custom',
|
||||
state: { open: false },
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
reset(defaultState);
|
||||
@ -119,7 +119,7 @@ const CustomModal: React.FC = () => {
|
||||
<TextField
|
||||
required
|
||||
autoFocus
|
||||
label={t<string>('builder.common.form.title.label')}
|
||||
label={t('builder.common.form.title.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -132,7 +132,7 @@ const CustomModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
label={t<string>('builder.common.form.subtitle.label')}
|
||||
label={t('builder.common.form.subtitle.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -147,7 +147,7 @@ const CustomModal: React.FC = () => {
|
||||
<DatePicker
|
||||
openTo="year"
|
||||
inputRef={field.ref}
|
||||
label={t<string>('builder.common.form.start-date.label')}
|
||||
label={t('builder.common.form.start-date.label')}
|
||||
value={dayjs(field.value)}
|
||||
views={['year', 'month', 'day']}
|
||||
slots={{
|
||||
@ -173,7 +173,7 @@ const CustomModal: React.FC = () => {
|
||||
<DatePicker
|
||||
openTo="year"
|
||||
inputRef={field.ref}
|
||||
label={t<string>('builder.common.form.end-date.label')}
|
||||
label={t('builder.common.form.end-date.label')}
|
||||
value={dayjs(field.value)}
|
||||
views={['year', 'month', 'day']}
|
||||
slots={{
|
||||
@ -181,7 +181,7 @@ const CustomModal: React.FC = () => {
|
||||
<TextField
|
||||
{...params}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message || t<string>('builder.common.form.end-date.help-text')}
|
||||
helperText={fieldState.error?.message || t('builder.common.form.end-date.help-text')}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
@ -197,7 +197,7 @@ const CustomModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
label={t<string>('builder.common.form.url.label')}
|
||||
label={t('builder.common.form.url.label')}
|
||||
placeholder="https://"
|
||||
className="col-span-2"
|
||||
error={!!fieldState.error}
|
||||
@ -212,7 +212,7 @@ const CustomModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
label={t<string>('builder.common.form.level.label')}
|
||||
label={t('builder.common.form.level.label')}
|
||||
className="col-span-2"
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
@ -226,11 +226,13 @@ const CustomModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<div className="col-span-2">
|
||||
<h4 className="mb-3 font-semibold">{t<string>('builder.common.form.levelNum.label')}</h4>
|
||||
<h4 className="mb-3 font-semibold">{t('builder.common.form.levelNum.label')}</h4>
|
||||
|
||||
<div className="px-10">
|
||||
<Slider
|
||||
{...field}
|
||||
name={field.name}
|
||||
value={field.value}
|
||||
onChange={(_, value) => field.onChange(value as number)}
|
||||
marks={[
|
||||
{
|
||||
value: 0,
|
||||
@ -250,7 +252,7 @@ const CustomModal: React.FC = () => {
|
||||
defaultValue={0}
|
||||
color="secondary"
|
||||
valueLabelDisplay="auto"
|
||||
aria-label={t<string>('builder.common.form.levelNum.label')}
|
||||
aria-label={t('builder.common.form.levelNum.label')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -267,7 +269,7 @@ const CustomModal: React.FC = () => {
|
||||
maxRows={6}
|
||||
className="col-span-2"
|
||||
error={!!fieldState.error}
|
||||
label={t<string>('builder.common.form.summary.label')}
|
||||
label={t('builder.common.form.summary.label')}
|
||||
helperText={fieldState.error?.message || <MarkdownSupported />}
|
||||
{...field}
|
||||
/>
|
||||
@ -279,7 +281,7 @@ const CustomModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<ArrayInput
|
||||
label={t<string>('builder.common.form.keywords.label')}
|
||||
label={t('builder.common.form.keywords.label')}
|
||||
value={field.value as string[]}
|
||||
onChange={field.onChange}
|
||||
errors={fieldState.error}
|
||||
|
||||
@ -2,7 +2,7 @@ import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||
import { Education, SectionPath } from '@reactive-resume/schema';
|
||||
import { Education, SectionPath } from 'schema';
|
||||
import dayjs from 'dayjs';
|
||||
import Joi from 'joi';
|
||||
import get from 'lodash/get';
|
||||
@ -63,8 +63,8 @@ const EducationModal: React.FC = () => {
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
const isEditMode = useMemo(() => !!item, [item]);
|
||||
|
||||
const addText = useMemo(() => t<string>('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t<string>('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
const addText = useMemo(() => t('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
|
||||
const { reset, control, handleSubmit } = useForm<FormData>({
|
||||
defaultValues: defaultState,
|
||||
@ -86,7 +86,7 @@ const EducationModal: React.FC = () => {
|
||||
setModalState({
|
||||
modal: `builder.${path}`,
|
||||
state: { open: false },
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
reset(defaultState);
|
||||
@ -114,7 +114,7 @@ const EducationModal: React.FC = () => {
|
||||
<TextField
|
||||
required
|
||||
autoFocus
|
||||
label={t<string>('builder.leftSidebar.sections.education.form.institution.label')}
|
||||
label={t('builder.leftSidebar.sections.education.form.institution.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -128,7 +128,7 @@ const EducationModal: React.FC = () => {
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
required
|
||||
label={t<string>('builder.leftSidebar.sections.education.form.degree.label')}
|
||||
label={t('builder.leftSidebar.sections.education.form.degree.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -141,7 +141,7 @@ const EducationModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
label={t<string>('builder.leftSidebar.sections.education.form.area-study.label')}
|
||||
label={t('builder.leftSidebar.sections.education.form.area-study.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -154,7 +154,7 @@ const EducationModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
label={t<string>('builder.leftSidebar.sections.education.form.grade.label')}
|
||||
label={t('builder.leftSidebar.sections.education.form.grade.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -169,17 +169,14 @@ const EducationModal: React.FC = () => {
|
||||
<DatePicker
|
||||
openTo="year"
|
||||
inputRef={field.ref}
|
||||
label={t<string>('builder.common.form.start-date.label')}
|
||||
label={t('builder.common.form.start-date.label')}
|
||||
value={dayjs(field.value)}
|
||||
views={['year', 'month', 'day']}
|
||||
slots={{
|
||||
textField: (params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message || params.inputProps?.placeholder}
|
||||
/>
|
||||
),
|
||||
slotProps={{
|
||||
textField: {
|
||||
error: !!fieldState.error,
|
||||
helperText: fieldState.error?.message || t('builder.common.form.start-date.help-text')
|
||||
},
|
||||
}}
|
||||
onChange={(date: dayjs.Dayjs | null) => {
|
||||
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||
@ -195,17 +192,14 @@ const EducationModal: React.FC = () => {
|
||||
<DatePicker
|
||||
openTo="year"
|
||||
inputRef={field.ref}
|
||||
label={t<string>('builder.common.form.end-date.label')}
|
||||
label={t('builder.common.form.end-date.label')}
|
||||
value={dayjs(field.value)}
|
||||
views={['year', 'month', 'day']}
|
||||
slots={{
|
||||
textField: (params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message || t<string>('builder.common.form.end-date.help-text')}
|
||||
/>
|
||||
),
|
||||
slotProps={{
|
||||
textField: {
|
||||
error: !!fieldState.error,
|
||||
helperText: fieldState.error?.message || t('builder.common.form.end-date.help-text')
|
||||
},
|
||||
}}
|
||||
onChange={(date: dayjs.Dayjs | null) => {
|
||||
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||
@ -219,7 +213,7 @@ const EducationModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
label={t<string>('builder.common.form.url.label')}
|
||||
label={t('builder.common.form.url.label')}
|
||||
placeholder="https://"
|
||||
className="col-span-2"
|
||||
error={!!fieldState.error}
|
||||
@ -237,7 +231,7 @@ const EducationModal: React.FC = () => {
|
||||
multiline
|
||||
minRows={3}
|
||||
maxRows={6}
|
||||
label={t<string>('builder.common.form.summary.label')}
|
||||
label={t('builder.common.form.summary.label')}
|
||||
className="col-span-2"
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message || <MarkdownSupported />}
|
||||
@ -251,7 +245,7 @@ const EducationModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<ArrayInput
|
||||
label={t<string>('builder.leftSidebar.sections.education.form.courses.label')}
|
||||
label={t('builder.leftSidebar.sections.education.form.courses.label')}
|
||||
value={field.value as string[]}
|
||||
onChange={field.onChange}
|
||||
errors={fieldState.error}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { Interest, SectionPath } from '@reactive-resume/schema';
|
||||
import { Interest, SectionPath } from 'schema';
|
||||
import Joi from 'joi';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
@ -41,8 +41,8 @@ const InterestModal: React.FC = () => {
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
const isEditMode = useMemo(() => !!item, [item]);
|
||||
|
||||
const addText = useMemo(() => t<string>('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t<string>('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
const addText = useMemo(() => t('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
|
||||
const { reset, control, handleSubmit } = useForm<FormData>({
|
||||
defaultValues: defaultState,
|
||||
@ -64,7 +64,7 @@ const InterestModal: React.FC = () => {
|
||||
setModalState({
|
||||
modal: `builder.${path}`,
|
||||
state: { open: false },
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
reset(defaultState);
|
||||
@ -92,7 +92,7 @@ const InterestModal: React.FC = () => {
|
||||
<TextField
|
||||
required
|
||||
autoFocus
|
||||
label={t<string>('builder.common.form.name.label')}
|
||||
label={t('builder.common.form.name.label')}
|
||||
className="col-span-2"
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
@ -106,7 +106,7 @@ const InterestModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<ArrayInput
|
||||
label={t<string>('builder.common.form.keywords.label')}
|
||||
label={t('builder.common.form.keywords.label')}
|
||||
value={field.value as string[]}
|
||||
onChange={field.onChange}
|
||||
errors={fieldState.error}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||
import { Button, Slider, TextField } from '@mui/material';
|
||||
import { Language, SectionPath } from '@reactive-resume/schema';
|
||||
import Joi from 'joi';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { Language, SectionPath } from 'schema';
|
||||
|
||||
import BaseModal from '@/components/shared/BaseModal';
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||
@ -42,8 +42,8 @@ const LanguageModal: React.FC = () => {
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
const isEditMode = useMemo(() => !!item, [item]);
|
||||
|
||||
const addText = useMemo(() => t<string>('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t<string>('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
const addText = useMemo(() => t('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
|
||||
const { reset, control, handleSubmit } = useForm<FormData>({
|
||||
defaultValues: defaultState,
|
||||
@ -65,7 +65,7 @@ const LanguageModal: React.FC = () => {
|
||||
setModalState({
|
||||
modal: `builder.${path}`,
|
||||
state: { open: false },
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
reset(defaultState);
|
||||
@ -93,7 +93,7 @@ const LanguageModal: React.FC = () => {
|
||||
<TextField
|
||||
required
|
||||
autoFocus
|
||||
label={t<string>('builder.common.form.name.label')}
|
||||
label={t('builder.common.form.name.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -107,7 +107,7 @@ const LanguageModal: React.FC = () => {
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
required
|
||||
label={t<string>('builder.common.form.level.label')}
|
||||
label={t('builder.common.form.level.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -120,11 +120,13 @@ const LanguageModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<div className="col-span-2">
|
||||
<h4 className="mb-3 font-semibold">{t<string>('builder.common.form.levelNum.label')}</h4>
|
||||
<h4 className="mb-3 font-semibold">{t('builder.common.form.levelNum.label')}</h4>
|
||||
|
||||
<div className="px-10">
|
||||
<Slider
|
||||
{...field}
|
||||
name={field.name}
|
||||
value={field.value}
|
||||
onChange={(_, value) => field.onChange(value as number)}
|
||||
marks={[
|
||||
{
|
||||
value: 0,
|
||||
@ -144,7 +146,7 @@ const LanguageModal: React.FC = () => {
|
||||
defaultValue={0}
|
||||
color="secondary"
|
||||
valueLabelDisplay="auto"
|
||||
aria-label={t<string>('builder.common.form.levelNum.label')}
|
||||
aria-label={t('builder.common.form.levelNum.label')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add, AlternateEmail, DriveFileRenameOutline } from '@mui/icons-material';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { Profile } from '@reactive-resume/schema';
|
||||
import { Profile } from 'schema';
|
||||
import Joi from 'joi';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
@ -42,11 +42,11 @@ const ProfileModal: React.FC = () => {
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
const isEditMode = useMemo(() => !!item, [item]);
|
||||
|
||||
const addText = t<string>('builder.common.actions.add', {
|
||||
token: t<string>('builder.leftSidebar.sections.profiles.heading', { count: 1 }),
|
||||
const addText = t('builder.common.actions.add', {
|
||||
token: t('builder.leftSidebar.sections.profiles.heading', { count: 1 }),
|
||||
});
|
||||
const editText = t<string>('builder.common.actions.edit', {
|
||||
token: t<string>('builder.leftSidebar.sections.profiles.heading', { count: 1 }),
|
||||
const editText = t('builder.common.actions.edit', {
|
||||
token: t('builder.leftSidebar.sections.profiles.heading', { count: 1 }),
|
||||
});
|
||||
|
||||
const { reset, control, handleSubmit } = useForm<FormData>({
|
||||
@ -69,7 +69,7 @@ const ProfileModal: React.FC = () => {
|
||||
setModalState({
|
||||
modal: `builder.${path}`,
|
||||
state: { open: false },
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
reset(defaultState);
|
||||
@ -97,7 +97,7 @@ const ProfileModal: React.FC = () => {
|
||||
<TextField
|
||||
required
|
||||
autoFocus
|
||||
label={t<string>('builder.leftSidebar.sections.profiles.form.network.label')}
|
||||
label={t('builder.leftSidebar.sections.profiles.form.network.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -111,7 +111,7 @@ const ProfileModal: React.FC = () => {
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
required
|
||||
label={t<string>('builder.leftSidebar.sections.profiles.form.username.label')}
|
||||
label={t('builder.leftSidebar.sections.profiles.form.username.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
InputProps={{
|
||||
@ -127,7 +127,7 @@ const ProfileModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
label={t<string>('builder.common.form.url.label')}
|
||||
label={t('builder.common.form.url.label')}
|
||||
className="col-span-2"
|
||||
placeholder="https://"
|
||||
error={!!fieldState.error}
|
||||
|
||||
@ -2,7 +2,7 @@ import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||
import { Project, SectionPath } from '@reactive-resume/schema';
|
||||
import { Project, SectionPath } from 'schema';
|
||||
import dayjs from 'dayjs';
|
||||
import Joi from 'joi';
|
||||
import get from 'lodash/get';
|
||||
@ -59,8 +59,8 @@ const ProjectModal: React.FC = () => {
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
const isEditMode = useMemo(() => !!item, [item]);
|
||||
|
||||
const addText = useMemo(() => t<string>('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t<string>('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
const addText = useMemo(() => t('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
|
||||
const { reset, control, handleSubmit } = useForm<FormData>({
|
||||
defaultValues: defaultState,
|
||||
@ -82,7 +82,7 @@ const ProjectModal: React.FC = () => {
|
||||
setModalState({
|
||||
modal: `builder.${path}`,
|
||||
state: { open: false },
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
reset(defaultState);
|
||||
@ -110,7 +110,7 @@ const ProjectModal: React.FC = () => {
|
||||
<TextField
|
||||
required
|
||||
autoFocus
|
||||
label={t<string>('builder.common.form.name.label')}
|
||||
label={t('builder.common.form.name.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -124,7 +124,7 @@ const ProjectModal: React.FC = () => {
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
required
|
||||
label={t<string>('builder.common.form.description.label')}
|
||||
label={t('builder.common.form.description.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -139,7 +139,7 @@ const ProjectModal: React.FC = () => {
|
||||
<DatePicker
|
||||
openTo="year"
|
||||
inputRef={field.ref}
|
||||
label={t<string>('builder.common.form.start-date.label')}
|
||||
label={t('builder.common.form.start-date.label')}
|
||||
value={dayjs(field.value)}
|
||||
views={['year', 'month', 'day']}
|
||||
slots={{
|
||||
@ -165,7 +165,7 @@ const ProjectModal: React.FC = () => {
|
||||
<DatePicker
|
||||
openTo="year"
|
||||
inputRef={field.ref}
|
||||
label={t<string>('builder.common.form.end-date.label')}
|
||||
label={t('builder.common.form.end-date.label')}
|
||||
value={dayjs(field.value)}
|
||||
views={['year', 'month', 'day']}
|
||||
slots={{
|
||||
@ -173,7 +173,7 @@ const ProjectModal: React.FC = () => {
|
||||
<TextField
|
||||
{...params}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message || t<string>('builder.common.form.end-date.help-text')}
|
||||
helperText={fieldState.error?.message || t('builder.common.form.end-date.help-text')}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
@ -189,7 +189,7 @@ const ProjectModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
label={t<string>('builder.common.form.url.label')}
|
||||
label={t('builder.common.form.url.label')}
|
||||
placeholder="https://"
|
||||
className="col-span-2"
|
||||
error={!!fieldState.error}
|
||||
@ -207,7 +207,7 @@ const ProjectModal: React.FC = () => {
|
||||
multiline
|
||||
minRows={3}
|
||||
maxRows={6}
|
||||
label={t<string>('builder.common.form.summary.label')}
|
||||
label={t('builder.common.form.summary.label')}
|
||||
className="col-span-2"
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message || <MarkdownSupported />}
|
||||
@ -221,7 +221,7 @@ const ProjectModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<ArrayInput
|
||||
label={t<string>('builder.common.form.keywords.label')}
|
||||
label={t('builder.common.form.keywords.label')}
|
||||
value={field.value as string[]}
|
||||
onChange={field.onChange}
|
||||
errors={fieldState.error}
|
||||
|
||||
@ -2,7 +2,7 @@ import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||
import { Publication, SectionPath } from '@reactive-resume/schema';
|
||||
import { Publication, SectionPath } from 'schema';
|
||||
import dayjs from 'dayjs';
|
||||
import Joi from 'joi';
|
||||
import get from 'lodash/get';
|
||||
@ -50,8 +50,8 @@ const PublicationModal: React.FC = () => {
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
const isEditMode = useMemo(() => !!item, [item]);
|
||||
|
||||
const addText = useMemo(() => t<string>('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t<string>('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
const addText = useMemo(() => t('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
|
||||
const { reset, control, handleSubmit } = useForm<FormData>({
|
||||
defaultValues: defaultState,
|
||||
@ -73,7 +73,7 @@ const PublicationModal: React.FC = () => {
|
||||
setModalState({
|
||||
modal: `builder.${path}`,
|
||||
state: { open: false },
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
reset(defaultState);
|
||||
@ -101,7 +101,7 @@ const PublicationModal: React.FC = () => {
|
||||
<TextField
|
||||
required
|
||||
autoFocus
|
||||
label={t<string>('builder.common.form.name.label')}
|
||||
label={t('builder.common.form.name.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -115,7 +115,7 @@ const PublicationModal: React.FC = () => {
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
required
|
||||
label={t<string>('builder.leftSidebar.sections.publications.form.publisher.label')}
|
||||
label={t('builder.leftSidebar.sections.publications.form.publisher.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -130,7 +130,7 @@ const PublicationModal: React.FC = () => {
|
||||
<DatePicker
|
||||
openTo="year"
|
||||
inputRef={field.ref}
|
||||
label={t<string>('builder.common.form.date.label')}
|
||||
label={t('builder.common.form.date.label')}
|
||||
value={dayjs(field.value)}
|
||||
views={['year', 'month', 'day']}
|
||||
slots={{
|
||||
@ -154,7 +154,7 @@ const PublicationModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
label={t<string>('builder.common.form.url.label')}
|
||||
label={t('builder.common.form.url.label')}
|
||||
placeholder="https://"
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
@ -171,7 +171,7 @@ const PublicationModal: React.FC = () => {
|
||||
multiline
|
||||
minRows={3}
|
||||
maxRows={6}
|
||||
label={t<string>('builder.common.form.summary.label')}
|
||||
label={t('builder.common.form.summary.label')}
|
||||
className="col-span-2"
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message || <MarkdownSupported />}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { Reference, SectionPath } from '@reactive-resume/schema';
|
||||
import { Reference, SectionPath } from 'schema';
|
||||
import Joi from 'joi';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
@ -47,8 +47,8 @@ const ReferenceModal: React.FC = () => {
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
const isEditMode = useMemo(() => !!item, [item]);
|
||||
|
||||
const addText = useMemo(() => t<string>('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t<string>('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
const addText = useMemo(() => t('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
|
||||
const { reset, control, handleSubmit } = useForm<FormData>({
|
||||
defaultValues: defaultState,
|
||||
@ -70,7 +70,7 @@ const ReferenceModal: React.FC = () => {
|
||||
setModalState({
|
||||
modal: `builder.${path}`,
|
||||
state: { open: false },
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
reset(defaultState);
|
||||
@ -98,7 +98,7 @@ const ReferenceModal: React.FC = () => {
|
||||
<TextField
|
||||
required
|
||||
autoFocus
|
||||
label={t<string>('builder.common.form.name.label')}
|
||||
label={t('builder.common.form.name.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -112,7 +112,7 @@ const ReferenceModal: React.FC = () => {
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
required
|
||||
label={t<string>('builder.leftSidebar.sections.references.form.relationship.label')}
|
||||
label={t('builder.leftSidebar.sections.references.form.relationship.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -125,7 +125,7 @@ const ReferenceModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
label={t<string>('builder.common.form.phone.label')}
|
||||
label={t('builder.common.form.phone.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -138,7 +138,7 @@ const ReferenceModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
label={t<string>('builder.common.form.email.label')}
|
||||
label={t('builder.common.form.email.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -154,7 +154,7 @@ const ReferenceModal: React.FC = () => {
|
||||
multiline
|
||||
minRows={3}
|
||||
maxRows={6}
|
||||
label={t<string>('builder.common.form.summary.label')}
|
||||
label={t('builder.common.form.summary.label')}
|
||||
className="col-span-2"
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message || <MarkdownSupported />}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||
import { Button, Slider, TextField } from '@mui/material';
|
||||
import { SectionPath, Skill } from '@reactive-resume/schema';
|
||||
import Joi from 'joi';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { SectionPath, Skill } from 'schema';
|
||||
|
||||
import ArrayInput from '@/components/shared/ArrayInput';
|
||||
import BaseModal from '@/components/shared/BaseModal';
|
||||
@ -45,8 +45,8 @@ const SkillModal: React.FC = () => {
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
const isEditMode = useMemo(() => !!item, [item]);
|
||||
|
||||
const addText = useMemo(() => t<string>('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t<string>('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
const addText = useMemo(() => t('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
|
||||
const { reset, control, handleSubmit } = useForm<FormData>({
|
||||
defaultValues: defaultState,
|
||||
@ -68,7 +68,7 @@ const SkillModal: React.FC = () => {
|
||||
setModalState({
|
||||
modal: `builder.${path}`,
|
||||
state: { open: false },
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
reset(defaultState);
|
||||
@ -96,7 +96,7 @@ const SkillModal: React.FC = () => {
|
||||
<TextField
|
||||
required
|
||||
autoFocus
|
||||
label={t<string>('builder.common.form.name.label')}
|
||||
label={t('builder.common.form.name.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -109,7 +109,7 @@ const SkillModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
label={t<string>('builder.common.form.level.label')}
|
||||
label={t('builder.common.form.level.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -122,11 +122,13 @@ const SkillModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<div className="col-span-2">
|
||||
<h4 className="mb-3 font-semibold">{t<string>('builder.common.form.levelNum.label')}</h4>
|
||||
<h4 className="mb-3 font-semibold">{t('builder.common.form.levelNum.label')}</h4>
|
||||
|
||||
<div className="px-3">
|
||||
<Slider
|
||||
{...field}
|
||||
name={field.name}
|
||||
value={field.value}
|
||||
onChange={(_, value) => field.onChange(value as number)}
|
||||
marks={[
|
||||
{
|
||||
value: 0,
|
||||
@ -146,7 +148,7 @@ const SkillModal: React.FC = () => {
|
||||
defaultValue={0}
|
||||
color="secondary"
|
||||
valueLabelDisplay="auto"
|
||||
aria-label={t<string>('builder.common.form.levelNum.label')}
|
||||
aria-label={t('builder.common.form.levelNum.label')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -158,7 +160,7 @@ const SkillModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<ArrayInput
|
||||
label={t<string>('builder.common.form.keywords.label')}
|
||||
label={t('builder.common.form.keywords.label')}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
errors={fieldState.error}
|
||||
|
||||
@ -2,7 +2,7 @@ import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||
import { SectionPath, Volunteer } from '@reactive-resume/schema';
|
||||
import { SectionPath, Volunteer } from 'schema';
|
||||
import dayjs from 'dayjs';
|
||||
import Joi from 'joi';
|
||||
import get from 'lodash/get';
|
||||
@ -56,8 +56,8 @@ const VolunteerModal: React.FC = () => {
|
||||
const item: FormData = get(payload, 'item', null);
|
||||
const isEditMode = useMemo(() => !!item, [item]);
|
||||
|
||||
const addText = useMemo(() => t<string>('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t<string>('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
const addText = useMemo(() => t('builder.common.actions.add', { token: heading }), [t, heading]);
|
||||
const editText = useMemo(() => t('builder.common.actions.edit', { token: heading }), [t, heading]);
|
||||
|
||||
const { reset, control, handleSubmit } = useForm<FormData>({
|
||||
defaultValues: defaultState,
|
||||
@ -79,7 +79,7 @@ const VolunteerModal: React.FC = () => {
|
||||
setModalState({
|
||||
modal: `builder.${path}`,
|
||||
state: { open: false },
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
reset(defaultState);
|
||||
@ -107,7 +107,7 @@ const VolunteerModal: React.FC = () => {
|
||||
<TextField
|
||||
required
|
||||
autoFocus
|
||||
label={t<string>('builder.leftSidebar.sections.volunteer.form.organization.label')}
|
||||
label={t('builder.leftSidebar.sections.volunteer.form.organization.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -121,7 +121,7 @@ const VolunteerModal: React.FC = () => {
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
required
|
||||
label={t<string>('builder.common.form.position.label')}
|
||||
label={t('builder.common.form.position.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -136,7 +136,7 @@ const VolunteerModal: React.FC = () => {
|
||||
<DatePicker
|
||||
openTo="year"
|
||||
inputRef={field.ref}
|
||||
label={t<string>('builder.common.form.start-date.label')}
|
||||
label={t('builder.common.form.start-date.label')}
|
||||
value={dayjs(field.value)}
|
||||
views={['year', 'month', 'day']}
|
||||
slots={{
|
||||
@ -162,7 +162,7 @@ const VolunteerModal: React.FC = () => {
|
||||
<DatePicker
|
||||
openTo="year"
|
||||
inputRef={field.ref}
|
||||
label={t<string>('builder.common.form.end-date.label')}
|
||||
label={t('builder.common.form.end-date.label')}
|
||||
value={dayjs(field.value)}
|
||||
views={['year', 'month', 'day']}
|
||||
slots={{
|
||||
@ -170,7 +170,7 @@ const VolunteerModal: React.FC = () => {
|
||||
<TextField
|
||||
{...params}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message || t<string>('builder.common.form.end-date.help-text')}
|
||||
helperText={fieldState.error?.message || t('builder.common.form.end-date.help-text')}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
@ -186,7 +186,7 @@ const VolunteerModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
label={t<string>('builder.common.form.url.label')}
|
||||
label={t('builder.common.form.url.label')}
|
||||
placeholder="https://"
|
||||
className="col-span-2"
|
||||
error={!!fieldState.error}
|
||||
@ -204,7 +204,7 @@ const VolunteerModal: React.FC = () => {
|
||||
multiline
|
||||
minRows={3}
|
||||
maxRows={6}
|
||||
label={t<string>('builder.common.form.summary.label')}
|
||||
label={t('builder.common.form.summary.label')}
|
||||
className="col-span-2"
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message || <MarkdownSupported />}
|
||||
|
||||
@ -2,7 +2,7 @@ import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add, DriveFileRenameOutline } from '@mui/icons-material';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||
import { WorkExperience } from '@reactive-resume/schema';
|
||||
import { WorkExperience } from 'schema';
|
||||
import dayjs from 'dayjs';
|
||||
import Joi from 'joi';
|
||||
import get from 'lodash/get';
|
||||
@ -57,13 +57,18 @@ const WorkModal: React.FC = () => {
|
||||
const isEditMode = useMemo(() => !!item, [item]);
|
||||
|
||||
const addText = useMemo(
|
||||
() => t<string>('builder.common.actions.add', { token: t<string>(`builder.leftSidebar.${path}.heading`, heading) }),
|
||||
[t, heading]
|
||||
() =>
|
||||
t('builder.common.actions.add', {
|
||||
token: t(`builder.leftSidebar.${path}.heading`, { defaultValue: heading }),
|
||||
}),
|
||||
[t, heading],
|
||||
);
|
||||
const editText = useMemo(
|
||||
() =>
|
||||
t<string>('builder.common.actions.edit', { token: t<string>(`builder.leftSidebar.${path}.heading`, heading) }),
|
||||
[t, heading]
|
||||
t('builder.common.actions.edit', {
|
||||
token: t(`builder.leftSidebar.${path}.heading`, { defaultValue: heading }),
|
||||
}),
|
||||
[t, heading],
|
||||
);
|
||||
|
||||
const { reset, control, handleSubmit } = useForm<FormData>({
|
||||
@ -86,7 +91,7 @@ const WorkModal: React.FC = () => {
|
||||
setModalState({
|
||||
modal: 'builder.sections.work',
|
||||
state: { open: false },
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
reset(defaultState);
|
||||
@ -114,7 +119,7 @@ const WorkModal: React.FC = () => {
|
||||
<TextField
|
||||
required
|
||||
autoFocus
|
||||
label={t<string>('builder.common.form.name.label')}
|
||||
label={t('builder.leftSidebar.sections.experience.form.name.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -128,7 +133,7 @@ const WorkModal: React.FC = () => {
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
required
|
||||
label={t<string>('builder.common.form.position.label')}
|
||||
label={t('builder.common.form.position.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -143,17 +148,14 @@ const WorkModal: React.FC = () => {
|
||||
<DatePicker
|
||||
openTo="year"
|
||||
inputRef={field.ref}
|
||||
label={t<string>('builder.common.form.start-date.label')}
|
||||
label={t('builder.common.form.start-date.label')}
|
||||
value={dayjs(field.value)}
|
||||
views={['year', 'month', 'day']}
|
||||
slots={{
|
||||
textField: (params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message || params.inputProps?.placeholder}
|
||||
/>
|
||||
),
|
||||
slotProps={{
|
||||
textField: {
|
||||
error: !!fieldState.error,
|
||||
helperText: fieldState.error?.message || t('builder.common.form.start-date.help-text')
|
||||
},
|
||||
}}
|
||||
onChange={(date: dayjs.Dayjs | null) => {
|
||||
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||
@ -169,17 +171,14 @@ const WorkModal: React.FC = () => {
|
||||
<DatePicker
|
||||
openTo="year"
|
||||
inputRef={field.ref}
|
||||
label={t<string>('builder.common.form.end-date.label')}
|
||||
label={t('builder.common.form.end-date.label')}
|
||||
value={dayjs(field.value)}
|
||||
views={['year', 'month', 'day']}
|
||||
slots={{
|
||||
textField: (params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message || t<string>('builder.common.form.end-date.help-text')}
|
||||
/>
|
||||
),
|
||||
slotProps={{
|
||||
textField: {
|
||||
error: !!fieldState.error,
|
||||
helperText: fieldState.error?.message || t('builder.common.form.end-date.help-text')
|
||||
},
|
||||
}}
|
||||
onChange={(date: dayjs.Dayjs | null) => {
|
||||
date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD'));
|
||||
@ -193,7 +192,7 @@ const WorkModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
label={t<string>('builder.common.form.url.label')}
|
||||
label={t('builder.common.form.url.label')}
|
||||
placeholder="https://"
|
||||
className="col-span-2"
|
||||
error={!!fieldState.error}
|
||||
@ -211,7 +210,7 @@ const WorkModal: React.FC = () => {
|
||||
multiline
|
||||
minRows={3}
|
||||
maxRows={6}
|
||||
label={t<string>('builder.common.form.summary.label')}
|
||||
label={t('builder.common.form.summary.label')}
|
||||
className="col-span-2"
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message || <MarkdownSupported />}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Add } from '@mui/icons-material';
|
||||
import { Button, FormControlLabel, FormGroup, Switch, TextField } from '@mui/material';
|
||||
import { Resume } from '@reactive-resume/schema';
|
||||
import { Resume } from 'schema';
|
||||
import Joi from 'joi';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useEffect } from 'react';
|
||||
@ -80,15 +80,15 @@ const CreateResumeModal: React.FC = () => {
|
||||
<BaseModal
|
||||
isOpen={isOpen}
|
||||
icon={<Add />}
|
||||
heading={t<string>('modals.dashboard.create-resume.heading')}
|
||||
heading={t('modals.dashboard.create-resume.heading')}
|
||||
handleClose={handleClose}
|
||||
footerChildren={
|
||||
<Button type="submit" disabled={isLoading} onClick={handleSubmit(onSubmit)}>
|
||||
{t<string>('modals.dashboard.create-resume.actions.create-resume')}
|
||||
{t('modals.dashboard.create-resume.actions.create-resume')}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<p>{t<string>('modals.dashboard.create-resume.body')}</p>
|
||||
<p>{t('modals.dashboard.create-resume.body')}</p>
|
||||
|
||||
<form className="grid gap-4">
|
||||
<Controller
|
||||
@ -97,7 +97,7 @@ const CreateResumeModal: React.FC = () => {
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
autoFocus
|
||||
label={t<string>('modals.dashboard.create-resume.form.name.label')}
|
||||
label={t('modals.dashboard.create-resume.form.name.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -110,7 +110,7 @@ const CreateResumeModal: React.FC = () => {
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
label={t<string>('modals.dashboard.create-resume.form.slug.label')}
|
||||
label={t('modals.dashboard.create-resume.form.slug.label')}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
{...field}
|
||||
@ -120,7 +120,7 @@ const CreateResumeModal: React.FC = () => {
|
||||
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
label={t<string>('modals.dashboard.create-resume.form.public.label')}
|
||||
label={t('modals.dashboard.create-resume.form.public.label')}
|
||||
control={
|
||||
<Controller
|
||||
name="isPublic"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Code, ImportExport, LinkedIn, TrackChanges, UploadFile } from '@mui/icons-material';
|
||||
import { Button, Divider } from '@mui/material';
|
||||
import { Integration, Resume } from '@reactive-resume/schema';
|
||||
import { Integration, Resume } from 'schema';
|
||||
import { Trans, useTranslation } from 'next-i18next';
|
||||
import { useRef } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
@ -63,7 +63,7 @@ const ImportExternalModal: React.FC = () => {
|
||||
const file = event.target.files[0];
|
||||
|
||||
if (file.size > FILE_UPLOAD_MAX_SIZE) {
|
||||
toast.error(t<string>('common.toast.error.upload-file-size'));
|
||||
toast.error(t('common.toast.error.upload-file-size'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -78,13 +78,13 @@ const ImportExternalModal: React.FC = () => {
|
||||
<BaseModal
|
||||
isOpen={isOpen}
|
||||
icon={<ImportExport />}
|
||||
heading={t<string>('modals.dashboard.import-external.heading')}
|
||||
heading={t('modals.dashboard.import-external.heading')}
|
||||
handleClose={handleClose}
|
||||
>
|
||||
<div className="grid gap-5">
|
||||
<h2 className="inline-flex items-center gap-2 text-lg font-medium">
|
||||
<LinkedIn />
|
||||
{t<string>('modals.dashboard.import-external.linkedin.heading')}
|
||||
{t('modals.dashboard.import-external.linkedin.heading')}
|
||||
</h2>
|
||||
|
||||
<p className="mb-2">
|
||||
@ -110,7 +110,7 @@ const ImportExternalModal: React.FC = () => {
|
||||
startIcon={<UploadFile />}
|
||||
onClick={() => handleClick('linkedin')}
|
||||
>
|
||||
{t<string>('modals.dashboard.import-external.linkedin.actions.upload-archive')}
|
||||
{t('modals.dashboard.import-external.linkedin.actions.upload-archive')}
|
||||
</Button>
|
||||
|
||||
<input
|
||||
@ -128,7 +128,7 @@ const ImportExternalModal: React.FC = () => {
|
||||
<div className="grid gap-5">
|
||||
<h2 className="inline-flex items-center gap-2 text-lg font-medium">
|
||||
<Code />
|
||||
{t<string>('modals.dashboard.import-external.json-resume.heading')}
|
||||
{t('modals.dashboard.import-external.json-resume.heading')}
|
||||
</h2>
|
||||
|
||||
<p className="mb-2">
|
||||
@ -154,7 +154,7 @@ const ImportExternalModal: React.FC = () => {
|
||||
startIcon={<UploadFile />}
|
||||
onClick={() => handleClick('json-resume')}
|
||||
>
|
||||
{t<string>('modals.dashboard.import-external.json-resume.actions.upload-json')}
|
||||
{t('modals.dashboard.import-external.json-resume.actions.upload-json')}
|
||||
</Button>
|
||||
|
||||
<input
|
||||
@ -172,10 +172,10 @@ const ImportExternalModal: React.FC = () => {
|
||||
<div className="grid gap-5">
|
||||
<h2 className="inline-flex items-center gap-2 text-lg font-medium">
|
||||
<TrackChanges />
|
||||
{t<string>('modals.dashboard.import-external.reactive-resume.heading')}
|
||||
{t('modals.dashboard.import-external.reactive-resume.heading')}
|
||||
</h2>
|
||||
|
||||
<p className="mb-2">{t<string>('modals.dashboard.import-external.reactive-resume.body')}</p>
|
||||
<p className="mb-2">{t('modals.dashboard.import-external.reactive-resume.body')}</p>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<Button
|
||||
@ -184,7 +184,7 @@ const ImportExternalModal: React.FC = () => {
|
||||
startIcon={<UploadFile />}
|
||||
onClick={() => handleClick('reactive-resume')}
|
||||
>
|
||||
{t<string>('modals.dashboard.import-external.reactive-resume.actions.upload-json')}
|
||||
{t('modals.dashboard.import-external.reactive-resume.actions.upload-json')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -193,7 +193,7 @@ const ImportExternalModal: React.FC = () => {
|
||||
startIcon={<UploadFile />}
|
||||
onClick={() => handleClick('reactive-resume-v2')}
|
||||
>
|
||||
{t<string>('modals.dashboard.import-external.reactive-resume.actions.upload-json-v2')}
|
||||
{t('modals.dashboard.import-external.reactive-resume.actions.upload-json-v2')}
|
||||
</Button>
|
||||
|
||||
<input
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user