mcpField
E
extension-invite-links

extension-invite-links

github.com/caffeinelabs生産性228.6k 回インストール6 日前に更新
リポジトリ

extension-invite-links について

Caffeine AI 向けの招待リンクと RSVP の拡張です。管理者が一意の招待コードを発行し、ゲストは認証なしで出欠を送信できます。extension-authorization が前提で、ミックスインがバックエンドの API を提供します。

主な機能

  1. 01一意の招待コード生成
  2. 02認証不要のゲスト RSVP 受付
  3. 03管理者による RSVP とコードの確認
  4. 04招待コード生成・取得 API
  5. 05URL パラメーターから招待コードを取得

活用シーン

  1. 01イベント用ゲスト招待リンクの作成
  2. 02ログイン不要の出欠回答収集
  3. 03管理者による RSVP と未使用コードの確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/caffeinelabs/skills --skill extension-invite-links

インストール後、Claude Code で/extension-invite-linksを入力して読み込まれたか確認します。

同梱ファイルmigration/v0.x.y-to-v1.x.y.md

詳細

招待リンクと出欠確認

Caffeine AI の招待リンクと出欠確認の拡張機能です。

概要

このスキルは、招待リンクの生成と出欠確認の収集機能を追加します。管理者は一意の招待コードを生成し、ゲストは認証なしで回答を送信できます。

前提条件:この連携は extension-authorization に依存するため、先に従う必要があります。

バックエンド

モジュール API

事前構築済みのモジュール mo:caffeineai-invite-links/invite-links-module.mo は、招待リンクと出欠確認の状態管理を低レベルで提供します。変更しないでください。

module {
    public type RSVP = {
        name : Text;
        attending : Bool;
        timestamp : Time.Time;
        inviteCode : Text;
    };

    public type InviteCode = {
        code : Text;
        created : Time.Time;
        used : Bool;
    };

    public type InviteLinksSystemState = {
        var rsvps : Map.Map<Text, RSVP>;
        var inviteCodes : Map.Map<Text, InviteCode>;
    };

    public func initState() : InviteLinksSystemState;
    public func generateUUID(blob: Blob) : Text;
    public func generateInviteCode(state: InviteLinksSystemState, code: Text);
    public func getInviteCodes(state: InviteLinksSystemState) : [InviteCode];
    public func submitRSVP(state: InviteLinksSystemState, name: Text, attending: Bool, inviteCode: Text);
    public func getAllRSVPs(state: InviteLinksSystemState) : [RSVP];
}

main.mo でのセットアップ

include MixinInviteLinks(accessControlState, inviteState) は、カスタムミックスインファイルではなく、main.mo に記述する必要があります。このミックスインは、以下のパブリックエンドポイントを自動的に提供します。

  • generateInviteCode()
  • submitRSVP(name, attending, inviteCode)
  • getAllRSVPs()
  • getInviteCodes()

これらの関数を再宣言しないでください。これらは MixinInviteLinks によってのみ提供されます。

import AccessControl "mo:caffeineai-authorization/access-control";
import MixinAuthorization "mo:caffeineai-authorization/MixinAuthorization";
import MixinInviteLinks "mo:caffeineai-invite-links/MixinInviteLinks";
import InviteLinksModule "mo:caffeineai-invite-links/invite-links-module";

actor {
  let accessControlState : AccessControl.AccessControlState;
  include MixinAuthorization(accessControlState, null);
  let inviteState : InviteLinksModule.InviteLinksSystemState;
  include MixinInviteLinks(accessControlState, inviteState);

  // Write additional application-specific code here.
};

マイグレーションチェーンのヘッド:

import AccessControl "mo:caffeineai-authorization/access-control";
import InviteLinksModule "mo:caffeineai-invite-links/invite-links-module";

module {
  type NewActor = {
    accessControlState : AccessControl.AccessControlState;
    inviteState : InviteLinksModule.InviteLinksSystemState;
  };

  public func migration(_old : {}) : NewActor {
    {
      accessControlState = AccessControl.initState();
      inviteState = InviteLinksModule.initState();
    };
  };
};

フロントエンド

招待リンクと出欠確認システムの機能:

以下は、フロントエンドで、管理者専用の招待リンク/出欠確認機能を備えた Internet Identity 認証を実装する例です。

import AdminDashboard from './components/AdminDashboard';
import GuestRSVP from './components/GuestRSVP';
import LoginButton from './components/LoginButton';
import { useIsCurrentUserAdmin } from './hooks/useQueries';

