Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@base-ui/react": "^1.6.0",
"@better-auth/passkey": "^1.5.5",
"@dnd-kit/react": "^0.4.0",
"@polinetwork/backend": "^0.17.1",
"@polinetwork/backend": "^0.18.0",
"@t3-oss/env-core": "^0.13.10",
"@tanstack/react-router": "1.170.17",
"@tanstack/react-start": "1.168.27",
Expand Down
2,631 changes: 885 additions & 1,746 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/assets/svg/whatsapp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions src/components/dashboard-navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ import {
CircleQuestionMark,
Database,
FolderKanban,
FolderTree,
Globe,
LayoutDashboard,
type LucideIcon,
Settings,
ShieldCheck,
Tags,
Users,
UsersRound,
} from "lucide-react"

import azureIcon from "@/assets/svg/azure.svg"
import telegramIcon from "@/assets/svg/telegram.svg"
import whatsappIcon from "@/assets/svg/whatsapp.svg"

export type DashboardNavigationItem = {
title: string
Expand Down Expand Up @@ -45,6 +48,12 @@ export const dashboardNavigation = [
{ title: "Grants", url: "/dashboard/telegram/grants", icon: ShieldCheck },
],
},
{
title: "WhatsApp",
icon: UsersRound,
iconSrc: whatsappIcon,
items: [{ title: "Groups", url: "/dashboard/whatsapp/groups", icon: Database }],
},
{
title: "Azure",
icon: UsersRound,
Expand All @@ -63,6 +72,8 @@ export const dashboardNavigation = [
{ title: "Associations", url: "/dashboard/web/associations", icon: Users },
{ title: "Guides", url: "/dashboard/web/guides", icon: BookOpen },
{ title: "FAQs", url: "/dashboard/web/faqs", icon: CircleQuestionMark },
{ title: "Group labels", url: "/dashboard/web/group-labels", icon: Tags },
{ title: "Categories", url: "/dashboard/web/groups-by-label", icon: FolderTree },
],
},
] as const satisfies readonly DashboardNavigationCategory[]
Expand Down
12 changes: 6 additions & 6 deletions src/features/associations/associations.functions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createServerFn } from "@tanstack/react-start"

import { adminMiddleware, writeAdminMiddleware } from "@/server/auth.middleware"
import { webAdminMiddleware, webWriteAdminMiddleware } from "@/server/auth.middleware"

