Compare commits

...

124 Commits

Author SHA1 Message Date
a293d209de update dependencies, bump up version to 3.8.4 2023-08-07 11:48:56 +02:00
5b9ea43090 Merge pull request #1418 from AmruthPillai/dependabot/npm_and_yarn/semver-5.7.2
Bump semver from 5.7.1 to 5.7.2
2023-08-07 09:48:05 +02:00
01b36ee8d8 Merge pull request #1420 from AmruthPillai/add-date-format-options
Add 'YYYY.MM' & 'YYYY.M' date format options
2023-08-07 09:30:07 +02:00
af1c314c36 Add 'YYYY.MM' & 'YYYY.M' date format options
closes #1419
2023-08-07 09:29:47 +02:00
d836e3d992 Bump semver from 5.7.1 to 5.7.2
Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v5.7.1...v5.7.2)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-03 06:19:13 +00:00
fb2db6839f Update README.md 2023-08-01 20:41:48 +02:00
787d0af9d1 Update README.md 2023-08-01 20:40:02 +02:00
95de63f387 Merge pull request #1411 from Jubair70/fix/work_modal_datepicker
fix: Work Modal Datepicker popup
2023-07-31 16:12:19 +02:00
456b896310 Merge branch 'main' into fix/work_modal_datepicker 2023-07-31 15:09:40 +06:00
974bf7e032 fix: Work Modal Datepicker 2023-07-31 15:02:17 +06:00
8fa5324a39 Merge pull request #1410 from AmruthPillai/dependabot/docker/server/playwright-v1.36.2-focal
Bump playwright from v1.36.1-focal to v1.36.2-focal in /server
2023-07-31 10:31:58 +02:00
4ac9289344 Bump playwright from v1.36.1-focal to v1.36.2-focal in /server
Bumps playwright from v1.36.1-focal to v1.36.2-focal.

