feat: add NightlySurveyPopover component for feature surveys#9083
feat: add NightlySurveyPopover component for feature surveys#9083christian-byrne merged 1 commit intomainfrom
Conversation
🎨 Storybook: ✅ Built — View Storybook |
🎭 Playwright: ✅ 531 passed, 0 failed · 2 flaky📊 Browser Reports
|
📝 WalkthroughWalkthroughAdds nightly survey localization strings, a Vue component that displays a delayed Typeform-based survey popover with eligibility and opt-out handling, and tests validating visibility, interactions, and config-driven behavior. Changes
Sequence DiagramsequenceDiagram
participant User
participant Popover as NightlySurveyPopover<br/>(Component)
participant Eligibility as useSurveyEligibility<br/>(Hook)
participant i18n as i18n<br/>(Localization)
participant Typeform as Typeform<br/>(External Script)
User->>Popover: Mount component
Popover->>Eligibility: Check eligibility & get delay
Eligibility-->>Popover: eligible, delay, optOut status
rect rgba(100, 150, 255, 0.5)
Note over Popover: Wait configured delay before showing
end
Popover->>i18n: Request localized strings
i18n-->>Popover: Return title/description/labels
Popover->>User: Show popover (emit 'shown')
User->>Popover: Click Accept / Not Now / Don't Ask Again
rect rgba(200, 150, 100, 0.5)
Popover->>Typeform: Load embed script (on Accept)
Typeform-->>Popover: Script loaded or error
end
Popover-->>User: Emit 'dismissed' or 'optedOut' and hide
Popover->>Popover: Cleanup timers on unmount
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
📦 Bundle: 4.37 MB gzip 🔴 +333 BDetailsSummary
Category Glance App Entry Points — 21.6 kB (baseline 21.6 kB) • ⚪ 0 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 951 kB (baseline 951 kB) • ⚪ 0 BGraph editor runtime, canvas, workflow orchestration
Status: 1 added / 1 removed Views & Navigation — 68.8 kB (baseline 68.8 kB) • ⚪ 0 BTop-level views, pages, and routed surfaces
Status: 9 added / 9 removed Panels & Settings — 436 kB (baseline 436 kB) • ⚪ 0 BConfiguration panels, inspectors, and settings screens
Status: 10 added / 10 removed User & Accounts — 16 kB (baseline 16 kB) • ⚪ 0 BAuthentication, profile, and account management bundles
Status: 5 added / 5 removed Editors & Dialogs — 738 B (baseline 738 B) • ⚪ 0 BModals, dialogs, drawers, and in-app editors
Status: 1 added / 1 removed UI Components — 47 kB (baseline 47 kB) • ⚪ 0 BReusable component library chunks
Status: 5 added / 5 removed Data & Services — 2.51 MB (baseline 2.51 MB) • ⚪ 0 BStores, services, APIs, and repositories
Status: 13 added / 13 removed Utilities & Hooks — 58.3 kB (baseline 58.3 kB) • ⚪ 0 BHelpers, composables, and utility bundles
Status: 13 added / 13 removed Vendor & Third-Party — 8.83 MB (baseline 8.83 MB) • ⚪ 0 BExternal libraries and shared vendor chunks
Other — 7.6 MB (baseline 7.6 MB) • 🔴 +308 BBundles that do not match a named category
Status: 51 added / 51 removed |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/platform/surveys/NightlySurveyPopover.vue (2)
35-38: Consider hiding Typeform widget when typeformId validation fails.When the typeformId fails alphanumeric validation, returning an empty string still sets
data-tf-widget=""in the DOM. This could cause the Typeform SDK to behave unexpectedly or display an error. Consider also settingtypeformErrorto true or using a separate flag to hide the Typeform widget entirely when the ID is invalid.♻️ Proposed approach
const typeformId = computed(() => { const id = config.typeformId - return /^[A-Za-z0-9]+$/.test(id) ? id : '' + return /^[A-Za-z0-9]+$/.test(id) ? id : null +}) + +const hasValidTypeformId = computed(() => typeformId.value !== null)Then in the template, use
v-show="isVisible && !typeformError && hasValidTypeformId"on the Typeform div.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/platform/surveys/NightlySurveyPopover.vue` around lines 35 - 38, The computed typeformId currently returns an empty string on invalid input but still renders data-tf-widget="", so update the logic to expose a validity flag and set an error state: keep the computed typeformId for the sanitized value, add a new boolean computed or ref named hasValidTypeformId that returns /^[A-Za-z0-9]+$/.test(config.typeformId), and set or toggle a typeformError ref to true when invalid; then update the template to hide the widget by using v-show="isVisible && !typeformError && hasValidTypeformId" on the Typeform div (referencing typeformId, hasValidTypeformId, and typeformError).
132-138: Simplify v-show condition –isVisiblecheck is redundant.The parent container already uses
v-if="isVisible"(line 107), so theisVisiblein thev-showcondition here is always true when this element is rendered.♻️ Proposed simplification
<div - v-show="isVisible && !typeformError" + v-show="!typeformError" ref="typeformRef" data-tf-auto-resize :data-tf-widget="typeformId" class="min-h-[300px]" />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/platform/surveys/NightlySurveyPopover.vue` around lines 132 - 138, The v-show on the Typeform container redundantly checks isVisible because the parent already uses v-if="isVisible"; update the element that currently has v-show="isVisible && !typeformError" (the div with ref="typeformRef" and :data-tf-widget="typeformId") to only check the error state (e.g., v-show="!typeformError") so it relies on the parent for visibility and only controls rendering for typeformError.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/platform/surveys/NightlySurveyPopover.test.ts`:
- Around line 150-156: The test uses optional chaining when clicking the opt-out
button which can hide a missing-button failure; before calling
optOutButton.trigger('click') assert the button exists (e.g.,
expect(optOutButton).toBeTruthy() or expect(optOutButton).not.toBeUndefined())
so the test fails if selector fails—locate the variables around
wrapper.findAll('button') and optOutButton and add an explicit existence
assertion immediately before invoking optOutButton.trigger('click'), then
proceed to trigger and assert wrapper.emitted('optedOut').
---
Nitpick comments:
In `@src/platform/surveys/NightlySurveyPopover.vue`:
- Around line 35-38: The computed typeformId currently returns an empty string
on invalid input but still renders data-tf-widget="", so update the logic to
expose a validity flag and set an error state: keep the computed typeformId for
the sanitized value, add a new boolean computed or ref named hasValidTypeformId
that returns /^[A-Za-z0-9]+$/.test(config.typeformId), and set or toggle a
typeformError ref to true when invalid; then update the template to hide the
widget by using v-show="isVisible && !typeformError && hasValidTypeformId" on
the Typeform div (referencing typeformId, hasValidTypeformId, and
typeformError).
- Around line 132-138: The v-show on the Typeform container redundantly checks
isVisible because the parent already uses v-if="isVisible"; update the element
that currently has v-show="isVisible && !typeformError" (the div with
ref="typeformRef" and :data-tf-widget="typeformId") to only check the error
state (e.g., v-show="!typeformError") so it relies on the parent for visibility
and only controls rendering for typeformError.
Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-019c7db5-bd68-713b-bf60-4b1022483308
1b8b0fa to
e25b7b8
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/platform/surveys/NightlySurveyPopover.vue (1)
134-140: RedundantisVisiblecheck in v-show condition.This
<div>is already inside a parentv-if="isVisible", so checkingisVisibleagain inv-showis unnecessary.♻️ Suggested simplification
<div - v-show="isVisible && !typeformError && isValidTypeformId" + v-show="!typeformError && isValidTypeformId" ref="typeformRef" data-tf-auto-resize :data-tf-widget="typeformId" class="min-h-[300px]" />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/platform/surveys/NightlySurveyPopover.vue` around lines 134 - 140, The v-show on the Typeform container redundantly checks isVisible even though the element is already wrapped by a parent v-if="isVisible"; update the v-show expression on the <div ref="typeformRef" :data-tf-widget="typeformId" ...> to remove isVisible so it becomes v-show="!typeformError && isValidTypeformId", keeping the same data-tf-auto-resize and class attributes unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/platform/surveys/NightlySurveyPopover.vue`:
- Around line 134-140: The v-show on the Typeform container redundantly checks
isVisible even though the element is already wrapped by a parent
v-if="isVisible"; update the v-show expression on the <div ref="typeformRef"
:data-tf-widget="typeformId" ...> to remove isVisible so it becomes
v-show="!typeformError && isValidTypeformId", keeping the same
data-tf-auto-resize and class attributes unchanged.
Summary
Adds NightlySurveyPopover component that displays a Typeform survey to eligible nightly users after a configurable delay.
Changes
useSurveyEligibilityto show/hide a survey popover with accept, dismiss, and opt-out actions. Loads Typeform embed script dynamically with HTTPS and deduplication.Review Focus
Part of Nightly Survey System
This is part 4 of a stacked PR chain:
┆Issue is synchronized with this Notion page by Unito