feat: add temporary sidebar share opinion prompt

Adds a dialog with call booking and survey actions for user feedback
Shows a one-time sidebar toast prompting users to share what’s useful or missing
Adds localized copy for the new feedback entry points
This commit is contained in:
Bohdan Triapitsyn
2026-07-06 00:37:24 +03:00
parent a6edc7baee
commit 086982c1b3
14 changed files with 188 additions and 1 deletions
@@ -35,6 +35,7 @@ import { useStickyProjectHeaders } from './sidebar/hooks/useStickyProjectHeaders
import { getGitHubPrStatusKey, usePrVisualSummaryByKeys, useGitHubPrStatusStore } from '@/stores/useGitHubPrStatusStore';
import { ProjectEditDialog } from '@/components/layout/ProjectEditDialog';
import { UpdateDialog } from '@/components/ui/UpdateDialog';
import { ShareOpinionDialog } from '@/components/feedback/ShareOpinionDialog';
import { SessionGroupSection } from './sidebar/SessionGroupSection';
import { SidebarHeader } from './sidebar/SidebarHeader';
import { SidebarActivitySections } from './sidebar/SidebarActivitySections';
@@ -80,6 +81,7 @@ import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
import { useGitHubAuthStore } from '@/stores/useGitHubAuthStore';
import { subscribeOpenchamberEvents } from '@/lib/openchamberEvents';
const SHARE_OPINION_TOAST_STORAGE_KEY = 'openchamber.shareOpinionToast.dismissed.v2';
const PROJECT_COLLAPSE_STORAGE_KEY = 'oc.sessions.projectCollapse';
const GROUP_ORDER_STORAGE_KEY = 'oc.sessions.groupOrder';
const GROUP_COLLAPSE_STORAGE_KEY = 'oc.sessions.groupCollapse';
@@ -196,6 +198,7 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
const newWorktreeDialogOpen = useUIStore((state) => state.isNewWorktreeDialogOpen);
const setNewWorktreeDialogOpen = useUIStore((state) => state.setNewWorktreeDialogOpen);
const [updateDialogOpen, setUpdateDialogOpen] = React.useState(false);
const [shareOpinionDialogOpen, setShareOpinionDialogOpen] = React.useState(false);
const [openSidebarMenuKey, setOpenSidebarMenuKey] = React.useState<string | null>(null);
const [renamingFolderId, setRenamingFolderId] = React.useState<string | null>(null);
const [renameFolderDraft, setRenameFolderDraft] = React.useState('');
@@ -635,6 +638,36 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
});
}, [t, updateStore]);
const handleOpenShareOpinionDialog = React.useCallback(() => {
setShareOpinionDialogOpen(true);
}, []);
React.useEffect(() => {
if (typeof window === 'undefined') {
return;
}
try {
if (window.localStorage.getItem(SHARE_OPINION_TOAST_STORAGE_KEY) === 'true') {
return;
}
window.localStorage.setItem(SHARE_OPINION_TOAST_STORAGE_KEY, 'true');
} catch {
// If storage is unavailable, still show once for this sidebar mount.
}
const timeoutId = window.setTimeout(() => {
toast.info(t('shareOpinion.toast.title'), {
description: t('shareOpinion.toast.description'),
action: {
label: t('shareOpinion.actions.shareOpinion'),
onClick: () => setShareOpinionDialogOpen(true),
},
duration: 12_000,
});
}, 1_000);
return () => window.clearTimeout(timeoutId);
}, [t]);
const handleOpenSettings = React.useCallback(() => {
if (mobileVariant) {
setSessionSwitcherOpen(false);
@@ -1619,10 +1652,16 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
onOpenShortcuts={toggleHelpDialog}
onOpenAbout={() => setAboutDialogOpen(true)}
onOpenUpdate={handleOpenUpdateDialog}
onOpenShareOpinion={handleOpenShareOpinionDialog}
showRuntimeButtons={!isVSCode}
showUpdateButton={showSidebarUpdateButton}
/>
<ShareOpinionDialog
open={shareOpinionDialogOpen}
onOpenChange={setShareOpinionDialogOpen}
/>
<UpdateDialog
open={updateDialogOpen}
onOpenChange={setUpdateDialogOpen}
@@ -9,6 +9,7 @@ type Props = {
onOpenShortcuts: () => void;
onOpenAbout: () => void;
onOpenUpdate: () => void;
onOpenShareOpinion: () => void;
showRuntimeButtons?: boolean;
showUpdateButton?: boolean;
};
@@ -20,6 +21,7 @@ export function SidebarFooter({
onOpenShortcuts,
onOpenAbout,
onOpenUpdate,
onOpenShareOpinion,
showRuntimeButtons = true,
showUpdateButton = true,
}: Props): React.ReactNode {
@@ -65,7 +67,17 @@ export function SidebarFooter({
>
{t('sessions.sidebar.footer.actions.update')}
</Button>
) : null}
) : (
<Button
type="button"
variant="default"
size="xs"
className="ml-auto border-[var(--primary-base)]/15 bg-[var(--primary-base)]/5 text-[var(--primary-base)]/60 hover:bg-[var(--primary-base)]/10 hover:text-[var(--primary-base)]/75"
onClick={onOpenShareOpinion}
>
{t('sessions.sidebar.footer.actions.shareOpinion')}
</Button>
)}
</div>
);
}