Skip to content

Commit 476c9d7

Browse files
authored
Merge pull request #4 from wajeshubham/firebase
Firebase configuration
2 parents 1c09335 + 1fd40d9 commit 476c9d7

28 files changed

+1427
-126
lines changed

.eslintrc.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
{
2-
"extends": "next/core-web-vitals"
3-
}
2+
"extends": "next/core-web-vitals",
3+
"rules": {
4+
"@next/next/no-img-element": "off"
5+
}
6+
}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
/.pnp
66
.pnp.js
77

8+
.env
9+
810
# testing
911
/coverage
1012

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,29 @@ You need `NodeJs` and `yarn` installed on your machine.
106106
npm install --global yarn
107107
```
108108

109+
### Firebase prerequisites (optional)
110+
111+
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.
112+
113+
```.env
114+
NEXT_PUBLIC_FIREBASE_API_KEY=<your_FIREBASE_APP_API_KEY>
115+
116+
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=<your_FIREBASE_APP_AUTH_DOMAIN>
117+
118+
NEXT_PUBLIC_FIREBASE_PROJECT_ID=<your_FIREBASE_APP_PROJECT_ID>
119+
120+
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=<your_FIREBASE_APP_STORAGE_BUCKET>
121+
122+
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=<your_FIREBASE_APP_MESSAGING_SENDER_ID>
123+
124+
NEXT_PUBLIC_FIREBASE_APP_ID=<your_FIREBASE_APP_APP_ID>
125+
126+
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=<your_FIREBASE_APP_MEASUREMENT_ID>
127+
128+
```
129+
130+
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.
131+
109132
### Installation
110133

111134
1. Clone the repo

components/ErrorText.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React from "react";
2+
import Button, { SnippngButtonType } from "./form/Button";
3+
import { FolderPlusIcon } from "@heroicons/react/24/outline";
4+
5+
interface Props {
6+
errorTitle: string;
7+
errorSubTitle?: string;
8+
errorActionProps?: SnippngButtonType;
9+
ErrorIcon?: ((props: React.SVGProps<SVGSVGElement>) => JSX.Element) | null;
10+
}
11+
12+
const ErrorText: React.FC<Props> = ({
13+
errorTitle,
14+
errorSubTitle,
15+
errorActionProps,
16+
ErrorIcon,
17+
}) => {
18+
return (
19+
<div className="text-center py-8">
20+
{ErrorIcon ? (
21+
<ErrorIcon className="mx-auto h-12 w-12 text-zinc-400" />
22+
) : (
23+
<FolderPlusIcon className="mx-auto h-12 w-12 text-zinc-400" />
24+
)}
25+
<h3 className="mt-2 text-sm font-medium dark:text-white text-zinc-900">
26+
{errorTitle}
27+
</h3>
28+
{errorSubTitle ? (
29+
<p className="mt-1 text-sm dark:text-zinc-400 text-zinc-500">
30+
{errorSubTitle}
31+
</p>
32+
) : null}
33+
{errorActionProps ? (
34+
<div className="mt-6">
35+
<Button {...errorActionProps}>{errorActionProps.children}</Button>
36+
</div>
37+
) : null}
38+
</div>
39+
);
40+
};
41+
42+
export default ErrorText;

components/Loader.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from "react";
2+
3+
const Loader = () => {
4+
return (
5+
<div className="flex space-x-2 w-full h-screen fixed inset-0 bg-zinc-700/50 z-30 justify-center items-center">
6+
<div aria-label="Loading..." role="status">
7+
<svg className="h-12 w-12 animate-spin" viewBox="3 3 18 18">
8+
<path
9+
className="fill-gray-200"
10+
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"
11+
></path>
12+
<path
13+
className="fill-gray-800"
14+
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"
15+
></path>
16+
</svg>
17+
</div>
18+
</div>
19+
);
20+
};
21+
22+
export default Loader;

components/SigninButton.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useAuth } from "@/context/AuthContext";
2+
import React from "react";
3+
import Button from "./form/Button";
4+
import GithubIcon from "./icons/GithubIcon";
5+
6+
const SigninButton = () => {
7+
const { loginWithGithub } = useAuth();
8+
9+
return (
10+
<Button onClick={loginWithGithub}>
11+
<GithubIcon className="inline-flex mr-1" />
12+
Signin
13+
</Button>
14+
);
15+
};
16+
17+
export default SigninButton;

components/editor/SnippngCodeArea.tsx

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,36 @@
1-
import { createRef, useContext, useState } from "react";
1+
import { createRef } from "react";
22

3-
import { DEFAULT_BASE_SETUP, DEFAULT_CODE_SNIPPET } from "@/lib/constants";
3+
import { DEFAULT_BASE_SETUP } from "@/lib/constants";
44
import { clsx, getEditorWrapperBg, getLanguage, getTheme } from "@/utils";
55

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

10-
import { SnippngEditorContext } from "@/context/SnippngEditorContext";
10+
import { useSnippngEditor } from "@/context/SnippngEditorContext";
1111
import { WidthHandler } from "@/lib/width-handler";
12+
import Button from "../form/Button";
13+
import Input from "../form/Input";
1214
import NoSSRWrapper from "../NoSSRWrapper";
1315
import SnippngControlHeader from "./SnippngControlHeader";
1416
import SnippngWindowControls from "./SnippngWindowControls";
1517

18+
import { db } from "@/config/firebase";
19+
import { useAuth } from "@/context/AuthContext";
20+
import {
21+
ArrowDownOnSquareStackIcon,
22+
ArrowPathIcon,
23+
} from "@heroicons/react/24/outline";
24+
import { addDoc, collection, doc, updateDoc } from "firebase/firestore";
25+
1626
const SnippngCodeArea = () => {
17-
const [code, setCode] = useState(DEFAULT_CODE_SNIPPET);
1827
const editorRef = createRef<HTMLDivElement>();
1928

20-
const { editorConfig, handleConfigChange } = useContext(SnippngEditorContext);
29+
const { editorConfig, handleConfigChange } = useSnippngEditor();
30+
const { user } = useAuth();
2131
const {
32+
code,
33+
snippetsName,
2234
selectedLang,
2335
selectedTheme,
2436
wrapperBg,
@@ -34,8 +46,36 @@ const SnippngCodeArea = () => {
3446
gradients,
3547
gradientAngle,
3648
editorWidth,
49+
uid,
3750
} = editorConfig;
3851

52+
const saveSnippet = async () => {
53+
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.
54+
55+
if (!user) return;
56+
try {
57+
const docRef = await addDoc(
58+
collection(db, "user", user.uid, "snippets"),
59+
editorConfig
60+
);
61+
} catch (e) {
62+
console.error("Error adding document: ", e);
63+
}
64+
};
65+
66+
const updateSnippet = async () => {
67+
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.
68+
69+
if (!user || !uid) return;
70+
try {
71+
await updateDoc(doc(db, "user", user.uid, "snippets", uid), {
72+
...editorConfig,
73+
});
74+
} catch (e) {
75+
console.error("Error adding document: ", e);
76+
}
77+
};
78+
3979
return (
4080
<>
4181
<section
@@ -98,15 +138,16 @@ const SnippngCodeArea = () => {
98138
// @ts-ignore
99139
theme={themes[getTheme(selectedTheme.id)]}
100140
indentWithTab
101-
onChange={(value) => {
102-
setCode(value);
103-
}}
141+
onChange={(value) => handleConfigChange("code")(value)}
104142
>
105143
<div className="absolute top-0 z-20 w-full text-white !px-3.5 !py-3 bg-inherit">
106144
{showFileName ? (
107145
<input
108146
id="file-name-input"
109-
defaultValue={fileName}
147+
value={fileName}
148+
onChange={(e) =>
149+
handleConfigChange("fileName")(e.target.value)
150+
}
110151
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"
111152
spellCheck={false}
112153
contentEditable
@@ -118,6 +159,41 @@ const SnippngCodeArea = () => {
118159
</CodeMirror>
119160
</div>
120161
</div>
162+
<div className="w-full mt-8 flex md:flex-row flex-col gap-4 justify-start items-center">
163+
<div className="w-full">
164+
<Input
165+
value={snippetsName}
166+
onChange={(e) =>
167+
handleConfigChange("snippetsName")(e.target.value)
168+
}
169+
placeholder="Snippet name..."
170+
/>
171+
</div>
172+
<div className="flex flex-shrink-0 gap-4 md:flex-row flex-col md:w-fit w-full">
173+
<Button
174+
StartIcon={ArrowDownOnSquareStackIcon}
175+
onClick={(e) => {
176+
e.stopPropagation();
177+
if (!user) return alert("Please login first");
178+
else saveSnippet();
179+
}}
180+
>
181+
{uid ? "Save separately" : "Save snippet"}
182+
</Button>
183+
{uid ? (
184+
<Button
185+
StartIcon={ArrowPathIcon}
186+
onClick={(e) => {
187+
e.stopPropagation();
188+
if (!user) return alert("Please login first");
189+
updateSnippet();
190+
}}
191+
>
192+
Update snippet
193+
</Button>
194+
) : null}
195+
</div>
196+
</div>
121197
</div>
122198
</NoSSRWrapper>
123199
</section>

components/editor/SnippngControlHeader.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { SnippngEditorContext } from "@/context/SnippngEditorContext";
1+
import { useSnippngEditor } from "@/context/SnippngEditorContext";
22
import { ColorPicker } from "@/lib/color-picker";
33
import { LANGUAGES, THEMES } from "@/lib/constants";
44
import { getEditorWrapperBg } from "@/utils";
@@ -11,7 +11,7 @@ import {
1111
SparklesIcon,
1212
} from "@heroicons/react/24/outline";
1313
import * as htmlToImage from "html-to-image";
14-
import React, { Fragment, useContext, useState } from "react";
14+
import React, { Fragment, useState } from "react";
1515
import Button from "../form/Button";
1616
import Checkbox from "../form/Checkbox";
1717
import Range from "../form/Range";
@@ -20,7 +20,7 @@ import Select from "../form/Select";
2020
const SnippngControlHeader = () => {
2121
const [downloadingSnippet, setDownloadingSnippet] = useState(false);
2222

23-
const { editorConfig, handleConfigChange } = useContext(SnippngEditorContext);
23+
const { editorConfig, handleConfigChange } = useSnippngEditor();
2424

2525
const {
2626
selectedLang,

components/form/Button.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { clsx } from "@/utils";
22
import React from "react";
33

4-
const Button: React.FC<
5-
React.ButtonHTMLAttributes<HTMLButtonElement> & {
6-
StartIcon?: ((props: React.SVGProps<SVGSVGElement>) => JSX.Element) | null;
7-
EndIcon?: ((props: React.SVGProps<SVGSVGElement>) => JSX.Element) | null;
8-
}
9-
> = ({ StartIcon, EndIcon, ...props }) => {
4+
interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {
5+
StartIcon?: ((props: React.SVGProps<SVGSVGElement>) => JSX.Element) | null;
6+
EndIcon?: ((props: React.SVGProps<SVGSVGElement>) => JSX.Element) | null;
7+
}
8+
9+
const Button: React.FC<Props> = ({ StartIcon, EndIcon, ...props }) => {
1010
return (
1111
<button
1212
{...props}
@@ -16,10 +16,12 @@ const Button: React.FC<
1616
)}
1717
>
1818
{StartIcon ? <StartIcon className="w-4 h-4 mr-2" /> : null}
19-
<span>{props.children}</span>
19+
{props.children}
2020
{EndIcon ? <EndIcon className="w-4 h-4 ml-2" /> : null}
2121
</button>
2222
);
2323
};
2424

2525
export default Button;
26+
27+
export type SnippngButtonType = Props;

components/form/Input.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { clsx } from "@/utils";
2+
import React from "react";
3+
4+
interface Props extends React.InputHTMLAttributes<HTMLInputElement> {
5+
label?: string;
6+
containerClassName?: string;
7+
}
8+
9+
const Input: React.FC<Props> = ({ label, containerClassName, ...props }) => {
10+
return (
11+
<div className="flex flex-col">
12+
{label ? (
13+
<label
14+
className="text-sm my-0.5 dark:text-white text-zinc-900"
15+
htmlFor={props.id}
16+
>
17+
{label}
18+
</label>
19+
) : null}
20+
<input
21+
{...props}
22+
className={clsx(
23+
"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",
24+
props.className ?? ""
25+
)}
26+
/>
27+
</div>
28+
);
29+
};
30+
31+
export default Input;

0 commit comments

Comments
 (0)