clear everything

This commit is contained in:
Amruth Pillai 2022-02-28 20:23:10 +01:00
parent ff892e3ea5
commit 2175256310
No known key found for this signature in database
GPG Key ID: E3C57DF9B80855AD
301 changed files with 0 additions and 84972 deletions

View File

@ -1,4 +0,0 @@
.cache/
node_modules/
functions/
public/

View File

@ -1,8 +0,0 @@
FIREBASE_APIKEY=""
FIREBASE_APPID=""
FIREBASE_AUTHDOMAIN=""
FIREBASE_DATABASEURL=""
FIREBASE_MEASUREMENTID=""
FIREBASE_MESSAGINGSENDERID=""
FIREBASE_PROJECTID=""
FIREBASE_STORAGEBUCKET=""

View File

@ -1,4 +0,0 @@
.cache
package.json
package-lock.json
public

View File

@ -1,36 +0,0 @@
{
"globals": {
"atob": true,
"Blob": true,
"fetch": true,
"window": true,
"document": true,
"FileReader": true,
"localStorage": true
},
"extends": [
"airbnb",
"plugin:jest/recommended",
"plugin:jest/style",
"prettier"
],
"plugins": ["jest", "prettier", "sort-imports-es6-autofix"],
"rules": {
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
"jsx-a11y/label-has-associated-control": 0,
"react/jsx-one-expression-per-line": 0,
"react/jsx-props-no-spreading": 0,
"prettier/prettier": ["error"],
"react/no-array-index-key": 0,
"jsx-a11y/anchor-is-valid": 0,
"react/button-has-type": 0,
"no-unused-expressions": 0,
"no-restricted-syntax": 0,
"no-param-reassign": 0,
"consistent-return": 0,
"no-nested-ternary": 0,
"react/prop-types": 0,
"no-plusplus": 0
}
}

View File

@ -1,14 +0,0 @@
{
"projects": {
"default": "rx-resume"
},
"targets": {
"rx-resume": {
"hosting": {
"rxresume": [
"public"
]
}
}
}
}

3
.github/FUNDING.yml vendored
View File

@ -1,3 +0,0 @@
# These are supported funding model platforms
custom: ['buymeacoffee.com/AmruthPillai']

View File

@ -1,34 +0,0 @@
name: CodeQL Analysis
on:
push:
branches: [ develop ]
pull_request:
branches: [ develop ]
schedule:
- cron: '37 16 * * 0'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@ -1,27 +0,0 @@
name: Run Unit Tests
on:
push:
branches: [ develop ]
pull_request:
branches: [ develop ]
jobs:
build:
name: Build
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test

76
.gitignore vendored
View File

@ -1,76 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Coverage directory used by Jest
test-coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# dotenv environment variable files
.env
# gatsby files
.cache/
public
# Firebase Files
.firebase
# Mac files
.DS_Store
# Yarn
yarn-error.log
.pnp/
.pnp.js
# Yarn Integrity file
.yarn-integrity

1
.husky/.gitignore vendored
View File

@ -1 +0,0 @@
_

View File

@ -1,4 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged

5
.idea/.gitignore vendored
View File

@ -1,5 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,58 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<HTMLCodeStyleSettings>
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
<option name="HTML_ENFORCE_QUOTES" value="true" />
</HTMLCodeStyleSettings>
<JSCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</JSCodeStyleSettings>
<TypeScriptCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</TypeScriptCodeStyleSettings>
<VueCodeStyleSettings>
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
</VueCodeStyleSettings>
<codeStyleSettings language="HTML">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="TypeScript">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Vue">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

View File

@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@ -1,7 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="Stylelint" enabled="true" level="ERROR" enabled_by_default="true" />
</profile>
</component>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Reactive-Resume.iml" filepath="$PROJECT_DIR$/.idea/Reactive-Resume.iml" />
</modules>
</component>
</project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

1
.nvmrc
View File

@ -1 +0,0 @@
16

View File

@ -1,4 +0,0 @@
.cache
package.json
package-lock.json
public

View File

@ -1,5 +0,0 @@
{
"semi": true,
"singleQuote": true,
"trailingComma": "all"
}

11
.vscode/settings.json vendored
View File

@ -1,11 +0,0 @@
{
"files.associations": {
"*.js": "javascriptreact"
},
"editor.codeActionsOnSave": {
"source.fixAll": true
},
"i18n-ally.localesPaths": ["src/i18n/locales"],
"i18n-ally.keystyle": "nested",
"css.validate": false
}

View File

