diff --git a/gatsby-browser.js b/gatsby-browser.js
index 05ab514c..1d0cb5ac 100644
--- a/gatsby-browser.js
+++ b/gatsby-browser.js
@@ -13,6 +13,7 @@ import { StorageProvider } from './src/contexts/StorageContext';
import { ThemeProvider } from './src/contexts/ThemeContext';
import { UserProvider } from './src/contexts/UserContext';
import './src/styles/global.css';
+import './src/styles/forms.css';
import './src/styles/shadows.css';
import './src/styles/tailwind.css';
import './src/styles/toastify.css';
diff --git a/package-lock.json b/package-lock.json
index 393194fd..a8a0e18b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18567,6 +18567,20 @@
"is-typedarray": "^1.0.0"
}
},
+ "typeit": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/typeit/-/typeit-7.0.4.tgz",
+ "integrity": "sha512-ETiVr3s4XOXUE9W+tVhF3gxGTf5z4tc35YjvWEQhqKsJhXWvpQlt/D/ZvIHkZzHegU3stxagjeG2pfm1/AWsYQ=="
+ },
+ "typeit-react": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/typeit-react/-/typeit-react-0.1.3.tgz",
+ "integrity": "sha512-9HiAghnq8NjLj67dakc9C4eCHyunsCgl7ZSo04w/7YWAjs12bHb/+J3geFfTgBVpbWCL6GPDZ7eWBXNZ3ZweGw==",
+ "requires": {
+ "@types/react": "^16.9.19",
+ "typeit": "^7.0.3"
+ }
+ },
"unbzip2-stream": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
diff --git a/package.json b/package.json
index 8336757d..871180ff 100644
--- a/package.json
+++ b/package.json
@@ -52,6 +52,7 @@
"react-scroll": "^1.8.0",
"react-toastify": "^6.0.8",
"short-unique-id": "^3.0.3",
+ "typeit-react": "^0.1.3",
"uuid": "^8.2.0",
"yup": "^0.29.1"
},
diff --git a/src/components/builder/right/sections/About.js b/src/components/builder/right/sections/About.js
index ba78267f..5256f838 100644
--- a/src/components/builder/right/sections/About.js
+++ b/src/components/builder/right/sections/About.js
@@ -1,5 +1,6 @@
import React, { memo } from 'react';
-import { FaCoffee } from 'react-icons/fa';
+import { FaCoffee, FaBug } from 'react-icons/fa';
+import { MdCode } from 'react-icons/md';
import Button from '../../../shared/Button';
import Heading from '../../../shared/Heading';
import styles from './About.module.css';
@@ -37,6 +38,75 @@ const About = () => {
+
+
+
Bug? Feature Request?
+
+
+ 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, or send me and email using the actions below.
+
+
+
+
+
+
+
Source Code
+
+
+ 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.
+
+
+
+
+
+
+
License Information
+
+
+ The project is governed under the MIT License, which you can read more
+ about below. Basically, you are allowed to use the project anywhere
+ provided you give credits to the original author.
+
+
+
+
+
+
);
};
diff --git a/src/components/builder/right/sections/Settings.js b/src/components/builder/right/sections/Settings.js
index d51ac347..27901769 100644
--- a/src/components/builder/right/sections/Settings.js
+++ b/src/components/builder/right/sections/Settings.js
@@ -1,4 +1,5 @@
import React, { memo, useContext, useState } from 'react';
+import { FaAngleDown } from 'react-icons/fa';
import UserContext from '../../../../contexts/UserContext';
import Button from '../../../shared/Button';
import Heading from '../../../shared/Heading';
@@ -6,6 +7,7 @@ import styles from './Settings.module.css';
import Input from '../../../shared/Input';
import ThemeContext from '../../../../contexts/ThemeContext';
import themeConfig from '../../../../data/themeConfig';
+import languageConfig from '../../../../data/languageConfig';
const Settings = () => {
const [deleteText, setDeleteText] = useState('Delete Account');
@@ -16,6 +18,10 @@ const Settings = () => {
setTheme(e.target.value);
};
+ const handleChangeLanguage = (e) => {
+ console.log(e.target.value);
+ };
+
const handleDeleteAccount = () => {
if (deleteText === 'Delete Account') {
setDeleteText('Are you sure?');
@@ -40,6 +46,37 @@ const Settings = () => {
onChange={handleChangeTheme}
/>
+
+ Language
+
+
+ {languageConfig.map((x) => (
+
+ {x.name}
+
+ ))}
+
+
+
+
+
+
+
+ If you would like to contribute by providing translations to Reactive
+ Resume in your language,{' '}
+
+ please visit this link
+
+ .
+
+
Danger Zone
diff --git a/src/components/landing/Hero.js b/src/components/landing/Hero.js
index 374ff973..b4115b34 100644
--- a/src/components/landing/Hero.js
+++ b/src/components/landing/Hero.js
@@ -1,5 +1,7 @@
import { navigate } from 'gatsby';
+import TypeIt from 'typeit-react';
import React, { memo, useContext } from 'react';
+import { FaGithub } from 'react-icons/fa';
import ModalContext from '../../contexts/ModalContext';
import UserContext from '../../contexts/UserContext';
import Button from '../shared/Button';
@@ -18,7 +20,21 @@ const Hero = () => {
-
Reactive Resume
+
Reactive Resume
+
+ {
+ return instance
+ .type('Creative Resume')
+ .pause(500)
+ .move(-11)
+ .delete(4)
+ .pause(250)
+ .type('Reac')
+ .move('END');
+ }}
+ />
+
A free and open-source resume builder.
@@ -33,6 +49,16 @@ const Hero = () => {
Login
)}
+
+
+
+ Source Code
+
+
diff --git a/src/components/shared/Avatar.js b/src/components/shared/Avatar.js
index 5b03123c..713f1aa8 100644
--- a/src/components/shared/Avatar.js
+++ b/src/components/shared/Avatar.js
@@ -18,7 +18,9 @@ const Avatar = ({ className }) => {
handleClose();
};
- const photoURL = useMemo(() => toUrl(user.email, 'size=128'), [user.email]);
+ const photoURL = useMemo(() => toUrl(user.email || '', 'size=128&d=retro'), [
+ user.email,
+ ]);
return (
@@ -31,7 +33,7 @@ const Avatar = ({ className }) => {
>
diff --git a/src/components/shared/Input.js b/src/components/shared/Input.js
index 9ac96d8f..7ca84135 100644
--- a/src/components/shared/Input.js
+++ b/src/components/shared/Input.js
@@ -46,7 +46,7 @@ const Input = ({
};
return (
-
+
{label}{' '}
@@ -115,7 +115,9 @@ const Input = ({
onChange={onChange}
>
{options.map((x) => (
- {x}
+
+ {x}
+
))}
diff --git a/src/components/shared/Input.module.css b/src/components/shared/Input.module.css
index 738bdd92..ad4f98d8 100644
--- a/src/components/shared/Input.module.css
+++ b/src/components/shared/Input.module.css
@@ -1,31 +1,3 @@
-.container label input,
-.container label textarea,
-.container label select {
- @apply w-full py-3 px-4 rounded text-primary-900 bg-primary-200 border border-transparent appearance-none;
-}
-
-.container label input::placeholder,
-.container label textarea::placeholder,
-.container label select::placeholder {
- @apply opacity-50;
-}
-
-.container label input:hover,
-.container label textarea:hover,
-.container label select:hover {
- @apply outline-none border-primary-400;
-}
-
-.container label input:focus,
-.container label textarea:focus,
-.container label select:focus {
- @apply outline-none border-primary-600;
-}
-
-.container label > p {
- @apply mt-1 text-red-600 text-xs;
-}
-
.circle {
left: 14px;
@apply absolute bg-primary-900 rounded-full w-6 h-6;
diff --git a/src/contexts/UserContext.js b/src/contexts/UserContext.js
index c203f9e1..5266391e 100644
--- a/src/contexts/UserContext.js
+++ b/src/contexts/UserContext.js
@@ -17,6 +17,7 @@ const defaultState = {
user: defaultUser,
logout: async () => {},
loginWithGoogle: async () => {},
+ loginAnonymously: async () => {},
deleteAccount: async () => {},
};
@@ -57,6 +58,14 @@ const UserProvider = ({ children }) => {
}
};
+ const loginAnonymously = async () => {
+ try {
+ return await firebase.auth().signInAnonymously();
+ } catch (error) {
+ toast.error(error.message);
+ }
+ };
+
const logout = () => {
firebase.auth().signOut();
localStorage.removeItem('user');
@@ -87,6 +96,7 @@ const UserProvider = ({ children }) => {
logout,
loading,
loginWithGoogle,
+ loginAnonymously,
deleteAccount,
}}
>
diff --git a/src/data/demoState.json b/src/data/demoState.json
index b52c460a..697ec80e 100644
--- a/src/data/demoState.json
+++ b/src/data/demoState.json
@@ -17,11 +17,11 @@
"title": "Venturesity Banyan Hack"
},
{
- "title": "Smart India Hackathon",
"awarder": "Govt. of India",
"date": "2017-04-01",
+ "id": "89c0171a-eae9-403e-9f4c-a757fb535c2b",
"summary": "",
- "id": "89c0171a-eae9-403e-9f4c-a757fb535c2b"
+ "title": "Smart India Hackathon"
}
],
"visible": true
@@ -44,11 +44,11 @@
"title": "VCP6-DCV"
},
{
- "title": "DCUCI 642-999",
- "issuer": "Cisco Systems",
"date": "2014-04-01",
+ "id": "11107df6-5f3c-49ae-bcd4-62b8baa181a1",
+ "issuer": "Cisco Systems",
"summary": "",
- "id": "11107df6-5f3c-49ae-bcd4-62b8baa181a1"
+ "title": "DCUCI 642-999"
}
],
"visible": true
@@ -82,8 +82,8 @@
"hobbies": {
"heading": "Hobbies",
"items": [
- { "name": "Poetry", "id": "788dcf5a-78ca-4866-8397-c7a29073d9a1" },
- { "name": "Travelling", "id": "e3523371-f50c-4348-8c5e-35fe84c0006d" },
+ { "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",
@@ -130,6 +130,31 @@
},
"font": "Open Sans",
"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"],
@@ -138,26 +163,9 @@
"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"]
]
},
- "template": "pikachu"
+ "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.",
@@ -222,12 +230,12 @@
"summary": ""
},
{
- "name": "Lorraine Beasley",
- "position": "Head of HR, Carson Logistics",
- "phone": "+1 661-808-4188",
"email": "l.beasley@carsonlogistics.com",
- "summary": "",
- "id": "94e3447b-0a78-4fb7-b14d-591982d35320"
+ "id": "94e3447b-0a78-4fb7-b14d-591982d35320",
+ "name": "Lorraine Beasley",
+ "phone": "+1 661-808-4188",
+ "position": "Head of HR, Carson Logistics",
+ "summary": ""
}
],
"visible": true
@@ -256,14 +264,14 @@
"name": "Call Center Management"
},
{
- "name": "Teambuilding & Training",
+ "id": "08d6c739-1465-41f7-8825-b8d94faa38d6",
"level": "Novice",
- "id": "08d6c739-1465-41f7-8825-b8d94faa38d6"
+ "name": "Teambuilding & Training"
},
{
- "name": "Continuous Improvement",
+ "id": "261b8fc3-aeec-4347-88a8-bcacb1a17aa3",
"level": "Fundamental Awareness",
- "id": "261b8fc3-aeec-4347-88a8-bcacb1a17aa3"
+ "name": "Continuous Improvement"
}
],
"visible": true
@@ -272,10 +280,10 @@
"heading": "Social",
"items": [
{
- "url": "https://pillai.xyz/instagram",
+ "id": "a832b37d-f11d-4a80-8b4d-24796e571b17",
"network": "Instagram",
- "username": "AmruthPillai",
- "id": "a832b37d-f11d-4a80-8b4d-24796e571b17"
+ "url": "https://pillai.xyz/instagram",
+ "username": "AmruthPillai"
},
{
"id": "a72107fa-a4a5-407d-9e85-39bdb9c0b11a",
@@ -315,12 +323,12 @@
},
{
"company": "Pizza Hut, Newark, NJ",
- "position": "Waiter",
- "website": "https://pizzahut.com",
- "startDate": "2005-08-01",
"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.",
- "id": "dd935088-6fe7-4a4b-8ff5-7417c32d2add"
+ "website": "https://pizzahut.com"
}
],
"visible": true
diff --git a/src/data/languageConfig.js b/src/data/languageConfig.js
new file mode 100644
index 00000000..466a1a7f
--- /dev/null
+++ b/src/data/languageConfig.js
@@ -0,0 +1,8 @@
+const languageConfig = [
+ {
+ key: 'en',
+ name: 'English',
+ },
+];
+
+export default languageConfig;
diff --git a/src/modals/AuthModal.js b/src/modals/AuthModal.js
index 83f6f9f3..3884a857 100644
--- a/src/modals/AuthModal.js
+++ b/src/modals/AuthModal.js
@@ -7,10 +7,13 @@ import BaseModal from './BaseModal';
const AuthModal = () => {
const [open, setOpen] = useState(false);
- const [isLoading, setLoading] = useState(false);
+ const [isLoadingGoogle, setLoadingGoogle] = useState(false);
+ const [isLoadingAnonymous, setLoadingAnonymous] = useState(false);
const { emitter, events } = useContext(ModalContext);
- const { user, loginWithGoogle, logout } = useContext(UserContext);
+ const { user, loginWithGoogle, loginAnonymously, logout } = useContext(
+ UserContext,
+ );
useEffect(() => {
const unbind = emitter.on(events.AUTH_MODAL, () => setOpen(true));
@@ -19,9 +22,15 @@ const AuthModal = () => {
}, [emitter, events]);
const handleSignInWithGoogle = async () => {
- setLoading(true);
+ setLoadingGoogle(true);
await loginWithGoogle();
- setLoading(false);
+ setLoadingGoogle(false);
+ };
+
+ const handleSignInAnonymously = async () => {
+ setLoadingAnonymous(true);
+ await loginAnonymously();
+ setLoadingAnonymous(false);
};
const handleGotoApp = () => {
@@ -30,7 +39,7 @@ const AuthModal = () => {
};
const getTitle = () =>
- user ? `Welcome, ${user.displayName}` : 'Who are you?';
+ user ? `Welcome, ${user.displayName || 'Agent 47'}` : 'Who are you?';
const getMessage = () =>
user
@@ -49,9 +58,18 @@ const AuthModal = () => {
);
const loggedOutAction = (
-
- Sign in with Google
-
+
+
+ Sign in with Google
+
+
+ Sign in Anonymously
+
+
);
return (
diff --git a/src/pages/app/builder.js b/src/pages/app/builder.js
index be29ee8a..f2262099 100644
--- a/src/pages/app/builder.js
+++ b/src/pages/app/builder.js
@@ -7,12 +7,17 @@ import RightSidebar from '../../components/builder/right/RightSidebar';
import LoadingScreen from '../../components/router/LoadingScreen';
import DatabaseContext from '../../contexts/DatabaseContext';
import { useDispatch } from '../../contexts/ResumeContext';
+import Button from '../../components/shared/Button';
const Builder = ({ id }) => {
const dispatch = useDispatch();
const [loading, setLoading] = useState(true);
const { getResume } = useContext(DatabaseContext);
+ const handleLoadDemoData = () => {
+ dispatch({ type: 'load_demo_data' });
+ };
+
useEffect(() => {
(async () => {
const resume = await getResume(id);
@@ -25,6 +30,21 @@ const Builder = ({ id }) => {
return null;
}
+ if (resume.createdAt === resume.updatedAt) {
+ toast.dark(() => (
+
+
+ Not sure where to begin? Try Loading Demo Data to
+ see what Reactive Resume has to offer.
+
+
+
+ Load Demo Data
+
+
+ ));
+ }
+
dispatch({ type: 'set_data', payload: resume });
return setLoading(false);
})();
diff --git a/src/pages/faq.js b/src/pages/faq.js
new file mode 100644
index 00000000..410833ce
--- /dev/null
+++ b/src/pages/faq.js
@@ -0,0 +1,89 @@
+import React from 'react';
+import { MdKeyboardArrowLeft } from 'react-icons/md';
+import { Link } from '@reach/router';
+import Wrapper from '../components/shared/Wrapper';
+
+const FrequentlyAskedQuestions = () => {
+ return (
+
+
+
+
+
+
+
+ Frequently Asked Questions
+
+
+
+
+
+ This is aimed to be the world's simplest privacy policy. This
+ document should explain to you why the app collects some
+ information, what happens when your account is deleted and some
+ other frequently asked questions answered regarding your privacy.
+
+
+
+ If you have any more questions, please raise an issue regarding the
+ same on GitHub and I'll answer as honest and quickly as
+ possible :)
+
+
+
+
+
+
+
+ What identifiable information is stored about me?
+
+
+ Your name and your email address is stored along with your user
+ information, if you signed in with Google, and of course, all the
+ information you input in your resume is also stored in the database.
+
+
+
+
+
+
+
+ Why are you using a database, why not keep everything local like in
+ the first version of Reactive Resume?
+
+
+ Not having a centralized database cause a lot more problems than I
+ could solve, mainly having a large chunk of the users of the app
+ having an outdated schema as the app evolved. This was a problem I
+ could not solve without having at least some control.
+
+
+
+ Also, a lot of users were having trouble printing their resumes on
+ their browsers, so with the help of Cloud Functions from Firebase,
+ you can now print your resumes remotely. None of the resumes are
+ stored, and they are sent to you immediately after generation, which
+ can be verified by looking through the source code.
+
+
+
+
+
+
+
+ How is this all free? There must be a catch!
+
+
+ Absolutely no catch to this freebie. This project
+ is just my way of giving back to the community that I've
+ learned so much from. If you'd like to show your appreciation
+ however, you can follow me on my social media and let me know, or
+ donate to the app to help pay the cloud bills.
+
+
+
+
+ );
+};
+
+export default FrequentlyAskedQuestions;
diff --git a/src/pages/index.js b/src/pages/index.js
index 0445aff8..4a959a1c 100644
--- a/src/pages/index.js
+++ b/src/pages/index.js
@@ -1,5 +1,6 @@
import React, { memo } from 'react';
import { Helmet } from 'react-helmet';
+import { Link } from '@reach/router';
import Hero from '../components/landing/Hero';
import Wrapper from '../components/shared/Wrapper';
@@ -14,6 +15,10 @@ const Home = () => {
+
+
Screenshots
+
+
Keep up with the latest trends in resume design without having to
@@ -51,12 +56,27 @@ const Home = () => {
You must be thinking, if you're not paying for the product,
then you are the product. Or, at least your data is?{' '}
Well, this is the exception . Your data is your own,
- as stated in the ridiculously simple Privacy Policy ,
- I don't do anything with the data, it just exists on a database
- for the convinient features provided by Reactive Resume.
+ as stated in the ridiculously simple{' '}
+ Privacy Policy, I don't do anything with
+ the data, it just exists on a database for the convinient features
+ provided by Reactive Resume.
+
+
+ Links of Interest
+
+
+ Frequently Asked Questions
+ Checkout Source Code
+ Upvote on Product Hunt
+ Raise an Issue on GitHub
+ Donate to Reactive Resume
+ Building Great Looking Resumes
+
+
+
Licensed under MIT
diff --git a/src/styles/forms.css b/src/styles/forms.css
new file mode 100644
index 00000000..78cd5f81
--- /dev/null
+++ b/src/styles/forms.css
@@ -0,0 +1,35 @@
+label {
+ @apply flex flex-col;
+}
+
+label > span:first-child {
+ @apply mb-1 text-primary-600 font-semibold tracking-wide text-xs uppercase;
+}
+
+label > p {
+ @apply mt-1 text-red-600 text-xs;
+}
+
+label input,
+label textarea,
+label select {
+ @apply w-full py-3 px-4 rounded text-primary-900 bg-primary-200 border border-transparent appearance-none;
+}
+
+label input::placeholder,
+label textarea::placeholder,
+label select::placeholder {
+ @apply opacity-50;
+}
+
+label input:hover,
+label textarea:hover,
+label select:hover {
+ @apply outline-none border-primary-400;
+}
+
+label input:focus,
+label textarea:focus,
+label select:focus {
+ @apply outline-none border-primary-600;
+}
diff --git a/src/styles/global.css b/src/styles/global.css
index 03b6bc43..dd77067b 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -26,14 +26,6 @@ section {
@apply grid grid-cols-1 gap-8;
}
-label {
- @apply flex flex-col;
-}
-
-label > span:first-child {
- @apply mb-1 text-primary-600 font-semibold tracking-wide text-xs uppercase;
-}
-
.MuiTooltip-tooltip {
font-size: 10px;
}
diff --git a/src/templates/blocks/Contact/ContactC.js b/src/templates/blocks/Contact/ContactC.js
index 249c651a..3752e94e 100644
--- a/src/templates/blocks/Contact/ContactC.js
+++ b/src/templates/blocks/Contact/ContactC.js
@@ -22,16 +22,18 @@ const ContactC = () => {
return (
-
-
Address
-
-
{data.profile.address.line1}
-
{data.profile.address.line2}
-
- {data.profile.address.city} {data.profile.address.pincode}
-
+ {data.profile.address.line1 && (
+
+
Address
+
+ {data.profile.address.line1}
+ {data.profile.address.line2}
+
+ {data.profile.address.city} {data.profile.address.pincode}
+
+
-
+ )}
{
-
-
Address
-
-
{data.profile.address.line1}
-
{data.profile.address.line2}
-
- {data.profile.address.city} {data.profile.address.pincode}
-
+ {data.profile.address.line1 && (
+
+
Address
+
+ {data.profile.address.line1}
+ {data.profile.address.line2}
+
+ {data.profile.address.city} {data.profile.address.pincode}
+
+
-
+ )}