Compare commits

...
883 Commits
Author SHA1 Message Date
Amruth Pillai 91c8624d80 Merge pull request #2485 from MohammedAlhalwachi/fix-gengar
fix(artboard): fix padding regression in Gengar template (#2463)
2026-01-08 09:26:47 +01:00
Mohammed Alhalwachi f6dc3bf8df fix(artboard): fix padding regression in Gengar template (#2463) 2025-12-23 22:17:50 +03:00
Amruth Pillai a9b8d1d06f Merge pull request #2483 from y8a5y/main
[Feature] Permalink to share/download resume json data
2025-12-23 03:12:39 +01:00
y8a5y 76b61d0135 fix(Contrib file): add steps to ensure successful CI 2025-12-22 10:40:40 +01:00
y8a5y d7409832ab fix(Resume Controller): address CI lint issue 2025-12-22 10:26:36 +01:00
y8a5y 1749f95086 fix(Sharing section): address CodeRabbit nitpick nb4 2025-12-21 22:43:08 +01:00
y8a5y 2e11e64881 fix(Resume Controller): address CodeRabbit nitpick nb1 2025-12-21 22:42:48 +01:00
y8a5y db86f3275b feature(Sharing section): add Raw Json URL card to Sharing section 2025-12-21 14:37:37 +01:00
y8a5y 62c09ea142 feature(Resume Controller): add route to get Resume without incrementing view count 2025-12-21 14:35:24 +01:00
y8a5y 589bd2c162 refacto(Resume Controller): reflecting previous change in controller 2025-12-21 14:34:57 +01:00
y8a5y 4c7ed8ea0d refacto(Resume Service): move view count increment outside of findOne 2025-12-21 14:30:02 +01:00
Amruth Pillai 25021d1b20 Set up CI with Azure Pipelines
[skip ci]
2025-11-07 12:11:28 +01:00
Amruth Pillai 53fdfdf8db Merge pull request #2437 from Sahilbhatane/main
fix(artboard): bypass Google Fonts for local system fonts and post PAGE_LOADED immediately; add utils docstrings and unit tests for isLocalFont issue #2435
2025-11-06 17:19:33 +01:00
sahil 8a45f2de4d fixed issue [Bug] <Error MIME al cargar fuente “Arial” desde Google Fonts en exportaciones públicas>
Fixes #2435
2025-11-05 19:08:38 +05:30
sahil 4ccc7bae40 Fixed issue [Bug] <Error MIME al cargar fuente “Arial” desde Google Fonts en exportaciones públicas>
Fixes #2435
2025-11-05 18:59:20 +05:30
sahil 2585c47de8 Fixed bug [Bug] <Error MIME al cargar fuente “Arial” desde Google Fonts en exportaciones públicas>
Fixes #2435
2025-11-05 16:33:56 +05:30
sahil b49798950a FIxed bug [Bug] <Error MIME al cargar fuente “Arial” desde Google Fonts en exportaciones públicas>
Fixes #2435
2025-11-05 16:12:57 +05:30
Amruth Pillai 03f15f91b3 chore(package): bump version to 4.5.5 2025-11-04 18:36:52 +01:00
Amruth Pillai 8e13d9c3ac Merge pull request #2434 from AmruthPillai/2275-bug-certifications-texts-dont-fit-inside-the-page
Bugfix: links/urls don't fit inside the page
2025-11-04 18:34:42 +01:00
Amruth Pillai 80932eb80c Fix bug: links/urls don't fit inside the page 2025-11-04 18:31:47 +01:00
Amruth Pillai b2ae2c05d8 chore(package): bump version to 4.5.4 2025-11-04 16:07:10 +01:00
Amruth Pillai e349fc9bd1 Merge pull request #2421 from LinuxKunaL/main
fix(rich-input.tsx): replace default scrollbar with themed scrollbar in Summary Editor, fixed issue #2420
2025-11-04 15:35:36 +01:00
Amruth Pillai 53213dfb26 Refactor RichInput component: adjust ScrollArea padding and EditorContent min-height 2025-11-04 15:27:40 +01:00
Amruth Pillai 40f27a53b6 Merge branch 'main' into LinuxKunaL/main 2025-11-04 15:23:22 +01:00
Amruth Pillai 2d900600bf update translations 2025-11-04 15:22:53 +01:00
Amruth Pillai 047e9c248f Merge pull request #2426 from abhas20/feature
Added collapsible feature for each section
2025-11-04 15:21:36 +01:00
Amruth Pillai 1cc1c39903 implement expand/collapse all sections 2025-11-04 15:21:17 +01:00
Amruth Pillai f6f2a29a7a Merge remote-tracking branch 'origin/main' into feature 2025-11-04 14:39:50 +01:00
Amruth Pillai 4368f6a887 Merge pull request #2432 from Ofsen/main
chore(puppeteer): using puppeteer-core instead of puppeteer
2025-11-04 14:39:06 +01:00
Amruth Pillai f6db9fb387 Merge branch 'main' into Ofsen/main 2025-11-04 14:37:41 +01:00
Amruth Pillai afa3804bea add wellKnown url 2025-11-04 14:36:22 +01:00
Amruth Pillai e2f43b4931 add funding-manifest-urls 2025-11-04 14:34:57 +01:00
Amruth Pillai 067fdd0921 remove format script 2025-11-04 12:35:11 +01:00
Amruth Pillai 1296e6bd45 Merge branch 'main' of github.com:AmruthPillai/Reactive-Resume 2025-11-03 23:21:53 +01:00
Amruth Pillai d883edb51f Add funding.json file and update package.json dependencies
- Introduced a new funding.json file to support project funding details.
- Updated package.json to version 4.5.3 and modified various dependencies, including @babel/core, @babel/preset-react, and others to their latest versions.
- Adjusted pnpm-lock.yaml to reflect the changes in dependencies and their versions.
2025-11-03 23:21:29 +01:00
Ofsen 51fcf13d37 chore(puppeteer): using puppeteer-core instead of puppeteer 2025-11-03 20:24:57 +01:00
abhas20 91ef87b4e0 Fix(builder-page) typo: renamed toogleSection 2025-10-25 08:10:02 +05:30
abhas20 4c0cc947a2 updated(builder-page): added collapsible feature for each section 2025-10-24 18:38:52 +05:30
Amruth Pillai 6fcb7a4845 Merge pull request #2419 from AmruthPillai/l10n
New Translations from Crowdin
2025-10-18 22:32:56 +02:00
Amruth Pillai 213f96b189 Merge pull request #2415 from GouravNG/feat/rich-input-highlight
[Feature] Highlight the selected options under Rich inputs.
2025-10-18 22:32:23 +02:00
Amruth Pillai e8d6d4ad3a Merge pull request #2412 from alay-dev/main
fix(template): fix gengar template summary section not rendering in sidebar
2025-10-18 22:31:10 +02:00
Amruth Pillai 87370cfdf0 Apply suggestion from @AmruthPillai 2025-10-18 22:30:51 +02:00
Amruth Pillai 112a644927 Merge pull request #2410 from JuanJesusAlejoSillero/main
docs: add Gengar, Glalie, and Leafish templates while also updating image paths to relative in-repo URLs in README.md
2025-10-18 22:29:49 +02:00
Crowdin Bot 2977e3528a New Crowdin translations by GitHub Action 2025-10-18 00:10:39 +00:00
LinuxKunaL fd7b5e0dd4 fix(rich-input.tsx): replace default scrollbar with themed scrollbar in Summary Editor, fixed #2420 2025-10-17 01:22:42 +05:30
Gourav 21fd079f94 feat(rich-input): updating datastate on pressing rich input options
- when selecting the rich input option earlier only aria-pressed="true" was changing and not datastate now updated the code to manually update the data-state
2025-10-13 22:52:25 +05:30
Gourav 9bdc61b50d feat(rich-input): Highlight the selected options of rich inputs 2025-10-13 22:07:01 +05:30
Alay Naru 2de24d5b55 fix(template): fix gengar template summary section not rendering in sidebar 2025-10-12 18:49:44 +05:30
Gourav Gunaga 1cba9d0fb9 Merge branch 'AmruthPillai:main' into main 2025-10-11 11:02:20 +05:30
Juan Jesús Alejo Sillero 8f532bf4a6 docs: add Gengar, Glalie, and Leafish templates while also updating image paths to relative in-repo URLs in README.md 2025-10-11 01:24:51 +02:00
Amruth Pillai 4efdabd475 Merge branch 'main' of github.com:AmruthPillai/Reactive-Resume 2025-10-09 00:10:28 +02:00
Amruth Pillai 8803101dcd fix for long waiting times 2025-10-09 00:10:25 +02:00
Amruth Pillai a4a9381b65 Merge pull request #2408 from AmruthPillai/dependabot/npm_and_yarn/npm_and_yarn-2752d6d8c0
Bump nodemailer from 6.10.1 to 7.0.7 in the npm_and_yarn group across 1 directory
2025-10-08 21:55:09 +02:00
dependabot[bot] 618d42019e Bump nodemailer in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [nodemailer](https://github.com/nodemailer/nodemailer).


Updates `nodemailer` from 6.10.1 to 7.0.7
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/nodemailer/compare/v6.10.1...v7.0.7)

---
updated-dependencies:
- dependency-name: nodemailer
  dependency-version: 7.0.7
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-08 19:51:38 +00:00
Amruth Pillai d9b56cfb5b - fixes #2401 2025-10-08 21:49:07 +02:00
Amruth Pillai 5624dc4f83 Merge pull request #2402 from GouravNG/revert/EnvExampleFile
revert: readded the .env.example file
2025-10-07 21:25:48 +02:00
Gourav Gunaga 313fe47050 Merge pull request #1 from GouravNG/revert/EnvExampleFile
revert: readded the .env.example file
2025-10-07 21:56:25 +05:30
Gourav 772332661f revert: readded the .env.example file 2025-10-07 21:54:59 +05:30
Amruth Pillai 4c6b512455 Update version to 4.5.0 in package.json and adjust PO revision dates across multiple language files to reflect recent changes. 2025-10-01 09:46:55 +00:00
Amruth Pillai cc576f5220 Merge pull request #2219 from mtdvlpr/main
fix(templates): don't show section when all items are hidden
2025-10-01 11:30:33 +02:00
Amruth Pillai b193329c23 Merge pull request #2205 from ImDarkShadow/main
fix(linkedin): use case-insensitive slug check to prevent missing image
2025-10-01 11:29:55 +02:00
Amruth Pillai 8e1459f092 Merge pull request #2244 from m4dd0c/draggable-tags
feat(dialogs): skills, custom-section, and interests dialogs support draggable tags.
2025-10-01 11:27:55 +02:00
Amruth Pillai e19b55ff65 refactor(auth): streamline logout process by clearing query data 2025-10-01 11:25:04 +02:00
Amruth Pillai 687161df98 Merge pull request #2249 from MagicalGlove/main
fix(bug): clear resumes on logout to prevent seeing resumes from previously logged in accounts
2025-10-01 11:24:30 +02:00
Amruth Pillai c54cfebf5f Merge branch 'main' of github.com:AmruthPillai/Reactive-Resume 2025-10-01 11:21:09 +02:00
Amruth Pillai e8d0ed1370 refactor(dashboard): replace useState with useLocalStorage for layout persistence 2025-10-01 11:21:06 +02:00
Amruth Pillai 87a17a5196 Merge pull request #2274 from th3fallen/patch-1
Fix regex for parsing linkedin array links
2025-10-01 11:19:35 +02:00
Amruth Pillai d19df3389c fix translation for zh-CN 2025-10-01 11:18:00 +02:00
Amruth Pillai faa4c606e5 Merge branch 'main' of github.com:AmruthPillai/Reactive-Resume 2025-10-01 11:17:03 +02:00
Amruth Pillai 210fbd18ac update translations 2025-10-01 11:16:59 +02:00
Amruth Pillai 3ba0141ab8 Merge pull request #2296 from cleves0315/main
fix(builder): update "Separate Links" translation
2025-10-01 11:16:41 +02:00
Amruth Pillai 279a2ddaeb Merge branch 'main' into main 2025-10-01 11:16:30 +02:00
Amruth Pillai ea7ee3dd7a Merge pull request #2299 from Moamal-2000/improve-accessibility
fix(homepage): resolve missing button labels and incorrect heading order
2025-10-01 11:14:16 +02:00
Amruth Pillai a55bea1e07 Merge branch 'main' into improve-accessibility 2025-10-01 11:13:56 +02:00
Amruth Pillai ed5cb7f17b bump version to v4.4.8 2025-10-01 11:11:59 +02:00
Amruth Pillai c7f8daaff3 Merge pull request #2375 from Avinash99b/main
fix(printer.service.ts): fix pdf rendering, issue #2374 solved
2025-10-01 11:01:31 +02:00
Amruth Pillai ac48ad78db Merge pull request #2382 from glconti/main
feat(openai): add Azure OpenAI support with configuration options
2025-10-01 10:56:07 +02:00
Amruth Pillai c18e18cce8 Merge pull request #2233 from SukkaW/jsd
replace unpkg w/ jsdelivr
2025-10-01 10:55:31 +02:00
Amruth Pillai 65ba13e503 update dependencies 2025-10-01 10:46:46 +02:00
Amruth Pillai 44ec9e1d43 remove version in lint-test-build.yml 2025-10-01 10:40:35 +02:00
Amruth Pillai 7c53949741 fix formatting 2025-10-01 10:39:49 +02:00
Amruth Pillai 6d37769e38 Update dependencies and refactor icon imports across the codebase 2025-10-01 10:38:50 +02:00
Amruth Pillai c525f8d2cc fix security issue, with notes being visible in public api response 2025-10-01 10:07:04 +02:00
Amruth Pillai 04dfcae898 update links to digitalocean 2025-10-01 09:44:26 +02:00
Amruth Pillai 9b5a99a8ca update hyperlinks 2025-10-01 09:39:26 +02:00
Amruth Pillai c44f1c5282 update hyperlinks 2025-10-01 09:39:00 +02:00
Amruth Pillai 24dfa99034 Update README with Warp Sponsorship
Added Warp sponsorship section and re-format app description.
2025-10-01 09:38:10 +02:00
Gianluigi Conti 35c0177729 fix(openai): update Azure API version and improve error handling 2025-09-15 22:44:28 +02:00
Gianluigi Conti 098d67cd8c feat(openai): add Azure OpenAI support with configuration options 2025-09-09 10:14:34 +02:00
Avinash Bathula d8e0ced54c fix(printer-service): ensure profile image fully loads before PDF render 2025-09-02 08:25:41 +05:30
Avinash Bathula 8e9b409bae fix(printer.service.ts): fix pdf rendering, issue #2374 solved 2025-09-02 07:45:20 +05:30
Amruth Pillai b995a6b6c0 Merge pull request #2346 from l0b0/main
chore: Remove executable flag from images
2025-08-08 08:58:38 +00:00
Victor Engmark 3e76a52306 Merge branch 'main' into main 2025-07-13 16:40:03 +02:00
Amruth Pillai 4e91a2e2ef fix build error for ci workflow 2025-07-03 15:40:09 +02:00
Amruth Pillai 4314912d5a security updates, dependencies house-keeping, add new translations etc. 2025-07-03 15:35:00 +02:00
Victor Engmark 93da5157ff chore: Remove executable flag from images 2025-06-30 14:52:56 +02:00
Moamal-2000 cc7bc4ffb8 fix(homepage): use h3 instead of h4 to maintain heading order in FeaturesSection component 2025-05-10 12:53:51 +03:00
Moamal-2000 78eefe9da1 fix(homepage): add accessible names to footer language button and theme toggle buttons 2025-05-10 12:45:13 +03:00
Cleves 7f7c4acdcb fix(builder): update "Separate Links" translation 2025-05-09 18:32:23 +08:00
Clark Tomlinson a23dbfa3df Fix regex for parsing linkedin array links
The second replace was incorrect, resulting in a link like `https://google.com]` which failed zod validation
2025-04-25 12:55:35 -04:00
Magical Glove 249104e7a3 Update logout.ts 2025-03-24 11:14:28 +01:00
Magical Glove 76bbe7de6b fix(bug): clear resumes on logout
fix(bug): When logged out, resumes aren't cleared and are therefore visible to next logged in user.
2025-03-24 10:23:14 +01:00
Manish Suthar 1994dde1f2 chore: handleDragOver moved to outer scope for better perf 2025-03-22 23:22:15 +05:30
Manish Suthar a8626e400d feat(dialogs): skills, custom-section, and interests dialogs supports draggable tags 2025-03-20 02:17:34 +05:30
SukkaW f5136da681 replace unpkg w/ jsdelivr 2025-03-15 22:44:18 +08:00
Manoah Tervoort 8efc243e43 fix(templates): don't show section when all items are hidden 2025-02-21 11:00:09 +01:00
Amruth Pillai cd21860535 Update typography.tsx
fixes #2061
2025-02-09 12:10:14 +01:00
Amruth Pillai 7054623678 chore(dependencies): update TypeScript ESLint and Vitest to latest versions
- Bump @typescript-eslint packages to 8.23.0
- Update Vitest and related packages to 2.1.9
- Minor version upgrades for ts-api-utils and other related dependencies
2025-02-03 19:51:39 +01:00
Pritam Das 37781d51f3 fix(linkedin): use case-insensitive slug check to prevent missing image 2025-02-04 00:02:43 +05:30
Amruth Pillai ec4e43d4fc sync translations from crowdin 2025-02-03 10:14:15 +01:00
Amruth Pillai 1d4529128f fix lint issues 2025-02-03 09:41:21 +01:00
Amruth Pillai 60ed3e2a8d Update lint-test-build.yml 2025-02-02 16:41:31 +01:00
Amruth Pillai 5b67e7c0b4 Update package.json 2025-02-02 16:40:12 +01:00
Amruth Pillai 1399d3c44b Merge pull request #2202 from Pieczasz/feat/2169-project-tags-order
feat(projects): allow reordering of project item tags
2025-02-02 16:39:47 +01:00
Pieczasz eb543cf32d feat(projects): allow reordering of project item tags
Related to #2169
2025-02-02 13:33:39 +01:00
Amruth Pillai 9c6d9833d6 Merge pull request #2201 from AmruthPillai/l10n
New Translations from Crowdin
2025-02-01 04:16:05 +01:00
Crowdin Bot f50cbd71b7 New Crowdin translations by GitHub Action 2025-02-01 00:10:44 +00:00
Amruth Pillai 3d5b3db321 Merge pull request #2198 from omimouni/main
feat(router): add global error boundary for route errors
2025-01-31 16:08:21 +01:00
Oumar Mimouni e438602773 fix(errorpage): added localProvider, replaced a with Link and added custom messages
- Add LocaleProvider wrapper for i18n support
- Replace a tag with React Router Link components
- Add custom error messages for different HTTP status codes
- Implement proper translation support using Trans and t components
2025-01-31 14:38:17 +01:00
Oumar Mimouni f42b29e4ac feat(router): add global error boundary for route errors
- Use React Router’s useRouteError to get error details.
- Create an ErrorPage component in pages/public/error.tsx.
- Show useful error messages based on status codes.
Add a button to help users go back or retry.
2025-01-31 14:01:18 +01:00
Amruth Pillai 92995d9c2b fix formatting and lint errors 2025-01-30 17:57:14 +01:00
Amruth Pillai b21f1648c4 Merge pull request #2188 from AmruthPillai/l10n
New Translations from Crowdin
2025-01-30 17:46:57 +01:00
Amruth Pillai 94c04b44df Merge pull request #2194 from Creative-Geek/patch-1
Allow Usage of OpenAI Compatible APIs
2025-01-30 17:46:31 +01:00
Amruth Pillai 54ed0678bf Merge pull request #2195 from ADecametre/main
Fix broken images
2025-01-30 17:45:17 +01:00
ADecametre 817ec96963 Fix TypeError 2025-01-30 09:14:44 -05:00
ADecametre 8ad5458e2a Fix broken images
- Allowed img tags to be used in summaries
2025-01-30 08:56:43 -05:00
Amruth Pillai a87c5edd60 attempt to fix ci errors 2025-01-30 10:41:38 +01:00
Amruth Pillai e4327736bd fix formatting issues 2025-01-30 09:35:10 +01:00
Amruth Pillai 1fddbe5f92 Update package.json 2025-01-30 08:19:25 +01:00
Amruth Pillai 2c482e7df8 Merge pull request #2186 from ADecametre/main
Fix broken hyperlinks, images and custom CSS (#2182)
2025-01-30 08:18:47 +01:00
Creative-Geek c8edcd3dad Allow usage of openai compatible apis
Changed front-end fields verification to allow the user to enter any openai compatible endpoint and api key.
to do: make a verify connection button and edit paragraphs to reflect the change
2025-01-30 08:49:22 +02:00
Crowdin Bot a82c25c7cb New Crowdin translations by GitHub Action 2025-01-30 00:09:54 +00:00
ADecametre 73b423030f Removed unused import 2025-01-27 23:10:29 -05:00
ADecametre 809551d0f8 Fix #2182 2025-01-27 19:23:28 -05:00
Amruth Pillai e795ec64d6 update translations 2025-01-27 09:59:37 +01:00
Amruth Pillai f8373e4798 Merge pull request #2179 from AmruthPillai/l10n
New Translations from Crowdin
2025-01-26 21:25:38 +01:00
Crowdin Bot a31c434fbc New Crowdin translations by GitHub Action 2025-01-26 00:10:57 +00:00
Amruth Pillai 1fa8aae80a bump version to v4.4.2 2025-01-26 01:00:49 +01:00
Amruth Pillai 27b60a4df9 fixes #2176, text align was not being reflected in summary sections 2025-01-26 01:00:38 +01:00
Amruth Pillai d21983aab4 update translations 2025-01-25 00:49:33 +01:00
Amruth Pillai 9406d78653 - include more local fonts, such as Times New Roman and Arial (fixes #2170)
- remove embedding fonts to PDF as it wasn't doing anything
2025-01-25 00:47:39 +01:00
Amruth Pillai c7ae0e94d7 sanitize all user inputs, fix #2172 2025-01-24 23:53:45 +01:00
Amruth Pillai 308a8e3ae3 update translations 2025-01-24 21:20:12 +01:00
Amruth Pillai 4c90cc1838 fix security vulnerability with update password API route 2025-01-24 21:13:24 +01:00
Amruth Pillai 460a40711e fix issue with missing DialogTitle/DialogDescription, fix issue with hot reloads 2025-01-19 22:01:37 +01:00
Amruth Pillai 18cf814779 fixes #2161, remove v1 validation requirement for openai api url 2025-01-17 22:38:45 +01:00
Amruth Pillai a9656afbbf sync translations from crowdin 2025-01-17 22:33:13 +01:00
Amruth Pillai 385fe008ce Merge branch 'main' of github.com:AmruthPillai/Reactive-Resume 2025-01-17 22:32:06 +01:00
Amruth Pillai 7e25e853d7 update dependencies 2025-01-17 22:31:57 +01:00
Amruth Pillai de5adbe3d2 Merge pull request #2160 from AmruthPillai/l10n
New Translations from Crowdin
2025-01-16 22:36:05 +01:00
Amruth Pillai c239ae3f49 update dependencies 2025-01-16 12:09:14 +01:00
Crowdin Bot 1bfdff5b30 New Crowdin translations by GitHub Action 2025-01-16 00:10:16 +00:00
Amruth Pillai 63db927924 - fixes #2153, attempt to fix 401 unauthorized error when implementing OIDC 2025-01-15 16:32:43 +01:00
Amruth Pillai 9a34e4af27 Merge pull request #2155 from AmruthPillai/l10n
New Translations from Crowdin
2025-01-15 15:59:02 +01:00
Crowdin Bot b5589338ec New Crowdin translations by GitHub Action 2025-01-15 00:10:04 +00:00
Amruth Pillai a19059aa76 Merge pull request #2152 from AmruthPillai/2151-bug-openid-authentication-only-works-when-node_env-is-development
fixes #2151: apply secure cookie session only if using SSL (https)
2025-01-14 10:24:11 +01:00
Amruth Pillai 15f962310b fix lint issue 2025-01-14 09:52:26 +01:00
Amruth Pillai a32def2086 fixes #2151, apply secure cookie session only if using SSL (https) 2025-01-14 09:45:57 +01:00
Amruth Pillai 21af624096 Merge pull request #2149 from AmruthPillai/feat/implement-openid-connect-strategy
Implement OpenID Connect Authentication Strategy
2025-01-13 16:22:43 +01:00
Amruth Pillai 227870ac78 fix issues suggested by coderabbit 2025-01-13 16:22:29 +01:00
Amruth Pillai 33cb3dbd6a ensure secure cookies are used in express-session 2025-01-13 16:04:27 +01:00
Amruth Pillai eb7813ac6f Implement OpenID Connect Authentication Strategy (works with Keycloak, Authentik etc.) 2025-01-13 15:56:29 +01:00
Amruth Pillai 0f8f2fe560 cleanup internal package resolution 2025-01-13 11:53:42 +01:00
Amruth Pillai 51f38f0884 update translations 2025-01-13 01:50:39 +01:00
Amruth Pillai 6b93fd179d Merge pull request #2147 from AmruthPillai/l10n
New Translations from Crowdin
2025-01-13 01:15:29 +01:00
Crowdin Bot 9385f36832 New Crowdin translations by GitHub Action 2025-01-13 00:10:39 +00:00
Amruth Pillai a5dc15dc08 Merge pull request #2146 from AmruthPillai/feat/custom-css
Implement Custom CSS Feature
2025-01-13 01:07:16 +01:00
Amruth Pillai eab996f7e7 some more updates 2025-01-13 01:07:01 +01:00
Amruth Pillai 43c5a33773 Implement Custom CSS Feature 2025-01-13 00:31:49 +01:00
Amruth Pillai 7fb0226ddc update to react-router v7 2025-01-12 19:41:18 +01:00
Amruth Pillai db6e7a7480 fix deploy step 2025-01-12 18:10:58 +01:00
Amruth Pillai 6335ad1571 fixes #2082, fixes #2066 - fallback to cuid2 if filename contains non-latin characters 2025-01-12 18:06:44 +01:00
Amruth Pillai 2d62504895 Merge pull request #2144 from AmruthPillai/chore/update-dependencies
Update dependencies, bump release version and a lot more
2025-01-12 17:43:07 +01:00
Amruth Pillai e34d0cebe5 fix deploy step in publish-docker-image 2025-01-12 17:42:51 +01:00
Amruth Pillai 0053d696ff Update ESLint configuration and schemas to use Zod library
- Changed ESLint configuration to target TypeScript files and added parser options for better integration.
- Updated various schemas across the application to replace `nestjs-zod/z` imports with `zod` for consistency.
- Refactored password validation in authentication schemas to use `z.string()` instead of `z.password()`.
- Enhanced date handling in user and resume schemas by introducing a new `dateSchema` utility.
- Updated `.ncurc.json` to target minor upgrades for dependencies.
2025-01-12 17:34:45 +01:00
Amruth Pillai 6fb0a72a56 fix dependencies to latest minor, add swc to no-update list 2025-01-12 17:01:20 +01:00
Amruth Pillai 5b3e91e34d update release version 2025-01-12 16:55:59 +01:00
Amruth Pillai d1a5a41e4d Enhance UI and Configuration:
- Added missing `type="button"` attributes to various toolbar buttons in the rich-input component for better accessibility.
- Updated Gengar template to ensure the summary section is only displayed when populated.
- Changed boolean values in Docker Compose files from unquoted to quoted strings for consistency and to prevent potential parsing issues.
2025-01-12 16:50:07 +01:00
Amruth Pillai d0a07686a5 Merge pull request #1997 from 2368883029/main
Fix(client): Fix Unintentional Form Submission on Rich-input Toolbar Actions
2025-01-12 16:41:35 +01:00
Amruth Pillai ffa4747ed6 Merge pull request #2002 from ilatypov/main
Allow the headless browser connect to the app.
2025-01-12 16:40:44 +01:00
Amruth Pillai b963813910 Merge branch 'main' into main 2025-01-12 16:40:36 +01:00
Amruth Pillai 58950ed0ef Merge pull request #2008 from busches/patch-1
fix(docs): Add missing themes to bug-report
2025-01-12 16:38:15 +01:00
Amruth Pillai 1c7a6c952f Merge pull request #2010 from busches/fix-2007
fix(Gengar): Hide Summary section unless populated
2025-01-12 16:37:01 +01:00
Amruth Pillai 3ae651fece resolve formatting and linting issues 2025-01-12 16:35:35 +01:00
Amruth Pillai bd52983780 Merge branch 'main' into fix-2007 2025-01-12 16:35:10 +01:00
Amruth Pillai 39daed3502 Merge branch 'infinia-yzl-main' 2025-01-12 16:25:25 +01:00
Amruth Pillai dda47f51ec Merge branch 'main' of github.com:infinia-yzl/Reactive-Resume into infinia-yzl-main 2025-01-12 16:24:54 +01:00
Amruth Pillai 374b9bcc58 Merge branch 'main' of github.com:AmruthPillai/Reactive-Resume 2025-01-12 16:23:48 +01:00
Amruth Pillai f0b18019d5 fix allowOverflow definition on scroll-area 2025-01-12 16:23:46 +01:00
Amruth Pillai 5c10f3d866 Merge pull request #2024 from plecrx/patch-1
fix(storage-controller): rm duplicate userID & use correct filename parameter
2025-01-12 16:09:05 +01:00
Amruth Pillai 5d839e5420 Merge pull request #2009 from busches/patch-2
fix(docs): Update CONTRIBUTING.md for Node 20
2025-01-12 16:07:44 +01:00
Amruth Pillai febbdefc0b Merge pull request #2035 from noreb001/bugfix/CardClippingIssue
bugfix: Fixed clipping issue with cards on dashboard page
2025-01-12 16:03:25 +01:00
Amruth Pillai 6110440682 resolves #2047, fix summary text in chikorita template, when in sidebar 2025-01-12 15:58:28 +01:00
Amruth Pillai d0a174d7b7 fix localhost translation logic in printer service 2025-01-12 15:52:30 +01:00
Amruth Pillai 6708570c49 Merge pull request #2091 from raymondyangdev/main
fix(templates): Add right border after website in Rhyhorn
2025-01-12 15:38:54 +01:00
Amruth Pillai bb4bbf4174 Merge pull request #2095 from jbilinski/upstream-0
fix(printer): refine "localhost" matching in printer.service.ts
2025-01-12 15:37:38 +01:00
Amruth Pillai a3ef6520e7 - refactor(templates): update sidebar display conditionals for consistency
- Modified multiple template files to change the sidebar length conditionals from `!== 0` to `> 0` for improved readability and consistency across components.

- Added Code of Conduct
2025-01-12 15:36:01 +01:00
Amruth Pillai 0fec5ce86d Merge pull request #2103 from 0x346e3730/feat/full-width-main-if-no-sidebar
feat(templates): make main full-width if sidebar is empty
2025-01-12 15:31:42 +01:00
Amruth Pillai 26e34b6b83 chore: update Prettier configuration and add Tailwind CSS plugin; refactor CSS classes for consistency
- Updated .prettierrc to include Tailwind CSS plugin and functions.
- Added prettier-plugin-tailwindcss to package.json and pnpm-lock.yaml.
- Refactored CSS classes in main.css and various TSX files for improved consistency and readability.
- Adjusted spacing in several components to enhance layout and maintain uniformity.
2025-01-12 15:29:23 +01:00
Amruth Pillai 007243f2c3 Merge pull request #2114 from Karanrajsinh/main
Fix #1660 [Mobile Frontend Issue] :  Section-Dialog Overflow In Mobile Device Along With Some Changes In Spacing And Font Size Of Sidebar
2025-01-12 15:28:18 +01:00
Amruth Pillai 9d0a0bba86 Merge branch 'main' into main 2025-01-12 15:17:55 +01:00
Amruth Pillai 89a44cc33a - normalize username and email fields to lowercase, resolves #1740
- add autoComplete attributes to auth flow for easier sign in/sign up
2025-01-12 15:13:11 +01:00
Amruth Pillai 92856b6f06 Merge pull request #1978 from googleknight/fix/profile-picture-adjustment-crash
fix(edit profile picture): fix the page crash happening when clicking on circular border radius
2025-01-12 13:46:26 +01:00
Amruth Pillai c968188080 Merge pull request #2133 from smuwonge/main
docs: remove period for consistency in feature list
2025-01-12 13:35:06 +01:00
Amruth Pillai db36bc9770 Merge pull request #2143 from AmruthPillai/fix/linkedin-logo-not-appearing
Release v4.3.2
2025-01-12 13:31:31 +01:00
Amruth Pillai 35df043f66 fix: handle edge case to display LinkedIn logo when used in Profiles 2025-01-12 13:24:57 +01:00
Amruth Pillai 3c4a26bc06 Update publish-docker-image.yml 2025-01-11 19:03:28 +01:00
Amruth Pillai 62046c49b5 Merge pull request #2140 from AmruthPillai/release/v4.3.1
release: v4.3.1
2025-01-11 16:15:17 +01:00
Amruth Pillai 3b73dcf29d fix issues brought up by coderabbit 2025-01-11 16:13:04 +01:00
Amruth Pillai 9b20c46348 revert ci workflow to non-distributed 2025-01-11 16:09:07 +01:00
Amruth Pillai 996ef650db update CI 2025-01-11 16:01:19 +01:00
Amruth Pillai 1c36ac1d68 update CI workflow 2025-01-11 15:59:36 +01:00
Amruth Pillai 198c269790 remove console.log statements 2025-01-11 15:55:06 +01:00
Amruth Pillai 58ef309b68 release: v4.3.1 2025-01-11 15:38:26 +01:00
Amruth Pillai 54bace451c Merge pull request #2138 from AmruthPillai/dependabot/npm_and_yarn/npm_and_yarn-45c02561f4
Bump the npm_and_yarn group across 1 directory with 10 updates
2025-01-11 14:03:54 +01:00
Amruth Pillai 193b15edc1 Merge branch 'main' into dependabot/npm_and_yarn/npm_and_yarn-45c02561f4 2025-01-11 13:22:39 +01:00
Amruth Pillai e4ec678512 pin versions of chromium 2025-01-11 13:17:14 +01:00
dependabot[bot] 29c18c1e89 Bump the npm_and_yarn group across 1 directory with 10 updates
Bumps the npm_and_yarn group with 10 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `5.4.2` | `5.4.6` |
| [braces](https://github.com/micromatch/braces) | `3.0.2` | `3.0.3` |
| [dset](https://github.com/lukeed/dset) | `3.1.3` | `3.1.4` |
| [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) | `4.3.5` | `4.5.1` |
| [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) | `2.0.6` | `2.0.7` |
| [nanoid](https://github.com/ai/nanoid) | `3.3.7` | `3.3.8` |
| [pug](https://github.com/pugjs/pug) | `3.0.2` | `3.0.3` |
| [rollup](https://github.com/rollup/rollup) | `4.21.0` | `4.30.1` |
| [webpack](https://github.com/webpack/webpack) | `5.90.3` | `5.97.1` |
| [ws](https://github.com/websockets/ws) | `7.5.9` | `7.5.10` |



Updates `vite` from 5.4.2 to 5.4.6
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.6/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.6/packages/vite)

Updates `braces` from 3.0.2 to 3.0.3
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

Updates `dset` from 3.1.3 to 3.1.4
- [Release notes](https://github.com/lukeed/dset/releases)
- [Commits](https://github.com/lukeed/dset/compare/v3.1.3...v3.1.4)

Updates `fast-xml-parser` from 4.3.5 to 4.5.1
- [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases)
- [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v4.3.5...v4.5.1)

Updates `http-proxy-middleware` from 2.0.6 to 2.0.7
- [Release notes](https://github.com/chimurai/http-proxy-middleware/releases)
- [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v2.0.7/CHANGELOG.md)
- [Commits](https://github.com/chimurai/http-proxy-middleware/compare/v2.0.6...v2.0.7)

Updates `nanoid` from 3.3.7 to 3.3.8
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.3.7...3.3.8)

Updates `pug` from 3.0.2 to 3.0.3
- [Release notes](https://github.com/pugjs/pug/releases)
- [Commits](https://github.com/pugjs/pug/compare/pug@3.0.2...pug@3.0.3)

Updates `rollup` from 4.21.0 to 4.30.1
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.21.0...v4.30.1)

Updates `webpack` from 5.90.3 to 5.97.1
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.90.3...v5.97.1)

Updates `ws` from 7.5.9 to 7.5.10
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.5.9...7.5.10)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  dependency-group: npm_and_yarn
- dependency-name: braces
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: dset
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: fast-xml-parser
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: http-proxy-middleware
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: nanoid
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: pug
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: rollup
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: webpack
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: ws
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-11 11:37:06 +00:00
Amruth Pillai 13d90e8138 update translations 2025-01-11 12:28:16 +01:00
Amruth Pillai 02ce1ad48c fix lint issues 2025-01-11 12:07:30 +01:00
Amruth Pillai 8a401de5c9 fix account deletion 2025-01-09 15:46:50 +01:00
Ilguiz Latypov 1c5c4d0117 fix: allow the headless browser connect to the app 2025-01-02 20:04:17 -05:00
Sharon Muwonge d6fee1e3a6 docs: remove period for consistency in feature list 2025-01-02 14:31:52 -08:00
Karanrajsinh d84aeee968 fix(builderpage): resolve section-dialog height overflow for mobile and adjust sidebar font and spacing styles 2024-12-01 10:17:01 +05:30
0x346e3730 c87242142d feat(templates): make main full-width if sidebar is empty 2024-11-14 09:39:57 +04:00
Jamie Bilinski 2c84976e28 Merge pull request #2 from jbilinski/jbilinski-patch-1
fix: refine "localhost" matching in printer.service.ts
2024-11-07 10:18:43 -08:00
Jamie Bilinski 31ed9f41a0 fix: refine "localhost" matching in printer.service.ts
changed localhost matching for local dev environments from a url string match to more specific regex that matches host property.
This was causing the "about:blank", no output error in (edge) case of printing a pdf from any url with string "localhost"
2024-11-07 10:16:35 -08:00
raymondyangdev 700b98fcb7 fix(templates): Add right border in header after website field with custom fields in Rhyhorn 2024-11-05 17:35:14 +13:00
Amruth Pillai e29f973889 Merge pull request #2073 from bocklucas/ft-add-ollama
Adding Ollama Support
2024-10-13 02:08:15 +02:00
Lucas Bock 6e25780b25 Adding Ollama Support 2024-10-12 18:44:23 -05:00
Ben 3f01a9e58e fixed clipping issue with cards on dashboard page 2024-09-06 12:43:27 +00:00
Amruth Pillai 1bed63a4af Merge pull request #2030 from rinczefi-user/rinczefi-patch
set endOfLine to auto to support the codebase also on windows machines
2024-09-04 16:53:52 +02:00
Inczefi Robert a530fce78c set endOfLine to auto to support the codebase also on windows machines 2024-09-04 14:55:11 +03:00
Amruth Pillai 87a395683d fix eslint issues 2024-09-02 12:50:57 +02:00
Amruth Pillai e3f3b8b464 bump version to 4.2.0 2024-09-02 12:31:56 +02:00
Prescilla Lecurieux b9de35f0d9 fix(storage-controller): rm duplicate userID & use correct filename parameter 2024-08-23 16:45:50 +02:00
infinia-yzl 5827576ffb chore(package/react-zoom-pan-pinch): Bump react-zoom-pan-pinch version to 3.6.1 from 3.4.4 2024-08-14 17:01:05 +08:00
infinia-yzl acc9becf1a feat(toggle-pan): Add toggle to switch between scroll to pan and scroll to zoom
- Modify wheel behaviour to pan by default for a more natural behaviour with trackpads (https://github.com/BetterTyped/react-zoom-pan-pinch/pull/447)
- Bump `react-zoom-pan-pinch` version to `3.6.1` from `3.4.4`
2024-08-14 16:25:10 +08:00
Scott Busche c2837838ee fix(Gengar): Hide Summary section unless populated 2024-08-04 14:36:58 -05:00
Scott Busche 304fd93ece Update CONTRIBUTING.md for Node 20
package.json and GitHub Actions both run with Node 20
2024-08-04 14:20:41 -05:00
Scott Busche 491bbcadcc Add missing themes to bug-report
A few themes were missing when I filled out a bug report.
2024-08-04 14:18:47 -05:00
Amruth Pillai df63f4b8a7 Merge pull request #1999 from StijnGroenen/feature/custom-field-url
feat(templates): added url support for custom fields
2024-07-22 12:04:36 +02:00
Stijn Groenen 29fc7c419b feat(templates): added url support for custom fields 2024-07-21 21:17:18 +02:00
2368883029 5bc0230a5a Fix: Can't insert image in a category other than summary 2024-07-21 00:54:00 -04:00
Shubham Mathur c9a2c27b2d fix(edit profile picture): fix the crash happening when clicking on circular border radius 2024-06-27 14:59:13 +05:30
Amruth Pillai 767573ce6d disable resume previews to lower load on server, fixes #1954 2024-06-13 11:40:17 +02:00
Amruth Pillai 713ce91683 bump version to 4.1.7 2024-06-10 08:04:56 +02:00
Amruth Pillai 8478a42cf0 Merge pull request #1943 from AmruthPillai/1900-bug-inputting-openai-api-key-throws-weird-error
#1900 - bug inputting openai api key throws weird error
2024-06-10 08:04:01 +02:00
Amruth Pillai 5e33f7e8f4 update dependencies, fix lint issues 2024-06-10 08:00:05 +02:00
Amruth Pillai 2292c25158 i18n: update translations 2024-06-10 07:51:09 +02:00
Amruth Pillai 13a7412498 Merge branch 'main' into 1900-bug-inputting-openai-api-key-throws-weird-error 2024-06-10 07:50:03 +02:00
Amruth Pillai a44846b632 feat: Add file validation for reactive-resume-json type in ImportDialog 2024-06-10 07:49:16 +02:00
Amruth Pillai 454aa20aa9 Merge pull request #1929 from HUAHUAI23/fix/client
fix(client): Fix issue with unhandled errors during resume import
2024-06-10 07:46:47 +02:00
Amruth Pillai e3b4105424 Merge pull request #1909 from DemaPy/patch-1
fix(client): fix Type Error in Picture Configuration Options
2024-06-10 07:46:35 +02:00
Amruth Pillai f0ede57786 Merge pull request #1941 from AmruthPillai/l10n
New Translations from Crowdin
2024-06-10 07:46:24 +02:00
Amruth Pillai ab10d026d6 Refactor OpenAI API key regex to accept project API keys, fixes #1900 2024-06-10 07:46:06 +02:00
Crowdin Bot a2dbd7ce2b New Crowdin translations by GitHub Action 2024-06-10 00:09:11 +00:00
Amruth Pillai eca51decc2 Merge pull request #1939 from abizek/fix/skip-crowdin-action-on-forks
Fix(actions): skip sync crowdin action on forks
2024-06-05 18:14:32 +02:00
abizek 06455da40a fix(actions): skip sync crowdin action on forks
Signed-off-by: abizek <abishekilango@protonmail.com>
2024-06-05 13:06:05 +05:30
Amruth Pillai 700b74a0b7 Merge pull request #1928 from AmruthPillai/l10n
New Translations from Crowdin
2024-06-04 09:58:42 +02:00
Amruth Pillai ae64515e93 Merge pull request #1937 from abizek/fix/double-issuer-name
fix(template): double issuer name in chikorita certificates
2024-06-04 09:57:57 +02:00
abizek 0992726d7e fix(template): chikorita certificate layout
Signed-off-by: abizek <abishekilango@protonmail.com>
2024-06-04 11:39:04 +05:30
abizek 38a442c40d fix(template): double issuer name in chikorita certificates
Signed-off-by: abizek <abishekilango@protonmail.com>
2024-06-04 10:55:41 +05:30
Crowdin Bot 14bcfb219a New Crowdin translations by GitHub Action 2024-06-04 00:08:28 +00:00
HUAHUAI23 d0fd1e3ff5 fix(client): Fix issue with unhandled errors during resume import 2024-05-30 12:39:25 +00:00
Amruth Pillai 3f29ca3add Merge pull request #1922 from AmruthPillai/1592-feature-the-possibility-to-disable-new-sign-ups
🚀 [Feature] Disable new signups through server controlled feature flags
2024-05-29 10:42:51 +02:00
Amruth Pillai bf498b5972 chore(i18n): update translation files 2024-05-29 10:39:53 +02:00
Amruth Pillai 2bc42493af chore(release): bump version to 4.1.6, update pnpm version 2024-05-29 10:31:41 +02:00
Amruth Pillai d18ef2e1a5 feat(feature-flags): fixes #1592, introduces new flags DISABLE_SIGNUPS and DISABLE_EMAIL_AUTH, renamed STORAGE_SKIP_BUCKET_CHECK 2024-05-29 10:30:38 +02:00
Dema 2409a5786a Update options.tsx
Fix for issue log:
https://github.com/AmruthPillai/Reactive-Resume/issues/1902
2024-05-22 12:58:27 +02:00
Amruth Pillai 1191bbca67 Merge pull request #1906 from AmruthPillai/l10n
New Translations from Crowdin
2024-05-22 08:09:51 +02:00
Crowdin Bot f5804ab458 New Crowdin translations by GitHub Action 2024-05-22 00:08:19 +00:00
Amruth Pillai 748b509b1d update dependencies, fix import sample resume issue, update pnpm version 2024-05-21 08:10:12 +02:00
Amruth Pillai 696f6f71b5 bump version to 4.1.4 2024-05-20 17:13:50 +02:00
Amruth Pillai 09ebcdf40f remove sentry integration, fix linting issues 2024-05-20 17:13:17 +02:00
Amruth Pillai 0ee0b6b2e9 Merge pull request #1823 from jzaehrin/feat/separate-links
feat: separate links option to reduce section item height
2024-05-20 17:02:56 +02:00
Amruth Pillai fe550ccc36 update pnpm version 2024-05-20 17:02:48 +02:00
Amruth Pillai 6dcbe78730 Merge branch 'main' into feat/separate-links 2024-05-20 17:01:16 +02:00
Amruth Pillai 73b29f78ab Fix linting, update translations, remove vite-plugin-chunk-split 2024-05-20 16:59:38 +02:00
Amruth Pillai 0124db049b Merge pull request #1780 from dhavalsavalia/custom-icons-in-header
fix(CustomField): add a way to have icon in custom fields
2024-05-20 16:44:37 +02:00
Amruth Pillai a033c3eff6 Merge branch 'main' into custom-icons-in-header 2024-05-20 16:44:11 +02:00
Amruth Pillai 97286739f2 Merge pull request #1842 from abizek/fix/custom-text-and-background-color
Fix(artboard): Text and background color
2024-05-20 16:42:36 +02:00
Amruth Pillai a74a8ed044 Merge pull request #1844 from abizek/fix/right-sidebar-page-section-scroll
Fix(builder): right sidebar scroll to page section
2024-05-20 16:41:25 +02:00
Amruth Pillai 376f72a22f Merge pull request #1845 from abizek/fix/nosepass-template-europass-logo
Fix(templates): serve europass logo from public assets
2024-05-20 16:40:55 +02:00
Amruth Pillai 77dee57324 Merge pull request #1885 from nickconway/fix/pdf-name
Fix: generate PDF name from title instead of id
2024-05-20 16:37:43 +02:00
Amruth Pillai 5d0c92e90d Merge pull request #1891 from AashishSinghal/main
Bug Fix #1847 - Fix Sidebar Height in Chikorita template
2024-05-20 16:36:57 +02:00
Amruth Pillai 868e6de7d9 Merge pull request #1843 from abizek/fix/tfa-length-error-message
Fix(tfa): error message for tfa code length
2024-05-20 16:36:23 +02:00
abizek e24f8850d2 merge branch main
Signed-off-by: abizek <abishekilango@protonmail.com>
2024-05-20 13:12:53 +05:30
abizek f30a76229b Revert "disable crowdin sync cron"
This reverts commit b1af5d9339.
2024-05-20 13:08:50 +05:30
abizek 02c6318f60 Merge main into fix/custom-text-and-background-color 2024-05-20 13:06:02 +05:30
Abishek Ilango 2f87dde48d Merge branch 'main' into fix/nosepass-template-europass-logo 2024-05-20 13:03:31 +05:30
Abishek Ilango d570b21635 Merge branch 'main' into fix/right-sidebar-page-section-scroll 2024-05-20 13:03:27 +05:30
Abishek Ilango b1af5d9339 disable crowdin sync cron 2024-05-20 12:47:21 +05:30
Amruth Pillai a598a7a7f0 Merge pull request #1899 from ToshY/issue/1897
fix(docker): add environment variable for puppeteer to launch with `ignoreHTTPSerrors` flag
2024-05-15 20:24:29 +02:00
ToshY f60fc63ee3 fix(docker): add environment variable for puppeteer to launch with ignore http errors flag 2024-05-15 19:52:22 +02:00
AashishSinghal 94eb549d25 Bug Fix #1847 - Fix Sidebar is not stretching to the full height in Chikorita template 2024-05-12 08:25:10 +05:30
Amruth Pillai 7a8b5d09c6 add ReleaseBot Discord Webhook to Publish Docker Image action 2024-05-10 11:46:25 +02:00
Amruth Pillai c15d9f7645 revert structuredClone as it is not supported on proxy objects 2024-05-10 11:27:13 +02:00
Amruth Pillai a102f62e28 replace JSON.parse(JSON.stringify({})) with structuredClone({}) 2024-05-10 11:00:15 +02:00
Amruth Pillai c1a58118c2 - add AuthRefreshProvider to refresh auth tokens every 5 minutes
- pull the latest crowdin language artifacts
2024-05-10 10:52:35 +02:00
Amruth Pillai b0d26e3230 Merge pull request #1889 from AmruthPillai/l10n
New Translations from Crowdin
2024-05-09 08:06:29 +02:00
Crowdin Bot 7e354b74bd New Crowdin translations by GitHub Action 2024-05-09 00:08:50 +00:00
Amruth Pillai e20bcb8c14 Merge pull request #1886 from AmruthPillai/l10n
New Translations from Crowdin
2024-05-08 09:21:26 +02:00
Crowdin Bot 506058aacb New Crowdin translations by GitHub Action 2024-05-08 00:07:50 +00:00
Nick Conway a7a0e4e652 Fix: generate PDF name from title instead of id 2024-05-07 13:13:10 -04:00
Amruth Pillai 7b394f1437 pin versions of pnpm and node 2024-05-07 12:33:16 +02:00
Amruth Pillai e2e2551db4 fix lint-test-build workflow 2024-05-07 12:31:01 +02:00
Amruth Pillai 52e062c0b5 fix eslint issues 2024-05-07 12:02:39 +02:00
Amruth Pillai e3785030e1 add code chunking to vite.config.ts 2024-05-07 11:45:33 +02:00
Amruth Pillai e21430a421 Merge pull request #1883 from AmruthPillai/l10n
New Translations from Crowdin
2024-05-07 09:25:38 +02:00
Crowdin Bot f66704af88 New Crowdin translations by GitHub Action 2024-05-07 00:08:07 +00:00
Amruth Pillai 7a65363296 add test to github action 2024-05-05 17:06:08 +02:00
Amruth Pillai 8180e8c7b8 add SENTRY_AUTH_TOKEN 2024-05-05 16:50:01 +02:00
Amruth Pillai 13b2a5be94 add max-parallel:1 to github action 2024-05-05 16:09:29 +02:00
Amruth Pillai 4dd5367572 attempt to fix lint issues 2024-05-05 15:02:16 +02:00
Amruth Pillai e87b05a93a release: v4.1.0 2024-05-05 14:55:06 +02:00
Amruth Pillai 68252c35fc update postgres version in templates 2024-05-03 23:03:23 +02:00
Amruth Pillai ac9b280bd5 revert runs-on 2024-05-03 21:48:28 +02:00
Amruth Pillai 32b8407b1a experimenting with self-hosted action runners 2024-05-03 21:18:25 +02:00
Amruth Pillai 989e8dee5b update dependencies 2024-05-03 20:55:13 +02:00
Amruth Pillai 5ed561812f add privacy policy, bump up version to 4.0.13 2024-05-03 20:53:58 +02:00
Amruth Pillai cba2eda5d0 fix artifact name 2024-05-03 16:43:20 +02:00
Amruth Pillai 1c7b44b604 migrate actions/upload-artifact to v4 2024-05-03 15:29:59 +02:00
Amruth Pillai 5d61e865a8 changes to Dockerfile 2024-05-03 15:12:38 +02:00
Amruth Pillai f32a85cec9 add emptyOutDir: true to vite.config.ts 2024-05-03 15:07:07 +02:00
Amruth Pillai c99ce90cf8 add ARG NX_CLOUD_ACCESS_TOKEN to Dockerfile 2024-05-03 14:52:59 +02:00
Amruth Pillai 6e2b960bdb fix dependencies installation location 2024-05-03 14:31:13 +02:00
Amruth Pillai 862c812ee1 add files to .dockerignore 2024-05-03 14:22:28 +02:00
Amruth Pillai 6424b15b76 update versions of workflows 2024-05-03 14:08:49 +02:00
Amruth Pillai 5e32673358 modify action workflows 2024-05-03 14:04:20 +02:00
Amruth Pillai 470f187c0b fix Dockerfile 2024-05-03 14:02:08 +02:00
Amruth Pillai 8b966946ea remove unnecessary pnpm-lock.yaml files 2024-05-03 13:53:39 +02:00
Amruth Pillai 8579a4c98d Merge pull request #1871 from AmruthPillai/l10n
New Translations from Crowdin
2024-05-03 13:31:56 +02:00
Amruth Pillai 8deff757a9 fix tiptap issues, update dependencies, fix typescript issues with minio client 2024-05-03 12:05:54 +02:00
Crowdin Bot d227cf64aa New Crowdin translations by GitHub Action 2024-05-03 00:08:45 +00:00
Amruth Pillai 458af1d840 Merge pull request #1870 from AmruthPillai/l10n
New Translations from Crowdin
2024-04-22 19:42:30 +02:00
Crowdin Bot 0024aec60a New Crowdin translations by GitHub Action 2024-04-22 00:08:42 +00:00
Amruth Pillai ec86536ace Merge pull request #1864 from AmruthPillai/l10n
New Translations from Crowdin
2024-04-18 06:20:17 +02:00
Crowdin Bot f7e2bfb078 New Crowdin translations by GitHub Action 2024-04-18 00:08:40 +00:00
Amruth Pillai 168be7dfb8 Merge pull request #1861 from AmruthPillai/l10n
New Translations from Crowdin
2024-04-12 09:12:00 +02:00
Crowdin Bot 832d0002e9 New Crowdin translations by GitHub Action 2024-04-12 00:08:14 +00:00
Amruth Pillai 1f9e3aa9d1 update dependencies, update translations 2024-04-10 15:43:56 +02:00
Amruth Pillai dd97c6d71f Merge pull request #1856 from WangZhiYao/main
feat(server): Support for SMTP-over-TLS
2024-04-09 10:30:17 +02:00
339 1f5dce2233 feat(server): Support for SMTP-over-TLS 2024-04-09 14:42:47 +08:00
Amruth Pillai fe77b14807 Merge pull request #1855 from AmruthPillai/l10n
New Translations from Crowdin
2024-04-08 11:10:22 +02:00
Crowdin Bot a36c49fa77 New Crowdin translations by GitHub Action 2024-04-08 00:08:19 +00:00
Abishek Ilango 589e782d71 Merge branch 'main' into fix/tfa-length-error-message 2024-04-06 11:16:43 +05:30
Abishek Ilango 31f396c01a Merge branch 'main' into fix/nosepass-template-europass-logo 2024-04-06 11:16:28 +05:30
Abishek Ilango 6d20eda423 Merge branch 'main' into fix/right-sidebar-page-section-scroll 2024-04-06 11:16:15 +05:30
Abishek Ilango 53b5bdc0b5 Merge branch 'main' into fix/custom-text-and-background-color 2024-04-06 11:16:02 +05:30
Amruth Pillai 709bf0a526 Merge pull request #1851 from AmruthPillai/l10n
New Translations from Crowdin
2024-04-05 08:49:19 +02:00
Crowdin Bot 7189c7f203 New Crowdin translations by GitHub Action 2024-04-05 00:08:37 +00:00
Amruth Pillai cdef456aac update dependencies 2024-04-03 09:58:08 +02:00
abizek 8c879841d7 fix(tfa): lazily translate error message 2024-04-02 17:05:29 +05:30
abizek 937e6b053d fix(templates): serve europass logo from public assets
fixes #1825
2024-04-02 14:05:00 +05:30
abizek ac9109c2b6 fix(builder): right sidebar scroll to page section 2024-04-02 12:28:17 +05:30
abizek fcc68750cf fix(tfa): error message for tfa code length 2024-04-02 12:22:30 +05:30
abizek afe52da92d fix(artboard): apply custom text and background color 2024-04-01 19:31:28 +05:30
Amruth Pillai a578bd1054 Merge pull request #1836 from AmruthPillai/l10n
New Translations from Crowdin
2024-03-31 10:22:04 +02:00
Crowdin Bot 116c038861 New Crowdin translations by GitHub Action 2024-03-31 00:09:18 +00:00
Amruth Pillai 6e6914fe6b Merge pull request #1830 from AmruthPillai/l10n
New Translations from Crowdin
2024-03-27 08:15:01 +01:00
Crowdin Bot 2bf04f2616 New Crowdin translations by GitHub Action 2024-03-27 00:08:21 +00:00
Jonathan Zaehringer 6f2e75f22b feat: separate links option at section level to reduce section item height 2024-03-25 17:27:33 +01:00
Amruth Pillai f6c2ae7504 remove banner on mobile/tablet devices 2024-03-25 15:08:39 +01:00
Amruth Pillai 890875ad9d - upgrade to browserless v2
- add missing languages
- add donation banner
- update dependencies
- bump version to 4.0.9
2024-03-22 12:11:15 +01:00
Amruth Pillai 11953af700 Merge pull request #1816 from AmruthPillai/dependabot/npm_and_yarn/follow-redirects-1.15.6
Bump follow-redirects from 1.15.5 to 1.15.6
2024-03-19 09:17:57 +01:00
Amruth Pillai 3c774102cf Merge pull request #1809 from Rash-Hit/debouncing-in-profile-icon
debounce in profile icon
2024-03-19 09:17:28 +01:00
Amruth Pillai fbf92160a3 Merge pull request #1815 from AmruthPillai/l10n
New Translations from Crowdin
2024-03-19 09:16:33 +01:00
Crowdin Bot a798845865 New Crowdin translations by GitHub Action 2024-03-19 00:08:05 +00:00
dependabot[bot] 7db57e04c0 Bump follow-redirects from 1.15.5 to 1.15.6
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.5 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.5...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-17 01:21:40 +00:00
Amruth Pillai b23efa773f Merge pull request #1813 from AmruthPillai/l10n
New Translations from Crowdin
2024-03-14 14:04:42 +01:00
Crowdin Bot 3b41b32f09 New Crowdin translations by GitHub Action 2024-03-14 00:07:49 +00:00
Amruth Pillai 5513b909e7 Merge pull request #1810 from AmruthPillai/l10n
New Translations from Crowdin
2024-03-12 06:46:38 +01:00
Crowdin Bot c1a50d4125 New Crowdin translations by GitHub Action 2024-03-12 00:07:52 +00:00
Anubhav Jain 7babde2d62 Merge branch 'AmruthPillai:main' into debouncing-in-profile-icon 2024-03-11 16:58:04 +05:30
Anubhav Jain 08a6415ba8 -- code refactored -- 2024-03-11 11:25:45 +00:00
Anubhav Jain 1ee9478200 debounce in profile icon 2024-03-11 10:28:50 +00:00
Amruth Pillai bbe8fb6655 Merge pull request #1808 from AmruthPillai/1805-bugfont-subset-and-font-variants-not-working
Add CommandList to Combobox component
2024-03-11 09:57:07 +01:00
Amruth Pillai 6405102cab add CommandList to Combobox component 2024-03-11 09:54:10 +01:00
Amruth Pillai 32445a5cd7 Merge pull request #1804 from AmruthPillai/l10n
New Translations from Crowdin
2024-03-11 09:44:55 +01:00
Crowdin Bot 5494d93e1d New Crowdin translations by GitHub Action 2024-03-11 00:08:17 +00:00
Amruth Pillai ad9647a3f4 revert @nestjs-modules/mailer to 1.10.3 2024-03-10 11:19:51 +01:00
Amruth Pillai 9ee7e3195b bump up version to 4.0.7 2024-03-10 11:14:23 +01:00
Amruth Pillai 53dfd4cb09 fix language selector 2024-03-10 11:12:15 +01:00
Amruth Pillai 7ceb0f6e39 add symbolic links to prettierignore 2024-03-10 10:50:45 +01:00
Amruth Pillai 7496461618 add symbolic links to compose files 2024-03-10 10:36:43 +01:00
Amruth Pillai b47b7824ff - bump up version to 4.0.6
- update dependencies
- remove test phase from CI script
- lint and format all files
2024-03-10 10:35:23 +01:00
Amruth Pillai ec77d13ebd add safety check for username in github strategy 2024-03-10 10:27:30 +01:00
Amruth Pillai c8f7989c1f refactor avoidTooShort function 2024-03-10 10:25:47 +01:00
Amruth Pillai ec612f0902 Merge pull request #1799 from mradigen/main
fix(github_auth): fix GithubStrategy not searching for user using username
2024-03-10 10:25:28 +01:00
Amruth Pillai 10b2ca8bf2 Merge pull request #1790 from gzsombor/import-fixes
Better error handling and more lenient on the imported values
2024-03-10 10:23:58 +01:00
Amruth Pillai 995b1e627b Merge pull request #1789 from skyworkz/bugfix/compose-setup-pdf-download
fix issue where PDF downloads fail on compose setup
2024-03-10 10:22:50 +01:00
Amruth Pillai f1d4ebb504 Merge pull request #1788 from abizek/main
Fix popover stacking in right sidebar theme section
2024-03-10 10:21:40 +01:00
Amruth Pillai 6c97c880b3 Merge pull request #1785 from tsp36/feat/chinese_name
modify the name field to a minimum of 2 characters
2024-03-10 10:21:07 +01:00
Amruth Pillai 8fc3c25714 Update libs/dto/src/user/user.ts 2024-03-10 10:21:01 +01:00
Amruth Pillai 19c4d31710 Merge pull request #1771 from iammursal/main
fix(import): Changing 'Filetype' clears the 'File' upload field properly
2024-03-10 10:15:52 +01:00
Amruth Pillai b17919e909 Merge pull request #1768 from bhumit070/main
fix(gengar template): link of personal project was overflowing outside.
2024-03-10 10:14:51 +01:00
Amruth Pillai 4c1c17c693 Merge pull request #1751 from Samrat-Saha-Sammy/main
fix(#1750): fixed downloaded PDF have issue(s) with Typography options
2024-03-10 10:13:09 +01:00
Amruth Pillai df99470df8 modifications to rich-input link component 2024-03-10 10:12:09 +01:00
Amruth Pillai a92528cdb7 Merge pull request #1729 from CorreyL/feature/ctrl-k-for-hyperlink
[Feature] Keyboard Shortcut for Hyperlink
2024-03-10 10:07:33 +01:00
Amruth Pillai 219e6999df Merge branch 'main' into feature/ctrl-k-for-hyperlink 2024-03-10 10:07:26 +01:00
Amruth Pillai 95ee77f65c Merge pull request #1727 from CorreyL/main
[Bugfix] Instantiate Link with inclusive set to false
2024-03-10 10:04:27 +01:00
Amruth Pillai eac26215a3 update messages.po translations from lingui 2024-03-10 10:01:09 +01:00
Amruth Pillai 783af5070d remove caching from resumes 2024-03-10 10:00:51 +01:00
Amruth Pillai 359c7f1c80 update dependencies 2024-03-10 10:00:33 +01:00
Zsombor Gegesy 71d3cea100 Fix build 2024-03-09 12:33:49 +01:00
Zsombor Gegesy befc5a67fc Format code, better function name 2024-03-09 11:50:56 +01:00
Zsombor Gegesy 5a2c222d61 Better error handling and more lenient on the imported values 2024-03-09 11:50:56 +01:00
Abishek Ilango 6a3c75c15c Merge branch 'main' into main 2024-03-08 18:01:28 +05:30
mradigen b6162d7bb0 fix(github_auth): fix GithubStrategy not searching for user using username 2024-03-06 15:40:37 +05:30
Amruth Pillai 550e15228e Merge pull request #1795 from busches/patch-1
Update minimum node to 18.17.0
2024-03-01 10:58:37 +01:00
Amruth Pillai 518f5b1fb8 Merge pull request #1796 from AmruthPillai/l10n
New Translations from Crowdin
2024-03-01 10:58:19 +01:00
Crowdin Bot 1e56f940d9 New Crowdin translations by GitHub Action 2024-03-01 00:09:05 +00:00
Scott Busche e83e9c61b5 Update minimum node to 18.17.0
The dependency on sharp will fail with anything between 18.0-<18.17.0 per https://sharp.pixelplumbing.com/install#prerequisites
2024-02-28 20:09:29 -06:00
abizek 269f4c8b4d fix(builder): fix popover stacking in right sidebar theme section
Fixes #1642
2024-02-28 00:16:18 +05:30
Amruth Pillai 24f0af890a Merge pull request #1781 from AmruthPillai/l10n
New Translations from Crowdin
2024-02-24 07:39:43 +01:00
Crowdin Bot 5ccd98bd0a New Crowdin translations by GitHub Action 2024-02-24 00:07:22 +00:00
Benny Cornelissen 21fe2e195c fix issue where PDF downloads fail on compose setup 2024-02-23 10:54:36 +01:00
tsp36 33168aa535 modify the name field to a minimum of 2 characters 2024-02-21 09:40:57 +08:00
Dhaval Savalia 2dce78200b feat(Profiles): hide network name when icon is present 2024-02-18 13:29:33 -05:00
Dhaval Savalia b92b4c9936 fix(CustomField): add a way to have icon in custom fields 2024-02-18 12:50:33 -05:00
Amruth Pillai c806dc890a Merge pull request #1773 from AmruthPillai/l10n
New Translations from Crowdin
2024-02-18 05:24:31 +01:00
Amruth Pillai 816023eea6 Merge pull request #1747 from theschles/main
fix(templates): Vertical-align-top instead of vertical-align-middle entry header data
2024-02-18 05:24:08 +01:00
Crowdin Bot 129ac7da38 New Crowdin translations by GitHub Action 2024-02-18 00:08:31 +00:00
theschles 1b80f751a3 Merge branch 'main' into main 2024-02-15 15:32:20 -08:00
Murshal Akhtar Ansari f30d299949 fix(import): Changing 'Filetype' clears the 'File' upload field properly 2024-02-14 21:14:02 +03:00
Amruth Pillai 5de1bafdc6 Merge pull request #1762 from AmruthPillai/l10n
New Translations from Crowdin
2024-02-14 10:03:28 +01:00
Crowdin Bot 2a8abd3a0b New Crowdin translations by GitHub Action 2024-02-14 00:07:50 +00:00
bhumit070 fdfcd37061 fix(gengar template): link of personal project was overflowing outsite of sidebar 2024-02-13 08:16:58 +05:30
Samrat Saha 5d146ca86e fix(#1750): fixed downloaded PDF have issue(s) with Typography options
Typography option toggle class were added in DOM #root but when server is printing the resume, a child section of the DOM #root is cloned and inserted for pdf processing which miss out the Typography option toggle class in the parent DOM, resulting in bug, Thus, the same class were added to each cloned DOM node.
2024-02-02 00:52:50 +05:30
Amruth Pillai e9ec397663 chore(i18n): sync sources from crowdin translations 2024-02-01 10:13:20 +01:00
Amruth Pillai c4f552f44a Merge pull request #1749 from AmruthPillai/l10n
New Translations from Crowdin
2024-02-01 06:14:40 +01:00
Crowdin Bot 1f274d8ae9 New Crowdin translations by GitHub Action 2024-02-01 00:09:11 +00:00
theschles 846050f031 Update pikachu.tsx 2024-01-30 10:10:09 -08:00
theschles d23b35de5e Update glalie.tsx 2024-01-30 10:08:47 -08:00
theschles 2e4c660c97 Update gengar.tsx 2024-01-30 10:08:23 -08:00
theschles 4d5dc3869e Update ditto.tsx 2024-01-30 10:07:48 -08:00
theschles d4ca61d751 Update chikorita.tsx 2024-01-30 10:05:52 -08:00
theschles 5f1da943b8 Update rhyhorn.tsx 2024-01-30 10:05:16 -08:00
theschles 0803ad7e2d Update bronzor.tsx 2024-01-30 10:05:13 -08:00
theschles 1326895e6b fix: resume entry header items should be top-vertical-aligned, not middle-vertical-aligned 2024-01-30 10:01:20 -08:00
Amruth Pillai bc17157204 Merge pull request #1744 from AmruthPillai/l10n
New Translations from Crowdin
2024-01-28 09:25:29 +01:00
Crowdin Bot 880b3b5d37 New Crowdin translations by GitHub Action 2024-01-28 00:08:41 +00:00
Amruth Pillai d9d4085591 Merge pull request #1742 from AmruthPillai/l10n
New Translations from Crowdin
2024-01-26 08:13:43 +01:00
Crowdin Bot acedc6b116 New Crowdin translations by GitHub Action 2024-01-26 00:08:13 +00:00
Amruth Pillai 4eac1c0024 Merge pull request #1737 from AmruthPillai/l10n
New Translations from Crowdin
2024-01-25 09:35:08 +01:00
Crowdin Bot e86c390862 New Crowdin translations by GitHub Action 2024-01-25 00:08:41 +00:00
Amruth Pillai 7f877861d1 Merge pull request #1734 from AmruthPillai/l10n
New Translations from Crowdin
2024-01-23 05:19:17 +01:00
Crowdin Bot 6f97c06e3d New Crowdin translations by GitHub Action 2024-01-23 00:08:46 +00:00
Amruth Pillai af4a96822c Merge pull request #1730 from AmruthPillai/l10n
New Translations from Crowdin
2024-01-22 09:09:25 +01:00
Crowdin Bot f4bedc668d New Crowdin translations by GitHub Action 2024-01-22 00:08:53 +00:00
CorreyL 8e8b695cbc Add a keyboard shortcut for adding a hyperlink
Use the conventional Ctrl+K keyboard shortcut for adding a hyperlink
2024-01-21 10:38:12 -08:00
CorreyL 37a869fa24 Move core logic of setLink to outer scope
Will allow code changes in a subsequent commit to access the function
and utilize the same core logic
2024-01-21 10:37:15 -08:00
CorreyL 176cac4fbe Instantiate Link with inclusive set to false
Ensures consistent behavior with other common text editors, wherein
after applying a link to text, the following text does not also contain
the link
2024-01-21 09:12:05 -08:00
Amruth Pillai debfd9167f add eslint ignore 2024-01-21 11:12:35 +01:00
Amruth Pillai b68b5a7747 add digitalocean attribution 2024-01-21 11:00:24 +01:00
Amruth Pillai 1aaaaeca20 format files 2024-01-21 10:44:22 +01:00
Amruth Pillai 0590367b7f Merge pull request #1725 from AmruthPillai/l10n
New Translations from Crowdin
2024-01-21 09:09:11 +01:00
Crowdin Bot 209947266b New Crowdin translations by GitHub Action 2024-01-21 00:09:20 +00:00
Amruth Pillai 8bca7f5390 release: v4.0.4 2024-01-19 10:04:23 +01:00
Amruth Pillai 643348046f Merge pull request #1714 from calumapplepie/patch-1
Update profile.ts to add default values
2024-01-19 09:58:04 +01:00
Amruth Pillai 9843f39510 Merge pull request #1717 from SergejKasper/fix-linkedIn-import-fails
fix(import): LinkedIn Profile.csv parsing fixes
2024-01-19 09:56:39 +01:00
Amruth Pillai 14362b92c1 Merge pull request #1721 from CorreyL/bugfix/save-pending-keyword-input
[Bugfix] Save Pending Keyword Input
2024-01-19 09:55:42 +01:00
CorreyL ac322a9bd4 Add pending inputs as a new Keyword
Ensures that any Keywords that were intended to be added are not lost
2024-01-18 19:45:29 -08:00
CorreyL 390f274d06 Dialogs that accept keywords, track pending inputs
In every dialog component that allows the input of Keyword, instantiate
a pendingKeyword State to track if the BadgeIput element has a pending
input
2024-01-18 19:42:24 -08:00
CorreyL da23b06f71 Capture the current Keyword in the input in state
Keeps a parent State that tracks if a pending Keyword is in the input
field up-to-date
2024-01-18 19:39:53 -08:00
Amruth Pillai 469f1d5cdd Merge pull request #1716 from SergejKasper/fix-po-loader-issue
fix(vite): fix .po file loader issue
2024-01-15 14:14:45 +01:00
Sergej Kasper 45a936c05d fix(import): LinkedIn Profile.csv parsing fixes 2024-01-15 10:43:32 +01:00
Sergej Kasper 84cafba0c2 fix(vite): fix .po file loader issue 2024-01-15 09:23:22 +01:00
Amruth Pillai e1ec60af92 Merge pull request #1705 from RJohnPaul/patch-1
Update project.json with cleaning and formatting
2024-01-12 08:40:19 +01:00
Amruth Pillai 3cde03e9cb Merge pull request #1711 from AmruthPillai/dependabot/npm_and_yarn/follow-redirects-1.15.4
build(deps): bump follow-redirects from 1.15.3 to 1.15.4
2024-01-12 08:40:02 +01:00
Amruth Pillai b7c3b84ba2 Merge pull request #1713 from AmruthPillai/l10n
New Translations from Crowdin
2024-01-12 08:39:53 +01:00
Calum McConnell b08b86ca9d Update profile.ts to add default values
Add some default values to the LinkedIn data import in case of missing data.
Default values used in place of optional data in order to minimize changes to other parts of the codebase; since optionals require an extra step to handle.  Default values of whitespace might work better,
but I cannot test to be sure.

Closes: #1604
2024-01-11 21:50:37 -05:00
Crowdin Bot a37edc2caa New Crowdin translations by GitHub Action 2024-01-12 00:08:18 +00:00
Amruth Pillai f0c778b37a Merge pull request #1710 from AmruthPillai/l10n
New Translations from Crowdin
2024-01-11 07:18:32 +01:00
dependabot[bot] 675a92a17f build(deps): bump follow-redirects from 1.15.3 to 1.15.4
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-11 04:32:26 +00:00
Crowdin Bot 9197729387 New Crowdin translations by GitHub Action 2024-01-11 00:08:30 +00:00
Amruth Pillai 99cb7f512e Merge pull request #1706 from datamoc/main
Minor changes on logo (more regular, smooth lines, png and svg favicon)
2024-01-09 09:54:05 +01:00
datamoc 0f765af468 Minor changes on logo (more regular, smooth lines, png and svg favicon)
.vs/* added in .gitignore
2024-01-06 18:37:48 +01:00
John Paul f5ce9af3e0 Update project.json with cleaning and formatting 2024-01-06 19:23:24 +05:30
Amruth Pillai 8500c30f59 Merge pull request #1704 from AmruthPillai/l10n
New Translations from Crowdin
2024-01-06 08:50:26 +01:00
Crowdin Bot 2f4065b5a3 New Crowdin translations by GitHub Action 2024-01-06 00:08:29 +00:00
Amruth Pillai fb4ecee897 Merge pull request #1703 from AmruthPillai/l10n
New Translations from Crowdin
2024-01-05 22:31:19 +01:00
Crowdin Bot 735352c3d3 New Crowdin translations by GitHub Action 2024-01-05 00:08:26 +00:00
Amruth Pillai f89ad7cd1a Merge pull request #1700 from AmruthPillai/l10n
New Translations from Crowdin
2024-01-04 09:35:20 +01:00
Crowdin Bot bfadfb1a4f New Crowdin translations by GitHub Action 2024-01-04 00:08:36 +00:00
Amruth Pillai af306746d3 Merge pull request #1696 from eltociear/patch-1
Update CONTRIBUTING.md
2024-01-02 09:06:41 +01:00
Amruth Pillai d67513b3f4 Merge pull request #1678 from AmruthPillai/l10n
New Translations from Crowdin
2024-01-02 09:06:09 +01:00
Crowdin Bot 0bae5f9422 New Crowdin translations by GitHub Action 2024-01-02 00:08:21 +00:00
Ikko Eltociear Ashimine 791d8e58d9 Update CONTRIBUTING.md
decriptive -> descriptive
2023-12-28 01:07:07 +09:00
Amruth Pillai 1fca2abd93 Merge pull request #1675 from AmruthPillai/l10n
New Translations from Crowdin
2023-12-19 09:09:38 +05:30
Crowdin Bot de2b7eb1ff New Crowdin translations by GitHub Action 2023-12-19 00:08:30 +00:00
Amruth Pillai c8840551a1 Merge pull request #1673 from AmruthPillai/l10n
New Translations from Crowdin
2023-12-17 09:06:31 +05:30
Crowdin Bot 302f112b15 New Crowdin translations by GitHub Action 2023-12-17 00:09:08 +00:00
Amruth Pillai f2d8f99fb3 Merge pull request #1669 from juliandaz11/patch-2
Update change-tone.ts
2023-12-15 11:24:18 +05:30
Amruth Pillai 7ed2b7dc16 Merge pull request #1668 from juliandaz11/patch-1
Update improve-writing.ts
2023-12-15 11:24:06 +05:30
Amruth Pillai acbed2ed74 Merge pull request #1670 from juliandaz11/patch-3
Update fix-grammar.ts
2023-12-15 11:23:54 +05:30
juliandaz11 a095cb8255 Update fix-grammar.ts
returns in the language of the text
2023-12-14 23:16:53 +00:00
juliandaz11 5fb6082cce Update change-tone.ts
returns in the language of the text
2023-12-14 23:15:44 +00:00
juliandaz11 51408eb03c Update improve-writing.ts
returns in the language of the text
2023-12-14 23:12:59 +00:00
Amruth Pillai c51b69ade5 Merge pull request #1666 from AmruthPillai/l10n
New Translations from Crowdin
2023-12-14 08:05:40 +05:30
Crowdin Bot db413378eb New Crowdin translations by GitHub Action 2023-12-14 00:08:32 +00:00
Amruth Pillai f82b163c7a Merge pull request #1662 from AmruthPillai/l10n
New Translations from Crowdin
2023-12-12 14:37:29 +05:30
Crowdin Bot 1a70a847f7 New Crowdin translations by GitHub Action 2023-12-12 00:08:28 +00:00
Amruth Pillai b8f3a62bc5 Merge pull request #1650 from AmruthPillai/l10n
New Translations from Crowdin
2023-12-11 13:21:02 +05:30
Crowdin Bot 4f23f5fe01 New Crowdin translations by GitHub Action 2023-12-11 00:08:39 +00:00
Amruth Pillai b5de1b764a Merge pull request #1649 from AmruthPillai/l10n
New Translations from Crowdin
2023-12-07 11:21:22 +05:30
Crowdin Bot 6444cd3175 New Crowdin translations by GitHub Action 2023-12-07 00:08:22 +00:00
Amruth Pillai 181aa4070c bump version to 4.0.3 2023-12-06 20:31:26 +05:30
Amruth Pillai 698c46e419 Merge pull request #1647 from AmruthPillai/dependabot/npm_and_yarn/vite-5.0.5
build(deps-dev): bump vite from 5.0.2 to 5.0.5
2023-12-06 19:50:13 +05:30
Amruth Pillai bbb878aaf4 Merge pull request #1648 from AmruthPillai/l10n
New Translations from Crowdin
2023-12-06 19:50:03 +05:30
Amruth Pillai f54890dc36 Merge pull request #1635 from MYSTYX7/main
Fixed Overlapping LocaleSwitch & ThemeSwitch Button #1634
2023-12-06 19:49:52 +05:30
Crowdin Bot 0737280c72 New Crowdin translations by GitHub Action 2023-12-06 00:08:33 +00:00
dependabot[bot] 9edfe257c4 build(deps-dev): bump vite from 5.0.2 to 5.0.5
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.2 to 5.0.5.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.0.5/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-05 23:47:14 +00:00
Amruth Pillai 28c1aaf3ff Merge pull request #1643 from AmruthPillai/l10n
New Translations from Crowdin
2023-12-05 06:59:08 +05:30
Crowdin Bot f8c662b064 New Crowdin translations by GitHub Action 2023-12-05 00:08:22 +00:00
MYSTYX7 aa8a8e378e fix(auth): Locale & Theme switch in authentication 2023-12-04 16:29:13 +05:30
MYSTYX7 11edfe5038 Merge branch 'main' of https://github.com/MYSTYX7/Reactive-Resume 2023-12-04 16:28:16 +05:30
Amruth Pillai 3ddb2c192d Merge pull request #1640 from otterDeveloper/bucket-skip-check
Add option to skip storage bucket check & creation in storage service
2023-12-04 11:36:43 +01:00
Amruth Pillai 5a149f9025 Merge branch 'main' into bucket-skip-check 2023-12-04 11:14:01 +01:00
Miguel Medina 9955a64c56 add warning if skipped bucket creation 2023-12-04 01:34:52 -06:00
Amruth Pillai 1de4b5c6a1 Merge pull request #1639 from AmruthPillai/l10n
New Translations from Crowdin
2023-12-04 05:38:29 +01:00
Miguel Medina b8c5aafa1d Add option to skip storage bucket check & creation in storage service 2023-12-03 19:59:01 -06:00
Crowdin Bot ac7e354ae2 New Crowdin translations by GitHub Action 2023-12-04 00:08:32 +00:00
Amruth Pillai 63697f97cb Merge pull request #1632 from AmruthPillai/l10n
New Translations from Crowdin
2023-12-03 05:14:32 +01:00
Amruth Pillai 45833b9067 Update CONTRIBUTING.md 2023-12-03 09:44:18 +05:30
Crowdin Bot 1562180784 New Crowdin translations by GitHub Action 2023-12-03 00:08:26 +00:00
MYSTYX7 7476b1f7f4 Merge branch 'main' of https://github.com/MYSTYX7/Reactive-Resume 2023-12-03 00:06:09 +05:30
MYSTYX7 d0ef127fe1 fix(auth): Fix Locale & ThemeSwitch button overlap 2023-12-03 00:05:59 +05:30
dependabot[bot] f7849e02ee build(deps-dev): bump @adobe/css-tools from 4.3.1 to 4.3.2
Bumps [@adobe/css-tools](https://github.com/adobe/css-tools) from 4.3.1 to 4.3.2.
- [Changelog](https://github.com/adobe/css-tools/blob/main/History.md)
- [Commits](https://github.com/adobe/css-tools/commits)

---
updated-dependencies:
- dependency-name: "@adobe/css-tools"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-03 00:05:23 +05:30
MYSTYX7 f7e92a516a fix(auth): Fix Locale & ThemeSwitch button overlap 2023-12-03 00:00:10 +05:30
MYSTYX7 d061829beb fix(auth): Fix Locale & ThemeSwitch button overlap 2023-12-02 23:59:55 +05:30
MYSTYX7 62f4640cb3 Merge branch 'main' of https://github.com/MYSTYX7/Reactive-Resume 2023-12-02 23:53:38 +05:30
MYSTYX7 fb41d3afc6 fix(auth): Fix Locale & ThemeSwitch button overlap 2023-12-02 23:53:34 +05:30
MYSTYX7 7124563225 fix(auth): Fix Locale & ThemeSwitch button overlap 2023-12-02 23:47:20 +05:30
MYSTYX7 a8ede8faed Minor change 2023-12-02 23:46:05 +05:30
MYSTYX7 b73aeb487b Fixed Locale & ThemeSwitch Button overlap issue 2023-12-02 23:36:08 +05:30
Amruth Pillai 11cd2d9b15 Merge pull request #1630 from AmruthPillai/dependabot/npm_and_yarn/adobe/css-tools-4.3.2
build(deps-dev): bump @adobe/css-tools from 4.3.1 to 4.3.2
2023-12-01 09:43:40 +01:00
dependabot[bot] 7aae3fc7d5 build(deps-dev): bump @adobe/css-tools from 4.3.1 to 4.3.2
Bumps [@adobe/css-tools](https://github.com/adobe/css-tools) from 4.3.1 to 4.3.2.
- [Changelog](https://github.com/adobe/css-tools/blob/main/History.md)
- [Commits](https://github.com/adobe/css-tools/commits)

---
updated-dependencies:
- dependency-name: "@adobe/css-tools"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-01 06:45:01 +00:00
Amruth Pillai 123e39270c Merge pull request #1629 from AmruthPillai/l10n
New Translations from Crowdin
2023-12-01 05:37:31 +01:00
Crowdin Bot fab7c44fa1 New Crowdin translations by GitHub Action 2023-12-01 00:09:26 +00:00
Amruth Pillai 86430bb143 Merge pull request #1622 from AmruthPillai/l10n
New Translations from Crowdin
2023-11-30 14:00:42 +01:00
Crowdin Bot c5b8d3b7b4 New Crowdin translations by GitHub Action 2023-11-30 00:08:21 +00:00
Amruth Pillai ceaabe2b66 Merge pull request #1601 from AmruthPillai/l10n
New Translations from Crowdin
2023-11-28 08:17:36 +01:00
Crowdin Bot 2f6da5266d New Crowdin translations by GitHub Action 2023-11-28 00:08:21 +00:00
Amruth Pillai 9378dc56e8 Merge pull request #1614 from justindhillon/fix-broken-links
Fixed broken link to LICENSE
2023-11-27 22:18:42 +01:00
Justin Dhillon 187b9bcf43 fixed broken link to LICENSE 2023-11-27 10:26:06 -08:00
Amruth Pillai a55c1950d0 Merge pull request #1597 from AmruthPillai/l10n
New Translations from Crowdin
2023-11-26 15:54:06 +01:00
Crowdin Bot 27bcd881a1 New Crowdin translations by GitHub Action 2023-11-26 14:53:35 +00:00
Amruth Pillai 922ef11883 ci: bump version to 4.0.2 2023-11-26 15:51:53 +01:00
Amruth Pillai 57aac2ab62 fix: label date -> date or date range, closes #1564 2023-11-26 15:50:02 +01:00
Amruth Pillai e26ca2adf1 feat: add env var VITE_DISABLE_SIGNUPS to allow admins to disable all signups, closes #1592 2023-11-26 15:48:17 +01:00
Amruth Pillai f090573147 - fix: add line-clamp-1 on button text, closes #1585
- fix: add drag handle icon to panels
2023-11-26 15:37:58 +01:00
Amruth Pillai 062748eb5c fix: openai chat completions api upgrade, closes #1586 2023-11-26 15:32:56 +01:00
Amruth Pillai 9298a7473e fix: wrap rich input toolbar, closes #1588 2023-11-26 15:26:39 +01:00
Amruth Pillai bf9cd2b248 fix: language selector in settings 2023-11-26 15:25:52 +01:00
Amruth Pillai 4f4919566d fix: language selector in settings 2023-11-26 15:25:47 +01:00
Amruth Pillai caa8a63d86 Merge pull request #1596 from AmruthPillai/1572-bug-popover-is-showing-behind-modal
fix: modify z-index for dialogs to fix popover being hidden
2023-11-26 15:16:55 +01:00
Amruth Pillai 73322acde1 fix: modify z-index for dialogs to fix popover being hidden 2023-11-26 14:55:37 +01:00
Amruth Pillai afb8b6389c Merge pull request #1595 from AmruthPillai/1589-feature-request-for-old-template
feat(templates): redesign and add back in the original templates from v3
2023-11-26 14:40:16 +01:00
Amruth Pillai 7db0c4d60a ci: run prettier formatting, fix ci workflow to not run twice 2023-11-26 14:37:12 +01:00
Amruth Pillai 446c5db68f feat(templates): redesign and add back in the original templates from v3 2023-11-26 14:33:40 +01:00
Amruth Pillai 765bf71220 Merge pull request #1593 from AmruthPillai/l10n
New Translations from Crowdin
2023-11-26 10:28:00 +01:00
Amruth Pillai d2342b78e6 Merge branch 'main' into l10n 2023-11-26 10:26:11 +01:00
Amruth Pillai 37e50684e3 fix(i18n): update sources 2023-11-26 10:22:42 +01:00
Crowdin Bot 2d31905d56 New Crowdin translations by GitHub Action 2023-11-26 00:08:57 +00:00
Amruth Pillai 9418bcf44f update dependencies 2023-11-25 02:48:48 +01:00
Amruth Pillai 68f8c22770 Merge pull request #1587 from AmruthPillai/l10n
New Translations from Crowdin
2023-11-25 01:32:43 +01:00
Crowdin Bot 3b7ab09e77 New Crowdin translations by GitHub Action 2023-11-25 00:08:04 +00:00
Amruth Pillai 6a7032fe90 Merge pull request #1580 from AmruthPillai/1563-bug-search-for-language-in-settings-does-not-work-as-expected
fix: implement fuzzy search with locale switcher and settings page
2023-11-24 03:56:07 +01:00
Amruth Pillai e9d888b07c fix: implement fuzzy search with locale switcher and settings page 2023-11-24 03:53:09 +01:00
Amruth Pillai 016e9aca77 add prisma generate to ci step 2023-11-24 02:00:28 +01:00
Amruth Pillai a4843cf7e6 ci: attempt to fix ci scripts by not importing prisma as type 2023-11-24 01:48:14 +01:00
Amruth Pillai 12b52af121 Merge pull request #1578 from AmruthPillai/l10n
New Translations from Crowdin
2023-11-24 01:25:23 +01:00
Crowdin Bot 2aa18aa262 New Crowdin translations by GitHub Action 2023-11-24 00:13:52 +00:00
Amruth Pillai 28e3fa5271 Merge pull request #1577 from AmruthPillai/l10n
New Translations from Crowdin
2023-11-24 01:11:45 +01:00
Amruth Pillai 26d5c83814 update translations 2023-11-24 01:11:02 +01:00
Crowdin Bot a695f13e3e New Crowdin translations by GitHub Action 2023-11-24 00:07:51 +00:00
Amruth Pillai 11ce4072d0 update github issue templates 2023-11-24 01:07:32 +01:00
Amruth Pillai 13dad73c64 update dependencies 2023-11-24 01:07:19 +01:00
Amruth Pillai c4d3dd89d7 feat: add product hunt banner to landing page 2023-11-24 01:07:02 +01:00
Amruth Pillai bdcae326f4 ci: pin postgres version to 15 2023-11-23 22:34:33 +01:00
Amruth Pillai 563abc074c Merge branch 'main' of github.com:AmruthPillai/Reactive-Resume 2023-11-23 22:34:23 +01:00
Amruth Pillai d36fc1b31d fix(parser): make reactive-resume v3 parser more lenient with validation 2023-11-23 22:34:21 +01:00
Amruth Pillai 1bf66e5e4a Merge pull request #1575 from MyLiveCV/BUG-1574
Bug Fixed - 1574 - JSON Schema Field issue
2023-11-23 22:31:31 +01:00
Amruth Pillai 4750410af0 Merge pull request #1571 from MyLiveCV/BUG-1569
BUG-1569 - Add Lock Option for List View
2023-11-23 22:31:01 +01:00
Anubhav Singh 0403308847 Bug Fixed - 1574 - JSON Schema Field issue 2023-11-24 01:40:02 +05:30
Anubhav Singh f2ca131aee BUG-1569 - Add Lock Option for List View 2023-11-24 00:31:11 +05:30
Amruth Pillai edc46da933 Merge pull request #1570 from MyLiveCV/BUG-1569
Bug Fix for 1569 - `On Dashboard in List View Lock Menu missing`
2023-11-23 19:59:21 +01:00
Amruth Pillai c53ecc5476 Merge pull request #1567 from MyLiveCV/BUG-1566
Bug Fix for 1566 - `Copy Link to resume Icon not working on Builder Toolbar for Public Resumes
2023-11-23 19:55:17 +01:00
Anubhav Singh 482c0b4982 BUG-1569 - Add Lock Option for List View 2023-11-24 00:21:09 +05:30
Anubhav Singh e00efd03a1 BUG-1566 - Add Copy functionality 2023-11-23 22:37:13 +05:30
Anubhav Singh 0984f11f39 Merge pull request #1 from AmruthPillai/main
main pull
2023-11-23 22:14:14 +05:30
Amruth Pillai 6e53a0f7a3 fix: auth layout if email is the only auth provider 2023-11-23 17:15:49 +01:00
Amruth Pillai 7131518f3f docs: uncomment postgres in development docker compose 2023-11-23 14:01:18 +01:00
Amruth Pillai 6ba1996084 fix: booleans not cooercing properly in env vars 2023-11-23 14:00:54 +01:00
Amruth Pillai 4411ee7d1d fix: add link to docs in hero section 2023-11-23 14:00:33 +01:00
Amruth Pillai cd1704740e docs: update .env.example with storage url suffix, closes #1562 2023-11-23 14:00:08 +01:00
Amruth Pillai 512f59f1e8 fix: update swarm.yml example 2023-11-23 11:55:02 +01:00
Amruth Pillai f6ad346f9b fix: add env var STORAGE_USE_SSL to toggle on/off SSL mode in MinIO Client, closes #1561 2023-11-23 10:13:08 +01:00
Amruth Pillai a661076686 fix: throw appropriate errors when connecting to storage bucket, closes #1561 2023-11-23 09:01:25 +01:00
Amruth Pillai d7abd74af3 - add workflow_dispatch to lint-test-build 2023-11-23 08:56:23 +01:00
Amruth Pillai 3f35672f72 Merge pull request #1559 from AmruthPillai/l10n
New Translations from Crowdin
2023-11-23 07:04:59 +01:00
Crowdin Bot 0555464586 New Crowdin translations by GitHub Action 2023-11-23 00:08:14 +00:00
Amruth Pillai 94f330e19b enable linux/arm64 builds 2023-11-23 00:15:39 +01:00
Amruth Pillai f716726e80 Update README.md 2023-11-23 00:11:05 +01:00
Amruth Pillai 8c6aaf9284 Update FUNDING.yml 2023-11-23 00:09:58 +01:00
Amruth Pillai aebfd92e09 - remove stale bot
- pin node version in dockerfile
2023-11-22 23:55:40 +01:00
Amruth Pillai fa7e2dad5f add concurrency to lint-test-build 2023-11-22 23:28:46 +01:00
Amruth Pillai 64d64958f6 add engines.node to package.json 2023-11-22 23:27:53 +01:00
Amruth Pillai c0869fdb3e add packageManager key to package.json 2023-11-22 23:26:56 +01:00
Amruth Pillai 04a9c988e6 implement ci action with nx cloud 2023-11-22 23:24:19 +01:00
Amruth Pillai 37c17ce8f3 fix ts error on parser 2023-11-22 23:04:44 +01:00
Amruth Pillai 6c7ca9472f Merge branch 'v4' of github.com:AmruthPillai/Reactive-Resume into v4 2023-11-22 22:57:37 +01:00
Amruth Pillai 4dfaafc929 bump version to 4.0.0 2023-11-22 22:57:36 +01:00
Amruth Pillai afdd3d7736 Merge pull request #1557 from AmruthPillai/l10n
New Translations from Crowdin
2023-11-22 22:55:33 +01:00
Amruth Pillai c19b759237 update github actions to modify triggers 2023-11-22 22:54:36 +01:00
Crowdin Bot 12505151c1 New Crowdin translations by GitHub Action 2023-11-22 21:51:35 +00:00
Amruth Pillai 4687091ebd fix an assorted set of issues, more info on linear 2023-11-22 22:50:21 +01:00
Amruth Pillai 4baecb22e9 fix: add logger for errors, return correct error when resume is locked 2023-11-22 22:25:54 +01:00
Amruth Pillai 27758c72e3 feat: add tooltip to url-input label icon, closes RX-14 2023-11-22 22:25:30 +01:00
Amruth Pillai 1580455b3f - update translations
- add mail_from env var
- update docs for swarm deployment
2023-11-22 22:05:54 +01:00
Amruth Pillai da2f4dba60 feat: implement back navigation on some of the auth flows, closes RX-13 2023-11-22 16:01:08 +01:00
Amruth Pillai bc5b4cb9ad Merge pull request #1556 from AmruthPillai/l10n
New Translations from Crowdin
2023-11-22 15:50:52 +01:00
Crowdin Bot 4c66f5c503 New Crowdin translations by GitHub Action 2023-11-22 14:48:56 +00:00
Amruth Pillai cf2c8e3fe4 Merge pull request #1555 from AmruthPillai/l10n
New Translations from Crowdin
2023-11-22 15:48:25 +01:00
Amruth Pillai 78c435681b fix: merge similar translation strings, closes RX-12 2023-11-22 15:47:27 +01:00
Crowdin Bot db83dabe86 New Crowdin translations by GitHub Action 2023-11-22 14:46:15 +00:00
Amruth Pillai 436efe0f59 fix: RX-11 merge similar strings 2023-11-22 15:44:49 +01:00
Amruth Pillai 69b99b9127 Set version of libraries to 0.0.1 2023-11-22 15:43:50 +01:00
Amruth Pillai df5a6e9151 Merge pull request #1554 from AmruthPillai/l10n
New Translations from Crowdin
2023-11-22 11:00:13 +01:00
Crowdin Bot bd04dc9b1e New Crowdin translations by GitHub Action 2023-11-22 08:09:42 +00:00
Amruth Pillai dfb3ef60dd - add docker-swarm deployment example
- update dependencies
2023-11-22 09:08:23 +01:00
Amruth Pillai 55e94ca792 Merge pull request #1553 from AmruthPillai/l10n
New Translations from Crowdin
2023-11-21 15:20:33 +01:00
Crowdin Bot 1b0e9f4b0c New Crowdin translations by GitHub Action 2023-11-21 14:11:50 +00:00
Amruth Pillai 23766ee007 Merge pull request #1552 from AmruthPillai/l10n
New Translations from Crowdin
2023-11-21 15:10:22 +01:00
Crowdin Bot 7258259cfd New Crowdin translations by GitHub Action 2023-11-21 13:41:38 +00:00
Amruth Pillai 8c621f0028 Merge pull request #1551 from AmruthPillai/l10n
New Translations from Crowdin
2023-11-21 14:40:19 +01:00
Crowdin Bot d109dcca87 New Crowdin translations by GitHub Action 2023-11-21 12:51:24 +00:00
Amruth Pillai 0846e04ea4 Merge pull request #1550 from AmruthPillai/l10n
New Translations from Crowdin
2023-11-21 13:49:56 +01:00
Crowdin Bot d9abcbadac New Crowdin translations by GitHub Action 2023-11-21 12:47:30 +00:00
Amruth Pillai 2a3ae68948 implement button to download PDF from a public resume page 2023-11-21 13:45:39 +01:00
Amruth Pillai 422865b3ad - update bug-report template
- update dependencies
2023-11-21 12:42:58 +01:00
Amruth Pillai 32a1a685cc add another "\n" to pull request template of sync crowdin action 2023-11-21 10:18:45 +01:00
Amruth Pillai cfd161f080 Merge pull request #1549 from AmruthPillai/l10n
New Translations from Crowdin
2023-11-21 09:50:11 +01:00
Crowdin Bot 0dcf229e19 New Crowdin translations by GitHub Action 2023-11-21 08:45:58 +00:00
Amruth Pillai 1825fc3283 - implement disable_email_auth env var
- add sync crowdin translations github action
2023-11-21 09:44:37 +01:00
Amruth Pillai 635f743e56 Merge pull request #1548 from otterDeveloper/v4-oauth-env-docs
[v4]docs: Add example of oauth callback url
2023-11-20 08:32:10 +01:00
Miguel Medina 16cb8c02ed docs: show example of oauth callback url 2023-11-19 15:24:24 -06:00
Amruth Pillai f9d965787d - fix pikachu template alignment
- update sample resume
2023-11-19 10:45:00 +01:00
Amruth Pillai 55d81f4771 revert lingui changes 2023-11-19 10:35:12 +01:00
Amruth Pillai 545dffa351 fix dockerfile 2023-11-19 10:22:19 +01:00
Amruth Pillai 7c507052fd replace lingui.config.ts with .linguirc 2023-11-19 10:15:14 +01:00
Amruth Pillai bb28de7571 fix sidebar padding on chikorita 2023-11-19 10:10:14 +01:00
Amruth Pillai 8b217dfcfa - upgrade react-resizable-panels to latest version
- update translations
- remove cypress
- add await to all return blocks
2023-11-19 09:52:55 +01:00
Amruth Pillai 9c4db2956b update README.md 2023-11-17 14:14:14 +01:00
Amruth Pillai 626c131f7d - Add templatesCount variable to FeaturesSection
component
2023-11-17 14:01:28 +01:00
Amruth Pillai fe9c19fc4e add "notranslate" tag, handle default locale fallback, fix certifications id in metadata 2023-11-17 13:28:58 +01:00
Amruth Pillai 33bcadd457 sort package.json 2023-11-17 09:02:38 +01:00
Amruth Pillai 9bf125e024 update i18n messages, remove pseudo locale 2023-11-17 08:58:27 +01:00
Amruth Pillai 78c06abbd4 update default layout 2023-11-17 08:44:53 +01:00
Amruth Pillai 34247f13b6 design nosepass template, add tests, add template previews 2023-11-17 08:31:12 +01:00
Amruth Pillai 0b4cb71320 export port 3000 on dockerfile 2023-11-15 16:44:27 +01:00
Amruth Pillai 8171e90a6c switch from sessionStorage to localStorage 2023-11-15 06:34:17 +01:00
Amruth Pillai f7a21df042 bump version 2023-11-14 21:55:47 +01:00
Amruth Pillai 72852f90e2 pin browserless/chrome version as latest it too unstable 2023-11-14 21:53:10 +01:00
Amruth Pillai 044ef8bdb5 use npm run start instead of node as CMD 2023-11-14 20:15:12 +01:00
Amruth Pillai 07421a5064 empty commit 2023-11-14 19:15:29 +01:00
Amruth Pillai 1a32f1bc26 switch from pnpm to npm 2023-11-14 19:11:17 +01:00
Amruth Pillai 02fdb8c85b switch from pnpm to npm 2023-11-14 19:07:56 +01:00
Amruth Pillai 8ae9215309 switch from pnpm to npm 2023-11-14 19:05:24 +01:00
Amruth Pillai 277bb2a70b fix(ci): modify prisma location 2023-11-14 16:37:23 +01:00
Amruth Pillai 457aed5f46 fix(printer): increase timeout to 15s 2023-11-14 16:06:16 +01:00
Amruth Pillai 36b1930c0d fix: remove crowdin_distribution_hash env 2023-11-14 14:00:27 +01:00
Amruth Pillai f24bbe7631 refactor: 🔊 crowdin cli test run, add/remove translations 2023-11-14 13:58:39 +01:00
Amruth Pillai 3fb8e06f15 fix(i18n): t macro usage 2023-11-14 13:27:35 +01:00
Amruth Pillai f49439f80c Merge pull request #1541 from AmruthPillai/l10n
New Crowdin updates
2023-11-14 13:25:14 +01:00
Amruth Pillai 3fbb473ecb New translations messages.po (Chinese Traditional) 2023-11-14 13:23:20 +01:00
Amruth Pillai f863c7d28d New translations messages.po (Chinese Simplified) 2023-11-14 13:23:19 +01:00
Amruth Pillai d7c5cf51e6 New translations messages.po (Ukrainian) 2023-11-14 13:23:17 +01:00
Amruth Pillai a11e1fe339 New translations messages.po (Turkish) 2023-11-14 13:23:16 +01:00
Amruth Pillai c9d469d973 New translations messages.po (Swedish) 2023-11-14 13:23:14 +01:00
Amruth Pillai 9f36b19e20 New translations messages.po (Serbian (Cyrillic)) 2023-11-14 13:23:13 +01:00
Amruth Pillai b6d77bae17 New translations messages.po (Russian) 2023-11-14 13:23:12 +01:00
Amruth Pillai ec4cf23c52 New translations messages.po (Portuguese) 2023-11-14 13:23:10 +01:00
Amruth Pillai 46c585f325 New translations messages.po (Polish) 2023-11-14 13:23:09 +01:00
Amruth Pillai cd63244289 New translations messages.po (Norwegian) 2023-11-14 13:23:08 +01:00
Amruth Pillai 204e7d0015 New translations messages.po (Dutch) 2023-11-14 13:23:07 +01:00
Amruth Pillai 6b136608f7 New translations messages.po (Lithuanian) 2023-11-14 13:23:06 +01:00
Amruth Pillai f73277b315 New translations messages.po (Korean) 2023-11-14 13:23:04 +01:00
Amruth Pillai a703baf054 New translations messages.po (Japanese) 2023-11-14 13:23:03 +01:00
Amruth Pillai 30479c20ee New translations messages.po (Italian) 2023-11-14 13:23:01 +01:00
Amruth Pillai cd2d0dcb8e New translations messages.po (Hungarian) 2023-11-14 13:23:00 +01:00
Amruth Pillai 60d3eff764 New translations messages.po (Hebrew) 2023-11-14 13:22:59 +01:00
Amruth Pillai a3e6409171 New translations messages.po (Finnish) 2023-11-14 13:22:58 +01:00
Amruth Pillai aa38217cb6 New translations messages.po (Greek) 2023-11-14 13:22:57 +01:00
Amruth Pillai b8f96a837a New translations messages.po (German) 2023-11-14 13:22:56 +01:00
Amruth Pillai bd33ae7798 New translations messages.po (Danish) 2023-11-14 13:22:55 +01:00
Amruth Pillai 1bd709f484 New translations messages.po (Czech) 2023-11-14 13:22:53 +01:00
Amruth Pillai 59378f162f New translations messages.po (Catalan) 2023-11-14 13:22:52 +01:00
Amruth Pillai cdf36cb49d New translations messages.po (Bulgarian) 2023-11-14 13:22:51 +01:00
Amruth Pillai 0c069b1e92 New translations messages.po (Arabic) 2023-11-14 13:22:50 +01:00
Amruth Pillai 774720bae0 New translations messages.po (Afrikaans) 2023-11-14 13:22:49 +01:00
Amruth Pillai d4f36eecf5 New translations messages.po (Spanish) 2023-11-14 13:22:47 +01:00
Amruth Pillai 0870bfaa02 New translations messages.po (French) 2023-11-14 13:22:46 +01:00
Amruth Pillai 350823e081 New translations messages.po (Romanian) 2023-11-14 13:22:45 +01:00
Amruth Pillai 2db52b7ef2 feat(i18n): translate error messages 2023-11-14 13:02:38 +01:00
Amruth Pillai 888b4db0bb fix(mobile): check for responsiveness, add scrollarea support 2023-11-14 11:43:28 +01:00
Amruth Pillai 6303071410 fix(i18n): load translations from fs 2023-11-14 09:14:21 +01:00
Amruth Pillai d1f54010c7 Update Crowdin configuration file 2023-11-14 09:13:08 +01:00
Amruth Pillai 49d0882d7c Merge pull request #1537 from AmruthPillai/l10n
New Crowdin updates
2023-11-14 07:53:49 +01:00
Amruth Pillai 5358951557 New translations messages.po (Zulu) 2023-11-14 07:51:50 +01:00
Amruth Pillai 0b6331c28d New translations messages.po (Nepali) 2023-11-14 07:51:49 +01:00
Amruth Pillai befc4f1cb0 New translations messages.po (Amharic) 2023-11-14 07:51:48 +01:00
Amruth Pillai b965839091 New translations messages.po (Odia) 2023-11-14 07:51:47 +01:00
Amruth Pillai 507621e3a8 New translations messages.po (Kannada) 2023-11-14 07:51:45 +01:00
Amruth Pillai 7e4c4f798a New translations messages.po (Malayalam) 2023-11-14 07:51:44 +01:00
Amruth Pillai 9b09b47b78 New translations messages.po (Telugu) 2023-11-14 07:51:43 +01:00
Amruth Pillai 3a64eefef7 New translations messages.po (Hindi) 2023-11-14 07:51:42 +01:00
Amruth Pillai 7b820503ae New translations messages.po (Thai) 2023-11-14 07:51:41 +01:00
Amruth Pillai 0c386671e1 New translations messages.po (Marathi) 2023-11-14 07:51:40 +01:00
Amruth Pillai 56f780e0eb New translations messages.po (Bengali) 2023-11-14 07:51:39 +01:00
Amruth Pillai 3b2698ca19 New translations messages.po (Tamil) 2023-11-14 07:51:38 +01:00
Amruth Pillai 8432fc5f10 New translations messages.po (Khmer) 2023-11-14 07:51:37 +01:00
Amruth Pillai 3996e49ae7 New translations messages.po (Persian) 2023-11-14 07:51:36 +01:00
Amruth Pillai a30504e6a5 New translations messages.po (Indonesian) 2023-11-14 07:51:35 +01:00
Amruth Pillai 77f704ac9b New translations messages.po (Portuguese, Brazilian) 2023-11-14 07:51:34 +01:00
Amruth Pillai 604a89051d New translations messages.po (Vietnamese) 2023-11-14 07:51:33 +01:00
Amruth Pillai e0b1d7ed67 New translations messages.po (English) 2023-11-14 07:51:32 +01:00
Amruth Pillai caa1b56fd3 New translations messages.po (Chinese Traditional) 2023-11-14 07:51:31 +01:00
Amruth Pillai 5955f5214d New translations messages.po (Chinese Simplified) 2023-11-14 07:51:30 +01:00
Amruth Pillai 2acb34fa90 New translations messages.po (Ukrainian) 2023-11-14 07:51:29 +01:00
Amruth Pillai 57a28e1462 New translations messages.po (Turkish) 2023-11-14 07:51:28 +01:00
Amruth Pillai dce63e926e New translations messages.po (Swedish) 2023-11-14 07:51:26 +01:00
Amruth Pillai 90eb4c770f New translations messages.po (Serbian (Cyrillic)) 2023-11-14 07:51:25 +01:00
Amruth Pillai 820d8720c0 New translations messages.po (Russian) 2023-11-14 07:51:24 +01:00
Amruth Pillai 3691f726e7 New translations messages.po (Portuguese) 2023-11-14 07:51:23 +01:00
Amruth Pillai 7339b7436c New translations messages.po (Polish) 2023-11-14 07:51:22 +01:00
Amruth Pillai 3364c9fbaa New translations messages.po (Norwegian) 2023-11-14 07:51:21 +01:00
Amruth Pillai db21cbc4ff New translations messages.po (Dutch) 2023-11-14 07:51:20 +01:00
Amruth Pillai e49c89cc39 New translations messages.po (Lithuanian) 2023-11-14 07:51:19 +01:00
Amruth Pillai ba25d92680 New translations messages.po (Korean) 2023-11-14 07:51:18 +01:00
Amruth Pillai ccb3513dbd New translations messages.po (Japanese) 2023-11-14 07:51:17 +01:00
Amruth Pillai e191c3ba67 New translations messages.po (Italian) 2023-11-14 07:51:16 +01:00
Amruth Pillai 267ae79724 New translations messages.po (Hungarian) 2023-11-14 07:51:15 +01:00
Amruth Pillai 08115fa521 New translations messages.po (Hebrew) 2023-11-14 07:51:14 +01:00
Amruth Pillai d4612d6e28 New translations messages.po (Finnish) 2023-11-14 07:51:13 +01:00
Amruth Pillai 0a95ce5a9c New translations messages.po (Greek) 2023-11-14 07:51:11 +01:00
Amruth Pillai 505f2f473e New translations messages.po (German) 2023-11-14 07:51:10 +01:00
Amruth Pillai a99198b9b7 New translations messages.po (Danish) 2023-11-14 07:51:09 +01:00
Amruth Pillai f2bc6f214d New translations messages.po (Czech) 2023-11-14 07:51:08 +01:00
Amruth Pillai 323cd07830 New translations messages.po (Catalan) 2023-11-14 07:51:07 +01:00
Amruth Pillai adc2adc6f4 New translations messages.po (Bulgarian) 2023-11-14 07:51:06 +01:00
Amruth Pillai 58f2291774 New translations messages.po (Arabic) 2023-11-14 07:51:05 +01:00
Amruth Pillai 1ed89053fe New translations messages.po (Afrikaans) 2023-11-14 07:51:04 +01:00
Amruth Pillai b4f72992ef New translations messages.po (Spanish) 2023-11-14 07:51:03 +01:00
Amruth Pillai f09acfbcaa New translations messages.po (French) 2023-11-14 07:51:01 +01:00
Amruth Pillai a6652b738f New translations messages.po (Romanian) 2023-11-14 07:51:00 +01:00
Amruth Pillai 041d04cd84 Merge pull request #1536 from AmruthPillai/l10n
New Crowdin updates
2023-11-14 07:49:02 +01:00
Amruth Pillai faf3ea47f9 Update .gitignore 2023-11-14 07:48:51 +01:00
Amruth Pillai 61f55bfb70 New translations messages.po (Zulu) 2023-11-14 07:30:25 +01:00
Amruth Pillai 73dc01e2df New translations messages.po (Nepali) 2023-11-14 07:30:24 +01:00
Amruth Pillai 800177e76e New translations messages.po (Amharic) 2023-11-14 07:30:23 +01:00
Amruth Pillai 6dbc0a3999 New translations messages.po (Odia) 2023-11-14 07:30:22 +01:00
Amruth Pillai 148de17b81 New translations messages.po (Kannada) 2023-11-14 07:30:20 +01:00
Amruth Pillai db3389d6a8 New translations messages.po (Malayalam) 2023-11-14 07:30:19 +01:00
Amruth Pillai 7390f38837 New translations messages.po (Telugu) 2023-11-14 07:30:18 +01:00
Amruth Pillai 133e5942cd New translations messages.po (Hindi) 2023-11-14 07:30:17 +01:00
Amruth Pillai 2de0b8792b New translations messages.po (Thai) 2023-11-14 07:30:16 +01:00
Amruth Pillai 36f1a2bdb3 New translations messages.po (Marathi) 2023-11-14 07:30:14 +01:00
Amruth Pillai 899d0b0d9c New translations messages.po (Bengali) 2023-11-14 07:30:14 +01:00
Amruth Pillai 8c4e21c503 New translations messages.po (Tamil) 2023-11-14 07:30:12 +01:00
Amruth Pillai a36f202d0a New translations messages.po (Khmer) 2023-11-14 07:30:11 +01:00
Amruth Pillai fe2f844e60 New translations messages.po (Persian) 2023-11-14 07:30:10 +01:00
Amruth Pillai cd80785f29 New translations messages.po (Indonesian) 2023-11-14 07:30:09 +01:00
Amruth Pillai bd065347cb New translations messages.po (Portuguese, Brazilian) 2023-11-14 07:30:08 +01:00
Amruth Pillai e0db70684a New translations messages.po (Vietnamese) 2023-11-14 07:30:07 +01:00
Amruth Pillai 0298e7836c New translations messages.po (English) 2023-11-14 07:30:06 +01:00
Amruth Pillai 1b9f1db8f8 New translations messages.po (Chinese Traditional) 2023-11-14 07:30:05 +01:00
Amruth Pillai 06a7a5192c New translations messages.po (Chinese Simplified) 2023-11-14 07:30:03 +01:00
Amruth Pillai 12958cec16 New translations messages.po (Ukrainian) 2023-11-14 07:30:02 +01:00
Amruth Pillai e7ca3aefec New translations messages.po (Turkish) 2023-11-14 07:30:01 +01:00
Amruth Pillai 45a626576f New translations messages.po (Swedish) 2023-11-14 07:30:00 +01:00
Amruth Pillai cf2caccedf New translations messages.po (Serbian (Cyrillic)) 2023-11-14 07:29:59 +01:00
Amruth Pillai 1d052a7856 New translations messages.po (Russian) 2023-11-14 07:29:58 +01:00
Amruth Pillai 47dce2c066 New translations messages.po (Portuguese) 2023-11-14 07:29:57 +01:00
Amruth Pillai 501f9b366e New translations messages.po (Polish) 2023-11-14 07:29:56 +01:00
Amruth Pillai 3dfa06308f New translations messages.po (Norwegian) 2023-11-14 07:29:55 +01:00
Amruth Pillai 0cb256d930 New translations messages.po (Dutch) 2023-11-14 07:29:54 +01:00
Amruth Pillai 5682f042af New translations messages.po (Lithuanian) 2023-11-14 07:29:53 +01:00
Amruth Pillai 0a6aa2e672 New translations messages.po (Korean) 2023-11-14 07:29:52 +01:00
Amruth Pillai af840e36f9 New translations messages.po (Japanese) 2023-11-14 07:29:51 +01:00
Amruth Pillai 7fc03550e1 New translations messages.po (Italian) 2023-11-14 07:29:50 +01:00
Amruth Pillai 4a24802637 New translations messages.po (Hungarian) 2023-11-14 07:29:49 +01:00
Amruth Pillai 0602ffc646 New translations messages.po (Hebrew) 2023-11-14 07:29:48 +01:00
Amruth Pillai 17ead86135 New translations messages.po (Finnish) 2023-11-14 07:29:47 +01:00
Amruth Pillai 8f24cb6769 New translations messages.po (Greek) 2023-11-14 07:29:46 +01:00
Amruth Pillai b451f6f8b8 New translations messages.po (German) 2023-11-14 07:29:45 +01:00
Amruth Pillai bdbb4534ee New translations messages.po (Danish) 2023-11-14 07:29:44 +01:00
Amruth Pillai 29d732bd68 New translations messages.po (Czech) 2023-11-14 07:29:43 +01:00
Amruth Pillai 620172709c New translations messages.po (Catalan) 2023-11-14 07:29:42 +01:00
Amruth Pillai 52a4b2831a New translations messages.po (Bulgarian) 2023-11-14 07:29:41 +01:00
Amruth Pillai 7f0f419a4b New translations messages.po (Arabic) 2023-11-14 07:29:40 +01:00
Amruth Pillai 7eae44c032 New translations messages.po (Afrikaans) 2023-11-14 07:29:39 +01:00
Amruth Pillai f50fe242e8 New translations messages.po (Spanish) 2023-11-14 07:29:38 +01:00
Amruth Pillai de5cf161bf New translations messages.po (French) 2023-11-14 07:29:37 +01:00
Amruth Pillai 8e5182cb26 New translations messages.po (Romanian) 2023-11-14 07:29:36 +01:00
Amruth Pillai 90ce0ad1bc Update Crowdin configuration file 2023-11-14 07:27:31 +01:00
Amruth Pillai 2f46acd707 Update crowdin.yml 2023-11-14 07:27:09 +01:00
Amruth Pillai a2f0a88e02 fix(ci): update actions 2023-11-13 18:47:21 +01:00
Amruth Pillai bdc8353196 fix(ci): update-translations 2023-11-13 17:08:05 +01:00
Amruth Pillai 6c9602e629 fix(ci): update-translations 2023-11-13 17:06:14 +01:00
Amruth Pillai d18b258761 feat(homepage): add new sections to homepage 2023-11-13 17:03:41 +01:00
Amruth Pillai 4b1e33db80 Update Crowdin configuration file 2023-11-10 13:19:01 +01:00
Amruth Pillai 48727be809 fix(i18n): delete local translations 2023-11-10 13:14:44 +01:00
Amruth Pillai d8c605d047 Merge pull request #1531 from AmruthPillai/l10n
New Translations
2023-11-10 11:23:43 +01:00
Crowdin Bot d5a0237975 New Crowdin translations by GitHub Action 2023-11-10 10:22:56 +00:00
Amruth Pillai f3ad994753 chore(i18n): update zu-ZA translations 2023-11-10 11:22:19 +01:00
Amruth Pillai 1057d390da feat(ci): add update translations ci workflow 2023-11-10 09:49:55 +01:00
Amruth Pillai 6f83937dbb feat(i18n): update crowdin configuration file 2023-11-10 09:36:20 +01:00
Amruth Pillai 6f6e53bc95 Update Crowdin configuration file 2023-11-10 09:35:14 +01:00
Amruth Pillai ca6724c4da fix(i18n): fix crowdin.yml 2023-11-10 09:32:35 +01:00
Amruth Pillai 0c7e551bf6 fix(github-action): update trigger for workflow 2023-11-10 09:31:29 +01:00
Amruth Pillai 69d3cd899c feat(i18n): change location of translation files 2023-11-10 09:29:52 +01:00
Amruth Pillai c52a0d4da7 Merge branch 'v4' of github.com:AmruthPillai/Reactive-Resume into v4 2023-11-10 09:29:38 +01:00
Amruth Pillai c10e5eb291 chore(deps): update dependencies 2023-11-10 09:29:35 +01:00
Amruth Pillai f41dbb3515 Update Crowdin configuration file 2023-11-10 09:21:03 +01:00
Amruth Pillai a07e4ee0b2 Merge branch 'v4' of github.com:AmruthPillai/Reactive-Resume into v4 2023-11-10 09:19:36 +01:00
Amruth Pillai 6573b7b8c0 fix(i18n): remove type in crowdin.yml 2023-11-10 09:19:18 +01:00
Amruth Pillai a72f6d2350 Update Crowdin configuration file 2023-11-10 09:16:23 +01:00
Amruth Pillai 210c215beb fix(i18n): remove type in crowdin.yml 2023-11-10 09:16:00 +01:00
Amruth Pillai 45c8b19081 Merge branch 'v4' of github.com:AmruthPillai/Reactive-Resume into v4 2023-11-10 09:15:25 +01:00
Amruth Pillai af1739892c fix(i18n): remove type in crowdin.yml 2023-11-10 09:15:23 +01:00
Amruth Pillai 5221b6f092 Update Crowdin configuration file 2023-11-10 09:13:37 +01:00
Amruth Pillai dec8bbc09e Merge pull request #1530 from AmruthPillai/feat(i18n)--implement-localization-using-linguijs
feat(i18n): implement localization using LinguiJS
2023-11-10 09:08:14 +01:00
Amruth Pillai 6ad4358d70 feat(i18n): implement localization using LinguiJS 2023-11-10 09:07:47 +01:00
Amruth Pillai 13d91411e3 chore(artboard): remove sample resume loaded on artboard for testing 2023-11-09 21:05:12 +01:00
Amruth Pillai 92bb9f96a0 feat(artboard): implement 8 new templates 2023-11-09 21:01:01 +01:00
Amruth Pillai 9acf7e8d22 Merge pull request #1524 from AmruthPillai/feat/v4/replace-templates-library-with-microfrontend-app
feat(templates): replace library with microfrontend app for templates
2023-11-07 16:43:26 +01:00
Amruth Pillai 1aa8aa6900 feat(templates): replace library with microfrontend app for templates 2023-11-07 16:37:16 +01:00
Amruth Pillai fca61543c5 perf: 🎨 remove fragment imports, optimize templates 2023-11-06 22:37:32 +01:00
Amruth Pillai 2d35057e57 Merge pull request #1519 from AmruthPillai/feat/v4/implement-resume-locking
feat(resume): ✨ implement resume locking feature
2023-11-06 13:57:44 +01:00
Amruth Pillai 015e284318 feat(resume): ✨ implement resume locking feature 2023-11-06 13:57:12 +01:00
Amruth Pillai 9a0402d525 Merge pull request #1518 from AmruthPillai/feat/v4/implement-create-sample-resume-action
feat(resume-dialog): ✨ implement create sample resume sub-action in create resume dialog
2023-11-06 12:11:36 +01:00
Amruth Pillai 685dc3aa6e feat(resume-dialog): ✨ implement create sample resume sub-action in create resume dialog 2023-11-06 12:11:07 +01:00
Amruth Pillai 9b833076db ci(registry): ✨ add quay.io to docker container registries 2023-11-06 10:17:29 +01:00
Amruth Pillai 37e94eb7f0 ci(dockerfile): 🚀 fix copy paths in dockerfile 2023-11-06 10:05:50 +01:00
Amruth Pillai d6620e0816 ci: 🐛 log error to console 2023-11-06 09:52:38 +01:00
Amruth Pillai a88a794f29 fix(server): 👷 pass errors down to controller 2023-11-06 09:41:06 +01:00
Amruth Pillai 4f5ccb9ab8 chore(server): update dependencies and add API tags to
controllers
2023-11-05 19:15:21 +01:00
Amruth Pillai e964965d59 docs(readme): 🎨 update formatting, change license header 2023-11-05 14:26:06 +01:00
Amruth Pillai fa248c47ad ci(dockerfile): copy prisma client correctly 2023-11-05 14:23:01 +01:00
Amruth Pillai d113f84c7e ci(compose): ⚡ update docker compose deployment examples 2023-11-05 13:38:56 +01:00
Amruth Pillai eef91cf905 ci(v4.0.0-alpha): 🚀 running the first ci worflow 2023-11-05 13:17:14 +01:00
Amruth Pillai 2f4fc71ecb ci(actions): 📝 implement close-stale-issues github actions workflow 2023-11-05 12:42:14 +01:00
Amruth Pillai 22933bd412 refactor(v4.0.0-alpha): beginning of a new era 2023-11-05 12:31:42 +01:00
Amruth Pillai 0ba6a444e2 project reset: clearing all files and folders 2023-08-31 13:22:03 +02:00
Amruth Pillai 0941ba398a Merge pull request #1441 from AmruthPillai/dependabot/gradle/app/org.jetbrains.kotlin.android-1.9.10
Bump org.jetbrains.kotlin.android from 1.9.0 to 1.9.10 in /app
2023-08-29 10:01:37 +02:00
Amruth Pillai e7b32890c6 Merge pull request #1440 from AmruthPillai/dependabot/github_actions/digitalocean/action-doctl-2.4.0
Bump digitalocean/action-doctl from 2.3.0 to 2.4.0
2023-08-29 10:01:29 +02:00
Amruth Pillai d4823c7b5e Merge pull request #1439 from AmruthPillai/dependabot/github_actions/actions/checkout-3.6.0
Bump actions/checkout from 3.5.3 to 3.6.0
2023-08-29 10:01:22 +02:00
Amruth Pillai 593b1909ab Merge pull request #1434 from AmruthPillai/dependabot/docker/server/playwright-v1.37.1-focal
Bump playwright from v1.36.2-focal to v1.37.1-focal in /server
2023-08-29 10:01:14 +02:00
Amruth Pillai 20cd4815f6 Merge pull request #1414 from Jubair70/fix_disable_click_outside_modal
fix: disable click outside all modals and check unsaved changes for education modal
2023-08-29 10:00:50 +02:00
dependabot[bot] e6b6b99e0f Bump org.jetbrains.kotlin.android from 1.9.0 to 1.9.10 in /app
Bumps [org.jetbrains.kotlin.android](https://github.com/JetBrains/kotlin) from 1.9.0 to 1.9.10.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/v1.9.10/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.0...v1.9.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-28 02:08:50 +00:00
dependabot[bot] a61e38b865 Bump digitalocean/action-doctl from 2.3.0 to 2.4.0
Bumps [digitalocean/action-doctl](https://github.com/digitalocean/action-doctl) from 2.3.0 to 2.4.0.
- [Release notes](https://github.com/digitalocean/action-doctl/releases)
- [Commits](https://github.com/digitalocean/action-doctl/compare/v2.3.0...v2.4.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-28 02:03:00 +00:00
dependabot[bot] e31ef6877b Bump actions/checkout from 3.5.3 to 3.6.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.3 to 3.6.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3.5.3...v3.6.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-28 02:02:57 +00:00
dependabot[bot] 67cc7ef258 Bump playwright from v1.36.2-focal to v1.37.1-focal in /server
Bumps playwright from v1.36.2-focal to v1.37.1-focal.

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-21 03:00:43 +00:00
Jubair70 ca6b10bdba fix formatting 2023-08-07 17:32:16 +06:00
Jubair Hossain cf7faa0e28 Merge branch 'main' into fix_disable_click_outside_modal 2023-08-07 16:31:24 +06:00
Amruth Pillai a293d209de update dependencies, bump up version to 3.8.4 2023-08-07 11:48:56 +02:00
Amruth Pillai 5b9ea43090 Merge pull request #1418 from AmruthPillai/dependabot/npm_and_yarn/semver-5.7.2
Bump semver from 5.7.1 to 5.7.2
2023-08-07 09:48:05 +02:00
Amruth Pillai 01b36ee8d8 Merge pull request #1420 from AmruthPillai/add-date-format-options
Add 'YYYY.MM' & 'YYYY.M' date format options
2023-08-07 09:30:07 +02:00
Amruth Pillai af1c314c36 Add 'YYYY.MM' & 'YYYY.M' date format options
closes #1419
2023-08-07 09:29:47 +02:00
Jubair Hossain 9a607a590a Merge branch 'main' into fix_disable_click_outside_modal 2023-08-07 12:04:29 +06:00
dependabot[bot] d836e3d992 Bump semver from 5.7.1 to 5.7.2
Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v5.7.1...v5.7.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-03 06:19:13 +00:00
Amruth Pillai fb2db6839f Update README.md 2023-08-01 20:41:48 +02:00
Amruth Pillai 787d0af9d1 Update README.md 2023-08-01 20:40:02 +02:00
Jubair Hossain c455cbb5ca Merge branch 'main' into fix_disable_click_outside_modal 2023-08-01 15:38:30 +06:00
Jubair70 dae142e5ce fix: disable click outside all modals and check unsaved changes for education modal 2023-08-01 15:29:31 +06:00
Amruth Pillai 95de63f387 Merge pull request #1411 from Jubair70/fix/work_modal_datepicker
fix: Work Modal Datepicker popup
2023-07-31 16:12:19 +02:00
Jubair Hossain 456b896310 Merge branch 'main' into fix/work_modal_datepicker 2023-07-31 15:09:40 +06:00
Jubair70 974bf7e032 fix: Work Modal Datepicker 2023-07-31 15:02:17 +06:00
Amruth Pillai 8fa5324a39 Merge pull request #1410 from AmruthPillai/dependabot/docker/server/playwright-v1.36.2-focal
Bump playwright from v1.36.1-focal to v1.36.2-focal in /server
2023-07-31 10:31:58 +02:00
dependabot[bot] 4ac9289344 Bump playwright from v1.36.1-focal to v1.36.2-focal in /server
Bumps playwright from v1.36.1-focal to v1.36.2-focal.

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-31 02:45:02 +00:00
1255 changed files with 170230 additions and 76856 deletions
+57 -22
View File
@@ -1,27 +1,62 @@
# 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
# Compiled Output
dist
tmp
/out-tsc
# Project Dependencies
**/node_modules
.git
.gitignore
node_modules
# Docker
compose*.yml
Dockerfile
.dockerignore
docker-compose.yml
# IDEs and Editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vs/*
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# Miscellaneous
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db
.editorconfig
.eslint*
# Generated Files
.nx
.swc
fly.toml
stats.html
tools/compose/*
tools/scripts/*
# Environment Variables
*.env*
!.env.example
# Lingui Compiled Messages
apps/client/src/locales/_build/
apps/client/src/locales/*/messages.mjs
+5 -1
View File
@@ -2,7 +2,11 @@ root = true
[*]
charset = utf-8
indent_size = 2
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false
+75 -30
View File
@@ -1,36 +1,81 @@
# Server + Client
TZ=UTC
PUBLIC_URL=http://localhost:3000
PUBLIC_SERVER_URL=http://localhost:3100
PUBLIC_GOOGLE_CLIENT_ID=
# Environment
NODE_ENV=development
# Server + Database
# Ports
PORT=3000
# URLs
# These URLs must reference a publicly accessible domain or IP address, not a docker container ID (depending on your compose setup)
PUBLIC_URL=http://localhost:3000
STORAGE_URL=http://localhost:9000/default # default is the bucket name specified in the STORAGE_BUCKET variable
# Database (Prisma/PostgreSQL)
# This can be swapped out to use any other database, like MySQL
# Note: This is used only in the compose.yml file
POSTGRES_PORT=5432
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
# Database (Prisma/PostgreSQL)
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/postgres?schema=public
# Client
PUBLIC_FLAG_DISABLE_SIGNUPS=false
# Authentication Secrets
# generated with `openssl rand -base64 64`
ACCESS_TOKEN_SECRET=access_token_secret
REFRESH_TOKEN_SECRET=refresh_token_secret
# Chrome Browser (for printing)
# generated with `openssl rand -hex 32`
CHROME_PORT=8080
CHROME_TOKEN=chrome_token
CHROME_URL=ws://localhost:8080
# Launch puppeteer with flag to ignore https errors
# CHROME_IGNORE_HTTPS_ERRORS=true
# Mail Server (for e-mails)
# For testing, you can use https://ethereal.email/create
MAIL_FROM=noreply@localhost
# SMTP_URL=smtp://username:password@smtp.ethereal.email:587
# Storage
STORAGE_ENDPOINT=localhost
STORAGE_PORT=9000
STORAGE_REGION=us-east-1
STORAGE_BUCKET=default
STORAGE_ACCESS_KEY=minioadmin
STORAGE_SECRET_KEY=minioadmin
STORAGE_USE_SSL=false
STORAGE_SKIP_BUCKET_CHECK=false
# Nx Cloud (Optional)
# NX_CLOUD_ACCESS_TOKEN=
# Crowdin (Optional)
# CROWDIN_PROJECT_ID=
# CROWDIN_PERSONAL_TOKEN=
# Feature Flags (Optional)
# DISABLE_SIGNUPS=false
# DISABLE_EMAIL_AUTH=false
# GitHub (OAuth, Optional)
# GITHUB_CLIENT_ID=
# GITHUB_CLIENT_SECRET=
# GITHUB_CALLBACK_URL=http://localhost:5173/api/auth/github/callback
# Google (OAuth, Optional)
# GOOGLE_CLIENT_ID=
# GOOGLE_CLIENT_SECRET=
# GOOGLE_CALLBACK_URL=http://localhost:5173/api/auth/google/callback
# OpenID (Optional)
# VITE_OPENID_NAME=
# OPENID_AUTHORIZATION_URL=
# OPENID_CALLBACK_URL=http://localhost:5173/api/auth/openid/callback
# OPENID_CLIENT_ID=
# OPENID_CLIENT_SECRET=
# OPENID_ISSUER=
# OPENID_SCOPE=openid profile email
# OPENID_TOKEN_URL=
# OPENID_USER_INFO_URL=
+1
View File
@@ -0,0 +1 @@
node_modules
+86 -33
View File
@@ -1,40 +1,93 @@
{
"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"
},
"root": true,
"ignorePatterns": ["**/*"],
"plugins": ["@nx"],
"overrides": [
{
"files": ["*.js"],
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"plugins": ["simple-import-sort", "unused-imports"],
"rules": {
"@typescript-eslint/no-var-requires": "off"
// eslint
"no-console": "error",
"no-return-await": "off",
// simple-import-sort
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
// unused-imports
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}
],
// nx
"@nx/enforce-module-boundaries": [
"error",
{
"allowCircularSelfDependency": true,
"enforceBuildableLibDependency": true,
"allow": [],
"depConstraints": [
{
"sourceTag": "*",
"onlyDependOnLibsWithTags": ["*"]
}
]
}
]
}
},
{
"files": ["*.ts", "*.tsx"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"sourceType": "module",
"ecmaVersion": "latest",
"project": ["tsconfig.*?.json"]
},
"extends": [
"plugin:@nx/typescript",
"plugin:@typescript-eslint/strict-type-checked",
"plugin:@typescript-eslint/stylistic-type-checked",
"plugin:unicorn/recommended"
],
"plugins": ["@typescript-eslint", "unicorn"],
"rules": {
// typescript-eslint
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/return-await": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/no-misused-promises": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/restrict-template-expressions": "off",
"@typescript-eslint/no-redundant-type-constituents": "off",
"@typescript-eslint/consistent-type-definitions": ["error", "type"],
// unicorn
"unicorn/no-null": "off",
"unicorn/prevent-abbreviations": "off",
"unicorn/prefer-string-replace-all": "off",
"unicorn/prefer-structured-clone": "off"
}
},
{
"files": ["*.js", "*.jsx"],
"extends": ["plugin:@nx/javascript"],
"rules": {
// eslint
"no-console": "warn",
"no-unused-vars": "off"
}
}
]
@@ -0,0 +1 @@
https://rxresu.me/funding.json
+1 -1
View File
@@ -1,2 +1,2 @@
github: AmruthPillai
custom: https://paypal.me/amruthde
open_collective: reactive-resume
@@ -1,9 +1,10 @@
name: 🐞 Bug Report
description: Create a bug report to help improve Reactive Resume.
title: '[Bug] <title>'
labels: [Bug, Needs Triage]
assignees: 'AmruthPillai'
description: Create a bug report to help improve Reactive Resume
title: "[Bug] <title>"
labels: [bug, v4, needs triage]
assignees: "AmruthPillai"
body:
- type: checkboxes
@@ -11,7 +12,7 @@ body:
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
- label: Yes, I have searched the existing issues and none of them match my problem.
required: true
- type: dropdown
@@ -20,7 +21,7 @@ body:
label: Product Variant
description: What variant of Reactive Resume are you using?
options:
- Cloud (http://rxresu.me)
- Cloud (https://rxresu.me)
- Self-Hosted
validations:
required: true
@@ -64,6 +65,28 @@ body:
validations:
required: false
- type: dropdown
id: template
attributes:
label: What template are you using?
description: Leave blank if the issue applies to all templates, or is not template-specific.
multiple: false
options:
- Azurill
- Bronzor
- Chikorita
- Ditto
- Gengar
- Glalie
- Kakuna
- Leafish
- Nosepass
- Onyx
- Pikachu
- Rhyhorn
validations:
required: false
- type: textarea
attributes:
label: Anything else?
@@ -1,17 +1,18 @@
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'
description: Suggest an feature or idea that you would like to see in Reactive Resume
title: "[Feature] <title>"
labels: [enhancement, v4, needs triage]
assignees: "AmruthPillai"
body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
label: Is there an existing issue for this feature?
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
- label: Yes, I have searched the existing issues and it doesn't exist.
required: true
- type: textarea
-22
View File
@@ -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'
-142
View File
@@ -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.5.3
- name: Retrieve version from package.json
id: version
uses: martinbeentjes/npm-get-version-action@v1.3.1
- name: Docker Metadaata
id: meta
uses: docker/metadata-action@v4.6.0
with:
images: amruthpillai/reactive-resume
tags: |
type=raw,value=${{ matrix.image }}-latest
type=raw,value=${{ matrix.image }}-${{ steps.version.outputs.current-version }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.2.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.9.1
- name: Login to Docker Hub
uses: docker/login-action@v2.2.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2.2.0
with:
registry: ghcr.io
username: $GITHUB_REPOSITORY_OWNER
password: ${{ secrets.GH_TOKEN }}
- name: Build and Push
id: build
uses: docker/build-push-action@v4.1.1
with:
context: .
push: true
platforms: linux/amd64
file: ${{ matrix.image }}/Dockerfile
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta.outputs.tags }}
build-args: |
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
build-arm64:
name: Build (arm64)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
image:
- client
- server
steps:
- name: Checkout the repository
uses: actions/checkout@v3.5.3
- name: Retrieve version from package.json
id: version
uses: martinbeentjes/npm-get-version-action@v1.3.1
- name: Docker Metadaata
id: meta
uses: docker/metadata-action@v4.6.0
with:
images: amruthpillai/reactive-resume
tags: |
type=raw,value=${{ matrix.image }}-arm64-latest
type=raw,value=${{ matrix.image }}-arm64-${{ steps.version.outputs.current-version }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.2.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.9.1
- name: Login to Docker Hub
uses: docker/login-action@v2.2.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2.2.0
with:
registry: ghcr.io
username: $GITHUB_REPOSITORY_OWNER
password: ${{ secrets.GH_TOKEN }}
- name: Build and Push
id: build
uses: docker/build-push-action@v4.1.1
with:
context: .
push: true
platforms: linux/arm64
file: ${{ matrix.image }}/Dockerfile
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta.outputs.tags }}
build-args: |
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
deploy:
name: Deploy
runs-on: ubuntu-latest
needs: build-amd64
steps:
- name: Install DigitalOcean CLI
uses: digitalocean/action-doctl@v2.3.0
with:
token: ${{ secrets.DIGITALOCEAN_TOKEN }}
- name: Create Deployment with Latest Version
run: doctl apps create-deployment ${{ secrets.DIGITALOCEAN_APP_ID }} --wait --force-rebuild
+45
View File
@@ -0,0 +1,45 @@
name: Lint, Test & Build
concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: true
on:
workflow_dispatch:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4.2.2
with:
fetch-depth: 2
- name: Setup pnpm
uses: pnpm/action-setup@v4.0.0
- name: Setup Node.js
uses: actions/setup-node@v4.1.0
with:
cache: "pnpm"
node-version: 22
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Lint
run: pnpm run lint
- name: Test
run: pnpm run test
- name: Build
run: pnpm run build
+167
View File
@@ -0,0 +1,167 @@
name: Publish Docker Image
on:
workflow_dispatch:
push:
tags:
- "*"
concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: true
env:
IMAGE: amruthpillai/reactive-resume
jobs:
build:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
strategy:
matrix:
platform:
- linux/amd64
- linux/arm64
steps:
- name: Checkout Repository
uses: actions/checkout@v4.1.4
- name: Extract version from package.json
id: version
run: echo "version=$(jq -r '.version' package.json)" >> "$GITHUB_OUTPUT"
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.0.0
- name: Login to Docker Hub
uses: docker/login-action@v3.1.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Login to GitHub Container Registery
uses: docker/login-action@v3.1.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ github.token }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.3.0
- name: Extract Docker Metadata
id: meta
uses: docker/metadata-action@v5.5.1
with:
tags: type=semver,pattern={{version}},prefix=v,value=${{ steps.version.outputs.version }}
images: |
${{ env.IMAGE }}
ghcr.io/${{ env.IMAGE }}
- name: Prepare a unique name for Artifacts
id: artifact_name
run: |
name=$(echo -n "${{ matrix.platform }}" | sed -e 's/[ \t:\/\\"<>|*?]/-/g' -e 's/--*/-/g')
echo "name=$name" >> "$GITHUB_OUTPUT"
- name: Build and Push by Digest
uses: docker/build-push-action@v5.3.0
id: build
with:
context: .
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true
build-args: |
NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
- name: Export Digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload Digest
uses: actions/upload-artifact@v4.3.3
with:
name: digests-${{ steps.artifact_name.outputs.name }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
merge:
runs-on: ubuntu-latest
needs:
- build
steps:
- name: Checkout Repository
uses: actions/checkout@v4.1.4
- name: Download Digest
uses: actions/download-artifact@v4.1.7
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Login to Docker Hub
uses: docker/login-action@v3.1.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Login to GitHub Container Registery
uses: docker/login-action@v3.1.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ github.token }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.3.0
- name: Extract Docker Metadata
id: meta
uses: docker/metadata-action@v5.5.1
with:
tags: type=semver,pattern={{version}},prefix=v,value=${{ needs.build.outputs.version }}
images: |
${{ env.IMAGE }}
ghcr.io/${{ env.IMAGE }}
- name: Create Docker Manifest List and Push
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.IMAGE }}@sha256:%s ' *)
- name: Inspect Image
run: |
docker buildx imagetools inspect ${{ env.IMAGE }}:${{ steps.meta.outputs.version }}
- name: Update Repository Description
uses: peter-evans/dockerhub-description@v4.0.0
with:
repository: ${{ github.repository }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Deploy the latest image on rxresu.me
run: curl -kX POST ${{ secrets.SERVICE_WEBHOOK }}
- name: Inform about the release on Discord
uses: sarisia/actions-status-discord@v1.14.3
if: always()
with:
username: ReleaseBot
webhook: ${{ secrets.DISCORD_WEBHOOK }}
status: ${{ job.status }}
title: "Release `${{ steps.meta.outputs.version }}`"
description: "A new version of Reactive Resume just dropped! 🚀"
url: "https://github.com/AmruthPillai/Reactive-Resume"
@@ -0,0 +1,31 @@
name: Sync Crowdin Translations
on:
workflow_dispatch:
schedule:
- cron: 0 0 * * * # everyday at midnight (UTC)
jobs:
sync:
if: github.repository == 'AmruthPillai/Reactive-Resume'
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4.1.1
- name: Sync Translations
uses: crowdin/github-action@v1.15.2
with:
upload_sources: true
upload_translations: true
download_translations: true
create_pull_request: true
localization_branch_name: "l10n"
pull_request_base_branch_name: "main"
pull_request_title: "New Translations from Crowdin"
pull_request_body: "You've got new translations to be merged into the app from contributors on Crowdin.\n\n_This pull request was automatically created by the [Crowdin Action](https://github.com/marketplace/actions/crowdin-action)._"
env:
GITHUB_TOKEN: ${{ github.token }}
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
+46 -11
View File
@@ -1,18 +1,53 @@
# Environment Variables
.env
.env.*
*.env
!.env.gitpod
!.env.example
# Compiled Output
dist
tmp
/out-tsc
# Project Dependencies
node_modules
# macOS
# IDEs and Editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vs/*
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# Miscellaneous
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db
# Intellij
.idea
# Generated Files
.nx
.swc
.turbo
fly.toml
stats.html
# Turborepo
.turbo
# Environment Variables
*.env*
!.env.example
# Lingui Compiled Messages
apps/client/src/locales/_build/
apps/client/src/locales/*/messages.*js
-41
View File
@@ -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
+16
View File
@@ -0,0 +1,16 @@
{
"$schema": "https://raw.githubusercontent.com/raineorshine/npm-check-updates/main/src/types/RunOptions.json",
"upgrade": true,
"target": "minor",
"install": "always",
"packageManager": "pnpm",
"reject": [
"nx",
"eslint",
"@nx/*",
"@swc/*",
"@swc-node/*",
"@reactive-resume/*",
"eslint-plugin-unused-imports"
]
}
+1
View File
@@ -1,2 +1,3 @@
auto-install-peers=true
enable-pre-post-scripts=true
strict-peer-dependencies=false
-1
View File
@@ -1 +0,0 @@
lts/*
+6 -28
View File
@@ -1,29 +1,7 @@
# 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
/dist
/coverage
/.nx/cache
stats.html
pnpm-lock.yaml
# Docker
Dockerfile
.dockerignore
docker-compose.yml
compose-dev.yml
compose.yml
+4 -2
View File
@@ -1,4 +1,6 @@
{
"printWidth": 120,
"singleQuote": true
"printWidth": 100,
"endOfLine": "auto",
"plugins": ["prettier-plugin-tailwindcss"],
"tailwindFunctions": ["cn", "cva"]
}
+2 -6
View File
@@ -1,7 +1,3 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"lokalise.i18n-ally"
]
}
"recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
}
+6 -19
View File
@@ -1,22 +1,9 @@
{
"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"
"i18n-ally.localesPaths": ["apps/client/src/locales"],
"tailwindCSS.experimental.classRegex": [
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
["cn\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
],
"conventionalCommits.scopes": [
"client",
"server",
"docker",
"dependencies"
]
}
"typescript.tsdk": "node_modules/typescript/lib"
}
+19
View File
@@ -0,0 +1,19 @@
# Changelog
### What’s changed from v3 to v4?
**The entire app has been rebuilt and reimagined from the ground up.**
The **user interface** has been greatly streamlined to prioritise your content and resume. The design of templates has also undergone a major overhaul. Previously, we utilised TailwindCSS for creating templates, but now you can rely on CSS (styled-components) to build any design you prefer. With this change, I hope to offer a **much wider variety of templates** compared to the previous version.
When it comes to features, there are many to mention, but some highlights include the **ability to use your own OpenAI API key** (stored on your browser) and leverage GPTs to enhance your resume writing skills. With this, you can improve your writing, correct spelling and grammar, and even adjust the tone of the text to be more confident or casual.
When you make your resume publicly available, you are provided with a link that you can share with potential recruiters and employers. This change allows you to **track the number of views or downloads your resume has received**, so you can stay informed about when someone has checked out your resume.
When it comes to **security**, you now have the option to protect your account with **two-factor authentication**. This means that whenever you log in to Reactive Resume, you will also need to enter a one-time code generated on your phone. This additional step ensures that only you have access to your account.
From a **design** perspective, the motivation behind this is to ensure that Reactive Resume is taken more seriously and not perceived as just another subpar side-project, which is often associated with free software. My goal is to demonstrate that this is not the case, and that **free and open source software can be just as good**, if not better, than paid alternatives.
From a **self-hosting perspective**, it has never been simpler. Instead of running two separate services on your Docker (one for the client and one for the server) and struggling to establish communication between them, now you only need to pull a single image. Additionally, there are a few dependent services available on Docker (such as Postgres, Minio etc.) that you can also pull and have them all working together seamlessly.
I'm excited for you to try out the app, as I've spent months building it to perfection. If you enjoy the experience of building your resume using the app, please consider supporting by [becoming a GitHub Sponsor](https://github.com/sponsors/AmruthPillai).
+28 -24
View File
@@ -6,8 +6,8 @@ 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.
nationality, personal appearance, race, caste, color, 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.
@@ -22,17 +22,17 @@ community include:
- 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
- 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
- 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
- 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
@@ -52,7 +52,7 @@ decisions when appropriate.
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,
Examples of representing our community include using an official email address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
@@ -60,7 +60,7 @@ representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
im.amruth@gmail.com.
[INSERT CONTACT METHOD].
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
@@ -82,15 +82,15 @@ behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**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.
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
@@ -109,20 +109,24 @@ Violating these terms may lead to a permanent ban.
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.
**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.
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
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.
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
+95
View File
@@ -0,0 +1,95 @@
# Contributing to Reactive Resume
## Getting the project set up locally
There are a number of Docker Compose examples that are suitable for a wide variety of deployment strategies depending on your use-case. All the examples can be found in the `tools/compose` folder.
To run the development environment of the application locally on your computer, please follow these steps:
#### Requirements
- Docker (with Docker Compose)
- Node.js 20 or higher (with pnpm)
### 1. Fork and Clone the Repository
```sh
git clone https://github.com/{your-github-username}/Reactive-Resume.git
cd Reactive-Resume
```
### 2. Install dependencies
```sh
pnpm install
```
### 3. Copy .env.example to .env
```sh
cp .env.example .env
```
Please have a brief look over the environment variables and change them if necessary, for example, change the ports if you have a conflicting service running on your machine already.
### 4. Fire up all the required services through Docker Compose
```sh
docker compose -f tools/compose/development.yml --env-file .env -p reactive-resume up -d
```
It should take just under half a minute for all the services to be booted up correctly. You can check the status of all services by running `docker compose -p reactive-resume ps`
### 5. Run the development server
```sh
pnpm prisma:migrate:dev
pnpm dev
```
If everything went well, the frontend should be running on `http://localhost:5173` and the backend api should be accessible through `http://localhost:3000`. There is a proxy present to also route all requests to `http://localhost:5173/api` directly to the API. If you need to change the `PORT` environment variable for the server, please make sure to update the `apps/client/proxy.conf.json` file as well with the new endpoint.
You can also visit `http://localhost:3000/api/health`, the health check endpoint of the server to check if the server is running correctly, and it is able to connect to all it's dependent services. The output of the health check endpoint should look like this:
```json
{
"status": "ok",
"info": {
"database": { "status": "up" },
"storage": { "status": "up" },
"browser": { "status": "up", "version": "Chrome/119.0.6045.9" }
},
"error": {},
"details": {
"database": { "status": "up" },
"storage": { "status": "up" },
"browser": { "status": "up", "version": "Chrome/119.0.6045.9" }
}
}
```
---
## Pushing changes to the app
Firstly, ensure that there is a GitHub Issue created for the feature or bugfix you are working on. If it does not exist, create an issue and assign it to yourself.
Once you are happy with the changes you've made locally, commit it to your repository. Note that the project makes use of Conventional Commits, so commit messages would have to be in a specific format for it to be accepted. For example, a commit message to fix the translation on the homepage could look like:
```
git commit -m "fix(homepage): fix typo on homepage in the faq section"
```
It helps to be as descriptive as possible in commit messages so that users can be aware of the changes made by you.
Then, you can run the basic checks, as to make sure the CI has the most chance of being successful:
```
pnpm run lint
pnpm run test
pnpm run build
```
Finally, create a pull request to merge the changes on your forked repository to the original repository hosted on AmruthPillai/Reactive-Resume. I can take a look at the changes you've made when I have the time and have it merged onto the app.
+47
View File
@@ -0,0 +1,47 @@
ARG NX_CLOUD_ACCESS_TOKEN
# --- Base Image ---
FROM node:lts-bullseye-slim AS base
ARG NX_CLOUD_ACCESS_TOKEN
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
WORKDIR /app
# --- Build Image ---
FROM base AS build
ARG NX_CLOUD_ACCESS_TOKEN
COPY .npmrc package.json pnpm-lock.yaml ./
COPY ./tools/prisma /app/tools/prisma
RUN pnpm install --frozen-lockfile
COPY . .
ENV NX_CLOUD_ACCESS_TOKEN=$NX_CLOUD_ACCESS_TOKEN
RUN pnpm run build
# --- Release Image ---
FROM base AS release
ARG NX_CLOUD_ACCESS_TOKEN
RUN apt update && apt install -y dumb-init --no-install-recommends && rm -rf /var/lib/apt/lists/*
COPY --chown=node:node --from=build /app/.npmrc /app/package.json /app/pnpm-lock.yaml ./
RUN pnpm install --prod --frozen-lockfile
COPY --chown=node:node --from=build /app/dist ./dist
COPY --chown=node:node --from=build /app/tools/prisma ./tools/prisma
RUN pnpm run prisma:generate
ENV TZ=UTC
ENV PORT=3000
ENV NODE_ENV=production
EXPOSE 3000
CMD [ "dumb-init", "pnpm", "run", "start" ]
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2022 Amruth Pillai
Copyright (c) 2023 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
+95 -148
View File
@@ -1,169 +1,116 @@
<img src="/client/public/logo/dark.svg" alt="Reactive Resume" width="256px" height="256px" />
<div align="center" markdown="1">
<a href="https://go.warp.dev/Reactive-Resume">
<img alt="Warp Sponsorship" width="400" src="https://github.com/warpdotdev/brand-assets/blob/main/Github/Sponsor/Warp-Github-LG-03.png?raw=true" />
</a>
### [Warp, built for coding with multiple AI agents.](https://go.warp.dev/Reactive-Resume)
[Available for MacOS, Linux, & Windows](https://go.warp.dev/Reactive-Resume)<br>
---
<img alt="Reactive Resume" width="800" src="https://i.imgur.com/FFc4nyZ.jpg" />
![App Version](https://img.shields.io/github/package-json/version/AmruthPillai/Reactive-Resume?label=version)
[![Docker Pulls](https://img.shields.io/docker/pulls/amruthpillai/reactive-resume)](https://hub.docker.com/repository/docker/amruthpillai/reactive-resume)
[![GitHub Sponsors](https://img.shields.io/github/sponsors/AmruthPillai)](https://github.com/sponsors/AmruthPillai)
[![Crowdin](https://badges.crowdin.net/reactive-resume/localized.svg)](https://crowdin.com/project/reactive-resume)
[![Discord](https://img.shields.io/discord/1173518977851473940?label=discord&link=https%3A%2F%2Fdiscord.gg%2FhzwkZbyvUW)](https://discord.gg/hzwkZbyvUW)
# 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)
A free and open-source resume builder that simplifies the process of creating, updating, and sharing your resume.
## [Go to App](https://rxresu.me) | [Docs](https://docs.rxresu.me)
### [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.
</div>
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.
## Description
## Table of Contents
Reactive Resume is a free and open-source resume builder that simplifies the process of creating, updating, and sharing your resume. With zero user tracking or advertising, your privacy is a top priority. The platform is extremely user-friendly and can be self-hosted in less than 30 seconds if you wish to own your data completely.
- [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)
It's available in multiple languages and comes packed with features such as real-time editing, dozens of templates, drag-and-drop customisation, and integration with OpenAI for enhancing your writing.
You can share a personalised link of your resume to potential employers, track its views or downloads, and customise your page layout by dragging-and-dropping sections. The platform also supports various font options and provides dozens of templates to choose from. And yes, there's even a dark mode for a more comfortable viewing experience.
Start creating your standout resume with Reactive Resume today!
## Templates
| Azurill | Bronzor | Chikorita |
| ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| <img src="./apps/client/public/templates/jpg/azurill.jpg" width="200px" /> | <img src="./apps/client/public/templates/jpg/bronzor.jpg" width="200px" /> | <img src="./apps/client/public/templates/jpg/chikorita.jpg" width="200px" /> |
| Ditto | Gengar | Glalie |
| -------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| <img src="./apps/client/public/templates/jpg/ditto.jpg" width="200px" /> | <img src="./apps/client/public/templates/jpg/gengar.jpg" width="200px" /> | <img src="./apps/client/public/templates/jpg/glalie.jpg" width="200px" /> |
| Kakuna | Leafish | Nosepass |
| ---------------------------------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| <img src="./apps/client/public/templates/jpg/kakuna.jpg" width="200px" /> | <img src="./apps/client/public/templates/jpg/leafish.jpg" width="200px" /> | <img src="./apps/client/public/templates/jpg/nosepass.jpg" width="200px" /> |
| Onyx | Pikachu | Rhyhorn |
| ------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| <img src="./apps/client/public/templates/jpg/onyx.jpg" width="200px" /> | <img src="./apps/client/public/templates/jpg/pikachu.jpg" width="200px" /> | <img src="./apps/client/public/templates/jpg/rhyhorn.jpg" width="200px" /> |
## 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?**
- **Free, forever** and open-source
- No telemetry, user tracking or advertising
- You can self-host the application in less than 30 seconds
- **Available in multiple languages** ([help add/improve your language here](https://translate.rxresu.me/))
- Use your email address (or a throw-away address, no problem) to create an account
- You can also sign in with your GitHub or Google account, and even set up two-factor authentication for extra security
- Create as many resumes as you like under a single account, optimising each resume for every job application based on its description for a higher ATS score
- **Bring your own OpenAI API key** and unlock features such as improving your writing, fixing spelling and grammar or changing the tone of your text in one-click
- Translate your resume into any language using ChatGPT and import it back for easier editing
- Create single page resumes or a resume that spans multiple pages easily
- Customize the colours and layouts to add a personal touch to your resume
- Customise your page layout as you like just by dragging-and-dropping sections
- Create custom sections that are specific to your industry if the existing ones don't fit
- Jot down personal notes specific to your resume that's only visible to you
- Lock a resume to prevent making any further edits (useful for master templates)
- **Dozens of templates** to choose from, ranging from professional to modern
- Design your resume using the standardised EuroPass design template
- Supports printing resumes in A4 or Letter page formats
- Design your resume with any font that's available on [Google Fonts](https://fonts.google.com/)
- **Share a personalised link of your resume** to companies or recruiters for them to get the latest updates
- You can track the number of views or downloads your public resume has received
- Built with state-of-the-art (at the moment) and dependable technologies that's battle tested and peer reviewed by the open-source community on GitHub
- **MIT License**, so do what you like with the code as long as you credit the original author
- And yes, there’s a dark mode too 🌓
## Languages
## Built With
- 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)
- React (Vite), for the frontend
- NestJS, for the backend
- Postgres (primary database)
- Prisma ORM, which frees you to switch to any other relational database with a few minor changes in the code
- Minio (for object storage: to store avatars, resume PDFs and previews)
- Browserless (for headless chrome, to print PDFs and generate previews)
- SMTP Server (to send password recovery emails)
- GitHub/Google OAuth (for quickly authenticating users)
- LinguiJS and Crowdin (for translation management and localization)
Help by [translating Reactive Resume](https://translate.rxresu.me) to your language!
## Star History
## 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
&nbsp;
<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 href="https://star-history.com/#AmruthPillai/Reactive-Resume&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=AmruthPillai/Reactive-Resume&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=AmruthPillai/Reactive-Resume&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=AmruthPillai/Reactive-Resume&type=Date" />
</picture>
</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.
Reactive Resume is packaged and distributed using the [MIT License](/LICENSE.md) 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/)
A passion project by [Amruth Pillai](https://www.amruthpillai.com/)
<p>
<a href="https://m.do.co/c/ceae1fff245e">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_blue.svg" width="200px">
</a>
</p>
+3 -2
View File
@@ -4,10 +4,11 @@
| Version | Supported |
| ------- | ------------------ |
| 3.x.x | :white_check_mark: |
| 4.x.x | :white_check_mark: |
| 3.x.x | :x: |
| 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/
Please raise an issue on GitHub to report any security vulnerabilities in the app. If the vulnerability is potentially lethal, email me about it on hello@amruthpillai.com.
-33
View File
@@ -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
-8
View File
@@ -1,8 +0,0 @@
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
-2
View File
@@ -1,2 +0,0 @@
/build
/release
-47
View File
@@ -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'
}
-21
View File
@@ -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
-26
View File
@@ -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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

@@ -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
}
}
@@ -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()
}
}
}
@@ -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>
@@ -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>
@@ -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>
@@ -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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>
-3
View File
@@ -1,3 +0,0 @@
<resources>
<string name="app_name">Reactive Resume</string>
</resources>
-6
View File
@@ -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>
-9
View File
@@ -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.0' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}
-23
View File
@@ -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
Binary file not shown.
-6
View File
@@ -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
Vendored
-185
View File
@@ -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" "$@"
-89
View File
@@ -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
-16
View File
@@ -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'
+36
View File
@@ -0,0 +1,36 @@
{
"extends": ["plugin:@nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
// eslint
"@typescript-eslint/no-require-imports": "off",
// react
"react/no-unescaped-entities": "off",
"react/jsx-sort-props": [
"error",
{
"reservedFirst": true,
"callbacksLast": true,
"shorthandFirst": true,
"noSortAlphabetically": true
}
],
// react-hooks
"react-hooks/exhaustive-deps": "off"
}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
+44
View File
@@ -0,0 +1,44 @@
<!doctype html>
<html lang="en-US" translate="no">
<head>
<base href="/" />
<!-- SEO -->
<title>Reactive Resume - A free and open-source resume builder</title>
<meta
name="description"
content="A free and open-source resume builder that simplifies the process of creating, updating, and sharing your resume."
/>
<!-- Meta -->
<meta charset="utf-8" />
<meta name="googlebot" content="notranslate" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- Favicon -->
<link
rel="icon"
type="image/svg+xml"
href="/icon/dark.svg"
media="(prefers-color-scheme: light)"
/>
<link
rel="icon"
type="image/svg+xml"
href="/icon/light.svg"
media="(prefers-color-scheme: dark)"
/>
<!-- Styles -->
<link rel="stylesheet" href="/src/styles/main.css" />
</head>
<body>
<div id="root"></div>
<!-- Scripts -->
<script type="module" src="/src/main.tsx"></script>
<!-- Phosphor Icons -->
<script defer src="https://cdn.jsdelivr.net/npm/@phosphor-icons/web"></script>
</body>
</html>
+10
View File
@@ -0,0 +1,10 @@
const path = require("node:path");
module.exports = {
plugins: {
tailwindcss: {
config: path.join(__dirname, "tailwind.config.js"),
},
autoprefixer: {},
},
};
+64
View File
@@ -0,0 +1,64 @@
{
"name": "artboard",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/artboard/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@nx/vite:build",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"outputPath": "dist/apps/artboard"
},
"configurations": {
"development": {
"mode": "development"
},
"production": {
"mode": "production"
}
}
},
"serve": {
"executor": "@nx/vite:dev-server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "artboard:build"
},
"configurations": {
"development": {
"buildTarget": "artboard:build:development",
"hmr": true
},
"production": {
"buildTarget": "artboard:build:production",
"hmr": false
}
}
},
"preview": {
"executor": "@nx/vite:preview-server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "artboard:build"
},
"configurations": {
"development": {
"buildTarget": "artboard:build:development"
},
"production": {
"buildTarget": "artboard:build:production"
}
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/artboard/**/*.{ts,tsx,js,jsx}"]
}
}
},
"tags": ["frontend"]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" fill="none"><rect width="256" height="256" rx="80" style="fill:#000;stroke:none;stroke-width:.396357"/><g fill="#09090b" fill-rule="evenodd" clip-rule="evenodd"><path d="m282.857 257.192-37.64 50.15 39.21.004 17.95-23.88 17.95 23.876h39.21l-75.11-100.256-39.21-.003zm23.775-31.764 13.696-18.345h39.21l-33.234 44.435zM119.537 135.21v129.485h36.626V230.29h19.993c17.99-.01 40.841-3.946 52.762-21.828 4.686-7.152 7.03-15.6 7.03-25.342 0-9.865-2.344-18.374-7.03-25.527-4.687-7.276-11.346-12.825-19.978-16.648-8.51-3.823-18.37-5.735-30.21-5.735zm90.963 95.183s-14.972 6.285-34.681 6.047l21.162 28.255h39.21zm-54.337-28.405h20.348c7.646 0 13.319-1.665 17.018-4.995 3.823-3.33 5.735-7.954 5.735-13.874 0-6.042-1.912-10.729-5.735-14.058-3.7-3.33-9.372-4.995-17.018-4.995h-20.348z" style="fill:#fafafa;fill-opacity:1;stroke-width:.9375" transform="matrix(.91667 0 0 .91667 -91.576 -74.838)"/></g></svg>

After

Width:  |  Height:  |  Size: 958 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"><path fill="#09090b" fill-rule="evenodd" d="m16.332 15.592-3.764 5.015h3.921l1.795-2.388 1.795 2.388H24L16.489 10.58h-3.921Zm2.377-3.177 1.37-1.834H24l-3.323 4.443zM0 3.393v12.949h3.663v-3.44h1.999c1.799-.002 4.084-.395 5.276-2.183.469-.716.703-1.56.703-2.535 0-.986-.234-1.837-.703-2.552-.469-.728-1.135-1.283-1.998-1.665-.85-.382-1.837-.574-3.02-.574Zm9.096 9.519s-1.497.628-3.468.604l2.116 2.826h3.921zm-5.433-2.84h2.034c.765 0 1.332-.167 1.702-.5.382-.333.574-.796.574-1.388 0-.604-.192-1.073-.574-1.405-.37-.333-.937-.5-1.702-.5H3.663Z" clip-rule="evenodd" style="stroke-width:.09375"/></svg>

After

Width:  |  Height:  |  Size: 672 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"><path fill="#09090b" fill-rule="evenodd" d="m16.332 15.592-3.764 5.015h3.921l1.795-2.388 1.795 2.388H24L16.489 10.58h-3.921Zm2.377-3.177 1.37-1.834H24l-3.323 4.443zM0 3.393v12.949h3.663v-3.44h1.999c1.799-.002 4.084-.395 5.276-2.183.469-.716.703-1.56.703-2.535 0-.986-.234-1.837-.703-2.552-.469-.728-1.135-1.283-1.998-1.665-.85-.382-1.837-.574-3.02-.574Zm9.096 9.519s-1.497.628-3.468.604l2.116 2.826h3.921zm-5.433-2.84h2.034c.765 0 1.332-.167 1.702-.5.382-.333.574-.796.574-1.388 0-.604-.192-1.073-.574-1.405-.37-.333-.937-.5-1.702-.5H3.663Z" clip-rule="evenodd" style="stroke-width:.09375;fill:#fafafa;fill-opacity:1"/></svg>

After

Width:  |  Height:  |  Size: 700 B

@@ -0,0 +1,24 @@
import { forwardRef } from "react";
type BrandIconProps = {
slug: string;
};
export const BrandIcon = forwardRef<HTMLImageElement, BrandIconProps>(({ slug }, ref) => {
if (slug.toLowerCase() === "linkedin") {
return (
<img
ref={ref}
alt="LinkedIn"
className="size-4"
src={`${window.location.origin}/support-logos/linkedin.svg`}
/>
);
}
return (
<img ref={ref} alt={slug} className="size-4" src={`https://cdn.simpleicons.org/${slug}`} />
);
});
BrandIcon.displayName = "BrandIcon";
+48
View File
@@ -0,0 +1,48 @@
import { useTheme } from "@reactive-resume/hooks";
import { cn, pageSizeMap } from "@reactive-resume/utils";
import { useArtboardStore } from "../store/artboard";
type Props = {
mode?: "preview" | "builder";
pageNumber: number;
children: React.ReactNode;
};
export const MM_TO_PX = 3.78;
export const Page = ({ mode = "preview", pageNumber, children }: Props) => {
const { isDarkMode } = useTheme();
const page = useArtboardStore((state) => state.resume.metadata.page);
const fontFamily = useArtboardStore((state) => state.resume.metadata.typography.font.family);
return (
<div
data-page={pageNumber}
className={cn("relative bg-background text-foreground", mode === "builder" && "shadow-2xl")}
style={{
fontFamily,
width: `${pageSizeMap[page.format].width * MM_TO_PX}px`,
minHeight: `${pageSizeMap[page.format].height * MM_TO_PX}px`,
}}
>
{mode === "builder" && page.options.pageNumbers && (
<div className={cn("absolute -top-7 left-0 font-bold", isDarkMode && "text-white")}>
Page {pageNumber}
</div>
)}
{children}
{mode === "builder" && page.options.breakLine && (
<div
className="absolute inset-x-0 border-b border-dashed"
style={{
top: `${pageSizeMap[page.format].height * MM_TO_PX}px`,
}}
/>
)}
</div>
);
};
+33
View File
@@ -0,0 +1,33 @@
import { cn, isUrl } from "@reactive-resume/utils";
import { useArtboardStore } from "../store/artboard";
type PictureProps = {
className?: string;
};
export const Picture = ({ className }: PictureProps) => {
const picture = useArtboardStore((state) => state.resume.basics.picture);
const fontSize = useArtboardStore((state) => state.resume.metadata.typography.font.size);
if (!isUrl(picture.url) || picture.effects.hidden) return null;
return (
<img
alt="Profile"
src={picture.url}
className={cn(
"relative z-20 object-cover",
picture.effects.border && "border-primary",
picture.effects.grayscale && "grayscale",
className,
)}
style={{
maxWidth: `${picture.size}px`,
aspectRatio: `${picture.aspectRatio}`,
borderRadius: `${picture.borderRadius}px`,
borderWidth: `${picture.effects.border ? fontSize / 3 : 0}px`,
}}
/>
);
};
+5
View File
@@ -0,0 +1,5 @@
import { HelmetData } from "react-helmet-async";
export const helmetData = new HelmetData({});
export const helmetContext = helmetData.context;
+14
View File
@@ -0,0 +1,14 @@
import { StrictMode } from "react";
import * as ReactDOM from "react-dom/client";
import { RouterProvider } from "react-router";
import { router } from "./router";
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const root = ReactDOM.createRoot(document.querySelector("#root")!);
root.render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
);
+87
View File
@@ -0,0 +1,87 @@
import { isLocalFont } from "@reactive-resume/utils";
import { useEffect, useMemo } from "react";
import { Helmet } from "react-helmet-async";
import { Outlet } from "react-router";
import webfontloader from "webfontloader";
import { useArtboardStore } from "../store/artboard";
export const ArtboardPage = () => {
const name = useArtboardStore((state) => state.resume.basics.name);
const metadata = useArtboardStore((state) => state.resume.metadata);
const fontString = useMemo(() => {
const family = metadata.typography.font.family;
const variants = metadata.typography.font.variants.join(",");
const subset = metadata.typography.font.subset;
return `${family}:${variants}:${subset}`;
}, [metadata.typography.font]);
useEffect(() => {
const family = metadata.typography.font.family;
if (isLocalFont(family)) {
let frame = 0;
frame = requestAnimationFrame(() => {
const width = window.document.body.offsetWidth;
const height = window.document.body.offsetHeight;
const message = { type: "PAGE_LOADED", payload: { width, height } };
window.postMessage(message, "*");
});
return () => { cancelAnimationFrame(frame); };
}
webfontloader.load({
google: { families: [fontString] },
active: () => {
const width = window.document.body.offsetWidth;
const height = window.document.body.offsetHeight;
const message = { type: "PAGE_LOADED", payload: { width, height } };
window.postMessage(message, "*");
},
});
}, [fontString]);
// Font Size & Line Height
useEffect(() => {
document.documentElement.style.setProperty("font-size", `${metadata.typography.font.size}px`);
document.documentElement.style.setProperty("line-height", `${metadata.typography.lineHeight}`);
document.documentElement.style.setProperty("--margin", `${metadata.page.margin}px`);
document.documentElement.style.setProperty("--font-size", `${metadata.typography.font.size}px`);
document.documentElement.style.setProperty(
"--line-height",
`${metadata.typography.lineHeight}`,
);
document.documentElement.style.setProperty("--color-foreground", metadata.theme.text);
document.documentElement.style.setProperty("--color-primary", metadata.theme.primary);
document.documentElement.style.setProperty("--color-background", metadata.theme.background);
}, [metadata]);
// Typography Options
useEffect(() => {
// eslint-disable-next-line unicorn/prefer-spread
const elements = Array.from(document.querySelectorAll(`[data-page]`));
for (const el of elements) {
el.classList.toggle("hide-icons", metadata.typography.hideIcons);
el.classList.toggle("underline-links", metadata.typography.underlineLinks);
}
}, [metadata]);
return (
<>
<Helmet>
<title>{name} | Reactive Resume</title>
{metadata.css.visible && (
<style id="custom-css" lang="css">
{metadata.css.value}
</style>
)}
</Helmet>
<Outlet />
</>
);
};
+84
View File
@@ -0,0 +1,84 @@
import type { SectionKey } from "@reactive-resume/schema";
import type { Template } from "@reactive-resume/utils";
import { pageSizeMap } from "@reactive-resume/utils";
import { AnimatePresence, motion } from "framer-motion";
import { useEffect, useMemo, useRef, useState } from "react";
import type { ReactZoomPanPinchRef } from "react-zoom-pan-pinch";
import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
import { MM_TO_PX, Page } from "../components/page";
import { useArtboardStore } from "../store/artboard";
import { getTemplate } from "../templates";
export const BuilderLayout = () => {
const [wheelPanning, setWheelPanning] = useState(true);
const transformRef = useRef<ReactZoomPanPinchRef>(null);
const layout = useArtboardStore((state) => state.resume.metadata.layout);
const format = useArtboardStore((state) => state.resume.metadata.page.format);
const template = useArtboardStore((state) => state.resume.metadata.template as Template);
const Template = useMemo(() => getTemplate(template), [template]);
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
if (event.origin !== window.location.origin) return;
if (event.data.type === "ZOOM_IN") transformRef.current?.zoomIn(0.2);
if (event.data.type === "ZOOM_OUT") transformRef.current?.zoomOut(0.2);
if (event.data.type === "CENTER_VIEW") transformRef.current?.centerView();
if (event.data.type === "RESET_VIEW") {
transformRef.current?.resetTransform(0);
setTimeout(() => transformRef.current?.centerView(0.8, 0), 10);
}
if (event.data.type === "TOGGLE_PAN_MODE") {
setWheelPanning(event.data.panMode);
}
};
window.addEventListener("message", handleMessage);
return () => {
window.removeEventListener("message", handleMessage);
};
}, [transformRef]);
return (
<TransformWrapper
ref={transformRef}
centerOnInit
maxScale={2}
minScale={0.4}
initialScale={0.8}
limitToBounds={false}
wheel={{ wheelDisabled: wheelPanning }}
panning={{ wheelPanning: wheelPanning }}
>
<TransformComponent
wrapperClass="!w-screen !h-screen"
contentClass="grid items-start justify-center space-x-12 pointer-events-none"
contentStyle={{
width: `${layout.length * (pageSizeMap[format].width * MM_TO_PX + 42)}px`,
gridTemplateColumns: `repeat(${layout.length}, 1fr)`,
}}
>
<AnimatePresence>
{layout.map((columns, pageIndex) => (
<motion.div
key={pageIndex}
layout
initial={{ opacity: 0, x: -200, y: 0 }}
animate={{ opacity: 1, x: 0, transition: { delay: pageIndex * 0.3 } }}
exit={{ opacity: 0, x: -200 }}
>
<Page mode="builder" pageNumber={pageIndex + 1}>
<Template isFirstPage={pageIndex === 0} columns={columns as SectionKey[][]} />
</Page>
</motion.div>
))}
</AnimatePresence>
</TransformComponent>
</TransformWrapper>
);
};
+24
View File
@@ -0,0 +1,24 @@
import type { SectionKey } from "@reactive-resume/schema";
import type { Template } from "@reactive-resume/utils";
import { useMemo } from "react";
import { Page } from "../components/page";
import { useArtboardStore } from "../store/artboard";
import { getTemplate } from "../templates";
export const PreviewLayout = () => {
const layout = useArtboardStore((state) => state.resume.metadata.layout);
const template = useArtboardStore((state) => state.resume.metadata.template as Template);
const Template = useMemo(() => getTemplate(template), [template]);
return (
<>
{layout.map((columns, pageIndex) => (
<Page key={pageIndex} mode="preview" pageNumber={pageIndex + 1}>
<Template isFirstPage={pageIndex === 0} columns={columns as SectionKey[][]} />
</Page>
))}
</>
);
};
+39
View File
@@ -0,0 +1,39 @@
import { useEffect } from "react";
import { HelmetProvider } from "react-helmet-async";
import { Outlet } from "react-router";
import { helmetContext } from "../constants/helmet";
import { useArtboardStore } from "../store/artboard";
export const Providers = () => {
const resume = useArtboardStore((state) => state.resume);
const setResume = useArtboardStore((state) => state.setResume);
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
if (event.origin !== window.location.origin) return;
if (event.data.type === "SET_RESUME") setResume(event.data.payload);
};
window.addEventListener("message", handleMessage, false);
return () => {
window.removeEventListener("message", handleMessage, false);
};
}, []);
useEffect(() => {
const resumeData = window.localStorage.getItem("resume");
if (resumeData) setResume(JSON.parse(resumeData));
}, [window.localStorage.getItem("resume")]);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!resume) return null;
return (
<HelmetProvider context={helmetContext}>
<Outlet />
</HelmetProvider>
);
};
+17
View File
@@ -0,0 +1,17 @@
import { createBrowserRouter, createRoutesFromChildren, Route } from "react-router";
import { ArtboardPage } from "../pages/artboard";
import { BuilderLayout } from "../pages/builder";
import { PreviewLayout } from "../pages/preview";
import { Providers } from "../providers";
export const routes = createRoutesFromChildren(
<Route element={<Providers />}>
<Route path="artboard" element={<ArtboardPage />}>
<Route path="builder" element={<BuilderLayout />} />
<Route path="preview" element={<PreviewLayout />} />
</Route>
</Route>,
);
export const router = createBrowserRouter(routes);
+14
View File
@@ -0,0 +1,14 @@
import type { ResumeData } from "@reactive-resume/schema";
import { create } from "zustand";
export type ArtboardStore = {
resume: ResumeData;
setResume: (resume: ResumeData) => void;
};
export const useArtboardStore = create<ArtboardStore>()((set) => ({
resume: null as unknown as ResumeData,
setResume: (resume) => {
set({ resume });
},
}));
+29
View File
@@ -0,0 +1,29 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
* {
font-variant-ligatures: none;
@apply border-current;
}
body {
overflow: hidden;
}
#root {
@apply antialiased;
}
[data-page].hide-icons .ph {
@apply hidden;
}
[data-page].underline-links a {
@apply underline underline-offset-2;
}
.wysiwyg {
@apply prose-foreground prose max-w-none prose-headings:mb-2 prose-headings:mt-0 prose-p:mb-2 prose-p:mt-0 prose-p:leading-normal prose-a:break-all prose-ol:mb-2 prose-ol:mt-0 prose-ul:mb-2 prose-ul:mt-0 prose-li:mb-2 prose-li:mt-0 prose-li:leading-normal prose-img:mb-2 prose-img:mt-0 prose-hr:mb-2 prose-hr:mt-0;
}
+576
View File
@@ -0,0 +1,576 @@
import type {
Award,
Certification,
CustomSection,
CustomSectionGroup,
Interest,
Language,
Profile,
Project,
Publication,
Reference,
SectionKey,
SectionWithItem,
Skill,
URL,
} from "@reactive-resume/schema";
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl, linearTransform, sanitize } from "@reactive-resume/utils";
import get from "lodash.get";
import React, { Fragment } from "react";
import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture";
import { useArtboardStore } from "../store/artboard";
import type { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
return (
<div className="flex flex-col items-center justify-center space-y-2 pb-2 text-center">
<Picture />
<div>
<div className="text-2xl font-bold">{basics.name}</div>
<div className="text-base">{basics.headline}</div>
</div>
<div className="flex flex-wrap items-center gap-x-3 gap-y-0.5 text-sm">
{basics.location && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-map-pin text-primary" />
<div>{basics.location}</div>
</div>
)}
{basics.phone && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-phone text-primary" />
<a href={`tel:${basics.phone}`} target="_blank" rel="noreferrer">
{basics.phone}
</a>
</div>
)}
{basics.email && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-at text-primary" />
<a href={`mailto:${basics.email}`} target="_blank" rel="noreferrer">
{basics.email}
</a>
</div>
)}
<Link url={basics.url} />
{basics.customFields.map((item) => (
<div key={item.id} className="flex items-center gap-x-1.5">
<i className={cn(`ph ph-bold ph-${item.icon}`, "text-primary")} />
{isUrl(item.value) ? (
<a href={item.value} target="_blank" rel="noreferrer noopener nofollow">
{item.name || item.value}
</a>
) : (
<span>{[item.name, item.value].filter(Boolean).join(": ")}</span>
)}
</div>
))}
</div>
</div>
);
};
const Summary = () => {
const section = useArtboardStore((state) => state.resume.sections.summary);
if (!section.visible || isEmptyString(section.content)) return null;
return (
<section id={section.id}>
<div className="mb-2 hidden font-bold text-primary group-[.main]:block">
<h4>{section.name}</h4>
</div>
<div className="mb-2 hidden items-center gap-x-2 text-center font-bold text-primary group-[.sidebar]:flex">
<div className="size-1.5 rounded-full border border-primary" />
<h4>{section.name}</h4>
<div className="size-1.5 rounded-full border border-primary" />
</div>
<main className={cn("relative space-y-2", "border-l border-primary pl-4")}>
<div className="absolute left-[-4.5px] top-[8px] hidden size-[8px] rounded-full bg-primary group-[.main]:block" />
<div
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }}
className="wysiwyg"
/>
</main>
</section>
);
};
type RatingProps = { level: number };
const Rating = ({ level }: RatingProps) => (
<div className="relative h-1 w-[128px] group-[.sidebar]:mx-auto">
<div className="absolute inset-0 h-1 w-[128px] rounded bg-primary opacity-25" />
<div
className="absolute inset-0 h-1 rounded bg-primary"
style={{ width: linearTransform(level, 0, 5, 0, 128) }}
/>
</div>
);
type LinkProps = {
url: URL;
icon?: React.ReactNode;
iconOnRight?: boolean;
label?: string;
className?: string;
};
const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
if (!isUrl(url.href)) return null;
return (
<div className="flex items-center gap-x-1.5 break-all">
{!iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
<a
href={url.href}
target="_blank"
rel="noreferrer noopener nofollow"
className={cn("line-clamp-1 max-w-fit", className)}
>
{label ?? (url.label || url.href)}
</a>
{iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
</div>
);
};
type LinkedEntityProps = {
name: string;
url: URL;
separateLinks: boolean;
className?: string;
};
const LinkedEntity = ({ name, url, separateLinks, className }: LinkedEntityProps) => {
return !separateLinks && isUrl(url.href) ? (
<Link
url={url}
label={name}
icon={<i className="ph ph-bold ph-globe text-primary" />}
iconOnRight={true}
className={className}
/>
) : (
<div className={className}>{name}</div>
);
};
type SectionProps<T> = {
section: SectionWithItem<T> | CustomSectionGroup;
children?: (item: T) => React.ReactNode;
className?: string;
urlKey?: keyof T;
levelKey?: keyof T;
summaryKey?: keyof T;
keywordsKey?: keyof T;
};
const Section = <T,>({
section,
children,
className,
urlKey,
levelKey,
summaryKey,
keywordsKey,
}: SectionProps<T>) => {
if (!section.visible || section.items.filter((item) => item.visible).length === 0) return null;
return (
<section id={section.id} className="grid">
<div className="mb-2 hidden font-bold text-primary group-[.main]:block">
<h4>{section.name}</h4>
</div>
<div className="mx-auto mb-2 hidden items-center gap-x-2 text-center font-bold text-primary group-[.sidebar]:flex">
<div className="size-1.5 rounded-full border border-primary" />
<h4>{section.name}</h4>
<div className="size-1.5 rounded-full border border-primary" />
</div>
<div
className="grid gap-x-6 gap-y-3 group-[.sidebar]:mx-auto group-[.sidebar]:text-center"
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
>
{section.items
.filter((item) => item.visible)
.map((item) => {
const url = (urlKey && get(item, urlKey)) as URL | undefined;
const level = (levelKey && get(item, levelKey, 0)) as number | undefined;
const summary = (summaryKey && get(item, summaryKey, "")) as string | undefined;
const keywords = (keywordsKey && get(item, keywordsKey, [])) as string[] | undefined;
return (
<div
key={item.id}
className={cn(
"relative space-y-2",
"border-primary group-[.main]:border-l group-[.main]:pl-4",
className,
)}
>
<div>{children?.(item as T)}</div>
{summary !== undefined && !isEmptyString(summary) && (
<div
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg"
/>
)}
{level !== undefined && level > 0 && <Rating level={level} />}
{keywords !== undefined && keywords.length > 0 && (
<p className="text-sm">{keywords.join(", ")}</p>
)}
{url !== undefined && section.separateLinks && <Link url={url} />}
<div className="absolute left-[-4.5px] top-px hidden size-[8px] rounded-full bg-primary group-[.main]:block" />
</div>
);
})}
</div>
</section>
);
};
const Profiles = () => {
const section = useArtboardStore((state) => state.resume.sections.profiles);
return (
<Section<Profile> section={section}>
{(item) => (
<div>
{isUrl(item.url.href) ? (
<Link url={item.url} label={item.username} icon={<BrandIcon slug={item.icon} />} />
) : (
<p>{item.username}</p>
)}
{!item.icon && <p className="text-sm">{item.network}</p>}
</div>
)}
</Section>
);
};
const Experience = () => {
const section = useArtboardStore((state) => state.resume.sections.experience);
return (
<Section<Experience> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.company}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.position}</div>
<div>{item.location}</div>
<div className="font-bold">{item.date}</div>
</div>
)}
</Section>
);
};
const Education = () => {
const section = useArtboardStore((state) => state.resume.sections.education);
return (
<Section<Education> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.institution}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.area}</div>
<div>{item.score}</div>
<div>{item.studyType}</div>
<div className="font-bold">{item.date}</div>
</div>
)}
</Section>
);
};
const Awards = () => {
const section = useArtboardStore((state) => state.resume.sections.awards);
return (
<Section<Award> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<div className="font-bold">{item.title}</div>
<LinkedEntity name={item.awarder} url={item.url} separateLinks={section.separateLinks} />
<div className="font-bold">{item.date}</div>
</div>
)}
</Section>
);
};
const Certifications = () => {
const section = useArtboardStore((state) => state.resume.sections.certifications);
return (
<Section<Certification> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<div className="font-bold">{item.name}</div>
<LinkedEntity name={item.issuer} url={item.url} separateLinks={section.separateLinks} />
<div className="font-bold">{item.date}</div>
</div>
)}
</Section>
);
};
const Skills = () => {
const section = useArtboardStore((state) => state.resume.sections.skills);
return (
<Section<Skill> section={section} levelKey="level" keywordsKey="keywords">
{(item) => (
<div>
<div className="font-bold">{item.name}</div>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Interests = () => {
const section = useArtboardStore((state) => state.resume.sections.interests);
return (
<Section<Interest> section={section} keywordsKey="keywords" className="space-y-0.5">
{(item) => <div className="font-bold">{item.name}</div>}
</Section>
);
};
const Publications = () => {
const section = useArtboardStore((state) => state.resume.sections.publications);
return (
<Section<Publication> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.publisher}</div>
<div className="font-bold">{item.date}</div>
</div>
)}
</Section>
);
};
const Volunteer = () => {
const section = useArtboardStore((state) => state.resume.sections.volunteer);
return (
<Section<Volunteer> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.organization}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.position}</div>
<div>{item.location}</div>
<div className="font-bold">{item.date}</div>
</div>
)}
</Section>
);
};
const Languages = () => {
const section = useArtboardStore((state) => state.resume.sections.languages);
return (
<Section<Language> section={section} levelKey="level">
{(item) => (
<div>
<div className="font-bold">{item.name}</div>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Projects = () => {
const section = useArtboardStore((state) => state.resume.sections.projects);
return (
<Section<Project> section={section} urlKey="url" summaryKey="summary" keywordsKey="keywords">
{(item) => (
<div>
<div>
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const References = () => {
const section = useArtboardStore((state) => state.resume.sections.references);
return (
<Section<Reference> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Custom = ({ id }: { id: string }) => {
const section = useArtboardStore((state) => state.resume.sections.custom[id]);
return (
<Section<CustomSection>
section={section}
urlKey="url"
summaryKey="summary"
keywordsKey="keywords"
>
{(item) => (
<div>
<div>
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
<div className="font-bold">{item.date}</div>
<div>{item.location}</div>
</div>
</div>
)}
</Section>
);
};
const mapSectionToComponent = (section: SectionKey) => {
switch (section) {
case "profiles": {
return <Profiles />;
}
case "summary": {
return <Summary />;
}
case "experience": {
return <Experience />;
}
case "education": {
return <Education />;
}
case "awards": {
return <Awards />;
}
case "certifications": {
return <Certifications />;
}
case "skills": {
return <Skills />;
}
case "interests": {
return <Interests />;
}
case "publications": {
return <Publications />;
}
case "volunteer": {
return <Volunteer />;
}
case "languages": {
return <Languages />;
}
case "projects": {
return <Projects />;
}
case "references": {
return <References />;
}
default: {
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
return null;
}
}
};
export const Azurill = ({ columns, isFirstPage = false }: TemplateProps) => {
const [main, sidebar] = columns;
return (
<div className="space-y-3 p-custom">
{isFirstPage && <Header />}
<div className="grid grid-cols-3 gap-x-4">
<div className="sidebar group space-y-4">
{sidebar.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}
</div>
<div
className={cn("main group space-y-4", sidebar.length > 0 ? "col-span-2" : "col-span-3")}
>
{main.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}
</div>
</div>
</div>
);
};
+585
View File
@@ -0,0 +1,585 @@
import type {
Award,
Certification,
CustomSection,
CustomSectionGroup,
Interest,
Language,
Profile,
Project,
Publication,
Reference,
SectionKey,
SectionWithItem,
Skill,
URL,
} from "@reactive-resume/schema";
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get";
import { Fragment } from "react";
import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture";
import { useArtboardStore } from "../store/artboard";
import type { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
return (
<div className="flex flex-col items-center space-y-2 text-center">
<Picture />
<div>
<div className="text-2xl font-bold">{basics.name}</div>
<div className="text-base">{basics.headline}</div>
</div>
<div className="flex flex-wrap items-center gap-x-2 gap-y-0.5 text-sm">
{basics.location && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-map-pin text-primary" />
<div>{basics.location}</div>
</div>
)}
{basics.phone && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-phone text-primary" />
<a href={`tel:${basics.phone}`} target="_blank" rel="noreferrer">
{basics.phone}
</a>
</div>
)}
{basics.email && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-at text-primary" />
<a href={`mailto:${basics.email}`} target="_blank" rel="noreferrer">
{basics.email}
</a>
</div>
)}
<Link url={basics.url} />
{basics.customFields.map((item) => (
<div key={item.id} className="flex items-center gap-x-1.5">
<i className={cn(`ph ph-bold ph-${item.icon}`, "text-primary")} />
{isUrl(item.value) ? (
<a href={item.value} target="_blank" rel="noreferrer noopener nofollow">
{item.name || item.value}
</a>
) : (
<span>{[item.name, item.value].filter(Boolean).join(": ")}</span>
)}
</div>
))}
</div>
</div>
);
};
const Summary = () => {
const section = useArtboardStore((state) => state.resume.sections.summary);
if (!section.visible || isEmptyString(section.content)) return null;
return (
<section id={section.id} className="grid grid-cols-5 border-t pt-2.5">
<div>
<h4 className="text-base font-bold">{section.name}</h4>
</div>
<div
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }}
className="wysiwyg col-span-4"
/>
</section>
);
};
type RatingProps = { level: number };
const Rating = ({ level }: RatingProps) => (
<div className="flex items-center gap-x-1.5">
{Array.from({ length: 5 }).map((_, index) => (
<div
key={index}
className={cn("size-2 rounded-full border border-primary", level > index && "bg-primary")}
/>
))}
</div>
);
type LinkProps = {
url: URL;
icon?: React.ReactNode;
iconOnRight?: boolean;
label?: string;
className?: string;
};
const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
if (!isUrl(url.href)) return null;
return (
<div className="flex items-center gap-x-1.5 break-all">
{!iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
<a
href={url.href}
target="_blank"
rel="noreferrer noopener nofollow"
className={cn("line-clamp-1 max-w-fit", className)}
>
{label ?? (url.label || url.href)}
</a>
{iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
</div>
);
};
type LinkedEntityProps = {
name: string;
url: URL;
separateLinks: boolean;
className?: string;
};
const LinkedEntity = ({ name, url, separateLinks, className }: LinkedEntityProps) => {
return !separateLinks && isUrl(url.href) ? (
<Link
url={url}
label={name}
icon={<i className="ph ph-bold ph-globe text-primary" />}
iconOnRight={true}
className={className}
/>
) : (
<div className={className}>{name}</div>
);
};
type SectionProps<T> = {
section: SectionWithItem<T> | CustomSectionGroup;
children?: (item: T) => React.ReactNode;
className?: string;
urlKey?: keyof T;
levelKey?: keyof T;
summaryKey?: keyof T;
keywordsKey?: keyof T;
};
const Section = <T,>({
section,
children,
className,
urlKey,
levelKey,
summaryKey,
keywordsKey,
}: SectionProps<T>) => {
if (!section.visible || section.items.filter((item) => item.visible).length === 0) return null;
return (
<section id={section.id} className="grid grid-cols-5 border-t pt-2.5">
<div>
<h4 className="text-base font-bold">{section.name}</h4>
</div>
<div
className="col-span-4 grid gap-x-6 gap-y-3"
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
>
{section.items
.filter((item) => item.visible)
.map((item) => {
const url = (urlKey && get(item, urlKey)) as URL | undefined;
const level = (levelKey && get(item, levelKey, 0)) as number | undefined;
const summary = (summaryKey && get(item, summaryKey, "")) as string | undefined;
const keywords = (keywordsKey && get(item, keywordsKey, [])) as string[] | undefined;
return (
<div key={item.id} className={cn("space-y-2", className)}>
<div>
{children?.(item as T)}
{url !== undefined && section.separateLinks && <Link url={url} />}
</div>
{summary !== undefined && !isEmptyString(summary) && (
<div
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg"
/>
)}
{level !== undefined && level > 0 && <Rating level={level} />}
{keywords !== undefined && keywords.length > 0 && (
<p className="text-sm">{keywords.join(", ")}</p>
)}
</div>
);
})}
</div>
</section>
);
};
const Profiles = () => {
const section = useArtboardStore((state) => state.resume.sections.profiles);
return (
<Section<Profile> section={section}>
{(item) => (
<div>
{isUrl(item.url.href) ? (
<Link url={item.url} label={item.username} icon={<BrandIcon slug={item.icon} />} />
) : (
<p>{item.username}</p>
)}
{!item.icon && <p className="text-sm">{item.network}</p>}
</div>
)}
</Section>
);
};
const Experience = () => {
const section = useArtboardStore((state) => state.resume.sections.experience);
return (
<Section<Experience> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between">
<div className="text-left">
<LinkedEntity
name={item.company}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.position}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
<div>{item.location}</div>
</div>
</div>
)}
</Section>
);
};
const Education = () => {
const section = useArtboardStore((state) => state.resume.sections.education);
return (
<Section<Education> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between">
<div className="text-left">
<LinkedEntity
name={item.institution}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.area}</div>
<div>{item.score}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
<div>{item.studyType}</div>
</div>
</div>
)}
</Section>
);
};
const Awards = () => {
const section = useArtboardStore((state) => state.resume.sections.awards);
return (
<Section<Award> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between">
<div className="text-left">
<div className="font-bold">{item.title}</div>
<LinkedEntity
name={item.awarder}
url={item.url}
separateLinks={section.separateLinks}
/>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const Certifications = () => {
const section = useArtboardStore((state) => state.resume.sections.certifications);
return (
<Section<Certification> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between">
<div className="text-left">
<div className="font-bold">{item.name}</div>
<LinkedEntity name={item.issuer} url={item.url} separateLinks={section.separateLinks} />
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const Skills = () => {
const section = useArtboardStore((state) => state.resume.sections.skills);
return (
<Section<Skill> section={section} levelKey="level" keywordsKey="keywords">
{(item) => (
<div className="space-y-0.5">
<div className="font-bold">{item.name}</div>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Interests = () => {
const section = useArtboardStore((state) => state.resume.sections.interests);
return (
<Section<Interest> section={section} keywordsKey="keywords" className="space-y-0.5">
{(item) => <div className="font-bold">{item.name}</div>}
</Section>
);
};
const Publications = () => {
const section = useArtboardStore((state) => state.resume.sections.publications);
return (
<Section<Publication> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between">
<div className="text-left">
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.publisher}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const Volunteer = () => {
const section = useArtboardStore((state) => state.resume.sections.volunteer);
return (
<Section<Volunteer> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between">
<div className="text-left">
<LinkedEntity
name={item.organization}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.position}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
<div>{item.location}</div>
</div>
</div>
)}
</Section>
);
};
const Languages = () => {
const section = useArtboardStore((state) => state.resume.sections.languages);
return (
<Section<Language> section={section} levelKey="level">
{(item) => (
<div className="space-y-0.5">
<div className="font-bold">{item.name}</div>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Projects = () => {
const section = useArtboardStore((state) => state.resume.sections.projects);
return (
<Section<Project> section={section} urlKey="url" summaryKey="summary" keywordsKey="keywords">
{(item) => (
<div className="flex items-start justify-between">
<div className="text-left">
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const References = () => {
const section = useArtboardStore((state) => state.resume.sections.references);
return (
<Section<Reference> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Custom = ({ id }: { id: string }) => {
const section = useArtboardStore((state) => state.resume.sections.custom[id]);
return (
<Section<CustomSection>
section={section}
urlKey="url"
summaryKey="summary"
keywordsKey="keywords"
>
{(item) => (
<div className="flex items-start justify-between">
<div className="text-left">
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
<div>{item.location}</div>
</div>
</div>
)}
</Section>
);
};
const mapSectionToComponent = (section: SectionKey) => {
switch (section) {
case "profiles": {
return <Profiles />;
}
case "summary": {
return <Summary />;
}
case "experience": {
return <Experience />;
}
case "education": {
return <Education />;
}
case "awards": {
return <Awards />;
}
case "certifications": {
return <Certifications />;
}
case "skills": {
return <Skills />;
}
case "interests": {
return <Interests />;
}
case "publications": {
return <Publications />;
}
case "volunteer": {
return <Volunteer />;
}
case "languages": {
return <Languages />;
}
case "projects": {
return <Projects />;
}
case "references": {
return <References />;
}
default: {
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
return null;
}
}
};
export const Bronzor = ({ columns, isFirstPage = false }: TemplateProps) => {
const [main, sidebar] = columns;
return (
<div className="space-y-4 p-custom">
{isFirstPage && <Header />}
<div className="space-y-4">
{main.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}
{sidebar.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}
</div>
</div>
);
};
+597
View File
@@ -0,0 +1,597 @@
import type {
Award,
Certification,
CustomSection,
CustomSectionGroup,
Interest,
Language,
Profile,
Project,
Publication,
Reference,
SectionKey,
SectionWithItem,
Skill,
URL,
} from "@reactive-resume/schema";
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get";
import { Fragment } from "react";
import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture";
import { useArtboardStore } from "../store/artboard";
import type { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
return (
<div className="flex items-center space-x-4">
<Picture />
<div className="space-y-2">
<div>
<div className="text-2xl font-bold">{basics.name}</div>
<div className="text-base">{basics.headline}</div>
</div>
<div className="flex flex-wrap items-center gap-x-2 gap-y-0.5 text-sm">
{basics.location && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-map-pin text-primary" />
<div>{basics.location}</div>
</div>
)}
{basics.phone && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-phone text-primary" />
<a href={`tel:${basics.phone}`} target="_blank" rel="noreferrer">
{basics.phone}
</a>
</div>
)}
{basics.email && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-at text-primary" />
<a href={`mailto:${basics.email}`} target="_blank" rel="noreferrer">
{basics.email}
</a>
</div>
)}
<Link url={basics.url} />
{basics.customFields.map((item) => (
<div key={item.id} className="flex items-center gap-x-1.5">
<i className={cn(`ph ph-bold ph-${item.icon}`, "text-primary")} />
{isUrl(item.value) ? (
<a href={item.value} target="_blank" rel="noreferrer noopener nofollow">
{item.name || item.value}
</a>
) : (
<span>{[item.name, item.value].filter(Boolean).join(": ")}</span>
)}
</div>
))}
</div>
</div>
</div>
);
};
const Summary = () => {
const section = useArtboardStore((state) => state.resume.sections.summary);
if (!section.visible || isEmptyString(section.content)) return null;
return (
<section id={section.id}>
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
<div
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }}
className="wysiwyg"
/>
</section>
);
};
type RatingProps = { level: number };
const Rating = ({ level }: RatingProps) => (
<div className="flex items-center gap-x-1.5">
{Array.from({ length: 5 }).map((_, index) => (
<div
key={index}
className={cn(
"size-2 rounded-full border border-primary group-[.sidebar]:border-background",
level > index && "bg-primary group-[.sidebar]:bg-background",
)}
/>
))}
</div>
);
type LinkProps = {
url: URL;
icon?: React.ReactNode;
iconOnRight?: boolean;
label?: string;
className?: string;
};
const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
if (!isUrl(url.href)) return null;
return (
<div className="flex items-center gap-x-1.5 break-all">
{!iconOnRight &&
(icon ?? <i className="ph ph-bold ph-link text-primary group-[.sidebar]:text-white" />)}
<a
href={url.href}
target="_blank"
rel="noreferrer noopener nofollow"
className={cn("line-clamp-1 max-w-fit", className)}
>
{label ?? (url.label || url.href)}
</a>
{iconOnRight &&
(icon ?? <i className="ph ph-bold ph-link text-primary group-[.sidebar]:text-white" />)}
</div>
);
};
type LinkedEntityProps = {
name: string;
url: URL;
separateLinks: boolean;
className?: string;
};
const LinkedEntity = ({ name, url, separateLinks, className }: LinkedEntityProps) => {
return !separateLinks && isUrl(url.href) ? (
<Link
url={url}
label={name}
icon={<i className="ph ph-bold ph-globe text-primary" />}
iconOnRight={true}
className={className}
/>
) : (
<div className={className}>{name}</div>
);
};
type SectionProps<T> = {
section: SectionWithItem<T> | CustomSectionGroup;
children?: (item: T) => React.ReactNode;
className?: string;
urlKey?: keyof T;
levelKey?: keyof T;
summaryKey?: keyof T;
keywordsKey?: keyof T;
};
const Section = <T,>({
section,
children,
className,
urlKey,
levelKey,
summaryKey,
keywordsKey,
}: SectionProps<T>) => {
if (!section.visible || section.items.filter((item) => item.visible).length === 0) return null;
return (
<section id={section.id} className="grid">
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
<div
className="grid gap-x-6 gap-y-3"
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
>
{section.items
.filter((item) => item.visible)
.map((item) => {
const url = (urlKey && get(item, urlKey)) as URL | undefined;
const level = (levelKey && get(item, levelKey, 0)) as number | undefined;
const summary = (summaryKey && get(item, summaryKey, "")) as string | undefined;
const keywords = (keywordsKey && get(item, keywordsKey, [])) as string[] | undefined;
return (
<div key={item.id} className={cn("space-y-2", className)}>
<div>
{children?.(item as T)}
{url !== undefined && section.separateLinks && <Link url={url} />}
</div>
{summary !== undefined && !isEmptyString(summary) && (
<div
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg group-[.sidebar]:prose-invert"
/>
)}
{level !== undefined && level > 0 && <Rating level={level} />}
{keywords !== undefined && keywords.length > 0 && (
<p className="text-sm">{keywords.join(", ")}</p>
)}
</div>
);
})}
</div>
</section>
);
};
const Experience = () => {
const section = useArtboardStore((state) => state.resume.sections.experience);
return (
<Section<Experience> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.company}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.position}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
<div>{item.location}</div>
</div>
</div>
)}
</Section>
);
};
const Education = () => {
const section = useArtboardStore((state) => state.resume.sections.education);
return (
<Section<Education> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.institution}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.area}</div>
<div>{item.score}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
<div>{item.studyType}</div>
</div>
</div>
)}
</Section>
);
};
const Profiles = () => {
const section = useArtboardStore((state) => state.resume.sections.profiles);
return (
<Section<Profile> section={section}>
{(item) => (
<div>
{isUrl(item.url.href) ? (
<Link url={item.url} label={item.username} icon={<BrandIcon slug={item.icon} />} />
) : (
<p>{item.username}</p>
)}
{!item.icon && <p className="text-sm">{item.network}</p>}
</div>
)}
</Section>
);
};
const Awards = () => {
const section = useArtboardStore((state) => state.resume.sections.awards);
return (
<Section<Award> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<div className="font-bold">{item.title}</div>
<LinkedEntity
name={item.awarder}
url={item.url}
separateLinks={section.separateLinks}
/>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const Certifications = () => {
const section = useArtboardStore((state) => state.resume.sections.certifications);
return (
<Section<Certification> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<div className="font-bold">{item.name}</div>
<LinkedEntity name={item.issuer} url={item.url} separateLinks={section.separateLinks} />
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const Skills = () => {
const section = useArtboardStore((state) => state.resume.sections.skills);
return (
<Section<Skill> section={section} levelKey="level" keywordsKey="keywords">
{(item) => (
<div>
<div className="font-bold">{item.name}</div>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Interests = () => {
const section = useArtboardStore((state) => state.resume.sections.interests);
return (
<Section<Interest> section={section} keywordsKey="keywords" className="space-y-0.5">
{(item) => <div className="font-bold">{item.name}</div>}
</Section>
);
};
const Publications = () => {
const section = useArtboardStore((state) => state.resume.sections.publications);
return (
<Section<Publication> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.publisher}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const Volunteer = () => {
const section = useArtboardStore((state) => state.resume.sections.volunteer);
return (
<Section<Volunteer> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.organization}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.position}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
<div>{item.location}</div>
</div>
</div>
)}
</Section>
);
};
const Languages = () => {
const section = useArtboardStore((state) => state.resume.sections.languages);
return (
<Section<Language> section={section} levelKey="level">
{(item) => (
<div className="space-y-0.5">
<div className="font-bold">{item.name}</div>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Projects = () => {
const section = useArtboardStore((state) => state.resume.sections.projects);
return (
<Section<Project> section={section} urlKey="url" summaryKey="summary" keywordsKey="keywords">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const References = () => {
const section = useArtboardStore((state) => state.resume.sections.references);
return (
<Section<Reference> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Custom = ({ id }: { id: string }) => {
const section = useArtboardStore((state) => state.resume.sections.custom[id]);
return (
<Section<CustomSection>
section={section}
urlKey="url"
summaryKey="summary"
keywordsKey="keywords"
>
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
<div>{item.location}</div>
</div>
</div>
)}
</Section>
);
};
const mapSectionToComponent = (section: SectionKey) => {
switch (section) {
case "profiles": {
return <Profiles />;
}
case "summary": {
return <Summary />;
}
case "experience": {
return <Experience />;
}
case "education": {
return <Education />;
}
case "awards": {
return <Awards />;
}
case "certifications": {
return <Certifications />;
}
case "skills": {
return <Skills />;
}
case "interests": {
return <Interests />;
}
case "publications": {
return <Publications />;
}
case "volunteer": {
return <Volunteer />;
}
case "languages": {
return <Languages />;
}
case "projects": {
return <Projects />;
}
case "references": {
return <References />;
}
default: {
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
return null;
}
}
};
export const Chikorita = ({ columns, isFirstPage = false }: TemplateProps) => {
const [main, sidebar] = columns;
return (
<div className="grid min-h-[inherit] grid-cols-3">
<div
className={cn(
"main group space-y-4 p-custom",
sidebar.length > 0 ? "col-span-2" : "col-span-3",
)}
>
{isFirstPage && <Header />}
{main.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}
</div>
<div
className={cn(
"sidebar group h-full space-y-4 bg-primary p-custom text-background",
sidebar.length === 0 && "hidden",
)}
>
{sidebar.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}
</div>
</div>
);
};
+626
View File
@@ -0,0 +1,626 @@
import type {
Award,
Certification,
CustomSection,
CustomSectionGroup,
Interest,
Language,
Profile,
Project,
Publication,
Reference,
SectionKey,
SectionWithItem,
Skill,
URL,
} from "@reactive-resume/schema";
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get";
import { Fragment } from "react";
import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture";
import { useArtboardStore } from "../store/artboard";
import type { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
return (
<div className="relative grid grid-cols-3 space-x-4 p-custom pb-0">
<Picture className="mx-auto" />
<div className="relative z-10 col-span-2 text-background">
<div className="space-y-0.5">
<h2 className="text-3xl font-bold">{basics.name}</h2>
<p>{basics.headline}</p>
</div>
<div className="col-span-2 col-start-2 mt-10 text-foreground">
<div className="flex flex-wrap items-center gap-x-2 gap-y-0.5 text-sm">
{basics.location && (
<>
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-map-pin text-primary" />
<div>{basics.location}</div>
</div>
<div className="bg-text size-1 rounded-full last:hidden" />
</>
)}
{basics.phone && (
<>
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-phone text-primary" />
<a href={`tel:${basics.phone}`} target="_blank" rel="noreferrer">
{basics.phone}
</a>
</div>
<div className="bg-text size-1 rounded-full last:hidden" />
</>
)}
{basics.email && (
<>
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-at text-primary" />
<a href={`mailto:${basics.email}`} target="_blank" rel="noreferrer">
{basics.email}
</a>
</div>
<div className="bg-text size-1 rounded-full last:hidden" />
</>
)}
{isUrl(basics.url.href) && (
<>
<Link url={basics.url} />
<div className="bg-text size-1 rounded-full last:hidden" />
</>
)}
{basics.customFields.map((item) => (
<Fragment key={item.id}>
<div className="flex items-center gap-x-1.5">
<i className={cn(`ph ph-bold ph-${item.icon}`, "text-primary")} />
{isUrl(item.value) ? (
<a href={item.value} target="_blank" rel="noreferrer noopener nofollow">
{item.name || item.value}
</a>
) : (
<span>{[item.name, item.value].filter(Boolean).join(": ")}</span>
)}
</div>
<div className="bg-text size-1 rounded-full last:hidden" />
</Fragment>
))}
</div>
</div>
</div>
</div>
);
};
const Summary = () => {
const section = useArtboardStore((state) => state.resume.sections.summary);
if (!section.visible || isEmptyString(section.content)) return null;
return (
<section id={section.id}>
<h4 className="mb-2 text-base font-bold">{section.name}</h4>
<div
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }}
className="wysiwyg"
/>
</section>
);
};
type RatingProps = { level: number };
const Rating = ({ level }: RatingProps) => (
<div className="flex items-center gap-x-1.5">
{Array.from({ length: 5 }).map((_, index) => (
<div
key={index}
className={cn("h-2 w-4 border border-primary", level > index && "bg-primary")}
/>
))}
</div>
);
type LinkProps = {
url: URL;
icon?: React.ReactNode;
iconOnRight?: boolean;
label?: string;
className?: string;
};
const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
if (!isUrl(url.href)) return null;
return (
<div className="flex items-center gap-x-1.5 break-all">
{!iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
<a
href={url.href}
target="_blank"
rel="noreferrer noopener nofollow"
className={cn("line-clamp-1 max-w-fit", className)}
>
{label ?? (url.label || url.href)}
</a>
{iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
</div>
);
};
type LinkedEntityProps = {
name: string;
url: URL;
separateLinks: boolean;
className?: string;
};
const LinkedEntity = ({ name, url, separateLinks, className }: LinkedEntityProps) => {
return !separateLinks && isUrl(url.href) ? (
<Link
url={url}
label={name}
icon={<i className="ph ph-bold ph-globe text-primary" />}
iconOnRight={true}
className={className}
/>
) : (
<div className={className}>{name}</div>
);
};
type SectionProps<T> = {
section: SectionWithItem<T> | CustomSectionGroup;
children?: (item: T) => React.ReactNode;
className?: string;
urlKey?: keyof T;
levelKey?: keyof T;
summaryKey?: keyof T;
keywordsKey?: keyof T;
};
const Section = <T,>({
section,
children,
className,
urlKey,
levelKey,
summaryKey,
keywordsKey,
}: SectionProps<T>) => {
if (!section.visible || section.items.filter((item) => item.visible).length === 0) return null;
return (
<section id={section.id} className="grid">
<h4 className="mb-2 text-base font-bold">{section.name}</h4>
<div
className="grid gap-x-6 gap-y-3"
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
>
{section.items
.filter((item) => item.visible)
.map((item) => {
const url = (urlKey && get(item, urlKey)) as URL | undefined;
const level = (levelKey && get(item, levelKey, 0)) as number | undefined;
const summary = (summaryKey && get(item, summaryKey, "")) as string | undefined;
const keywords = (keywordsKey && get(item, keywordsKey, [])) as string[] | undefined;
return (
<div
key={item.id}
className={cn("relative space-y-2 pl-4 group-[.sidebar]:pl-0", className)}
>
<div className="relative -ml-4 group-[.sidebar]:ml-0">
<div className="pl-4 group-[.sidebar]:pl-0">
{children?.(item as T)}
{url !== undefined && section.separateLinks && <Link url={url} />}
</div>
<div className="absolute inset-y-0 -left-px border-l-4 border-primary group-[.sidebar]:hidden" />
</div>
{summary !== undefined && !isEmptyString(summary) && (
<div
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg"
/>
)}
{level !== undefined && level > 0 && <Rating level={level} />}
{keywords !== undefined && keywords.length > 0 && (
<p className="text-sm">{keywords.join(", ")}</p>
)}
<div className="absolute inset-y-0 left-0 border-l border-primary group-[.sidebar]:hidden" />
</div>
);
})}
</div>
</section>
);
};
const Profiles = () => {
const section = useArtboardStore((state) => state.resume.sections.profiles);
return (
<Section<Profile> section={section}>
{(item) => (
<div>
{isUrl(item.url.href) ? (
<Link url={item.url} label={item.username} icon={<BrandIcon slug={item.icon} />} />
) : (
<p>{item.username}</p>
)}
{!item.icon && <p className="text-sm">{item.network}</p>}
</div>
)}
</Section>
);
};
const Experience = () => {
const section = useArtboardStore((state) => state.resume.sections.experience);
return (
<Section<Experience> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.company}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.position}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
<div>{item.location}</div>
</div>
</div>
)}
</Section>
);
};
const Education = () => {
const section = useArtboardStore((state) => state.resume.sections.education);
return (
<Section<Education> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.institution}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.area}</div>
<div>{item.score}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
<div>{item.studyType}</div>
</div>
</div>
)}
</Section>
);
};
const Awards = () => {
const section = useArtboardStore((state) => state.resume.sections.awards);
return (
<Section<Award> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<div className="font-bold">{item.title}</div>
<LinkedEntity
name={item.awarder}
url={item.url}
separateLinks={section.separateLinks}
/>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const Certifications = () => {
const section = useArtboardStore((state) => state.resume.sections.certifications);
return (
<Section<Certification> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<div className="font-bold">{item.name}</div>
<LinkedEntity name={item.issuer} url={item.url} separateLinks={section.separateLinks} />
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const Skills = () => {
const section = useArtboardStore((state) => state.resume.sections.skills);
return (
<Section<Skill> section={section} levelKey="level" keywordsKey="keywords">
{(item) => (
<div>
<div className="font-bold">{item.name}</div>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Interests = () => {
const section = useArtboardStore((state) => state.resume.sections.interests);
return (
<Section<Interest> section={section} className="space-y-0" keywordsKey="keywords">
{(item) => <div className="font-bold">{item.name}</div>}
</Section>
);
};
const Publications = () => {
const section = useArtboardStore((state) => state.resume.sections.publications);
return (
<Section<Publication> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.publisher}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const Volunteer = () => {
const section = useArtboardStore((state) => state.resume.sections.volunteer);
return (
<Section<Volunteer> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.organization}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.position}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
<div>{item.location}</div>
</div>
</div>
)}
</Section>
);
};
const Languages = () => {
const section = useArtboardStore((state) => state.resume.sections.languages);
return (
<Section<Language> section={section} levelKey="level">
{(item) => (
<div>
<div className="font-bold">{item.name}</div>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Projects = () => {
const section = useArtboardStore((state) => state.resume.sections.projects);
return (
<Section<Project> section={section} urlKey="url" summaryKey="summary" keywordsKey="keywords">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const References = () => {
const section = useArtboardStore((state) => state.resume.sections.references);
return (
<Section<Reference> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Custom = ({ id }: { id: string }) => {
const section = useArtboardStore((state) => state.resume.sections.custom[id]);
return (
<Section<CustomSection>
section={section}
urlKey="url"
summaryKey="summary"
keywordsKey="keywords"
>
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
<div>{item.location}</div>
</div>
</div>
)}
</Section>
);
};
const mapSectionToComponent = (section: SectionKey) => {
switch (section) {
case "profiles": {
return <Profiles />;
}
case "summary": {
return <Summary />;
}
case "experience": {
return <Experience />;
}
case "education": {
return <Education />;
}
case "awards": {
return <Awards />;
}
case "certifications": {
return <Certifications />;
}
case "skills": {
return <Skills />;
}
case "interests": {
return <Interests />;
}
case "publications": {
return <Publications />;
}
case "volunteer": {
return <Volunteer />;
}
case "languages": {
return <Languages />;
}
case "projects": {
return <Projects />;
}
case "references": {
return <References />;
}
default: {
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
return null;
}
}
};
export const Ditto = ({ columns, isFirstPage = false }: TemplateProps) => {
const [main, sidebar] = columns;
return (
<div>
{isFirstPage && (
<div className="relative">
<Header />
<div className="absolute inset-x-0 top-0 h-[85px] w-full bg-primary" />
</div>
)}
<div className="grid grid-cols-3">
<div className="sidebar group space-y-4 p-custom">
{sidebar.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}
</div>
<div
className={cn(
"main group space-y-4 p-custom",
sidebar.length > 0 ? "col-span-2" : "col-span-3",
)}
>
{main.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}
</div>
</div>
</div>
);
};
+603
View File
@@ -0,0 +1,603 @@
import type {
Award,
Certification,
CustomSection,
CustomSectionGroup,
Interest,
Language,
Profile,
Project,
Publication,
Reference,
SectionKey,
SectionWithItem,
Skill,
URL,
} from "@reactive-resume/schema";
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, hexToRgb, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get";
import { Fragment } from "react";
import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture";
import { useArtboardStore } from "../store/artboard";
import type { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
return (
<div className="space-y-4 bg-primary p-custom text-background">
<Picture className="border-background" />
<div>
<h2 className="text-2xl font-bold">{basics.name}</h2>
<p>{basics.headline}</p>
</div>
<div className="flex flex-col items-start gap-y-2 text-sm">
{basics.location && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-map-pin" />
<div>{basics.location}</div>
</div>
)}
{basics.phone && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-phone" />
<a href={`tel:${basics.phone}`} target="_blank" rel="noreferrer">
{basics.phone}
</a>
</div>
)}
{basics.email && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-at" />
<a href={`mailto:${basics.email}`} target="_blank" rel="noreferrer">
{basics.email}
</a>
</div>
)}
{isUrl(basics.url.href) && <Link url={basics.url} />}
{basics.customFields.map((item) => (
<Fragment key={item.id}>
<div className="flex items-center gap-x-1.5">
<i className={cn(`ph ph-bold ph-${item.icon}`)} />
{isUrl(item.value) ? (
<a href={item.value} target="_blank" rel="noreferrer noopener nofollow">
{item.name || item.value}
</a>
) : (
<span>{[item.name, item.value].filter(Boolean).join(": ")}</span>
)}
</div>
</Fragment>
))}
</div>
</div>
);
};
const Summary = () => {
const section = useArtboardStore((state) => state.resume.sections.summary);
const primaryColor = useArtboardStore((state) => state.resume.metadata.theme.primary);
if (!section.visible || isEmptyString(section.content)) return null;
return (
<div className="space-y-4 p-custom" style={{ backgroundColor: hexToRgb(primaryColor, 0.2) }}>
<section id={section.id}>
<div
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }}
className="wysiwyg"
/>
</section>
</div>
);
};
type RatingProps = { level: number };
const Rating = ({ level }: RatingProps) => (
<div className="flex items-center gap-x-1">
{Array.from({ length: 5 }).map((_, index) => (
<div
key={index}
className={cn("h-2.5 w-5 border border-primary", level > index && "bg-primary")}
/>
))}
</div>
);
type LinkProps = {
url: URL;
icon?: React.ReactNode;
iconOnRight?: boolean;
label?: string;
className?: string;
};
const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
if (!isUrl(url.href)) return null;
return (
<div className="flex items-center gap-x-1.5 break-all">
{!iconOnRight &&
(icon ?? (
<i className="ph ph-bold ph-link text-primary group-[.sidebar]:text-background" />
))}
<a
href={url.href}
target="_blank"
rel="noreferrer noopener nofollow"
className={cn("line-clamp-1 max-w-fit", className)}
>
{label ?? (url.label || url.href)}
</a>
{iconOnRight &&
(icon ?? (
<i className="ph ph-bold ph-link text-primary group-[.sidebar]:text-background" />
))}
</div>
);
};
type LinkedEntityProps = {
name: string;
url: URL;
separateLinks: boolean;
className?: string;
};
const LinkedEntity = ({ name, url, separateLinks, className }: LinkedEntityProps) => {
return !separateLinks && isUrl(url.href) ? (
<Link
url={url}
label={name}
icon={<i className="ph ph-bold ph-globe text-primary group-[.sidebar]:text-primary" />}
iconOnRight={true}
className={className}
/>
) : (
<div className={className}>{name}</div>
);
};
type SectionProps<T> = {
section: SectionWithItem<T> | CustomSectionGroup;
children?: (item: T) => React.ReactNode;
className?: string;
urlKey?: keyof T;
levelKey?: keyof T;
summaryKey?: keyof T;
keywordsKey?: keyof T;
};
const Section = <T,>({
section,
children,
className,
urlKey,
levelKey,
summaryKey,
keywordsKey,
}: SectionProps<T>) => {
if (!section.visible || section.items.filter((item) => item.visible).length === 0) return null;
return (
<section id={section.id} className="my-custom grid px-custom">
<h4 className="mb-2 border-b border-primary text-base font-bold">{section.name}</h4>
<div
className="grid gap-x-6 gap-y-3"
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
>
{section.items
.filter((item) => item.visible)
.map((item) => {
const url = (urlKey && get(item, urlKey)) as URL | undefined;
const level = (levelKey && get(item, levelKey, 0)) as number | undefined;
const summary = (summaryKey && get(item, summaryKey, "")) as string | undefined;
const keywords = (keywordsKey && get(item, keywordsKey, [])) as string[] | undefined;
return (
<div key={item.id} className={cn("space-y-2", className)}>
<div>
{children?.(item as T)}
{url !== undefined && section.separateLinks && <Link url={url} />}
</div>
{summary !== undefined && !isEmptyString(summary) && (
<div
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg"
/>
)}
{level !== undefined && level > 0 && <Rating level={level} />}
{keywords !== undefined && keywords.length > 0 && (
<p className="text-sm">{keywords.join(", ")}</p>
)}
</div>
);
})}
</div>
</section>
);
};
const Profiles = () => {
const section = useArtboardStore((state) => state.resume.sections.profiles);
return (
<Section<Profile> section={section}>
{(item) => (
<div>
{isUrl(item.url.href) ? (
<Link url={item.url} label={item.username} icon={<BrandIcon slug={item.icon} />} />
) : (
<p>{item.username}</p>
)}
{!item.icon && <p className="text-sm">{item.network}</p>}
</div>
)}
</Section>
);
};
const Experience = () => {
const section = useArtboardStore((state) => state.resume.sections.experience);
return (
<Section<Experience> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.company}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.position}</div>
</div>
<div className="shrink-0 text-right group-[.sidebar]:text-left">
<div className="font-bold">{item.date}</div>
<div>{item.location}</div>
</div>
</div>
)}
</Section>
);
};
const Education = () => {
const section = useArtboardStore((state) => state.resume.sections.education);
return (
<Section<Education> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.institution}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.area}</div>
<div>{item.score}</div>
</div>
<div className="shrink-0 text-right group-[.sidebar]:text-left">
<div className="font-bold">{item.date}</div>
<div>{item.studyType}</div>
</div>
</div>
)}
</Section>
);
};
const Awards = () => {
const section = useArtboardStore((state) => state.resume.sections.awards);
return (
<Section<Award> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<div className="font-bold">{item.title}</div>
<LinkedEntity
name={item.awarder}
url={item.url}
separateLinks={section.separateLinks}
/>
</div>
<div className="shrink-0 text-right group-[.sidebar]:text-left">
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const Certifications = () => {
const section = useArtboardStore((state) => state.resume.sections.certifications);
return (
<Section<Certification> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<div className="font-bold">{item.name}</div>
<LinkedEntity name={item.issuer} url={item.url} separateLinks={section.separateLinks} />
</div>
<div className="shrink-0 text-right group-[.sidebar]:text-left">
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const Skills = () => {
const section = useArtboardStore((state) => state.resume.sections.skills);
return (
<Section<Skill> section={section} levelKey="level" keywordsKey="keywords">
{(item) => (
<div>
<div className="font-bold">{item.name}</div>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Interests = () => {
const section = useArtboardStore((state) => state.resume.sections.interests);
return (
<Section<Interest> section={section} className="space-y-1" keywordsKey="keywords">
{(item) => <div className="font-bold">{item.name}</div>}
</Section>
);
};
const Publications = () => {
const section = useArtboardStore((state) => state.resume.sections.publications);
return (
<Section<Publication> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.publisher}</div>
</div>
<div className="shrink-0 text-right group-[.sidebar]:text-left">
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const Volunteer = () => {
const section = useArtboardStore((state) => state.resume.sections.volunteer);
return (
<Section<Volunteer> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.organization}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.position}</div>
</div>
<div className="shrink-0 text-right group-[.sidebar]:text-left">
<div className="font-bold">{item.date}</div>
<div>{item.location}</div>
</div>
</div>
)}
</Section>
);
};
const Languages = () => {
const section = useArtboardStore((state) => state.resume.sections.languages);
return (
<Section<Language> section={section} levelKey="level">
{(item) => (
<div>
<div className="font-bold">{item.name}</div>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Projects = () => {
const section = useArtboardStore((state) => state.resume.sections.projects);
return (
<Section<Project> section={section} urlKey="url" summaryKey="summary" keywordsKey="keywords">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
</div>
<div className="shrink-0 text-right group-[.sidebar]:text-left">
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const References = () => {
const section = useArtboardStore((state) => state.resume.sections.references);
return (
<Section<Reference> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Custom = ({ id }: { id: string }) => {
const section = useArtboardStore((state) => state.resume.sections.custom[id]);
return (
<Section<CustomSection>
section={section}
urlKey="url"
summaryKey="summary"
keywordsKey="keywords"
>
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
</div>
<div className="shrink-0 text-right group-[.sidebar]:text-left">
<div className="font-bold">{item.date}</div>
<div>{item.location}</div>
</div>
</div>
)}
</Section>
);
};
const mapSectionToComponent = (section: SectionKey) => {
switch (section) {
case "profiles": {
return <Profiles />;
}
case "experience": {
return <Experience />;
}
case "education": {
return <Education />;
}
case "summary": {
return <Summary />;
}
case "awards": {
return <Awards />;
}
case "certifications": {
return <Certifications />;
}
case "skills": {
return <Skills />;
}
case "interests": {
return <Interests />;
}
case "publications": {
return <Publications />;
}
case "volunteer": {
return <Volunteer />;
}
case "languages": {
return <Languages />;
}
case "projects": {
return <Projects />;
}
case "references": {
return <References />;
}
default: {
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
return null;
}
}
};
export const Gengar = ({ columns, isFirstPage = false }: TemplateProps) => {
const [main, sidebar] = columns;
const primaryColor = useArtboardStore((state) => state.resume.metadata.theme.primary);
return (
<div className="grid min-h-[inherit] grid-cols-3">
<div
className={cn(
"sidebar group flex flex-col",
!(isFirstPage || sidebar.length > 0) && "hidden",
)}
>
{isFirstPage && <Header />}
<div className="flex-1" style={{ backgroundColor: hexToRgb(primaryColor, 0.2) }}>
{sidebar.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}
</div>
</div>
<div className={cn("main group", sidebar.length > 0 ? "col-span-2" : "col-span-3")}>
{main.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}
</div>
</div>
);
};
+612
View File
@@ -0,0 +1,612 @@
import type {
Award,
Certification,
CustomSection,
CustomSectionGroup,
Interest,
Language,
Profile,
Project,
Publication,
Reference,
SectionKey,
SectionWithItem,
Skill,
URL,
} from "@reactive-resume/schema";
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import {
cn,
hexToRgb,
isEmptyString,
isUrl,
linearTransform,
sanitize,
} from "@reactive-resume/utils";
import get from "lodash.get";
import { Fragment } from "react";
import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture";
import { useArtboardStore } from "../store/artboard";
import type { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
return (
<div className="flex flex-col items-center space-y-4 text-center">
<Picture />
<div className="space-y-4">
<div>
<div className="text-2xl font-bold">{basics.name}</div>
<div className="text-base">{basics.headline}</div>
</div>
<div className="flex flex-col items-start gap-y-1.5 rounded border border-primary px-3 py-4 text-left text-sm">
{basics.location && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-map-pin text-primary" />
<div>{basics.location}</div>
</div>
)}
{basics.phone && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-phone text-primary" />
<a href={`tel:${basics.phone}`} target="_blank" rel="noreferrer">
{basics.phone}
</a>
</div>
)}
{basics.email && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-at text-primary" />
<a href={`mailto:${basics.email}`} target="_blank" rel="noreferrer">
{basics.email}
</a>
</div>
)}
<Link url={basics.url} />
{basics.customFields.map((item) => (
<div key={item.id} className="flex items-center gap-x-1.5">
<i className={cn(`ph ph-bold ph-${item.icon} text-primary`)} />
{isUrl(item.value) ? (
<a href={item.value} target="_blank" rel="noreferrer noopener nofollow">
{item.name || item.value}
</a>
) : (
<span>{[item.name, item.value].filter(Boolean).join(": ")}</span>
)}
</div>
))}
</div>
</div>
</div>
);
};
const Summary = () => {
const section = useArtboardStore((state) => state.resume.sections.summary);
if (!section.visible || isEmptyString(section.content)) return null;
return (
<section id={section.id}>
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
<div
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }}
className="wysiwyg"
/>
</section>
);
};
type RatingProps = { level: number };
const Rating = ({ level }: RatingProps) => {
const primaryColor = useArtboardStore((state) => state.resume.metadata.theme.primary);
return (
<div className="relative">
<div
className="h-2.5 w-full rounded-sm"
style={{ backgroundColor: hexToRgb(primaryColor, 0.4) }}
/>
<div
className="absolute inset-y-0 left-0 h-2.5 w-full rounded-sm bg-primary"
style={{ width: `${linearTransform(level, 0, 5, 0, 100)}%` }}
/>
</div>
);
};
type LinkProps = {
url: URL;
icon?: React.ReactNode;
iconOnRight?: boolean;
label?: string;
className?: string;
};
const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
if (!isUrl(url.href)) return null;
return (
<div className="flex items-center gap-x-1.5 break-all">
{!iconOnRight &&
(icon ?? <i className="ph ph-bold ph-link text-primary group-[.sidebar]:text-primary" />)}
<a
href={url.href}
target="_blank"
rel="noreferrer noopener nofollow"
className={cn("line-clamp-1 max-w-fit", className)}
>
{label ?? (url.label || url.href)}
</a>
{iconOnRight &&
(icon ?? <i className="ph ph-bold ph-link text-primary group-[.sidebar]:text-primary" />)}
</div>
);
};
type LinkedEntityProps = {
name: string;
url: URL;
separateLinks: boolean;
className?: string;
};
const LinkedEntity = ({ name, url, separateLinks, className }: LinkedEntityProps) => {
return !separateLinks && isUrl(url.href) ? (
<Link
url={url}
label={name}
icon={<i className="ph ph-bold ph-globe text-primary group-[.sidebar]:text-primary" />}
iconOnRight={true}
className={className}
/>
) : (
<div className={className}>{name}</div>
);
};
type SectionProps<T> = {
section: SectionWithItem<T> | CustomSectionGroup;
children?: (item: T) => React.ReactNode;
className?: string;
urlKey?: keyof T;
levelKey?: keyof T;
summaryKey?: keyof T;
keywordsKey?: keyof T;
};
const Section = <T,>({
section,
children,
className,
urlKey,
levelKey,
summaryKey,
keywordsKey,
}: SectionProps<T>) => {
if (!section.visible || section.items.filter((item) => item.visible).length === 0) return null;
return (
<section id={section.id} className="grid">
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold group-[.sidebar]:text-primary">
{section.name}
</h4>
<div
className="grid gap-x-6 gap-y-3"
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
>
{section.items
.filter((item) => item.visible)
.map((item) => {
const url = (urlKey && get(item, urlKey)) as URL | undefined;
const level = (levelKey && get(item, levelKey, 0)) as number | undefined;
const summary = (summaryKey && get(item, summaryKey, "")) as string | undefined;
const keywords = (keywordsKey && get(item, keywordsKey, [])) as string[] | undefined;
return (
<div key={item.id} className={cn("space-y-2", className)}>
<div>
{children?.(item as T)}
{url !== undefined && section.separateLinks && <Link url={url} />}
</div>
{summary !== undefined && !isEmptyString(summary) && (
<div
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg"
/>
)}
{level !== undefined && level > 0 && <Rating level={level} />}
{keywords !== undefined && keywords.length > 0 && (
<p className="text-sm">{keywords.join(", ")}</p>
)}
</div>
);
})}
</div>
</section>
);
};
const Experience = () => {
const section = useArtboardStore((state) => state.resume.sections.experience);
return (
<Section<Experience> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.company}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.position}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
<div>{item.location}</div>
</div>
</div>
)}
</Section>
);
};
const Education = () => {
const section = useArtboardStore((state) => state.resume.sections.education);
return (
<Section<Education> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.institution}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.area}</div>
<div>{item.score}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
<div>{item.studyType}</div>
</div>
</div>
)}
</Section>
);
};
const Profiles = () => {
const section = useArtboardStore((state) => state.resume.sections.profiles);
return (
<Section<Profile> section={section}>
{(item) => (
<div>
{isUrl(item.url.href) ? (
<Link url={item.url} label={item.username} icon={<BrandIcon slug={item.icon} />} />
) : (
<p>{item.username}</p>
)}
{!item.icon && <p className="text-sm">{item.network}</p>}
</div>
)}
</Section>
);
};
const Awards = () => {
const section = useArtboardStore((state) => state.resume.sections.awards);
return (
<Section<Award> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<div className="font-bold">{item.title}</div>
<LinkedEntity
name={item.awarder}
url={item.url}
separateLinks={section.separateLinks}
/>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const Certifications = () => {
const section = useArtboardStore((state) => state.resume.sections.certifications);
return (
<Section<Certification> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<div className="font-bold">{item.name}</div>
<LinkedEntity name={item.issuer} url={item.url} separateLinks={section.separateLinks} />
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const Skills = () => {
const section = useArtboardStore((state) => state.resume.sections.skills);
return (
<Section<Skill> section={section} levelKey="level" keywordsKey="keywords">
{(item) => (
<div>
<div className="font-bold">{item.name}</div>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Interests = () => {
const section = useArtboardStore((state) => state.resume.sections.interests);
return (
<Section<Interest> section={section} keywordsKey="keywords" className="space-y-0.5">
{(item) => <div className="font-bold">{item.name}</div>}
</Section>
);
};
const Publications = () => {
const section = useArtboardStore((state) => state.resume.sections.publications);
return (
<Section<Publication> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.publisher}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const Volunteer = () => {
const section = useArtboardStore((state) => state.resume.sections.volunteer);
return (
<Section<Volunteer> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.organization}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.position}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
<div>{item.location}</div>
</div>
</div>
)}
</Section>
);
};
const Languages = () => {
const section = useArtboardStore((state) => state.resume.sections.languages);
return (
<Section<Language> section={section} levelKey="level">
{(item) => (
<div className="space-y-0.5">
<div className="font-bold">{item.name}</div>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Projects = () => {
const section = useArtboardStore((state) => state.resume.sections.projects);
return (
<Section<Project> section={section} urlKey="url" summaryKey="summary" keywordsKey="keywords">
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const References = () => {
const section = useArtboardStore((state) => state.resume.sections.references);
return (
<Section<Reference> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Custom = ({ id }: { id: string }) => {
const section = useArtboardStore((state) => state.resume.sections.custom[id]);
return (
<Section<CustomSection>
section={section}
urlKey="url"
summaryKey="summary"
keywordsKey="keywords"
>
{(item) => (
<div className="flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start">
<div className="text-left">
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
</div>
<div className="shrink-0 text-right">
<div className="font-bold">{item.date}</div>
<div>{item.location}</div>
</div>
</div>
)}
</Section>
);
};
const mapSectionToComponent = (section: SectionKey) => {
switch (section) {
case "profiles": {
return <Profiles />;
}
case "summary": {
return <Summary />;
}
case "experience": {
return <Experience />;
}
case "education": {
return <Education />;
}
case "awards": {
return <Awards />;
}
case "certifications": {
return <Certifications />;
}
case "skills": {
return <Skills />;
}
case "interests": {
return <Interests />;
}
case "publications": {
return <Publications />;
}
case "volunteer": {
return <Volunteer />;
}
case "languages": {
return <Languages />;
}
case "projects": {
return <Projects />;
}
case "references": {
return <References />;
}
default: {
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
return null;
}
}
};
export const Glalie = ({ columns, isFirstPage = false }: TemplateProps) => {
const [main, sidebar] = columns;
const primaryColor = useArtboardStore((state) => state.resume.metadata.theme.primary);
return (
<div className="grid min-h-[inherit] grid-cols-3">
<div
className={cn("sidebar group space-y-4 p-custom", sidebar.length === 0 && "hidden")}
style={{ backgroundColor: hexToRgb(primaryColor, 0.2) }}
>
{isFirstPage && <Header />}
{sidebar.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}
</div>
<div
className={cn(
"main group space-y-4 p-custom",
sidebar.length > 0 ? "col-span-2" : "col-span-3",
)}
>
{main.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}
</div>
</div>
);
};
+58
View File
@@ -0,0 +1,58 @@
import type { Template } from "@reactive-resume/utils";
import { Azurill } from "./azurill";
import { Bronzor } from "./bronzor";
import { Chikorita } from "./chikorita";
import { Ditto } from "./ditto";
import { Gengar } from "./gengar";
import { Glalie } from "./glalie";
import { Kakuna } from "./kakuna";
import { Leafish } from "./leafish";
import { Nosepass } from "./nosepass";
import { Onyx } from "./onyx";
import { Pikachu } from "./pikachu";
import { Rhyhorn } from "./rhyhorn";
export const getTemplate = (template: Template) => {
switch (template) {
case "azurill": {
return Azurill;
}
case "bronzor": {
return Bronzor;
}
case "chikorita": {
return Chikorita;
}
case "ditto": {
return Ditto;
}
case "gengar": {
return Gengar;
}
case "glalie": {
return Glalie;
}
case "kakuna": {
return Kakuna;
}
case "leafish": {
return Leafish;
}
case "nosepass": {
return Nosepass;
}
case "onyx": {
return Onyx;
}
case "pikachu": {
return Pikachu;
}
case "rhyhorn": {
return Rhyhorn;
}
default: {
return Onyx;
}
}
};
+541
View File
@@ -0,0 +1,541 @@
import type {
Award,
Certification,
CustomSection,
CustomSectionGroup,
Interest,
Language,
Project,
Publication,
Reference,
SectionKey,
SectionWithItem,
Skill,
URL,
} from "@reactive-resume/schema";
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get";
import React, { Fragment } from "react";
import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture";
import { useArtboardStore } from "../store/artboard";
import type { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
const profiles = useArtboardStore((state) => state.resume.sections.profiles);
return (
<div className="flex flex-col items-center justify-center space-y-2 pb-2 text-center">
<Picture />
<div>
<div className="text-2xl font-bold">{basics.name}</div>
<div className="text-base">{basics.headline}</div>
</div>
<div className="flex flex-wrap items-center gap-x-3 gap-y-0.5 text-sm">
{basics.location && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-map-pin text-primary" />
<div>{basics.location}</div>
</div>
)}
{basics.phone && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-phone text-primary" />
<a href={`tel:${basics.phone}`} target="_blank" rel="noreferrer">
{basics.phone}
</a>
</div>
)}
{basics.email && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-at text-primary" />
<a href={`mailto:${basics.email}`} target="_blank" rel="noreferrer">
{basics.email}
</a>
</div>
)}
<Link url={basics.url} />
{basics.customFields.map((item) => (
<div key={item.id} className="flex items-center gap-x-1.5">
<i className={cn(`ph ph-bold ph-${item.icon}`, "text-primary")} />
{isUrl(item.value) ? (
<a href={item.value} target="_blank" rel="noreferrer noopener nofollow">
{item.name || item.value}
</a>
) : (
<span>{[item.name, item.value].filter(Boolean).join(": ")}</span>
)}
</div>
))}
</div>
{profiles.visible && profiles.items.length > 0 && (
<div className="flex items-center gap-x-3 gap-y-0.5">
{profiles.items
.filter((item) => item.visible)
.map((item) => (
<div key={item.id} className="flex items-center gap-x-2">
<Link
url={item.url}
label={item.username}
className="text-sm"
icon={<BrandIcon slug={item.icon} />}
/>
</div>
))}
</div>
)}
</div>
);
};
const Summary = () => {
const section = useArtboardStore((state) => state.resume.sections.summary);
if (!section.visible || isEmptyString(section.content)) return null;
return (
<section id={section.id}>
<h4 className="mb-2 border-b border-primary text-center font-bold text-primary">
{section.name}
</h4>
<div
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }}
className="wysiwyg"
/>
</section>
);
};
type RatingProps = { level: number };
const Rating = ({ level }: RatingProps) => (
<div className="flex items-center gap-x-1.5">
{Array.from({ length: 5 }).map((_, index) => (
<div
key={index}
className={cn("h-3 w-5 rounded border-2 border-primary", level > index && "bg-primary")}
/>
))}
</div>
);
type LinkProps = {
url: URL;
icon?: React.ReactNode;
iconOnRight?: boolean;
label?: string;
className?: string;
};
const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
if (!isUrl(url.href)) return null;
return (
<div className="flex items-center gap-x-1.5 break-all">
{!iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
<a
href={url.href}
target="_blank"
rel="noreferrer noopener nofollow"
className={cn("line-clamp-1 max-w-fit", className)}
>
{label ?? (url.label || url.href)}
</a>
{iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
</div>
);
};
type LinkedEntityProps = {
name: string;
url: URL;
separateLinks: boolean;
className?: string;
};
const LinkedEntity = ({ name, url, separateLinks, className }: LinkedEntityProps) => {
return !separateLinks && isUrl(url.href) ? (
<Link
url={url}
label={name}
icon={<i className="ph ph-bold ph-globe text-primary" />}
iconOnRight={true}
className={className}
/>
) : (
<div className={className}>{name}</div>
);
};
type SectionProps<T> = {
section: SectionWithItem<T> | CustomSectionGroup;
children?: (item: T) => React.ReactNode;
className?: string;
urlKey?: keyof T;
levelKey?: keyof T;
summaryKey?: keyof T;
keywordsKey?: keyof T;
};
const Section = <T,>({
section,
children,
className,
urlKey,
levelKey,
summaryKey,
keywordsKey,
}: SectionProps<T>) => {
if (!section.visible || section.items.filter((item) => item.visible).length === 0) return null;
return (
<section id={section.id} className="grid">
<h4 className="mb-2 border-b border-primary text-center font-bold text-primary">
{section.name}
</h4>
<div
className="grid gap-x-6 gap-y-3"
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
>
{section.items
.filter((item) => item.visible)
.map((item) => {
const url = (urlKey && get(item, urlKey)) as URL | undefined;
const level = (levelKey && get(item, levelKey, 0)) as number | undefined;
const summary = (summaryKey && get(item, summaryKey, "")) as string | undefined;
const keywords = (keywordsKey && get(item, keywordsKey, [])) as string[] | undefined;
return (
<div key={item.id} className={cn("space-y-2", className)}>
<div>{children?.(item as T)}</div>
{summary !== undefined && !isEmptyString(summary) && (
<div
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg"
/>
)}
{level !== undefined && level > 0 && <Rating level={level} />}
{keywords !== undefined && keywords.length > 0 && (
<p className="text-sm">{keywords.join(", ")}</p>
)}
{url !== undefined && section.separateLinks && <Link url={url} />}
</div>
);
})}
</div>
</section>
);
};
const Experience = () => {
const section = useArtboardStore((state) => state.resume.sections.experience);
return (
<Section<Experience> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.company}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.position}</div>
<div>{item.location}</div>
<div className="font-bold">{item.date}</div>
</div>
)}
</Section>
);
};
const Education = () => {
const section = useArtboardStore((state) => state.resume.sections.education);
return (
<Section<Education> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.institution}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.area}</div>
<div>{item.score}</div>
<div>{item.studyType}</div>
<div className="font-bold">{item.date}</div>
</div>
)}
</Section>
);
};
const Awards = () => {
const section = useArtboardStore((state) => state.resume.sections.awards);
return (
<Section<Award> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<div className="font-bold">{item.title}</div>
<LinkedEntity name={item.awarder} url={item.url} separateLinks={section.separateLinks} />
<div className="font-bold">{item.date}</div>
</div>
)}
</Section>
);
};
const Certifications = () => {
const section = useArtboardStore((state) => state.resume.sections.certifications);
return (
<Section<Certification> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<div className="font-bold">{item.name}</div>
<LinkedEntity name={item.issuer} url={item.url} separateLinks={section.separateLinks} />
<div className="font-bold">{item.date}</div>
</div>
)}
</Section>
);
};
const Skills = () => {
const section = useArtboardStore((state) => state.resume.sections.skills);
return (
<Section<Skill> section={section} levelKey="level" keywordsKey="keywords">
{(item) => (
<div>
<div className="font-bold">{item.name}</div>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Interests = () => {
const section = useArtboardStore((state) => state.resume.sections.interests);
return (
<Section<Interest> section={section} keywordsKey="keywords" className="space-y-0.5">
{(item) => <div className="font-bold">{item.name}</div>}
</Section>
);
};
const Publications = () => {
const section = useArtboardStore((state) => state.resume.sections.publications);
return (
<Section<Publication> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.publisher}</div>
<div className="font-bold">{item.date}</div>
</div>
)}
</Section>
);
};
const Volunteer = () => {
const section = useArtboardStore((state) => state.resume.sections.volunteer);
return (
<Section<Volunteer> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.organization}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.position}</div>
<div>{item.location}</div>
<div className="font-bold">{item.date}</div>
</div>
)}
</Section>
);
};
const Languages = () => {
const section = useArtboardStore((state) => state.resume.sections.languages);
return (
<Section<Language> section={section} levelKey="level">
{(item) => (
<div>
<div className="font-bold">{item.name}</div>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Projects = () => {
const section = useArtboardStore((state) => state.resume.sections.projects);
return (
<Section<Project> section={section} urlKey="url" summaryKey="summary" keywordsKey="keywords">
{(item) => (
<div>
<div>
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const References = () => {
const section = useArtboardStore((state) => state.resume.sections.references);
return (
<Section<Reference> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Custom = ({ id }: { id: string }) => {
const section = useArtboardStore((state) => state.resume.sections.custom[id]);
return (
<Section<CustomSection>
section={section}
urlKey="url"
summaryKey="summary"
keywordsKey="keywords"
>
{(item) => (
<div>
<div>
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
<div>{item.location}</div>
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const mapSectionToComponent = (section: SectionKey) => {
switch (section) {
case "summary": {
return <Summary />;
}
case "experience": {
return <Experience />;
}
case "education": {
return <Education />;
}
case "awards": {
return <Awards />;
}
case "certifications": {
return <Certifications />;
}
case "skills": {
return <Skills />;
}
case "interests": {
return <Interests />;
}
case "publications": {
return <Publications />;
}
case "volunteer": {
return <Volunteer />;
}
case "languages": {
return <Languages />;
}
case "projects": {
return <Projects />;
}
case "references": {
return <References />;
}
default: {
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
return null;
}
}
};
export const Kakuna = ({ columns, isFirstPage = false }: TemplateProps) => {
const [main, sidebar] = columns;
return (
<div className="space-y-4 p-custom">
{isFirstPage && <Header />}
<div className="space-y-4">
{main.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}
{sidebar.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}
</div>
</div>
);
};
+537
View File
@@ -0,0 +1,537 @@
import type {
Award,
Certification,
CustomSection,
CustomSectionGroup,
Interest,
Language,
Project,
Publication,
Reference,
SectionKey,
SectionWithItem,
Skill,
URL,
} from "@reactive-resume/schema";
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, hexToRgb, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get";
import React, { Fragment } from "react";
import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture";
import { useArtboardStore } from "../store/artboard";
import type { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
const section = useArtboardStore((state) => state.resume.sections.summary);
const profiles = useArtboardStore((state) => state.resume.sections.profiles);
const primaryColor = useArtboardStore((state) => state.resume.metadata.theme.primary);
return (
<div>
<div
className="flex items-center space-x-8 p-custom"
style={{ backgroundColor: hexToRgb(primaryColor, 0.2) }}
>
<div className="space-y-3">
<div>
<div className="text-3xl font-bold">{basics.name}</div>
<div className="text-base font-medium text-primary">{basics.headline}</div>
</div>
<div
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }}
className="wysiwyg"
/>
</div>
<Picture />
</div>
<div className="space-y-3 p-custom" style={{ backgroundColor: hexToRgb(primaryColor, 0.4) }}>
<div className="flex flex-wrap items-center gap-x-3 gap-y-0.5 text-sm">
{basics.location && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-map-pin text-primary" />
<div>{basics.location}</div>
</div>
)}
{basics.phone && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-phone text-primary" />
<a href={`tel:${basics.phone}`} target="_blank" rel="noreferrer">
{basics.phone}
</a>
</div>
)}
{basics.email && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-at text-primary" />
<a href={`mailto:${basics.email}`} target="_blank" rel="noreferrer">
{basics.email}
</a>
</div>
)}
<Link url={basics.url} />
{basics.customFields.map((item) => (
<div key={item.id} className="flex items-center gap-x-1.5">
<i className={cn(`ph ph-bold ph-${item.icon}`, "text-primary")} />
{isUrl(item.value) ? (
<a href={item.value} target="_blank" rel="noreferrer noopener nofollow">
{item.name || item.value}
</a>
) : (
<span>{[item.name, item.value].filter(Boolean).join(": ")}</span>
)}
</div>
))}
</div>
{profiles.visible && profiles.items.length > 0 && (
<div className="flex items-center gap-x-3 gap-y-0.5">
{profiles.items
.filter((item) => item.visible)
.map((item) => (
<div key={item.id} className="flex items-center gap-x-2">
<Link
url={item.url}
label={item.username}
className="text-sm"
icon={<BrandIcon slug={item.icon} />}
/>
</div>
))}
</div>
)}
</div>
</div>
);
};
type RatingProps = { level: number };
const Rating = ({ level }: RatingProps) => (
<div className="flex items-center gap-x-1.5">
{Array.from({ length: 5 }).map((_, index) => (
<div
key={index}
className={cn("h-3 w-6 border-2 border-primary", level > index && "bg-primary")}
/>
))}
</div>
);
type LinkProps = {
url: URL;
icon?: React.ReactNode;
iconOnRight?: boolean;
label?: string;
className?: string;
};
const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
if (!isUrl(url.href)) return null;
return (
<div className="flex items-center gap-x-1.5 break-all">
{!iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
<a
href={url.href}
target="_blank"
rel="noreferrer noopener nofollow"
className={cn("line-clamp-1 max-w-fit", className)}
>
{label ?? (url.label || url.href)}
</a>
{iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
</div>
);
};
type LinkedEntityProps = {
name: string;
url: URL;
separateLinks: boolean;
className?: string;
};
const LinkedEntity = ({ name, url, separateLinks, className }: LinkedEntityProps) => {
return !separateLinks && isUrl(url.href) ? (
<Link
url={url}
label={name}
icon={<i className="ph ph-bold ph-globe text-primary" />}
iconOnRight={true}
className={className}
/>
) : (
<div className={className}>{name}</div>
);
};
type SectionProps<T> = {
section: SectionWithItem<T> | CustomSectionGroup;
children?: (item: T) => React.ReactNode;
className?: string;
urlKey?: keyof T;
levelKey?: keyof T;
summaryKey?: keyof T;
keywordsKey?: keyof T;
};
const Section = <T,>({
section,
children,
className,
urlKey,
levelKey,
summaryKey,
keywordsKey,
}: SectionProps<T>) => {
if (!section.visible || section.items.filter((item) => item.visible).length === 0) return null;
return (
<section id={section.id} className="grid">
<h4 className="mb-2 border-b border-primary text-left font-bold text-primary">
{section.name}
</h4>
<div
className="grid gap-x-6 gap-y-3"
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
>
{section.items
.filter((item) => item.visible)
.map((item) => {
const url = (urlKey && get(item, urlKey)) as URL | undefined;
const level = (levelKey && get(item, levelKey, 0)) as number | undefined;
const summary = (summaryKey && get(item, summaryKey, "")) as string | undefined;
const keywords = (keywordsKey && get(item, keywordsKey, [])) as string[] | undefined;
return (
<div key={item.id} className={cn("space-y-2", className)}>
<div>{children?.(item as T)}</div>
{summary !== undefined && !isEmptyString(summary) && (
<div
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg"
/>
)}
{level !== undefined && level > 0 && <Rating level={level} />}
{keywords !== undefined && keywords.length > 0 && (
<p className="text-sm">{keywords.join(", ")}</p>
)}
{url !== undefined && section.separateLinks && <Link url={url} />}
</div>
);
})}
</div>
</section>
);
};
const Experience = () => {
const section = useArtboardStore((state) => state.resume.sections.experience);
return (
<Section<Experience> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.company}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.position}</div>
<div>{item.location}</div>
<div className="font-bold">{item.date}</div>
</div>
)}
</Section>
);
};
const Education = () => {
const section = useArtboardStore((state) => state.resume.sections.education);
return (
<Section<Education> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.institution}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.area}</div>
<div>{item.score}</div>
<div>{item.studyType}</div>
<div className="font-bold">{item.date}</div>
</div>
)}
</Section>
);
};
const Awards = () => {
const section = useArtboardStore((state) => state.resume.sections.awards);
return (
<Section<Award> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<div className="font-bold">{item.title}</div>
<LinkedEntity name={item.awarder} url={item.url} separateLinks={section.separateLinks} />
<div className="font-bold">{item.date}</div>
</div>
)}
</Section>
);
};
const Certifications = () => {
const section = useArtboardStore((state) => state.resume.sections.certifications);
return (
<Section<Certification> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<div className="font-bold">{item.name}</div>
<LinkedEntity name={item.issuer} url={item.url} separateLinks={section.separateLinks} />
<div className="font-bold">{item.date}</div>
</div>
)}
</Section>
);
};
const Skills = () => {
const section = useArtboardStore((state) => state.resume.sections.skills);
return (
<Section<Skill> section={section} levelKey="level" keywordsKey="keywords">
{(item) => (
<div>
<div className="font-bold">{item.name}</div>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Interests = () => {
const section = useArtboardStore((state) => state.resume.sections.interests);
return (
<Section<Interest> section={section} keywordsKey="keywords" className="space-y-0.5">
{(item) => <div className="font-bold">{item.name}</div>}
</Section>
);
};
const Publications = () => {
const section = useArtboardStore((state) => state.resume.sections.publications);
return (
<Section<Publication> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.publisher}</div>
<div className="font-bold">{item.date}</div>
</div>
)}
</Section>
);
};
const Volunteer = () => {
const section = useArtboardStore((state) => state.resume.sections.volunteer);
return (
<Section<Volunteer> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.organization}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.position}</div>
<div>{item.location}</div>
<div className="font-bold">{item.date}</div>
</div>
)}
</Section>
);
};
const Languages = () => {
const section = useArtboardStore((state) => state.resume.sections.languages);
return (
<Section<Language> section={section} levelKey="level">
{(item) => (
<div>
<div className="font-bold">{item.name}</div>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Projects = () => {
const section = useArtboardStore((state) => state.resume.sections.projects);
return (
<Section<Project> section={section} urlKey="url" summaryKey="summary" keywordsKey="keywords">
{(item) => (
<div>
<div>
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const References = () => {
const section = useArtboardStore((state) => state.resume.sections.references);
return (
<Section<Reference> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Custom = ({ id }: { id: string }) => {
const section = useArtboardStore((state) => state.resume.sections.custom[id]);
return (
<Section<CustomSection>
section={section}
urlKey="url"
summaryKey="summary"
keywordsKey="keywords"
>
{(item) => (
<div>
<div>
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
<div>{item.location}</div>
<div className="font-bold">{item.date}</div>
</div>
</div>
)}
</Section>
);
};
const mapSectionToComponent = (section: SectionKey) => {
switch (section) {
case "experience": {
return <Experience />;
}
case "education": {
return <Education />;
}
case "awards": {
return <Awards />;
}
case "certifications": {
return <Certifications />;
}
case "skills": {
return <Skills />;
}
case "interests": {
return <Interests />;
}
case "publications": {
return <Publications />;
}
case "volunteer": {
return <Volunteer />;
}
case "languages": {
return <Languages />;
}
case "projects": {
return <Projects />;
}
case "references": {
return <References />;
}
default: {
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
return null;
}
}
};
export const Leafish = ({ columns, isFirstPage = false }: TemplateProps) => {
const [main, sidebar] = columns;
return (
<div>
{isFirstPage && <Header />}
<div className="grid grid-cols-2 items-start space-x-6 p-custom">
<div className={cn("grid gap-y-4", sidebar.length === 0 && "col-span-2")}>
{main.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}
</div>
<div className={cn("grid gap-y-4", sidebar.length === 0 && "hidden")}>
{sidebar.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}
</div>
</div>
</div>
);
};
+600
View File
@@ -0,0 +1,600 @@
import type {
Award,
Certification,
CustomSection,
CustomSectionGroup,
Interest,
Language,
Profile,
Project,
Publication,
Reference,
SectionKey,
SectionWithItem,
Skill,
URL,
} from "@reactive-resume/schema";
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get";
import { Fragment } from "react";
import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture";
import { useArtboardStore } from "../store/artboard";
import type { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
return (
<div className="grid grid-cols-4 gap-x-6">
<div className="mt-1 space-y-2 text-right">
<Picture className="ml-auto" />
</div>
<div className="col-span-3 space-y-2">
<div>
<div className="text-2xl font-bold">{basics.name}</div>
<div className="text-base">{basics.headline}</div>
</div>
<div className="space-y-1 text-sm">
{basics.location && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-map-pin text-primary" />
<div>{basics.location}</div>
</div>
)}
{basics.phone && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-phone text-primary" />
<a href={`tel:${basics.phone}`} target="_blank" rel="noreferrer">
{basics.phone}
</a>
</div>
)}
{basics.email && (
<div className="flex items-center gap-x-1.5">
<i className="ph ph-bold ph-at text-primary" />
<a href={`mailto:${basics.email}`} target="_blank" rel="noreferrer">
{basics.email}
</a>
</div>
)}
<Link url={basics.url} />
</div>
<div className="flex flex-wrap gap-x-3 text-sm">
{basics.customFields.map((item) => (
<div key={item.id} className="flex items-center gap-x-1.5">
<i className={cn(`ph ph-bold ph-${item.icon}`, "text-primary")} />
{isUrl(item.value) ? (
<a href={item.value} target="_blank" rel="noreferrer noopener nofollow">
{item.name || item.value}
</a>
) : (
<>
<span className="text-primary">{item.name}</span>
<span>{item.value}</span>
</>
)}
</div>
))}
</div>
</div>
</div>
);
};
const Summary = () => {
const section = useArtboardStore((state) => state.resume.sections.summary);
if (!section.visible || isEmptyString(section.content)) return null;
return (
<section id={section.id} className="grid grid-cols-4 gap-x-6">
<div className="text-right">
<h4 className="font-medium text-primary">{section.name}</h4>
</div>
<div className="col-span-3">
<div className="relative">
<hr className="mt-3 border-primary pb-3" />
<div className="absolute bottom-3 right-0 size-3 bg-primary" />
</div>
<div
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }}
className="wysiwyg"
/>
</div>
</section>
);
};
type LinkProps = {
url: URL;
icon?: React.ReactNode;
iconOnRight?: boolean;
label?: string;
className?: string;
};
const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
if (!isUrl(url.href)) return null;
return (
<div className="flex items-center gap-x-1.5 break-all">
{!iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
<a
href={url.href}
target="_blank"
rel="noreferrer noopener nofollow"
className={cn("line-clamp-1 max-w-fit", className)}
>
{label ?? (url.label || url.href)}
</a>
{iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
</div>
);
};
type LinkedEntityProps = {
name: string;
url: URL;
separateLinks: boolean;
className?: string;
};
const LinkedEntity = ({ name, url, separateLinks, className }: LinkedEntityProps) => {
return !separateLinks && isUrl(url.href) ? (
<Link
url={url}
label={name}
icon={<i className="ph ph-bold ph-globe text-primary" />}
iconOnRight={true}
className={className}
/>
) : (
<div className={className}>{name}</div>
);
};
type SectionProps<T> = {
section: SectionWithItem<T> | CustomSectionGroup;
children?: (item: T) => React.ReactNode;
urlKey?: keyof T;
dateKey?: keyof T;
levelKey?: keyof T;
summaryKey?: keyof T;
keywordsKey?: keyof T;
};
const Section = <T,>({
section,
children,
urlKey,
dateKey,
summaryKey,
keywordsKey,
}: SectionProps<T>) => {
if (!section.visible || section.items.filter((item) => item.visible).length === 0) return null;
return (
<section id={section.id} className={cn("grid", dateKey !== undefined && "gap-y-4")}>
<div className="grid grid-cols-4 gap-x-6">
<div className="text-right">
<h4 className="font-medium text-primary">{section.name}</h4>
</div>
<div className="col-span-3">
<div className="relative">
<hr className="mt-3 border-primary" />
<div className="absolute bottom-0 right-0 size-3 bg-primary" />
</div>
</div>
</div>
{dateKey !== undefined && (
<div className="grid grid-cols-4 gap-x-6 gap-y-4">
{section.items
.filter((item) => item.visible)
.map((item) => {
const url = (urlKey && get(item, urlKey)) as URL | undefined;
const date = (dateKey && get(item, dateKey, "")) as string | undefined;
const summary = (summaryKey && get(item, summaryKey, "")) as string | undefined;
const keywords = (keywordsKey && get(item, keywordsKey, [])) as string[] | undefined;
return (
<Fragment key={item.id}>
<div className="text-right font-medium text-primary">{date}</div>
<div className="col-span-3 space-y-1">
{children?.(item as T)}
{url !== undefined && section.separateLinks && <Link url={url} />}
{summary !== undefined && !isEmptyString(summary) && (
<div
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg"
/>
)}
{keywords !== undefined && keywords.length > 0 && (
<p className="text-sm">{keywords.join(", ")}</p>
)}
</div>
</Fragment>
);
})}
</div>
)}
{dateKey === undefined && (
<div className="grid grid-cols-4 gap-x-6">
<div
className="col-span-3 col-start-2 grid gap-x-6 gap-y-3"
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
>
{section.items
.filter((item) => item.visible)
.map((item) => {
const url = (urlKey && get(item, urlKey)) as URL | undefined;
const summary = (summaryKey && get(item, summaryKey, "")) as string | undefined;
const keywords = (keywordsKey && get(item, keywordsKey, [])) as
| string[]
| undefined;
return (
<div key={item.id}>
{children?.(item as T)}
{url !== undefined && section.separateLinks && <Link url={url} />}
{summary !== undefined && !isEmptyString(summary) && (
<div
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg"
/>
)}
{keywords !== undefined && keywords.length > 0 && (
<p className="text-sm">{keywords.join(", ")}</p>
)}
</div>
);
})}
</div>
</div>
)}
</section>
);
};
const Profiles = () => {
const section = useArtboardStore((state) => state.resume.sections.profiles);
return (
<Section<Profile> section={section}>
{(item) => (
<div>
{isUrl(item.url.href) ? (
<Link url={item.url} label={item.username} icon={<BrandIcon slug={item.icon} />} />
) : (
<p>{item.username}</p>
)}
{!item.icon && <p className="text-sm">{item.network}</p>}
</div>
)}
</Section>
);
};
const Experience = () => {
const section = useArtboardStore((state) => state.resume.sections.experience);
return (
<Section<Experience> section={section} urlKey="url" dateKey="date" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.company}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.position}</div>
<div>{item.location}</div>
</div>
)}
</Section>
);
};
const Education = () => {
const section = useArtboardStore((state) => state.resume.sections.education);
return (
<Section<Education> section={section} urlKey="url" dateKey="date" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.institution}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.area}</div>
<div>{item.studyType}</div>
<div>{item.score}</div>
</div>
)}
</Section>
);
};
const Awards = () => {
const section = useArtboardStore((state) => state.resume.sections.awards);
return (
<Section<Award> section={section} urlKey="url" dateKey="date" summaryKey="summary">
{(item) => (
<div>
<div className="font-bold">{item.title}</div>
<LinkedEntity name={item.awarder} url={item.url} separateLinks={section.separateLinks} />
</div>
)}
</Section>
);
};
const Certifications = () => {
const section = useArtboardStore((state) => state.resume.sections.certifications);
return (
<Section<Certification> section={section} urlKey="url" dateKey="date" summaryKey="summary">
{(item) => (
<div>
<div className="font-bold">{item.name}</div>
<LinkedEntity name={item.issuer} url={item.url} separateLinks={section.separateLinks} />
</div>
)}
</Section>
);
};
const Skills = () => {
const section = useArtboardStore((state) => state.resume.sections.skills);
return (
<Section<Skill> section={section} levelKey="level" keywordsKey="keywords">
{(item) => (
<div>
<div className="font-bold">{item.name}</div>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Interests = () => {
const section = useArtboardStore((state) => state.resume.sections.interests);
return (
<Section<Interest> section={section} keywordsKey="keywords">
{(item) => <div className="font-bold">{item.name}</div>}
</Section>
);
};
const Publications = () => {
const section = useArtboardStore((state) => state.resume.sections.publications);
return (
<Section<Publication> section={section} urlKey="url" dateKey="date" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.publisher}</div>
</div>
)}
</Section>
);
};
const Volunteer = () => {
const section = useArtboardStore((state) => state.resume.sections.volunteer);
return (
<Section<Volunteer> section={section} urlKey="url" dateKey="date" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.organization}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.position}</div>
<div>{item.location}</div>
</div>
)}
</Section>
);
};
const Languages = () => {
const section = useArtboardStore((state) => state.resume.sections.languages);
return (
<Section<Language> section={section} levelKey="level">
{(item) => (
<div>
<div className="font-bold">{item.name}</div>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Projects = () => {
const section = useArtboardStore((state) => state.resume.sections.projects);
return (
<Section<Project>
section={section}
urlKey="url"
dateKey="date"
summaryKey="summary"
keywordsKey="keywords"
>
{(item) => (
<div>
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const References = () => {
const section = useArtboardStore((state) => state.resume.sections.references);
return (
<Section<Reference> section={section} urlKey="url" summaryKey="summary">
{(item) => (
<div>
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
</div>
)}
</Section>
);
};
const Custom = ({ id }: { id: string }) => {
const section = useArtboardStore((state) => state.resume.sections.custom[id]);
return (
<Section<CustomSection>
section={section}
urlKey="url"
dateKey="date"
summaryKey="summary"
keywordsKey="keywords"
>
{(item) => (
<div>
<LinkedEntity
name={item.name}
url={item.url}
separateLinks={section.separateLinks}
className="font-bold"
/>
<div>{item.description}</div>
<div>{item.location}</div>
</div>
)}
</Section>
);
};
const mapSectionToComponent = (section: SectionKey) => {
switch (section) {
case "profiles": {
return <Profiles />;
}
case "summary": {
return <Summary />;
}
case "experience": {
return <Experience />;
}
case "education": {
return <Education />;
}
case "awards": {
return <Awards />;
}
case "certifications": {
return <Certifications />;
}
case "skills": {
return <Skills />;
}
case "interests": {
return <Interests />;
}
case "publications": {
return <Publications />;
}
case "volunteer": {
return <Volunteer />;
}
case "languages": {
return <Languages />;
}
case "projects": {
return <Projects />;
}
case "references": {
return <References />;
}
default: {
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
return null;
}
}
};
export const Nosepass = ({ columns, isFirstPage = false }: TemplateProps) => {
const name = useArtboardStore((state) => state.resume.basics.name);
const [main, sidebar] = columns;
return (
<div className="space-y-6 p-custom">
<div className="flex items-center justify-between">
<img alt="Europass Logo" className="h-[42px]" src="/assets/europass.png" />
<p className="font-medium text-primary">Curriculum Vitae</p>
<p className="font-medium text-primary">{name}</p>
</div>
{isFirstPage && <Header />}
<div className="space-y-4">
{main.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}
{sidebar.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}
</div>
</div>
);
};

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