---
updated-dependencies:
- dependency-name: playwright
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-31 02:45:02 +00:00
d67272cf9e bump up version to 3.8.3 2023-07-27 20:09:26 +02:00
f937a88b9d attempt to fix leafish template issues 2023-07-27 20:08:47 +02:00
7465a7ec78 Merge pull request #1407 from AmruthPillai/fix/id-null-when-section-duplicated-cloned
Fix issue when a section is duplicated/cloned, ID is null
2023-07-27 19:51:01 +02:00
b53d8854df bump up version to 3.8.2 2023-07-27 19:43:19 +02:00
bffa0be909 fix dev dotenv script 2023-07-27 19:42:48 +02:00
06fee1696e update dependencies, fix birthDate error outline 2023-07-27 19:42:25 +02:00
f9a11092a6 Fix issue when a section is duplicated/cloned, ID is null 2023-07-27 18:35:19 +02:00
9c76999945 add start pipeline command to turbo 2023-07-25 15:37:10 +02:00
d23d1a615e Merge pull request #1403 from Jubair70/fix_education_modal_datepicker
fix: Education Date picker modal UI component
2023-07-25 14:50:46 +02:00
a5701a37a6 Merge pull request #1400 from rcwell/main
Fixed Date entered on any <Date> field is one day earlier
2023-07-25 14:50:39 +02:00
a739b25f42 update all dependencies 2023-07-25 14:50:10 +02:00
145aa14ba0 fix: Education Date picker modal 2023-07-24 18:27:39 +06:00
94358bf61c don't convert date to local date 2023-07-23 07:46:19 +14:00
ce50df61a5 Merge pull request #1395 from AmruthPillai/i18n_main
New Crowdin updates
2023-07-21 22:14:17 +02:00
f18da54dfa Merge branch 'main' into i18n_main 2023-07-21 22:14:11 +02:00
14c5e36fae Merge pull request #1396 from Jubair70/Jubair70/fixes-date-selection
Fixed date selection by selecting month after year
2023-07-21 22:13:06 +02:00
1483f9b4f2 Merge branch 'main' into Jubair70/fixes-date-selection 2023-07-21 15:04:53 +06:00
f7d8e4ebb4 slots removed 2023-07-21 15:03:58 +06:00
7c42d6e607 New translations modals.json (Portuguese, Brazilian) 2023-07-20 23:34:56 +02:00
08dea8ad8b New translations landing.json (Portuguese, Brazilian) 2023-07-20 23:34:41 +02:00
950d7ea4e7 New translations common.json (Portuguese, Brazilian) 2023-07-20 23:34:15 +02:00
ebc12042a9 New translations builder.json (Nepali) 2023-07-20 23:34:05 +02:00
d8168d2a9d New translations builder.json (Amharic) 2023-07-20 23:34:04 +02:00
7cfda3c83d New translations builder.json (Odia) 2023-07-20 23:34:03 +02:00
8fcfbdd69d New translations builder.json (Kannada) 2023-07-20 23:34:03 +02:00
1eb52261f2 New translations builder.json (Malayalam) 2023-07-20 23:34:02 +02:00
88400b769d New translations builder.json (Hindi) 2023-07-20 23:34:01 +02:00
b6831fc532 New translations builder.json (Marathi) 2023-07-20 23:34:00 +02:00
b231b60b5a New translations builder.json (Tamil) 2023-07-20 23:33:59 +02:00
2679c9ebc2 New translations builder.json (Khmer) 2023-07-20 23:33:58 +02:00
278253b809 New translations builder.json (Persian) 2023-07-20 23:33:57 +02:00
8a933de0bd New translations builder.json (Indonesian) 2023-07-20 23:33:57 +02:00
704cba06f4 New translations builder.json (Portuguese, Brazilian) 2023-07-20 23:33:56 +02:00
b946098bd0 New translations builder.json (Vietnamese) 2023-07-20 23:33:55 +02:00
f7b95f7679 New translations builder.json (Ukrainian) 2023-07-20 23:33:54 +02:00
e38967874e New translations builder.json (Turkish) 2023-07-20 23:33:53 +02:00
8368c4e183 New translations builder.json (Serbian (Cyrillic)) 2023-07-20 23:33:52 +02:00
951f14ef69 New translations builder.json (Russian) 2023-07-20 23:33:51 +02:00
4a75be95ef New translations builder.json (Portuguese) 2023-07-20 23:33:51 +02:00
1125557fbc New translations builder.json (Polish) 2023-07-20 23:33:50 +02:00
db63138307 New translations builder.json (Korean) 2023-07-20 23:33:48 +02:00
a52feac93b New translations builder.json (Italian) 2023-07-20 23:33:47 +02:00
ad7b6ad2c6 New translations builder.json (Hungarian) 2023-07-20 23:33:46 +02:00
33e3850bb7 New translations builder.json (Hebrew) 2023-07-20 23:33:45 +02:00
c29605dbd0 New translations builder.json (Finnish) 2023-07-20 23:33:44 +02:00
14b2ba4f73 New translations builder.json (Greek) 2023-07-20 23:33:43 +02:00
8868684125 New translations builder.json (Danish) 2023-07-20 23:33:39 +02:00
c1ceb0cd50 New translations builder.json (Czech) 2023-07-20 23:33:38 +02:00
1105f672a5 New translations builder.json (Catalan) 2023-07-20 23:33:37 +02:00
67cc49c068 New translations builder.json (Bulgarian) 2023-07-20 23:33:36 +02:00
505406508b New translations builder.json (Arabic) 2023-07-20 23:33:35 +02:00
bfd37951df New translations builder.json (Spanish) 2023-07-20 23:33:34 +02:00
339cae05f1 New translations builder.json (French) 2023-07-20 23:33:34 +02:00
48069c10a4 New translations builder.json (Romanian) 2023-07-20 23:33:33 +02:00
51317b2901 New translations builder.json (Japanese) 2023-07-20 22:32:27 +02:00
e5ce53b2aa Merge pull request #1399 from thomasmazon/thomas-improve-pt-br-translation
Thomas: fixing some Portuguese Brazilian labels
2023-07-20 22:32:00 +02:00
2bc7c93174 Thomas: fixing some Portuguese Brazilian labels 2023-07-20 11:56:54 -03:00
1d97f01942 Jubair70:fix/date-selection 2023-07-19 17:58:13 +06:00
5ad517f1d3 Jubair70:fix/date-selection 2023-07-19 17:53:23 +06:00
8088c70038 New translations builder.json (Chinese Simplified) 2023-07-19 13:52:09 +02:00
e36df82ba9 Merge pull request #1393 from AmruthPillai/dependabot/docker/server/playwright-v1.36.1-focal
Bump playwright from v1.35.1-focal to v1.36.1-focal in /server
2023-07-19 08:45:18 +02:00
de513a12da Merge pull request #1392 from AmruthPillai/dependabot/github_actions/docker/setup-buildx-action-2.9.1
Bump docker/setup-buildx-action from 2.9.0 to 2.9.1
2023-07-19 08:45:11 +02:00
06f1a813ce Merge pull request #1387 from AmruthPillai/i18n_main
New Crowdin updates
2023-07-19 08:45:01 +02:00
1de9195f20 Merge branch 'main' into i18n_main 2023-07-19 08:44:54 +02:00
eaa21ead3e Update build-deploy.yml 2023-07-19 08:44:47 +02:00
3ea4a9b000 Bump playwright from v1.35.1-focal to v1.36.1-focal in /server
Bumps playwright from v1.35.1-focal to v1.36.1-focal.

