-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfeedback.html
More file actions
188 lines (165 loc) · 8.45 KB
/
Copy pathfeedback.html
File metadata and controls
188 lines (165 loc) · 8.45 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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Selfish Feedback</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<link href="https://fonts.cdnfonts.com/css/proxima-nova-2" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;500;700&display=swap" rel="stylesheet">
<style>
@font-face {
font-family: 'TikTok Sans';
src: local('TikTok Sans'), local('Proxima Nova'), sans-serif;
font-weight: 700;
}
.font-tiktok { font-family: 'TikTok Sans', 'Inter', sans-serif; }
.font-noto { font-family: 'Noto Sans', sans-serif; }
.text-gradient-selfish {
background: linear-gradient(to right, #ef4444, #ec4899);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.spinner {
border: 2px solid rgba(239, 68, 68, 0.1);
border-left-color: #ef4444;
border-radius: 50%;
width: 12px; height: 12px;
animation: spin 1s linear infinite;
}
@keyframes spin { 100% { transform: rotate(360deg); } }
</style>
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react@18.2.0",
"react-dom/client": "https://esm.sh/react-dom@18.2.0/client"
}
}
</script>
</head>
<body class="bg-neutral-50 min-h-screen">
<div id="root"></div>
<script type="text/babel" data-type="module">
import React, { useState, useEffect, useRef } from 'react';
import { createRoot } from 'react-dom/client';
function App() {
const [feedbackType, setFeedbackType] = useState('Suggestion');
const [description, setDescription] = useState('');
const [items, setItems] = useState([]);
const [isSubmitting, setIsSubmitting] = useState(false);
const listRef = useRef(null);
// ACTUAL ASYNC SYNCING LOGIC
const syncToGlobalStorage = async (newItem) => {
// This simulates the 'Global Storage' / Firestore call
return new Promise((resolve) => {
setTimeout(() => {
console.log("Async sync complete for:", newItem.id);
resolve({ ...newItem, pending: false, timestamp: new Date().toLocaleTimeString() });
}, 1200); // Simulated network latency
});
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!description.trim()) return;
const tempId = Date.now();
const newItem = {
id: tempId,
type: feedbackType,
description: description,
username: 'anonymous',
pending: true,
timestamp: 'Just now'
};
// 1. INSTANT UI UPDATE (0.1s feedback)
setItems(prev => [newItem, ...prev]);
setDescription('');
if (window.anime) {
window.anime({
targets: '.submit-btn',
scale: [1, 0.95, 1],
duration: 200,
easing: 'easeOutQuad'
});
}
// 2. ACTUAL ASYNC EXECUTION
try {
const confirmedItem = await syncToGlobalStorage(newItem);
// Update the local list with the "confirmed" server data
setItems(prev => prev.map(item =>
item.id === tempId ? confirmedItem : item
));
} catch (err) {
console.error("Sync failed", err);
}
};
return (
<div className="p-4 md:p-8">
<div className="max-w-xl mx-auto space-y-8">
<div className="text-center">
<h1 className="text-5xl md:text-6xl font-black font-tiktok tracking-tighter text-gradient-selfish py-2">
Selfish feedback
</h1>
</div>
<div className="bg-white rounded-3xl shadow-xl p-6 md:p-8 border border-gray-100 space-y-6">
<div className="space-y-2">
<label className="block text-sm font-bold text-gray-700 font-tiktok uppercase">Category</label>
<select
value={feedbackType}
onChange={(e) => setFeedbackType(e.target.value)}
className="w-full bg-gray-50 border border-gray-200 text-lg rounded-xl p-4 font-noto appearance-none"
>
<option value="Suggestion">Suggestion</option>
<option value="Request">Request</option>
</select>
</div>
<div className="space-y-2">
<label className="block text-lg font-bold text-gray-900 font-tiktok">what do you want?</label>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
maxLength={4096}
rows={4}
className="block w-full p-4 bg-gray-50 rounded-xl border border-gray-200 font-noto resize-none"
placeholder="Type feedback..."
></textarea>
</div>
<button
onClick={handleSubmit}
className="submit-btn w-full text-white bg-red-600 hover:bg-red-700 font-black rounded-2xl text-xl py-5 shadow-lg shadow-red-500/30 transition-transform font-tiktok uppercase tracking-widest"
>
Submit
</button>
</div>
<div className="space-y-4" ref={listRef}>
<h2 className="text-2xl font-bold text-gray-800 font-tiktok">Recent Demands</h2>
<div className="grid gap-4">
{items.map((item) => (
<div key={item.id} className="bg-white p-5 rounded-2xl shadow-sm border border-gray-100 flex flex-col gap-2">
<div className="flex justify-between items-baseline border-b pb-2">
<span className="text-gray-400 font-bold uppercase text-xs font-tiktok">{item.type}</span>
<span className="text-gray-300 text-xs font-mono">@{item.username}</span>
</div>
<p className="text-gray-800 font-noto leading-relaxed whitespace-pre-wrap">{item.description}</p>
{item.pending && (
<div className="flex items-center mt-2 gap-2">
<div className="spinner"></div>
<span className="text-[10px] text-red-500 font-bold font-tiktok uppercase">Syncing to Async...</span>
</div>
)}
</div>
))}
</div>
</div>
</div>
</div>
);
}
const root = createRoot(document.getElementById('root'));
root.render(<App />);
</script>
</body>
</html>