-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement chairperson and participant views
This allows a meeting's secretary/chair to a current motion at the /m/:meetingId/chair route and for others to view when that changes at the /m/:meetingId/participant route instantly. Shout-out to this user on GitHub for the optimal system-font CSS: picturepan2/spectre#666 (comment) Resolves #1
- Loading branch information
1 parent
0d3fe5e
commit ff13720
Showing
15 changed files
with
1,192 additions
and
43 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { initializeApp } from "firebase/app"; | ||
import { getDatabase } from "firebase/database"; | ||
|
||
const options = { | ||
apiKey: process.env.REACT_APP_FIREBASE_API_KEY, | ||
authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN, | ||
databaseURL: process.env.REACT_APP_DATABASE_URL, | ||
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID, | ||
storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET, | ||
messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID, | ||
appId: process.env.REACT_APP_FIREBASE_APP_ID, | ||
}; | ||
|
||
const firebase = initializeApp(options); | ||
|
||
export const db = getDatabase(firebase); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
import { ref, child, set, push, onValue } from "@firebase/database"; | ||
import { Field, Form, Formik, FormikHelpers, FormikValues } from "formik"; | ||
import React from "react"; | ||
import { useNavigate, useParams } from "react-router-dom"; | ||
import { db } from "../../lib/firebase"; | ||
|
||
export default function ChairDashboard() { | ||
const navigate = useNavigate(); | ||
const { meetingId } = useParams(); | ||
|
||
const [newMotion, setNewMotion] = React.useState(""); | ||
|
||
const [currentMotion, setCurrentMotion] = React.useState(""); | ||
|
||
React.useEffect(() => { | ||
const meetingRef = ref(db, `/meetings/${meetingId}`); | ||
const unsubscribe = onValue(meetingRef, (snapshot) => { | ||
const val = snapshot.val() as MeetingInfo; | ||
setCurrentMotion(val.currentMotion); | ||
}); | ||
return () => { | ||
unsubscribe(); | ||
}; | ||
}, []); | ||
|
||
const handleSubmit = (data: MeetingInfo, startNow: boolean) => { | ||
const meetingRef = ref(db, `/meetings/${meetingId}`); | ||
push(meetingRef, data).then((newRef) => { | ||
const meetingId = newRef.key; | ||
navigate(`/chair/${meetingId}`); | ||
}); | ||
}; | ||
|
||
/** | ||
* Send the current motion in the text box to the server. | ||
* | ||
* @param motion The new motion to set | ||
*/ | ||
const updateCurrentMotion = () => { | ||
const meetingRef = ref(db, `/meetings/${meetingId}`); | ||
set(meetingRef, { | ||
currentMotion: newMotion, | ||
}) | ||
.then(() => { | ||
// Reset after successful update | ||
setNewMotion(""); | ||
}) | ||
.catch((error) => { | ||
console.error(error); | ||
}); | ||
}; | ||
|
||
const handleFormUpdate = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
const content = event.currentTarget.value; | ||
setNewMotion(content); | ||
}; | ||
|
||
const shouldDisableButton = newMotion.trim() === ""; | ||
|
||
return ( | ||
<main> | ||
<section className="absolute bottom-0 left-0 right-0 bg-primary-1"> | ||
<div className="p-4 mx-auto mx-6xl"> | ||
<div className="text-xl font-semibold">Update current motion</div> | ||
<div className="flex mt-2"> | ||
<input | ||
className="flex-1 p-2 rounded-md" | ||
type="text" | ||
name="motion" | ||
id="motion" | ||
value={newMotion} | ||
onChange={handleFormUpdate} | ||
placeholder={currentMotion} | ||
/> | ||
<button | ||
className="ml-2 p-2 rounded-md bg-white disabled:bg-gray-200" | ||
disabled={shouldDisableButton} | ||
onClick={updateCurrentMotion} | ||
> | ||
Update | ||
</button> | ||
</div> | ||
</div> | ||
</section> | ||
{/* <div className="max-w-4xl mx-auto p-4"> | ||
<CreateMeetingDialog onSubmit={handleSubmit} /> | ||
</div> */} | ||
</main> | ||
); | ||
} | ||
|
||
interface CreateMeetingDialogProps { | ||
onSubmit: (data: MeetingInfo, startNow: boolean) => void; | ||
} | ||
|
||
function CreateMeetingDialog({ onSubmit }: CreateMeetingDialogProps) { | ||
const handleSubmit = ( | ||
values: MeetingInfo, | ||
actions: FormikHelpers<MeetingInfo> | ||
) => { | ||
onSubmit(values, true); | ||
}; | ||
|
||
return ( | ||
<div className="p-8 bg-primary-1 rounded-lg"> | ||
<Formik | ||
initialValues={{ | ||
title: "Just another meeting", | ||
startTime: "", | ||
endTime: "", | ||
agendaUrl: "", | ||
currentMotion: "", | ||
}} | ||
onSubmit={handleSubmit} | ||
> | ||
<Form> | ||
<div className="py-2"> | ||
<label className="text-xl font-bold" htmlFor="title"> | ||
Meeting Name | ||
</label> | ||
<Field className="block p-2 rounded-md" id="title" name="title" /> | ||
</div> | ||
<div> | ||
<label htmlFor="startTime">Start Time</label> | ||
<Field name="startTime" type="date" /> | ||
<label htmlFor="endTime">End Time</label> | ||
<Field name="endTime" type="date" /> | ||
</div> | ||
<label htmlFor="agendaUrl">Agenda</label> | ||
<div> | ||
<button className="p-2 bg-primary-4 rounded-md" type="submit"> | ||
Start now | ||
</button> | ||
</div> | ||
</Form> | ||
</Formik> | ||
</div> | ||
); | ||
} | ||
|
||
type MeetingInfo = { | ||
title: string; | ||
startTime: string; | ||
endTime: string; | ||
agendaUrl: string; | ||
currentMotion: string; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { Link } from "react-router-dom"; | ||
import IconButton from "./components/IconButton"; | ||
|
||
export default function LandingPage() { | ||
return ( | ||
<div className="h-full px-4 xl:grid xl:grid-cols-12"> | ||
<section className="inline-block rounded-[16px] py-8 xl:col-start-2 xl:col-span-5 bg-primary-1 px-8 mt-[64px] shadow-md"> | ||
<section> | ||
<div className="text-[96px] font-bold">ParliPro</div> | ||
<div className="mt-4 text-[34px] font-semibold"> | ||
Presiding over meetings, done simply. | ||
</div> | ||
</section> | ||
<section className="mt-[96px] space-x-4 space-y-4"> | ||
<IconButton to="/m/test/chair">Start meeting</IconButton> | ||
<IconButton to="/m/test/participant">Join meeting</IconButton> | ||
{/* <div className="text-md"> | ||
or open a{" "} | ||
<Link | ||
className="text-blue-500 hover:underline focus:underline" | ||
to="/companion" | ||
> | ||
companion display | ||
</Link> | ||
</div> */} | ||
</section> | ||
</section> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { Link } from "react-router-dom"; | ||
|
||
interface IconButtonProps { | ||
icon?: JSX.Element; | ||
color?: string; | ||
to?: string; | ||
} | ||
|
||
export default function IconButton({ | ||
icon, | ||
color, | ||
children, | ||
to = "#", | ||
}: React.PropsWithChildren<IconButtonProps>) { | ||
return ( | ||
<Link | ||
to={to} | ||
className="inline-block p-3 space-x-4 rounded-md shadow-sm hover:shadow-md focus:shadow-md bg-red-400 text-white" | ||
style={{ | ||
color: color, | ||
}} | ||
> | ||
{children} | ||
</Link> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import React from "react"; | ||
import { useParams } from "react-router-dom"; | ||
import { onValue, ref } from "firebase/database"; | ||
import { db } from "../../../lib/firebase"; | ||
|
||
type MeetingState = { | ||
currentMotion: string; | ||
}; | ||
|
||
export default function ParticipantDisplay() { | ||
const [state, setState] = React.useState<MeetingState>(); | ||
const { meetingId } = useParams(); | ||
|
||
React.useEffect(() => { | ||
const meetingRef = ref(db, `meetings/${meetingId}`); | ||
const unsubscribe = onValue(meetingRef, (snapshot) => { | ||
const updatedState = snapshot.val() as MeetingState; | ||
if (!updatedState) { | ||
return; | ||
} | ||
setState(updatedState); | ||
}); | ||
return () => { | ||
unsubscribe(); | ||
}; | ||
}, [meetingId]); | ||
|
||
return ( | ||
<main className="p-4"> | ||
<div className="max-w-4xl mx-auto p-8 bg-primary-1 rounded-[24px]"> | ||
<div className="text-2xl font-bold">Current motion</div> | ||
<div className="text-xl font-bold"> | ||
{state?.currentMotion ?? "Meeting not in session"} | ||
</div> | ||
<div className="mt-4 text-lg">Current meeting: {meetingId}</div> | ||
</div> | ||
</main> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { Outlet } from "react-router-dom"; | ||
|
||
export default function IndexRoute() { | ||
return ( | ||
<> | ||
<Outlet /> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { Outlet } from "react-router-dom"; | ||
|
||
/** | ||
* Route: /m/:meetingId | ||
*/ | ||
export default function MeetingRoute() { | ||
return ( | ||
<> | ||
{/* TODO: Add navigation */} | ||
<Outlet /> | ||
</> | ||
); | ||
} |
Oops, something went wrong.