---
updated-dependencies:
- dependency-name: playwright
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-17 02:36:05 +00:00
f0484c1c28 Bump docker/setup-buildx-action from 2.9.0 to 2.9.1
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.9.0 to 2.9.1.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.9.0...v2.9.1)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-17 02:11:08 +00:00
7ebda09a5f New translations builder.json (German) 2023-07-14 22:35:05 +02:00
161ca0ee28 run prettier formatting on all files 2023-07-13 08:54:37 +02:00
984078db76 parallelize ci workflow 2023-07-12 16:26:07 +02:00
fbc0ae8918 fix use of dev dependency 2023-07-12 16:21:46 +02:00
3e93656f1f update issue templates 2023-07-12 16:02:16 +02:00
01bf17d7c8 Update README.md 2023-07-12 16:01:09 +02:00
651013fcf2 Merge pull request #1381 from AmruthPillai/fix/issue-with-private-resumes
fix(client): 🐛 do not allow private resumes to be viewable or downloadable through the link
2023-07-12 15:59:57 +02:00
1c2d796c50 fix(client): 🐛 do not allow private resumes to be viewable or downloadable through the link 2023-07-12 15:59:22 +02:00
5ef4bfcb6b Merge pull request #1379 from AmruthPillai/dependabot/github_actions/docker/setup-buildx-action-2.9.0
Bump docker/setup-buildx-action from 2.8.0 to 2.9.0
2023-07-12 14:47:30 +02:00
a305b6419e Merge pull request #1378 from AmruthPillai/dependabot/gradle/app/org.jetbrains.kotlin.android-1.9.0
Bump org.jetbrains.kotlin.android from 1.8.22 to 1.9.0 in /app
2023-07-12 14:47:22 +02:00
dcfdff2abe Bump docker/setup-buildx-action from 2.8.0 to 2.9.0
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.8.0 to 2.9.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.8.0...v2.9.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 03:00:37 +00:00
de77a6039a Bump org.jetbrains.kotlin.android from 1.8.22 to 1.9.0 in /app
Bumps [org.jetbrains.kotlin.android](https://github.com/JetBrains/kotlin) from 1.8.22 to 1.9.0.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.8.22...v1.9.0)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin.android
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 02:23:11 +00:00
e44eab55c3 Merge pull request #1376 from AmruthPillai/dependabot/github_actions/docker/setup-buildx-action-2.8.0
Bump docker/setup-buildx-action from 2.7.0 to 2.8.0
2023-07-04 13:49:24 +02:00
c73ad9a627 Bump docker/setup-buildx-action from 2.7.0 to 2.8.0
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.7.0 to 2.8.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.7.0...v2.8.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-03 02:45:58 +00:00
be3d4a4f7c Merge pull request #1374 from AmruthPillai/i18n_main
New Crowdin updates
2023-07-02 08:01:43 +02:00
c86792901b New translations builder.json (Swedish) 2023-06-29 15:56:08 +02:00
a2645a10f0 Merge pull request #1369 from AmruthPillai/dependabot/github_actions/docker/setup-buildx-action-2.7.0
Bump docker/setup-buildx-action from 2.6.0 to 2.7.0
2023-06-22 16:13:22 +02:00
772d8a0d41 Merge pull request #1368 from AmruthPillai/dependabot/github_actions/docker/build-push-action-4.1.1
Bump docker/build-push-action from 4.1.0 to 4.1.1
2023-06-22 16:13:14 +02:00
57348c13b2 Merge pull request #1367 from AmruthPillai/dependabot/github_actions/docker/metadata-action-4.6.0
Bump docker/metadata-action from 4.5.0 to 4.6.0
2023-06-22 16:13:03 +02:00
ffb92a967e Merge pull request #1366 from AmruthPillai/dependabot/docker/server/playwright-v1.35.1-focal
Bump playwright from v1.35.0-focal to v1.35.1-focal in /server
2023-06-22 16:10:59 +02:00
ed933f0452 Bump docker/setup-buildx-action from 2.6.0 to 2.7.0
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.6.0 to 2.7.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.6.0...v2.7.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-19 02:58:00 +00:00
26e67fe457 Bump docker/build-push-action from 4.1.0 to 4.1.1
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-19 02:57:55 +00:00
4507d2d032 Bump docker/metadata-action from 4.5.0 to 4.6.0
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v4.5.0...v4.6.0)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-19 02:57:50 +00:00
97b43d2fc9 Bump playwright from v1.35.0-focal to v1.35.1-focal in /server
Bumps playwright from v1.35.0-focal to v1.35.1-focal.

