diff --git a/README.MD b/README.MD index cabb027..8f26fd1 100644 --- a/README.MD +++ b/README.MD @@ -381,6 +381,102 @@ __Tip__: Notice that you can use Markdown in the cover letter, summaries and des ## Thanks to - SEO resume photo by [Markus Winkler](https://unsplash.com/@markuswinkler?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/resume?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText). +### Creating New Templates + +Creating a new resume template allows for further customization and variety in how your resume data is presented. Here's how to add a new template to the Resume Builder: + +**1. File Structure:** + +Templates are located within the `src/components/ResumeTemplates/` directory. Each new template must reside in its own subdirectory. For example, if your new template is named "Elegant", its files would be in `src/components/ResumeTemplates/Elegant/`. + +**2. Main Template File:** + +Each template directory must contain an `Index.jsx` file (e.g., `src/components/ResumeTemplates/Elegant/Index.jsx`). This file is the entry point for your template and should export the main React component that renders the resume. + +**3. Component Props:** + +Your template component (e.g., `Elegant`) will receive several props: + +* `jsonResume`: This object contains the parsed resume data, structured according to the JSON Resume schema (with any custom extensions used by this project). You'll use this data to populate your template. +* `isPrinting`: A boolean flag that indicates if the resume is currently being rendered for a print view. This can be used to apply print-specific styles. +* `customTranslations`: An object containing user-defined translations for various fields, allowing for internationalization of template labels. +* `coverLetterVariables`: An object containing variables extracted from the cover letter, which can be used for dynamic content if your template supports cover letters. + +**4. Styling and Sub-components:** + +You can style your template using Material-UI's `makeStyles` hook or any other CSS-in-JS solution you prefer. For more complex templates, you can break down the structure into smaller, reusable sub-components within your template's directory. + +**5. Registering the Template in `gatsby-node.js`:** + +For the build system and the frontend to recognize your new template, it needs to be "registered": + +* Open the `gatsby-node.js` file located in the root of the project. +* Locate the `TEMPLATES_PATH` constant. This constant points to the directory where template folders are stored (i.e., `src/components/ResumeTemplates`). +* Find the `disabledTemplates` array. If you want your template to be active immediately, ensure its directory name is *not* included in this array. +* The build process reads the subdirectories within `TEMPLATES_PATH` and, after filtering out any `disabledTemplates`, creates a global variable called `TEMPLATES_LIST`. This list is then available to the frontend, allowing users to select your new template. + +**6. Basic Example Structure (Conceptual):** + +```jsx +// src/components/ResumeTemplates/MyNewTemplate/Index.jsx +import React from 'react'; +import { makeStyles } from '@material-ui/core/styles'; + +const useStyles = makeStyles((theme) => ({ + // Your styles here + container: { + padding: theme.spacing(2), + fontFamily: 'Arial, sans-serif', // Example style + }, + header: { + textAlign: 'center', + marginBottom: theme.spacing(2), + }, + sectionTitle: { + fontWeight: 'bold', + marginTop: theme.spacing(2), + marginBottom: theme.spacing(1), + borderBottom: '1px solid #ccc', + paddingBottom: theme.spacing(0.5), + } +})); + +const MyNewTemplate = ({ jsonResume, isPrinting, customTranslations, coverLetterVariables }) => { + const classes = useStyles(); + // Safely access data, providing fallbacks if jsonResume is null or parts are missing + const basics = jsonResume?.basics || {}; + const work = jsonResume?.work || []; + // Example of using customTranslations or defaulting + const workTitle = customTranslations?.work || 'Work Experience'; + + return ( +
+
+

{basics.name || 'Your Name'}

+

{basics.label || 'Your Title'}

+

{basics.email || 'your.email@example.com'}

+
+ +
+

{workTitle}

