Skip to content
This repository has been archived by the owner on Dec 19, 2024. It is now read-only.

Commit

Permalink
Merge pull request #78 from brown-ccv/feat-admin-page
Browse files Browse the repository at this point in the history
Feat: admin page (history)
  • Loading branch information
hetd54 authored Aug 6, 2024
2 parents 0e0c2f2 + 2bf4512 commit 1ca9a3a
Show file tree
Hide file tree
Showing 11 changed files with 804 additions and 377 deletions.
8 changes: 7 additions & 1 deletion firebase.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
"ui": {
"enabled": true
},
"singleProjectMode": true
"singleProjectMode": true,
"firestore": {
"port": 8080
},
"auth": {
"port": 9099
}
}
}
938 changes: 570 additions & 368 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"build": "astro check && astro build",
"preview": "astro preview",
"astro": "astro",
"check": "astro check"
"check": "astro check",
"emulators": "firebase emulators:start"
},
"dependencies": {
"@astrojs/check": "^0.5.3",
Expand All @@ -27,7 +28,7 @@
"@types/react": "^18.2.64",
"@types/react-dom": "^18.2.21",
"astro": "^4.3.6",
"firebase": "^10.12.4",
"firebase": "^9.23.0",
"i": "^0.3.7",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
36 changes: 36 additions & 0 deletions src/components/ActivityPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useState } from "react"
import type { UserInfo } from "firebase/auth"
import Login from "../components/Login"
import { useActivityData } from "../hooks/activity.ts"
import ActivityTable from "./ActivityTable.tsx"

const ActivityPage = () => {
const [user, setUser] = useState<UserInfo | null | undefined>(null)
const setUserFunction = (loggedUser: UserInfo | null | undefined) => {
setUser(loggedUser)
}
const activityData = useActivityData(user)

return (
<div className="space-y-8">
<section className="space-y-6">
<Login currentUser={user} setUserFunction={setUserFunction} />
{!user && (
<p>
This section of the website is reserved for administrators to view download statistics.
</p>
)}
</section>
{user && activityData && (
<section className="space-y-2">
<h3>
<span className="font-bold px-2">{activityData.length}</span> download(s)
</h3>
<ActivityTable data={activityData} />
</section>
)}
</div>
)
}

export default ActivityPage
47 changes: 47 additions & 0 deletions src/components/ActivityTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from "react"
import type { activityType } from "../hooks/activity.ts"

export interface ActivityTableProps {
data: activityType[]
}