---
updated-dependencies:
- dependency-name: playwright
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-19 02:57:47 +00:00
88916a54d3 Merge pull request #1362 from AmruthPillai/i18n_main
New Crowdin updates
2023-06-15 10:04:54 +02:00
3aa57ebce8 Merge pull request #1358 from AmruthPillai/dependabot/gradle/app/org.jetbrains.kotlin.android-1.8.22
Bump org.jetbrains.kotlin.android from 1.8.21 to 1.8.22 in /app
2023-06-15 10:04:48 +02:00
623d300da3 Merge pull request #1357 from AmruthPillai/dependabot/github_actions/actions/checkout-3.5.3
Bump actions/checkout from 3.5.2 to 3.5.3
2023-06-15 10:04:40 +02:00
10d7562e7a Merge pull request #1356 from AmruthPillai/dependabot/docker/client/node-20-alpine
Bump node from 18-alpine to 20-alpine in /client
2023-06-15 10:04:33 +02:00
807e747018 Merge pull request #1355 from AmruthPillai/dependabot/github_actions/docker/build-push-action-4.1.0
Bump docker/build-push-action from 4.0.0 to 4.1.0
2023-06-15 10:04:26 +02:00
1301cdce12 Merge pull request #1354 from AmruthPillai/dependabot/docker/server/playwright-v1.35.0-focal
Bump playwright from v1.34.3-focal to v1.35.0-focal in /server
2023-06-15 10:04:17 +02:00
c5fcbf5982 New translations common.json (Dutch) 2023-06-14 17:29:51 +02:00
a9daaeba55 New translations builder.json (Dutch) 2023-06-14 17:29:49 +02:00
dc33c35433 Bump org.jetbrains.kotlin.android from 1.8.21 to 1.8.22 in /app
Bumps [org.jetbrains.kotlin.android](https://github.com/JetBrains/kotlin) from 1.8.21 to 1.8.22.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/v1.8.22/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.8.21...v1.8.22)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin.android
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-12 03:00:24 +00:00
189605484a Bump actions/checkout from 3.5.2 to 3.5.3
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.2 to 3.5.3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3.5.2...v3.5.3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-12 02:58:29 +00:00
bb3e93d976 Bump node from 18-alpine to 20-alpine in /client
Bumps node from 18-alpine to 20-alpine.

---
updated-dependencies:
- dependency-name: node
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-12 02:58:18 +00:00
44d692aad1 Bump docker/build-push-action from 4.0.0 to 4.1.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4.0.0...v4.1.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-12 02:58:18 +00:00
bb0ca824b8 Bump playwright from v1.34.3-focal to v1.35.0-focal in /server
Bumps playwright from v1.34.3-focal to v1.35.0-focal.

---
updated-dependencies:
- dependency-name: playwright
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-12 02:57:59 +00:00
a0b8de4ab4 Merge pull request #1347 from AlexTu2/main
add Discord icon
2023-06-09 09:06:47 +02:00
f73a80c684 add Discord icon 2023-06-08 22:52:38 -04:00
0eddb7d5a3 remove old ci action workflows 2023-06-08 16:28:08 +02:00
6ff36cb1e4 fix build-deploy script 2023-06-08 16:27:14 +02:00
c513d68813 Add push: true to Build and Deploy Script 2023-06-08 15:25:16 +02:00
8d3f4e031c update dependencies, rework on github actions ci script 2023-06-08 15:05:15 +02:00
3aa8778a67 Update README.md 2023-06-08 13:51:31 +02:00
d4a3cec3c2 create new github actions workflow "Build and Push Multi-Arch Docker Images using Manifest" 2023-06-08 13:48:35 +02:00
210 changed files with 3927 additions and 3731 deletions

2
.github/FUNDING.yml vendored
View File

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

View File

@ -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
View 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

View File

@ -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. -->

View 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
View 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

View File

@ -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

View File

@ -1,61 +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]
arch: [linux/amd64, linux/arm64]
steps:
- name: Checkout the repository
uses: actions/checkout@v3.5.2
- id: version
name: App Version
uses: martinbeentjes/npm-get-version-action@v1.3.1
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.1.0
with:
platforms: ${{ matrix.arch }}
- 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: ${{ matrix.arch }}
file: ${{ matrix.image }}/Dockerfile
build-args: |
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
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 }}

