extension-invite-links について
Caffeine AI 向けの招待リンクと RSVP の拡張です。管理者が一意の招待コードを発行し、ゲストは認証なしで出欠を送信できます。extension-authorization が前提で、ミックスインがバックエンドの API を提供します。
主な機能
- 01一意の招待コード生成
- 02認証不要のゲスト RSVP 受付
- 03管理者による RSVP とコードの確認
- 04招待コード生成・取得 API
- 05URL パラメーターから招待コードを取得
活用シーン
- 01イベント用ゲスト招待リンクの作成
- 02ログイン不要の出欠回答収集
- 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) - 招待リンクをクリップボードにコピーする機能
- 認証状態に応じた管理者/ゲスト表示の切り替え
- 基本的なフォーム検証とエラー処理
- 表示用のタイムスタンプ変換