206 wiersze
5.4 KiB
TypeScript
206 wiersze
5.4 KiB
TypeScript
import { FirebaseApp, FirebaseOptions, initializeApp } from "firebase/app";
|
|
import {
|
|
getAuth,
|
|
signInWithPopup,
|
|
GithubAuthProvider,
|
|
onAuthStateChanged,
|
|
signOut,
|
|
Auth,
|
|
User,
|
|
} from "firebase/auth";
|
|
import {
|
|
FirebaseFirestore,
|
|
getFirestore,
|
|
collection,
|
|
getDocs,
|
|
query,
|
|
orderBy,
|
|
CollectionReference,
|
|
Timestamp,
|
|
addDoc,
|
|
} from "firebase/firestore";
|
|
import React, { useCallback, useContext, useEffect, useState } from "react";
|
|
import { AuthContext } from "./auth-context";
|
|
import {
|
|
GalleryComposition,
|
|
GalleryContext,
|
|
GallerySubmitStatus,
|
|
} from "./gallery-context";
|
|
|
|
const GALLERY_COLLECTION = "compositions";
|
|
|
|
const DEFAULT_APP_CONFIG: FirebaseOptions = {
|
|
apiKey: "AIzaSyAV1kkVvSKEicEa8rLke9o_BxYBu1rb8kw",
|
|
authDomain: "mystic-addaf.firebaseapp.com",
|
|
projectId: "mystic-addaf",
|
|
storageBucket: "mystic-addaf.appspot.com",
|
|
messagingSenderId: "26787182745",
|
|
appId: "1:26787182745:web:e4fbd9439b9279fe966008",
|
|
measurementId: "G-JHKRSK1PR6",
|
|
};
|
|
|
|
type FirebaseAppContext = {
|
|
app: FirebaseApp;
|
|
auth: Auth;
|
|
provider: GithubAuthProvider;
|
|
db: FirebaseFirestore;
|
|
};
|
|
|
|
export const FirebaseAppContext =
|
|
React.createContext<FirebaseAppContext | null>(null);
|
|
|
|
/**
|
|
* A Firebase app provider. Any other components that use Firebase must
|
|
* be a child of this.
|
|
*
|
|
* Note this component is assumed to never be unmounted, nor
|
|
* for its non-children props to change.
|
|
*/
|
|
export const FirebaseAppProvider: React.FC<{ config?: FirebaseOptions }> = ({
|
|
config,
|
|
children,
|
|
}) => {
|
|
const [value, setValue] = useState<FirebaseAppContext | null>(null);
|
|
|
|
useEffect(() => {
|
|
const app = initializeApp(config || DEFAULT_APP_CONFIG);
|
|
const auth = getAuth(app);
|
|
const provider = new GithubAuthProvider();
|
|
const db = getFirestore(app);
|
|
|
|
setValue({ app, auth, provider, db });
|
|
}, [config]);
|
|
|
|
return <FirebaseAppContext.Provider value={value} children={children} />;
|
|
};
|
|
|
|
/**
|
|
* A Firebase GitHub authentication provider. Must be a child of a
|
|
* `FirebaseAppProvider`.
|
|
*
|
|
* Note this component is assumed to never be unmounted.
|
|
*/
|
|
export const FirebaseGithubAuthProvider: React.FC<{}> = ({ children }) => {
|
|
const appCtx = useContext(FirebaseAppContext);
|
|
const [user, setUser] = useState<User | null>(null);
|
|
const [error, setError] = useState<string | undefined>(undefined);
|
|
|
|
const handleError = (e: Error) => setError(e.message);
|
|
|
|
useEffect(() => {
|
|
if (!appCtx) return;
|
|
|
|
onAuthStateChanged(appCtx.auth, setUser);
|
|
}, [appCtx]);
|
|
|
|
const context: AuthContext = {
|
|
loggedInUser: user && {
|
|
id: user.uid,
|
|
name: user.displayName || `GitHub user ${user.uid}`,
|
|
},
|
|
providerName: appCtx && "GitHub",
|
|
error,
|
|
login: useCallback(() => {
|
|
setError(undefined);
|
|
appCtx &&
|
|
signInWithPopup(appCtx.auth, appCtx.provider).catch(handleError);
|
|
}, [appCtx]),
|
|
logout: useCallback(() => {
|
|
setError(undefined);
|
|
appCtx && signOut(appCtx.auth).catch(handleError);
|
|
}, [appCtx]),
|
|
};
|
|
|
|
return <AuthContext.Provider value={context} children={children} />;
|
|
};
|
|
|
|
type FirebaseCompositionDocument = Omit<
|
|
GalleryComposition,
|
|
"id" | "createdAt"
|
|
> & {
|
|
createdAt: Timestamp;
|
|
};
|
|
|
|
function getGalleryCollection(appCtx: FirebaseAppContext) {
|
|
return collection(
|
|
appCtx.db,
|
|
GALLERY_COLLECTION
|
|
) as CollectionReference<FirebaseCompositionDocument>;
|
|
}
|
|
|
|
function docToComp(
|
|
doc: FirebaseCompositionDocument,
|
|
id: string
|
|
): GalleryComposition {
|
|
const { createdAt, ...data } = doc;
|
|
return {
|
|
...data,
|
|
id,
|
|
createdAt: createdAt.toDate(),
|
|
};
|
|
}
|
|
|
|
export const FirebaseGalleryProvider: React.FC<{}> = ({ children }) => {
|
|
const appCtx = useContext(FirebaseAppContext);
|
|
const [compositions, setCompositions] = useState<GalleryComposition[]>([]);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState<string | undefined>(undefined);
|
|
const [lastRefresh, setLastRefresh] = useState(0);
|
|
const [submitStatus, setSubmitStatus] = useState<GallerySubmitStatus>("idle");
|
|
const [lastSubmission, setLastSubmission] = useState<
|
|
GalleryComposition | undefined
|
|
>(undefined);
|
|
|
|
const context: GalleryContext = {
|
|
compositions,
|
|
isLoading,
|
|
error,
|
|
lastRefresh,
|
|
lastSubmission,
|
|
submitStatus,
|
|
submit(props) {
|
|
if (!(appCtx && submitStatus === "idle")) return;
|
|
|
|
const doc: FirebaseCompositionDocument = {
|
|
...props,
|
|
createdAt: Timestamp.now(),
|
|
};
|
|
|
|
setSubmitStatus("submitting");
|
|
setLastSubmission(undefined);
|
|
addDoc(getGalleryCollection(appCtx), doc)
|
|
.then((docRef) => {
|
|
const comp = docToComp(doc, docRef.id);
|
|
setSubmitStatus("idle");
|
|
setCompositions([comp, ...compositions]);
|
|
setLastSubmission(comp);
|
|
})
|
|
.catch((e) => {
|
|
setSubmitStatus("error");
|
|
console.log(e);
|
|
});
|
|
},
|
|
refresh: useCallback(() => {
|
|
if (!(appCtx && !isLoading)) return false;
|
|
|
|
setError(undefined);
|
|
setIsLoading(true);
|
|
getDocs(query(getGalleryCollection(appCtx), orderBy("createdAt", "desc")))
|
|
.then((snapshot) => {
|
|
setLastRefresh(Date.now());
|
|
setIsLoading(false);
|
|
setCompositions(
|
|
snapshot.docs.map((doc) => docToComp(doc.data(), doc.id))
|
|
);
|
|
})
|
|
.catch((e) => {
|
|
setIsLoading(false);
|
|
setError(e.message);
|
|
});
|
|
return true;
|
|
}, [appCtx, isLoading]),
|
|
};
|
|
|
|
return <GalleryContext.Provider value={context} children={children} />;
|
|
};
|