View File

@ -30,7 +30,7 @@ ports:
onOpen: ignore
visibility: private
# Client
# Server
- port: 3100
onOpen: ignore
visibility: public

View File

@ -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 thats 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? Its as easy as editing 3 values and youre done. You dont 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)

View File

@ -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.21' apply false
id 'org.jetbrains.kotlin.android' version '1.9.0' apply false
}
task clean(type: Delete) {

View File

@ -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",

View File

@ -1,4 +1,4 @@
FROM node:18-alpine AS base
FROM node:20-alpine AS base
FROM base AS deps
RUN apk add --no-cache libc6-compat

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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 {

View File

@ -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',
})}

View File

@ -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;
}
}

View File

@ -20,7 +20,6 @@ import {
useMediaQuery,
useTheme,
} from '@mui/material';
import clsx from 'clsx';
import get from 'lodash/get';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
@ -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>

View File

@ -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) &,

View File

@ -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>
);
};

View File

@ -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;

View File

@ -8,7 +8,7 @@ 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>

View File

@ -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

View File

@ -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>

View File

@ -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">

View File

@ -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} />

View File

@ -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>

View File

@ -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`, { defaultValue: 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>
) : (

View File

@ -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) => (

View File

@ -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;

View File

@ -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>

View File

@ -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"

View File

@ -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>

View File

@ -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;

View File

@ -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>

View File

@ -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;

View File

@ -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>

View File

@ -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>

View File

@ -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)} />
}

View File

@ -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) => (

View File

@ -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)}
/>

View File

@ -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" />
</>
);
};

View File

@ -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 {

View File

@ -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 {

View File

@ -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>

View 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;

View 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;

View 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;

View 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;

View File

View 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;

View File

@ -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;

View 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;

View 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;

View 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;

View File

@ -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>
</>
);

View File

@ -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;
}

View 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>
);

View File

@ -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">

View File

@ -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`, { defaultValue: 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>

View 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;

View File

@ -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;

View File

@ -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, '');

View File

