mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-14 14:57:00 +10:00
clear everything
This commit is contained in:
@@ -1,38 +0,0 @@
|
||||
---
|
||||
slug: '/blog/acing-video-interviews'
|
||||
date: '2020-07-15'
|
||||
title: 'Acing Video Interviews'
|
||||
---
|
||||
|
||||
Today, as in-person interviewing has had to cease or slow due to restrictions during the Covid-19 pandemic, thousands of professionals are now needing to learn how to effectively interview in a new way using online platforms, such as Zoom, GoToMeeting and others.
|
||||
Here are 10 helpful tips for making the best impression you can in your video interview, and demonstrating that you’re a great fit for the role.
|
||||
|
||||
#### Be conscious of what’s in the view
|
||||
|
||||
While so many of us are now working remotely and using Zoom or other platforms for our meetings, we’ve grown more accustomed to seeing people in their home settings, and noticing their home décor, pets, family members, and other aspects of their personal life in the background.
|
||||
|
||||
For an interview, it’s fine to be in your home or living room but try to present whatever people see as neutral and professional as possible. You want to let yourself and your words, conversation and experience speak most powerfully about your qualifications and suitability for the job. And you want to avoid the chance that your interviewer will be distracted by what’s behind you, or perhaps have a negative reaction to any personal items (such as a plate of food behind you or a messy room) in your home.
|
||||
|
||||
#### Select professional attire
|
||||
|
||||
Even though you’re conducting the interview from your home, remember you are being judged and assessed for your fit for the role, so dress professionally, just as you would if you were meeting in person.
|
||||
|
||||
#### Ready your sound and video equipment
|
||||
|
||||
Make sure that you have working Wi-Fi, a strong connection, and a quality headset or microphone so there are no tech issues during your call. Invest in quality equipment for audio and video work.
|
||||
|
||||
#### Demonstrate positive body language and behavior
|
||||
|
||||
Just as in an in-person interview, you want to demonstrate through your voice and body language that you’re interested, engaged, and professional in demeanor and language. Make sure you are not distracted (with your pet, or by loud sounds or interruptions in your home, etc.). If you know there will be significant interruptions or distractions during the scheduled time for the interview, see if you can change it to a time when those interruptions are at minimum.
|
||||
|
||||
#### Engage the interviewer with eye contact and connection
|
||||
|
||||
Make sure you smile, come across as engaging and interested, and make strong eye contact. Try not to look away during your interview or look down at your notes too frequently. Your eye contact reveals a good deal about how you’re feeling and thinking about what the interviewer is sharing with you.
|
||||
|
||||
#### As with every interview, prepare, prepare, prepare
|
||||
|
||||
Be fully prepared for your interview. Do your research in advance, understand clearly from what the hiring manager has shared in advance what they’re looking for in the role and be ready to talk about why you’re potentially very well suited to it. Have in front of you some written sound bites and bullet points that speak to how you can leverage your great talents and abilities and hit the ground running successfully in this job.
|
||||
|
||||
#### Finally, remember that you’re talented, experienced and have so much value to offer and that the interview is a two-way street
|
||||
|
||||
Don’t lose sight of the fact that you have a great deal to offer and so much experience and talent to leverage to be of service in important ways. Make sure too that you understand this is a two-way street and you are interviewing the hiring manager about the role and the organization just as much as they are interviewing you. Have your list of questions that you want to make sure you cover so that you will get a strong sense of this role, the work, and if you would truly be a fit, both emotionally and functionally.
|
||||
@@ -1,45 +0,0 @@
|
||||
---
|
||||
slug: '/blog/ats-friendly-resumes'
|
||||
date: '2020-07-14'
|
||||
title: 'ATS-Friendly Resumes'
|
||||
---
|
||||
|
||||
An ATS (Applicant Tracking System) is software used by companies to help them quickly evaluate potential candidates for any given job opening.
|
||||
|
||||
ATS software automatically scans and processes each job application a company receives, and ranks them according to their relevant qualifications. It then produces a shortlist of qualified candidates to be reviewed by a hiring manager. If your resume doesn’t meet the requirements of a company’s ATS, your application will likely be rejected before a hiring manager even gets to look at it.
|
||||
|
||||
Applicant tracking systems (ATS) eliminate over 70% of applicants before their resume even reaches a hiring manager. Make sure your application makes the cut by learning how to write an ATS-friendly resume with our expert tips, examples, and ATS resume templates.
|
||||
|
||||
#### What is an ATS-compliant Resume?
|
||||
|
||||
An ATS-compliant resume is a resume designed specifically to make it easier for ATS software to find the information it’s looking for.
|
||||
|
||||
For example, this could mean using an easy-to-read resume format, or removing objects such as tables or images because they’re difficult for the ATS to parse. Resumes designed to be compliant with ATS software have a much higher chance of getting into the hands of a human hiring manager, which is one step closer to an interview.
|
||||
|
||||
#### How to design an ATS-friendly Resume
|
||||
|
||||
Here are six tips to help you make a more ATS-friendly resume and ultimately beat the applicant tracking system.
|
||||
|
||||
#### 1. Follow a standard resume format
|
||||
|
||||
Use a chronological resume to ensure the software can parse your experience section.
|
||||
|
||||
#### 2. Correctly label your section
|
||||
|
||||
By sticking to common headings, you prevent the bot from placing your qualifications under the wrong categories, or misreading your sections altogether.
|
||||
|
||||
#### 3. Include job-related keywords
|
||||
|
||||
To help determine whether your qualifications are relevant to the position, ATS software scans your resume for specific job-related resume keywords. To increase your chance of getting into the interview pool, look through the job listing for these words to include on your resume.
|
||||
|
||||
#### 4. Use an ATS-friendly resume template
|
||||
|
||||
Many job seekers use fancy resume templates to help them stand out from other candidates. However, templates with graphic elements, tables, or unique fonts are difficult for most ATS software to read.
|
||||
|
||||
#### 5. Use a common resume font
|
||||
|
||||
Most ATS software is programmed to read more common typefaces. Using an unusual or outdated looking font can result in your resume being rendered incorrectly, with large chunks of your information left unreadable.
|
||||
|
||||
#### 6. Save your resume as the proper file type
|
||||
|
||||
PDFs are the preferred file format for most companies today, and are easily understood by any modern applicant tracking system.
|
||||
@@ -1,92 +0,0 @@
|
||||
---
|
||||
slug: '/blog/design-beautiful-resumes'
|
||||
date: '2020-07-13'
|
||||
title: 'Designing Beautiful Resumes'
|
||||
---
|
||||
|
||||
Follow these 16 pro tips to help your design resume stand out from the crowd.
|
||||
|
||||
With designers fighting it out for every job that comes along, it's important that you stand out from the crowd. Whether you're just starting out or a seasoned pro applying for a better position, your design resume needs to be first rate for you to stand a chance of getting an interview.
|
||||
|
||||
For your design resume to really shine, you need to think carefully about how it's designed as well as what's written. Here, we'll cover both, as we walk you through the process of creating a stellar designer resume. You'll be landing that dream design job in no time.
|
||||
|
||||
#### 1. Avoid word processors
|
||||
|
||||
Microsoft Word might be okay if you’re applying for an admin position, but if you’re after a design job or something creative, its limited and idiosyncratic layout options just won't cut it. Art directors will be paying close attention to the layout of your resume as much as the content, so use InDesign CC or even Illustrator CC to design something special.
|
||||
|
||||
Whatever program you use to design your resume in, PDF is the best format to supply it in. This enables you to create good-looking documents that are completely cross-platform.
|
||||
|
||||
#### 2. Choose your fonts wisely
|
||||
|
||||
You’re a designer, so your resume should follow the latest trends in typography, right? Wrong! The aim of any resume should be legibility, so it’s generally a wise idea to stick to simple, readable fonts. You don't need to shell out lots of cash to find something suitable either – take a look at our list of the best free fonts for designers.
|
||||
|
||||
And if you would like to use more than one font, you can also check our perfect font pairings.
|
||||
|
||||
#### 3. Consider using colour
|
||||
|
||||
For most non-design-related jobs, a resume designed or printed in colour is probably a waste of time. However, for design positions, touches of colour are an acceptable way to add a discreet personal touch. Use colour carefully, however, and don't go over the top. Green type on a yellow page will stand out for all the wrong reasons. See our post on colour theory for more info on this.
|
||||
|
||||
#### 4. Be brief
|
||||
|
||||
Art directors do not have the time or the inclination to read your entire life story. Your resume should ideally fit onto one side of A4, and if it's any longer than two pages, you’re waffling and including too much stuff.
|
||||
|
||||
Don’t be tempted to mask a lack of experience with verbosity. Clean, well-laid-out resumes will always win over flabby ones – remember, the aim is to intrigue and impress. Point the recipient in the direction of an online portfolio to see more.
|
||||
|
||||
#### 5. Include your contact info
|
||||
|
||||
As a minimum, your resume should include your name and contact details, including your email address, phone number and online portfolio URL. Don't assume that because these are at the bottom of the email you sent, you don't need to include them. Make life easier for your potential employer.
|
||||
|
||||
This should be followed by a breakdown of your work experience, then your education. In both cases, this should be most recent first. Work experience should include dates, job title and a brief synopsis of your role. Don't bother including jobs you did years ago that are irrelevant to the job you're applying for. References are generally optional.
|
||||
|
||||
#### 6. Don't lie on your resume
|
||||
|
||||
We once received a resume from an unnamed individual who claimed to have created quite a stunning website. We would have been extremely impressed were it not for the fact that we had actually designed the site.
|
||||
|
||||
Needless to say, that resume went straight in the bin and the sender was rewarded with a strongly worded email. Honesty is always the best policy, as you stand a good chance of being found out if you start 'elaborating' in your resume.
|
||||
|
||||
#### 6. Include samples of work
|
||||
|
||||
By not including any samples of your work with your resume, you’re pretty much guaranteeing that the recipient will not consider you for the post. If you work with motion, stills are perfect, unless you’ve been specifically asked to include a showreel. On the other hand, don't go overboard with images – that's a job for your online graphic design portfolio, which you can provide a link to. Alternatively, you can provide a curated version of your portfolio in PDF format.
|
||||
|
||||
#### 7. Keep it simple
|
||||
|
||||
Unless you’re really confident and sure about what you’re doing, keep the typographic flourishes and fanciful designs at bay, ensure the layout is simple and clear and the information is cleanly presented. After all, the last thing you want is the recipient squinting because you thought dark grey text on a black background was a great idea.
|
||||
|
||||
#### 8. Show your personality
|
||||
|
||||
Simple does not have to mean dull. A resume is a reflection of your disposition and persona, and the recipient will be scanning it, consciously or not, for elements that distinguish your resume from the other hundreds they have to wade through. Make your resume stand out with an idiosyncratic design and personal touches... just don't overdo it.
|
||||
|
||||
#### 9. Beware the novelty approach to resumes
|
||||
|
||||
We’ve had resumes written on scrunched up paper; arriving in the form of a jigsaw; and playing cards. We’ve had giant resume posters, inflatable resumes and resumes crafted using delicate and complex paper engineering.
|
||||
|
||||
Off-the-wall resumes stick in the mind (you can see some of the best examples in our roundup of creative resumes) but they're a risky proposition. On the one hand you might appear like a creative thinker, on the other it might seem pretentious and excessive. It depends on the recipient.
|
||||
|
||||
#### 10. Don't plagiarise
|
||||
|
||||
We've all seen this clever resume concept... so don't try to pass it off as your idea
|
||||
|
||||
A surprising number of graduates see an inspiring resume design concept and copy it. What can they be thinking? We all have access to the same internet, and if a particularly inventive resume design has caught your eye, there's a strong chance it's been shared virally within the industry and will have caught the eye of your potential employer, too. Your resume should showcase your creativity, not someone else's.
|
||||
|
||||
#### 11. Use proper prints, not photocopies
|
||||
|
||||
Photocopies are cheap, but sadly they also look cheap, especially second and third generation copies. Type starts to break up, images are contrasty and full of noise, fingerprints and other blemishes begin to show up, and the results can look slightly askew. Fresh laser prints or sharp inkjet prints on the best quality paper available are the minimum standard. For more info, check out our designer's guide to printing.
|
||||
|
||||
#### 12. Demonstrate consistency
|
||||
|
||||
Real-world design projects are usually centred around a single, consistent theme or concept that runs throughout the logo, branding, literature and so on. Your résumé, portfolio and covering letter need to demonstrate the same consistency. For example, are bulleted lists presented in the same style across each of your pages? Is the colour scheme consistent?
|
||||
|
||||
#### 13. Spend time on the covering letter
|
||||
|
||||
Most of the time, when you apply for a job, your resume will need to be accompanied by a covering letter. This should look formal and business-like: this isn't the place to showcase your creativity and imagination. The text should complement the CV and it's best to keep it short and to the point (three paragraphs is a good rule of thumb).
|
||||
|
||||
Make it obvious you haven't just copied and pasted the same letter you've used to apply for a hundred other jobs. Write it in a way that's personal to the particular job and company you're applying for.
|
||||
|
||||
#### 14. Create multiple resumes
|
||||
|
||||
If you're applying for multiple jobs, you should create multiple resumes, each targeting a specific role and the kind of experience and skills the prospective employers are looking for. To take an obvious example, if the job specifically mentions InDesign as a requirement then you should make this first on your list of skills, and possibly expand the description of how and where you've used it.
|
||||
|
||||
#### 15. Check your spelling!
|
||||
|
||||
If you're getting this one wrong, you're in trouble
|
||||
If you're applying for a job as a designer, does it matter how well you write? The simple answer is yes. Spelling and grammar mistakes will make you appear uneducated, ignorant and/or lazy – and none of these represent the image you're trying to convey. So, always double-check your grammar and spelling, and get others to check it too (it's easy to miss your own mistakes)
|
||||
@@ -1,37 +0,0 @@
|
||||
---
|
||||
slug: '/blog/jobs-during-covid-19'
|
||||
date: '2020-07-16'
|
||||
title: 'Jobs During COVID-19'
|
||||
---
|
||||
|
||||
As companies move to remote work to fight the coronavirus pandemic and an increasing number of workers are being laid off or furloughed, you might be wondering if you should continue to send out resumes or just assume that no one is hiring for the foreseeable future. It’s true that economists are predicting a recession, but career experts say it’s best to keep networking and applying, provided you change your approach a bit to acknowledge these are uncertain times.
|
||||
|
||||
Be prepared for job openings to be put on hold or disappear, even if they’ve been open for a while. That doesn’t mean they won’t open up again in a few months. Landers admits she herself was getting ready to hire someone but decided to put that on hold for a few weeks.
|
||||
|
||||
With all that said, you can still be actively working on your job search. These tips will help you navigate the process during the pandemic and the accompanying economic slowdown.
|
||||
|
||||
#### 1. Consider How Urgent You Are Searching
|
||||
|
||||
While many industries have and will continue to be hit hard by the COVID-19 pandemic, others are still hiring. If you’re unemployed and need a stopgap, consider looking there or wherever else you can find an opportunity that makes sense for you—and pays the rent and puts food on the table—in the meantime.
|
||||
|
||||
#### 2. Get Comfortable Networking Online
|
||||
|
||||
Events will be cancelled for a while, so you’ll need to find a new networking strategy. Seek out like-minded professionals online and ask about virtual events.
|
||||
Look for professional groups to join on Facebook and LinkedIn. Both platforms offer a wide range of options with groups for every profession. For instance, if you’re looking for a job in marketing, you could join LinkedIn’s Global Marketing and Communications Professionals group. Join in the conversation, post and comment, and make yourself visible.
|
||||
|
||||
#### 3. Stay In Touch
|
||||
|
||||
Maybe you recently had a promising interview and a job offer seemed to be on the horizon, but now the company has moved to remote work and you haven’t heard from the hiring manager. What should you do? Check in with the hiring manager by email, acknowledging that they might be scrambling to help their employees get used to the new setup.
|
||||
|
||||
#### 4. Gather Intel
|
||||
|
||||
The COVID-19 crisis can provide a unique glimpse into company culture. Take note of how leadership deals with this emergency and treats its employees by following the company on social media and watching for any media coverage.
|
||||
You can mention what you read and listened to and use your specific knowledge to drive home how you could help the company achieve its goals if hired.
|
||||
|
||||
#### 5. Use the Time to Reflect
|
||||
|
||||
Job seekers often jump at the first available opportunity or go into their search without fully considering what they want to do next. Take advantage of the slowing job market by getting clarity about where you want to work and the type of role and title you're seeking.
|
||||
|
||||
#### 6. Boost Your Skills
|
||||
|
||||
Now is the perfect time to work on bolstering your qualifications, Moser says. Analyze job descriptions by listing each required skill and experience. Then consider whether you have that exact skill, if you have the skill but haven’t used it in a few years, or if you’re lacking the skill entirely. Use that information to determine what you need to brush up on to make yourself an even better candidate when the job market picks up again.
|
||||
@@ -1,47 +0,0 @@
|
||||
/* eslint-disable react/no-danger */
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { graphql } from 'gatsby';
|
||||
import React from 'react';
|
||||
import Hero from './landing/Hero';
|
||||
import * as styles from './Blog.module.css';
|
||||
import Wrapper from './shared/Wrapper';
|
||||
|
||||
export default function Template({ data }) {
|
||||
const { markdownRemark } = data;
|
||||
const { frontmatter, html } = markdownRemark;
|
||||
return (
|
||||
<Wrapper>
|
||||
<div className="my-24 container">
|
||||
<Hero />
|
||||
|
||||
<Helmet>
|
||||
<title>{frontmatter.title} | Reactive Resume</title>
|
||||
<link
|
||||
rel="canonical"
|
||||
href={`https://rxresu.me/blog/${frontmatter.slug}`}
|
||||
/>
|
||||
</Helmet>
|
||||
|
||||
<h1 className="mt-16 text-3xl">{frontmatter.title}</h1>
|
||||
<h2>{frontmatter.date}</h2>
|
||||
<div
|
||||
className={styles.container}
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
</div>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export const pageQuery = graphql`
|
||||
query ($slug: String!) {
|
||||
markdownRemark(frontmatter: { slug: { eq: $slug } }) {
|
||||
html
|
||||
frontmatter {
|
||||
date(formatString: "MMMM DD, YYYY")
|
||||
slug
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -1,11 +0,0 @@
|
||||
.container {
|
||||
@apply leading-loose my-8;
|
||||
}
|
||||
|
||||
.container p {
|
||||
@apply my-4;
|
||||
}
|
||||
|
||||
.container h4 {
|
||||
@apply font-medium text-xl;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo } from 'react';
|
||||
import * as styles from './Artboard.module.css';
|
||||
import { useSelector } from '../../../contexts/ResumeContext';
|
||||
import Castform from '../../../templates/Castform';
|
||||
import Celebi from '../../../templates/Celebi';
|
||||
import Gengar from '../../../templates/Gengar';
|
||||
import Glalie from '../../../templates/Glalie';
|
||||
import Onyx from '../../../templates/Onyx';
|
||||
import Pikachu from '../../../templates/Pikachu';
|
||||
|
||||
const Artboard = () => {
|
||||
const state = useSelector();
|
||||
const { t } = useTranslation();
|
||||
const { id, name, metadata } = state;
|
||||
const { template } = metadata;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>
|
||||
{name} | {t('shared.appName')}
|
||||
</title>
|
||||
<link rel="canonical" href={`https://rxresu.me/app/builder/${id}`} />
|
||||
</Helmet>
|
||||
|
||||
<div id="page" className={styles.container}>
|
||||
{template === 'onyx' && <Onyx data={state} />}
|
||||
{template === 'pikachu' && <Pikachu data={state} />}
|
||||
{template === 'gengar' && <Gengar data={state} />}
|
||||
{template === 'castform' && <Castform data={state} />}
|
||||
{template === 'glalie' && <Glalie data={state} />}
|
||||
{template === 'celebi' && <Celebi data={state} />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Artboard);
|
||||
@@ -1,11 +0,0 @@
|
||||
@media screen {
|
||||
.container {
|
||||
width: 210mm;
|
||||
height: 297mm;
|
||||
overflow: scroll;
|
||||
margin: 2rem auto;
|
||||
box-shadow: var(--shadow);
|
||||
|
||||
@apply bg-white rounded;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import { Link } from 'gatsby';
|
||||
import { Tooltip } from '@material-ui/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo } from 'react';
|
||||
import Avatar from '../../shared/Avatar';
|
||||
import Logo from '../../shared/Logo';
|
||||
import * as styles from './LeftNavbar.module.css';
|
||||
import SectionIcon from '../../shared/SectionIcon';
|
||||
import sections from '../../../data/leftSections';
|
||||
|
||||
const LeftNavbar = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Tooltip title={t('builder.tooltips.backToDashboard')} placement="right">
|
||||
<div>
|
||||
<Link to="/app/dashboard">
|
||||
<Logo size="40px" />
|
||||
</Link>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
<hr className="my-6" />
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 text-primary-500">
|
||||
{sections.map((x) => (
|
||||
<SectionIcon
|
||||
key={x.id}
|
||||
section={x}
|
||||
containerId="LeftSidebar"
|
||||
tooltipPlacement="right"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<hr className="mt-auto my-6" />
|
||||
|
||||
<Avatar />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(LeftNavbar);
|
||||
@@ -1,7 +0,0 @@
|
||||
.container {
|
||||
width: 75px;
|
||||
z-index: 20;
|
||||
box-shadow: var(--left-shadow);
|
||||
|
||||
@apply px-4 py-6 h-screen flex flex-col items-center;
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import { Element } from 'react-scroll';
|
||||
import React, { Fragment, memo, useContext } from 'react';
|
||||
import * as styles from './LeftSidebar.module.css';
|
||||
import Awards from './sections/Awards';
|
||||
import Certifications from './sections/Certifications';
|
||||
import Education from './sections/Education';
|
||||
import Hobbies from './sections/Hobbies';
|
||||
import Languages from './sections/Languages';
|
||||
import LeftNavbar from './LeftNavbar';
|
||||
import Objective from './sections/Objective';
|
||||
import Profile from './sections/Profile';
|
||||
import Projects from './sections/Projects';
|
||||
import References from './sections/References';
|
||||
import Skills from './sections/Skills';
|
||||
import Social from './sections/Social';
|
||||
import Work from './sections/Work';
|
||||
import sections from '../../../data/leftSections';
|
||||
import SettingsContext from '../../../contexts/SettingsContext';
|
||||
|
||||
const getComponent = (id) => {
|
||||
switch (id) {
|
||||
case 'profile':
|
||||
return Profile;
|
||||
case 'social':
|
||||
return Social;
|
||||
case 'objective':
|
||||
return Objective;
|
||||
case 'work':
|
||||
return Work;
|
||||
case 'education':
|
||||
return Education;
|
||||
case 'projects':
|
||||
return Projects;
|
||||
case 'awards':
|
||||
return Awards;
|
||||
case 'certifications':
|
||||
return Certifications;
|
||||
case 'skills':
|
||||
return Skills;
|
||||
case 'hobbies':
|
||||
return Hobbies;
|
||||
case 'languages':
|
||||
return Languages;
|
||||
case 'references':
|
||||
return References;
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
};
|
||||
|
||||
const SidebarSection = ({ id, event }) => {
|
||||
const Component = getComponent(id);
|
||||
|
||||
return (
|
||||
<Fragment key={id}>
|
||||
<Element name={id}>
|
||||
<Component id={id} event={event} />
|
||||
</Element>
|
||||
<hr />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const LeftSidebar = () => {
|
||||
const { isSideBarOpen } = useContext(SettingsContext);
|
||||
return (
|
||||
<div className="flex">
|
||||
<LeftNavbar />
|
||||
{isSideBarOpen && (
|
||||
<div id="LeftSidebar" className={styles.container}>
|
||||
{sections.map(SidebarSection)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default memo(LeftSidebar);
|
||||
@@ -1,6 +0,0 @@
|
||||
.container {
|
||||
z-index: 10;
|
||||
|
||||
@apply w-full h-screen overflow-scroll p-8;
|
||||
@apply grid gap-8;
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import Input from '../../../shared/Input';
|
||||
import List from '../../lists/List';
|
||||
|
||||
const Awards = ({ id, event }) => {
|
||||
const path = `${id}.items`;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading id={id} />
|
||||
|
||||
<Input
|
||||
name="heading"
|
||||
label={t('builder.sections.heading')}
|
||||
path={`${id}.heading`}
|
||||
/>
|
||||
|
||||
<List
|
||||
path={path}
|
||||
event={event}
|
||||
titlePath="title"
|
||||
subtitlePath="awarder"
|
||||
textPath="summary"
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Awards);
|
||||
@@ -1,32 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import Input from '../../../shared/Input';
|
||||
import List from '../../lists/List';
|
||||
|
||||
const Certifications = ({ id, event }) => {
|
||||
const path = `${id}.items`;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading id={id} />
|
||||
|
||||
<Input
|
||||
name="heading"
|
||||
label={t('builder.sections.heading')}
|
||||
path={`${id}.heading`}
|
||||
/>
|
||||
|
||||
<List
|
||||
path={path}
|
||||
event={event}
|
||||
titlePath="title"
|
||||
subtitlePath="issuer"
|
||||
textPath="summary"
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Certifications);
|
||||
@@ -1,32 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import Input from '../../../shared/Input';
|
||||
import List from '../../lists/List';
|
||||
|
||||
const Education = ({ id, event }) => {
|
||||
const path = `${id}.items`;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading id={id} />
|
||||
|
||||
<Input
|
||||
name="heading"
|
||||
label={t('builder.sections.heading')}
|
||||
path={`${id}.heading`}
|
||||
/>
|
||||
|
||||
<List
|
||||
hasDate
|
||||
path={path}
|
||||
event={event}
|
||||
titlePath="institution"
|
||||
textPath="field"
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Education);
|
||||
@@ -1,26 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import Input from '../../../shared/Input';
|
||||
import List from '../../lists/List';
|
||||
|
||||
const Hobbies = ({ id, event }) => {
|
||||
const path = `${id}.items`;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading id={id} />
|
||||
|
||||
<Input
|
||||
name="heading"
|
||||
label={t('builder.sections.heading')}
|
||||
path={`${id}.heading`}
|
||||
/>
|
||||
|
||||
<List path={path} event={event} titlePath="name" />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Hobbies);
|
||||
@@ -1,26 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import Input from '../../../shared/Input';
|
||||
import List from '../../lists/List';
|
||||
|
||||
const Languages = ({ id, event }) => {
|
||||
const path = `${id}.items`;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading id={id} />
|
||||
|
||||
<Input
|
||||
name="heading"
|
||||
label={t('builder.sections.heading')}
|
||||
path={`${id}.heading`}
|
||||
/>
|
||||
|
||||
<List path={path} event={event} titlePath="name" subtitlePath="fluency" />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Languages);
|
||||
@@ -1,28 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import Input from '../../../shared/Input';
|
||||
|
||||
const Objective = ({ id }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading id={id} />
|
||||
|
||||
<Input
|
||||
name="heading"
|
||||
label={t('builder.sections.heading')}
|
||||
path={`${id}.heading`}
|
||||
/>
|
||||
|
||||
<Input
|
||||
type="textarea"
|
||||
label={t('shared.forms.summary')}
|
||||
path="objective.body"
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Objective);
|
||||
@@ -1,95 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import Input from '../../../shared/Input';
|
||||
import PhotoUpload from '../../../shared/PhotoUpload';
|
||||
|
||||
const Profile = ({ id }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading id={id} />
|
||||
|
||||
<Input
|
||||
name="heading"
|
||||
label={t('builder.sections.heading')}
|
||||
path={`${id}.heading`}
|
||||
/>
|
||||
|
||||
<PhotoUpload />
|
||||
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<Input
|
||||
name="firstName"
|
||||
label={t('builder.profile.firstName')}
|
||||
path="profile.firstName"
|
||||
/>
|
||||
<Input
|
||||
name="lastName"
|
||||
label={t('builder.profile.lastName')}
|
||||
path="profile.lastName"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
name="subtitle"
|
||||
label={t('shared.forms.subtitle')}
|
||||
path="profile.subtitle"
|
||||
/>
|
||||
|
||||
<Input
|
||||
type="date"
|
||||
name="birthDate"
|
||||
label={t('builder.profile.birthDate')}
|
||||
path="profile.birthDate"
|
||||
/>
|
||||
|
||||
<hr />
|
||||
|
||||
<Input
|
||||
name="addressLine1"
|
||||
label={t('builder.profile.address.line1')}
|
||||
path="profile.address.line1"
|
||||
/>
|
||||
<Input
|
||||
name="addressLine2"
|
||||
label={t('builder.profile.address.line2')}
|
||||
path="profile.address.line2"
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<Input
|
||||
name="city"
|
||||
label={t('builder.profile.address.city')}
|
||||
path="profile.address.city"
|
||||
/>
|
||||
<Input
|
||||
name="pincode"
|
||||
label={t('builder.profile.address.pincode')}
|
||||
path="profile.address.pincode"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<Input
|
||||
name="phone"
|
||||
label={t('shared.forms.phone')}
|
||||
path="profile.phone"
|
||||
/>
|
||||
<Input
|
||||
name="website"
|
||||
label={t('shared.forms.website')}
|
||||
path="profile.website"
|
||||
/>
|
||||
<Input
|
||||
name="email"
|
||||
label={t('shared.forms.email')}
|
||||
path="profile.email"
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Profile);
|
||||
@@ -1,32 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import Input from '../../../shared/Input';
|
||||
import List from '../../lists/List';
|
||||
|
||||
const Projects = ({ id, event }) => {
|
||||
const path = `${id}.items`;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading id={id} />
|
||||
|
||||
<Input
|
||||
name="heading"
|
||||
label={t('builder.sections.heading')}
|
||||
path={`${id}.heading`}
|
||||
/>
|
||||
|
||||
<List
|
||||
path={path}
|
||||
event={event}
|
||||
titlePath="title"
|
||||
subtitlePath="link"
|
||||
textPath="summary"
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Projects);
|
||||
@@ -1,32 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import Input from '../../../shared/Input';
|
||||
import List from '../../lists/List';
|
||||
|
||||
const References = ({ id, event }) => {
|
||||
const path = `${id}.items`;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading id={id} />
|
||||
|
||||
<Input
|
||||
name="heading"
|
||||
label={t('builder.sections.heading')}
|
||||
path={`${id}.heading`}
|
||||
/>
|
||||
|
||||
<List
|
||||
path={path}
|
||||
event={event}
|
||||
titlePath="name"
|
||||
subtitlePath="position"
|
||||
textPath="summary"
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(References);
|
||||
@@ -1,26 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import Input from '../../../shared/Input';
|
||||
import List from '../../lists/List';
|
||||
|
||||
const Skills = ({ id, event }) => {
|
||||
const path = `${id}.items`;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading id={id} />
|
||||
|
||||
<Input
|
||||
name="heading"
|
||||
label={t('builder.sections.heading')}
|
||||
path={`${id}.heading`}
|
||||
/>
|
||||
|
||||
<List path={path} event={event} titlePath="name" subtitlePath="level" />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Skills);
|
||||
@@ -1,31 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import Input from '../../../shared/Input';
|
||||
import List from '../../lists/List';
|
||||
|
||||
const Social = ({ id, event }) => {
|
||||
const path = `${id}.items`;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading id={id} />
|
||||
|
||||
<Input
|
||||
name="heading"
|
||||
label={t('builder.sections.heading')}
|
||||
path={`${id}.heading`}
|
||||
/>
|
||||
|
||||
<List
|
||||
path={path}
|
||||
event={event}
|
||||
titlePath="network"
|
||||
subtitlePath="username"
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Social);
|
||||
@@ -1,32 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import Input from '../../../shared/Input';
|
||||
import List from '../../lists/List';
|
||||
|
||||
const Work = ({ id, event }) => {
|
||||
const path = `${id}.items`;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading id={id} />
|
||||
|
||||
<Input
|
||||
name="heading"
|
||||
label={t('builder.sections.heading')}
|
||||
path={`${id}.heading`}
|
||||
/>
|
||||
|
||||
<List
|
||||
hasDate
|
||||
path={path}
|
||||
event={event}
|
||||
titlePath="company"
|
||||
textPath="summary"
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Work);
|
||||
@@ -1,12 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo } from 'react';
|
||||
|
||||
const EmptyList = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="py-6 opacity-75 text-center">{t('builder.emptyList')}</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(EmptyList);
|
||||
@@ -1,115 +0,0 @@
|
||||
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
||||
import { MdAdd } from 'react-icons/md';
|
||||
import { get, isEmpty } from 'lodash';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo, useContext } from 'react';
|
||||
import * as styles from './List.module.css';
|
||||
import { formatDateRange, reorder } from '../../../utils';
|
||||
import { useDispatch, useSelector } from '../../../contexts/ResumeContext';
|
||||
import Button from '../../shared/Button';
|
||||
import EmptyList from './EmptyList';
|
||||
import ListItem from './ListItem';
|
||||
import ModalContext from '../../../contexts/ModalContext';
|
||||
|
||||
const List = ({
|
||||
path,
|
||||
title,
|
||||
titlePath,
|
||||
subtitle,
|
||||
subtitlePath,
|
||||
text,
|
||||
textPath,
|
||||
hasDate,
|
||||
event,
|
||||
}) => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const items = useSelector(path, []);
|
||||
const { emitter } = useContext(ModalContext);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleAdd = () => emitter.emit(event);
|
||||
|
||||
const handleEdit = (data) => emitter.emit(event, data);
|
||||
|
||||
const onDragEnd = (result) => {
|
||||
const { source, destination } = result;
|
||||
|
||||
if (!destination) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceDroppableId = source.droppableId;
|
||||
const destinationDroppableId = destination.droppableId;
|
||||
if (sourceDroppableId !== destinationDroppableId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (source.index === destination.index) {
|
||||
return;
|
||||
}
|
||||
|
||||
const itemsReordered = reorder(items, source.index, destination.index);
|
||||
dispatch({
|
||||
type: 'on_input',
|
||||
payload: {
|
||||
path,
|
||||
value: itemsReordered,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className={styles.list}>
|
||||
{isEmpty(items) ? (
|
||||
<EmptyList />
|
||||
) : (
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<Droppable droppableId={path}>
|
||||
{(dropProvided) => (
|
||||
<div
|
||||
ref={dropProvided.innerRef}
|
||||
{...dropProvided.droppableProps}
|
||||
>
|
||||
{items.map((x, i) => (
|
||||
<ListItem
|
||||
key={x.id}
|
||||
data={x}
|
||||
path={path}
|
||||
title={title || get(x, titlePath, '')}
|
||||
subtitle={
|
||||
subtitle ||
|
||||
get(x, subtitlePath, '') ||
|
||||
(hasDate &&
|
||||
formatDateRange(
|
||||
{
|
||||
startDate: x.startDate,
|
||||
endDate: x.endDate,
|
||||
language: i18n.language,
|
||||
},
|
||||
t,
|
||||
))
|
||||
}
|
||||
text={text || get(x, textPath, '')}
|
||||
onEdit={() => handleEdit(x)}
|
||||
isFirst={i === 0}
|
||||
isLast={i === items.length - 1}
|
||||
index={i}
|
||||
/>
|
||||
))}
|
||||
{dropProvided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button outline icon={MdAdd} onClick={handleAdd} className="mt-8 ml-auto">
|
||||
{t('shared.buttons.add')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(List);
|
||||
@@ -1,3 +0,0 @@
|
||||
.list {
|
||||
@apply flex flex-col border border-primary-200 rounded;
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
import { Draggable } from 'react-beautiful-dnd';
|
||||
import { IoIosArrowDown, IoIosArrowUp } from 'react-icons/io';
|
||||
import { MdMoreVert } from 'react-icons/md';
|
||||
import { Menu, MenuItem } from '@material-ui/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo, useState } from 'react';
|
||||
import Switch from '@material-ui/core/Switch';
|
||||
import { useDispatch } from '../../../contexts/ResumeContext';
|
||||
import * as styles from './ListItem.module.css';
|
||||
|
||||
const dataTestIdPrefix = 'list-item-';
|
||||
|
||||
const ListItem = ({
|
||||
title,
|
||||
subtitle,
|
||||
text,
|
||||
path,
|
||||
data,
|
||||
isFirst,
|
||||
isLast,
|
||||
onEdit,
|
||||
index,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const [deleteClickCount, setDeleteClickCount] = useState(0);
|
||||
|
||||
const handleClick = (event) => setAnchorEl(event.currentTarget);
|
||||
const toggleSwitchState = 'isVisible' in data ? data.isVisible : true;
|
||||
|
||||
const handleClose = () => setAnchorEl(null);
|
||||
|
||||
const handleEdit = () => {
|
||||
onEdit();
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const handleMoveUp = () => {
|
||||
dispatch({
|
||||
type: 'on_move_item_up',
|
||||
payload: {
|
||||
path,
|
||||
value: data,
|
||||
},
|
||||
});
|
||||
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const handleMoveDown = () => {
|
||||
dispatch({
|
||||
type: 'on_move_item_down',
|
||||
payload: {
|
||||
path,
|
||||
value: data,
|
||||
},
|
||||
});
|
||||
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
dispatch({
|
||||
type: 'on_delete_item',
|
||||
payload: {
|
||||
path,
|
||||
value: data,
|
||||
},
|
||||
});
|
||||
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const handleToggleUse = () => {
|
||||
dispatch({
|
||||
type: 'on_toggle_use_item',
|
||||
payload: {
|
||||
path,
|
||||
value: data,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const checkConfirmationAndDelte = () => {
|
||||
if (deleteClickCount === 1) {
|
||||
handleDelete();
|
||||
} else if (deleteClickCount < 1) {
|
||||
setDeleteClickCount(deleteClickCount + 1);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Draggable draggableId={data.id} index={index}>
|
||||
{(dragProvided) => (
|
||||
<div
|
||||
ref={dragProvided.innerRef}
|
||||
className={styles.listItem}
|
||||
data-testid={`${dataTestIdPrefix}${path}`}
|
||||
{...dragProvided.draggableProps}
|
||||
{...dragProvided.dragHandleProps}
|
||||
>
|
||||
<div className="grid">
|
||||
<span className="font-medium truncate">{title}</span>
|
||||
|
||||
{subtitle && (
|
||||
<span className="mt-1 text-sm opacity-75 truncate">
|
||||
{subtitle}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{text && (
|
||||
<span className="w-4/5 mt-5 text-sm opacity-75 truncate">
|
||||
{text}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.toggleButton}>
|
||||
<Switch
|
||||
checked={toggleSwitchState}
|
||||
onChange={handleToggleUse}
|
||||
color="primary"
|
||||
name="toggleSwitch"
|
||||
className="toggle-button"
|
||||
inputProps={{ 'aria-label': 'primary checkbox' }}
|
||||
/>
|
||||
|
||||
<div className={styles.menu}>
|
||||
<MdMoreVert
|
||||
size="18px"
|
||||
aria-haspopup="true"
|
||||
onClick={handleClick}
|
||||
className="cursor-context-menu"
|
||||
/>
|
||||
<Menu
|
||||
keepMounted
|
||||
anchorEl={anchorEl}
|
||||
onClose={() => {
|
||||
setDeleteClickCount(0);
|
||||
handleClose();
|
||||
}}
|
||||
open={Boolean(anchorEl)}
|
||||
>
|
||||
<div className="flex items-center space-around">
|
||||
<MenuItem disabled={isFirst} onClick={handleMoveUp}>
|
||||
<IoIosArrowUp size="18px" />
|
||||
</MenuItem>
|
||||
<MenuItem disabled={isLast} onClick={handleMoveDown}>
|
||||
<IoIosArrowDown size="18px" />
|
||||
</MenuItem>
|
||||
</div>
|
||||
<MenuItem onClick={handleEdit}>
|
||||
{t('shared.buttons.edit')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={checkConfirmationAndDelte}>
|
||||
<span className="text-red-600 font-medium">
|
||||
{deleteClickCount
|
||||
? t('shared.buttons.confirmation')
|
||||
: t('shared.buttons.delete')}
|
||||
</span>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ListItem);
|
||||
|
||||
export { dataTestIdPrefix };
|
||||
@@ -1,21 +0,0 @@
|
||||
.list-item {
|
||||
@apply flex items-center justify-between border-t border-primary-200 px-6 py-5;
|
||||
}
|
||||
|
||||
.list-item:first-child {
|
||||
@apply border-t-0;
|
||||
}
|
||||
|
||||
.menu {
|
||||
@apply opacity-0;
|
||||
@apply transition-opacity duration-200 ease-in-out;
|
||||
}
|
||||
|
||||
.list-item:hover .menu {
|
||||
@apply opacity-100;
|
||||
@apply transition-opacity duration-200 ease-in-out;
|
||||
}
|
||||
|
||||
.toggle-button {
|
||||
@apply flex items-center justify-center
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import React, { memo, useContext } from 'react';
|
||||
import { BsBoxArrowRight, BsBoxArrowLeft } from 'react-icons/bs';
|
||||
import * as styles from './RightNavbar.module.css';
|
||||
import SectionIcon from '../../shared/SectionIcon';
|
||||
import SyncIndicator from './SyncIndicator';
|
||||
import sections from '../../../data/rightSections';
|
||||
import SettingsContext from '../../../contexts/SettingsContext';
|
||||
|
||||
const SideBarToggleIcon = ({ className }) => {
|
||||
const { isSideBarOpen, setSideBarToggle } = useContext(SettingsContext);
|
||||
const ToggleIcon = isSideBarOpen ? BsBoxArrowRight : BsBoxArrowLeft;
|
||||
return (
|
||||
<button
|
||||
className={className}
|
||||
onClick={() => setSideBarToggle(!isSideBarOpen)}
|
||||
>
|
||||
<ToggleIcon size="18px" />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
const RightNavbar = () => (
|
||||
<div className={styles.container}>
|
||||
<div className="grid grid-cols-1 gap-4 text-primary-500">
|
||||
{sections.map((x) => (
|
||||
<SectionIcon
|
||||
key={x.id}
|
||||
section={x}
|
||||
containerId="RightSidebar"
|
||||
tooltipPlacement="left"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<SideBarToggleIcon className="mt-auto" />
|
||||
<hr className=" my-6" />
|
||||
<SyncIndicator />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default memo(RightNavbar);
|
||||
@@ -1,7 +0,0 @@
|
||||
.container {
|
||||
width: 75px;
|
||||
z-index: 20;
|
||||
box-shadow: var(--right-shadow);
|
||||
|
||||
@apply px-4 py-6 h-screen flex flex-col items-center;
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import { Element } from 'react-scroll';
|
||||
import React, { Fragment, memo, useContext } from 'react';
|
||||
import * as styles from './RightSidebar.module.css';
|
||||
import About from './sections/About';
|
||||
import Actions from './sections/Actions';
|
||||
import Colors from './sections/Colors';
|
||||
import FontSize from './sections/FontSize';
|
||||
import Fonts from './sections/Fonts';
|
||||
import Layout from './sections/Layout';
|
||||
import RightNavbar from './RightNavbar';
|
||||
import Settings from './sections/Settings';
|
||||
import Templates from './sections/Templates';
|
||||
import sections from '../../../data/rightSections';
|
||||
import SettingsContext from '../../../contexts/SettingsContext';
|
||||
|
||||
const getComponent = (id) => {
|
||||
switch (id) {
|
||||
case 'templates':
|
||||
return Templates;
|
||||
case 'layout':
|
||||
return Layout;
|
||||
case 'colors':
|
||||
return Colors;
|
||||
case 'fonts':
|
||||
return Fonts;
|
||||
case 'actions':
|
||||
return Actions;
|
||||
case 'settings':
|
||||
return Settings;
|
||||
case 'about':
|
||||
return About;
|
||||
case 'font-size':
|
||||
return FontSize;
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
};
|
||||
|
||||
const SidebarSection = ({ id, event }) => {
|
||||
const Component = getComponent(id);
|
||||
return (
|
||||
<Fragment key={id}>
|
||||
<Element name={id}>
|
||||
<Component id={id} event={event} />
|
||||
</Element>
|
||||
<hr />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const RightSidebar = () => {
|
||||
const { isSideBarOpen } = useContext(SettingsContext);
|
||||
return (
|
||||
<div className="flex">
|
||||
{isSideBarOpen && (
|
||||
<div id="RightSidebar" className={styles.container}>
|
||||
{sections.map(SidebarSection)}
|
||||
</div>
|
||||
)}
|
||||
<RightNavbar />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(RightSidebar);
|
||||
@@ -1,6 +0,0 @@
|
||||
.container {
|
||||
z-index: 10;
|
||||
|
||||
@apply w-full h-screen overflow-scroll p-8;
|
||||
@apply grid gap-8;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { MdSync } from 'react-icons/md';
|
||||
import React, { memo, useContext } from 'react';
|
||||
import cx from 'classnames';
|
||||
import DatabaseContext from '../../../contexts/DatabaseContext';
|
||||
|
||||
const SyncIndicator = () => {
|
||||
const { isUpdating } = useContext(DatabaseContext);
|
||||
|
||||
return (
|
||||
<div className="text-4xl">
|
||||
<MdSync className={cx({ spin: isUpdating })} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(SyncIndicator);
|
||||
@@ -1,107 +0,0 @@
|
||||
import { FaBug, FaCoffee, FaExternalLinkAlt } from 'react-icons/fa';
|
||||
import { MdCode } from 'react-icons/md';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { graphql, useStaticQuery } from 'gatsby';
|
||||
import React, { memo } from 'react';
|
||||
import Button from '../../../shared/Button';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import * as styles from './About.module.css';
|
||||
|
||||
const About = ({ id }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { site } = useStaticQuery(graphql`
|
||||
query {
|
||||
site {
|
||||
siteMetadata {
|
||||
version
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading id={id} />
|
||||
|
||||
<div className={styles.container}>
|
||||
<h5>{t('builder.about.donate.heading')}</h5>
|
||||
|
||||
<p className="leading-loose">
|
||||
<Trans t={t} i18nKey="builder.about.donate.text">
|
||||
A<span className="font-bold">B</span>C
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<div className="mt-4 flex">
|
||||
<a
|
||||
href="https://www.buymeacoffee.com/AmruthPillai"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<Button icon={FaCoffee}>{t('builder.about.donate.button')}</Button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.container}>
|
||||
<h5>{t('builder.about.bugFeature.heading')}</h5>
|
||||
|
||||
<p className="leading-loose">{t('builder.about.bugFeature.text')}</p>
|
||||
|
||||
<div className="mt-4 flex">
|
||||
<a
|
||||
href="https://github.com/AmruthPillai/Reactive-Resume/issues/new"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<Button icon={FaBug}>{t('builder.about.bugFeature.button')}</Button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.container}>
|
||||
<h5>{t('builder.about.appreciate.heading')}</h5>
|
||||
|
||||
<p className="leading-loose">{t('builder.about.appreciate.text')}</p>
|
||||
|
||||
<div className="mt-4 flex">
|
||||
<a href="https://amruthpillai.com" rel="noreferrer" target="_blank">
|
||||
<Button icon={FaExternalLinkAlt}>amruthpillai.com</Button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.container}>
|
||||
<h5>{t('builder.about.sourceCode.heading')}</h5>
|
||||
|
||||
<p className="leading-loose">{t('builder.about.sourceCode.text')}</p>
|
||||
|
||||
<div className="mt-4 flex">
|
||||
<a
|
||||
href="https://github.com/AmruthPillai/Reactive-Resume"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<Button icon={MdCode}>
|
||||
{t('builder.about.sourceCode.button')}
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="my-4 text-center opacity-50 text-sm flex flex-col items-center justify-center">
|
||||
<Trans t={t} i18nKey="builder.about.footer">
|
||||
A
|
||||
<a href="https://amruthpillai.com" rel="noreferrer" target="_blank">
|
||||
B
|
||||
</a>
|
||||
</Trans>
|
||||
|
||||
<span className="mt-2 font-medium">v{site.siteMetadata.version}</span>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(About);
|
||||
@@ -1,11 +0,0 @@
|
||||
.container {
|
||||
@apply bg-primary-100 rounded grid gap-5 p-8;
|
||||
}
|
||||
|
||||
.container h5 {
|
||||
@apply text-lg font-semibold;
|
||||
}
|
||||
|
||||
.container p {
|
||||
@apply text-sm font-medium;
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
import { FaFileExport, FaFileImport } from 'react-icons/fa';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo, useContext, useState } from 'react';
|
||||
import * as styles from './Actions.module.css';
|
||||
import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
|
||||
import Button from '../../../shared/Button';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import Input from '../../../shared/Input';
|
||||
import ModalContext from '../../../../contexts/ModalContext';
|
||||
|
||||
const Actions = ({ id }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [loadDemoText, setLoadDemoText] = useState(
|
||||
t('builder.actions.loadDemoData.button'),
|
||||
);
|
||||
const [resetText, setResetText] = useState(
|
||||
t('builder.actions.resetEverything.button'),
|
||||
);
|
||||
|
||||
const state = useSelector();
|
||||
const dispatch = useDispatch();
|
||||
const { emitter, events } = useContext(ModalContext);
|
||||
|
||||
const handleImport = () => emitter.emit(events.IMPORT_MODAL);
|
||||
|
||||
const handleExport = () => emitter.emit(events.EXPORT_MODAL);
|
||||
|
||||
const getSharableUrl = () => {
|
||||
const shareId = state.id;
|
||||
return `https://rxresu.me/r/${shareId}`;
|
||||
};
|
||||
|
||||
const handleOpenLink = () => {
|
||||
if (typeof window !== `undefined`) {
|
||||
window && window.open(getSharableUrl());
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoadDemo = () => {
|
||||
if (loadDemoText === t('builder.actions.loadDemoData.button')) {
|
||||
setLoadDemoText(t('shared.buttons.confirmation'));
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({ type: 'load_demo_data' });
|
||||
setLoadDemoText(t('builder.actions.loadDemoData.button'));
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
if (resetText === t('builder.actions.resetEverything.button')) {
|
||||
setResetText(t('shared.buttons.confirmation'));
|
||||
return;
|
||||
}
|
||||
|
||||
setResetText(t('builder.actions.resetEverything.button'));
|
||||
dispatch({ type: 'reset_data' });
|
||||
};
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading id={id} />
|
||||
|
||||
<div className={styles.container}>
|
||||
<h5>{t('builder.actions.import.heading')}</h5>
|
||||
|
||||
<p className="leading-loose">{t('builder.actions.import.text')}</p>
|
||||
|
||||
<div className="mt-4 flex">
|
||||
<Button icon={FaFileImport} onClick={handleImport}>
|
||||
{t('builder.actions.import.button')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.container}>
|
||||
<h5>{t('builder.actions.export.heading')}</h5>
|
||||
|
||||
<p className="leading-loose">{t('builder.actions.export.text')}</p>
|
||||
|
||||
<div className="mt-4 flex">
|
||||
<Button icon={FaFileExport} onClick={handleExport}>
|
||||
{t('builder.actions.export.button')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.container}>
|
||||
<h5>{t('builder.actions.share.heading')}</h5>
|
||||
|
||||
<p className="leading-loose">{t('builder.actions.share.text')}</p>
|
||||
|
||||
<div>
|
||||
<Input
|
||||
type="action"
|
||||
value={getSharableUrl()}
|
||||
onClick={handleOpenLink}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.container}>
|
||||
<h5>{t('builder.actions.loadDemoData.button')}</h5>
|
||||
|
||||
<p className="leading-loose">
|
||||
{t('builder.actions.loadDemoData.text')}
|
||||
</p>
|
||||
|
||||
<div className="mt-4 flex">
|
||||
<Button onClick={handleLoadDemo}>{loadDemoText}</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.container}>
|
||||
<h5>{t('builder.actions.resetEverything.button')}</h5>
|
||||
|
||||
<p className="leading-loose">
|
||||
{t('builder.actions.resetEverything.text')}
|
||||
</p>
|
||||
|
||||
<div className="mt-4 flex">
|
||||
<Button onClick={handleReset}>{resetText}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Actions);
|
||||
@@ -1,11 +0,0 @@
|
||||
.container {
|
||||
@apply bg-primary-100 rounded grid gap-5 p-8;
|
||||
}
|
||||
|
||||
.container h5 {
|
||||
@apply text-lg font-semibold;
|
||||
}
|
||||
|
||||
.container p {
|
||||
@apply text-sm font-medium;
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/* eslint-disable jsx-a11y/control-has-associated-label */
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo } from 'react';
|
||||
import * as styles from './Colors.module.css';
|
||||
import { handleKeyUp } from '../../../../utils';
|
||||
import { useDispatch } from '../../../../contexts/ResumeContext';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import Input from '../../../shared/Input';
|
||||
import colorOptions from '../../../../data/colorOptions';
|
||||
|
||||
const Colors = ({ id }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleClick = (value) => {
|
||||
dispatch({
|
||||
type: 'on_input',
|
||||
payload: {
|
||||
path: 'metadata.colors.primary',
|
||||
value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading id={id} />
|
||||
|
||||
<div className="mb-6 grid grid-cols-8 gap-x-2 gap-y-6">
|
||||
{colorOptions.map((color) => (
|
||||
<div
|
||||
key={color}
|
||||
tabIndex="0"
|
||||
role="button"
|
||||
className={styles.circle}
|
||||
style={{ backgroundColor: color }}
|
||||
onKeyUp={(e) => handleKeyUp(e, () => handleClick(color))}
|
||||
onClick={() => handleClick(color)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Input
|
||||
type="color"
|
||||
name="primary"
|
||||
label={t('builder.colors.primary')}
|
||||
placeholder="#FF4081"
|
||||
path="metadata.colors.primary"
|
||||
/>
|
||||
|
||||
<Input
|
||||
type="color"
|
||||
name="text"
|
||||
label={t('builder.colors.text')}
|
||||
placeholder="#444444"
|
||||
path="metadata.colors.text"
|
||||
/>
|
||||
|
||||
<Input
|
||||
type="color"
|
||||
name="background"
|
||||
label={t('builder.colors.background')}
|
||||
placeholder="#FFFFFF"
|
||||
path="metadata.colors.background"
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Colors);
|
||||
@@ -1,11 +0,0 @@
|
||||
.circle {
|
||||
@apply cursor-pointer rounded-full h-6 w-6;
|
||||
}
|
||||
|
||||
.circle:focus {
|
||||
@apply outline-none;
|
||||
}
|
||||
|
||||
.circle:hover {
|
||||
@apply opacity-75;
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/* eslint-disable jsx-a11y/control-has-associated-label */
|
||||
import React, { memo, useEffect, useState } from 'react';
|
||||
import { scaler } from '../../../../utils';
|
||||
import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import fontSizeOptions from '../../../../data/fontSizeOptions';
|
||||
|
||||
const FontSizes = ({ id }) => {
|
||||
const steps = 20;
|
||||
const min = 0;
|
||||
const max = min + steps - 1;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const fontSize = useSelector('metadata.fontSize');
|
||||
const [scale, setScale] = useState(fontSize || 7);
|
||||
|
||||
useEffect(() => {
|
||||
for (const [key, sizeDefault] of Object.entries(fontSizeOptions)) {
|
||||
document.documentElement.style.setProperty(
|
||||
key,
|
||||
`${scaler(scale) * sizeDefault}rem`,
|
||||
);
|
||||
}
|
||||
}, [scale]);
|
||||
|
||||
const onChange = (event) => {
|
||||
const { value } = event.target;
|
||||
|
||||
setScale(value);
|
||||
|
||||
dispatch({
|
||||
type: 'on_input',
|
||||
payload: {
|
||||
path: 'metadata.fontSize',
|
||||
value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading id={id} />
|
||||
|
||||
<input
|
||||
step={1}
|
||||
min={min}
|
||||
max={max}
|
||||
type="range"
|
||||
onChange={onChange}
|
||||
defaultValue={scale}
|
||||
className="rounded-lg overflow-hidden appearance-none bg-gray-400 h-4 w-full"
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(FontSizes);
|
||||
@@ -1,46 +0,0 @@
|
||||
import React, { memo } from 'react';
|
||||
import cx from 'classnames';
|
||||
import * as styles from './Fonts.module.css';
|
||||
import { handleKeyUp } from '../../../../utils';
|
||||
import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import fontOptions from '../../../../data/fontOptions';
|
||||
|
||||
const Fonts = ({ id }) => {
|
||||
const dispatch = useDispatch();
|
||||
const font = useSelector('metadata.font');
|
||||
|
||||
const handleClick = (value) => {
|
||||
dispatch({
|
||||
type: 'on_input',
|
||||
payload: {
|
||||
path: 'metadata.font',
|
||||
value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading id={id} />
|
||||
|
||||
<div className="grid grid-cols-2 gap-8">
|
||||
{fontOptions.map((x) => (
|
||||
<div
|
||||
key={x}
|
||||
tabIndex="0"
|
||||
role="button"
|
||||
style={{ fontFamily: x }}
|
||||
className={cx(styles.font, { [styles.selected]: font === x })}
|
||||
onKeyUp={(e) => handleKeyUp(e, () => handleClick(x))}
|
||||
onClick={() => handleClick(x)}
|
||||
>
|
||||
{x}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Fonts);
|
||||
@@ -1,15 +0,0 @@
|
||||
.font {
|
||||
@apply px-6 py-6 text-xl font-medium truncate text-center border border-transparent bg-primary-100 rounded;
|
||||
}
|
||||
|
||||
.font:focus {
|
||||
@apply outline-none border border-primary-400;
|
||||
}
|
||||
|
||||
.font:hover {
|
||||
@apply border border-primary-400;
|
||||
}
|
||||
|
||||
.font.selected {
|
||||
@apply outline-none border border-primary-600;
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo, useState } from 'react';
|
||||
import * as styles from './Layout.module.css';
|
||||
import { move, reorder } from '../../../../utils';
|
||||
import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
|
||||
import Button from '../../../shared/Button';
|
||||
import Heading from '../../../shared/Heading';
|
||||
|
||||
const Layout = ({ id }) => {
|
||||
const { t } = useTranslation();
|
||||
const [resetLayoutText, setResetLayoutText] = useState(
|
||||
t('builder.layout.reset'),
|
||||
);
|
||||
|
||||
const template = useSelector('metadata.template');
|
||||
const blocks = useSelector(`metadata.layout.${template}`, [[]]);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onDragEnd = (result) => {
|
||||
const { source, destination } = result;
|
||||
|
||||
if (!destination) {
|
||||
return;
|
||||
}
|
||||
const sInd = +source.droppableId;
|
||||
const dInd = +destination.droppableId;
|
||||
|
||||
if (sInd === dInd) {
|
||||
const items = reorder(blocks[sInd], source.index, destination.index);
|
||||
const newState = [...blocks];
|
||||
newState[sInd] = items;
|
||||
dispatch({
|
||||
type: 'on_input',
|
||||
payload: {
|
||||
path: `metadata.layout.${template}`,
|
||||
value: newState,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
const newResult = move(blocks[sInd], blocks[dInd], source, destination);
|
||||
const newState = [...blocks];
|
||||
newState[sInd] = newResult[sInd];
|
||||
newState[dInd] = newResult[dInd];
|
||||
dispatch({
|
||||
type: 'on_input',
|
||||
payload: {
|
||||
path: `metadata.layout.${template}`,
|
||||
value: newState,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetLayout = () => {
|
||||
if (resetLayoutText === t('builder.layout.reset')) {
|
||||
setResetLayoutText(t('shared.buttons.confirmation'));
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({ type: 'reset_layout' });
|
||||
setResetLayoutText(t('builder.layout.reset'));
|
||||
};
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading id={id} />
|
||||
|
||||
<p className="leading-loose">
|
||||
{t('builder.layout.text', { count: blocks.length })}
|
||||
</p>
|
||||
|
||||
<div className={`grid gap-8 grid-cols-${blocks.length}`}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
{blocks.map((el, ind) => (
|
||||
<Droppable key={ind} droppableId={`${ind}`}>
|
||||
{(dropProvided) => (
|
||||
<div
|
||||
ref={dropProvided.innerRef}
|
||||
className={styles.droppable}
|
||||
{...dropProvided.droppableProps}
|
||||
>
|
||||
<div className="grid gap-3">
|
||||
<span className="uppercase font-semibold text-xs">
|
||||
{t('builder.layout.block')} {ind + 1}
|
||||
</span>
|
||||
{el.map((item, index) => (
|
||||
<Draggable key={item} index={index} draggableId={item}>
|
||||
{(dragProvided) => (
|
||||
<div
|
||||
ref={dragProvided.innerRef}
|
||||
className={styles.draggable}
|
||||
{...dragProvided.draggableProps}
|
||||
{...dragProvided.dragHandleProps}
|
||||
>
|
||||
{t(`builder.sections.${item}`)}
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
</div>
|
||||
{dropProvided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
))}
|
||||
</DragDropContext>
|
||||
</div>
|
||||
|
||||
<div className="flex">
|
||||
<Button onClick={handleResetLayout}>{resetLayoutText}</Button>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Layout);
|
||||
@@ -1,7 +0,0 @@
|
||||
.droppable {
|
||||
@apply p-6 bg-primary-100 col-span-1 rounded;
|
||||
}
|
||||
|
||||
.draggable {
|
||||
@apply px-4 py-2 font-medium rounded bg-primary-200;
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
import { FaAngleDown } from 'react-icons/fa';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { toast } from 'react-toastify';
|
||||
import React, { memo, useContext, useState } from 'react';
|
||||
import * as styles from './Settings.module.css';
|
||||
import { languages } from '../../../../i18n';
|
||||
import { useDispatch } from '../../../../contexts/ResumeContext';
|
||||
import Button from '../../../shared/Button';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import Input from '../../../shared/Input';
|
||||
import SettingsContext from '../../../../contexts/SettingsContext';
|
||||
import UserContext from '../../../../contexts/UserContext';
|
||||
import themeConfig from '../../../../data/themeConfig';
|
||||
|
||||
const Settings = ({ id }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [deleteText, setDeleteText] = useState(
|
||||
t('builder.settings.dangerZone.button'),
|
||||
);
|
||||
const [isDeleteAccountInProgress, setDeleteAccountInProgress] =
|
||||
useState(false);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { deleteAccount } = useContext(UserContext);
|
||||
const { theme, setTheme, language, setLanguage } =
|
||||
useContext(SettingsContext);
|
||||
|
||||
const handleChangeTheme = (e) => {
|
||||
setTheme(e.target.value);
|
||||
};
|
||||
|
||||
const handleChangeLanguage = (e) => {
|
||||
const lang = e.target.value;
|
||||
setLanguage(lang);
|
||||
dispatch({ type: 'change_language', payload: lang });
|
||||
};
|
||||
|
||||
const handleDeleteAccount = async () => {
|
||||
if (deleteText === t('builder.settings.dangerZone.button')) {
|
||||
setDeleteText(t('shared.buttons.confirmation'));
|
||||
return;
|
||||
}
|
||||
|
||||
setDeleteAccountInProgress(true);
|
||||
|
||||
try {
|
||||
await deleteAccount();
|
||||
} catch (error) {
|
||||
toast.error('An error occurred deleting your account.');
|
||||
setDeleteAccountInProgress(false);
|
||||
setDeleteText(t('builder.settings.dangerZone.button'));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading id={id} />
|
||||
|
||||
<Input
|
||||
label={t('builder.settings.theme')}
|
||||
type="dropdown"
|
||||
options={Object.keys(themeConfig)}
|
||||
value={theme}
|
||||
onChange={handleChangeTheme}
|
||||
/>
|
||||
|
||||
<label>
|
||||
<span>{t('builder.settings.language')}</span>
|
||||
<div className="relative grid items-center">
|
||||
<select onChange={handleChangeLanguage} value={language}>
|
||||
{languages.map((x) => (
|
||||
<option key={x.code} value={x.code}>
|
||||
{x.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<FaAngleDown
|
||||
size="16px"
|
||||
className="absolute right-0 opacity-50 hover:opacity-75 mx-4"
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<p className="text-sm leading-loose">
|
||||
<Trans t={t} i18nKey="builder.settings.translate">
|
||||
A
|
||||
<a
|
||||
href="https://github.com/AmruthPillai/Reactive-Resume#translation"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
B
|
||||
</a>
|
||||
C
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<div className={styles.container}>
|
||||
<h5>{t('builder.settings.dangerZone.heading')}</h5>
|
||||
|
||||
<p className="leading-loose">{t('builder.settings.dangerZone.text')}</p>
|
||||
|
||||
<div className="mt-4 flex">
|
||||
<Button
|
||||
isDelete
|
||||
onClick={handleDeleteAccount}
|
||||
isLoading={isDeleteAccountInProgress}
|
||||
>
|
||||
{deleteText}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Settings);
|
||||
@@ -1,11 +0,0 @@
|
||||
.container {
|
||||
@apply bg-primary-100 rounded grid gap-5 p-8;
|
||||
}
|
||||
|
||||
.container h5 {
|
||||
@apply text-lg font-semibold;
|
||||
}
|
||||
|
||||
.container p {
|
||||
@apply text-sm font-medium;
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
import { graphql, useStaticQuery } from 'gatsby';
|
||||
import { GatsbyImage } from 'gatsby-plugin-image';
|
||||
import React, { memo } from 'react';
|
||||
import cx from 'classnames';
|
||||
import * as styles from './Templates.module.css';
|
||||
import { handleKeyUp } from '../../../../utils';
|
||||
import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import templateOptions from '../../../../data/templateOptions';
|
||||
|
||||
const Templates = ({ id }) => {
|
||||
const dispatch = useDispatch();
|
||||
const template = useSelector('metadata.template');
|
||||
|
||||
const previews = useStaticQuery(graphql`
|
||||
query {
|
||||
onyx: file(relativePath: { eq: "templates/onyx.png" }) {
|
||||
childImageSharp {
|
||||
gatsbyImageData(layout: FIXED, height: 240)
|
||||
}
|
||||
}
|
||||
pikachu: file(relativePath: { eq: "templates/pikachu.png" }) {
|
||||
childImageSharp {
|
||||
gatsbyImageData(layout: FIXED, height: 240)
|
||||
}
|
||||
}
|
||||
gengar: file(relativePath: { eq: "templates/gengar.png" }) {
|
||||
childImageSharp {
|
||||
gatsbyImageData(layout: FIXED, height: 240)
|
||||
}
|
||||
}
|
||||
castform: file(relativePath: { eq: "templates/castform.png" }) {
|
||||
childImageSharp {
|
||||
gatsbyImageData(layout: FIXED, height: 240)
|
||||
}
|
||||
}
|
||||
glalie: file(relativePath: { eq: "templates/glalie.png" }) {
|
||||
childImageSharp {
|
||||
gatsbyImageData(layout: FIXED, height: 240)
|
||||
}
|
||||
}
|
||||
celebi: file(relativePath: { eq: "templates/celebi.png" }) {
|
||||
childImageSharp {
|
||||
gatsbyImageData(layout: FIXED, height: 240)
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const handleClick = (value) => {
|
||||
dispatch({
|
||||
type: 'on_input',
|
||||
payload: {
|
||||
path: 'metadata.template',
|
||||
value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading id={id} />
|
||||
|
||||
<div className="grid grid-cols-2 gap-8">
|
||||
{templateOptions.map((x) => (
|
||||
<div
|
||||
key={x.id}
|
||||
tabIndex="0"
|
||||
role="button"
|
||||
onKeyUp={(e) => handleKeyUp(e, () => handleClick(x.id))}
|
||||
onClick={() => handleClick(x.id)}
|
||||
className={cx(styles.template, {
|
||||
[styles.selected]: template === x.id,
|
||||
})}
|
||||
>
|
||||
<GatsbyImage
|
||||
alt={x.name}
|
||||
image={previews[x.id].childImageSharp.gatsbyImageData}
|
||||
/>
|
||||
<span>{x.name}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Templates);
|
||||
@@ -1,27 +0,0 @@
|
||||
.template {
|
||||
@apply w-full rounded flex flex-col items-center;
|
||||
}
|
||||
|
||||
.template:focus {
|
||||
@apply outline-none;
|
||||
}
|
||||
|
||||
.template img {
|
||||
height: 240px;
|
||||
|
||||
@apply w-full object-cover border border-transparent rounded;
|
||||
@apply transition-opacity duration-200 ease-in-out;
|
||||
}
|
||||
|
||||
.template.selected img {
|
||||
@apply border-primary-600;
|
||||
}
|
||||
|
||||
.template:hover img {
|
||||
@apply cursor-pointer opacity-75;
|
||||
@apply transition-opacity duration-200 ease-in-out;
|
||||
}
|
||||
|
||||
.template span {
|
||||
@apply mt-1 text-center text-sm font-semibold;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import { MdAdd } from 'react-icons/md';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo, useContext } from 'react';
|
||||
import * as styles from './CreateResume.module.css';
|
||||
import { handleKeyUp } from '../../utils';
|
||||
import ModalContext from '../../contexts/ModalContext';
|
||||
|
||||
const createResumeButtonDataTestId = 'create-resume-button';
|
||||
|
||||
const CreateResume = () => {
|
||||
const { t } = useTranslation();
|
||||
const { emitter, events } = useContext(ModalContext);
|
||||
|
||||
const handleClick = () => emitter.emit(events.CREATE_RESUME_MODAL);
|
||||
|
||||
return (
|
||||
<div className={styles.resume}>
|
||||
<div className={styles.backdrop}>
|
||||
<MdAdd size="48" />
|
||||
</div>
|
||||
<div
|
||||
data-testid={createResumeButtonDataTestId}
|
||||
tabIndex="0"
|
||||
role="button"
|
||||
className={styles.page}
|
||||
onClick={handleClick}
|
||||
onKeyUp={(e) => handleKeyUp(e, handleClick)}
|
||||
>
|
||||
<MdAdd size="48" />
|
||||
</div>
|
||||
<div className={styles.meta}>
|
||||
<p>{t('dashboard.createResume')}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(CreateResume);
|
||||
|
||||
export { createResumeButtonDataTestId };
|
||||
@@ -1,34 +0,0 @@
|
||||
.resume {
|
||||
@apply relative flex flex-col items-center;
|
||||
}
|
||||
|
||||
.resume > .backdrop {
|
||||
max-width: 184px;
|
||||
height: 260px;
|
||||
|
||||
@apply rounded absolute w-full bg-black text-white shadow;
|
||||
@apply absolute flex justify-center items-center;
|
||||
}
|
||||
|
||||
.resume > .page {
|
||||
max-width: 184px;
|
||||
height: 260px;
|
||||
|
||||
@apply rounded absolute w-full bg-primary-100 text-primary-400;
|
||||
@apply transition-opacity duration-200 ease-in-out;
|
||||
@apply cursor-pointer absolute flex justify-center items-center;
|
||||
}
|
||||
|
||||
.resume > .page:hover {
|
||||
@apply transition-opacity duration-200 ease-in-out opacity-25;
|
||||
}
|
||||
|
||||
.resume > .meta {
|
||||
margin-top: 260px;
|
||||
|
||||
@apply text-center;
|
||||
}
|
||||
|
||||
.resume > .meta p {
|
||||
@apply mt-3 font-medium leading-normal;
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
import { MdMoreHoriz, MdOpenInNew } from 'react-icons/md';
|
||||
import { Menu, MenuItem } from '@material-ui/core';
|
||||
import { navigate } from 'gatsby';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import dayjs from 'dayjs';
|
||||
import DatabaseContext from '../../contexts/DatabaseContext';
|
||||
import ModalContext from '../../contexts/ModalContext';
|
||||
import * as styles from './ResumePreview.module.css';
|
||||
|
||||
const menuToggleDataTestIdPrefix = 'resume-preview-menu-toggle-';
|
||||
|
||||
const ResumePreview = ({ resume }) => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const { emitter, events } = useContext(ModalContext);
|
||||
const { duplicateResume, deleteResume } = useContext(DatabaseContext);
|
||||
|
||||
const handleOpen = () => navigate(`/app/builder/${resume.id}`);
|
||||
|
||||
const handleMenuClick = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleDuplicate = () => {
|
||||
duplicateResume(resume);
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleRename = () => {
|
||||
emitter.emit(events.CREATE_RESUME_MODAL, resume);
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteResume(resume.id);
|
||||
toast(t('dashboard.toasts.deleted', { name: resume.name }));
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleMenuClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.resume}>
|
||||
<div className={styles.backdrop}>
|
||||
<img src={resume.preview} alt={resume.name} />
|
||||
</div>
|
||||
<div className={styles.page}>
|
||||
<MdOpenInNew
|
||||
color="#fff"
|
||||
size="48"
|
||||
className="cursor-pointer"
|
||||
onClick={handleOpen}
|
||||
/>
|
||||
<MdMoreHoriz
|
||||
data-testid={`${menuToggleDataTestIdPrefix}${resume.id}`}
|
||||
color="#fff"
|
||||
size="48"
|
||||
className="cursor-pointer"
|
||||
aria-haspopup="true"
|
||||
onClick={handleMenuClick}
|
||||
/>
|
||||
<Menu
|
||||
keepMounted
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={handleMenuClose}
|
||||
>
|
||||
<MenuItem onClick={handleDuplicate}>
|
||||
{t('dashboard.buttons.duplicate')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleRename}>
|
||||
{t('dashboard.buttons.rename')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleDelete}>
|
||||
<span className="text-red-600 font-medium">
|
||||
{t('shared.buttons.delete')}
|
||||
</span>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
<div className={styles.meta}>
|
||||
<span>{resume.name}</span>
|
||||
{resume.updatedAt && (
|
||||
<span>
|
||||
{t('dashboard.lastUpdated', {
|
||||
timestamp: dayjs(resume.updatedAt)
|
||||
.locale(i18n.language.substr(0, 2))
|
||||
.fromNow(),
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResumePreview;
|
||||
|
||||
export { menuToggleDataTestIdPrefix };
|
||||
@@ -1,44 +0,0 @@
|
||||
.resume {
|
||||
@apply relative flex flex-col items-center;
|
||||
}
|
||||
|
||||
.resume > .backdrop {
|
||||
max-width: 184px;
|
||||
height: 260px;
|
||||
|
||||
@apply rounded absolute w-full bg-black shadow;
|
||||
}
|
||||
|
||||
.resume > .backdrop img {
|
||||
max-width: 184px;
|
||||
height: 260px;
|
||||
|
||||
@apply w-full object-cover rounded;
|
||||
}
|
||||
|
||||
.resume > .page {
|
||||
max-width: 184px;
|
||||
height: 260px;
|
||||
|
||||
@apply rounded absolute w-full bg-black;
|
||||
@apply opacity-0 transition-opacity duration-200 ease-in-out;
|
||||
@apply absolute text-primary-500 flex flex-col justify-evenly items-center;
|
||||
}
|
||||
|
||||
.resume > .page:hover {
|
||||
@apply opacity-75 transition-opacity duration-200 ease-in-out;
|
||||
}
|
||||
|
||||
.resume > .meta {
|
||||
margin-top: 260px;
|
||||
|
||||
@apply flex flex-col text-center items-center;
|
||||
}
|
||||
|
||||
.resume > .meta span:first-child {
|
||||
@apply mt-3 font-medium leading-normal;
|
||||
}
|
||||
|
||||
.resume > .meta span:last-child {
|
||||
font-size: 10px;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Link } from 'gatsby';
|
||||
import React, { memo } from 'react';
|
||||
import * as styles from './TopNavbar.module.css';
|
||||
import Avatar from '../shared/Avatar';
|
||||
import Logo from '../shared/Logo';
|
||||
|
||||
const TopNavbar = () => (
|
||||
<div className={styles.navbar}>
|
||||
<div className="container">
|
||||
<Link to="/">
|
||||
<Logo size="40px" />
|
||||
</Link>
|
||||
|
||||
<Avatar className="ml-8" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default memo(TopNavbar);
|
||||
@@ -1,10 +0,0 @@
|
||||
.navbar {
|
||||
height: 65px;
|
||||
box-shadow: var(--bottom-shadow);
|
||||
|
||||
@apply w-full;
|
||||
}
|
||||
|
||||
.navbar > div {
|
||||
@apply h-full flex items-center justify-between;
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import { Link } from '@reach/router';
|
||||
import { navigate } from 'gatsby';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo, useContext } from 'react';
|
||||
import Button from '../shared/Button';
|
||||
import Logo from '../shared/Logo';
|
||||
import ModalContext from '../../contexts/ModalContext';
|
||||
import UserContext from '../../contexts/UserContext';
|
||||
|
||||
const Hero = () => {
|
||||
const { t } = useTranslation();
|
||||
const { emitter, events } = useContext(ModalContext);
|
||||
const { user, loading } = useContext(UserContext);
|
||||
|
||||
const handleLogin = () => emitter.emit(events.AUTH_MODAL);
|
||||
|
||||
const handleGotoApp = () => navigate('/app/dashboard');
|
||||
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row items-center">
|
||||
<Link to="/">
|
||||
<span className="sr-only">Reactive Resume</span>
|
||||
<Logo className="shadow-lg" size="256px" />
|
||||
</Link>
|
||||
|
||||
<div className="mt-12 md:mt-0 md:ml-12">
|
||||
<h1 className="text-5xl font-bold">{t('shared.appName')}</h1>
|
||||
<h2 className="mt-1 text-3xl text-primary-500">
|
||||
{t('shared.shortDescription')}
|
||||
</h2>
|
||||
|
||||
<div className="mt-12">
|
||||
{user ? (
|
||||
<Button
|
||||
onClick={handleGotoApp}
|
||||
isLoading={loading}
|
||||
className="mx-auto md:mx-0"
|
||||
>
|
||||
{t('landing.hero.goToApp')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={handleLogin}
|
||||
isLoading={loading}
|
||||
className="mx-auto md:mx-0"
|
||||
>
|
||||
{t('shared.buttons.login')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Hero);
|
||||
@@ -1,77 +0,0 @@
|
||||
import { graphql, useStaticQuery } from 'gatsby';
|
||||
import { GatsbyImage } from 'gatsby-plugin-image';
|
||||
import React from 'react';
|
||||
import * as styles from './Screenshots.module.css';
|
||||
|
||||
const Screenshots = () => {
|
||||
const screenshots = useStaticQuery(graphql`
|
||||
query {
|
||||
screen1: file(relativePath: { eq: "screenshots/screen-1.png" }) {
|
||||
childImageSharp {
|
||||
original {
|
||||
src
|
||||
}
|
||||
gatsbyImageData(layout: FIXED, width: 400)
|
||||
}
|
||||
}
|
||||
screen2: file(relativePath: { eq: "screenshots/screen-2.png" }) {
|
||||
childImageSharp {
|
||||
original {
|
||||
src
|
||||
}
|
||||
gatsbyImageData(layout: FIXED, width: 400)
|
||||
}
|
||||
}
|
||||
screen3: file(relativePath: { eq: "screenshots/screen-3.png" }) {
|
||||
childImageSharp {
|
||||
original {
|
||||
src
|
||||
}
|
||||
gatsbyImageData(layout: FIXED, width: 400)
|
||||
}
|
||||
}
|
||||
screen4: file(relativePath: { eq: "screenshots/screen-4.png" }) {
|
||||
childImageSharp {
|
||||
original {
|
||||
src
|
||||
}
|
||||
gatsbyImageData(layout: FIXED, width: 400)
|
||||
}
|
||||
}
|
||||
screen5: file(relativePath: { eq: "screenshots/screen-5.png" }) {
|
||||
childImageSharp {
|
||||
original {
|
||||
src
|
||||
}
|
||||
gatsbyImageData(layout: FIXED, width: 400)
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
return (
|
||||
<div className="mt-24 mb-4">
|
||||
<div className="text-xl uppercase font-bold mb-8">Screenshots</div>
|
||||
|
||||
<div className="flex w-full overflow-x-scroll">
|
||||
{Object.keys(screenshots).map((x) => (
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styles.screenshot}
|
||||
key={screenshots[x].childImageSharp.original.src}
|
||||
href={screenshots[x].childImageSharp.original.src}
|
||||
>
|
||||
<span className="sr-only">Reactive Resume Screenshot</span>
|
||||
<GatsbyImage
|
||||
image={screenshots[x].childImageSharp.gatsbyImageData}
|
||||
alt="Reactive Resume Screenshot"
|
||||
/>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Screenshots;
|
||||
@@ -1,16 +0,0 @@
|
||||
.screenshot {
|
||||
filter: grayscale(100%);
|
||||
|
||||
@apply shadow-xl rounded border-2 border-primary-100 ml-10;
|
||||
@apply transition-all duration-200 ease-in-out;
|
||||
}
|
||||
|
||||
.screenshot:hover {
|
||||
filter: grayscale(0%);
|
||||
|
||||
@apply transition-all duration-200 ease-in-out;
|
||||
}
|
||||
|
||||
.screenshot:first-child {
|
||||
@apply ml-0;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Fade, Modal } from '@material-ui/core';
|
||||
import React, { memo } from 'react';
|
||||
import { getRandomTip } from '../../data/tips';
|
||||
import Logo from '../shared/Logo';
|
||||
|
||||
const dataTestId = 'loading-screen';
|
||||
|
||||
const LoadingScreen = () => (
|
||||
<Modal data-testid={dataTestId} open hideBackdrop>
|
||||
<Fade in>
|
||||
<div className="w-screen h-screen flex justify-center items-center outline-none">
|
||||
<div className="flex flex-col items-center">
|
||||
<Logo size="48px" className="mb-4" />
|
||||
<span className="font-medium opacity-75">{getRandomTip()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Fade>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
export default memo(LoadingScreen);
|
||||
export { dataTestId };
|
||||
@@ -1,21 +0,0 @@
|
||||
import { navigate } from 'gatsby';
|
||||
import React, { memo, useContext } from 'react';
|
||||
import LoadingScreen from './LoadingScreen';
|
||||
import UserContext from '../../contexts/UserContext';
|
||||
|
||||
const PrivateRoute = ({ component: Component, ...props }) => {
|
||||
const { user, loading } = useContext(UserContext);
|
||||
|
||||
if (loading) {
|
||||
return <LoadingScreen />;
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
navigate('/');
|
||||
return null;
|
||||
}
|
||||
|
||||
return <Component user={user} {...props} />;
|
||||
};
|
||||
|
||||
export default memo(PrivateRoute);
|
||||
@@ -1,55 +0,0 @@
|
||||
import { Menu, MenuItem } from '@material-ui/core';
|
||||
import { toUrl } from 'gatsby-source-gravatar';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo, useContext, useMemo, useState } from 'react';
|
||||
import cx from 'classnames';
|
||||
import UserContext from '../../contexts/UserContext';
|
||||
import { handleKeyUp } from '../../utils';
|
||||
import * as styles from './Avatar.module.css';
|
||||
|
||||
const Avatar = ({ className }) => {
|
||||
const { t } = useTranslation();
|
||||
const { user, logout } = useContext(UserContext);
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
|
||||
const handleClick = (event) => setAnchorEl(event.currentTarget);
|
||||
const handleClose = () => setAnchorEl(null);
|
||||
|
||||
const handleLogout = () => {
|
||||
logout();
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const photoURL = useMemo(
|
||||
() => toUrl(user.email || '', 'size=128&d=retro'),
|
||||
[user.email],
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
tabIndex="0"
|
||||
role="button"
|
||||
className="flex focus:outline-none"
|
||||
onClick={handleClick}
|
||||
onKeyUp={(e) => handleKeyUp(e, handleClick)}
|
||||
>
|
||||
<img
|
||||
src={photoURL}
|
||||
alt={user.displayName || 'Anonymous User'}
|
||||
className={cx(styles.container, className)}
|
||||
/>
|
||||
</div>
|
||||
<Menu
|
||||
keepMounted
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleClose}
|
||||
open={Boolean(anchorEl)}
|
||||
>
|
||||
<MenuItem onClick={handleLogout}>{t('shared.buttons.logout')}</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Avatar);
|
||||
@@ -1,3 +0,0 @@
|
||||
.container {
|
||||
@apply w-12 h-12 rounded-full;
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo } from 'react';
|
||||
import cx from 'classnames';
|
||||
import { handleKeyUp } from '../../utils';
|
||||
import * as styles from './Button.module.css';
|
||||
|
||||
const Button = ({
|
||||
icon,
|
||||
onClick,
|
||||
outline,
|
||||
children,
|
||||
className,
|
||||
isLoading,
|
||||
isDelete,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const Icon = icon;
|
||||
|
||||
return (
|
||||
<button
|
||||
onKeyUp={(e) => handleKeyUp(e, onClick)}
|
||||
onClick={isLoading ? undefined : onClick}
|
||||
className={cx(styles.container, className, {
|
||||
[styles.outline]: outline,
|
||||
[styles.remove]: isDelete,
|
||||
})}
|
||||
>
|
||||
{icon && <Icon size="14" className="mr-3" />}
|
||||
{isLoading ? t('shared.buttons.loading') : children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Button);
|
||||
@@ -1,44 +0,0 @@
|
||||
.container {
|
||||
font-size: 11px;
|
||||
padding: 6px 18px;
|
||||
|
||||
@apply relative flex items-center cursor-pointer rounded font-semibold bg-primary-900 border border-primary-900 text-primary-50;
|
||||
@apply transition-colors duration-200 ease-in-out;
|
||||
}
|
||||
|
||||
.container:hover {
|
||||
@apply bg-primary-700 border-primary-700;
|
||||
@apply transition-colors duration-200 ease-in-out;
|
||||
}
|
||||
|
||||
.container:active {
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.container:focus {
|
||||
@apply outline-none;
|
||||
}
|
||||
|
||||
.container.outline {
|
||||
@apply border border-primary-900 bg-transparent text-primary-900;
|
||||
}
|
||||
|
||||
.container.outline:hover {
|
||||
@apply bg-primary-200;
|
||||
}
|
||||
|
||||
.container.outline:focus {
|
||||
@apply outline-none;
|
||||
}
|
||||
|
||||
.container.remove {
|
||||
@apply bg-red-600 border-red-600 text-white;
|
||||
}
|
||||
|
||||
.container.remove:hover {
|
||||
@apply bg-red-700 border-red-700;
|
||||
}
|
||||
|
||||
.container.remove:focus {
|
||||
@apply outline-none;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from '../../contexts/ResumeContext';
|
||||
|
||||
const Heading = ({ id }) => {
|
||||
const { t } = useTranslation();
|
||||
const heading = useSelector(`${id}.heading`, t(`builder.sections.${id}`));
|
||||
|
||||
return <h2 className="text-4xl focus:outline-none">{heading}</h2>;
|
||||
};
|
||||
|
||||
export default memo(Heading);
|
||||
@@ -1,164 +0,0 @@
|
||||
import { FaAngleDown } from 'react-icons/fa';
|
||||
import { MdOpenInNew } from 'react-icons/md';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { isFunction } from 'lodash';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import React, { memo, useEffect, useState } from 'react';
|
||||
import cx from 'classnames';
|
||||
import { useDispatch, useSelector } from '../../contexts/ResumeContext';
|
||||
import { handleKeyUp } from '../../utils';
|
||||
import * as styles from './Input.module.css';
|
||||
|
||||
const Input = ({
|
||||
name,
|
||||
path,
|
||||
label,
|
||||
error,
|
||||
value,
|
||||
onBlur,
|
||||
options,
|
||||
touched,
|
||||
onClick,
|
||||
onChange,
|
||||
className,
|
||||
isRequired,
|
||||
placeholder,
|
||||
type = 'text',
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [uuid, setUuid] = useState(null);
|
||||
const stateValue = useSelector(path, '');
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
setUuid(uuidv4());
|
||||
}, []);
|
||||
|
||||
value = path ? stateValue : value;
|
||||
onChange = isFunction(onChange)
|
||||
? onChange
|
||||
: (e) => {
|
||||
dispatch({
|
||||
type: 'on_input',
|
||||
payload: {
|
||||
path,
|
||||
value: e.target.value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<label htmlFor={uuid}>
|
||||
<span>
|
||||
{label}{' '}
|
||||
{isRequired && (
|
||||
<span className="opacity-75 font-normal lowercase">
|
||||
({t('shared.forms.required')})
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
|
||||
{(type === 'text' || type === 'date') && (
|
||||
<div className="relative grid items-center">
|
||||
<input
|
||||
id={uuid}
|
||||
name={name}
|
||||
type={type}
|
||||
value={value}
|
||||
onBlur={onBlur}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{type === 'textarea' && (
|
||||
<div className="flex flex-col">
|
||||
<textarea
|
||||
id={uuid}
|
||||
rows="4"
|
||||
name={name}
|
||||
value={value}
|
||||
onBlur={onBlur}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
|
||||
<p className="mt-2 text-sm opacity-75">
|
||||
<Trans t={t} i18nKey="shared.forms.markdown">
|
||||
A
|
||||
<a
|
||||
href="https://www.markdownguide.org/basic-syntax/"
|
||||
className="text-blue-600"
|
||||
target="blank"
|
||||
>
|
||||
B
|
||||
</a>
|
||||
C
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{type === 'dropdown' && (
|
||||
<div className="relative grid items-center">
|
||||
<select
|
||||
id={uuid}
|
||||
name={name}
|
||||
value={value}
|
||||
onBlur={onBlur}
|
||||
onChange={onChange}
|
||||
>
|
||||
{options.map((x) => (
|
||||
<option key={x} value={x}>
|
||||
{x}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<FaAngleDown
|
||||
size="16px"
|
||||
className="absolute right-0 opacity-50 hover:opacity-75 mx-4"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{type === 'color' && (
|
||||
<div className="relative grid items-center">
|
||||
<div className={styles.circle} style={{ backgroundColor: value }} />
|
||||
|
||||
<input
|
||||
id={uuid}
|
||||
name={name}
|
||||
type="text"
|
||||
value={value}
|
||||
onBlur={onBlur}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{type === 'action' && (
|
||||
<div className={cx('relative grid items-center', styles.readOnly)}>
|
||||
<input readOnly id={uuid} name={name} type="text" value={value} />
|
||||
|
||||
<div
|
||||
tabIndex="0"
|
||||
role="button"
|
||||
onClick={onClick}
|
||||
onKeyUp={(e) => handleKeyUp(e, onClick)}
|
||||
>
|
||||
<MdOpenInNew size="16px" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && touched && <p>{error}</p>}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Input);
|
||||
@@ -1,30 +0,0 @@
|
||||
.circle {
|
||||
left: 14px;
|
||||
|
||||
@apply absolute bg-primary-900 rounded-full w-6 h-6;
|
||||
}
|
||||
|
||||
div.circle + input {
|
||||
padding-left: 44px;
|
||||
}
|
||||
|
||||
.read-only input:hover {
|
||||
cursor: default;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.read-only input:focus {
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.read-only div {
|
||||
@apply absolute rounded-r top-0 right-0 bottom-0 w-20 flex justify-center items-center bg-primary-50;
|
||||
}
|
||||
|
||||
.read-only div:hover {
|
||||
@apply bg-primary-100;
|
||||
}
|
||||
|
||||
.read-only div:focus {
|
||||
@apply outline-none;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { graphql, useStaticQuery } from 'gatsby';
|
||||
import { GatsbyImage } from 'gatsby-plugin-image';
|
||||
import React, { memo } from 'react';
|
||||
import cx from 'classnames';
|
||||
import * as styles from './Logo.module.css';
|
||||
|
||||
const Logo = ({ size = '256px', className }) => {
|
||||
const { file } = useStaticQuery(graphql`
|
||||
query {
|
||||
file(relativePath: { eq: "logo.png" }) {
|
||||
childImageSharp {
|
||||
gatsbyImageData(layout: FIXED)
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
return (
|
||||
<GatsbyImage
|
||||
loading="eager"
|
||||
alt="Reactive Resume"
|
||||
className={cx(styles.logo, className)}
|
||||
style={{ width: size, height: size }}
|
||||
image={file.childImageSharp.gatsbyImageData}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Logo);
|
||||
@@ -1,9 +0,0 @@
|
||||
.logo {
|
||||
box-shadow: var(--shadow);
|
||||
|
||||
@apply rounded;
|
||||
}
|
||||
|
||||
.logo:hover {
|
||||
box-shadow: var(--shadow-strong);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import React, { memo } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import gfm from 'remark-gfm';
|
||||
import cx from 'classnames';
|
||||
|
||||
const Markdown = ({ children, className, ...props }) => (
|
||||
<ReactMarkdown
|
||||
className={cx('markdown', className)}
|
||||
remarkPlugins={[gfm]}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</ReactMarkdown>
|
||||
);
|
||||
|
||||
export default memo(Markdown);
|
||||
@@ -1,58 +0,0 @@
|
||||
import { MdFileUpload } from 'react-icons/md';
|
||||
import { Tooltip } from '@material-ui/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo, useContext, useRef } from 'react';
|
||||
import { handleKeyUp } from '../../utils';
|
||||
import Input from './Input';
|
||||
import * as styles from './PhotoUpload.module.css';
|
||||
import StorageContext from '../../contexts/StorageContext';
|
||||
|
||||
const PhotoUpload = () => {
|
||||
const fileInputRef = useRef(null);
|
||||
const { t } = useTranslation();
|
||||
const { uploadPhotograph } = useContext(StorageContext);
|
||||
|
||||
const handleIconClick = () => {
|
||||
fileInputRef.current.click();
|
||||
};
|
||||
|
||||
const handleImageUpload = (e) => {
|
||||
const file = e.target.files[0];
|
||||
uploadPhotograph(file);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<Tooltip
|
||||
title={t('builder.tooltips.uploadPhotograph')}
|
||||
placement="right-start"
|
||||
>
|
||||
<div
|
||||
role="button"
|
||||
tabIndex="0"
|
||||
className={styles.circle}
|
||||
onClick={handleIconClick}
|
||||
onKeyUp={(e) => handleKeyUp(e, handleIconClick)}
|
||||
>
|
||||
<MdFileUpload size="22px" />
|
||||
<input
|
||||
name="file"
|
||||
type="file"
|
||||
ref={fileInputRef}
|
||||
className="hidden"
|
||||
onChange={handleImageUpload}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
<Input
|
||||
name="photograph"
|
||||
label={t('builder.profile.photograph')}
|
||||
className="pl-6 w-full"
|
||||
path="profile.photograph"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(PhotoUpload);
|
||||
@@ -1,17 +0,0 @@
|
||||
.circle {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
flex: 0 0 60px;
|
||||
|
||||
@apply flex items-center justify-center cursor-pointer bg-primary-200 text-primary-500 rounded-full;
|
||||
@apply transition-opacity duration-200 ease-in-out;
|
||||
}
|
||||
|
||||
.circle:focus {
|
||||
@apply outline-none;
|
||||
}
|
||||
|
||||
.circle:hover {
|
||||
@apply opacity-75;
|
||||
@apply transition-opacity duration-200 ease-in-out;
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import { Link } from 'react-scroll';
|
||||
import { Tooltip } from '@material-ui/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { memo } from 'react';
|
||||
import * as styles from './SectionIcon.module.css';
|
||||
|
||||
const SectionIcon = ({ section, containerId, tooltipPlacement }) => {
|
||||
const { t } = useTranslation();
|
||||
const { id, icon: Icon } = section;
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={t(`builder.sections.${id}`)}
|
||||
placement={tooltipPlacement}
|
||||
arrow
|
||||
>
|
||||
<Link
|
||||
spy
|
||||
smooth
|
||||
to={id}
|
||||
offset={-18}
|
||||
duration={500}
|
||||
containerId={containerId}
|
||||
activeClass="text-primary-900"
|
||||
className={styles.icon}
|
||||
>
|
||||
<Icon size="18px" />
|
||||
</Link>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(SectionIcon);
|
||||
@@ -1,11 +0,0 @@
|
||||
.icon {
|
||||
@apply py-2 cursor-pointer;
|
||||
}
|
||||
|
||||
.icon:focus {
|
||||
@apply outline-none text-primary-900;
|
||||
}
|
||||
|
||||
.icon:hover {
|
||||
@apply text-primary-900;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { Slide, toast } from 'react-toastify';
|
||||
import React, { memo, useEffect } from 'react';
|
||||
import ModalRegistrar from '../../modals/ModalRegistrar';
|
||||
|
||||
const Wrapper = ({ children }) => {
|
||||
useEffect(() => {
|
||||
toast.configure({
|
||||
role: 'alert',
|
||||
hideProgressBar: true,
|
||||
transition: Slide,
|
||||
closeButton: false,
|
||||
position: 'bottom-right',
|
||||
pauseOnFocusLoss: false,
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<html lang="en" />
|
||||
<title>Reactive Resume</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="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."
|
||||
/>
|
||||
<link rel="canonical" href="https://rxresu.me" />
|
||||
<meta property="og:url" content="https://rxresu.me" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="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."
|
||||
/>
|
||||
<meta property="og:image" content="http://rxresu.me/images/share.png" />
|
||||
</Helmet>
|
||||
|
||||
{children}
|
||||
|
||||
<ModalRegistrar />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Wrapper);
|
||||
@@ -1,18 +0,0 @@
|
||||
const ModalEvents = {
|
||||
AUTH_MODAL: 'auth_modal',
|
||||
CREATE_RESUME_MODAL: 'create_resume_modal',
|
||||
SOCIAL_MODAL: 'social_modal',
|
||||
WORK_MODAL: 'work_modal',
|
||||
EDUCATION_MODAL: 'education_modal',
|
||||
PROJECT_MODAL: 'project_modal',
|
||||
AWARD_MODAL: 'award_modal',
|
||||
CERTIFICATION_MODAL: 'certification_modal',
|
||||
SKILL_MODAL: 'skill_modal',
|
||||
HOBBY_MODAL: 'hobby_modal',
|
||||
LANGUAGE_MODAL: 'language_modal',
|
||||
REFERENCE_MODAL: 'reference_modal',
|
||||
IMPORT_MODAL: 'import_modal',
|
||||
EXPORT_MODAL: 'export_modal',
|
||||
};
|
||||
|
||||
export default ModalEvents;
|
||||
@@ -1,134 +0,0 @@
|
||||
import { debounce } from 'lodash';
|
||||
import React, { createContext, memo, useContext, useState } from 'react';
|
||||
import ShortUniqueId from 'short-unique-id';
|
||||
import firebase from 'gatsby-plugin-firebase';
|
||||
import UserContext from './UserContext';
|
||||
import { getUnsplashPhoto } from '../utils';
|
||||
import initialState from '../data/initialState.json';
|
||||
|
||||
const DEBOUNCE_WAIT_TIME = 4000;
|
||||
|
||||
const defaultState = {
|
||||
isUpdating: false,
|
||||
createResume: async () => {},
|
||||
duplicateResume: async () => {},
|
||||
deleteResume: () => {},
|
||||
getResume: async () => {},
|
||||
getResumes: async () => {},
|
||||
updateResume: async () => {},
|
||||
debouncedUpdateResume: async () => {},
|
||||
};
|
||||
|
||||
const DatabaseContext = createContext(defaultState);
|
||||
|
||||
const DatabaseProvider = ({ children }) => {
|
||||
const dictionary = 'abcdefghijklmnopqrstuvwxyz1234567890'.split('');
|
||||
const uuid = new ShortUniqueId({ dictionary });
|
||||
|
||||
const [isUpdating, setUpdating] = useState(false);
|
||||
const { user } = useContext(UserContext);
|
||||
|
||||
const getResume = async (id) => {
|
||||
try {
|
||||
const snapshot = await firebase
|
||||
.database()
|
||||
.ref(`resumes/${id}`)
|
||||
.once('value');
|
||||
return snapshot.val();
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const createResume = async ({ name }) => {
|
||||
const id = uuid();
|
||||
const preview = await getUnsplashPhoto();
|
||||
const createdAt = firebase.database.ServerValue.TIMESTAMP;
|
||||
|
||||
let firstName;
|
||||
let lastName;
|
||||
|
||||
if (!user.isAnonymous) {
|
||||
[firstName, lastName] = user.displayName.split(' ');
|
||||
}
|
||||
|
||||
const resume = {
|
||||
...initialState,
|
||||
id,
|
||||
name,
|
||||
user: user.uid,
|
||||
preview,
|
||||
profile: {
|
||||
...initialState.profile,
|
||||
firstName: firstName || '',
|
||||
lastName: lastName || '',
|
||||
},
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
};
|
||||
|
||||
firebase.database().ref(`resumes/${id}`).set(resume);
|
||||
};
|
||||
|
||||
const duplicateResume = async (originalResume) => {
|
||||
const id = uuid();
|
||||
const preview = await getUnsplashPhoto();
|
||||
const createdAt = firebase.database.ServerValue.TIMESTAMP;
|
||||
|
||||
const resume = {
|
||||
...originalResume,
|
||||
id,
|
||||
name: `${originalResume.name} Copy`,
|
||||
preview,
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
};
|
||||
|
||||
firebase.database().ref(`resumes/${id}`).set(resume);
|
||||
};
|
||||
|
||||
const updateResume = async (resume) => {
|
||||
setUpdating(true);
|
||||
|
||||
await firebase
|
||||
.database()
|
||||
.ref(`resumes/${resume.id}`)
|
||||
.update({
|
||||
...resume,
|
||||
updatedAt: firebase.database.ServerValue.TIMESTAMP,
|
||||
});
|
||||
|
||||
setUpdating(false);
|
||||
};
|
||||
|
||||
const debouncedUpdateResume = debounce(updateResume, DEBOUNCE_WAIT_TIME);
|
||||
|
||||
const deleteResume = async (id) => {
|
||||
await firebase.database().ref(`/resumes/${id}`).remove();
|
||||
};
|
||||
|
||||
return (
|
||||
<DatabaseContext.Provider
|
||||
value={{
|
||||
isUpdating,
|
||||
getResume,
|
||||
createResume,
|
||||
duplicateResume,
|
||||
updateResume,
|
||||
deleteResume,
|
||||
debouncedUpdateResume,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</DatabaseContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default DatabaseContext;
|
||||
|
||||
const memoizedProvider = memo(DatabaseProvider);
|
||||
|
||||
export {
|
||||
memoizedProvider as DatabaseProvider,
|
||||
DEBOUNCE_WAIT_TIME as DebounceWaitTime,
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
import { createNanoEvents } from 'nanoevents';
|
||||
import React, { createContext, memo } from 'react';
|
||||
import ModalEvents from '../constants/ModalEvents';
|
||||
|
||||
const emitter = createNanoEvents();
|
||||
|
||||
const defaultState = { events: ModalEvents, emitter };
|
||||
|
||||
const ModalContext = createContext(defaultState);
|
||||
|
||||
const ModalProvider = ({ children }) => (
|
||||
<ModalContext.Provider value={defaultState}>{children}</ModalContext.Provider>
|
||||
);
|
||||
|
||||
export default ModalContext;
|
||||
|
||||
const memoizedProvider = memo(ModalProvider);
|
||||
|
||||
export { memoizedProvider as ModalProvider };
|
||||
@@ -1,7 +0,0 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
const defaultState = {};
|
||||
|
||||
const PageContext = createContext(defaultState);
|
||||
|
||||
export default PageContext;
|
||||
@@ -1,296 +0,0 @@
|
||||
import {
|
||||
clone,
|
||||
findIndex,
|
||||
get,
|
||||
has,
|
||||
isUndefined,
|
||||
merge,
|
||||
set,
|
||||
setWith,
|
||||
} from 'lodash';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import React, {
|
||||
createContext,
|
||||
memo,
|
||||
useCallback,
|
||||
useContext,
|
||||
useReducer,
|
||||
} from 'react';
|
||||
import arrayMove from 'array-move';
|
||||
import i18next from 'i18next';
|
||||
import demoState from '../data/demoState.json';
|
||||
import DatabaseContext from './DatabaseContext';
|
||||
import initialState from '../data/initialState.json';
|
||||
|
||||
const ResumeContext = createContext({});
|
||||
|
||||
const ResumeProvider = ({ children }) => {
|
||||
const { debouncedUpdateResume } = useContext(DatabaseContext);
|
||||
|
||||
const memoizedReducer = useCallback(
|
||||
(state, { type, payload }) => {
|
||||
let newState;
|
||||
let index;
|
||||
let items;
|
||||
let temp;
|
||||
|
||||
switch (type) {
|
||||
case 'on_add_item':
|
||||
delete payload.value.temp;
|
||||
items = get(state, payload.path, []);
|
||||
newState = setWith(
|
||||
clone(state),
|
||||
payload.path,
|
||||
[...items, payload.value],
|
||||
clone,
|
||||
);
|
||||
debouncedUpdateResume(newState);
|
||||
return newState;
|
||||
|
||||
case 'on_edit_item':
|
||||
delete payload.value.temp;
|
||||
items = get(state, payload.path);
|
||||
index = findIndex(items, ['id', payload.value.id]);
|
||||
newState = setWith(
|
||||
clone(state),
|
||||
`${payload.path}[${index}]`,
|
||||
payload.value,
|
||||
clone,
|
||||
);
|
||||
debouncedUpdateResume(newState);
|
||||
return newState;
|
||||
|
||||
case 'on_delete_item':
|
||||
items = get(state, payload.path);
|
||||
index = findIndex(items, ['id', payload.value.id]);
|
||||
items.splice(index, 1);
|
||||
newState = setWith(clone(state), payload.path, items, clone);
|
||||
debouncedUpdateResume(newState);
|
||||
return newState;
|
||||
|
||||
case 'on_toggle_use_item':
|
||||
items = get(state, payload.path);
|
||||
index = findIndex(items, ['id', payload.value.id]);
|
||||
if ('isVisible' in items[index]) {
|
||||
items[index].isVisible = !items[index].isVisible;
|
||||
} else {
|
||||
items[index].isVisible = false;
|
||||
}
|
||||
newState = setWith(clone(state), payload.path, items, clone);
|
||||
debouncedUpdateResume(newState);
|
||||
return newState;
|
||||
|
||||
case 'on_move_item_up':
|
||||
items = get(state, payload.path);
|
||||
index = findIndex(items, ['id', payload.value.id]);
|
||||
items = arrayMove(items, index, index - 1);
|
||||
newState = setWith(clone(state), payload.path, items, clone);
|
||||
debouncedUpdateResume(newState);
|
||||
return newState;
|
||||
|
||||
case 'on_move_item_down':
|
||||
items = get(state, payload.path);
|
||||
index = findIndex(items, ['id', payload.value.id]);
|
||||
items = arrayMove(items, index, index + 1);
|
||||
newState = setWith(clone(state), payload.path, items, clone);
|
||||
debouncedUpdateResume(newState);
|
||||
return newState;
|
||||
|
||||
case 'change_language':
|
||||
newState = set(clone(state), 'metadata.language', payload);
|
||||
items = get(
|
||||
i18next.getDataByLanguage(payload),
|
||||
'translation.builder.sections',
|
||||
);
|
||||
Object.keys(items).forEach((key) => {
|
||||
has(newState, `${key}.heading`) &&
|
||||
set(newState, `${key}.heading`, items[key]);
|
||||
});
|
||||
debouncedUpdateResume(newState);
|
||||
return newState;
|
||||
|
||||
case 'reset_layout':
|
||||
temp = get(state, 'metadata.template');
|
||||
items = get(initialState, `metadata.layout.${temp}`);
|
||||
newState = setWith(
|
||||
clone(state),
|
||||
`metadata.layout.${temp}`,
|
||||
items,
|
||||
clone,
|
||||
);
|
||||
debouncedUpdateResume(newState);
|
||||
return newState;
|
||||
|
||||
case 'on_input':
|
||||
newState = setWith(clone(state), payload.path, payload.value, clone);
|
||||
debouncedUpdateResume(newState);
|
||||
return newState;
|
||||
|
||||
case 'on_import':
|
||||
temp = clone(state);
|
||||
newState = payload;
|
||||
newState.id = temp.id;
|
||||
newState.user = temp.user;
|
||||
newState.name = temp.name;
|
||||
newState.createdAt = temp.createdAt;
|
||||
newState.updatedAt = temp.updatedAt;
|
||||
debouncedUpdateResume(newState);
|
||||
return newState;
|
||||
|
||||
case 'on_import_jsonresume':
|
||||
temp = clone(state);
|
||||
newState = initialState;
|
||||
newState.id = temp.id;
|
||||
newState.user = temp.user;
|
||||
newState.name = temp.name;
|
||||
newState.preview = temp.preview;
|
||||
newState.createdAt = temp.createdAt;
|
||||
newState.updatedAt = temp.updatedAt;
|
||||
newState.profile = {
|
||||
address: {
|
||||
city: get(payload, 'basics.location.city'),
|
||||
line1: get(payload, 'basics.location.address'),
|
||||
line2: get(payload, 'basics.location.region'),
|
||||
pincode: get(payload, 'basics.location.postalCode'),
|
||||
},
|
||||
email: get(payload, 'basics.email'),
|
||||
firstName: get(payload, 'basics.name'),
|
||||
phone: get(payload, 'basics.phone'),
|
||||
photograph: get(payload, 'basics.picture'),
|
||||
subtitle: get(payload, 'basics.label'),
|
||||
website: get(payload, 'basics.website'),
|
||||
};
|
||||
newState.social.items = get(payload, 'basics.profiles')
|
||||
? payload.basics.profiles.map((x) => ({
|
||||
id: uuidv4(),
|
||||
network: x.network,
|
||||
username: x.username,
|
||||
url: x.url,
|
||||
}))
|
||||
: [];
|
||||
newState.objective.body = get(payload, 'basics.summary');
|
||||
newState.work.items = payload.work
|
||||
? payload.work.map((x) => ({
|
||||
id: uuidv4(),
|
||||
company: x.company,
|
||||
endDate: x.endDate,
|
||||
position: x.position,
|
||||
startDate: x.startDate,
|
||||
summary: x.summary,
|
||||
website: x.website,
|
||||
}))
|
||||
: [];
|
||||
newState.education.items = payload.education
|
||||
? payload.education.map((x) => ({
|
||||
id: uuidv4(),
|
||||
degree: x.studyType,
|
||||
endDate: x.endDate,
|
||||
field: x.area,
|
||||
gpa: x.gpa,
|
||||
institution: x.institution,
|
||||
startDate: x.startDate,
|
||||
summary: x.courses.join('\n'),
|
||||
}))
|
||||
: [];
|
||||
newState.awards.items = payload.awards
|
||||
? payload.awards.map((x) => ({
|
||||
id: uuidv4(),
|
||||
awarder: x.awarder,
|
||||
date: x.date,
|
||||
summary: x.summary,
|
||||
title: x.title,
|
||||
}))
|
||||
: [];
|
||||
newState.skills.items = payload.skills
|
||||
? payload.skills.map((x) => ({
|
||||
id: uuidv4(),
|
||||
level: 'Fundamental Awareness',
|
||||
name: x.name,
|
||||
}))
|
||||
: [];
|
||||
newState.hobbies.items = payload.interests
|
||||
? payload.interests.map((x) => ({
|
||||
id: uuidv4(),
|
||||
name: x.name,
|
||||
}))
|
||||
: [];
|
||||
newState.languages.items = payload.languages
|
||||
? payload.languages.map((x) => ({
|
||||
id: uuidv4(),
|
||||
name: x.language,
|
||||
fluency: x.fluency,
|
||||
}))
|
||||
: [];
|
||||
newState.references.items = payload.references
|
||||
? payload.references.map((x) => ({
|
||||
id: uuidv4(),
|
||||
name: x.name,
|
||||
summary: x.reference,
|
||||
}))
|
||||
: [];
|
||||
debouncedUpdateResume(newState);
|
||||
return newState;
|
||||
|
||||
case 'set_data':
|
||||
newState = payload;
|
||||
debouncedUpdateResume(newState);
|
||||
return newState;
|
||||
|
||||
case 'reset_data':
|
||||
temp = clone(state);
|
||||
newState = initialState;
|
||||
newState.id = temp.id;
|
||||
newState.user = temp.user;
|
||||
newState.name = temp.name;
|
||||
newState.preview = temp.preview;
|
||||
newState.createdAt = temp.createdAt;
|
||||
newState.updatedAt = temp.updatedAt;
|
||||
debouncedUpdateResume(newState);
|
||||
return newState;
|
||||
|
||||
case 'load_demo_data':
|
||||
newState = merge(clone(state), demoState);
|
||||
newState.metadata.layout = demoState.metadata.layout;
|
||||
debouncedUpdateResume(newState);
|
||||
return newState;
|
||||
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
},
|
||||
[debouncedUpdateResume],
|
||||
);
|
||||
|
||||
const [state, dispatch] = useReducer(memoizedReducer, initialState);
|
||||
|
||||
return (
|
||||
<ResumeContext.Provider value={{ state, dispatch }}>
|
||||
{children}
|
||||
</ResumeContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const useSelector = (path, fallback) => {
|
||||
const { state } = useContext(ResumeContext);
|
||||
let value = get(state, path);
|
||||
|
||||
if (isUndefined(value)) {
|
||||
value = isUndefined(fallback) ? state : fallback;
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
const useDispatch = () => {
|
||||
const { dispatch } = useContext(ResumeContext);
|
||||
return dispatch;
|
||||
};
|
||||
|
||||
const memoizedProvider = memo(ResumeProvider);
|
||||
|
||||
export {
|
||||
ResumeContext,
|
||||
memoizedProvider as ResumeProvider,
|
||||
useSelector,
|
||||
useDispatch,
|
||||
};
|
||||
@@ -1,65 +0,0 @@
|
||||
import React, { createContext, memo, useEffect, useState } from 'react';
|
||||
import i18next from 'i18next';
|
||||
import themeConfig from '../data/themeConfig';
|
||||
|
||||
const languageStorageItemKey = 'language';
|
||||
|
||||
const defaultState = {
|
||||
theme: 'Dark',
|
||||
setTheme: () => {},
|
||||
language: 'en',
|
||||
setLanguage: () => {},
|
||||
isSideBarOpen: true,
|
||||
setSideBarToggle: () => {},
|
||||
};
|
||||
|
||||
const SettingsContext = createContext(defaultState);
|
||||
|
||||
const SettingsProvider = ({ children }) => {
|
||||
const [theme, setTheme] = useState(defaultState.theme);
|
||||
const [language, setLanguage] = useState(defaultState.theme);
|
||||
const [isSideBarOpen, setSideBarToggle] = useState(
|
||||
defaultState.isSideBarOpen,
|
||||
);
|
||||
useEffect(() => {
|
||||
const prefTheme = localStorage.getItem('theme') || defaultState.theme;
|
||||
const prefLanguage =
|
||||
localStorage.getItem(languageStorageItemKey) || defaultState.language;
|
||||
setTheme(prefTheme);
|
||||
setLanguage(prefLanguage);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem('theme', theme);
|
||||
const colorConfig = themeConfig[theme];
|
||||
for (const [key, value] of Object.entries(colorConfig)) {
|
||||
document.documentElement.style.setProperty(key, value);
|
||||
}
|
||||
}, [theme]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(languageStorageItemKey, language);
|
||||
i18next.changeLanguage(language);
|
||||
}, [language]);
|
||||
|
||||
return (
|
||||
<SettingsContext.Provider
|
||||
value={{
|
||||
theme,
|
||||
setTheme,
|
||||
language,
|
||||
setLanguage,
|
||||
isSideBarOpen,
|
||||
setSideBarToggle,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SettingsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsContext;
|
||||
|
||||
const memoizedProvider = memo(SettingsProvider);
|
||||
|
||||
export { memoizedProvider as SettingsProvider, languageStorageItemKey };
|
||||
@@ -1,98 +0,0 @@
|
||||
import { toast } from 'react-toastify';
|
||||
import React, { createContext, memo, useContext, useRef } from 'react';
|
||||
import firebase from 'gatsby-plugin-firebase';
|
||||
import { isFileImage } from '../utils';
|
||||
import { useDispatch, useSelector } from './ResumeContext';
|
||||
import UserContext from './UserContext';
|
||||
|
||||
const defaultState = {
|
||||
uploadPhotograph: async () => {},
|
||||
};
|
||||
|
||||
const StorageContext = createContext(defaultState);
|
||||
|
||||
const StorageProvider = ({ children }) => {
|
||||
const toastId = useRef(null);
|
||||
|
||||
const { user } = useContext(UserContext);
|
||||
|
||||
const id = useSelector('id');
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const uploadPhotograph = async (file) => {
|
||||
if (!file) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isFileImage(file)) {
|
||||
toast.error(
|
||||
"You tried to upload a file that was not an image. That won't look good on your resume. Please try again.",
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (file.size > 2097152) {
|
||||
toast.error(
|
||||
"Your image seems to be bigger than 2 MB. That's way too much. Maybe consider reducing its size?",
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const uploadTask = firebase
|
||||
.storage()
|
||||
.ref(`/users/${user.uid}/photographs/${id}`)
|
||||
.put(file);
|
||||
|
||||
let progress = 0;
|
||||
toastId.current = toast('Firing up engines...', {
|
||||
progress,
|
||||
});
|
||||
|
||||
uploadTask.on(
|
||||
'state_changed',
|
||||
(snapshot) => {
|
||||
progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
|
||||
toast.update(toastId.current, {
|
||||
render: 'Uploading...',
|
||||
progress,
|
||||
hideProgressBar: false,
|
||||
});
|
||||
},
|
||||
(error) => toast.error(error),
|
||||
async () => {
|
||||
const downloadURL = await uploadTask.snapshot.ref.getDownloadURL();
|
||||
dispatch({
|
||||
type: 'on_input',
|
||||
payload: {
|
||||
path: 'profile.photograph',
|
||||
value: downloadURL,
|
||||
},
|
||||
});
|
||||
|
||||
toast.update(toastId.current, {
|
||||
render:
|
||||
'Your photograph was uploaded successfully... and you look great!',
|
||||
progress,
|
||||
autoClose: 2000,
|
||||
hideProgressBar: true,
|
||||
});
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StorageContext.Provider
|
||||
value={{
|
||||
uploadPhotograph,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</StorageContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default StorageContext;
|
||||
|
||||
const memoizedProvider = memo(StorageProvider);
|
||||
|
||||
export { memoizedProvider as StorageProvider };
|
||||
@@ -1,153 +0,0 @@
|
||||
import { navigate } from '@reach/router';
|
||||
import { pick } from 'lodash';
|
||||
import { toast } from 'react-toastify';
|
||||
import React, { createContext, memo, useEffect, useState } from 'react';
|
||||
import firebase from 'gatsby-plugin-firebase';
|
||||
import useAuthState from '../hooks/useAuthState';
|
||||
|
||||
const defaultUser = {
|
||||
uid: null,
|
||||
email: null,
|
||||
displayName: null,
|
||||
isAnonymous: false,
|
||||
};
|
||||
|
||||
const defaultState = {
|
||||
loading: false,
|
||||
user: defaultUser,
|
||||
logout: async () => {},
|
||||
loginWithGoogle: async () => {},
|
||||
loginAnonymously: async () => {},
|
||||
deleteAccount: async () => {},
|
||||
};
|
||||
|
||||
const UserContext = createContext(defaultState);
|
||||
|
||||
const UserProvider = ({ children }) => {
|
||||
const [firebaseUser, loading] = useAuthState(firebase);
|
||||
const [user, setUser] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const localUser = JSON.parse(localStorage.getItem('user'));
|
||||
setUser(localUser);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (firebaseUser) {
|
||||
const remoteUser = pick(firebaseUser, Object.keys(defaultUser));
|
||||
localStorage.setItem('user', JSON.stringify(remoteUser));
|
||||
setUser(remoteUser);
|
||||
|
||||
const addUserToDatabase = async () => {
|
||||
const userRef = firebase.database().ref(`users/${remoteUser.uid}`);
|
||||
const snapshot = await userRef.once('value');
|
||||
!snapshot.val() && userRef.set(remoteUser);
|
||||
};
|
||||
|
||||
addUserToDatabase();
|
||||
}
|
||||
}, [firebaseUser]);
|
||||
|
||||
const loginWithGoogle = async () => {
|
||||
const provider = new firebase.auth.GoogleAuthProvider();
|
||||
|
||||
try {
|
||||
return await firebase.auth().signInWithPopup(provider);
|
||||
} catch (error) {
|
||||
toast.error(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const loginAnonymously = async () => {
|
||||
try {
|
||||
return await firebase.auth().signInAnonymously();
|
||||
} catch (error) {
|
||||
toast.error(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
await firebase.auth().signOut();
|
||||
localStorage.removeItem('user');
|
||||
setUser(null);
|
||||
navigate('/');
|
||||
};
|
||||
|
||||
const reauthenticateWithGoogle = async () => {
|
||||
const { currentUser } = firebase.auth();
|
||||
const provider = new firebase.auth.GoogleAuthProvider();
|
||||
|
||||
try {
|
||||
const userCredential = await currentUser.reauthenticateWithPopup(
|
||||
provider,
|
||||
);
|
||||
return userCredential;
|
||||
} catch (error) {
|
||||
toast.error(error.message);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const reauthenticate = async () => {
|
||||
const { currentUser } = firebase.auth();
|
||||
|
||||
if (currentUser.isAnonymous) {
|
||||
return;
|
||||
}
|
||||
|
||||
const googleAuthProvider = new firebase.auth.GoogleAuthProvider();
|
||||
const authProviderIsGoogle =
|
||||
currentUser.providerData &&
|
||||
currentUser.providerData.length > 0 &&
|
||||
currentUser.providerData[0].providerId === googleAuthProvider.providerId;
|
||||
|
||||
if (authProviderIsGoogle) {
|
||||
await reauthenticateWithGoogle();
|
||||
} else {
|
||||
const errorMessage = 'Unable to determine reauthentication method.';
|
||||
toast.error(errorMessage);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteAccount = async () => {
|
||||
const { currentUser } = firebase.auth();
|
||||
const deleteUser = firebase.functions().httpsCallable('deleteUser');
|
||||
|
||||
await reauthenticate();
|
||||
|
||||
await deleteUser();
|
||||
|
||||
try {
|
||||
await currentUser.delete();
|
||||
} catch (error) {
|
||||
toast.error(error.message);
|
||||
} finally {
|
||||
await logout();
|
||||
toast(
|
||||
"It's sad to see you go, but we respect your privacy. All your data has been deleted successfully. Hope to see you again soon!",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<UserContext.Provider
|
||||
value={{
|
||||
user,
|
||||
logout,
|
||||
loading,
|
||||
loginWithGoogle,
|
||||
loginAnonymously,
|
||||
deleteAccount,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</UserContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserContext;
|
||||
|
||||
const memoizedProvider = memo(UserProvider);
|
||||
|
||||
export { memoizedProvider as UserProvider };
|
||||
@@ -1,20 +0,0 @@
|
||||
const colorOptions = [
|
||||
'#f44336',
|
||||
'#E91E63',
|
||||
'#9C27B0',
|
||||
'#673AB7',
|
||||
'#3F51B5',
|
||||
'#2196F3',
|
||||
'#03A9F4',
|
||||
'#00BCD4',
|
||||
'#009688',
|
||||
'#4CAF50',
|
||||
'#8BC34A',
|
||||
'#CDDC39',
|
||||
'#FFEB3B',
|
||||
'#FFC107',
|
||||
'#FF9800',
|
||||
'#FF5722',
|
||||
];
|
||||
|
||||
export default colorOptions;
|
||||
@@ -1,347 +0,0 @@
|
||||
{
|
||||
"awards": {
|
||||
"heading": "Awards",
|
||||
"visible": true,
|
||||
"items": [
|
||||
{
|
||||
"awarder": "Google",
|
||||
"date": "2019-04-01",
|
||||
"id": "6f857f2b-6312-4a0d-907d-2e17991954eb",
|
||||
"summary": "",
|
||||
"title": "International Flutter Hackathon"
|
||||
},
|
||||
{
|
||||
"awarder": "Venturesity",
|
||||
"date": "2016-06-01",
|
||||
"id": "f6efa3f9-9741-4e36-a538-ba0d9779bc61",
|
||||
"summary": "",
|
||||
"title": "Venturesity Banyan Hack"
|
||||
},
|
||||
{
|
||||
"awarder": "Govt. of India",
|
||||
"date": "2017-04-01",
|
||||
"id": "89c0171a-eae9-403e-9f4c-a757fb535c2b",
|
||||
"summary": "",
|
||||
"title": "Smart India Hackathon"
|
||||
}
|
||||
]
|
||||
},
|
||||
"certifications": {
|
||||
"heading": "Certifications",
|
||||
"visible": true,
|
||||
"items": [
|
||||
{
|
||||
"date": "2018-02-01",
|
||||
"id": "d2ec12bc-7876-46bc-afd4-11ae06faf3bd",
|
||||
"issuer": "Cisco Systems",
|
||||
"summary": "",
|
||||
"title": "CCNP"
|
||||
},
|
||||
{
|
||||
"date": "2019-06-01",
|
||||
"id": "f8312288-53ae-4504-a768-4b67aea95926",
|
||||
"issuer": "VMWare",
|
||||
"summary": "",
|
||||
"title": "VCP6-DCV"
|
||||
},
|
||||
{
|
||||
"date": "2014-04-01",
|
||||
"id": "11107df6-5f3c-49ae-bcd4-62b8baa181a1",
|
||||
"issuer": "Cisco Systems",
|
||||
"summary": "",
|
||||
"title": "DCUCI 642-999"
|
||||
}
|
||||
]
|
||||
},
|
||||
"education": {
|
||||
"heading": "Education",
|
||||
"visible": true,
|
||||
"items": [
|
||||
{
|
||||
"degree": "Masters",
|
||||
"endDate": "2002-08-01",
|
||||
"field": "Computer Science",
|
||||
"gpa": "7.2 CGPA",
|
||||
"id": "c42e2a5a-3f0d-497e-838b-ac2019dcf045",
|
||||
"institution": "The City College of New York, NYC, NY",
|
||||
"startDate": "2001-09-01",
|
||||
"summary": ""
|
||||
},
|
||||
{
|
||||
"degree": "Bachelors",
|
||||
"endDate": "2001-08-01",
|
||||
"field": "Computer Science",
|
||||
"gpa": "8.4 CGPA",
|
||||
"id": "278490a2-c327-4e83-8be8-adf913a9b36c",
|
||||
"institution": "University of California, Berkeley, CA",
|
||||
"startDate": "1997-09-01",
|
||||
"summary": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
"hobbies": {
|
||||
"heading": "Hobbies",
|
||||
"visible": true,
|
||||
"items": [
|
||||
{
|
||||
"id": "788dcf5a-78ca-4866-8397-c7a29073d9a1",
|
||||
"name": "Poetry"
|
||||
},
|
||||
{
|
||||
"id": "e3523371-f50c-4348-8c5e-35fe84c0006d",
|
||||
"name": "Travelling"
|
||||
},
|
||||
{
|
||||
"id": "92c35e3b-6cd7-4cea-b505-61347ec61b68",
|
||||
"name": "Photography"
|
||||
},
|
||||
{
|
||||
"id": "d36f2089-93a9-4f30-a425-3dd81c6b89df",
|
||||
"name": "Playing Badminton"
|
||||
},
|
||||
{
|
||||
"id": "d1da41a9-ae83-48fb-8047-d45ebd869a69",
|
||||
"name": "Developing Reactive Resume"
|
||||
}
|
||||
]
|
||||
},
|
||||
"languages": {
|
||||
"heading": "Languages",
|
||||
"visible": true,
|
||||
"items": [
|
||||
{
|
||||
"fluency": "Very Fluent",
|
||||
"id": "78d8cf32-84c7-431d-969b-fdf277968026",
|
||||
"name": "English"
|
||||
},
|
||||
{
|
||||
"fluency": "Native Tongue",
|
||||
"id": "9e0bd5ed-b88d-4046-8fb9-ecba54d29924",
|
||||
"name": "Tamil"
|
||||
},
|
||||
{
|
||||
"fluency": "Native Tongue",
|
||||
"id": "cb895aa9-c485-4bf3-a9e3-08e8f219451a",
|
||||
"name": "Kannada"
|
||||
},
|
||||
{
|
||||
"fluency": "Learning on Duolingo",
|
||||
"id": "8fff60fc-0cd6-47e2-b64f-fb249d1af0d1",
|
||||
"name": "German"
|
||||
}
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"colors": {
|
||||
"background": "#FFFFFF",
|
||||
"primary": "#009688",
|
||||
"text": "#212121"
|
||||
},
|
||||
"font": "Open Sans",
|
||||
"fontSize": "9",
|
||||
"language": "en",
|
||||
"layout": {
|
||||
"castform": [
|
||||
["awards", "certifications", "languages", "hobbies"],
|
||||
["objective", "work", "education", "skills", "projects", "references"]
|
||||
],
|
||||
"celebi": [
|
||||
["awards", "certifications", "languages", "hobbies"],
|
||||
["objective", "work", "education", "skills", "projects", "references"]
|
||||
],
|
||||
"gengar": [
|
||||
["objective", "skills"],
|
||||
["awards", "certifications", "languages", "references", "hobbies"],
|
||||
["work", "education", "projects"]
|
||||
],
|
||||
"glalie": [
|
||||
["awards", "certifications", "hobbies"],
|
||||
[
|
||||
"objective",
|
||||
"work",
|
||||
"education",
|
||||
"skills",
|
||||
"projects",
|
||||
"languages",
|
||||
"references"
|
||||
]
|
||||
],
|
||||
"onyx": [
|
||||
["objective", "work", "education", "projects"],
|
||||
["hobbies", "languages", "awards", "certifications"],
|
||||
["skills", "references"]
|
||||
],
|
||||
"pikachu": [
|
||||
["skills", "languages", "hobbies", "awards", "certifications"],
|
||||
["work", "education", "projects", "references"]
|
||||
]
|
||||
},
|
||||
"template": "castform"
|
||||
},
|
||||
"objective": {
|
||||
"body": "To obtain a job within my chosen field that will challenge me and allow me to use my education, skills and past experiences in a way that is mutually beneficial to both myself and my employer and allow for future growth and advancement.",
|
||||
"heading": "Objective",
|
||||
"visible": true
|
||||
},
|
||||
"profile": {
|
||||
"address": {
|
||||
"city": "Bangalore, India -",
|
||||
"line1": "#5/A, Banashankari Nivas,",
|
||||
"line2": "Brindavan Layout, Subramanyapura,",
|
||||
"pincode": "560061"
|
||||
},
|
||||
"birthDate": "1995-08-06",
|
||||
"email": "hello@amruthpillai.com",
|
||||
"firstName": "Amruth",
|
||||
"heading": "Profile",
|
||||
"lastName": "Pillai",
|
||||
"photograph": "https://rxresu.me/images/picture.jpg",
|
||||
"subtitle": "Full Stack Web Developer",
|
||||
"website": "amruthpillai.com",
|
||||
"phone": "+91 98453 36113"
|
||||
},
|
||||
"projects": {
|
||||
"heading": "Projects",
|
||||
"visible": true,
|
||||
"items": [
|
||||
{
|
||||
"date": "2020-07-01",
|
||||
"id": "c768dcca-90f5-4242-a608-6759b4f667fb",
|
||||
"link": "https://github.com/AmruthPillai/Reactive-Resume",
|
||||
"summary": "Reactive Resume, a free and open-source resume builder that works for you. A few of the important features that make it awesome are minimalistic UI/UX, extensive customizability, portability, regularly updated templates, etc.\n\nFor more information, check out [rxresu.me](https://github.com/AmruthPillai/Reactive-Resume)",
|
||||
"title": "Reactive Resume"
|
||||
},
|
||||
{
|
||||
"date": "2020-04-01",
|
||||
"id": "6ca600b1-c21f-4d7b-8431-f7144d537dd3",
|
||||
"link": "https://amruthpillai.com",
|
||||
"summary": "Resume on the Web has been a project that I've been focused on since the early 2014s. I didn't want my information to be displayed on just a sheet of paper that only HRs or Talent Scouts had the privilege of reading, I wanted it to be accessible to everyone. And that's how this project was conceptualized.",
|
||||
"title": "Resume on the Web"
|
||||
}
|
||||
]
|
||||
},
|
||||
"public": true,
|
||||
"references": {
|
||||
"heading": "References",
|
||||
"visible": true,
|
||||
"items": [
|
||||
{
|
||||
"email": "willywonka@goldenticket.com",
|
||||
"id": "168339fd-3c4b-4f2f-bd3a-ef184be81700",
|
||||
"name": "Willy Wonka",
|
||||
"phone": "+1 (802) 234-2398",
|
||||
"position": "CEO at Chocolate Factory",
|
||||
"summary": ""
|
||||
},
|
||||
{
|
||||
"email": "elanmusk@nottesla.com",
|
||||
"id": "350465b9-9989-43cc-b97e-4115b8980304",
|
||||
"name": "Elangovan Musk",
|
||||
"phone": "+91 93893 34353",
|
||||
"position": "CEO at Newton Motors",
|
||||
"summary": ""
|
||||
},
|
||||
{
|
||||
"email": "l.beasley@carsonlogistics.com",
|
||||
"id": "94e3447b-0a78-4fb7-b14d-591982d35320",
|
||||
"name": "Lorraine Beasley",
|
||||
"phone": "+1 661-808-4188",
|
||||
"position": "Head of HR, Carson Logistics",
|
||||
"summary": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
"skills": {
|
||||
"heading": "Skills",
|
||||
"visible": true,
|
||||
"items": [
|
||||
{
|
||||
"id": "54e5bceb-d0e9-4f04-98d1-48a34f7cf920",
|
||||
"level": "Advanced",
|
||||
"name": "Customer Service Expertise"
|
||||
},
|
||||
{
|
||||
"id": "f0274f62-2252-4cc0-bf12-9e1070942c50",
|
||||
"level": "Intermediate",
|
||||
"name": "High-Volume Call Center"
|
||||
},
|
||||
{
|
||||
"id": "689e2852-df1b-4d41-bda8-c41c88196264",
|
||||
"level": "Intermediate",
|
||||
"name": "Team Leader/Problem Solver"
|
||||
},
|
||||
{
|
||||
"id": "3a4f73b1-50c1-4a85-a4b0-2a55dfe5053a",
|
||||
"level": "Novice",
|
||||
"name": "Call Center Management"
|
||||
},
|
||||
{
|
||||
"id": "08d6c739-1465-41f7-8825-b8d94faa38d6",
|
||||
"level": "Novice",
|
||||
"name": "Teambuilding & Training"
|
||||
},
|
||||
{
|
||||
"id": "261b8fc3-aeec-4347-88a8-bcacb1a17aa3",
|
||||
"level": "Fundamental Awareness",
|
||||
"name": "Continuous Improvement"
|
||||
}
|
||||
]
|
||||
},
|
||||
"social": {
|
||||
"heading": "Social Network",
|
||||
"visible": true,
|
||||
"items": [
|
||||
{
|
||||
"id": "a832b37d-f11d-4a80-8b4d-24796e571b17",
|
||||
"network": "Instagram",
|
||||
"url": "https://pillai.xyz/instagram",
|
||||
"username": "AmruthPillai"
|
||||
},
|
||||
{
|
||||
"id": "a72107fa-a4a5-407d-9e85-39bdb9c0b11a",
|
||||
"network": "Twitter",
|
||||
"url": "https://pillai.xyz/twitter",
|
||||
"username": "KingOKings"
|
||||
},
|
||||
{
|
||||
"id": "1dd46fdd-b3a3-4786-89ce-2e77c0823aba",
|
||||
"network": "LinkedIn",
|
||||
"url": "https://pillai.xyz/linkedin",
|
||||
"username": "AmruthPillai"
|
||||
}
|
||||
]
|
||||
},
|
||||
"work": {
|
||||
"heading": "Work Experience",
|
||||
"visible": true,
|
||||
"items": [
|
||||
{
|
||||
"company": "On Point Electronics, NYC, NY",
|
||||
"endDate": "2018-07-01",
|
||||
"id": "d7c64937-0cb9-41b1-a3a6-0679c882fe63",
|
||||
"position": "Customer Service Representative",
|
||||
"startDate": "2013-01-01",
|
||||
"summary": "- Organized customer information and account data for business planning and customer service purposes.\n- Created excel spreadsheets to track customer data and perform intense reconciliation process.\n- Received 97% positive customer survey results.\n- Speed on calls was 10% above team average. \n**Key Achievement:** Designed and executed an automatized system for following up with customers, increasing customer retention by 22%.",
|
||||
"website": "https://onpoint.com"
|
||||
},
|
||||
{
|
||||
"company": "Excelsior Communications, NYC, NY",
|
||||
"endDate": "2012-12-01",
|
||||
"id": "f5c5dcfe-2a60-4169-a2f1-b305355518ea",
|
||||
"position": "Customer Service Representative",
|
||||
"startDate": "2009-10-01",
|
||||
"summary": "- Worked as a full time customer service rep in a high volume call center.\n- Received \"Associate of the Month\" award six times.\n- Chosen as an example for other associates in trainings. \n**Key Achievement:** Received Customer Appreciation bonus in three of four years.",
|
||||
"website": "https://excelsior.com"
|
||||
},
|
||||
{
|
||||
"company": "Pizza Hut, Newark, NJ",
|
||||
"endDate": "2009-09-01",
|
||||
"id": "dd935088-6fe7-4a4b-8ff5-7417c32d2add",
|
||||
"position": "Waiter",
|
||||
"startDate": "2005-08-01",
|
||||
"summary": "- Worked passionately in customer service in a high volume restaurant.\n- Completed the FAST customer service training class.\n- Maintained a high tip average thanks to consistent customer satisfaction.",
|
||||
"website": "https://pizzahut.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
const fontOptions = [
|
||||
'Lato',
|
||||
'Montserrat',
|
||||
'Nunito',
|
||||
'Open Sans',
|
||||
'Raleway',
|
||||
'Rubik',
|
||||
'Source Sans Pro',
|
||||
'Titillium Web',
|
||||
];
|
||||
|
||||
export default fontOptions;
|
||||
@@ -1,18 +0,0 @@
|
||||
const fontSizeOptions = {
|
||||
'--text-xs-size': 0.75,
|
||||
'--text-sm-size': 0.875,
|
||||
'--text-lg-size': 1.125,
|
||||
'--text-xl-size': 1.25,
|
||||
'--text-2xl-size': 1.5,
|
||||
'--text-3xl-size': 1.875,
|
||||
'--text-4xl-size': 2.25,
|
||||
'--text-xs-line-height': 1,
|
||||
'--text-sm-line-height': 1.25,
|
||||
'--text-lg-line-height': 1.75,
|
||||
'--text-xl-line-height': 1.75,
|
||||
'--text-2xl-line-height': 2,
|
||||
'--text-3xl-line-height': 2.25,
|
||||
'--text-4xl-line-height': 2.5,
|
||||
};
|
||||
|
||||
export default fontSizeOptions;
|
||||
@@ -1,121 +0,0 @@
|
||||
{
|
||||
"profile": {
|
||||
"heading": "Profile",
|
||||
"photograph": "",
|
||||
"firstName": "",
|
||||
"lastName": "",
|
||||
"subtitle": "",
|
||||
"birthDate": "",
|
||||
"address": {
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"city": "",
|
||||
"pincode": ""
|
||||
},
|
||||
"website": "",
|
||||
"email": ""
|
||||
},
|
||||
"social": {
|
||||
"heading": "Social Network",
|
||||
"visible": true,
|
||||
"items": []
|
||||
},
|
||||
"objective": {
|
||||
"heading": "Objective",
|
||||
"visible": true,
|
||||
"body": ""
|
||||
},
|
||||
"work": {
|
||||
"heading": "Work Experience",
|
||||
"visible": true,
|
||||
"items": []
|
||||
},
|
||||
"education": {
|
||||
"heading": "Education",
|
||||
"visible": true,
|
||||
"items": []
|
||||
},
|
||||
"projects": {
|
||||
"heading": "Projects",
|
||||
"visible": true,
|
||||
"items": []
|
||||
},
|
||||
"awards": {
|
||||
"heading": "Awards",
|
||||
"visible": true,
|
||||
"items": []
|
||||
},
|
||||
"certifications": {
|
||||
"heading": "Certifications",
|
||||
"visible": true,
|
||||
"items": []
|
||||
},
|
||||
"skills": {
|
||||
"heading": "Skills",
|
||||
"visible": true,
|
||||
"items": []
|
||||
},
|
||||
"hobbies": {
|
||||
"heading": "Hobbies",
|
||||
"visible": true,
|
||||
"items": []
|
||||
},
|
||||
"languages": {
|
||||
"heading": "Languages",
|
||||
"visible": true,
|
||||
"items": []
|
||||
},
|
||||
"references": {
|
||||
"heading": "References",
|
||||
"visible": true,
|
||||
"items": []
|
||||
},
|
||||
"metadata": {
|
||||
"language": "en",
|
||||
"template": "onyx",
|
||||
"font": "Montserrat",
|
||||
"fontSize": 9,
|
||||
"layout": {
|
||||
"onyx": [
|
||||
["objective", "work", "education", "projects"],
|
||||
["hobbies", "languages", "awards", "certifications"],
|
||||
["skills", "references"]
|
||||
],
|
||||
"pikachu": [
|
||||
["skills", "languages", "hobbies", "awards", "certifications"],
|
||||
["work", "education", "projects", "references"]
|
||||
],
|
||||
"gengar": [
|
||||
["objective", "skills"],
|
||||
["awards", "certifications", "languages", "references", "hobbies"],
|
||||
["work", "education", "projects"]
|
||||
],
|
||||
"castform": [
|
||||
["awards", "certifications", "languages", "hobbies"],
|
||||
["objective", "work", "education", "skills", "projects", "references"]
|
||||
],
|
||||
"glalie": [
|
||||
["awards", "certifications", "hobbies"],
|
||||
[
|
||||
"objective",
|
||||
"work",
|
||||
"education",
|
||||
"skills",
|
||||
"projects",
|
||||
"languages",
|
||||
"references"
|
||||
]
|
||||
],
|
||||
"celebi": [
|
||||
["awards", "certifications", "languages", "hobbies"],
|
||||
["objective", "work", "education", "skills", "projects", "references"]
|
||||
]
|
||||
},
|
||||
"colors": {
|
||||
"text": "#444444",
|
||||
"primary": "#5875DB",
|
||||
"background": "#FFFFFF"
|
||||
}
|
||||
},
|
||||
"public": true
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import { AiFillSafetyCertificate, AiOutlineTwitter } from 'react-icons/ai';
|
||||
import { BsTools } from 'react-icons/bs';
|
||||
import { FaAward, FaProjectDiagram, FaUserFriends } from 'react-icons/fa';
|
||||
import {
|
||||
IoLogoGameControllerB,
|
||||
IoMdBriefcase,
|
||||
IoMdDocument,
|
||||
} from 'react-icons/io';
|
||||
import { MdPerson, MdSchool, MdTranslate } from 'react-icons/md';
|
||||
import ModalEvents from '../constants/ModalEvents';
|
||||
|
||||
export default [
|
||||
{
|
||||
id: 'profile',
|
||||
icon: MdPerson,
|
||||
fixed: true,
|
||||
},
|
||||
{
|
||||
id: 'social',
|
||||
icon: AiOutlineTwitter,
|
||||
event: ModalEvents.SOCIAL_MODAL,
|
||||
fixed: true,
|
||||
},
|
||||
{
|
||||
id: 'objective',
|
||||
icon: IoMdDocument,
|
||||
},
|
||||
{
|
||||
id: 'work',
|
||||
icon: IoMdBriefcase,
|
||||
event: ModalEvents.WORK_MODAL,
|
||||
},
|
||||
{
|
||||
id: 'education',
|
||||
icon: MdSchool,
|
||||
event: ModalEvents.EDUCATION_MODAL,
|
||||
},
|
||||
{
|
||||
id: 'projects',
|
||||
icon: FaProjectDiagram,
|
||||
event: ModalEvents.PROJECT_MODAL,
|
||||
},
|
||||
{
|
||||
id: 'awards',
|
||||
icon: FaAward,
|
||||
event: ModalEvents.AWARD_MODAL,
|
||||
},
|
||||
{
|
||||
id: 'certifications',
|
||||
icon: AiFillSafetyCertificate,
|
||||
event: ModalEvents.CERTIFICATION_MODAL,
|
||||
},
|
||||
{
|
||||
id: 'skills',
|
||||
icon: BsTools,
|
||||
event: ModalEvents.SKILL_MODAL,
|
||||
},
|
||||
{
|
||||
id: 'hobbies',
|
||||
icon: IoLogoGameControllerB,
|
||||
event: ModalEvents.HOBBY_MODAL,
|
||||
},
|
||||
{
|
||||
id: 'languages',
|
||||
icon: MdTranslate,
|
||||
event: ModalEvents.LANGUAGE_MODAL,
|
||||
},
|
||||
{
|
||||
id: 'references',
|
||||
icon: FaUserFriends,
|
||||
event: ModalEvents.REFERENCE_MODAL,
|
||||
},
|
||||
];
|
||||
@@ -1,45 +0,0 @@
|
||||
import {
|
||||
MdColorLens,
|
||||
MdDashboard,
|
||||
MdFontDownload,
|
||||
MdFormatSize,
|
||||
MdImportExport,
|
||||
MdInfo,
|
||||
MdSettings,
|
||||
MdStyle,
|
||||
} from 'react-icons/md';
|
||||
|
||||
export default [
|
||||
{
|
||||
id: 'templates',
|
||||
icon: MdStyle,
|
||||
},
|
||||
{
|
||||
id: 'layout',
|
||||
icon: MdDashboard,
|
||||
},
|
||||
{
|
||||
id: 'colors',
|
||||
icon: MdColorLens,
|
||||
},
|
||||
{
|
||||
id: 'fonts',
|
||||
icon: MdFontDownload,
|
||||
},
|
||||
{
|
||||
id: 'font-size',
|
||||
icon: MdFormatSize,
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
icon: MdImportExport,
|
||||
},
|
||||
{
|
||||
id: 'settings',
|
||||
icon: MdSettings,
|
||||
},
|
||||
{
|
||||
id: 'about',
|
||||
icon: MdInfo,
|
||||
},
|
||||
];
|
||||
@@ -1,28 +0,0 @@
|
||||
const templateOptions = [
|
||||
{
|
||||
id: 'onyx',
|
||||
name: 'Onyx',
|
||||
},
|
||||
{
|
||||
id: 'pikachu',
|
||||
name: 'Pikachu',
|
||||
},
|
||||
{
|
||||
id: 'gengar',
|
||||
name: 'Gengar',
|
||||
},
|
||||
{
|
||||
id: 'castform',
|
||||
name: 'Castform',
|
||||
},
|
||||
{
|
||||
id: 'glalie',
|
||||
name: 'Glalie',
|
||||
},
|
||||
{
|
||||
id: 'celebi',
|
||||
name: 'Celebi',
|
||||
},
|
||||
];
|
||||
|
||||
export default templateOptions;
|
||||
@@ -1,40 +0,0 @@
|
||||
const themeConfig = {
|
||||
Light: {
|
||||
'--color-primary-50': '#FFFFFF',
|
||||
'--color-primary-100': '#FAFAFA',
|
||||
'--color-primary-200': '#F1F0F0',
|
||||
'--color-primary-300': '#D8D2CD',
|
||||
'--color-primary-400': '#CDC4BA',
|
||||
'--color-primary-500': '#ABA59D',
|
||||
'--color-primary-600': '#8A8680',
|
||||
'--color-primary-700': '#686663',
|
||||
'--color-primary-800': '#484745',
|
||||
'--color-primary-900': '#282727',
|
||||
},
|
||||
Dark: {
|
||||
'--color-primary-50': '#212121',
|
||||
'--color-primary-100': '#2c2c2c',
|
||||
'--color-primary-200': '#424242',
|
||||
'--color-primary-300': '#616161',
|
||||
'--color-primary-400': '#757575',
|
||||
'--color-primary-500': '#9e9e9e',
|
||||
'--color-primary-600': '#bdbdbd',
|
||||
'--color-primary-700': '#e0e0e0',
|
||||
'--color-primary-800': '#eeeeee',
|
||||
'--color-primary-900': '#f5f5f5',
|
||||
},
|
||||
AMOLED: {
|
||||
'--color-primary-50': '#010101',
|
||||
'--color-primary-100': '#121212',
|
||||
'--color-primary-200': '#222222',
|
||||
'--color-primary-300': '#333333',
|
||||
'--color-primary-400': '#444',
|
||||
'--color-primary-500': '#696969',
|
||||
'--color-primary-600': '#8F8F8F',
|
||||
'--color-primary-700': '#B4B4B4',
|
||||
'--color-primary-800': '#DADADA',
|
||||
'--color-primary-900': '#FFFFFF',
|
||||
},
|
||||
};
|
||||
|
||||
export default themeConfig;
|
||||
@@ -1,18 +0,0 @@
|
||||
export const tips = [
|
||||
'Create a professional email address.',
|
||||
'Update your contact information.',
|
||||
'Set your font size to 10-12 points.',
|
||||
'Use reverse-chronological order.',
|
||||
'Align your content to the left to make it skimmable.',
|
||||
'Make strategic use of bold, caps, and italics.',
|
||||
'Choose an attractive and readable font.',
|
||||
'Only add jobs you’ve had in the past 10-15 years.',
|
||||
'Give your sections simple subheadings.',
|
||||
'Include URLs to social media profiles, personal websites, and your blog.',
|
||||
];
|
||||
|
||||
export const getRandomTip = () => {
|
||||
const tip = tips[Math.floor(Math.random() * tips.length)];
|
||||
const index = tips.indexOf(tip) + 1;
|
||||
return `Tip #${index}: ${tip}`;
|
||||
};
|
||||
@@ -1,54 +0,0 @@
|
||||
import { useEffect, useReducer, useState } from 'react';
|
||||
|
||||
export default function useAuthState(firebase) {
|
||||
const [auth, setAuth] = useState(undefined);
|
||||
|
||||
const [state, dispatch] = useReducer(
|
||||
(innerState, action) => {
|
||||
switch (action.type) {
|
||||
case 'auth_state_changed':
|
||||
return {
|
||||
...innerState,
|
||||
user: action.user,
|
||||
loading: false,
|
||||
};
|
||||
case 'error':
|
||||
return {
|
||||
...innerState,
|
||||
error: action.error,
|
||||
loading: false,
|
||||
};
|
||||
default:
|
||||
return innerState;
|
||||
}
|
||||
},
|
||||
{
|
||||
user: undefined,
|
||||
loading: true,
|
||||
error: undefined,
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setAuth(firebase.auth());
|
||||
}, [firebase]);
|
||||
|
||||
useEffect(() => {
|
||||
if (auth === undefined) return;
|
||||
|
||||
const unsubscribe = auth.onAuthStateChanged(
|
||||
(user) => {
|
||||
dispatch({ type: 'auth_state_changed', user });
|
||||
},
|
||||
(error) => {
|
||||
dispatch({ type: 'error', error });
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [auth]);
|
||||
|
||||
return [state.user, state.loading, state.error];
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import i18n from 'i18next';
|
||||
import resources from './locales';
|
||||
|
||||
const languages = [
|
||||
{
|
||||
code: 'ar',
|
||||
name: 'Arabic (عربى)',
|
||||
},
|
||||
{
|
||||
code: 'bg',
|
||||
name: 'Bulgarian (Български)',
|
||||
},
|
||||
{
|
||||
code: 'bn',
|
||||
name: 'Bengali (বাংলা)',
|
||||
},
|
||||
{
|
||||
code: 'cs',
|
||||
name: 'Czech (čeština)',
|
||||
},
|
||||
{
|
||||
code: 'zh',
|
||||
name: 'Chinese Simplified (简体中文)',
|
||||
},
|
||||
{
|
||||
code: 'da',
|
||||
name: 'Danish (Dansk)',
|
||||
},
|
||||
{
|
||||
code: 'nl',
|
||||
name: 'Dutch (Nederlands)',
|
||||
},
|
||||
{
|
||||
code: 'en',
|
||||
name: 'English (US)',
|
||||
},
|
||||
{
|
||||
code: 'fi',
|
||||
name: 'Finnish (Suomalainen)',
|
||||
},
|
||||
{
|
||||
code: 'fr',
|
||||
name: 'French (Français)',
|
||||
},
|
||||
{
|
||||
code: 'de',
|
||||
name: 'German (Deutsch)',
|
||||
},
|
||||
{
|
||||
code: 'el',
|
||||
name: 'Greek (Ελληνικά)',
|
||||
},
|
||||
{
|
||||
code: 'he',
|
||||
name: 'Hebrew (עִברִית)',
|
||||
},
|
||||
{
|
||||
code: 'hi',
|
||||
name: 'Hindi (हिंदी)',
|
||||
},
|
||||
{
|
||||
code: 'id',
|
||||
name: 'Indonesian (Bahasa Indonesia)',
|
||||
},
|
||||
{
|
||||
code: 'it',
|
||||
name: 'Italian (Italiano)',
|
||||
},
|
||||
{
|
||||
code: 'ja',
|
||||
name: 'Japanese (日本語)',
|
||||
},
|
||||
{
|
||||
code: 'kn',
|
||||
name: 'Kannada (ಕನ್ನಡ)',
|
||||
},
|
||||
{
|
||||
code: 'lt',
|
||||
name: 'Lithuanian (Lietuvių)',
|
||||
},
|
||||
{
|
||||
code: 'nb',
|
||||
name: 'Norwegian (Norsk)',
|
||||
},
|
||||
{
|
||||
code: 'fa',
|
||||
name: 'Persian (Farsi)',
|
||||
},
|
||||
{
|
||||
code: 'pl',
|
||||
name: 'Polish (Polskie)',
|
||||
},
|
||||
{
|
||||
code: 'ptBr',
|
||||
name: 'Portuguese (Brazilian)',
|
||||
},
|
||||
{
|
||||
code: 'ptPt',
|
||||
name: 'Portuguese (Portugal)',
|
||||
},
|
||||
{
|
||||
code: 'ro',
|
||||
name: 'Romanian (Română)',
|
||||
},
|
||||
{
|
||||
code: 'ru',
|
||||
name: 'Russian (русский)',
|
||||
},
|
||||
{
|
||||
code: 'sk',
|
||||
name: 'Slovak (Slovenčina)',
|
||||
},
|
||||
{
|
||||
code: 'es',
|
||||
name: 'Spanish (Español)',
|
||||
},
|
||||
{
|
||||
code: 'sv',
|
||||
name: 'Swedish (Svenska)',
|
||||
},
|
||||
{
|
||||
code: 'tr',
|
||||
name: 'Turkish (Türkçe)',
|
||||
},
|
||||
{
|
||||
code: 'uk',
|
||||
name: 'Ukrainian (Українська)',
|
||||
},
|
||||
];
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
resources,
|
||||
lng: 'en',
|
||||
fallbackLng: 'en',
|
||||
});
|
||||
|
||||
export { languages };
|
||||
|
||||
export default i18n;
|
||||
@@ -1,254 +0,0 @@
|
||||
{
|
||||
"shared": {
|
||||
"appName": "Reactive Resume",
|
||||
"shortDescription": "سيرة ذاتية مجانية ومفتوحة المصدر.",
|
||||
"forms": {
|
||||
"name": "اسم",
|
||||
"title": "عنوان",
|
||||
"subtitle": "العنوان الفرعي",
|
||||
"required": "مطلوب",
|
||||
"website": "موقع الكتروني",
|
||||
"date": "تاريخ",
|
||||
"present": "هدية",
|
||||
"position": "منصب",
|
||||
"startDate": "تاريخ البدء",
|
||||
"endDate": "تاريخ الانتهاء",
|
||||
"address": "عنوان",
|
||||
"phone": "رقم الهاتف",
|
||||
"email": "عنوان البريد الالكترونى",
|
||||
"summary": "ملخص",
|
||||
"markdown": "تدعم كتلة النص هذه علامة <1> تخفيض السعر </1>.",
|
||||
"contact": "اتصل بنا",
|
||||
"validation": {
|
||||
"min": "الرجاء إدخال {{number}} أحرف على الأقل.",
|
||||
"dateRange": "يجب أن يكون تاريخ الانتهاء بعد تاريخ البدء.",
|
||||
"email": "يجب أن يكون عنوان بريد إلكتروني صالح.",
|
||||
"required": "هذا مطلوب.",
|
||||
"url": "يجب أن يكون عنوان URL صالحًا."
|
||||
}
|
||||
},
|
||||
"buttons": {
|
||||
"add": "أضف",
|
||||
"edit": "تعديل",
|
||||
"cancel": "إلغاء",
|
||||
"delete": "حذف",
|
||||
"loading": "جار التحميل...",
|
||||
"confirmation": "هل أنت واثق؟",
|
||||
"login": "تسجيل الدخول",
|
||||
"logout": "تسجيل خروج"
|
||||
}
|
||||
},
|
||||
"landing": {
|
||||
"hero": {
|
||||
"goToApp": "انتقل إلى التطبيق"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "لوحة",
|
||||
"createResume": "إنشاء سيرة ذاتية",
|
||||
"editResume": "تحرير السيرة الذاتية",
|
||||
"lastUpdated": "آخر تحديث في {{timestamp}}",
|
||||
"toasts": {
|
||||
"deleted": "تم حذف {{name}} بنجاح"
|
||||
},
|
||||
"buttons": {
|
||||
"duplicate": "كرر",
|
||||
"rename": "إعادة تسمية"
|
||||
},
|
||||
"helpText": "ستقوم بإنشاء سيرة ذاتية جديدة من البداية ، ولكن أولاً ، دعنا نطلق عليها اسمًا. يمكن أن يكون هذا اسم الدور الذي تريد التقدم له ، أو إذا كنت تقوم بعمل سيرة ذاتية لصديق ، يمكنك تسميته سيرة Alex. "
|
||||
},
|
||||
"builder": {
|
||||
"toasts": {
|
||||
"formErrors": "قد تحتاج إلى ملء جميع الحقول المطلوبة قبل إرسال هذا النموذج.",
|
||||
"doesNotExist": "السيرة الذاتية التي تبحث عنها لم تعد موجودة ... أو ربما لم تكن موجودة؟",
|
||||
"loadDemoData": "لست متأكدًا من أين تبدأ؟ حاول تحميل البيانات التجريبية لمعرفة ما تقدمه السيرة الذاتية التفاعلية.",
|
||||
"printError": "تواجه وظيفة السحابة بعض المشاكل ، يرجى المحاولة مرة أخرى لاحقًا أو استخدام ميزة طباعة المتصفح."
|
||||
},
|
||||
"sections": {
|
||||
"heading": "عنوان",
|
||||
"profile": "الملف الشخصي",
|
||||
"social": "شبكة اجتماعية",
|
||||
"objective": "هدف",
|
||||
"work": "خبرة العمل",
|
||||
"education": "التعليم",
|
||||
"project": "مشروع",
|
||||
"projects": "المشاريع",
|
||||
"award": "جائزة",
|
||||
"awards": "الجوائز",
|
||||
"certification": "شهادة",
|
||||
"certifications": "الشهادات",
|
||||
"skill": "مهارة",
|
||||
"skills": "المهارات",
|
||||
"hobby": "هواية",
|
||||
"hobbies": "الهوايات",
|
||||
"language": "لغة",
|
||||
"languages": "اللغات",
|
||||
"reference": "مرجع",
|
||||
"references": "المراجع",
|
||||
"templates": "قوالب",
|
||||
"layout": "التصاميم",
|
||||
"colors": "الألوان",
|
||||
"font-size": "حجم الخط",
|
||||
"fonts": "الخطوط",
|
||||
"actions": "أجراءات",
|
||||
"settings": "الإعدادات",
|
||||
"about": "حول"
|
||||
},
|
||||
"profile": {
|
||||
"photograph": "صورة فوتوغرافية",
|
||||
"firstName": "الاسم الاول",
|
||||
"lastName": "اسم العائلة",
|
||||
"birthDate": "تاريخ الميلاد",
|
||||
"address": {
|
||||
"line1": "العنوان الأول",
|
||||
"line2": "العنوان الثاني",
|
||||
"city": "مدينة",
|
||||
"pincode": "الرمز البريدي"
|
||||
}
|
||||
},
|
||||
"social": {
|
||||
"network": "الشبكات الاجتماعية",
|
||||
"username": "اسم المستخدم",
|
||||
"url": "رابط الموقع"
|
||||
},
|
||||
"work": {
|
||||
"company": "شركة"
|
||||
},
|
||||
"education": {
|
||||
"institution": "المعهد",
|
||||
"field": "مجال الدراسة",
|
||||
"degree": "نوع الدراسة",
|
||||
"gpa": "المعدل"
|
||||
},
|
||||
"awards": {
|
||||
"awarder": "مقدم الجائزة"
|
||||
},
|
||||
"certifications": {
|
||||
"issuer": "جهة الإصدار"
|
||||
},
|
||||
"skills": {
|
||||
"level": "مستوى"
|
||||
},
|
||||
"languages": {
|
||||
"fluency": "إتقان"
|
||||
},
|
||||
"layout": {
|
||||
"block": "مجموعة",
|
||||
"reset": "إعادة تعيين التصميم",
|
||||
"text": "يدعم هذا التصميم {{count}} من الكتل."
|
||||
},
|
||||
"colors": {
|
||||
"primary": "اللون الأساسى",
|
||||
"text": "لون الخط",
|
||||
"background": "لون الخلفية"
|
||||
},
|
||||
"actions": {
|
||||
"import": {
|
||||
"heading": "استيراد سيرتك الذاتية",
|
||||
"text": "يمكنك استيراد معلوماتك من مصادر متنوعة مثل JSON أو LinkedIn الخاص بك لملء معظم البيانات لسيرتك الذاتية. ",
|
||||
"button": "استيراد"
|
||||
},
|
||||
"export": {
|
||||
"heading": "تصدير سيرتك الذاتية",
|
||||
"text": "قم بتصدير سيرتك الذاتية بتنسيق PDF لمشاركتها مع جهات التوظيف أو JSON التي ستتمكن من استيرادها مرة أخرى إلى هذا التطبيق على جهاز كمبيوتر آخر.",
|
||||
"button": "تصدير"
|
||||
},
|
||||
"share": {
|
||||
"heading": "شارك سيرتك الذاتية",
|
||||
"text": "يمكن الوصول إلى الرابط أدناه بشكل عام إذا اخترت مشاركته ، وسيرى المشاهدون أحدث إصدار من سيرتك الذاتية في أي وقت."
|
||||
},
|
||||
"loadDemoData": {
|
||||
"text": "غير واضح بشأن ما يجب القيام به مع صفحة فارغة جديدة؟ قم بتحميل بعض البيانات التجريبية لمعرفة كيف يجب أن تبدو السيرة الذاتية ويمكنك البدء في التحرير من هناك.",
|
||||
"button": "تحميل البيانات التجريبية"
|
||||
},
|
||||
"resetEverything": {
|
||||
"text": "تشعر وكأنك ارتكبت الكثير من الأخطاء؟ لا تقلق ، امسح كل شيء بنقرة واحدة فقط ، ولكن كن حذرًا إذا لم تكن هناك نسخ احتياطية.",
|
||||
"button": "إعادة تعيين كل شيء"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"theme": "موضوع",
|
||||
"language": "لغة",
|
||||
"translate": "إذا كنت ترغب في المساهمة بتقديم ترجمات بلغتك <1>please visit this link</1>.",
|
||||
"dangerZone": {
|
||||
"heading": "منطقة الخطر",
|
||||
"text": "إذا كنت ترغب في حذف حسابك ومسح جميع سيرتك الذاتية ، فبمجرد زر واحد. يرجى توخي الحذر لأن هذه عملية لا رجعة فيها.",
|
||||
"button": "حذف الحساب"
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"donate": {
|
||||
"heading": "تبرع الى Reactive Resume",
|
||||
"text": "أحاول أن أفعل ما بوسعي ، ولكن إذا وجدت التطبيق مفيدًا ، أو إذا كنت في وضع أفضل من الآخرين الذين يعتمدون على هذا المشروع في وظيفتهم الأولى <1>please consider donating as little as $5 to help keep the project alive</1> :)",
|
||||
"button": "اشتري لي قهوة!"
|
||||
},
|
||||
"bugFeature": {
|
||||
"heading": "خلل برمجي؟ طلب المواصفات؟",
|
||||
"text": "شيء يوقف تقدمك من جعل السيرة الذاتية؟ هل وجدت خللًا مزعجًا لن يستقيل؟ تحدث عنه في قسم مشكلات GitHub باستخدام الإجراءات أدناه.",
|
||||
"button": "رفع مشكلة"
|
||||
},
|
||||
"appreciate": {
|
||||
"heading": "اعجبت ب Reactive Resume?",
|
||||
"text": "لا أتعب أبدًا من سماع القصص حول كيف ساعد هذا التطبيق الأشخاص ، وإذا كان قد ساعدك ، أو إذا وجدت للتو Resactive Resume أداة رائعة ، فأخبرني بذلك. يمكنك التواصل معي على موقع الويب الخاص بي."
|
||||
},
|
||||
"sourceCode": {
|
||||
"heading": "مصدر الرمز",
|
||||
"text": "هل تريد تشغيل المشروع من مصدره؟ هل أنت مطور على استعداد للمساهمة في تطوير هذا المشروع مفتوح المصدر؟ انقر فوق الزر أدناه.",
|
||||
"button": "Github مال المصدر"
|
||||
},
|
||||
"footer": "مصنوع بالحب من قبل <1>Amruth Pillai</1>"
|
||||
},
|
||||
"tooltips": {
|
||||
"uploadPhotograph": "تحميل صورة",
|
||||
"backToDashboard": "ارجع إلى لوحة المعلومات"
|
||||
},
|
||||
"emptyList": "هذه القائمة فارغة."
|
||||
},
|
||||
"modals": {
|
||||
"auth": {
|
||||
"whoAreYou": "من أنت؟",
|
||||
"welcome": "مرحبًا {{name}}!",
|
||||
"loggedOutText": "يحتاج Reactive Resume رد الفعل إلى معرفة من أنت حتى يتمكن من مصادقتك بأمان في التطبيق ويظهر لك معلوماتك فقط. بمجرد دخولك ، يمكنك البدء في بناء سيرتك الذاتية ، وتحريرها لإضافة مهارات جديدة أو مشاركتها مع العالم! ",
|
||||
"loggedInText": "ممتاز. الآن بعد أن قمت بمصادقة نفسك ، يمكننا متابعة السبب الحقيقي لوجودك هنا. انقر فوق الزر Go to App لبدء إنشاء سيرتك الذاتية!",
|
||||
"buttons": {
|
||||
"google": "الدخول مع جوجل",
|
||||
"anonymous": "الدخول بتخفي"
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"button": "حدد ملف",
|
||||
"reactiveResume": {
|
||||
"heading": "استيراد من Reactive Resume",
|
||||
"text": "Reactive Resume لها تنسيق مخطط خاص بها لتحقيق أقصى استفادة من جميع الإمكانات القابلة للتخصيص التي تقدمها. إذا كنت ترغب في استيراد نسخة احتياطية من سيرتك الذاتية المصممة باستخدام هذا التطبيق ، فما عليك سوى تحميل الملف باستخدام الزر أدناه."
|
||||
},
|
||||
"jsonResume": {
|
||||
"heading": "استيراد من JSON",
|
||||
"text": "JSON Resume هو معيار مفتوح لبنية مخطط السيرة الذاتية. إذا كنت واحدًا من العديد من المتحمسين الذين لديهم سيرتهم الذاتية جاهزة بهذا التنسيق ، فكل ما يتطلبه الأمر هو نقرة واحدة فقط لبدء Reactive Resume"
|
||||
},
|
||||
"linkedIn": {
|
||||
"heading": "استيراد من LinkedIn",
|
||||
"text": "يمكنك استيراد ملف JSON الذي تم تصديره من LinkedIn من خلال النقر على الزر أدناه وتحديد الملف المناسب."
|
||||
}
|
||||
},
|
||||
"export": {
|
||||
"printDialog": {
|
||||
"heading": "استخدم مربع حوار الطباعة الخاص بالمتصفح",
|
||||
"text": "بالنسبة لأولئك منكم الذين يريدون حلاً سريعًا ، لا تحتاج إلى البحث أكثر من متصفحك. كل ما عليك فعله هو الضغط على Ctrl / Cmd + P وفتح مربع حوار الطباعة على متصفحك وطباعة سيرتك الذاتية على الفور.",
|
||||
"button": "طباعة سيرتي الذاتية"
|
||||
},
|
||||
"downloadPDF": {
|
||||
"heading": "تحميل PDF",
|
||||
"text": "تتيح لك هذه الخيارات طباعة صفحة واحدة ، نسخة غير مقيدة من سيرتك الذاتية ، مثالية لأولئك الذين لديهم الكثير من المحتوى. وبدلاً من ذلك ، يمكنك تنزيل نسخة متعددة الصفحات من سيرتك الذاتية أيضًا بنقرة واحدة فقط.",
|
||||
"buttons": {
|
||||
"single": "صفحة واحدة من سيرتي الذاتية",
|
||||
"multi": "صفحة متعددة من سيرتي الذاتية"
|
||||
}
|
||||
},
|
||||
"jsonFormat": {
|
||||
"heading": "تصدير إلى تنسيق JSON",
|
||||
"text": "يمكنك أيضًا تصدير بياناتك إلى تنسيق JSON للاحتفاظ بها بشكل آمن بحيث يمكنك استيرادها بسهولة مرة أخرى إلى السيرة الذاتية التفاعلية متى أردت تعديل السيرة الذاتية أو إنشائها.",
|
||||
"button": "تصدير JSON"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
{
|
||||
"shared": {
|
||||
"appName": "Reactive Resume",
|
||||
"shortDescription": "A free and open source resume builder.",
|
||||
"forms": {
|
||||
"name": "Име",
|
||||
"title": "Заглавие",
|
||||
"subtitle": "Подзаглавие",
|
||||
"required": "задължително",
|
||||
"website": "Уебсайт",
|
||||
"date": "Дата",
|
||||
"present": "Настояще",
|
||||
"position": "Позиция",
|
||||
"startDate": "Начална Дата",
|
||||
"endDate": "Крайна Дата",
|
||||
"address": "Адрес",
|
||||
"phone": "Телефонен Номер",
|
||||
"email": "Email адрес",
|
||||
"summary": "Обобщение",
|
||||
"markdown": "Този текстови блок поддържа <1>markdown</1>.",
|
||||
"contact": "Контакти",
|
||||
"validation": {
|
||||
"min": "Моля въведете поне {{number}} символа.",
|
||||
"dateRange": "Крайната дата трябва да е след началната дата.",
|
||||
"email": "Email адресът трябва да е валиден.",
|
||||
"required": "Това поле е задължително.",
|
||||
"url": "Линкът трябва да е валиден."
|
||||
}
|
||||
},
|
||||
"buttons": {
|
||||
"add": "Добави",
|
||||
"edit": "Промени",
|
||||
"cancel": "Отказ",
|
||||
"delete": "Изтрии",
|
||||
"loading": "Зарежда...",
|
||||
"confirmation": "Сигурен?",
|
||||
"login": "Вход",
|
||||
"logout": "Изход"
|
||||
}
|
||||
},
|
||||
"landing": {
|
||||
"hero": {
|
||||
"goToApp": "Към приложението"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"createResume": "Създай СВ/Резюме",
|
||||
"editResume": "Промени",
|
||||
"lastUpdated": "Последна промяна {{timestamp}}",
|
||||
"toasts": {
|
||||
"deleted": "{{name}} беше изтрито успешно."
|
||||
},
|
||||
"buttons": {
|
||||
"duplicate": "Дубликирай",
|
||||
"rename": "Преименувай"
|
||||
},
|
||||
"helpText": "Ще правиш ново резюме, от празен шаблон. Нека запомнем с име, например \"Моето Резюме\". Името може да е позицията, за която кандидатствате, или ако го правите за приятел може да го кръстите \"На Гошо СВ-то\"."
|
||||
},
|
||||
"builder": {
|
||||
"toasts": {
|
||||
"formErrors": "Изглежда, че трябва да попълните всяко едно от полетата преди да предадете.",
|
||||
"doesNotExist": "Резюмето, което търсите не съществува.... или никога не е съществувало?",
|
||||
"loadDemoData": "Не сте сигурни от къде да започнете? Заредете демо данните, за да видите какво предлага Reactive Resume.",
|
||||
"printError": "Cloud функцията се сблъска с няколко проблема, моля опитайте по-късно или използвайте опцията за принтиране."
|
||||
},
|
||||
"sections": {
|
||||
"heading": "Заглавие",
|
||||
"profile": "Профил",
|
||||
"social": "Социални мрежи",
|
||||
"objective": "Цел",
|
||||
"work": "Професионален опит",
|
||||
"education": "Образование",
|
||||
"project": "Проект",
|
||||
"projects": "Проекти",
|
||||
"award": "Награда",
|
||||
"awards": "Награди",
|
||||
"certification": "Сертификат",
|
||||
"certifications": "Сертификати",
|
||||
"skill": "Умение",
|
||||
"skills": "Умения",
|
||||
"hobby": "Хоби",
|
||||
"hobbies": "Хобита",
|
||||
"language": "Език",
|
||||
"languages": "Езици",
|
||||
"reference": "Препоръка",
|
||||
"references": "Препоръки",
|
||||
"templates": "Шаблони",
|
||||
"layout": "Оформление",
|
||||
"colors": "Цветове",
|
||||
"font-size": "Размер на шрифта",
|
||||
"fonts": "Шрифтове",
|
||||
"actions": "Операции",
|
||||
"settings": "Настройки",
|
||||
"about": "За"
|
||||
},
|
||||
"profile": {
|
||||
"photograph": "Снимка",
|
||||
"firstName": "Име",
|
||||
"lastName": "Фамилия",
|
||||
"birthDate": "Дата на раждане",
|
||||
"address": {
|
||||
"line1": "Улица",
|
||||
"line2": "Блок/Номер/Къща/Вход",
|
||||
"city": "Град",
|
||||
"pincode": "Пощенски код"
|
||||
}
|
||||
},
|
||||
"social": {
|
||||
"network": "Платформа (например Instagram)",
|
||||
"username": "Потребителско име",
|
||||
"url": "Линк към профила"
|
||||
},
|
||||
"work": {
|
||||
"company": "Фирма"
|
||||
},
|
||||
"education": {
|
||||
"institution": "Институт",
|
||||
"field": "Специалност",
|
||||
"degree": "Степен",
|
||||
"gpa": "БАЛ"
|
||||
},
|
||||
"awards": {
|
||||
"awarder": "Награждаващ"
|
||||
},
|
||||
"certifications": {
|
||||
"issuer": "Емитент"
|
||||
},
|
||||
"skills": {
|
||||
"level": "Ниво"
|
||||
},
|
||||
"languages": {
|
||||
"fluency": "Ниво на умеене"
|
||||
},
|
||||
"layout": {
|
||||
"block": "Блок",
|
||||
"reset": "Рестартирай оформлението",
|
||||
"text": "Този шаблон поддържа {{count}} блока."
|
||||
},
|
||||
"colors": {
|
||||
"primary": "Основен цвят",
|
||||
"text": "Втори цвят",
|
||||
"background": "Цвят на фона"
|
||||
},
|
||||
"actions": {
|
||||
"import": {
|
||||
"heading": "Качи твоето резюме",
|
||||
"text": "Можеш да качиш предишното си резюме стига то да е във формат JSON Resume (при изтегляне като JSON го получавате в този международен формат), или ако е от LinkedIn. Това ще ти позволи да попълниш повечето или всичките данни за резюмето ти.",
|
||||
"button": "Качи"
|
||||
},
|
||||
"export": {
|
||||
"heading": "Изтегли твоето резюме",
|
||||
"text": "Изтегли като PDF, за да можеш да го споделиш в платформата където кандидастваш или го изтегли като JSON, за да можед да го качиш и да си спестиш време в бъдеще.",
|
||||
"button": "Изтегли"
|
||||
},
|
||||
"share": {
|
||||
"heading": "Сподели твоето резюме",
|
||||
"text": "Линкът ще бъде публично достъпен и всеки с линка ще може да види резюмето ти онлайн без да има нужда да го тегли."
|
||||
},
|
||||
"loadDemoData": {
|
||||
"text": "Зареди демо данни, за да получиш представа как трябва да изглежда завършено. От там нататък си ти!",
|
||||
"button": "Зареди демо данни"
|
||||
},
|
||||
"resetEverything": {
|
||||
"text": "Направил си твърде много грешки и искаш да започнеш отново? Няма проблем, можеш да изчистиш цялото резюме и да започнеш от нулата. ВНИМАВАЙ, защото тази версия не се пази и ще бъде заниваги загубна, ако изчистиш всичко!",
|
||||
"button": "Изтрии всичко"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"theme": "Тема",
|
||||
"language": "Език",
|
||||
"translate": "Ако искаш да помогнеш в превеждането на сайта можеш да го направиш от <1>този линк</1>.",
|
||||
"dangerZone": {
|
||||
"heading": "Опасна зона",
|
||||
"text": "Тук можеш да изтриеш акаунта си и с него всичките данни, всички предишни резюмета, а не само това. Внимавай, защото процесът е необратим и акаунта ти ще бъде изтрит завинаги!",
|
||||
"button": "Изтрии акаунта"
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"donate": {
|
||||
"heading": "Направи дарение и подкрепи Reactive Resume",
|
||||
"text": "Приложение, което използвате е напълно безплатно и до момента се поддържа само от добри хора, които отделят време и усилия да го поддържат. Ако можете да го си позволите <1>моля дарете колкото можете :) Всичко помага и ще гарантира че следващият път като се върнете сървъра още е онлайн.</1>",
|
||||
"button": "Направи дарение"
|
||||
},
|
||||
"bugFeature": {
|
||||
"heading": "Bug? Feature Request?",
|
||||
"text": "Something halting your progress from making a resume? Found a pesky bug that just won't quit? Talk about it on the GitHub Issues section using the actions below.",
|
||||
"button": "Raise an Issue"
|
||||
},
|
||||
"appreciate": {
|
||||
"heading": "Loved Reactive Resume?",
|
||||
"text": "I never get tired of hearing stories of how this app helped people, and if it helped you, or you just found Reactive Resume to be an awesome tool, do let me know. You can reach out to me on my website."
|
||||
},
|
||||
"sourceCode": {
|
||||
"heading": "Source Code",
|
||||
"text": "Want to run the project from its source? Are you a developer willing to contribute to the open-source development of this project? Click the button below.",
|
||||
"button": "GitHub Repo"
|
||||
},
|
||||
"footer": "Направено с ЛЮБОВ от <1>Amruth Pillai</1>"
|
||||
},
|
||||
"tooltips": {
|
||||
"uploadPhotograph": "Качи снимка",
|
||||
"backToDashboard": "Върни се към Dashboard"
|
||||
},
|
||||
"emptyList": "Този списък е празен."
|
||||
},
|
||||
"modals": {
|
||||
"auth": {
|
||||
"whoAreYou": "Кой си ти?",
|
||||
"welcome": "Добре дошъл, {{name}}!",
|
||||
"loggedOutText": "Reactive Resume трябва да знае кой си, за да може да те въведе в системата и да ти показна само твоята информация. Щом влезеш, можеш да започнеш с резюмето си, да го пременяш или да го споделиш със света!",
|
||||
"loggedInText": "Супер! Сега като си влязъл, нека се върнем към причината поради, която си тук. Натисни на Към приложението бутона, за да започнеш с резюмето си!",
|
||||
"buttons": {
|
||||
"google": "Влез чрез Google",
|
||||
"anonymous": "Влез анонимно"
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"button": "Избор на файл",
|
||||
"reactiveResume": {
|
||||
"heading": "Качи от Reactive Resume",
|
||||
"text": "Reactive Resume има свой собствен формат за пазене на данни. За да качите файл, който по-рано сте изтеглили (*.json) моля изберете го."
|
||||
},
|
||||
"jsonResume": {
|
||||
"heading": "Качване на JSON Resume",
|
||||
"text": "JSON Resume е стандартен формат за структуриране на резюмета. Ако не сте един от ентусиастите, които вече имат резюземе във този формат можете да започнете само с един клик с помощта на Reactive Resume."
|
||||
},
|
||||
"linkedIn": {
|
||||
"heading": "Качване от LinkedIn",
|
||||
"text": "Можете да качите и JSON, койте сте изтеглили от вашият LinkedIn."
|
||||
}
|
||||
},
|
||||
"export": {
|
||||
"printDialog": {
|
||||
"heading": "Използвай опцията за принтиране на браузъра",
|
||||
"text": "За тези от вас, които търсят бързо решение. Натиснете Ctrl/Cmd + P и принтирайте вашето резюме моментално.",
|
||||
"button": "Принтирай"
|
||||
},
|
||||
"downloadPDF": {
|
||||
"heading": "Изтегли като PDF",
|
||||
"text": "Опциите са ви, на една станица и на много страници. В зависимост от вашите нужди изберете подходящото да вас.",
|
||||
"buttons": {
|
||||
"single": "Една страница",
|
||||
"multi": "Много страници"
|
||||
}
|
||||
},
|
||||
"jsonFormat": {
|
||||
"heading": "Изтегли във формат JSON Format",
|
||||
"text": "Също можете да го изтеглите в този формат, ако желаете да го съхраните на ваше устройство и по-късно да качите обратно тук за да правите промени по него.",
|
||||
"button": "Изтегли като JSON"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,253 +0,0 @@
|
||||
{
|
||||
"shared": {
|
||||
"appName": "রিআক্টিভ রেসুমে",
|
||||
"shortDescription": "একটি নিখরচায় ও মুক্ত উত্স জীবনবৃত্তান্ত নির্মাতা।",
|
||||
"forms": {
|
||||
"name": "নাম",
|
||||
"title": "শিরোনাম",
|
||||
"subtitle": "গৌণ নাম",
|
||||
"required": "প্রয়োজনীয়",
|
||||
"website": "ওয়েবসাইট",
|
||||
"date": "তারিখ",
|
||||
"present": "বর্তমান",
|
||||
"position": "পদ",
|
||||
"startDate": "শুরুর তারিখ",
|
||||
"endDate": "শেষ তারিখ",
|
||||
"address": "ঠিকানা",
|
||||
"phone": "ফোন নম্বর",
|
||||
"email": "ইমেল",
|
||||
"summary": "সারসংক্ষেপ",
|
||||
"markdown": "এই পাঠ্য ব্লক <1>মার্কডাউন</1> সমর্থন করে।",
|
||||
"validation": {
|
||||
"min": "কমপক্ষে {{number}} টি অক্ষর লিখুন।",
|
||||
"dateRange": "শেষের তারিখ অবশ্যই শুরু তারিখের চেয়ে পরে হতে হবে।",
|
||||
"email": "একটি বৈধ ইমেইল ঠিকানা আবশ্যক।",
|
||||
"required": "এটি একটি প্রয়োজনীয় ক্ষেত্র।",
|
||||
"url": "অবশ্যই একটি বৈধ ইউআরএল হওয়া উচিত।"
|
||||
}
|
||||
},
|
||||
"buttons": {
|
||||
"add": "যোগ করুন",
|
||||
"edit": "সম্পাদনা করুন",
|
||||
"cancel": "বাতিল করুন",
|
||||
"delete": "মুছুন",
|
||||
"loading": "লোড হচ্ছে...",
|
||||
"confirmation": "আপনি কি নিশ্চিত?",
|
||||
"login": "প্রবেশ করুন",
|
||||
"logout": "প্রস্থান করুন"
|
||||
}
|
||||
},
|
||||
"landing": {
|
||||
"hero": {
|
||||
"goToApp": "অ্যাপ্লিকেশন এ যান"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "ড্যাশবোর্ড",
|
||||
"createResume": "জীবনবৃত্তান্ত তৈরি করুন",
|
||||
"editResume": "জীবনবৃত্তান্ত সম্পাদনা করুন",
|
||||
"lastUpdated": "সর্বশেষ আপডেট {{timestamp}}",
|
||||
"toasts": {
|
||||
"deleted": "{{name}} সফলভাবে মুছে ফেলা হয়েছে"
|
||||
},
|
||||
"buttons": {
|
||||
"duplicate": "প্রতিলিপি",
|
||||
"rename": "নতুন নামকরণ করুন"
|
||||
},
|
||||
"helpText": "আপনি শুরু থেকে একটি নতুন জীবনবৃত্তান্ত তৈরি করতে যাচ্ছেন, তবে প্রথমে, এটিকে একটি নাম দিন। এটি আপনি যে ভূমিকার জন্য আবেদন করতে চান তার নাম হতে পারে বা আপনি যদি কোনও বন্ধুর জন্য জীবনবৃত্তান্ত তৈরি করেন তবে আপনি এটিকে অ্যালেক্সের জীবনবৃত্তান্ত বলতে পারেন।"
|
||||
},
|
||||
"builder": {
|
||||
"toasts": {
|
||||
"formErrors": "এই ফর্মটি জমা দেওয়ার আগে আপনাকে প্রয়োজনীয় সমস্ত ক্ষেত্রগুলি পূরণ করতে হবে।",
|
||||
"doesNotExist": "আপনি যে জীবনবৃত্তান্তটি সন্ধান করেছিলেন সেটি আর নেই ... বা সম্ভবত কখনও ছিল না?",
|
||||
"loadDemoData": "কোথায় শুরু করবেন তা নিশ্চিত নন? রিঅ্যাকটিভ রেজ্যুমে কী প্রস্তাব দেয় তা দেখতে ডেমো তথ্য লোড করার চেষ্টা করুন।",
|
||||
"printError": "ক্লাউড ফাংশনটিতে কিছু সমস্যার মধ্যে চলছে, দয়া করে পরে আবার চেষ্টা করুন বা ব্রাউজার মুদ্রণ বৈশিষ্ট্যটি ব্যবহার করুন।"
|
||||
},
|
||||
"sections": {
|
||||
"heading": "শিরোনাম",
|
||||
"profile": "পার্শ্বচিত্র",
|
||||
"social": "সামাজিক যোগাযোগ মাধ্যম",
|
||||
"objective": "উদ্দেশ্য",
|
||||
"work": "কর্মদক্ষতা",
|
||||
"education": "শিক্ষা",
|
||||
"project": "প্রকল্প",
|
||||
"projects": "প্রকল্প",
|
||||
"award": "পুরষ্কার",
|
||||
"awards": "পুরষ্কার",
|
||||
"certification": "শংসাপত্র",
|
||||
"certifications": "শংসাপত্র",
|
||||
"skill": "দক্ষতা",
|
||||
"skills": "দক্ষতা",
|
||||
"hobby": "শখ",
|
||||
"hobbies": "শখ",
|
||||
"language": "ভাষা",
|
||||
"languages": "ভাষা",
|
||||
"reference": "উল্লেখ",
|
||||
"references": "উল্লেখ",
|
||||
"templates": "কাঠামো",
|
||||
"layout": "বিন্যাস",
|
||||
"colors": "রঙ",
|
||||
"font-size": "অক্ষরের আকার",
|
||||
"fonts": "হরফ",
|
||||
"actions": "পদক্ষেপ",
|
||||
"settings": "বিন্যাস",
|
||||
"about": "সম্বন্ধে"
|
||||
},
|
||||
"profile": {
|
||||
"photograph": "ছবি",
|
||||
"firstName": "নামের প্রথম অংশ",
|
||||
"lastName": "নামের শেষাংশ",
|
||||
"birthDate": "জন্ম তারিখ",
|
||||
"address": {
|
||||
"line1": "ঠিকানা লাইন এক",
|
||||
"line2": "ঠিকানা লাইন দুই",
|
||||
"city": "শহর",
|
||||
"pincode": "জিপ কোড"
|
||||
}
|
||||
},
|
||||
"social": {
|
||||
"network": "যোগাযোগ মাধ্যম",
|
||||
"username": "ব্যবহারকারীর নাম",
|
||||
"url": "ইউআরএল"
|
||||
},
|
||||
"work": {
|
||||
"company": "সংস্থা"
|
||||
},
|
||||
"education": {
|
||||
"institution": "প্রতিষ্ঠান",
|
||||
"field": "অধ্যয়নের ক্ষেত্র",
|
||||
"degree": "কি ধরনের ডিগ্রি",
|
||||
"gpa": "জিপিএ"
|
||||
},
|
||||
"awards": {
|
||||
"awarder": "কে এই পুরষ্কার দিয়েছে"
|
||||
},
|
||||
"certifications": {
|
||||
"issuer": "কে জারি করেছে"
|
||||
},
|
||||
"skills": {
|
||||
"level": "স্তর"
|
||||
},
|
||||
"languages": {
|
||||
"fluency": "সাবলীলতা"
|
||||
},
|
||||
"layout": {
|
||||
"block": "সারি",
|
||||
"reset": "বিন্যাস পুনরায় সেট করুন",
|
||||
"text": "এই কাঠামোটি {{count}} সারি সমর্থন করে।"
|
||||
},
|
||||
"colors": {
|
||||
"primary": "মৌলিক রঙ",
|
||||
"text": "লেখার রঙ",
|
||||
"background": "পেছনের রঙ"
|
||||
},
|
||||
"actions": {
|
||||
"import": {
|
||||
"heading": "আপনার জীবনবৃত্তান্তটি আমদানি করুন",
|
||||
"text": "আপনি আপনার জীবনবৃত্তান্তের জন্য বেশিরভাগ তথ্য, স্বয়ংক্রিয়ভাবে পূরণের জন্য, জেএসএন রেজিউমে বা লিঙ্কডইনের মত বিভিন্ন উত্স থেকে তথ্য আমদানি করতে পারেন।",
|
||||
"button": "আমদানি"
|
||||
},
|
||||
"export": {
|
||||
"heading": "আপনার জীবনবৃত্তান্ত রফতানি করুন",
|
||||
"text": "নিয়োগকারীদের আপনার জীবনবৃত্তান্তটি পাঠাতে পিডিএফ হিসাবে রফতানি করুন বা জেএসওএনও আপনি রফতানি করতে পারেন যাতে অন্য কম্পিউটারে এই অ্যাপ্লিকেশনটিতে ফিরে আমদানি করতে সক্ষম হবেন।",
|
||||
"button": "রফতানি"
|
||||
},
|
||||
"share": {
|
||||
"heading": "আপনার জীবনবৃত্তান্ত পাঠান",
|
||||
"text": "আপনি যদি ভাগ করে নেওয়ার পছন্দ করেন তবে নীচের লিঙ্কটি সর্বজনীনভাবে অ্যাক্সেসযোগ্য হবে এবং দর্শকরা যে কোনও সময় আপনার জীবনবৃত্তির সর্বশেষতম সংস্করণ দেখতে পাবেন।"
|
||||
},
|
||||
"loadDemoData": {
|
||||
"text": "একটি টাটকা ফাঁকা পাতা দিয়ে কী করবেন তা অস্পষ্ট? জীবনবৃত্তান্ত কেমন হওয়া উচিত তা দেখতে কিছু ডেমো ডেটা লোড করুন এবং আপনি সেখান থেকে সম্পাদনা শুরু করতে পারেন।",
|
||||
"button": "ডেমো ডেটা লোড করুন"
|
||||
},
|
||||
"resetEverything": {
|
||||
"text": "আপনি অনেক বেশি ভুল করেছেন বলে মনে হচ্ছে? কোনও উদ্বেগ নেই, কেবলমাত্র একটি ক্লিক দিয়ে সবকিছু সাফ করুন, তবে ব্যাকআপ না থাকলে সতর্ক হন।",
|
||||
"button": "সবকিছু রিসেট করুন"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"theme": "বিষয়",
|
||||
"language": "ভাষা",
|
||||
"translate": "আপনি যদি আপনার ভাষায় অনুবাদ সরবরাহ করতে চান, <1>দয়া করে এই লিঙ্কটি দেখুন</1>।",
|
||||
"dangerZone": {
|
||||
"heading": "বিপদজনক এলাকা",
|
||||
"text": "আপনি যদি নিজের অ্যাকাউন্টটি এবং আপনার সমস্ত জীবনবৃত্তান্ত মুছতে চান তবে এটি কেবল একটি বোতাম টিপলেই হবে। দয়া করে সতর্ক থাকুন কারণ এটি একটি অপরিবর্তনীয় প্রক্রিয়া।",
|
||||
"button": "অ্যাকাউন্ট মুছুন"
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"donate": {
|
||||
"heading": "রিআক্টিভ রেসুমেকে দান করুন",
|
||||
"text": "আমি যা করতে পারি তা করার চেষ্টা করি, তবে আপনি যদি অ্যাপটিকে সহায়ক বলে মনে করেন বা প্রথম কাজের জন্য এই প্রকল্পের উপর নির্ভরশীল অন্যদের চেয়ে আপনি যদি ভাল অবস্থানে রয়েছেন, <1> দয়া করে সহায়তার জন্য ৫ ডলার অনুদান হিসাবে বিবেচনা করুন, প্রকল্পটি জীবিত রাখার জন্য</1> :)",
|
||||
"button": "আমাকে একটি কফি কিনে দিন!"
|
||||
},
|
||||
"bugFeature": {
|
||||
"heading": "সমস্যা? বৈশিষ্ট্য অনুরোধ?",
|
||||
"text": "আপনাকে জীবনবৃত্তান্ত তৈরি করা থেকে কিছু থামাছে? একটি সমস্যা খুঁজে পেয়েছে যা পিছু ছাড়বে না? নীচের ক্রিয়াগুলি ব্যবহার করে গিটহাব ইস্যু বিভাগে এটি সম্পর্কে কথা বলুন।",
|
||||
"button": "একটি সমস্যা উত্থাপন করুন"
|
||||
},
|
||||
"appreciate": {
|
||||
"heading": "রিআক্টিভ রেসুমে পছন্দ?",
|
||||
"text": "এই অ্যাপ্লিকেশনটি কীভাবে মানুষকে সহায়তা করেছিল সেগুলির গল্প শুনে আমি কখনই ক্লান্ত হয়ে উঠি না এবং যদি এটি আপনাকে সহায়তা করে বা আপনি যদি রিআক্টিভ রেসুমেকে একটি দুর্দান্ত সরঞ্জাম হিসাবে পেয়ে থাকেন তবে আমাকে জানান। আপনি আমার ওয়েবসাইটে আমার সাথে যোগাযোগ করতে পারেন।"
|
||||
},
|
||||
"sourceCode": {
|
||||
"heading": "উত্স কোড",
|
||||
"text": "প্রকল্পটি এর উত্স থেকে চালাতে চান? আপনি কি এই প্রকল্পের মুক্ত-উত্স বিকাশে অবদান রাখতে আগ্রহী? নীচের বোতামে টিপুন।",
|
||||
"button": "গিটহাব রেপো"
|
||||
},
|
||||
"footer": "<1>অমৃত পিল্লাই</1> দ্বারা প্রেমের সাথে তৈরি"
|
||||
},
|
||||
"tooltips": {
|
||||
"uploadPhotograph": "ছবি আপলোড করুন",
|
||||
"backToDashboard": "ড্যাশবোর্ডে ফিরে যান"
|
||||
},
|
||||
"emptyList": "এই তালিকাটি খালি।"
|
||||
},
|
||||
"modals": {
|
||||
"auth": {
|
||||
"whoAreYou": "আপনি কে?",
|
||||
"welcome": "স্বাগতম, {{name}}!",
|
||||
"loggedOutText": "আপনি কে তা রিআক্টিভ রেসুমেকে জানতে হবে যাতে এটি আপনাকে নিরাপদে অ্যাপে প্রমাণীকরণ করতে পারে এবং আপনাকে কেবল আপনার তথ্য প্রদর্শন করতে পারে। একবার আপনি প্রবেশ করার পরে, আপনি নিজের জীবনবৃত্তান্ত তৈরি করতে, নতুন দক্ষতা যুক্ত করতে এডিট করতে বা এটি বিশ্বের সাথে ভাগ করে নিতে শুরু করতে পারেন!",
|
||||
"loggedInText": "অসাধারণ. এখন আপনি নিজেকে প্রমাণীকরণ করেছেন, আপনি এখানে আসার প্রকৃত কারণ নিয়ে আমরা এগিয়ে যেতে পারি। আপনার জীবনবৃত্তান্ত তৈরি শুরু করতে অ্যাপ্লিকেশন এ যান বোতামে টিপুন!",
|
||||
"buttons": {
|
||||
"google": "গুগল দিয়ে প্রবেশ করুন",
|
||||
"anonymous": "বেনামে প্রবেশ করুন"
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"button": "ফাইল নির্বাচন করুন",
|
||||
"reactiveResume": {
|
||||
"heading": "রিআক্টিভ রেসুমে থেকে আমদানি করুন",
|
||||
"text": "জাজা কাস্টমাইজযোগ্য অফার দেওয়া যেতে পারে তার সর্বাধিক দেওয়ার জন্য রিআক্টিভ রেসুমের নিজস্ব স্কিমা ফর্ম্যাট রয়েছে। আপনি যদি এই অ্যাপ্লিকেশনটির সাথে তৈরি নিজের জীবনবৃত্তান্তের ব্যাকআপ আমদানি করতে চান তবে নীচের বোতামটি ব্যবহার করে কেবল ফাইলটি আপলোড করুন।"
|
||||
},
|
||||
"jsonResume": {
|
||||
"heading": "জেএসএন জীবনবৃত্তান্ত থেকে আমদানি করুন",
|
||||
"text": "জেএসএন জীবনবৃত্তান্ত, জীবনবৃত্তান্ত স্কিমা কাঠামোর জন্য একটি মুক্ত মান। যদি আপনি এমন অনেক উত্সাহী ব্যক্তিদের মধ্যে এক জন যাঁদের এই ফর্ম্যাটটিতে তাদের জীবনবৃত্তান্ত প্রস্তুত রয়েছে, রিআক্টিভ রেসুমে পুনরায় শুরু করার জন্য এটি কেবলমাত্র একটি ক্লিক লাগে।"
|
||||
},
|
||||
"linkedIn": {
|
||||
"heading": "লিঙ্কডইন থেকে আমদানি করুন",
|
||||
"text": "আপনি নীচের বোতাম টিপে এবং উপযুক্ত ফাইলটি নির্বাচন করে, লিঙ্কডইন থেকে রফতানি হওয়া জেএসএন আমদানি করতে পারেন।"
|
||||
}
|
||||
},
|
||||
"export": {
|
||||
"printDialog": {
|
||||
"heading": "ব্রাউজারের মুদ্রণ ডায়ালগটি ব্যবহার করুন",
|
||||
"text": "যারা দ্রুত সমাধান চান তাদের জন্য ব্রাউজার ছাড়া আর কোনও কিছু দরকার নেই। আপনাকে যা করতে হবে তা হ'ল সিটিআরএল/সিএমডি+পি টিপুন এবং আপনার ব্রাউজারে মুদ্রণ ডায়ালগটি খুলুন এবং সঙ্গে সঙ্গে আপনার জীবনবৃত্তান্ত মুদ্রণ করুন।",
|
||||
"button": "জীবনবৃত্তান্ত মুদ্রণ করুন"
|
||||
},
|
||||
"downloadPDF": {
|
||||
"heading": "পিডিএফ ডাউনলোড করুন",
|
||||
"text": "এই বিকল্পগুলি আপনাকে আপনার জীবনবৃত্তান্তের একক পৃষ্ঠা, অসংকুচিত সংস্করণ মুদ্রণ করতে দেয়, যাঁদের প্রচুর সামগ্রী রয়েছে তাদের জন্য উপযুক্ত। বিকল্পভাবে, আপনি কেবল একটি ক্লিকের মাধ্যমে আপনার জীবনবৃত্তির একটি বহু-পৃষ্ঠার সংস্করণ ডাউনলোড করতে পারেন।",
|
||||
"buttons": {
|
||||
"single": "এক পাতা জীবনবৃত্তান্ত",
|
||||
"multi": "বহু পৃষ্ঠা জীবনবৃত্তান্ত"
|
||||
}
|
||||
},
|
||||
"jsonFormat": {
|
||||
"heading": "জেএসওএন আকারে রফতানি করুন",
|
||||
"text": "সুরক্ষিত রাখার জন্য আপনি জেএসওএন আকারে আপনার তথ্য রফতানি করতে পারেন যাতে আপনি যখনই সম্পাদনা বা পুনরায় সূচনা করতে চান তা সহজেই এটিকে রিআক্টিভ রেসুমেতে আমদানি করতে পারেন।",
|
||||
"button": "জেএসওএন রফতানি করুন"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
{
|
||||
"shared": {
|
||||
"appName": "Reactive Resume",
|
||||
"shortDescription": "Svobodný a open source životopis online.",
|
||||
"forms": {
|
||||
"name": "Jméno",
|
||||
"title": "Název",
|
||||
"subtitle": "Podtitul",
|
||||
"required": "požadované",
|
||||
"website": "Webová stránka",
|
||||
"date": "Datum",
|
||||
"present": "Současnost",
|
||||
"position": "Pozice",
|
||||
"startDate": "Počáteční datum",
|
||||
"endDate": "Datum ukončení",
|
||||
"address": "Adresa",
|
||||
"phone": "Telefonní číslo",
|
||||
"email": "E-mailová adresa",
|
||||
"summary": "Shrnutí",
|
||||
"markdown": "Tento textový blok podporuje <1>markdown</1>.",
|
||||
"contact": "Kontakt",
|
||||
"validation": {
|
||||
"min": "Zadejte alespoň {{number}} znaků.",
|
||||
"dateRange": "Koncové datum musí být pozdější než počáteční datum.",
|
||||
"email": "Musí obsahovat platnou emailovou adresu.",
|
||||
"required": "Toto pole je povinné.",
|
||||
"url": "Musí být platná URL adresa."
|
||||
}
|
||||
},
|
||||
"buttons": {
|
||||
"add": "Přidat",
|
||||
"edit": "Upravit",
|
||||
"cancel": "Zrušit",
|
||||
"delete": "Odstranit",
|
||||
"loading": "Načítání...",
|
||||
"confirmation": "Opravdu?",
|
||||
"login": "Přihlásit se",
|
||||
"logout": "Odhlásit se"
|
||||
}
|
||||
},
|
||||
"landing": {
|
||||
"hero": {
|
||||
"goToApp": "Přejít do aplikace"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Hlavní nabídka",
|
||||
"createResume": "Vytvořit životopis",
|
||||
"editResume": "Upravit životopis",
|
||||
"lastUpdated": "Poslední aktualizace {{timestamp}}",
|
||||
"toasts": {
|
||||
"deleted": "{{name}} byl úspěšně smazán"
|
||||
},
|
||||
"buttons": {
|
||||
"duplicate": "Duplikovat",
|
||||
"rename": "Přejmenovat"
|
||||
},
|
||||
"helpText": "Budete vytvářet nový životopis od nuly, ale nejprve mu dejte jméno. To může být název pozice, na kterou chcete zažádat, nebo pokud děláte životopis pro přítele, můžete to nazvat Frantův životopis."
|
||||
},
|
||||
"builder": {
|
||||
"toasts": {
|
||||
"formErrors": "Možná budete muset vyplnit všechna povinná pole před odesláním tohoto formuláře.",
|
||||
"doesNotExist": "Životopis, který jste hledali, již neexistuje... nebo možná nikdy neexistoval?",
|
||||
"loadDemoData": "Nejste si jisti, kde začít? Zkuste načíst demo data a uvidíte, co může Reactive Resume nabídnout.",
|
||||
"printError": "U cloudové funkce dochází k potížím, zkuste to prosím později nebo použijte funkci tisku v prohlížeči."
|
||||
},
|
||||
"sections": {
|
||||
"heading": "Hlavička",
|
||||
"profile": "Profil",
|
||||
"social": "Sociální sítě",
|
||||
"objective": "Cíl",
|
||||
"work": "Pracovní zkušenosti",
|
||||
"education": "Vzdělání",
|
||||
"project": "Projekt",
|
||||
"projects": "Projekty",
|
||||
"award": "Ocenění",
|
||||
"awards": "Ocenění",
|
||||
"certification": "Certifikace",
|
||||
"certifications": "Certifikace",
|
||||
"skill": "Dovednost",
|
||||
"skills": "Dovednosti",
|
||||
"hobby": "Koníček",
|
||||
"hobbies": "Koníčky",
|
||||
"language": "Jazyk",
|
||||
"languages": "Jazyky",
|
||||
"reference": "Reference",
|
||||
"references": "Reference",
|
||||
"templates": "Šablony",
|
||||
"layout": "Rozvržení",
|
||||
"colors": "Barvy",
|
||||
"font-size": "Velikost písma",
|
||||
"fonts": "Písma",
|
||||
"actions": "Akce",
|
||||
"settings": "Nastavení",
|
||||
"about": "O aplikaci"
|
||||
},
|
||||
"profile": {
|
||||
"photograph": "Fotografie",
|
||||
"firstName": "Jméno",
|
||||
"lastName": "Příjmení",
|
||||
"birthDate": "Datum narození",
|
||||
"address": {
|
||||
"line1": "Adresní řádek 1",
|
||||
"line2": "Adresní řádek 2",
|
||||
"city": "Město",
|
||||
"pincode": "PSČ"
|
||||
}
|
||||
},
|
||||
"social": {
|
||||
"network": "Síť",
|
||||
"username": "Uživatelské jméno",
|
||||
"url": "Adresa URL"
|
||||
},
|
||||
"work": {
|
||||
"company": "Společnost"
|
||||
},
|
||||
"education": {
|
||||
"institution": "Instituce",
|
||||
"field": "Studijní obor",
|
||||
"degree": "Druh titulu",
|
||||
"gpa": "GPA"
|
||||
},
|
||||
"awards": {
|
||||
"awarder": "Ocenění"
|
||||
},
|
||||
"certifications": {
|
||||
"issuer": "Vydavatel"
|
||||
},
|
||||
"skills": {
|
||||
"level": "Úroveň"
|
||||
},
|
||||
"languages": {
|
||||
"fluency": "Plynulost"
|
||||
},
|
||||
"layout": {
|
||||
"block": "Blok",
|
||||
"reset": "Resetovat rozložení",
|
||||
"text": "Tato šablona podporuje {{count}} bloků."
|
||||
},
|
||||
"colors": {
|
||||
"primary": "Primární Barva",
|
||||
"text": "Barva textu",
|
||||
"background": "Barva pozadí"
|
||||
},
|
||||
"actions": {
|
||||
"import": {
|
||||
"heading": "Nahrejte svůj životopis",
|
||||
"text": "Můžete importovat své informace z různých zdrojů, jako je JSON Resume nebo LinkedIn, abyste mohli automaticky vyplnit většinu dat pro váš životopis.",
|
||||
"button": "Nahrát"
|
||||
},
|
||||
"export": {
|
||||
"heading": "Exportujte svůj životopis",
|
||||
"text": "Exportujte svůj životopis jako PDF a sdílejte s náborovými pracovníky nebo JSON, který budete moci importovat zpět do této aplikace na jiném počítači.",
|
||||
"button": "Exportovat"
|
||||
},
|
||||
"share": {
|
||||
"heading": "Sdílejte svůj životopis",
|
||||
"text": "Odkaz níže bude veřejně přístupný, pokud se ho rozhodnete sdílet, lidem se zobrazí nejnovější verze vašeho životopisu."
|
||||
},
|
||||
"loadDemoData": {
|
||||
"text": "Nejste si jisti, co dělat s novou prázdnou stránkou? Načtěte ukázková data, abyste viděli, jak by měl životopis vypadat, a odtud můžete začít upravovat.",
|
||||
"button": "Načíst ukázková data"
|
||||
},
|
||||
"resetEverything": {
|
||||
"text": "Máte pocit, že jste udělali příliš mnoho chyb? Žádné starosti, vše vymažte jediným kliknutím, ale buďte opatrní, pokud neexistují žádné zálohy.",
|
||||
"button": "Obnovit vše"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"theme": "Motiv",
|
||||
"language": "Jazyk",
|
||||
"translate": "Pokud chcete přispět poskytnutím překladů do vašeho jazyka, <1> navštivte tento odkaz </1>.",
|
||||
"dangerZone": {
|
||||
"heading": "Nebezpečná zóna",
|
||||
"text": "Pokud chcete smazat svůj účet a vymazat všechny své životopisy, zmáčkněte tlačítko. Buďte opatrní, protože se jedná o nevratný proces.",
|
||||
"button": "Smazat účet"
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"donate": {
|
||||
"heading": "Přispějte na Reactive Resume",
|
||||
"text": "Snažím se dělat, co můžu, ale pokud vám aplikace přišla užitečná, nebo jste v lepší pozici než ostatní, kteří jsou závislí na tomto projektu při svém prvním zaměstnání, <1> zvažte prosím darování pouhých 5 $, které vám pomohou udržet projekt naživu </1> :)",
|
||||
"button": "Kup mi kávu!"
|
||||
},
|
||||
"bugFeature": {
|
||||
"heading": "Chyba? Požadavek na funkci?",
|
||||
"text": "Něco brzdí tvůj postup k vytvoření životopisu? Našli jste otravnou chybu, která prostě nepřestane? Promluvte si o tom v sekci GitHub Issues pomocí tlačítka níže.",
|
||||
"button": "Nahlásit problém"
|
||||
},
|
||||
"appreciate": {
|
||||
"heading": "Líbí se vám Reactive Resume?",
|
||||
"text": "Nikdy mě neomrzí poslouchat příběhy o tom, jak tato aplikace pomohla lidem, a pokud vám pomohla, nebo jste právě našli Reactive Resume jako úžasný nástroj, dejte mi vědět. Můžete mě kontaktovat na mých webových stránkách."
|
||||
},
|
||||
"sourceCode": {
|
||||
"heading": "Zdrojový kód",
|
||||
"text": "Chcete spustit projekt z jeho zdroje? Jste vývojář ochotný přispět k vývoji open-source tohoto projektu? Klikněte na tlačítko níže.",
|
||||
"button": "GitHub repozitář"
|
||||
},
|
||||
"footer": "Vyrobil s láskou <1> Amruth Pillai </1>"
|
||||
},
|
||||
"tooltips": {
|
||||
"uploadPhotograph": "Nahrát fotografii",
|
||||
"backToDashboard": "Přejít zpět na řídicí panel"
|
||||
},
|
||||
"emptyList": "Tento seznam je prázdný."
|
||||
},
|
||||
"modals": {
|
||||
"auth": {
|
||||
"whoAreYou": "Kdo jsi?",
|
||||
"welcome": "Vítej, {{name}}!",
|
||||
"loggedOutText": "Reactive Resume potřebuje vědět, kdo jste, aby vás mohl bezpečně ověřit do aplikace a zobrazit pouze vaše informace. Jakmile se nacházíte, můžete začít budovat svůj životopis, upravovat jej a přidávat nové dovednosti nebo jej sdílet se světem!",
|
||||
"loggedInText": "Skvělé. Nyní, když jste se ověřili, můžeme pokračovat se skutečným důvodem, proč jste tu. Kliknutím na tlačítko Přejít na aplikaci zahájíte vytváření životopisu!",
|
||||
"buttons": {
|
||||
"google": "Přihlaste se pomocí Google",
|
||||
"anonymous": "Přihlásit se anonymně"
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"button": "Vybrat soubor",
|
||||
"reactiveResume": {
|
||||
"heading": "Importovat z Reactive Resume",
|
||||
"text": "Reactive Resume má svůj vlastní formát schémat, aby co nejvíce využil všech nastavitelných možností, které může nabídnout. Pokud chcete importovat zálohu svého pokračování, vytvořenou pomocí této aplikace, stačí nahrát soubor pomocí tlačítka níže."
|
||||
},
|
||||
"jsonResume": {
|
||||
"heading": "Importovat z JSON Resume",
|
||||
"text": "JSON Resume je otevřený standard pro strukturu schématu životopisu. Pokud jste jedním z mnoha nadšenců, kteří mají svůj životopis připravený v tomto formátu, stačí pouze jedno kliknutí a můžete začít s Reactive Resume."
|
||||
},
|
||||
"linkedIn": {
|
||||
"heading": "Importovat z LinkedIn",
|
||||
"text": "JSON exportovaný z LinkedIn můžete importovat kliknutím na tlačítko níže a výběrem příslušného souboru."
|
||||
}
|
||||
},
|
||||
"export": {
|
||||
"printDialog": {
|
||||
"heading": "Použít tiskový dialog prohlížeče",
|
||||
"text": "Pro ty z vás, kteří chtějí rychlé řešení, se nemusíte dívat dále než váš prohlížeč. Vše, co musíte udělat, je stiskněte Ctrl/Cmd + P a otevřete dialogové okno s tiskem ve vašem prohlížeči a můžete jej ihned vytisknout.",
|
||||
"button": "Vytisknout životopis"
|
||||
},
|
||||
"downloadPDF": {
|
||||
"heading": "Stáhnout PDF",
|
||||
"text": "Tyto možnosti vám umožní vytisknout neomezenou verzi vašeho životopisu na jednu stránku, ideální pro ty, kteří mají hodně obsahu. Alternativně si můžete jediným kliknutím stáhnout také vícestránkovou verzi vašeho životopisu.",
|
||||
"buttons": {
|
||||
"single": "Životopis na jednu stránku",
|
||||
"multi": "Životopis na více stránek"
|
||||
}
|
||||
},
|
||||
"jsonFormat": {
|
||||
"heading": "Exportovat do formátu JSON",
|
||||
"text": "Můžete také exportovat svá data do formátu JSON pro bezpečné uchování, abyste je mohli snadno importovat zpět do Reactive Resume, kdykoli budete chtít upravit nebo vygenerovat životopis.",
|
||||
"button": "Exportovat JSON"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
{
|
||||
"shared": {
|
||||
"appName": "Reactive Resume",
|
||||
"shortDescription": "En gratis og open source løsning til at opbygge et resume.",
|
||||
"forms": {
|
||||
"name": "Navn",
|
||||
"title": "Titel",
|
||||
"subtitle": "Undertitel",
|
||||
"required": "påkrævet",
|
||||
"website": "Hjemmeside",
|
||||
"date": "Dato",
|
||||
"present": "Nuværende",
|
||||
"position": "Stilling",
|
||||
"startDate": "Startdato",
|
||||
"endDate": "Slutdato",
|
||||
"address": "Adresse",
|
||||
"phone": "Telefonnummer",
|
||||
"email": "E-mailadresse",
|
||||
"summary": "Opsummering",
|
||||
"markdown": "Dette tekstfelt understøtter <1>markdown</1>.",
|
||||
"contact": "Kontakt",
|
||||
"validation": {
|
||||
"min": "Indtast venligst mindst {{number}} tegn.",
|
||||
"dateRange": "Slutdato skal være senere end startdato.",
|
||||
"email": "Dette skal være en gyldig mailadresse.",
|
||||
"required": "Dette er et obligatorisk felt.",
|
||||
"url": "Skal være en korrekt URL."
|
||||
}
|
||||
},
|
||||
"buttons": {
|
||||
"add": "Tilføj",
|
||||
"edit": "Rediger",
|
||||
"cancel": "Annuller",
|
||||
"delete": "Slet",
|
||||
"loading": "Indlæser...",
|
||||
"confirmation": "Er du sikker?",
|
||||
"login": "Log ind",
|
||||
"logout": "Log af"
|
||||
}
|
||||
},
|
||||
"landing": {
|
||||
"hero": {
|
||||
"goToApp": "Gå til app"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Betjeningspanel",
|
||||
"createResume": "Opret resume",
|
||||
"editResume": "Rediger resume",
|
||||
"lastUpdated": "Sidst opdateret {{timestamp}}",
|
||||
"toasts": {
|
||||
"deleted": "{{name}} blev slettet med succes"
|
||||
},
|
||||
"buttons": {
|
||||
"duplicate": "Duplikér",
|
||||
"rename": "Omdøb"
|
||||
},
|
||||
"helpText": "Du er ved at oprette et nyt resume fra begyndelsen, men lad os først give det et navn. Det kan være navnet på den stilling du vil søge, eller hvis du laver et resume for en ven, så kan du kalde det Anders' resume."
|
||||
},
|
||||
"builder": {
|
||||
"toasts": {
|
||||
"formErrors": "Du skal udfylde alle de obligatoriske felter før du indsender denne formular.",
|
||||
"doesNotExist": "Det resumer du kigger efter findes ikke længere... eller måske fandtes det aldrig?",
|
||||
"loadDemoData": "Er du i tvivl om hvor du skal begynde? Prøv at indlæse demo data for at se hvad Reactive Resume har at tilbyde.",
|
||||
"printError": "Skyfunktionen er løbet ind i nogle problemer, prøv igen senere eller brug browserens udskriftsfunktion."
|
||||
},
|
||||
"sections": {
|
||||
"heading": "Overskrift",
|
||||
"profile": "Profil",
|
||||
"social": "Sociale netværk",
|
||||
"objective": "Mål",
|
||||
"work": "Erfaring",
|
||||
"education": "Uddannelse",
|
||||
"project": "Projekt",
|
||||
"projects": "Projekter",
|
||||
"award": "Præmier",
|
||||
"awards": "Præmier",
|
||||
"certification": "Certificering",
|
||||
"certifications": "Certificeringer",
|
||||
"skill": "Evne",
|
||||
"skills": "Evner",
|
||||
"hobby": "Hobby",
|
||||
"hobbies": "Hobbyer",
|
||||
"language": "Sprog",
|
||||
"languages": "Sprog",
|
||||
"reference": "Reference",
|
||||
"references": "Referencer",
|
||||
"templates": "Skabeloner",
|
||||
"layout": "Layout",
|
||||
"colors": "Farver",
|
||||
"font-size": "Skriftstørrelse",
|
||||
"fonts": "Skrifttyper",
|
||||
"actions": "Handlinger",
|
||||
"settings": "Indstillinger",
|
||||
"about": "Om"
|
||||
},
|
||||
"profile": {
|
||||
"photograph": "Fotografi",
|
||||
"firstName": "Fornavn",
|
||||
"lastName": "Efternavn",
|
||||
"birthDate": "Fødselsdato",
|
||||
"address": {
|
||||
"line1": "Adresse linie 1",
|
||||
"line2": "Adresse linie 2",
|
||||
"city": "By",
|
||||
"pincode": "Postnummer"
|
||||
}
|
||||
},
|
||||
"social": {
|
||||
"network": "Tjeneste",
|
||||
"username": "Brugernavn",
|
||||
"url": "Webadresse"
|
||||
},
|
||||
"work": {
|
||||
"company": "Virksomhed"
|
||||
},
|
||||
"education": {
|
||||
"institution": "Institution",
|
||||
"field": "Studieretning",
|
||||
"degree": "Uddannelsesgrad",
|
||||
"gpa": "Karaktergennemsnit"
|
||||
},
|
||||
"awards": {
|
||||
"awarder": "Udsteder"
|
||||
},
|
||||
"certifications": {
|
||||
"issuer": "Udsteder"
|
||||
},
|
||||
"skills": {
|
||||
"level": "Niveau"
|
||||
},
|
||||
"languages": {
|
||||
"fluency": "Udtryksevne"
|
||||
},
|
||||
"layout": {
|
||||
"block": "Rubrik",
|
||||
"reset": "Nulstil udseende",
|
||||
"text": "Denne skabelon understøtter {{count}} rubrikker."
|
||||
},
|
||||
"colors": {
|
||||
"primary": "Primær farve",
|
||||
"text": "Tekstfarve",
|
||||
"background": "Baggrundsfarve"
|
||||
},
|
||||
"actions": {
|
||||
"import": {
|
||||
"heading": "Importer dit resume",
|
||||
"text": "Du kan importere dine informationer fra forskellige kilder som JSON resume eller din LinkedIn profil for at automatisk udfylde de fleste data i det resume.",
|
||||
"button": "Importér"
|
||||
},
|
||||
"export": {
|
||||
"heading": "Exportér dit resume",
|
||||
"text": "Eksportér dit resume som PDF for at dele det med headhuntere eller som JSON som gør det muligt at importere det tilbage i denne applikation på en anden computer.",
|
||||
"button": "Eksportér"
|
||||
},
|
||||
"share": {
|
||||
"heading": "Del dit resumé",
|
||||
"text": "Linket herunder vil være offentlig tilgængelig, hvis du deler det, og de besøgende vil altid se den seneste version af dit CV."
|
||||
},
|
||||
"loadDemoData": {
|
||||
"text": "Er du i tvivl om, hvad du skal gøre med en blank side? Indlæs nogle demo data, og se hvordan et resume kan se ud og du kan begynde at redigere herfra.",
|
||||
"button": "Indlæs demo data"
|
||||
},
|
||||
"resetEverything": {
|
||||
"text": "Føler du, at du har lavet for mange fejl? Ingen grund til bekymringer, ryd alt med blot et klik, men pas på hvis du ikke har en kopi.",
|
||||
"button": "Nulstil alt"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"theme": "Tema",
|
||||
"language": "Sprog",
|
||||
"translate": "Hvis du ønsker at bidrage ved at oversætte til dit eget sprog, <1>så besøg denne side</1>.",
|
||||
"dangerZone": {
|
||||
"heading": "Farezone",
|
||||
"text": "Hvis du ønsker at nedlægge din konto og slette alle dine resuméer, så er det kun et klik væk. Vær opmærksom på, at denne handling ikke kan fortrydes.",
|
||||
"button": "Slet konto"
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"donate": {
|
||||
"heading": "Donér til Reactive Resume",
|
||||
"text": "Jeg forsøger at gøre hvad jeg kan, men hvis du synes at denne app hjælper dig, eller at du er i en bedre position end andre som er afhængige af denne app for at få deres første job, så <1>overvej venligst at donere så lidt som $5 for at hjælpe med holde projektet i live</1> :-)",
|
||||
"button": "Giv mig en kop kaffe!"
|
||||
},
|
||||
"bugFeature": {
|
||||
"heading": "Fejl? Ønsker til en ny funktion?",
|
||||
"text": "Er der noget som hindrer dig i at komme videre med dit resume? Har du fundet en dum fejl som ikke vil forsvinde? Fortæl om det på GitHub i issues-sektionen via at bruge handlingerne herunder.",
|
||||
"button": "Rejse et issue"
|
||||
},
|
||||
"appreciate": {
|
||||
"heading": "Elsker du Reactive Resume?",
|
||||
"text": "Jeg bliver aldrig træt af at høre historier om, hvordan denne app har hjulpet folk, og hvis det har hjulpet dig eller du blot synes at Reactive Resume er et fantastisk værktøj, så vil jeg gerne vide det. Du kan tage fat i mig via min hjemmeside."
|
||||
},
|
||||
"sourceCode": {
|
||||
"heading": "Kildekode",
|
||||
"text": "Ønsker du at køre projektet fra kildekoden? Er du udvikler som ønsker at bidrage til open source udviklingen af dette projekt? Klik på knappen herunder.",
|
||||
"button": "GitHub Repo"
|
||||
},
|
||||
"footer": "Lavet med kærlighed af <1>Amruth Pillai</1>"
|
||||
},
|
||||
"tooltips": {
|
||||
"uploadPhotograph": "Upload fotografi",
|
||||
"backToDashboard": "Tilbage til panel"
|
||||
},
|
||||
"emptyList": "Denne liste er tom."
|
||||
},
|
||||
"modals": {
|
||||
"auth": {
|
||||
"whoAreYou": "Hvem er du?",
|
||||
"welcome": "Velkommen {{name}}!",
|
||||
"loggedOutText": "Reactive Resume skal vide hvem du er, så vi på sikker vis kan verificere dig i app'en og kun vise dig din information. Når du er inde, kan du begynde at bygge dit resume, redigere det for at tilføje nye evner eller dele det med verden!",
|
||||
"loggedInText": "Sådan. Nu hvor du har verificeret dig selv, kan vi komme videre med den virkelige grund till at du er her. Klik på Gå til App knappen for at påbegynde dit resume!",
|
||||
"buttons": {
|
||||
"google": "Log ind med Google",
|
||||
"anonymous": "Log ind anonymt"
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"button": "Vælg fil",
|
||||
"reactiveResume": {
|
||||
"heading": "Importér fra Reactive Resume",
|
||||
"text": "Reactive Resume har sit eget Schema format for at udnytte alle de muligheder som der tilbydes. Hvis du ønsker at importere en backup af dit resume som er lavet med denne app, så upload filen med knappen herunder."
|
||||
},
|
||||
"jsonResume": {
|
||||
"heading": "Importér fra JSON Resume",
|
||||
"text": "JSON Resume er en åben standard for resume schema struktur. Hvis du er en af de mange entusiaster som har deres resume klar i dette format, kræver det kun et enkelt klik at begynde med Reactive Resume."
|
||||
},
|
||||
"linkedIn": {
|
||||
"heading": "Importér fra LinkedIn",
|
||||
"text": "Du kan importere en JSON-fil som er eksporteret fra LinkedIn, ved at klikke på knappen herunder og vælge den pågældende fil."
|
||||
}
|
||||
},
|
||||
"export": {
|
||||
"printDialog": {
|
||||
"heading": "Brug browserens print dialogboks",
|
||||
"text": "For dem af jer som ønsker en hurtig løsning, så skal du ikke brugere mere end din browser. Alt hvad du skal gøre er, at trykke Ctrl/Cmd + P og åbne en print dialogboks i browseren og straks printe dit resume.",
|
||||
"button": "Print resume"
|
||||
},
|
||||
"downloadPDF": {
|
||||
"heading": "Download PDF",
|
||||
"text": "Det er muligt at printe en én-sidet og ubegrænset version af dit resume, perfekt for dem som har meget indhold. Alternativt kan du downloade en fler-sidet version af dit resume med et enkelt klik.",
|
||||
"buttons": {
|
||||
"single": "Én-sidet resume",
|
||||
"multi": "Fler-sidet resume"
|
||||
}
|
||||
},
|
||||
"jsonFormat": {
|
||||
"heading": "Eksportér til JSON formatet",
|
||||
"text": "Du kan også eksportere dine data i JSON format for at opbevare dem sikker, så du nemt kan genimportere dem i Reactive Resume når du ønsker at ændre eller generere et resume.",
|
||||
"button": "Eksportér JSON"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user