-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinvite.html
More file actions
134 lines (118 loc) · 8.73 KB
/
Copy pathinvite.html
File metadata and controls
134 lines (118 loc) · 8.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Join Server — 360</title>
<link href="/assets/css/main.css" rel="stylesheet"/>
<link href="/assets/css/wide.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{min-height:100vh;display:flex;align-items:center;justify-content:center;background:var(--bg);font-family:var(--font);}
.inv-card{background:var(--dc-sidebar-bg);border:1.5px solid var(--dc-sep);border-radius:20px;padding:40px 36px;max-width:440px;width:90%;text-align:center;box-shadow:0 24px 64px rgba(0,0,0,.4);}
.inv-icon{font-size:56px;margin-bottom:16px;line-height:1;}
.inv-server-name{font-size:24px;font-weight:800;color:var(--dc-text);margin-bottom:6px;}
.inv-desc{font-size:14px;color:var(--dc-muted);margin-bottom:4px;line-height:1.5;}
.inv-meta{font-size:12px;color:var(--dc-muted);margin-bottom:24px;}
.inv-meta span{display:inline-flex;align-items:center;gap:4px;margin:0 6px;}
.inv-online::before{content:'';display:inline-block;width:8px;height:8px;border-radius:50%;background:#22c55e;}
.inv-btn{display:block;width:100%;padding:13px;border-radius:12px;border:none;background:var(--a);color:#fff;font-size:15px;font-weight:700;cursor:pointer;transition:opacity .15s;margin-bottom:10px;}
.inv-btn:hover{opacity:.88;}
.inv-btn-sec{background:var(--dc-input-bg);color:var(--dc-text);border:1.5px solid var(--dc-sep);}
.inv-error{color:#ef4444;font-size:14px;margin-top:12px;}
.inv-expired{opacity:.6;}
.inv-spinner{font-size:32px;animation:spin 1s linear infinite;margin:20px auto;display:block;}
@keyframes spin{to{transform:rotate(360deg);}}
.inv-channels{text-align:left;background:var(--dc-input-bg);border-radius:10px;padding:12px 14px;margin:16px 0;font-size:13px;}
.inv-ch{display:flex;align-items:center;gap:6px;color:var(--dc-muted);padding:3px 0;}
.inv-rules{text-align:left;background:var(--dc-input-bg);border-radius:10px;padding:12px 14px;margin:16px 0;font-size:13px;color:var(--dc-text);white-space:pre-wrap;max-height:160px;overflow-y:auto;}
</style>
<script src="assets/js/octicons.js"></script>
<meta content="Join Server 360 — part of the 360 web platform." name="description"/><meta content="noindex, nofollow" name="robots"/><meta content="website" property="og:type"/><meta content="Join Server — 360" property="og:title"/><meta content="Join Server 360 — part of the 360 web platform." property="og:description"/><meta content="https://360-search.com/invite.html" property="og:url"/><meta content="360" property="og:site_name"/><meta content="summary" name="twitter:card"/><meta content="Join Server — 360" name="twitter:title"/><meta content="Join Server 360 — part of the 360 web platform." name="twitter:description"/><link href="https://360-search.com/invite.html" rel="canonical"/></head>
<body>
<div class="inv-card" id="inv-card">
<div class="inv-spinner"><span data-octicon="hourglass"></span></div>
</div>
<script>
const SB_URL = 'https://wiswfpfsjiowtrdyqpxy.supabase.co';
const SB_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Indpc3dmcGZzamlvd3RyZHlxcHh5Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjgzMzg4OTcsImV4cCI6MjA4MzkxNDg5N30.z_4FtM2c8UwgrRlafPYjolQuod4IoHQats95XHio1zM';
const sb = supabase.createClient(SB_URL, SB_KEY);
async function init() {
// Support /invite/CODE and ?invite=CODE and /chat?invite=CODE
const pathMatch = location.pathname.match(/\/invite\/([^\/]+)/);
const code = pathMatch?.[1] || new URLSearchParams(location.search).get('invite') || new URLSearchParams(location.search).get('code');
const card = document.getElementById('inv-card');
if (!code) { card.innerHTML = '<div class="inv-icon"><span data-octicon="x-circle"></span></div><div class="inv-server-name">Invalid Link</div><div class="inv-desc">This invite link is invalid.</div>'; return; }
// Load invite
const { data: inv, error } = await sb.from('server_invites').select('*').eq('code', code).maybeSingle();
if (error || !inv) { card.innerHTML = '<div class="inv-icon"><span data-octicon="x-circle"></span></div><div class="inv-server-name">Unknown Invite</div><div class="inv-desc">This invite link doesn\'t exist or has expired.</div>'; return; }
if (inv.used && !inv.multi_use) { card.innerHTML = '<div class="inv-icon"><span data-octicon="hourglass"></span></div><div class="inv-server-name">Invite Used</div><div class="inv-desc">This invite link has already been used.</div>'; return; }
if (inv.expires_at && new Date(inv.expires_at) < new Date()) { card.innerHTML = '<div class="inv-icon"><span data-octicon="hourglass"></span></div><div class="inv-server-name">Invite Expired</div><div class="inv-desc">This invite link has expired.</div>'; return; }
// Load server
const { data: server } = await sb.from('servers').select('*').eq('id', inv.server_id).maybeSingle();
if (!server) { card.innerHTML = '<div class="inv-icon"><span data-octicon="x-circle"></span></div><div class="inv-server-name">Server Not Found</div>'; return; }
// Load channels preview
const { data: channels } = await sb.from('channels').select('name,category').eq('server_id', server.id).eq('is_public', true).order('position').limit(5);
// Load member count
const { count: memberCount } = await sb.from('server_members').select('*', { count: 'exact', head: true }).eq('server_id', server.id);
// Check auth
const { data: { session } } = await sb.auth.getSession();
const authed = !!session?.user;
// Check already member
let alreadyMember = false;
if (authed) {
const { data: mem } = await sb.from('server_members').select('id').eq('server_id', server.id).eq('user_id', session.user.id).maybeSingle();
alreadyMember = !!mem;
}
const channelPreview = (channels||[]).map(c => `<div class="inv-ch"># ${c.name}</div>`).join('');
const rulesHtml = server.onboarding_rules ? `<div class="inv-rules">${server.onboarding_rules}</div>` : '';
card.innerHTML = `
<div class="inv-icon">${server.icon || '<span data-octicon="globe"></span>'}</div>
<div class="inv-server-name">${esc(server.name)}</div>
${server.description ? `<div class="inv-desc">${esc(server.description)}</div>` : ''}
<div class="inv-meta">
<span><b>${memberCount||0}</b> members</span>
${server.verified ? '<span><span data-octicon="check-circle"></span> Verified</span>' : ''}
</div>
${rulesHtml ? `<details><summary style="cursor:pointer;font-size:13px;color:var(--dc-muted);margin-bottom:8px;"><span data-octicon="law"></span> Server Rules</summary>${rulesHtml}</details>` : ''}
${channelPreview ? `<div class="inv-channels">${channelPreview}${(channels?.length||0)>=5?'<div class="inv-ch" style="opacity:.5">and more…</div>':''}</div>` : ''}
${alreadyMember
? `<button class="inv-btn" onclick="goToServer('${server.id}','${server.slug||''}')">Already a member — Go to server →</button>`
: authed
? `<button class="inv-btn" id="join-btn" onclick="joinServer('${inv.id}','${server.id}','${server.slug||''}','${code}')">Accept Invite & Join</button>`
: `<button class="inv-btn" onclick="signInAndJoin('${code}')">Sign in to Join</button>`
}
<div id="inv-msg"></div>
`;
}
function esc(s){ const d=document.createElement('div'); d.textContent=s||''; return d.innerHTML; }
async function joinServer(invId, serverId, slug, code) {
const btn = document.getElementById('join-btn');
if(btn) { btn.disabled=true; btn.textContent='Joining…'; }
const { data: { session } } = await sb.auth.getSession();
if(!session) { signInAndJoin(code); return; }
// Mark invite used
await sb.from('server_invites').update({ used: true, used_by: session.user.id }).eq('id', invId);
// Add to server
const { error } = await sb.from('server_members').upsert({ server_id: serverId, user_id: session.user.id }, { onConflict: 'server_id,user_id' });
if(error) { document.getElementById('inv-msg').innerHTML=`<div class="inv-error"><span data-octicon="x-circle"></span> ${error.message}</div>`; if(btn){btn.disabled=false;btn.textContent='Accept Invite & Join';} return; }
// Check onboarding
const { data: server } = await sb.from('servers').select('onboarding_enabled,slug').eq('id', serverId).maybeSingle();
if(server?.onboarding_enabled) {
location.href = `/onboarding?server=${serverId}&from=invite`;
} else {
goToServer(serverId, slug || server?.slug || '');
}
}
function goToServer(serverId, slug) {
if(slug) location.href = `/chat?s=${encodeURIComponent(slug)}`;
else location.href = `/chat?s=${encodeURIComponent(serverId)}`;
}
function signInAndJoin(code) {
location.href = `/account?from=/chat%3Finvite%3D${code}`;
}
init();
</script>
</body>
</html>