extension-invite-links
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseInvite Links & RSVP
邀请链接与RSVP
Invite links & RSVP extension for Caffeine AI.
为Caffeine AI打造的邀请链接与RSVP扩展功能。
Overview
概述
This skill adds invite-link generation and RSVP collection. Admins generate unique invite codes; guests use them to submit responses without authentication.
该技能新增了邀请链接生成和RSVP收集功能。管理员可生成唯一邀请码;访客无需验证身份即可使用邀请码提交回复。
Backend
后端实现
Invite links and RSVP system functionality:
Prerequisite: You must follow extension-authorization first, as this integration depends on it.
There is a prefabricated module that cannot be modified. It provides invite links with RSVP management.
mo:caffeineai-invite-links/invite-links-module.momo
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>;
};
// State management
public func initState() : InviteLinksSystemState;
// UUID generation
public func generateUUID(blob: Blob) : Text;
// Invite code management
public func generateInviteCode(state: InviteLinksSystemState, code: Text);
public func getInviteCodes(state: InviteLinksSystemState) : [InviteCode];
// RSVP management
public func submitRSVP(state: InviteLinksSystemState, name: Text, attending: Bool, inviteCode: Text);
public func getAllRSVPs(state: InviteLinksSystemState) : [RSVP];
}Usage (all the following functions are required to be added):
motoko
import AccessControl "mo:caffeineai-authorization/access-control";
import MixinAuthorization "mo:caffeineai-authorization/MixinAuthorization";
import InviteLinksModule "mo:caffeineai-invite-links/invite-links-module";
import Text "mo:core/Text";
import Random "mo:core/Random";
import Runtime "mo:core/Runtime";
actor {
// Include authorization component
let accessControlState = AccessControl.initState();
include MixinAuthorization(accessControlState);
// Initialize the invite links system state
let inviteState = InviteLinksModule.initState();
// Generate invite code (admin only)
public shared ({ caller }) func generateInviteCode() : async Text {
if (not (AccessControl.hasPermission(accessControlState, caller, #admin))) {
Runtime.trap("Unauthorized: Only admins can generate invite codes");
};
let blob = await Random.blob();
let code = InviteLinksModule.generateUUID(blob);
InviteLinksModule.generateInviteCode(inviteState, code);
code;
};
// Submit RSVP (public, but requires valid invite code)
public shared func submitRSVP(name: Text, attending: Bool, inviteCode: Text) : async () {
InviteLinksModule.submitRSVP(inviteState, name, attending, inviteCode);
};
// Get all RSVPs (admin only)
public query ({ caller }) func getAllRSVPs() : async [InviteLinksModule.RSVP] {
if (not (AccessControl.hasPermission(accessControlState, caller, #admin))) {
Runtime.trap("Unauthorized: Only admins can view RSVPs");
};
InviteLinksModule.getAllRSVPs(inviteState);
};
// Get all invite codes (admin only)
public query ({ caller }) func getInviteCodes() : async [InviteLinksModule.InviteCode] {
if (not (AccessControl.hasPermission(accessControlState, caller, #admin))) {
Runtime.trap("Unauthorized: Only admins can view invite codes");
};
InviteLinksModule.getInviteCodes(inviteState);
};
// Write additional application-specific code here.
// Authorization is handled using the AccessControl component.
// Use admin-only checks as shown above for protected functions.
};IMPORTANT: Apply the right authorization to each public function using the AccessControl component.
邀请链接与RSVP系统功能:
前置条件:由于本集成依赖于权限扩展,您必须先遵循扩展权限机制的指引。
我们提供了一个不可修改的预制模块 ,它实现了邀请链接与RSVP管理功能。
mo:caffeineai-invite-links/invite-links-module.momo
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>;
};
// State management
public func initState() : InviteLinksSystemState;
// UUID generation
public func generateUUID(blob: Blob) : Text;
// Invite code management
public func generateInviteCode(state: InviteLinksSystemState, code: Text);
public func getInviteCodes(state: InviteLinksSystemState) : [InviteCode];
// RSVP management
public func submitRSVP(state: InviteLinksSystemState, name: Text, attending: Bool, inviteCode: Text);
public func getAllRSVPs(state: InviteLinksSystemState) : [RSVP];
}使用方法(以下所有函数均需添加):
motoko
import AccessControl "mo:caffeineai-authorization/access-control";
import MixinAuthorization "mo:caffeineai-authorization/MixinAuthorization";
import InviteLinksModule "mo:caffeineai-invite-links/invite-links-module";
import Text "mo:core/Text";
import Random "mo:core/Random";
import Runtime "mo:core/Runtime";
actor {
// Include authorization component
let accessControlState = AccessControl.initState();
include MixinAuthorization(accessControlState);
// Initialize the invite links system state
let inviteState = InviteLinksModule.initState();
// Generate invite code (admin only)
public shared ({ caller }) func generateInviteCode() : async Text {
if (not (AccessControl.hasPermission(accessControlState, caller, #admin))) {
Runtime.trap("Unauthorized: Only admins can generate invite codes");
};
let blob = await Random.blob();
let code = InviteLinksModule.generateUUID(blob);
InviteLinksModule.generateInviteCode(inviteState, code);
code;
};
// Submit RSVP (public, but requires valid invite code)
public shared func submitRSVP(name: Text, attending: Bool, inviteCode: Text) : async () {
InviteLinksModule.submitRSVP(inviteState, name, attending, inviteCode);
};
// Get all RSVPs (admin only)
public query ({ caller }) func getAllRSVPs() : async [InviteLinksModule.RSVP] {
if (not (AccessControl.hasPermission(accessControlState, caller, #admin))) {
Runtime.trap("Unauthorized: Only admins can view RSVPs");
};
InviteLinksModule.getAllRSVPs(inviteState);
};
// Get all invite codes (admin only)
public query ({ caller }) func getInviteCodes() : async [InviteLinksModule.InviteCode] {
if (not (AccessControl.hasPermission(accessControlState, caller, #admin))) {
Runtime.trap("Unauthorized: Only admins can view invite codes");
};
InviteLinksModule.getInviteCodes(inviteState);
};
// Write additional application-specific code here.
// Authorization is handled using the AccessControl component.
// Use admin-only checks as shown above for protected functions.
};重要提示:请使用AccessControl组件为每个公开函数配置正确的权限。
Frontend
前端实现
Invite links and RSVP system functionality:
Here is an example how to implement Internet Identity authentication with admin-only invite links / RSVPs in the frontend:
typescript
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>
);
}typescript
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>
);
}邀请链接与RSVP系统功能:
以下是在前端实现基于Internet Identity认证的管理员专属邀请链接/RSVP功能示例:
typescript
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>
);
}typescript
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>
);
}Admin Dashboard
管理员控制台
typescript
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>
);
}typescript
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>
);
}Required Hooks
必备Hooks
- - Check if current user is admin
useIsCurrentUserAdmin() - - Submit RSVP mutation
useSubmitRSVP() - - Fetch all RSVPs (admin only)
useGetAllRSVPs() - - Fetch invite codes (admin only)
useGetInviteCodes() - - Generate new invite code (admin only)
useGenerateInviteCode() - - Internet Identity authentication
useInternetIdentity()
- - 检查当前用户是否为管理员
useIsCurrentUserAdmin() - - 提交RSVP变更请求
useSubmitRSVP() - - 获取所有RSVP记录(仅管理员可用)
useGetAllRSVPs() - - 获取邀请码列表(仅管理员可用)
useGetInviteCodes() - - 生成新邀请码(仅管理员可用)
useGenerateInviteCode() - - Internet Identity认证
useInternetIdentity()
Key Features
核心功能
- URL parameter parsing for invite codes ()
?code=xyz - Copy invite links to clipboard functionality
- Admin/guest view switching based on authentication
- Basic form validation and error handling
- Timestamp conversion for display
- 邀请码URL参数解析()
?code=xyz - 邀请链接复制到剪贴板功能
- 基于认证状态切换管理员/访客视图
- 基础表单验证与错误处理
- 时间戳转换用于展示