@ -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 {

View File

@ -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>

View File

@ -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;

View File

@ -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>

View File

@ -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,8 +58,9 @@ const ResumeInput: React.FC<Props> = ({ type = 'text', label, path, className, m
<DatePicker
openTo="year"
label={label}
value={dayjs(value)}
slots={{ textField: (params) => <TextField {...params} error={false} className={className} /> }}
className={className}
views={['year', 'month', 'day']}
value={isEmpty(value) ? null : dayjs(value)}
onChange={(date: dayjs.Dayjs | null) => {
if (!date) return onChangeValue('');
if (dayjs(date).isValid()) return onChangeValue(dayjs(date).format('YYYY-MM-DD'));

View File

@ -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>')`,
},
},

View 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 };

View File

@ -54,5 +54,5 @@ export const languageMap: Record<string, Language> = languages.reduce(
...acc,
[lang.code]: lang,
}),
{}
{},
);

View File

@ -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' },
},
});

View File

@ -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
View 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',
};

View File

@ -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>
</>

View File

@ -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 /> }}

View File

@ -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}

View File

@ -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}

View File

@ -89,7 +89,7 @@ const UserProfileModal = () => {
render={({ field, fieldState }) => (
<TextField
autoFocus
label={t<string>('modals.auth.profile.form.name.label')}
label={t('modals.auth.profile.form.name.label')}
error={!!fieldState.error}
helperText={fieldState.error?.message}
{...field}
@ -114,16 +114,16 @@ const UserProfileModal = () => {
render={({ field, fieldState }) => (
<TextField
disabled
label={t<string>('modals.auth.profile.form.email.label')}
label={t('modals.auth.profile.form.email.label')}
error={!!fieldState.error}
helperText={t<string>('modals.auth.profile.form.email.help-text')}
helperText={t('modals.auth.profile.form.email.help-text')}
{...field}
/>
)}
/>
<div>
<Button onClick={handleUpdate}>{t<string>('modals.auth.profile.actions.save')}</Button>
<Button onClick={handleUpdate}>{t('modals.auth.profile.actions.save')}</Button>
</div>
</form>
@ -133,12 +133,10 @@ const UserProfileModal = () => {
<div className="flex items-center gap-2">
<CrisisAlert />
<h5 className="font-medium">{t<string>('modals.auth.profile.delete-account.heading')}</h5>
<h5 className="font-medium">{t('modals.auth.profile.delete-account.heading')}</h5>
</div>
<p className="text-xs opacity-75">
{t<string>('modals.auth.profile.delete-account.body', { keyword: 'delete' })}
</p>
<p className="text-xs opacity-75">{t('modals.auth.profile.delete-account.body', { keyword: 'delete' })}</p>
<div className="flex max-w-xs flex-col gap-4">
<TextField
@ -149,7 +147,7 @@ const UserProfileModal = () => {
<div>
<Button variant="contained" color="error" disabled={!isDeleteTextValid} onClick={handleDelete}>
{t<string>('modals.auth.profile.delete-account.actions.delete')}
{t('modals.auth.profile.delete-account.actions.delete')}
</Button>
</div>
</div>

View File

@ -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 />}

View File

@ -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 />}

View File

@ -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 '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}

View File

@ -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}

View File

@ -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}

View File

@ -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 '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>

View File

@ -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}

View File

@ -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}

View File

@ -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 />}

View File

@ -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 />}

View File

@ -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 '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}

View File

@ -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 />}

View File