export default function App() {
  const { data: isAdmin } = useIsCurrentUserAdmin();

  return (
    <div className="min-h-screen bg-gradient-to-br from-purple-100 to-pink-100">
      <header className="p-4 bg-white/80 backdrop-blur-sm shadow-sm">
        <div className="max-w-7xl mx-auto flex justify-between items-center">
          <h1 className="text-3xl font-bold text-purple-800">RSVP</h1>
          <LoginButton />
        </div>
      </header>
      <main className="max-w-7xl mx-auto p-4 mt-8">
        {isAdmin ? <AdminDashboard /> : <GuestRSVP />}
      </main>
    </div>
  );
}
import { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { useSubmitRSVP } from '../hooks/useQueries';

export default function GuestRSVP() {
  const [name, setName] = useState('');
  const [inviteCode, setInviteCode] = useState('');
  const [attending, setAttending] = useState(true);
  const [submitted, setSubmitted] = useState(false);
  const submitRSVP = useSubmitRSVP();

  // Auto-populate invite code from URL
  useEffect(() => {
    const codeFromUrl = new URLSearchParams(window.location.search).get('code');
    if (codeFromUrl) setInviteCode(codeFromUrl);
  }, []);

  const handleSubmit = (e) => {
    e.preventDefault();
    submitRSVP.mutate({ name, attending, inviteCode }, {
      onSuccess: () => setSubmitted(true)
    });
  };

  if (submitted) return <div>Thank you! Your RSVP has been submitted.</div>;

  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={(e) => setName(e.target.value)} placeholder="Your Name" required />
      <input value={inviteCode} onChange={(e) => setInviteCode(e.target.value)} placeholder="Invite Code" required />
      <label>
        <input type="radio" checked={attending} onChange={() => setAttending(true)} />
        Yes, I'll attend
      </label>
      <label>
        <input type="radio" checked={!attending} onChange={() => setAttending(false)} />
        No, I can't attend
      </label>
      <button type="submit" disabled={submitRSVP.isPending}>Submit RSVP</button>
      {submitRSVP.error && <p>Error: {submitRSVP.error.message}</p>}
    </form>
  );
}
管理者ダッシュボード
import { useGetAllRSVPs, useGetInviteCodes, useGenerateInviteCode } from '../hooks/useQueries';

export default function AdminDashboard() {
  const { data: rsvps } = useGetAllRSVPs();
  const { data: inviteCodes } = useGetInviteCodes();
  const generateInviteCode = useGenerateInviteCode();

  const unusedCodes = inviteCodes?.filter(code => !code.used) || [];
  const attendingCount = rsvps?.filter(rsvp => rsvp.attending).length || 0;

  return (
    <div>
      <div>
        <h2>Statistics</h2>
        <p>Total RSVPs: {rsvps?.length || 0}</p>
        <p>Attending: {attendingCount}</p>
        <p>Not Attending: {(rsvps?.length || 0) - attendingCount}</p>
      </div>

      <div>
        <h2>Invite Codes</h2>
        <button onClick={() => generateInviteCode.mutate()}>Generate New Code</button>
        {unusedCodes.map(code => (
          <div key={code.code}>
            <code>{code.code}</code>
            <button onClick={() => navigator.clipboard.writeText(`${window.location.origin}?code=${code.code}`)}>
              Copy Link
            </button>
          </div>
        ))}
      </div>

      <div>
        <h2>RSVPs</h2>
        <table>
          <thead><tr><th>Name</th><th>Status</th><th>Date</th></tr></thead>
          <tbody>
            {rsvps?.map(rsvp => (
              <tr key={rsvp.inviteCode}>
                <td>{rsvp.name}</td>
                <td>{rsvp.attending ? 'Attending' : 'Not Attending'}</td>
                <td>{new Date(Number(rsvp.timestamp / BigInt(1000000))).toLocaleDateString()}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

必須フック

  • useIsCurrentUserAdmin() - 現在のユーザーが管理者かどうかを確認
  • useSubmitRSVP() - 出欠確認の送信ミューテーション
  • useGetAllRSVPs() - すべての出欠確認を取得(管理者のみ)
  • useGetInviteCodes() - 招待コードを取得(管理者のみ)
  • useGenerateInviteCode() - 新しい招待コードを生成(管理者のみ)
  • useInternetIdentity() - Internet Identity 認証

主な機能

  • 招待コードの URL パラメーター解析(?code=xyz)
  • 招待リンクをクリップボードにコピーする機能
  • 認証状態に応じた管理者/ゲスト表示の切り替え
  • 基本的なフォーム検証とエラー処理
  • 表示用のタイムスタンプ変換

コメント