const ActivityTable: React.FC<ActivityTableProps> = ({ data }) => {
return (
<div className="w-full overflow-x-scroll no-scrollbar">
<table className="table-fixed border-spacing-2 w-full">
<thead>
<tr className="text-xl bg-neutral-100 text-left text-neutral-900">
<th>Description</th>
<th className="w-1/4">User</th>
<th className="w-1/5">Download Date</th>
</tr>
</thead>

<tbody>
{data &&
data.map(({ name, institution, email, description, date }, i) => {
const stringDate = date.toDate().toDateString()
return (
<tr key={i} className="align-top">
<td>
<p>{description}</p>
</td>
<td>
<div>
<p className="text-lg font-semibold text-neutral-900">{name}</p>
<p className="text-neutral-700 italic overflow-hidden overflow-ellipsis ">
{email}
</p>
<p className="small">{institution}</p>
</div>
</td>
<td>{stringDate}</td>
</tr>
)
})}
</tbody>
</table>
</div>
)
}
export default ActivityTable
4 changes: 2 additions & 2 deletions src/components/DataForm.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Controller, type SubmitHandler, useForm } from "react-hook-form"
import * as Form from "@radix-ui/react-form"
import React from "react"
import { addHistoryData } from "../firebase"
import { addActivityData } from "../firebase"
import { Input } from "./Input.tsx"
import { Textarea } from "./Textarea.tsx"
import Button from "./Button.tsx"
Expand All @@ -17,7 +17,7 @@ const DataForm = () => {
const { handleSubmit, control, register } = useForm<Inputs>()
const formRef = React.useRef<HTMLFormElement>(null)
const onSubmit: SubmitHandler<Inputs> = async (data) => {
await addHistoryData(data)
await addActivityData(data)
if (formRef.current) formRef.current.submit()
}
return (
Expand Down
41 changes: 41 additions & 0 deletions src/components/Login.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, { useState } from "react"
import { type UserInfo } from "firebase/auth"
import { handleLogin, handleLogout } from "../firebase"
import Button from "./Button.tsx"

interface LoginProps {
currentUser: UserInfo | null | undefined
setUserFunction: (loggedUser: UserInfo | null | undefined) => void
}

const Login: React.FC<LoginProps> = ({ currentUser, setUserFunction }) => {
const [message, setMessage] = useState("")

const login = async () => {
await handleLogin().then((loggedUser) => {
if (!loggedUser)
setMessage(
"You must be an admin on this project in order to see this data. If you believe this to be an error, please reach out to David Lindstrom ([email protected])."
)
setUserFunction(loggedUser)
})
}
const logout = async () => {
await handleLogout()
setUserFunction(null)
setMessage("")
}
return (
<section className="flex flex-col gap-6">
<div>
{currentUser ? (
<Button onClick={() => logout()}>Log Out</Button>
) : (
<Button onClick={() => login()}>Log In</Button>
)}
</div>
{message && <p className="font-semibold text-primary-300">{message}</p>}
</section>
)
}
export default Login
10 changes: 9 additions & 1 deletion src/consts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
export const SITE_TITLE = "MMP"
export const SITE_DESCRIPTION = "Mesoamerican Migration Project"

export const LINKS = ["people", "news", "publications", "study-design", "data", "documentation"]
export const LINKS = [
"people",
"news",
"publications",
"study-design",
"data",
"documentation",
"activity",
]
56 changes: 53 additions & 3 deletions src/firebase.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,71 @@
import { initializeApp } from "firebase/app"
import { getFirestore, addDoc, collection } from "firebase/firestore"
import { addDoc, collection, getDocs, getFirestore, Timestamp } from "firebase/firestore"
import { getAuth, GoogleAuthProvider, signInWithPopup, signOut } from "firebase/auth"
import firebaseConfig from "./firebase-config.json"

const provider = new GoogleAuthProvider()
provider.setCustomParameters({
hd: "brown.edu",
})

const app = initializeApp(firebaseConfig)
const db = getFirestore(app)
export const auth = getAuth(app)

export const addHistoryData = async (inputs) => {
export const addActivityData = async (inputs) => {
try {
const docRef = await addDoc(collection(db, "history"), {
name: inputs.name,
institution: inputs.institution,
email: inputs.email,
description: inputs.description,
date: new Date(),
date: Timestamp.now(),
})
console.log("Document written with ID: ", docRef.id)
} catch (e) {
console.error("Error adding document: ", e)
}
}

export const getAdminUsers = async () => {
const querySnapshot = await getDocs(collection(db, "admin"))
const data = []
querySnapshot.forEach((doc) => {
data.push(doc.data())
})
return data.map((user) => {
return user.email
})
}

export const getActivityData = async () => {
const querySnapshot = await getDocs(collection(db, "history"))
const data = []
querySnapshot.forEach((doc) => {
data.push(doc.data())
})
return data
}

export const handleLogin = async () => {
try {
const result = await signInWithPopup(auth, provider)
const { email } = result.user
const adminUsers = await getAdminUsers()
if (!adminUsers.includes(email.toLowerCase())) {
return null
} else {
return result.user
}
} catch (error) {
console.log(error)
}
}

export const handleLogout = async () => {
try {
await signOut(auth)
} catch (error) {
console.log(error)
}
}
28 changes: 28 additions & 0 deletions src/hooks/activity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { Timestamp } from "firebase/firestore"
import type { UserInfo } from "firebase/auth"
import { useEffect, useState } from "react"
import { getActivityData } from "../firebase"

export interface activityType {
name: string
institution: string
email: string
description: string
date: Timestamp
}

const getData = async () => {
return await getActivityData()
}

export function useActivityData(user: UserInfo | null | undefined) {
const [activityData, setActivityData] = useState<activityType[] | null>(null)
useEffect(() => {
if (user) {
getData().then((data) => {
setActivityData(data)
})
}
}, [user])
return activityData
}
8 changes: 8 additions & 0 deletions src/pages/activity.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
import Layout from "../layouts/Layout.astro"
import ActivityPage from "../components/ActivityPage"
---

<Layout title="Admin" description="History Table of Downloads">
<ActivityPage client:only="react" />
</Layout>

0 comments on commit 1ca9a3a

Please sign in to comment.