Skip to content

Commit

Permalink
feat: integrate UtahID sign in
Browse files Browse the repository at this point in the history
ref #9
  • Loading branch information
stdavis committed Dec 17, 2024
1 parent d8ed074 commit 61cb637
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 139 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"orgid",
"prebuild",
"tanstack",
"utahid",
"venv",
"VITE"
],
Expand Down
99 changes: 54 additions & 45 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { useQuery } from '@tanstack/react-query';
import { Drawer, Footer, Header, SocialMedia, UgrcLogo } from '@ugrc/utah-design-system';
import { Drawer, Footer, Header, SocialMedia, UgrcLogo, useFirebaseAuth, UtahIdLogin } from '@ugrc/utah-design-system';
import { collection, getDocs } from 'firebase/firestore';
import PropTypes from 'prop-types';
import { useState } from 'react';
import { useOverlayTrigger } from 'react-aria';
import { useOverlayTriggerState } from 'react-stately';
import { BackupItem } from './components/BackupItem';
import { BackupSchedule } from './components/BackupSchedule';
import { useFirebaseApp } from './components/contexts';
import { MoonwalkBackup } from './components/types';
import { useFirestore } from './contexts/FirestoreProvider';

const version = import.meta.env.PACKAGE_VERSION;

Expand All @@ -21,10 +20,6 @@ const ErrorFallback = ({ error }: { error: Error }) => {
);
};

ErrorFallback.propTypes = {
error: PropTypes.object,
};

