mcpField
W
waitlist

waitlist

github.com/tushaarmehtaa营销与增长1239 次安装2 周前更新
代码仓库

关于 waitlist

waitlist 是一项为现有项目搭建候补名单功能的技能,先检查项目使用的技术栈,再生成相应实现。内容涵盖报名表、数据存储、确认邮件、推荐追踪和管理视图。

主要功能

  1. 01检测现有项目技术栈
  2. 02创建报名接口与表单
  3. 03适配数据库存储
  4. 04发送确认邮件
  5. 05支持推荐追踪与管理视图

适用场景

  1. 01为产品上线收集候补用户
  2. 02将候补名单接入现有数据库
  3. 03向报名者发送确认邮件
  4. 04查看报名记录与推荐数据

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/tushaarmehtaa/tushar-skills --skill waitlist

安装后在 Claude Code 中输入/waitlist确认已加载。

详细介绍

构建一个完整的候补名单功能——收集邮箱、存储数据、发送确认邮件并提供管理视图。先读取项目,再接入现有技术栈。

第 1 阶段:检测项目

cat package.json | grep -E "next|supabase|prisma|drizzle|resend"

检查以下内容:

  • 数据库:Supabase / Prisma / Drizzle / 原始 Postgres?
  • 邮件:是否已安装 Resend?
  • 身份验证:是否有身份验证提供商?(管理视图需要)
  • 现有候补名单表:在架构文件中搜索 waitlist

第 2 阶段:询问用户

I'll scaffold a waitlist for [product name].

Quick decisions:

1. What info do you want to collect?
   a) Email only (default — highest conversion)
   b) Email + name
   c) Email + name + "what are you building?" (optional field)

2. Referral tracking?
   a) No — simple list
   b) Yes — each signup gets a unique referral link, referred count tracked

3. Confirmation email?
   a) Yes — send a "you're on the list" email via Resend (default)
   b) No

4. Do you have Resend and a verified sending domain? (yes / need to set up)

第 3 阶段:数据库架构

根据检测到的数据库进行调整。创建一个 waitlist 表:

Supabase / 原始 SQL:

create table public.waitlist (
  id uuid primary key default gen_random_uuid(),
  email text not null unique,
  name text,
  note text,                              -- optional "what are you building?" field
  referral_code text unique,              -- their shareable code (if referral enabled)
  referred_by text references public.waitlist(referral_code),  -- who sent them
  referral_count integer default 0,       -- how many they've referred
  position integer,                       -- queue position (set by trigger)
  confirmed_at timestamptz,               -- null = unconfirmed
  created_at timestamptz default now()
);

-- Auto-increment position on insert
create or replace function set_waitlist_position()
returns trigger as $$
begin
  new.position = (select coalesce(max(position), 0) + 1 from public.waitlist);
  return new;
end;
$$ language plpgsql;

create trigger waitlist_position_trigger
  before insert on public.waitlist
  for each row execute function set_waitlist_position();

如果不需要推荐跟踪,请移除 referral_code、referred_by、referral_count。

Prisma:

model Waitlist {
  id           String    @id @default(cuid())
  email        String    @unique
  name         String?
  note         String?
  referralCode String?   @unique
  referredBy   String?
  referralCount Int      @default(0)
  position     Int
  confirmedAt  DateTime?
  createdAt    DateTime  @default(now())
}

第 4 阶段:注册 API 路由

// app/api/waitlist/route.ts
import { NextResponse } from 'next/server';

export async function POST(req: Request) {
  const { email, name, note, referredBy } = await req.json();

  // Validate email
  if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
    return NextResponse.json({ error: 'Valid email required' }, { status: 400 });
  }

  // Normalize
  const normalizedEmail = email.toLowerCase().trim();

  // Check for duplicate
  const existing = await db.query.waitlist.findFirst({
    where: eq(waitlist.email, normalizedEmail),
  });

  if (existing) {
    // Return success — don't tell spammers which emails are registered
    return NextResponse.json({
      success: true,
      position: existing.position,
      alreadyRegistered: true,
    });
  }

  // Generate referral code if enabled
  const referralCode = generateReferralCode(normalizedEmail);  // e.g., first 8 chars of email hash

  // Insert
  const entry = await db.insert(waitlistTable).values({
    email: normalizedEmail,
    name: name?.trim() || null,
    note: note?.trim() || null,
    referralCode,
    referredBy: referredBy || null,
  }).returning();

  // Increment referrer's count
  if (referredBy) {
    await db.update(waitlistTable)
      .set({ referralCount: sql`referral_count + 1` })
      .where(eq(waitlistTable.referralCode, referredBy));
  }

  // Send confirmation email (if Resend is set up)
  if (process.env.RESEND_API_KEY) {
    await sendWaitlistConfirmation({
      email: normalizedEmail,
      name: name || 'there',
      position: entry[0].position,
      referralCode,
    });
  }

  return NextResponse.json({
    success: true,
    position: entry[0].position,
    referralCode,
  });
}

