├── .gitignore
├── README.md
├── addQuestion.js
├── images
└── example.png
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
├── question_answer_pairs
│ ├── qa_pair1
│ │ ├── answer.md
│ │ └── question.md
│ ├── qa_pair2
│ │ ├── answer.md
│ │ └── question.md
│ ├── qa_pair3
│ │ ├── answer.md
│ │ └── question.md
│ ├── qa_pair4
│ │ ├── answer.md
│ │ └── question.md
│ └── qa_pair5
│ │ ├── answer.md
│ │ └── question.md
└── robots.txt
├── run.js
├── setup.js
└── src
├── App.css
├── App.js
├── App.test.js
├── CodeBlock.js
├── Global.css
├── Prism.css
├── fonts
├── ptsans_bold.woff2
├── ptsans_bolditalic.woff2
├── ptsans_italic.woff2
└── ptsans_regular.woff2
├── icons.js
├── index.css
├── index.js
├── qa_pairs.js
├── serviceWorker.js
└── setupTests.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Interview Flashcards App
2 |
3 | An app that allows you to write (or borrow) practice interview questions and answers in **markdown**, and then test yourself with the mobile app on-the-go!
4 |
5 | **Mobile App Demo: [https://interview-flashcards.netlify.com/](https://interview-flashcards.netlify.com/)**
6 |
7 | 
8 |
9 | ### Why this one?
10 | There are _plenty_ of flashcard apps out there to help developers with interview prep. However, I noticed a few things about most of them that I wasn't thrilled about:
11 |
12 | * Requires an account
13 | * Must use their WYSIWYG to submit custom questions and answers
14 | * No code syntax highlighting
15 | * Not built for mobile use
16 | * Advertisements and/or paid plans
17 | * Overly complex
18 |
19 | I longed for an app where I could simply write my questions and answers in **markdown files** and then use a **simple mobile app** to practice with. Besides, most collections of open source interview questions out there exist as markdown files (i.e. [https://github.com/yangshun/front-end-interview-handbook](https://github.com/yangshun/front-end-interview-handbook)). Borrow or write the interview questions that are relevant to you!
20 |
21 | ### How to Use
22 |
23 | 1. Clone or fork the repo
24 | 2. `cd interview-flashcards && npm start` (bootstrapped with create-react-app)
25 | 3. Add any questions and answers to the `public/question_answer_pairs` directory
26 | 4. `npm run build` for production or `npm start` to use locally
27 | 5. Push to github and then publish to a hosting platform of your choice ([Netlify](https://www.netlify.com/) perhaps), or just run locally and hit your computers IP on port 3000 on your mobile device
28 |
29 | ## Available Scripts
30 |
31 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
32 |
33 | In the project directory, you can run:
34 |
35 | ### `npm start`
36 |
37 | Runs the app in the development mode.
38 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
39 |
40 | The page will reload if you make edits.
41 | You will also see any lint errors in the console.
42 |
43 | ### `npm run add (num: optional)`
44 |
45 | Writes additional qa_pair directories to the `/question_answer_pairs` directory.
46 |
47 | ### `npm run setup`
48 |
49 | Reads the `/question_answer_pairs` directory and updates the mobile app. Run this when you delete any qa_pair directories.
50 |
51 | ### Run `npm run` to see more
52 |
53 | ### Supported Versions:
54 |
55 | * Node: v11.15.0
56 | * NPM: v6.7.0
--------------------------------------------------------------------------------
/addQuestion.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 |
4 | const questionsDir = path.join(__dirname, '/public/question_answer_pairs');
5 |
6 | async function createDirectory() {
7 | const filesNum = fs.readdirSync(questionsDir).length;
8 | const num = filesNum + 1;
9 | const directoryName = path.join(questionsDir, `qa_pair${num}`);
10 | fs.mkdirSync(directoryName);
11 | fs.writeFileSync(path.join(directoryName, 'question.md'), `## Question ${num}`);
12 | fs.writeFileSync(path.join(directoryName, 'answer.md'), `## Answer ${num}`);
13 | console.log(`Successfully created a new directory: ${directoryName}`);
14 | }
15 |
16 | if (process.argv[2] && !isNaN(process.argv[2])) {
17 | (async () => {
18 | const num = Number(process.argv[2]);
19 | for (let x = 0; x < num; x++) {
20 | await createDirectory(x + 1);
21 | }
22 | })();
23 | } else {
24 | createDirectory();
25 | }
--------------------------------------------------------------------------------
/images/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sdennett55/interview-flashcards/037c71915a3fd50a5f869373482b93eebebda9bf/images/example.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend-interview",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^4.2.4",
7 | "@testing-library/react": "^9.4.0",
8 | "@testing-library/user-event": "^7.2.1",
9 | "classnames": "^2.2.6",
10 | "react": "^16.12.0",
11 | "react-dom": "^16.12.0",
12 | "react-markdown": "^4.3.1",
13 | "react-responsive-carousel": "^3.1.51",
14 | "react-scripts": "3.3.1",
15 | "react-syntax-highlighter": "^12.2.1"
16 | },
17 | "scripts": {
18 | "start": "node setup.js && react-scripts start",
19 | "build": "node setup.js && react-scripts build",
20 | "setup": "node setup.js",
21 | "test": "node test.js",
22 | "eject": "react-scripts eject",
23 | "add": "node run"
24 | },
25 | "eslintConfig": {
26 | "extends": "react-app"
27 | },
28 | "browserslist": {
29 | "production": [
30 | ">0.2%",
31 | "not dead",
32 | "not op_mini all"
33 | ],
34 | "development": [
35 | "last 1 chrome version",
36 | "last 1 firefox version",
37 | "last 1 safari version"
38 | ]
39 | },
40 | "devDependencies": {
41 | "prettier": "^1.19.1",
42 | "raw.macro": "^0.3.0"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sdennett55/interview-flashcards/037c71915a3fd50a5f869373482b93eebebda9bf/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | Interview Flashcards
28 |
29 |
30 |
31 |
32 |
42 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sdennett55/interview-flashcards/037c71915a3fd50a5f869373482b93eebebda9bf/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sdennett55/interview-flashcards/037c71915a3fd50a5f869373482b93eebebda9bf/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Interview Flashcards",
3 | "name": "Interview Flashcards App",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "fullscreen",
23 | "theme_color": "#ffffff",
24 | "background_color": "#1b262c"
25 | }
26 |
--------------------------------------------------------------------------------
/public/question_answer_pairs/qa_pair1/answer.md:
--------------------------------------------------------------------------------
1 | There's no simple explanation for `this`; it is one of the most confusing concepts in JavaScript. A hand-wavey explanation is that the value of `this` depends on how the function is called. I have read many explanations on `this` online, and I found [Arnav Aggrawal](https://medium.com/@arnav_aggarwal)'s explanation to be the clearest. The following rules are applied:
2 |
3 | 1. If the `new` keyword is used when calling the function, `this` inside the function is a brand new object.
4 | 2. If `apply`, `call`, or `bind` are used to call/create a function, `this` inside the function is the object that is passed in as the argument.
5 | 3. If a function is called as a method, such as `obj.method()` — `this` is the object that the function is a property of.
6 | 4. If a function is invoked as a free function invocation, meaning it was invoked without any of the conditions present above, `this` is the global object. In a browser, it is the `window` object. If in strict mode (`'use strict'`), `this` will be `undefined` instead of the global object.
7 | 5. If multiple of the above rules apply, the rule that is higher wins and will set the `this` value.
8 | 6. If the function is an ES2015 arrow function, it ignores all the rules above and receives the `this` value of its surrounding scope at the time it is created.
9 |
10 | For an in-depth explanation, do check out his [article on Medium](https://codeburst.io/the-simple-rules-to-this-in-javascript-35d97f31bde3).
--------------------------------------------------------------------------------
/public/question_answer_pairs/qa_pair1/question.md:
--------------------------------------------------------------------------------
1 | ### Explain how `this` works in JavaScript
--------------------------------------------------------------------------------
/public/question_answer_pairs/qa_pair2/answer.md:
--------------------------------------------------------------------------------
1 | A higher-order function is any function that takes one or more functions as arguments, which it uses to operate on some data, and/or returns a function as a result. Higher-order functions are meant to abstract some operation that is performed repeatedly. The classic example of this is `map`, which takes an array and a function as arguments. `map` then uses this function to transform each item in the array, returning a new array with the transformed data. Other popular examples in JavaScript are `forEach`, `filter`, and `reduce`. A higher-order function doesn't just need to be manipulating arrays as there are many use cases for returning a function from another function. `Function.prototype.bind` is one such example in JavaScript.
--------------------------------------------------------------------------------
/public/question_answer_pairs/qa_pair2/question.md:
--------------------------------------------------------------------------------
1 | ### What is the definition of a higher-order function?
--------------------------------------------------------------------------------
/public/question_answer_pairs/qa_pair3/answer.md:
--------------------------------------------------------------------------------
1 | This depends on the JavaScript environment.
2 |
3 | On the client (browser environment), as long as the variables/functions are declared in the global scope (`window`), all scripts can refer to them. Alternatively, adopt the Asynchronous Module Definition (AMD) via RequireJS for a more modular approach.
4 |
5 | On the server (Node.js), the common way has been to use CommonJS. Each file is treated as a module and it can export variables and functions by attaching them to the `module.exports` object.
6 |
7 | ES2015 defines a module syntax which aims to replace both AMD and CommonJS. This will eventually be supported in both browser and Node environments.
--------------------------------------------------------------------------------
/public/question_answer_pairs/qa_pair3/question.md:
--------------------------------------------------------------------------------
1 | ### How can you share code between files?
--------------------------------------------------------------------------------
/public/question_answer_pairs/qa_pair4/answer.md:
--------------------------------------------------------------------------------
1 | **Pros**
2 |
3 | - Easier change detection - Object equality can be determined in a performant and easy manner through referential equality. This is useful for comparing object differences in React and Redux.
4 | - Programs with immutable objects are less complicated to think about, since you don't need to worry about how an object may evolve over time.
5 | - Defensive copies are no longer necessary when immutable objects are returning from or passed to functions, since there is no possibility an immutable object will be modified by it.
6 | - Easy sharing via references - One copy of an object is just as good as another, so you can cache objects or reuse the same object multiple times.
7 | - Thread-safe - Immutable objects can be safely used between threads in a multi-threaded environment since there is no risk of them being modified in other concurrently running threads.
8 | - Using libraries like ImmmutableJS, objects are modified using structural sharing and less memory is needed for having multiple objects with similar structures.
9 |
10 | **Cons**
11 |
12 | - Naive implementations of immutable data structures and its operations can result in extremely poor performance because new objects are created each time. It is recommended to use libraries for efficient immutable data structures and operations that leverage on structural sharing.
13 | - Allocation (and deallocation) of many small objects rather than modifying existing ones can cause a performance impact. The complexity of either the allocator or the garbage collector usually depends on the number of objects on the heap.
14 | - Cyclic data structures such as graphs are difficult to build. If you have two objects which can't be modified after initialization, how can you get them to point to each other?
--------------------------------------------------------------------------------
/public/question_answer_pairs/qa_pair4/question.md:
--------------------------------------------------------------------------------
1 | ### What are the pros and cons of immutability?
--------------------------------------------------------------------------------
/public/question_answer_pairs/qa_pair5/answer.md:
--------------------------------------------------------------------------------
1 | `==` is the abstract equality operator while `===` is the strict equality operator. The `==` operator will compare for equality after doing any necessary type conversions. The `===` operator will not do type conversion, so if two values are not the same type `===` will simply return `false`. When using `==`, funky things can happen, such as:
2 |
3 | ```js
4 | 1 == "1"; // true
5 | 1 == [1]; // true
6 | 1 == true; // true
7 | 0 == ""; // true
8 | 0 == "0"; // true
9 | 0 == false; // true
10 | ```
11 |
12 | My advice is never to use the `==` operator, except for convenience when comparing against `null` or `undefined`, where `a == null` will return `true` if `a` is `null` or `undefined`.
13 |
14 | ```js
15 | var a = null;
16 | console.log(a == null); // true
17 | console.log(a == undefined); // true
18 | ```
--------------------------------------------------------------------------------
/public/question_answer_pairs/qa_pair5/question.md:
--------------------------------------------------------------------------------
1 | ### What is the difference between `==` and `===`?
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/run.js:
--------------------------------------------------------------------------------
1 | const execSync = require('child_process').execSync;
2 |
3 | const arg = process.argv[2] || 1;
4 |
5 | execSync('node addQuestion.js ' + arg, {stdio:[0, 1, 2]});
6 | execSync('node setup.js', {stdio:[0, 1, 2]});
--------------------------------------------------------------------------------
/setup.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var fs = require('fs');
3 | var prettier = require('prettier');
4 |
5 | var questionsFolder = path.join(__dirname, '/public/question_answer_pairs');
6 |
7 | // Get paths to each question directory
8 | fs.readdir(questionsFolder, (error, directories) => {
9 | if (error) {
10 | console.log("There was an error reading the public/question_answer_pairs directory: ", error);
11 | }
12 |
13 | const dirObjects = directories.map(directory => {
14 | return `{question: '/question_answer_pairs/${directory}/question.md', answer: '/question_answer_pairs/${directory}/answer.md'},`
15 | }).join('');
16 |
17 | const qaPairsFile = `
18 | // PLEASE NOTE THAT THIS IS AN AUTO-GENERATED FILE.
19 | // PLEASE DO NOT UPDATE
20 | const qaPairs = [
21 | ${dirObjects}
22 | ];
23 |
24 | export default qaPairs;
25 | `;
26 |
27 | fs.writeFileSync(path.join(__dirname, '/src/qa_pairs.js'), prettier.format(qaPairsFile, { parser: 'babel' }));
28 | });
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .Slide {
2 | display: flex;
3 | justify-content: center;
4 | align-items: center;
5 | flex-direction: column;
6 | height: 100vh;
7 | -webkit-perspective: 300vw;
8 | perspective: 300vw;
9 | background-color: #1b262c;
10 | }
11 | .Card {
12 | display: flex;
13 | flex-wrap: wrap;
14 | justify-content: center;
15 | align-items: center;
16 | flex-direction: column;
17 | height: 100vh;
18 | color: #fff;
19 | width: 100%;
20 | padding: 1em;
21 | border: 0;
22 | -webkit-overflow-scrolling: touch;
23 | width: calc(100% - 4rem);
24 | margin: 2rem 0;
25 | border-radius: 10px;
26 | cursor: pointer;
27 | position: relative;
28 | -webkit-transition: -webkit-transform .7s;
29 | transition: -webkit-transform .7s;
30 | transition: transform .7s;
31 | transition: transform .7s,-webkit-transform .7s;
32 | -webkit-transform-style: preserve-3d;
33 | transform-style: preserve-3d;
34 | -webkit-transform: translateZ(0);
35 | transform: translateZ(0);
36 | }
37 | .is-flipped .Card {
38 | -webkit-transform: rotateY(180deg) translateZ(0);
39 | transform: rotateY(180deg) translateZ(0);
40 | }
41 | .Card-front,
42 | .Card-back {
43 | position: absolute;
44 | top: 0;
45 | left: 0;
46 | width: 100%;
47 | height: 100%;
48 | display: flex;
49 | flex-direction: column;
50 | align-items: center;
51 | justify-content: center;
52 | overflow: hidden;
53 | -webkit-backface-visibility: hidden;
54 | backface-visibility: hidden;
55 | -webkit-transform: translateZ(0);
56 | transform: translateZ(0);
57 | border-radius: 5px;
58 | box-shadow: 0 0 15px rgba(0,0,0,.5);
59 | padding: 2em;
60 | line-height: 1.1;
61 | }
62 | .Card-front {
63 | color: #fff;
64 | text-shadow: 1px 1px 3px rgba(0,0,0,.5);
65 | font-size: 1.75em;
66 | overflow: hidden;
67 | background-color: #3282b8;
68 | }
69 | .Card-front::before {
70 | content: '';
71 | position: absolute;
72 | top: 0;
73 | left: 0;
74 | width: 100%;
75 | height: 100%;
76 | z-index: -1;
77 | opacity: .4;
78 | background-color: #3282b8;
79 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120' viewBox='0 0 120 120'%3E%3Cpolygon fill='%23ffffff' fill-opacity='.1' points='120 0 120 60 90 30 60 0 0 0 0 0 60 60 0 120 60 120 90 90 120 60 120 0'/%3E%3C/svg%3E");
80 | }
81 | .Card-front:not(.Card-front--noNumbers)::after {
82 | content: '';
83 | position: absolute;
84 | top: -2.5em;
85 | left: -2.5em;
86 | width: 5em;
87 | height: 5em;
88 | border-radius: 50%;
89 | background: #0f4c75;
90 | z-index: -1;
91 | opacity: .8;
92 | }
93 | .Card-back {
94 | color: #3282b8;
95 | font-size: 1.25em;
96 | -webkit-transform: translateZ(0) rotateY(180deg);
97 | transform: translateZ(0) rotateY(180deg);
98 | background-color: #fff;
99 | overflow-y: auto;
100 | -webkit-overflow-scrolling: touch;
101 | justify-content: flex-start;
102 | text-align: left;
103 | line-height: normal;
104 | color: #1b262c;
105 | align-items: flex-start;
106 | font-size: 1rem;
107 | }
108 | @media (orientation: landscape) {
109 | .Card-back {
110 | padding: 2em 4em;
111 | }
112 | }
113 | .Card-back a {
114 | color: #165783;
115 | font-weight: bold;
116 | }
117 | .Card-backInner {
118 | max-width: 1200px;
119 | margin: 0 auto;
120 | }
121 | .Card-reshuffleBtn {
122 | position: absolute;
123 | bottom: 1rem;
124 | right: 1rem;
125 | background-color: rgba(15,76,117, .8);
126 | border-radius: 50%;
127 | width: 2em;
128 | height: 2em;
129 | display: flex;
130 | align-items: center;
131 | justify-content: center;
132 | -webkit-appearance: none;
133 | appearance: none;
134 | border: 0;
135 | padding: 0;
136 | font: inherit;
137 | }
138 | .Card-reshuffleIcon {
139 | width: 80%;
140 | height: 80%;
141 | fill: #fff;
142 | }
143 | .Card-reshuffleBtn:hover,
144 | .Card-reshuffleBtn:focus {
145 | background-color: #fff;
146 | }
147 | .Card-reshuffleBtn:focus {
148 | outline: 0;
149 | }
150 | .Card-reshuffleBtn:hover .Card-reshuffleIcon,
151 | .Card-reshuffleBtn:focus .Card-reshuffleIcon {
152 | fill: #3282b8;
153 | }
154 | .Card-reshuffleNum {
155 | position: absolute;
156 | top: 50%;
157 | left: 50%;
158 | transform: translate(-50%, -50%);
159 | font-size: .5em;
160 | text-align: center;
161 | color: #fff;
162 | }
163 | .Card-reshuffleBtn:hover .Card-reshuffleNum,
164 | .Card-reshuffleBtn:focus .Card-reshuffleNum {
165 | color: #3282b8
166 | }
167 | .Card-num {
168 | position: absolute;
169 | top: 13px;
170 | left: 2px;
171 | width: 55px;
172 | text-align: center;
173 | font-weight: bold;
174 | font-size: .8em;
175 | letter-spacing: .02em;
176 | }
177 | .Card-icon {
178 | position: absolute;
179 | width: 30px;
180 | height: 30px;
181 | bottom: 3rem;
182 | background: 0;
183 | border: 0;
184 | font: inherit;
185 | padding: 0;
186 | opacity: 0;
187 | transition: opacity 0s;
188 | pointer-events: none;
189 | transform: translateZ(0);
190 | }
191 | .Card-icon--left {
192 | left: 3rem;
193 | fill: #fd5e53;
194 | }
195 | .Card-icon--right {
196 | right: 3rem;
197 | width: 35px;
198 | height: 31px;
199 | fill: #21bf73;
200 | }
201 | .Card-refreshBtn {
202 | background: rgba(15,76,117, .8);
203 | color: #fff;
204 | border: 0;
205 | border-radius: 10px;
206 | padding: 1em;
207 | font: inherit;
208 | }
209 | .is-flipped .Card-icon {
210 | opacity: 1;
211 | transition: opacity .3s .7s;
212 | pointer-events: all;
213 | }
214 | .Card-front code {
215 | background: rgba(255,255,255,.9);
216 | color: #3282b8;
217 | text-shadow: none;
218 | }
219 | .Card:focus {
220 | outline: 0;
221 | -webkit-highlight-color: none;
222 | -webkit-tap-highlight-color: none;
223 | }
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import qaPairs from "./qa_pairs";
3 | import CodeBlock from './CodeBlock';
4 | import { Carousel } from 'react-responsive-carousel';
5 | import cx from 'classnames';
6 | import ReactMarkdown from 'react-markdown';
7 | import { CheckIcon, XIcon, RefreshIcon } from './icons';
8 | import "react-responsive-carousel/lib/styles/carousel.min.css";
9 | import "./Global.css";
10 | import "./Prism.css";
11 | import './App.css';
12 |
13 | const initialData = [
14 | { question: '## Loading...', answer: '## Loading Answer...' },
15 | { question: '## Loading...', answer: '## Loading Answer...' },
16 | { question: '## Loading...', answer: '## Loading Answer...' },
17 | ];
18 |
19 | const alterData = (data, selectedItem) => data.map((x, index) => {
20 | var temp = { ...x };
21 | if (index === selectedItem) {
22 | temp.isShowingAnswer = !x.isShowingAnswer;
23 | } else {
24 | temp.isShowingAnswer = false;
25 | }
26 |
27 | return temp;
28 | });
29 |
30 | const resetData = data => data.map(x => {
31 | var temp = { ...x };
32 | temp.isShowingAnswer = false;
33 | return temp;
34 | });
35 |
36 | const goToNextCard = ({ e, filteredData, selectedItem, setSelectedItem }) => {
37 | if (e) {
38 | e.stopPropagation();
39 | }
40 |
41 | if (selectedItem < filteredData.length - 1) {
42 | setSelectedItem(selectedItem + 1);
43 | } else {
44 | setSelectedItem(0);
45 | }
46 | }
47 |
48 | const handleStatusClick = ({ e, filteredData, setFilteredData, selectedItem, setSelectedItem, status }) => {
49 | e.stopPropagation();
50 |
51 | if (status === 'good') {
52 | const temp = [...filteredData].map(x => ({ ...x }));
53 | temp.splice(selectedItem, 1);
54 | setFilteredData(temp);
55 | } else {
56 | goToNextCard({ selectedItem, filteredData, setSelectedItem });
57 | }
58 | }
59 |
60 | const fullRefresh = ({ e, data, setFilteredData, setSelectedItem }) => {
61 | if (e) {
62 | e.stopPropagation();
63 | }
64 | setFilteredData(data);
65 | };
66 |
67 | function App() {
68 | const [data, setData] = useState(initialData);
69 | const [selectedItem, setSelectedItem] = useState(0);
70 | const [filteredData, setFilteredData] = useState(initialData);
71 |
72 | useEffect(() => {
73 | // If there is still some cards left and the selectedItem is going to be one past the last, swing it around to the first card
74 | if (filteredData.length > 0 && selectedItem > filteredData.length - 1) {
75 | setSelectedItem(0);
76 | }
77 | }, [filteredData.length]);
78 |
79 | useEffect(() => {
80 | // Fetching markdown on initial mount
81 | var fetchMarkdownPromises = qaPairs.map(q => Object.values(q).reduce((total, item) => { return [...total, item]; }, [])).flat().map(x => fetch(x));
82 | Promise.all(fetchMarkdownPromises)
83 | .then(response => Promise.all(response.map(x => x.text())))
84 | .then(markdownText => {
85 | var temp = qaPairs.map((q, i) => { q.question = markdownText[i + i]; q.answer = markdownText[i + i + 1]; return q; });
86 | setData([...temp]);
87 | setFilteredData([...temp]);
88 | });
89 | }, []);
90 |
91 | return (
92 |
93 |
{ setSelectedItem(index); setFilteredData(resetData(filteredData)); }} selectedItem={selectedItem} useKeyboardArrows emulateTouch>
94 | {filteredData.map((item, index) => (
95 |
98 |
{ setFilteredData(alterData(filteredData, selectedItem)) }}>
99 |
100 |
{`${index + 1}/${filteredData.length}`}
101 |
102 | {filteredData.length < data.length && (
103 |
107 | )}
108 |
109 |
114 |
115 |
118 |
121 |
122 | ))}
123 |
124 | {filteredData.length === 0 && (
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 | )}
133 |
134 | );
135 | }
136 |
137 | export default App;
138 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import App from './App';
4 |
5 | // TODO: Write tests. Yeah, I know, I'm the worst.
6 |
7 | // test('renders learn react link', () => {
8 | // const { getByText } = render();
9 | // const linkElement = getByText(/learn react/i);
10 | // expect(linkElement).toBeInTheDocument();
11 | // });
12 |
--------------------------------------------------------------------------------
/src/CodeBlock.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
3 | import { docco } from 'react-syntax-highlighter/dist/esm/styles/hljs';
4 |
5 | const CodeBlock = props => {
6 | return (
7 |
8 | {props.value}
9 |
10 | );
11 | }
12 |
13 | export default CodeBlock;
--------------------------------------------------------------------------------
/src/Global.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'PT_Sans';
3 | src: url(./fonts/ptsans_regular.woff2) format('woff2');
4 | }
5 | @font-face {
6 | font-family: 'PT_Sans';
7 | src: url(./fonts/ptsans_bolditalic.woff2) format('woff2');
8 | font-weight: bold;
9 | font-style: italic;
10 | }
11 | @font-face {
12 | font-family: 'PT_Sans';
13 | src: url(./fonts/ptsans_bold.woff2) format('woff2');
14 | font-weight: bold;
15 | }
16 | @font-face {
17 | font-family: 'PT_Sans';
18 | src: url(./fonts/ptsans_italic.woff2) format('woff2');
19 | font-style: italic;
20 | }
21 |
22 | html {
23 | box-sizing: border-box;
24 | }
25 | *,
26 | *:before,
27 | *:after {
28 | box-sizing: inherit;
29 | }
30 | body {
31 | text-align: center;
32 | font-family: 'PT_Sans';
33 | }
34 | button {
35 | font: inherit;
36 | -webkit-appearance: none;
37 | appearance: none;
38 | padding: 0;
39 | border: 0;
40 | }
41 | p {
42 | margin: .5em 0;
43 | }
44 | ul,
45 | ol {
46 | text-align: left;
47 | padding-top: .5em;
48 | }
49 | ul li,
50 | ol li {
51 | margin: .5em 0;
52 | }
--------------------------------------------------------------------------------
/src/Prism.css:
--------------------------------------------------------------------------------
1 | /* PrismJS 1.19.0
2 | https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
3 | /**
4 | * prism.js default theme for JavaScript, CSS and HTML
5 | * Based on dabblet (http://dabblet.com)
6 | * @author Lea Verou
7 | */
8 |
9 | pre {
10 | border-radius: 5px;
11 | padding: 15px !important;
12 | max-width: 100%;
13 | -webkit-overflow-scrolling: touch;
14 | min-height: fit-content;
15 | -webkit-backface-visibility: hidden;
16 | backface-visibility: hidden;
17 | }
18 | code {
19 | background: #3282b8;
20 | color: white;
21 | border-radius: 3px;
22 | padding: 0px 2px;
23 | }
24 | pre code {
25 | padding: 0;
26 | border-radius: 0;
27 | color: inherit;
28 | background: inherit;
29 | }
30 | code[class*="language-"],
31 | pre[class*="language-"] {
32 | color: black;
33 | background: none;
34 | text-shadow: 0 1px white;
35 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
36 | font-size: 1em;
37 | text-align: left;
38 | white-space: pre;
39 | word-spacing: normal;
40 | word-break: normal;
41 | word-wrap: normal;
42 | line-height: 1.5;
43 |
44 | -moz-tab-size: 4;
45 | -o-tab-size: 4;
46 | tab-size: 4;
47 |
48 | -webkit-hyphens: none;
49 | -moz-hyphens: none;
50 | -ms-hyphens: none;
51 | hyphens: none;
52 | }
53 |
54 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
55 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
56 | text-shadow: none;
57 | background: #b3d4fc;
58 | }
59 |
60 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
61 | code[class*="language-"]::selection, code[class*="language-"] ::selection {
62 | text-shadow: none;
63 | background: #b3d4fc;
64 | }
65 |
66 | @media print {
67 | code[class*="language-"],
68 | pre[class*="language-"] {
69 | text-shadow: none;
70 | }
71 | }
72 |
73 | /* Code blocks */
74 | pre[class*="language-"] {
75 | padding: 1em;
76 | margin: .5em 0;
77 | overflow: auto;
78 | }
79 |
80 | :not(pre) > code[class*="language-"],
81 | pre[class*="language-"] {
82 | background: #f5f2f0;
83 | }
84 |
85 | /* Inline code */
86 | :not(pre) > code[class*="language-"] {
87 | padding: .1em;
88 | border-radius: .3em;
89 | white-space: normal;
90 | }
91 |
92 | .token.comment,
93 | .token.prolog,
94 | .token.doctype,
95 | .token.cdata {
96 | color: slategray;
97 | }
98 |
99 | .token.punctuation {
100 | color: #999;
101 | }
102 |
103 | .token.namespace {
104 | opacity: .7;
105 | }
106 |
107 | .token.property,
108 | .token.tag,
109 | .token.boolean,
110 | .token.number,
111 | .token.constant,
112 | .token.symbol,
113 | .token.deleted {
114 | color: #905;
115 | }
116 |
117 | .token.selector,
118 | .token.attr-name,
119 | .token.string,
120 | .token.char,
121 | .token.builtin,
122 | .token.inserted {
123 | color: #690;
124 | }
125 |
126 | .token.operator,
127 | .token.entity,
128 | .token.url,
129 | .language-css .token.string,
130 | .style .token.string {
131 | color: #9a6e3a;
132 | background: hsla(0, 0%, 100%, .5);
133 | }
134 |
135 | .token.atrule,
136 | .token.attr-value,
137 | .token.keyword {
138 | color: #07a;
139 | }
140 |
141 | .token.function,
142 | .token.class-name {
143 | color: #DD4A68;
144 | }
145 |
146 | .token.regex,
147 | .token.important,
148 | .token.variable {
149 | color: #e90;
150 | }
151 |
152 | .token.important,
153 | .token.bold {
154 | font-weight: bold;
155 | }
156 | .token.italic {
157 | font-style: italic;
158 | }
159 |
160 | .token.entity {
161 | cursor: help;
162 | }
163 |
--------------------------------------------------------------------------------
/src/fonts/ptsans_bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sdennett55/interview-flashcards/037c71915a3fd50a5f869373482b93eebebda9bf/src/fonts/ptsans_bold.woff2
--------------------------------------------------------------------------------
/src/fonts/ptsans_bolditalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sdennett55/interview-flashcards/037c71915a3fd50a5f869373482b93eebebda9bf/src/fonts/ptsans_bolditalic.woff2
--------------------------------------------------------------------------------
/src/fonts/ptsans_italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sdennett55/interview-flashcards/037c71915a3fd50a5f869373482b93eebebda9bf/src/fonts/ptsans_italic.woff2
--------------------------------------------------------------------------------
/src/fonts/ptsans_regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sdennett55/interview-flashcards/037c71915a3fd50a5f869373482b93eebebda9bf/src/fonts/ptsans_regular.woff2
--------------------------------------------------------------------------------
/src/icons.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | export function CheckIcon(props) {
4 | return (
5 |
9 | )
10 | };
11 |
12 | export function XIcon(props) {
13 | return (
14 |
20 | )
21 | };
22 |
23 |
24 | export function RefreshIcon(props) {
25 | return (
26 |
30 | )
31 | };
32 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(, document.getElementById('root'));
8 |
9 | // If you want your app to work offline and load faster, you can change
10 | // unregister() to register() below. Note this comes with some pitfalls.
11 | // Learn more about service workers: https://bit.ly/CRA-PWA
12 | serviceWorker.unregister();
13 |
--------------------------------------------------------------------------------
/src/qa_pairs.js:
--------------------------------------------------------------------------------
1 | // PLEASE NOTE THAT THIS IS AN AUTO-GENERATED FILE.
2 | // PLEASE DO NOT UPDATE
3 | const qaPairs = [
4 | {
5 | question: "/question_answer_pairs/qa_pair1/question.md",
6 | answer: "/question_answer_pairs/qa_pair1/answer.md"
7 | },
8 | {
9 | question: "/question_answer_pairs/qa_pair2/question.md",
10 | answer: "/question_answer_pairs/qa_pair2/answer.md"
11 | },
12 | {
13 | question: "/question_answer_pairs/qa_pair3/question.md",
14 | answer: "/question_answer_pairs/qa_pair3/answer.md"
15 | },
16 | {
17 | question: "/question_answer_pairs/qa_pair4/question.md",
18 | answer: "/question_answer_pairs/qa_pair4/answer.md"
19 | },
20 | {
21 | question: "/question_answer_pairs/qa_pair5/question.md",
22 | answer: "/question_answer_pairs/qa_pair5/answer.md"
23 | }
24 | ];
25 |
26 | export default qaPairs;
27 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' }
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready.then(registration => {
134 | registration.unregister();
135 | });
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------