const links = [
{
key: 'UGRC Homepage',
Expand Down Expand Up @@ -61,8 +56,11 @@ export default function App() {
);
const [selected, setSelected] = useState<MoonwalkBackup | undefined>();

const { firestore } = useFirebaseApp();
const { firestore } = useFirestore();
const { currentUser, logout } = useFirebaseAuth();
const getMoonwalkData = async () => {
if (!firestore) return;

const snapshot = await getDocs(collection(firestore, 'items'));

return snapshot.docs.map((doc) => {
Expand All @@ -81,53 +79,64 @@ export default function App() {
return (
<>
<main className="flex h-screen flex-col md:gap-2">
<Header links={links}>
<Header links={links} currentUser={currentUser} logout={logout}>
<div className="flex h-full grow items-center gap-3">
<UgrcLogo />
<h2 className="font-heading text-3xl font-black text-zinc-600 sm:text-5xl dark:text-zinc-100">
Project Moonwalk 🕺🏻
</h2>
</div>
</Header>
<section className="relative flex min-h-0 flex-1 overflow-x-hidden md:mr-2">
<Drawer main state={sideBarState} {...sideBarTriggerProps}>
<div className="mx-2 mb-2 grid grid-cols-1 gap-2">
<h2 className="text-xl font-bold">Available restore points</h2>
<div className="flex flex-col gap-4 rounded border border-zinc-200 p-3 dark:border-zinc-700">
{selected && <BackupSchedule item={selected} />}
{currentUser ? (
<section className="relative flex min-h-0 flex-1 overflow-x-hidden md:mr-2">
<Drawer main state={sideBarState} {...sideBarTriggerProps}>
<div className="mx-2 mb-2 grid grid-cols-1 gap-2">
<h2 className="text-xl font-bold">Available restore points</h2>
<div className="flex flex-col gap-4 rounded border border-zinc-200 p-3 dark:border-zinc-700">
{selected && <BackupSchedule item={selected} />}
</div>
</div>
</Drawer>
<div className="relative flex flex-1 flex-col rounded border border-b-0 border-zinc-200 dark:border-0 dark:border-zinc-700">
<div className="relative flex-1 space-y-2 overflow-y-scroll px-2 py-1.5 dark:rounded">
{!isPending &&
data?.map((item: MoonwalkBackup, i) => (
<BackupItem
key={i}
moonwalk={item}
select={(item) => {
setSelected(item);
sideBarState.open();
}}
/>
))}
{error && <ErrorFallback error={error} />}
<Drawer
type="tray"
className="shadow-inner dark:shadow-white/20"
allowFullScreen
state={trayState}
{...trayTriggerProps}
>
<section className="grid gap-2 px-7 pt-2">
<h2 className="text-center">What&#39;s here?</h2>
Features: 25,001
</section>
</Drawer>
</div>
<SocialMedia />
</div>
</Drawer>
<div className="relative flex flex-1 flex-col rounded border border-b-0 border-zinc-200 dark:border-0 dark:border-zinc-700">
<div className="relative flex-1 space-y-2 overflow-y-scroll px-2 py-1.5 dark:rounded">
{!isPending &&
data?.map((item: MoonwalkBackup, i) => (
<BackupItem
key={i}
moonwalk={item}
select={(item) => {
setSelected(item);
sideBarState.open();
}}
/>
))}
{error && <ErrorFallback error={error} />}
<Drawer
type="tray"
className="shadow-inner dark:shadow-white/20"
allowFullScreen
state={trayState}
{...trayTriggerProps}
>
<section className="grid gap-2 px-7 pt-2">
<h2 className="text-center">What&#39;s here?</h2>
Features: 25,001
</section>
</Drawer>
</section>
) : (
<section className="flex flex-1 items-center justify-center">
<div className="flex flex-col items-center gap-4">
<h2 className="text-center text-2xl font-bold">Please log in to use the application</h2>
<div>
<UtahIdLogin />
</div>
</div>
<SocialMedia />
</div>
</section>
</section>
)}
</main>
<Footer />
</>
Expand Down
4 changes: 2 additions & 2 deletions src/components/BackupSchedule.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { useMutation } from '@tanstack/react-query';
import { Button } from '@ugrc/utah-design-system';
import { httpsCallable } from 'firebase/functions';
import { useFirebaseApp } from './contexts';
import { useFirebaseFunctions } from '../contexts/FirebaseFunctionsProvider';
import { MoonwalkBackup, Version } from './types';

export const BackupSchedule = ({ item }: { item: MoonwalkBackup }) => {
const { functions } = useFirebaseApp();
const { functions } = useFirebaseFunctions();
const restore = httpsCallable(functions, 'restore');
const restoreMutation = async (version: Version): Promise<string> => {
const result = await restore({ item_id: item.itemId, category: version.category, generation: version.generation });
Expand Down
35 changes: 0 additions & 35 deletions src/components/contexts/AnalyticsProvider.tsx

This file was deleted.

49 changes: 0 additions & 49 deletions src/components/contexts/FirebaseAppProvider.tsx

This file was deleted.

2 changes: 0 additions & 2 deletions src/components/contexts/index.ts

This file was deleted.

Empty file removed src/components/hooks/index.ts
Empty file.
39 changes: 39 additions & 0 deletions src/contexts/FirebaseFunctionsProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useFirebaseApp } from '@ugrc/utah-design-system';
import { connectFunctionsEmulator, Functions, getFunctions } from 'firebase/functions';
import { createContext, type ReactNode, useContext } from 'react';

type FunctionsContextValue = {
functions: Functions;
};

type FunctionsProviderProps = {
children: ReactNode;
};

const FunctionsContext = createContext<FunctionsContextValue | null>(null);

export const FirebaseFunctionsProvider = (props: FunctionsProviderProps) => {
const app = useFirebaseApp();
const sdk = getFunctions(app);

if (import.meta.env.DEV) {
console.log('Connecting to Firebase Functions emulator');
connectFunctionsEmulator(sdk, 'localhost', 5001);
}

if (!app) {
throw new Error('You cannot use the FirebaseFunctionsProvider outside of a <FirebaseAppProvider />');
}

return <FunctionsContext.Provider value={{ functions: sdk }} {...props} />;
};

export const useFirebaseFunctions = () => {
const value = useContext(FunctionsContext);

if (value === null) {
throw new Error('useFirebaseFunctions must be used within a FirebaseFunctionsProvider');
}

return value;
};
39 changes: 39 additions & 0 deletions src/contexts/FirestoreProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useFirebaseApp } from '@ugrc/utah-design-system';
import { connectFirestoreEmulator, Firestore, getFirestore } from 'firebase/firestore';
import { createContext, type ReactNode, useContext } from 'react';

type StoreContextValue = {
firestore: Firestore;
};

type FirestoreProviderProps = {
children: ReactNode;
};

const FirestoreContext = createContext<StoreContextValue | null>(null);

export const FirestoreProvider = (props: FirestoreProviderProps) => {
const app = useFirebaseApp();
const sdk = getFirestore(app);

if (import.meta.env.DEV) {
console.log('Connecting to Firestore emulator');
connectFirestoreEmulator(sdk, 'localhost', 8080);
}

if (!app) {
throw new Error('You cannot use the FirestoreProvider outside of a <FirebaseAppProvider />');
}

return <FirestoreContext.Provider value={{ firestore: sdk }} {...props} />;
};

export const useFirestore = () => {
const value = useContext(FirestoreContext);

if (value === null) {
throw new Error('useFirestore must be used within a FirestoreProvider');
}

return value;
};
43 changes: 37 additions & 6 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { FirebaseAnalyticsProvider, FirebaseAppProvider, FirebaseAuthProvider } from '@ugrc/utah-design-system';
import { OAuthProvider } from 'firebase/auth';
import React from 'react';
import { createRoot } from 'react-dom/client';
import { ErrorBoundary } from 'react-error-boundary';
import App from './App';
import { FirebaseAppProvider } from './components/contexts';
import { FirebaseFunctionsProvider } from './contexts/FirebaseFunctionsProvider';
import { FirestoreProvider } from './contexts/FirestoreProvider';
import './index.css';

let firebaseConfig = {
Expand All @@ -15,18 +19,45 @@ let firebaseConfig = {
measurementId: '',
};

const provider = new OAuthProvider('oidc.utahid');
// provider.addScope('app:UGRCMoonwalk'); // request submitted to create this app in AP Admin

if (import.meta.env.VITE_FIREBASE_CONFIG) {
firebaseConfig = JSON.parse(import.meta.env.VITE_FIREBASE_CONFIG);
}

const MainErrorFallback = ({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: () => void }) => {
return (
<div className="static flex h-screen w-screen items-center justify-center">
<div className="flex-col items-center">
<h1>Something went wrong</h1>
<pre className="text-red-500">{error.message}</pre>
<button className="w-full rounded-full border p-1" onClick={resetErrorBoundary}>
Try again
</button>
</div>
</div>
);
};

const queryClient = new QueryClient();

createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<FirebaseAppProvider firebaseConfig={firebaseConfig}>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</FirebaseAppProvider>
<ErrorBoundary FallbackComponent={MainErrorFallback} onReset={() => window.location.reload()}>
<FirebaseAppProvider config={firebaseConfig}>
<FirebaseAuthProvider provider={provider}>
<FirebaseAnalyticsProvider>
<FirestoreProvider>
<FirebaseFunctionsProvider>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</FirebaseFunctionsProvider>
</FirestoreProvider>
</FirebaseAnalyticsProvider>
</FirebaseAuthProvider>
</FirebaseAppProvider>
</ErrorBoundary>
</React.StrictMode>,
);

0 comments on commit 61cb637

Please sign in to comment.