Refine workspaces and event publishing; harden uploads and email delivery
This commit is contained in:
@@ -1,39 +1,16 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { headers } from "next/headers";
|
||||
import Link from "next/link";
|
||||
import { auth } from "@/server/auth";
|
||||
import { getPlatformRole } from "@/server/roles";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { createServerCaller } from "@/trpc/server";
|
||||
import { BackendShell } from "@/components/backend-shell";
|
||||
|
||||
import { DashboardTabBar } from "@/components/dashboard-tab-bar";
|
||||
|
||||
export default async function AdminLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const session = await auth.api.getSession({ headers: await headers() });
|
||||
if (!session) redirect("/sign-in?callbackURL=/admin");
|
||||
const role = await getPlatformRole(session.user.id);
|
||||
if (!role) redirect("/dashboard");
|
||||
export default async function AdminLayout({ children }: { children: React.ReactNode }) {
|
||||
const caller = await createServerCaller();
|
||||
const viewer = await caller.viewer.me();
|
||||
if (!viewer.session) redirect("/sign-in?callbackURL=/admin");
|
||||
if (!viewer.platformRole) redirect("/dashboard");
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className="page-pad mx-auto w-full max-w-6xl py-6 pb-24 sm:py-10 sm:pb-10">
|
||||
<div className="mb-6 hidden items-center gap-1 sm:flex">
|
||||
<Button asChild variant="ghost" size="sm">
|
||||
<Link href="/admin">Overview</Link>
|
||||
</Button>
|
||||
<Button asChild variant="ghost" size="sm">
|
||||
<Link href="/admin/settings">Settings</Link>
|
||||
</Button>
|
||||
<Button asChild variant="ghost" size="sm">
|
||||
<Link href="/dashboard">Dashboard</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="reveal">{children}</div>
|
||||
</main>
|
||||
<DashboardTabBar showAdmin area="admin" />
|
||||
</>
|
||||
<BackendShell area="platform" groups={viewer.groups} activeGroupId={viewer.activeGroupId} showAdmin>
|
||||
<div className="reveal">{children}</div>
|
||||
</BackendShell>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { NativeSelect } from "@/components/ui/native-select";
|
||||
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { api } from "@/trpc/react";
|
||||
@@ -78,8 +80,8 @@ export function PlatformCodes({
|
||||
) : null}
|
||||
{groups.length > 0 ? (
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<select
|
||||
className="rounded-md border bg-background px-2 py-1.5 text-sm"
|
||||
<NativeSelect
|
||||
|
||||
value={groupId}
|
||||
onChange={(event) => setGroupId(event.target.value)}
|
||||
>
|
||||
@@ -88,7 +90,7 @@ export function PlatformCodes({
|
||||
{group.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</NativeSelect>
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={!groupId}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import type { PlatformRole } from "@album/contracts";
|
||||
@@ -55,25 +57,27 @@ export function PlatformUsers() {
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{row.platformRole ? (
|
||||
<Badge variant="secondary">{row.platformRole}</Badge>
|
||||
<Badge variant="secondary" className="capitalize">{row.platformRole.replaceAll("_", " ")}</Badge>
|
||||
) : null}
|
||||
<select
|
||||
className="rounded-md border bg-background px-2 py-1.5 text-sm"
|
||||
<Select
|
||||
value={row.platformRole ?? "none"}
|
||||
onChange={(event) => {
|
||||
const value = event.target.value as PlatformRole | "none";
|
||||
onValueChange={(selected) => {
|
||||
const value = selected as PlatformRole | "none";
|
||||
setRole.mutate({
|
||||
userId: row.id,
|
||||
role: value === "none" ? null : value,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SelectTrigger aria-label={`Platform role for ${row.name}`}><SelectValue /></SelectTrigger>
|
||||
<SelectContent><SelectGroup>
|
||||
{roles.map((role) => (
|
||||
<option key={role} value={role}>
|
||||
{role}
|
||||
</option>
|
||||
<SelectItem key={role} value={role} className="capitalize">
|
||||
{role.replaceAll("_", " ").replace(/\b\w/g, (letter) => letter.toUpperCase())}
|
||||
</SelectItem>
|
||||
))}
|
||||
</select>
|
||||
</SelectGroup></SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { NativeSelect } from "@/components/ui/native-select";
|
||||
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import type { EventCreatePolicy } from "@album/contracts";
|
||||
@@ -61,9 +63,9 @@ export function DeploymentSettingsForm({
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="policy">Event creation</FieldLabel>
|
||||
<select
|
||||
<NativeSelect
|
||||
id="policy"
|
||||
className="rounded-md border bg-background px-2 py-2"
|
||||
|
||||
value={policy}
|
||||
onChange={(event) =>
|
||||
setPolicy(event.target.value as EventCreatePolicy)
|
||||
@@ -72,7 +74,7 @@ export function DeploymentSettingsForm({
|
||||
<option value="open">Open (quota still applies)</option>
|
||||
<option value="invite">Invite code required</option>
|
||||
<option value="admin_only">Administrators only</option>
|
||||
</select>
|
||||
</NativeSelect>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="limit">Default event limit for new groups</FieldLabel>
|
||||
|
||||
Reference in New Issue
Block a user