@ -1,65 +0,0 @@
# Deployment
This is a guide on how to build the source from scratch, along with setting up Firebase and related cloud functions to be able to export PDFs just like the original deployment of [rxresu.me](http://rxresu.me/).
### Requirements
- A Firebase project
- Works on both Linux, macOS and Windows
- Requires Node.js & NPM installed on the machine
### Setting up Firebase
1. Create a new Firebase project by visiting [Firebase Console](https://console.firebase.google.com/) and clicking on `Add Project`
![Screenshot 2021-03-13 at 10 19 10 AM](https://user-images.githubusercontent.com/1134738/111019495-97a73800-83e5-11eb-9eb1-6da100d839ba.png)
2. Disable Google Analytics, or keep it enabled as per your requirements. Most people wouldn't need it.
![Screenshot 2021-03-13 at 10 19 23 AM](https://user-images.githubusercontent.com/1134738/111019521-bc9bab00-83e5-11eb-9365-e521577e7a90.png)
3. Wait until Project is created, then click on Continue
![Screenshot 2021-03-13 at 10 21 30 AM](https://user-images.githubusercontent.com/1134738/111019543-e5bc3b80-83e5-11eb-923f-fc4fb2c6d84f.png)
4. Navigate to Realtime Database, and click on `Create Database`
![Screenshot 2021-03-13 at 10 28 57 AM](https://user-images.githubusercontent.com/1134738/111019691-f02b0500-83e6-11eb-9112-c3123273d035.png)
5. Select any location that's nearby to you, and most importantly, create the database in `Test Mode` and click on Enable
![Screenshot 2021-03-13 at 10 30 01 AM](https://user-images.githubusercontent.com/1134738/111019724-16e93b80-83e7-11eb-9713-06a7adf0c5d4.png)
6. Go back to Project Overview and click on `Web` and skip through every other step by clicking `Next`.
![Screenshot 2021-03-13 at 10 27 34 AM](https://user-images.githubusercontent.com/1134738/111019839-b4446f80-83e7-11eb-9fe2-183b06f6f829.png)
7. Copy configuration variables of your project, or keep this page open as you will need it later
![ezgif com-gif-maker](https://user-images.githubusercontent.com/1134738/111019829-9d9e1880-83e7-11eb-8ccc-573db1039b10.gif)
### Cloning the Repository
1. Run this command on your machine's terminal or Command Prompt
```
git clone git@github.com:AmruthPillai/Reactive-Resume.git
```
<img width="550" alt="Screenshot 2021-03-13 at 10 38 16 AM" src="https://user-images.githubusercontent.com/1134738/111019919-3df43d00-83e8-11eb-8d6b-d9fe0cc74a3a.png">
2. Copy the file `.env.example` to `.env` and start editing the file
```
cp .env.example .env
```
<img width="317" alt="Screenshot 2021-03-13 at 10 50 21 AM" src="https://user-images.githubusercontent.com/1134738/111020166-ed7ddf00-83e9-11eb-9cbb-a8732243bbd5.png">
3. Copy configuration variables from last step to the .env file, it's fine to have `FIREBASE_MEASUREMENTID` empty if you had Google Analytics disabled.
<img width="696" alt="Screenshot 2021-03-13 at 10 51 53 AM" src="https://user-images.githubusercontent.com/1134738/111020217-3c2b7900-83ea-11eb-801d-d8719cf23608.png">
4. Run `npm install` on the terminal/command prompt
5. After that's done, run `npm run build` and allow some time for the process to build

View File

@ -1,38 +0,0 @@
FROM node:alpine as builder
WORKDIR /app
RUN apk add --update --no-cache \
g++ \
yasm \
bash \
make \
automake \
autoconf \
libtool \
zlib-dev \
libpng-dev
RUN apk add --update --no-cache \
--repository http://dl-3.alpinelinux.org/alpine/edge/community \
--repository http://dl-3.alpinelinux.org/alpine/edge/main \
vips-dev
COPY package*.json ./
RUN npm ci
ARG FIREBASE_APIKEY
ARG FIREBASE_APPID
ARG FIREBASE_AUTHDOMAIN
ARG FIREBASE_DATABASEURL
ARG FIREBASE_MEASUREMENTID
ARG FIREBASE_MESSAGINGSENDERID
ARG FIREBASE_PROJECTID
ARG FIREBASE_STORAGEBUCKET
COPY . ./
RUN npm run build
FROM nginx:alpine
RUN rm -rf /usr/share/nginx/html
COPY --from=builder /app/public/ /usr/share/nginx/html
COPY server.conf /etc/nginx/conf.d/default.conf

21
LICENSE
View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2020 Amruth Pillai
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

138
README.md
View File

@ -1,138 +0,0 @@
<img src="https://raw.githubusercontent.com/AmruthPillai/Reactive-Resume/develop/static/images/logo.png" width="256px" />
## A free and open source resume builder.
[![Crowdin](https://badges.crowdin.net/reactive-resume/localized.svg)](https://crowdin.com/project/reactive-resume)
[![GitHub](https://img.shields.io/github/license/AmruthPillai/Reactive-Resume)](https://github.com/AmruthPillai/Reactive-Resume/blob/develop/LICENSE)
### [Go to App](https://rxresu.me/)
### What is this app all about?
Reactive Resume is a free and open source resume builder thats built to make the mundane tasks of creating, updating and sharing your resume as easy as 1, 2, 3. With this app, you can create multiple resumes, share them with recruiters through a unique link and print as PDF, all for free, no advertisements, without losing the integrity and privacy of your data.
You have complete control over what goes into your resume, how it looks, what colors, what templates, even the layout in which sections placed. Want a dark mode resume? Its as easy as editing 3 values and youre done. You dont need to wait to see your changes either. Everything you type, everything you change, appears immediately on your resume and gets updated in real time.
### Features
- Manage multiple resumes with one account
- Sync your data across devices
- Sign in with Google, or sign in anonymously just to test the app
- Send your resume to others with a unique sharable link
- Choose from 6 vibrant templates and more coming soon
- Structure sections and change layouts the way you want to
- Rename sections according to your language/industry
- Mix and match colors to any degree, even a dark mode resume?
- Pick from a variety of crisp and clear fonts
- Easy to translate to your own language
- Import your existing [JSON Resume](https://jsonresume.org/) in one click
- No advertisements, no data sharing, no marketing emails
- **Everything is free, and theres no catch!**
### Screenshots
<img src="https://raw.githubusercontent.com/AmruthPillai/Reactive-Resume/develop/static/images/screenshots/screen-1.png" width="400px" />
&nbsp;
<img src="https://raw.githubusercontent.com/AmruthPillai/Reactive-Resume/develop/static/images/screenshots/screen-3.png" width="400px" />
&nbsp;
<img src="https://raw.githubusercontent.com/AmruthPillai/Reactive-Resume/develop/static/images/screenshots/screen-5.png" width="400px" />
### Translation
To translate the app, just fork the repository, go to `src/i18n/locales` and duplicate the `en.json` file to a new file `your-lang-code.json` and translate all of the strings inside. It's a simple process that would take just a few minutes, and by contributing, your name could also be added down below as a contributor.
For those of you familiar with the Crowdin Platform, you could do that too and just head to http://crowdin.com/project/reactive-resume/ to translate the app over there. They have a great interface that helps you navigate through various strings and manage translations.
##### Languages Currently Supported
- Arabic (عربى)
- Bengali (বাংলা)
- Czech (čeština)
- Chinese Simplified (简体中文)
- Danish (Dansk)
- Dutch (Nederlands)
- English (US)
- Finnish (Suomalainen)
- French (Français)
- German (Deutsch)
- Greek (Ελληνικά)
- Hebrew (עִברִית)
- Hindi (हिंदी)
- Indonesian (Bahasa Indonesia)
- Italian (Italiano)
- Japanese (日本語)
- Kannada (ಕನ್ನಡ)
- Lithuanian (Lietuvių)
- Norwegian (Norsk)
- Persian (Farsi)
- Polish (Polskie)
- Portuguese (Brazilian)
- Portuguese (Portugal)
- Romanian (Română)
- Russian (русский)
- Slovak (Slovenčina)
- Spanish (Español)
- Swedish (Svenska)
- Turkish (Türkçe)
- Ukrainian (Українська)
Thank you to all the amazing people who have contributed to Reactive Resume by translating it into their native language.
### Building from Source
Want to run your own instance of Reactive Resume? You are very much free to do so. The requirements to build from source are:
- NodeJS/NPM
- A Firebase Project
1. First, clone this project repository
```
git clone https://github.com/AmruthPillai/Reactive-Resume.git
cd Reactive-Resume
```
2. Run npm install to install dependencies for the project
```
npm install
```
3. Create a `.env` file and fill it with your Firebase credentials
You can get these by setting up a firebase web app [here](https://console.firebase.google.com/u/0/).
Also note that you'll need to set up a Realtime Database, _not_ a Firestore Database, to get the correct value for `FIREBASE_DATABASEURL`. Be sure to set it to test mode so you can read/write data. Just remember to either revert these or remove the database after your testing is completed.
```
FIREBASE_APIKEY=""
FIREBASE_APPID=""
FIREBASE_AUTHDOMAIN=""
FIREBASE_DATABASEURL=""
FIREBASE_MEASUREMENTID=""
FIREBASE_MESSAGINGSENDERID=""
FIREBASE_PROJECTID=""
FIREBASE_STORAGEBUCKET=""
```
4. Run `npm run start` to run the development server or `npm run build` to build the production app.
And that's it! 🎉
### Donation
I try to do what I can, but if you found the app helpful, or you're in a better position than the others who depend on this project for their first job, please consider donating as little as \$5 to help keep the project alive :)
#### https://www.buymeacoffee.com/AmruthPillai
![Please buy me a coffee](https://i.imgur.com/x7g6kvF.png)
### Appreciation
Thank you to everyone who made this project possible, including the many users who voiced their opinions, created issues and PRs to the original Reactive Resume project, and helped me along the way to make this a reality.
---
![The Great Gatsby](https://camo.githubusercontent.com/a615c7e1ef9a850f5427cdc153186763305bb853/68747470733a2f2f692e696d6775722e636f6d2f4472386a3569762e676966)
###### Made with Love by [Amruth Pillai](https://amruthpillai.com/)

View File

@ -1,12 +0,0 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 2.0.0 | :white_check_mark: |
| 1.0.0 | :x: |
## Reporting a Vulnerability
Use the [GitHub Issues](https://github.com/AmruthPillai/Reactive-Resume/issues) tab to report a vulnerability

View File

@ -1,17 +0,0 @@
import { delay } from '../../src/utils/index';
const ReachRouter = jest.requireActual('@reach/router');
const defaultDelayInMilliseconds = 100;
// eslint-disable-next-line no-unused-vars
const navigate = async (to, options) => {
await delay(defaultDelayInMilliseconds);
return Promise.resolve();
};
module.exports = {
...ReachRouter,
navigate: jest.fn(navigate),
};

View File

@ -1,35 +0,0 @@
import FirebaseStub, { AuthConstants } from '../../gatsby-plugin-firebase';
test('sets current user to anonymous user 1', async () => {
await FirebaseStub.auth().signInAnonymously();
const { currentUser } = FirebaseStub.auth();
expect(currentUser).toBeTruthy();
expect(currentUser.uid).toEqual(AuthConstants.anonymousUser1.uid);
});
test('calls onAuthStateChanged observer with anonymous user 1', async () => {
let user = null;
let error = null;
FirebaseStub.auth().onAuthStateChanged(
(_user) => {
user = _user;
},
(_error) => {
error = _error;
},
);
await FirebaseStub.auth().signInAnonymously();
expect(user).toBeTruthy();
expect(user.uid).toEqual(AuthConstants.anonymousUser1.uid);
expect(error).toBeNull();
});
test('returns anonymous user 1', async () => {
const user = await FirebaseStub.auth().signInAnonymously();
expect(user).toBeTruthy();
expect(user.uid).toEqual(AuthConstants.anonymousUser1.uid);
});

View File

@ -1,54 +0,0 @@
import FirebaseStub, { AuthConstants } from '../../gatsby-plugin-firebase';
describe('with Google auth provider', () => {
test('sets current user to Google user 3', async () => {
await FirebaseStub.auth().signInWithPopup(
new FirebaseStub.auth.GoogleAuthProvider(),
);
const { currentUser } = FirebaseStub.auth();
expect(currentUser).toBeTruthy();
expect(currentUser.uid).toEqual(AuthConstants.googleUser3.uid);
});
test('sets current user provider data', async () => {
const provider = new FirebaseStub.auth.GoogleAuthProvider();
await FirebaseStub.auth().signInWithPopup(provider);
const { currentUser } = FirebaseStub.auth();
expect(currentUser).toBeTruthy();
expect(currentUser.providerData).toBeTruthy();
expect(currentUser.providerData).toHaveLength(1);
expect(currentUser.providerData[0].providerId).toEqual(provider.providerId);
});
test('calls onAuthStateChanged observer with Google user 3', async () => {
let user = null;
let error = null;
FirebaseStub.auth().onAuthStateChanged(
(_user) => {
user = _user;
},
(_error) => {
error = _error;
},
);
await FirebaseStub.auth().signInWithPopup(
new FirebaseStub.auth.GoogleAuthProvider(),
);
expect(user).toBeTruthy();
expect(user.uid).toEqual(AuthConstants.googleUser3.uid);
expect(error).toBeNull();
});
test('returns Google user 3', async () => {
const user = await FirebaseStub.auth().signInWithPopup(
new FirebaseStub.auth.GoogleAuthProvider(),
);
expect(user).toBeTruthy();
expect(user.uid).toEqual(AuthConstants.googleUser3.uid);
});
});

View File

@ -1,29 +0,0 @@
import FirebaseStub from '../../gatsby-plugin-firebase';
test('sets current user to null', async () => {
await FirebaseStub.auth().signInAnonymously();
await FirebaseStub.auth().signOut();
const { currentUser } = FirebaseStub.auth();
expect(currentUser).toBeNull();
});
test('calls onAuthStateChanged observer with null', async () => {
let user = null;
let error = null;
FirebaseStub.auth().onAuthStateChanged(
(_user) => {
user = _user;
},
(_error) => {
error = _error;
},
);
await FirebaseStub.auth().signInAnonymously();
await FirebaseStub.auth().signOut();
expect(user).toBeNull();
expect(error).toBeNull();
});

View File

@ -1,35 +0,0 @@
import FirebaseStub from '../../gatsby-plugin-firebase';
test('reuses existing Auth instance', () => {
const auth1 = FirebaseStub.auth();
const auth2 = FirebaseStub.auth();
expect(auth1.uuid).toBeTruthy();
expect(auth2.uuid).toBeTruthy();
expect(auth1.uuid).toEqual(auth2.uuid);
});
test('onAuthStateChanged unsubscribe removes observer', () => {
const observer = () => {};
const unsubscribe = FirebaseStub.auth().onAuthStateChanged(observer);
expect(unsubscribe).toBeTruthy();
expect(
FirebaseStub.auth().onAuthStateChangedObservers.indexOf(observer),
).toBeGreaterThanOrEqual(0);
unsubscribe();
expect(
FirebaseStub.auth().onAuthStateChangedObservers.indexOf(observer),
).not.toBeGreaterThanOrEqual(0);
});
test('current user delete calls signOut', async () => {
const mockSignOut = jest.spyOn(FirebaseStub.auth(), 'signOut');
await FirebaseStub.auth().signInAnonymously();
const { currentUser } = FirebaseStub.auth();
await currentUser.delete();
expect(mockSignOut).toHaveBeenCalledTimes(1);
});

View File

@ -1,53 +0,0 @@
import { waitFor } from '@testing-library/react';
import FirebaseStub, { DatabaseConstants } from '../../gatsby-plugin-firebase';
test('can filter resumes by user', async () => {
FirebaseStub.database().initializeData();
let snapshotValue = null;
FirebaseStub.database()
.ref(DatabaseConstants.resumesPath)
.orderByChild('user')
.equalTo(DatabaseConstants.user1.uid)
.on('value', (snapshot) => {
snapshotValue = snapshot.val();
});
await waitFor(() =>
snapshotValue ? Promise.resolve(true) : Promise.reject(),
);
expect(snapshotValue).not.toBeNull();
expect(Object.keys(snapshotValue)).toHaveLength(2);
Object.values(snapshotValue).forEach((resume) =>
expect(resume.user).toEqual(DatabaseConstants.user1.uid),
);
});
test('previously set query parameters are not kept when retrieving reference again', () => {
FirebaseStub.database().initializeData();
let reference = null;
reference = FirebaseStub.database().ref(DatabaseConstants.resumesPath);
expect(reference).toBeTruthy();
const { uuid } = reference;
expect(reference.orderByChildPath).toHaveLength(0);
expect(reference.equalToValue).toHaveLength(0);
reference = FirebaseStub.database()
.ref(DatabaseConstants.resumesPath)
.orderByChild('user')
.equalTo('testuser1');
expect(reference).toBeTruthy();
expect(reference.uuid).toBe(uuid);
expect(reference.orderByChildPath).toBe('user');
expect(reference.equalToValue).toBe('testuser1');
reference = FirebaseStub.database().ref(DatabaseConstants.resumesPath);
expect(reference).toBeTruthy();
expect(reference.uuid).toBe(uuid);
expect(reference.orderByChildPath).toHaveLength(0);
expect(reference.equalToValue).toHaveLength(0);
});

View File

@ -1,51 +0,0 @@
import { waitFor } from '@testing-library/react';
import FirebaseStub, { DatabaseConstants } from '../../gatsby-plugin-firebase';
test('removes event callbacks', async () => {
FirebaseStub.database().initializeData();
const userUid = DatabaseConstants.user1.uid;
let valueCallbackSnapshotValue = null;
const valueCallback = jest.fn((snapshot) => {
valueCallbackSnapshotValue = snapshot.val();
});
FirebaseStub.database()
.ref(DatabaseConstants.resumesPath)
.orderByChild('user')
.equalTo(userUid)
.on('value', valueCallback);
await waitFor(() => valueCallback.mock.calls[0][0]);
valueCallback.mockClear();
valueCallbackSnapshotValue = null;
let childRemovedCallbackSnapshotValue = null;
const childRemovedCallback = jest.fn((snapshot) => {
childRemovedCallbackSnapshotValue = snapshot.val();
});
FirebaseStub.database()
.ref(DatabaseConstants.resumesPath)
.orderByChild('user')
.equalTo(userUid)
.on('child_removed', childRemovedCallback);
const removedResume = (
await FirebaseStub.database()
.ref(
`${DatabaseConstants.resumesPath}/${DatabaseConstants.demoStateResume1Id}`,
)
.once('value')
).val();
expect(removedResume).toBeTruthy();
FirebaseStub.database().ref(DatabaseConstants.resumesPath).off();
await FirebaseStub.database()
.ref(`${DatabaseConstants.resumesPath}/${removedResume.id}`)
.remove();
expect(childRemovedCallback.mock.calls).toHaveLength(0);
expect(childRemovedCallbackSnapshotValue).toBeNull();
expect(valueCallback.mock.calls).toHaveLength(0);
expect(valueCallbackSnapshotValue).toBeNull();
});

View File

@ -1,45 +0,0 @@
import { waitFor } from '@testing-library/react';
import FirebaseStub, { DatabaseConstants } from '../../gatsby-plugin-firebase';
test('triggers event with true if on the connected reference path', async () => {
FirebaseStub.database().initializeData();
let snapshotValue = null;
FirebaseStub.database()
.ref(DatabaseConstants.connectedPath)
.on('value', (snapshot) => {
snapshotValue = snapshot.val();
});
await waitFor(() =>
snapshotValue ? Promise.resolve(true) : Promise.reject(),
);
expect(snapshotValue).not.toBeNull();
expect(snapshotValue).toBe(true);
});
test('triggers event with resumes if on the resumes reference path', async () => {
FirebaseStub.database().initializeData();
const resumesDataSnapshot = await FirebaseStub.database()
.ref(DatabaseConstants.resumesPath)
.once('value');
const resumes = resumesDataSnapshot.val();
let snapshotValue = null;
FirebaseStub.database()
.ref(DatabaseConstants.resumesPath)
.on('value', (snapshot) => {
snapshotValue = snapshot.val();
});
await waitFor(() =>
snapshotValue ? Promise.resolve(true) : Promise.reject(),
);
expect(snapshotValue).not.toBeNull();
expect(snapshotValue).toEqual(resumes);
});

View File

@ -1,59 +0,0 @@
import FirebaseStub, { DatabaseConstants } from '../../gatsby-plugin-firebase';
test('retrieves resume if it exists', async () => {
FirebaseStub.database().initializeData();
const resumeId = DatabaseConstants.demoStateResume1Id;
const resume = (
await FirebaseStub.database()
.ref(`${DatabaseConstants.resumesPath}/${resumeId}`)
.once('value')
).val();
expect(resume).toBeTruthy();
expect(resume.id).toEqual(resumeId);
});
test('retrieves null if resume does not exist', async () => {
FirebaseStub.database().initializeData();
const resumeId = 'invalidResumeId';
const resume = (
await FirebaseStub.database()
.ref(`${DatabaseConstants.resumesPath}/${resumeId}`)
.once('value')
).val();
expect(resume).toBeNull();
});
test('retrieves user if it exists', async () => {
FirebaseStub.database().initializeData();
const expectedUser = DatabaseConstants.user1;
const user = (
await FirebaseStub.database()
.ref(`${DatabaseConstants.usersPath}/${expectedUser.uid}`)
.once('value')
).val();
expect(user).toBeTruthy();
expect(user).toEqual(expectedUser);
});
test('retrieves null if user does not exist', async () => {
FirebaseStub.database().initializeData();
const userId = 'invalidUserId';
const user = (
await FirebaseStub.database()
.ref(`${DatabaseConstants.usersPath}/${userId}`)
.once('value')
).val();
expect(user).toBeNull();
});

View File

@ -1,77 +0,0 @@
import { waitFor } from '@testing-library/react';
import FirebaseStub, { DatabaseConstants } from '../../gatsby-plugin-firebase';
test('deletes data', async () => {
FirebaseStub.database().initializeData();
const resumeId = DatabaseConstants.demoStateResume1Id;
const removedResume = (
await FirebaseStub.database()
.ref(`${DatabaseConstants.resumesPath}/${resumeId}`)
.once('value')
).val();
expect(removedResume).toBeTruthy();
await FirebaseStub.database()
.ref(`${DatabaseConstants.resumesPath}/${resumeId}`)
.remove();
const actualResume = (
await FirebaseStub.database()
.ref(`${DatabaseConstants.resumesPath}/${resumeId}`)
.once('value')
).val();
expect(actualResume).toBeNull();
});
test('triggers events', async () => {
FirebaseStub.database().initializeData();
const userUid = DatabaseConstants.user1.uid;
let valueCallbackSnapshotValue = null;
const valueCallback = jest.fn((snapshot) => {
valueCallbackSnapshotValue = snapshot.val();
});
FirebaseStub.database()
.ref(DatabaseConstants.resumesPath)
.orderByChild('user')
.equalTo(userUid)
.on('value', valueCallback);
await waitFor(() => valueCallback.mock.calls[0][0]);
valueCallback.mockClear();
valueCallbackSnapshotValue = null;
let childRemovedCallbackSnapshotValue = null;
const childRemovedCallback = jest.fn((snapshot) => {
childRemovedCallbackSnapshotValue = snapshot.val();
});
FirebaseStub.database()
.ref(DatabaseConstants.resumesPath)
.orderByChild('user')
.equalTo(userUid)
.on('child_removed', childRemovedCallback);
const removedResume = (
await FirebaseStub.database()
.ref(
`${DatabaseConstants.resumesPath}/${DatabaseConstants.demoStateResume1Id}`,
)
.once('value')
).val();
expect(removedResume).toBeTruthy();
expect(removedResume.user).toEqual(userUid);
await FirebaseStub.database()
.ref(`${DatabaseConstants.resumesPath}/${removedResume.id}`)
.remove();
await waitFor(() => childRemovedCallback.mock.calls[0][0]);
expect(childRemovedCallback.mock.calls).toHaveLength(1);
expect(childRemovedCallbackSnapshotValue).toBeTruthy();
expect(childRemovedCallbackSnapshotValue).toEqual(removedResume);
await waitFor(() => valueCallback.mock.calls[0][0]);
expect(valueCallback.mock.calls).toHaveLength(1);
expect(valueCallbackSnapshotValue).toBeTruthy();
expect(removedResume.id in valueCallbackSnapshotValue).toBe(false);
});

View File

@ -1,71 +0,0 @@
import { waitFor } from '@testing-library/react';
import FirebaseStub, { DatabaseConstants } from '../../gatsby-plugin-firebase';
test('inserts data', async () => {
FirebaseStub.database().initializeData();
const existingResume = (
await FirebaseStub.database()
.ref(
`${DatabaseConstants.resumesPath}/${DatabaseConstants.demoStateResume1Id}`,
)
.once('value')
).val();
expect(existingResume).toBeTruthy();
const newResume = JSON.parse(JSON.stringify(existingResume));
newResume.id = 'newre1';
newResume.name = `Test Resume ${newResume.id}`;
await FirebaseStub.database()
.ref(`${DatabaseConstants.resumesPath}/${newResume.id}`)
.set(newResume);
const actualResume = (
await FirebaseStub.database()
.ref(`${DatabaseConstants.resumesPath}/${newResume.id}`)
.once('value')
).val();
expect(actualResume).toBeTruthy();
expect(actualResume).toEqual(newResume);
});
test('triggers events', async () => {
FirebaseStub.database().initializeData();
let snapshotValue = null;
const callback = jest.fn((snapshot) => {
snapshotValue = snapshot.val();
});
FirebaseStub.database()
.ref(DatabaseConstants.resumesPath)
.orderByChild('user')
.equalTo(DatabaseConstants.user1.uid)
.on('value', callback);
await waitFor(() => callback.mock.calls[0][0]);
callback.mockClear();
snapshotValue = null;
const existingResume = (
await FirebaseStub.database()
.ref(
`${DatabaseConstants.resumesPath}/${DatabaseConstants.demoStateResume1Id}`,
)
.once('value')
).val();
expect(existingResume).toBeTruthy();
const newResume = JSON.parse(JSON.stringify(existingResume));
newResume.id = 'newre1';
newResume.name = `Test Resume ${newResume.id}`;
await FirebaseStub.database()
.ref(`${DatabaseConstants.resumesPath}/${newResume.id}`)
.set(newResume);
await waitFor(() => callback.mock.calls[0][0]);
expect(callback.mock.calls).toHaveLength(1);
expect(snapshotValue).not.toBeNull();
expect(Object.keys(snapshotValue)).toHaveLength(3);
expect(snapshotValue[newResume.id]).toBeTruthy();
expect(snapshotValue[newResume.id]).toEqual(newResume);
});

View File

@ -1,26 +0,0 @@
import FirebaseStub, { DatabaseConstants } from '../../gatsby-plugin-firebase';
test('reuses existing Reference instance', () => {
const ref1 = FirebaseStub.database().ref(
`${DatabaseConstants.resumesPath}/123`,
);
const ref2 = FirebaseStub.database().ref(
`${DatabaseConstants.resumesPath}/123`,
);
expect(ref1).toBeTruthy();
expect(ref2).toBeTruthy();
expect(ref1).toEqual(ref2);
});
test('leading slash in reference path is ignored', () => {
const path = `${DatabaseConstants.resumesPath}/123`;
const ref1 = FirebaseStub.database().ref(path);
expect(ref1).toBeTruthy();
expect(ref1.path).toEqual(path);
const ref2 = FirebaseStub.database().ref(`/${path}`);
expect(ref2).toBeTruthy();
expect(ref2).toEqual(ref1);
});

View File

@ -1,86 +0,0 @@
import { waitFor } from '@testing-library/react';
import FirebaseStub, { DatabaseConstants } from '../../gatsby-plugin-firebase';
test('can spy on it', async () => {
FirebaseStub.database().initializeData();
const referencePath = `${DatabaseConstants.resumesPath}/123456`;
const functionSpy = jest.spyOn(
FirebaseStub.database().ref(referencePath),
'update',
);
const updateArgument = 'test value 123';
await FirebaseStub.database().ref(referencePath).update(updateArgument);
expect(functionSpy).toHaveBeenCalledTimes(1);
const functionCallArgument = functionSpy.mock.calls[0][0];
expect(functionCallArgument).toBeTruthy();
expect(functionCallArgument).toEqual(updateArgument);
});
test('updates data', async () => {
FirebaseStub.database().initializeData();
const resumeId = DatabaseConstants.demoStateResume1Id;
const existingResume = (
await FirebaseStub.database()
.ref(`${DatabaseConstants.resumesPath}/${resumeId}`)
.once('value')
).val();
expect(existingResume).toBeTruthy();
const resumeName = 'Test Resume renamed';
existingResume.name = resumeName;
await FirebaseStub.database()
.ref(`${DatabaseConstants.resumesPath}/${resumeId}`)
.update(existingResume);
const actualResume = (
await FirebaseStub.database()
.ref(`${DatabaseConstants.resumesPath}/${resumeId}`)
.once('value')
).val();
expect(actualResume).toBeTruthy();
expect(existingResume).toEqual(actualResume);
expect(actualResume.name).toEqual(resumeName);
});
test('triggers events', async () => {
FirebaseStub.database().initializeData();
let snapshotValue = null;
const callback = jest.fn((snapshot) => {
snapshotValue = snapshot.val();
});
FirebaseStub.database()
.ref(DatabaseConstants.resumesPath)
.orderByChild('user')
.equalTo(DatabaseConstants.user1.uid)
.on('value', callback);
await waitFor(() => callback.mock.calls[0][0]);
callback.mockClear();
snapshotValue = null;
const existingResume = (
await FirebaseStub.database()
.ref(
`${DatabaseConstants.resumesPath}/${DatabaseConstants.demoStateResume1Id}`,
)
.once('value')
).val();
expect(existingResume).toBeTruthy();
existingResume.name = 'Test Resume renamed';
await FirebaseStub.database()
.ref(`${DatabaseConstants.resumesPath}/${existingResume.id}`)
.update(existingResume);
await waitFor(() => callback.mock.calls[0][0]);
expect(callback.mock.calls).toHaveLength(1);
expect(snapshotValue).not.toBeNull();
expect(Object.keys(snapshotValue)).toHaveLength(2);
expect(snapshotValue[existingResume.id]).toBeTruthy();
expect(snapshotValue[existingResume.id]).toEqual(existingResume);
});

View File

@ -1,67 +0,0 @@
import FirebaseStub, { DatabaseConstants } from '../../gatsby-plugin-firebase';
test('reuses existing Database instance', () => {
const database1 = FirebaseStub.database();
const database2 = FirebaseStub.database();
expect(database1.uuid).toBeTruthy();
expect(database2.uuid).toBeTruthy();
expect(database1.uuid).toEqual(database2.uuid);
});
test('ServerValue.TIMESTAMP returns current time in milliseconds', () => {
const now = new Date().getTime();
const timestamp = FirebaseStub.database.ServerValue.TIMESTAMP;
expect(timestamp).toBeTruthy();
expect(timestamp).toBeGreaterThanOrEqual(now);
});
test('initializing data sets up resumes and users', async () => {
FirebaseStub.database().initializeData();
const resumesRef = FirebaseStub.database().ref(DatabaseConstants.resumesPath);
const resumesDataSnapshot = await resumesRef.once('value');
const resumes = resumesDataSnapshot.val();
expect(resumes).toBeTruthy();
expect(Object.keys(resumes)).toHaveLength(5);
const demoStateResume1 = resumes[DatabaseConstants.demoStateResume1Id];
expect(demoStateResume1).toBeTruthy();
expect(demoStateResume1.id).toEqual(DatabaseConstants.demoStateResume1Id);
expect(demoStateResume1.user).toEqual(DatabaseConstants.user1.uid);
const demoStateResume2 = resumes[DatabaseConstants.demoStateResume2Id];
expect(demoStateResume2).toBeTruthy();
expect(demoStateResume2.id).toEqual(DatabaseConstants.demoStateResume2Id);
expect(demoStateResume2.user).toEqual(DatabaseConstants.user2.uid);
const initialStateResume1 = resumes[DatabaseConstants.initialStateResume1Id];
expect(initialStateResume1).toBeTruthy();
expect(initialStateResume1.id).toEqual(
DatabaseConstants.initialStateResume1Id,
);
expect(initialStateResume1.user).toEqual(DatabaseConstants.user1.uid);
const demoStateResume3 = resumes[DatabaseConstants.demoStateResume3Id];
expect(demoStateResume3).toBeTruthy();
expect(demoStateResume3.id).toEqual(DatabaseConstants.demoStateResume3Id);
expect(demoStateResume3.user).toEqual(DatabaseConstants.user3.uid);
const initialStateResume2 = resumes[DatabaseConstants.initialStateResume2Id];
expect(initialStateResume2).toBeTruthy();
expect(initialStateResume2.id).toEqual(
DatabaseConstants.initialStateResume2Id,
);
expect(initialStateResume2.user).toEqual(DatabaseConstants.user3.uid);
const usersRef = FirebaseStub.database().ref(DatabaseConstants.usersPath);
const usersDataSnapshot = await usersRef.once('value');
const users = usersDataSnapshot.val();
expect(users).toBeTruthy();
expect(Object.keys(users)).toHaveLength(3);
const anonymousUser1 = users[DatabaseConstants.user1.uid];
expect(anonymousUser1).toBeTruthy();
expect(anonymousUser1).toEqual(DatabaseConstants.user1);
const anonymousUser2 = users[DatabaseConstants.user2.uid];
expect(anonymousUser2).toBeTruthy();
expect(anonymousUser2).toEqual(DatabaseConstants.user2);
const googleUser3 = users[DatabaseConstants.user3.uid];
expect(googleUser3).toBeTruthy();
expect(googleUser3).toEqual(DatabaseConstants.user3);
});

View File

@ -1,19 +0,0 @@
import FirebaseStub from '../../gatsby-plugin-firebase';
test('reuses existing Functions instance', () => {
const functions1 = FirebaseStub.functions();
const functions2 = FirebaseStub.functions();
expect(functions1.uuid).toBeTruthy();
expect(functions2.uuid).toBeTruthy();
expect(functions1.uuid).toEqual(functions2.uuid);
});
test('deleteUser function returns true', async () => {
const deleteUser = FirebaseStub.functions().httpsCallable('deleteUser');
const result = await deleteUser();
expect(result).toBeTruthy();
expect(result.data).toEqual(true);
});

View File

@ -1,2 +0,0 @@
const mockFile = 'test-file-stub';
export default mockFile;

View File

@ -1,33 +0,0 @@
import Auth from './gatsby-plugin-firebase/auth/auth';
import AuthConstants from './gatsby-plugin-firebase/constants/auth';
import Database from './gatsby-plugin-firebase/database/database';
import DatabaseConstants from './gatsby-plugin-firebase/constants/database';
import Functions from './gatsby-plugin-firebase/functions/functions';
import FunctionsConstants from './gatsby-plugin-firebase/constants/functions';
import GoogleAuthProvider from './gatsby-plugin-firebase/auth/googleAuthProvider';
class FirebaseStub {
static auth() {
return Auth.instance;
}
static database() {
return Database.instance;
}
static functions() {
return Functions.instance;
}
}
FirebaseStub.auth.GoogleAuthProvider = GoogleAuthProvider;
FirebaseStub.database.ServerValue = {};
Object.defineProperty(FirebaseStub.database.ServerValue, 'TIMESTAMP', {
get() {
return new Date().getTime();
},
});
export default FirebaseStub;
export { AuthConstants, DatabaseConstants, FunctionsConstants };

View File

@ -1,125 +0,0 @@
/* eslint-disable no-underscore-dangle */
import { v4 as uuidv4 } from 'uuid';
import { delay } from '../../../src/utils/index';
import AuthProvider from './authProvider';
import Constants from '../constants/auth';
import GoogleAuthProvider from './googleAuthProvider';
import User from './user';
const singleton = Symbol('');
const singletonEnforcer = Symbol('');
class Auth {
constructor(enforcer) {
if (enforcer !== singletonEnforcer) {
throw new Error('Cannot construct singleton');
}
this._uuid = uuidv4();
this._currentUser = null;
this._onAuthStateChangedObservers = [];
}
static get instance() {
if (!this[singleton]) {
this[singleton] = new Auth(singletonEnforcer);
}
return this[singleton];
}
get currentUser() {
return this._currentUser;
}
get uuid() {
return this._uuid;
}
get onAuthStateChangedObservers() {
return this._onAuthStateChangedObservers;
}
onAuthStateChanged(observer) {
this.onAuthStateChangedObservers.push(observer);
return () => {
this._onAuthStateChangedObservers =
this.onAuthStateChangedObservers.filter((obs) => obs !== observer);
};
}
async signInAnonymously() {
const user = Constants.anonymousUser1;
this._currentUser = new User(
user.displayName,
user.email,
user.providerId,
user.uid,
user.isAnonymous,
this.signOut.bind(this),
);
await delay(Constants.defaultDelayInMilliseconds);
this.onAuthStateChangedObservers.forEach((observer) =>
observer(this._currentUser),
);
return this._currentUser;
}
/**
* Authenticates with popup.
*
* @param {AuthProvider} provider The provider to authenticate.
*/
async signInWithPopup(provider) {
if (!provider) {
throw new Error('provider must be provided.');
} else if (!(provider instanceof AuthProvider)) {
throw new Error('provider should be an AuthProvider.');
}
if (!(provider instanceof GoogleAuthProvider)) {
throw new Error(
`${provider.constructor.name} is currently not supported.`,
);
}
const user = Constants.googleUser3;
this._currentUser = new User(
user.displayName,
user.email,
user.providerId,
user.uid,
user.isAnonymous,
this.signOut.bind(this),
);
await delay(Constants.defaultDelayInMilliseconds);
this.onAuthStateChangedObservers.forEach((observer) =>
observer(this._currentUser),
);
return this._currentUser;
}
async signOut() {
if (this._currentUser === null) {
return;
}
this._currentUser = null;
await delay(Constants.defaultDelayInMilliseconds);
this.onAuthStateChangedObservers.forEach((observer) => observer(null));
}
}
export default Auth;

View File

@ -1,23 +0,0 @@
/* eslint-disable no-underscore-dangle */
class AuthProvider {
/**
* Creates a new auth provider.
*
* @param {string} providerId Provider ID.
*/
constructor(providerId) {
if (!providerId) {
throw new Error('providerId must be provided.');
} else if (typeof providerId !== 'string') {
throw new Error('providerId should be a string.');
} else {
this._providerId = providerId;
}
}
get providerId() {
return this._providerId;
}
}
export default AuthProvider;

View File

@ -1,10 +0,0 @@
import AuthProvider from './authProvider';
import Constants from '../constants/auth';
class GoogleAuthProvider extends AuthProvider {
constructor() {
super(Constants.googleAuthProviderId);
}
}
export default GoogleAuthProvider;

View File

@ -1,67 +0,0 @@
/* eslint-disable no-underscore-dangle */
import Constants from '../constants/auth';
// eslint-disable-next-line no-unused-vars
import AuthProvider from './authProvider';
import UserInfo from './userInfo';
import { delay } from '../../../src/utils/index';
class User extends UserInfo {
/**
* Creates a new user.
*
* @param {string|null} displayName Display name.
* @param {string|null} email Email.
* @param {string} providerId Auth provider ID.
* @param {string} uid The user's unique ID.
* @param {boolean} isAnonymous Is anonymous.
* @param {function():Promise<void>} deleteUser Delete user callback.
*/
constructor(displayName, email, providerId, uid, isAnonymous, deleteUser) {
super(displayName, email, providerId, uid);
if (!deleteUser) {
throw new Error('deleteUser must be provided.');
} else if (typeof deleteUser !== 'function') {
throw new Error('deleteUser should be a function.');
} else {
this._deleteUser = deleteUser;
}
this._isAnonymous = isAnonymous;
this._providerData = [];
if (!isAnonymous) {
this._providerData.push(
new UserInfo(displayName, email, providerId, uid),
);
}
}
get isAnonymous() {
return this._isAnonymous;
}
get providerData() {
return this._providerData;
}
async delete() {
await delay(Constants.defaultDelayInMilliseconds);
await this._deleteUser();
}
/**
* Reauthenticates the user with popup.
*
* @param {AuthProvider} provider The provider to authenticate.
*/
// eslint-disable-next-line no-unused-vars
async reauthenticateWithPopup(provider) {
await delay(Constants.defaultDelayInMilliseconds);
return this;
}
}
export default User;

View File

@ -1,47 +0,0 @@
/* eslint-disable no-underscore-dangle */
class UserInfo {
/**
* Creates a new user profile information.
*
* @param {string|null} displayName Display name.
* @param {string|null} email Email.
* @param {string} providerId Auth provider ID.
* @param {string} uid The user's unique ID.
*/
constructor(displayName, email, providerId, uid) {
if (!uid) {
throw new Error('uid must be provided.');
} else if (typeof uid !== 'string') {
throw new Error('uid should be a string.');
} else {
this._uid = uid;
}
if (typeof providerId !== 'string') {
throw new Error('providerId should be a string.');
} else {
this._providerId = providerId;
}
this._displayName = displayName;
this._email = email;
}
get displayName() {
return this._displayName;
}
get email() {
return this._email;
}
get providerId() {
return this._providerId;
}
get uid() {
return this._uid;
}
}
export default UserInfo;

View File

@ -1,51 +0,0 @@
const googleAuthProviderId = 'google.com';
const anonymousUser1 = {
displayName: 'Anonymous User 1',
email: 'anonymous1@noemail.com',
isAnonymous: true,
providerId: '',
uid: 'anonym1',
};
const anonymousUser2 = {
displayName: 'Anonymous User 2',
email: 'anonymous2@noemail.com',
isAnonymous: true,
providerId: '',
uid: 'anonym2',
};
const googleUser3 = {
displayName: 'Google User 3',
email: 'google3@noemail.com',
isAnonymous: false,
providerId: googleAuthProviderId,
uid: 'google3',
};
const defaultDelayInMilliseconds = 100;
class Auth {
static get googleAuthProviderId() {
return googleAuthProviderId;
}
static get anonymousUser1() {
return anonymousUser1;
}
static get anonymousUser2() {
return anonymousUser2;
}
static get googleUser3() {
return googleUser3;
}
static get defaultDelayInMilliseconds() {
return defaultDelayInMilliseconds;
}
}
export default Auth;

View File

@ -1,89 +0,0 @@
import AuthConstants from './auth';
const valueEventType = 'value';
const childRemovedEventType = 'child_removed';
const resumesPath = 'resumes';
const usersPath = 'users';
const connectedPath = '.info/connected';
const demoStateResume1Id = 'demo_1';
const demoStateResume2Id = 'demo_2';
const demoStateResume3Id = 'demo_3';
const initialStateResume1Id = 'init_1';
const initialStateResume2Id = 'init_2';
const user1 = {
uid: AuthConstants.anonymousUser1.uid,
isAnonymous: AuthConstants.anonymousUser1.isAnonymous,
};
const user2 = {
uid: AuthConstants.anonymousUser2.uid,
isAnonymous: AuthConstants.anonymousUser2.isAnonymous,
};
const user3 = {
uid: AuthConstants.googleUser3.uid,
isAnonymous: AuthConstants.googleUser3.isAnonymous,
};
const defaultDelayInMilliseconds = 100;
class Database {
static get valueEventType() {
return valueEventType;
}
static get childRemovedEventType() {
return childRemovedEventType;
}
static get resumesPath() {
return resumesPath;
}
static get usersPath() {
return usersPath;
}
static get connectedPath() {
return connectedPath;
}
static get demoStateResume1Id() {
return demoStateResume1Id;
}
static get demoStateResume2Id() {
return demoStateResume2Id;
}
static get demoStateResume3Id() {
return demoStateResume3Id;
}
static get initialStateResume1Id() {
return initialStateResume1Id;
}
static get initialStateResume2Id() {
return initialStateResume2Id;
}
static get user1() {
return user1;
}
static get user2() {
return user2;
}
static get user3() {
return user3;
}
static get defaultDelayInMilliseconds() {
return defaultDelayInMilliseconds;
}
}
export default Database;

View File

@ -1,15 +0,0 @@
const deleteUserFunctionName = 'deleteUser';
const defaultDelayInMilliseconds = 100;
class Functions {
static get deleteUserFunctionName() {
return deleteUserFunctionName;
}
static get defaultDelayInMilliseconds() {
return defaultDelayInMilliseconds;
}
}
export default Functions;

View File

@ -1,24 +0,0 @@
/* eslint-disable no-underscore-dangle */
class DataSnapshot {
constructor(getData, value = undefined) {
if (!getData) {
throw new Error('getData must be provided.');
} else if (typeof getData !== 'function') {
throw new Error('getData should be a function.');
}
this._getData = getData;
this._value = value;
}
get value() {
return this._value;
}
val() {
return typeof this.value !== 'undefined' ? this.value : this._getData();
}
}
export default DataSnapshot;

View File

@ -1,170 +0,0 @@
/* eslint-disable no-underscore-dangle */
import { v4 as uuidv4 } from 'uuid';
import fs from 'fs';
import path from 'path';
import DatabaseConstants from '../constants/database';
import Reference from './reference';
const singleton = Symbol('');
const singletonEnforcer = Symbol('');
const readFile = (fileRelativePath) => {
const fileAbsolutePath = path.resolve(__dirname, fileRelativePath);
const fileBuffer = fs.readFileSync(fileAbsolutePath);
const fileData = JSON.parse(fileBuffer);
return fileData;
};
class Database {
constructor(enforcer) {
if (enforcer !== singletonEnforcer) {
throw new Error('Cannot construct singleton');
}
this._uuid = uuidv4();
this._data = {};
this._references = {};
}
static get instance() {
if (!this[singleton]) {
this[singleton] = new Database(singletonEnforcer);
}
return this[singleton];
}
get uuid() {
return this._uuid;
}
_getData(dataPath) {
if (!dataPath) {
throw new Error('dataPath must be provided.');
}
const dataPathElements = dataPath.split('/');
if (!(dataPathElements[0] in this._data)) {
return null;
}
if (dataPathElements.length === 1) {
return this._data[dataPathElements[0]];
}
if (dataPathElements.length === 2) {
if (!(dataPathElements[1] in this._data[dataPathElements[0]])) {
return null;
}
return this._data[dataPathElements[0]][dataPathElements[1]];
}
return null;
}
_getReference(referencePath) {
return referencePath in this._references
? this._references[referencePath]
: null;
}
_setData(dataPath, value) {
if (!dataPath) {
throw new Error('dataPath must be provided.');
}
if (typeof value === 'undefined') {
throw new Error('value is undefined.');
}
const dataPathElements = dataPath.split('/');
if (dataPathElements.length !== 2) {
return;
}
if (!(dataPathElements[0] in this._data)) {
return;
}
if (!dataPathElements[1]) {
return;
}
if (value === null) {
delete this._data[dataPathElements[0]][dataPathElements[1]];
} else {
this._data[dataPathElements[0]][dataPathElements[1]] = value;
}
}
initializeData() {
const resumes = {};
const date = new Date('December 15, 2020 11:20:25');
const demoStateResume1 = readFile('../../../src/data/demoState.json');
demoStateResume1.updatedAt = date.valueOf();
date.setMonth(date.getMonth() - 2);
demoStateResume1.createdAt = date.valueOf();
demoStateResume1.user = DatabaseConstants.user1.uid;
resumes[DatabaseConstants.demoStateResume1Id] = demoStateResume1;
const demoStateResume2 = JSON.parse(JSON.stringify(demoStateResume1));
demoStateResume2.user = DatabaseConstants.user2.uid;
resumes[DatabaseConstants.demoStateResume2Id] = demoStateResume2;
const initialStateResume1 = readFile('../../../src/data/initialState.json');
initialStateResume1.updatedAt = date.valueOf();
initialStateResume1.createdAt = date.valueOf();
initialStateResume1.user = DatabaseConstants.user1.uid;
resumes[DatabaseConstants.initialStateResume1Id] = initialStateResume1;
const demoStateResume3 = readFile('../../../src/data/demoState.json');
demoStateResume3.updatedAt = date.valueOf();
date.setMonth(date.getMonth() - 2);
demoStateResume3.createdAt = date.valueOf();
demoStateResume3.user = DatabaseConstants.user3.uid;
resumes[DatabaseConstants.demoStateResume3Id] = demoStateResume3;
const initialStateResume2 = readFile('../../../src/data/initialState.json');
initialStateResume2.updatedAt = date.valueOf();
initialStateResume2.createdAt = date.valueOf();
initialStateResume2.user = DatabaseConstants.user3.uid;
resumes[DatabaseConstants.initialStateResume2Id] = initialStateResume2;
Object.keys(resumes).forEach((key) => {
const resume = resumes[key];
resume.id = key;
resume.name = `Test Resume ${key}`;
});
this._data[DatabaseConstants.resumesPath] = resumes;
const users = {};
users[DatabaseConstants.user1.uid] = DatabaseConstants.user1;
users[DatabaseConstants.user2.uid] = DatabaseConstants.user2;
users[DatabaseConstants.user3.uid] = DatabaseConstants.user3;
this._data[DatabaseConstants.usersPath] = users;
}
ref(referencePath) {
const newRef = new Reference(
referencePath,
(dataPath) => this._getData(dataPath),
(dataPath, value) => this._setData(dataPath, value),
(refPath) => this._getReference(refPath),
);
const existingRef = this._getReference(newRef.path);
if (existingRef) {
existingRef.initializeQueryParameters();
return existingRef;
}
this._references[newRef.path] = newRef;
return newRef;
}
}
export default Database;

View File

@ -1,215 +0,0 @@
/* eslint-disable no-underscore-dangle */
import { v4 as uuidv4 } from 'uuid';
import { delay } from '../../../src/utils/index';
import DataSnapshot from './dataSnapshot';
import DatabaseConstants from '../constants/database';
const parsePath = (path) => {
if (!path) {
throw new Error('path must be provided.');
} else if (typeof path !== 'string') {
throw new Error('path should be a string.');
} else {
let parsedPath = path.trim();
if (parsedPath[0] === '/') {
parsedPath = parsedPath.substring(1);
}
return parsedPath;
}
};
class Reference {
constructor(path, getDatabaseData, setDatabaseData, getReference) {
this._path = parsePath(path);
this._uuid = uuidv4();
if (this.path === DatabaseConstants.connectedPath) {
this._dataSnapshot = new DataSnapshot(() => {}, true);
} else {
this._dataSnapshot = new DataSnapshot(() => this._getData());
}
if (!getDatabaseData) {
throw new Error('getDatabaseData must be provided.');
} else if (typeof getDatabaseData !== 'function') {
throw new Error('getDatabaseData should be a function.');
}
this._getDatabaseData = getDatabaseData;
if (!setDatabaseData) {
throw new Error('setDatabaseData must be provided.');
} else if (typeof getDatabaseData !== 'function') {
throw new Error('setDatabaseData should be a function.');
}
this._setDatabaseData = setDatabaseData;
if (!getReference) {
throw new Error('getReference must be provided.');
} else if (typeof getDatabaseData !== 'function') {
throw new Error('getReference should be a function.');
}
this._getReference = getReference;
this._eventCallbacks = {};
this.initializeQueryParameters();
}
get path() {
return this._path;
}
get uuid() {
return this._uuid;
}
get eventCallbacks() {
return this._eventCallbacks;
}
get orderByChildPath() {
return this._orderByChildPath;
}
get equalToValue() {
return this._equalToValue;
}
_getData() {
const databaseData = this._getDatabaseData(this.path);
if (!databaseData) {
return null;
}
if (this.orderByChildPath && this.equalToValue) {
return Object.fromEntries(
Object.entries(databaseData).filter(
([, value]) => value[this.orderByChildPath] === this.equalToValue,
),
);
}
return databaseData;
}
_getParent() {
const pathElements = this.path.split('/');
let parent = null;
if (pathElements.length === 2) {
parent = this._getReference(pathElements[0]);
}
return parent;
}
_handleDataUpdate(value) {
if (typeof value === 'undefined') {
throw new Error('value must be provided.');
}
const currentData = this._getData();
const parentReference = this._getParent();
this._setDatabaseData(this.path, value);
if (value === null) {
if (parentReference) {
parentReference.triggerEventCallback(
DatabaseConstants.childRemovedEventType,
currentData,
);
}
} else {
this.triggerEventCallback(DatabaseConstants.valueEventType);
}
if (parentReference) {
parentReference.triggerEventCallback(DatabaseConstants.valueEventType);
}
}
triggerEventCallback(eventType, snapshotValue = undefined) {
if (!(eventType in this.eventCallbacks)) {
return;
}
const snapshot =
this.path === DatabaseConstants.connectedPath
? this._dataSnapshot
: new DataSnapshot(() => this._getData(), snapshotValue);
this.eventCallbacks[eventType](snapshot);
}
equalTo(value) {
this._equalToValue = value;
return this;
}
initializeQueryParameters() {
this._orderByChildPath = '';
this._equalToValue = '';
}
off() {
this._eventCallbacks = {};
}
on(eventType, callback) {
this.eventCallbacks[eventType] = callback;
if (eventType === DatabaseConstants.valueEventType) {
setTimeout(() => {
this.triggerEventCallback(eventType);
}, DatabaseConstants.defaultDelayInMilliseconds);
}
return callback;
}
async once(eventType) {
if (!eventType) {
throw new Error('eventType must be provided.');
} else if (typeof eventType !== 'string') {
throw new Error('eventType should be a string.');
}
await delay(DatabaseConstants.defaultDelayInMilliseconds);
return this._dataSnapshot;
}
orderByChild(path) {
this._orderByChildPath = path;
return this;
}
async update(value) {
await delay(DatabaseConstants.defaultDelayInMilliseconds);
this._handleDataUpdate(value);
}
async remove() {
await delay(DatabaseConstants.defaultDelayInMilliseconds);
this._handleDataUpdate(null);
}
async set(value) {
await delay(DatabaseConstants.defaultDelayInMilliseconds);
this._handleDataUpdate(value);
}
}
export default Reference;

View File

@ -1,53 +0,0 @@
/* eslint-disable no-underscore-dangle */
import { v4 as uuidv4 } from 'uuid';
import FunctionsConstants from '../constants/functions';
import HttpsCallableResult from './httpsCallableResult';
import { delay } from '../../../src/utils/index';
const singleton = Symbol('');
const singletonEnforcer = Symbol('');
const deleteUser = async () => {
await delay(FunctionsConstants.defaultDelayInMilliseconds);
return new HttpsCallableResult(true);
};
class Functions {
constructor(enforcer) {
if (enforcer !== singletonEnforcer) {
throw new Error('Cannot construct singleton');
}
this._uuid = uuidv4();
this._httpsCallables = {};
this._httpsCallables[FunctionsConstants.deleteUserFunctionName] =
deleteUser;
}
static get instance() {
if (!this[singleton]) {
this[singleton] = new Functions(singletonEnforcer);
}
return this[singleton];
}
get uuid() {
return this._uuid;
}
httpsCallable(name) {
if (!name) {
throw new Error('name must be provided.');
} else if (typeof name !== 'string') {
throw new Error('name should be a string.');
}
return this._httpsCallables[name];
}
}
export default Functions;

View File

@ -1,19 +0,0 @@
/* eslint-disable no-underscore-dangle */
import { v4 as uuidv4 } from 'uuid';
class HttpsCallableResult {
constructor(data) {
this._uuid = uuidv4();
this._data = data;
}
get data() {
return this._data;
}
get uuid() {
return this._uuid;
}
}
export default HttpsCallableResult;

View File

@ -1,71 +0,0 @@
import React from 'react';
import { delay } from '../src/utils/index';
const Gatsby = jest.requireActual('gatsby');
const imageData = {
images: {
fallback: {
src: `image_src.jpg`,
srcSet: `image_src_set.jpg 1x`,
},
},
layout: `fixed`,
width: 1,
height: 2,
};
const childImageSharp = { gatsbyImageData: imageData };
const useStaticQuery = () => ({
site: {
siteMetadata: {
title: 'Test title',
description: 'Test description',
author: 'Test author',
siteUrl: 'https://testsiteurl/',
},
},
file: {
childImageSharp,
},
onyx: {
childImageSharp,
},
pikachu: {
childImageSharp,
},
gengar: {
childImageSharp,
},
castform: {
childImageSharp,
},
glalie: {
childImageSharp,
},
celebi: {
childImageSharp,
},
});
const defaultDelayInMilliseconds = 100;
const navigate = async () => {
await delay(defaultDelayInMilliseconds);
return Promise.resolve();
};
module.exports = {
...Gatsby,
graphql: jest.fn(),
Link: jest.fn().mockImplementation(({ to, ...rest }) =>
React.createElement('a', {
...rest,
href: to,
}),
),
navigate: jest.fn(navigate),
useStaticQuery,
};

View File

@ -1,3 +0,0 @@
files:
- source: /src/i18n/locales/en.json
translation: /src/i18n/locales/%two_letters_code%.json

View File

@ -1,19 +0,0 @@
{
"rules": {
"resumes": {
".indexOn": "user",
".read": "auth !== null && query.orderByChild === 'user' && query.equalTo === auth.uid",
"$rid": {
".read": "data.child('public').val() === true || data.child('user').val() === auth.uid",
".write": " !data.exists() || data.child('user').val() === auth.uid || (!newData.exists() && data.child('user').val() === auth.uid)"
}
},
"users": {
".indexOn": "isAnonymous",
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}

View File

@ -1,30 +0,0 @@
{
"hosting": [
{
"site": "rxresume",
"public": "public/",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
],
"emulators": {
"functions": {
"port": 5001
},
"hosting": {
"port": 5000
},
"ui": {
"enabled": true
}
},
"functions": {
"source": "functions",
"runtime": "nodejs12"
}
}

View File

@ -1 +0,0 @@
node_modules/

View File

@ -1,207 +0,0 @@
const admin = require('firebase-admin');
const functions = require('firebase-functions');
const puppeteer = require('puppeteer');
admin.initializeApp();
const BASE_URL = 'https://rxresu.me/r/';
function timeout(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
const deleteUserFunctionHandler = async (_, { auth }) => {
if (!auth) {
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called while authenticated.',
);
}
try {
const userId = auth.uid;
if (!userId) {
throw new Error("Could not retrieve the user's unique ID.");
}
const updates = {};
const userResumesDataSnapshot = await admin
.database()
.ref('resumes')
.orderByChild('user')
.equalTo(userId)
.once('value');
const userResumes = userResumesDataSnapshot.val();
if (userResumes) {
Object.keys(userResumes).forEach(async (resumeId) => {
if (resumeId) {
updates[`resumes/${resumeId}`] = null;
}
});
}
const userDataSnapshot = await admin
.database()
.ref(`users/${userId}`)
.once('value');
const user = userDataSnapshot.val();
if (user) {
updates[`users/${userId}`] = null;
}
if (Object.keys(updates).length > 0) {
await admin.database().ref().update(updates);
}
const storageUserFolderPath = `users/${userId}/`;
await admin.storage().bucket().deleteFiles({
prefix: storageUserFolderPath,
});
return true;
} catch (error) {
throw new functions.https.HttpsError('internal', error.message);
}
};
/**
* Tries to navigate the page to a given URL.
*
* @param {puppeteer.Page} page Page.
* @param {string} url URL to navigate page to.
* @param {puppeteer.PuppeteerLifeCycleEvent} waitUntil When to consider navigation succeeded.
* @returns {Promise<string>} Returns null if no error occurred, otherwise returns the error message.
*/
const tryGotoPage = async (page, url, waitUntil) => {
let httpResponse;
try {
httpResponse = await page.goto(url, {
waitUntil,
});
} catch (error) {
return `page.goto (waitUntil: "${waitUntil}") threw an error with message "${error.message}"`;
}
if (httpResponse === null) {
return `page.goto (waitUntil: "${waitUntil}") returned a null response`;
}
if (!httpResponse.ok()) {
return `page.goto (waitUntil: "${waitUntil}") returned a response with HTTP status ${httpResponse.status()} "${httpResponse.statusText()}"`;
}
return null;
};
/**
* Creates a page and navigates to a given URL.
*
* @param {puppeteer.Browser} browser Browser.
* @param {string} url URL to navigate to.
* @returns {Promise<{page: puppeteer.Page, errors: string[]}>} Returns an object with the page if no error occurred, otherwise returns an object with the list of error messages.
*/
const gotoPage = async (browser, url) => {
const errors = [];
const waitUntilArray = ['networkidle0', 'networkidle2'];
for (let index = 0; index < waitUntilArray.length; index++) {
/* eslint-disable no-await-in-loop */
const waitUntil = waitUntilArray[index];
const page = await browser.newPage();
await page.setCacheEnabled(false);
const error = await tryGotoPage(page, url, waitUntil);
if (!error) {
return { page, errors: null };
}
errors.push(error);
await page.close();
}
return { page: null, errors };
};
const printResumeFunctionHandler = async ({ id, type }, { auth }) => {
if (!id) {
throw new functions.https.HttpsError(
'invalid-argument',
'The function must be called with argument "id" containing the resume ID.',
);
}
if (!type) {
throw new functions.https.HttpsError(
'invalid-argument',
'The function must be called with argument "type" containing the type of resume.',
);
}
if (!auth) {
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called while authenticated.',
);
}
try {
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox'],
});
const url = BASE_URL + id;
const { page, errors } = await gotoPage(browser, url);
if (errors && errors.length > 0) {
throw new Error(errors.join(' - '));
}
await timeout(6000);
await page.emulateMediaType('print');
let pdf;
if (type === 'single') {
const height = await page.evaluate(() => {
const { body } = document;
const html = document.documentElement;
const maxHeight = Math.max(
body.scrollHeight,
body.offsetHeight,
html.clientHeight,
html.scrollHeight,
html.offsetHeight,
);
return maxHeight;
});
pdf = await page.pdf({
printBackground: true,
width: `21cm`,
height: `${height}px`,
pageRanges: '1',
});
} else {
pdf = await page.pdf({
format: 'A4',
printBackground: true,
});
}
await browser.close();
return Buffer.from(pdf).toString('base64');
} catch (error) {
throw new functions.https.HttpsError('internal', error.message);
}
};
exports.deleteUser = functions
.runWith({ memory: '256MB' })
.https.onCall(deleteUserFunctionHandler);
exports.printResume = functions
.runWith({ memory: '1GB' })
.https.onCall(printResumeFunctionHandler);

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +0,0 @@
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"serve": "firebase emulators:start --only functions",
"shell": "firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"engines": {
"node": "14"
},
"dependencies": {
"firebase-admin": "^9.9.0",
"firebase-functions": "^3.14.1",
"puppeteer": "10.0.0"
},
"devDependencies": {
"firebase-functions-test": "^0.3.0"
},
"private": true
}

View File

@ -1,46 +0,0 @@
/* eslint-disable import/no-extraneous-dependencies */
import './src/i18n';
import './src/styles/forms.css';
import './src/styles/global.css';
import './src/styles/shadows.css';
import './src/styles/tailwind.css';
import './src/styles/toastify.css';
import './src/utils/dayjs';
import 'animate.css';
import 'firebase/analytics';
import 'firebase/auth';
import 'firebase/database';
import 'firebase/functions';
import 'firebase/storage';
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core';
import React from 'react';
import { DatabaseProvider } from './src/contexts/DatabaseContext';
import { ModalProvider } from './src/contexts/ModalContext';
import { ResumeProvider } from './src/contexts/ResumeContext';
import { SettingsProvider } from './src/contexts/SettingsContext';
import { StorageProvider } from './src/contexts/StorageContext';
import { UserProvider } from './src/contexts/UserContext';
const theme = createMuiTheme({
typography: {
fontWeightRegular: 500,
fontFamily: ['Montserrat', 'sans-serif'].join(','),
},
});
// eslint-disable-next-line import/prefer-default-export
export const wrapRootElement = ({ element }) => (
<SettingsProvider>
<MuiThemeProvider theme={theme}>
<ModalProvider>
<UserProvider>
<DatabaseProvider>
<ResumeProvider>
<StorageProvider>{element}</StorageProvider>
</ResumeProvider>
</DatabaseProvider>
</UserProvider>
</ModalProvider>
</MuiThemeProvider>
</SettingsProvider>
);

View File

@ -1,136 +0,0 @@
require('dotenv').config();
module.exports = {
siteMetadata: {
title: 'Reactive Resume',
siteUrl: 'https://rxresu.me',
description: 'A free and open source resume builder.',
version: '2.7.10',
},
flags: { PRESERVE_WEBPACK_CACHE: true },
plugins: [
'gatsby-plugin-react-helmet',
{
resolve: 'gatsby-plugin-eslint',
options: {
test: /\.js$|\.jsx$/,
exclude: /(node_modules|.cache|public|_this_is_virtual_fs_path_)/,
stages: ['develop'],
options: {
emitWarning: true,
failOnError: false,
},
},
},
{
resolve: 'gatsby-plugin-manifest',
options: {
name: 'Reactive Resume',
short_name: 'Reactive Resume',
start_url: '/',
background_color: '#212121',
icon: `static/images/logo.png`,
orientation: 'landscape',
theme_color: '#212121',
display: 'standalone',
},
},
`gatsby-plugin-image`,
`gatsby-plugin-offline`,
{
resolve: 'gatsby-plugin-webfonts',
options: {
fonts: {
google: [
{
family: 'Lato',
variants: ['400', '700'],
subsets: ['latin-ext'],
},
{
family: 'Montserrat',
variants: ['400', '500', '600', '700'],
subsets: ['latin-ext'],
},
{
family: 'Nunito',
variants: ['400', '600', '700'],
subsets: ['latin-ext'],
},
{
family: 'Open Sans',
variants: ['400', '600', '700'],
subsets: ['latin-ext'],
},
{
family: 'Raleway',
variants: ['400', '500', '700'],
subsets: ['latin-ext'],
},
{
family: 'Rubik',
variants: ['400', '500', '700'],
subsets: ['latin-ext'],
},
{
family: 'Source Sans Pro',
variants: ['400', '600', '700'],
subsets: ['latin-ext'],
},
{
family: 'Titillium Web',
variants: ['400', '600', '700'],
subsets: ['latin-ext'],
},
],
},
},
},
{
resolve: 'gatsby-plugin-create-client-paths',
options: { prefixes: ['/app/*'] },
},
{
resolve: 'gatsby-plugin-material-ui',
options: {
stylesProvider: {
injectFirst: true,
},
},
},
'gatsby-plugin-postcss',
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'articles',
path: `${__dirname}/src/articles`,
},
},
'gatsby-transformer-remark',
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'images',
path: `${__dirname}/static/images/`,
},
},
'gatsby-plugin-sharp',
'gatsby-transformer-sharp',
{
resolve: 'gatsby-plugin-firebase',
options: {
credentials: {
apiKey: process.env.FIREBASE_APIKEY,
authDomain: process.env.FIREBASE_AUTHDOMAIN,
databaseURL: process.env.FIREBASE_DATABASEURL,
projectId: process.env.FIREBASE_PROJECTID,
storageBucket: process.env.FIREBASE_STORAGEBUCKET,
messagingSenderId: process.env.FIREBASE_MESSAGINGSENDERID,
appId: process.env.FIREBASE_APPID,
measurementId: process.env.FIREBASE_MEASUREMENTID,
},
},
},
'gatsby-plugin-sitemap',
],
};

View File

@ -1,78 +0,0 @@
exports.onCreateWebpackConfig = ({ stage, actions, plugins, getConfig }) => {
actions.setWebpackConfig({
resolve: {
alias: {
path: require.resolve('path-browserify'),
},
fallback: {
fs: false,
},
},
});
if (stage === 'build-javascript' || stage === 'develop') {
actions.setWebpackConfig({
plugins: [plugins.provide({ process: 'process/browser' })],
});
}
const config = getConfig();
const miniCssExtractPlugin = config.plugins.find(
(plugin) => plugin.constructor.name === 'MiniCssExtractPlugin',
);
miniCssExtractPlugin && (miniCssExtractPlugin.options.ignoreOrder = true);
actions.replaceWebpackConfig(config);
if (stage === 'build-html') {
actions.setWebpackConfig({
externals: [/^firebase/],
});
}
};
exports.onCreatePage = async ({ page, actions }) => {
const { createPage } = actions;
if (page.path.match(/^\/r/)) {
page.matchPath = '/r/*';
createPage(page);
}
};
exports.createPages = async ({ actions, graphql, reporter }) => {
const { createPage } = actions;
const blogTemplate = require.resolve(`./src/components/Blog.js`);
const result = await graphql(`
{
allMarkdownRemark(
sort: { order: DESC, fields: [frontmatter___date] }
limit: 1000
) {
edges {
node {
frontmatter {
slug
}
}
}
}
}
`);
if (result.errors) {
reporter.panicOnBuild(`Error while running GraphQL query.`);
return;
}
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
createPage({
path: node.frontmatter.slug,
component: blogTemplate,
context: {
slug: node.frontmatter.slug,
},
});
});
};

View File

@ -1,5 +0,0 @@
const babelOptions = {
presets: ['babel-preset-gatsby'],
};
module.exports = require('babel-jest').default.createTransformer(babelOptions);

View File

@ -1,39 +0,0 @@
const esModules = ['gatsby', 'nanoevents'].join('|');
module.exports = {
testRegex: '/*.test.js$',
collectCoverage: true,
collectCoverageFrom: [
'**/*.{js,jsx}',
'!\\.cache/**',
'!node_modules/**',
'!public/**',
'!test-coverage/**',
],
coverageDirectory: 'test-coverage',
coverageThreshold: {
global: {
branches: 0,
functions: 0,
lines: 0,
statements: 0,
},
},
verbose: true,
transform: {
'^.+\\.jsx?$': `<rootDir>/jest-preprocess.js`,
},
moduleNameMapper: {
'.+\\.(css|styl|less|sass|scss)$': `identity-obj-proxy`,
'.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': `<rootDir>/__mocks__/file-mock.js`,
},
testPathIgnorePatterns: [`node_modules`, `\\.cache`],
transformIgnorePatterns: [`node_modules/(?!${esModules})`],
globals: {
__PATH_PREFIX__: ``,
},
testURL: `http://localhost`,
setupFiles: [`<rootDir>/loadershim.js`],
setupFilesAfterEnv: [`<rootDir>/jest.setup.js`],
testEnvironment: 'jsdom',
};

View File

@ -1,4 +0,0 @@
import '@testing-library/jest-dom/extend-expect';
import fetchMock from 'jest-fetch-mock';
fetchMock.enableMocks();

View File

@ -1,4 +0,0 @@
// eslint-disable-next-line no-underscore-dangle
global.___loader = {
enqueue: jest.fn(),
};

58788
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,105 +0,0 @@
{
"name": "reactive-resume",
"private": true,
"description": "A free and open-source resume builder.",
"version": "2.0.0",
"license": "MIT",
"scripts": {
"build": "gatsby build",
"clean": "gatsby clean",
"deploy": "firebase deploy",
"develop": "gatsby develop -H 0.0.0.0",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"",
"lint:fix": "eslint --fix .",
"lint": "eslint .",
"serve": "gatsby serve -H 0.0.0.0",
"start": "npm run develop",
"test": "jest",
"prepare": "husky install"
},
"dependencies": {
"@material-ui/core": "^4.11.4",
"@reach/router": "^1.3.4",
"animate.css": "^4.1.1",
"array-move": "^3.0.1",
"autoprefixer": "^10.2.6",
"classnames": "^2.3.1",
"dayjs": "^1.10.5",
"dotenv": "^10.0.0",
"downloadjs": "^1.4.7",
"firebase": "^8.6.8",
"formik": "^2.2.9",
"gatsby": "^3.8.0",
"gatsby-plugin-create-client-paths": "^3.8.0",
"gatsby-plugin-firebase": "^0.2.0-beta.4",
"gatsby-plugin-image": "^1.8.0",
"gatsby-plugin-manifest": "^3.8.0",
"gatsby-plugin-material-ui": "^3.0.1",
"gatsby-plugin-offline": "^4.8.0",
"gatsby-plugin-postcss": "^4.8.0",
"gatsby-plugin-react-helmet": "^4.8.0",
"gatsby-plugin-sharp": "^3.8.0",
"gatsby-plugin-sitemap": "^4.4.0",
"gatsby-plugin-webfonts": "^2.1.0",
"gatsby-source-filesystem": "^3.8.0",
"gatsby-source-gravatar": "^1.0.1",
"gatsby-transformer-remark": "^4.5.0",
"gatsby-transformer-sharp": "^3.8.0",
"i18next": "^20.3.2",
"lodash": "^4.17.21",
"nanoevents": "^6.0.0",
"path-browserify": "^1.0.1",
"postcss": "^8.3.5",
"process": "^0.11.10",
"react": "^17.0.2",
"react-beautiful-dnd": "^13.1.0",
"react-dom": "^17.0.2",
"react-helmet": "^6.1.0",
"react-i18next": "^11.11.0",
"react-icons": "^4.2.0",
"react-markdown": "^6.0.2",
"react-scroll": "^1.8.2",
"react-toastify": "^7.0.4",
"remark-gfm": "^1.0.0",
"short-unique-id": "^4.3.3",
"uuid": "^8.3.2",
"yup": "^0.32.9"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.0.0",
"babel-jest": "^27.0.5",
"babel-preset-gatsby": "^1.8.0",
"eslint": "^7.29.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-config-prettier": "^8.3.0",
"eslint-loader": "^4.0.2",
"eslint-plugin-jest": "^24.3.6",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-sort-imports-es6-autofix": "^0.6.0",
"eslint-webpack-plugin": "^2.5.4",
"gatsby-plugin-eslint": "^3.0.0",
"husky": "^6.0.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^27.0.5",
"jest-fetch-mock": "^3.0.3",
"lint-staged": "^11.0.0",
"prettier": "2.3.2",
"stylelint": "^13.13.1",
"stylelint-config-standard": "^22.0.0",
"tailwindcss": "^2.2.4"
},
"repository": {
"type": "git",
"url": "https://github.com/AmruthPillai/Reactive-Resume"
},
"bugs": {
"url": "https://github.com/AmruthPillai/Reactive-Resume/issues"
},
"lint-staged": {
"*.js": "eslint --cache --fix",
"*.css": "stylelint --fix"
}
}

View File

@ -1,6 +0,0 @@
module.exports = {
plugins: {
autoprefixer: {},
tailwindcss: {},
},
};

View File

@ -1,19 +0,0 @@
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
autoindex off;
charset urtf-8;
error_page 404 /404.html;
location ~* \.(html)$ {
add_header Cache-Control "no-store";
expires off;
}
rewrite ^([^.\?]*[^/])$ $1/ permanent;
try_files $uri $uri/ $uri/index.html =404;
}

View File

@ -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 youre a great fit for the role.
#### Be conscious of whats in the view
While so many of us are now working remotely and using Zoom or other platforms for our meetings, weve 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, its 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 whats 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 youre 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 youre 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 youre 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 theyre looking for in the role and be ready to talk about why youre 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 youre talented, experienced and have so much value to offer and that the interview is a two-way street
Dont 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.

View File

@ -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 doesnt meet the requirements of a companys 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 its looking for.
For example, this could mean using an easy-to-read resume format, or removing objects such as tables or images because theyre 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.

View File

@ -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 youre applying for an admin position, but if youre 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
Youre a designer, so your resume should follow the latest trends in typography, right? Wrong! The aim of any resume should be legibility, so its 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, youre waffling and including too much stuff.
Dont 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, youre pretty much guaranteeing that the recipient will not consider you for the post. If you work with motion, stills are perfect, unless youve 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 youre really confident and sure about what youre 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
Weve had resumes written on scrunched up paper; arriving in the form of a jigsaw; and playing cards. Weve 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)

View File

@ -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. Its true that economists are predicting a recession, but career experts say its 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 theyve been open for a while. That doesnt mean they wont 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 youre 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 youll 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 youre looking for a job in marketing, you could join LinkedIns 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 havent 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 havent used it in a few years, or if youre 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.

View File

@ -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
}
}
}
`;

View File

@ -1,11 +0,0 @@
.container {
@apply leading-loose my-8;
}
.container p {
@apply my-4;
}
.container h4 {
@apply font-medium text-xl;
}

View File

@ -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);

View File

@ -1,11 +0,0 @@
@media screen {
.container {
width: 210mm;
height: 297mm;
overflow: scroll;
margin: 2rem auto;
box-shadow: var(--shadow);
@apply bg-white rounded;
}
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -1,6 +0,0 @@
.container {
z-index: 10;
@apply w-full h-screen overflow-scroll p-8;
@apply grid gap-8;
}

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

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