function generateReferralCode(email: string): string {
  const { createHash } = require('crypto');
  return createHash('sha256').update(email).digest('hex').slice(0, 8);
}

第 5 阶段:确认邮件

// lib/emails/waitlist-confirmation.ts
import { Resend } from 'resend';

const resend = new Resend(process.env.RESEND_API_KEY);

export async function sendWaitlistConfirmation({
  email, name, position, referralCode,
}: { email: string; name: string; position: number; referralCode?: string }) {
  const referralUrl = referralCode
    ? `${process.env.NEXT_PUBLIC_APP_URL}/?ref=${referralCode}`
    : null;

  await resend.emails.send({
    from: `[Product Name] <[email protected]>`,
    to: email,
    subject: `you're #${position} on the list`,
    html: `
      <div style="font-family: sans-serif; max-width: 520px; margin: 0 auto; padding: 40px 20px; color: #111;">
        <p>Hey ${name},</p>
        <p>You're <strong>#${position}</strong> on the waitlist. We'll reach out when you're up.</p>
        ${referralUrl ? `
        <p style="margin-top: 24px;">
          Move up the list — share your link:<br>
          <a href="${referralUrl}" style="color: #000; font-weight: bold;">${referralUrl}</a>
        </p>
        ` : ''}
        <p style="color: #666; font-size: 13px; margin-top: 40px;">— [Your Name]</p>
      </div>
    `,
  });
}

第 6 阶段:注册表单组件

// components/waitlist-form.tsx
'use client';
import { useState } from 'react';

export function WaitlistForm({ referralCode }: { referralCode?: string }) {
  const [email, setEmail] = useState('');
  const [state, setState] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
  const [position, setPosition] = useState<number | null>(null);
  const [userReferralCode, setUserReferralCode] = useState<string | null>(null);

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    setState('loading');

    const res = await fetch('/api/waitlist', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, referredBy: referralCode }),
    });

    const data = await res.json();

    if (data.success) {
      setState('success');
      setPosition(data.position);
      setUserReferralCode(data.referralCode);
    } else {
      setState('error');
    }
  }

  if (state === 'success') {
    return (
      <div>
        <p>you're #{position} on the list.</p>
        {userReferralCode && (
          <p>
            share your link to move up:{' '}
            <code>{`${window.location.origin}/?ref=${userReferralCode}`}</code>
          </p>
        )}
      </div>
    );
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="[email protected]"
        required
        disabled={state === 'loading'}
      />
      <button type="submit" disabled={state === 'loading'}>
        {state === 'loading' ? 'joining...' : 'join waitlist'}
      </button>
      {state === 'error' && <p>something went wrong. try again.</p>}
    </form>
  );
}

在页面中从 URL 参数读取推荐码:

// app/page.tsx
export default function Page({ searchParams }: { searchParams: { ref?: string } }) {
  return <WaitlistForm referralCode={searchParams.ref} />;
}

第 7 阶段:管理视图

使用需要密钥请求头的简单路由即可——候补名单管理无需完整身份验证:

// app/api/admin/waitlist/route.ts
export async function GET(req: Request) {
  if (req.headers.get('x-admin-secret') !== process.env.ADMIN_SECRET) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 });
  }

  const signups = await db.select().from(waitlistTable).orderBy(asc(waitlistTable.position));

  return Response.json({
    total: signups.length,
    signups,
  });
}

使用以下命令获取数据:curl -H "x-admin-secret: $ADMIN_SECRET" https://yoursite.com/api/admin/waitlist

验证

[ ] Duplicate email returns success with alreadyRegistered: true (no error, no leak)
[ ] Email normalized to lowercase before storage
[ ] Position increments correctly — first signup is #1
[ ] Confirmation email arrives in inbox, not spam
[ ] Referral code in URL (?ref=) is captured and stored on signup
[ ] Referrer's count increments when referred signup completes
[ ] Admin route requires x-admin-secret header
[ ] ADMIN_SECRET and RESEND_API_KEY in .env.example
[ ] Form disables input during submission
[ ] Success state shows position and referral link

评论

支持 Markdown