Adjust waitlist

This commit is contained in:
Eric
2026-11-10 17:40:17 +01:00
parent 19e55402c3
commit 2e18b6568d
6 changed files with 52 additions and 194 deletions
@@ -1,167 +1,52 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
import { onMounted } from 'vue';
import Card from '../base/Card.vue';
import { iconFrequencies, type WaitlistProduct } from './products';
import type { WaitlistProduct } from './products';
import './waitlist.css';
defineProps<{
product: WaitlistProduct;
}>();
const expectations = ref('');
const topFeatures = ref<string[]>([]);
const iconFrequency = ref('');
const isSubmitting = ref(false);
const isSubmitted = ref(false);
const errorMessage = ref('');
const hasAnswers = computed(
() => expectations.value.trim() !== '' || topFeatures.value.length > 0 || iconFrequency.value,
);
async function onSubmit() {
isSubmitting.value = true;
errorMessage.value = '';
try {
const response = await fetch('/api/waitlist/survey', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
expectations: expectations.value.trim(),
top_features: topFeatures.value,
icon_frequency: iconFrequency.value,
}),
});
if (response.ok) {
isSubmitted.value = true;
return;
}
errorMessage.value =
response.status === 401
? "This session has expired, so we couldn't save your answers."
: 'Something went wrong. Please try again.';
} catch {
errorMessage.value = 'Something went wrong. Please try again.';
} finally {
isSubmitting.value = false;
/** Without Tally's script the embed still works, it just doesn't resize to its content. */
function loadEmbeds() {
if (window.Tally) {
window.Tally.loadEmbeds();
return;
}
document
.querySelectorAll<HTMLIFrameElement>('iframe[data-tally-src]:not([src])')
.forEach((iframe) => {
iframe.src = iframe.dataset.tallySrc ?? '';
});
}
onMounted(() => {
if (window.Tally) {
loadEmbeds();
return;
}
const script = document.createElement('script');
script.src = 'https://tally.so/widgets/embed.js';
script.onload = loadEmbeds;
script.onerror = loadEmbeds;
document.body.appendChild(script);
});
</script>
<template>
<Card
v-if="isSubmitted"
class="waitlist-card"
role="status"
>
<div class="waitlist-card-header">
<h2 class="waitlist-card-title">Thanks for sharing</h2>
<p class="waitlist-card-description">
Your answers help us decide what to build first. We'll be in touch.
</p>
</div>
</Card>
<Card
v-else
class="waitlist-card"
>
<div class="waitlist-card-header">
<h2 class="waitlist-card-title">Help shape {{ product.name }}</h2>
<p class="waitlist-card-description">Three optional questions. Answer as many as you like.</p>
</div>
<form
class="waitlist-form"
@submit.prevent="onSubmit"
>
<div class="waitlist-field">
<label
class="waitlist-label"
for="survey-expectations"
>{{ product.survey.expectationsLabel }}</label
>
<textarea
id="survey-expectations"
v-model="expectations"
class="waitlist-input"
name="expectations"
maxlength="1000"
rows="3"
/>
</div>
<fieldset class="waitlist-field">
<legend class="waitlist-label">Which features matter most?</legend>
<label
v-for="feature in product.survey.features"
:key="feature"
class="choice"
>
<input
v-model="topFeatures"
type="checkbox"
name="top_features"
:value="feature"
/>
{{ feature }}
</label>
</fieldset>
<fieldset class="waitlist-field">
<legend class="waitlist-label">How often do you add icons to a project?</legend>
<label
v-for="frequency in iconFrequencies"
:key="frequency"
class="choice"
>
<input
v-model="iconFrequency"
type="radio"
name="icon_frequency"
:value="frequency"
/>
{{ frequency }}
</label>
</fieldset>
<div class="waitlist-actions">
<VPButton
class="waitlist-button"
type="submit"
size="big"
:text="isSubmitting ? 'Sending…' : 'Send answers'"
:disabled="isSubmitting || !hasAnswers"
/>
<div aria-live="polite">
<p
v-if="errorMessage"
class="waitlist-error"
>
{{ errorMessage }}
</p>
</div>
</div>
</form>
<Card class="waitlist-card">
<iframe
:data-tally-src="product.survey.embedSrc"
loading="lazy"
width="100%"
height="857"
frameborder="0"
marginheight="0"
marginwidth="0"
:title="product.survey.title"
/>
</Card>
</template>
<style scoped>
.choice {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
line-height: 24px;
color: var(--vp-c-text-1);
cursor: pointer;
}
.choice input {
accent-color: var(--vp-c-brand-5);
}
</style>
@@ -11,8 +11,8 @@ export interface WaitlistProduct {
useCaseLabel: string;
useCasePlaceholder: string;
survey: {
expectationsLabel: string;
features: string[];
title: string;
embedSrc: string;
};
}
@@ -30,15 +30,9 @@ export const waitlistProducts: Record<string, WaitlistProduct> = {
useCaseLabel: 'Which AI tool would you use it with?',
useCasePlaceholder: 'e.g. Claude Code, Cursor, Copilot',
survey: {
expectationsLabel: 'What would you want the MCP to do for you?',
features: [
'Icon search',
'Inserting the right import',
'Framework-specific code',
'Generating icon sets',
],
title: 'Help shape Lucide MCP',
embedSrc:
'https://tally.so/embed/4aJ4xr?alignLeft=1&transparentBackground=1&dynamicHeight=1&formEventsForwarding=1',
},
},
};
export const iconFrequencies = ['Daily', 'Weekly', 'Monthly', 'Rarely'];
@@ -3,7 +3,6 @@ div.waitlist-card.card {
gap: 20px;
width: 100%;
padding: 32px;
border-radius: 16px;
box-shadow: 0 5px 11px 4px rgba(0, 0, 0, 0.15);
}
@@ -65,33 +64,6 @@ input.waitlist-input.input {
font-size: 16px;
}
/* Input only renders single-line fields, so the survey's textarea mirrors its look. */
textarea.waitlist-input {
width: 100%;
min-height: 88px;
padding: 10px 12px;
border: 1px solid var(--vp-c-border);
border-radius: 8px;
background-color: var(--vp-c-bg);
font-size: 16px;
line-height: 24px;
resize: vertical;
transition:
border-color 0.25s,
box-shadow 0.25s;
}
textarea.waitlist-input:hover,
textarea.waitlist-input:focus {
outline: none;
border-color: var(--vp-c-brand);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--vp-c-brand) 15%, transparent);
}
.waitlist-input::placeholder {
color: var(--vp-c-text-3);
}
.waitlist-actions {
margin-top: 20px;
}
+7
View File
@@ -260,6 +260,13 @@ html::-webkit-scrollbar-thumb:active {
vertical-align: 1px;
}
/* No room for the label next to the logo at these widths. */
@media (min-width: 768px) and (max-width: 879.98px) {
.VPNavBarMenu .nav-badge {
display: none;
}
}
/* The menu only just fits next to the logo at these widths. */
@media (min-width: 768px) and (max-width: 829.98px) {
.VPNav .VPNavBarMenu .VPNavBarMenuLink,
+1
View File
@@ -53,6 +53,7 @@ declare global {
};
Tally: {
// From https://tally.so/help/developer-resources#2f676e40530a460ea6a634b8441f6001
loadEmbeds: () => void;
openPopup: (
formId: string,
options?: {
+2 -3
View File
@@ -33,7 +33,7 @@ OpenPanel doesn't set cookies or store anything in your browser. Along with thes
When you join a waitlist, we store your email address, the product you signed up for, the date, and any answers you give on the form or in the optional follow-up questions. We use this to send you launch updates for that product and to decide what to build. You are only added after you click the link in the confirmation email.
Emails and contacts are handled by [Resend](https://resend.com/legal/privacy-policy), our email provider, which acts as our processor. You can unsubscribe from any email. We keep your details until the waitlist closes or you ask us to delete them, whichever comes first.
Emails and contacts are handled by [Resend](https://resend.com/legal/privacy-policy), our email provider, which acts as our processor. The optional follow-up questions are handled by [Tally](https://tally.so/help/gdpr), a Belgian form provider that stores responses in the EU and also acts as our processor. You can unsubscribe from any email. We keep your details until the waitlist closes or you ask us to delete them, whichever comes first.
### Search
@@ -60,9 +60,8 @@ lucide.dev does not use tracking or advertising cookies of its own.
| Appearance | Local storage | Remembers light or dark mode | Until you clear them |
| Framework choice | Local storage | Remembers which framework you're reading the docs for | Until you clear them |
| Recent and favourite searches | Local storage | Shows them in the search box | Until you clear them |
| Waitlist survey | Cookie | Links your optional answers to the waitlist signup you just confirmed | Short-lived, set only after you confirm a signup |
These items are needed for features you use, or they store choices you made yourself. Apart from the waitlist survey cookie, which is sent to lucide.dev to save your answers, they never leave your browser. You can clear them at any time in your browser settings.
These items are needed for features you use, or they store choices you made yourself. They never leave your browser, and you can clear them at any time in your browser settings.
## Legal basis