import {
associationIdInput,
Expand All @@ -10,7 +10,7 @@ import {
} from "./associations.validation"

export const getAssociations = createServerFn()
.middleware([adminMiddleware])
.middleware([webAdminMiddleware])
.handler(({ context }) => context.backend.web.associations.getAllAssociations.query())

function associationFormData(data: ReturnType<typeof parseCreateAssociationForm>) {
Expand All @@ -23,7 +23,7 @@ function associationFormData(data: ReturnType<typeof parseCreateAssociationForm>
}

export const createAssociation = createServerFn({ method: "POST" })
.middleware([writeAdminMiddleware])
.middleware([webWriteAdminMiddleware])
.validator(parseCreateAssociationForm)
.handler(async ({ data, context }) => {
const formData = associationFormData(data)
Expand All @@ -32,7 +32,7 @@ export const createAssociation = createServerFn({ method: "POST" })
})

export const editAssociation = createServerFn({ method: "POST" })
.middleware([writeAdminMiddleware])
.middleware([webWriteAdminMiddleware])
.validator(parseEditAssociationForm)
.handler(async ({ data, context }) => {
const formData = associationFormData(data)
Expand All @@ -44,7 +44,7 @@ export const editAssociation = createServerFn({ method: "POST" })
})

export const editAssociationLinks = createServerFn({ method: "POST" })
.middleware([writeAdminMiddleware])
.middleware([webWriteAdminMiddleware])
.validator(associationLinksInput)
.handler(async ({ data, context }) => {
const result = await context.backend.web.associations.editAssociationLinks.mutate({
Expand All @@ -56,7 +56,7 @@ export const editAssociationLinks = createServerFn({ method: "POST" })
})

export const deleteAssociation = createServerFn({ method: "POST" })
.middleware([writeAdminMiddleware])
.middleware([webWriteAdminMiddleware])
.validator(associationIdInput)
.handler(async ({ data, context }) => {
const result = await context.backend.web.associations.deleteAssociation.mutate(data)
Expand Down
20 changes: 15 additions & 5 deletions src/features/azure/group-membership.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,15 @@ const MAX_VISIBLE_MEMBERS = 7

type MemberChoice = Pick<AzureMember, "id" | "displayName" | "mail">

export function GroupMembership({ group, directoryMembers }: { group: AzureGroup; directoryMembers: AzureMember[] }) {
export function GroupMembership({
group,
directoryMembers,
canWrite,
}: {
group: AzureGroup
directoryMembers: AzureMember[]
canWrite: boolean
}) {
const sortedMembers = [...group.members].sort((a, b) => a.displayName.localeCompare(b.displayName))
const visibleMembers = sortedMembers.slice(0, MAX_VISIBLE_MEMBERS)
const hiddenMembers = sortedMembers.slice(MAX_VISIBLE_MEMBERS)
Expand All @@ -43,10 +51,12 @@ export function GroupMembership({ group, directoryMembers }: { group: AzureGroup
) : (
<p className="text-sm text-muted-foreground">No members yet</p>
)}
<div className="flex items-center gap-1">
<MembershipDialog group={group} directoryMembers={directoryMembers} mode="add" />
<MembershipDialog group={group} directoryMembers={directoryMembers} mode="remove" />
</div>
{canWrite && (
<div className="flex items-center gap-1">
<MembershipDialog group={group} directoryMembers={directoryMembers} mode="add" />
<MembershipDialog group={group} directoryMembers={directoryMembers} mode="remove" />
</div>
)}
</div>
)
}
Expand Down
38 changes: 32 additions & 6 deletions src/features/azure/groups-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,17 @@ import { GroupMembership } from "./group-membership"
export function AzureGroupsPage({
groups,
directoryMembers,
canWrite,
}: {
groups: AzureGroup[]
directoryMembers: AzureMember[]
canWrite: boolean
}) {
return (
<div className="animate-appear">
<GroupsHeader groups={groups} />
{groups.length ? (
<GroupsList groups={groups} directoryMembers={directoryMembers} />
<GroupsList groups={groups} directoryMembers={directoryMembers} canWrite={canWrite} />
) : (
<EmptyState
icon={UsersRound}
Expand Down Expand Up @@ -62,7 +64,15 @@ function GroupsHeader({ groups }: { groups: AzureGroup[] }) {
)
}

function GroupsList({ groups, directoryMembers }: { groups: AzureGroup[]; directoryMembers: AzureMember[] }) {
function GroupsList({
groups,
directoryMembers,
canWrite,
}: {
groups: AzureGroup[]
directoryMembers: AzureMember[]
canWrite: boolean
}) {
const sortedGroups = useMemo(() => [...groups].sort((a, b) => a.displayName.localeCompare(b.displayName)), [groups])
const multiMemberGroups = sortedGroups.filter((group) => group.members.length > 1)
const singleMemberGroups = sortedGroups.filter((group) => group.members.length <= 1)
Expand All @@ -74,11 +84,17 @@ function GroupsList({ groups, directoryMembers }: { groups: AzureGroup[]; direct
title="Groups with 2+ members"
groups={multiMemberGroups}
directoryMembers={directoryMembers}
canWrite={canWrite}
defaultOpen
/>
)}
{singleMemberGroups.length > 0 && (
<GroupSection title="Groups with 0–1 member" groups={singleMemberGroups} directoryMembers={directoryMembers} />
<GroupSection
title="Groups with 0–1 member"
groups={singleMemberGroups}
directoryMembers={directoryMembers}
canWrite={canWrite}
/>
)}
</div>
)
Expand All @@ -88,11 +104,13 @@ function GroupSection({
title,
groups,
directoryMembers,
canWrite,
defaultOpen = false,
}: {
title: string
groups: AzureGroup[]
directoryMembers: AzureMember[]
canWrite: boolean
defaultOpen?: boolean
}) {
return (
Expand All @@ -115,7 +133,7 @@ function GroupSection({
{groups.map((group) => (
<div key={group.id}>
<Separator />
<GroupRow group={group} directoryMembers={directoryMembers} />
<GroupRow group={group} directoryMembers={directoryMembers} canWrite={canWrite} />
</div>
))}
</CollapsibleContent>
Expand All @@ -124,7 +142,15 @@ function GroupSection({
)
}

function GroupRow({ group, directoryMembers }: { group: AzureGroup; directoryMembers: AzureMember[] }) {
function GroupRow({
group,
directoryMembers,
canWrite,
}: {
group: AzureGroup
directoryMembers: AzureMember[]
canWrite: boolean
}) {
return (
<section className="grid gap-5 px-4 py-5 lg:grid-cols-[minmax(14rem,0.75fr)_minmax(0,1.25fr)] lg:items-center">
<div className="flex min-w-0 flex-col gap-2">
Expand All @@ -142,7 +168,7 @@ function GroupRow({ group, directoryMembers }: { group: AzureGroup; directoryMem
</Badge>
</div>

<GroupMembership group={group} directoryMembers={directoryMembers} />
<GroupMembership group={group} directoryMembers={directoryMembers} canWrite={canWrite} />
</section>
)
}
Expand Down
46 changes: 26 additions & 20 deletions src/features/azure/members-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function memberFilterFrom<Value>(value: Value): MemberFilter {
return result.success ? result.data : { query: "", membersOnly: false }
}

export function AzureMembersPage({ initialMembers }: { initialMembers: AzureMember[] }) {
export function AzureMembersPage({ initialMembers, canWrite }: { initialMembers: AzureMember[]; canWrite: boolean }) {
const router = useRouter()
const [dialog, setDialog] = useState<MemberDialogState | null>(null)
const [members, setMembers] = useState(initialMembers)
Expand Down Expand Up @@ -107,22 +107,26 @@ export function AzureMembersPage({ initialMembers }: { initialMembers: AzureMemb
</div>
),
}),
memberColumnHelper.display({
id: "actions",
header: "",
cell: ({ row }) => (
<Button
variant="link"
size="sm"
className="text-primary"
onClick={() => setDialog({ mode: "edit", member: row.original })}
>
Manage
</Button>
),
}),
...(canWrite
? [
memberColumnHelper.display({
id: "actions",
header: "",
cell: ({ row }) => (
<Button
variant="link"
size="sm"
className="text-primary"
onClick={() => setDialog({ mode: "edit", member: row.original })}
>
Manage
</Button>
),
}),
]
: []),
])
}, [])
}, [canWrite])
const table = useAppTable({
key: "azure-members",
columns,
Expand Down Expand Up @@ -162,9 +166,11 @@ export function AzureMembersPage({ initialMembers }: { initialMembers: AzureMemb
table.setPageIndex(0)
}}
action={
<Button onClick={() => setDialog({ mode: "create" })}>
<Plus data-icon="inline-start" /> Add member
</Button>
canWrite ? (
<Button onClick={() => setDialog({ mode: "create" })}>
<Plus data-icon="inline-start" /> Add member
</Button>
) : undefined
}
>
<Button
Expand Down Expand Up @@ -254,7 +260,7 @@ export function AzureMembersPage({ initialMembers }: { initialMembers: AzureMemb
onPageSizeChange={(pageSize) => table.setPageSize(pageSize)}
/>
)}
{dialog && (
{canWrite && dialog && (
<MemberDialog
dialog={dialog}
onClose={() => setDialog(null)}
Expand Down
9 changes: 8 additions & 1 deletion src/features/faqs/faqs-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export default function FAQsPage({ initFAQs }: { initFAQs: FAQs }) {
setCategoryId(res.id)
toast.success("Category created successfully!")
} catch (e: unknown) {
console.error(e)
const errorMessage = e instanceof Error ? e.message : String(e)
toast.error(`Failed to create category: ${errorMessage}`)
throw e
Expand All @@ -91,6 +92,7 @@ export default function FAQsPage({ initFAQs }: { initFAQs: FAQs }) {
)
toast.success("Category updated successfully!")
} catch (e: unknown) {
console.error(e)
const errorMessage = e instanceof Error ? e.message : String(e)
toast.error(`Failed to update category: ${errorMessage}`)
throw e
Expand All @@ -109,6 +111,7 @@ export default function FAQsPage({ initFAQs }: { initFAQs: FAQs }) {
})
toast.success("Category deleted successfully.")
} catch (e: unknown) {
console.error(e)
const errorMessage = e instanceof Error ? e.message : String(e)
toast.error(`Failed to delete category: ${errorMessage}`)
}
Expand Down Expand Up @@ -224,6 +227,7 @@ export default function FAQsPage({ initFAQs }: { initFAQs: FAQs }) {
setEditAnswerEn("")
toast.success("FAQ saved successfully.")
} catch (e: unknown) {
console.error(e)
const errorMessage = e instanceof Error ? e.message : String(e)
toast.error(`Failed to save FAQ: ${errorMessage}`)
}
Expand Down Expand Up @@ -260,7 +264,10 @@ export default function FAQsPage({ initFAQs }: { initFAQs: FAQs }) {
}
toast.success("FAQ deleted successfully.")
})
.catch((e: string) => toast.error(`Failed to delete FAQ: ${e}`))
.catch((e: string) => {
console.error(e)
toast.error(`Failed to delete FAQ: ${e}`)
})
}

const handleCancel = (id: number) => {
Expand Down
Loading
Loading