tangled
alpha
login
or
join now
xan.lol
/
flushes.app
forked from
atpota.to/flushes.app
0
fork
atom
The 1st decentralized social network for sharing when you're on the toilet. Post a "flush" today! Powered by the AT Protocol.
0
fork
atom
overview
issues
pulls
pipelines
fix
dame-is
3 months ago
64f4a17e
995f9fb3
+174
-4
2 changed files
expand all
collapse all
unified
split
src
app
feed
feed.module.css
page.tsx
+52
src/app/feed/feed.module.css
···
149
149
margin-bottom: 0.75rem;
150
150
}
151
151
152
152
+
.headerRight {
153
153
+
display: flex;
154
154
+
align-items: center;
155
155
+
gap: 0.75rem;
156
156
+
}
157
157
+
158
158
+
.editButton {
159
159
+
background: none;
160
160
+
border: 1px solid var(--tile-border);
161
161
+
color: var(--text-color);
162
162
+
padding: 6px;
163
163
+
cursor: pointer;
164
164
+
display: flex;
165
165
+
align-items: center;
166
166
+
justify-content: center;
167
167
+
transition: all 0.2s;
168
168
+
width: 32px;
169
169
+
height: 32px;
170
170
+
border-radius: 4px;
171
171
+
}
172
172
+
173
173
+
.editButton svg {
174
174
+
width: 16px;
175
175
+
height: 16px;
176
176
+
}
177
177
+
178
178
+
.editButton:hover {
179
179
+
border-color: var(--primary-color);
180
180
+
color: var(--primary-color);
181
181
+
background: rgba(91, 173, 240, 0.05);
182
182
+
}
183
183
+
184
184
+
.actionError {
185
185
+
background: var(--error-background);
186
186
+
border: 1px solid var(--error-color);
187
187
+
color: var(--error-color);
188
188
+
padding: 1rem;
189
189
+
margin-bottom: 1rem;
190
190
+
border-radius: 4px;
191
191
+
font-size: 0.9rem;
192
192
+
}
193
193
+
194
194
+
.actionSuccess {
195
195
+
background: var(--success-background);
196
196
+
border: 1px solid var(--success-color);
197
197
+
color: var(--success-text);
198
198
+
padding: 1rem;
199
199
+
margin-bottom: 1rem;
200
200
+
border-radius: 4px;
201
201
+
font-size: 0.9rem;
202
202
+
}
203
203
+
152
204
.authorLink {
153
205
color: var(--primary-color);
154
206
font-weight: 600;
+122
-4
src/app/feed/page.tsx
···
5
5
import styles from './feed.module.css';
6
6
import { formatRelativeTime } from '@/lib/time-utils';
7
7
import { useAuth } from '@/lib/auth-context';
8
8
+
import EditFlushModal from '@/components/EditFlushModal';
8
9
9
10
// Types for our feed entries
10
11
interface FlushingEntry {
···
22
23
const [entries, setEntries] = useState<FlushingEntry[]>([]);
23
24
const [loading, setLoading] = useState(true);
24
25
const [error, setError] = useState<string | null>(null);
25
25
-
const { isAuthenticated, handle } = useAuth();
26
26
+
const { isAuthenticated, session } = useAuth();
27
27
+
const [editingFlush, setEditingFlush] = useState<FlushingEntry | null>(null);
28
28
+
const [actionError, setActionError] = useState<string | null>(null);
29
29
+
const [actionSuccess, setActionSuccess] = useState<string | null>(null);
26
30
27
31
useEffect(() => {
28
32
// Fetch the latest entries when the component mounts
···
133
137
}
134
138
};
135
139
140
140
+
// Check if the current user owns this flush
141
141
+
const isOwnFlush = (authorDid: string) => {
142
142
+
if (!session) return false;
143
143
+
return session.sub === authorDid;
144
144
+
};
145
145
+
146
146
+
// Handle updating a flush
147
147
+
const handleUpdateFlush = async (text: string, emoji: string) => {
148
148
+
if (!session || !editingFlush) {
149
149
+
setActionError('You must be logged in to update a flush');
150
150
+
return;
151
151
+
}
152
152
+
153
153
+
try {
154
154
+
setActionError(null);
155
155
+
setActionSuccess(null);
156
156
+
157
157
+
const { updateFlushRecord } = await import('@/lib/api-client');
158
158
+
159
159
+
await updateFlushRecord(
160
160
+
session,
161
161
+
editingFlush.uri,
162
162
+
text,
163
163
+
emoji,
164
164
+
editingFlush.createdAt
165
165
+
);
166
166
+
167
167
+
setActionSuccess('Flush updated successfully!');
168
168
+
169
169
+
// Update the local state
170
170
+
setEntries(entries.map(entry =>
171
171
+
entry.uri === editingFlush.uri
172
172
+
? { ...entry, text, emoji }
173
173
+
: entry
174
174
+
));
175
175
+
176
176
+
// Clear success message after 3 seconds
177
177
+
setTimeout(() => setActionSuccess(null), 3000);
178
178
+
} catch (error: any) {
179
179
+
console.error('Error updating flush:', error);
180
180
+
setActionError(error.message || 'Failed to update flush');
181
181
+
}
182
182
+
};
183
183
+
184
184
+
// Handle deleting a flush
185
185
+
const handleDeleteFlush = async () => {
186
186
+
if (!session || !editingFlush) {
187
187
+
setActionError('You must be logged in to delete a flush');
188
188
+
return;
189
189
+
}
190
190
+
191
191
+
try {
192
192
+
setActionError(null);
193
193
+
setActionSuccess(null);
194
194
+
195
195
+
const { deleteFlushRecord } = await import('@/lib/api-client');
196
196
+
197
197
+
await deleteFlushRecord(session, editingFlush.uri);
198
198
+
199
199
+
setActionSuccess('Flush deleted successfully!');
200
200
+
201
201
+
// Remove from local state
202
202
+
setEntries(entries.filter(entry => entry.uri !== editingFlush.uri));
203
203
+
204
204
+
// Clear success message after 3 seconds
205
205
+
setTimeout(() => setActionSuccess(null), 3000);
206
206
+
} catch (error: any) {
207
207
+
console.error('Error deleting flush:', error);
208
208
+
setActionError(error.message || 'Failed to delete flush');
209
209
+
}
210
210
+
};
211
211
+
136
212
// No longer needed - using formatRelativeTime from time-utils
137
213
138
214
return (
139
215
<div className={styles.container}>
140
216
217
217
+
{/* Action messages */}
218
218
+
{actionError && (
219
219
+
<div className={styles.actionError}>
220
220
+
{actionError}
221
221
+
</div>
222
222
+
)}
223
223
+
224
224
+
{actionSuccess && (
225
225
+
<div className={styles.actionSuccess}>
226
226
+
{actionSuccess}
227
227
+
</div>
228
228
+
)}
229
229
+
230
230
+
{/* Edit Modal */}
231
231
+
<EditFlushModal
232
232
+
isOpen={editingFlush !== null}
233
233
+
flushData={editingFlush ? {
234
234
+
uri: editingFlush.uri,
235
235
+
text: editingFlush.text,
236
236
+
emoji: editingFlush.emoji,
237
237
+
created_at: editingFlush.createdAt
238
238
+
} : null}
239
239
+
onSave={handleUpdateFlush}
240
240
+
onDelete={handleDeleteFlush}
241
241
+
onClose={() => setEditingFlush(null)}
242
242
+
/>
243
243
+
141
244
<header className={styles.header}>
142
245
<h1>Flushing Feed</h1>
143
246
<p className={styles.subtitle}>
···
185
288
>
186
289
@{entry.authorHandle}
187
290
</a>
188
188
-
<span className={styles.timestamp}>
189
189
-
{formatRelativeTime(entry.createdAt)}
190
190
-
</span>
291
291
+
<div className={styles.headerRight}>
292
292
+
<span className={styles.timestamp}>
293
293
+
{formatRelativeTime(entry.createdAt)}
294
294
+
</span>
295
295
+
{isOwnFlush(entry.authorDid) && isAuthenticated && (
296
296
+
<button
297
297
+
className={styles.editButton}
298
298
+
onClick={() => setEditingFlush(entry)}
299
299
+
aria-label="Edit flush"
300
300
+
title="Edit or delete this flush"
301
301
+
>
302
302
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
303
303
+
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
304
304
+
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
305
305
+
</svg>
306
306
+
</button>
307
307
+
)}
308
308
+
</div>
191
309
</div>
192
310
<div className={styles.content}>
193
311
<span className={styles.emoji}>{entry.emoji}</span>