Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ export function AMADetails() {
useRealtimeInvalidate(amaQuestionsChannel(params.id, params.amaId), () => {
void invalidateAMAQuestions(queryClient, params.id, params.amaId);
});
const [showEndConfirm, setShowEndConfirm] = useState(false);
const [showCloseConfirm, setShowCloseConfirm] = useState(false);
const [actionError, setActionError] = useState<string | null>(null);
const [successMessage, setSuccessMessage] = useState<string | null>(null);
const [configForm, setConfigForm] = useState<ConfigFormData | null>(null);
Expand All @@ -197,8 +197,9 @@ export function AMADetails() {
const exportQuestions = useExportAMAQuestions(params.id, params.amaId);

// Guests can view this whole page (session info, channels, stats) -- they just can't edit any of it
// or take the destructive/maintenance actions below (config edit, prompt edit, repost, export, end).
// Every mutating button/card on this page is gated on `canManage`.
// or take the maintenance actions below (config edit, prompt edit, repost, export, close/reopen
// submissions). Every mutating button/card on this page is gated on `canManage`. None of them are gated
// on the session being open anymore (#299) -- a closed session is still actively worked on.
const { canManage } = useGuildAccess(params.id);

// See GrantsList.tsx for why this also checks `ama === undefined`: a background refetch failure keeps the
Expand Down Expand Up @@ -458,22 +459,42 @@ export function AMADetails() {
}
};

const handleEndAMA = async () => {
if (!showEndConfirm) {
setShowEndConfirm(true);
// Closing only stops new submissions (#299) -- the rest of this page (triage, answers, config, export) stays
// just as relevant afterwards, so this deliberately stays put instead of navigating back to the session list.
const handleCloseSubmissions = async () => {
if (!showCloseConfirm) {
setShowCloseConfirm(true);
return;
}

setActionError(null);
setSuccessMessage(null);

try {
await updateAMA.mutateAsync({ ended: true });
router.push(`/dashboard/${params.id}/ama/amas`);
setSuccessMessage('Question submissions closed. Existing questions can still be reviewed and answered.');
} catch (error) {
setActionError(error instanceof APIError ? error.message : 'Failed to end AMA. Please try again.');
console.error('Failed to end AMA:', error);
setActionError(
error instanceof APIError ? error.message : 'Failed to close question submissions. Please try again.',
);
console.error('Failed to close AMA question submissions:', error);
} finally {
setShowEndConfirm(false);
setShowCloseConfirm(false);
}
};

const handleReopenSubmissions = async () => {
setActionError(null);
setSuccessMessage(null);

try {
await updateAMA.mutateAsync({ ended: false });
setSuccessMessage('Question submissions reopened.');
} catch (error) {
setActionError(
error instanceof APIError ? error.message : 'Failed to reopen question submissions. Please try again.',
);
console.error('Failed to reopen AMA question submissions:', error);
}
};

Expand Down Expand Up @@ -556,7 +577,7 @@ export function AMADetails() {
<div className="rounded-lg border border-on-secondary bg-card p-6 dark:border-on-secondary-dark dark:bg-card-dark">
<div className="mb-4 flex items-center justify-between">
<h2 className="text-xl font-medium text-primary dark:text-primary-dark">Session Information</h2>
{canManage && !ama.ended && !editing && !promptEditing && (
{canManage && !editing && !promptEditing && (
<Button
className="px-3 py-1.5 text-sm bg-on-tertiary dark:bg-on-tertiary-dark text-primary dark:text-primary-dark rounded-md hover:bg-on-secondary dark:hover:bg-on-secondary-dark transition-colors disabled:opacity-50"
isDisabled={isGuildInfoLoading || (guildInfo === undefined && Boolean(guildInfoError))}
Expand Down Expand Up @@ -585,13 +606,15 @@ export function AMADetails() {
)}

<div>
<p className="text-sm font-medium text-secondary dark:text-secondary-dark mb-1">Status</p>
<p className="text-sm font-medium text-secondary dark:text-secondary-dark mb-1">Question Submissions</p>
<span
className={`inline-block rounded px-3 py-1 text-sm font-medium ${
ama.ended ? 'bg-misc-danger/10 text-misc-danger' : 'bg-misc-accent/10 text-misc-accent'
ama.ended
? 'bg-on-tertiary text-secondary dark:bg-on-tertiary-dark dark:text-secondary-dark'
: 'bg-misc-accent/10 text-misc-accent'
}`}
>
{ama.ended ? 'Ended' : 'Active'}
{ama.ended ? 'Closed' : 'Open'}
</span>
</div>

Expand Down Expand Up @@ -632,7 +655,7 @@ export function AMADetails() {
error={configErrors.scheduledCloseAt}
helper={
<p className="mt-1 text-sm text-secondary dark:text-secondary-dark">
Optional - automatically ends the AMA at this date/time. Clear to cancel it.
Optional - automatically closes question submissions at this date/time. Clear to cancel it.
</p>
}
id="edit-scheduled-close-at"
Expand Down Expand Up @@ -879,7 +902,7 @@ export function AMADetails() {
<div className="rounded-lg border border-on-secondary bg-card p-6 dark:border-on-secondary-dark dark:bg-card-dark lg:col-span-2">
<div className="mb-4 flex items-center justify-between">
<h2 className="text-xl font-medium text-primary dark:text-primary-dark">Prompt Message</h2>
{canManage && ama.promptMessageExists && !ama.ended && !editing && !promptEditing && (
{canManage && ama.promptMessageExists && !editing && !promptEditing && (
<Button
className="px-3 py-1.5 text-sm bg-on-tertiary dark:bg-on-tertiary-dark text-primary dark:text-primary-dark rounded-md hover:bg-on-secondary dark:hover:bg-on-secondary-dark transition-colors disabled:opacity-50"
onPress={startPromptEdit}
Expand All @@ -901,6 +924,9 @@ export function AMADetails() {
</span>
</div>

{/* The one action still gated on submissions being open, matching `amaRepostSelect.ts`'s own guard:
the prompt message is the submit-a-question entry point, so reposting it on a closed session just
hands people a button that turns them away. Reopen first, then repost. */}
{canManage && !ama.promptMessageExists && !ama.ended && (
<div className="pt-2">
<Button
Expand Down Expand Up @@ -1042,26 +1068,51 @@ export function AMADetails() {
)}
</div>

{/* Actions Card */}
{canManage && !ama.ended && (
<div className="rounded-lg border border-misc-danger/20 bg-card p-6 dark:border-misc-danger/20 dark:bg-card-dark lg:col-span-2">
<h2 className="text-xl font-medium text-misc-danger mb-4">Danger Zone</h2>
{showEndConfirm ? (
{/* Question Submissions Card */}
{canManage && (
<div className="rounded-lg border border-on-secondary bg-card p-6 dark:border-on-secondary-dark dark:bg-card-dark lg:col-span-2">
<h2 className="mb-4 text-xl font-medium text-primary dark:text-primary-dark">Question Submissions</h2>
{ama.ended ? (
<div className="space-y-4">
<p className="text-base text-primary dark:text-primary-dark">
Are you sure you want to end this AMA? This action is <strong>irreversible</strong>.
This AMA is closed to new questions - the &quot;Submit a question&quot; button turns people away.
Everything else still works: questions already submitted can be reviewed, answered and exported.
</p>
<Button
className="px-3 py-2.5 bg-misc-accent text-white rounded-md hover:opacity-90 transition-opacity disabled:opacity-50"
isDisabled={updateAMA.isPending}
onPress={handleReopenSubmissions}
type="button"
>
Reopen Question Submissions
</Button>
{/* A scheduled close date that has already lapsed is cleared server-side on reopen, otherwise
ama-bot's sweep would close the session again within a minute -- called out here so that
silently-cleared date isn't a surprise. */}
{ama.scheduledCloseAt && new Date(ama.scheduledCloseAt).getTime() <= Date.now() && (
<p className="text-sm text-secondary dark:text-secondary-dark">
Reopening also clears the scheduled close date, since it has already passed.
</p>
)}
</div>
) : showCloseConfirm ? (
<div className="space-y-4">
<p className="text-base text-primary dark:text-primary-dark">
Close question submissions for this AMA? Questions already submitted stay fully manageable - you just
stop receiving new ones. You can reopen submissions here at any time.
</p>
<div className="flex gap-3">
<Button
className="px-3 py-2.5 bg-misc-danger text-white rounded-md hover:bg-misc-danger/90 transition-colors disabled:opacity-50"
onPress={handleEndAMA}
isDisabled={updateAMA.isPending}
onPress={handleCloseSubmissions}
type="button"
>
Yes, End AMA
Yes, Close Submissions
</Button>
<Button
className="px-3 py-2.5 bg-on-tertiary dark:bg-on-tertiary-dark text-primary dark:text-primary-dark rounded-md hover:bg-on-secondary dark:hover:bg-on-secondary-dark transition-colors"
onPress={() => setShowEndConfirm(false)}
onPress={() => setShowCloseConfirm(false)}
type="button"
>
Cancel
Expand All @@ -1071,14 +1122,15 @@ export function AMADetails() {
) : (
<div className="space-y-4">
<p className="text-base text-primary dark:text-primary-dark">
Ending an AMA will prevent new questions from being submitted. This action cannot be undone.
This AMA is accepting new questions. Closing submissions stops new ones from coming in; reviewing and
answering what is already here carries on as normal.
</p>
<Button
className="px-3 py-2.5 bg-misc-danger text-white rounded-md hover:bg-misc-danger/90 transition-colors"
onPress={handleEndAMA}
onPress={handleCloseSubmissions}
type="button"
>
End AMA Session
Close Question Submissions
</Button>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ export function AMASessionCard({ data }: AMASessionCardProps) {
<span
className={cn(
'rounded px-2 py-1 text-xs font-medium',
data.ended ? 'bg-misc-danger/10 text-misc-danger' : 'bg-misc-accent/10 text-misc-accent',
data.ended
? 'bg-on-tertiary text-secondary dark:bg-on-tertiary-dark dark:text-secondary-dark'
: 'bg-misc-accent/10 text-misc-accent',
)}
>
{data.ended ? 'Ended' : 'Active'}
{data.ended ? 'Closed' : 'Open'}
</span>
</div>
</Link>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@ export function AMASessionsList() {
const sort = useSortOption();

const searchQuery = searchParams.get('search') ?? '';
const includeEnded = searchParams.get('include_ended') === 'true';
// Everything is listed by default; the toggle narrows down to sessions still accepting questions (#299).
const openOnly = searchParams.get('open_only') === 'true';

const { data: sessions, isLoading, error } = useAMAs(params.id, includeEnded);
const { data: sessions, isLoading, error } = useAMAs(params.id, !openOnly);
// Creating a session is manager-only -- a guest only ever sees the specific AMA(s) they're scoped to
// (already filtered server-side, see `getAMAs.ts`), so there's nothing for a "create" card to do here.
const { canManage } = useGuildAccess(params.id);
Expand Down Expand Up @@ -96,7 +97,13 @@ export function AMASessionsList() {
<ul className="grid grid-cols-1 gap-4 md:grid-cols-3 lg:grid-cols-4">
{createCardItem}
<li className="md:col-span-2 lg:col-span-3">
{includeEnded ? (
{openOnly ? (
<EmptyState
icon={<FaComments className="h-8 w-8 text-secondary dark:text-secondary-dark" />}
subtitle='There may be closed sessions hidden - turn off "Hide Closed" above to see them.'
title="No AMA sessions accepting questions"
/>
) : (
<EmptyState
icon={<FaComments className="h-8 w-8 text-secondary dark:text-secondary-dark" />}
subtitle={
Expand All @@ -106,12 +113,6 @@ export function AMASessionsList() {
}
title={canManage ? 'No AMA sessions yet' : 'No AMA sessions shared with you'}
/>
) : (
<EmptyState
icon={<FaComments className="h-8 w-8 text-secondary dark:text-secondary-dark" />}
subtitle='There may be ended sessions hidden - toggle "Include Ended" above to check.'
title="No active AMA sessions"
/>
)}
</li>
</ul>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use client';

import { Button } from '@/components/common/Button';
import { useURLParam } from '@/hooks/useURLParam';

/**
* Closing an AMA only stops new question submissions (#299) -- a closed session is still very much being
* worked on (triage, answers, exports), so the list shows everything by default and this narrows it down to
* the ones still taking questions, rather than the other way around.
*/
export function OpenOnlyToggle() {
const [openOnlyParam, setOpenOnly] = useURLParam('open_only');
const openOnly = openOnlyParam === 'true';

return (
<Button
aria-pressed={openOnly}
className={`h-10 px-4 py-2 border rounded-md transition-colors text-sm ${
openOnly
? 'bg-misc-accent border-misc-accent text-primary-dark'
: 'border-on-secondary dark:border-on-secondary-dark text-primary dark:text-primary-dark opacity-70'
}`}
onPress={() => setOpenOnly(openOnly ? null : 'true')}
type="button"
>
Hide Closed
</Button>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ export function CreateAMAForm() {
error={errors.scheduledCloseAt}
helper={
<p className="mt-1 text-sm text-secondary dark:text-secondary-dark">
Optional - automatically ends the AMA at this date/time. Can be changed later.
Optional - automatically stops accepting new questions at this date/time. Can be changed later.
</p>
}
id="scheduledCloseAt"
Expand Down
4 changes: 2 additions & 2 deletions apps/website/src/app/dashboard/[id]/ama/amas/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AMASessionsHeading } from './_components/AMASessionsHeading';
import { AMASessionsList } from './_components/AMASessionsList';
import { IncludeEndedToggle } from './_components/IncludeEndedToggle';
import { OpenOnlyToggle } from './_components/OpenOnlyToggle';
import { SortMenu } from './_components/SortMenu';
import { SearchBar } from '@/components/common/SearchBar';
import { DashboardCrumbs } from '@/components/dashboard/DashboardCrumbs';
Expand All @@ -13,7 +13,7 @@ export default function AMAMangementPage() {
<AMASessionsHeading />
<SearchBar placeholder="Search AMA sessions...">
<SortMenu />
<IncludeEndedToggle />
<OpenOnlyToggle />
</SearchBar>
</div>

Expand Down
2 changes: 1 addition & 1 deletion apps/website/src/hooks/useURLParam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useCallback } from 'react';

/**
* Reads/writes a single query-string param, replacing the current history entry. Centralizes the
* read-searchParams / mutate / push-new-url boilerplate that filter/sort controls (`IncludeEndedToggle`,
* read-searchParams / mutate / push-new-url boilerplate that filter/sort controls (`OpenOnlyToggle`,
* `SortMenu`, ...) all need — keeps list-page state consistently URL-driven (shareable, survives back/forward)
* without each control reimplementing the same `URLSearchParams` dance.
*/
Expand Down
Loading
Loading