api-ix commited on
Commit
09d89c6
·
verified ·
1 Parent(s): c2e8d23

Create session.html

Browse files
Files changed (1) hide show
  1. session.html +238 -0
session.html ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8"/>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
6
+ <title>Contact Session | VCF Session</title>
7
+
8
+ <style>
9
+ :root {
10
+ --primary: #2563eb;
11
+ --primary-hover: #1d4ed8;
12
+ --success: #059669;
13
+ --error: #dc2626;
14
+ --warning: #d97706;
15
+ --bg-primary: #ffffff;
16
+ --bg-secondary: #f8fafc;
17
+ --bg-card: #ffffff;
18
+ --text-primary: #0f172a;
19
+ --text-secondary: #64748b;
20
+ --text-muted: #94a3b8;
21
+ --border: #e2e8f0;
22
+ --radius: 8px;
23
+ --radius-lg: 12px;
24
+ --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
25
+ --transition: all .2s cubic-bezier(.4,0,.2,1);
26
+ }
27
+ @media (prefers-color-scheme: dark) {
28
+ :root {
29
+ --primary: #3b82f6;
30
+ --primary-hover: #2563eb;
31
+ --bg-primary: #0f172a;
32
+ --bg-secondary: #1e293b;
33
+ --bg-card: #1e293b;
34
+ --text-primary: #f1f5f9;
35
+ --text-secondary: #cbd5e1;
36
+ --text-muted: #64748b;
37
+ --border: #334155;
38
+ }
39
+ }
40
+ * { box-sizing: border-box; margin: 0; padding: 0; }
41
+ html, body {
42
+ height: 100%;
43
+ font-family: var(--font-sans);
44
+ background: var(--bg-primary);
45
+ color: var(--text-primary);
46
+ line-height: 1.6;
47
+ }
48
+ .container { min-height: 100vh; padding: 1rem; background: var(--bg-secondary); }
49
+ .header { text-align: center; padding: 2rem 0; }
50
+ .session-title { font-size: 1.875rem; font-weight: 700; margin-bottom: .5rem; }
51
+ .session-status { display: flex; align-items: center; justify-content: center; gap: .5rem; color: var(--text-secondary); }
52
+ .status-indicator { width: 8px; height: 8px; border-radius: 50%; background: var(--success); animation: pulse 2s infinite; }
53
+ .status-indicator.expired { background: var(--error); animation: none; }
54
+ @keyframes pulse { 50% { opacity: .5; } }
55
+ .countdown { font-weight: 600; color: var(--primary); }
56
+ .countdown.warning { color: var(--warning); }
57
+ .countdown.expired { color: var(--error); }
58
+ .main-content { max-width: 600px; margin: 0 auto; display: grid; gap: 2rem; }
59
+ .card { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: 0 4px 6px -1px rgb(0 0 0 / .1); padding: 2rem; }
60
+ .card-header { margin-bottom: 1.5rem; }
61
+ .card-title { font-size: 1.25rem; font-weight: 600; }
62
+ .card-description { color: var(--text-secondary); font-size: .875rem; }
63
+ .form-group { margin-bottom: 1rem; }
64
+ .form-label { display: block; font-size: .875rem; font-weight: 600; margin-bottom: .5rem; }
65
+ .form-input { width: 100%; padding: .75rem 1rem; border: 1px solid var(--border); border-radius: var(--radius); font-size: 1rem; background: var(--bg-primary); color: var(--text-primary); }
66
+ .form-input:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgb(37 99 235 / .1); }
67
+ .form-input.error { border-color: var(--error); }
68
+ .error-text { color: var(--error); font-size: .75rem; margin-top: .25rem; display: none; }
69
+ .add-btn { width: 100%; padding: .875rem 1.5rem; background: var(--primary); color: #fff; border: none; border-radius: var(--radius); font-size: 1rem; font-weight: 600; cursor: pointer; transition: var(--transition); }
70
+ .add-btn:hover:not(:disabled) { background: var(--primary-hover); }
71
+ .add-btn:disabled { background: var(--text-muted); cursor: not-allowed; }
72
+ .success-message { background: #f0fdf4; border: 1px solid #bbf7d0; color: var(--success); padding: .75rem 1rem; border-radius: var(--radius); margin-bottom: 1rem; display: none; }
73
+ .download-section { text-align: center; }
74
+ .download-btn { display: inline-flex; align-items: center; gap: .5rem; padding: .875rem 2rem; background: var(--success); color: #fff; text-decoration: none; border-radius: var(--radius); font-weight: 600; }
75
+ .download-btn:hover { background: #047857; }
76
+ .footer { text-align: center; padding: 2rem 1rem; color: var(--text-muted); font-size: .875rem; }
77
+ .footer a { color: var(--primary); text-decoration: none; }
78
+ .footer a:hover { text-decoration: underline; }
79
+ .session-expired { text-align: center; padding: 3rem 1rem; }
80
+ .expired-icon { width: 64px; height: 64px; background: var(--error); border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 1.5rem; color: #fff; font-size: 2rem; }
81
+ @media (max-width: 640px) { .container { padding: .5rem; } .card { padding: 1.5rem; } }
82
+ </style>
83
+ </head>
84
+
85
+ <body>
86
+ <div class="container">
87
+ <div class="header">
88
+ <h1 id="sessionTitle" class="session-title">Loading…</h1>
89
+ <div class="session-status">
90
+ <div id="statusIndicator" class="status-indicator"></div>
91
+ <span>Session expires in <span id="countdown" class="countdown">--:--</span></span>
92
+ </div>
93
+ </div>
94
+
95
+ <div class="main-content">
96
+ <!-- Add-Contact Card -->
97
+ <div id="contactForm" class="card">
98
+ <div class="card-header">
99
+ <h2 class="card-title">Add Your Contact</h2>
100
+ <p class="card-description">Share your contact information with other participants</p>
101
+ </div>
102
+
103
+ <div id="successMessage" class="success-message">Contact added!</div>
104
+
105
+ <form id="addContactForm">
106
+ <div class="form-group">
107
+ <label for="contactName" class="form-label">Full Name *</label>
108
+ <input id="contactName" type="text" class="form-input" placeholder="John Doe" required autocomplete="name">
109
+ <div id="nameError" class="error-text"></div>
110
+ </div>
111
+
112
+ <div class="form-group">
113
+ <label for="contactPhone" class="form-label">Phone Number *</label>
114
+ <input id="contactPhone" type="tel" class="form-input" placeholder="+1234567890" required autocomplete="tel">
115
+ <div id="phoneError" class="error-text"></div>
116
+ </div>
117
+
118
+ <button type="submit" id="addBtn" class="add-btn">Add Contact</button>
119
+ </form>
120
+ </div>
121
+
122
+ <!-- Expired Notice -->
123
+ <div id="expiredCard" class="card" style="display:none;">
124
+ <div class="session-expired">
125
+ <div class="expired-icon">✖</div>
126
+ <h2 class="card-title">Session Expired</h2>
127
+ <p class="card-description">This session has ended. You may still download the collected contacts.</p>
128
+ </div>
129
+ </div>
130
+
131
+ <!-- Download Section -->
132
+ <div id="downloadSection" class="card" style="display:none;">
133
+ <div class="download-section">
134
+ <a id="downloadBtn" class="download-btn" href="">Download VCF File</a>
135
+ <p class="card-description">Import the file into any contacts app</p>
136
+ </div>
137
+ </div>
138
+ </div>
139
+
140
+ <div class="footer">
141
+ Join our WhatsApp group:
142
+ <a href="https://chat.whatsapp.com/JRe69IJCINM3qYX3gM8MI9?mode=ems_copy_c" target="_blank" rel="noopener">VCF GROUP</a>
143
+ </div>
144
+ </div>
145
+
146
+ <script>
147
+ const sessionId = window.location.pathname.split('/').pop();
148
+ if (!sessionId) window.location.href = '/';
149
+
150
+ const sessionTitle = document.getElementById('sessionTitle');
151
+ const statusInd = document.getElementById('statusIndicator');
152
+ const countdown = document.getElementById('countdown');
153
+ const contactForm = document.getElementById('contactForm');
154
+ const expiredCard = document.getElementById('expiredCard');
155
+ const downloadSect = document.getElementById('downloadSection');
156
+ const downloadBtn = document.getElementById('downloadBtn');
157
+ const addForm = document.getElementById('addContactForm');
158
+ const successMsg = document.getElementById('successMessage');
159
+ const nameInput = document.getElementById('contactName');
160
+ const phoneInput = document.getElementById('contactPhone');
161
+ const nameError = document.getElementById('nameError');
162
+ const phoneError = document.getElementById('phoneError');
163
+
164
+ let sessionData = null;
165
+ let isExpired = false;
166
+ let countdownTimer = null;
167
+
168
+ const sanitize = str => str.replace(/[\x00-\x1F\x7F]/g, ' ').trim();
169
+
170
+ async function loadSession() {
171
+ const res = await fetch(`/api/session/${sessionId}`);
172
+ if (!res.ok) { document.body.innerHTML = `<div class="card empty-state"><h2>Session Not Found</h2><a href="/">← Home</a></div>`; return; }
173
+ sessionData = await res.json();
174
+ sessionTitle.textContent = sessionData.name;
175
+ startCountdown();
176
+ }
177
+ loadSession();
178
+
179
+ function startCountdown() {
180
+ function tick() {
181
+ const left = sessionData.expiresAt - Date.now();
182
+ if (left <= 0) {
183
+ isExpired = true;
184
+ countdown.textContent = 'Expired';
185
+ countdown.classList.add('expired');
186
+ statusInd.classList.add('expired');
187
+ contactForm.style.display = 'none';
188
+ expiredCard.style.display = 'block';
189
+ downloadSect.style.display = 'block';
190
+ downloadBtn.href = `/api/session/${sessionId}/contacts.vcf`;
191
+ clearInterval(countdownTimer);
192
+ return;
193
+ }
194
+ const m = String(Math.floor(left / 60000)).padStart(2, '0');
195
+ const s = String(Math.floor((left % 60000) / 1000)).padStart(2, '0');
196
+ countdown.textContent = `${m}:${s}`;
197
+ if (left < 300000) countdown.classList.add('warning');
198
+ }
199
+ tick();
200
+ countdownTimer = setInterval(tick, 1000);
201
+ }
202
+
203
+ function showError(el, msg) { el.classList.add('error'); el.nextElementSibling.textContent = msg; el.nextElementSibling.style.display = 'block'; }
204
+ function hideError(el) { el.classList.remove('error'); el.nextElementSibling.style.display = 'none'; }
205
+
206
+ addForm.addEventListener('submit', async e => {
207
+ e.preventDefault();
208
+ if (isExpired) return;
209
+
210
+ const name = sanitize(nameInput.value);
211
+ const phone = sanitize(phoneInput.value);
212
+
213
+ let ok = true;
214
+ if (!name) { showError(nameInput, 'Name required'); ok = false; } else hideError(nameInput);
215
+ if (!phone) { showError(phoneInput, 'Phone required'); ok = false; } else hideError(phoneInput);
216
+ if (!ok) return;
217
+
218
+ const res = await fetch(`/api/session/${sessionId}/contact`, {
219
+ method: 'POST',
220
+ headers: { 'Content-Type': 'application/json' },
221
+ body: JSON.stringify({ name, phone })
222
+ });
223
+
224
+ if (res.ok) {
225
+ successMsg.style.display = 'block';
226
+ addForm.reset();
227
+ await loadSession();
228
+ setTimeout(() => successMsg.style.display = 'none', 3000);
229
+ } else {
230
+ const err = await res.json();
231
+ alert(err.error || 'Failed to add contact');
232
+ }
233
+ });
234
+
235
+ window.addEventListener('beforeunload', () => clearInterval(countdownTimer));
236
+ </script>
237
+ </body>
238
+ </html>