├── src
├── data
│ ├── viewTypes.js
│ ├── statusMessages.js
│ ├── Theme
│ │ └── ThemeOptions.js
│ ├── theme
│ │ └── themeOptions.js
│ ├── OptionTypes.js
│ ├── optionTypes.js
│ ├── Templates.js
│ └── templates.js
├── assets
│ ├── bg-1.png
│ ├── dots.png
│ ├── logo.png
│ ├── rsvp.png
│ ├── sort.png
│ ├── newForm.png
│ ├── animation.gif
│ ├── eventReg.png
│ ├── partyInv.png
│ ├── contactInfo.png
│ ├── fonts
│ │ ├── ProductSans-Black.woff
│ │ ├── ProductSans-Bold.woff
│ │ ├── ProductSans-Light.woff
│ │ ├── ProductSans-Thin.woff
│ │ ├── ProductSans-Italic.woff
│ │ ├── ProductSans-Medium.woff
│ │ ├── ProductSans-Regular.woff
│ │ ├── ProductSans-BoldItalic.woff
│ │ ├── ProductSans-ThinItalic.woff
│ │ ├── ProductSans-BlackItalic.woff
│ │ ├── ProductSans-LightItalic.woff
│ │ ├── ProductSans-MediumItalic.woff
│ │ ├── ThemeFonts.css
│ │ └── fonts.css
│ ├── listView.svg
│ ├── logo2.svg
│ └── colors
│ │ └── Colors.css
├── components
│ ├── form
│ │ ├── Settings.js
│ │ ├── Responses.js
│ │ ├── Edit.js
│ │ ├── FormTile
│ │ │ └── FormTile.js
│ │ └── formTile
│ │ │ └── FormTile.js
│ ├── helpers
│ │ ├── GenerateKey.js
│ │ ├── generateKey.js
│ │ ├── openInNewTab.js
│ │ ├── CreateOption.js
│ │ ├── createOption.js
│ │ ├── validations.js
│ │ ├── GenerateForm.js
│ │ ├── generateForm.js
│ │ ├── CreateQuestion.js
│ │ └── createQuestion.js
│ ├── loaders
│ │ └── page.loader.js
│ ├── buttons
│ │ ├── OutLinedButton.js
│ │ ├── outLinedButton.js
│ │ ├── FilledButton.js
│ │ └── filledButton.js
│ ├── routes
│ │ ├── private.routes.js
│ │ └── public.routes.js
│ ├── layout
│ │ ├── Headers
│ │ │ ├── LandingHeader.js
│ │ │ ├── HomeHeader.js
│ │ │ └── FormHeader.js
│ │ ├── headers
│ │ │ ├── LandingHeader.js
│ │ │ ├── HomeHeader.js
│ │ │ └── FormHeader.js
│ │ ├── Navigation
│ │ │ └── NavBar.js
│ │ └── navigation
│ │ │ └── NavBar.js
│ ├── dropdown
│ │ ├── useOutsideAlert.js
│ │ ├── Dropdown.js
│ │ ├── DropdownwithIcon.js
│ │ └── CustomDropdown.js
│ ├── slider
│ │ ├── Slider.js
│ │ ├── slider.js
│ │ └── slider.css
│ ├── theme
│ │ ├── BackgroundColorComponent.js
│ │ ├── backgroundColorComponent.js
│ │ ├── ColorComponent.js
│ │ ├── colorComponent.js
│ │ ├── ThemeEditor.js
│ │ └── themeEditor.js
│ ├── icon
│ │ └── Icon.js
│ ├── cards
│ │ ├── Options
│ │ │ ├── displayOptions.js
│ │ │ ├── option.js
│ │ │ └── individualOption.js
│ │ ├── options
│ │ │ ├── DisplayOptions.js
│ │ │ ├── Option.js
│ │ │ └── IndividualOption.js
│ │ ├── TitleCard.js
│ │ └── QuestionCard.js
│ ├── toolbar
│ │ ├── ToolBar.js
│ │ └── toolBar.js
│ └── modals
│ │ └── RenameModal.js
├── setupTests.js
├── App.test.js
├── reportWebVitals.js
├── store
│ ├── index.js
│ ├── data
│ │ ├── allForms.slice.js
│ │ └── form.slice.js
│ └── authentication
│ │ └── authentication.slice.js
├── views
│ ├── 404.js
│ ├── CreateForm.js
│ ├── Landing.js
│ └── Home.js
├── services
│ └── firebase
│ │ ├── config.firebase.js
│ │ ├── auth.firebase.js
│ │ └── firestore.firebase.js
├── index.js
├── index.css
└── App.js
├── public
├── robots.txt
├── favicon.ico
├── logo192.png
├── logo512.png
├── logo2.svg
├── manifest.json
└── index.html
├── jsconfig.json
├── postcss.config.js
├── .env.example
├── .gitignore
├── tailwind.config.js
├── README.md
└── package.json
/src/data/viewTypes.js:
--------------------------------------------------------------------------------
1 | export const LIST = "List";
2 | export const GRID = "Grid";
3 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/public/logo512.png
--------------------------------------------------------------------------------
/src/assets/bg-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/src/assets/bg-1.png
--------------------------------------------------------------------------------
/src/assets/dots.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/src/assets/dots.png
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/src/assets/rsvp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/src/assets/rsvp.png
--------------------------------------------------------------------------------
/src/assets/sort.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/src/assets/sort.png
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "src"
4 | },
5 | "include": ["src"]
6 | }
7 |
--------------------------------------------------------------------------------
/src/assets/newForm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/src/assets/newForm.png
--------------------------------------------------------------------------------
/src/assets/animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/src/assets/animation.gif
--------------------------------------------------------------------------------
/src/assets/eventReg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/src/assets/eventReg.png
--------------------------------------------------------------------------------
/src/assets/partyInv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/src/assets/partyInv.png
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/src/assets/contactInfo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/src/assets/contactInfo.png
--------------------------------------------------------------------------------
/src/components/form/Settings.js:
--------------------------------------------------------------------------------
1 | const Settings = () => {
2 | return
I am settings
;
3 | };
4 |
5 | export default Settings;
6 |
--------------------------------------------------------------------------------
/src/assets/fonts/ProductSans-Black.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/src/assets/fonts/ProductSans-Black.woff
--------------------------------------------------------------------------------
/src/assets/fonts/ProductSans-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/src/assets/fonts/ProductSans-Bold.woff
--------------------------------------------------------------------------------
/src/assets/fonts/ProductSans-Light.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/src/assets/fonts/ProductSans-Light.woff
--------------------------------------------------------------------------------
/src/assets/fonts/ProductSans-Thin.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/src/assets/fonts/ProductSans-Thin.woff
--------------------------------------------------------------------------------
/src/components/form/Responses.js:
--------------------------------------------------------------------------------
1 | const Responses = () => {
2 | return I am a response
;
3 | };
4 |
5 | export default Responses;
6 |
--------------------------------------------------------------------------------
/src/assets/fonts/ProductSans-Italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/src/assets/fonts/ProductSans-Italic.woff
--------------------------------------------------------------------------------
/src/assets/fonts/ProductSans-Medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/src/assets/fonts/ProductSans-Medium.woff
--------------------------------------------------------------------------------
/src/assets/fonts/ProductSans-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/src/assets/fonts/ProductSans-Regular.woff
--------------------------------------------------------------------------------
/src/assets/fonts/ProductSans-BoldItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/src/assets/fonts/ProductSans-BoldItalic.woff
--------------------------------------------------------------------------------
/src/assets/fonts/ProductSans-ThinItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/src/assets/fonts/ProductSans-ThinItalic.woff
--------------------------------------------------------------------------------
/src/assets/fonts/ProductSans-BlackItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/src/assets/fonts/ProductSans-BlackItalic.woff
--------------------------------------------------------------------------------
/src/assets/fonts/ProductSans-LightItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/src/assets/fonts/ProductSans-LightItalic.woff
--------------------------------------------------------------------------------
/src/assets/fonts/ProductSans-MediumItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thezaeemaanwar/google-forms-clone/HEAD/src/assets/fonts/ProductSans-MediumItalic.woff
--------------------------------------------------------------------------------
/src/components/helpers/GenerateKey.js:
--------------------------------------------------------------------------------
1 | const generateKey = (pre) => {
2 | return `${pre}_${new Date().getTime()}`;
3 | };
4 |
5 | export default generateKey;
6 |
--------------------------------------------------------------------------------
/src/components/helpers/generateKey.js:
--------------------------------------------------------------------------------
1 | const generateKey = (pre) => {
2 | return `${pre}_${new Date().getTime()}`;
3 | };
4 |
5 | export default generateKey;
6 |
--------------------------------------------------------------------------------
/src/components/helpers/openInNewTab.js:
--------------------------------------------------------------------------------
1 | const openInNewTab = (url) => {
2 | const win = window.open(url, "_blank");
3 | win.focus();
4 | };
5 |
6 | export default openInNewTab;
7 |
--------------------------------------------------------------------------------
/src/assets/listView.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/assets/fonts/ThemeFonts.css:
--------------------------------------------------------------------------------
1 | .basic-text {
2 | @apply font-basic;
3 | }
4 | .decorative-text {
5 | @apply font-decorative;
6 | }
7 | .playful-text {
8 | @apply font-playful;
9 | }
10 | .formal-text {
11 | @apply font-formal;
12 | }
13 |
--------------------------------------------------------------------------------
/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';
6 |
--------------------------------------------------------------------------------
/src/components/helpers/CreateOption.js:
--------------------------------------------------------------------------------
1 | import generateKey from "components/helpers/generateKey";
2 |
3 | const createOption = (pre) => {
4 | const option = { id: generateKey("option" + pre), text: "Option" };
5 | return option;
6 | };
7 |
8 | export default createOption;
9 |
--------------------------------------------------------------------------------
/src/components/helpers/createOption.js:
--------------------------------------------------------------------------------
1 | import generateKey from "components/helpers/generateKey";
2 |
3 | const createOption = (pre) => {
4 | const option = { id: generateKey("option" + pre), text: "Option" };
5 | return option;
6 | };
7 |
8 | export default createOption;
9 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from "@testing-library/react";
2 | import App from "App";
3 |
4 | test("renders learn react link", () => {
5 | render( );
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/public/logo2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/assets/logo2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/components/loaders/page.loader.js:
--------------------------------------------------------------------------------
1 | import anim from "assets/animation.gif";
2 |
3 | const Loading = () => {
4 | return (
5 |
6 |
7 |
8 | );
9 | };
10 |
11 | export default Loading;
12 |
--------------------------------------------------------------------------------
/src/components/buttons/OutLinedButton.js:
--------------------------------------------------------------------------------
1 | const OutLinedButton = ({ color, text, onClick }) => {
2 | return (
3 |
7 | {text}
8 |
9 | );
10 | };
11 |
12 | export default OutLinedButton;
13 |
--------------------------------------------------------------------------------
/src/components/buttons/outLinedButton.js:
--------------------------------------------------------------------------------
1 | const OutLinedButton = ({ color, text, onClick }) => {
2 | return (
3 |
7 | {text}
8 |
9 | );
10 | };
11 |
12 | export default OutLinedButton;
13 |
--------------------------------------------------------------------------------
/src/data/statusMessages.js:
--------------------------------------------------------------------------------
1 | export const ERR_NOT_AUTHORISED = "User Not Authorised";
2 | export const ERR_NOT_FOUND = "Data not found";
3 | export const ERR_INVALID_DATA = "Invalid Data";
4 | export const SUCCESS_SAVED = "All changes saved in Drive";
5 | export const PROGRESS_SAVING = "Saving...";
6 | export const ERR_SAVING_FAILED = "Couldn't update in Drive";
7 |
--------------------------------------------------------------------------------
/src/components/buttons/FilledButton.js:
--------------------------------------------------------------------------------
1 | const FilledButton = ({ color, background, text, onClick }) => {
2 | return (
3 |
7 | {text}
8 |
9 | );
10 | };
11 |
12 | export default FilledButton;
13 |
--------------------------------------------------------------------------------
/src/components/buttons/filledButton.js:
--------------------------------------------------------------------------------
1 | const FilledButton = ({ color, background, text, onClick }) => {
2 | return (
3 |
7 | {text}
8 |
9 | );
10 | };
11 |
12 | export default FilledButton;
13 |
--------------------------------------------------------------------------------
/src/components/helpers/validations.js:
--------------------------------------------------------------------------------
1 | import * as Yup from "yup";
2 |
3 | const headerSchema = Yup.object().shape({
4 | title: Yup.string().required("Title is required"),
5 | });
6 |
7 | const questionSchema = Yup.object().shape({
8 | title: Yup.string().required("Question title is required"),
9 | });
10 |
11 | export { questionSchema, headerSchema };
12 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | REACT_APP_API_KEY=your_key_given_by_firebase
2 | REACT_APP_AUTH_DOMAIN=your_key_given_by_firebase
3 | REACT_APP_PROJECT_ID=your_key_given_by_firebase
4 | REACT_APP_STORAGE_BUCKET=your_key_given_by_firebase
5 | REACT_APP_MESSAGING_SENDER_ID=your_key_given_by_firebase
6 | REACT_APP_APP_ID=your_key_given_by_firebase
7 | REACT_APP_MEASUREMENT_ID=your_key_given_by_firebase
8 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 | import authReducer from "store/authentication/authentication.slice";
3 | import formReducer from "store/data/form.slice";
4 | import allFormSReducder from "store/data/allForms.slice";
5 |
6 | export default configureStore({
7 | reducer: {
8 | authentication: authReducer,
9 | form: formReducer,
10 | allForms: allFormSReducder,
11 | },
12 | });
13 |
--------------------------------------------------------------------------------
/src/components/helpers/GenerateForm.js:
--------------------------------------------------------------------------------
1 | const generateFormPreview = (id, title, img, date, shared) => {
2 | return { id, title, img: img ? img : "", date, shared };
3 | };
4 |
5 | const generateForm = (
6 | id,
7 | theme,
8 | title,
9 | description,
10 | questions,
11 | date,
12 | shared
13 | ) => {
14 | return { id, theme, title, description, questions, date, shared };
15 | };
16 |
17 | export { generateForm, generateFormPreview };
18 |
--------------------------------------------------------------------------------
/src/components/helpers/generateForm.js:
--------------------------------------------------------------------------------
1 | const generateFormPreview = (id, title, img, date, shared) => {
2 | return { id, title, img: img ? img : "", date, shared };
3 | };
4 |
5 | const generateForm = (
6 | id,
7 | theme,
8 | title,
9 | description,
10 | questions,
11 | date,
12 | shared
13 | ) => {
14 | return { id, theme, title, description, questions, date, shared };
15 | };
16 |
17 | export { generateForm, generateFormPreview };
18 |
--------------------------------------------------------------------------------
/src/components/routes/private.routes.js:
--------------------------------------------------------------------------------
1 | import { Navigate, useLocation } from "react-router-dom";
2 | import { useSelector } from "react-redux";
3 |
4 | const PrivateRoute = ({ children }) => {
5 | const location = useLocation();
6 | const { logged } = useSelector((state) => state.authentication);
7 | return logged ? (
8 | children
9 | ) : (
10 |
11 | );
12 | };
13 |
14 | export default PrivateRoute;
15 |
--------------------------------------------------------------------------------
/src/data/Theme/ThemeOptions.js:
--------------------------------------------------------------------------------
1 | export const colors = [
2 | "red",
3 | "purple",
4 | "indigo",
5 | "blue",
6 | "lightblue",
7 | "cyan",
8 | "redorange",
9 | "orange",
10 | "teal",
11 | "green",
12 | "bluegray",
13 | "gray",
14 | ];
15 |
16 | export const opacities = [10, 20, 30, 0];
17 |
18 | export const fonts = [
19 | { id: 1, text: "basic" },
20 | { id: 2, text: "decorative" },
21 | { id: 3, text: "formal" },
22 | { id: 4, text: "playful" },
23 | ];
24 |
--------------------------------------------------------------------------------
/src/data/theme/themeOptions.js:
--------------------------------------------------------------------------------
1 | export const colors = [
2 | "red",
3 | "purple",
4 | "indigo",
5 | "blue",
6 | "lightblue",
7 | "cyan",
8 | "redorange",
9 | "orange",
10 | "teal",
11 | "green",
12 | "bluegray",
13 | "gray",
14 | ];
15 |
16 | export const opacities = [10, 20, 30, 0];
17 |
18 | export const fonts = [
19 | { id: 1, text: "basic" },
20 | { id: 2, text: "decorative" },
21 | { id: 3, text: "formal" },
22 | { id: 4, text: "playful" },
23 | ];
24 |
--------------------------------------------------------------------------------
/src/views/404.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | const NotFound = () => {
4 | return (
5 |
6 |
Page not found
7 |
8 | Click{" "}
9 |
10 | here
11 | {" "}
12 | to return to homepage.
13 |
14 |
15 | );
16 | };
17 |
18 | export default NotFound;
19 |
--------------------------------------------------------------------------------
/src/components/helpers/CreateQuestion.js:
--------------------------------------------------------------------------------
1 | import generateKey from "components/helpers/generateKey";
2 | import { MULTIPLE_CHOICE } from "data/optionTypes";
3 | import createOption from "components/helpers/createOption";
4 |
5 | const createQuestion = (len) => {
6 | const question = {
7 | id: generateKey("question" + len),
8 | title: "Question",
9 | optionType: MULTIPLE_CHOICE,
10 | options: [createOption(0)],
11 | required: false,
12 | };
13 | return question;
14 | };
15 | export default createQuestion;
16 |
--------------------------------------------------------------------------------
/src/components/helpers/createQuestion.js:
--------------------------------------------------------------------------------
1 | import generateKey from "components/helpers/generateKey";
2 | import { MULTIPLE_CHOICE } from "data/optionTypes";
3 | import createOption from "components/helpers/createOption";
4 |
5 | const createQuestion = (len) => {
6 | const question = {
7 | id: generateKey("question" + len),
8 | title: "Question",
9 | optionType: MULTIPLE_CHOICE,
10 | options: [createOption(0)],
11 | required: false,
12 | };
13 | return question;
14 | };
15 | export default createQuestion;
16 |
--------------------------------------------------------------------------------
/src/components/layout/Headers/LandingHeader.js:
--------------------------------------------------------------------------------
1 | import logo from "assets/logo.png";
2 |
3 | const LandingHeader = () => {
4 | return (
5 |
6 |
7 |
8 |
Google
9 |
Forms
10 |
11 |
12 | );
13 | };
14 |
15 | export default LandingHeader;
16 |
--------------------------------------------------------------------------------
/src/components/layout/headers/LandingHeader.js:
--------------------------------------------------------------------------------
1 | import logo from "assets/logo.png";
2 |
3 | const LandingHeader = () => {
4 | return (
5 |
6 |
7 |
8 |
Google
9 |
Forms
10 |
11 |
12 | );
13 | };
14 |
15 | export default LandingHeader;
16 |
--------------------------------------------------------------------------------
/src/components/dropdown/useOutsideAlert.js:
--------------------------------------------------------------------------------
1 | const { useEffect } = require("react");
2 |
3 | const useOutsideAlert = (ref, actionCallback) => {
4 | useEffect(() => {
5 | function handleClickOutside(event) {
6 | if (ref.current && !ref.current.contains(event.target)) {
7 | actionCallback();
8 | }
9 | }
10 | document.addEventListener("mousedown", handleClickOutside);
11 | return () => {
12 | document.removeEventListener("mousedown", handleClickOutside);
13 | };
14 | }, [ref, actionCallback]);
15 | };
16 |
17 | export default useOutsideAlert;
18 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
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": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/slider/Slider.js:
--------------------------------------------------------------------------------
1 | import "components/slider/slider.css";
2 | import { PropTypes } from "prop-types";
3 |
4 | const Slider = ({ theme, required, toggleRequired }) => {
5 | return (
6 |
7 |
12 |
13 |
14 | );
15 | };
16 |
17 | Slider.propTypes = {
18 | toggleRequired: PropTypes.func.isRequired,
19 | };
20 |
21 | export default Slider;
22 |
--------------------------------------------------------------------------------
/src/components/slider/slider.js:
--------------------------------------------------------------------------------
1 | import "components/slider/slider.css";
2 | import { PropTypes } from "prop-types";
3 |
4 | const Slider = ({ theme, required, toggleRequired }) => {
5 | return (
6 |
7 |
12 |
13 |
14 | );
15 | };
16 |
17 | Slider.propTypes = {
18 | toggleRequired: PropTypes.func.isRequired,
19 | };
20 |
21 | export default Slider;
22 |
--------------------------------------------------------------------------------
/src/store/data/allForms.slice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | export const allFormsSlice = createSlice({
4 | name: "allForms",
5 | initialState: {
6 | loading: true,
7 | forms: [],
8 | error: null,
9 | },
10 | reducers: {
11 | setForms: (state, action) => {
12 | state.forms = action.payload.forms;
13 | state.error = action.payload.error;
14 | },
15 | setLoading: (state, action) => {
16 | state.loading = action.payload.loading;
17 | },
18 | },
19 | });
20 |
21 | export const { setForms, setLoading, forms, loading } = allFormsSlice.actions;
22 | export default allFormsSlice.reducer;
23 |
--------------------------------------------------------------------------------
/src/components/theme/BackgroundColorComponent.js:
--------------------------------------------------------------------------------
1 | import { opacities } from "data/theme/themeOptions";
2 | import ColorComponent from "components/theme/colorComponent";
3 |
4 | const BackgroundColorComponent = ({ color, opacity, setOpacity }) => {
5 | return (
6 |
7 | {opacities.map((op) => (
8 | setOpacity(op)}
11 | color={`${color + op}`}
12 | selected={opacity === op}
13 | border={op === 0 || op === 10}
14 | />
15 | ))}
16 |
17 | );
18 | };
19 |
20 | export default BackgroundColorComponent;
21 |
--------------------------------------------------------------------------------
/src/components/theme/backgroundColorComponent.js:
--------------------------------------------------------------------------------
1 | import { opacities } from "data/theme/themeOptions";
2 | import ColorComponent from "components/theme/colorComponent";
3 |
4 | const BackgroundColorComponent = ({ color, opacity, setOpacity }) => {
5 | return (
6 |
7 | {opacities.map((op) => (
8 | setOpacity(op)}
11 | color={`${color + op}`}
12 | selected={opacity === op}
13 | border={op === 0 || op === 10}
14 | />
15 | ))}
16 |
17 | );
18 | };
19 |
20 | export default BackgroundColorComponent;
21 |
--------------------------------------------------------------------------------
/src/components/icon/Icon.js:
--------------------------------------------------------------------------------
1 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
2 | import { PropTypes } from "prop-types";
3 |
4 | const Icon = ({ label, icon, onClick }) => {
5 | return (
6 |
11 |
12 |
13 | );
14 | };
15 |
16 | Icon.propTypes = {
17 | label: PropTypes.string,
18 | icon: PropTypes.object.isRequired,
19 | onClick: PropTypes.func,
20 | };
21 |
22 | Icon.defaultProps = {
23 | label: "icon",
24 | };
25 | export default Icon;
26 |
--------------------------------------------------------------------------------
/src/services/firebase/config.firebase.js:
--------------------------------------------------------------------------------
1 | import { initializeApp } from "firebase/app";
2 | import { getFirestore } from "firebase/firestore";
3 |
4 | const firebaseConfig = {
5 | apiKey: process.env.REACT_APP_API_KEY,
6 | authDomain: process.env.REACT_APP_AUTH_DOMAIN,
7 | projectId: process.env.REACT_APP_PROJECT_ID,
8 | storageBucket: process.env.REACT_APP_STORAGE_BUCKET,
9 | messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID,
10 | appId: process.env.REACT_APP_APP_ID,
11 | measurementId: process.env.REACT_APP_MEASUREMENT_ID,
12 | };
13 |
14 | // Initialize Firebase
15 | const firebase_app = initializeApp(firebaseConfig);
16 | export const db = getFirestore(firebase_app);
17 | export default firebase_app;
18 |
--------------------------------------------------------------------------------
/src/components/cards/Options/displayOptions.js:
--------------------------------------------------------------------------------
1 | import { CHECKBOX, MULTIPLE_CHOICE } from "data/optionTypes";
2 |
3 | const DisplayOptions = ({ type, options }) => {
4 | if (type === MULTIPLE_CHOICE || type === CHECKBOX)
5 | return (
6 |
7 | {options.map((op) => (
8 |
9 |
14 |
15 | {op.text}
16 |
17 |
18 | ))}
19 |
20 | );
21 | else return
;
22 | };
23 |
24 | export default DisplayOptions;
25 |
--------------------------------------------------------------------------------
/src/components/cards/options/DisplayOptions.js:
--------------------------------------------------------------------------------
1 | import { CHECKBOX, MULTIPLE_CHOICE } from "data/optionTypes";
2 |
3 | const DisplayOptions = ({ type, options }) => {
4 | if (type === MULTIPLE_CHOICE || type === CHECKBOX)
5 | return (
6 |
7 | {options.map((op) => (
8 |
9 |
14 |
15 | {op.text}
16 |
17 |
18 | ))}
19 |
20 | );
21 | else return
;
22 | };
23 |
24 | export default DisplayOptions;
25 |
--------------------------------------------------------------------------------
/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 reportWebVitals from "reportWebVitals";
6 | import { Provider } from "react-redux";
7 | import { BrowserRouter as Router } from "react-router-dom";
8 | import store from "store/index";
9 |
10 | ReactDOM.render(
11 |
12 |
13 |
14 |
15 |
16 |
17 | ,
18 | document.getElementById("root")
19 | );
20 |
21 | // If you want to start measuring performance in your app, pass a function
22 | // to log results (for example: reportWebVitals(console.log))
23 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
24 | reportWebVitals();
25 |
--------------------------------------------------------------------------------
/src/store/authentication/authentication.slice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | export const authSlice = createSlice({
4 | name: "authentication",
5 | initialState: {
6 | loading: true,
7 | logged: false,
8 | user: null,
9 | error: null,
10 | },
11 | reducers: {
12 | setUser: (state, action) => {
13 | state.loading = false;
14 | action.payload.user ? (state.logged = true) : (state.logged = false);
15 | state.user = action.payload.user;
16 | state.error = action.payload.error;
17 | },
18 | startLoading: (state) => {
19 | state.loading = true;
20 | },
21 | stopLoading: (state) => {
22 | state.loading = false;
23 | },
24 | },
25 | });
26 |
27 | export const { startLoading, stopLoading, setUser } = authSlice.actions;
28 | export default authSlice.reducer;
29 |
--------------------------------------------------------------------------------
/src/components/toolbar/ToolBar.js:
--------------------------------------------------------------------------------
1 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
2 | import { toolBarActions } from "data/templates";
3 | import PropTypes from "prop-types";
4 |
5 | const ToolBar = ({ addQuestion }) => {
6 | return (
7 |
10 | {toolBarActions.map((action) => (
11 |
12 | {}}
16 | />
17 |
18 | ))}
19 |
20 | );
21 | };
22 |
23 | ToolBar.propTypes = {
24 | addQuestion: PropTypes.func.isRequired,
25 | };
26 |
27 | export default ToolBar;
28 |
--------------------------------------------------------------------------------
/src/components/toolbar/toolBar.js:
--------------------------------------------------------------------------------
1 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
2 | import { toolBarActions } from "data/templates";
3 | import PropTypes from "prop-types";
4 |
5 | const ToolBar = ({ addQuestion }) => {
6 | return (
7 |
10 | {toolBarActions.map((action) => (
11 |
12 | {}}
16 | />
17 |
18 | ))}
19 |
20 | );
21 | };
22 |
23 | ToolBar.propTypes = {
24 | addQuestion: PropTypes.func.isRequired,
25 | };
26 |
27 | export default ToolBar;
28 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,500;0,600;0,700;0,800;1,400;1,500;1,600&display=swap");
2 | @import url("https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@300&family=Parisienne&family=Patrick+Hand&display=swap");
3 | @import "assets/fonts/fonts.css";
4 | @import "assets/colors/Colors.css";
5 | @import "assets/fonts/ThemeFonts.css";
6 |
7 | @tailwind base;
8 | @tailwind components;
9 | @tailwind utilities;
10 |
11 | @layer base {
12 | body {
13 | @apply text-fontGrey;
14 | @apply font-regular;
15 | font-weight: 500;
16 | }
17 | }
18 |
19 | input[type="checkbox"],
20 | input[type="radio"] {
21 | /* Double-sized Checkboxes */
22 | -ms-transform: scale(1.5); /* IE */
23 | -moz-transform: scale(1.5); /* FF */
24 | -webkit-transform: scale(1.5); /* Safari and Chrome */
25 | -o-transform: scale(1.5); /* Opera */
26 | transform: scale(1.5);
27 | padding: 10px;
28 | }
29 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: ["./src/**/*.{js,jsx,ts,tsx}"],
3 | theme: {
4 | extend: {
5 | colors: {
6 | blue: "#1A73E8",
7 | grey: "#F1F3F4",
8 | hoverGrey: "#DDE1E2",
9 | fontGrey: "#5f6368",
10 | purple: "#7248B9",
11 | indigo: "#3F51B5",
12 | red: "#DB4437",
13 | lightblue: "#03A9F4",
14 | cyan: "#00BCD4",
15 | redorange: "#FF5722",
16 | orange: "#FF9800",
17 | teal: "#009688",
18 | green: "#4CAF50",
19 | bluegray: "#607D8B",
20 | gray: "#9E9E9E",
21 | },
22 | fontFamily: {
23 | regular: ["Product Sans Regular"],
24 | bold: ["Product Sans Bold"],
25 | thin: ["Product Sans Thin Regular"],
26 | basic: ["Product Sans Regular"],
27 | decorative: ["Parisienne", "cursive"],
28 | playful: ["Patrick Hand", "cursive"],
29 | formal: ["Cormorant Garamond", "serif"],
30 | },
31 | },
32 | },
33 | plugins: [],
34 | };
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Google Forms Clone
2 | This is an attempt of creating google forms clone using React.js and Firebase.
3 |
4 | ## Getting Started
5 | ### 1. Clone the repository:
6 | To clone the repository, run the command
7 | git clone https://github.com/thezaeemaanwar/google-forms-clone
8 |
9 | ### 2. Install the dependencies:
10 | To install the dependencies, run
11 | npm install
12 | OR
13 | yarn install
14 | Based on whichever package manager suits you
15 |
16 | ### 3. Set up Firebase:
17 | - Create an account on firebase, if already, create a web project.
18 | - Copy all the keys from the firebase project config.
19 |
20 | ### 4. Set Up environment variables:
21 | - Create a file with name ```.env``` in the root directory
22 | - Copy the contents of ```.env.example``` and paste into ```.env``` file.
23 | - Replace all the keys with your keys obtained by firebase
24 |
25 | ### 5. Start the development Server:
26 | To start the development server, run
27 | npm start
28 | OR
29 | yarn start
30 | Whichever you used before for installing dependencies.
31 |
--------------------------------------------------------------------------------
/src/services/firebase/auth.firebase.js:
--------------------------------------------------------------------------------
1 | import firebase_app from "services/firebase/config.firebase";
2 | import { getAuth, onAuthStateChanged } from "firebase/auth";
3 | import {
4 | signInWithRedirect,
5 | GoogleAuthProvider,
6 | signOut as firebaseSignOut,
7 | } from "firebase/auth";
8 |
9 | const SignIn = (dispatchCallback) => {
10 | const auth = getAuth();
11 | const provider = new GoogleAuthProvider();
12 | signInWithRedirect(auth, provider)
13 | .then((re) => {
14 | dispatchCallback({ user: re.user });
15 | })
16 | .catch((err) => {
17 | console.error(err);
18 | dispatchCallback({ error: err });
19 | });
20 | };
21 |
22 | const checkLogged = (dispatchCallback) => {
23 | const auth = getAuth();
24 | onAuthStateChanged(auth, (user) => {
25 | if (user) {
26 | dispatchCallback({
27 | uid: user.uid,
28 | displayName: user.displayName,
29 | profileImage: user.photoURL,
30 | });
31 | } else {
32 | dispatchCallback(null);
33 | }
34 | });
35 | };
36 |
37 | const SignOut = (dispatchCallback) => {
38 | const auth = getAuth();
39 | firebaseSignOut(auth);
40 | dispatchCallback(null);
41 | };
42 |
43 | export { SignIn, SignOut, checkLogged };
44 |
--------------------------------------------------------------------------------
/src/components/theme/ColorComponent.js:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3 | import { faCheck } from "@fortawesome/free-solid-svg-icons";
4 |
5 | const ColorComponent = ({ selected, color, selectColor, border }) => {
6 | return (
7 | selectColor(color)}
9 | onClick={selectColor}
10 | className={`cursor-pointer ${
11 | selected ? "h-8 w-8" : "h-7 w-7"
12 | } rounded-full ${color}-bg flex items-center justify-center ${
13 | border ? "border" : null
14 | } hover:shadow-lg hover:w-8 hover:h-8`}
15 | >
16 | {selected ? (
17 |
28 | ) : null}
29 |
30 | );
31 | };
32 |
33 | ColorComponent.defaultProps = {
34 | selected: false,
35 | };
36 | ColorComponent.propTypes = {
37 | selected: PropTypes.bool,
38 | color: PropTypes.string.isRequired,
39 | };
40 |
41 | export default ColorComponent;
42 |
--------------------------------------------------------------------------------
/src/components/theme/colorComponent.js:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3 | import { faCheck } from "@fortawesome/free-solid-svg-icons";
4 |
5 | const ColorComponent = ({ selected, color, selectColor, border }) => {
6 | return (
7 | selectColor(color)}
9 | onClick={selectColor}
10 | className={`cursor-pointer ${
11 | selected ? "h-8 w-8" : "h-7 w-7"
12 | } rounded-full ${color}-bg flex items-center justify-center ${
13 | border ? "border" : null
14 | } hover:shadow-lg hover:w-8 hover:h-8`}
15 | >
16 | {selected ? (
17 |
28 | ) : null}
29 |
30 | );
31 | };
32 |
33 | ColorComponent.defaultProps = {
34 | selected: false,
35 | };
36 | ColorComponent.propTypes = {
37 | selected: PropTypes.bool,
38 | color: PropTypes.string.isRequired,
39 | };
40 |
41 | export default ColorComponent;
42 |
--------------------------------------------------------------------------------
/src/components/slider/slider.css:
--------------------------------------------------------------------------------
1 | .switch {
2 | position: relative;
3 | /* display: inline-block; */
4 | width: 37px;
5 | height: 20px;
6 | }
7 |
8 | .switch input {
9 | opacity: 0;
10 | width: 0;
11 | height: 0;
12 | }
13 |
14 | .slider {
15 | height: 14px;
16 | position: absolute;
17 | cursor: pointer;
18 | border-radius: 34px;
19 | top: 0;
20 | left: 0;
21 | right: 0;
22 | bottom: 0;
23 | background-color: #b9b9b9;
24 | -webkit-transition: 0.4s;
25 | transition: 0.4s;
26 | /* @apply bg-grey */
27 | }
28 |
29 | .slider:before {
30 | position: absolute;
31 | content: "";
32 | top: -3px;
33 | height: 20px;
34 | width: 20px;
35 | /* left: -4px; */
36 | bottom: 4px;
37 | background-color: white;
38 | box-shadow: 0px 1px 3px rgb(0 0 0 / 40%);
39 | -webkit-transition: 0.4s;
40 | border-radius: 50%;
41 | transition: 0.4s;
42 | }
43 |
44 | input:checked + .slider {
45 | background-color: #8650ee;
46 | }
47 |
48 | input:focus + .slider {
49 | box-shadow: 0 0 1px #3196f3;
50 | }
51 |
52 | input:checked + .slider:before {
53 | -webkit-transform: translateX(17px);
54 | -ms-transform: translateX(17px);
55 | transform: translateX(17px);
56 | }
57 |
58 | /* Rounded sliders */
59 | .slider.round {
60 | border-radius: 34px;
61 | }
62 |
63 | .slider.round:before {
64 | border-radius: 50%;
65 | }
66 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import { useDispatch, useSelector } from "react-redux";
2 | import PublicRoutes from "components/routes/public.routes";
3 | import Loading from "components/loaders/page.loader";
4 | import { useEffect } from "react";
5 | import { checkLogged } from "services/firebase/auth.firebase";
6 | import { setUser } from "store/authentication/authentication.slice";
7 | import { getFormsFromFirebase } from "services/firebase/firestore.firebase";
8 | import { setForms, setLoading } from "store/data/allForms.slice";
9 |
10 | const App = () => {
11 | const dispatch = useDispatch();
12 | const { logged, loading, user } = useSelector(
13 | (state) => state.authentication
14 | );
15 |
16 | useEffect(() => {
17 | const dispatchCallback = (user) => {
18 | dispatch(setUser({ user }));
19 | };
20 | checkLogged(dispatchCallback);
21 | }, [dispatch]);
22 |
23 | useEffect(() => {
24 | dispatch(setLoading(true));
25 | const dispatchCallback = (forms) => {
26 | dispatch(setForms({ forms }));
27 | };
28 | const loadDispatch = () => dispatch(setLoading(false));
29 | if (user) {
30 | getFormsFromFirebase(user.uid, dispatchCallback, loadDispatch);
31 | }
32 | }, [user, dispatch]);
33 |
34 | if (loading) return ;
35 | else
36 | return (
37 |
40 | );
41 | };
42 |
43 | export default App;
44 |
--------------------------------------------------------------------------------
/src/data/OptionTypes.js:
--------------------------------------------------------------------------------
1 | import {
2 | faAlignLeft,
3 | faCalendarDay,
4 | faCheckSquare,
5 | faChevronDown,
6 | faCloudUploadAlt,
7 | faEllipsisH,
8 | faGripHorizontal,
9 | } from "@fortawesome/free-solid-svg-icons";
10 | import { faDotCircle, faClock } from "@fortawesome/free-regular-svg-icons";
11 |
12 | export const SHORT_ANSWER = "Short answer";
13 | export const PARAGRAPH = "Paragraph";
14 | export const MULTIPLE_CHOICE = "Multiple choice";
15 | export const CHECKBOX = "Checkbox";
16 | export const DROPDOWN = "Dropdown";
17 | export const FILE_UPLOAD = "File upload";
18 | export const LINEAR_SCALE = "Linear scale";
19 | export const MULTIPLE_CHOICE_GRID = "Multiple choice grid";
20 | export const CHECKBOX_GRID = "Checkbox grid";
21 | export const DATE = "Date";
22 | export const TIME = "Time";
23 | export const dropdownOptions = [
24 | { id: 1, text: SHORT_ANSWER, icon: faAlignLeft },
25 | { id: 2, text: PARAGRAPH, icon: faAlignLeft },
26 | { id: 3, text: MULTIPLE_CHOICE, icon: faDotCircle },
27 | { id: 4, text: CHECKBOX, icon: faCheckSquare },
28 | { id: 5, text: DROPDOWN, icon: faChevronDown },
29 | { id: 6, text: FILE_UPLOAD, icon: faCloudUploadAlt },
30 | { id: 7, text: LINEAR_SCALE, icon: faEllipsisH },
31 | { id: 8, text: MULTIPLE_CHOICE_GRID, icon: faGripHorizontal },
32 | { id: 9, text: CHECKBOX_GRID, icon: faGripHorizontal },
33 | { id: 10, text: DATE, icon: faCalendarDay },
34 | { id: 11, text: TIME, icon: faClock },
35 | ];
36 |
--------------------------------------------------------------------------------
/src/data/optionTypes.js:
--------------------------------------------------------------------------------
1 | import {
2 | faAlignLeft,
3 | faCalendarDay,
4 | faCheckSquare,
5 | faChevronDown,
6 | faCloudUploadAlt,
7 | faEllipsisH,
8 | faGripHorizontal,
9 | } from "@fortawesome/free-solid-svg-icons";
10 | import { faDotCircle, faClock } from "@fortawesome/free-regular-svg-icons";
11 |
12 | export const SHORT_ANSWER = "Short answer";
13 | export const PARAGRAPH = "Paragraph";
14 | export const MULTIPLE_CHOICE = "Multiple choice";
15 | export const CHECKBOX = "Checkbox";
16 | export const DROPDOWN = "Dropdown";
17 | export const FILE_UPLOAD = "File upload";
18 | export const LINEAR_SCALE = "Linear scale";
19 | export const MULTIPLE_CHOICE_GRID = "Multiple choice grid";
20 | export const CHECKBOX_GRID = "Checkbox grid";
21 | export const DATE = "Date";
22 | export const TIME = "Time";
23 | export const dropdownOptions = [
24 | { id: 1, text: SHORT_ANSWER, icon: faAlignLeft },
25 | { id: 2, text: PARAGRAPH, icon: faAlignLeft },
26 | { id: 3, text: MULTIPLE_CHOICE, icon: faDotCircle },
27 | { id: 4, text: CHECKBOX, icon: faCheckSquare },
28 | { id: 5, text: DROPDOWN, icon: faChevronDown },
29 | { id: 6, text: FILE_UPLOAD, icon: faCloudUploadAlt },
30 | { id: 7, text: LINEAR_SCALE, icon: faEllipsisH },
31 | { id: 8, text: MULTIPLE_CHOICE_GRID, icon: faGripHorizontal },
32 | { id: 9, text: CHECKBOX_GRID, icon: faGripHorizontal },
33 | { id: 10, text: DATE, icon: faCalendarDay },
34 | { id: 11, text: TIME, icon: faClock },
35 | ];
36 |
--------------------------------------------------------------------------------
/src/components/layout/Navigation/NavBar.js:
--------------------------------------------------------------------------------
1 | import { NavLink } from "react-router-dom";
2 | import { PropTypes } from "prop-types";
3 | import { useSelector } from "react-redux";
4 |
5 | const NavBar = ({ type }) => {
6 | const { theme } = useSelector((state) => state.form);
7 | return (
8 |
9 |
12 | nav.isActive
13 | ? `${theme.color}-border ${theme.color}-text border-b-4 p-2 px-4`
14 | : "border-b-4 p-2 px-4 border-white"
15 | }
16 | >
17 | Questions
18 |
19 |
22 | nav.isActive
23 | ? `${theme.color}-border ${theme.color}-text border-b-4 p-2 px-4 `
24 | : "border-b-4 p-2 px-4 border-white"
25 | }
26 | >
27 | Responses
28 |
29 |
32 | nav.isActive
33 | ? `${theme.color}-border ${theme.color}-text border-b-4 p-2 px-4`
34 | : "border-b-4 p-2 px-4 border-white"
35 | }
36 | >
37 | Settings
38 |
39 |
40 | );
41 | };
42 |
43 | NavBar.defaultProps = {
44 | type: "blank",
45 | };
46 | NavBar.propTypes = {
47 | type: PropTypes.string,
48 | };
49 | export default NavBar;
50 |
--------------------------------------------------------------------------------
/src/components/layout/navigation/NavBar.js:
--------------------------------------------------------------------------------
1 | import { NavLink } from "react-router-dom";
2 | import { PropTypes } from "prop-types";
3 | import { useSelector } from "react-redux";
4 |
5 | const NavBar = ({ type }) => {
6 | const { theme } = useSelector((state) => state.form);
7 | return (
8 |
9 |
12 | nav.isActive
13 | ? `${theme.color}-border ${theme.color}-text border-b-4 p-2 px-4`
14 | : "border-b-4 p-2 px-4 border-white"
15 | }
16 | >
17 | Questions
18 |
19 |
22 | nav.isActive
23 | ? `${theme.color}-border ${theme.color}-text border-b-4 p-2 px-4 `
24 | : "border-b-4 p-2 px-4 border-white"
25 | }
26 | >
27 | Responses
28 |
29 |
32 | nav.isActive
33 | ? `${theme.color}-border ${theme.color}-text border-b-4 p-2 px-4`
34 | : "border-b-4 p-2 px-4 border-white"
35 | }
36 | >
37 | Settings
38 |
39 |
40 | );
41 | };
42 |
43 | NavBar.defaultProps = {
44 | type: "blank",
45 | };
46 | NavBar.propTypes = {
47 | type: PropTypes.string,
48 | };
49 | export default NavBar;
50 |
--------------------------------------------------------------------------------
/src/components/modals/RenameModal.js:
--------------------------------------------------------------------------------
1 | import FilledButton from "components/buttons/FilledButton";
2 | import OutLinedButton from "components/buttons/OutLinedButton";
3 | import { useFormik } from "formik";
4 | import PropTypes from "prop-types";
5 |
6 | const RenameModal = ({ closeModal, renameCallBack }) => {
7 | const { handleChange, handleSubmit, values } = useFormik({
8 | initialValues: { name: "" },
9 | onSubmit: (values) => {
10 | renameCallBack(values.name);
11 | },
12 | });
13 | return (
14 |
43 | );
44 | };
45 |
46 | RenameModal.propTypes = {
47 | closeModal: PropTypes.func.isRequired,
48 | renameCallBack: PropTypes.func.isRequired
49 | };
50 |
51 | export default RenameModal;
52 |
--------------------------------------------------------------------------------
/src/components/cards/Options/option.js:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import IndividualOption from "components/cards/options/IndividualOption";
3 | import { SHORT_ANSWER, PARAGRAPH, MULTIPLE_CHOICE } from "data/optionTypes";
4 | import createOption from "components/helpers/createOption";
5 |
6 | const OptionCard = ({ type, options, setOptions }) => {
7 | const addNewOption = () => {
8 | const ops = [...options];
9 | ops.push(createOption(options.length));
10 | setOptions(ops);
11 | };
12 | const saveOptions = (option) => {
13 | const ops = [...options];
14 | const i = ops.findIndex((o) => o.id === option.id);
15 | ops[i] = option;
16 | setOptions(ops);
17 | };
18 | const deleteOption = (opId) => {
19 | const temp = [...options];
20 | const ind = temp.findIndex((e) => e.id === opId);
21 | temp.splice(ind, 1);
22 | setOptions(temp);
23 | };
24 |
25 | if (type === SHORT_ANSWER || type === PARAGRAPH)
26 | return (
27 |
28 |
29 |
30 | );
31 | else
32 | return (
33 |
34 | {options.map((option) => (
35 |
42 | ))}
43 |
addNewOption()}
46 | >
47 | Add Options
48 |
49 |
50 | );
51 | };
52 |
53 | OptionCard.defaultProps = {
54 | type: MULTIPLE_CHOICE,
55 | options: [],
56 | };
57 | OptionCard.propTypes = {
58 | type: PropTypes.string,
59 | options: PropTypes.array,
60 | setOptions: PropTypes.func,
61 | };
62 | export default OptionCard;
63 |
--------------------------------------------------------------------------------
/src/components/cards/options/Option.js:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import IndividualOption from "components/cards/options/IndividualOption";
3 | import { SHORT_ANSWER, PARAGRAPH, MULTIPLE_CHOICE } from "data/optionTypes";
4 | import createOption from "components/helpers/createOption";
5 |
6 | const OptionCard = ({ type, options, setOptions }) => {
7 | const addNewOption = () => {
8 | const ops = [...options];
9 | ops.push(createOption(options.length));
10 | setOptions(ops);
11 | };
12 | const saveOptions = (option) => {
13 | const ops = [...options];
14 | const i = ops.findIndex((o) => o.id === option.id);
15 | ops[i] = option;
16 | setOptions(ops);
17 | };
18 | const deleteOption = (opId) => {
19 | const temp = [...options];
20 | const ind = temp.findIndex((e) => e.id === opId);
21 | temp.splice(ind, 1);
22 | setOptions(temp);
23 | };
24 |
25 | if (type === SHORT_ANSWER || type === PARAGRAPH)
26 | return (
27 |
28 |
29 |
30 | );
31 | else
32 | return (
33 |
34 | {options.map((option) => (
35 |
42 | ))}
43 |
addNewOption()}
46 | >
47 | Add Options
48 |
49 |
50 | );
51 | };
52 |
53 | OptionCard.defaultProps = {
54 | type: MULTIPLE_CHOICE,
55 | options: [],
56 | };
57 | OptionCard.propTypes = {
58 | type: PropTypes.string,
59 | options: PropTypes.array,
60 | setOptions: PropTypes.func,
61 | };
62 | export default OptionCard;
63 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | Google Forms
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "google-forms-clone",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@fortawesome/fontawesome-svg-core": "^1.2.36",
7 | "@fortawesome/free-regular-svg-icons": "^5.15.4",
8 | "@fortawesome/free-solid-svg-icons": "^5.15.4",
9 | "@fortawesome/react-fontawesome": "^0.1.16",
10 | "@reduxjs/toolkit": "^1.7.1",
11 | "@testing-library/jest-dom": "^5.16.1",
12 | "@testing-library/react": "^12.1.2",
13 | "@testing-library/user-event": "^13.5.0",
14 | "firebase": "^9.6.4",
15 | "formik": "^2.2.9",
16 | "prop-types": "^15.8.1",
17 | "react": "^17.0.2",
18 | "react-beautiful-dnd": "^13.1.0",
19 | "react-dom": "^17.0.2",
20 | "react-redux": "^7.2.6",
21 | "react-router-dom": "^6.2.1",
22 | "react-scripts": "5.0.0",
23 | "web-vitals": "^2.1.3",
24 | "yup": "^0.32.11"
25 | },
26 | "scripts": {
27 | "start": "react-scripts start",
28 | "build": "react-scripts build",
29 | "test": "react-scripts test",
30 | "eject": "react-scripts eject"
31 | },
32 | "eslintConfig": {
33 | "extends": [
34 | "react-app",
35 | "react-app/jest"
36 | ]
37 | },
38 | "browserslist": {
39 | "production": [
40 | ">0.2%",
41 | "not dead",
42 | "not op_mini all"
43 | ],
44 | "development": [
45 | "last 1 chrome version",
46 | "last 1 firefox version",
47 | "last 1 safari version"
48 | ]
49 | },
50 | "devDependencies": {
51 | "autoprefixer": "^10.4.2",
52 | "eslint": "^7.32.0",
53 | "eslint-config-react-app": "^7.0.0",
54 | "eslint-config-standard": "^16.0.3",
55 | "eslint-plugin-import": "^2.25.4",
56 | "eslint-plugin-node": "^11.1.0",
57 | "eslint-plugin-promise": "^5.2.0",
58 | "eslint-plugin-react": "^7.28.0",
59 | "postcss": "^8.4.5",
60 | "tailwindcss": "^3.0.15"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/views/CreateForm.js:
--------------------------------------------------------------------------------
1 | import ThemeEditor from "components/theme/themeEditor";
2 | import FormHeader from "components/layout/headers/FormHeader";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import { useParams, Outlet } from "react-router-dom";
5 | import { useEffect, useState } from "react";
6 | import { getForm } from "services/firebase/firestore.firebase";
7 | import { setForm, setLoading } from "store/data/form.slice";
8 | import Loading from "components/loaders/page.loader";
9 |
10 | const CreateForm = () => {
11 | const { type } = useParams();
12 | const { theme, title } = useSelector((state) => state.form);
13 | const [themeEditorVisibility, setThemeEditorVisibility] = useState(false);
14 | const toggleThemeEditor = () => {
15 | setThemeEditorVisibility(!themeEditorVisibility);
16 | };
17 |
18 | const { user } = useSelector((state) => state.authentication);
19 | const { loading } = useSelector((state) => state.form);
20 | const dispatch = useDispatch();
21 |
22 | useEffect(() => {
23 | if (type !== "blank") {
24 | const dispatchCallback = (formData) => {
25 | dispatch(setForm(formData.form));
26 | };
27 | dispatch(setLoading(true));
28 | getForm(user.uid, type, dispatchCallback);
29 | }
30 | }, [user.uid, type, dispatch]);
31 | if (loading) return ;
32 | else
33 | return (
34 |
35 |
40 |
45 |
46 |
47 | {themeEditorVisibility ? (
48 |
49 | ) : null}
50 |
51 | );
52 | };
53 |
54 | export default CreateForm;
55 |
--------------------------------------------------------------------------------
/src/components/dropdown/Dropdown.js:
--------------------------------------------------------------------------------
1 | import {
2 | faCaretDown,
3 | faCaretUp,
4 | faCheck,
5 | } from "@fortawesome/free-solid-svg-icons";
6 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
7 | import { useState } from "react";
8 |
9 | const Dropdown = ({ options, setSelected, defaultSelected }) => {
10 | const [selectedOption, setSelectedOption] = useState(
11 | defaultSelected ? defaultSelected : options[0].text
12 | );
13 | const [openDropdown, setOpenDropdown] = useState(false);
14 | const toggleDropdownOptions = () => {
15 | setOpenDropdown(!openDropdown);
16 | };
17 | const handleSelectOption = (op) => {
18 | setSelectedOption(op);
19 | setSelected(op);
20 | toggleDropdownOptions();
21 | };
22 | return (
23 |
24 |
28 |
{selectedOption}
29 | {openDropdown ? (
30 |
31 | ) : (
32 |
33 | )}
34 |
35 | {openDropdown ? (
36 |
37 | {options.map((op) => (
38 | {
41 | handleSelectOption(op.text);
42 | }}
43 | >
44 | {op.icon ? (
45 |
46 | ) : op.text === selectedOption ? (
47 |
48 | ) : (
49 |
50 | )}
51 | {op.text}
52 |
53 | ))}
54 |
55 | ) : null}
56 |
57 | );
58 | };
59 | export default Dropdown;
60 |
--------------------------------------------------------------------------------
/src/components/routes/public.routes.js:
--------------------------------------------------------------------------------
1 | import { Routes, Route } from "react-router-dom";
2 | import CreateForm from "views/CreateForm";
3 | import Home from "views/Home";
4 | import { useSelector } from "react-redux";
5 | import Responses from "components/form/Responses";
6 | import Settings from "components/form/Settings";
7 | import NotFound from "views/404";
8 | import Edit from "components/form/Edit";
9 | import PropTypes from "prop-types";
10 | import Landing from "views/Landing";
11 | import PrivateRoute from "components/routes/private.routes";
12 |
13 | const PublicRoutes = ({ logged }) => {
14 | const { theme } = useSelector((state) => state.form);
15 |
16 | return (
17 |
18 |
22 |
23 |
24 | }
25 | />
26 |
30 |
31 |
32 | }
33 | >
34 |
35 |
40 |
41 |
42 | }
43 | />
44 |
48 |
49 |
50 | }
51 | />
52 |
56 |
57 |
58 | }
59 | />
60 |
61 |
62 | } />
63 | } />
64 |
65 | );
66 | };
67 |
68 | PublicRoutes.defaultProps = {
69 | logged: false,
70 | };
71 | PublicRoutes.propTypes = {
72 | logged: PropTypes.bool,
73 | };
74 | export default PublicRoutes;
75 |
--------------------------------------------------------------------------------
/src/components/cards/Options/individualOption.js:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import { useState } from "react";
3 | import { CHECKBOX, MULTIPLE_CHOICE } from "data/optionTypes";
4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5 | import { faTimes } from "@fortawesome/free-solid-svg-icons";
6 | import { useSelector } from "react-redux";
7 |
8 | const IndividualOption = ({ type, option, deleteOption, saveOption }) => {
9 | const [iOption, setIOption] = useState(option.text);
10 |
11 | const handleInputChange = (e) => {
12 | setIOption(e.target.value);
13 | };
14 |
15 | const handleFocusOut = () => {
16 | saveOption({ id: option.id, text: iOption });
17 | };
18 | const { theme } = useSelector((state) => state.form);
19 |
20 | return (
21 |
49 | );
50 | };
51 |
52 | IndividualOption.propTypes = {
53 | type: PropTypes.string,
54 | option: PropTypes.object,
55 | deleteOption: PropTypes.func.isRequired,
56 | saveOption: PropTypes.func.isRequired,
57 | };
58 |
59 | IndividualOption.defaultProps = {
60 | option: { id: undefined, text: "option" },
61 | type: MULTIPLE_CHOICE,
62 | };
63 |
64 | export default IndividualOption;
65 |
--------------------------------------------------------------------------------
/src/components/cards/options/IndividualOption.js:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import { useState } from "react";
3 | import { CHECKBOX, MULTIPLE_CHOICE } from "data/optionTypes";
4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5 | import { faTimes } from "@fortawesome/free-solid-svg-icons";
6 | import { useSelector } from "react-redux";
7 |
8 | const IndividualOption = ({ type, option, deleteOption, saveOption }) => {
9 | const [iOption, setIOption] = useState(option.text);
10 |
11 | const handleInputChange = (e) => {
12 | setIOption(e.target.value);
13 | };
14 |
15 | const handleFocusOut = () => {
16 | saveOption({ id: option.id, text: iOption });
17 | };
18 | const { theme } = useSelector((state) => state.form);
19 |
20 | return (
21 |
49 | );
50 | };
51 |
52 | IndividualOption.propTypes = {
53 | type: PropTypes.string,
54 | option: PropTypes.object,
55 | deleteOption: PropTypes.func.isRequired,
56 | saveOption: PropTypes.func.isRequired,
57 | };
58 |
59 | IndividualOption.defaultProps = {
60 | option: { id: undefined, text: "option" },
61 | type: MULTIPLE_CHOICE,
62 | };
63 |
64 | export default IndividualOption;
65 |
--------------------------------------------------------------------------------
/src/views/Landing.js:
--------------------------------------------------------------------------------
1 | import FilledButton from "components/buttons/FilledButton";
2 | import OutLinedButton from "components/buttons/OutLinedButton";
3 | import LandingHeader from "components/layout/headers/LandingHeader";
4 | import bg from "assets/bg-1.png";
5 | import { useDispatch } from "react-redux";
6 | import {
7 | setUser,
8 | startLoading,
9 | } from "store/authentication/authentication.slice";
10 | import { SignIn } from "services/firebase/auth.firebase";
11 | import { useNavigate } from "react-router-dom";
12 |
13 | const Landing = () => {
14 | const dispatch = useDispatch();
15 | const navigate = useNavigate();
16 |
17 | const dispatchCallback = (payload) => {
18 | dispatch(setUser(payload));
19 | };
20 | const signIn = () => {
21 | dispatch(startLoading());
22 | SignIn(dispatchCallback);
23 | navigate("/");
24 | };
25 |
26 | return (
27 |
28 |
29 |
30 |
31 |
32 |
36 | Get insights quickly, with Google Forms
37 |
38 |
39 | Easily create and share online forms and surveys, and analyze
40 | responses in real-time.
41 |
42 |
43 |
48 | signIn()}
52 | />
53 |
54 |
55 |
56 |
61 |
62 |
63 |
64 |
65 | );
66 | };
67 |
68 | export default Landing;
69 |
--------------------------------------------------------------------------------
/src/components/dropdown/DropdownwithIcon.js:
--------------------------------------------------------------------------------
1 | import { faEllipsisV } from "@fortawesome/free-solid-svg-icons";
2 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3 | import { useRef, useState } from "react";
4 | import useOutsideAlert from "components/dropdown/useOutsideAlert";
5 | import PropTypes from "prop-types";
6 |
7 | const DropdownWithIcon = ({
8 | options,
9 | renameEvent,
10 | removeEvent,
11 | openInNewTab,
12 | }) => {
13 | const dropDownRef = useRef(null);
14 | const [openDropdown, setOpenDropdown] = useState(false);
15 | const toggleDropdownOptions = () => {
16 | setOpenDropdown(!openDropdown);
17 | };
18 | const handleSelectOption = (op) => {
19 | toggleDropdownOptions();
20 | if (op === "Rename") renameEvent();
21 | else if (op === "Remove") removeEvent();
22 | else openInNewTab();
23 | };
24 | const actionCallback = () => {
25 | setOpenDropdown(false);
26 | };
27 | useOutsideAlert(dropDownRef, actionCallback);
28 | return (
29 |
30 |
38 | {openDropdown ? (
39 |
43 | {options.map((op) => (
44 | {
48 | handleSelectOption(op.text);
49 | }}
50 | >
51 | {op.icon ? (
52 |
53 | ) : (
54 |
55 | )}
56 | {op.text}
57 |
58 | ))}
59 |
60 | ) : null}
61 |
62 | );
63 | };
64 |
65 | DropdownWithIcon.propTypes = {
66 | options: PropTypes.array,
67 | renameEvent: PropTypes.func,
68 | removeEvent: PropTypes.func,
69 | openInNewTab: PropTypes.func.isRequired,
70 | };
71 |
72 | export default DropdownWithIcon;
73 |
--------------------------------------------------------------------------------
/src/data/Templates.js:
--------------------------------------------------------------------------------
1 | import eventReg from "assets/eventReg.png";
2 | import newForm from "assets/newForm.png";
3 | import partyInv from "assets/partyInv.png";
4 | import rsvp from "assets/rsvp.png";
5 | import contactInfo from "assets/contactInfo.png";
6 | import {
7 | faImage,
8 | faPlayCircle,
9 | faPlusSquare,
10 | } from "@fortawesome/free-regular-svg-icons";
11 | import { dropdownOptions } from "data/optionTypes";
12 | import {
13 | faFileImport,
14 | faPlusCircle,
15 | faTextHeight,
16 | } from "@fortawesome/free-solid-svg-icons";
17 |
18 | export const formTemplates = [
19 | { name: "Blank", img: newForm, url: "/create/blank/edit" },
20 | {
21 | name: "Event Registration",
22 | img: eventReg,
23 | url: "/create/event-registeration",
24 | },
25 | {
26 | name: "Contact Information",
27 | img: contactInfo,
28 | url: "/create/contact-information",
29 | },
30 | { name: "Party Invite", img: partyInv, url: "/create/party-invite" },
31 | { name: "RSVP", img: rsvp, url: "/create/rsvp" },
32 | ];
33 |
34 | export const ownershipFilters = [
35 | { text: "Owned by anyone" },
36 | { text: "Owned by me" },
37 | { text: "Not owned by me" },
38 | ];
39 |
40 | export const formSamples = [
41 | {
42 | id: "nudrigb43754398",
43 | title: "CN Lab Groups",
44 | img: contactInfo,
45 | date: "Jul 10, 2021",
46 | lastAction: "modified",
47 | shared: false,
48 | },
49 | {
50 | id: "dghejrhgkeergh",
51 | title: "Party Invite",
52 | img: partyInv,
53 | date: "Jan 11, 2022",
54 | lastAction: "created",
55 | shared: true,
56 | },
57 | {
58 | id: "hrejbdyugdgerhg",
59 | title: "Event Registeration",
60 | img: eventReg,
61 | date: "Apr 23, 2020",
62 | lastAction: "created",
63 | shared: false,
64 | },
65 | ];
66 |
67 | export const questionTemplate = [
68 | {
69 | id: "gyusegvybct",
70 | title: "Question",
71 | options: [{ id: 0, text: "Option 1" }],
72 | optionType: dropdownOptions[2],
73 | required: true,
74 | },
75 | {
76 | id: "hv4nu5huy45nh",
77 | title: "Question",
78 | options: [{ id: 0, text: "Option 1" }],
79 | optionType: dropdownOptions[3],
80 | required: false,
81 | },
82 | ];
83 |
84 | export const toolBarActions = [
85 | { id: 1, icon: faPlusCircle, label: "Add Question" },
86 | { id: 2, icon: faFileImport, label: "Import Question" },
87 | { id: 3, icon: faTextHeight, label: "Add Title and Description" },
88 | { id: 4, icon: faImage, label: "Add Image" },
89 | { id: 5, icon: faPlayCircle, label: "Add Video" },
90 | { id: 6, icon: faPlusSquare, label: "Add Section" },
91 | ];
92 |
--------------------------------------------------------------------------------
/src/data/templates.js:
--------------------------------------------------------------------------------
1 | import eventReg from "assets/eventReg.png";
2 | import newForm from "assets/newForm.png";
3 | import partyInv from "assets/partyInv.png";
4 | import rsvp from "assets/rsvp.png";
5 | import contactInfo from "assets/contactInfo.png";
6 | import {
7 | faImage,
8 | faPlayCircle,
9 | faPlusSquare,
10 | } from "@fortawesome/free-regular-svg-icons";
11 | import { dropdownOptions } from "data/optionTypes";
12 | import {
13 | faFileImport,
14 | faPlusCircle,
15 | faTextHeight,
16 | } from "@fortawesome/free-solid-svg-icons";
17 |
18 | export const formTemplates = [
19 | { name: "Blank", img: newForm, url: "/create/blank/edit" },
20 | {
21 | name: "Event Registration",
22 | img: eventReg,
23 | url: "/create/event-registeration",
24 | },
25 | {
26 | name: "Contact Information",
27 | img: contactInfo,
28 | url: "/create/contact-information",
29 | },
30 | { name: "Party Invite", img: partyInv, url: "/create/party-invite" },
31 | { name: "RSVP", img: rsvp, url: "/create/rsvp" },
32 | ];
33 |
34 | export const ownershipFilters = [
35 | { text: "Owned by anyone" },
36 | { text: "Owned by me" },
37 | { text: "Not owned by me" },
38 | ];
39 |
40 | export const formSamples = [
41 | {
42 | id: "nudrigb43754398",
43 | title: "CN Lab Groups",
44 | img: contactInfo,
45 | date: "Jul 10, 2021",
46 | lastAction: "modified",
47 | shared: false,
48 | },
49 | {
50 | id: "dghejrhgkeergh",
51 | title: "Party Invite",
52 | img: partyInv,
53 | date: "Jan 11, 2022",
54 | lastAction: "created",
55 | shared: true,
56 | },
57 | {
58 | id: "hrejbdyugdgerhg",
59 | title: "Event Registeration",
60 | img: eventReg,
61 | date: "Apr 23, 2020",
62 | lastAction: "created",
63 | shared: false,
64 | },
65 | ];
66 |
67 | export const questionTemplate = [
68 | {
69 | id: "gyusegvybct",
70 | title: "Question",
71 | options: [{ id: 0, text: "Option 1" }],
72 | optionType: dropdownOptions[2],
73 | required: true,
74 | },
75 | {
76 | id: "hv4nu5huy45nh",
77 | title: "Question",
78 | options: [{ id: 0, text: "Option 1" }],
79 | optionType: dropdownOptions[3],
80 | required: false,
81 | },
82 | ];
83 |
84 | export const toolBarActions = [
85 | { id: 1, icon: faPlusCircle, label: "Add Question" },
86 | { id: 2, icon: faFileImport, label: "Import Question" },
87 | { id: 3, icon: faTextHeight, label: "Add Title and Description" },
88 | { id: 4, icon: faImage, label: "Add Image" },
89 | { id: 5, icon: faPlayCircle, label: "Add Video" },
90 | { id: 6, icon: faPlusSquare, label: "Add Section" },
91 | ];
92 |
--------------------------------------------------------------------------------
/src/components/layout/Headers/HomeHeader.js:
--------------------------------------------------------------------------------
1 | import { useSelector, useDispatch } from "react-redux";
2 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3 | import { faBars, faSearch } from "@fortawesome/free-solid-svg-icons";
4 | import Logo from "assets/logo.png";
5 | import dots from "assets/dots.png";
6 | import { useState } from "react";
7 | import { SignOut } from "services/firebase/auth.firebase";
8 | import {
9 | startLoading,
10 | setUser,
11 | } from "store/authentication/authentication.slice";
12 | import Icon from "components/icon/Icon";
13 |
14 | const HomeHeader = () => {
15 | const { user } = useSelector((state) => state.authentication);
16 | const dispatch = useDispatch();
17 | const [ddState, setDdState] = useState(false);
18 | const toggleDropdown = () => {
19 | setDdState(!ddState);
20 | };
21 |
22 | const signOut = () => {
23 | dispatch(startLoading());
24 | SignOut((user) => {
25 | dispatch(setUser({user}));
26 | });
27 | };
28 |
29 | return (
30 |
31 |
32 |
33 |
34 |
Forms
35 |
36 |
46 |
47 |
48 |
49 |
50 |
51 |
56 | {ddState ? (
57 |
61 | Logout
62 |
63 | ) : null}
64 |
65 |
66 |
67 | );
68 | };
69 | export default HomeHeader;
70 |
--------------------------------------------------------------------------------
/src/components/layout/headers/HomeHeader.js:
--------------------------------------------------------------------------------
1 | import { useSelector, useDispatch } from "react-redux";
2 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3 | import { faBars, faSearch } from "@fortawesome/free-solid-svg-icons";
4 | import Logo from "assets/logo.png";
5 | import dots from "assets/dots.png";
6 | import { useState } from "react";
7 | import { SignOut } from "services/firebase/auth.firebase";
8 | import {
9 | startLoading,
10 | setUser,
11 | } from "store/authentication/authentication.slice";
12 | import Icon from "components/icon/Icon";
13 |
14 | const HomeHeader = () => {
15 | const { user } = useSelector((state) => state.authentication);
16 | const dispatch = useDispatch();
17 | const [ddState, setDdState] = useState(false);
18 | const toggleDropdown = () => {
19 | setDdState(!ddState);
20 | };
21 |
22 | const signOut = () => {
23 | dispatch(startLoading());
24 | SignOut((user) => {
25 | dispatch(setUser({user}));
26 | });
27 | };
28 |
29 | return (
30 |
31 |
32 |
33 |
34 |
Forms
35 |
36 |
46 |
47 |
48 |
49 |
50 |
51 |
56 | {ddState ? (
57 |
61 | Logout
62 |
63 | ) : null}
64 |
65 |
66 |
67 | );
68 | };
69 | export default HomeHeader;
70 |
--------------------------------------------------------------------------------
/src/assets/fonts/fonts.css:
--------------------------------------------------------------------------------
1 | /* #### Generated By: http://www.cufonfonts.com #### */
2 |
3 | @font-face {
4 | font-family: "Product Sans Regular";
5 | font-style: normal;
6 | font-weight: normal;
7 | src: local("Product Sans Regular"),
8 | url("ProductSans-Regular.woff") format("woff");
9 | }
10 |
11 | @font-face {
12 | font-family: "Product Sans Italic";
13 | font-style: normal;
14 | font-weight: normal;
15 | src: local("Product Sans Italic"),
16 | url("ProductSans-Italic.woff") format("woff");
17 | }
18 |
19 | @font-face {
20 | font-family: "Product Sans Thin Regular";
21 | font-style: normal;
22 | font-weight: normal;
23 | src: local("Product Sans Thin Regular"),
24 | url("ProductSans-Thin.woff") format("woff");
25 | }
26 |
27 | @font-face {
28 | font-family: "Product Sans Light Regular";
29 | font-style: normal;
30 | font-weight: normal;
31 | src: local("Product Sans Light Regular"),
32 | url("ProductSans-Light.woff") format("woff");
33 | }
34 |
35 | @font-face {
36 | font-family: "Product Sans Medium Regular";
37 | font-style: normal;
38 | font-weight: normal;
39 | src: local("Product Sans Medium Regular"),
40 | url("ProductSans-Medium.woff") format("woff");
41 | }
42 |
43 | @font-face {
44 | font-family: "Product Sans Black Regular";
45 | font-style: normal;
46 | font-weight: normal;
47 | src: local("Product Sans Black Regular"),
48 | url("ProductSans-Black.woff") format("woff");
49 | }
50 |
51 | @font-face {
52 | font-family: "Product Sans Thin Italic";
53 | font-style: normal;
54 | font-weight: normal;
55 | src: local("Product Sans Thin Italic"),
56 | url("ProductSans-ThinItalic.woff") format("woff");
57 | }
58 |
59 | @font-face {
60 | font-family: "Product Sans Light Italic";
61 | font-style: normal;
62 | font-weight: normal;
63 | src: local("Product Sans Light Italic"),
64 | url("ProductSans-LightItalic.woff") format("woff");
65 | }
66 |
67 | @font-face {
68 | font-family: "Product Sans Medium Italic";
69 | font-style: normal;
70 | font-weight: normal;
71 | src: local("Product Sans Medium Italic"),
72 | url("ProductSans-MediumItalic.woff") format("woff");
73 | }
74 |
75 | @font-face {
76 | font-family: "Product Sans Bold";
77 | font-style: normal;
78 | font-weight: normal;
79 | src: local("Product Sans Bold"), url("ProductSans-Bold.woff") format("woff");
80 | }
81 |
82 | @font-face {
83 | font-family: "Product Sans Bold Italic";
84 | font-style: normal;
85 | font-weight: normal;
86 | src: local("Product Sans Bold Italic"),
87 | url("ProductSans-BoldItalic.woff") format("woff");
88 | }
89 |
90 | @font-face {
91 | font-family: "Product Sans Black Italic";
92 | font-style: normal;
93 | font-weight: normal;
94 | src: local("Product Sans Black Italic"),
95 | url("ProductSans-BlackItalic.woff") format("woff");
96 | }
97 |
--------------------------------------------------------------------------------
/src/components/dropdown/CustomDropdown.js:
--------------------------------------------------------------------------------
1 | import {
2 | faCaretDown,
3 | faCaretUp,
4 | faCheck,
5 | } from "@fortawesome/free-solid-svg-icons";
6 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
7 | import { useState } from "react";
8 | import PropTypes from "prop-types";
9 |
10 | const CustomDropdown = ({ options, setSelected, defaultSelected, type }) => {
11 | const [selectedOption, setSelectedOption] = useState(
12 | defaultSelected ? defaultSelected : options[0]
13 | );
14 | const [openDropdown, setOpenDropdown] = useState(false);
15 | const toggleDropdownOptions = () => {
16 | setOpenDropdown(!openDropdown);
17 | };
18 | const handleSelectOption = (op) => {
19 | setSelectedOption(op);
20 | setSelected(op.text);
21 | toggleDropdownOptions();
22 | };
23 |
24 | return (
25 |
26 |
30 |
31 | {selectedOption.icon ? (
32 |
33 | ) : null}
34 |
35 |
40 | {selectedOption.text}
41 |
42 |
43 | {openDropdown ? (
44 |
45 | ) : (
46 |
47 | )}
48 |
49 | {openDropdown ? (
50 |
51 | {options.map((op) => (
52 | {
58 | handleSelectOption(op);
59 | }}
60 | >
61 | {op.icon ? (
62 |
63 | ) : op.text === selectedOption.text ? (
64 |
65 | ) : (
66 |
67 | )}
68 | {op.text}
69 |
70 | ))}
71 |
72 | ) : null}
73 |
74 | );
75 | };
76 |
77 | CustomDropdown.propTypes = {
78 | options: PropTypes.array.isRequired,
79 | setSelected: PropTypes.func.isRequired,
80 | defaultSelected: PropTypes.object,
81 | type: PropTypes.string,
82 | };
83 |
84 | CustomDropdown.defaultProps = {
85 | defaultSelected: null,
86 | type: undefined,
87 | };
88 |
89 | export default CustomDropdown;
90 |
--------------------------------------------------------------------------------
/src/components/cards/TitleCard.js:
--------------------------------------------------------------------------------
1 | import { PROGRESS_SAVING } from "data/statusMessages";
2 | import PropTypes from "prop-types";
3 | import { useState } from "react";
4 | import { useDispatch, useSelector } from "react-redux";
5 | import {
6 | setFormDescriptionInDB,
7 | setFormTitleInDB,
8 | } from "services/firebase/firestore.firebase";
9 | import { setTitle, setDescription, setSaved } from "store/data/form.slice";
10 | import { useFormik } from "formik";
11 | import { headerSchema as validationSchema } from "components/helpers/validations";
12 |
13 | const TitleCard = ({ selected }) => {
14 | const dispatch = useDispatch();
15 | const { id, theme, title, description } = useSelector((state) => state.form);
16 | const [formDescription, setFormDescription] = useState(description);
17 |
18 | const { handleChange, values, errors } = useFormik({
19 | initialValues: { title },
20 | validationSchema,
21 | });
22 |
23 | const savedCallBack = (msg) => {
24 | dispatch(setSaved(msg));
25 | };
26 |
27 | const handleDescriptionChange = (e) => {
28 | setFormDescription(e.target.value);
29 | };
30 | const saveTitle = (e) => {
31 | if (!errors.title) {
32 | savedCallBack(PROGRESS_SAVING);
33 | setTitle(values.title);
34 | setFormTitleInDB(id, values.title, savedCallBack);
35 | }
36 | };
37 | const saveDescription = (e) => {
38 | setDescription(formDescription);
39 | savedCallBack(PROGRESS_SAVING);
40 | setFormDescriptionInDB(id, formDescription, savedCallBack);
41 | };
42 |
43 | return (
44 |
75 | );
76 | };
77 |
78 | TitleCard.defaultProps = {
79 | title: "Untitled Form",
80 | description: "",
81 | color: "purple",
82 | selected: true,
83 | };
84 |
85 | TitleCard.propTypes = {
86 | title: PropTypes.string,
87 | description: PropTypes.string,
88 | color: PropTypes.string,
89 | };
90 |
91 | export default TitleCard;
92 |
--------------------------------------------------------------------------------
/src/components/layout/Headers/FormHeader.js:
--------------------------------------------------------------------------------
1 | import { faFolder, faStar, faEye } from "@fortawesome/free-regular-svg-icons";
2 | import {
3 | faEllipsisV,
4 | faPalette,
5 | faRedo,
6 | faUndo,
7 | } from "@fortawesome/free-solid-svg-icons";
8 | import logo from "assets/logo.png";
9 | import FilledButton from "components/buttons/FilledButton";
10 | import { useSelector, useDispatch } from "react-redux";
11 | import { useState } from "react";
12 | import { SignOut } from "services/firebase/auth.firebase";
13 | import {
14 | startLoading,
15 | setUser,
16 | } from "store/authentication/authentication.slice";
17 | import { Link } from "react-router-dom";
18 | import Icon from "components/icon/Icon";
19 | import NavBar from "components/layout/navigation/NavBar";
20 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
21 |
22 | const FormHeader = ({ id, title, toggleThemeEditor }) => {
23 | const dispatch = useDispatch();
24 | const [ddState, setDdState] = useState(false);
25 | const toggleDropdown = () => {
26 | setDdState(!ddState);
27 | };
28 | const signOut = () => {
29 | dispatch(startLoading());
30 | SignOut((user) => {
31 | dispatch(setUser({ user }));
32 | });
33 | };
34 | const { user } = useSelector((state) => state.authentication);
35 | const { saved } = useSelector((state) => state.form);
36 | return (
37 |
38 |
39 |
40 |
41 |
42 |
43 |
{title}
44 |
45 |
46 |
47 |
{saved}
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
61 |
62 |
63 |
64 |
69 | {ddState ? (
70 |
74 | Logout
75 |
76 | ) : null}
77 |
78 |
79 |
80 |
81 |
82 | );
83 | };
84 |
85 | FormHeader.defaultProps = {
86 | title: "Untitled Form",
87 | };
88 |
89 | export default FormHeader;
90 |
--------------------------------------------------------------------------------
/src/components/layout/headers/FormHeader.js:
--------------------------------------------------------------------------------
1 | import { faFolder, faStar, faEye } from "@fortawesome/free-regular-svg-icons";
2 | import {
3 | faEllipsisV,
4 | faPalette,
5 | faRedo,
6 | faUndo,
7 | } from "@fortawesome/free-solid-svg-icons";
8 | import logo from "assets/logo.png";
9 | import FilledButton from "components/buttons/FilledButton";
10 | import { useSelector, useDispatch } from "react-redux";
11 | import { useState } from "react";
12 | import { SignOut } from "services/firebase/auth.firebase";
13 | import {
14 | startLoading,
15 | setUser,
16 | } from "store/authentication/authentication.slice";
17 | import { Link } from "react-router-dom";
18 | import Icon from "components/icon/Icon";
19 | import NavBar from "components/layout/navigation/NavBar";
20 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
21 |
22 | const FormHeader = ({ id, title, toggleThemeEditor }) => {
23 | const dispatch = useDispatch();
24 | const [ddState, setDdState] = useState(false);
25 | const toggleDropdown = () => {
26 | setDdState(!ddState);
27 | };
28 | const signOut = () => {
29 | dispatch(startLoading());
30 | SignOut((user) => {
31 | dispatch(setUser({ user }));
32 | });
33 | };
34 | const { user } = useSelector((state) => state.authentication);
35 | const { saved } = useSelector((state) => state.form);
36 | return (
37 |
38 |
39 |
40 |
41 |
42 |
43 |
{title}
44 |
45 |
46 |
47 |
{saved}
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
61 |
62 |
63 |
64 |
69 | {ddState ? (
70 |
74 | Logout
75 |
76 | ) : null}
77 |
78 |
79 |
80 |
81 |
82 | );
83 | };
84 |
85 | FormHeader.defaultProps = {
86 | title: "Untitled Form",
87 | };
88 |
89 | export default FormHeader;
90 |
--------------------------------------------------------------------------------
/src/components/form/Edit.js:
--------------------------------------------------------------------------------
1 | import TitleCard from "components/cards/TitleCard";
2 | import QuestionCard from "components/cards/QuestionCard";
3 | import { useState } from "react";
4 | import { useDispatch, useSelector } from "react-redux";
5 | import ToolBar from "components/toolbar/toolBar";
6 | import createQuestion from "components/helpers/createQuestion";
7 | import {
8 | addQuestion,
9 | setDraggedQuestion,
10 | setQuestion,
11 | setSaved,
12 | } from "store/data/form.slice";
13 | import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
14 | import { addQuestionInDB } from "services/firebase/firestore.firebase";
15 | import { PROGRESS_SAVING } from "data/statusMessages";
16 |
17 | const Edit = () => {
18 | const { questions, id } = useSelector((state) => state.form);
19 | const [selected, setSelected] = useState(questions[0].id);
20 | const dispatch = useDispatch();
21 |
22 | const savedCallBack = (saved) => {
23 | dispatch(setSaved(saved));
24 | };
25 | const setFormQuestion = (qid, question) => {
26 | dispatch(setQuestion({ id: qid, question }));
27 | };
28 |
29 | const addNewQuestion = () => {
30 | const quest = { question: createQuestion(questions.length) };
31 | dispatch(addQuestion(quest));
32 | dispatch(setSaved(PROGRESS_SAVING));
33 | addQuestionInDB(id, quest.question, savedCallBack);
34 | };
35 |
36 | const selectQuestionCard = (qid) => {
37 | setSelected(qid);
38 | };
39 |
40 | const handleOnDragEnd = (result) => {
41 | if (!result.destination) return;
42 | dispatch(setDraggedQuestion({ result }));
43 | };
44 |
45 | return (
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | {(provided) => (
55 |
60 | {questions.map((question, idx) => (
61 |
66 | {(provided) => (
67 |
72 |
79 |
80 | )}
81 |
82 | ))}
83 | {provided.placeholder}
84 |
85 | )}
86 |
87 |
88 |
89 |
90 |
91 |
92 | );
93 | };
94 |
95 | export default Edit;
96 |
--------------------------------------------------------------------------------
/src/store/data/form.slice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 | import createQuestion from "components/helpers/createQuestion";
3 | import generateKey from "components/helpers/generateKey";
4 |
5 | export const formSlice = createSlice({
6 | name: "Untitled Form",
7 | initialState: {
8 | loading: false,
9 | saved: "",
10 | id: "",
11 | theme: {
12 | color: "purple",
13 | font: "basic",
14 | backgroundOpacity: 10,
15 | },
16 | title: "Untitled Form",
17 | description: "",
18 | questions: [createQuestion(0)],
19 | date: "",
20 | shared: true,
21 | error: null,
22 | },
23 | reducers: {
24 | addQuestion: (state, action) => {
25 | const temp = [...state.questions];
26 | const question = action.payload.question;
27 | temp.push(question);
28 | state.questions = temp;
29 | },
30 | setQuestion: (state, action) => {
31 | const temp = [...state.questions];
32 | const ind = temp.findIndex((x) => x.id === action.payload.id);
33 | temp[ind] = action.payload.question;
34 | state.questions = temp;
35 | },
36 | removeQuestion: (state, action) => {
37 | const temp = state.questions;
38 | const index = temp.findIndex((e) => e.id === action.payload.id);
39 | temp.splice(index, 1);
40 | state.questions = temp;
41 | },
42 | setDraggedQuestion: (state, action) => {
43 | const result = action.payload.result;
44 | const temp = [...state.questions];
45 | const [reorderedItem] = temp.splice(result.source.index, 1);
46 | temp.splice(result.destination.index, 0, reorderedItem);
47 | state.questions = temp;
48 | },
49 | duplicateQuestion: (state, action) => {
50 | const temp = state.questions;
51 | const index = temp.findIndex((e) => e.id === action.payload.id);
52 | const question = { ...action.payload.question };
53 | question.id = generateKey("question" + index);
54 | temp.splice(index, 0, question);
55 | state.questions = temp;
56 | },
57 | setColor: (state, action) => {
58 | state.theme.color = action.payload.color;
59 | },
60 | setFont: (state, action) => {
61 | state.theme.font = action.payload.font;
62 | },
63 | setBackgroundOpacity: (state, action) => {
64 | state.theme.backgroundOpacity = action.payload.opacity;
65 | },
66 | setTitle: (state, action) => {
67 | state.title = action.payload.title;
68 | },
69 | setDescription: (state, action) => {
70 | state.description = action.payload.description;
71 | },
72 | setForm: (state, action) => {
73 | if (action.payload.error) state.error = action.payload.error;
74 | else {
75 | state.id = action.payload.id;
76 | state.theme = action.payload.theme;
77 | state.title = action.payload.title;
78 | state.description = action.payload.description;
79 | state.questions = action.payload.questions;
80 | state.loading = false;
81 | state.date = action.payload.date;
82 | state.shared = action.payload.shared;
83 | }
84 | },
85 | setLoading: (state, action) => {
86 | state.loading = action.payload;
87 | },
88 | setSaved: (state, action) => {
89 | state.saved = action.payload;
90 | },
91 | },
92 | });
93 |
94 | export const {
95 | addQuestion,
96 | setQuestion,
97 | removeQuestion,
98 | theme,
99 | setColor,
100 | setFont,
101 | setBackgroundOpacity,
102 | setTitle,
103 | setDescription,
104 | duplicateQuestion,
105 | setDraggedQuestion,
106 | setForm,
107 | setLoading,
108 | setSaved,
109 | } = formSlice.actions;
110 | export default formSlice.reducer;
111 |
--------------------------------------------------------------------------------
/src/components/form/FormTile/FormTile.js:
--------------------------------------------------------------------------------
1 | import {
2 | faTextHeight,
3 | faTrashAlt,
4 | faExternalLinkAlt,
5 | } from "@fortawesome/free-solid-svg-icons";
6 | import slogo from "assets/logo2.svg";
7 | import DropdownWithIcon from "components/dropdown/DropdownwithIcon";
8 | import openInNewTab from "components/helpers/openInNewTab";
9 | import { LIST } from "data/viewTypes";
10 | import PropTypes from "prop-types";
11 | import { deleteFormFromDB } from "services/firebase/firestore.firebase";
12 | import RenameModal from "components/modals/RenameModal";
13 | import { useState } from "react";
14 |
15 | const FormTile = ({ formData, gridView, onClick, removeForm, renameForm }) => {
16 | const [renameModalVisibility, setRenameModalVisibility] = useState(false);
17 | const actions = [
18 | { id: 1, icon: faTextHeight, text: "Rename" },
19 | { id: 2, icon: faTrashAlt, text: "Remove" },
20 | { id: 3, icon: faExternalLinkAlt, text: "Open in new tab" },
21 | ];
22 | const renameCallBack = (name) => {
23 | renameForm(formData.id, name);
24 | };
25 | const toggleRenameModal = () => {
26 | setRenameModalVisibility(!renameModalVisibility);
27 | };
28 | const removeEvent = () => {
29 | deleteFormFromDB(formData.id);
30 | removeForm(formData.id);
31 | };
32 | const openLinkInNewTab = () => {
33 | openInNewTab(`/create/${formData.id}/edit`);
34 | };
35 |
36 | if (gridView)
37 | return (
38 |
39 |
40 |
41 |
42 |
43 |
44 |
{formData.title}
45 |
46 |
47 | {formData.date}
48 |
54 | {renameModalVisibility && (
55 |
59 | )}
60 |
61 | );
62 | else
63 | return (
64 |
68 |
69 |
70 |
71 |
72 |
{formData.title}
73 |
74 |
75 |
76 | {formData.lastAction === "modified" ? (
77 |
78 | Modified{" "}
79 |
80 | ) : null}
81 |
82 | {formData.date}
83 |
84 |
85 |
90 |
91 |
92 |
93 | );
94 | };
95 |
96 | FormTile.defaultProps = {
97 | type: LIST,
98 | };
99 | FormTile.propTypes = {
100 | type: PropTypes.string,
101 | formData: PropTypes.object.isRequired,
102 | onClick: PropTypes.func.isRequired,
103 | };
104 |
105 | export default FormTile;
106 |
--------------------------------------------------------------------------------
/src/components/form/formTile/FormTile.js:
--------------------------------------------------------------------------------
1 | import {
2 | faTextHeight,
3 | faTrashAlt,
4 | faExternalLinkAlt,
5 | } from "@fortawesome/free-solid-svg-icons";
6 | import slogo from "assets/logo2.svg";
7 | import DropdownWithIcon from "components/dropdown/DropdownwithIcon";
8 | import openInNewTab from "components/helpers/openInNewTab";
9 | import { LIST } from "data/viewTypes";
10 | import PropTypes from "prop-types";
11 | import { deleteFormFromDB } from "services/firebase/firestore.firebase";
12 | import RenameModal from "components/modals/RenameModal";
13 | import { useState } from "react";
14 |
15 | const FormTile = ({ formData, gridView, onClick, removeForm, renameForm }) => {
16 | const [renameModalVisibility, setRenameModalVisibility] = useState(false);
17 | const actions = [
18 | { id: 1, icon: faTextHeight, text: "Rename" },
19 | { id: 2, icon: faTrashAlt, text: "Remove" },
20 | { id: 3, icon: faExternalLinkAlt, text: "Open in new tab" },
21 | ];
22 | const renameCallBack = (name) => {
23 | renameForm(formData.id, name);
24 | };
25 | const toggleRenameModal = () => {
26 | setRenameModalVisibility(!renameModalVisibility);
27 | };
28 | const removeEvent = () => {
29 | deleteFormFromDB(formData.id);
30 | removeForm(formData.id);
31 | };
32 | const openLinkInNewTab = () => {
33 | openInNewTab(`/create/${formData.id}/edit`);
34 | };
35 |
36 | if (gridView)
37 | return (
38 |
39 |
40 |
41 |
42 |
43 |
44 |
{formData.title}
45 |
46 |
47 | {formData.date}
48 |
54 | {renameModalVisibility && (
55 |
59 | )}
60 |
61 | );
62 | else
63 | return (
64 |
68 |
69 |
70 |
71 |
72 |
{formData.title}
73 |
74 |
75 |
76 | {formData.lastAction === "modified" ? (
77 |
78 | Modified{" "}
79 |
80 | ) : null}
81 |
82 | {formData.date}
83 |
84 |
85 |
90 |
91 |
92 |
93 | );
94 | };
95 |
96 | FormTile.defaultProps = {
97 | type: LIST,
98 | };
99 | FormTile.propTypes = {
100 | type: PropTypes.string,
101 | formData: PropTypes.object.isRequired,
102 | onClick: PropTypes.func.isRequired,
103 | };
104 |
105 | export default FormTile;
106 |
--------------------------------------------------------------------------------
/src/components/theme/ThemeEditor.js:
--------------------------------------------------------------------------------
1 | import { faImage } from "@fortawesome/free-regular-svg-icons";
2 | import { faPalette, faTimes } from "@fortawesome/free-solid-svg-icons";
3 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4 | import Icon from "components/icon/Icon";
5 | import { useSelector } from "react-redux";
6 | import { colors, fonts } from "data/theme/themeOptions";
7 | import ColorComponent from "components/theme/colorComponent";
8 | import BackgroundColorComponent from "components/theme/backgroundColorComponent";
9 | import { useDispatch } from "react-redux";
10 | import {
11 | setBackgroundOpacity,
12 | setColor,
13 | setFont,
14 | setSaved,
15 | } from "store/data/form.slice";
16 | import PropTypes from "prop-types";
17 | import CustomDropdown from "components/dropdown/CustomDropdown";
18 | import { PROGRESS_SAVING } from "data/statusMessages";
19 | import { setThemeInDB } from "services/firebase/firestore.firebase";
20 |
21 | const ThemeEditor = ({ toggleThemeEditor }) => {
22 | const { id, theme } = useSelector((state) => state.form);
23 | const dispatch = useDispatch();
24 |
25 | const savedCallBack = (msg) => {
26 | dispatch(setSaved(msg));
27 | };
28 | const selectColor = (color) => {
29 | dispatch(setColor({ color }));
30 | savedCallBack(PROGRESS_SAVING);
31 | const temp = { ...theme };
32 | temp.color = color;
33 | setThemeInDB(id, temp, savedCallBack);
34 | };
35 | const setBGOpacity = (opacity) => {
36 | dispatch(setBackgroundOpacity({ opacity }));
37 | savedCallBack(PROGRESS_SAVING);
38 | const temp = { ...theme };
39 | temp.backgroundOpacity = opacity;
40 | setThemeInDB(id, temp, savedCallBack);
41 | };
42 | const setFormFont = (font) => {
43 | dispatch(setFont({ font: font }));
44 | savedCallBack(PROGRESS_SAVING);
45 | const temp = { ...theme };
46 | temp.font = font;
47 | setThemeInDB(id, temp, savedCallBack);
48 | };
49 | return (
50 |
51 |
52 |
53 |
57 |
Theme Options
58 |
59 |
60 |
61 |
62 |
HEADER
63 |
67 |
68 |
69 |
Choose Image
70 |
71 |
78 |
79 |
80 |
81 |
THEME COLOR
82 |
83 | {colors.map((color) => (
84 | selectColor(color)}
89 | />
90 | ))}
91 |
92 |
93 |
94 |
BACKGROUND COLOR
95 |
100 |
101 |
102 |
FONT STYLE
103 |
104 |
110 |
111 |
112 |
113 | );
114 | };
115 |
116 | ThemeEditor.propTypes = {
117 | toggleThemeEditor: PropTypes.func.isRequired,
118 | };
119 |
120 | export default ThemeEditor;
121 |
--------------------------------------------------------------------------------
/src/components/theme/themeEditor.js:
--------------------------------------------------------------------------------
1 | import { faImage } from "@fortawesome/free-regular-svg-icons";
2 | import { faPalette, faTimes } from "@fortawesome/free-solid-svg-icons";
3 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4 | import Icon from "components/icon/Icon";
5 | import { useSelector } from "react-redux";
6 | import { colors, fonts } from "data/theme/themeOptions";
7 | import ColorComponent from "components/theme/colorComponent";
8 | import BackgroundColorComponent from "components/theme/backgroundColorComponent";
9 | import { useDispatch } from "react-redux";
10 | import {
11 | setBackgroundOpacity,
12 | setColor,
13 | setFont,
14 | setSaved,
15 | } from "store/data/form.slice";
16 | import PropTypes from "prop-types";
17 | import CustomDropdown from "components/dropdown/CustomDropdown";
18 | import { PROGRESS_SAVING } from "data/statusMessages";
19 | import { setThemeInDB } from "services/firebase/firestore.firebase";
20 |
21 | const ThemeEditor = ({ toggleThemeEditor }) => {
22 | const { id, theme } = useSelector((state) => state.form);
23 | const dispatch = useDispatch();
24 |
25 | const savedCallBack = (msg) => {
26 | dispatch(setSaved(msg));
27 | };
28 | const selectColor = (color) => {
29 | dispatch(setColor({ color }));
30 | savedCallBack(PROGRESS_SAVING);
31 | const temp = { ...theme };
32 | temp.color = color;
33 | setThemeInDB(id, temp, savedCallBack);
34 | };
35 | const setBGOpacity = (opacity) => {
36 | dispatch(setBackgroundOpacity({ opacity }));
37 | savedCallBack(PROGRESS_SAVING);
38 | const temp = { ...theme };
39 | temp.backgroundOpacity = opacity;
40 | setThemeInDB(id, temp, savedCallBack);
41 | };
42 | const setFormFont = (font) => {
43 | dispatch(setFont({ font: font }));
44 | savedCallBack(PROGRESS_SAVING);
45 | const temp = { ...theme };
46 | temp.font = font;
47 | setThemeInDB(id, temp, savedCallBack);
48 | };
49 | return (
50 |
51 |
52 |
53 |
57 |
Theme Options
58 |
59 |
60 |
61 |
62 |
HEADER
63 |
67 |
68 |
69 |
Choose Image
70 |
71 |
78 |
79 |
80 |
81 |
THEME COLOR
82 |
83 | {colors.map((color) => (
84 | selectColor(color)}
89 | />
90 | ))}
91 |
92 |
93 |
94 |
BACKGROUND COLOR
95 |
100 |
101 |
102 |
FONT STYLE
103 |
104 |
110 |
111 |
112 |
113 | );
114 | };
115 |
116 | ThemeEditor.propTypes = {
117 | toggleThemeEditor: PropTypes.func.isRequired,
118 | };
119 |
120 | export default ThemeEditor;
121 |
--------------------------------------------------------------------------------
/src/assets/colors/Colors.css:
--------------------------------------------------------------------------------
1 | .gray-bg {
2 | @apply bg-gray;
3 | }
4 | .gray-border {
5 | @apply border-gray;
6 | }
7 | .gray-text {
8 | @apply text-gray;
9 | }
10 | .grayTextField:focus {
11 | @apply border-gray;
12 | }
13 | .gray10-bg {
14 | @apply bg-gray/10;
15 | }
16 | .gray20-bg {
17 | @apply bg-gray/20;
18 | }
19 | .gray30-bg {
20 | @apply bg-gray/30;
21 | }
22 | .grey0-bg {
23 | background-color: #f6f6f6;
24 | }
25 |
26 | .bluegray-bg {
27 | @apply bg-bluegray;
28 | }
29 | .bluegray-border {
30 | @apply border-bluegray;
31 | }
32 | .bluegray-text {
33 | @apply text-bluegray;
34 | }
35 | .bluegrayTextField:focus {
36 | @apply border-bluegray;
37 | }
38 | .bluegray10-bg {
39 | @apply bg-bluegray/10;
40 | }
41 | .bluegray20-bg {
42 | @apply bg-bluegray/20;
43 | }
44 | .bluegray30-bg {
45 | @apply bg-bluegray/30;
46 | }
47 | .bluegrey0-bg {
48 | background-color: #f6f6f6;
49 | }
50 |
51 | .green-bg {
52 | @apply bg-green;
53 | }
54 | .green-border {
55 | @apply border-green;
56 | }
57 | .green-text {
58 | @apply text-green;
59 | }
60 | .greenTextField:focus {
61 | @apply border-green;
62 | }
63 | .green10-bg {
64 | @apply bg-green/10;
65 | }
66 | .green20-bg {
67 | @apply bg-green/20;
68 | }
69 | .green30-bg {
70 | @apply bg-green/30;
71 | }
72 | .green0-bg {
73 | background-color: #f6f6f6;
74 | }
75 |
76 | .teal-bg {
77 | @apply bg-teal;
78 | }
79 | .teal-border {
80 | @apply border-teal;
81 | }
82 | .teal-text {
83 | @apply text-teal;
84 | }
85 | .tealTextField:focus {
86 | @apply border-teal;
87 | }
88 | .teal10-bg {
89 | @apply bg-teal/10;
90 | }
91 | .teal20-bg {
92 | @apply bg-teal/20;
93 | }
94 | .teal30-bg {
95 | @apply bg-teal/30;
96 | }
97 | .teal0-bg {
98 | background-color: #f6f6f6;
99 | }
100 |
101 | .orange-bg {
102 | @apply bg-orange;
103 | }
104 | .orange-border {
105 | @apply border-orange;
106 | }
107 | .orange-text {
108 | @apply text-orange;
109 | }
110 | .orangeTextField:focus {
111 | @apply border-orange;
112 | }
113 | .orange10-bg {
114 | @apply bg-orange/10;
115 | }
116 | .orange20-bg {
117 | @apply bg-orange/20;
118 | }
119 | .orange30-bg {
120 | @apply bg-orange/30;
121 | }
122 | .orange0-bg {
123 | background-color: #f6f6f6;
124 | }
125 |
126 | .redorange-bg {
127 | @apply bg-redorange;
128 | }
129 | .redorange-border {
130 | @apply border-redorange;
131 | }
132 | .redorange-text {
133 | @apply text-redorange;
134 | }
135 | .redorangeTextField:focus {
136 | @apply border-redorange;
137 | }
138 | .redorange10-bg {
139 | @apply bg-redorange/10;
140 | }
141 | .redorange20-bg {
142 | @apply bg-redorange/20;
143 | }
144 | .redorange30-bg {
145 | @apply bg-redorange/30;
146 | }
147 | .redorange0-bg {
148 | background-color: #f6f6f6;
149 | }
150 |
151 | .cyan-bg {
152 | @apply bg-cyan;
153 | }
154 | .cyan-border {
155 | @apply border-cyan;
156 | }
157 | .cyan-text {
158 | @apply text-cyan;
159 | }
160 | .cyanTextField:focus {
161 | @apply border-cyan;
162 | }
163 | .cyan10-bg {
164 | @apply bg-cyan/10;
165 | }
166 | .cyan20-bg {
167 | @apply bg-cyan/20;
168 | }
169 | .cyan30-bg {
170 | @apply bg-cyan/30;
171 | }
172 | .cyan0-bg {
173 | background-color: #f6f6f6;
174 | }
175 |
176 | .lightblue-bg {
177 | @apply bg-lightblue;
178 | }
179 | .lightblue-border {
180 | @apply border-lightblue;
181 | }
182 | .lightblue-text {
183 | @apply text-lightblue;
184 | }
185 | .lightblueTextField:focus {
186 | @apply border-lightblue;
187 | }
188 | .lightblue10-bg {
189 | @apply bg-lightblue/10;
190 | }
191 | .lightblue20-bg {
192 | @apply bg-lightblue/20;
193 | }
194 | .lightblue30-bg {
195 | @apply bg-lightblue/30;
196 | }
197 | .lightblue0-bg {
198 | background-color: #f6f6f6;
199 | }
200 |
201 | .indigo-bg {
202 | @apply bg-indigo;
203 | }
204 | .indigo-border {
205 | @apply border-indigo;
206 | }
207 | .indigo-text {
208 | @apply text-indigo;
209 | }
210 | .indigoTextField:focus {
211 | @apply border-indigo;
212 | }
213 | .indigo10-bg {
214 | @apply bg-indigo/10;
215 | }
216 | .indigo20-bg {
217 | @apply bg-indigo/20;
218 | }
219 | .indigo30-bg {
220 | @apply bg-indigo/30;
221 | }
222 | .indigo0-bg {
223 | background-color: #f6f6f6;
224 | }
225 |
226 | .red-bg {
227 | @apply bg-red;
228 | }
229 | .red-border {
230 | @apply border-red;
231 | }
232 | .red-text {
233 | @apply text-red;
234 | }
235 | .redTextField:focus {
236 | @apply border-red;
237 | }
238 | .red10-bg {
239 | @apply bg-red/10;
240 | }
241 | .red20-bg {
242 | @apply bg-red/20;
243 | }
244 | .red30-bg {
245 | @apply bg-red/30;
246 | }
247 | .red0-bg {
248 | background-color: #f6f6f6;
249 | }
250 |
251 | .blue-bg {
252 | @apply bg-blue;
253 | }
254 | .blue-border {
255 | @apply border-blue;
256 | }
257 | .blue-text {
258 | @apply text-blue;
259 | }
260 | .blueTextField:focus {
261 | @apply border-blue;
262 | }
263 | .blue10-bg {
264 | @apply bg-blue/10;
265 | }
266 | .blue20-bg {
267 | @apply bg-blue/20;
268 | }
269 | .blue30-bg {
270 | @apply bg-blue/30;
271 | }
272 | .blue0-bg {
273 | background-color: #f6f6f6;
274 | }
275 |
276 | .purple-bg {
277 | @apply bg-purple;
278 | }
279 | .purple-border {
280 | @apply border-purple;
281 | }
282 | .purple-text {
283 | @apply text-purple;
284 | }
285 | .purpleTextField:focus {
286 | @apply border-purple;
287 | }
288 | .purple10-bg {
289 | @apply bg-purple/10;
290 | }
291 | .purple20-bg {
292 | @apply bg-purple/20;
293 | }
294 | .purple30-bg {
295 | @apply bg-purple/30;
296 | }
297 | .purple0-bg {
298 | background-color: #f6f6f6;
299 | }
300 |
--------------------------------------------------------------------------------
/src/services/firebase/firestore.firebase.js:
--------------------------------------------------------------------------------
1 | import { db } from "services/firebase/config.firebase";
2 | import {
3 | collection,
4 | getDocs,
5 | doc,
6 | getDoc,
7 | addDoc,
8 | setDoc,
9 | updateDoc,
10 | query,
11 | where,
12 | arrayRemove,
13 | arrayUnion,
14 | deleteDoc,
15 | } from "firebase/firestore";
16 | import {
17 | generateForm,
18 | generateFormPreview,
19 | } from "components/helpers/generateForm";
20 | import {
21 | ERR_NOT_AUTHORISED,
22 | ERR_SAVING_FAILED,
23 | SUCCESS_SAVED,
24 | } from "data/statusMessages";
25 |
26 | const formCollection = "forms";
27 |
28 | const getFormsFromFirebase = async (uid, dispatchCallback, loadDispatch) => {
29 | try {
30 | const q = query(collection(db, formCollection), where("uid", "==", uid));
31 | const qSnapshot = await getDocs(q);
32 | const formsData = [];
33 | qSnapshot.forEach((form) => {
34 | formsData.push(
35 | generateFormPreview(
36 | form.id,
37 | form.data().name,
38 | form.data().img,
39 | form.data().date.toDate().toDateString(),
40 | form.data().shared
41 | )
42 | );
43 | });
44 | dispatchCallback(formsData);
45 | loadDispatch();
46 | } catch (e) {
47 | console.error(e);
48 | }
49 | };
50 |
51 | const getForm = async (uid, formId, dispatchCallback) => {
52 | try {
53 | const docSnap = await getDoc(doc(db, formCollection, formId));
54 | const data = docSnap.data();
55 | if (data.uid !== uid) {
56 | dispatchCallback({ error: ERR_NOT_AUTHORISED });
57 | } else {
58 | if (docSnap.exists())
59 | dispatchCallback({
60 | form: generateForm(
61 | formId,
62 | data.theme,
63 | data.title,
64 | data.description,
65 | data.questions
66 | ),
67 | });
68 | else console.error("No such form exists");
69 | }
70 | } catch (e) {
71 | console.error(e);
72 | }
73 | };
74 |
75 | const renameFormInDB = async (formId, name) => {
76 | try {
77 | const docRef = doc(db, formCollection, formId);
78 | await updateDoc(docRef, { name });
79 | } catch (err) {
80 | console.error(err);
81 | }
82 | };
83 |
84 | const addFormInDB = async (uid, form, dispatchCallback) => {
85 | form.uid = uid;
86 | try {
87 | const docRef = await addDoc(collection(db, "forms"), form);
88 | form.id = docRef.id;
89 | form.date = form.date.toDateString();
90 | await dispatchCallback(form);
91 | return docRef.id;
92 | } catch (e) {
93 | console.error(e);
94 | }
95 | };
96 |
97 | const setFormInDB = async (formId, form) => {
98 | try {
99 | const docRef = doc(db, formCollection, formId);
100 | await setDoc(docRef, form);
101 | } catch (e) {
102 | console.error(e);
103 | }
104 | };
105 |
106 | const deleteFormFromDB = async (formId) => {
107 | const docRef = doc(db, formCollection, formId);
108 | await deleteDoc(docRef);
109 | };
110 | const getTemplateFromDB = async (name) => {
111 | try {
112 | const docRef = doc(db, "templates", name);
113 | await getDoc(docRef);
114 | } catch (e) {
115 | console.error(e);
116 | }
117 | };
118 | const addQuestionInDB = async (formId, question, savedCallBack) => {
119 | try {
120 | const docRef = doc(db, formCollection, formId);
121 | await updateDoc(docRef, {
122 | questions: arrayUnion(question),
123 | });
124 | savedCallBack(SUCCESS_SAVED);
125 | } catch (e) {
126 | console.error(e);
127 | }
128 | };
129 |
130 | const setQuestionsInDB = async (formId, questions, savedCallBack) => {
131 | try {
132 | const formRef = doc(db, formCollection, formId);
133 | await updateDoc(formRef, { questions });
134 | savedCallBack(SUCCESS_SAVED);
135 | } catch (e) {
136 | console.error("Set Question Error:", e);
137 | savedCallBack(ERR_SAVING_FAILED);
138 | }
139 | };
140 |
141 | const removeQuestionFromDB = async (formId, question, savedCallBack) => {
142 | const qRef = await getDoc(doc(db, formCollection, formId));
143 | var myQ = {};
144 | qRef.data().questions.forEach((q) => {
145 | if (q.id === question.id) myQ = q;
146 | });
147 | try {
148 | const docRef = doc(db, formCollection, formId);
149 | await updateDoc(docRef, {
150 | questions: arrayRemove(myQ),
151 | });
152 | savedCallBack(SUCCESS_SAVED);
153 | } catch (e) {
154 | console.error("Remove Question Error: ", e);
155 | savedCallBack(ERR_SAVING_FAILED);
156 | }
157 | };
158 |
159 | const setThemeInDB = async (formId, theme, savedCallBack) => {
160 | try {
161 | const docRef = doc(db, formCollection, formId);
162 | await updateDoc(docRef, { theme });
163 | savedCallBack(SUCCESS_SAVED);
164 | } catch (e) {
165 | console.error(e);
166 | savedCallBack(ERR_SAVING_FAILED);
167 | }
168 | };
169 |
170 | const setSharedInDB = async (formId, shared, savedCallBack) => {
171 | try {
172 | const docRef = doc(db, formCollection, formId);
173 | await updateDoc(docRef, { shared });
174 | savedCallBack(SUCCESS_SAVED);
175 | } catch (e) {
176 | console.error(e);
177 | savedCallBack(ERR_SAVING_FAILED);
178 | }
179 | };
180 | const setFormTitleInDB = async (formId, title, savedCallBack) => {
181 | try {
182 | const docRef = doc(db, formCollection, formId);
183 | await updateDoc(docRef, { title });
184 | savedCallBack(SUCCESS_SAVED);
185 | } catch (e) {
186 | console.error(e);
187 | savedCallBack(ERR_SAVING_FAILED);
188 | }
189 | };
190 | const setFormDescriptionInDB = async (formId, description, savedCallBack) => {
191 | try {
192 | const docRef = doc(db, formCollection, formId);
193 | await updateDoc(docRef, { description });
194 | savedCallBack(SUCCESS_SAVED);
195 | } catch (e) {
196 | console.error(e);
197 | savedCallBack(ERR_SAVING_FAILED);
198 | }
199 | };
200 |
201 | export {
202 | getFormsFromFirebase,
203 | getForm,
204 | addFormInDB,
205 | setFormInDB,
206 | deleteFormFromDB,
207 | addQuestionInDB,
208 | setQuestionsInDB,
209 | removeQuestionFromDB,
210 | setThemeInDB,
211 | setSharedInDB,
212 | setFormTitleInDB,
213 | setFormDescriptionInDB,
214 | getTemplateFromDB,
215 | renameFormInDB,
216 | };
217 |
--------------------------------------------------------------------------------
/src/components/cards/QuestionCard.js:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import CustomDropdown from "components/dropdown/CustomDropdown";
3 | import OptionCard from "components/cards/options/Option";
4 | import { dropdownOptions } from "data/optionTypes";
5 | import DisplayOptions from "components/cards/options/DisplayOptions";
6 | import { faClone, faTrashAlt } from "@fortawesome/free-regular-svg-icons";
7 | import Slider from "components/slider/slider";
8 | import { useDispatch, useSelector } from "react-redux";
9 | import {
10 | faEllipsisV,
11 | faGripHorizontal,
12 | } from "@fortawesome/free-solid-svg-icons";
13 | import Icon from "components/icon/Icon";
14 | import {
15 | setQuestion,
16 | removeQuestion,
17 | duplicateQuestion,
18 | setSaved,
19 | } from "store/data/form.slice";
20 | import createQuestion from "components/helpers/createQuestion";
21 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
22 | import {
23 | removeQuestionFromDB,
24 | setQuestionsInDB,
25 | } from "services/firebase/firestore.firebase";
26 | import { PROGRESS_SAVING, SUCCESS_SAVED } from "data/statusMessages";
27 | import { useFormik } from "formik";
28 | import { questionSchema as validationSchema } from "components/helpers/validations";
29 |
30 | const QuestionCard = ({ question, selected, onClick }) => {
31 | const { id, theme, questions } = useSelector((state) => state.form);
32 | const dispatch = useDispatch();
33 |
34 | const { handleChange, values, errors } = useFormik({
35 | initialValues: { title: question.title },
36 | validationSchema,
37 | });
38 |
39 | const setOptionType = (opType) => {
40 | const temp = { ...question };
41 | temp.optionType = opType;
42 | dispatch(setQuestion({ id: question.id, question: temp }));
43 | };
44 |
45 | const savedCallBack = (status) => {
46 | if (status.error) dispatch(setSaved("Error Saving data in Drive"));
47 | dispatch(setSaved(SUCCESS_SAVED));
48 | };
49 |
50 | const setOptions = (options) => {
51 | const temp = { ...question };
52 | temp.options = options;
53 | dispatch(setQuestion({ id: question.id, question: temp }));
54 | dispatch(setSaved(PROGRESS_SAVING));
55 | setQuestionsInDB(id, questions, savedCallBack);
56 | };
57 |
58 | const toggleRequired = (e) => {
59 | const temp = { ...question };
60 | temp.required = !question.required;
61 | dispatch(setQuestion({ id: question.id, question: temp }));
62 |
63 | dispatch(setSaved(PROGRESS_SAVING));
64 | const tempQ = [...questions];
65 | const ind = tempQ.findIndex((x) => x.id === question.id);
66 | tempQ[ind] = question;
67 | setQuestionsInDB(id, tempQ, savedCallBack);
68 | };
69 |
70 | const deleteQuestion = (qid) => {
71 | dispatch(setSaved(PROGRESS_SAVING));
72 | dispatch(removeQuestion({ id: qid }));
73 | removeQuestionFromDB(id, question, savedCallBack);
74 | };
75 |
76 | const handleDuplicateQuestion = (id) => {
77 | dispatch(duplicateQuestion({ id: question.id, question }));
78 | dispatch(setSaved(PROGRESS_SAVING));
79 | setQuestionsInDB(id, questions, savedCallBack);
80 | };
81 |
82 | const saveTitle = () => {
83 | const ques = { ...question };
84 | ques.title = values.title;
85 | if (!errors.title) {
86 | dispatch(setQuestion({ id: ques.id, question: ques }));
87 | dispatch(setSaved(PROGRESS_SAVING));
88 | setQuestionsInDB(id, questions, savedCallBack);
89 | }
90 | };
91 |
92 | return (
93 | {
98 | onClick(question.id);
99 | }}
100 | >
101 |
102 |
106 |
107 | {!selected ? (
108 |
109 |
110 |
111 | {question.title}
112 |
113 | {question.required ? (
114 |
*
115 | ) : (
116 |
117 | )}
118 |
119 |
120 |
124 |
125 |
126 | ) : (
127 |
128 |
129 |
138 |
139 | d.text === question.optionType
146 | )
147 | ]
148 | }
149 | />
150 |
151 |
152 | {errors.title ? errors.title : null}
153 |
154 |
155 |
160 |
161 |
162 |
167 |
deleteQuestion(question.id)}
169 | icon={faTrashAlt}
170 | label="Delete Question"
171 | />
172 |
173 |
174 |
175 | Required
176 |
177 |
183 |
184 |
185 |
186 |
187 | )}
188 |
189 | );
190 | };
191 |
192 | QuestionCard.defaultProps = {
193 | question: createQuestion(0),
194 | selected: false,
195 | };
196 | QuestionCard.propTypes = {
197 | question: PropTypes.object,
198 | selected: PropTypes.bool,
199 | onClick: PropTypes.func.isRequired,
200 | };
201 |
202 | export default QuestionCard;
203 |
--------------------------------------------------------------------------------
/src/views/Home.js:
--------------------------------------------------------------------------------
1 | import HomeHeader from "components/layout/headers/HomeHeader";
2 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3 | import { useEffect, useState } from "react";
4 | import {
5 | faEllipsisV,
6 | faChevronUp,
7 | faChevronDown,
8 | faThList,
9 | faGripHorizontal,
10 | } from "@fortawesome/free-solid-svg-icons";
11 | import { faFolder } from "@fortawesome/free-regular-svg-icons";
12 | import { useNavigate } from "react-router-dom";
13 | import { formTemplates, ownershipFilters } from "data/templates";
14 | import FormTile from "components/form/formTile/FormTile";
15 | import Dropdown from "components/dropdown/Dropdown";
16 | import sortIcon from "assets/sort.png";
17 | import { useDispatch, useSelector } from "react-redux";
18 | import Loading from "components/loaders/page.loader";
19 | import {
20 | addFormInDB,
21 | getTemplateFromDB,
22 | renameFormInDB,
23 | } from "services/firebase/firestore.firebase";
24 | import { setForm, setLoading } from "store/data/form.slice";
25 | import { setForms } from "store/data/allForms.slice";
26 |
27 | const Home = () => {
28 | const displayDate = "Yesterday";
29 | const [gridView, setGridView] = useState(false);
30 | const [ownedFilter, setOwnedFilter] = useState(ownershipFilters[1]);
31 | const { forms, loading } = useSelector((state) => state.allForms);
32 | const { user } = useSelector((state) => state.authentication);
33 | const { theme, title, description, questions } = useSelector(
34 | (state) => state.form
35 | );
36 | const dispatch = useDispatch();
37 | const navigate = useNavigate();
38 | const toggleGridView = () => {
39 | setGridView(!gridView);
40 | };
41 |
42 | const dispatchCallBack = (form) => {
43 | dispatch(setForm(form));
44 | };
45 |
46 | const addNewForm = async (name, uid) => {
47 | const myForm = {
48 | theme,
49 | title,
50 | description,
51 | questions,
52 | date: new Date(),
53 | shared: true,
54 | name: "Untitled Form",
55 | };
56 | await getTemplateFromDB(name);
57 | dispatch(setLoading(true));
58 | const myId = await addFormInDB(uid, myForm, dispatchCallBack);
59 | console.log("id", myId);
60 | dispatch(setLoading(false));
61 | navigate(`/create/${myId}/edit`);
62 | };
63 |
64 | const openForm = (formId) => {
65 | navigate(`/create/${formId}/edit`);
66 | };
67 |
68 | const removeForm = (formId) => {
69 | const temp = [...forms];
70 | const i = temp.findIndex((e) => e.id === formId);
71 | temp.splice(i, 1);
72 | dispatch(setForms({ forms: temp }));
73 | };
74 | const renameForm = (formId, name) => {
75 | const i = forms.findIndex((e) => e.id === formId);
76 | const temp = { ...forms[i] };
77 | temp.name = name;
78 | dispatch(setForms({ forms: temp }));
79 | renameFormInDB(formId, name);
80 | };
81 |
82 | return (
83 |
84 |
85 |
86 |
87 |
88 |
Start a new form
89 |
90 |
91 |
Template Gallery
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | {formTemplates.map((temp, i) => (
104 |
105 |
addNewForm(temp.name, user.uid, temp)}
110 | />
111 |
{temp.name}
112 |
113 | ))}
114 |
115 |
116 |
117 |
118 |
{displayDate}
119 |
124 |
125 |
129 | {gridView ? (
130 |
134 | ) : (
135 |
139 | )}
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 | {loading ? (
150 |
151 | ) : (
152 |
157 | {forms.length ? (
158 | 1 &&
159 | forms.map((form) => (
160 |
openForm(form.id)}
167 | />
168 | ))
169 | ) : (
170 | No Forms Yet
171 | )}
172 |
173 | )}
174 |
175 |
176 |
177 | );
178 | };
179 |
180 | export default Home;
181 |
--------------------------------------------------------------------------------