@ -58,17 +58,17 @@ const WorkModal: React.FC = () => {
const addText = useMemo(
() =>
t<string>('builder.common.actions.add', {
token: t<string>(`builder.leftSidebar.${path}.heading`, { defaultValue: heading }),
t('builder.common.actions.add', {
token: t(`builder.leftSidebar.${path}.heading`, { defaultValue: heading }),
}),
[t, heading]
[t, heading],
);
const editText = useMemo(
() =>
t<string>('builder.common.actions.edit', {
token: t<string>(`builder.leftSidebar.${path}.heading`, { defaultValue: heading }),
t('builder.common.actions.edit', {
token: t(`builder.leftSidebar.${path}.heading`, { defaultValue: heading }),
}),
[t, heading]
[t, heading],
);
const { reset, control, handleSubmit } = useForm<FormData>({
@ -91,7 +91,7 @@ const WorkModal: React.FC = () => {
setModalState({
modal: 'builder.sections.work',
state: { open: false },
})
}),
);
reset(defaultState);
@ -119,7 +119,7 @@ const WorkModal: React.FC = () => {
<TextField
required
autoFocus
label={t<string>('builder.leftSidebar.sections.experience.form.name.label')}
label={t('builder.leftSidebar.sections.experience.form.name.label')}
error={!!fieldState.error}
helperText={fieldState.error?.message}
{...field}
@ -133,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}
@ -148,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'));
@ -174,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'));
@ -198,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}
@ -216,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 />}

View File

@ -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"

View File

@ -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

View File

@ -92,11 +92,11 @@ const RenameResumeModal: React.FC = () => {
<BaseModal
icon={<DriveFileRenameOutline />}
isOpen={isOpen}
heading={t<string>('modals.dashboard.rename-resume.heading')}
heading={t('modals.dashboard.rename-resume.heading')}
handleClose={handleClose}
footerChildren={
<Button type="submit" disabled={isLoading} onClick={handleSubmit(onSubmit)}>
{t<string>('modals.dashboard.rename-resume.actions.rename-resume')}
{t('modals.dashboard.rename-resume.actions.rename-resume')}
</Button>
}
>
@ -107,7 +107,7 @@ const RenameResumeModal: React.FC = () => {
render={({ field, fieldState }) => (
<TextField
autoFocus
label={t<string>('modals.dashboard.rename-resume.form.name.label')}
label={t('modals.dashboard.rename-resume.form.name.label')}
error={!!fieldState.error}
helperText={fieldState.error?.message}
{...field}
@ -120,7 +120,7 @@ const RenameResumeModal: React.FC = () => {
control={control}
render={({ field, fieldState }) => (
<TextField
label={t<string>('modals.dashboard.rename-resume.form.slug.label')}
label={t('modals.dashboard.rename-resume.form.slug.label')}
error={!!fieldState.error}
helperText={fieldState.error?.message}
{...field}

View File

@ -3,80 +3,87 @@
"scripts": {
"dev": "react-env --prefix PUBLIC -- next dev",
"lint": "next lint --fix",
"build": "next build && npm run sitemap",
"build": "next build",
"start": "react-env --prefix PUBLIC -- next start",
"sitemap": "next-sitemap --config next-sitemap.config.js"
},
"dependencies": {
"@beam-australia/react-env": "^3.1.1",
"@emotion/css": "^11.11.0",
"@emotion/css": "^11.11.2",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@hello-pangea/dnd": "^16.2.0",
"@hookform/resolvers": "3.1.0",
"@fontsource/material-icons": "^5.0.7",
"@hello-pangea/dnd": "^16.3.0",
"@hookform/resolvers": "3.2.0",
"@monaco-editor/react": "^4.5.1",
"@mui/icons-material": "^5.11.16",
"@mui/lab": "^5.0.0-alpha.133",
"@mui/material": "^5.13.4",
"@mui/system": "^5.13.2",
"@mui/x-date-pickers": "6.6.0",
"@react-oauth/google": "^0.11.0",
"@mui/icons-material": "^5.14.3",
"@mui/lab": "^5.0.0-alpha.138",
"@mui/material": "^5.14.3",
"@mui/system": "^5.14.3",
"@mui/x-date-pickers": "6.11.0",
"@radix-ui/react-separator": "^1.0.3",
"@react-oauth/google": "^0.11.1",
"@reduxjs/toolkit": "^1.9.5",
"axios": "^1.4.0",
"clsx": "^1.2.1",
"dayjs": "^1.11.8",
"clsx": "^2.0.0",
"dayjs": "^1.11.9",
"downloadjs": "^1.4.7",
"joi": "^17.9.2",
"lodash": "^4.17.21",
"md5-hex": "^4.0.0",
"monaco-editor": "^0.39.0",
"nanoid": "3.3.4",
"next": "13.4.4",
"next-i18next": "^13.3.0",
"monaco-editor": "^0.41.0",
"nanoid": "^3.3.6",
"next": "13.4.13",
"next-i18next": "^14.0.0",
"react": "^18.2.0",
"react-colorful": "^5.6.1",
"react-dnd": "16.0.1",
"react-dnd-html5-backend": "16.0.1",
"react-dom": "^18.2.0",
"react-hook-form": "^7.44.3",
"react-github-btn": "^1.4.0",
"react-hook-form": "^7.45.4",
"react-hot-toast": "2.4.1",
"react-icons": "^4.9.0",
"react-icons": "^4.10.1",
"react-markdown": "^8.0.7",
"react-parallax-tilt": "^1.7.157",
"react-query": "^3.39.3",
"react-redux": "^8.0.7",
"react-zoom-pan-pinch": "^3.0.8",
"react-redux": "^8.1.2",
"react-zoom-pan-pinch": "^3.1.0",
"redux": "^4.2.1",
"redux-persist": "^6.0.0",
"redux-saga": "^1.2.3",
"redux-undo": "^1.0.1",
"redux-undo": "^1.1.0",
"rehype-katex": "^6.0.3",
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
"sharp": "^0.32.1",
"sharp": "^0.32.4",
"tailwind-merge": "^1.14.0",
"uuid": "^9.0.0",
"webfontloader": "^1.6.28"
},
"devDependencies": {
"@babel/core": "^7.22.1",
"schema": "workspace:*",
"@babel/core": "^7.22.9",
"@fontsource/ibm-plex-sans": "^5.0.8",
"@tailwindcss/forms": "^0.5.4",
"@tailwindcss/typography": "^0.5.9",
"@types/downloadjs": "^1.4.3",
"@types/lodash": "^4.14.195",
"@types/node": "^20.2.5",
"@types/react": "^18.2.8",
"@types/react-dom": "^18.2.4",
"@types/lodash": "^4.14.196",
"@types/node": "^20.4.8",
"@types/react": "^18.2.18",
"@types/react-dom": "^18.2.7",
"@types/react-redux": "^7.1.25",
"@types/uuid": "^9.0.1",
"@types/uuid": "^9.0.2",
"@types/webfontloader": "^1.6.35",
"autoprefixer": "^10.4.14",
"csstype": "^3.1.2",
"eslint-config-next": "^13.4.4",
"eslint-plugin-tailwindcss": "^3.12.1",
"eslint-plugin-unused-imports": "^2.0.0",
"next-sitemap": "^4.1.3",
"postcss": "^8.4.24",
"sass": "^1.62.1",
"tailwindcss": "^3.3.2",
"typescript": "^5.1.3"
"eslint-config-next": "^13.4.13",
"eslint-plugin-tailwindcss": "^3.13.0",
"eslint-plugin-unused-imports": "^3.0.0",
"next-sitemap": "^4.1.8",
"postcss": "^8.4.27",
"sass": "^1.64.2",
"schema": "workspace:*",
"tailwindcss": "^3.3.3",
"typescript": "^5.1.6"
}
}

View File

@ -42,14 +42,10 @@ const Build: NextPage<Props> = ({ username, slug }) => {
`resume/${username}/${slug}`,
() => fetchResumeByIdentifier({ username, slug }),
{
cacheTime: 0,
refetchOnMount: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
onSuccess: (resume) => {
dispatch(setResume(resume));
},
}
},
);
useEffect(() => {
@ -62,7 +58,7 @@ const Build: NextPage<Props> = ({ username, slug }) => {
<div className={styles.container}>
<Head>
<title>
{resume.name} | {t<string>('common.title')}
{resume.name} | {t('common.title')}
</title>
</Head>

View File

@ -57,6 +57,16 @@ const Preview: NextPage<Props> = ({ username, slug, resume: initialData }) => {
useEffect(() => {
if (initialData && !isEmpty(initialData)) {
const errorObj = JSON.parse(JSON.stringify(initialData));
const statusCode: number | null = get(errorObj, 'statusCode', null);
if (statusCode === 404) {
toast.error('The resume you were looking for does not exist, or maybe it never did?');
router.push('/');
return;
}
dispatch(setResume(initialData));
}
}, [dispatch, initialData]);
@ -73,10 +83,6 @@ const Preview: NextPage<Props> = ({ username, slug, resume: initialData }) => {
useQuery<Resume>(`resume/${username}/${slug}`, () => fetchResumeByIdentifier({ username, slug }), {
initialData,
retry: false,
refetchOnMount: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
onSuccess: (data) => {
dispatch(setResume(data));
},

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