diff --git a/packages/ui/src/components/feedback/ShareOpinionDialog.tsx b/packages/ui/src/components/feedback/ShareOpinionDialog.tsx new file mode 100644 index 00000000..10959136 --- /dev/null +++ b/packages/ui/src/components/feedback/ShareOpinionDialog.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { Icon } from '@/components/icon/Icon'; +import { SimpleMarkdownRenderer } from '@/components/chat/MarkdownRenderer'; +import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog'; +import { useI18n } from '@/lib/i18n'; +import { openExternalUrl } from '@/lib/url'; + +type ShareOpinionDialogProps = { + open: boolean; + onOpenChange: (open: boolean) => void; +}; + +const SHARE_OPINION_MARKDOWN = `**Help shape what OpenChamber becomes next!** + +Hey 👋, + +OpenChamber has grown mostly through word of mouth, GitHub issues, Discord feedback, and people telling me what is broken, confusing, or surprisingly useful. + +I'm planning the next chapter now - mobile, better remote access, a tighter VS Code ↔ desktop/web/mobile flow, and more, but before building too much, I want to hear from the people actually using it. + +I'm doing a round of short 1-on-1 calls. No sales pitch, no formal script - just a real conversation about how you use OpenChamber, what you love, what frustrates you, and what would make it much more valuable. + +You can book a call or use the short survey below. + +What you get: + +- A direct chance to influence the roadmap +- Your pain points and feature requests prioritized with more context +- A Power User role in Discord for people helping shape the product +- My genuine thanks for helping make OpenChamber better + +**This project is what it is because of your feedback. Thank you, genuinely.** + +I'll remove this button in two weeks 🙂`; + +export function ShareOpinionDialog({ open, onOpenChange }: ShareOpinionDialogProps): React.ReactNode { + const { t } = useI18n(); + + return ( + + + {t('shareOpinion.dialog.title')} + + + void openExternalUrl('https://calendly.com/artmore/30min')} + > + + {t('shareOpinion.actions.bookCall')} + + void openExternalUrl('https://forms.gle/cdMVKUGs5QuLWkA86')} + > + + {t('shareOpinion.actions.shortSurvey')} + + + + + ); +} diff --git a/packages/ui/src/components/icon/sprite.ts b/packages/ui/src/components/icon/sprite.ts index 5f1df0dc..0cc872c6 100644 --- a/packages/ui/src/components/icon/sprite.ts +++ b/packages/ui/src/components/icon/sprite.ts @@ -227,6 +227,7 @@ export const iconSpriteData = { "unpin": ``, "user-3": ``, "user": ``, + "video-chat": ``, "volume-up": ``, "window": ``, } as const satisfies Record; diff --git a/packages/ui/src/components/session/SessionSidebar.tsx b/packages/ui/src/components/session/SessionSidebar.tsx index af841951..f8e8b562 100644 --- a/packages/ui/src/components/session/SessionSidebar.tsx +++ b/packages/ui/src/components/session/SessionSidebar.tsx @@ -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 = ({ 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(null); const [renamingFolderId, setRenamingFolderId] = React.useState(null); const [renameFolderDraft, setRenameFolderDraft] = React.useState(''); @@ -635,6 +638,36 @@ export const SessionSidebar: React.FC = ({ }); }, [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 = ({ onOpenShortcuts={toggleHelpDialog} onOpenAbout={() => setAboutDialogOpen(true)} onOpenUpdate={handleOpenUpdateDialog} + onOpenShareOpinion={handleOpenShareOpinionDialog} showRuntimeButtons={!isVSCode} showUpdateButton={showSidebarUpdateButton} /> + + 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')} - ) : null} + ) : ( + + {t('sessions.sidebar.footer.actions.shareOpinion')} + + )} ); } diff --git a/packages/ui/src/lib/i18n/messages/en.ts b/packages/ui/src/lib/i18n/messages/en.ts index ecc04004..175439bd 100644 --- a/packages/ui/src/lib/i18n/messages/en.ts +++ b/packages/ui/src/lib/i18n/messages/en.ts @@ -22,6 +22,13 @@ export const dict = { 'pwa.installPrompt.dismiss': 'Dismiss', 'pwa.installPrompt.started': 'Install started', 'pwa.installPrompt.installed': 'OpenChamber installed', + 'shareOpinion.actions.shareOpinion': 'Share opinion', + 'shareOpinion.actions.bookCall': 'Book a call', + 'shareOpinion.actions.shortSurvey': 'Short survey', + 'shareOpinion.dialog.title': 'Share your opinion', + 'shareOpinion.toast.title': 'Help shape OpenChamber', + 'shareOpinion.toast.description': 'Share what is useful, confusing, or missing to help shape what comes next.', + 'sessions.sidebar.footer.actions.shareOpinion': 'Share opinion', 'layout.mainTab.chat': 'Chat', 'layout.mainTab.plan': 'Plan', 'layout.mainTab.diff': 'Diff', diff --git a/packages/ui/src/lib/i18n/messages/es.ts b/packages/ui/src/lib/i18n/messages/es.ts index 5c412588..22f56c48 100644 --- a/packages/ui/src/lib/i18n/messages/es.ts +++ b/packages/ui/src/lib/i18n/messages/es.ts @@ -23,6 +23,13 @@ export const dict: Record = { "pwa.installPrompt.dismiss": "Descartar", "pwa.installPrompt.started": "Instalación iniciada", "pwa.installPrompt.installed": "OpenChamber se instaló", + "shareOpinion.actions.shareOpinion": "Compartir opinión", + "shareOpinion.actions.bookCall": "Reservar llamada", + "shareOpinion.actions.shortSurvey": "Encuesta breve", + "shareOpinion.dialog.title": "Comparte tu opinión", + "shareOpinion.toast.title": "Ayuda a dar forma a OpenChamber", + "shareOpinion.toast.description": "Cuenta qué es útil, confuso o falta para ayudar a definir lo que viene después.", + "sessions.sidebar.footer.actions.shareOpinion": "Compartir opinión", "layout.mainTab.chat": "Chat", "layout.mainTab.plan": "Plan", "layout.mainTab.diff": "Diff", diff --git a/packages/ui/src/lib/i18n/messages/fr.ts b/packages/ui/src/lib/i18n/messages/fr.ts index efd36d71..330028e5 100644 --- a/packages/ui/src/lib/i18n/messages/fr.ts +++ b/packages/ui/src/lib/i18n/messages/fr.ts @@ -22,6 +22,13 @@ export const dict = { 'pwa.installPrompt.dismiss': 'Ignorer', 'pwa.installPrompt.started': 'Installation démarrée', 'pwa.installPrompt.installed': 'OpenChamber installé', + 'shareOpinion.actions.shareOpinion': 'Donner son avis', + 'shareOpinion.actions.bookCall': 'Réserver un appel', + 'shareOpinion.actions.shortSurvey': 'Court sondage', + 'shareOpinion.dialog.title': 'Donnez votre avis', + 'shareOpinion.toast.title': 'Aidez à façonner OpenChamber', + 'shareOpinion.toast.description': 'Dites ce qui est utile, confus ou manquant pour aider à définir la suite.', + 'sessions.sidebar.footer.actions.shareOpinion': 'Donner son avis', 'layout.mainTab.chat': 'Chat', 'layout.mainTab.plan': 'Plan', 'layout.mainTab.diff': 'Diff', diff --git a/packages/ui/src/lib/i18n/messages/ja.ts b/packages/ui/src/lib/i18n/messages/ja.ts index b48f61f3..df98d74a 100644 --- a/packages/ui/src/lib/i18n/messages/ja.ts +++ b/packages/ui/src/lib/i18n/messages/ja.ts @@ -23,6 +23,13 @@ export const dict: Record = { 'pwa.installPrompt.dismiss': '閉じる', 'pwa.installPrompt.started': 'インストールを開始しました', 'pwa.installPrompt.installed': 'OpenChamberをインストールしました', + 'shareOpinion.actions.shareOpinion': '意見を共有', + 'shareOpinion.actions.bookCall': '通話を予約', + 'shareOpinion.actions.shortSurvey': '短いアンケート', + 'shareOpinion.dialog.title': 'ご意見をお聞かせください', + 'shareOpinion.toast.title': 'OpenChamber の次を一緒に作る', + 'shareOpinion.toast.description': '便利な点、分かりにくい点、足りない点を教えて、次の方向づくりに参加してください。', + 'sessions.sidebar.footer.actions.shareOpinion': '意見を共有', 'layout.mainTab.chat': 'チャット', 'layout.mainTab.plan': '計画', 'layout.mainTab.diff': '差分', diff --git a/packages/ui/src/lib/i18n/messages/ko.ts b/packages/ui/src/lib/i18n/messages/ko.ts index 91b83456..8047d3c6 100644 --- a/packages/ui/src/lib/i18n/messages/ko.ts +++ b/packages/ui/src/lib/i18n/messages/ko.ts @@ -23,6 +23,13 @@ export const dict: Record = { 'pwa.installPrompt.dismiss': '닫기', 'pwa.installPrompt.started': '설치가 시작되었습니다', 'pwa.installPrompt.installed': 'OpenChamber가 설치되었습니다', + 'shareOpinion.actions.shareOpinion': '의견 공유', + 'shareOpinion.actions.bookCall': '통화 예약', + 'shareOpinion.actions.shortSurvey': '짧은 설문', + 'shareOpinion.dialog.title': '의견을 공유해 주세요', + 'shareOpinion.toast.title': 'OpenChamber의 방향을 함께 만들어 주세요', + 'shareOpinion.toast.description': '유용한 점, 혼란스러운 점, 부족한 점을 알려 주셔서 다음 방향을 함께 만들어 주세요.', + 'sessions.sidebar.footer.actions.shareOpinion': '의견 공유', 'layout.mainTab.chat': '채팅', 'layout.mainTab.plan': '계획', 'layout.mainTab.diff': '변경사항', diff --git a/packages/ui/src/lib/i18n/messages/pl.ts b/packages/ui/src/lib/i18n/messages/pl.ts index 2ecc05c4..2b4c2feb 100644 --- a/packages/ui/src/lib/i18n/messages/pl.ts +++ b/packages/ui/src/lib/i18n/messages/pl.ts @@ -24,6 +24,13 @@ export const dict: Record = { 'pwa.installPrompt.dismiss': 'Odrzuć', 'pwa.installPrompt.started': 'Instalacja rozpoczęta', 'pwa.installPrompt.installed': 'OpenChamber został zainstalowany', + 'shareOpinion.actions.shareOpinion': 'Podziel się opinią', + 'shareOpinion.actions.bookCall': 'Umów rozmowę', + 'shareOpinion.actions.shortSurvey': 'Krótka ankieta', + 'shareOpinion.dialog.title': 'Podziel się opinią', + 'shareOpinion.toast.title': 'Pomóż kształtować OpenChamber', + 'shareOpinion.toast.description': 'Powiedz, co jest przydatne, mylące albo czego brakuje, aby pomóc kształtować kolejne kroki.', + 'sessions.sidebar.footer.actions.shareOpinion': 'Podziel się opinią', 'layout.mainTab.chat': 'Czat', 'layout.mainTab.plan': 'Plan', 'layout.mainTab.diff': 'Różnice', diff --git a/packages/ui/src/lib/i18n/messages/pt-BR.ts b/packages/ui/src/lib/i18n/messages/pt-BR.ts index 00950b28..2ffd48ac 100644 --- a/packages/ui/src/lib/i18n/messages/pt-BR.ts +++ b/packages/ui/src/lib/i18n/messages/pt-BR.ts @@ -23,6 +23,13 @@ export const dict: Record = { "pwa.installPrompt.dismiss": "Dispensar", "pwa.installPrompt.started": "Instalação iniciada", "pwa.installPrompt.installed": "OpenChamber instalado", + "shareOpinion.actions.shareOpinion": "Compartilhar opinião", + "shareOpinion.actions.bookCall": "Agendar chamada", + "shareOpinion.actions.shortSurvey": "Pesquisa breve", + "shareOpinion.dialog.title": "Compartilhe sua opinião", + "shareOpinion.toast.title": "Ajude a moldar o OpenChamber", + "shareOpinion.toast.description": "Conte o que é útil, confuso ou está faltando para ajudar a definir os próximos passos.", + "sessions.sidebar.footer.actions.shareOpinion": "Compartilhar opinião", "layout.mainTab.chat": "Chat", "layout.mainTab.plan": "Plano", "layout.mainTab.diff": "Diff", diff --git a/packages/ui/src/lib/i18n/messages/uk.ts b/packages/ui/src/lib/i18n/messages/uk.ts index 2553e302..40792dc8 100644 --- a/packages/ui/src/lib/i18n/messages/uk.ts +++ b/packages/ui/src/lib/i18n/messages/uk.ts @@ -23,6 +23,13 @@ export const dict: Record = { "pwa.installPrompt.dismiss": "Сховати", "pwa.installPrompt.started": "Установлення розпочато", "pwa.installPrompt.installed": "OpenChamber встановлено", + "shareOpinion.actions.shareOpinion": "Поділитися думкою", + "shareOpinion.actions.bookCall": "Забронювати дзвінок", + "shareOpinion.actions.shortSurvey": "Коротке опитування", + "shareOpinion.dialog.title": "Поділіться своєю думкою", + "shareOpinion.toast.title": "Допоможіть сформувати OpenChamber", + "shareOpinion.toast.description": "Розкажіть, що корисне, незрозуміле чи відсутнє, щоб допомогти сформувати наступні кроки.", + "sessions.sidebar.footer.actions.shareOpinion": "Поділитися думкою", "layout.mainTab.chat": "Чат", "layout.mainTab.plan": "План", "layout.mainTab.diff": "Diff", diff --git a/packages/ui/src/lib/i18n/messages/zh-CN.ts b/packages/ui/src/lib/i18n/messages/zh-CN.ts index eee31d39..7c68825c 100644 --- a/packages/ui/src/lib/i18n/messages/zh-CN.ts +++ b/packages/ui/src/lib/i18n/messages/zh-CN.ts @@ -23,6 +23,13 @@ export const dict: Record = { 'pwa.installPrompt.dismiss': '忽略', 'pwa.installPrompt.started': '已开始安装', 'pwa.installPrompt.installed': 'OpenChamber 已安装', + 'shareOpinion.actions.shareOpinion': '分享意见', + 'shareOpinion.actions.bookCall': '预约通话', + 'shareOpinion.actions.shortSurvey': '简短问卷', + 'shareOpinion.dialog.title': '分享你的意见', + 'shareOpinion.toast.title': '帮助塑造 OpenChamber', + 'shareOpinion.toast.description': '告诉我们哪些有用、令人困惑或缺失,帮助塑造下一步。', + 'sessions.sidebar.footer.actions.shareOpinion': '分享意见', 'layout.mainTab.chat': '聊天', 'layout.mainTab.plan': '计划', 'layout.mainTab.diff': '差异', diff --git a/packages/ui/src/lib/i18n/messages/zh-TW.ts b/packages/ui/src/lib/i18n/messages/zh-TW.ts index b6cac65f..605b3eae 100644 --- a/packages/ui/src/lib/i18n/messages/zh-TW.ts +++ b/packages/ui/src/lib/i18n/messages/zh-TW.ts @@ -23,6 +23,13 @@ export const dict: Record = { 'pwa.installPrompt.dismiss': '忽略', 'pwa.installPrompt.started': '已開始安裝', 'pwa.installPrompt.installed': 'OpenChamber 已安裝', + 'shareOpinion.actions.shareOpinion': '分享意見', + 'shareOpinion.actions.bookCall': '預約通話', + 'shareOpinion.actions.shortSurvey': '簡短問卷', + 'shareOpinion.dialog.title': '分享你的意見', + 'shareOpinion.toast.title': '協助塑造 OpenChamber', + 'shareOpinion.toast.description': '告訴我們哪些有用、令人困惑或缺少什麼,協助塑造下一步。', + 'sessions.sidebar.footer.actions.shareOpinion': '分享意見', 'layout.mainTab.chat': '聊天', 'layout.mainTab.plan': '計畫', 'layout.mainTab.diff': '差異',