From 0ba6a444e204e002b32034f6bbc87d4a198d4467 Mon Sep 17 00:00:00 2001 From: Amruth Pillai Date: Thu, 31 Aug 2023 13:22:03 +0200 Subject: [PATCH] project reset: clearing all files and folders --- .dockerignore | 27 - .editorconfig | 8 - .env.example | 36 - .eslintrc.json | 41 - .github/FUNDING.yml | 2 - .github/ISSUE_TEMPLATE/bug-report.yaml | 75 - .github/ISSUE_TEMPLATE/feature-request.yaml | 22 - .github/dependabot.yml | 22 - .github/workflows/build-deploy.yml | 142 - .gitignore | 18 - .gitpod.yml | 41 - .npmrc | 2 - .nvmrc | 1 - .prettierignore | 29 - .prettierrc | 4 - .vscode/extensions.json | 7 - .vscode/settings.json | 22 - CODE_OF_CONDUCT.md | 128 - LICENSE | 21 - README.md | 179 - SECURITY.md | 13 - app/.gitignore | 33 - app/app/.editorconfig | 8 - app/app/.gitignore | 2 - app/app/build.gradle | 47 - app/app/proguard-rules.pro | 21 - app/app/src/main/AndroidManifest.xml | 26 - app/app/src/main/ic_launcher-playstore.png | Bin 12399 -> 0 bytes .../java/me/rxresu/app/CustomWebViewClient.kt | 21 - .../main/java/me/rxresu/app/MainActivity.kt | 34 - app/app/src/main/res/layout/activity_main.xml | 15 - app/app/src/main/res/layout/content_main.xml | 17 - .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 - .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 1663 -> 0 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 2193 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 3487 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 1110 -> 0 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 1466 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 2180 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 2256 -> 0 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 2930 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 5030 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 3423 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 4471 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 7902 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 4768 -> 0 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 6216 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 11525 -> 0 bytes .../res/values/ic_launcher_background.xml | 4 - app/app/src/main/res/values/strings.xml | 3 - app/app/src/main/res/values/themes.xml | 6 - app/build.gradle | 9 - app/gradle.properties | 23 - app/gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 0 bytes app/gradle/wrapper/gradle-wrapper.properties | 6 - app/gradlew | 185 - app/gradlew.bat | 89 - app/settings.gradle | 16 - client/.eslintrc.json | 36 - client/.gitignore | 42 - client/Dockerfile | 45 - client/Dockerfile.standalone | 43 - .../Center/ArtboardController.module.scss | 28 - .../build/Center/ArtboardController.tsx | 168 - .../build/Center/Center.module.scss | 13 - client/components/build/Center/Center.tsx | 57 - .../build/Center/Header.module.scss | 25 - client/components/build/Center/Header.tsx | 215 - .../components/build/Center/Page.module.scss | 43 - client/components/build/Center/Page.tsx | 57 - .../build/LeftSidebar/LeftSidebar.module.scss | 45 - .../build/LeftSidebar/LeftSidebar.tsx | 173 - .../build/LeftSidebar/sections/Basics.tsx | 89 - .../build/LeftSidebar/sections/Location.tsx | 31 - .../LeftSidebar/sections/PhotoFilters.tsx | 97 - .../LeftSidebar/sections/PhotoUpload.tsx | 83 - .../build/LeftSidebar/sections/Profiles.tsx | 52 - .../build/LeftSidebar/sections/Section.tsx | 120 - .../LeftSidebar/sections/SectionSettings.tsx | 66 - .../RightSidebar/RightSidebar.module.scss | 50 - .../build/RightSidebar/RightSidebar.tsx | 86 - .../build/RightSidebar/sections/CustomCSS.tsx | 51 - .../build/RightSidebar/sections/Export.tsx | 84 - .../RightSidebar/sections/Layout.module.scss | 43 - .../build/RightSidebar/sections/Layout.tsx | 147 - .../RightSidebar/sections/Links.module.scss | 20 - .../build/RightSidebar/sections/Links.tsx | 68 - .../build/RightSidebar/sections/Settings.tsx | 246 - .../build/RightSidebar/sections/Sharing.tsx | 78 - .../sections/Templates.module.scss | 22 - .../build/RightSidebar/sections/Templates.tsx | 53 - .../RightSidebar/sections/Theme.module.scss | 8 - .../build/RightSidebar/sections/Theme.tsx | 59 - .../sections/Typography.module.scss | 11 - .../RightSidebar/sections/Typography.tsx | 106 - .../dashboard/ResumeCard.module.scss | 24 - client/components/dashboard/ResumeCard.tsx | 37 - .../dashboard/ResumePreview.module.scss | 37 - client/components/dashboard/ResumePreview.tsx | 188 - client/components/home/Actions.tsx | 44 - client/components/home/Background.tsx | 16 - client/components/home/Footer.tsx | 47 - client/components/home/Header.tsx | 24 - client/components/home/Hero.tsx | 0 client/components/home/Pattern.tsx | 28 - client/components/home/Testimony.module.scss | 12 - client/components/home/Testimony.tsx | 17 - client/components/home/sections/Hero.tsx | 50 - client/components/home/sections/Logo.tsx | 51 - client/components/home/sections/Stats.tsx | 27 - .../components/shared/ArrayInput.module.scss | 15 - client/components/shared/ArrayInput.tsx | 70 - client/components/shared/Avatar.module.scss | 3 - client/components/shared/Avatar.tsx | 77 - .../components/shared/BaseModal.module.scss | 39 - client/components/shared/BaseModal.tsx | 62 - client/components/shared/ColorAvatar.tsx | 20 - client/components/shared/ColorPicker.tsx | 68 - client/components/shared/Copyright.tsx | 19 - client/components/shared/Footer.tsx | 27 - client/components/shared/Heading.module.scss | 17 - client/components/shared/Heading.tsx | 100 - client/components/shared/Icon.tsx | 27 - .../shared/LanguageSwitcher.module.scss | 13 - client/components/shared/LanguageSwitcher.tsx | 53 - client/components/shared/List.module.scss | 7 - client/components/shared/List.tsx | 96 - client/components/shared/ListItem.module.scss | 18 - client/components/shared/ListItem.tsx | 157 - client/components/shared/Loading.module.scss | 32 - client/components/shared/Loading.tsx | 19 - client/components/shared/Logo.tsx | 26 - client/components/shared/Markdown.tsx | 27 - .../components/shared/MarkdownSupported.tsx | 20 - client/components/shared/ResumeInput.tsx | 75 - client/components/shared/ThemeSwitch.tsx | 50 - client/components/ui/Separator.tsx | 25 - client/config/colors.ts | 20 - client/config/languages.ts | 58 - client/config/screenshots.ts | 31 - client/config/sections.tsx | 270 - client/config/theme.ts | 88 - client/constants/flags.ts | 3 - client/constants/index.ts | 23 - client/constants/tilt.ts | 11 - client/data/testimonials.ts | 38 - client/modals/auth/ForgotPasswordModal.tsx | 91 - client/modals/auth/LoginModal.tsx | 173 - client/modals/auth/RegisterModal.tsx | 195 - client/modals/auth/ResetPasswordModal.tsx | 112 - client/modals/auth/UserProfileModal.tsx | 159 - client/modals/builder/sections/AwardModal.tsx | 188 - .../builder/sections/CertificateModal.tsx | 188 - .../modals/builder/sections/CustomModal.tsx | 298 - .../builder/sections/EducationModal.tsx | 275 - .../modals/builder/sections/InterestModal.tsx | 123 - .../modals/builder/sections/LanguageModal.tsx | 161 - .../modals/builder/sections/ProfileModal.tsx | 145 - .../modals/builder/sections/ProjectModal.tsx | 238 - .../builder/sections/PublicationModal.tsx | 188 - .../builder/sections/ReferenceModal.tsx | 171 - client/modals/builder/sections/SkillModal.tsx | 178 - .../builder/sections/VolunteerModal.tsx | 221 - client/modals/builder/sections/WorkModal.tsx | 227 - client/modals/dashboard/CreateResumeModal.tsx | 138 - .../modals/dashboard/ImportExternalModal.tsx | 220 - client/modals/dashboard/RenameResumeModal.tsx | 135 - client/modals/index.tsx | 82 - client/next-env.d.ts | 5 - client/next-i18next.config.js | 56 - client/next-sitemap.config.js | 6 - client/next.config.js | 50 - client/package.json | 89 - client/pages/[username]/[slug]/build.tsx | 72 - client/pages/[username]/[slug]/index.tsx | 148 - client/pages/[username]/[slug]/printer.tsx | 89 - client/pages/_app.tsx | 67 - client/pages/_document.tsx | 77 - client/pages/dashboard.tsx | 78 - client/pages/home.tsx | 35 - client/pages/index.tsx | 223 - client/pages/meta/privacy.tsx | 125 - client/pages/meta/service.tsx | 107 - client/pages/r/[shortId].tsx | 106 - client/postcss.config.js | 6 - client/public/favicon.ico | Bin 69694 -> 0 bytes client/public/icon/dark.svg | 8 - client/public/icon/light.svg | 8 - .../public/images/brand-logos/dark/amazon.svg | 33 - .../public/images/brand-logos/dark/google.svg | 25 - .../images/brand-logos/dark/postman.svg | 15 - .../public/images/brand-logos/dark/twilio.svg | 12 - .../images/brand-logos/dark/zalando.svg | 14 - .../images/brand-logos/light/amazon.svg | 33 - .../images/brand-logos/light/google.svg | 25 - .../images/brand-logos/light/postman.svg | 15 - .../images/brand-logos/light/twilio.svg | 12 - .../images/brand-logos/light/zalando.svg | 14 - client/public/images/covers/cover-0ee139.jpeg | Bin 60002 -> 0 bytes client/public/images/covers/cover-1ab08.jpeg | Bin 20667 -> 0 bytes client/public/images/covers/cover-1f8c9.jpeg | Bin 71871 -> 0 bytes client/public/images/covers/cover-1fe54f.jpeg | Bin 170818 -> 0 bytes client/public/images/covers/cover-253f4a.jpeg | Bin 130981 -> 0 bytes client/public/images/covers/cover-33aec.jpeg | Bin 74746 -> 0 bytes client/public/images/covers/cover-3sc.jpeg | Bin 111492 -> 0 bytes client/public/images/covers/cover-466cb.jpeg | Bin 148481 -> 0 bytes client/public/images/covers/cover-478b3.jpeg | Bin 65827 -> 0 bytes client/public/images/covers/cover-4d9.jpeg | Bin 132997 -> 0 bytes client/public/images/covers/cover-4ed.jpeg | Bin 73425 -> 0 bytes client/public/images/covers/cover-4fd88.jpeg | Bin 162270 -> 0 bytes client/public/images/covers/cover-50f3f3.jpeg | Bin 91837 -> 0 bytes client/public/images/covers/cover-6b8ae.jpeg | Bin 89951 -> 0 bytes client/public/images/covers/cover-6fa09.jpeg | Bin 68580 -> 0 bytes client/public/images/covers/cover-713b2f.jpeg | Bin 160639 -> 0 bytes client/public/images/covers/cover-737f2.jpeg | Bin 193334 -> 0 bytes client/public/images/covers/cover-73dab8.jpeg | Bin 75147 -> 0 bytes client/public/images/covers/cover-79df42.jpeg | Bin 155962 -> 0 bytes client/public/images/covers/cover-7b601.jpeg | Bin 93514 -> 0 bytes client/public/images/covers/cover-7dh.jpeg | Bin 200674 -> 0 bytes client/public/images/covers/cover-7e6ae.jpeg | Bin 30757 -> 0 bytes client/public/images/covers/cover-94b.jpeg | Bin 207302 -> 0 bytes client/public/images/covers/cover-96bdd.jpeg | Bin 51401 -> 0 bytes client/public/images/covers/cover-98afd.jpeg | Bin 135907 -> 0 bytes client/public/images/covers/cover-9hk.jpeg | Bin 87639 -> 0 bytes client/public/images/covers/cover-b26e75.jpeg | Bin 205930 -> 0 bytes client/public/images/covers/cover-b6ea6.jpeg | Bin 157181 -> 0 bytes client/public/images/covers/cover-c219f2.jpeg | Bin 151305 -> 0 bytes client/public/images/covers/cover-c3642.jpeg | Bin 152537 -> 0 bytes client/public/images/covers/cover-c584b.jpeg | Bin 161366 -> 0 bytes client/public/images/covers/cover-c682cb.jpeg | Bin 69242 -> 0 bytes client/public/images/covers/cover-c82a8.jpeg | Bin 85229 -> 0 bytes client/public/images/covers/cover-d312a7.jpeg | Bin 93191 -> 0 bytes client/public/images/covers/cover-dcbd8.jpeg | Bin 170499 -> 0 bytes client/public/images/covers/cover-df274.jpeg | Bin 96674 -> 0 bytes client/public/images/covers/cover-e26ee.jpeg | Bin 27728 -> 0 bytes client/public/images/covers/cover-f3034.jpeg | Bin 245270 -> 0 bytes client/public/images/covers/cover-fec87.jpeg | Bin 126183 -> 0 bytes client/public/images/sample-photo.jpg | Bin 58327 -> 0 bytes .../public/images/screenshots/add-section.png | Bin 142607 -> 0 bytes client/public/images/screenshots/builder.png | Bin 236917 -> 0 bytes .../public/images/screenshots/dashboard.png | Bin 50494 -> 0 bytes .../images/screenshots/import-external.png | Bin 25234 -> 0 bytes .../public/images/screenshots/page-layout.png | Bin 41373 -> 0 bytes client/public/images/screenshots/preview.png | Bin 96131 -> 0 bytes .../public/images/sponsors/digitalocean.svg | 101 - .../images/sponsors/digitaloceanLight.svg | 101 - client/public/images/templates/castform.jpg | Bin 137499 -> 0 bytes client/public/images/templates/gengar.jpg | Bin 109553 -> 0 bytes client/public/images/templates/glalie.jpg | Bin 104934 -> 0 bytes client/public/images/templates/kakuna.jpg | Bin 84986 -> 0 bytes client/public/images/templates/leafish.jpg | Bin 191884 -> 0 bytes client/public/images/templates/onyx.jpg | Bin 82663 -> 0 bytes client/public/images/templates/pikachu.jpg | Bin 105654 -> 0 bytes client/public/locales/am/builder.json | 377 - client/public/locales/am/common.json | 29 - client/public/locales/am/dashboard.json | 25 - client/public/locales/am/landing.json | 43 - client/public/locales/am/modals.json | 160 - client/public/locales/ar/builder.json | 377 - client/public/locales/ar/common.json | 29 - client/public/locales/ar/dashboard.json | 25 - client/public/locales/ar/landing.json | 43 - client/public/locales/ar/modals.json | 160 - client/public/locales/bg/builder.json | 377 - client/public/locales/bg/common.json | 29 - client/public/locales/bg/dashboard.json | 25 - client/public/locales/bg/landing.json | 43 - client/public/locales/bg/modals.json | 160 - client/public/locales/bn/builder.json | 377 - client/public/locales/bn/common.json | 29 - client/public/locales/bn/dashboard.json | 25 - client/public/locales/bn/landing.json | 43 - client/public/locales/bn/modals.json | 160 - client/public/locales/ca/builder.json | 377 - client/public/locales/ca/common.json | 29 - client/public/locales/ca/dashboard.json | 25 - client/public/locales/ca/landing.json | 43 - client/public/locales/ca/modals.json | 160 - client/public/locales/cs/builder.json | 377 - client/public/locales/cs/common.json | 29 - client/public/locales/cs/dashboard.json | 25 - client/public/locales/cs/landing.json | 43 - client/public/locales/cs/modals.json | 160 - client/public/locales/da/builder.json | 377 - client/public/locales/da/common.json | 29 - client/public/locales/da/dashboard.json | 25 - client/public/locales/da/landing.json | 43 - client/public/locales/da/modals.json | 160 - client/public/locales/de/builder.json | 377 - client/public/locales/de/common.json | 29 - client/public/locales/de/dashboard.json | 25 - client/public/locales/de/landing.json | 43 - client/public/locales/de/modals.json | 160 - client/public/locales/el/builder.json | 377 - client/public/locales/el/common.json | 29 - client/public/locales/el/dashboard.json | 25 - client/public/locales/el/landing.json | 43 - client/public/locales/el/modals.json | 160 - client/public/locales/en/builder.json | 377 - client/public/locales/en/common.json | 29 - client/public/locales/en/dashboard.json | 25 - client/public/locales/en/landing.json | 43 - client/public/locales/en/modals.json | 160 - client/public/locales/es/builder.json | 377 - client/public/locales/es/common.json | 29 - client/public/locales/es/dashboard.json | 25 - client/public/locales/es/landing.json | 43 - client/public/locales/es/modals.json | 160 - client/public/locales/fa/builder.json | 377 - client/public/locales/fa/common.json | 29 - client/public/locales/fa/dashboard.json | 25 - client/public/locales/fa/landing.json | 43 - client/public/locales/fa/modals.json | 160 - client/public/locales/fi/builder.json | 377 - client/public/locales/fi/common.json | 29 - client/public/locales/fi/dashboard.json | 25 - client/public/locales/fi/landing.json | 43 - client/public/locales/fi/modals.json | 160 - client/public/locales/fr/builder.json | 377 - client/public/locales/fr/common.json | 29 - client/public/locales/fr/dashboard.json | 25 - client/public/locales/fr/landing.json | 43 - client/public/locales/fr/modals.json | 160 - client/public/locales/he/builder.json | 377 - client/public/locales/he/common.json | 29 - client/public/locales/he/dashboard.json | 25 - client/public/locales/he/landing.json | 43 - client/public/locales/he/modals.json | 160 - client/public/locales/hi/builder.json | 377 - client/public/locales/hi/common.json | 29 - client/public/locales/hi/dashboard.json | 25 - client/public/locales/hi/landing.json | 43 - client/public/locales/hi/modals.json | 160 - client/public/locales/hu/builder.json | 377 - client/public/locales/hu/common.json | 29 - client/public/locales/hu/dashboard.json | 25 - client/public/locales/hu/landing.json | 43 - client/public/locales/hu/modals.json | 160 - client/public/locales/id/builder.json | 377 - client/public/locales/id/common.json | 29 - client/public/locales/id/dashboard.json | 25 - client/public/locales/id/landing.json | 43 - client/public/locales/id/modals.json | 160 - client/public/locales/it/builder.json | 377 - client/public/locales/it/common.json | 29 - client/public/locales/it/dashboard.json | 25 - client/public/locales/it/landing.json | 43 - client/public/locales/it/modals.json | 160 - client/public/locales/ja/builder.json | 377 - client/public/locales/ja/common.json | 29 - client/public/locales/ja/dashboard.json | 25 - client/public/locales/ja/landing.json | 43 - client/public/locales/ja/modals.json | 160 - client/public/locales/km/builder.json | 377 - client/public/locales/km/common.json | 29 - client/public/locales/km/dashboard.json | 25 - client/public/locales/km/landing.json | 43 - client/public/locales/km/modals.json | 160 - client/public/locales/kn/builder.json | 377 - client/public/locales/kn/common.json | 29 - client/public/locales/kn/dashboard.json | 25 - client/public/locales/kn/landing.json | 43 - client/public/locales/kn/modals.json | 160 - client/public/locales/ko/builder.json | 377 - client/public/locales/ko/common.json | 29 - client/public/locales/ko/dashboard.json | 25 - client/public/locales/ko/landing.json | 43 - client/public/locales/ko/modals.json | 160 - client/public/locales/ml/builder.json | 377 - client/public/locales/ml/common.json | 29 - client/public/locales/ml/dashboard.json | 25 - client/public/locales/ml/landing.json | 43 - client/public/locales/ml/modals.json | 160 - client/public/locales/mr/builder.json | 377 - client/public/locales/mr/common.json | 29 - client/public/locales/mr/dashboard.json | 25 - client/public/locales/mr/landing.json | 43 - client/public/locales/mr/modals.json | 160 - client/public/locales/ne/builder.json | 377 - client/public/locales/ne/common.json | 29 - client/public/locales/ne/dashboard.json | 25 - client/public/locales/ne/landing.json | 43 - client/public/locales/ne/modals.json | 160 - client/public/locales/nl/builder.json | 377 - client/public/locales/nl/common.json | 29 - client/public/locales/nl/dashboard.json | 25 - client/public/locales/nl/landing.json | 43 - client/public/locales/nl/modals.json | 160 - client/public/locales/no/builder.json | 377 - client/public/locales/no/common.json | 29 - client/public/locales/no/dashboard.json | 25 - client/public/locales/no/landing.json | 43 - client/public/locales/no/modals.json | 160 - client/public/locales/or/builder.json | 377 - client/public/locales/or/common.json | 29 - client/public/locales/or/dashboard.json | 25 - client/public/locales/or/landing.json | 43 - client/public/locales/or/modals.json | 160 - client/public/locales/pl/builder.json | 377 - client/public/locales/pl/common.json | 29 - client/public/locales/pl/dashboard.json | 25 - client/public/locales/pl/landing.json | 43 - client/public/locales/pl/modals.json | 160 - client/public/locales/pt-BR/builder.json | 379 - client/public/locales/pt-BR/common.json | 29 - client/public/locales/pt-BR/dashboard.json | 25 - client/public/locales/pt-BR/landing.json | 43 - client/public/locales/pt-BR/modals.json | 160 - client/public/locales/pt/builder.json | 377 - client/public/locales/pt/common.json | 29 - client/public/locales/pt/dashboard.json | 25 - client/public/locales/pt/landing.json | 43 - client/public/locales/pt/modals.json | 160 - client/public/locales/ro/builder.json | 377 - client/public/locales/ro/common.json | 29 - client/public/locales/ro/dashboard.json | 25 - client/public/locales/ro/landing.json | 43 - client/public/locales/ro/modals.json | 160 - client/public/locales/ru/builder.json | 377 - client/public/locales/ru/common.json | 29 - client/public/locales/ru/dashboard.json | 25 - client/public/locales/ru/landing.json | 43 - client/public/locales/ru/modals.json | 160 - client/public/locales/sr/builder.json | 377 - client/public/locales/sr/common.json | 29 - client/public/locales/sr/dashboard.json | 25 - client/public/locales/sr/landing.json | 43 - client/public/locales/sr/modals.json | 160 - client/public/locales/sv/builder.json | 377 - client/public/locales/sv/common.json | 29 - client/public/locales/sv/dashboard.json | 25 - client/public/locales/sv/landing.json | 43 - client/public/locales/sv/modals.json | 160 - client/public/locales/ta/builder.json | 377 - client/public/locales/ta/common.json | 29 - client/public/locales/ta/dashboard.json | 25 - client/public/locales/ta/landing.json | 43 - client/public/locales/ta/modals.json | 160 - client/public/locales/tr/builder.json | 377 - client/public/locales/tr/common.json | 29 - client/public/locales/tr/dashboard.json | 25 - client/public/locales/tr/landing.json | 43 - client/public/locales/tr/modals.json | 160 - client/public/locales/uk/builder.json | 377 - client/public/locales/uk/common.json | 29 - client/public/locales/uk/dashboard.json | 25 - client/public/locales/uk/landing.json | 43 - client/public/locales/uk/modals.json | 160 - client/public/locales/vi/builder.json | 377 - client/public/locales/vi/common.json | 29 - client/public/locales/vi/dashboard.json | 25 - client/public/locales/vi/landing.json | 43 - client/public/locales/vi/modals.json | 160 - client/public/locales/zh/builder.json | 377 - client/public/locales/zh/common.json | 29 - client/public/locales/zh/dashboard.json | 25 - client/public/locales/zh/landing.json | 43 - client/public/locales/zh/modals.json | 160 - client/public/logo/dark.svg | 18 - client/public/logo/light.svg | 18 - client/public/robots.txt | 9 - client/services/auth.ts | 101 - client/services/axios.ts | 52 - client/services/fonts.ts | 5 - client/services/integrations.ts | 19 - client/services/printer.ts | 14 - client/services/react-query.ts | 15 - client/services/resume.ts | 122 - client/store/auth/authSlice.ts | 33 - client/store/build/buildSlice.ts | 71 - client/store/hooks.ts | 6 - client/store/index.ts | 45 - client/store/modal/modalSlice.ts | 59 - client/store/resume/resumeSlice.ts | 139 - client/store/sagas/sync.ts | 38 - client/store/storage.ts | 17 - client/styles/globals.scss | 47 - client/styles/pages/Build.module.scss | 3 - client/styles/pages/Dashboard.module.scss | 11 - client/styles/pages/Home.module.scss | 58 - client/styles/pages/Preview.module.scss | 20 - client/styles/pages/Printer.module.scss | 3 - client/styles/partials/_forms.scss | 25 - client/styles/partials/_reset.scss | 280 - client/styles/partials/_toast.scss | 3 - client/tailwind.config.js | 34 - .../templates/Castform/Castform.module.scss | 29 - client/templates/Castform/Castform.tsx | 46 - client/templates/Castform/widgets/Heading.tsx | 22 - .../templates/Castform/widgets/Masthead.tsx | 107 - client/templates/Castform/widgets/Section.tsx | 130 - client/templates/Gengar/Gengar.module.scss | 29 - client/templates/Gengar/Gengar.tsx | 51 - client/templates/Gengar/widgets/Heading.tsx | 19 - client/templates/Gengar/widgets/Masthead.tsx | 107 - client/templates/Gengar/widgets/Section.tsx | 123 - client/templates/Glalie/Glalie.module.scss | 21 - client/templates/Glalie/Glalie.tsx | 37 - .../templates/Glalie/widgets/BadgeDisplay.tsx | 32 - client/templates/Glalie/widgets/Heading.tsx | 19 - client/templates/Glalie/widgets/Masthead.tsx | 71 - client/templates/Glalie/widgets/Section.tsx | 116 - client/templates/Kakuna/Kakuna.module.scss | 21 - client/templates/Kakuna/Kakuna.tsx | 35 - .../templates/Kakuna/widgets/BadgeDisplay.tsx | 31 - client/templates/Kakuna/widgets/Heading.tsx | 5 - client/templates/Kakuna/widgets/Masthead.tsx | 64 - client/templates/Kakuna/widgets/Section.tsx | 124 - client/templates/Leafish/Leafish.module.scss | 11 - client/templates/Leafish/Leafish.tsx | 28 - client/templates/Leafish/widgets/Heading.tsx | 19 - client/templates/Leafish/widgets/Masthead.tsx | 74 - client/templates/Leafish/widgets/Section.tsx | 122 - client/templates/Onyx/Onyx.module.scss | 21 - client/templates/Onyx/Onyx.tsx | 35 - client/templates/Onyx/widgets/Heading.tsx | 16 - client/templates/Onyx/widgets/Masthead.tsx | 69 - client/templates/Onyx/widgets/Section.tsx | 123 - client/templates/Pikachu/Pikachu.module.scss | 21 - client/templates/Pikachu/Pikachu.tsx | 33 - client/templates/Pikachu/widgets/Heading.tsx | 19 - client/templates/Pikachu/widgets/Masthead.tsx | 86 - client/templates/Pikachu/widgets/Section.tsx | 115 - client/templates/sectionMap.tsx | 67 - client/templates/shared/DataDisplay.tsx | 35 - client/templates/templateMap.tsx | 63 - client/tsconfig.json | 39 - client/types/app.d.ts | 7 - client/types/environment.d.ts | 10 - client/utils/date.ts | 54 - client/utils/getCookie.ts | 8 - client/utils/getGravatarUrl.ts | 9 - client/utils/getProfileIcon.tsx | 63 - client/utils/getResumeUrl.ts | 36 - client/utils/isBrowser.ts | 3 - client/utils/string.ts | 3 - client/utils/styles.ts | 95 - client/utils/template.ts | 63 - client/wrappers/DateWrapper.tsx | 66 - client/wrappers/FontWrapper.tsx | 29 - client/wrappers/ThemeWrapper.tsx | 34 - client/wrappers/index.tsx | 15 - compose-dev.yml | 73 - compose.yml | 65 - crowdin.yml | 3 - package.json | 40 - pnpm-lock.yaml | 11078 -------- pnpm-workspace.yaml | 4 - schema/.eslintrc.json | 4 - schema/.gitignore | 1 - schema/package.json | 15 - schema/src/atoms.ts | 4 - schema/src/basics.ts | 42 - schema/src/fonts.ts | 18 - schema/src/index.ts | 8 - schema/src/integrations.ts | 1 - schema/src/metadata.ts | 37 - schema/src/resume.ts | 19 - schema/src/section.ts | 155 - schema/src/user.ts | 14 - schema/tsconfig.json | 8 - scripts/generate-env.ts | 66 - server/.eslintrc.json | 7 - server/.gitignore | 36 - server/Dockerfile | 52 - server/nest-cli.json | 7 - server/package.json | 75 - server/src/app.module.ts | 49 - server/src/assets/exports/.gitkeep | 0 server/src/assets/index.html | 13 - server/src/assets/uploads/.gitkeep | 0 server/src/auth/auth.controller.ts | 78 - server/src/auth/auth.module.ts | 33 - server/src/auth/auth.service.ts | 144 - server/src/auth/dto/forgot-password.dto.ts | 7 - server/src/auth/dto/login.dto.ts | 10 - server/src/auth/dto/register.dto.ts | 18 - server/src/auth/dto/reset-password.dto.ts | 11 - server/src/auth/dto/update-profile.dto.ts | 7 - server/src/auth/guards/jwt.guard.ts | 5 - server/src/auth/guards/local.guard.ts | 5 - server/src/auth/guards/optional-jwt.guard.ts | 11 - server/src/auth/strategy/jwt.strategy.ts | 25 - server/src/auth/strategy/local.strategy.ts | 18 - server/src/config/app.config.ts | 10 - server/src/config/auth.config.ts | 6 - server/src/config/cache.config.ts | 5 - server/src/config/config.module.ts | 71 - server/src/config/database.config.ts | 10 - server/src/config/google.config.ts | 7 - server/src/config/mail.config.ts | 12 - server/src/config/storage.config.ts | 10 - server/src/constants/index.ts | 2 - server/src/database/database.module.ts | 30 - server/src/database/errorCodes.enum.ts | 3 - server/src/decorators/cookie.decorator.ts | 6 - server/src/decorators/user.decorator.ts | 8 - server/src/environments/environment.prod.ts | 3 - server/src/environments/environment.ts | 3 - server/src/filters/all-exceptions.filter.ts | 23 - server/src/fonts/assets/cachedResponse.json | 22037 ---------------- server/src/fonts/fonts.controller.ts | 18 - server/src/fonts/fonts.module.ts | 17 - server/src/fonts/fonts.service.ts | 32 - server/src/health/health.controller.ts | 22 - server/src/health/health.module.ts | 11 - .../integrations/integrations.controller.ts | 56 - .../src/integrations/integrations.module.ts | 37 - .../src/integrations/integrations.service.ts | 963 - server/src/mail/dto/send-mail.dto.ts | 30 - server/src/mail/mail.module.ts | 18 - server/src/mail/mail.service.ts | 56 - server/src/main.ts | 30 - server/src/printer/printer.controller.ts | 21 - server/src/printer/printer.module.ts | 12 - server/src/printer/printer.service.ts | 156 - server/src/resume/data/covers.ts | 41 - server/src/resume/data/defaultState.ts | 165 - server/src/resume/data/sampleData.ts | 460 - server/src/resume/dto/create-resume.dto.ts | 18 - server/src/resume/dto/update-resume.dto.ts | 5 - server/src/resume/entities/resume.entity.ts | 52 - server/src/resume/resume.controller.ts | 116 - server/src/resume/resume.module.ts | 26 - server/src/resume/resume.service.ts | 310 - .../src/users/dto/create-google-user.dto.ts | 23 - server/src/users/dto/create-user.dto.ts | 30 - server/src/users/dto/update-user.dto.ts | 5 - server/src/users/entities/user.entity.ts | 43 - server/src/users/users.module.ts | 14 - server/src/users/users.service.ts | 127 - server/tsconfig.build.json | 4 - server/tsconfig.json | 35 - tsconfig.base.json | 10 - turbo.json | 21 - 636 files changed, 79529 deletions(-) delete mode 100644 .dockerignore delete mode 100644 .editorconfig delete mode 100644 .env.example delete mode 100644 .eslintrc.json delete mode 100644 .github/FUNDING.yml delete mode 100644 .github/ISSUE_TEMPLATE/bug-report.yaml delete mode 100644 .github/ISSUE_TEMPLATE/feature-request.yaml delete mode 100644 .github/dependabot.yml delete mode 100644 .github/workflows/build-deploy.yml delete mode 100644 .gitignore delete mode 100644 .gitpod.yml delete mode 100644 .npmrc delete mode 100644 .nvmrc delete mode 100644 .prettierignore delete mode 100644 .prettierrc delete mode 100644 .vscode/extensions.json delete mode 100644 .vscode/settings.json delete mode 100644 CODE_OF_CONDUCT.md delete mode 100644 LICENSE delete mode 100644 README.md delete mode 100644 SECURITY.md delete mode 100644 app/.gitignore delete mode 100644 app/app/.editorconfig delete mode 100644 app/app/.gitignore delete mode 100644 app/app/build.gradle delete mode 100644 app/app/proguard-rules.pro delete mode 100644 app/app/src/main/AndroidManifest.xml delete mode 100644 app/app/src/main/ic_launcher-playstore.png delete mode 100644 app/app/src/main/java/me/rxresu/app/CustomWebViewClient.kt delete mode 100644 app/app/src/main/java/me/rxresu/app/MainActivity.kt delete mode 100644 app/app/src/main/res/layout/activity_main.xml delete mode 100644 app/app/src/main/res/layout/content_main.xml delete mode 100644 app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml delete mode 100644 app/app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 app/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png delete mode 100644 app/app/src/main/res/mipmap-hdpi/ic_launcher_round.png delete mode 100644 app/app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 app/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png delete mode 100644 app/app/src/main/res/mipmap-mdpi/ic_launcher_round.png delete mode 100644 app/app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 app/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png delete mode 100644 app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 app/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png delete mode 100644 app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png delete mode 100644 app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png delete mode 100644 app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png delete mode 100644 app/app/src/main/res/values/ic_launcher_background.xml delete mode 100644 app/app/src/main/res/values/strings.xml delete mode 100644 app/app/src/main/res/values/themes.xml delete mode 100644 app/build.gradle delete mode 100644 app/gradle.properties delete mode 100644 app/gradle/wrapper/gradle-wrapper.jar delete mode 100644 app/gradle/wrapper/gradle-wrapper.properties delete mode 100755 app/gradlew delete mode 100644 app/gradlew.bat delete mode 100644 app/settings.gradle delete mode 100644 client/.eslintrc.json delete mode 100644 client/.gitignore delete mode 100644 client/Dockerfile delete mode 100644 client/Dockerfile.standalone delete mode 100644 client/components/build/Center/ArtboardController.module.scss delete mode 100644 client/components/build/Center/ArtboardController.tsx delete mode 100644 client/components/build/Center/Center.module.scss delete mode 100644 client/components/build/Center/Center.tsx delete mode 100644 client/components/build/Center/Header.module.scss delete mode 100644 client/components/build/Center/Header.tsx delete mode 100644 client/components/build/Center/Page.module.scss delete mode 100644 client/components/build/Center/Page.tsx delete mode 100644 client/components/build/LeftSidebar/LeftSidebar.module.scss delete mode 100644 client/components/build/LeftSidebar/LeftSidebar.tsx delete mode 100644 client/components/build/LeftSidebar/sections/Basics.tsx delete mode 100644 client/components/build/LeftSidebar/sections/Location.tsx delete mode 100644 client/components/build/LeftSidebar/sections/PhotoFilters.tsx delete mode 100644 client/components/build/LeftSidebar/sections/PhotoUpload.tsx delete mode 100644 client/components/build/LeftSidebar/sections/Profiles.tsx delete mode 100644 client/components/build/LeftSidebar/sections/Section.tsx delete mode 100644 client/components/build/LeftSidebar/sections/SectionSettings.tsx delete mode 100644 client/components/build/RightSidebar/RightSidebar.module.scss delete mode 100644 client/components/build/RightSidebar/RightSidebar.tsx delete mode 100644 client/components/build/RightSidebar/sections/CustomCSS.tsx delete mode 100644 client/components/build/RightSidebar/sections/Export.tsx delete mode 100644 client/components/build/RightSidebar/sections/Layout.module.scss delete mode 100644 client/components/build/RightSidebar/sections/Layout.tsx delete mode 100644 client/components/build/RightSidebar/sections/Links.module.scss delete mode 100644 client/components/build/RightSidebar/sections/Links.tsx delete mode 100644 client/components/build/RightSidebar/sections/Settings.tsx delete mode 100644 client/components/build/RightSidebar/sections/Sharing.tsx delete mode 100644 client/components/build/RightSidebar/sections/Templates.module.scss delete mode 100644 client/components/build/RightSidebar/sections/Templates.tsx delete mode 100644 client/components/build/RightSidebar/sections/Theme.module.scss delete mode 100644 client/components/build/RightSidebar/sections/Theme.tsx delete mode 100644 client/components/build/RightSidebar/sections/Typography.module.scss delete mode 100644 client/components/build/RightSidebar/sections/Typography.tsx delete mode 100644 client/components/dashboard/ResumeCard.module.scss delete mode 100644 client/components/dashboard/ResumeCard.tsx delete mode 100644 client/components/dashboard/ResumePreview.module.scss delete mode 100644 client/components/dashboard/ResumePreview.tsx delete mode 100644 client/components/home/Actions.tsx delete mode 100644 client/components/home/Background.tsx delete mode 100644 client/components/home/Footer.tsx delete mode 100644 client/components/home/Header.tsx delete mode 100644 client/components/home/Hero.tsx delete mode 100644 client/components/home/Pattern.tsx delete mode 100644 client/components/home/Testimony.module.scss delete mode 100644 client/components/home/Testimony.tsx delete mode 100644 client/components/home/sections/Hero.tsx delete mode 100644 client/components/home/sections/Logo.tsx delete mode 100644 client/components/home/sections/Stats.tsx delete mode 100644 client/components/shared/ArrayInput.module.scss delete mode 100644 client/components/shared/ArrayInput.tsx delete mode 100644 client/components/shared/Avatar.module.scss delete mode 100644 client/components/shared/Avatar.tsx delete mode 100644 client/components/shared/BaseModal.module.scss delete mode 100644 client/components/shared/BaseModal.tsx delete mode 100644 client/components/shared/ColorAvatar.tsx delete mode 100644 client/components/shared/ColorPicker.tsx delete mode 100644 client/components/shared/Copyright.tsx delete mode 100644 client/components/shared/Footer.tsx delete mode 100644 client/components/shared/Heading.module.scss delete mode 100644 client/components/shared/Heading.tsx delete mode 100644 client/components/shared/Icon.tsx delete mode 100644 client/components/shared/LanguageSwitcher.module.scss delete mode 100644 client/components/shared/LanguageSwitcher.tsx delete mode 100644 client/components/shared/List.module.scss delete mode 100644 client/components/shared/List.tsx delete mode 100644 client/components/shared/ListItem.module.scss delete mode 100644 client/components/shared/ListItem.tsx delete mode 100644 client/components/shared/Loading.module.scss delete mode 100644 client/components/shared/Loading.tsx delete mode 100644 client/components/shared/Logo.tsx delete mode 100644 client/components/shared/Markdown.tsx delete mode 100644 client/components/shared/MarkdownSupported.tsx delete mode 100644 client/components/shared/ResumeInput.tsx delete mode 100644 client/components/shared/ThemeSwitch.tsx delete mode 100644 client/components/ui/Separator.tsx delete mode 100644 client/config/colors.ts delete mode 100644 client/config/languages.ts delete mode 100644 client/config/screenshots.ts delete mode 100644 client/config/sections.tsx delete mode 100644 client/config/theme.ts delete mode 100644 client/constants/flags.ts delete mode 100644 client/constants/index.ts delete mode 100644 client/constants/tilt.ts delete mode 100644 client/data/testimonials.ts delete mode 100644 client/modals/auth/ForgotPasswordModal.tsx delete mode 100644 client/modals/auth/LoginModal.tsx delete mode 100644 client/modals/auth/RegisterModal.tsx delete mode 100644 client/modals/auth/ResetPasswordModal.tsx delete mode 100644 client/modals/auth/UserProfileModal.tsx delete mode 100644 client/modals/builder/sections/AwardModal.tsx delete mode 100644 client/modals/builder/sections/CertificateModal.tsx delete mode 100644 client/modals/builder/sections/CustomModal.tsx delete mode 100644 client/modals/builder/sections/EducationModal.tsx delete mode 100644 client/modals/builder/sections/InterestModal.tsx delete mode 100644 client/modals/builder/sections/LanguageModal.tsx delete mode 100644 client/modals/builder/sections/ProfileModal.tsx delete mode 100644 client/modals/builder/sections/ProjectModal.tsx delete mode 100644 client/modals/builder/sections/PublicationModal.tsx delete mode 100644 client/modals/builder/sections/ReferenceModal.tsx delete mode 100644 client/modals/builder/sections/SkillModal.tsx delete mode 100644 client/modals/builder/sections/VolunteerModal.tsx delete mode 100644 client/modals/builder/sections/WorkModal.tsx delete mode 100644 client/modals/dashboard/CreateResumeModal.tsx delete mode 100644 client/modals/dashboard/ImportExternalModal.tsx delete mode 100644 client/modals/dashboard/RenameResumeModal.tsx delete mode 100644 client/modals/index.tsx delete mode 100644 client/next-env.d.ts delete mode 100644 client/next-i18next.config.js delete mode 100644 client/next-sitemap.config.js delete mode 100644 client/next.config.js delete mode 100644 client/package.json delete mode 100644 client/pages/[username]/[slug]/build.tsx delete mode 100644 client/pages/[username]/[slug]/index.tsx delete mode 100644 client/pages/[username]/[slug]/printer.tsx delete mode 100644 client/pages/_app.tsx delete mode 100644 client/pages/_document.tsx delete mode 100644 client/pages/dashboard.tsx delete mode 100644 client/pages/home.tsx delete mode 100644 client/pages/index.tsx delete mode 100644 client/pages/meta/privacy.tsx delete mode 100644 client/pages/meta/service.tsx delete mode 100644 client/pages/r/[shortId].tsx delete mode 100644 client/postcss.config.js delete mode 100644 client/public/favicon.ico delete mode 100644 client/public/icon/dark.svg delete mode 100644 client/public/icon/light.svg delete mode 100644 client/public/images/brand-logos/dark/amazon.svg delete mode 100644 client/public/images/brand-logos/dark/google.svg delete mode 100644 client/public/images/brand-logos/dark/postman.svg delete mode 100644 client/public/images/brand-logos/dark/twilio.svg delete mode 100644 client/public/images/brand-logos/dark/zalando.svg delete mode 100644 client/public/images/brand-logos/light/amazon.svg delete mode 100644 client/public/images/brand-logos/light/google.svg delete mode 100644 client/public/images/brand-logos/light/postman.svg delete mode 100644 client/public/images/brand-logos/light/twilio.svg delete mode 100644 client/public/images/brand-logos/light/zalando.svg delete mode 100644 client/public/images/covers/cover-0ee139.jpeg delete mode 100644 client/public/images/covers/cover-1ab08.jpeg delete mode 100644 client/public/images/covers/cover-1f8c9.jpeg delete mode 100644 client/public/images/covers/cover-1fe54f.jpeg delete mode 100644 client/public/images/covers/cover-253f4a.jpeg delete mode 100644 client/public/images/covers/cover-33aec.jpeg delete mode 100644 client/public/images/covers/cover-3sc.jpeg delete mode 100644 client/public/images/covers/cover-466cb.jpeg delete mode 100644 client/public/images/covers/cover-478b3.jpeg delete mode 100644 client/public/images/covers/cover-4d9.jpeg delete mode 100644 client/public/images/covers/cover-4ed.jpeg delete mode 100644 client/public/images/covers/cover-4fd88.jpeg delete mode 100644 client/public/images/covers/cover-50f3f3.jpeg delete mode 100644 client/public/images/covers/cover-6b8ae.jpeg delete mode 100644 client/public/images/covers/cover-6fa09.jpeg delete mode 100644 client/public/images/covers/cover-713b2f.jpeg delete mode 100644 client/public/images/covers/cover-737f2.jpeg delete mode 100644 client/public/images/covers/cover-73dab8.jpeg delete mode 100644 client/public/images/covers/cover-79df42.jpeg delete mode 100644 client/public/images/covers/cover-7b601.jpeg delete mode 100644 client/public/images/covers/cover-7dh.jpeg delete mode 100644 client/public/images/covers/cover-7e6ae.jpeg delete mode 100644 client/public/images/covers/cover-94b.jpeg delete mode 100644 client/public/images/covers/cover-96bdd.jpeg delete mode 100644 client/public/images/covers/cover-98afd.jpeg delete mode 100644 client/public/images/covers/cover-9hk.jpeg delete mode 100644 client/public/images/covers/cover-b26e75.jpeg delete mode 100644 client/public/images/covers/cover-b6ea6.jpeg delete mode 100644 client/public/images/covers/cover-c219f2.jpeg delete mode 100644 client/public/images/covers/cover-c3642.jpeg delete mode 100644 client/public/images/covers/cover-c584b.jpeg delete mode 100644 client/public/images/covers/cover-c682cb.jpeg delete mode 100644 client/public/images/covers/cover-c82a8.jpeg delete mode 100644 client/public/images/covers/cover-d312a7.jpeg delete mode 100644 client/public/images/covers/cover-dcbd8.jpeg delete mode 100644 client/public/images/covers/cover-df274.jpeg delete mode 100644 client/public/images/covers/cover-e26ee.jpeg delete mode 100644 client/public/images/covers/cover-f3034.jpeg delete mode 100644 client/public/images/covers/cover-fec87.jpeg delete mode 100644 client/public/images/sample-photo.jpg delete mode 100644 client/public/images/screenshots/add-section.png delete mode 100644 client/public/images/screenshots/builder.png delete mode 100644 client/public/images/screenshots/dashboard.png delete mode 100644 client/public/images/screenshots/import-external.png delete mode 100644 client/public/images/screenshots/page-layout.png delete mode 100644 client/public/images/screenshots/preview.png delete mode 100644 client/public/images/sponsors/digitalocean.svg delete mode 100644 client/public/images/sponsors/digitaloceanLight.svg delete mode 100644 client/public/images/templates/castform.jpg delete mode 100644 client/public/images/templates/gengar.jpg delete mode 100644 client/public/images/templates/glalie.jpg delete mode 100644 client/public/images/templates/kakuna.jpg delete mode 100644 client/public/images/templates/leafish.jpg delete mode 100644 client/public/images/templates/onyx.jpg delete mode 100644 client/public/images/templates/pikachu.jpg delete mode 100644 client/public/locales/am/builder.json delete mode 100644 client/public/locales/am/common.json delete mode 100644 client/public/locales/am/dashboard.json delete mode 100644 client/public/locales/am/landing.json delete mode 100644 client/public/locales/am/modals.json delete mode 100644 client/public/locales/ar/builder.json delete mode 100644 client/public/locales/ar/common.json delete mode 100644 client/public/locales/ar/dashboard.json delete mode 100644 client/public/locales/ar/landing.json delete mode 100644 client/public/locales/ar/modals.json delete mode 100644 client/public/locales/bg/builder.json delete mode 100644 client/public/locales/bg/common.json delete mode 100644 client/public/locales/bg/dashboard.json delete mode 100644 client/public/locales/bg/landing.json delete mode 100644 client/public/locales/bg/modals.json delete mode 100644 client/public/locales/bn/builder.json delete mode 100644 client/public/locales/bn/common.json delete mode 100644 client/public/locales/bn/dashboard.json delete mode 100644 client/public/locales/bn/landing.json delete mode 100644 client/public/locales/bn/modals.json delete mode 100644 client/public/locales/ca/builder.json delete mode 100644 client/public/locales/ca/common.json delete mode 100644 client/public/locales/ca/dashboard.json delete mode 100644 client/public/locales/ca/landing.json delete mode 100644 client/public/locales/ca/modals.json delete mode 100644 client/public/locales/cs/builder.json delete mode 100644 client/public/locales/cs/common.json delete mode 100644 client/public/locales/cs/dashboard.json delete mode 100644 client/public/locales/cs/landing.json delete mode 100644 client/public/locales/cs/modals.json delete mode 100644 client/public/locales/da/builder.json delete mode 100644 client/public/locales/da/common.json delete mode 100644 client/public/locales/da/dashboard.json delete mode 100644 client/public/locales/da/landing.json delete mode 100644 client/public/locales/da/modals.json delete mode 100644 client/public/locales/de/builder.json delete mode 100644 client/public/locales/de/common.json delete mode 100644 client/public/locales/de/dashboard.json delete mode 100644 client/public/locales/de/landing.json delete mode 100644 client/public/locales/de/modals.json delete mode 100644 client/public/locales/el/builder.json delete mode 100644 client/public/locales/el/common.json delete mode 100644 client/public/locales/el/dashboard.json delete mode 100644 client/public/locales/el/landing.json delete mode 100644 client/public/locales/el/modals.json delete mode 100644 client/public/locales/en/builder.json delete mode 100644 client/public/locales/en/common.json delete mode 100644 client/public/locales/en/dashboard.json delete mode 100644 client/public/locales/en/landing.json delete mode 100644 client/public/locales/en/modals.json delete mode 100644 client/public/locales/es/builder.json delete mode 100644 client/public/locales/es/common.json delete mode 100644 client/public/locales/es/dashboard.json delete mode 100644 client/public/locales/es/landing.json delete mode 100644 client/public/locales/es/modals.json delete mode 100644 client/public/locales/fa/builder.json delete mode 100644 client/public/locales/fa/common.json delete mode 100644 client/public/locales/fa/dashboard.json delete mode 100644 client/public/locales/fa/landing.json delete mode 100644 client/public/locales/fa/modals.json delete mode 100644 client/public/locales/fi/builder.json delete mode 100644 client/public/locales/fi/common.json delete mode 100644 client/public/locales/fi/dashboard.json delete mode 100644 client/public/locales/fi/landing.json delete mode 100644 client/public/locales/fi/modals.json delete mode 100644 client/public/locales/fr/builder.json delete mode 100644 client/public/locales/fr/common.json delete mode 100644 client/public/locales/fr/dashboard.json delete mode 100644 client/public/locales/fr/landing.json delete mode 100644 client/public/locales/fr/modals.json delete mode 100644 client/public/locales/he/builder.json delete mode 100644 client/public/locales/he/common.json delete mode 100644 client/public/locales/he/dashboard.json delete mode 100644 client/public/locales/he/landing.json delete mode 100644 client/public/locales/he/modals.json delete mode 100644 client/public/locales/hi/builder.json delete mode 100644 client/public/locales/hi/common.json delete mode 100644 client/public/locales/hi/dashboard.json delete mode 100644 client/public/locales/hi/landing.json delete mode 100644 client/public/locales/hi/modals.json delete mode 100644 client/public/locales/hu/builder.json delete mode 100644 client/public/locales/hu/common.json delete mode 100644 client/public/locales/hu/dashboard.json delete mode 100644 client/public/locales/hu/landing.json delete mode 100644 client/public/locales/hu/modals.json delete mode 100644 client/public/locales/id/builder.json delete mode 100644 client/public/locales/id/common.json delete mode 100644 client/public/locales/id/dashboard.json delete mode 100644 client/public/locales/id/landing.json delete mode 100644 client/public/locales/id/modals.json delete mode 100644 client/public/locales/it/builder.json delete mode 100644 client/public/locales/it/common.json delete mode 100644 client/public/locales/it/dashboard.json delete mode 100644 client/public/locales/it/landing.json delete mode 100644 client/public/locales/it/modals.json delete mode 100644 client/public/locales/ja/builder.json delete mode 100644 client/public/locales/ja/common.json delete mode 100644 client/public/locales/ja/dashboard.json delete mode 100644 client/public/locales/ja/landing.json delete mode 100644 client/public/locales/ja/modals.json delete mode 100644 client/public/locales/km/builder.json delete mode 100644 client/public/locales/km/common.json delete mode 100644 client/public/locales/km/dashboard.json delete mode 100644 client/public/locales/km/landing.json delete mode 100644 client/public/locales/km/modals.json delete mode 100644 client/public/locales/kn/builder.json delete mode 100644 client/public/locales/kn/common.json delete mode 100644 client/public/locales/kn/dashboard.json delete mode 100644 client/public/locales/kn/landing.json delete mode 100644 client/public/locales/kn/modals.json delete mode 100644 client/public/locales/ko/builder.json delete mode 100644 client/public/locales/ko/common.json delete mode 100644 client/public/locales/ko/dashboard.json delete mode 100644 client/public/locales/ko/landing.json delete mode 100644 client/public/locales/ko/modals.json delete mode 100644 client/public/locales/ml/builder.json delete mode 100644 client/public/locales/ml/common.json delete mode 100644 client/public/locales/ml/dashboard.json delete mode 100644 client/public/locales/ml/landing.json delete mode 100644 client/public/locales/ml/modals.json delete mode 100644 client/public/locales/mr/builder.json delete mode 100644 client/public/locales/mr/common.json delete mode 100644 client/public/locales/mr/dashboard.json delete mode 100644 client/public/locales/mr/landing.json delete mode 100644 client/public/locales/mr/modals.json delete mode 100644 client/public/locales/ne/builder.json delete mode 100644 client/public/locales/ne/common.json delete mode 100644 client/public/locales/ne/dashboard.json delete mode 100644 client/public/locales/ne/landing.json delete mode 100644 client/public/locales/ne/modals.json delete mode 100644 client/public/locales/nl/builder.json delete mode 100644 client/public/locales/nl/common.json delete mode 100644 client/public/locales/nl/dashboard.json delete mode 100644 client/public/locales/nl/landing.json delete mode 100644 client/public/locales/nl/modals.json delete mode 100644 client/public/locales/no/builder.json delete mode 100644 client/public/locales/no/common.json delete mode 100644 client/public/locales/no/dashboard.json delete mode 100644 client/public/locales/no/landing.json delete mode 100644 client/public/locales/no/modals.json delete mode 100644 client/public/locales/or/builder.json delete mode 100644 client/public/locales/or/common.json delete mode 100644 client/public/locales/or/dashboard.json delete mode 100644 client/public/locales/or/landing.json delete mode 100644 client/public/locales/or/modals.json delete mode 100644 client/public/locales/pl/builder.json delete mode 100644 client/public/locales/pl/common.json delete mode 100644 client/public/locales/pl/dashboard.json delete mode 100644 client/public/locales/pl/landing.json delete mode 100644 client/public/locales/pl/modals.json delete mode 100644 client/public/locales/pt-BR/builder.json delete mode 100644 client/public/locales/pt-BR/common.json delete mode 100644 client/public/locales/pt-BR/dashboard.json delete mode 100644 client/public/locales/pt-BR/landing.json delete mode 100644 client/public/locales/pt-BR/modals.json delete mode 100644 client/public/locales/pt/builder.json delete mode 100644 client/public/locales/pt/common.json delete mode 100644 client/public/locales/pt/dashboard.json delete mode 100644 client/public/locales/pt/landing.json delete mode 100644 client/public/locales/pt/modals.json delete mode 100644 client/public/locales/ro/builder.json delete mode 100644 client/public/locales/ro/common.json delete mode 100644 client/public/locales/ro/dashboard.json delete mode 100644 client/public/locales/ro/landing.json delete mode 100644 client/public/locales/ro/modals.json delete mode 100644 client/public/locales/ru/builder.json delete mode 100644 client/public/locales/ru/common.json delete mode 100644 client/public/locales/ru/dashboard.json delete mode 100644 client/public/locales/ru/landing.json delete mode 100644 client/public/locales/ru/modals.json delete mode 100644 client/public/locales/sr/builder.json delete mode 100644 client/public/locales/sr/common.json delete mode 100644 client/public/locales/sr/dashboard.json delete mode 100644 client/public/locales/sr/landing.json delete mode 100644 client/public/locales/sr/modals.json delete mode 100644 client/public/locales/sv/builder.json delete mode 100644 client/public/locales/sv/common.json delete mode 100644 client/public/locales/sv/dashboard.json delete mode 100644 client/public/locales/sv/landing.json delete mode 100644 client/public/locales/sv/modals.json delete mode 100644 client/public/locales/ta/builder.json delete mode 100644 client/public/locales/ta/common.json delete mode 100644 client/public/locales/ta/dashboard.json delete mode 100644 client/public/locales/ta/landing.json delete mode 100644 client/public/locales/ta/modals.json delete mode 100644 client/public/locales/tr/builder.json delete mode 100644 client/public/locales/tr/common.json delete mode 100644 client/public/locales/tr/dashboard.json delete mode 100644 client/public/locales/tr/landing.json delete mode 100644 client/public/locales/tr/modals.json delete mode 100644 client/public/locales/uk/builder.json delete mode 100644 client/public/locales/uk/common.json delete mode 100644 client/public/locales/uk/dashboard.json delete mode 100644 client/public/locales/uk/landing.json delete mode 100644 client/public/locales/uk/modals.json delete mode 100644 client/public/locales/vi/builder.json delete mode 100644 client/public/locales/vi/common.json delete mode 100644 client/public/locales/vi/dashboard.json delete mode 100644 client/public/locales/vi/landing.json delete mode 100644 client/public/locales/vi/modals.json delete mode 100644 client/public/locales/zh/builder.json delete mode 100644 client/public/locales/zh/common.json delete mode 100644 client/public/locales/zh/dashboard.json delete mode 100644 client/public/locales/zh/landing.json delete mode 100644 client/public/locales/zh/modals.json delete mode 100644 client/public/logo/dark.svg delete mode 100644 client/public/logo/light.svg delete mode 100644 client/public/robots.txt delete mode 100644 client/services/auth.ts delete mode 100644 client/services/axios.ts delete mode 100644 client/services/fonts.ts delete mode 100644 client/services/integrations.ts delete mode 100644 client/services/printer.ts delete mode 100644 client/services/react-query.ts delete mode 100644 client/services/resume.ts delete mode 100644 client/store/auth/authSlice.ts delete mode 100644 client/store/build/buildSlice.ts delete mode 100644 client/store/hooks.ts delete mode 100644 client/store/index.ts delete mode 100644 client/store/modal/modalSlice.ts delete mode 100644 client/store/resume/resumeSlice.ts delete mode 100644 client/store/sagas/sync.ts delete mode 100644 client/store/storage.ts delete mode 100644 client/styles/globals.scss delete mode 100644 client/styles/pages/Build.module.scss delete mode 100644 client/styles/pages/Dashboard.module.scss delete mode 100644 client/styles/pages/Home.module.scss delete mode 100644 client/styles/pages/Preview.module.scss delete mode 100644 client/styles/pages/Printer.module.scss delete mode 100644 client/styles/partials/_forms.scss delete mode 100644 client/styles/partials/_reset.scss delete mode 100644 client/styles/partials/_toast.scss delete mode 100644 client/tailwind.config.js delete mode 100644 client/templates/Castform/Castform.module.scss delete mode 100644 client/templates/Castform/Castform.tsx delete mode 100644 client/templates/Castform/widgets/Heading.tsx delete mode 100644 client/templates/Castform/widgets/Masthead.tsx delete mode 100644 client/templates/Castform/widgets/Section.tsx delete mode 100644 client/templates/Gengar/Gengar.module.scss delete mode 100644 client/templates/Gengar/Gengar.tsx delete mode 100644 client/templates/Gengar/widgets/Heading.tsx delete mode 100644 client/templates/Gengar/widgets/Masthead.tsx delete mode 100644 client/templates/Gengar/widgets/Section.tsx delete mode 100644 client/templates/Glalie/Glalie.module.scss delete mode 100644 client/templates/Glalie/Glalie.tsx delete mode 100644 client/templates/Glalie/widgets/BadgeDisplay.tsx delete mode 100644 client/templates/Glalie/widgets/Heading.tsx delete mode 100644 client/templates/Glalie/widgets/Masthead.tsx delete mode 100644 client/templates/Glalie/widgets/Section.tsx delete mode 100644 client/templates/Kakuna/Kakuna.module.scss delete mode 100644 client/templates/Kakuna/Kakuna.tsx delete mode 100644 client/templates/Kakuna/widgets/BadgeDisplay.tsx delete mode 100644 client/templates/Kakuna/widgets/Heading.tsx delete mode 100644 client/templates/Kakuna/widgets/Masthead.tsx delete mode 100644 client/templates/Kakuna/widgets/Section.tsx delete mode 100644 client/templates/Leafish/Leafish.module.scss delete mode 100644 client/templates/Leafish/Leafish.tsx delete mode 100644 client/templates/Leafish/widgets/Heading.tsx delete mode 100644 client/templates/Leafish/widgets/Masthead.tsx delete mode 100644 client/templates/Leafish/widgets/Section.tsx delete mode 100644 client/templates/Onyx/Onyx.module.scss delete mode 100644 client/templates/Onyx/Onyx.tsx delete mode 100644 client/templates/Onyx/widgets/Heading.tsx delete mode 100644 client/templates/Onyx/widgets/Masthead.tsx delete mode 100644 client/templates/Onyx/widgets/Section.tsx delete mode 100644 client/templates/Pikachu/Pikachu.module.scss delete mode 100644 client/templates/Pikachu/Pikachu.tsx delete mode 100644 client/templates/Pikachu/widgets/Heading.tsx delete mode 100644 client/templates/Pikachu/widgets/Masthead.tsx delete mode 100644 client/templates/Pikachu/widgets/Section.tsx delete mode 100644 client/templates/sectionMap.tsx delete mode 100644 client/templates/shared/DataDisplay.tsx delete mode 100644 client/templates/templateMap.tsx delete mode 100644 client/tsconfig.json delete mode 100644 client/types/app.d.ts delete mode 100644 client/types/environment.d.ts delete mode 100644 client/utils/date.ts delete mode 100644 client/utils/getCookie.ts delete mode 100644 client/utils/getGravatarUrl.ts delete mode 100644 client/utils/getProfileIcon.tsx delete mode 100644 client/utils/getResumeUrl.ts delete mode 100644 client/utils/isBrowser.ts delete mode 100644 client/utils/string.ts delete mode 100644 client/utils/styles.ts delete mode 100644 client/utils/template.ts delete mode 100644 client/wrappers/DateWrapper.tsx delete mode 100644 client/wrappers/FontWrapper.tsx delete mode 100644 client/wrappers/ThemeWrapper.tsx delete mode 100644 client/wrappers/index.tsx delete mode 100644 compose-dev.yml delete mode 100644 compose.yml delete mode 100644 crowdin.yml delete mode 100644 package.json delete mode 100644 pnpm-lock.yaml delete mode 100644 pnpm-workspace.yaml delete mode 100644 schema/.eslintrc.json delete mode 100644 schema/.gitignore delete mode 100644 schema/package.json delete mode 100644 schema/src/atoms.ts delete mode 100644 schema/src/basics.ts delete mode 100644 schema/src/fonts.ts delete mode 100644 schema/src/index.ts delete mode 100644 schema/src/integrations.ts delete mode 100644 schema/src/metadata.ts delete mode 100644 schema/src/resume.ts delete mode 100644 schema/src/section.ts delete mode 100644 schema/src/user.ts delete mode 100644 schema/tsconfig.json delete mode 100644 scripts/generate-env.ts delete mode 100644 server/.eslintrc.json delete mode 100644 server/.gitignore delete mode 100644 server/Dockerfile delete mode 100644 server/nest-cli.json delete mode 100644 server/package.json delete mode 100644 server/src/app.module.ts delete mode 100644 server/src/assets/exports/.gitkeep delete mode 100644 server/src/assets/index.html delete mode 100644 server/src/assets/uploads/.gitkeep delete mode 100644 server/src/auth/auth.controller.ts delete mode 100644 server/src/auth/auth.module.ts delete mode 100644 server/src/auth/auth.service.ts delete mode 100644 server/src/auth/dto/forgot-password.dto.ts delete mode 100644 server/src/auth/dto/login.dto.ts delete mode 100644 server/src/auth/dto/register.dto.ts delete mode 100644 server/src/auth/dto/reset-password.dto.ts delete mode 100644 server/src/auth/dto/update-profile.dto.ts delete mode 100644 server/src/auth/guards/jwt.guard.ts delete mode 100644 server/src/auth/guards/local.guard.ts delete mode 100644 server/src/auth/guards/optional-jwt.guard.ts delete mode 100644 server/src/auth/strategy/jwt.strategy.ts delete mode 100644 server/src/auth/strategy/local.strategy.ts delete mode 100644 server/src/config/app.config.ts delete mode 100644 server/src/config/auth.config.ts delete mode 100644 server/src/config/cache.config.ts delete mode 100644 server/src/config/config.module.ts delete mode 100644 server/src/config/database.config.ts delete mode 100644 server/src/config/google.config.ts delete mode 100644 server/src/config/mail.config.ts delete mode 100644 server/src/config/storage.config.ts delete mode 100644 server/src/constants/index.ts delete mode 100644 server/src/database/database.module.ts delete mode 100644 server/src/database/errorCodes.enum.ts delete mode 100644 server/src/decorators/cookie.decorator.ts delete mode 100644 server/src/decorators/user.decorator.ts delete mode 100644 server/src/environments/environment.prod.ts delete mode 100644 server/src/environments/environment.ts delete mode 100644 server/src/filters/all-exceptions.filter.ts delete mode 100644 server/src/fonts/assets/cachedResponse.json delete mode 100644 server/src/fonts/fonts.controller.ts delete mode 100644 server/src/fonts/fonts.module.ts delete mode 100644 server/src/fonts/fonts.service.ts delete mode 100644 server/src/health/health.controller.ts delete mode 100644 server/src/health/health.module.ts delete mode 100644 server/src/integrations/integrations.controller.ts delete mode 100644 server/src/integrations/integrations.module.ts delete mode 100644 server/src/integrations/integrations.service.ts delete mode 100644 server/src/mail/dto/send-mail.dto.ts delete mode 100644 server/src/mail/mail.module.ts delete mode 100644 server/src/mail/mail.service.ts delete mode 100644 server/src/main.ts delete mode 100644 server/src/printer/printer.controller.ts delete mode 100644 server/src/printer/printer.module.ts delete mode 100644 server/src/printer/printer.service.ts delete mode 100644 server/src/resume/data/covers.ts delete mode 100644 server/src/resume/data/defaultState.ts delete mode 100644 server/src/resume/data/sampleData.ts delete mode 100644 server/src/resume/dto/create-resume.dto.ts delete mode 100644 server/src/resume/dto/update-resume.dto.ts delete mode 100644 server/src/resume/entities/resume.entity.ts delete mode 100644 server/src/resume/resume.controller.ts delete mode 100644 server/src/resume/resume.module.ts delete mode 100644 server/src/resume/resume.service.ts delete mode 100644 server/src/users/dto/create-google-user.dto.ts delete mode 100644 server/src/users/dto/create-user.dto.ts delete mode 100644 server/src/users/dto/update-user.dto.ts delete mode 100644 server/src/users/entities/user.entity.ts delete mode 100644 server/src/users/users.module.ts delete mode 100644 server/src/users/users.service.ts delete mode 100644 server/tsconfig.build.json delete mode 100644 server/tsconfig.json delete mode 100644 tsconfig.base.json delete mode 100644 turbo.json diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 39261c41..00000000 --- a/.dockerignore +++ /dev/null @@ -1,27 +0,0 @@ -# Android App -/app - -# Build Artifacts -/schema/dist -/server/dist -/client/.next - -# IDEs -.vscode - -# Project Metadata -.crowdin.yml - -# Documentation -README.md -SECURITY.md -CHANGELOG.md -CODE_OF_CONDUCT.md - -# Project Dependencies -**/node_modules - -# Docker -Dockerfile -.dockerignore -docker-compose.yml diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 7278fb63..00000000 --- a/.editorconfig +++ /dev/null @@ -1,8 +0,0 @@ -root = true - -[*] -charset = utf-8 -indent_size = 2 -indent_style = space -insert_final_newline = true -trim_trailing_whitespace = true diff --git a/.env.example b/.env.example deleted file mode 100644 index 1692f9f6..00000000 --- a/.env.example +++ /dev/null @@ -1,36 +0,0 @@ -# Server + Client -TZ=UTC -PUBLIC_URL=http://localhost:3000 -PUBLIC_SERVER_URL=http://localhost:3100 -PUBLIC_GOOGLE_CLIENT_ID= - -# Server + Database -POSTGRES_DB=postgres -POSTGRES_USER=postgres -POSTGRES_PASSWORD=postgres - -# Server -SECRET_KEY= -POSTGRES_HOST=localhost -POSTGRES_PORT=5432 -POSTGRES_SSL_CERT= -JWT_SECRET= -JWT_EXPIRY_TIME=604800 -GOOGLE_CLIENT_SECRET= -GOOGLE_API_KEY= -MAIL_FROM_NAME= -MAIL_FROM_EMAIL= -MAIL_HOST= -MAIL_PORT= -MAIL_USERNAME= -MAIL_PASSWORD= -STORAGE_BUCKET= -STORAGE_REGION= -STORAGE_ENDPOINT= -STORAGE_URL_PREFIX= -STORAGE_ACCESS_KEY= -STORAGE_SECRET_KEY= -PDF_DELETION_TIME=345600000 - -# Client -PUBLIC_FLAG_DISABLE_SIGNUPS=false diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 1bab8c6f..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "parser": "@typescript-eslint/parser", - "extends": ["plugin:@typescript-eslint/recommended"], - "plugins": ["@typescript-eslint/eslint-plugin", "unused-imports", "simple-import-sort"], - "rules": { - // ESLint - "no-unused-vars": "off", - - // Unused Imports - "unused-imports/no-unused-imports": "error", - "unused-imports/no-unused-vars": [ - "warn", - { - "vars": "all", - "args": "none", - "varsIgnorePattern": "^_", - "argsIgnorePattern": "^_" - } - ], - - // Simple Import Sort - "simple-import-sort/imports": "error", - "simple-import-sort/exports": "error", - - // TypeScript ESLint - "@typescript-eslint/no-unused-vars": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/interface-name-prefix": "off", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/explicit-module-boundary-types": "off" - }, - "overrides": [ - { - "files": ["*.js"], - "rules": { - "@typescript-eslint/no-var-requires": "off" - } - } - ] -} diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 1f81cc42..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,2 +0,0 @@ -github: AmruthPillai -custom: https://paypal.me/amruthde diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml deleted file mode 100644 index 11cdfb09..00000000 --- a/.github/ISSUE_TEMPLATE/bug-report.yaml +++ /dev/null @@ -1,75 +0,0 @@ -name: 🐞 Bug Report -description: Create a bug report to help improve Reactive Resume. - -title: '[Bug] ' -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 diff --git a/.github/ISSUE_TEMPLATE/feature-request.yaml b/.github/ISSUE_TEMPLATE/feature-request.yaml deleted file mode 100644 index d91dd226..00000000 --- a/.github/ISSUE_TEMPLATE/feature-request.yaml +++ /dev/null @@ -1,22 +0,0 @@ -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 diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 8448be6b..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,22 +0,0 @@ -version: 2 - -updates: - - package-ecosystem: 'docker' - directory: '/server' - schedule: - interval: 'weekly' - - - package-ecosystem: 'docker' - directory: '/client' - schedule: - interval: 'weekly' - - - package-ecosystem: 'gradle' - directory: '/app' - schedule: - interval: 'weekly' - - - package-ecosystem: 'github-actions' - directory: '/' - schedule: - interval: 'weekly' diff --git a/.github/workflows/build-deploy.yml b/.github/workflows/build-deploy.yml deleted file mode 100644 index a2f857c1..00000000 --- a/.github/workflows/build-deploy.yml +++ /dev/null @@ -1,142 +0,0 @@ -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.6.0 - - - 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.6.0 - - - 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.4.0 - with: - token: ${{ secrets.DIGITALOCEAN_TOKEN }} - - - name: Create Deployment with Latest Version - run: doctl apps create-deployment ${{ secrets.DIGITALOCEAN_APP_ID }} --wait --force-rebuild diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 4e5621cf..00000000 --- a/.gitignore +++ /dev/null @@ -1,18 +0,0 @@ -# Environment Variables -.env -.env.* -*.env -!.env.gitpod -!.env.example - -# Project Dependencies -node_modules - -# macOS -.DS_Store - -# Intellij -.idea - -# Turborepo -.turbo \ No newline at end of file diff --git a/.gitpod.yml b/.gitpod.yml deleted file mode 100644 index 7806449a..00000000 --- a/.gitpod.yml +++ /dev/null @@ -1,41 +0,0 @@ -tasks: - - name: Run PostgreSQL Database - command: docker run --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=postgres -d postgres - - - name: Install Project Dependencies - command: | - pnpm install - pnpm dlx playwright install --with-deps chromium - gp sync-done deps - - - name: Generate Environment Variables - init: gp sync-await deps - command: | - if [ -f .env ]; then - echo "Found .env in workspace, skipping generation" - else - pnpm generate-env - fi - gp sync-done env - - - name: Build and Run Project - init: gp sync-await env - command: | - pnpm build - pnpm start - -ports: - # PostgreSQL - - port: 5432 - onOpen: ignore - visibility: private - - # Server - - port: 3100 - onOpen: ignore - visibility: public - - # Client - - port: 3000 - onOpen: open-browser - visibility: public diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 8051a481..00000000 --- a/.npmrc +++ /dev/null @@ -1,2 +0,0 @@ -auto-install-peers=true -strict-peer-dependencies=false \ No newline at end of file diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index 1a2f5bd2..00000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -lts/* \ No newline at end of file diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 1b149073..00000000 --- a/.prettierignore +++ /dev/null @@ -1,29 +0,0 @@ -# Android App -/app - -# Schema -schema/dist - -# Server -server/dist - -# Client -client/.next -client/public/__ENV.js - -# IDEs -.vscode - -# Project Metadata -LICENSE -README.md -CHANGELOG.md - -# Project Dependencies -node_modules -pnpm-lock.yaml - -# Docker -Dockerfile -.dockerignore -docker-compose.yml \ No newline at end of file diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 3f584f60..00000000 --- a/.prettierrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "printWidth": 120, - "singleQuote": true -} diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index d8fa4eca..00000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "recommendations": [ - "dbaeumer.vscode-eslint", - "esbenp.prettier-vscode", - "lokalise.i18n-ally" - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index fba60638..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "css.validate": false, - "scss.validate": false, - "editor.wordWrap": "on", - "npm.packageManager": "pnpm", - "editor.formatOnSave": true, - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.codeActionsOnSave": { - "source.fixAll.eslint": true - }, - "eslint.workingDirectories": [ - "schema", - "client", - "server" - ], - "conventionalCommits.scopes": [ - "client", - "server", - "docker", - "dependencies" - ] -} \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index cbefc2ac..00000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,128 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -- Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -- The use of sexualized language or imagery, and sexual attention or - advances of any kind -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or email - address, without their explicit permission -- Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -im.amruth@gmail.com. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. - -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 26f5c650..00000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 Amruth Pillai - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index 73cbe4c6..00000000 --- a/README.md +++ /dev/null @@ -1,179 +0,0 @@ -<img src="/client/public/logo/dark.svg" alt="Reactive Resume" width="256px" height="256px" /> - -# Reactive Resume - -[![Project Version](https://img.shields.io/github/package-json/v/AmruthPillai/Reactive-Resume?style=flat-square)](https://github.com/AmruthPillai/Reactive-Resume/releases) -[![Project License](https://img.shields.io/github/license/AmruthPillai/Reactive-Resume?style=flat-square)](https://github.com/AmruthPillai/Reactive-Resume/blob/main/LICENSE) -[![Crowdin](https://badges.crowdin.net/reactive-resume/localized.svg)](https://translate.rxresu.me) -[![Docker Pulls](https://img.shields.io/docker/pulls/amruthpillai/reactive-resume?style=flat-square)](https://hub.docker.com/r/amruthpillai/reactive-resume) -![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/AmruthPillai/Reactive-Resume/docker-build-push.yml?branch=main&label=docker%20build&style=flat-square) -[![Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod)](https://gitpod.io/#https://github.com/AmruthPillai/Reactive-Resume) -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FAmruthPillai%2FReactive-Resume.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FAmruthPillai%2FReactive-Resume?ref=badge_shield) - -## [Go to App](https://rxresu.me) | [Docs](https://docs.rxresu.me) - -Reactive Resume is a free and open source resume builder that’s built to make the mundane tasks of creating, updating and sharing your resume as easy as 1, 2, 3. With this app, you can create multiple resumes, share them with recruiters through a unique link and print as PDF, all for free, no advertisements, without losing the integrity and privacy of your data. - -You have complete control over what goes into your resume, how it looks, what colors, what templates, even the layout in which sections placed. Want a dark mode resume? It’s as easy as editing 3 values and you’re done. You don’t need to wait to see your changes either. Everything you type, everything you change, appears immediately on your resume and gets updated in real time. - -## ❗️ Important Notice - -Due to increasing and recurring costs from Google Cloud, I would have to take down the Version 1 and Version 2 editions of the application that live on https://v1.rxresu.me/ and https://v2.rxresu.me/ respectively. - -I plan to take down the servers on 1st September 2023, so if you have any data on the earlier versions, please migrate it over to the latest version on https://rxresu.me/ as soon as you can. - -The current version will remain unchanged, and is still actively supported. It will have no effect on these changes. - -Thank you to the 400,000+ people using the app! 🙏 - -## Table of Contents - -- [Reactive Resume](#reactive-resume) - - [Go to App | Docs](#go-to-app--docs) - - [Table of Contents](#table-of-contents) - - [Features](#features) - - [Languages](#languages) - - [Tutorial](#tutorial) - - [Build from Source](#build-from-source) - - [Contributing](#contributing) - - [Report Bugs and Feature Requests](#report-bugs-and-feature-requests) - - [Donations](#donations) - - [GitHub Sponsor](#github-sponsor) - - [PayPal](#paypal) - - [GitHub Star History](#github-star-history) - - [Infrastructure](#infrastructure) - - [Contributors Wall](#contributors-wall) - - [License](#license) - -## Features - -- Free, forever -- No Advertising -- No User Tracking -- Sync your data across devices -- Accessible in multiple languages -- Import data from [LinkedIn](https://www.linkedin.com/), [JSON Resume](https://jsonresume.org/) -- Manage multiple resumes with one account -- Open Source (with large community support) -- Send your resume to others with a unique sharable link -- Pick any font from [Google Fonts](https://fonts.google.com/) to use on your resume -- Choose from 6 vibrant templates and more coming soon -- Export your resume to JSON or PDF format with just one click -- Create an account using your email, or just Sign in with Google -- Mix and match colors to any degree, even a dark mode resume? -- Add sections, add pages and change layouts the way you want to -- Tailor-made Backend and Database, isolated from Google, Amazon etc. -- **Oh, and did I mention that it's free?** - -## Languages - -- Amharic (አማርኛ) -- Arabic (اَلْعَرَبِيَّةُ) -- Bengali (বাংলা) -- Bulgarian (български) -- Catalan (Valencian) -- Chinese (中文) -- Czech (čeština) -- Danish (Dansk) -- Dutch (Nederlands) -- English -- Finnish (Suomi) -- French (Français) -- German (Deutsch) -- Greek (Ελληνικά) -- Hebrew (Ivrit) -- Hindi (हिन्दी) -- Hungarian (Magyar) -- Indonesian (Bahasa Indonesia) -- Italian (Italiano) -- Japanese (日本語) -- Kannada (ಕನ್ನಡ) -- Khmer (ភាសាខ្មែរ) -- Korean (한국어) -- Malayalam (മലയാളം) -- Marathi (मराठी) -- Nepali (नेपाली) -- Norwegian (Norsk) -- Odia (ଓଡ଼ିଆ) -- Persian (فارسی) -- Polish (Polski) -- Portuguese (Português) -- Romanian (limba română) -- Russian (русский) -- Serbian (српски језик) -- Spanish (Español) -- Swedish (Svenska) -- Tamil (தமிழ்) -- Turkish (Türkçe) -- Ukrainian (Українська мова) -- Vietnamese (Tiếng Việt) - -Help by [translating Reactive Resume](https://translate.rxresu.me) to your language! - -## Tutorial - -The docs include an extensive [Tutorial](https://docs.rxresu.me/tutorial) section which outline the features of Reactive Resume and help you through building your first resume on the app. - -## Build from Source - -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/AmruthPillai/Reactive-Resume) - -Initially building the image and project on Gitpod will take at least ~10 minutes, so please be patient on first launch. - -For extensive information on how to build the app on your local machine, head over to the docs [Source Code](https://docs.rxresu.me/source-code) section. - -## Contributing - -This project makes use of [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) style and workflow for commit messages to ensure that the CHANGELOG is auto-generated. In general, this project follows the "fork-and-pull" Git workflow. - -1. **Fork** the repo on GitHub -2. **Clone** the project to your own machine -3. **Commit** changes to your own branch -4. **Push** your work back up to your fork -5. Submit a **Pull Request** so that we can review your changes - -NOTE: Be sure to merge the latest from `main` before making a pull request! - -## Report Bugs and Feature Requests - -Use the [GitHub Issues](https://github.com/AmruthPillai/Reactive-Resume/issues/new/choose) platform to notify me about bugs or new features that you would like to see in Reactive Resume. Please check before creating new issues as there might already be one. - -## Donations - -Reactive Resume would be nothing without the folks who supported me and kept the project alive in the beginning, and your continued support is what keeps me going. If you found Reactive Resume to be useful, helpful or just insightful and appreciate the effort I took to make the project, please consider donating as little or as much as you can. - -### [GitHub Sponsor](https://github.com/sponsors/AmruthPillai) -### [PayPal](https://paypal.me/RajaRajanA) - -## GitHub Star History - -[![Star History Chart](https://api.star-history.com/svg?repos=AmruthPillai/Reactive-Resume&type=Date)](https://star-history.com/#AmruthPillai/Reactive-Resume&Date) - -## Infrastructure - -- [Next.js](https://nextjs.org/), frontend -- [NestJS](https://nestjs.com/), backend -- [PostgreSQL](https://www.postgresql.org/), database -- [DigitalOcean](https://www.digitalocean.com/), infrastructure provider -- [Crowdin](https://translate.rxresu.me/), translation management platform - -  - -<a href="https://pillai.xyz/digitalocean"> - <img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_blue.svg" width="200px" /> -</a> - -## Contributors Wall -<a href="https://github.com/AmruthPillai/Reactive-Resume/graphs/contributors"> - <img src="https://contrib.rocks/image?repo=AmruthPillai/Reactive-Resume" /> -</a> - - -_Note: It may take up to 24h for the [contrib.rocks](https://contrib.rocks/image?repo=AmruthPillai/Reactive-Resume) plugin to update because it's refreshed once a day._ - -## License - -Reactive Resume is packaged and distributed using the [MIT License](https://choosealicense.com/licenses/mit/) which allows for commercial use, distribution, modification and private use provided that all copies of the software contain the same license and copyright. - -_By the community, for the community._ -A passion project by [Amruth Pillai](https://amruthpillai.com/) diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 79a62a5c..00000000 --- a/SECURITY.md +++ /dev/null @@ -1,13 +0,0 @@ -# Security Policy - -## Supported Versions - -| Version | Supported | -| ------- | ------------------ | -| 3.x.x | :white_check_mark: | -| 2.x.x | :x: | -| 1.x.x | :x: | - -## Reporting a Vulnerability - -Create an issue on GitHub or send me an email through the contact form on my website at https://amruthpillai.com/ diff --git a/app/.gitignore b/app/.gitignore deleted file mode 100644 index 16dcd122..00000000 --- a/app/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -# Gradle files -.gradle/ -build/ - -# Local configuration file (sdk path, etc) -local.properties - -# Log/OS Files -*.log - -# Android Studio generated files and folders -captures/ -.externalNativeBuild/ -.cxx/ -*.apk -output.json - -# IntelliJ -*.iml -.idea/ -misc.xml -deploymentTargetDropDown.xml -render.experimental.xml - -# Keystore files -*.jks -*.keystore - -# Google Services (e.g. APIs or Firebase) -google-services.json - -# Android Profiling -*.hprof \ No newline at end of file diff --git a/app/app/.editorconfig b/app/app/.editorconfig deleted file mode 100644 index 86a14cae..00000000 --- a/app/app/.editorconfig +++ /dev/null @@ -1,8 +0,0 @@ -root = true - -[*] -charset = utf-8 -indent_size = 4 -indent_style = space -insert_final_newline = true -trim_trailing_whitespace = true diff --git a/app/app/.gitignore b/app/app/.gitignore deleted file mode 100644 index 956c004d..00000000 --- a/app/app/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/build -/release \ No newline at end of file diff --git a/app/app/build.gradle b/app/app/build.gradle deleted file mode 100644 index 786e1303..00000000 --- a/app/app/build.gradle +++ /dev/null @@ -1,47 +0,0 @@ -plugins { - id 'com.android.application' - id 'org.jetbrains.kotlin.android' -} - -android { - compileSdk 32 - - defaultConfig { - applicationId "me.rxresu.app" - minSdk 21 - targetSdk 32 - versionCode 3 - versionName "1.0" - resConfigs 'en' - - } - - buildTypes { - release { - minifyEnabled true - shrinkResources true - zipAlignEnabled true - - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = '1.8' - } - - buildFeatures { - viewBinding true - } - namespace 'me.rxresu.app' -} - -dependencies { - implementation 'androidx.core:core-ktx:1.7.0' - implementation 'com.google.android.material:material:1.5.0' -} diff --git a/app/app/proguard-rules.pro b/app/app/proguard-rules.pro deleted file mode 100644 index 481bb434..00000000 --- a/app/app/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/app/src/main/AndroidManifest.xml b/app/app/src/main/AndroidManifest.xml deleted file mode 100644 index bc8fe1cd..00000000 --- a/app/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest - xmlns:android="http://schemas.android.com/apk/res/android"> - - <uses-permission android:name="android.permission.INTERNET"/> - - <application - android:allowBackup="true" - android:icon="@mipmap/ic_launcher" - android:label="@string/app_name" - android:roundIcon="@mipmap/ic_launcher_round" - android:supportsRtl="true" - android:theme="@style/AppTheme"> - <activity - android:configChanges="orientation|screenSize" - android:name=".MainActivity" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - </activity> - </application> - -</manifest> diff --git a/app/app/src/main/ic_launcher-playstore.png b/app/app/src/main/ic_launcher-playstore.png deleted file mode 100644 index ef2861c34ba84153385d5f640f436bc0c72f44a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12399 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 JKI?MtzX2(_kI(=B diff --git a/app/app/src/main/java/me/rxresu/app/CustomWebViewClient.kt b/app/app/src/main/java/me/rxresu/app/CustomWebViewClient.kt deleted file mode 100644 index a97da13e..00000000 --- a/app/app/src/main/java/me/rxresu/app/CustomWebViewClient.kt +++ /dev/null @@ -1,21 +0,0 @@ -package me.rxresu.app - -import android.content.Intent -import android.net.Uri -import android.webkit.WebView -import android.webkit.WebViewClient - -internal class CustomWebViewClient : WebViewClient() { - override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { - val hostname = "rxresu.me" - val uri = Uri.parse(url) - - if (uri.host != null && uri.host!!.endsWith(hostname)) { - return false - } - - view.context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) - - return true - } -} diff --git a/app/app/src/main/java/me/rxresu/app/MainActivity.kt b/app/app/src/main/java/me/rxresu/app/MainActivity.kt deleted file mode 100644 index 6897ec52..00000000 --- a/app/app/src/main/java/me/rxresu/app/MainActivity.kt +++ /dev/null @@ -1,34 +0,0 @@ -package me.rxresu.app - -import android.annotation.SuppressLint -import android.os.Bundle -import android.webkit.WebView -import androidx.appcompat.app.AppCompatActivity - -class MainActivity : AppCompatActivity() { - private lateinit var webView: WebView - - private var url = "https://rxresu.me" - - @SuppressLint("SetJavaScriptEnabled") - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - - webView = findViewById(R.id.webview) - - webView.webViewClient = CustomWebViewClient() - webView.settings.javaScriptEnabled = true - webView.settings.userAgentString = "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Mobile Safari/537.36" - - webView.loadUrl(url) - } - - override fun onBackPressed() { - if (webView.canGoBack()) { - webView.goBack() - } else { - super.onBackPressed() - } - } -} diff --git a/app/app/src/main/res/layout/activity_main.xml b/app/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index b1ce2b18..00000000 --- a/app/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,15 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<androidx.coordinatorlayout.widget.CoordinatorLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - tools:context=".MainActivity"> - - <include - layout="@layout/content_main" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:visibility="visible" /> - -</androidx.coordinatorlayout.widget.CoordinatorLayout> diff --git a/app/app/src/main/res/layout/content_main.xml b/app/app/src/main/res/layout/content_main.xml deleted file mode 100644 index 238cd022..00000000 --- a/app/app/src/main/res/layout/content_main.xml +++ /dev/null @@ -1,17 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<androidx.constraintlayout.widget.ConstraintLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <WebView - android:id="@+id/webview" - android:layout_width="0dp" - android:layout_height="0dp" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - -</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index 036d09bc..00000000 --- a/app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> - <background android:drawable="@color/ic_launcher_background"/> - <foreground android:drawable="@mipmap/ic_launcher_foreground"/> -</adaptive-icon> \ No newline at end of file diff --git a/app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index 036d09bc..00000000 --- a/app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> - <background android:drawable="@color/ic_launcher_background"/> - <foreground android:drawable="@mipmap/ic_launcher_foreground"/> -</adaptive-icon> \ No newline at end of file diff --git a/app/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 91830192f3bb6f86a2143dbddf62f066001a9d1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1663 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 JPDHLkV1ha@7u5g& diff --git a/app/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index 323126341cc00cc10dd6ecb347c75e6a98b52da8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2193 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 diff --git a/app/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 966baa4266dfa6960de28ac4febea0da0b4f6c34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3487 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 N002ovPDHLkV1iVehhzW% diff --git a/app/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index fe4857c9fec20adc4958105386a90b75f20bd35d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1110 zcmV-c1gZOpP)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm000CXNkl<ZcwX&U zOG_kI6rPNeVI~8!nb{0v0ux9GnaK|jmox4RZWIw|pNN8Pd?1R5qTqWcju9UV6%j!( zuGEF%8&N?8MFbV!&u-hdt*u|qxv3tit2AwAyj>mA2R?3f)jj8aU)8y%?(KT?$Y)+` z0Q?9~42GYUmrEn3#|-Uxqs%Qn>W*~$itq#BtwG?iU){#-R&#Wq_B+Doo12^M>+5T? zkx)2%W=?93x0>Uk-ERMb96^p=1FO|qL)5;xQvj67a&&a`ngr~!6SX#Kuc<Wz%Gfr( znZ#>vU|`^lnyZu1$tT>-&CUIEb#-;12;dmHyWvSN2StFjqod<jHCHEdoE#h+{Fww; z9M0N+o`zaLb93{1HCHFo2=K}$(9qEELCw|4d;%oFCqNs}(~u&7u486q2IJ%7n4Fx% z?(VLx&qG0qfU~nRL`O#>EG!JMv9SmV3BmC2u&&=jK$-xP$%M+vN?0rwL`6lRxVTu? z?;#*nKwMlLa&vPL92|`H_IA*#9{v6O*xK4cXJ;q2x3_h^?n0`7l#~=?WMm*RG7=jb z8|dxrMR<5Pl9G~;ot=%-(^H+VyO1K_{QO)HY-?-7`uaN3($dh~-Hnrz6Co%lC<tR? zV>-Wo11SO+T#4xVmtzK}Peeq7NUf}_Ebn~+3{E2^$i(@#&1OSaSC>emm6a7VH8qJ= z>*p?{2)MYoKz@Ec^78V~($XS?Y;JCf)S`hzT5(I85SYzo93CEGXJ<!XZ*NchX2I;k zH$od<x&R(DCME_$LqkYTPDW8t5mHlAQBY7I=0IU#AzE8oQCC-ol9CcMHZ~$5Awi^8 zdV0E;KQ%Qqq6p;V<mlpiOh9RADXOZf1mVogOp!LVwY5l0Ohk2cwGb5_A1?${R8*k6 zyc|6}J>togm6eIfUteFZi!WUO=l%5bv=A~oJ1f$Jmla(9=jZ1!H8q9f<72VR;N!Km zH7qYLV{vg&EISq!7I1KIfQg9-QGT`Yr3>&Rd;+uq8C-?9!h8+53Tfls1}OqKzqwR? z4P2E^sRH=!u?Tm-%M82n=5_~C1gx&EiZ`x17#SH6FFI{3SCAq=x#l*sG42B?0(cTG zEiJjkLu@sr2)Mkw6qC>u?pta82~q@j68~EP|J^@#soeeItxtefyT7TqIvJFY2DH!p zo+jx&JK4SD0|#;Azi-U6@A#29_VxAY1+d}o&o|F#x!cN>hK+?7Wsb4An$gm`owz;6 zU7wiPl#6}=0Riu6eg7wI|17j_KP2^5ZaoRdnEU(tmeJACPJe%YP9OemeYXHMrNeW= zyTHJ}kF*W^-DENu0v`Jl8FOJyF3CHLJBEgaKBdDKgqH^61#`JKdA0Fmq1^k{NZdJL cedZ<h7nE*~&BvpCQUCw|07*qoM6N<$f`W<(!vFvP diff --git a/app/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png deleted file mode 100644 index 7b17b97f4acf6bc2ef2dac1a806276518d3d1e27..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1466 zcmah}eK-?%9N&c{%k9csG_~tGS!YNsd0*L-w@@RlJ4^{BYi?ngSth+mbj9*?Gm0J+ zMqY+y$xGyI6g7o;nMGzadAI4db$9*M-5<a2?|DAY@A>{d&*%I7e6D!9AJEp?s09E3 zw4I$WhZXl}<<SHxYHF}gI{=^&?~K{!Me>*Q^}Fi&L4Y4tEYSzM5pHM`&-e9dombV( z8j3X6b01A6BepcSbYh$yG^9=Mzt#;m$9&8$t3$LGZR()uv%%aOA}J&KQCx9g-)I|w zS=zUYJm-5LMelgBdE-~gB8teHwW2Ip=51tnfp`yqt6$NG4{P_84>C>>JWUj^!GFTD zb}=*0`>W3peK?e5HhFRSUavs#uA&K|Y&Dyno?ca1SvkO_(N@jRihTrz!36i+miIf% ze9`EHGS^$Ci4=ZOz^LCb;HXzZ!V-<P$u0(ItAP&>uj<P4aHYn?SWDK~C|PvRb7<js z{NuJZxA&;XamYXLXx93$u&{fXHqoXN`T6-oV`JmVSuy`*aBy%PWq?aIqMQ*_OH)WB zRg(&SZcc7)17&%xT`pVf%0yB9mkwjG{dILTvUE5x*=5F#^x-_13|GA#-B3_f_2yM~ z_XSjRRMf?(Kbg#^=H}*?0bX7s?o8Y5y}G)(_>hn$o8h4$rChLpdV>5hNqTqY&R!ys zI7H@fIMLUhG8hK>@h|GEUJYj3o9ek8JXm$SDaDx@5g}zk$7C{DF>Zcp0KA+NuN0mF zg+lQZir;N8zc3qRDT+LMc4A{-C9|E!n|wR9BSInH*Z^sHuF%!ZEzQB(+q+8Gl-g?H zo&}FVDzzPp8?sK%%rwzxX>GlEh~L$vlTrrVga2T9Nj2AbkobyTU}(UT*fq;W+qTuM zgtkGfRPv5A>+pMe;F8IH&LPg46(|F%{p~ntlA{Fzov_j>eYUa^cTUoj3d08lVJ2%= zjge=^(-mBAVujPwB?Y8G>pA&y^qa;OO+3rU$f$pK_--|<h$>q+lrri;pwsDnot@VW zLTXgPZh)-CMl+U*DU!Fl7l%C<mRt5bmrAA6jMH&({&D;3bA`N2n@IteTmsfY+S(#| zJ+kc64Otg1sGl5bZEx4EhzL8c@s7nZwX%3^Y<}n5+=M>0+D@s>eKWY;5i3vl=^Raw zNMveg=!mBm`E!5X3O0t3Ha#QB79J%hakVZ=q|yi<KR=URH8r*A1z>L_AN|7Yx5^*# zP9P|mc4RtA!2Fvj!q>PucCn-&DveHWX(wW_*dEVpHrrx5?moZm%BVJA=iA>Se0 zyG~9EoB=R#IiEg`cCffyKel<Gvy01(ABS#<UKjq#DlbpB?-zVttgQ87u~<}=XhtOv zJlP!#D`81VNzk~xwe|SUojZdf)$>|gT3p`OkD((Hu!1beT7E?~tOl{5F(&REsClC@ zCv2O#o_+@XxI=vSOzkBwYy{L;4&+yqm+zjJEfRmpoRUbi&t#Z?Q~Mk(Ek>~-NfWfL z?KtEeSiVulQyf%B=jrlh``Ah`iJJ6(j_(A}FBdZ)g>ywp`pEAJ<{fjm)K5$l?}<|M zPyncCp|m^{*(a16f5{Lv-(3yk4nIMUh_)swe^B$K)#a=>Ss?5jwUl-T7U0a25`e{n zSG|{<*iI{jtCi@fTBT9q6%%h3rX+-tki;I<r-bW!cnX8ZE?gEWyw(ZW3_liRfVny{ zBd<|q%J<5JeW>xJ#nFG^kWbE~Urj*s#*Chipw!1`Y1$RFT16%UoE_aUEC(FzFO5^* AIsgCw diff --git a/app/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 063a4c849e065681ce764748d113a07b38581f38..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2180 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 GLSTY}b|@<V diff --git a/app/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 25d8bf4264db0629285b1d09af056999db3c9615..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2256 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 bzAd~wT;lN4VJt!LsQ{LyXH4phy^{V7Hz_QL diff --git a/app/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index ae0fa8034404552fab928b6b981edaad4e3dd5fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2930 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 diff --git a/app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 14424fc393bf7b5462c47581eb856953fc4a1bce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5030 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 diff --git a/app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 3d3d5b8d80147dc8ad92e622af86e47cac85b885..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3423 zcmbVPcTf{bw>O9yDMCOLkQPAN1pzUJP6$mTAV}|!08)ie1msdw0zo8HDWL`>6e-d> zTs26M2ui#ds&ojw1{C4te((MDX5O3mzMVO{vuDr#c6avdIloOXH8Egk;b%E><_x>x z9o_qkHsv1yo@dlc`m-$zy@a8zwndoJdVYAoWlN#n4~-;@W7hLxflQmC_RO%#<Jumf z#*7OrQhkjgAQn@Q`~Z-PXXwY@PiNRXdGtz50VS|^T%t0bFi@GCuEo5<!>2O`y62GD zvCW&iU;Ud()#_Z7lr&bRYiQ>Anv47Xi*#~Ccjz9%Dl_~4EZ?Pb?pLG|Yl#UdR#<H@ z|3Beg$`xMEN2Y?1D-eS_B#AuadzGQ6rbTt~Is7HK-H&?7^VEXx;v|`MqM-Mzui0UM zal)b&{sSI`_Xf*tQMu0|wGr4PP;wwL^HDu+d3_o({_+z}SnEbCbCzFn4{~y%)+pEZ zg15JKU{&ZC)yB_H&ZQS)8k5t)Db{H@9Z8>52xT*WI8e@FR&S)<8(;#2-14C<e|c5W z(b3VamMN{Y;I@-{+s~V*SFb}#M`zotPqsJ9%FA!Eg(A$6IOH`;OUtBSlFt5e$bF#V z7_yDeJQwOOq$69-)`2#`m$#t{Bvk3P36hN=>u-h(!5v%ooE;sjn*7GCDp1tm?b)94 zeoS6PwHX?{?cX5AgNsopOox}1mxNkbSv5tFu%HLrBN?|)O$XE#^c-gFiJ0;e`F&-n z*lQ!rHI<d>b`}=FDvHL2au#-WcTYxwC)+}@Oxd5u+~m%W;svZz>m_eT9IR=9MpS|p z#kDwOs<=(-)6&w6R>Bh)Hh&aNwuknzu(Gyujbs4(S`C97Cmq2|0}c~RGE!29tcsDd z36fC@NpvshA4OSCk5`gKiw1k-MC9_ixy(T+0bgI+!>9}uQ3&9k7!#YW&n(Ui{|`m< zCX?!;wd}_mtxJ@%9O|a>M@L7r^3sC)tXE`Bg`<yaTc_;fen{SAnqBEP5RSBPjNJcr znxz@}vrMw^&-?4s-P6{%6fAI=c_L!7lQvAy>8gu81$hD31ywDK^OV0RxV~quk0wJ# z#T|BM6XkcnM+4k6Y(ot3USWX<F7nqwc`d3}!XaqXF|=IF0C&h3<phL#XYu@DCGIfN z9UblU(If`gCstHqlqKPy6z~JK|C>Jj>tALnOvv_-RVxURgpKv_{Cda_^$&*&rd~0v zaq6TLX+NX<c=yb*JL<OuePfdQ^9OtXPIppc_^!wPM*HSRYJ-<Kd|gk8e)4OxcW4(n zJ+ZL=ion=;KJYOLgTc^B3!`q<MDLQJmYY+Z-}XnH{aq@7gn&z6v|qB2#_SSb#tS(4 zI#WgbYIVB_ITtEVpkq>q#<`ipWZ~Ps*{We%MUUr_pfAGKw#Of~8%J*UT-6lj7frn~ zoUMqWe3I|3Y~YZALZMN|V?H&ON)JBoUH^XJl7Paq!~<0l=@6s=iC81%KD>YJw(r(t zH-XCw(3<XvFDuZ9_&8+ts?xc8%KCGroIPS_&=H5hvnuL)2^llb1F?TDtdtfWK+K$@ ze}}+TYHbFC_d$u)uos@EhjYnY5l}Z>Sha`uP=Q9*!(&Rh`CLK+1`Wn*(L?rrWcSdg znhD%|0JETs%uM_Oe`eA(=+Ek3%de8TstJaH$+}|7kDb{7VLl=|gfezjeSKJI;`8S$ z5Vt<g!Z6t(t<w(B8+r&k4`^5#W;|cud)*m1oeworqHYtUM3__!mu)q62bC@&U5O|0 z!chv2QT{ufKBG6B`Pf0AVZ-+>Gwjd_&a3?~fU}2@z#yD-BIC{=1P@VRxWiN+Kfh>E zdW?{Rp<x1ED>_PGv28*_SpsR;wjO?^*wV`C=YY2-HYOIPdl%+2S`7(uc$g_`i)`Dd zqitn@qyfeS>eSKo^F%pi`g-&9Pbaaw>^>H3!N?0fB?=?-7=LA*S2(aEk?ceYuA^vA zon8t~gXu@KH#M_M;ew8#ZAS?Aj^794-bXtNw2^ySkkQ??Z*Qz~QI&tp^6vVsDQ*Ak zmn_V_c}3o7aL54f#S>V0)hSH7^I*)!gKPA+<|Sdx%BhG=ji9J6#d?dH^IlW*TozGj z5fV~sOTPQ~rlvv|BQWNm#7XLecZwd1ttTI^`CHU<{j30;1=QEnE*<5X`4Dy122Unh zgN(x`Ru^;qZ})Bi06Pj|zbX|2<<GemAH2IDd~0D0CCfo@7=R<_6G8WmBhRcR7epS| zG9u<e#8AJ)4%6(6wv~9`F~jenjyvQ2-IHte!E^bcQ)YU4dfR$#*u)-W-h(M$G9Kdn zt-{j7DXCGrR7T`aS9Pz67Bu=D%*m;s?bk;8+^j=O03jzm;&{84d?MF8fsDIS*2)P^ zZ>M0V=y|@3vxUv48nA8P=U-YoDSm?Zx=0%1-P-05_e6xQ^+}r(v%AYxIGIj=mz0zc z&nsI3z|43fyr%ssSPHKrm=R`M8FtJ#<gAfN*yU@^F;9$eJeMyYP6gE^RK`qvmxvKb zOaDH{y!Tmc6X8OwbC-Cl{&VoL_c+D$>|lY$`25}5%NhNVAgk%F$W@-<GP75Mnxm$3 z0c@`I$#3;>0({Ye<f^LFmgdoB+j@_q|As9)$-#?kVb8d7-XXoKfL9Y*i~2>Ez^NXh zFaJu5Pr0=@(Q58D-Q}opy!8RCn30v$n-%-^Iv??T{S!LNGEQwnq3fHZ2;RIohO<R* zo0y4oJ;e#C4c7b0UI(`xbS=4Y8v>{T*jiyOE8Le1Fc3G=Ui*GlOYnQ?N^$(_NN76o zmLM~Q1$WLXuQc?*IUOYfl_nyKsECt+iASkLwh6&&UTEHbg*OhH5wje~t<bjdh$>w? z^I^#bjjmmB2dQVar1dd8ZGs}sp|O`@s}=j#GBH_LYl`_Q$V{TRIDvp@V&fLMQLP>J zQwK7t7?!}v$@fcqz9Wt~Cod62c`*^+sCN)@_&KB_(<9T4qkD@(%evY@y7TRrI4icu zR3}WhdFI&?uRYUzSHZSWn)RmYgIIm`CpI)6RjK%03d?a4tR;T^H#IVJxJvlZx!2^R ziJG8k;qI-WcI3^MjF<U-#o+(RRH9r=b!P)}OjIwhMdR)Ou_`r;n4&&+aW3?do<7Xo z-JOr)4WMIFZc<ktHg&p)5Pxy6T^|lZsE-*^5OL~#zoxz#i!+OhbAVDfv5fh&!V;pZ zUi6`6p%RAC#aOHFa9M@j8D0${iZHPNM2nnV$}e*c*??ZZeLuqE^e-#m@<2RaO?M95 zjRADwe}F&^T@P=3)19#{6ayCp8x!Nu2gku;Om{^^i^T0@&XLUX)wS{7ywN*9zYb$# zV`AsQOk7O=uB1K!$k{b4-<2!K21+TRS<8xwe)yAaU1$0V^{RkUdwP1Ze6|HZ0Cx#1 z*uuhs!S3$vDFR`T#ILuI;&4qlz=gZBe}h3S#%VO#T?1G)gZNM=T<vo9O>3)TjXsjd zu_}A0O#EwmROAPxz1vHSY+a&E_~u}q{0xr$f_^hYz(9QdKDO`Ok(DGSY1EoLhX|<# zd$M+tsc&6%x^@Hdfl|+gPdD{p>Q9QzQ=>sflXVT^O$9cRJ^UdwN_p#4llOGjZ;a~2 z@dd2ZVu%G$7#D`DggUoBTA#Y@H|BtlRSRBDG4}lj8lCRp*cPl}<Uf?lSk$us`Iq9l z%OI5nOnM$apHrw9y;j@1gC%eqaV=+D#Jat4XMl{YuIsh&A4_V@^wEz@Ax|H*WFnmJ zXp!E3#MB6!{CLK3*hqsTM;SD47hA$0a6sUzs@DUbycqP%ptI9$mXr{_`ghg%r>pYY zIuFH1ku!>1i;VQu_;{Zd$t{84y*HTmeL=YvVR(Wf-$L1j3}`?lC6_oJN0dJ*^D#5~ zGC0aesvq`OM};TaLVPT3ZEeY)^VLvr+R<A5koQ0AvP4bog3;Z(nv_P9R2&GUnWd_z zHk#l%fR}G3*rcsYdyT2|+bnsI@s5PnI#e&tdVh2JRJgUZRVO%3{`Hf0&n|GRjMhvS z?K1pcMlk&bDUMgA)_aMB<0Up6)W#vnMM$fBF+T>`om$BY`n)nxte5y{@5CLK9eYbh z7JIQ4tWO$9;Hwpjldg%ZY}MPdCsX;kR#_Wfb^BB67J91e8fmhxIgmvtG)NJR)Omrm z9P2@mpJNkRoF9BsF+l#U`ldR`Z!U(0p}1;=B4M)F{v6v7MNumPXEH>f7A^{Y<>>Ay z@PM!Jd>RBC@_i_umy;C<_pJ<oiwQN`_TCvd^8mAlngiL*9Ov1_WA;QP{&%_3cfy)$ X&h>I*k>JhvIGr)nGtsSvyFC3bC7o<8 diff --git a/app/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 7c5959f40dd440d5ffe55dc982f17ce8a4a9c97e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4471 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 Bv?Txl diff --git a/app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 618a63323f072105f2e8bf7e3d2754767b30d147..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7902 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 E0Tt>{ApigX diff --git a/app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4f931699a045843a2d624264476506b8ff9f30ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4768 zcmb_gXIK+kyG;Z_2~DM=ND)D*^b$a%h)Po_(&d0OLq|Z05C|O+RB8|iN>fBYLJ1u_ z^cDmuArJ)VND)Xv|Az0}Ki{AG-22@7W6yqOX3v^;?=|n*>z(-fMtV#PJPaTZi0SSf zorgf*de-P5!0{o$ofZVT;Br^zmg#f5-#JikcC*}0<OLj9PcJ?>r`p+hJZKb=oRf9g zrN-GNQ0KvR8AT%PVgH<UXku~#MeCP<=u@`nxc8B%C>pkcJFK@-Sl^e#Aw(T?sC9*- zuC`9@tP$S2)o(@<u3gR9C6^zVX%YPnN%X|P!Be$*FQZpl`S(TH7^442-9#9dBi=Hg z7E}8xK?e!JX*HmY@TT`~J(`?_Gtj)2J;&n)bM|gG_B+zlf7F*RIE;><8d?6Z$J=IZ zue5c1cqLs=qDnH)TG8sMq=Mk<t?<VltJR2WxXzohE5&Bf+l8tm@&^gwYufJ9^=4O7 z86P;Ph~=bP=~ASh_oYkHOpOh=*RT`alcC@!GOT_JT3A%HxbX~?;GgMX!dH)pIhbJZ ztY%lxa;JTw75wY?@Nl!`id*5YB$^|9f>Y)JSekRvv+t&L*n!F=zRxABQ^*<nwIajp z{!eD58+s<L&?Fjv&g>g|RqDS#m6j$O#pk6^bWIymU8n2)iy9k`s4;2ZEsolk`lsDF z%~2P^4N>>#hh6W)e){yO|9hc6u@c`q@KNd3QE)`6+?2;e#m|XU3ikQJk%P;v+Xc6Z zFqqwE^5*8|G3V#lg@uK=?8$gMzU$$^qg5dkHAaVLccWqRm9C@ko>ii8LH$)o3)bJ? z-_NA1g^?dc%~;_xf4j>2XFEe_WgfaZGBT3VPnT9;bvjw)a@uaU#c8vhet@06?-bR| z!DBL2fznOnFk|LZqmW(8Dk^ShxZx<<^RS64Skk%EC(;3L0ZcBAp8l^+eTwYt?8hut zyfO|tU1(-&i6Von_Ks(nN?tRM4UbQltAeU(yE#9{v05V2aj6QLIUf7T+VBs0$umMC z)tY19ho$^wLi)4hmRdfWz<o*qTm~0}vBrLZ=dbS9T_39RwPQEpN$0HGUwcC)stO8z zdC4MNpB_QDG(suyu9D`EJGTACMaFq2f!vz%3Sm@?yo#Siti(k{?G;5iAQO^K2qTkX zBlUX_R>LGBNIQm6g}$tiku~w-eQwS5hYntd(T;AAL&mI9t2T@JGg+&K?M_};eiMSj z`YK+9${<U2p~VR_JX#>5xs(cakb!tZmL;g-Y$F)?FC_iHHP(nLVU)JeodG4-uPLt< zg-{l=tz$urZqRvA2?_b7yzmod>}({oUpT|YVWWOcYG<{y93|aMiG;J{7-KHUYErLX z>AmFFFJn2}6cRYZI^UTzzow$S#3qSkWQ`DocwY)xuXLVmjc`6b*vsEZSk!~WwGMK% zX4&s9565_2qtCULmj+P2b0gSXx>E!e<3)0}p08JS^-l*9J%aaE*~OqPc`s`AcSnr1 zmpBFz0aODB<xiiqwv)KH_(0ktJ8m(S@+Oj1Nu#UX69nB8M^Gj#(6dAh=Sba`u&4`( z)2xBmAIMqoK^VDM#l%sZ-2CSzdF*EzgYy5N`Opim!GbACVC8`0z46%IZ{p>R7sRCh zJ!pjy^evV}$@u0mrpv@w-HGq{EZFa7G-pgNxJtV(Lz*S$pU`Ew(Uld<H=d4)npj(} z$@t9a_^p-I#$>7m`MQ?b|7?%W_dPx&E6F+c)7chJ$N-ZK=md3L4t$3t&x8yF5?_pa zH?xbIKcBi9>9iuuA!#*we`Bq@MKQxJ*sX-%KW@9^(G0cw=gIdxXqTZ}=k<xo#jY#i zgvHPrjm5Is8Cl>YbXi@A;Ek*;#eDkI%g7@yx_f|K%7tqjEPuc$By<W;>F~b=4=T?y z`i)t-1ikZar+0figgMav^o_xHOI^xVOiGFr%yJ4>58k~oQe?Qp$flO(dPXonjV0Qw z)LbDoXg*0vE0V^S#Kfx@HAw&C$B)GX@h@~uwE20@=i>9{lQj-XK7Ra2pQ9(w2dr`y z<7^&8x1U9WVqbEpajPB5ZT)-b<|*M_&|+3TnOb0zrRbqx2*8<qC#Y2eohVf=YDxll zR@!VL+buUTe@8vLo{D6HFZ8D6ljGL`qHq>e?q1&d(7{>7=brXYcYnrSA^ka>Jyb5b zK2)R)Q0>SBB3yi#iCj2_gWJfhenaXRW1vvxHAvP&(bt#%{k8Tr9+Rd76g>%CV$lA5 z+P-s%Rx6N7D2K$vhVtyFiLnBZ5PvPdI14PZ(s=-dNwmEs)pF!38KwZ1)qIIonw66M zeyL@GTl)q(k9-?0Ut5l(`rfA-Boh%`k?}Z8<*IMg(CW52nedjRK!Uodv0mopzh-J` z`rKMLQc#cLj+`yx$yHzr&AI--BRcLCBP&(&!D=aHjYiEhaoh&`=FJ<7oHoI)G2k~I z7u50UHQ0*Fm(qr$4XM%y8-#7DnV6f`7+QAH5&T#spoDN%f~sV|OGaFKO3TjY2Yjgu zv5qU$100F=mbjWY*KUyDFp$g^&7s)Z%K~@e2ZrxjPq3rD>~U-Z>(!bm<ETaTfY#(K zVY<WIX=9e5`O)7{fVwF;1hV6^wI^11{w=!an0HG5{+CLp4;4-DpUUO5$+Y>5hbSHs zMI!*3j>rCpvONP);P^c8e%9O<D-Z+25)N5uSI$H2)C5qpjy{|gK|xrhy1j77>7sHK zjG%2`>5RB$h<ajPZudLwFXIcmtD@f=$B!HIi9t)3H<h=hsrwcG>I4#?2S)0b8_G<+ zc`{*;qxz$PLj!rpmKoqwvw~#NxB*<QBV*NC;#_zO55KaIRGRh|v+cOeD`S~>o$tAe zW%f{ffLS7lptx!2m$Q-V?`M_g^h8(q()ICw3z-rOn4yp3WT#2Afksw~D<j29pP}Af zH<U)|)@Jq9Z1zjP)c0O22ycoY7lu6PbIP!FzWwTwn~~~DfyXNoKICCw`B#B*FJg1P zUu*TaUG_F<NEyg7`w)m`<Np3WQ~6tn&%4XHCx_>xnkf#(S3UPKb}BqjU)AKtr;U1& zWauVNil4)&=w9a(q)u^D=|oJ)=9EkxOg98>#*AAe)~ys}Hy-9r%d9SiALw2>lc^ri z(N*2;+T3KdKXKzvNo(WfKe>zpy|1%a^H0q5`{)d{W<6_9TUbl8QN2}aT)OsWFGm41 zC0h4aY?k<`&#KP7zlAJwUyvqa<~3D2_V4BsWCF_Tcj16Zm|>`au{7CjZE}hL`AFRb z@>JjuI2=hIF@g>B6=LaUhzC0*Ivbpjs6{UYeSQ5VwQ2h7yyADh2_jz)HyVZ-PY%|Y zV48trAhou^G%_@2Q_B8R#(aU(38t*1q+1S6#O+dl(U{%j=BlMW4fD|gauo+!LHk#a zug6EHS)Ib;(#JF|Gv2!%etJwUesO`?iHizXN@4OIR9VVAQ^)bH=SVjTa_%z-+MjeY zJC$e~)CuuKCrutj7Hu%TIW{hzwP_1h{#>FJ-f~6*zbgX%1>5;8TnOg&vSBK5w40dl zmFzP4)yYnSsX#$LU1B@T1xmdsP-s#A>|-EOO;RD*#HWewz1})tahTMZowHQ@oWF$} zBS#D%-Ybk0CQa_znAd}#OSPxcOkEU-z~k1<(Rd=ZO@u;Dya-53q$3CtOqa1SQ{7#l z;3>x|DJv)L8Js%0+#`~=OWh}b-8kG)b%Z+xd@VA3yMwdoBd#NnGemsA^D~GmZvMov zUt`$M=EdHRDS~O0IAiOc@;67V?{cq(#fP<JUf_8)EZ%|zS>+MD)Fb$PYrC53a-zQ! zYQ%TET5cTU>PPS<r2VBluNxQURQG?qt>w1n_l69IZv3t#=91o>c#Jf(gZcwxmh#*_ zrSBwj$q4>dA39?JlxT#RYn2N-8bM2F%Gmb6chB|bG>o&dH&K_%ndm@!<waM{uYo)_ zZw3bKt&DWRls+;odM~(I9vh<_r+)%^w5yT4<6{Tu|24D0<JG+HTplY}?vg)ftCTlC z_-#E++?YF~-d_IYnw?{1e^V^@PZa${HRO%^OFLM*!GToKA&vKB=*jV16eBCRKH!f{ z?CH&{Thja3kfg~}G#xAFN2Z<qouwf%^<@d<)*U~Kh#UpDd8e<Yc%REauKE;Ax=h_I zs{!I1TPM|vy>$6Vyz6M=l}TqcAvE_?edRmP%huHEd--7WppV!7Wm&`>`TteZ<f!=Y z#$&+2qjOUc?$HghYz=VQ_<m1y{h^0A(LjjmNd)8lWJX-50M*~D+~|ElbI7c7%8<sP z#I`rmaO=UVGInl$XkBSSTb}C+mbQVY|L|=oG~$oG?B?!GrUUXEl-;xRfuHILcnJIR zr<q4Q)^Dj7B&#`}0><;nYI1agJXipz^(#GqV=Xq^J~`a%JG<>o9Ge)U8<V^HkI<iq z{aLeY75aOhlwLR2>|$}Y7enX5ZX!;DPS5r!OMWVh`V)l2e<5#02ZkCxu<?0ZDaMoh zAMyR)eWoE?PBRBE)^8?w(T`igLhyMxB8I5lK=G5OfM=Gm#*NN(Bq}joQmzoO>Khs= z9!`n{2a`9NVvi?#>Z`16ZKpHp%mDw~&B1s6vAH?PKTqSui9_`m_EEW=e0}AOR!`f8 zfZz7v--Q><%*-THz&a*>`9E=AYjOG(!*o`*2mv+6hiYwRl>#CB)a`glHXEHoAwWKh zNiKFyP777f9$BvnnF<JeZR1AJLXF=kS8^p4V#YKIn>bj8K!6kzr%w9CrKFlGii%>4 z5Q#*d&nc{yU(+Df@87orCTsbre8BFo-X?PKAs)WX&jJr`1=FUS^RGPe?MPsE%2xE4 zn}hp!1Lanw_QSI9-zWw6OAm{i82mw?e0oO4IQKvyW_+$qcinSxr{Wv)6!Ao@uA-uX z#g>DLGl@or&1Ut?Q@(gGivUx7xn&I~H4Ic%D9<TQwO$Fh$5VIW3$pse$;n#JTM^F5 z+|~&O_YUe+dL2ifx!Owqwn!T1(UJ%A!)$mhM_0*0tOXZ>pVRT$a<OgwvwhCP9iqha z$x!DTVSNLCK#9{H2#ATu#qn#{rtFN%zS22z?0lIOla3n=JN(@R*c5wA%%X!6g7EI( zBTgzn4FL{=tGE;De56w|GO~Mn?@0@{PcjSsfW<}4wHHIHg7==v!N(*VO;$3>Vwm;x zuN%sA>HuMIP}y@bxurx;qNzNLw|EbC$XF~kmXnQ*;0i-_D5Eg0K&b$Uoe$OhO4asu zX{U)ENE3NL1C%aaGoVmvraT+Cf1~qL)0qw2!~<56q2baMc?*xpcz8L{e|2=_zL>-Q z5NxAvzNXT#%c#oPLKT=t^%$!HP%dV3O4(S5T<CEwjlgHgxqOk7lCrZhHQjYSJvsKO zwc3|M-GaH&Nfyep_l*&w*Jv#Ar~2NwsMI-jy=s{Rwk99hYZR8gEBIQk({Voa;CESh zdBkGC0-TD!iBD>F1xcdmF%h?M3}-N+g~IaDH4c*p4O?T>zN-z_y_}Sbo6p5QTgK(X z?d)dGa~OC>$st*>dsbT*uq(|#0R|a^yMRg<AcPmq&NZg}d>4*FL_p=VTQEoXKQQYy zZStf`o{+mIITp#Rx}2PTGhXM9p1ggvw=znT_j(eVT^<>5G?)AXC`-4p5mJd}utiVH ztByQtoav7jMq%%IzYoO!iiF_K0mEIX%h24w3$2nUw#pY%%Kf`PKE?{get1}iV&F?P z+4L2Ycmw6pgu0&dK%d7%I^&pmo|?^(V_THavNJEJRQ3jo;Eq;_Nruc~(s+%NQ$KkG z2aX!ADdbxCqxv8#l7SLJE!Jtukya2wB_{bM_V85i42xoYWG)>RPDz?Yf7PNW3@=8t nG<m17_uT&Pe&$r}Y@hNL%y+smN$CN9o<MhXjdZ?f*+>2h)e7-0 diff --git a/app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 4ddb06f72a75c5fbe039f15d2601ba0b5cf82d43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6216 zcmd^DX;70{l-0%#?Gm87ZBRhdwyh!rTv(!jprW{epb&_J9c3|L7eWLQ(N-}CaYJ@c zkbO%8BoItM2q+L1*@A|}06|%W2qa`-J|uL{R87@XP0gRKp^87>OTBmRednBe-o0me z{uFqF@`g2Q)_~8PK5=o)nzf|m|DQm>m$&EltFKwJMgGi*W7Z*#lYRF}RIhZ&Ixm66 zq1HCXHbvcuw0!om?#E+CbqH$5e~MI7|2;DOiKWk8>&-XecYbHMN1}I}e!^6UHFy1K zhb#WDi8(FQ!eXG=yye=|tRXxbs_je11l%1+{?`1_mp-%&IPu^2r>bDyQH8?vk*JR7 z8{(OZyx^z(34x%^QO_oeJhDzIu$&&|Vn(Xg-cBrED*LIEx?>cq)>$QfC~G;*NQOPL zJg;iG2l(_vMz-@ER%XLU_1d6-{u{>+`yEYLUY7g+zT9*7)OPF(?AIFE1!{M((*T-H zA@{z#a;_wBv_<4Lc%$ii(dItY{c3^k7f)g0x{t|j(}~B7G{!O$by0~uj-i@Mh|dl! zDwb-pMe>h5oh#&yd~4%AI;Z8O=i8r*H~hByd;NRv8Y#0#{lfkQy?mR(0Mq)l4|$-? zjS=G}X<#(sT%4A-p7)0*X&cO}rCZ6SN1WW|U-vW9jd<i~nw**0>i4{elbV-azUZwC zLgCKt|M{@*(dViZBh|Mc#h~s>9=c1yfu-4wn*poeKZ6v@f+s(SN}t(FcNo$mA&yae z`YO5oq0`=7M;6!}Y@BeCS=QtDF*$7ZU7_28jYqP-DbCJPOB%bs3c@~FRIlPYT#Muk zRUgW@4jeO(n33PlOI^@uH4>szH=JFDVZh*`WncB3?Ku9YnoG#k?0lPI$CT_F7p2jd z<we|%+DD`4TG%?^1S0}Jwb+#^)c&c02zwGg`Xw*0<?tSeT;>W&!{aZ@MD^*-8akAP z31-6%J!xFI50^*aiF<oeJ((SF<t>k&{=Ek)1=HV~n4RBZFkSUuevM`jek;UQjx}l& zH{UhM{f>Qm$nLIc3BSQuu4uZEcdg62yzl8hp<2U8TLPOj%XRLm2AoF46cNT5k{Kd? zIQQA=4FPpLlvMd8D+$Eh+rxwnu<bE@LAQvTh=#LoyUC&Xfvrbi#+qtz()^rJGD z+B>3B5b|O)vg9E#B^QB^=6R?MJdqxy)o9`6Cq}R}pz~I$**;Y>^@fboWc7oPk8oRi z|C2_^*wqV>>8(Dm|K)}I$5-Bhz2LfrSZtUrV<9k<O6Bodjg2WAuNehfGo~*K=O?in zxE0cT<*a3puh5rxZ#M25&Ms*{`o7j>IjTc?;Cqt&j(c=qc}!SRhlTO$pynL?2~uq$ zGYK}j7qMnFMg{Q1L`kcUBXDyfYdgp5UoyGzZZTfl2H<fH!Ix592gmY@eWEATn*>Tl z(i0ca&Zi*Z*8;~2%jH^%>gTFu<eSG{K`{g23#CKT)M*;I_!P1tV5HRT(rPI_mqa)O zb-@J<#9dE6k5*KwCtgmjxXmBnFJXbOMxIzDZZimek=A_tk+iUAWrSlctiZ4+pJO)m z{O<Nf3zuR5@m*nKR%ut6J@C;qJdQW8B*9EfxAia37IZC)#n&Wc=zb|E?|-az&2&nB zK8(st*Q)yZrkh`+FX0a*Er~{r1zr<f-G#`cj2QhqT_@WVEH|Ohcsvi`*!pJmUV-C# zY%Uhl7Ow_QkQ-jbe9437+l=h3e+e**Om9^#x1TNx8J2RRz52~E-}JNc)7LZH=|T>2 zV9^Fe3oo>?I1*G(|8?UzDM{gAXk7x_s3_kRE7B;gdX&w|PcgvADO;razWOaxub#n# zZkNVytw-YfXwZfl8-lxjRI-1Ny`^axdWevRqblW`O@5Of%CL5d6zPqqs~`|`oVb9N zT{#qEFuU39Qi;1cs>mPZ6vxx1|HI+qI+0-jrVfh2Un{XP;~YOE`uZ-Du%`PW)M%B? z=@ASHD+kuv4)=fEO=HuB=X#vdx?1mD=iJ*fs{{SzpWoeXfZ)UY%m78e37VX^d*{`I zKOCB(4A})Tt^FVJ(63&)etIh<_eC(&9xt9-=Ihs5dEeCV`Ch}5*(|S}dOx=C?pyiG zdMTz!vw4N?J?N=A)QL9cMrl5qRn>;uD;z0O@u58LimxxcC2vH*errF)djj?Q9?Bt8 z>XJn0QD!2}v8%l1#hb=28CWoXSP_s6O&h2dpsZaF<}+;y5C_If&T@;tpQGBrIQ1w^ zG`t5H?TH`DHWqtH4nQqecFYn^Vdx*P9u*>&g`&iNs9uzTxUoVR5xsZf1Y|*u%1uPs z;rroDPc>1mygxq0|G9BR&ST2O;%PiYSfAkEN}^y86R&2-#PEW-5ZiE|O7r2YA68H{ zG}$+5VK7;0Q~M>4I#r2kC0iVs>m1zq#xQGHVULXLDQR1Qm}jDI*pN!0X$Sm(9u?3g zyP?XlHWPX8R6y7bf82QDJG^|zR9=e#(mX5QXtA9b!`}O6@C3hZk5Rkw030HCdkR#3 z+vU@WU(j?1*z%3FAX*j#&=9pwT<dBEz>>DuRm}Zc8=~M72l0)2vrCoF*^0R^`a7%A zI`<j@C1I?GE(uD#Db=lVL|;IYCbPj<GDB9UEhu};Ip44NoE~M@fPvfZ-$v~#@mtB_ zhW<)%pPm06uUyXK?_8G|lk!Lb_V9jTTzxJj_6*wu(3bwjj_Tx9LNb(`*mlQh<%IDP z4rh>}5x_pPm3vDHnEA{I^FoSt0Mr6M!%*UN?$4HPNumRNxIs10ti-D$%kW6^Rw;I^ zbxAoHM59UpkV!0lUGw_Y?|P&hN^v*3Zt|idb|kjO^%3Q6kA%Psbqbv<tI<-Pp;Ln= z8%?+`5L<rTc$tGa&=MGva?-WqQ=Aj7qL=J;3Kx=QO;-#wmlECq44uMNVR>A40428w zNoOT<#rSO#3Uq*T-Td4J20y_NP$g!ptz&O;2<INNMix>v%A#Ow*?-M`KozHSaUSqL z&l(H}TAVlLRC)O}3qHx?DT4QLv`!JAWRQZa<`zJExZgS~eBnK~$Qe_7{Z^0C`cr-3 z3w<5+lKFDFcl5o*8hPCQ5RpVU?Mve`_MNA?c(A*7O{s4SHRQU_zuigHaT0#$0YQ4@ zrY*zUj>&?9+m`@Qg^f=Z>-n$|i+0Udqy@UBOF|@*!-eyPVZXNoaLkP%+6Ambm?uh( zA<qTuQKKGZ<2=r2v0YhS?<;R)symTSndPM|g^jZk)VX4y)f#%IdV;X_ozbxVL*8u5 z<e54{Y*;7IrS-hgJ>s{^QeDG|6OC4+Uyy5kS?QRc`&;x^ns6#<Ue)xTDNZ67Esue~ zXE3k7y+0@GQOditT4`T`wl3-GsPR%oP&3K24{@+XHAa6dr<?V<>&%`M9f|}R1sjwP z|7=n?Gh~gsWvl=$80TO3&7&$!JmG-^c<ev_jpar(58ZCXK+QzRhdm2o_p7(enx@pW zC`q%QK*>a1;R)hh7zqq(yzt{d;?9^u>AF5_<)D(rl2#+Cu1~VQVE8do$rTa_()&Un zE}2fghb)*I%)Z@;9BL?}FwMI#KH*XW>f)rr0$N0uzB>ZwwY{nS?a=dp0(8gc@a6Ux z5k&1ooQ|iWwr(b%Xu?qLeb^@vpTyBq>v6)12cJTXMW5u8Vl`~#T2${4m&R=AAGBNB zj-#C~_-E<Ds}yI%$nCI#5KYdss&2>DfuJ4-n0LS652^VJ_jaCP;Emi7#)5Yi^Y?|d z5J}s0Lv_XN1rF5U#{0fh@%+aH==pd%vasQ+kOXwvg~Emmj<8G%4~STzMiC`FXt$Ca z;N<S8m<Iv2`If=4(S|?<#-1#5RwTy}Ft6$T1`b;mwtU^)>JqIC$0&m-irer250L+~ zxPiOUuCzZ1qzE~~Ae8#zcu6~xnn_uXZS1nQe<YrXJEv9gLf1&Wf7qnpuV&)OZJ`t7 zTQCmd+kO?<yom5m8=ba5BNNnHw(uw}jIlbzdi}06$+zroipxop<g3fx@W2Itmx5A% zD+VXp&`3S;W7vx1jGNfXL=mP7IZpgq0%I-KtcbPKU(M*{m>uP34?nV5xOKRA+{rhZ zNT}bMevI1?z&P2JCTR8mHPl_|GmDZ3ELZ64yFJ;_V4~5YCyGsSg>%)&ZM%rk1me28 z90<gr_$0LQU2zSG15w6`zQzky-wO%KEVPC)yAw7-*$*T3rV&ai6nyLbnYOsFEP{5u zr>DO+GWd68M6&q^s_MA?>Es4ERObR_N6YXmD~G=wrDlX<5~nQ~zli?cG7qH4)S$T~ zT+NCl2rpW-s-=cbRk(k7pILYyG@mbVue}4dO1*-SX&Tr8el1xXPk}s-`B=@N7F@_* z=W$hMru~bWzxk!@D!OG6jD|eHj5rr?>mH5T@erv+%<*(4Up5H7_LdV(8#mh(93JVQ z?QTVuVOWz#+Tpeiy5p!s<M%xhqJQ6d(E|!RA;?7f3P6R^2HoEVnM~!HT6bIVlqr zd&l9aCbC-@62#Q)+0<X{fy}7Gc#{j3Fmz#`vq2_{cz~ufcD**{`se3&=T-5dnTGQ1 zME%Lqjx5oWB_)U^mt32e4~8cDy;%?3uB}se(D;Jkc_!>1dE!SxZ77TwxeE)xLuk;c zA8T(7jsN`N);`)q>rjSkQGyTsB~H9p;{$QvfWLjZ3u(a{nuq<>TjuMgLw#<6U+gGs zxElntpYsXitrMK~xzNbOCNNa7boju9nT4%Epd*`^Mvg?ms%b9*>0R9#j=q!B0M>Zw zN&F>hcO`8fVLF48&SJ4)iRJ~H?=!uJGT!h7{*$%^jVinPE!|>R8h&$QUALHbcycN; zBC0a0b3D~`=b(t+Ng$pcD$vsUVQP`O38e)iA#M%?*(jT%J5fI`g>M21>hf(B#I=A) z)rm|9Zi-ClIrT+<qe?a>8rPZdAqSP{nu|vVZ8q1Cl#iS3)k;sjKJfC;<0iNHGH!s( zMs0^1Kp!2L&gpTbt-3Qkz?sI(I#7-4g4||86gU3Fng1OtqLf{J_oybX_Br6TX*@I6 zWWsoJ7QytOm0PMga}K0fe)BM|SsUpF2-{OMt>1B%R9t%XND991u_987{&+$6DeMlL zKZ9SlPZe^|s4+#$0iY`(-RcVwk|S5OKd@$S>?;C)kkWu~#F&^k4g`Xli7gJx)Wsze zP%Wln$$xJ13>DuVM#%+ZVQ=f~j?e;RxzE>>i$?C&jDQ6e_z!w}Lj#^1P=pc)bMyfp zn&>3rC>0haWf8Co!St6Q=j9E*9vv08aZO}Y2uZ#%p~v{L#{8O^+npH_|AS7eJ_LM) zB7+5hq5;X5<MCbk!gx$=JwnJ(%r*npjsZ%ln$dg0u5mIL&Q3X`_^Tm*@YYUDUc5Ta zv>-K#v1^7rh#gIV@g*8u0uj28GI+B_YksI%7v8+Jbw}bQC_562aU;HA9mRy~-Xc86 zrDLr7x2w~fn%wTtGcIs;(hP$USV-M-%KaV0Xk`rjO;OJuEn!0o3gA#ZI=w>udyW6S zlONc6z(QROo?_=Jx@ZQk@b(M~n0u69dG2Lz#}YhJ-!1Khu6RYtnYL<04=-osEoRno z>_0u7qe^u4gE#F~m-YEYhs3y~91jBh8Y=@^ugu~=!DceI?lndvtplU|pMtU?YEFz) zo8JVzI&@B^quMz=U~u;_8Dw&1gqQRF3y1w4D=3vtvB)NxFRu?p!)EatqW*hZ`aif< z=`!U^$Xu6g5?;bfyDHZOJO^f*>ej8`X*Vz)058v#I#u(`lk`U7?yX;89T1z|!daQ| zQ!DlzNT|YA8DYGlx{selFkio~UR)U{E$1{93+g6*H@wS*2rSki;iLx~lSvBAjF{W` z9U05DU~7O2AkxzMnKV_{`qBjMxP<r{D-WOl-SqG=39x-N=9*n0WvHGak;diJmAQ@i zo8`AOFjdGuprNJX?($D^)c4hjF@Hx~q5(!rUxUk;|6eTr>js@v?Ri(ZZI+x&pek)s z5BeTh$v&Yhk9SKAE>C}OXdND2E8KN(t_4^|tSiI{%_cIveqQIUVf2XZf_bemy6N9H g>|V3x22KQFy6WEhr9XFMIsP*z&z~qd?tJq<0GqL+hX4Qo diff --git a/app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 862a03de4bea222e526f89c0781b86951b3779c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11525 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 S8G4|-8KkD9tym*(1^XZI?{|Oz diff --git a/app/app/src/main/res/values/ic_launcher_background.xml b/app/app/src/main/res/values/ic_launcher_background.xml deleted file mode 100644 index c5d5899f..00000000 --- a/app/app/src/main/res/values/ic_launcher_background.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <color name="ic_launcher_background">#FFFFFF</color> -</resources> \ No newline at end of file diff --git a/app/app/src/main/res/values/strings.xml b/app/app/src/main/res/values/strings.xml deleted file mode 100644 index 14dd8a46..00000000 --- a/app/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ -<resources> - <string name="app_name">Reactive Resume</string> -</resources> diff --git a/app/app/src/main/res/values/themes.xml b/app/app/src/main/res/values/themes.xml deleted file mode 100644 index b81c93fa..00000000 --- a/app/app/src/main/res/values/themes.xml +++ /dev/null @@ -1,6 +0,0 @@ -<resources> - <style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> - <item name="windowActionBar">false</item> - <item name="windowNoTitle">true</item> - </style> -</resources> diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index fc9d0cdc..00000000 --- a/app/build.gradle +++ /dev/null @@ -1,9 +0,0 @@ -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.9.10' apply false -} - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/app/gradle.properties b/app/gradle.properties deleted file mode 100644 index 3c7a8bd3..00000000 --- a/app/gradle.properties +++ /dev/null @@ -1,23 +0,0 @@ -# Project-wide Gradle settings. -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true -# AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app"s APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn -android.useAndroidX=true -# Kotlin code style for this project: "official" or "obsolete": -kotlin.code.style=official -# Enables namespacing of each library's R class so that its R class includes only the -# resources declared in the library itself and none from the library's dependencies, -# thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true diff --git a/app/gradle/wrapper/gradle-wrapper.jar b/app/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59203 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 E2O(~oYXATM diff --git a/app/gradle/wrapper/gradle-wrapper.properties b/app/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 211f6a25..00000000 --- a/app/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Wed Mar 09 21:34:49 CET 2022 -distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip -distributionPath=wrapper/dists -zipStorePath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME diff --git a/app/gradlew b/app/gradlew deleted file mode 100755 index 4f906e0c..00000000 --- a/app/gradlew +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env sh - -# -# Copyright 2015 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -exec "$JAVACMD" "$@" diff --git a/app/gradlew.bat b/app/gradlew.bat deleted file mode 100644 index ac1b06f9..00000000 --- a/app/gradlew.bat +++ /dev/null @@ -1,89 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/app/settings.gradle b/app/settings.gradle deleted file mode 100644 index 3ec57465..00000000 --- a/app/settings.gradle +++ /dev/null @@ -1,16 +0,0 @@ -pluginManagement { - repositories { - gradlePluginPortal() - google() - mavenCentral() - } -} -dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) - repositories { - google() - mavenCentral() - } -} -rootProject.name = "Reactive Resume" -include ':app' diff --git a/client/.eslintrc.json b/client/.eslintrc.json deleted file mode 100644 index 823a26af..00000000 --- a/client/.eslintrc.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "extends": ["../.eslintrc.json", "next/core-web-vitals", "plugin:tailwindcss/recommended"], - "plugins": ["unused-imports"], - "ignorePatterns": [".next", "__ENV.js"], - "settings": { - "next": { - "rootDir": "client" - } - }, - "rules": { - // Next.js - "@next/next/no-img-element": "off", - "@next/next/no-sync-scripts": "off", - - // React - "react/no-unescaped-entities": "off", - - // React Hooks - "react-hooks/exhaustive-deps": "off", - - // Unused Imports - "unused-imports/no-unused-imports": "error", - "unused-imports/no-unused-vars": [ - "warn", - { - "vars": "all", - "args": "none", - "varsIgnorePattern": "^_", - "argsIgnorePattern": "^_" - } - ], - - // Tailwind CSS - "tailwindcss/no-custom-classname": ["warn", { "whitelist": ["preview-mode", "printer-mode", "markdown"] }] - } -} diff --git a/client/.gitignore b/client/.gitignore deleted file mode 100644 index 6cf4f584..00000000 --- a/client/.gitignore +++ /dev/null @@ -1,42 +0,0 @@ -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# local env files -.env.local -.env.development.local -.env.test.local -.env.production.local - -# vercel -.vercel - -# typescript -*.tsbuildinfo - -# react-env -__ENV.js - -# next-sitemap -sitemap*.xml diff --git a/client/Dockerfile b/client/Dockerfile deleted file mode 100644 index b1c86259..00000000 --- a/client/Dockerfile +++ /dev/null @@ -1,45 +0,0 @@ -FROM node:lts-alpine AS base - -WORKDIR /app - -RUN apk add --no-cache g++ git make curl python3 libc6-compat \ - && corepack enable && corepack prepare pnpm@latest --activate - -FROM base as dependencies - -COPY package.json pnpm-*.yaml ./ -COPY ./schema/package.json ./schema/package.json -COPY ./client/package.json ./client/package.json - -RUN pnpm install --frozen-lockfile - -FROM base as builder - -COPY . . - -COPY --from=dependencies /app/node_modules ./node_modules -COPY --from=dependencies /app/schema/node_modules ./schema/node_modules -COPY --from=dependencies /app/client/node_modules ./client/node_modules - -ARG TURBO_TOKEN -ENV TURBO_TOKEN=$TURBO_TOKEN - -RUN pnpm exec turbo --filter client build - -FROM base as production - -COPY --from=builder /app/package.json /app/pnpm-*.yaml ./ -COPY --from=builder /app/client/package.json ./client/package.json - -RUN pnpm install --filter client --prod --frozen-lockfile --workspace-root - -COPY --from=builder /app/client/.next ./client/.next -COPY --from=builder /app/client/public ./client/public -COPY --from=builder /app/client/next.config.js ./client/next.config.js -COPY --from=builder /app/client/next-i18next.config.js ./client/next-i18next.config.js - -EXPOSE 3000 - -ENV PORT 3000 - -CMD [ "pnpm", "run", "--filter", "client", "start" ] \ No newline at end of file diff --git a/client/Dockerfile.standalone b/client/Dockerfile.standalone deleted file mode 100644 index 24ffbf82..00000000 --- a/client/Dockerfile.standalone +++ /dev/null @@ -1,43 +0,0 @@ -FROM node:20-alpine AS base - -FROM base AS deps -RUN apk add --no-cache libc6-compat -WORKDIR /app - -COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ -RUN \ - if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ - elif [ -f package-lock.json ]; then npm ci; \ - elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \ - else echo "Lockfile not found." && exit 1; \ - fi - -FROM base AS builder -WORKDIR /app -COPY --from=deps /app/node_modules ./node_modules -COPY . . - -ENV NEXT_TELEMETRY_DISABLED 1 - -RUN yarn global add pnpm && pnpm build - -FROM base AS runner -WORKDIR /app - -ENV NODE_ENV production -ENV NEXT_TELEMETRY_DISABLED 1 - -RUN addgroup --system --gid 1001 nodejs -RUN adduser --system --uid 1001 nextjs - -COPY --from=builder /app/public ./public -COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ -COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static - -USER nextjs - -EXPOSE 3000 - -ENV PORT 3000 - -CMD ["node", "client/server.js"] \ No newline at end of file diff --git a/client/components/build/Center/ArtboardController.module.scss b/client/components/build/Center/ArtboardController.module.scss deleted file mode 100644 index a4a47d4d..00000000 --- a/client/components/build/Center/ArtboardController.module.scss +++ /dev/null @@ -1,28 +0,0 @@ -.container { - @apply flex w-auto items-center justify-center; - @apply fixed inset-x-0 bottom-6; - @apply transition-[margin-left,margin-right] duration-200; -} - -.pushLeft { - @apply xl:ml-[30vw] 2xl:ml-[28vw]; -} - -.pushRight { - @apply xl:mr-[30vw] 2xl:mr-[28vw]; -} - -.controller { - @apply z-20 flex items-center justify-center shadow-lg; - @apply flex rounded-l-full rounded-r-full px-4; - @apply bg-zinc-50 dark:bg-zinc-900; - @apply opacity-70 transition-opacity duration-200 hover:opacity-100; - - > button { - @apply px-2.5 py-2.5; - } - - > hr { - @apply mx-3 h-5 w-0.5 bg-zinc-900/40 dark:bg-zinc-50/20; - } -} diff --git a/client/components/build/Center/ArtboardController.tsx b/client/components/build/Center/ArtboardController.tsx deleted file mode 100644 index 7cbd0b52..00000000 --- a/client/components/build/Center/ArtboardController.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { - AlignHorizontalCenter, - AlignVerticalCenter, - Download, - FilterCenterFocus, - InsertPageBreak, - Link, - RedoOutlined, - UndoOutlined, - ViewSidebar, - ZoomIn, - ZoomOut, -} from '@mui/icons-material'; -import { ButtonBase, Divider, Tooltip, useMediaQuery, useTheme } from '@mui/material'; -import dayjs from 'dayjs'; -import get from 'lodash/get'; -import { useTranslation } from 'next-i18next'; -import toast from 'react-hot-toast'; -import { useMutation } from 'react-query'; -import { ReactZoomPanPinchHandlers } from 'react-zoom-pan-pinch'; -import { ActionCreators } from 'redux-undo'; - -import { ServerError } from '@/services/axios'; -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'; - -const ArtboardController: React.FC<ReactZoomPanPinchHandlers> = ({ zoomIn, zoomOut, centerView }) => { - const { t } = useTranslation(); - - const theme = useTheme(); - const dispatch = useAppDispatch(); - - const isDesktop = useMediaQuery(theme.breakpoints.up('sm')); - - const { past, present: resume, future } = useAppSelector((state) => state.resume); - const pages = get(resume, 'metadata.layout'); - const { left, right } = useAppSelector((state) => state.build.sidebar); - const orientation = useAppSelector((state) => state.build.page.orientation); - - const { mutateAsync, isLoading } = useMutation<string, ServerError, PrintResumeAsPdfParams>(printResumeAsPdf); - - const handleUndo = () => dispatch(ActionCreators.undo()); - const handleRedo = () => dispatch(ActionCreators.redo()); - - const handleTogglePageBreakLine = () => dispatch(togglePageBreakLine()); - - const handleTogglePageOrientation = () => dispatch(togglePageOrientation()); - - const handleToggleSidebar = () => { - dispatch(toggleSidebar({ sidebar: 'left' })); - dispatch(toggleSidebar({ sidebar: 'right' })); - }; - - const handleCopyLink = async () => { - const url = getResumeUrl(resume, { withHost: true }); - await navigator.clipboard.writeText(url); - - toast.success(t('common.toast.success.resume-link-copied')); - }; - - const handleExportPDF = async () => { - const download = (await import('downloadjs')).default; - - const slug = get(resume, 'slug'); - const username = get(resume, 'user.username'); - const updatedAt = get(resume, 'updatedAt'); - - const url = await mutateAsync({ username, slug, lastUpdated: dayjs(updatedAt).unix().toString() }); - - download(url); - }; - - return ( - <div - className={cn({ - [styles.container]: true, - [styles.pushLeft]: left.open, - [styles.pushRight]: right.open, - })} - > - <div className={styles.controller}> - <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('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('builder.controller.tooltip.zoom-in')}> - <ButtonBase onClick={() => zoomIn(0.25)}> - <ZoomIn fontSize="medium" /> - </ButtonBase> - </Tooltip> - - <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('builder.controller.tooltip.center-artboard')}> - <ButtonBase onClick={() => centerView(0.95)}> - <FilterCenterFocus fontSize="medium" /> - </ButtonBase> - </Tooltip> - - <Divider /> - - {isDesktop && ( - <> - <Tooltip arrow placement="top" title={t('builder.controller.tooltip.toggle-orientation')}> - <ButtonBase - onClick={handleTogglePageOrientation} - className={cn({ 'pointer-events-none opacity-50': pages.length === 1 })} - > - {orientation === 'vertical' ? ( - <AlignHorizontalCenter fontSize="medium" /> - ) : ( - <AlignVerticalCenter fontSize="medium" /> - )} - </ButtonBase> - </Tooltip> - - <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('builder.controller.tooltip.toggle-sidebars')}> - <ButtonBase onClick={handleToggleSidebar}> - <ViewSidebar fontSize="medium" /> - </ButtonBase> - </Tooltip> - - <Divider /> - </> - )} - - <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('builder.controller.tooltip.export-pdf')}> - <ButtonBase onClick={handleExportPDF} disabled={isLoading}> - <Download fontSize="medium" /> - </ButtonBase> - </Tooltip> - </div> - </div> - ); -}; - -export default ArtboardController; diff --git a/client/components/build/Center/Center.module.scss b/client/components/build/Center/Center.module.scss deleted file mode 100644 index 1c86ed27..00000000 --- a/client/components/build/Center/Center.module.scss +++ /dev/null @@ -1,13 +0,0 @@ -.center { - @apply mx-0 flex flex-grow pt-12 lg:pt-16; - @apply transition-[margin-left,margin-right] duration-200; - @apply bg-zinc-100 dark:bg-zinc-900; -} - -.wrapper { - @apply h-full w-full overflow-visible #{!important}; -} - -.artboard { - @apply flex gap-8; -} diff --git a/client/components/build/Center/Center.tsx b/client/components/build/Center/Center.tsx deleted file mode 100644 index 92c8984d..00000000 --- a/client/components/build/Center/Center.tsx +++ /dev/null @@ -1,57 +0,0 @@ -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'; -import Header from './Header'; -import Page from './Page'; - -const Center = () => { - const orientation = useAppSelector((state) => state.build.page.orientation); - - const resume = useAppSelector((state) => state.resume.present); - const layout: string[][][] = get(resume, 'metadata.layout'); - - if (isEmpty(resume)) return null; - - return ( - <div className={cn(styles.center)}> - <Header /> - - <TransformWrapper - centerOnInit - minScale={0.25} - initialScale={0.95} - limitToBounds={false} - centerZoomedOut={false} - pinch={{ step: 1 }} - wheel={{ step: 0.1 }} - > - {(controllerProps) => ( - <> - <TransformComponent wrapperClass={styles.wrapper}> - <div - className={cn({ - [styles.artboard]: true, - 'flex-col': orientation === 'vertical', - })} - > - {layout.map((_, pageIndex) => ( - <Page key={pageIndex} page={pageIndex} showPageNumbers /> - ))} - </div> - </TransformComponent> - - <ArtboardController {...controllerProps} /> - </> - )} - </TransformWrapper> - </div> - ); -}; - -export default Center; diff --git a/client/components/build/Center/Header.module.scss b/client/components/build/Center/Header.module.scss deleted file mode 100644 index 3bad08e9..00000000 --- a/client/components/build/Center/Header.module.scss +++ /dev/null @@ -1,25 +0,0 @@ -.header { - @apply mx-0 flex justify-between shadow; - @apply bg-zinc-900 text-zinc-100; - @apply transition-[margin-left,margin-right] duration-200; - - button > svg { - @apply text-base text-zinc-100; - } -} - -.pushLeft { - @apply xl:ml-[30vw] 2xl:ml-[28vw]; -} - -.pushRight { - @apply xl:mr-[30vw] 2xl:mr-[28vw]; -} - -.title { - @apply flex items-center justify-center; - - h1 { - @apply ml-2; - } -} diff --git a/client/components/build/Center/Header.tsx b/client/components/build/Center/Header.tsx deleted file mode 100644 index 18a8d0f6..00000000 --- a/client/components/build/Center/Header.tsx +++ /dev/null @@ -1,215 +0,0 @@ -import { - ChevronLeft as ChevronLeftIcon, - ChevronRight as ChevronRightIcon, - CopyAll, - Delete, - DriveFileRenameOutline, - Home as HomeIcon, - KeyboardArrowDown as KeyboardArrowDownIcon, - Link as LinkIcon, -} from '@mui/icons-material'; -import { - AppBar, - IconButton, - ListItemIcon, - ListItemText, - Menu, - MenuItem, - Toolbar, - Tooltip, - useMediaQuery, - useTheme, -} from '@mui/material'; -import get from 'lodash/get'; -import { useRouter } from 'next/router'; -import { useTranslation } from 'next-i18next'; -import { useEffect, useMemo, useState } from 'react'; -import toast from 'react-hot-toast'; -import { useMutation } from 'react-query'; -import { Resume } from 'schema'; - -import { RESUMES_QUERY } from '@/constants/index'; -import { ServerError } from '@/services/axios'; -import queryClient from '@/services/react-query'; -import { deleteResume, DeleteResumeParams, duplicateResume, DuplicateResumeParams } from '@/services/resume'; -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'; - -const Header = () => { - const theme = useTheme(); - - const router = useRouter(); - - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const isDesktop = useMediaQuery(theme.breakpoints.up('lg')); - - const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null); - - const resume = useAppSelector((state) => state.resume.present); - const { left, right } = useAppSelector((state) => state.build.sidebar); - - const { mutateAsync: deleteMutation } = useMutation<void, ServerError, DeleteResumeParams>(deleteResume); - const { mutateAsync: duplicateMutation } = useMutation<Resume, ServerError, DuplicateResumeParams>(duplicateResume); - - const name = useMemo(() => get(resume, 'name'), [resume]); - - useEffect(() => { - if (isDesktop) { - dispatch(setSidebarState({ sidebar: 'left', state: { open: true } })); - dispatch(setSidebarState({ sidebar: 'right', state: { open: true } })); - } else { - dispatch(setSidebarState({ sidebar: 'left', state: { open: false } })); - dispatch(setSidebarState({ sidebar: 'right', state: { open: false } })); - } - }, [isDesktop, dispatch]); - - const toggleLeftSidebar = () => dispatch(toggleSidebar({ sidebar: 'left' })); - - const toggleRightSidebar = () => dispatch(toggleSidebar({ sidebar: 'right' })); - - const goBack = () => router.push('/dashboard'); - - const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - const handleRename = () => { - handleClose(); - - dispatch( - setModalState({ - modal: 'dashboard.rename-resume', - state: { - open: true, - payload: { - item: resume, - onComplete: (newResume: Resume) => { - queryClient.invalidateQueries(RESUMES_QUERY); - - router.push(`/${resume.user.username}/${newResume.slug}/build`); - }, - }, - }, - }), - ); - }; - - const handleDuplicate = async () => { - handleClose(); - - const newResume = await duplicateMutation({ id: resume.id }); - - queryClient.invalidateQueries(RESUMES_QUERY); - - router.push(`/${resume.user.username}/${newResume.slug}/build`); - }; - - const handleDelete = async () => { - handleClose(); - - await deleteMutation({ id: resume.id }); - - queryClient.invalidateQueries(RESUMES_QUERY); - - goBack(); - }; - - const handleShareLink = async () => { - handleClose(); - - const url = getResumeUrl(resume, { withHost: true }); - await navigator.clipboard.writeText(url); - - toast.success(t('common.toast.success.resume-link-copied')); - }; - - return ( - <AppBar elevation={0} position="fixed"> - <Toolbar - variant="regular" - className={cn({ - [styles.header]: true, - [styles.pushLeft]: left.open, - [styles.pushRight]: right.open, - })} - > - <IconButton onClick={toggleLeftSidebar}>{left.open ? <ChevronLeftIcon /> : <ChevronRightIcon />}</IconButton> - - <div className={styles.title}> - <IconButton className="opacity-50 hover:opacity-100" onClick={goBack}> - <HomeIcon /> - </IconButton> - - <span className="opacity-50">{'/'}</span> - - <h1>{name}</h1> - - <IconButton onClick={handleClick}> - <KeyboardArrowDownIcon /> - </IconButton> - - <Menu open={Boolean(anchorEl)} anchorEl={anchorEl} onClose={handleClose}> - <MenuItem onClick={handleRename}> - <ListItemIcon> - <DriveFileRenameOutline className="scale-90" /> - </ListItemIcon> - <ListItemText>{t('builder.header.menu.rename')}</ListItemText> - </MenuItem> - - <MenuItem onClick={handleDuplicate}> - <ListItemIcon> - <CopyAll className="scale-90" /> - </ListItemIcon> - <ListItemText>{t('builder.header.menu.duplicate')}</ListItemText> - </MenuItem> - - {resume.public ? ( - <MenuItem onClick={handleShareLink}> - <ListItemIcon> - <LinkIcon className="scale-90" /> - </ListItemIcon> - <ListItemText>{t('builder.header.menu.share-link')}</ListItemText> - </MenuItem> - ) : ( - <Tooltip arrow placement="right" title={t('builder.header.menu.tooltips.share-link')}> - <div> - <MenuItem> - <ListItemIcon> - <LinkIcon className="scale-90" /> - </ListItemIcon> - <ListItemText>{t('builder.header.menu.share-link')}</ListItemText> - </MenuItem> - </div> - </Tooltip> - )} - - <Tooltip arrow placement="right" title={t('builder.header.menu.tooltips.delete')}> - <MenuItem onClick={handleDelete}> - <ListItemIcon> - <Delete className="scale-90" /> - </ListItemIcon> - <ListItemText>{t('builder.header.menu.delete')}</ListItemText> - </MenuItem> - </Tooltip> - </Menu> - </div> - - <IconButton onClick={toggleRightSidebar}>{right.open ? <ChevronRightIcon /> : <ChevronLeftIcon />}</IconButton> - </Toolbar> - </AppBar> - ); -}; - -export default Header; diff --git a/client/components/build/Center/Page.module.scss b/client/components/build/Center/Page.module.scss deleted file mode 100644 index e8489134..00000000 --- a/client/components/build/Center/Page.module.scss +++ /dev/null @@ -1,43 +0,0 @@ -.container { - @apply flex flex-col items-center gap-2; - @apply rounded-sm; -} - -.page { - width: 210mm; - min-height: 297mm; - - @apply relative z-50 grid shadow; - @apply print:shadow-none; - - :global(.printer-mode) & { - @apply shadow-none; - } - - &.break::after { - content: 'Page Break'; - top: calc(297mm - 19px); - - @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) &, - :global(.printer-mode) & { - @apply hidden; - } - } - - &.format-letter { - width: 216mm; - min-height: 279mm; - - &.break::after { - top: calc(279mm - 19px); - } - } -} - -.pageNumber { - @apply text-center font-bold print:hidden; -} diff --git a/client/components/build/Center/Page.tsx b/client/components/build/Center/Page.tsx deleted file mode 100644 index 3d8e3bf9..00000000 --- a/client/components/build/Center/Page.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { css } from '@emotion/css'; -import clsx from 'clsx'; -import get from 'lodash/get'; -import isEmpty from 'lodash/isEmpty'; -import { useTranslation } from 'next-i18next'; -import { useMemo } from 'react'; -import { CustomCSS, PageConfig, ThemeConfig, Typography } from 'schema'; - -import { useAppSelector } from '@/store/hooks'; -import templateMap from '@/templates/templateMap'; -import { generateThemeStyles, generateTypographyStyles } from '@/utils/styles'; -import { PageProps } from '@/utils/template'; - -import styles from './Page.module.scss'; - -type Props = PageProps & { - showPageNumbers?: boolean; -}; - -const Page: React.FC<Props> = ({ page, showPageNumbers = false }) => { - const { t } = useTranslation(); - - const resume = useAppSelector((state) => state.resume.present); - const breakLine: boolean = useAppSelector((state) => state.build.page.breakLine); - - const theme: ThemeConfig = get(resume, 'metadata.theme'); - const customCSS: CustomCSS = get(resume, 'metadata.css'); - const template: string = get(resume, 'metadata.template'); - const typography: Typography = get(resume, 'metadata.typography'); - const pageConfig: PageConfig = get(resume, 'metadata.page', {} as PageConfig); - - const themeCSS = useMemo(() => !isEmpty(theme) && generateThemeStyles(theme), [theme]); - const typographyCSS = useMemo(() => !isEmpty(typography) && generateTypographyStyles(typography), [typography]); - const TemplatePage: React.FC<PageProps> | null = useMemo(() => templateMap[template].component, [template]); - - return ( - <div className={styles.container} data-page={page + 1} data-format={pageConfig?.format || 'A4'}> - <div - className={clsx({ - reset: true, - [styles.page]: true, - [styles.break]: breakLine, - [styles['format-letter']]: pageConfig?.format === 'Letter', - [css(themeCSS)]: true, - [css(typographyCSS)]: true, - [css(customCSS.value)]: customCSS.visible, - })} - > - {TemplatePage && <TemplatePage page={page} />} - </div> - - {showPageNumbers && <h4 className={styles.pageNumber}>{`${t('builder.common.glossary.page')} ${page + 1}`}</h4>} - </div> - ); -}; - -export default Page; diff --git a/client/components/build/LeftSidebar/LeftSidebar.module.scss b/client/components/build/LeftSidebar/LeftSidebar.module.scss deleted file mode 100644 index d335048b..00000000 --- a/client/components/build/LeftSidebar/LeftSidebar.module.scss +++ /dev/null @@ -1,45 +0,0 @@ -.container { - @apply h-screen w-[95vw] md:w-[70vw] lg:w-[50vw] xl:w-[30vw] 2xl:w-[28vw]; - @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-zinc-100 shadow dark:bg-zinc-900; - @apply flex flex-col items-center justify-between; - - hr { - @apply mt-2; - } - - > div { - @apply grid gap-2; - } - - .sections svg { - @apply opacity-75 transition-opacity hover:opacity-100; - } - } - - main { - @apply overflow-y-scroll p-4; - @apply absolute inset-y-0 left-12 right-0 md:left-16; - - > section { - @apply grid gap-4; - @apply pt-5 pb-7 first:pt-0; - @apply border-b border-zinc-900/10 last:border-b-0 dark:border-zinc-100/10; - - hr { - @apply my-2; - } - } - - -ms-overflow-style: none; - - &::-webkit-scrollbar { - display: none; - } - } -} diff --git a/client/components/build/LeftSidebar/LeftSidebar.tsx b/client/components/build/LeftSidebar/LeftSidebar.tsx deleted file mode 100644 index 47cbdf6b..00000000 --- a/client/components/build/LeftSidebar/LeftSidebar.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import { Add, Star } from '@mui/icons-material'; -import { Button, Divider, IconButton, SwipeableDrawer, Tooltip, useMediaQuery, useTheme } from '@mui/material'; -import cloneDeep from 'lodash/cloneDeep'; -import get from 'lodash/get'; -import Link from 'next/link'; -import { useTranslation } from 'next-i18next'; -import React, { ReactComponentElement, useMemo } from 'react'; -import { Section as SectionRecord } from 'schema'; -import { validate } from 'uuid'; - -import Icon from '@/components/shared/Icon'; -import { getCustomSections, getSectionsByType, left } from '@/config/sections'; -import { setSidebarState } from '@/store/build/buildSlice'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { addSection } from '@/store/resume/resumeSlice'; - -import styles from './LeftSidebar.module.scss'; -import Section from './sections/Section'; - -const LeftSidebar = () => { - const theme = useTheme(); - - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const isDesktop = useMediaQuery(theme.breakpoints.up('lg')); - - const sections = useAppSelector((state) => state.resume.present.sections); - - const { open } = useAppSelector((state) => state.build.sidebar.left); - - const customSections = useMemo(() => getCustomSections(sections), [sections]); - - const handleOpen = () => dispatch(setSidebarState({ sidebar: 'left', state: { open: true } })); - - const handleClose = () => dispatch(setSidebarState({ sidebar: 'left', state: { open: false } })); - - const handleClick = (id: string) => { - const elementId = validate(id) ? `#section-${id}` : `#${id}`; - const section = document.querySelector(elementId); - - if (section) { - section.scrollIntoView({ behavior: 'smooth', block: 'start' }); - } - }; - - const handleAddSection = () => { - const newSection: SectionRecord = { - name: 'Custom Section', - type: 'custom', - visible: true, - columns: 2, - items: [], - }; - - dispatch(addSection({ value: newSection, type: 'custom' })); - }; - - const sectionsList = () => { - const sectionsComponents: Array<ReactComponentElement<any>> = []; - - for (const item of left) { - const id = (item as any).id; - const component = (item as any).component; - const type = component.props.type; - const addMore = !!component.props.addMore; - - sectionsComponents.push( - <section key={id} id={id}> - {component} - </section>, - ); - - if (addMore) { - const additionalSections = getSectionsByType(sections, type); - const elements = []; - for (const element of additionalSections) { - const newId = element.id; - - const props = cloneDeep(component.props); - props.path = 'sections.' + newId; - props.name = element.name; - props.isDeletable = true; - props.addMore = false; - props.isDuplicated = true; - const newComponent = React.cloneElement(component, props); - - elements.push( - <section key={newId} id={`section-${newId}`}> - {newComponent} - </section>, - ); - } - sectionsComponents.push(...elements); - } - } - - return sectionsComponents; - }; - - return ( - <SwipeableDrawer - open={open} - anchor="left" - onOpen={handleOpen} - onClose={handleClose} - PaperProps={{ className: '!shadow-lg' }} - variant={isDesktop ? 'persistent' : 'temporary'} - > - <div className={styles.container}> - <nav className="overflow-y-auto"> - <div> - <Link href="/dashboard"> - <IconButton> - <Icon size={24} /> - </IconButton> - </Link> - <Divider /> - </div> - - <div className={styles.sections}> - {left.map(({ id, icon }) => ( - <Tooltip - arrow - key={id} - placement="right" - title={t(`builder.leftSidebar.sections.${id}.heading`, get(sections, `${id}.name`))} - > - <IconButton onClick={() => handleClick(id)}>{icon}</IconButton> - </Tooltip> - ))} - - {customSections.map(({ id }) => ( - <Tooltip - key={id} - title={t(`builder.leftSidebar.sections.${id}.heading`, get(sections, `${id}.name`))} - placement="right" - arrow - > - <IconButton onClick={() => id && handleClick(id)}> - <Star /> - </IconButton> - </Tooltip> - ))} - </div> - - <div /> - </nav> - - <main> - {sectionsList()} - - {customSections.map(({ id }) => ( - <section key={id} id={`section-${id}`}> - <Section path={`sections.${id}`} type="custom" isEditable isHideable isDeletable /> - </section> - ))} - - <div className="py-6 text-right"> - <Button fullWidth variant="outlined" startIcon={<Add />} onClick={handleAddSection}> - {t('builder.common.actions.add', { - token: t('builder.leftSidebar.sections.section.heading'), - })} - </Button> - </div> - </main> - </div> - </SwipeableDrawer> - ); -}; - -export default LeftSidebar; diff --git a/client/components/build/LeftSidebar/sections/Basics.tsx b/client/components/build/LeftSidebar/sections/Basics.tsx deleted file mode 100644 index 1f276619..00000000 --- a/client/components/build/LeftSidebar/sections/Basics.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { PhotoFilter } from '@mui/icons-material'; -import { Button, Divider, Popover } from '@mui/material'; -import { useTranslation } from 'next-i18next'; -import { useState } from 'react'; - -import Heading from '@/components/shared/Heading'; -import ResumeInput from '@/components/shared/ResumeInput'; - -import PhotoFilters from './PhotoFilters'; -import PhotoUpload from './PhotoUpload'; - -const Basics = () => { - const { t } = useTranslation(); - - const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null); - - const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - return ( - <> - <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"> - <div className="mx-auto"> - <PhotoUpload /> - </div> - - <div className="grid w-full gap-2 sm:col-span-2"> - <ResumeInput label={t('builder.leftSidebar.sections.basics.name.label')} path="basics.name" /> - - <Button variant="outlined" startIcon={<PhotoFilter />} onClick={handleClick}> - {t('builder.leftSidebar.sections.basics.actions.photo-filters')} - </Button> - - <Popover - open={Boolean(anchorEl)} - anchorEl={anchorEl} - onClose={handleClose} - anchorOrigin={{ - vertical: 'bottom', - horizontal: 'center', - }} - transformOrigin={{ - vertical: 'top', - horizontal: 'center', - }} - > - <PhotoFilters /> - </Popover> - </div> - </div> - - <ResumeInput - type="date" - label={t('builder.leftSidebar.sections.basics.birthdate.label')} - path="basics.birthdate" - className="sm:col-span-2" - /> - <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('builder.leftSidebar.sections.basics.headline.label')} - path="basics.headline" - className="sm:col-span-2" - /> - <ResumeInput - type="textarea" - label={t('builder.common.form.summary.label')} - path="basics.summary" - className="sm:col-span-2" - markdownSupported - /> - </div> - </> - ); -}; - -export default Basics; diff --git a/client/components/build/LeftSidebar/sections/Location.tsx b/client/components/build/LeftSidebar/sections/Location.tsx deleted file mode 100644 index cced13d8..00000000 --- a/client/components/build/LeftSidebar/sections/Location.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { useTranslation } from 'next-i18next'; - -import Heading from '@/components/shared/Heading'; -import ResumeInput from '@/components/shared/ResumeInput'; - -const Location = () => { - const { t } = useTranslation(); - - return ( - <> - <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('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('builder.leftSidebar.sections.location.postal-code.label')} - path="basics.location.postalCode" - /> - </div> - </> - ); -}; - -export default Location; diff --git a/client/components/build/LeftSidebar/sections/PhotoFilters.tsx b/client/components/build/LeftSidebar/sections/PhotoFilters.tsx deleted file mode 100644 index e2abac02..00000000 --- a/client/components/build/LeftSidebar/sections/PhotoFilters.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { Circle, Square, SquareRounded } from '@mui/icons-material'; -import { Checkbox, Divider, FormControlLabel, Slider, ToggleButton, ToggleButtonGroup } from '@mui/material'; -import get from 'lodash/get'; -import { useTranslation } from 'next-i18next'; -import { Photo, PhotoShape } from 'schema'; - -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setResumeState } from '@/store/resume/resumeSlice'; - -const PhotoFilters = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const photo: Photo = useAppSelector((state) => get(state.resume.present, 'basics.photo')); - const size: number = get(photo, 'filters.size', 128); - const shape: PhotoShape = get(photo, 'filters.shape', 'square'); - const grayscale: boolean = get(photo, 'filters.grayscale', false); - const border: boolean = get(photo, 'filters.border', false); - - const handleChangeSize = (size: number | number[]) => - dispatch(setResumeState({ path: 'basics.photo.filters.size', value: size })); - - const handleChangeShape = (shape: PhotoShape) => - dispatch(setResumeState({ path: 'basics.photo.filters.shape', value: shape })); - - const handleSetGrayscale = (value: boolean) => - dispatch(setResumeState({ path: 'basics.photo.filters.grayscale', value })); - - const handleSetBorder = (value: boolean) => dispatch(setResumeState({ path: 'basics.photo.filters.border', value })); - - return ( - <div className="flex flex-col gap-2 p-5 dark:bg-zinc-900"> - <div> - <h4 className="font-medium">{t('builder.leftSidebar.sections.basics.photo-filters.size.heading')}</h4> - - <div className="mx-2"> - <Slider - min={32} - max={512} - step={2} - marks={[ - { value: 32, label: '32' }, - { value: 128, label: '128' }, - { value: 256, label: '256' }, - { value: 512, label: '512' }, - ]} - value={size} - onChange={(_, value: number | number[]) => handleChangeSize(value)} - /> - </div> - </div> - - <Divider /> - - <div> - <h4 className="font-medium">{t('builder.leftSidebar.sections.basics.photo-filters.effects.heading')}</h4> - - <div className="flex items-center"> - <FormControlLabel - label={t('builder.leftSidebar.sections.basics.photo-filters.effects.grayscale.label')} - control={ - <Checkbox color="secondary" checked={grayscale} onChange={(_, value) => handleSetGrayscale(value)} /> - } - /> - - <FormControlLabel - label={t('builder.leftSidebar.sections.basics.photo-filters.effects.border.label')} - control={<Checkbox color="secondary" checked={border} onChange={(_, value) => handleSetBorder(value)} />} - /> - </div> - </div> - - <Divider /> - - <div className="flex flex-col gap-2"> - <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"> - <Square fontSize="small" /> - </ToggleButton> - - <ToggleButton size="small" value="rounded-square" className="w-14"> - <SquareRounded fontSize="small" /> - </ToggleButton> - - <ToggleButton size="small" value="circle" className="w-14"> - <Circle fontSize="small" /> - </ToggleButton> - </ToggleButtonGroup> - </div> - </div> - ); -}; - -export default PhotoFilters; diff --git a/client/components/build/LeftSidebar/sections/PhotoUpload.tsx b/client/components/build/LeftSidebar/sections/PhotoUpload.tsx deleted file mode 100644 index f7710742..00000000 --- a/client/components/build/LeftSidebar/sections/PhotoUpload.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { Avatar, IconButton, Skeleton, Tooltip } from '@mui/material'; -import get from 'lodash/get'; -import isEmpty from 'lodash/isEmpty'; -import { useTranslation } from 'next-i18next'; -import React, { useRef } from 'react'; -import toast from 'react-hot-toast'; -import { useMutation } from 'react-query'; -import { Photo, Resume } from 'schema'; - -import { ServerError } from '@/services/axios'; -import { deletePhoto, DeletePhotoParams, uploadPhoto, UploadPhotoParams } from '@/services/resume'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setResumeState } from '@/store/resume/resumeSlice'; - -const FILE_UPLOAD_MAX_SIZE = 2000000; // 2 MB - -const PhotoUpload: React.FC = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const fileInputRef = useRef<HTMLInputElement>(null); - - const id: number = useAppSelector((state) => get(state.resume.present, 'id')); - const photo: Photo = useAppSelector((state) => get(state.resume.present, 'basics.photo')); - - const { mutateAsync: uploadMutation, isLoading } = useMutation<Resume, ServerError, UploadPhotoParams>(uploadPhoto); - - const { mutateAsync: deleteMutation } = useMutation<Resume, ServerError, DeletePhotoParams>(deletePhoto); - - const handleClick = async () => { - if (fileInputRef.current) { - if (!isEmpty(photo.url)) { - try { - await deleteMutation({ id }); - } finally { - dispatch(setResumeState({ path: 'basics.photo.url', value: '' })); - } - } else { - fileInputRef.current.click(); - } - - fileInputRef.current.value = ''; - } - }; - - const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => { - if (event.target.files && event.target.files[0]) { - const file = event.target.files[0]; - - if (file.size > FILE_UPLOAD_MAX_SIZE) { - toast.error(t('common.toast.error.upload-photo-size')); - return; - } - - const resume = await uploadMutation({ id, file }); - - dispatch(setResumeState({ path: 'basics.photo.url', value: get(resume, 'basics.photo.url', '') })); - } - }; - - return ( - <IconButton onClick={handleClick}> - {isLoading ? ( - <Skeleton variant="circular" width={96} height={96} /> - ) : ( - <Tooltip - title={ - isEmpty(photo.url) - ? (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} /> - </Tooltip> - )} - - <input hidden type="file" ref={fileInputRef} onChange={handleChange} accept="image/*" /> - </IconButton> - ); -}; - -export default PhotoUpload; diff --git a/client/components/build/LeftSidebar/sections/Profiles.tsx b/client/components/build/LeftSidebar/sections/Profiles.tsx deleted file mode 100644 index 770e1657..00000000 --- a/client/components/build/LeftSidebar/sections/Profiles.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Add } from '@mui/icons-material'; -import { Button } from '@mui/material'; -import { useTranslation } from 'next-i18next'; -import { ListItem } from 'schema'; - -import Heading from '@/components/shared/Heading'; -import List from '@/components/shared/List'; -import { useAppDispatch } from '@/store/hooks'; -import { setModalState } from '@/store/modal/modalSlice'; -import { duplicateItem } from '@/store/resume/resumeSlice'; - -const Profiles = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const handleAdd = () => { - dispatch(setModalState({ modal: 'builder.sections.profile', state: { open: true } })); - }; - - const handleEdit = (item: ListItem) => { - dispatch(setModalState({ modal: 'builder.sections.profile', state: { open: true, payload: { item } } })); - }; - - const handleDuplicate = (item: ListItem) => { - dispatch(duplicateItem({ path: 'basics.profiles', value: item })); - }; - - return ( - <> - <Heading path="sections.profiles" name={t('builder.leftSidebar.sections.profiles.heading')} /> - - <List - path="basics.profiles" - titleKey="username" - subtitleKey="network" - onEdit={handleEdit} - onDuplicate={handleDuplicate} - /> - - <footer className="flex justify-end"> - <Button variant="outlined" startIcon={<Add />} onClick={handleAdd}> - {t('builder.common.actions.add', { - token: t('builder.leftSidebar.sections.profiles.heading', { count: 1 }), - })} - </Button> - </footer> - </> - ); -}; - -export default Profiles; diff --git a/client/components/build/LeftSidebar/sections/Section.tsx b/client/components/build/LeftSidebar/sections/Section.tsx deleted file mode 100644 index baba6507..00000000 --- a/client/components/build/LeftSidebar/sections/Section.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { Add } from '@mui/icons-material'; -import { Button } from '@mui/material'; -import clsx from 'clsx'; -import get from 'lodash/get'; -import { useTranslation } from 'next-i18next'; -import { ListItem, Section as SectionRecord, SectionType } from 'schema'; -import { validate } from 'uuid'; - -import Heading from '@/components/shared/Heading'; -import List from '@/components/shared/List'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { ModalName, setModalState } from '@/store/modal/modalSlice'; -import { duplicateItem, duplicateSection } from '@/store/resume/resumeSlice'; - -import SectionSettings from './SectionSettings'; - -type Props = { - path: `sections.${string}`; - type?: SectionType; - name?: string; - titleKey?: string; - subtitleKey?: string; - isEditable?: boolean; - isHideable?: boolean; - isDeletable?: boolean; - addMore?: boolean; - isDuplicated?: boolean; -}; - -const Section: React.FC<Props> = ({ - path, - name = 'Section Name', - type = 'basic', - titleKey = 'title', - subtitleKey = 'subtitle', - isEditable = false, - isHideable = false, - isDeletable = false, - addMore = false, - isDuplicated = false, -}) => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const heading = useAppSelector<string>((state) => get(state.resume.present, `${path}.name`, name)); - const visibility = useAppSelector<boolean>((state) => get(state.resume.present, `${path}.visible`, true)); - - const handleAdd = () => { - const modal: ModalName = `builder.sections.${type}`; - - dispatch(setModalState({ modal, state: { open: true, payload: { path } } })); - }; - - const handleEdit = (item: ListItem) => { - const id = path.split('.')[1]; - let modal: ModalName = validate(id) ? 'builder.sections.custom' : `builder.${path}`; - - const payload = validate(id) ? { path, item } : { item }; - - if (isDuplicated) { - modal = `builder.sections.${type}`; - payload.path = path; - } - - dispatch(setModalState({ modal, state: { open: true, payload } })); - }; - - const handleDuplicate = (item: ListItem) => dispatch(duplicateItem({ path: `${path}.items`, value: item })); - - const handleDuplicateSection = () => { - const newSection: SectionRecord = { - name: `${heading}`, - type: type, - visible: true, - columns: 2, - items: [], - isDuplicated: true, - }; - - dispatch(duplicateSection({ value: newSection, type })); - }; - - return ( - <> - <Heading path={path} name={name} isEditable={isEditable} isHideable={isHideable} isDeletable={isDeletable} /> - - <List - path={`${path}.items`} - titleKey={titleKey} - subtitleKey={subtitleKey} - onEdit={handleEdit} - onDuplicate={handleDuplicate} - className={clsx({ 'opacity-50': !visibility })} - /> - - <footer className="flex items-center justify-between"> - <SectionSettings path={path} /> - - <Button variant="outlined" startIcon={<Add />} onClick={handleAdd}> - {t('builder.common.actions.add', { - token: t(`builder.leftSidebar.${path}.heading`, { defaultValue: heading }), - })} - </Button> - </footer> - - {addMore ? ( - <div className="py-6 text-right"> - <Button fullWidth variant="outlined" startIcon={<Add />} onClick={handleDuplicateSection}> - {t('builder.common.actions.duplicate')} - </Button> - </div> - ) : ( - <></> - )} - </> - ); -}; - -export default Section; diff --git a/client/components/build/LeftSidebar/sections/SectionSettings.tsx b/client/components/build/LeftSidebar/sections/SectionSettings.tsx deleted file mode 100644 index a41bea57..00000000 --- a/client/components/build/LeftSidebar/sections/SectionSettings.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { ViewWeek } from '@mui/icons-material'; -import { ButtonBase, Popover, ToggleButton, ToggleButtonGroup, Tooltip } from '@mui/material'; -import get from 'lodash/get'; -import { useTranslation } from 'next-i18next'; -import { useState } from 'react'; - -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setResumeState } from '@/store/resume/resumeSlice'; - -type Props = { - path: string; -}; - -const SectionSettings: React.FC<Props> = ({ path }) => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null); - - const columns = useAppSelector<number>((state) => get(state.resume.present, `${path}.columns`, 2)); - - const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - const handleSetColumns = (index: number) => dispatch(setResumeState({ path: `${path}.columns`, value: index })); - - return ( - <div> - <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> - </Tooltip> - - <Popover - open={Boolean(anchorEl)} - anchorEl={anchorEl} - onClose={handleClose} - anchorOrigin={{ - vertical: 'bottom', - horizontal: 'left', - }} - > - <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) => ( - <ToggleButton key={index} value={index} size="small" className="w-12"> - {index} - </ToggleButton> - ))} - </ToggleButtonGroup> - </div> - </Popover> - </div> - ); -}; - -export default SectionSettings; diff --git a/client/components/build/RightSidebar/RightSidebar.module.scss b/client/components/build/RightSidebar/RightSidebar.module.scss deleted file mode 100644 index cc53fb82..00000000 --- a/client/components/build/RightSidebar/RightSidebar.module.scss +++ /dev/null @@ -1,50 +0,0 @@ -.container { - @apply h-screen w-[95vw] md:w-[70vw] lg:w-[50vw] xl:w-[30vw] 2xl:w-[28vw]; - @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-zinc-100 shadow dark:bg-zinc-900; - @apply flex flex-col items-center justify-between; - - hr { - @apply mt-2; - } - - > div { - @apply grid gap-2; - } - - .sections svg { - @apply opacity-75 transition-opacity hover:opacity-100; - } - } - - main { - @apply overflow-y-scroll p-4; - @apply absolute inset-y-0 right-12 left-0 md:right-16; - - > section { - @apply grid gap-4; - @apply pt-5 pb-7 first:pt-0; - @apply border-b border-zinc-900/10 last:border-b-0 dark:border-zinc-50/10; - - hr { - @apply my-2; - } - } - - -ms-overflow-style: none; - - &::-webkit-scrollbar { - display: none; - } - } - - .footer { - @apply flex flex-col items-center justify-center gap-3 py-4; - @apply text-center text-xs leading-normal opacity-40; - } -} diff --git a/client/components/build/RightSidebar/RightSidebar.tsx b/client/components/build/RightSidebar/RightSidebar.tsx deleted file mode 100644 index 9c637015..00000000 --- a/client/components/build/RightSidebar/RightSidebar.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { Divider, IconButton, SwipeableDrawer, Tooltip, useMediaQuery, useTheme } from '@mui/material'; -import capitalize from 'lodash/capitalize'; -import { useTranslation } from 'next-i18next'; - -import Avatar from '@/components/shared/Avatar'; -import Footer from '@/components/shared/Footer'; -import { right } from '@/config/sections'; -import { setSidebarState } from '@/store/build/buildSlice'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; - -import styles from './RightSidebar.module.scss'; - -const RightSidebar = () => { - const theme = useTheme(); - - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const isDesktop = useMediaQuery(theme.breakpoints.up('lg')); - - const { open } = useAppSelector((state) => state.build.sidebar.right); - - const handleOpen = () => dispatch(setSidebarState({ sidebar: 'right', state: { open: true } })); - - const handleClose = () => dispatch(setSidebarState({ sidebar: 'right', state: { open: false } })); - - const handleClick = (id: string) => { - const section = document.querySelector(`#${id}`); - - if (section) { - section.scrollIntoView({ behavior: 'smooth', block: 'start' }); - } - }; - - return ( - <SwipeableDrawer - open={open} - anchor="right" - onOpen={handleOpen} - onClose={handleClose} - PaperProps={{ className: '!shadow-lg' }} - variant={isDesktop ? 'persistent' : 'temporary'} - > - <div className={styles.container}> - <nav className="overflow-y-auto"> - <div> - <Avatar size={24} /> - <Divider /> - </div> - - <div className={styles.sections}> - {right.map(({ id, icon }) => ( - <Tooltip - key={id} - arrow - placement="right" - title={t(`builder.rightSidebar.sections.${id}.heading`, { defaultValue: capitalize(id) })} - > - <IconButton onClick={() => handleClick(id)}>{icon}</IconButton> - </Tooltip> - ))} - </div> - - <div /> - </nav> - - <main> - {right.map(({ id, component }) => ( - <section key={id} id={id}> - {component} - </section> - ))} - - <footer className={styles.footer}> - <Footer /> - - <div>v{process.env.appVersion}</div> - </footer> - </main> - </div> - </SwipeableDrawer> - ); -}; - -export default RightSidebar; diff --git a/client/components/build/RightSidebar/sections/CustomCSS.tsx b/client/components/build/RightSidebar/sections/CustomCSS.tsx deleted file mode 100644 index 875b901f..00000000 --- a/client/components/build/RightSidebar/sections/CustomCSS.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import Editor from '@monaco-editor/react'; -import { useTheme } from '@mui/material'; -import clsx from 'clsx'; -import get from 'lodash/get'; -import { useTranslation } from 'next-i18next'; -import React from 'react'; -import { CustomCSS as CustomCSSType } from 'schema'; - -import Heading from '@/components/shared/Heading'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setResumeState } from '@/store/resume/resumeSlice'; - -const CustomCSS = () => { - const theme = useTheme(); - - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const customCSS: CustomCSSType = useAppSelector((state) => - get(state.resume.present, 'metadata.css', {} as CustomCSSType), - ); - - const handleChange = (value: string | undefined) => { - dispatch(setResumeState({ path: 'metadata.css.value', value })); - }; - - return ( - <> - <Heading path="metadata.css" name={t('builder.rightSidebar.sections.css.heading')} isHideable /> - - <Editor - height="200px" - language="css" - value={customCSS.value} - onChange={handleChange} - className={clsx({ 'opacity-50': !customCSS.visible })} - theme={theme.palette.mode === 'dark' ? 'vs-dark' : 'light'} - options={{ - minimap: { enabled: false }, - overviewRulerLanes: 0, - scrollBeyondLastColumn: 5, - overviewRulerBorder: false, - scrollBeyondLastLine: true, - }} - /> - </> - ); -}; - -export default CustomCSS; diff --git a/client/components/build/RightSidebar/sections/Export.tsx b/client/components/build/RightSidebar/sections/Export.tsx deleted file mode 100644 index 057913a3..00000000 --- a/client/components/build/RightSidebar/sections/Export.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { PictureAsPdf, Schema } from '@mui/icons-material'; -import { List, ListItem, ListItemButton, ListItemText } from '@mui/material'; -import dayjs from 'dayjs'; -import get from 'lodash/get'; -import pick from 'lodash/pick'; -import { useTranslation } from 'next-i18next'; -import { useMutation } from 'react-query'; - -import Heading from '@/components/shared/Heading'; -import { ServerError } from '@/services/axios'; -import { printResumeAsPdf, PrintResumeAsPdfParams } from '@/services/printer'; -import { useAppSelector } from '@/store/hooks'; - -const Export = () => { - const { t } = useTranslation(); - - const resume = useAppSelector((state) => state.resume.present); - - const { mutateAsync, isLoading } = useMutation<string, ServerError, PrintResumeAsPdfParams>(printResumeAsPdf); - - const pdfListItemText = { - normal: { - primary: t('builder.rightSidebar.sections.export.pdf.normal.primary'), - secondary: t('builder.rightSidebar.sections.export.pdf.normal.secondary'), - }, - loading: { - 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')).nanoid; - const download = (await import('downloadjs')).default; - - const redactedResume = pick(resume, ['basics', 'sections', 'metadata', 'public']); - const jsonString = JSON.stringify(redactedResume, null, 4); - const jsonBlob = new Blob([jsonString], { type: 'application/json;charset=utf-8' }); - const filename = `RxResume_JSONExport_${nanoid()}.json`; - - download(jsonBlob, filename); - }; - - const handleExportPDF = async () => { - const download = (await import('downloadjs')).default; - - const slug = get(resume, 'slug'); - const username = get(resume, 'user.username'); - const updatedAt = get(resume, 'updatedAt'); - - const url = await mutateAsync({ username, slug, lastUpdated: dayjs(updatedAt).unix().toString() }); - - download(url); - }; - - return ( - <> - <Heading path="metadata.export" name={t('builder.rightSidebar.sections.export.heading')} /> - - <List sx={{ padding: 0 }}> - <ListItem sx={{ padding: 0 }}> - <ListItemButton className="gap-6" onClick={handleExportJSON}> - <Schema /> - - <ListItemText - primary={t('builder.rightSidebar.sections.export.json.primary')} - secondary={t('builder.rightSidebar.sections.export.json.secondary')} - /> - </ListItemButton> - </ListItem> - - <ListItem sx={{ padding: 0 }}> - <ListItemButton className="gap-6" onClick={handleExportPDF} disabled={isLoading}> - <PictureAsPdf /> - - <ListItemText {...(isLoading ? pdfListItemText.loading : pdfListItemText.normal)} /> - </ListItemButton> - </ListItem> - </List> - </> - ); -}; - -export default Export; diff --git a/client/components/build/RightSidebar/sections/Layout.module.scss b/client/components/build/RightSidebar/sections/Layout.module.scss deleted file mode 100644 index 333de3fd..00000000 --- a/client/components/build/RightSidebar/sections/Layout.module.scss +++ /dev/null @@ -1,43 +0,0 @@ -.page { - @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; - @apply rotate-0 hover:rotate-90; - @apply transition-[opacity,transform]; - } - - .container { - @apply grid grid-cols-2 gap-2; - } - - .heading { - @apply relative z-10 my-3; - @apply text-xs font-semibold; - } -} - -.column { - @apply relative w-full px-4; - - .heading { - @apply relative z-10 my-3; - @apply text-xs font-semibold; - } - - .base { - @apply absolute inset-0 w-4/5; - @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-zinc-900/90 text-zinc-50 dark:bg-zinc-50/90 dark:text-zinc-900; - - &.disabled { - @apply opacity-60; - } -} diff --git a/client/components/build/RightSidebar/sections/Layout.tsx b/client/components/build/RightSidebar/sections/Layout.tsx deleted file mode 100644 index b3a122ef..00000000 --- a/client/components/build/RightSidebar/sections/Layout.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { DragDropContext, Draggable, DraggableLocation, Droppable, DropResult } from '@hello-pangea/dnd'; -import { Add, Close, Restore } from '@mui/icons-material'; -import { Button, IconButton, Tooltip } from '@mui/material'; -import clsx from 'clsx'; -import cloneDeep from 'lodash/cloneDeep'; -import get from 'lodash/get'; -import { useTranslation } from 'next-i18next'; - -import Heading from '@/components/shared/Heading'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { addPage, deletePage, setResumeState } from '@/store/resume/resumeSlice'; - -import styles from './Layout.module.scss'; - -const getIndices = (location: DraggableLocation) => ({ - page: +location.droppableId.split('.')[0], - column: +location.droppableId.split('.')[1], - section: +location.index, -}); - -const Layout = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const layout = useAppSelector((state) => state.resume.present.metadata.layout); - const resumeSections = useAppSelector((state) => state.resume.present.sections); - - const onDragEnd = (dropResult: DropResult) => { - const { source: srcLoc, destination: destLoc } = dropResult; - - if (!destLoc) return; - - const newLayout = cloneDeep(layout); - - const srcIndex = getIndices(srcLoc); - const destIndex = getIndices(destLoc); - const section = layout[srcIndex.page][srcIndex.column][srcIndex.section]; - - // Remove item at source - newLayout[srcIndex.page][srcIndex.column].splice(srcIndex.section, 1); - - // Insert item at destination - newLayout[destIndex.page][destIndex.column].splice(destIndex.section, 0, section); - - dispatch(setResumeState({ path: 'metadata.layout', value: newLayout })); - }; - - const handleAddPage = () => dispatch(addPage()); - - const handleDeletePage = (page: number) => dispatch(deletePage({ page })); - - const handleResetLayout = () => { - for (let i = layout.length - 1; i > 0; i--) { - handleDeletePage(i); - } - }; - - return ( - <> - <Heading - path="metadata.layout" - name={t('builder.rightSidebar.sections.layout.heading')} - action={ - <Tooltip title={t('builder.rightSidebar.sections.layout.tooltip.reset-layout')}> - <IconButton onClick={handleResetLayout}> - <Restore /> - </IconButton> - </Tooltip> - } - /> - - <DragDropContext onDragEnd={onDragEnd}> - {/* Pages */} - {layout.map((columns, pageIndex) => ( - <div key={pageIndex} className={styles.page}> - <div className="flex items-center justify-between pr-3"> - <p className={styles.heading}> - {t('builder.common.glossary.page')} {pageIndex + 1} - </p> - - <div className={clsx(styles.delete, { hidden: pageIndex === 0 })}> - <Tooltip - title={ - t('builder.common.actions.delete', { - token: t('builder.common.glossary.page'), - }) as string - } - > - <IconButton size="small" onClick={() => handleDeletePage(pageIndex)}> - <Close fontSize="small" /> - </IconButton> - </Tooltip> - </div> - </div> - - <div className={styles.container}> - {/* Sections */} - {columns.map((sections, columnIndex) => { - const index = `${pageIndex}.${columnIndex}`; - - return ( - <Droppable key={index} droppableId={index}> - {(provided) => ( - <div ref={provided.innerRef} className={styles.column} {...provided.droppableProps}> - <p className={styles.heading}>{columnIndex ? 'Sidebar' : 'Main'}</p> - - <div className={styles.base} /> - - {/* Sections */} - {sections.map((sectionId, sectionIndex) => ( - <Draggable key={sectionId} draggableId={sectionId} index={sectionIndex}> - {(provided) => ( - <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}> - <div - className={clsx(styles.section, { - [styles.disabled]: !get(resumeSections, `${sectionId}.visible`, true), - })} - > - {get(resumeSections, `${sectionId}.name`, '') as string} - </div> - </div> - )} - </Draggable> - ))} - - {provided.placeholder} - </div> - )} - </Droppable> - ); - })} - </div> - </div> - ))} - - <div className="flex items-center justify-end"> - <Button variant="outlined" startIcon={<Add />} onClick={handleAddPage}> - {t('builder.common.actions.add', { token: t('builder.common.glossary.page') })} - </Button> - </div> - </DragDropContext> - </> - ); -}; - -export default Layout; diff --git a/client/components/build/RightSidebar/sections/Links.module.scss b/client/components/build/RightSidebar/sections/Links.module.scss deleted file mode 100644 index 975e9fe2..00000000 --- a/client/components/build/RightSidebar/sections/Links.module.scss +++ /dev/null @@ -1,20 +0,0 @@ -.container { - @apply grid gap-4; - - .section { - @apply grid gap-2 rounded p-6; - @apply bg-zinc-100 dark:bg-zinc-900; - - h2 { - @apply inline-flex items-center gap-2 text-base font-medium; - } - - p { - @apply mb-3 text-xs leading-loose; - } - } - - a { - @apply hover:no-underline; - } -} diff --git a/client/components/build/RightSidebar/sections/Links.tsx b/client/components/build/RightSidebar/sections/Links.tsx deleted file mode 100644 index 3688b814..00000000 --- a/client/components/build/RightSidebar/sections/Links.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { BugReport, Coffee, GitHub, Link, Savings } from '@mui/icons-material'; -import { Button } from '@mui/material'; -import { useTranslation } from 'next-i18next'; - -import Heading from '@/components/shared/Heading'; -import { DOCS_URL, DONATION_URL, GITHUB_ISSUES_URL, GITHUB_URL, REDDIT_URL } from '@/constants/index'; - -import styles from './Links.module.scss'; - -const Links = () => { - const { t } = useTranslation(); - - return ( - <> - <Heading path="metadata.links" name={t('builder.rightSidebar.sections.links.heading')} /> - - <div className={styles.container}> - <div className={styles.section}> - <h2> - <Savings fontSize="small" /> - {t('builder.rightSidebar.sections.links.donate.heading')} - </h2> - - <p>{t('builder.rightSidebar.sections.links.donate.body')}</p> - - <a href={DONATION_URL} target="_blank" rel="noreferrer"> - <Button startIcon={<Coffee />}>{t('builder.rightSidebar.sections.links.donate.button')}</Button> - </a> - </div> - - <div className={styles.section}> - <h2> - <BugReport fontSize="small" /> - {t('builder.rightSidebar.sections.links.bugs-features.heading')} - </h2> - - <p>{t('builder.rightSidebar.sections.links.bugs-features.body')}</p> - - <a href={GITHUB_ISSUES_URL} target="_blank" rel="noreferrer"> - <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('builder.rightSidebar.sections.links.github')} - </Button> - </a> - - <a href={REDDIT_URL} target="_blank" rel="noreferrer"> - <Button variant="text" startIcon={<Link />}> - {t('builder.rightSidebar.sections.links.reddit')} - </Button> - </a> - - <a href={DOCS_URL} target="_blank" rel="noreferrer"> - <Button variant="text" startIcon={<Link />}> - {t('builder.rightSidebar.sections.links.docs')} - </Button> - </a> - </div> - </div> - </> - ); -}; - -export default Links; diff --git a/client/components/build/RightSidebar/sections/Settings.tsx b/client/components/build/RightSidebar/sections/Settings.tsx deleted file mode 100644 index 11d9b3f0..00000000 --- a/client/components/build/RightSidebar/sections/Settings.tsx +++ /dev/null @@ -1,246 +0,0 @@ -import { Anchor, DeleteForever, Palette } from '@mui/icons-material'; -import { - Autocomplete, - List, - ListItem, - ListItemButton, - ListItemIcon, - ListItemText, - ListSubheader, - Switch, - TextField, -} from '@mui/material'; -import dayjs from 'dayjs'; -import get from 'lodash/get'; -import { useRouter } from 'next/router'; -import { useTranslation } from 'next-i18next'; -import { useMemo, useState } from 'react'; -import { useMutation } from 'react-query'; -import { DateConfig, PageConfig, Resume } from 'schema'; - -import Heading from '@/components/shared/Heading'; -import ThemeSwitch from '@/components/shared/ThemeSwitch'; -import { Language, languageMap, languages } from '@/config/languages'; -import { ServerError } from '@/services/axios'; -import queryClient from '@/services/react-query'; -import { loadSampleData, LoadSampleDataParams, resetResume, ResetResumeParams } from '@/services/resume'; -import { setTheme, togglePageBreakLine, togglePageOrientation } from '@/store/build/buildSlice'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setResumeState } from '@/store/resume/resumeSlice'; -import { dateFormatOptions } from '@/utils/date'; - -const Settings = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const { locale, ...router } = useRouter(); - - const [confirmReset, setConfirmReset] = useState(false); - - const resume = useAppSelector((state) => state.resume.present); - const theme = useAppSelector((state) => state.build.theme); - const pages = useAppSelector((state) => state.resume.present.metadata.layout); - const breakLine = useAppSelector((state) => state.build.page.breakLine); - const orientation = useAppSelector((state) => state.build.page.orientation); - - const id: number = useMemo(() => get(resume, 'id'), [resume]); - const slug: string = useMemo(() => get(resume, 'slug'), [resume]); - const username: string = useMemo(() => get(resume, 'user.username'), [resume]); - const dateConfig: DateConfig = useMemo(() => get(resume, 'metadata.date'), [resume]); - const pageConfig: PageConfig | undefined = useMemo(() => get(resume, 'metadata.page'), [resume]); - - const isDarkMode = useMemo(() => theme === 'dark', [theme]); - const exampleDateString = useMemo(() => `Eg. ${dayjs().format(dateConfig.format)}`, [dateConfig.format]); - const themeString = useMemo(() => (isDarkMode ? 'Matte Black Everything' : 'As bright as your future'), [isDarkMode]); - - const { mutateAsync: loadSampleDataMutation } = useMutation<Resume, ServerError, LoadSampleDataParams>( - loadSampleData, - ); - const { mutateAsync: resetResumeMutation } = useMutation<Resume, ServerError, ResetResumeParams>(resetResume); - - const handleSetTheme = (value: boolean) => dispatch(setTheme({ theme: value ? 'dark' : 'light' })); - - const handleChangePageFormat = (value: PageConfig['format'] | null) => - dispatch(setResumeState({ path: 'metadata.page.format', value })); - - const handleChangeDateFormat = (value: string | null) => - dispatch(setResumeState({ path: 'metadata.date.format', value })); - - const handleChangeLanguage = (value: Language | null) => { - const { pathname, asPath, query, push } = router; - const code = value?.code || 'en'; - - document.cookie = `NEXT_LOCALE=${code}; path=/; expires=2147483647`; - dispatch(setResumeState({ path: 'metadata.locale', value: code })); - - push({ pathname, query }, asPath, { locale: code }); - }; - - const handleLoadSampleData = async () => { - await loadSampleDataMutation({ id }); - - queryClient.invalidateQueries(`resume/${username}/${slug}`); - }; - - const handleResetResume = async () => { - if (!confirmReset) { - return setConfirmReset(true); - } - - await resetResumeMutation({ id }); - await queryClient.invalidateQueries(`resume/${username}/${slug}`); - - setConfirmReset(false); - }; - - return ( - <> - <Heading path="metadata.settings" name={t('builder.rightSidebar.sections.settings.heading')} /> - - <List disablePadding> - {/* Global Settings */} - <> - <ListSubheader disableSticky className="rounded"> - {t('builder.rightSidebar.sections.settings.global.heading')} - </ListSubheader> - - <ListItem> - <ListItemIcon> - <Palette /> - </ListItemIcon> - <ListItemText - primary={t('builder.rightSidebar.sections.settings.global.theme.primary')} - secondary={themeString} - /> - <ThemeSwitch checked={isDarkMode} onChange={(_, value: boolean) => handleSetTheme(value)} /> - </ListItem> - - <ListItem className="flex-col"> - <ListItemText - className="w-full" - primary={t('builder.rightSidebar.sections.settings.global.date.primary')} - secondary={t('builder.rightSidebar.sections.settings.global.date.secondary')} - /> - <Autocomplete<string, false, true, false> - disableClearable - className="my-2 w-full" - options={dateFormatOptions} - value={dateConfig.format} - onChange={(_, value) => handleChangeDateFormat(value)} - renderInput={(params) => <TextField {...params} helperText={exampleDateString} />} - /> - </ListItem> - - <ListItem className="flex-col"> - <ListItemText - className="w-full" - primary={t('builder.rightSidebar.sections.settings.global.language.primary')} - secondary={t('builder.rightSidebar.sections.settings.global.language.secondary')} - /> - <Autocomplete<Language, false, true, false> - disableClearable - className="my-2 w-full" - options={languages} - value={languageMap[locale ?? 'en']} - isOptionEqualToValue={(a, b) => a.code === b.code} - onChange={(_, value) => handleChangeLanguage(value)} - renderInput={(params) => <TextField {...params} />} - getOptionLabel={(language) => { - if (language.localName) { - return `${language.name} (${language.localName})`; - } - - return language.name; - }} - /> - </ListItem> - </> - - {/* Page Settings */} - <> - <ListSubheader disableSticky className="rounded"> - {t('builder.rightSidebar.sections.settings.page.heading')} - </ListSubheader> - - <ListItem className="flex-col"> - <ListItemText - className="w-full" - 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 - defaultValue="A4" - className="my-2 w-full" - options={['A4', 'Letter']} - value={pageConfig?.format || 'A4'} - renderInput={(params) => <TextField {...params} />} - onChange={(_, value) => handleChangePageFormat(value)} - /> - </ListItem> - - <ListItem> - <ListItemText - primary={t('builder.rightSidebar.sections.settings.page.orientation.primary')} - secondary={ - pages.length === 1 - ? t('builder.rightSidebar.sections.settings.page.orientation.disabled') - : t('builder.rightSidebar.sections.settings.page.orientation.secondary') - } - /> - <Switch - color="secondary" - disabled={pages.length === 1} - checked={orientation === 'horizontal'} - onChange={() => dispatch(togglePageOrientation())} - /> - </ListItem> - - <ListItem> - <ListItemText - 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> - </> - - {/* Resume Settings */} - <> - <ListSubheader disableSticky className="rounded"> - {t('builder.rightSidebar.sections.settings.resume.heading')} - </ListSubheader> - - <ListItem disableGutters> - <ListItemButton onClick={handleLoadSampleData}> - <ListItemIcon> - <Anchor /> - </ListItemIcon> - <ListItemText - primary={t('builder.rightSidebar.sections.settings.resume.sample.primary')} - secondary={t('builder.rightSidebar.sections.settings.resume.sample.secondary')} - /> - </ListItemButton> - </ListItem> - - <ListItem disableGutters> - <ListItemButton onClick={handleResetResume}> - <ListItemIcon> - <DeleteForever /> - </ListItemIcon> - <ListItemText - primary={ - confirmReset ? 'Are you sure?' : t('builder.rightSidebar.sections.settings.resume.reset.primary') - } - secondary={t('builder.rightSidebar.sections.settings.resume.reset.secondary')} - /> - </ListItemButton> - </ListItem> - </> - </List> - </> - ); -}; - -export default Settings; diff --git a/client/components/build/RightSidebar/sections/Sharing.tsx b/client/components/build/RightSidebar/sections/Sharing.tsx deleted file mode 100644 index c7683efb..00000000 --- a/client/components/build/RightSidebar/sections/Sharing.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { CopyAll } from '@mui/icons-material'; -import { Checkbox, FormControlLabel, IconButton, List, ListItem, ListItemText, Switch, TextField } from '@mui/material'; -import get from 'lodash/get'; -import { useTranslation } from 'next-i18next'; -import { useMemo, useState } from 'react'; -import toast from 'react-hot-toast'; - -import Heading from '@/components/shared/Heading'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setResumeState } from '@/store/resume/resumeSlice'; -import getResumeUrl from '@/utils/getResumeUrl'; - -const Sharing = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const [showShortUrl, setShowShortUrl] = useState(false); - - const resume = useAppSelector((state) => state.resume.present); - const isPublic = useMemo(() => get(resume, 'public'), [resume]); - const url = useMemo(() => getResumeUrl(resume, { withHost: true }), [resume]); - const shortUrl = useMemo(() => getResumeUrl(resume, { withHost: true, shortUrl: true }), [resume]); - - const handleSetVisibility = (value: boolean) => dispatch(setResumeState({ path: 'public', value })); - - const handleCopyToClipboard = async () => { - const text = showShortUrl ? shortUrl : url; - - await navigator.clipboard.writeText(text); - - toast.success(t('common.toast.success.resume-link-copied')); - }; - - return ( - <> - <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('builder.rightSidebar.sections.sharing.visibility.title')} - secondary={t('builder.rightSidebar.sections.sharing.visibility.subtitle')} - /> - <Switch color="secondary" checked={isPublic} onChange={(_, value) => handleSetVisibility(value)} /> - </div> - - <div className="mt-2 w-full"> - <TextField - disabled - fullWidth - value={showShortUrl ? shortUrl : url} - InputProps={{ - endAdornment: ( - <IconButton onClick={handleCopyToClipboard}> - <CopyAll /> - </IconButton> - ), - }} - /> - </div> - - <div className="mt-1 flex w-full"> - <FormControlLabel - label={t('builder.rightSidebar.sections.sharing.short-url.label')} - control={ - <Checkbox className="mr-1" checked={showShortUrl} onChange={(_, value) => setShowShortUrl(value)} /> - } - /> - </div> - </ListItem> - </List> - </> - ); -}; - -export default Sharing; diff --git a/client/components/build/RightSidebar/sections/Templates.module.scss b/client/components/build/RightSidebar/sections/Templates.module.scss deleted file mode 100644 index 6a4de7c5..00000000 --- a/client/components/build/RightSidebar/sections/Templates.module.scss +++ /dev/null @@ -1,22 +0,0 @@ -.container { - @apply grid grid-cols-2 gap-4; -} - -.template { - @apply grid text-center; - - .preview { - aspect-ratio: 1 / 1.4142; - - @apply relative grid rounded; - @apply border-2 border-transparent; - - &.selected { - @apply border-black dark:border-white; - } - } - - .label { - @apply mt-1 text-xs font-medium; - } -} diff --git a/client/components/build/RightSidebar/sections/Templates.tsx b/client/components/build/RightSidebar/sections/Templates.tsx deleted file mode 100644 index 38e36b13..00000000 --- a/client/components/build/RightSidebar/sections/Templates.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { ButtonBase } from '@mui/material'; -import clsx from 'clsx'; -import get from 'lodash/get'; -import Image from 'next/image'; -import { useTranslation } from 'next-i18next'; - -import Heading from '@/components/shared/Heading'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setResumeState } from '@/store/resume/resumeSlice'; -import templateMap, { TemplateMeta } from '@/templates/templateMap'; - -import styles from './Templates.module.scss'; - -const Templates = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const currentTemplate: string = useAppSelector((state) => get(state.resume.present, 'metadata.template')); - - const handleChange = (template: TemplateMeta) => { - dispatch(setResumeState({ path: 'metadata.template', value: template.id })); - }; - - return ( - <> - <Heading path="metadata.templates" name={t('builder.rightSidebar.sections.templates.heading')} /> - - <div className={styles.container}> - {Object.values(templateMap).map((template) => ( - <div key={template.id} className={styles.template}> - <div className={clsx(styles.preview, { [styles.selected]: template.id === currentTemplate })}> - <ButtonBase onClick={() => handleChange(template)}> - <Image - fill - priority - alt={template.name} - src={template.preview} - className="rounded-sm" - sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" - /> - </ButtonBase> - </div> - - <p className={styles.label}>{template.name}</p> - </div> - ))} - </div> - </> - ); -}; - -export default Templates; diff --git a/client/components/build/RightSidebar/sections/Theme.module.scss b/client/components/build/RightSidebar/sections/Theme.module.scss deleted file mode 100644 index 383c1bf9..00000000 --- a/client/components/build/RightSidebar/sections/Theme.module.scss +++ /dev/null @@ -1,8 +0,0 @@ -.container { - @apply grid gap-4 sm:grid-cols-2; -} - -.colorOptions { - @apply col-span-2 mb-4; - @apply grid grid-cols-8 justify-items-center gap-y-2; -} diff --git a/client/components/build/RightSidebar/sections/Theme.tsx b/client/components/build/RightSidebar/sections/Theme.tsx deleted file mode 100644 index a8174c4c..00000000 --- a/client/components/build/RightSidebar/sections/Theme.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import get from 'lodash/get'; -import { useTranslation } from 'next-i18next'; -import { ThemeConfig } from 'schema'; - -import ColorAvatar from '@/components/shared/ColorAvatar'; -import ColorPicker from '@/components/shared/ColorPicker'; -import Heading from '@/components/shared/Heading'; -import { colorOptions } from '@/config/colors'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setResumeState } from '@/store/resume/resumeSlice'; - -import styles from './Theme.module.scss'; - -const Theme = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const { background, text, primary } = useAppSelector<ThemeConfig>((state) => - get(state.resume.present, 'metadata.theme'), - ); - - const handleChange = (property: string, color: string) => { - dispatch(setResumeState({ path: `metadata.theme.${property}`, value: color[0] !== '#' ? `#${color}` : color })); - }; - - return ( - <> - <Heading path="metadata.theme" name={t('builder.rightSidebar.sections.theme.heading')} /> - - <div className={styles.container}> - <div className={styles.colorOptions}> - {colorOptions.map((color) => ( - <ColorAvatar key={color} color={color} onClick={(color) => handleChange('primary', color)} /> - ))} - </div> - - <ColorPicker - label={t('builder.rightSidebar.sections.theme.form.primary.label')} - color={primary} - className="col-span-2" - onChange={(color) => handleChange('primary', color)} - /> - <ColorPicker - label={t('builder.rightSidebar.sections.theme.form.background.label')} - color={background} - onChange={(color) => handleChange('background', color)} - /> - <ColorPicker - label={t('builder.rightSidebar.sections.theme.form.text.label')} - color={text} - onChange={(color) => handleChange('text', color)} - /> - </div> - </> - ); -}; - -export default Theme; diff --git a/client/components/build/RightSidebar/sections/Typography.module.scss b/client/components/build/RightSidebar/sections/Typography.module.scss deleted file mode 100644 index 6a70f62f..00000000 --- a/client/components/build/RightSidebar/sections/Typography.module.scss +++ /dev/null @@ -1,11 +0,0 @@ -.container { - @apply grid gap-4 xl:grid-cols-2; -} - -.subheading { - @apply mt-2 font-medium; -} - -.slider { - @apply px-6; -} diff --git a/client/components/build/RightSidebar/sections/Typography.tsx b/client/components/build/RightSidebar/sections/Typography.tsx deleted file mode 100644 index 9b84fb35..00000000 --- a/client/components/build/RightSidebar/sections/Typography.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { Autocomplete, Skeleton, Slider, TextField } from '@mui/material'; -import get from 'lodash/get'; -import isEmpty from 'lodash/isEmpty'; -import { useTranslation } from 'next-i18next'; -import { useQuery } from 'react-query'; -import { Font, TypeCategory, TypeProperty, Typography as TypographyType } from 'schema'; - -import Heading from '@/components/shared/Heading'; -import { FONTS_QUERY } from '@/constants/index'; -import { fetchFonts } from '@/services/fonts'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setResumeState } from '@/store/resume/resumeSlice'; - -import styles from './Typography.module.scss'; - -const TypographySkeleton: React.FC = () => ( - <> - <Skeleton variant="text" /> - <div className={styles.container}> - <Skeleton variant="rectangular" height={60} /> - <Skeleton variant="rectangular" height={60} /> - </div> - </> -); - -type WidgetProps = { - label: string; - category: TypeCategory; -}; - -const Widgets: React.FC<WidgetProps> = ({ label, category }) => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const { family, size } = useAppSelector<TypographyType>((state) => get(state.resume.present, 'metadata.typography')); - - const { data: fonts } = useQuery(FONTS_QUERY, fetchFonts, { - select: (fonts) => fonts.sort((a, b) => a.category.localeCompare(b.category)), - }); - - const handleChange = (property: TypeProperty, value: number | number[] | Font | null) => { - if (!value) return; - - dispatch( - setResumeState({ - path: `metadata.typography.${property}.${category}`, - value: property === 'family' ? (value as Font).family : value, - }), - ); - }; - - if (!fonts || isEmpty(fonts)) return <TypographySkeleton />; - - return ( - <> - <h5 className={styles.subheading}>{label}</h5> - - <div className={styles.container}> - <div className={styles.slider}> - <Slider - min={12} - max={36} - step={1} - marks={[ - { value: 12, label: '12px' }, - { value: 24, label: t('builder.rightSidebar.sections.typography.form.font-size.label') }, - { value: 36, label: '36px' }, - ]} - valueLabelDisplay="auto" - value={size[category]} - onChange={(_, size: number | number[]) => handleChange('size', size)} - /> - </div> - - <Autocomplete<Font, false, boolean, false> - options={fonts} - disableClearable={true} - groupBy={(font) => font.category} - getOptionLabel={(font) => font.family} - isOptionEqualToValue={(a, b) => a.family === b.family} - value={fonts.find((font) => font.family === family[category])} - onChange={(_, font: Font | null) => handleChange('family', font)} - renderInput={(params) => ( - <TextField {...params} label={t('builder.rightSidebar.sections.typography.form.font-family.label')} /> - )} - /> - </div> - </> - ); -}; - -const Typography = () => { - const { t } = useTranslation(); - - return ( - <> - <Heading path="metadata.typography" name={t('builder.rightSidebar.sections.typography.heading')} /> - - <Widgets label={t('builder.rightSidebar.sections.typography.widgets.headings.label')} category="heading" /> - <Widgets label={t('builder.rightSidebar.sections.typography.widgets.body.label')} category="body" /> - </> - ); -}; - -export default Typography; diff --git a/client/components/dashboard/ResumeCard.module.scss b/client/components/dashboard/ResumeCard.module.scss deleted file mode 100644 index 61e3693d..00000000 --- a/client/components/dashboard/ResumeCard.module.scss +++ /dev/null @@ -1,24 +0,0 @@ -.resume { - @apply flex flex-col gap-2; - - .preview { - aspect-ratio: 1 / 1.41; - - @apply flex items-center justify-center shadow; - @apply cursor-pointer rounded-sm bg-zinc-100 transition-opacity hover:opacity-80 dark:bg-zinc-900; - } - - footer { - @apply flex items-center justify-between; - - .meta { - p:first-child { - @apply text-sm font-semibold leading-relaxed; - } - - p:last-child { - @apply text-xs leading-relaxed opacity-50; - } - } - } -} diff --git a/client/components/dashboard/ResumeCard.tsx b/client/components/dashboard/ResumeCard.tsx deleted file mode 100644 index a1854532..00000000 --- a/client/components/dashboard/ResumeCard.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { SvgIconComponent } from '@mui/icons-material'; -import { ButtonBase } from '@mui/material'; - -import { useAppDispatch } from '@/store/hooks'; -import { ModalName, setModalState } from '@/store/modal/modalSlice'; - -import styles from './ResumeCard.module.scss'; - -type Props = { - modal: ModalName; - icon: SvgIconComponent; - title: string; - subtitle: string; -}; - -const ResumeCard: React.FC<Props> = ({ modal, icon: Icon, title, subtitle }) => { - const dispatch = useAppDispatch(); - - const handleClick = () => dispatch(setModalState({ modal, state: { open: true } })); - - return ( - <section className={styles.resume}> - <ButtonBase className={styles.preview} onClick={handleClick}> - <Icon sx={{ fontSize: 64 }} /> - </ButtonBase> - - <footer> - <div className={styles.meta}> - <p>{title}</p> - <p>{subtitle}</p> - </div> - </footer> - </section> - ); -}; - -export default ResumeCard; diff --git a/client/components/dashboard/ResumePreview.module.scss b/client/components/dashboard/ResumePreview.module.scss deleted file mode 100644 index 0126d01b..00000000 --- a/client/components/dashboard/ResumePreview.module.scss +++ /dev/null @@ -1,37 +0,0 @@ -.resume { - @apply flex flex-col gap-2; - - .preview { - aspect-ratio: 1 / 1.41; - - @apply relative cursor-pointer rounded-sm shadow; - @apply bg-zinc-100 transition-opacity hover:opacity-80 dark:bg-zinc-900; - } - - footer { - @apply flex items-center justify-between overflow-hidden; - - .meta { - flex: 4; - @apply flex flex-col overflow-hidden; - - p { - @apply overflow-hidden text-ellipsis whitespace-nowrap; - - &:first-child { - @apply text-sm font-semibold leading-relaxed; - } - - &:last-child { - @apply text-xs leading-relaxed opacity-50; - } - } - } - - .menu { - flex: 1; - - @apply h-full w-full cursor-pointer rounded text-lg; - } - } -} diff --git a/client/components/dashboard/ResumePreview.tsx b/client/components/dashboard/ResumePreview.tsx deleted file mode 100644 index adb1cc64..00000000 --- a/client/components/dashboard/ResumePreview.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import { - ContentCopy, - DeleteOutline, - DriveFileRenameOutline, - Link as LinkIcon, - MoreVert, - OpenInNew, -} from '@mui/icons-material'; -import { ButtonBase, ListItemIcon, ListItemText, Menu, MenuItem, Tooltip } from '@mui/material'; -import Image from 'next/image'; -import Link from 'next/link'; -import { useRouter } from 'next/router'; -import { useTranslation } from 'next-i18next'; -import { useState } from 'react'; -import toast from 'react-hot-toast'; -import { useMutation } from 'react-query'; -import { Resume } from 'schema'; - -import { RESUMES_QUERY } from '@/constants/index'; -import { ServerError } from '@/services/axios'; -import queryClient from '@/services/react-query'; -import { deleteResume, DeleteResumeParams, duplicateResume, DuplicateResumeParams } from '@/services/resume'; -import { useAppDispatch } from '@/store/hooks'; -import { setModalState } from '@/store/modal/modalSlice'; -import { getRelativeTime } from '@/utils/date'; -import getResumeUrl from '@/utils/getResumeUrl'; - -import styles from './ResumePreview.module.scss'; - -type Props = { - resume: Resume; -}; - -const ResumePreview: React.FC<Props> = ({ resume }) => { - const router = useRouter(); - - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const [anchorEl, setAnchorEl] = useState<Element | null>(null); - - const { mutateAsync: duplicateMutation } = useMutation<Resume, ServerError, DuplicateResumeParams>(duplicateResume); - - const { mutateAsync: deleteMutation } = useMutation<void, ServerError, DeleteResumeParams>(deleteResume); - - const handleOpen = () => { - handleClose(); - - router.push({ - pathname: '/[username]/[slug]/build', - query: { username: resume.user.username, slug: resume.slug }, - }); - }; - - const handleOpenMenu = (event: React.MouseEvent<Element>) => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - const handleRename = () => { - handleClose(); - - dispatch( - setModalState({ - modal: 'dashboard.rename-resume', - state: { - open: true, - payload: { - item: resume, - onComplete: () => { - queryClient.invalidateQueries(RESUMES_QUERY); - }, - }, - }, - }), - ); - }; - - const handleDuplicate = async () => { - handleClose(); - - await duplicateMutation({ id: resume.id }); - - queryClient.invalidateQueries(RESUMES_QUERY); - }; - - const handleShareLink = async () => { - handleClose(); - - const url = getResumeUrl(resume, { withHost: true }); - await navigator.clipboard.writeText(url); - - toast.success(t('common.toast.success.resume-link-copied')); - }; - - const handleDelete = async () => { - handleClose(); - - await deleteMutation({ id: resume.id }); - - queryClient.invalidateQueries(RESUMES_QUERY); - }; - - return ( - <section className={styles.resume}> - <Link - passHref - href={{ - pathname: '/[username]/[slug]/build', - query: { username: resume.user.username, slug: resume.slug }, - }} - > - <ButtonBase className={styles.preview}> - {resume.image ? <Image src={resume.image} alt={resume.name} priority width={400} height={0} /> : null} - </ButtonBase> - </Link> - - <footer> - <div className={styles.meta}> - <p>{resume.name}</p> - <p>{t('dashboard.resume.timestamp', { timestamp: getRelativeTime(resume.updatedAt) })}</p> - </div> - - <ButtonBase className={styles.menu} onClick={handleOpenMenu}> - <MoreVert /> - </ButtonBase> - - <Menu anchorEl={anchorEl} onClose={handleClose} open={Boolean(anchorEl)}> - <MenuItem onClick={handleOpen}> - <ListItemIcon> - <OpenInNew className="scale-90" /> - </ListItemIcon> - <ListItemText>{t('dashboard.resume.menu.open')}</ListItemText> - </MenuItem> - - <MenuItem onClick={handleRename}> - <ListItemIcon> - <DriveFileRenameOutline className="scale-90" /> - </ListItemIcon> - <ListItemText>{t('dashboard.resume.menu.rename')}</ListItemText> - </MenuItem> - - <MenuItem onClick={handleDuplicate}> - <ListItemIcon> - <ContentCopy className="scale-90" /> - </ListItemIcon> - <ListItemText>{t('dashboard.resume.menu.duplicate')}</ListItemText> - </MenuItem> - - {resume.public ? ( - <MenuItem onClick={handleShareLink}> - <ListItemIcon> - <LinkIcon className="scale-90" /> - </ListItemIcon> - <ListItemText>{t('dashboard.resume.menu.share-link')}</ListItemText> - </MenuItem> - ) : ( - <Tooltip arrow placement="right" title={t('dashboard.resume.menu.tooltips.share-link')}> - <div> - <MenuItem> - <ListItemIcon> - <LinkIcon className="scale-90" /> - </ListItemIcon> - <ListItemText>{t('dashboard.resume.menu.share-link')}</ListItemText> - </MenuItem> - </div> - </Tooltip> - )} - - <Tooltip arrow placement="right" title={t('dashboard.resume.menu.tooltips.delete')}> - <MenuItem onClick={handleDelete}> - <ListItemIcon> - <DeleteOutline className="scale-90" /> - </ListItemIcon> - <ListItemText>{t('dashboard.resume.menu.delete')}</ListItemText> - </MenuItem> - </Tooltip> - </Menu> - </footer> - </section> - ); -}; - -export default ResumePreview; diff --git a/client/components/home/Actions.tsx b/client/components/home/Actions.tsx deleted file mode 100644 index a55b31cb..00000000 --- a/client/components/home/Actions.tsx +++ /dev/null @@ -1,44 +0,0 @@ -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; diff --git a/client/components/home/Background.tsx b/client/components/home/Background.tsx deleted file mode 100644 index 5e366243..00000000 --- a/client/components/home/Background.tsx +++ /dev/null @@ -1,16 +0,0 @@ -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; diff --git a/client/components/home/Footer.tsx b/client/components/home/Footer.tsx deleted file mode 100644 index b297d0c1..00000000 --- a/client/components/home/Footer.tsx +++ /dev/null @@ -1,47 +0,0 @@ -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; diff --git a/client/components/home/Header.tsx b/client/components/home/Header.tsx deleted file mode 100644 index a3d88cfb..00000000 --- a/client/components/home/Header.tsx +++ /dev/null @@ -1,24 +0,0 @@ -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; diff --git a/client/components/home/Hero.tsx b/client/components/home/Hero.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/client/components/home/Pattern.tsx b/client/components/home/Pattern.tsx deleted file mode 100644 index 501ae14b..00000000 --- a/client/components/home/Pattern.tsx +++ /dev/null @@ -1,28 +0,0 @@ -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; diff --git a/client/components/home/Testimony.module.scss b/client/components/home/Testimony.module.scss deleted file mode 100644 index 2ec5657f..00000000 --- a/client/components/home/Testimony.module.scss +++ /dev/null @@ -1,12 +0,0 @@ -.testimony { - @apply grid gap-2; - @apply rounded border-2 p-4 dark:border-zinc-900; - - blockquote { - @apply text-justify text-xs leading-normal opacity-90; - } - - figcaption { - @apply text-xs opacity-60; - } -} diff --git a/client/components/home/Testimony.tsx b/client/components/home/Testimony.tsx deleted file mode 100644 index 674c762a..00000000 --- a/client/components/home/Testimony.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Testimony as TestimonyType } from '@/data/testimonials'; - -import styles from './Testimony.module.scss'; - -type Props = TestimonyType; - -const Testimony: React.FC<Props> = ({ name, message }) => { - return ( - <div className={styles.testimony}> - <blockquote>{message}</blockquote> - - <figcaption>— {name}</figcaption> - </div> - ); -}; - -export default Testimony; diff --git a/client/components/home/sections/Hero.tsx b/client/components/home/sections/Hero.tsx deleted file mode 100644 index 16ef2107..00000000 --- a/client/components/home/sections/Hero.tsx +++ /dev/null @@ -1,50 +0,0 @@ -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; diff --git a/client/components/home/sections/Logo.tsx b/client/components/home/sections/Logo.tsx deleted file mode 100644 index 77ed3398..00000000 --- a/client/components/home/sections/Logo.tsx +++ /dev/null @@ -1,51 +0,0 @@ -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; diff --git a/client/components/home/sections/Stats.tsx b/client/components/home/sections/Stats.tsx deleted file mode 100644 index 000058f3..00000000 --- a/client/components/home/sections/Stats.tsx +++ /dev/null @@ -1,27 +0,0 @@ -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; diff --git a/client/components/shared/ArrayInput.module.scss b/client/components/shared/ArrayInput.module.scss deleted file mode 100644 index 3247205c..00000000 --- a/client/components/shared/ArrayInput.module.scss +++ /dev/null @@ -1,15 +0,0 @@ -.header { - @apply mb-4 flex items-center justify-between; - - .label { - @apply text-base font-semibold; - } -} - -.inputGrid { - @apply grid grid-cols-2 gap-4; - - .delete { - @apply opacity-25 hover:opacity-75; - } -} diff --git a/client/components/shared/ArrayInput.tsx b/client/components/shared/ArrayInput.tsx deleted file mode 100644 index a10592b6..00000000 --- a/client/components/shared/ArrayInput.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { Add, Delete } from '@mui/icons-material'; -import { IconButton, InputAdornment, TextField } from '@mui/material'; -import get from 'lodash/get'; -import { useEffect, useState } from 'react'; -import { FieldError } from 'react-hook-form'; - -import styles from './ArrayInput.module.scss'; - -type Props = { - label: string; - value?: string[]; - className?: string; - onChange: (event: any) => void; - errors?: FieldError | FieldError[]; -}; - -const ArrayInput: React.FC<Props> = ({ value, label, onChange, errors, className }) => { - const [items, setItems] = useState<string[]>(value || []); - - const onAdd = () => setItems([...items, '']); - - const onDelete = (index: number) => setItems(items.filter((_, idx) => idx !== index)); - - const handleChange = (event: React.ChangeEvent<HTMLInputElement>, index: number) => { - const tempItems = [...items]; - tempItems[index] = event.target.value; - setItems(tempItems); - }; - - useEffect(() => { - onChange(items); - }, [onChange, items]); - - return ( - <div className={className}> - <header className={styles.header}> - <h3 className={styles.label}> - {label} <small>({items.length})</small> - </h3> - - <IconButton onClick={onAdd}> - <Add /> - </IconButton> - </header> - - <div className={styles.inputGrid}> - {items.map((value, index) => ( - <TextField - key={index} - value={value} - onChange={(event: React.ChangeEvent<HTMLInputElement>) => handleChange(event, index)} - error={!!get(errors, index, false)} - helperText={get(errors, `${index}.message`, '')} - InputProps={{ - endAdornment: ( - <InputAdornment position="end"> - <IconButton edge="end" onClick={() => onDelete(index)} className={styles.delete}> - <Delete /> - </IconButton> - </InputAdornment> - ), - }} - /> - ))} - </div> - </div> - ); -}; - -export default ArrayInput; diff --git a/client/components/shared/Avatar.module.scss b/client/components/shared/Avatar.module.scss deleted file mode 100644 index 12a57fc2..00000000 --- a/client/components/shared/Avatar.module.scss +++ /dev/null @@ -1,3 +0,0 @@ -.avatar { - @apply cursor-pointer rounded-full; -} diff --git a/client/components/shared/Avatar.tsx b/client/components/shared/Avatar.tsx deleted file mode 100644 index 01402206..00000000 --- a/client/components/shared/Avatar.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { Divider, IconButton, Menu, MenuItem } from '@mui/material'; -import Image from 'next/image'; -import { useRouter } from 'next/router'; -import { useTranslation } from 'next-i18next'; -import { useState } from 'react'; - -import { logout } from '@/store/auth/authSlice'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setModalState } from '@/store/modal/modalSlice'; -import getGravatarUrl from '@/utils/getGravatarUrl'; - -import styles from './Avatar.module.scss'; - -type Props = { - size?: number; - interactive?: boolean; -}; - -const Avatar: React.FC<Props> = ({ size = 64, interactive = true }) => { - const router = useRouter(); - - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const [anchorEl, setAnchorEl] = useState<Element | null>(null); - - const user = useAppSelector((state) => state.auth.user); - const email = user?.email || ''; - - const handleOpen = (event: React.MouseEvent<Element>) => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - const handleOpenProfile = () => { - dispatch(setModalState({ modal: 'auth.profile', state: { open: true } })); - handleClose(); - }; - - const handleLogout = () => { - dispatch(logout()); - handleClose(); - - router.push('/'); - }; - - return ( - <> - <IconButton onClick={handleOpen} disabled={!interactive}> - <Image - width={size} - height={size} - className={styles.avatar} - src={getGravatarUrl(email, size)} - alt={user?.name ?? 'User Avatar'} - /> - </IconButton> - - <Menu anchorEl={anchorEl} onClose={handleClose} open={Boolean(anchorEl)}> - <MenuItem onClick={handleOpenProfile}> - <div> - <span className="text-xs opacity-50">{t('common.avatar.menu.greeting')},</span> - <p>{user?.name}</p> - </div> - </MenuItem> - <Divider /> - <MenuItem onClick={handleLogout}>{t('common.avatar.menu.logout')}</MenuItem> - </Menu> - </> - ); -}; - -export default Avatar; diff --git a/client/components/shared/BaseModal.module.scss b/client/components/shared/BaseModal.module.scss deleted file mode 100644 index 2d6d0cbb..00000000 --- a/client/components/shared/BaseModal.module.scss +++ /dev/null @@ -1,39 +0,0 @@ -.content { - @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-zinc-100 dark:bg-zinc-900 lg:overflow-auto; - @apply max-h-[90vh] min-h-fit; - - &::-webkit-scrollbar { - display: none; - } -} - -.header { - @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; - - > div { - @apply flex items-center gap-2; - } - - button { - @apply flex items-center justify-center; - @apply rotate-0 transition-transform hover:rotate-90; - } - - h1 { - @apply text-base font-medium; - } -} - -.body { - @apply grid gap-4 pt-4 pb-6; -} - -.footer { - @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; -} diff --git a/client/components/shared/BaseModal.tsx b/client/components/shared/BaseModal.tsx deleted file mode 100644 index 96d9a20b..00000000 --- a/client/components/shared/BaseModal.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { Close as CloseIcon } from '@mui/icons-material'; -import { Fade, IconButton, Modal } from '@mui/material'; -import { useRouter } from 'next/router'; - -import styles from './BaseModal.module.scss'; - -type Props = { - isOpen: boolean; - heading: string; - icon?: React.ReactNode; - children?: React.ReactNode; - footerChildren?: React.ReactNode; - handleClose: () => void; -}; - -const BaseModal: React.FC<Props> = ({ icon, isOpen, heading, children, handleClose, footerChildren }) => { - const router = useRouter(); - const { pathname, query } = router; - - const onClose = (event: object, reason: string) => { - router.push({ pathname, query }, ''); - if (reason === 'backdropClick') return; - handleClose(); - }; - - const onIconClose = () => { - router.push({ pathname, query }, ''); - handleClose(); - }; - - return ( - <Modal - open={isOpen} - onClose={(event, reason) => onClose(event, reason)} - closeAfterTransition - aria-labelledby={heading} - classes={{ root: 'flex items-center justify-center' }} - > - <Fade in={isOpen}> - <div className={styles.content}> - <header className={styles.header}> - <div> - {icon} - {icon && <span className="mx-1 opacity-25">/</span>} - <h1>{heading}</h1> - </div> - - <IconButton size="small" onClick={onIconClose}> - <CloseIcon sx={{ fontSize: 18 }} /> - </IconButton> - </header> - - <div className={styles.body}>{children}</div> - - {footerChildren ? <footer className={styles.footer}>{footerChildren}</footer> : null} - </div> - </Fade> - </Modal> - ); -}; - -export default BaseModal; diff --git a/client/components/shared/ColorAvatar.tsx b/client/components/shared/ColorAvatar.tsx deleted file mode 100644 index f2d2e28b..00000000 --- a/client/components/shared/ColorAvatar.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Avatar, IconButton } from '@mui/material'; -import isFunction from 'lodash/isFunction'; - -type Props = { - color: string; - size?: number; - onClick?: (color: string) => void; -}; - -const ColorAvatar: React.FC<Props> = ({ color, size = 20, onClick }) => { - const handleClick = () => isFunction(onClick) && onClick(color); - - return ( - <IconButton onClick={handleClick}> - <Avatar sx={{ bgcolor: color, width: size, height: size }}> </Avatar> - </IconButton> - ); -}; - -export default ColorAvatar; diff --git a/client/components/shared/ColorPicker.tsx b/client/components/shared/ColorPicker.tsx deleted file mode 100644 index 895ba5c5..00000000 --- a/client/components/shared/ColorPicker.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { Popover, TextField } from '@mui/material'; -import React, { useMemo, useState } from 'react'; -import { HexColorPicker } from 'react-colorful'; - -import { hexColorPattern } from '@/config/colors'; - -import ColorAvatar from './ColorAvatar'; - -type Props = { - label: string; - color: string; - className?: string; - onChange: (color: string) => void; -}; - -const ColorPicker: React.FC<Props> = ({ label, color, onChange, className }) => { - const isValid = useMemo(() => hexColorPattern.test(color), [color]); - - const [anchorEl, setAnchorEl] = useState<HTMLInputElement | null>(null); - const isOpen = Boolean(anchorEl); - - const handleOpen = (event: React.MouseEvent<HTMLInputElement>) => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { - if (hexColorPattern.test(event.target.value)) { - onChange(event.target.value); - } - }; - - return ( - <> - <TextField - label={label} - value={color} - error={!isValid} - onClick={handleOpen} - onChange={handleChange} - className={className} - InputProps={{ - startAdornment: ( - <div className="mr-2"> - <ColorAvatar color={color} /> - </div> - ), - }} - /> - <Popover - open={isOpen} - anchorEl={anchorEl} - onClose={handleClose} - anchorOrigin={{ - vertical: 'bottom', - horizontal: 'left', - }} - > - <HexColorPicker color={color} onChange={onChange} className="overflow-hidden" /> - </Popover> - </> - ); -}; - -export default ColorPicker; diff --git a/client/components/shared/Copyright.tsx b/client/components/shared/Copyright.tsx deleted file mode 100644 index 287a2968..00000000 --- a/client/components/shared/Copyright.tsx +++ /dev/null @@ -1,19 +0,0 @@ -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> -); diff --git a/client/components/shared/Footer.tsx b/client/components/shared/Footer.tsx deleted file mode 100644 index 1bd8f2f6..00000000 --- a/client/components/shared/Footer.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import clsx from 'clsx'; -import { Trans, useTranslation } from 'next-i18next'; - -type Props = { - className?: string; -}; - -const Footer: React.FC<Props> = ({ className }) => { - const { t } = useTranslation(); - - return ( - <div className={clsx('text-xs', className)}> - <p>{t('common.footer.license')}</p> - - <p> - <Trans t={t} i18nKey="common.footer.credit"> - A passion project by - <a href="https://www.amruthpillai.com/" target="_blank" rel="noreferrer"> - Amruth Pillai - </a> - </Trans> - </p> - </div> - ); -}; - -export default Footer; diff --git a/client/components/shared/Heading.module.scss b/client/components/shared/Heading.module.scss deleted file mode 100644 index 756123a0..00000000 --- a/client/components/shared/Heading.module.scss +++ /dev/null @@ -1,17 +0,0 @@ -.container { - @apply flex items-center justify-between; - - h1 { - @apply text-2xl; - } - - .actions { - @apply flex gap-2 opacity-75 transition-opacity lg:opacity-50 dark:lg:opacity-25; - } - - &:hover { - .actions { - @apply opacity-75; - } - } -} diff --git a/client/components/shared/Heading.tsx b/client/components/shared/Heading.tsx deleted file mode 100644 index 2bd0f981..00000000 --- a/client/components/shared/Heading.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { Check, Delete, DriveFileRenameOutline, Grade, Visibility, VisibilityOff } from '@mui/icons-material'; -import { IconButton, TextField, Tooltip } from '@mui/material'; -import clsx from 'clsx'; -import get from 'lodash/get'; -import { useTranslation } from 'next-i18next'; -import React, { useMemo, useState } from 'react'; - -import sections from '@/config/sections'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { deleteSection, setResumeState } from '@/store/resume/resumeSlice'; - -import styles from './Heading.module.scss'; - -type Props = { - path: string; - name?: string; - isEditable?: boolean; - isHideable?: boolean; - isDeletable?: boolean; - action?: React.ReactNode; -}; - -const Heading: React.FC<Props> = ({ - path, - name, - isEditable = false, - isHideable = false, - isDeletable = false, - action, -}) => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`, name)); - const visibility = useAppSelector((state) => get(state.resume.present, `${path}.visible`, true)); - - const [editMode, setEditMode] = useState(false); - - const id = useMemo(() => path.split('.')[1], [path]); - - const icon = sections.find((x) => x.id === id)?.icon || <Grade />; - - const toggleVisibility = () => { - dispatch(setResumeState({ path: `${path}.visible`, value: !visibility })); - }; - - const toggleEditMode = () => setEditMode(!editMode); - - const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { - dispatch(setResumeState({ path: `${path}.name`, value: event.target.value })); - }; - - const handleDelete = () => { - dispatch(deleteSection({ path })); - }; - - return ( - <div className={styles.container}> - <div className="flex w-full items-center gap-3"> - <div className="opacity-50">{icon}</div> - {editMode ? ( - <TextField size="small" value={heading} className="w-3/4" onChange={handleChange} /> - ) : ( - <h1>{t(`builder.leftSidebar.${path}.heading`, { defaultValue: heading })}</h1> - )} - </div> - - <div - className={clsx(styles.actions, { - '!opacity-75': editMode, - })} - > - {isEditable && ( - <Tooltip title={t('builder.common.tooltip.rename-section')}> - <IconButton onClick={toggleEditMode}>{editMode ? <Check /> : <DriveFileRenameOutline />}</IconButton> - </Tooltip> - )} - - {isHideable && ( - <Tooltip title={t('builder.common.tooltip.toggle-visibility')}> - <IconButton onClick={toggleVisibility}>{visibility ? <Visibility /> : <VisibilityOff />}</IconButton> - </Tooltip> - )} - - {isDeletable && ( - <Tooltip title={t('builder.common.tooltip.delete-section')}> - <IconButton onClick={handleDelete}> - <Delete /> - </IconButton> - </Tooltip> - )} - - {action} - </div> - </div> - ); -}; - -export default Heading; diff --git a/client/components/shared/Icon.tsx b/client/components/shared/Icon.tsx deleted file mode 100644 index 2d419cd0..00000000 --- a/client/components/shared/Icon.tsx +++ /dev/null @@ -1,27 +0,0 @@ -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; diff --git a/client/components/shared/LanguageSwitcher.module.scss b/client/components/shared/LanguageSwitcher.module.scss deleted file mode 100644 index e95f04ea..00000000 --- a/client/components/shared/LanguageSwitcher.module.scss +++ /dev/null @@ -1,13 +0,0 @@ -.popover { - width: 460px; - - @apply px-4 py-2; -} - -.container { - @apply grid grid-cols-3 items-center justify-center gap-x-2; -} - -.language { - @apply cursor-pointer py-2 px-4 text-center hover:underline; -} diff --git a/client/components/shared/LanguageSwitcher.tsx b/client/components/shared/LanguageSwitcher.tsx deleted file mode 100644 index f8eef27c..00000000 --- a/client/components/shared/LanguageSwitcher.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Language } from '@mui/icons-material'; -import { IconButton, Menu, MenuItem } from '@mui/material'; -import { useRouter } from 'next/router'; -import { MouseEvent, useState } from 'react'; - -import { languages } from '@/config/languages'; -import { TRANSLATE_URL } from '@/constants/index'; - -const LanguageSwitcher = () => { - const router = useRouter(); - - const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null); - - const handleClick = (event: MouseEvent<HTMLButtonElement>) => setAnchorEl(event.currentTarget); - - const handleClose = () => setAnchorEl(null); - - const handleChange = (locale: string) => { - const { pathname, asPath, query } = router; - - handleClose(); - - document.cookie = `NEXT_LOCALE=${locale}; path=/; expires=2147483647`; - - router.push({ pathname, query }, asPath, { locale }); - }; - - const handleAddLanguage = () => window.open(TRANSLATE_URL, '_blank'); - - return ( - <div> - <IconButton onClick={handleClick}> - <Language /> - </IconButton> - - <Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleClose}> - {languages.map(({ code, name, localName }) => ( - <MenuItem key={code} onClick={() => handleChange(code)}> - {name} {localName && `(${localName})`} - </MenuItem> - ))} - - <MenuItem> - <span className="font-bold" onClick={handleAddLanguage}> - Add your language - </span> - </MenuItem> - </Menu> - </div> - ); -}; - -export default LanguageSwitcher; diff --git a/client/components/shared/List.module.scss b/client/components/shared/List.module.scss deleted file mode 100644 index a16aa947..00000000 --- a/client/components/shared/List.module.scss +++ /dev/null @@ -1,7 +0,0 @@ -.container { - @apply rounded-lg border dark:border-zinc-50/10; - - .empty { - @apply py-8 text-center; - } -} diff --git a/client/components/shared/List.tsx b/client/components/shared/List.tsx deleted file mode 100644 index 07d16d50..00000000 --- a/client/components/shared/List.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import clsx from 'clsx'; -import get from 'lodash/get'; -import isArray from 'lodash/isArray'; -import isEmpty from 'lodash/isEmpty'; -import isFunction from 'lodash/isFunction'; -import { useTranslation } from 'next-i18next'; -import { useCallback } from 'react'; -import { DndProvider } from 'react-dnd'; -import { HTML5Backend } from 'react-dnd-html5-backend'; -import { ListItem as ListItemType } from 'schema'; - -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { deleteItem, setResumeState } from '@/store/resume/resumeSlice'; - -import styles from './List.module.scss'; -import ListItem from './ListItem'; - -type Props = { - path: string; - titleKey?: string; - subtitleKey?: string; - onEdit?: (item: ListItemType) => void; - onDuplicate?: (item: ListItemType) => void; - className?: string; -}; - -const List: React.FC<Props> = ({ - path, - titleKey = 'title', - subtitleKey = 'subtitle', - onEdit, - onDuplicate, - className, -}) => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const list: Array<ListItemType> = useAppSelector((state) => get(state.resume.present, path, [])); - - const handleEdit = (item: ListItemType) => { - isFunction(onEdit) && onEdit(item); - }; - - const handleDuplicate = (item: ListItemType) => { - isFunction(onDuplicate) && onDuplicate(item); - }; - - const handleDelete = (item: ListItemType) => { - dispatch(deleteItem({ path, value: item })); - }; - - const handleMove = useCallback( - (dragIndex: number, hoverIndex: number) => { - const dragItem = list[dragIndex]; - const newList = [...list]; - - newList.splice(dragIndex, 1); - newList.splice(hoverIndex, 0, dragItem); - - dispatch(setResumeState({ path, value: newList })); - }, - [list, dispatch, path], - ); - - return ( - <DndProvider backend={HTML5Backend}> - <div className={clsx(styles.container, className)}> - {isEmpty(list) && <div className={styles.empty}>{t('builder.common.list.empty-text')}</div>} - - {list.map((item, index) => { - const title = get(item, titleKey, ''); - const subtitleObj = get(item, subtitleKey); - const subtitle: string = isArray(subtitleObj) ? subtitleObj.join(', ') : subtitleObj; - - return ( - <ListItem - key={item.id} - path={path} - item={item} - index={index} - title={title} - subtitle={subtitle} - onMove={handleMove} - onEdit={handleEdit} - onDelete={handleDelete} - onDuplicate={handleDuplicate} - /> - ); - })} - </div> - </DndProvider> - ); -}; - -export default List; diff --git a/client/components/shared/ListItem.module.scss b/client/components/shared/ListItem.module.scss deleted file mode 100644 index e02fbfac..00000000 --- a/client/components/shared/ListItem.module.scss +++ /dev/null @@ -1,18 +0,0 @@ -.item { - @apply flex items-center justify-between; - @apply py-5 pl-5 pr-2; - @apply border-b border-zinc-900/10 last:border-0 dark:border-zinc-50/10; - @apply cursor-move transition-opacity; - - .meta { - @apply grid gap-1; - - .title { - @apply font-semibold; - } - - .subtitle { - @apply text-xs opacity-50; - } - } -} diff --git a/client/components/shared/ListItem.tsx b/client/components/shared/ListItem.tsx deleted file mode 100644 index 986421c7..00000000 --- a/client/components/shared/ListItem.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import { DeleteOutline, DriveFileRenameOutline, FileCopy, MoreVert } from '@mui/icons-material'; -import { Divider, IconButton, ListItemIcon, ListItemText, Menu, MenuItem, Tooltip } from '@mui/material'; -import clsx from 'clsx'; -import isFunction from 'lodash/isFunction'; -import { useTranslation } from 'next-i18next'; -import React, { useRef, useState } from 'react'; -import { DropTargetMonitor, useDrag, useDrop, XYCoord } from 'react-dnd'; -import { ListItem as ListItemType } from 'schema'; - -import styles from './ListItem.module.scss'; - -interface DragItem { - id: string; - type: string; - index: number; -} - -type Props = { - item: ListItemType; - path: string; - index: number; - title: string; - subtitle?: string; - onMove?: (dragIndex: number, hoverIndex: number) => void; - onEdit?: (item: ListItemType) => void; - onDelete?: (item: ListItemType) => void; - onDuplicate?: (item: ListItemType) => void; -}; - -const ListItem: React.FC<Props> = ({ item, path, index, title, subtitle, onMove, onEdit, onDelete, onDuplicate }) => { - const { t } = useTranslation(); - - const ref = useRef<HTMLDivElement>(null); - const [anchorEl, setAnchorEl] = useState<Element | null>(null); - - const [{ handlerId }, drop] = useDrop<DragItem, any, any>({ - accept: path, - collect(monitor) { - return { handlerId: monitor.getHandlerId() }; - }, - hover(item: DragItem, monitor: DropTargetMonitor) { - if (!ref.current) { - return; - } - const dragIndex = item.index; - const hoverIndex = index; - - if (dragIndex === hoverIndex) { - return; - } - - const hoverBoundingRect = ref.current?.getBoundingClientRect(); - const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; - const clientOffset = monitor.getClientOffset(); - const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top; - - if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) { - return; - } - - if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) { - return; - } - - isFunction(onMove) && onMove(dragIndex, hoverIndex); - - item.index = hoverIndex; - }, - }); - - const [{ isDragging }, drag] = useDrag({ - type: path, - item: () => { - return { id: item.id, index }; - }, - collect: (monitor) => ({ - isDragging: monitor.isDragging(), - }), - }); - - const handleOpen = (event: React.MouseEvent<Element>) => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - const handleEdit = (item: ListItemType) => { - isFunction(onEdit) && onEdit(item); - handleClose(); - }; - - const handleDelete = (item: ListItemType) => { - isFunction(onDelete) && onDelete(item); - handleClose(); - }; - - const handleDuplicate = (item: ListItemType) => { - isFunction(onDuplicate) && onDuplicate(item); - handleClose(); - }; - - drag(drop(ref)); - - return ( - <div - ref={ref} - data-handler-id={handlerId} - className={clsx(styles.item, { - ['opacity-25']: isDragging, - })} - > - <div className={styles.meta}> - <h1 className={styles.title}>{title}</h1> - <h2 className={styles.subtitle}>{subtitle}</h2> - </div> - - <div> - <IconButton onClick={handleOpen}> - <MoreVert /> - </IconButton> - - <Menu anchorEl={anchorEl} onClose={handleClose} open={Boolean(anchorEl)}> - <MenuItem onClick={() => handleEdit(item)}> - <ListItemIcon> - <DriveFileRenameOutline className="scale-90" /> - </ListItemIcon> - <ListItemText>{t('builder.common.list.actions.edit')}</ListItemText> - </MenuItem> - - <MenuItem onClick={() => handleDuplicate(item)}> - <ListItemIcon> - <FileCopy className="scale-90" /> - </ListItemIcon> - <ListItemText>{t('builder.common.list.actions.duplicate')}</ListItemText> - </MenuItem> - - <Divider /> - - <Tooltip arrow placement="right" title={t('builder.common.tooltip.delete-item')}> - <div> - <MenuItem onClick={() => handleDelete(item)}> - <ListItemIcon> - <DeleteOutline className="scale-90" /> - </ListItemIcon> - <ListItemText>{t('builder.common.list.actions.delete')}</ListItemText> - </MenuItem> - </div> - </Tooltip> - </Menu> - </div> - </div> - ); -}; - -export default ListItem; diff --git a/client/components/shared/Loading.module.scss b/client/components/shared/Loading.module.scss deleted file mode 100644 index 9559bdbd..00000000 --- a/client/components/shared/Loading.module.scss +++ /dev/null @@ -1,32 +0,0 @@ -.loading { - animation: progress 2s linear infinite; - - @apply fixed top-0 z-50; - @apply bg-primary-500 shadow-primary-500/50 h-0.5 w-screen shadow; -} - -@keyframes progress { - 0% { - left: 0%; - right: 100%; - width: 0; - } - - 10% { - left: 0%; - right: 75%; - width: 25%; - } - - 90% { - left: 75%; - right: 0%; - width: 25%; - } - - 100% { - left: 100%; - right: 0%; - width: 0; - } -} diff --git a/client/components/shared/Loading.tsx b/client/components/shared/Loading.tsx deleted file mode 100644 index 701601f8..00000000 --- a/client/components/shared/Loading.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { useRouter } from 'next/router'; -import { useIsFetching, useIsMutating } from 'react-query'; - -import styles from './Loading.module.scss'; - -const Loading: React.FC = () => { - const { isReady } = useRouter(); - - const isFetching = useIsFetching(); - const isMutating = useIsMutating(); - - if (!isFetching && !isMutating && isReady) { - return null; - } - - return <div className={styles.loading} />; -}; - -export default Loading; diff --git a/client/components/shared/Logo.tsx b/client/components/shared/Logo.tsx deleted file mode 100644 index a8f029e4..00000000 --- a/client/components/shared/Logo.tsx +++ /dev/null @@ -1,26 +0,0 @@ -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 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; diff --git a/client/components/shared/Markdown.tsx b/client/components/shared/Markdown.tsx deleted file mode 100644 index 8e613abe..00000000 --- a/client/components/shared/Markdown.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import clsx from 'clsx'; -import isEmpty from 'lodash/isEmpty'; -import ReactMarkdown from 'react-markdown'; -import rehypeKatex from 'rehype-katex'; -import remarkGfm from 'remark-gfm'; -import remarkMath from 'remark-math'; - -type Props = { - children?: string; - className?: string; -}; - -const Markdown: React.FC<Props> = ({ className, children }) => { - if (!children || isEmpty(children)) return null; - - return ( - <ReactMarkdown - rehypePlugins={[rehypeKatex]} - className={clsx('markdown', className)} - remarkPlugins={[remarkGfm, remarkMath]} - > - {children} - </ReactMarkdown> - ); -}; - -export default Markdown; diff --git a/client/components/shared/MarkdownSupported.tsx b/client/components/shared/MarkdownSupported.tsx deleted file mode 100644 index 2fb46904..00000000 --- a/client/components/shared/MarkdownSupported.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Link } from '@mui/material'; -import { Trans, useTranslation } from 'next-i18next'; - -const MarkdownSupported: React.FC = () => { - const { t } = useTranslation(); - - return ( - <span className="inline-block pt-1 opacity-75"> - <Trans t={t} i18nKey="common.markdown.help-text"> - This section supports - <Link href="https://www.markdownguide.org/cheat-sheet/" target="_blank" rel="noreferrer"> - markdown - </Link> - formatting. - </Trans> - </span> - ); -}; - -export default MarkdownSupported; diff --git a/client/components/shared/ResumeInput.tsx b/client/components/shared/ResumeInput.tsx deleted file mode 100644 index 6f6d3232..00000000 --- a/client/components/shared/ResumeInput.tsx +++ /dev/null @@ -1,75 +0,0 @@ -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'; -import { setResumeState } from '@/store/resume/resumeSlice'; - -import MarkdownSupported from './MarkdownSupported'; - -interface Props { - type?: 'text' | 'textarea' | 'date'; - label: string; - path: string; - className?: string; - markdownSupported?: boolean; -} - -const ResumeInput: React.FC<Props> = ({ type = 'text', label, path, className, markdownSupported = false }) => { - const dispatch = useAppDispatch(); - - const stateValue = useAppSelector((state) => get(state.resume.present, path, '')); - - useEffect(() => { - setValue(stateValue); - }, [stateValue]); - - const [value, setValue] = useState<string>(stateValue); - - const onChange = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { - setValue(event.target.value); - dispatch(setResumeState({ path, value: event.target.value })); - }; - - const onChangeValue = (value: string) => { - setValue(value); - dispatch(setResumeState({ path, value })); - }; - - if (type === 'textarea') { - return ( - <TextField - rows={5} - multiline - label={label} - value={value} - onChange={onChange} - className={className} - helperText={markdownSupported && <MarkdownSupported />} - /> - ); - } - - if (type === 'date') { - return ( - <DatePicker - openTo="year" - label={label} - 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')); - }} - /> - ); - } - - return <TextField type={type} label={label} value={value} onChange={onChange} className={className} />; -}; - -export default ResumeInput; diff --git a/client/components/shared/ThemeSwitch.tsx b/client/components/shared/ThemeSwitch.tsx deleted file mode 100644 index 2b414a09..00000000 --- a/client/components/shared/ThemeSwitch.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { styled, Switch } from '@mui/material'; - -const ThemeSwitch = styled(Switch)(({ theme }) => ({ - width: 62, - height: 34, - padding: 7, - '& .MuiSwitch-switchBase': { - margin: 1, - padding: 0, - transform: 'translateX(6px)', - '&.Mui-checked': { - color: '#fff', - 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', - )}" 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': { - opacity: 1, - backgroundColor: theme.palette.mode === 'dark' ? '#8796A5' : '#aab4be', - }, - }, - }, - '& .MuiSwitch-thumb': { - backgroundColor: theme.palette.mode === 'dark' ? '#003892' : '#001e3c', - width: 32, - height: 32, - '&:before': { - content: "''", - position: 'absolute', - width: '100%', - height: '100%', - left: 0, - top: 0, - 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', - )}" 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>')`, - }, - }, - '& .MuiSwitch-track': { - opacity: 1, - backgroundColor: theme.palette.mode === 'dark' ? '#8796A5' : '#aab4be', - borderRadius: 20 / 2, - }, -})); - -export default ThemeSwitch; diff --git a/client/components/ui/Separator.tsx b/client/components/ui/Separator.tsx deleted file mode 100644 index 91d1894f..00000000 --- a/client/components/ui/Separator.tsx +++ /dev/null @@ -1,25 +0,0 @@ -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 }; diff --git a/client/config/colors.ts b/client/config/colors.ts deleted file mode 100644 index b62d9bd3..00000000 --- a/client/config/colors.ts +++ /dev/null @@ -1,20 +0,0 @@ -export const hexColorPattern = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i; - -export const colorOptions: string[] = [ - '#f44336', - '#e91e63', - '#9c27b0', - '#673ab7', - '#3f51b5', - '#4896d5', - '#03a9f4', - '#00bcd4', - '#009688', - '#4caf50', - '#8bc34a', - '#cddc39', - '#ffeb3b', - '#ffc107', - '#222222', - '#dddddd', -]; diff --git a/client/config/languages.ts b/client/config/languages.ts deleted file mode 100644 index 9a9f3ac3..00000000 --- a/client/config/languages.ts +++ /dev/null @@ -1,58 +0,0 @@ -export type Language = { - code: string; - name: string; - localName?: string; - isRTL?: boolean; -}; - -export const languages: Language[] = [ - { code: 'am', name: 'Amharic', localName: 'አማርኛ' }, - { code: 'ar', name: 'Arabic', localName: 'اَلْعَرَبِيَّةُ', isRTL: true }, - { code: 'bg', name: 'Bulgarian', localName: 'български' }, - { code: 'bn', name: 'Bengali', localName: 'বাংলা' }, - { code: 'ca', name: 'Catalan', localName: 'Valencian' }, - { code: 'cs', name: 'Czech', localName: 'čeština' }, - { code: 'da', name: 'Danish', localName: 'Dansk' }, - { code: 'de', name: 'German', localName: 'Deutsch Formell / Sie' }, - { code: 'el', name: 'Greek', localName: 'Ελληνικά' }, - { code: 'en', name: 'English' }, - { code: 'es', name: 'Spanish', localName: 'Español' }, - { code: 'fa', name: 'Persian', localName: 'فارسی', isRTL: true }, - { code: 'fi', name: 'Finnish', localName: 'Suomi' }, - { code: 'fr', name: 'French', localName: 'Français' }, - { code: 'he', name: 'Hebrew', localName: 'Ivrit', isRTL: true }, - { code: 'hi', name: 'Hindi', localName: 'हिन्दी' }, - { code: 'hu', name: 'Hungarian', localName: 'Magyar' }, - { code: 'id', name: 'Indonesian', localName: 'Bahasa Indonesia' }, - { code: 'it', name: 'Italian', localName: 'Italiano' }, - { code: 'ja', name: 'Japanese', localName: '日本語' }, - { code: 'km', name: 'Khmer', localName: 'ភាសាខ្មែរ' }, - { code: 'kn', name: 'Kannada', localName: 'ಕನ್ನಡ' }, - { code: 'ko', name: 'Korean', localName: '한국어' }, - { code: 'ml', name: 'Malayalam', localName: 'മലയാളം' }, - { code: 'mr', name: 'Marathi', localName: 'मराठी' }, - { code: 'ne', name: 'Nepali', localName: 'नेपाली' }, - { code: 'nl', name: 'Dutch', localName: 'Nederlands' }, - { code: 'no', name: 'Norwegian', localName: 'Norsk' }, - { code: 'or', name: 'Odia', localName: 'ଓଡ଼ିଆ' }, - { code: 'pl', name: 'Polish', localName: 'Polski' }, - { code: 'pt', name: 'Portuguese', localName: 'Português' }, - { code: 'pt-BR', name: 'Brazilian Portuguese', localName: 'Brasil' }, - { code: 'ro', name: 'Romanian', localName: 'limba română' }, - { code: 'ru', name: 'Russian', localName: 'русский' }, - { code: 'sr', name: 'Serbian', localName: 'српски језик' }, - { code: 'sv', name: 'Swedish', localName: 'Svenska' }, - { code: 'ta', name: 'Tamil', localName: 'தமிழ்' }, - { code: 'tr', name: 'Turkish', localName: 'Türkçe' }, - { code: 'uk', name: 'Ukranian', localName: 'Українська мова' }, - { code: 'vi', name: 'Vietnamese', localName: 'Tiếng Việt' }, - { code: 'zh', name: 'Chinese', localName: '中文' }, -].sort((a, b) => a.name.localeCompare(b.name)); - -export const languageMap: Record<string, Language> = languages.reduce( - (acc, lang) => ({ - ...acc, - [lang.code]: lang, - }), - {}, -); diff --git a/client/config/screenshots.ts b/client/config/screenshots.ts deleted file mode 100644 index d174afcc..00000000 --- a/client/config/screenshots.ts +++ /dev/null @@ -1,31 +0,0 @@ -type Screenshot = { - src: string; - alt: string; -}; - -export const screenshots: Screenshot[] = [ - { - src: '/images/screenshots/dashboard.png', - alt: 'Create multiple resumes under one account', - }, - { - src: '/images/screenshots/import-external.png', - alt: 'Import your data from LinkedIn, JSON Resume or Reactive Resume', - }, - { - src: '/images/screenshots/builder.png', - alt: 'Variety of features to personalize your resume to your liking', - }, - { - src: '/images/screenshots/add-section.png', - alt: 'Multiple pre-built sections which can be renamed, or just create your own section', - }, - { - src: '/images/screenshots/page-layout.png', - alt: 'Create multiple pages, manage section layouts as easy as dragging them around', - }, - { - src: '/images/screenshots/preview.png', - alt: 'Get a unique link to your resume which can be shared with anyone for the latest information', - }, -]; diff --git a/client/config/sections.tsx b/client/config/sections.tsx deleted file mode 100644 index b0d6f356..00000000 --- a/client/config/sections.tsx +++ /dev/null @@ -1,270 +0,0 @@ -import { - Architecture, - CardGiftcard, - Category, - Coffee, - Download, - EmojiEvents, - FontDownload, - Groups, - Language, - Link as LinkIcon, - Map, - Margin, - MenuBook, - Palette, - Person, - Sailing, - School, - Settings as SettingsIcon, - Share, - Style, - Twitter, - VolunteerActivism, - Work, -} from '@mui/icons-material'; -import isEmpty from 'lodash/isEmpty'; -import { Section as SectionRecord, SectionType } from 'schema'; - -import Basics from '@/components/build/LeftSidebar/sections/Basics'; -import Location from '@/components/build/LeftSidebar/sections/Location'; -import Profiles from '@/components/build/LeftSidebar/sections/Profiles'; -import Section from '@/components/build/LeftSidebar/sections/Section'; -import CustomCSS from '@/components/build/RightSidebar/sections/CustomCSS'; -import Export from '@/components/build/RightSidebar/sections/Export'; -import Layout from '@/components/build/RightSidebar/sections/Layout'; -import Links from '@/components/build/RightSidebar/sections/Links'; -import Settings from '@/components/build/RightSidebar/sections/Settings'; -import Sharing from '@/components/build/RightSidebar/sections/Sharing'; -import Templates from '@/components/build/RightSidebar/sections/Templates'; -import Theme from '@/components/build/RightSidebar/sections/Theme'; -import Typography from '@/components/build/RightSidebar/sections/Typography'; -import { SidebarSection } from '@/types/app'; - -export const left: SidebarSection[] = [ - { - id: 'basics', - icon: <Person />, - component: <Basics />, - }, - { - id: 'location', - icon: <Map />, - component: <Location />, - }, - { - id: 'profiles', - icon: <Twitter />, - component: <Profiles />, - }, - { - id: 'work', - icon: <Work />, - component: ( - <Section - type={'work'} - addMore={true} - path="sections.work" - titleKey="name" - subtitleKey="position" - isEditable - isHideable - /> - ), - }, - { - id: 'education', - icon: <School />, - component: ( - <Section - type={'education'} - path="sections.education" - titleKey="institution" - subtitleKey="area" - isEditable - isHideable - /> - ), - }, - { - id: 'awards', - icon: <EmojiEvents />, - component: ( - <Section type={'awards'} path="sections.awards" titleKey="title" subtitleKey="awarder" isEditable isHideable /> - ), - }, - { - id: 'certifications', - icon: <CardGiftcard />, - component: ( - <Section - type={'certifications'} - path="sections.certifications" - titleKey="name" - subtitleKey="issuer" - isEditable - isHideable - /> - ), - }, - { - id: 'publications', - icon: <MenuBook />, - component: ( - <Section - type={'publications'} - path="sections.publications" - titleKey="name" - subtitleKey="publisher" - isEditable - isHideable - /> - ), - }, - { - id: 'skills', - icon: <Architecture />, - component: ( - <Section type={'skills'} path="sections.skills" titleKey="name" subtitleKey="level" isEditable isHideable /> - ), - }, - { - id: 'languages', - icon: <Language />, - component: ( - <Section type={'languages'} path="sections.languages" titleKey="name" subtitleKey="level" isEditable isHideable /> - ), - }, - { - id: 'interests', - icon: <Sailing />, - component: ( - <Section - type={'interests'} - path="sections.interests" - titleKey="name" - subtitleKey="keywords" - isEditable - isHideable - /> - ), - }, - { - id: 'volunteer', - icon: <VolunteerActivism />, - component: ( - <Section - type={'volunteer'} - path="sections.volunteer" - titleKey="organization" - subtitleKey="position" - isEditable - isHideable - /> - ), - }, - { - id: 'projects', - icon: <Coffee />, - component: ( - <Section - type={'projects'} - path="sections.projects" - titleKey="name" - subtitleKey="description" - isEditable - isHideable - /> - ), - }, - { - id: 'references', - icon: <Groups />, - component: ( - <Section - type={'references'} - path="sections.references" - titleKey="name" - subtitleKey="relationship" - isEditable - isHideable - /> - ), - }, -]; - -export const right: SidebarSection[] = [ - { - id: 'templates', - icon: <Category />, - component: <Templates />, - }, - { - id: 'layout', - icon: <Margin />, - component: <Layout />, - }, - { - id: 'typography', - icon: <FontDownload />, - component: <Typography />, - }, - { - id: 'theme', - icon: <Palette />, - component: <Theme />, - }, - { - id: 'css', - icon: <Style />, - component: <CustomCSS />, - }, - { - id: 'sharing', - icon: <Share />, - component: <Sharing />, - }, - { - id: 'export', - icon: <Download />, - component: <Export />, - }, - { - id: 'settings', - icon: <SettingsIcon />, - component: <Settings />, - }, - { - id: 'links', - icon: <LinkIcon />, - component: <Links />, - }, -]; - -export const getSectionsByType = (sections: Record<string, SectionRecord>, type: SectionType): SectionRecord[] => { - if (isEmpty(sections)) return []; - - return Object.entries(sections).reduce((acc, [id, section]) => { - if (section.type.startsWith(type) && section.isDuplicated) { - return [...acc, { ...section, id }]; - } - - return acc; - }, [] as SectionRecord[]); -}; - -export const getCustomSections = (sections: Record<string, SectionRecord>): SectionRecord[] => { - if (isEmpty(sections)) return []; - - return Object.entries(sections).reduce((acc, [id, section]) => { - if (section.type === 'custom') { - return [...acc, { ...section, id }]; - } - - return acc; - }, [] as SectionRecord[]); -}; - -const sections = [...left, ...right]; - -export default sections; diff --git a/client/config/theme.ts b/client/config/theme.ts deleted file mode 100644 index 2bc6548f..00000000 --- a/client/config/theme.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { createTheme, ThemeOptions } from '@mui/material/styles'; - -const theme: ThemeOptions = { - typography: { - fontSize: 12, - fontFamily: '"IBM Plex Sans", sans-serif', - }, - components: { - MuiButton: { - defaultProps: { - size: 'small', - variant: 'contained', - disableElevation: true, - }, - styleOverrides: { - root: { - textTransform: 'none', - padding: '6px 20px', - }, - }, - }, - MuiTextField: { - defaultProps: { - variant: 'outlined', - }, - }, - MuiInputBase: { - styleOverrides: { - input: { - boxShadow: 'none !important', - }, - }, - }, - MuiAppBar: { - styleOverrides: { - root: { - zIndex: 30, - }, - }, - }, - MuiTooltip: { - styleOverrides: { - tooltip: { - fontSize: 12, - }, - }, - }, - MuiDrawer: { - styleOverrides: { - root: { - zIndex: 40, - }, - paper: { - border: 'none', - }, - }, - }, - MuiModal: { - defaultProps: { - componentsProps: { - backdrop: { - className: 'backdrop-blur-sm', - }, - }, - }, - }, - }, -}; - -export const lightTheme = createTheme({ - ...theme, - palette: { - mode: 'light', - background: { default: '#fafafa', paper: '#f4f4f5' }, - primary: { main: '#18181b' }, - secondary: { main: '#14b8a6' }, - }, -}); - -export const darkTheme = createTheme({ - ...theme, - palette: { - mode: 'dark', - background: { default: '#09090b', paper: '#18181b' }, - primary: { main: '#f4f4f5' }, - secondary: { main: '#0d9488' }, - }, -}); diff --git a/client/constants/flags.ts b/client/constants/flags.ts deleted file mode 100644 index f78838b7..00000000 --- a/client/constants/flags.ts +++ /dev/null @@ -1,3 +0,0 @@ -import env from '@beam-australia/react-env'; - -export const FLAG_DISABLE_SIGNUPS = env('FLAG_DISABLE_SIGNUPS') === 'true'; diff --git a/client/constants/index.ts b/client/constants/index.ts deleted file mode 100644 index c0db8868..00000000 --- a/client/constants/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -// React Queries -export const FONTS_QUERY = 'fonts'; -export const RESUMES_QUERY = 'resumes'; - -// Regular Expressions -export const VALID_URL_REGEX = /[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&/=]*)/; - -// Date Formats -export const FILENAME_TIMESTAMP = 'DDMMYYYYHHmmss'; - -// Links -export const DOCS_URL = 'https://docs.rxresu.me'; -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/'; -export const GITHUB_URL = 'https://github.com/AmruthPillai/Reactive-Resume'; -export const PRODUCT_HUNT_URL = 'https://www.producthunt.com/posts/reactive-resume-v3'; -export const GITHUB_ISSUES_URL = 'https://github.com/AmruthPillai/Reactive-Resume/issues/new/choose'; - -// Default Error Message -export const DEFAULT_ERROR_MESSAGE = - 'Something went wrong while performing this action, please report this issue on GitHub.'; diff --git a/client/constants/tilt.ts b/client/constants/tilt.ts deleted file mode 100644 index 927e634a..00000000 --- a/client/constants/tilt.ts +++ /dev/null @@ -1,11 +0,0 @@ -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', -}; diff --git a/client/data/testimonials.ts b/client/data/testimonials.ts deleted file mode 100644 index 372e7403..00000000 --- a/client/data/testimonials.ts +++ /dev/null @@ -1,38 +0,0 @@ -export type Testimony = { - name: string; - message: string; -}; - -const testimonials: Testimony[] = [ - { - name: 'Yogeshwar B.', - message: `Hi Amruth! First off, many thanks for making RxResume! It was love at first sight for me when I saw it was a FOSS resume-builder with such a polished UI. It's been of great help for me as I've been applying to MS programs in international universities. The generated resume is slick as hell, and I spent a fraction of the time building it that I would have spent, had I chosen the "Microsoft Word/Google Docs" route. And the best thing was I spent absolutely no money on it!`, - }, - { - name: 'Chandani B.', - message: `Hi Amruth, Thank you for creating reactive resume and keeping it open source. I am about to use it in updating my profile and therefore wanted to express my gratitude. -As a new enthusiast with no previous background in coding, I thought it would be amazing to connect with you to learn more. Thanks`, - }, - { - name: 'Aadith R.', - message: `Hey, I just found out about Reactive Resume through a co-worker and it's damn good. Minimalist, Contains all the necessary things and is also editable.`, - }, - { - name: 'Krzysztof W.', - message: `I have just used Your CV builder to make something that jus might get me a job. Very nice website and so easy to use, perfect outcome. if i get the job i'll "buy you coffee" for sure i will recommend it to friends`, - }, - { - name: 'Bharat M.', - message: `Hi Amruth, I Just came across your open-source app, Reactive Resume while trying to search for some good resume builders online. I can't express how useful it is! I wish I found it earlier than yesterday! :D I appreciate your work. From now on, I'm gonna recommend the app to whoever I feel could make good use of it! Thank you very much for your work! I will remember this and would like to buy you coffee as soon as I can. What's better than donating for such a good cause! Thanks again. Have a great day!`, - }, - { - name: 'Shlok C.', - message: `Hey Amruth, I recently stumbled upon reactive resume and have started playing around with it. Kudos to you for building such an awesome open-source resume building tool..... Best wishes!`, - }, - { - name: 'Joao R.', - message: `Hi Amruth, Let me congratulate you on the awesome RxResume. After long and extensive searches on the internet for a good and clean CV generator, RxResume is truly a gem. I've been using it to create all my CVs.`, - }, -]; - -export default testimonials; diff --git a/client/modals/auth/ForgotPasswordModal.tsx b/client/modals/auth/ForgotPasswordModal.tsx deleted file mode 100644 index d28bd198..00000000 --- a/client/modals/auth/ForgotPasswordModal.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { joiResolver } from '@hookform/resolvers/joi'; -import { Password } from '@mui/icons-material'; -import { Button, TextField } from '@mui/material'; -import Joi from 'joi'; -import { useTranslation } from 'next-i18next'; -import { Controller, useForm } from 'react-hook-form'; -import { useMutation } from 'react-query'; - -import BaseModal from '@/components/shared/BaseModal'; -import { forgotPassword, ForgotPasswordParams } from '@/services/auth'; -import { ServerError } from '@/services/axios'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setModalState } from '@/store/modal/modalSlice'; - -type FormData = { - email: string; -}; - -const defaultState: FormData = { - email: '', -}; - -const schema = Joi.object({ - email: Joi.string() - .email({ tlds: { allow: false } }) - .required(), -}); - -const ForgotPasswordModal: React.FC = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const { open: isOpen } = useAppSelector((state) => state.modal['auth.forgot']); - - const { mutate, isLoading } = useMutation<void, ServerError, ForgotPasswordParams>(forgotPassword); - - const { reset, control, handleSubmit } = useForm<FormData>({ - defaultValues: defaultState, - resolver: joiResolver(schema), - }); - - const handleClose = () => { - dispatch(setModalState({ modal: 'auth.forgot', state: { open: false } })); - reset(); - }; - - const onSubmit = ({ email }: FormData) => { - mutate({ email }, { onSettled: handleClose }); - }; - - return ( - <> - <BaseModal - icon={<Password />} - isOpen={isOpen} - heading={t('modals.auth.forgot-password.heading')} - handleClose={handleClose} - footerChildren={ - <Button type="submit" disabled={isLoading} onClick={handleSubmit(onSubmit)}> - {t('modals.auth.forgot-password.actions.send-email')} - </Button> - } - > - <div className="grid gap-4"> - <p>{t('modals.auth.forgot-password.body')}</p> - - <form className="grid gap-4 xl:w-2/3"> - <Controller - name="email" - control={control} - render={({ field, fieldState }) => ( - <TextField - autoFocus - label={t('modals.auth.forgot-password.form.email.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - </form> - - <p className="text-xs">{t('modals.auth.forgot-password.help-text')}</p> - </div> - </BaseModal> - </> - ); -}; - -export default ForgotPasswordModal; diff --git a/client/modals/auth/LoginModal.tsx b/client/modals/auth/LoginModal.tsx deleted file mode 100644 index 51e9c1af..00000000 --- a/client/modals/auth/LoginModal.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import env from '@beam-australia/react-env'; -import { joiResolver } from '@hookform/resolvers/joi'; -import { Login, Visibility, VisibilityOff } from '@mui/icons-material'; -import { Button, IconButton, InputAdornment, TextField } from '@mui/material'; -import { CredentialResponse, GoogleLogin } from '@react-oauth/google'; -import Joi from 'joi'; -import isEmpty from 'lodash/isEmpty'; -import { Trans, useTranslation } from 'next-i18next'; -import { useMemo, useState } from 'react'; -import { Controller, useForm } from 'react-hook-form'; -import toast from 'react-hot-toast'; -import { useIsMutating, useMutation } from 'react-query'; - -import BaseModal from '@/components/shared/BaseModal'; -import { FLAG_DISABLE_SIGNUPS } from '@/constants/flags'; -import { login, LoginParams, loginWithGoogle, LoginWithGoogleParams } from '@/services/auth'; -import { ServerError } from '@/services/axios'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setModalState } from '@/store/modal/modalSlice'; - -type FormData = { - identifier: string; - password: string; -}; - -const defaultState: FormData = { - identifier: '', - password: '', -}; - -const schema = Joi.object({ - identifier: Joi.string().required(), - password: Joi.string().min(6).required(), -}); - -const LoginModal: React.FC = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const [showPassword, setShowPassword] = useState(false); - - const isMutating = useIsMutating(); - const isLoading = useMemo(() => isMutating > 0, [isMutating]); - - const { open: isOpen } = useAppSelector((state) => state.modal['auth.login']); - - const { reset, control, handleSubmit } = useForm<FormData>({ - defaultValues: defaultState, - resolver: joiResolver(schema), - }); - - const { mutateAsync: loginMutation } = useMutation<void, ServerError, LoginParams>(login); - - const { mutateAsync: loginWithGoogleMutation } = useMutation<void, ServerError, LoginWithGoogleParams>( - loginWithGoogle, - ); - - const handleClose = () => { - dispatch(setModalState({ modal: 'auth.login', state: { open: false } })); - reset(); - }; - - const onSubmit = async ({ identifier, password }: FormData) => { - await loginMutation({ identifier, password }); - - handleClose(); - }; - - const handleCreateAccount = () => { - handleClose(); - dispatch(setModalState({ modal: 'auth.register', state: { open: true } })); - }; - - const handleRecoverAccount = () => { - handleClose(); - dispatch(setModalState({ modal: 'auth.forgot', state: { open: true } })); - }; - - const handleLoginWithGoogle = async (response: CredentialResponse) => { - if (response.credential) { - await loginWithGoogleMutation({ credential: response.credential }, { onError: handleGoogleLoginError }); - - handleClose(); - } - }; - - const handleGoogleLoginError = () => { - toast.error("Google doesn't seem to be responding, please try logging in using email/password instead."); - }; - - const PasswordVisibility = (): React.ReactElement => { - const handleToggle = () => setShowPassword((showPassword) => !showPassword); - - return ( - <InputAdornment position="end"> - <IconButton edge="end" onClick={handleToggle}> - {showPassword ? <VisibilityOff /> : <Visibility />} - </IconButton> - </InputAdornment> - ); - }; - - return ( - <BaseModal - icon={<Login />} - isOpen={isOpen} - heading={t('modals.auth.login.heading')} - handleClose={handleClose} - footerChildren={ - <div className="flex gap-4"> - {!isEmpty(env('GOOGLE_CLIENT_ID')) && ( - <GoogleLogin onSuccess={handleLoginWithGoogle} onError={handleGoogleLoginError} /> - )} - - <Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}> - {t('modals.auth.login.actions.login')} - </Button> - </div> - } - > - <p>{t('modals.auth.login.body')}</p> - - <form className="grid gap-4"> - <Controller - name="identifier" - control={control} - render={({ field, fieldState }) => ( - <TextField - autoFocus - label={t('modals.auth.login.form.username.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message || t('modals.auth.login.form.username.help-text')} - {...field} - /> - )} - /> - - <Controller - name="password" - control={control} - render={({ field, fieldState }) => ( - <TextField - type={showPassword ? 'text' : 'password'} - label={t('modals.auth.login.form.password.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - InputProps={{ endAdornment: <PasswordVisibility /> }} - {...field} - /> - )} - /> - </form> - - {!FLAG_DISABLE_SIGNUPS && ( - <p className="text-xs"> - <Trans t={t} i18nKey="modals.auth.login.register-text"> - If you don't have one, you can <a onClick={handleCreateAccount}>create an account here.</a> - </Trans> - </p> - )} - - <p className="text-xs"> - <Trans t={t} i18nKey="modals.auth.login.recover-text"> - In case you have forgotten your password, you can - <a onClick={handleRecoverAccount}>recover your account here.</a> - </Trans> - </p> - </BaseModal> - ); -}; - -export default LoginModal; diff --git a/client/modals/auth/RegisterModal.tsx b/client/modals/auth/RegisterModal.tsx deleted file mode 100644 index 3c7d8a8b..00000000 --- a/client/modals/auth/RegisterModal.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import env from '@beam-australia/react-env'; -import { joiResolver } from '@hookform/resolvers/joi'; -import { HowToReg } from '@mui/icons-material'; -import { Button, TextField } from '@mui/material'; -import { CredentialResponse, GoogleLogin } from '@react-oauth/google'; -import Joi from 'joi'; -import isEmpty from 'lodash/isEmpty'; -import { Trans, useTranslation } from 'next-i18next'; -import { Controller, useForm } from 'react-hook-form'; -import toast from 'react-hot-toast'; -import { useMutation } from 'react-query'; - -import BaseModal from '@/components/shared/BaseModal'; -import { loginWithGoogle, LoginWithGoogleParams, register as registerUser, RegisterParams } from '@/services/auth'; -import { ServerError } from '@/services/axios'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setModalState } from '@/store/modal/modalSlice'; - -type FormData = { - name: string; - username: string; - email: string; - password: string; - confirmPassword: string; -}; - -const defaultState: FormData = { - name: '', - username: '', - email: '', - password: '', - confirmPassword: '', -}; - -const schema = Joi.object({ - name: Joi.string().required(), - username: Joi.string() - .lowercase() - .min(3) - .regex(/^[a-z0-9-]+$/, 'only lowercase characters, numbers and hyphens') - .required(), - email: Joi.string() - .email({ tlds: { allow: false } }) - .required(), - password: Joi.string().min(6).required(), - confirmPassword: Joi.string().min(6).required().valid(Joi.ref('password')), -}); - -const RegisterModal: React.FC = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const { open: isOpen } = useAppSelector((state) => state.modal['auth.register']); - - const { reset, control, handleSubmit } = useForm<FormData>({ - defaultValues: defaultState, - resolver: joiResolver(schema), - }); - - const { mutateAsync, isLoading } = useMutation<void, ServerError, RegisterParams>(registerUser); - - const { mutateAsync: loginWithGoogleMutation } = useMutation<void, ServerError, LoginWithGoogleParams>( - loginWithGoogle, - ); - - const handleClose = () => { - dispatch(setModalState({ modal: 'auth.register', state: { open: false } })); - reset(); - }; - - const onSubmit = async ({ name, username, email, password }: FormData) => { - await mutateAsync({ name, username, email, password }); - handleClose(); - }; - - const handleLogin = () => { - handleClose(); - dispatch(setModalState({ modal: 'auth.login', state: { open: true } })); - }; - - const handleLoginWithGoogle = async (response: CredentialResponse) => { - if (response.credential) { - await loginWithGoogleMutation({ credential: response.credential }, { onError: handleGoogleLoginError }); - - handleClose(); - } - }; - - const handleGoogleLoginError = () => { - toast("Google doesn't seem to be responding, please try logging in using email/password instead."); - }; - - return ( - <BaseModal - icon={<HowToReg />} - isOpen={isOpen} - heading={t('modals.auth.register.heading')} - handleClose={handleClose} - footerChildren={ - <div className="flex gap-4"> - {!isEmpty(env('GOOGLE_CLIENT_ID')) && ( - <GoogleLogin onSuccess={handleLoginWithGoogle} onError={handleGoogleLoginError} /> - )} - - <Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}> - {t('modals.auth.register.actions.register')} - </Button> - </div> - } - > - <p>{t('modals.auth.register.body')}</p> - - <form className="grid gap-4 md:grid-cols-2"> - <Controller - name="name" - control={control} - render={({ field, fieldState }) => ( - <TextField - autoFocus - label={t('modals.auth.register.form.name.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="username" - control={control} - render={({ field, fieldState }) => ( - <TextField - label={t('modals.auth.register.form.username.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="email" - control={control} - render={({ field, fieldState }) => ( - <TextField - type="email" - label={t('modals.auth.register.form.email.label')} - className="col-span-2" - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="password" - control={control} - render={({ field, fieldState }) => ( - <TextField - type="password" - label={t('modals.auth.register.form.password.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="confirmPassword" - control={control} - render={({ field, fieldState }) => ( - <TextField - type="password" - label={t('modals.auth.register.form.confirm-password.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - </form> - - <p className="text-xs"> - <Trans t={t} i18nKey="modals.auth.register.loginText"> - If you already have an account, you can <a onClick={handleLogin}>login here</a>. - </Trans> - </p> - </BaseModal> - ); -}; - -export default RegisterModal; diff --git a/client/modals/auth/ResetPasswordModal.tsx b/client/modals/auth/ResetPasswordModal.tsx deleted file mode 100644 index 5a03f781..00000000 --- a/client/modals/auth/ResetPasswordModal.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { joiResolver } from '@hookform/resolvers/joi'; -import { LockReset } from '@mui/icons-material'; -import { Button, TextField } from '@mui/material'; -import Joi from 'joi'; -import get from 'lodash/get'; -import isEmpty from 'lodash/isEmpty'; -import { useTranslation } from 'next-i18next'; -import { Controller, useForm } from 'react-hook-form'; -import { useMutation } from 'react-query'; - -import BaseModal from '@/components/shared/BaseModal'; -import { resetPassword, ResetPasswordParams } from '@/services/auth'; -import { ServerError } from '@/services/axios'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { ModalState, setModalState } from '@/store/modal/modalSlice'; - -type Payload = { - resetToken?: string; -}; - -type FormData = { - password: string; - confirmPassword: string; -}; - -const defaultState: FormData = { - password: '', - confirmPassword: '', -}; - -const schema = Joi.object({ - password: Joi.string().min(6).required(), - confirmPassword: Joi.string().min(6).required().valid(Joi.ref('password')), -}); - -const ResetPasswordModal: React.FC = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const { open: isOpen, payload } = useAppSelector((state) => state.modal['auth.reset']) as ModalState; - const resetData = get(payload, 'item', {}) as Payload; - - const { mutateAsync, isLoading } = useMutation<void, ServerError, ResetPasswordParams>(resetPassword); - - const { reset, control, handleSubmit } = useForm<FormData>({ - defaultValues: defaultState, - resolver: joiResolver(schema), - }); - - const handleClose = () => { - dispatch(setModalState({ modal: 'auth.reset', state: { open: false } })); - reset(); - }; - - const onSubmit = async ({ password }: FormData) => { - if (!resetData.resetToken || isEmpty(resetData.resetToken)) return; - - await mutateAsync({ resetToken: resetData.resetToken, password }); - - handleClose(); - }; - - return ( - <BaseModal - icon={<LockReset />} - isOpen={isOpen} - heading={t('modals.auth.reset-password.heading')} - handleClose={handleClose} - footerChildren={ - <Button type="submit" disabled={isLoading} onClick={handleSubmit(onSubmit)}> - {t('modals.auth.reset-password.actions.set-password')} - </Button> - } - > - <p>{t('modals.auth.reset-password.body')}</p> - - <form className="grid gap-4 md:grid-cols-2"> - <Controller - name="password" - control={control} - render={({ field, fieldState }) => ( - <TextField - autoFocus - type="password" - label={t('modals.auth.reset-password.form.password.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="confirmPassword" - control={control} - render={({ field, fieldState }) => ( - <TextField - type="password" - label={t('modals.auth.reset-password.form.confirm-password.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - </form> - </BaseModal> - ); -}; - -export default ResetPasswordModal; diff --git a/client/modals/auth/UserProfileModal.tsx b/client/modals/auth/UserProfileModal.tsx deleted file mode 100644 index b83f831b..00000000 --- a/client/modals/auth/UserProfileModal.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import { joiResolver } from '@hookform/resolvers/joi'; -import { CrisisAlert, ManageAccounts } from '@mui/icons-material'; -import { Button, Divider, TextField } from '@mui/material'; -import Joi from 'joi'; -import { useRouter } from 'next/router'; -import { Trans, useTranslation } from 'next-i18next'; -import { useEffect, useMemo, useState } from 'react'; -import { Controller, useForm } from 'react-hook-form'; -import { useMutation } from 'react-query'; - -import Avatar from '@/components/shared/Avatar'; -import BaseModal from '@/components/shared/BaseModal'; -import { deleteAccount, updateProfile, UpdateProfileParams } from '@/services/auth'; -import { ServerError } from '@/services/axios'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setModalState } from '@/store/modal/modalSlice'; - -type FormData = { - name: string; - email: string; -}; - -const defaultState: FormData = { - name: '', - email: '', -}; - -const schema = Joi.object({ - name: Joi.string().required(), - email: Joi.string() - .email({ tlds: { allow: false } }) - .required(), -}); - -const UserProfileModal = () => { - const router = useRouter(); - - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const [deleteText, setDeleteText] = useState<string>(''); - const isDeleteTextValid = useMemo(() => deleteText.toLowerCase() === 'delete', [deleteText]); - - const user = useAppSelector((state) => state.auth.user); - const { open: isOpen } = useAppSelector((state) => state.modal['auth.profile']); - - const { mutateAsync: deleteAccountMutation } = useMutation<void, ServerError>(deleteAccount); - const { mutateAsync: updateProfileMutation } = useMutation<void, ServerError, UpdateProfileParams>(updateProfile); - - const { reset, getFieldState, control, handleSubmit } = useForm<FormData>({ - defaultValues: defaultState, - resolver: joiResolver(schema), - }); - - useEffect(() => { - if (user && !getFieldState('name').isTouched && !getFieldState('email').isTouched) { - reset({ name: user.name, email: user.email }); - } - }, [user]); - - const handleClose = () => { - dispatch(setModalState({ modal: 'auth.profile', state: { open: false } })); - }; - - const handleUpdate = handleSubmit(async (data) => { - handleClose(); - await updateProfileMutation({ name: data.name }); - }); - - const handleDelete = async () => { - await deleteAccountMutation(); - handleClose(); - - router.push('/'); - }; - - return ( - <BaseModal isOpen={isOpen} handleClose={handleClose} heading="Your Account" icon={<ManageAccounts />}> - <div className="grid gap-4"> - <form className="grid gap-4 xl:w-2/3"> - <div className="flex items-center gap-4"> - <Avatar interactive={false} /> - - <div className="grid flex-1 gap-1.5"> - <Controller - name="name" - control={control} - render={({ field, fieldState }) => ( - <TextField - autoFocus - label={t('modals.auth.profile.form.name.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <p className="pl-4 text-[10.25px] opacity-50"> - <Trans t={t} i18nKey="modals.auth.profile.form.avatar.help-text"> - You can update your profile picture on{' '} - <a href="https://gravatar.com/" target="_blank" rel="noreferrer"> - Gravatar - </a> - </Trans> - </p> - </div> - </div> - - <Controller - name="email" - control={control} - render={({ field, fieldState }) => ( - <TextField - disabled - label={t('modals.auth.profile.form.email.label')} - error={!!fieldState.error} - helperText={t('modals.auth.profile.form.email.help-text')} - {...field} - /> - )} - /> - - <div> - <Button onClick={handleUpdate}>{t('modals.auth.profile.actions.save')}</Button> - </div> - </form> - - <div className="my-2"> - <Divider /> - </div> - - <div className="flex items-center gap-2"> - <CrisisAlert /> - <h5 className="font-medium">{t('modals.auth.profile.delete-account.heading')}</h5> - </div> - - <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 - value={deleteText} - placeholder="Type 'delete' to confirm" - onChange={(e) => setDeleteText(e.target.value)} - /> - - <div> - <Button variant="contained" color="error" disabled={!isDeleteTextValid} onClick={handleDelete}> - {t('modals.auth.profile.delete-account.actions.delete')} - </Button> - </div> - </div> - </div> - </BaseModal> - ); -}; - -export default UserProfileModal; diff --git a/client/modals/builder/sections/AwardModal.tsx b/client/modals/builder/sections/AwardModal.tsx deleted file mode 100644 index 1eb555c8..00000000 --- a/client/modals/builder/sections/AwardModal.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import { joiResolver } from '@hookform/resolvers/joi'; -import { Add, DriveFileRenameOutline } from '@mui/icons-material'; -import { Button, TextField } from '@mui/material'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { Award, SectionPath } from 'schema'; -import dayjs from 'dayjs'; -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 BaseModal from '@/components/shared/BaseModal'; -import MarkdownSupported from '@/components/shared/MarkdownSupported'; -import { VALID_URL_REGEX } from '@/constants/index'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setModalState } from '@/store/modal/modalSlice'; -import { addItem, editItem } from '@/store/resume/resumeSlice'; - -type FormData = Award; - -const path: SectionPath = 'sections.awards'; - -const defaultState: FormData = { - title: '', - awarder: '', - date: '', - url: '', - summary: '', -}; - -const schema = Joi.object<FormData>().keys({ - id: Joi.string(), - title: Joi.string().required(), - awarder: Joi.string().required(), - date: Joi.string().allow(''), - url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''), - summary: Joi.string().allow(''), -}); - -const AwardModal: React.FC = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`)); - const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]); - - const item: FormData = get(payload, 'item', null); - const isEditMode = useMemo(() => !!item, [item]); - - 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, - resolver: joiResolver(schema), - }); - - const onSubmit = (formData: FormData) => { - if (isEditMode) { - dispatch(editItem({ path: `${path}.items`, value: formData })); - } else { - dispatch(addItem({ path: `${path}.items`, value: formData })); - } - - handleClose(); - }; - - const handleClose = () => { - dispatch( - setModalState({ - modal: `builder.${path}`, - state: { open: false }, - }), - ); - - reset(defaultState); - }; - - useEffect(() => { - if (!isEmpty(item)) { - reset(item); - } - }, [item, reset]); - - return ( - <BaseModal - icon={isEditMode ? <DriveFileRenameOutline /> : <Add />} - isOpen={isOpen} - handleClose={handleClose} - heading={isEditMode ? editText : addText} - footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} - > - <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}> - <Controller - name="title" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - autoFocus - label={t('builder.common.form.title.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="awarder" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - label={t('builder.leftSidebar.sections.awards.form.awarder.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="date" - control={control} - render={({ field, fieldState }) => ( - <DatePicker - openTo="year" - inputRef={field.ref} - label={t('builder.common.form.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} - /> - ), - }} - onChange={(date: dayjs.Dayjs | null) => { - date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD')); - }} - /> - )} - /> - - <Controller - name="url" - control={control} - render={({ field, fieldState }) => ( - <TextField - label={t('builder.common.form.url.label')} - placeholder="https://" - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="summary" - control={control} - render={({ field, fieldState }) => ( - <TextField - multiline - minRows={3} - maxRows={6} - label={t('builder.common.form.summary.label')} - className="col-span-2" - error={!!fieldState.error} - helperText={fieldState.error?.message || <MarkdownSupported />} - {...field} - /> - )} - /> - <input type="submit" style={{ display: 'none' }} /> - </form> - </BaseModal> - ); -}; - -export default AwardModal; diff --git a/client/modals/builder/sections/CertificateModal.tsx b/client/modals/builder/sections/CertificateModal.tsx deleted file mode 100644 index c5addd4e..00000000 --- a/client/modals/builder/sections/CertificateModal.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import { joiResolver } from '@hookform/resolvers/joi'; -import { Add, DriveFileRenameOutline } from '@mui/icons-material'; -import { Button, TextField } from '@mui/material'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { Certificate, SectionPath } from 'schema'; -import dayjs from 'dayjs'; -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 BaseModal from '@/components/shared/BaseModal'; -import MarkdownSupported from '@/components/shared/MarkdownSupported'; -import { VALID_URL_REGEX } from '@/constants/index'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setModalState } from '@/store/modal/modalSlice'; -import { addItem, editItem } from '@/store/resume/resumeSlice'; - -type FormData = Certificate; - -const path: SectionPath = 'sections.certifications'; - -const defaultState: FormData = { - name: '', - issuer: '', - date: '', - url: '', - summary: '', -}; - -const schema = Joi.object<FormData>().keys({ - id: Joi.string(), - name: Joi.string().required(), - issuer: Joi.string().required(), - date: Joi.string().allow(''), - url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''), - summary: Joi.string().allow(''), -}); - -const CertificateModal: React.FC = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`)); - const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]); - const item: FormData = get(payload, 'item', null); - - const isEditMode = useMemo(() => !!item, [item]); - - 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, - resolver: joiResolver(schema), - }); - - const onSubmit = (formData: FormData) => { - if (isEditMode) { - dispatch(editItem({ path: `${path}.items`, value: formData })); - } else { - dispatch(addItem({ path: `${path}.items`, value: formData })); - } - - handleClose(); - }; - - const handleClose = () => { - dispatch( - setModalState({ - modal: `builder.${path}`, - state: { open: false }, - }), - ); - - reset(defaultState); - }; - - useEffect(() => { - if (!isEmpty(item)) { - reset(item); - } - }, [item, reset]); - - return ( - <BaseModal - icon={isEditMode ? <DriveFileRenameOutline /> : <Add />} - isOpen={isOpen} - handleClose={handleClose} - heading={isEditMode ? editText : addText} - footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} - > - <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}> - <Controller - name="name" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - autoFocus - label={t('builder.common.form.name.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="issuer" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - label={t('builder.leftSidebar.sections.certifications.form.issuer.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="date" - control={control} - render={({ field, fieldState }) => ( - <DatePicker - openTo="year" - inputRef={field.ref} - label={t('builder.common.form.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} - /> - ), - }} - onChange={(date: dayjs.Dayjs | null) => { - date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD')); - }} - /> - )} - /> - - <Controller - name="url" - control={control} - render={({ field, fieldState }) => ( - <TextField - label={t('builder.common.form.url.label')} - placeholder="https://" - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="summary" - control={control} - render={({ field, fieldState }) => ( - <TextField - multiline - minRows={3} - maxRows={6} - label={t('builder.common.form.summary.label')} - className="col-span-2" - error={!!fieldState.error} - helperText={fieldState.error?.message || <MarkdownSupported />} - {...field} - /> - )} - /> - <input type="submit" style={{ display: 'none' }} /> - </form> - </BaseModal> - ); -}; - -export default CertificateModal; diff --git a/client/modals/builder/sections/CustomModal.tsx b/client/modals/builder/sections/CustomModal.tsx deleted file mode 100644 index 33d20b1d..00000000 --- a/client/modals/builder/sections/CustomModal.tsx +++ /dev/null @@ -1,298 +0,0 @@ -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 dayjs from 'dayjs'; -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 { Custom } from 'schema'; - -import ArrayInput from '@/components/shared/ArrayInput'; -import BaseModal from '@/components/shared/BaseModal'; -import MarkdownSupported from '@/components/shared/MarkdownSupported'; -import { VALID_URL_REGEX } from '@/constants/index'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setModalState } from '@/store/modal/modalSlice'; -import { addItem, editItem } from '@/store/resume/resumeSlice'; - -type FormData = Custom; - -export type CustomModalPayload = { - path: string; - item?: Custom; -}; - -const defaultState: FormData = { - title: '', - subtitle: '', - date: { - start: '', - end: '', - }, - url: '', - level: '', - levelNum: 0, - summary: '', - keywords: [], -}; - -const schema = Joi.object<FormData>().keys({ - id: Joi.string(), - title: Joi.string().required(), - subtitle: Joi.string().allow(''), - date: Joi.object().keys({ - start: Joi.string().allow(''), - end: Joi.string().allow(''), - }), - url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''), - level: Joi.string().allow(''), - levelNum: Joi.number().min(0).max(10), - summary: Joi.string().allow(''), - keywords: Joi.array().items(Joi.string().optional()), -}); - -const CustomModal: React.FC = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const { open: isOpen, payload } = useAppSelector((state) => state.modal['builder.sections.custom']); - - const path: string = get(payload, 'path', 'sections.custom'); - const item: FormData = get(payload, 'item', null); - const isEditMode = useMemo(() => !!item, [item]); - - const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`)); - - 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, - resolver: joiResolver(schema), - }); - - const onSubmit = (formData: FormData) => { - if (isEditMode) { - dispatch(editItem({ path: `${path}.items`, value: formData })); - } else { - dispatch(addItem({ path: `${path}.items`, value: formData })); - } - - handleClose(); - }; - - const handleClose = () => { - dispatch( - setModalState({ - modal: 'builder.sections.custom', - state: { open: false }, - }), - ); - - reset(defaultState); - }; - - useEffect(() => { - if (!isEmpty(item)) { - reset(item); - } - }, [item, reset]); - - return ( - <BaseModal - icon={isEditMode ? <DriveFileRenameOutline /> : <Add />} - isOpen={isOpen} - handleClose={handleClose} - heading={isEditMode ? editText : addText} - footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} - > - <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}> - <Controller - name="title" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - autoFocus - label={t('builder.common.form.title.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="subtitle" - control={control} - render={({ field, fieldState }) => ( - <TextField - label={t('builder.common.form.subtitle.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="date.start" - control={control} - render={({ field, fieldState }) => ( - <DatePicker - openTo="year" - inputRef={field.ref} - 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} - /> - ), - }} - onChange={(date: dayjs.Dayjs | null) => { - date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD')); - }} - /> - )} - /> - - <Controller - name="date.end" - control={control} - render={({ field, fieldState }) => ( - <DatePicker - openTo="year" - inputRef={field.ref} - 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('builder.common.form.end-date.help-text')} - /> - ), - }} - onChange={(date: dayjs.Dayjs | null) => { - date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD')); - }} - /> - )} - /> - - <Controller - name="url" - control={control} - render={({ field, fieldState }) => ( - <TextField - label={t('builder.common.form.url.label')} - placeholder="https://" - className="col-span-2" - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="level" - control={control} - render={({ field, fieldState }) => ( - <TextField - label={t('builder.common.form.level.label')} - className="col-span-2" - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="levelNum" - control={control} - render={({ field }) => ( - <div className="col-span-2"> - <h4 className="mb-3 font-semibold">{t('builder.common.form.levelNum.label')}</h4> - - <div className="px-10"> - <Slider - name={field.name} - value={field.value} - onChange={(_, value) => field.onChange(value as number)} - marks={[ - { - value: 0, - label: 'Disable', - }, - { - value: 1, - label: 'Beginner', - }, - { - value: 10, - label: 'Expert', - }, - ]} - min={0} - max={10} - defaultValue={0} - color="secondary" - valueLabelDisplay="auto" - aria-label={t('builder.common.form.levelNum.label')} - /> - </div> - </div> - )} - /> - - <Controller - name="summary" - control={control} - render={({ field, fieldState }) => ( - <TextField - multiline - minRows={3} - maxRows={6} - className="col-span-2" - error={!!fieldState.error} - label={t('builder.common.form.summary.label')} - helperText={fieldState.error?.message || <MarkdownSupported />} - {...field} - /> - )} - /> - - <Controller - name="keywords" - control={control} - render={({ field, fieldState }) => ( - <ArrayInput - label={t('builder.common.form.keywords.label')} - value={field.value as string[]} - onChange={field.onChange} - errors={fieldState.error} - className="col-span-2" - /> - )} - /> - <input type="submit" style={{ display: 'none' }} /> - </form> - </BaseModal> - ); -}; - -export default CustomModal; diff --git a/client/modals/builder/sections/EducationModal.tsx b/client/modals/builder/sections/EducationModal.tsx deleted file mode 100644 index 114c3fa3..00000000 --- a/client/modals/builder/sections/EducationModal.tsx +++ /dev/null @@ -1,275 +0,0 @@ -import { joiResolver } from '@hookform/resolvers/joi'; -import { Add, DriveFileRenameOutline } from '@mui/icons-material'; -import { Button, TextField } from '@mui/material'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import dayjs from 'dayjs'; -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 { Education, SectionPath } from 'schema'; - -import ArrayInput from '@/components/shared/ArrayInput'; -import BaseModal from '@/components/shared/BaseModal'; -import MarkdownSupported from '@/components/shared/MarkdownSupported'; -import { VALID_URL_REGEX } from '@/constants/index'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setModalState } from '@/store/modal/modalSlice'; -import { addItem, editItem } from '@/store/resume/resumeSlice'; - -type FormData = Education; - -const path: SectionPath = 'sections.education'; - -const defaultState: FormData = { - institution: '', - degree: '', - area: '', - score: '', - date: { - start: '', - end: '', - }, - url: '', - summary: '', - courses: [], -}; - -const schema = Joi.object<FormData>().keys({ - id: Joi.string(), - institution: Joi.string().required(), - degree: Joi.string().required(), - area: Joi.string().allow(''), - score: Joi.string().allow(''), - date: Joi.object().keys({ - start: Joi.string().allow(''), - end: Joi.string().allow(''), - }), - url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''), - summary: Joi.string().allow(''), - courses: Joi.array().items(Joi.string().optional()), -}); - -const EducationModal: React.FC = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`)); - const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]); - - const item: FormData = get(payload, 'item', null); - const isEditMode = useMemo(() => !!item, [item]); - - 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, - formState: { isDirty }, - } = useForm<FormData>({ - defaultValues: defaultState, - resolver: joiResolver(schema), - }); - - const onSubmit = (formData: FormData) => { - if (isEditMode) { - dispatch(editItem({ path: `${path}.items`, value: formData })); - } else { - dispatch(addItem({ path: `${path}.items`, value: formData })); - } - - handleClose(); - }; - - const handleClose = () => { - dispatch( - setModalState({ - modal: `builder.${path}`, - state: { open: false }, - }), - ); - - reset(defaultState); - }; - - const handleModalClose = () => { - if (isDirty) { - if (confirm('You have unsaved changes, Do you want to discard the changes?')) handleClose(); - else return; - } - handleClose(); - }; - - useEffect(() => { - if (!isEmpty(item)) { - reset(item); - } - }, [item, reset]); - - return ( - <BaseModal - icon={isEditMode ? <DriveFileRenameOutline /> : <Add />} - isOpen={isOpen} - handleClose={handleModalClose} - heading={isEditMode ? editText : addText} - footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} - > - <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}> - <Controller - name="institution" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - autoFocus - label={t('builder.leftSidebar.sections.education.form.institution.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="degree" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - label={t('builder.leftSidebar.sections.education.form.degree.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="area" - control={control} - render={({ field, fieldState }) => ( - <TextField - label={t('builder.leftSidebar.sections.education.form.area-study.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="score" - control={control} - render={({ field, fieldState }) => ( - <TextField - label={t('builder.leftSidebar.sections.education.form.grade.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="date.start" - control={control} - render={({ field, fieldState }) => ( - <DatePicker - openTo="year" - inputRef={field.ref} - label={t('builder.common.form.start-date.label')} - value={dayjs(field.value)} - views={['year', 'month', 'day']} - 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')); - }} - /> - )} - /> - - <Controller - name="date.end" - control={control} - render={({ field, fieldState }) => ( - <DatePicker - openTo="year" - inputRef={field.ref} - label={t('builder.common.form.end-date.label')} - value={dayjs(field.value)} - views={['year', 'month', 'day']} - 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')); - }} - /> - )} - /> - - <Controller - name="url" - control={control} - render={({ field, fieldState }) => ( - <TextField - label={t('builder.common.form.url.label')} - placeholder="https://" - className="col-span-2" - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="summary" - control={control} - render={({ field, fieldState }) => ( - <TextField - multiline - minRows={3} - maxRows={6} - label={t('builder.common.form.summary.label')} - className="col-span-2" - error={!!fieldState.error} - helperText={fieldState.error?.message || <MarkdownSupported />} - {...field} - /> - )} - /> - - <Controller - name="courses" - control={control} - render={({ field, fieldState }) => ( - <ArrayInput - label={t('builder.leftSidebar.sections.education.form.courses.label')} - value={field.value as string[]} - onChange={field.onChange} - errors={fieldState.error} - className="col-span-2" - /> - )} - /> - <input type="submit" style={{ display: 'none' }} /> - </form> - </BaseModal> - ); -}; - -export default EducationModal; diff --git a/client/modals/builder/sections/InterestModal.tsx b/client/modals/builder/sections/InterestModal.tsx deleted file mode 100644 index 54ea8a18..00000000 --- a/client/modals/builder/sections/InterestModal.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { joiResolver } from '@hookform/resolvers/joi'; -import { Add, DriveFileRenameOutline } from '@mui/icons-material'; -import { Button, TextField } from '@mui/material'; -import { Interest, 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 ArrayInput from '@/components/shared/ArrayInput'; -import BaseModal from '@/components/shared/BaseModal'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setModalState } from '@/store/modal/modalSlice'; -import { addItem, editItem } from '@/store/resume/resumeSlice'; - -type FormData = Interest; - -const path: SectionPath = 'sections.interests'; - -const defaultState: FormData = { - name: '', - keywords: [], -}; - -const schema = Joi.object<FormData>().keys({ - id: Joi.string(), - name: Joi.string().required(), - keywords: Joi.array().items(Joi.string().optional()), -}); - -const InterestModal: React.FC = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`)); - const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]); - - const item: FormData = get(payload, 'item', null); - const isEditMode = useMemo(() => !!item, [item]); - - 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, - resolver: joiResolver(schema), - }); - - const onSubmit = (formData: FormData) => { - if (isEditMode) { - dispatch(editItem({ path: `${path}.items`, value: formData })); - } else { - dispatch(addItem({ path: `${path}.items`, value: formData })); - } - - handleClose(); - }; - - const handleClose = () => { - dispatch( - setModalState({ - modal: `builder.${path}`, - state: { open: false }, - }), - ); - - reset(defaultState); - }; - - useEffect(() => { - if (!isEmpty(item)) { - reset(item); - } - }, [item, reset]); - - return ( - <BaseModal - icon={isEditMode ? <DriveFileRenameOutline /> : <Add />} - isOpen={isOpen} - handleClose={handleClose} - heading={isEditMode ? editText : addText} - footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} - > - <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}> - <Controller - name="name" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - autoFocus - label={t('builder.common.form.name.label')} - className="col-span-2" - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="keywords" - control={control} - render={({ field, fieldState }) => ( - <ArrayInput - label={t('builder.common.form.keywords.label')} - value={field.value as string[]} - onChange={field.onChange} - errors={fieldState.error} - className="col-span-2" - /> - )} - /> - <input type="submit" style={{ display: 'none' }} /> - </form> - </BaseModal> - ); -}; - -export default InterestModal; diff --git a/client/modals/builder/sections/LanguageModal.tsx b/client/modals/builder/sections/LanguageModal.tsx deleted file mode 100644 index fc3bb6c7..00000000 --- a/client/modals/builder/sections/LanguageModal.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import { joiResolver } from '@hookform/resolvers/joi'; -import { Add, DriveFileRenameOutline } from '@mui/icons-material'; -import { Button, Slider, TextField } from '@mui/material'; -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'; -import { setModalState } from '@/store/modal/modalSlice'; -import { addItem, editItem } from '@/store/resume/resumeSlice'; - -type FormData = Language; - -const path: SectionPath = 'sections.languages'; - -const defaultState: FormData = { - name: '', - level: '', - levelNum: 0, -}; - -const schema = Joi.object<FormData>().keys({ - id: Joi.string(), - name: Joi.string().required(), - level: Joi.string().required(), - levelNum: Joi.number().min(0).max(10).required(), -}); - -const LanguageModal: React.FC = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`)); - const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]); - - const item: FormData = get(payload, 'item', null); - const isEditMode = useMemo(() => !!item, [item]); - - 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, - resolver: joiResolver(schema), - }); - - const onSubmit = (formData: FormData) => { - if (isEditMode) { - dispatch(editItem({ path: `${path}.items`, value: formData })); - } else { - dispatch(addItem({ path: `${path}.items`, value: formData })); - } - - handleClose(); - }; - - const handleClose = () => { - dispatch( - setModalState({ - modal: `builder.${path}`, - state: { open: false }, - }), - ); - - reset(defaultState); - }; - - useEffect(() => { - if (!isEmpty(item)) { - reset(item); - } - }, [item, reset]); - - return ( - <BaseModal - icon={isEditMode ? <DriveFileRenameOutline /> : <Add />} - isOpen={isOpen} - handleClose={handleClose} - heading={isEditMode ? editText : addText} - footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} - > - <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}> - <Controller - name="name" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - autoFocus - label={t('builder.common.form.name.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="level" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - label={t('builder.common.form.level.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="levelNum" - control={control} - render={({ field }) => ( - <div className="col-span-2"> - <h4 className="mb-3 font-semibold">{t('builder.common.form.levelNum.label')}</h4> - - <div className="px-10"> - <Slider - name={field.name} - value={field.value} - onChange={(_, value) => field.onChange(value as number)} - marks={[ - { - value: 0, - label: 'Disable', - }, - { - value: 1, - label: 'Beginner', - }, - { - value: 10, - label: 'Expert', - }, - ]} - min={0} - max={10} - defaultValue={0} - color="secondary" - valueLabelDisplay="auto" - aria-label={t('builder.common.form.levelNum.label')} - /> - </div> - </div> - )} - /> - <input type="submit" style={{ display: 'none' }} /> - </form> - </BaseModal> - ); -}; - -export default LanguageModal; diff --git a/client/modals/builder/sections/ProfileModal.tsx b/client/modals/builder/sections/ProfileModal.tsx deleted file mode 100644 index 83210cba..00000000 --- a/client/modals/builder/sections/ProfileModal.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import { joiResolver } from '@hookform/resolvers/joi'; -import { Add, AlternateEmail, DriveFileRenameOutline } from '@mui/icons-material'; -import { Button, TextField } from '@mui/material'; -import { Profile } 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 BaseModal from '@/components/shared/BaseModal'; -import { VALID_URL_REGEX } from '@/constants/index'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setModalState } from '@/store/modal/modalSlice'; -import { addItem, editItem } from '@/store/resume/resumeSlice'; - -type FormData = Profile; - -const path = 'sections.profile'; - -const defaultState: FormData = { - network: '', - username: '', - url: '', -}; - -const schema = Joi.object<FormData>({ - id: Joi.string(), - network: Joi.string().required(), - username: Joi.string().required(), - url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''), -}); - -const ProfileModal: React.FC = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]); - - const item: FormData = get(payload, 'item', null); - const isEditMode = useMemo(() => !!item, [item]); - - const addText = t('builder.common.actions.add', { - token: t('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>({ - defaultValues: defaultState, - resolver: joiResolver(schema), - }); - - const onSubmit = (formData: FormData) => { - if (isEditMode) { - dispatch(editItem({ path: 'basics.profiles', value: formData })); - } else { - dispatch(addItem({ path: 'basics.profiles', value: formData })); - } - - handleClose(); - }; - - const handleClose = () => { - dispatch( - setModalState({ - modal: `builder.${path}`, - state: { open: false }, - }), - ); - - reset(defaultState); - }; - - useEffect(() => { - if (!isEmpty(item)) { - reset(item); - } - }, [item, reset]); - - return ( - <BaseModal - icon={isEditMode ? <DriveFileRenameOutline /> : <Add />} - isOpen={isOpen} - heading={isEditMode ? editText : addText} - handleClose={handleClose} - footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} - > - <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}> - <Controller - name="network" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - autoFocus - label={t('builder.leftSidebar.sections.profiles.form.network.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="username" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - label={t('builder.leftSidebar.sections.profiles.form.username.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - InputProps={{ - startAdornment: <AlternateEmail className="mr-2" />, - }} - {...field} - /> - )} - /> - - <Controller - name="url" - control={control} - render={({ field, fieldState }) => ( - <TextField - label={t('builder.common.form.url.label')} - className="col-span-2" - placeholder="https://" - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - <input type="submit" style={{ display: 'none' }} /> - </form> - </BaseModal> - ); -}; - -export default ProfileModal; diff --git a/client/modals/builder/sections/ProjectModal.tsx b/client/modals/builder/sections/ProjectModal.tsx deleted file mode 100644 index d71cac98..00000000 --- a/client/modals/builder/sections/ProjectModal.tsx +++ /dev/null @@ -1,238 +0,0 @@ -import { joiResolver } from '@hookform/resolvers/joi'; -import { Add, DriveFileRenameOutline } from '@mui/icons-material'; -import { Button, TextField } from '@mui/material'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { Project, SectionPath } from 'schema'; -import dayjs from 'dayjs'; -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 ArrayInput from '@/components/shared/ArrayInput'; -import BaseModal from '@/components/shared/BaseModal'; -import MarkdownSupported from '@/components/shared/MarkdownSupported'; -import { VALID_URL_REGEX } from '@/constants/index'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setModalState } from '@/store/modal/modalSlice'; -import { addItem, editItem } from '@/store/resume/resumeSlice'; - -type FormData = Project; - -const path: SectionPath = 'sections.projects'; - -const defaultState: FormData = { - name: '', - description: '', - date: { - start: '', - end: '', - }, - url: '', - summary: '', - keywords: [], -}; - -const schema = Joi.object<FormData>().keys({ - id: Joi.string(), - name: Joi.string().required(), - description: Joi.string().required(), - date: Joi.object().keys({ - start: Joi.string().allow(''), - end: Joi.string().allow(''), - }), - url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''), - summary: Joi.string().allow(''), - keywords: Joi.array().items(Joi.string().optional()), -}); - -const ProjectModal: React.FC = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`)); - const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]); - - const item: FormData = get(payload, 'item', null); - const isEditMode = useMemo(() => !!item, [item]); - - 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, - resolver: joiResolver(schema), - }); - - const onSubmit = (formData: FormData) => { - if (isEditMode) { - dispatch(editItem({ path: `${path}.items`, value: formData })); - } else { - dispatch(addItem({ path: `${path}.items`, value: formData })); - } - - handleClose(); - }; - - const handleClose = () => { - dispatch( - setModalState({ - modal: `builder.${path}`, - state: { open: false }, - }), - ); - - reset(defaultState); - }; - - useEffect(() => { - if (!isEmpty(item)) { - reset(item); - } - }, [item, reset]); - - return ( - <BaseModal - icon={isEditMode ? <DriveFileRenameOutline /> : <Add />} - isOpen={isOpen} - handleClose={handleClose} - heading={isEditMode ? editText : addText} - footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} - > - <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}> - <Controller - name="name" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - autoFocus - label={t('builder.common.form.name.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="description" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - label={t('builder.common.form.description.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="date.start" - control={control} - render={({ field, fieldState }) => ( - <DatePicker - openTo="year" - inputRef={field.ref} - 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} - /> - ), - }} - onChange={(date: dayjs.Dayjs | null) => { - date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD')); - }} - /> - )} - /> - - <Controller - name="date.end" - control={control} - render={({ field, fieldState }) => ( - <DatePicker - openTo="year" - inputRef={field.ref} - 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('builder.common.form.end-date.help-text')} - /> - ), - }} - onChange={(date: dayjs.Dayjs | null) => { - date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD')); - }} - /> - )} - /> - - <Controller - name="url" - control={control} - render={({ field, fieldState }) => ( - <TextField - label={t('builder.common.form.url.label')} - placeholder="https://" - className="col-span-2" - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="summary" - control={control} - render={({ field, fieldState }) => ( - <TextField - multiline - minRows={3} - maxRows={6} - label={t('builder.common.form.summary.label')} - className="col-span-2" - error={!!fieldState.error} - helperText={fieldState.error?.message || <MarkdownSupported />} - {...field} - /> - )} - /> - - <Controller - name="keywords" - control={control} - render={({ field, fieldState }) => ( - <ArrayInput - label={t('builder.common.form.keywords.label')} - value={field.value as string[]} - onChange={field.onChange} - errors={fieldState.error} - className="col-span-2" - /> - )} - /> - <input type="submit" style={{ display: 'none' }} /> - </form> - </BaseModal> - ); -}; - -export default ProjectModal; diff --git a/client/modals/builder/sections/PublicationModal.tsx b/client/modals/builder/sections/PublicationModal.tsx deleted file mode 100644 index f0964367..00000000 --- a/client/modals/builder/sections/PublicationModal.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import { joiResolver } from '@hookform/resolvers/joi'; -import { Add, DriveFileRenameOutline } from '@mui/icons-material'; -import { Button, TextField } from '@mui/material'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { Publication, SectionPath } from 'schema'; -import dayjs from 'dayjs'; -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 BaseModal from '@/components/shared/BaseModal'; -import MarkdownSupported from '@/components/shared/MarkdownSupported'; -import { VALID_URL_REGEX } from '@/constants/index'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setModalState } from '@/store/modal/modalSlice'; -import { addItem, editItem } from '@/store/resume/resumeSlice'; - -type FormData = Publication; - -const path: SectionPath = 'sections.publications'; - -const defaultState: FormData = { - name: '', - publisher: '', - date: '', - url: '', - summary: '', -}; - -const schema = Joi.object<FormData>().keys({ - id: Joi.string(), - name: Joi.string().required(), - publisher: Joi.string().required(), - date: Joi.string().allow(''), - url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''), - summary: Joi.string().allow(''), -}); - -const PublicationModal: React.FC = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`)); - const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]); - - const item: FormData = get(payload, 'item', null); - const isEditMode = useMemo(() => !!item, [item]); - - 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, - resolver: joiResolver(schema), - }); - - const onSubmit = (formData: FormData) => { - if (isEditMode) { - dispatch(editItem({ path: `${path}.items`, value: formData })); - } else { - dispatch(addItem({ path: `${path}.items`, value: formData })); - } - - handleClose(); - }; - - const handleClose = () => { - dispatch( - setModalState({ - modal: `builder.${path}`, - state: { open: false }, - }), - ); - - reset(defaultState); - }; - - useEffect(() => { - if (!isEmpty(item)) { - reset(item); - } - }, [item, reset]); - - return ( - <BaseModal - icon={isEditMode ? <DriveFileRenameOutline /> : <Add />} - isOpen={isOpen} - handleClose={handleClose} - heading={isEditMode ? editText : addText} - footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} - > - <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}> - <Controller - name="name" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - autoFocus - label={t('builder.common.form.name.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="publisher" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - label={t('builder.leftSidebar.sections.publications.form.publisher.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="date" - control={control} - render={({ field, fieldState }) => ( - <DatePicker - openTo="year" - inputRef={field.ref} - label={t('builder.common.form.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} - /> - ), - }} - onChange={(date: dayjs.Dayjs | null) => { - date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD')); - }} - /> - )} - /> - - <Controller - name="url" - control={control} - render={({ field, fieldState }) => ( - <TextField - label={t('builder.common.form.url.label')} - placeholder="https://" - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="summary" - control={control} - render={({ field, fieldState }) => ( - <TextField - multiline - minRows={3} - maxRows={6} - label={t('builder.common.form.summary.label')} - className="col-span-2" - error={!!fieldState.error} - helperText={fieldState.error?.message || <MarkdownSupported />} - {...field} - /> - )} - /> - <input type="submit" style={{ display: 'none' }} /> - </form> - </BaseModal> - ); -}; - -export default PublicationModal; diff --git a/client/modals/builder/sections/ReferenceModal.tsx b/client/modals/builder/sections/ReferenceModal.tsx deleted file mode 100644 index dc89ecc4..00000000 --- a/client/modals/builder/sections/ReferenceModal.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import { joiResolver } from '@hookform/resolvers/joi'; -import { Add, DriveFileRenameOutline } from '@mui/icons-material'; -import { Button, TextField } from '@mui/material'; -import { Reference, 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 BaseModal from '@/components/shared/BaseModal'; -import MarkdownSupported from '@/components/shared/MarkdownSupported'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setModalState } from '@/store/modal/modalSlice'; -import { addItem, editItem } from '@/store/resume/resumeSlice'; - -type FormData = Reference; - -const path: SectionPath = 'sections.references'; - -const defaultState: FormData = { - name: '', - relationship: '', - phone: '', - email: '', - summary: '', -}; - -const schema = Joi.object<FormData>().keys({ - id: Joi.string(), - name: Joi.string().required(), - relationship: Joi.string().required(), - phone: Joi.string().allow(''), - email: Joi.string().allow(''), - summary: Joi.string().allow(''), -}); - -const ReferenceModal: React.FC = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`)); - const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]); - - const item: FormData = get(payload, 'item', null); - const isEditMode = useMemo(() => !!item, [item]); - - 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, - resolver: joiResolver(schema), - }); - - const onSubmit = (formData: FormData) => { - if (isEditMode) { - dispatch(editItem({ path: `${path}.items`, value: formData })); - } else { - dispatch(addItem({ path: `${path}.items`, value: formData })); - } - - handleClose(); - }; - - const handleClose = () => { - dispatch( - setModalState({ - modal: `builder.${path}`, - state: { open: false }, - }), - ); - - reset(defaultState); - }; - - useEffect(() => { - if (!isEmpty(item)) { - reset(item); - } - }, [item, reset]); - - return ( - <BaseModal - icon={isEditMode ? <DriveFileRenameOutline /> : <Add />} - isOpen={isOpen} - handleClose={handleClose} - heading={isEditMode ? editText : addText} - footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} - > - <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}> - <Controller - name="name" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - autoFocus - label={t('builder.common.form.name.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="relationship" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - label={t('builder.leftSidebar.sections.references.form.relationship.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="phone" - control={control} - render={({ field, fieldState }) => ( - <TextField - label={t('builder.common.form.phone.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="email" - control={control} - render={({ field, fieldState }) => ( - <TextField - label={t('builder.common.form.email.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="summary" - control={control} - render={({ field, fieldState }) => ( - <TextField - multiline - minRows={3} - maxRows={6} - label={t('builder.common.form.summary.label')} - className="col-span-2" - error={!!fieldState.error} - helperText={fieldState.error?.message || <MarkdownSupported />} - {...field} - /> - )} - /> - <input type="submit" style={{ display: 'none' }} /> - </form> - </BaseModal> - ); -}; - -export default ReferenceModal; diff --git a/client/modals/builder/sections/SkillModal.tsx b/client/modals/builder/sections/SkillModal.tsx deleted file mode 100644 index eb55ac0d..00000000 --- a/client/modals/builder/sections/SkillModal.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import { joiResolver } from '@hookform/resolvers/joi'; -import { Add, DriveFileRenameOutline } from '@mui/icons-material'; -import { Button, Slider, TextField } from '@mui/material'; -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'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setModalState } from '@/store/modal/modalSlice'; -import { addItem, editItem } from '@/store/resume/resumeSlice'; - -type FormData = Skill; - -const path: SectionPath = 'sections.skills'; - -const defaultState: FormData = { - name: '', - level: '', - levelNum: 0, - keywords: [], -}; - -const schema = Joi.object<FormData>().keys({ - id: Joi.string(), - name: Joi.string().required(), - level: Joi.string().allow(''), - levelNum: Joi.number().min(0).max(10).required(), - keywords: Joi.array().items(Joi.string().optional()), -}); - -const SkillModal: React.FC = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`)); - const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]); - - const item: FormData = get(payload, 'item', null); - const isEditMode = useMemo(() => !!item, [item]); - - 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, - resolver: joiResolver(schema), - }); - - const onSubmit = (formData: FormData) => { - if (isEditMode) { - dispatch(editItem({ path: `${path}.items`, value: formData })); - } else { - dispatch(addItem({ path: `${path}.items`, value: formData })); - } - - handleClose(); - }; - - const handleClose = () => { - dispatch( - setModalState({ - modal: `builder.${path}`, - state: { open: false }, - }), - ); - - reset(defaultState); - }; - - useEffect(() => { - if (!isEmpty(item)) { - reset(item); - } - }, [item, reset]); - - return ( - <BaseModal - icon={isEditMode ? <DriveFileRenameOutline /> : <Add />} - isOpen={isOpen} - handleClose={handleClose} - heading={isEditMode ? editText : addText} - footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} - > - <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}> - <Controller - name="name" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - autoFocus - label={t('builder.common.form.name.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="level" - control={control} - render={({ field, fieldState }) => ( - <TextField - label={t('builder.common.form.level.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="levelNum" - control={control} - render={({ field }) => ( - <div className="col-span-2"> - <h4 className="mb-3 font-semibold">{t('builder.common.form.levelNum.label')}</h4> - - <div className="px-3"> - <Slider - name={field.name} - value={field.value} - onChange={(_, value) => field.onChange(value as number)} - marks={[ - { - value: 0, - label: 'Disable', - }, - { - value: 1, - label: 'Beginner', - }, - { - value: 10, - label: 'Expert', - }, - ]} - min={0} - max={10} - defaultValue={0} - color="secondary" - valueLabelDisplay="auto" - aria-label={t('builder.common.form.levelNum.label')} - /> - </div> - </div> - )} - /> - - <Controller - name="keywords" - control={control} - render={({ field, fieldState }) => ( - <ArrayInput - label={t('builder.common.form.keywords.label')} - value={field.value} - onChange={field.onChange} - errors={fieldState.error} - className="col-span-2" - /> - )} - /> - - <input type="submit" style={{ display: 'none' }} /> - </form> - </BaseModal> - ); -}; - -export default SkillModal; diff --git a/client/modals/builder/sections/VolunteerModal.tsx b/client/modals/builder/sections/VolunteerModal.tsx deleted file mode 100644 index 4fb98a13..00000000 --- a/client/modals/builder/sections/VolunteerModal.tsx +++ /dev/null @@ -1,221 +0,0 @@ -import { joiResolver } from '@hookform/resolvers/joi'; -import { Add, DriveFileRenameOutline } from '@mui/icons-material'; -import { Button, TextField } from '@mui/material'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { SectionPath, Volunteer } from 'schema'; -import dayjs from 'dayjs'; -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 BaseModal from '@/components/shared/BaseModal'; -import MarkdownSupported from '@/components/shared/MarkdownSupported'; -import { VALID_URL_REGEX } from '@/constants/index'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setModalState } from '@/store/modal/modalSlice'; -import { addItem, editItem } from '@/store/resume/resumeSlice'; - -type FormData = Volunteer; - -const path: SectionPath = 'sections.volunteer'; - -const defaultState: FormData = { - organization: '', - position: '', - date: { - start: '', - end: '', - }, - url: '', - summary: '', -}; - -const schema = Joi.object<FormData>().keys({ - id: Joi.string(), - organization: Joi.string().required(), - position: Joi.string().required(), - date: Joi.object().keys({ - start: Joi.string().allow(''), - end: Joi.string().allow(''), - }), - url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''), - summary: Joi.string().allow(''), -}); - -const VolunteerModal: React.FC = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`)); - const { open: isOpen, payload } = useAppSelector((state) => state.modal[`builder.${path}`]); - - const item: FormData = get(payload, 'item', null); - const isEditMode = useMemo(() => !!item, [item]); - - 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, - resolver: joiResolver(schema), - }); - - const onSubmit = (formData: FormData) => { - if (isEditMode) { - dispatch(editItem({ path: `${path}.items`, value: formData })); - } else { - dispatch(addItem({ path: `${path}.items`, value: formData })); - } - - handleClose(); - }; - - const handleClose = () => { - dispatch( - setModalState({ - modal: `builder.${path}`, - state: { open: false }, - }), - ); - - reset(defaultState); - }; - - useEffect(() => { - if (!isEmpty(item)) { - reset(item); - } - }, [item, reset]); - - return ( - <BaseModal - icon={isEditMode ? <DriveFileRenameOutline /> : <Add />} - isOpen={isOpen} - handleClose={handleClose} - heading={isEditMode ? editText : addText} - footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} - > - <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}> - <Controller - name="organization" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - autoFocus - label={t('builder.leftSidebar.sections.volunteer.form.organization.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="position" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - label={t('builder.common.form.position.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="date.start" - control={control} - render={({ field, fieldState }) => ( - <DatePicker - openTo="year" - inputRef={field.ref} - 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} - /> - ), - }} - onChange={(date: dayjs.Dayjs | null) => { - date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD')); - }} - /> - )} - /> - - <Controller - name="date.end" - control={control} - render={({ field, fieldState }) => ( - <DatePicker - openTo="year" - inputRef={field.ref} - 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('builder.common.form.end-date.help-text')} - /> - ), - }} - onChange={(date: dayjs.Dayjs | null) => { - date && dayjs(date).isValid() && field.onChange(dayjs(date).format('YYYY-MM-DD')); - }} - /> - )} - /> - - <Controller - name="url" - control={control} - render={({ field, fieldState }) => ( - <TextField - label={t('builder.common.form.url.label')} - placeholder="https://" - className="col-span-2" - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="summary" - control={control} - render={({ field, fieldState }) => ( - <TextField - multiline - minRows={3} - maxRows={6} - label={t('builder.common.form.summary.label')} - className="col-span-2" - error={!!fieldState.error} - helperText={fieldState.error?.message || <MarkdownSupported />} - {...field} - /> - )} - /> - <input type="submit" style={{ display: 'none' }} /> - </form> - </BaseModal> - ); -}; - -export default VolunteerModal; diff --git a/client/modals/builder/sections/WorkModal.tsx b/client/modals/builder/sections/WorkModal.tsx deleted file mode 100644 index cc5c8781..00000000 --- a/client/modals/builder/sections/WorkModal.tsx +++ /dev/null @@ -1,227 +0,0 @@ -import { joiResolver } from '@hookform/resolvers/joi'; -import { Add, DriveFileRenameOutline } from '@mui/icons-material'; -import { Button, TextField } from '@mui/material'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { WorkExperience } from 'schema'; -import dayjs from 'dayjs'; -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 BaseModal from '@/components/shared/BaseModal'; -import MarkdownSupported from '@/components/shared/MarkdownSupported'; -import { VALID_URL_REGEX } from '@/constants/index'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setModalState } from '@/store/modal/modalSlice'; -import { addItem, editItem } from '@/store/resume/resumeSlice'; - -type FormData = WorkExperience; - -const defaultState: FormData = { - name: '', - position: '', - date: { - start: '', - end: '', - }, - url: '', - summary: '', -}; - -const schema = Joi.object<FormData>().keys({ - id: Joi.string(), - name: Joi.string().required(), - position: Joi.string().required(), - date: Joi.object().keys({ - start: Joi.string().allow(''), - end: Joi.string().allow(''), - }), - url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''), - summary: Joi.string().allow(''), -}); - -const WorkModal: React.FC = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const { open: isOpen, payload } = useAppSelector((state) => state.modal['builder.sections.work']); - const path: string = get(payload, 'path', 'sections.work'); - const item: FormData = get(payload, 'item', null); - - const heading = useAppSelector((state) => get(state.resume.present, `${path}.name`)); - - const isEditMode = useMemo(() => !!item, [item]); - - const addText = useMemo( - () => - t('builder.common.actions.add', { - token: t(`builder.leftSidebar.${path}.heading`, { defaultValue: heading }), - }), - [t, heading], - ); - const editText = useMemo( - () => - t('builder.common.actions.edit', { - token: t(`builder.leftSidebar.${path}.heading`, { defaultValue: heading }), - }), - [t, heading], - ); - - const { reset, control, handleSubmit } = useForm<FormData>({ - defaultValues: defaultState, - resolver: joiResolver(schema), - }); - - const onSubmit = (formData: FormData) => { - if (isEditMode) { - dispatch(editItem({ path: `${path}.items`, value: formData })); - } else { - dispatch(addItem({ path: `${path}.items`, value: formData })); - } - - handleClose(); - }; - - const handleClose = () => { - dispatch( - setModalState({ - modal: 'builder.sections.work', - state: { open: false }, - }), - ); - - reset(defaultState); - }; - - useEffect(() => { - if (!isEmpty(item)) { - reset(item); - } - }, [item, reset]); - - return ( - <BaseModal - icon={isEditMode ? <DriveFileRenameOutline /> : <Add />} - isOpen={isOpen} - handleClose={handleClose} - heading={isEditMode ? editText : addText} - footerChildren={<Button onClick={handleSubmit(onSubmit)}>{isEditMode ? editText : addText}</Button>} - > - <form className="my-2 grid grid-cols-2 gap-4" onSubmit={handleSubmit(onSubmit)}> - <Controller - name="name" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - autoFocus - label={t('builder.leftSidebar.sections.experience.form.name.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="position" - control={control} - render={({ field, fieldState }) => ( - <TextField - required - label={t('builder.common.form.position.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="date.start" - control={control} - render={({ field, fieldState }) => ( - <DatePicker - openTo="year" - inputRef={field.ref} - label={t('builder.common.form.start-date.label')} - value={dayjs(field.value)} - views={['year', 'month', 'day']} - 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')); - }} - /> - )} - /> - - <Controller - name="date.end" - control={control} - render={({ field, fieldState }) => ( - <DatePicker - openTo="year" - inputRef={field.ref} - label={t('builder.common.form.end-date.label')} - value={dayjs(field.value)} - views={['year', 'month', 'day']} - 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')); - }} - /> - )} - /> - - <Controller - name="url" - control={control} - render={({ field, fieldState }) => ( - <TextField - label={t('builder.common.form.url.label')} - placeholder="https://" - className="col-span-2" - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="summary" - control={control} - render={({ field, fieldState }) => ( - <TextField - multiline - minRows={3} - maxRows={6} - label={t('builder.common.form.summary.label')} - className="col-span-2" - error={!!fieldState.error} - helperText={fieldState.error?.message || <MarkdownSupported />} - {...field} - /> - )} - /> - <input type="submit" style={{ display: 'none' }} /> - </form> - </BaseModal> - ); -}; - -export default WorkModal; diff --git a/client/modals/dashboard/CreateResumeModal.tsx b/client/modals/dashboard/CreateResumeModal.tsx deleted file mode 100644 index 8513b988..00000000 --- a/client/modals/dashboard/CreateResumeModal.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import { joiResolver } from '@hookform/resolvers/joi'; -import { Add } from '@mui/icons-material'; -import { Button, FormControlLabel, FormGroup, Switch, TextField } from '@mui/material'; -import { Resume } from 'schema'; -import Joi from 'joi'; -import { useTranslation } from 'next-i18next'; -import { useEffect } from 'react'; -import { Controller, useForm } from 'react-hook-form'; -import { useMutation } from 'react-query'; - -import BaseModal from '@/components/shared/BaseModal'; -import { RESUMES_QUERY } from '@/constants/index'; -import { ServerError } from '@/services/axios'; -import queryClient from '@/services/react-query'; -import { createResume, CreateResumeParams } from '@/services/resume'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setModalState } from '@/store/modal/modalSlice'; - -type FormData = { - name: string; - slug: string; - isPublic: boolean; -}; - -const defaultState: FormData = { - name: '', - slug: '', - isPublic: true, -}; - -const schema = Joi.object({ - name: Joi.string().required(), - slug: Joi.string() - .lowercase() - .min(3) - .regex(/^[a-z0-9-]+$/, 'only lowercase characters, numbers and hyphens') - .required(), - isPublic: Joi.boolean().default(true).required(), -}); - -const CreateResumeModal: React.FC = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const { open: isOpen } = useAppSelector((state) => state.modal['dashboard.create-resume']); - - const { mutateAsync, isLoading } = useMutation<Resume, ServerError, CreateResumeParams>(createResume); - - const { reset, watch, control, setValue, handleSubmit } = useForm<FormData>({ - defaultValues: defaultState, - resolver: joiResolver(schema), - }); - const name = watch('name'); - - useEffect(() => { - const slug = name - ? name - .toLowerCase() - .replace(/[^\w\s]/gi, '') - .replace(/[ ]/gi, '-') - : ''; - - setValue('slug', slug); - }, [name, setValue]); - - const onSubmit = async ({ name, slug, isPublic }: FormData) => { - await mutateAsync({ name, slug, public: isPublic }); - await queryClient.invalidateQueries(RESUMES_QUERY); - - handleClose(); - }; - - const handleClose = () => { - dispatch(setModalState({ modal: 'dashboard.create-resume', state: { open: false } })); - reset(); - }; - - return ( - <BaseModal - isOpen={isOpen} - icon={<Add />} - heading={t('modals.dashboard.create-resume.heading')} - handleClose={handleClose} - footerChildren={ - <Button type="submit" disabled={isLoading} onClick={handleSubmit(onSubmit)}> - {t('modals.dashboard.create-resume.actions.create-resume')} - </Button> - } - > - <p>{t('modals.dashboard.create-resume.body')}</p> - - <form className="grid gap-4"> - <Controller - name="name" - control={control} - render={({ field, fieldState }) => ( - <TextField - autoFocus - label={t('modals.dashboard.create-resume.form.name.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="slug" - control={control} - render={({ field, fieldState }) => ( - <TextField - label={t('modals.dashboard.create-resume.form.slug.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <FormGroup> - <FormControlLabel - label={t('modals.dashboard.create-resume.form.public.label')} - control={ - <Controller - name="isPublic" - control={control} - render={({ field }) => <Switch defaultChecked color="secondary" {...field} />} - /> - } - /> - </FormGroup> - </form> - </BaseModal> - ); -}; - -export default CreateResumeModal; diff --git a/client/modals/dashboard/ImportExternalModal.tsx b/client/modals/dashboard/ImportExternalModal.tsx deleted file mode 100644 index 22d922b0..00000000 --- a/client/modals/dashboard/ImportExternalModal.tsx +++ /dev/null @@ -1,220 +0,0 @@ -import { Code, ImportExport, LinkedIn, TrackChanges, UploadFile } from '@mui/icons-material'; -import { Button, Divider } from '@mui/material'; -import { Integration, Resume } from 'schema'; -import { Trans, useTranslation } from 'next-i18next'; -import { useRef } from 'react'; -import toast from 'react-hot-toast'; -import { useMutation } from 'react-query'; - -import BaseModal from '@/components/shared/BaseModal'; -import { RESUMES_QUERY } from '@/constants/index'; -import { ServerError } from '@/services/axios'; -import { importFromExternal, ImportFromExternalParams } from '@/services/integrations'; -import queryClient from '@/services/react-query'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setModalState } from '@/store/modal/modalSlice'; - -const FILE_UPLOAD_MAX_SIZE = 2000000; // 2 MB - -const ImportExternalModal: React.FC = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const linkedinInputRef = useRef<HTMLInputElement>(null); - const jsonResumeInputRef = useRef<HTMLInputElement>(null); - const reactiveResumeInputRef = useRef<HTMLInputElement>(null); - const reactiveResumeV2InputRef = useRef<HTMLInputElement>(null); - - const { open: isOpen } = useAppSelector((state) => state.modal['dashboard.import-external']); - - const { mutateAsync, isLoading } = useMutation<Resume, ServerError, ImportFromExternalParams>(importFromExternal); - - const handleClose = () => { - dispatch(setModalState({ modal: 'dashboard.import-external', state: { open: false } })); - }; - - const handleClick = (integration: Integration) => { - if (integration === 'linkedin') { - if (linkedinInputRef.current) { - linkedinInputRef.current.click(); - linkedinInputRef.current.value = ''; - } - } else if (integration === 'json-resume') { - if (jsonResumeInputRef.current) { - jsonResumeInputRef.current.click(); - jsonResumeInputRef.current.value = ''; - } - } else if (integration === 'reactive-resume') { - if (reactiveResumeInputRef.current) { - reactiveResumeInputRef.current.click(); - reactiveResumeInputRef.current.value = ''; - } - } else if (integration === 'reactive-resume-v2') { - if (reactiveResumeV2InputRef.current) { - reactiveResumeV2InputRef.current.click(); - reactiveResumeV2InputRef.current.value = ''; - } - } - }; - - const handleChange = async (event: React.ChangeEvent<HTMLInputElement>, integration: Integration) => { - if (event.target.files && event.target.files[0]) { - const file = event.target.files[0]; - - if (file.size > FILE_UPLOAD_MAX_SIZE) { - toast.error(t('common.toast.error.upload-file-size')); - return; - } - - await mutateAsync({ integration, file }); - queryClient.invalidateQueries(RESUMES_QUERY); - - handleClose(); - } - }; - - return ( - <BaseModal - isOpen={isOpen} - icon={<ImportExport />} - 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('modals.dashboard.import-external.linkedin.heading')} - </h2> - - <p className="mb-2"> - <Trans t={t} i18nKey="modals.dashboard.import-external.linkedin.body"> - You can save time by exporting your data from LinkedIn and using it to auto-fill fields on Reactive Resume. - Head over to the - <a - href="https://www.linkedin.com/psettings/member-data" - className="underline" - rel="noreferrer" - target="_blank" - > - Data Privacy - </a> - section on LinkedIn and request an archive of your data. Once it is available, upload the ZIP file below. - </Trans> - </p> - - <div> - <Button - variant="contained" - disabled={isLoading} - startIcon={<UploadFile />} - onClick={() => handleClick('linkedin')} - > - {t('modals.dashboard.import-external.linkedin.actions.upload-archive')} - </Button> - - <input - hidden - type="file" - ref={linkedinInputRef} - onChange={(event) => handleChange(event, 'linkedin')} - accept="application/zip" - /> - </div> - </div> - - <Divider className="py-2" /> - - <div className="grid gap-5"> - <h2 className="inline-flex items-center gap-2 text-lg font-medium"> - <Code /> - {t('modals.dashboard.import-external.json-resume.heading')} - </h2> - - <p className="mb-2"> - <Trans t={t} i18nKey="modals.dashboard.import-external.json-resume.body"> - If you have a - <a - href="https://github.com/jsonresume/resume-schema" - className="underline" - rel="noreferrer" - target="_blank" - > - validated JSON Resume - </a> - ready to go, you can use it to fast-track your development on Reactive Resume. Click the button below and - upload a valid JSON file to begin. - </Trans> - </p> - - <div> - <Button - variant="contained" - disabled={isLoading} - startIcon={<UploadFile />} - onClick={() => handleClick('json-resume')} - > - {t('modals.dashboard.import-external.json-resume.actions.upload-json')} - </Button> - - <input - hidden - type="file" - ref={jsonResumeInputRef} - onChange={(event) => handleChange(event, 'json-resume')} - accept="application/json" - /> - </div> - </div> - - <Divider className="py-2" /> - - <div className="grid gap-5"> - <h2 className="inline-flex items-center gap-2 text-lg font-medium"> - <TrackChanges /> - {t('modals.dashboard.import-external.reactive-resume.heading')} - </h2> - - <p className="mb-2">{t('modals.dashboard.import-external.reactive-resume.body')}</p> - - <div className="flex gap-4"> - <Button - variant="contained" - disabled={isLoading} - startIcon={<UploadFile />} - onClick={() => handleClick('reactive-resume')} - > - {t('modals.dashboard.import-external.reactive-resume.actions.upload-json')} - </Button> - - <Button - variant="contained" - disabled={isLoading} - startIcon={<UploadFile />} - onClick={() => handleClick('reactive-resume-v2')} - > - {t('modals.dashboard.import-external.reactive-resume.actions.upload-json-v2')} - </Button> - - <input - hidden - type="file" - ref={reactiveResumeInputRef} - onChange={(event) => handleChange(event, 'reactive-resume')} - accept="application/json" - /> - - <input - hidden - type="file" - ref={reactiveResumeV2InputRef} - onChange={(event) => handleChange(event, 'reactive-resume-v2')} - accept="application/json" - /> - </div> - </div> - </BaseModal> - ); -}; - -export default ImportExternalModal; diff --git a/client/modals/dashboard/RenameResumeModal.tsx b/client/modals/dashboard/RenameResumeModal.tsx deleted file mode 100644 index a0e18c42..00000000 --- a/client/modals/dashboard/RenameResumeModal.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import { joiResolver } from '@hookform/resolvers/joi'; -import { DriveFileRenameOutline } from '@mui/icons-material'; -import { Button, TextField } from '@mui/material'; -import { Resume } from 'schema'; -import Joi from 'joi'; -import get from 'lodash/get'; -import noop from 'lodash/noop'; -import { useTranslation } from 'next-i18next'; -import { useEffect } from 'react'; -import { Controller, useForm } from 'react-hook-form'; -import { useMutation } from 'react-query'; - -import BaseModal from '@/components/shared/BaseModal'; -import { RESUMES_QUERY } from '@/constants/index'; -import { ServerError } from '@/services/axios'; -import queryClient from '@/services/react-query'; -import { renameResume, RenameResumeParams } from '@/services/resume'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { ModalState, setModalState } from '@/store/modal/modalSlice'; - -type FormData = { - name: string; - slug: string; -}; - -const schema = Joi.object({ - name: Joi.string().required(), - slug: Joi.string() - .lowercase() - .min(3) - .regex(/^[a-z0-9-]+$/, 'only lowercase characters, numbers and hyphens') - .required(), -}); - -const RenameResumeModal: React.FC = () => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const { open: isOpen, payload } = useAppSelector((state) => state.modal['dashboard.rename-resume']) as ModalState; - const resume: Resume = get(payload, 'item') as Resume; - const onComplete = get(payload, 'onComplete', noop); - - const { mutateAsync, isLoading } = useMutation<Resume, ServerError, RenameResumeParams>(renameResume); - - const { reset, watch, control, setValue, handleSubmit } = useForm<FormData>({ - defaultValues: { - name: resume?.name, - slug: resume?.slug, - }, - resolver: joiResolver(schema), - }); - const name = watch('name'); - - useEffect(() => { - const slug = name - ? name - .toLowerCase() - .replace(/[^\w\s]/gi, '') - .replace(/[ ]/gi, '-') - : ''; - - setValue('slug', slug); - }, [name, setValue]); - - useEffect(() => { - if (!resume) return; - - const { name, slug }: FormData = resume; - - reset({ name, slug }); - }, [resume, reset]); - - const onSubmit = async ({ name, slug }: FormData) => { - if (!resume) return; - - const newResume = await mutateAsync({ id: resume.id, name, slug }); - - onComplete && onComplete(newResume); - - queryClient.invalidateQueries(RESUMES_QUERY); - - handleClose(); - }; - - const handleClose = () => { - dispatch(setModalState({ modal: 'dashboard.rename-resume', state: { open: false } })); - reset(); - }; - - return ( - <BaseModal - icon={<DriveFileRenameOutline />} - isOpen={isOpen} - heading={t('modals.dashboard.rename-resume.heading')} - handleClose={handleClose} - footerChildren={ - <Button type="submit" disabled={isLoading} onClick={handleSubmit(onSubmit)}> - {t('modals.dashboard.rename-resume.actions.rename-resume')} - </Button> - } - > - <form className="grid gap-4"> - <Controller - name="name" - control={control} - render={({ field, fieldState }) => ( - <TextField - autoFocus - label={t('modals.dashboard.rename-resume.form.name.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - - <Controller - name="slug" - control={control} - render={({ field, fieldState }) => ( - <TextField - label={t('modals.dashboard.rename-resume.form.slug.label')} - error={!!fieldState.error} - helperText={fieldState.error?.message} - {...field} - /> - )} - /> - </form> - </BaseModal> - ); -}; - -export default RenameResumeModal; diff --git a/client/modals/index.tsx b/client/modals/index.tsx deleted file mode 100644 index 6a03e0ce..00000000 --- a/client/modals/index.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { useRouter } from 'next/router'; -import { useEffect } from 'react'; - -import { useAppDispatch } from '@/store/hooks'; -import { ModalName, setModalState } from '@/store/modal/modalSlice'; - -import ForgotPasswordModal from './auth/ForgotPasswordModal'; -import LoginModal from './auth/LoginModal'; -import RegisterModal from './auth/RegisterModal'; -import ResetPasswordModal from './auth/ResetPasswordModal'; -import UserProfileModal from './auth/UserProfileModal'; -import AwardModal from './builder/sections/AwardModal'; -import CertificateModal from './builder/sections/CertificateModal'; -import CustomModal from './builder/sections/CustomModal'; -import EducationModal from './builder/sections/EducationModal'; -import InterestModal from './builder/sections/InterestModal'; -import LanguageModal from './builder/sections/LanguageModal'; -import ProfileModal from './builder/sections/ProfileModal'; -import ProjectModal from './builder/sections/ProjectModal'; -import PublicationModal from './builder/sections/PublicationModal'; -import ReferenceModal from './builder/sections/ReferenceModal'; -import SkillModal from './builder/sections/SkillModal'; -import VolunteerModal from './builder/sections/VolunteerModal'; -import WorkModal from './builder/sections/WorkModal'; -import CreateResumeModal from './dashboard/CreateResumeModal'; -import ImportExternalModal from './dashboard/ImportExternalModal'; -import RenameResumeModal from './dashboard/RenameResumeModal'; - -type QueryParams = { - modal?: ModalName; -}; - -const ModalWrapper: React.FC = () => { - const router = useRouter(); - - const dispatch = useAppDispatch(); - - useEffect(() => { - const { modal, ...rest } = router.query as QueryParams; - - if (!modal) return; - - dispatch(setModalState({ modal, state: { open: true, payload: { item: rest } } })); - }, [router.query, dispatch]); - - return ( - <> - {/* Authentication */} - <LoginModal /> - <RegisterModal /> - <ForgotPasswordModal /> - <ResetPasswordModal /> - <UserProfileModal /> - - {/* Dashboard */} - <CreateResumeModal /> - <ImportExternalModal /> - <RenameResumeModal /> - - {/* Builder */} - - {/* Sections */} - <ProfileModal /> - <WorkModal /> - <EducationModal /> - <AwardModal /> - <CertificateModal /> - <PublicationModal /> - <SkillModal /> - <LanguageModal /> - <InterestModal /> - <VolunteerModal /> - <ProjectModal /> - <ReferenceModal /> - - {/* Custom Sections */} - <CustomModal /> - </> - ); -}; - -export default ModalWrapper; diff --git a/client/next-env.d.ts b/client/next-env.d.ts deleted file mode 100644 index 4f11a03d..00000000 --- a/client/next-env.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// <reference types="next" /> -/// <reference types="next/image-types/global" /> - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/client/next-i18next.config.js b/client/next-i18next.config.js deleted file mode 100644 index c2150d5a..00000000 --- a/client/next-i18next.config.js +++ /dev/null @@ -1,56 +0,0 @@ -const path = require('path'); - -const i18nConfig = { - i18n: { - defaultLocale: 'en', - locales: [ - 'am', - 'ar', - 'bg', - 'bn', - 'ca', - 'cs', - 'da', - 'de', - 'el', - 'en', - 'es', - 'fa', - 'fi', - 'fr', - 'he', - 'hi', - 'hu', - 'id', - 'it', - 'ja', - 'km', - 'kn', - 'ko', - 'ml', - 'mr', - 'ne', - 'nl', - 'no', - 'or', - 'pl', - 'pt', - 'pt-BR', - 'ro', - 'ru', - 'sr', - 'sv', - 'ta', - 'tr', - 'uk', - 'vi', - 'zh', - ], - }, - nsSeparator: '.', - returnNull: false, - localePath: path.resolve('./public/locales'), - ns: ['common', 'modals', 'landing', 'dashboard', 'builder'], -}; - -module.exports = i18nConfig; diff --git a/client/next-sitemap.config.js b/client/next-sitemap.config.js deleted file mode 100644 index b82e133c..00000000 --- a/client/next-sitemap.config.js +++ /dev/null @@ -1,6 +0,0 @@ -/** @type {import('next-sitemap').IConfig} */ -module.exports = { - siteUrl: 'https://rxresu.me', - changefreq: 'monthly', - generateIndexSitemap: false, -}; diff --git a/client/next.config.js b/client/next.config.js deleted file mode 100644 index 33494d51..00000000 --- a/client/next.config.js +++ /dev/null @@ -1,50 +0,0 @@ -const { version } = require('../package.json'); -const { i18n } = require('./next-i18next.config'); - -/** @type {import('next').NextConfig} */ -const nextConfig = { - reactStrictMode: true, - - i18n, - - env: { - appVersion: version, - }, - - images: { - domains: ['cdn.rxresu.me', 'www.gravatar.com'], - }, - - // Hack to make Tailwind darkMode 'class' strategy with CSS Modules - // Ref: https://github.com/tailwindlabs/tailwindcss/issues/3258#issuecomment-968368156 - webpack: (config) => { - const rules = config.module.rules.find((r) => !!r.oneOf); - - rules.oneOf.forEach((loaders) => { - if (Array.isArray(loaders.use)) { - loaders.use.forEach((l) => { - if (typeof l !== 'string' && typeof l.loader === 'string' && /(?<!post)css-loader/.test(l.loader)) { - if (!l.options.modules) return; - const { getLocalIdent, ...others } = l.options.modules; - - l.options = { - ...l.options, - modules: { - ...others, - getLocalIdent: (ctx, localIdentName, localName, options) => { - if (localName === 'dark') return localName; - - return getLocalIdent(ctx, localIdentName, localName, options); - }, - }, - }; - } - }); - } - }); - - return config; - }, -}; - -module.exports = nextConfig; diff --git a/client/package.json b/client/package.json deleted file mode 100644 index b95cbb94..00000000 --- a/client/package.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "name": "client", - "scripts": { - "dev": "react-env --prefix PUBLIC -- next dev", - "lint": "next lint --fix", - "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.2", - "@emotion/react": "^11.11.1", - "@emotion/styled": "^11.11.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.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": "^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.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-github-btn": "^1.4.0", - "react-hook-form": "^7.45.4", - "react-hot-toast": "2.4.1", - "react-icons": "^4.10.1", - "react-markdown": "^8.0.7", - "react-parallax-tilt": "^1.7.157", - "react-query": "^3.39.3", - "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.1.0", - "rehype-katex": "^6.0.3", - "remark-gfm": "^3.0.1", - "remark-math": "^5.1.1", - "sharp": "^0.32.4", - "tailwind-merge": "^1.14.0", - "uuid": "^9.0.0", - "webfontloader": "^1.6.28" - }, - "devDependencies": { - "@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.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.2", - "@types/webfontloader": "^1.6.35", - "autoprefixer": "^10.4.14", - "csstype": "^3.1.2", - "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" - } -} diff --git a/client/pages/[username]/[slug]/build.tsx b/client/pages/[username]/[slug]/build.tsx deleted file mode 100644 index 7049bce3..00000000 --- a/client/pages/[username]/[slug]/build.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import isEmpty from 'lodash/isEmpty'; -import { GetServerSideProps, NextPage } from 'next'; -import Head from 'next/head'; -import { useTranslation } from 'next-i18next'; -import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; -import { useEffect } from 'react'; -import { useQuery } from 'react-query'; -import { Resume } from 'schema'; - -import Center from '@/components/build/Center/Center'; -import LeftSidebar from '@/components/build/LeftSidebar/LeftSidebar'; -import RightSidebar from '@/components/build/RightSidebar/RightSidebar'; -import { fetchResumeByIdentifier } from '@/services/resume'; -import { useAppDispatch } from '@/store/hooks'; -import { setResume } from '@/store/resume/resumeSlice'; -import styles from '@/styles/pages/Build.module.scss'; - -type QueryParams = { - username: string; - slug: string; -}; - -type Props = { - username: string; - slug: string; -}; - -export const getServerSideProps: GetServerSideProps<Props> = async ({ query, locale = 'en' }) => { - const { username, slug } = query as QueryParams; - - return { - props: { username, slug, ...(await serverSideTranslations(locale, ['common', 'modals', 'builder'])) }, - }; -}; - -const Build: NextPage<Props> = ({ username, slug }) => { - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - const { data: resume } = useQuery<Resume>( - `resume/${username}/${slug}`, - () => fetchResumeByIdentifier({ username, slug }), - { - onSuccess: (resume) => { - dispatch(setResume(resume)); - }, - }, - ); - - useEffect(() => { - if (resume) dispatch(setResume(resume)); - }, [resume, dispatch]); - - if (!resume || isEmpty(resume)) return null; - - return ( - <div className={styles.container}> - <Head> - <title> - {resume.name} | {t('common.title')} - - - - -
- - - ); -}; - -export default Build; diff --git a/client/pages/[username]/[slug]/index.tsx b/client/pages/[username]/[slug]/index.tsx deleted file mode 100644 index eaea2df3..00000000 --- a/client/pages/[username]/[slug]/index.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import { Download, Downloading } from '@mui/icons-material'; -import { ButtonBase } from '@mui/material'; -import clsx from 'clsx'; -import dayjs from 'dayjs'; -import download from 'downloadjs'; -import get from 'lodash/get'; -import isEmpty from 'lodash/isEmpty'; -import { GetServerSideProps, NextPage } from 'next'; -import Link from 'next/link'; -import { useRouter } from 'next/router'; -import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; -import { useEffect } from 'react'; -import toast from 'react-hot-toast'; -import { useMutation, useQuery } from 'react-query'; -import { Resume } from 'schema'; - -import Page from '@/components/build/Center/Page'; -import { DEFAULT_ERROR_MESSAGE } from '@/constants/index'; -import { ServerError } from '@/services/axios'; -import { printResumeAsPdf, PrintResumeAsPdfParams } from '@/services/printer'; -import { fetchResumeByIdentifier } from '@/services/resume'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setResume } from '@/store/resume/resumeSlice'; -import styles from '@/styles/pages/Preview.module.scss'; - -type QueryParams = { - slug: string; - username: string; -}; - -type Props = { - slug: string; - resume?: Resume; - username: string; -}; - -export const getServerSideProps: GetServerSideProps = async ({ query, locale = 'en' }) => { - const { username, slug } = query as QueryParams; - - try { - const resume = await fetchResumeByIdentifier({ username, slug }); - - return { - props: { username, slug, resume, ...(await serverSideTranslations(locale, ['common'])) }, - }; - } catch { - return { props: { username, slug, ...(await serverSideTranslations(locale, ['common'])) } }; - } -}; - -const Preview: NextPage = ({ username, slug, resume: initialData }) => { - const router = useRouter(); - - const dispatch = useAppDispatch(); - - const resume = useAppSelector((state) => state.resume.present); - - 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]); - - useEffect(() => { - const locale = get(resume, 'metadata.locale', 'en'); - - if (!isEmpty(resume) && router.locale !== locale) { - const { pathname, asPath, query } = router; - - router.push({ pathname, query }, asPath, { locale }); - } - }, [resume, router]); - - useQuery(`resume/${username}/${slug}`, () => fetchResumeByIdentifier({ username, slug }), { - initialData, - onSuccess: (data) => { - dispatch(setResume(data)); - }, - onError: (error) => { - const errorObj = JSON.parse(JSON.stringify(error)); - const statusCode: number = get(errorObj, 'status', 404); - - if (statusCode === 404) { - toast.error('The resume you were looking for does not exist, or maybe it never did?'); - - router.push('/'); - } - }, - }); - - const { mutateAsync, isLoading } = useMutation(printResumeAsPdf); - - if (isEmpty(resume)) return null; - - const layout: string[][][] = get(resume, 'metadata.layout', []); - - const handleDownload = async () => { - try { - const updatedAt = get(resume, 'updatedAt'); - - const url = await mutateAsync({ username, slug, lastUpdated: dayjs(updatedAt).unix().toString() }); - - download(url); - } catch { - toast.error(DEFAULT_ERROR_MESSAGE); - } - }; - - return ( -
- {layout.map((_, pageIndex) => ( - - ))} - -
- - {isLoading ? ( - <> - -

Please wait

- - ) : ( - <> - -

Download PDF

- - )} -
-
- -

- Made with Reactive Resume -

-
- ); -}; - -export default Preview; diff --git a/client/pages/[username]/[slug]/printer.tsx b/client/pages/[username]/[slug]/printer.tsx deleted file mode 100644 index 20329ee2..00000000 --- a/client/pages/[username]/[slug]/printer.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import clsx from 'clsx'; -import get from 'lodash/get'; -import isEmpty from 'lodash/isEmpty'; -import { GetServerSideProps, NextPage } from 'next'; -import { useRouter } from 'next/router'; -import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; -import { useEffect } from 'react'; -import { Resume } from 'schema'; - -import Page from '@/components/build/Center/Page'; -import { fetchResumeByIdentifier } from '@/services/resume'; -import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { setResume } from '@/store/resume/resumeSlice'; -import styles from '@/styles/pages/Printer.module.scss'; - -type QueryParams = { - slug: string; - username: string; - secretKey?: string; -}; - -type Props = { - resume?: Resume; - locale: string; - redirect?: any; -}; - -export const getServerSideProps: GetServerSideProps, QueryParams> = async ({ - query, - locale = 'en', -}) => { - const { username, slug, secretKey } = query as QueryParams; - - try { - if (isEmpty(secretKey)) throw new Error('There is no secret key!'); - - const resume = await fetchResumeByIdentifier({ username, slug, options: { secretKey } }); - const displayLocale = get(resume, 'metadata.locale') ?? locale; - - return { - props: { - resume, - locale: displayLocale, - ...(await serverSideTranslations(displayLocale, ['common'])), - }, - }; - } catch (error) { - return { - redirect: { - permanent: false, - destination: '/', - }, - }; - } -}; - -const Printer: NextPage = ({ resume: initialData, locale }) => { - const router = useRouter(); - - const dispatch = useAppDispatch(); - - const resume = useAppSelector((state) => state.resume.present); - - useEffect(() => { - if (router.locale !== locale) { - const { pathname, asPath, query } = router; - - router.push({ pathname, query }, asPath, { locale }); - } - }, [router, locale]); - - useEffect(() => { - if (initialData) dispatch(setResume(initialData)); - }, [dispatch, initialData]); - - if (!resume || isEmpty(resume)) return null; - - const layout: string[][][] = get(resume, 'metadata.layout', []); - - return ( -
- {layout.map((_, pageIndex) => ( - - ))} -
- ); -}; - -export default Printer; diff --git a/client/pages/_app.tsx b/client/pages/_app.tsx deleted file mode 100644 index 4a3e609a..00000000 --- a/client/pages/_app.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import '@/styles/globals.scss'; -import '@fontsource/material-icons'; -import '@fontsource/ibm-plex-sans/300.css'; -import '@fontsource/ibm-plex-sans/400.css'; -import '@fontsource/ibm-plex-sans/500.css'; -import '@fontsource/ibm-plex-sans/600.css'; -import '@fontsource/ibm-plex-sans/700.css'; - -import env from '@beam-australia/react-env'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { GoogleOAuthProvider } from '@react-oauth/google'; -import type { AppProps } from 'next/app'; -import Head from 'next/head'; -import { appWithTranslation } from 'next-i18next'; -import { Toaster } from 'react-hot-toast'; -import { QueryClientProvider } from 'react-query'; -import { Provider as ReduxProvider } from 'react-redux'; -import { PersistGate } from 'redux-persist/integration/react'; - -import Loading from '@/components/shared/Loading'; -import ModalWrapper from '@/modals/index'; -import queryClient from '@/services/react-query'; -import store, { persistor } from '@/store/index'; -import WrapperRegistry from '@/wrappers/index'; - -const App = ({ Component, pageProps }: AppProps): JSX.Element => ( - <> - - Reactive Resume | A free and open source resume builder - - - - - - - - - - - - - - - - - - - - - - - - - -); - -export default appWithTranslation(App); diff --git a/client/pages/_document.tsx b/client/pages/_document.tsx deleted file mode 100644 index 32183bf4..00000000 --- a/client/pages/_document.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { NextPage } from 'next'; -import NextDocument, { DocumentContext, Head, Html, Main, NextScript } from 'next/document'; -import Script from 'next/script'; - -const Document: NextPage = () => ( - - -