diff --git a/common/group.ts b/common/group.ts
index 7d3215ae..c449f451 100644
--- a/common/group.ts
+++ b/common/group.ts
@@ -10,6 +10,7 @@ export type Group = {
anyoneCanJoin: boolean
contractIds: string[]
+ dashboardId?: string
chatDisabled?: boolean
mostRecentChatActivityTime?: number
mostRecentContractAddedTime?: number
diff --git a/firestore.rules b/firestore.rules
index fe45071b..122ceed8 100644
--- a/firestore.rules
+++ b/firestore.rules
@@ -160,7 +160,7 @@ service cloud.firestore {
allow update: if request.auth.uid == resource.data.creatorId
&& request.resource.data.diff(resource.data)
.affectedKeys()
- .hasOnly(['name', 'about', 'contractIds', 'memberIds', 'anyoneCanJoin' ]);
+ .hasOnly(['name', 'about', 'contractIds', 'memberIds', 'anyoneCanJoin', 'dashboardId' ]);
allow update: if (request.auth.uid in resource.data.memberIds || resource.data.anyoneCanJoin)
&& request.resource.data.diff(resource.data)
.affectedKeys()
diff --git a/web/components/groups/group-dashboard.tsx b/web/components/groups/group-dashboard.tsx
new file mode 100644
index 00000000..3d18140a
--- /dev/null
+++ b/web/components/groups/group-dashboard.tsx
@@ -0,0 +1,143 @@
+import { useAdmin } from 'web/hooks/use-admin'
+import { Row } from '../layout/row'
+import { Content } from '../editor'
+import { TextEditor, useTextEditor } from 'web/components/editor'
+import { Button } from '../button'
+import { Spacer } from '../layout/spacer'
+import { Group } from 'common/group'
+import { deleteFieldFromGroup, updateGroup } from 'web/lib/firebase/groups'
+import PencilIcon from '@heroicons/react/solid/PencilIcon'
+import { DocumentRemoveIcon } from '@heroicons/react/solid'
+import { createDashboard } from 'web/lib/firebase/api'
+import { Dashboard } from 'common/dashboard'
+import { deleteDashboard, updateDashboard } from 'web/lib/firebase/dashboards'
+import { useRouter } from 'next/router'
+import { useState } from 'react'
+
+export function GroupDashboard(props: {
+ group: Group
+ isCreator: boolean
+ dashboard: Dashboard
+}) {
+ const { group, isCreator, dashboard } = props
+ const isAdmin = useAdmin()
+
+ if (group.dashboardId == null && !isCreator) {
+ return
No dashboard has been created
+ }
+
+ return (
+
+ {isCreator || isAdmin ? (
+
+ ) : (
+
+ )}
+
+ )
+}
+
+function RichEditGroupDashboard(props: { group: Group; dashboard: Dashboard }) {
+ const { group, dashboard } = props
+ const [editing, setEditing] = useState(false)
+ const [isSubmitting, setIsSubmitting] = useState(false)
+ const router = useRouter()
+
+ const { editor, upload } = useTextEditor({
+ defaultValue: dashboard.content,
+ disabled: isSubmitting,
+ })
+
+ async function saveDashboard() {
+ if (!editor) return
+ const newDashboard = {
+ name: group.name,
+ content: editor.getJSON(),
+ }
+
+ if (group.dashboardId == null) {
+ const result = await createDashboard(newDashboard).catch((e) => {
+ console.error(e)
+ return e
+ })
+ await updateGroup(group, {
+ dashboardId: result.dashboard.id,
+ })
+ } else {
+ await updateDashboard(dashboard, {
+ content: newDashboard.content,
+ })
+ }
+ await router.replace(router.asPath)
+ }
+
+ async function deleteGroupDashboard() {
+ await deleteDashboard(dashboard)
+ await deleteFieldFromGroup(group, 'dashboardId')
+ await router.replace(router.asPath)
+ }
+
+ return editing ? (
+ <>
+
+
+
+
+
+
+ >
+ ) : (
+ <>
+ {group.dashboardId == null ? (
+
+
+ No dashboard has been created yet.
+
+
+
+
+ ) : (
+
+
+
+
+
+
+
+
+
+
+ )}
+ >
+ )
+}
diff --git a/web/lib/firebase/groups.ts b/web/lib/firebase/groups.ts
index 3f5d18af..4815e430 100644
--- a/web/lib/firebase/groups.ts
+++ b/web/lib/firebase/groups.ts
@@ -1,5 +1,6 @@
import {
deleteDoc,
+ deleteField,
doc,
getDocs,
query,
@@ -17,6 +18,7 @@ import {
} from './utils'
import { Contract } from 'common/contract'
import { updateContract } from 'web/lib/firebase/contracts'
+import * as admin from 'firebase-admin'
export const groups = coll('groups')
@@ -28,6 +30,7 @@ export function groupPath(
| 'about'
| typeof GROUP_CHAT_SLUG
| 'leaderboards'
+ | 'dashboard'
) {
return `/group/${groupSlug}${subpath ? `/${subpath}` : ''}`
}
@@ -36,6 +39,10 @@ export function updateGroup(group: Group, updates: Partial) {
return updateDoc(doc(groups, group.id), updates)
}
+export function deleteFieldFromGroup(group: Group, field: string) {
+ return updateDoc(doc(groups, group.id), { [field]: deleteField() })
+}
+
export function deleteGroup(group: Group) {
return deleteDoc(doc(groups, group.id))
}
diff --git a/web/pages/group/[...slugs]/index.tsx b/web/pages/group/[...slugs]/index.tsx
index 28658a16..05e14c47 100644
--- a/web/pages/group/[...slugs]/index.tsx
+++ b/web/pages/group/[...slugs]/index.tsx
@@ -45,6 +45,10 @@ import { Button } from 'web/components/button'
import { listAllCommentsOnGroup } from 'web/lib/firebase/comments'
import { GroupComment } from 'common/comment'
import { REFERRAL_AMOUNT } from 'common/economy'
+import { GroupDashboard } from 'web/components/groups/group-dashboard'
+import { getDashboard } from 'web/lib/firebase/dashboards'
+import { Dashboard } from 'common/dashboard'
+import { Spacer } from 'web/components/layout/spacer'
export const getStaticProps = fromPropz(getStaticPropz)
export async function getStaticPropz(props: { params: { slugs: string[] } }) {
@@ -57,6 +61,10 @@ export async function getStaticPropz(props: { params: { slugs: string[] } }) {
const contracts =
(group && (await listContractsByGroupSlug(group.slug))) ?? []
+ const dashboard =
+ group &&
+ group.dashboardId != null &&
+ (await getDashboard(group.dashboardId))
const bets = await Promise.all(
contracts.map((contract: Contract) => listAllBets(contract.id))
)
@@ -83,6 +91,7 @@ export async function getStaticPropz(props: { params: { slugs: string[] } }) {
creatorScores,
topCreators,
messages,
+ dashboard,
},
revalidate: 60, // regenerate after a minute
@@ -121,6 +130,7 @@ export default function GroupPage(props: {
creatorScores: { [userId: string]: number }
topCreators: User[]
messages: GroupComment[]
+ dashboard: Dashboard
}) {
props = usePropz(props, getStaticPropz) ?? {
group: null,
@@ -139,6 +149,7 @@ export default function GroupPage(props: {
topTraders,
creatorScores,
topCreators,
+ dashboard,
} = props
const router = useRouter()
@@ -176,6 +187,16 @@ export default function GroupPage(props: {
const aboutTab = (
+ {group.dashboardId != null || isCreator ? (
+
+ ) : (
+
+ )}
+