+ {work.map((job, index) => ( +
+

{job.name} - {job.position}

+

{job.startDate} - {job.endDate}

+

{job.summary}

+
+ ))} +
+ + {/* Add more sections for education, skills, etc. */} +
+ ); +}; + +export default MyNewTemplate; +``` +This provides a basic framework. You would then flesh out the JSX to render all relevant parts of the `jsonResume` data according to your new template's design. + ## F.A.Q. **Q: Can you implement function?** @@ -393,7 +489,6 @@ A: Thank you! You can contribute by coding new features, creating pull requests, ## TODO - Fix all TODOs in the code. - Add PropTypes to the components (or even better, migrate to Typescript). -- Document how to create new templates. - Add a form to manually input resume data. - Support Redux Tools extension. - Use [jsPDF](https://github.com/MrRio/jsPDF). diff --git a/README_V1.MD b/README_V1.MD index 0344ee8..669f0c3 100644 --- a/README_V1.MD +++ b/README_V1.MD @@ -293,12 +293,6 @@ A: I can try. Open a issue and I'll see what I can do. A: Thank you! You can help by codding more features, creating pull requests, or donating via [https://bunq.me/BuyMeASoda](https://bunq.me/BuyMeASoda) -## TODO -- Add list of recent used documents -- Add option to save a built resume -- Remove bootstrap include from template.html -- Make build.js smaller / add lazyloader - ## License MIT License diff --git a/README_V2.MD b/README_V2.MD index 327cf0b..de44e16 100644 --- a/README_V2.MD +++ b/README_V2.MD @@ -43,8 +43,8 @@ First of all you need to create a Google Spreadsheet following the same rules as - **countryCode:** Country code where you're located - **network:** Social media name - **username:** Social media name - -TODO: list all +- **coverLetter**: Cover letter template +- **__translation__**: Custom translations ## Libraries - I'm using [react.js](https://github.com/facebook/react), [js-xlsx](https://github.com/sheetjs/js-xlsx) and [Material UI](https://material-ui.com/). @@ -187,13 +187,6 @@ A: I can try. Open a issue and I'll see what I can do. A: Thank you! You can help by codding more features, creating pull requests, or donating via [https://bunq.me/BuyMeASoda](https://bunq.me/BuyMeASoda) -## TODO -- Add list of recent used documents -- Add option to save a built resume -- Add check for valid spreadsheet URL on text input -- Add various missing error handlers -- Add job offers based on uploaded CV? (check for online APIs) - ## License MIT License diff --git a/README_V3.MD b/README_V3.MD index 9c00257..52f9c05 100644 --- a/README_V3.MD +++ b/README_V3.MD @@ -241,15 +241,6 @@ A: I can try. Open an issue, and I'll see what I can do. A: Thank you! You can help by codding more features, creating pull requests, or donating via [https://bunq.me/BuyMeASoda](https://bunq.me/BuyMeASoda) -## TODO -- Actually create a second template -- Add unit tests -- Add list of recent used documents -- Add option to save a built resume -- Add check for valid spreadsheet URL on text input -- Add various missing error handlers -- Add job offers based on uploaded CV? (check for online APIs) - ## License MIT License diff --git a/gatsby-node.js b/gatsby-node.js index 8ef3a2c..3d4109e 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -62,10 +62,13 @@ exports.onCreatePage = async ({ page, actions }) => { exports.onCreateWebpackConfig = async ({ plugins, actions }) => { const templates = await fs.readdir(TEMPLATES_PATH); - // TODO this fixes the 'React Refresh Babel' error when NODE_ENV is 'local' for some reason - if (process.env.NODE_ENV !== 'production') { - process.env.NODE_ENV = 'development'; - } + // TODO: The following lines were a workaround for a 'React Refresh Babel' error that occurred when NODE_ENV was set to 'local'. + // This workaround forced NODE_ENV to 'development' in such cases. + // It has been commented out to see if the underlying issue has been resolved or to encourage a more proper fix. + // If the 'React Refresh Babel' error reappears when NODE_ENV is 'local', this workaround might need to be reinstated or, preferably, the root cause addressed. + // if (process.env.NODE_ENV !== 'production') { + // process.env.NODE_ENV = 'development'; + // } actions.setWebpackConfig({ plugins: [ diff --git a/src/components/DropZone.jsx b/src/components/DropZone.jsx index 38b904d..e36734a 100644 --- a/src/components/DropZone.jsx +++ b/src/components/DropZone.jsx @@ -19,13 +19,13 @@ const DropZone = ({ handleFile, disabled, maxLength }) => { } if (files.length > maxLength) { - // TODO + alert(intl.formatMessage({ id: 'file_limit_exceeded' })); } else { // do what ever you want handleFile(files[0]); } }, - [handleFile, maxLength] + [handleFile, intl, maxLength] // Added intl to dependency array ); return ( diff --git a/src/components/LanguageSelector.jsx b/src/components/LanguageSelector.jsx index db67e92..8c14097 100644 --- a/src/components/LanguageSelector.jsx +++ b/src/components/LanguageSelector.jsx @@ -3,6 +3,7 @@ import { MenuItem, Select } from '@material-ui/core'; import { IconFlagBR, IconFlagUS, IconFlagES, IconFlagFR, IconFlagRU, IconFlagDE } from 'material-ui-flags'; import { makeStyles } from '@material-ui/core/styles'; import { changeLocale } from 'gatsby-plugin-react-intl'; +import PropTypes from 'prop-types'; import IconFlagJP from './IconFlagJA'; const useStyles = makeStyles((theme) => ({ @@ -64,7 +65,8 @@ const LanguageSelector = ({ currentLocale, onLanguageChange }) => { }; LanguageSelector.propTypes = { - // TODO + currentLocale: PropTypes.string.isRequired, + onLanguageChange: PropTypes.func, }; export default LanguageSelector; diff --git a/src/components/ResumeDrawerItems/Items/Basics.jsx b/src/components/ResumeDrawerItems/Items/Basics.jsx index c3fa168..846d900 100644 --- a/src/components/ResumeDrawerItems/Items/Basics.jsx +++ b/src/components/ResumeDrawerItems/Items/Basics.jsx @@ -138,8 +138,7 @@ function Basics({ basics }) { /> )} diff --git a/src/components/ResumeDrawerItems/Items/Education.jsx b/src/components/ResumeDrawerItems/Items/Education.jsx index b72f929..86a27c6 100644 --- a/src/components/ResumeDrawerItems/Items/Education.jsx +++ b/src/components/ResumeDrawerItems/Items/Education.jsx @@ -92,8 +92,7 @@ function Education({ education: educations }) { return (
diff --git a/src/components/ResumeDrawerItems/Items/Interests.jsx b/src/components/ResumeDrawerItems/Items/Interests.jsx index 21359b9..0760a6d 100644 --- a/src/components/ResumeDrawerItems/Items/Interests.jsx +++ b/src/components/ResumeDrawerItems/Items/Interests.jsx @@ -92,8 +92,7 @@ function Interest({ interests }) { return (
diff --git a/src/components/ResumeDrawerItems/Items/Volunteer.jsx b/src/components/ResumeDrawerItems/Items/Volunteer.jsx index f1c2ee1..4dda05d 100644 --- a/src/components/ResumeDrawerItems/Items/Volunteer.jsx +++ b/src/components/ResumeDrawerItems/Items/Volunteer.jsx @@ -94,8 +94,7 @@ function Volunteer({ volunteer: volunteerData }) { return (
diff --git a/src/components/ResumeDrawerItems/Items/Work.jsx b/src/components/ResumeDrawerItems/Items/Work.jsx index f7a8fd2..1a13ad3 100644 --- a/src/components/ResumeDrawerItems/Items/Work.jsx +++ b/src/components/ResumeDrawerItems/Items/Work.jsx @@ -106,8 +106,7 @@ function Work({ work: workData }) { return (
diff --git a/src/components/TemplateSelector.jsx b/src/components/TemplateSelector.jsx index 432f2d2..3773829 100644 --- a/src/components/TemplateSelector.jsx +++ b/src/components/TemplateSelector.jsx @@ -4,6 +4,7 @@ import { MenuItem, Select } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import { v4 as uuid } from 'uuid'; import { useIntl } from 'gatsby-plugin-react-intl'; +import PropTypes from 'prop-types'; // Hooks import { useSelector } from '../store/StoreProvider'; @@ -11,9 +12,7 @@ import { useSelector } from '../store/StoreProvider'; // Actions import { selectResumeTemplate } from '../store/selectors'; -const useStyles = makeStyles((theme) => ({ - // TODO -})); +const useStyles = makeStyles((theme) => ({})); const TemplateSelector = ({ onSelect, className }) => { const intl = useIntl(); @@ -41,7 +40,8 @@ const TemplateSelector = ({ onSelect, className }) => { }; TemplateSelector.propTypes = { - // TODO + onSelect: PropTypes.func.isRequired, + className: PropTypes.string, }; export default TemplateSelector; diff --git a/src/intl/de.json b/src/intl/de.json index 2b7427e..dd89c1b 100644 --- a/src/intl/de.json +++ b/src/intl/de.json @@ -157,7 +157,12 @@ }, "error": { "something_went_wrong_parsing": "Hoppla, beim Parsen Ihrer Tabellen-URL ist etwas schiefgelaufen. Versuchen Sie, die Tabelle herunterzuladen und hier hochzuladen.", - "something_went_wrong_loading": "Hoppla, etwas ist schiefgelaufen, bitte versuchen Sie es erneut." + "something_went_wrong_loading": "Hoppla, etwas ist schiefgelaufen, bitte versuchen Sie es erneut.", + "invalid_google_sheet_url": "Please enter a valid Google Spreadsheet URL.", + "resume_fetch_failed": "Could not fetch the resume data. Please check the username and repository.", + "resume_invalid_json": "The fetched resume data is not valid JSON.", + "resume_empty_json": "The fetched resume data is empty or invalid.", + "resume_processing_failed": "Failed to process the resume data." }, "notfound": { "title": "404: Nicht gefunden", @@ -186,5 +191,6 @@ "third_party_cookies_item_2": "Von Zeit zu Zeit testen wir neue Funktionen und nehmen subtile Änderungen an der Art und Weise vor, wie die Website bereitgestellt wird. Wenn wir noch neue Funktionen testen, können diese Cookies verwendet werden, um sicherzustellen, dass Sie während der Nutzung der Seite eine konsistente Erfahrung erhalten und um sicherzustellen, dass wir verstehen, welche Optimierungen unsere Benutzer am meisten schätzen.", "more_information": "Weitere Informationen", "more_information_text": "Hoffentlich hat dies die Dinge für Sie geklärt. Wie bereits erwähnt, ist es in der Regel sicherer, Cookies aktiviert zu lassen, falls sie mit einer der Funktionen interagieren, die Sie auf unserer Seite verwenden." - } + }, + "file_limit_exceeded": "You have exceeded the maximum number of files allowed." } diff --git a/src/intl/en.json b/src/intl/en.json index 177b6e3..62e0972 100644 --- a/src/intl/en.json +++ b/src/intl/en.json @@ -157,7 +157,12 @@ }, "error": { "something_went_wrong_parsing": "Whoops, something went wrong when parsing your spreadsheet URL. Try downloading the spreadsheet and uploading it here.", - "something_went_wrong_loading": "Whoops, something went wrong, please try again." + "something_went_wrong_loading": "Whoops, something went wrong, please try again.", + "invalid_google_sheet_url": "Please enter a valid Google Spreadsheet URL.", + "resume_fetch_failed": "Could not fetch the resume data. Please check the username and repository.", + "resume_invalid_json": "The fetched resume data is not valid JSON.", + "resume_empty_json": "The fetched resume data is empty or invalid.", + "resume_processing_failed": "Failed to process the resume data." }, "notfound": { "title": "404: Not found", @@ -186,5 +191,6 @@ "third_party_cookies_item_2": "From time to time we test new features and make subtle changes to the way that the site is delivered. When we are still testing new features these cookies may be used to ensure that you receive a consistent experience whilst on the site whilst ensuring we understand which optimisations our users appreciate the most.", "more_information": "More information", "more_information_text": "Hopefully that has clarified things for you and as was previously mentioned if there is something that you aren't sure whether you need or not it's usually safer to leave cookies enabled in case it does interact with one of the features you use on our site." - } + }, + "file_limit_exceeded": "You have exceeded the maximum number of files allowed." } diff --git a/src/intl/es.json b/src/intl/es.json index 49491af..b984e60 100644 --- a/src/intl/es.json +++ b/src/intl/es.json @@ -157,7 +157,12 @@ }, "error": { "something_went_wrong_parsing": "¡Vaya! Se produjo un error al descargar la hoja de cálculo desde la URL proporcionada. Intente descargar la hoja de cálculo manualmente y cárguela aquí.", - "something_went_wrong_loading": "Vaya, algo salió mal. Vuelve a intentarlo." + "something_went_wrong_loading": "Vaya, algo salió mal. Vuelve a intentarlo.", + "invalid_google_sheet_url": "Please enter a valid Google Spreadsheet URL.", + "resume_fetch_failed": "Could not fetch the resume data. Please check the username and repository.", + "resume_invalid_json": "The fetched resume data is not valid JSON.", + "resume_empty_json": "The fetched resume data is empty or invalid.", + "resume_processing_failed": "Failed to process the resume data." }, "notfound": { "title": "404: No Encontrado", @@ -182,9 +187,10 @@ "site_preferences_cookie_text": "Con el fin de proveerte una gran experiencia en este sitio te proveemos la capacidad de configurar cómo se correra este sitio cuando tu lo uses. Con el fin de recordar tus configuraciones necesitamos almacenar cookies para que esta información pueda ser invocada cada vez que interactuas con una página que es afectada por tus configuraciones.", "third_party_cookies": "Cookies de terceros", "third_party_cookies_text": "En algunos casos especiales también utilizamos cookies proveidas por terceros de confianza. La sección a continuación indica con detalles qué cookies de terceros podrás encontrar en este sitio.", - "third_party_cookies_item_1": "Este sitio usa las herramientas de analítica de Google que es una de las soluciones de analítica más reconocida y de confianza en la web para ayudarnos a entender cómo usas el sitio y cómo podemos mejorar tu experiencia. Estas cookies puden rastrear cosas como cuánto tiempo permaneces en el sitio y las páginas que visitas para que podamos seguir produciendo contenido atractivo. Para más información sobre cookies de las herramientas de analítica de Google, visite la página oficial.", - "third_party_cookies_item_2": "De vez en cuando probamos nuevas funcionalidades y realizamos cambios sutiles en la forma en como mostramos el sitio. Cuando seguimos probando nuevas funcionalidades estas cookies pueden ser usadas para asegurar que recibas una experiencia consistente mientras que nos aseguramos de entender que optimizaciones son más apreciadas por los usuarios.", + "third_party_cookies_item_1": "Este sitio usa las herramientas de analítica de Google che è una delle soluzioni di analítica más reconocida y de confianza en la web para ayudarnos a entender cómo usas el sitio y cómo podemos mejorar tu experiencia. Estas cookies puden rastrear cosas como cuánto tiempo permaneces en el sitio y las páginas que visitas para que podamos seguir produciendo contenido atractivo. Para más información sobre cookies de las herramientas de analítica de Google, visite la página oficial.", + "third_party_cookies_item_2": "De vez en quando probamos nuevas funcionalidades y realizamos cambios sutiles en la forma en como mostramos el sitio. Cuando seguimos probando nuevas funcionalidades estas cookies pueden ser usadas para asegurar que recibas una experiencia consistente mientras que nos aseguramos de entender que optimizaciones son más apreciadas por los usuarios.", "more_information": "Más información", "more_information_text": "Ojalá esto halla aclarado las cosas para ti y como mencionamos anteriormente si hay algo de lo que no estás seguro si es necesaria o no, usualmente es más seguro dejar las cookies habilitadas en caso que estas interactuen con alguna de las características que usas en nuestro sitio." - } + }, + "file_limit_exceeded": "You have exceeded the maximum number of files allowed." } diff --git a/src/intl/ja.json b/src/intl/ja.json index 7bc9bd0..007616c 100644 --- a/src/intl/ja.json +++ b/src/intl/ja.json @@ -157,7 +157,12 @@ }, "error": { "something_went_wrong_parsing": "おっと、スプレッドシートURLの解析中に問題が発生しました。スプレッドシートをダウンロードしてこちらにアップロードしてみてください。", - "something_went_wrong_loading": "おっと、問題が発生しました。もう一度お試しください。" + "something_went_wrong_loading": "おっと、問題が発生しました。もう一度お試しください。", + "invalid_google_sheet_url": "Please enter a valid Google Spreadsheet URL.", + "resume_fetch_failed": "Could not fetch the resume data. Please check the username and repository.", + "resume_invalid_json": "The fetched resume data is not valid JSON.", + "resume_empty_json": "The fetched resume data is empty or invalid.", + "resume_processing_failed": "Failed to process the resume data." }, "notfound": { "title": "404: 見つかりません", @@ -186,5 +191,6 @@ "third_party_cookies_item_2": "当サイトでは、新しい機能をテストしたり、提供方法を微調整することがあります。新しい機能をまだテストしている場合、これらのクッキーが使用されることがあり、ユーザーに一貫した体験を提供し、どの最適化がユーザーに好まれているかを理解するために役立ちます。", "more_information": "さらに詳しい情報", "more_information_text": "これで疑問点が解決されていれば幸いです。前述のように、特定の機能に影響を与える可能性がある場合、安全のためにクッキーを有効にしておくことをお勧めします。" - } + }, + "file_limit_exceeded": "You have exceeded the maximum number of files allowed." } diff --git a/src/intl/pt-br.json b/src/intl/pt-br.json index 999e05d..2204c4d 100644 --- a/src/intl/pt-br.json +++ b/src/intl/pt-br.json @@ -157,7 +157,12 @@ }, "error": { "something_went_wrong_parsing": "Ops, algo deu errado ao baixar a sua planilha da URL fornecida. Tente fazer o download da planilha manualmente e carregue-a aqui.", - "something_went_wrong_loading": "Ops, algo deu errado, por favor tente novamente." + "something_went_wrong_loading": "Ops, algo deu errado, por favor tente novamente.", + "invalid_google_sheet_url": "Please enter a valid Google Spreadsheet URL.", + "resume_fetch_failed": "Could not fetch the resume data. Please check the username and repository.", + "resume_invalid_json": "The fetched resume data is not valid JSON.", + "resume_empty_json": "The fetched resume data is empty or invalid.", + "resume_processing_failed": "Failed to process the resume data." }, "notfound": { "title": "404: Não encontrado", @@ -171,7 +176,7 @@ "we_use_cookies": "Usamos cookies para garantir que você obtenha a melhor experiência em nosso site. Ao usar nosso site, você concorda com nossa ", "title": "Política de cookies", "what_are_cookies": "O que são cookies?", - "what_are_cookies_text": "Como é prática comum em quase todos os sites profissionais, este site usa cookies, que são pequenos arquivos baixados para o seu computador, para melhorar a sua experiência. Esta página descreve quais informações eles coletam, como as usamos e por que às vezes precisamos armazenar esses cookies. Também compartilharemos como você pode evitar que esses cookies sejam armazenados, no entanto, isso pode diminuir ou 'quebrar' certos elementos da funcionalidade do site. Para obter mais informações gerais sobre cookies, leia ", + "what_are_cookies_text": "Como é prática comum em quase todos os sites profissionais, este site usa cookies, que são pequenos arquivos baixados para o seu computador, para mejorar a sua experiência. Esta página descreve quais informações eles coletam, como as usamos e por que às vezes precisamos armazenar esses cookies. Também compartilharemos como você pode evitar que esses cookies sejam armazenados, no entanto, isso pode diminuir ou 'quebrar' certos elementos da funcionalidade do site. Para obter mais informações gerais sobre cookies, leia ", "what_are_cookies_more_info_url": "https://pt.wikipedia.org/wiki/Cookie_(informática)", "how_we_use_cookies": "Como usamos cookies", "how_we_use_cookies_text": "Usamos cookies por vários motivos detalhados abaixo. Infelizmente, na maioria dos casos, não há opções padrão da indústria para desabilitar cookies sem desabilitar completamente a funcionalidade e os recursos que eles adicionam a este site. Recomenda-se que você deixe todos os cookies se não tiver certeza se precisa deles ou não, caso sejam usados para fornecer um serviço que você usa.", @@ -182,9 +187,10 @@ "site_preferences_cookie_text": "Para lhe proporcionar uma excelente experiência neste site, fornecemos a funcionalidade para definir as suas preferências de funcionamento deste site quando o utiliza. Para lembrar suas preferências, precisamos definir cookies para que essas informações possam ser chamadas sempre que você interagir com uma página afetada por suas preferências.", "third_party_cookies": "Cookies de terceiros", "third_party_cookies_text": "Em alguns casos especiais, também usamos cookies fornecidos por terceiros confiáveis. A seção a seguir detalha quais cookies de terceiros você pode encontrar neste site.", - "third_party_cookies_item_1": "Este site usa o Google Analytics, que é uma das soluções de análise mais difundidas e confiáveis na web para nos ajudar a entender como você usa o site e como podemos melhorar sua experiência. Esses cookies podem rastrear coisas como quanto tempo você passa no site e as páginas que você visita para que possamos continuar a produzir conteúdo envolvente. Para obter mais informações sobre os cookies do Google Analytics, consulte a página oficial do Google Analytics.", + "third_party_cookies_item_1": "Este site usa o Google Analytics, que é uma das soluções de análise mais difundidas e confiáveis na web para nos ajudar a entender como você usa o site e como podemos mejorar sua experiência. Esses cookies podem rastrear coisas como quanto tempo você passa no site e as páginas que você visita para que possamos continuar a produzir conteúdo envolvente. Para obter mais informações sobre os cookies do Google Analytics, consulte a página oficial do Google Analytics.", "third_party_cookies_item_2": "De vez em quando, testamos novos recursos e fazemos mudanças sutis na maneira como o site é fornecido. Quando ainda estamos testando novos recursos, esses cookies podem ser usados para garantir que você receba uma experiência consistente enquanto estiver no site, garantindo que entendemos quais otimizações nossos usuários mais apreciam.", "more_information": "Mais informações", "more_information_text": "Esperamos que isso tenha esclarecido as coisas para você e, conforme mencionado anteriormente, se há algo que você não tem certeza se precisa ou não, geralmente é mais seguro deixar os cookies ativados, caso eles interajam com um dos recursos que você usa em nosso site." - } + }, + "file_limit_exceeded": "You have exceeded the maximum number of files allowed." } diff --git a/src/intl/ru.json b/src/intl/ru.json index 91890ff..372bcdc 100644 --- a/src/intl/ru.json +++ b/src/intl/ru.json @@ -157,7 +157,12 @@ }, "error": { "something_went_wrong_parsing": "Упс, при разборе URL вашей таблицы произошла ошибка. Попробуйте загрузить таблицу и загрузить её сюда.", - "something_went_wrong_loading": "Упс, что-то пошло не так, попробуйте еще раз." + "something_went_wrong_loading": "Упс, что-то пошло не так, попробуйте еще раз.", + "invalid_google_sheet_url": "Please enter a valid Google Spreadsheet URL.", + "resume_fetch_failed": "Could not fetch the resume data. Please check the username and repository.", + "resume_invalid_json": "The fetched resume data is not valid JSON.", + "resume_empty_json": "The fetched resume data is empty or invalid.", + "resume_processing_failed": "Failed to process the resume data." }, "notfound": { "title": "404: Не найдено", @@ -186,5 +191,6 @@ "third_party_cookies_item_2": "Время от времени мы тестируем новые функции и вносим тонкие изменения в способ предоставления сайта. Когда мы всё ещё тестируем новые функции, эти файлы cookie могут использоваться для обеспечения того, чтобы вы получали постоянный опыт на сайте, и чтобы мы могли понять, какие оптимизации больше всего нравятся нашим пользователям.", "more_information": "Дополнительная информация", "more_information_text": "Надеемся, это помогло вам лучше понять ситуацию. Как уже упоминалось, если вы не уверены, нужны ли они вам или нет, обычно безопаснее оставить файлы cookie включенными на случай, если они взаимодействуют с одной из функций, которые вы используете на нашем сайте." - } + }, + "file_limit_exceeded": "You have exceeded the maximum number of files allowed." } diff --git a/src/pages/Build.jsx b/src/pages/Build.jsx index 9fca030..4140bde 100644 --- a/src/pages/Build.jsx +++ b/src/pages/Build.jsx @@ -109,10 +109,22 @@ const BuildPage = ({ params, uri, location }) => { ...paramFormValues, }, onSubmit: (values) => { - // TODO + console.log('Form submitted with values:', values); + // In a real scenario, you would process these values, e.g., pass them to getResumeJsonFromFormik + // For now, logging is sufficient. }, - validate: (values, props) => { - // TODO + validate: (values) => { + const errors = {}; + if (!values['basics-0-name']) { + errors['basics-0-name'] = 'Required'; + } + // Add more checks for other fields as needed, for example: + // if (!values['basics-0-email']) { + // errors['basics-0-email'] = 'Required'; + // } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values['basics-0-email'])) { + // errors['basics-0-email'] = 'Invalid email address'; + // } + return errors; }, }); diff --git a/src/pages/ResumeViewer.jsx b/src/pages/ResumeViewer.jsx index 3ad31d5..0a6598b 100644 --- a/src/pages/ResumeViewer.jsx +++ b/src/pages/ResumeViewer.jsx @@ -4,6 +4,7 @@ import { lazy, Suspense, useEffect, useMemo, useState } from 'react'; import { navigate, useIntl, RawIntlProvider } from 'gatsby-plugin-react-intl'; import { v4 as uuid } from 'uuid'; import { cloneDeep } from 'lodash'; +import { Typography } from '@material-ui/core'; // Added Typography import // Components import SEO from '../components/SEO'; @@ -38,17 +39,16 @@ const importTemplate = (template) => const ResumeViewer = ({ params, uri }) => { const intl = useIntl(); - const [username, lang] = (params['*'] || '').split('/'); // TODO - const template = uri.split('/').pop(); // TODO + const [username, lang] = (params['*'] || '').split('/'); + const template = uri.split('/').pop(); const isPrinting = useDetectPrint(); + const [errorMessage, setErrorMessage] = useState(null); // Added errorMessage state const pageIntl = useMemo(() => { const newIntl = templateIntls.find((tempIntl) => tempIntl.locale === lang); - if (!newIntl) { return templateIntls.find((tempIntl) => tempIntl.locale === intl.defaultLocale); } - return newIntl; }, [intl.defaultLocale, lang]); @@ -57,33 +57,46 @@ const ResumeViewer = ({ params, uri }) => { const dispatch = useDispatch(); const validTemplate = TEMPLATES_LIST.find((templateName) => templateName.toLowerCase() === template.toLowerCase()); - // TODO const hasData = isObjectNotEmpty(toggleableJsonResume); useEffect(() => { const fetchResumeJsonAndLoadTemplate = async () => { + if (!username) { + // This case was previously navigate('/'), now setting an error or handling appropriately. + // For this refactor, if username is missing, we can't fetch, so an error is appropriate. + setErrorMessage(intl.formatMessage({ id: 'error.resume_fetch_failed' })); + return; + } + const jsonString = await fetchGithubResumeJson(username); + if (!jsonString) { // Check if jsonString is null or empty (fetch failure) + setErrorMessage(intl.formatMessage({ id: 'error.resume_fetch_failed' })); + return; + } + if (!isValidJsonString(jsonString)) { - navigate('/'); + setErrorMessage(intl.formatMessage({ id: 'error.resume_invalid_json' })); + return; } const jsonResume = JSON.parse(jsonString); if (!isObjectNotEmpty(jsonResume)) { - navigate('/'); + setErrorMessage(intl.formatMessage({ id: 'error.resume_empty_json' })); + return; } const toggleableObject = convertToToggleableObject( cloneDeep({ ...jsonResume, - // eslint-disable-next-line no-underscore-dangle __translation__: jsonResume.__translation__, enableSourceDataDownload: jsonResume.enableSourceDataDownload, - // Cover Letter not supported for the viewer - coverLetter: {}, + coverLetter: {}, // Cover Letter not supported for the viewer }) ); + if (!isObjectNotEmpty(toggleableObject)) { - navigate('/'); + setErrorMessage(intl.formatMessage({ id: 'error.resume_processing_failed' })); + return; } dispatch(setToggleableJsonResume(toggleableObject)); @@ -91,10 +104,8 @@ const ResumeViewer = ({ params, uri }) => { setResumeTemplate([