Skip to content

Commit

Permalink
Merge pull request #4 from wajeshubham/firebase
Browse files Browse the repository at this point in the history
Firebase configuration
  • Loading branch information
wajeshubham authored Jan 31, 2023
2 parents 1c09335 + 1fd40d9 commit 476c9d7
Show file tree
Hide file tree
Showing 28 changed files with 1,427 additions and 126 deletions.
7 changes: 5 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{
"extends": "next/core-web-vitals"
}
"extends": "next/core-web-vitals",
"rules": {
"@next/next/no-img-element": "off"
}
}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
/.pnp
.pnp.js

.env

# testing
/coverage

Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,29 @@ You need `NodeJs` and `yarn` installed on your machine.
npm install --global yarn
```

### Firebase prerequisites (optional)

Firebase is used in this project for authentications and to store snippets. In order to contribute in the part requiring Firebase, create a file called `.env` inside the root folder and add the following credentials in it once you create a Firebase app.

```.env
NEXT_PUBLIC_FIREBASE_API_KEY=<your_FIREBASE_APP_API_KEY>
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=<your_FIREBASE_APP_AUTH_DOMAIN>
NEXT_PUBLIC_FIREBASE_PROJECT_ID=<your_FIREBASE_APP_PROJECT_ID>
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=<your_FIREBASE_APP_STORAGE_BUCKET>
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=<your_FIREBASE_APP_MESSAGING_SENDER_ID>
NEXT_PUBLIC_FIREBASE_APP_ID=<your_FIREBASE_APP_APP_ID>
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=<your_FIREBASE_APP_MEASUREMENT_ID>
```

It does not matter what credentials you add to your `.env` file, as the app won't crash while developing since the error is taken care of for the Firebase services that are unavailable.

### Installation

1. Clone the repo
Expand Down
42 changes: 42 additions & 0 deletions components/ErrorText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from "react";
import Button, { SnippngButtonType } from "./form/Button";
import { FolderPlusIcon } from "@heroicons/react/24/outline";

interface Props {
errorTitle: string;
errorSubTitle?: string;
errorActionProps?: SnippngButtonType;
ErrorIcon?: ((props: React.SVGProps<SVGSVGElement>) => JSX.Element) | null;
}

const ErrorText: React.FC<Props> = ({
errorTitle,
errorSubTitle,
errorActionProps,
ErrorIcon,
}) => {
return (
<div className="text-center py-8">
{ErrorIcon ? (
<ErrorIcon className="mx-auto h-12 w-12 text-zinc-400" />
) : (
<FolderPlusIcon className="mx-auto h-12 w-12 text-zinc-400" />
)}
<h3 className="mt-2 text-sm font-medium dark:text-white text-zinc-900">
{errorTitle}
</h3>
{errorSubTitle ? (
<p className="mt-1 text-sm dark:text-zinc-400 text-zinc-500">
{errorSubTitle}
</p>
) : null}
{errorActionProps ? (
<div className="mt-6">
<Button {...errorActionProps}>{errorActionProps.children}</Button>
</div>
) : null}
</div>
);
};

export default ErrorText;
22 changes: 22 additions & 0 deletions components/Loader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from "react";

const Loader = () => {
return (
<div className="flex space-x-2 w-full h-screen fixed inset-0 bg-zinc-700/50 z-30 justify-center items-center">
<div aria-label="Loading..." role="status">
<svg className="h-12 w-12 animate-spin" viewBox="3 3 18 18">
<path
className="fill-gray-200"
d="M12 5C8.13401 5 5 8.13401 5 12C5 15.866 8.13401 19 12 19C15.866 19 19 15.866 19 12C19 8.13401 15.866 5 12 5ZM3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12Z"
></path>
<path
className="fill-gray-800"
d="M16.9497 7.05015C14.2161 4.31648 9.78392 4.31648 7.05025 7.05015C6.65973 7.44067 6.02656 7.44067 5.63604 7.05015C5.24551 6.65962 5.24551 6.02646 5.63604 5.63593C9.15076 2.12121 14.8492 2.12121 18.364 5.63593C18.7545 6.02646 18.7545 6.65962 18.364 7.05015C17.9734 7.44067 17.3403 7.44067 16.9497 7.05015Z"
></path>
</svg>
</div>
</div>
);
};

export default Loader;
17 changes: 17 additions & 0 deletions components/SigninButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useAuth } from "@/context/AuthContext";
import React from "react";
import Button from "./form/Button";
import GithubIcon from "./icons/GithubIcon";

const SigninButton = () => {
const { loginWithGithub } = useAuth();

return (
<Button onClick={loginWithGithub}>
<GithubIcon className="inline-flex mr-1" />
Signin
</Button>
);
};

export default SigninButton;
94 changes: 85 additions & 9 deletions components/editor/SnippngCodeArea.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
import { createRef, useContext, useState } from "react";
import { createRef } from "react";

import { DEFAULT_BASE_SETUP, DEFAULT_CODE_SNIPPET } from "@/lib/constants";
import { DEFAULT_BASE_SETUP } from "@/lib/constants";
import { clsx, getEditorWrapperBg, getLanguage, getTheme } from "@/utils";

import { langs, loadLanguage } from "@uiw/codemirror-extensions-langs";
import * as themes from "@uiw/codemirror-themes-all";
import CodeMirror from "@uiw/react-codemirror";

import { SnippngEditorContext } from "@/context/SnippngEditorContext";
import { useSnippngEditor } from "@/context/SnippngEditorContext";
import { WidthHandler } from "@/lib/width-handler";
import Button from "../form/Button";
import Input from "../form/Input";
import NoSSRWrapper from "../NoSSRWrapper";
import SnippngControlHeader from "./SnippngControlHeader";
import SnippngWindowControls from "./SnippngWindowControls";

import { db } from "@/config/firebase";
import { useAuth } from "@/context/AuthContext";
import {
ArrowDownOnSquareStackIcon,
ArrowPathIcon,
} from "@heroicons/react/24/outline";
import { addDoc, collection, doc, updateDoc } from "firebase/firestore";

const SnippngCodeArea = () => {
const [code, setCode] = useState(DEFAULT_CODE_SNIPPET);
const editorRef = createRef<HTMLDivElement>();

const { editorConfig, handleConfigChange } = useContext(SnippngEditorContext);
const { editorConfig, handleConfigChange } = useSnippngEditor();
const { user } = useAuth();
const {
code,
snippetsName,
selectedLang,
selectedTheme,
wrapperBg,
Expand All @@ -34,8 +46,36 @@ const SnippngCodeArea = () => {
gradients,
gradientAngle,
editorWidth,
uid,
} = editorConfig;

const saveSnippet = async () => {
if (!db) return console.log(Error("Firebase is not configured")); // This is to handle error when there is no `.env` file. So, that app doesn't crash while developing without `.env` file.

if (!user) return;
try {
const docRef = await addDoc(
collection(db, "user", user.uid, "snippets"),
editorConfig
);
} catch (e) {
console.error("Error adding document: ", e);
}
};

const updateSnippet = async () => {
if (!db) return console.log(Error("Firebase is not configured")); // This is to handle error when there is no `.env` file. So, that app doesn't crash while developing without `.env` file.

if (!user || !uid) return;
try {
await updateDoc(doc(db, "user", user.uid, "snippets", uid), {
...editorConfig,
});
} catch (e) {
console.error("Error adding document: ", e);
}
};

return (
<>
<section
Expand Down Expand Up @@ -98,15 +138,16 @@ const SnippngCodeArea = () => {
// @ts-ignore
theme={themes[getTheme(selectedTheme.id)]}
indentWithTab
onChange={(value) => {
setCode(value);
}}
onChange={(value) => handleConfigChange("code")(value)}
>
<div className="absolute top-0 z-20 w-full text-white !px-3.5 !py-3 bg-inherit">
{showFileName ? (
<input
id="file-name-input"
defaultValue={fileName}
value={fileName}
onChange={(e) =>
handleConfigChange("fileName")(e.target.value)
}
className="absolute bg-transparent w-72 text-center top-2 -translate-x-1/2 left-1/2 text-xs font-extralight text-zinc-400 focus:border-b-[0.1px] border-zinc-500 outline-none ring-0"
spellCheck={false}
contentEditable
Expand All @@ -118,6 +159,41 @@ const SnippngCodeArea = () => {
</CodeMirror>
</div>
</div>
<div className="w-full mt-8 flex md:flex-row flex-col gap-4 justify-start items-center">
<div className="w-full">
<Input
value={snippetsName}
onChange={(e) =>
handleConfigChange("snippetsName")(e.target.value)
}
placeholder="Snippet name..."
/>
</div>
<div className="flex flex-shrink-0 gap-4 md:flex-row flex-col md:w-fit w-full">
<Button
StartIcon={ArrowDownOnSquareStackIcon}
onClick={(e) => {
e.stopPropagation();
if (!user) return alert("Please login first");
else saveSnippet();
}}
>
{uid ? "Save separately" : "Save snippet"}
</Button>
{uid ? (
<Button
StartIcon={ArrowPathIcon}
onClick={(e) => {
e.stopPropagation();
if (!user) return alert("Please login first");
updateSnippet();
}}
>
Update snippet
</Button>
) : null}
</div>
</div>
</div>
</NoSSRWrapper>
</section>
Expand Down
6 changes: 3 additions & 3 deletions components/editor/SnippngControlHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SnippngEditorContext } from "@/context/SnippngEditorContext";
import { useSnippngEditor } from "@/context/SnippngEditorContext";
import { ColorPicker } from "@/lib/color-picker";
import { LANGUAGES, THEMES } from "@/lib/constants";
import { getEditorWrapperBg } from "@/utils";
Expand All @@ -11,7 +11,7 @@ import {
SparklesIcon,
} from "@heroicons/react/24/outline";
import * as htmlToImage from "html-to-image";
import React, { Fragment, useContext, useState } from "react";
import React, { Fragment, useState } from "react";
import Button from "../form/Button";
import Checkbox from "../form/Checkbox";
import Range from "../form/Range";
Expand All @@ -20,7 +20,7 @@ import Select from "../form/Select";
const SnippngControlHeader = () => {
const [downloadingSnippet, setDownloadingSnippet] = useState(false);

const { editorConfig, handleConfigChange } = useContext(SnippngEditorContext);
const { editorConfig, handleConfigChange } = useSnippngEditor();

const {
selectedLang,
Expand Down
16 changes: 9 additions & 7 deletions components/form/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { clsx } from "@/utils";
import React from "react";

const Button: React.FC<
React.ButtonHTMLAttributes<HTMLButtonElement> & {
StartIcon?: ((props: React.SVGProps<SVGSVGElement>) => JSX.Element) | null;
EndIcon?: ((props: React.SVGProps<SVGSVGElement>) => JSX.Element) | null;
}
> = ({ StartIcon, EndIcon, ...props }) => {
interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {
StartIcon?: ((props: React.SVGProps<SVGSVGElement>) => JSX.Element) | null;
EndIcon?: ((props: React.SVGProps<SVGSVGElement>) => JSX.Element) | null;
}

const Button: React.FC<Props> = ({ StartIcon, EndIcon, ...props }) => {
return (
<button
{...props}
Expand All @@ -16,10 +16,12 @@ const Button: React.FC<
)}
>
{StartIcon ? <StartIcon className="w-4 h-4 mr-2" /> : null}
<span>{props.children}</span>
{props.children}
{EndIcon ? <EndIcon className="w-4 h-4 ml-2" /> : null}
</button>
);
};

export default Button;

export type SnippngButtonType = Props;
31 changes: 31 additions & 0 deletions components/form/Input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { clsx } from "@/utils";
import React from "react";

interface Props extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string;
containerClassName?: string;
}

const Input: React.FC<Props> = ({ label, containerClassName, ...props }) => {
return (
<div className="flex flex-col">
{label ? (
<label
className="text-sm my-0.5 dark:text-white text-zinc-900"
htmlFor={props.id}
>
{label}
</label>
) : null}
<input
{...props}
className={clsx(
"w-full dark:bg-zinc-700 placeholder:dark:text-zinc-400 block px-2 py-1.5 bg-zinc-100 border-[1px] dark:border-zinc-400 border-zinc-300 dark:text-white text-zinc-900 outline-none rounded-sm",
props.className ?? ""
)}
/>
</div>
);
};

export default Input;
22 changes: 22 additions & 0 deletions components/icons/GithubIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from "react";

const GithubIcon: React.FC<React.SVGAttributes<SVGSVGElement>> = ({
...props
}) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 28 28"
{...props}
>
<path
className="dark:fill-white fill-black"
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"
/>
</svg>
);
};

export default GithubIcon;
Loading

0 comments on commit 476c9d7

Please sign in to comment.