CI / check (push) Canceled after 0s
Web and mobile: - Follower/following lists (tap the counts), with Follow buttons and "Remove" on your own followers. - Profile ⋯ menu: block (with confirmation) / unblock, and report with a reason and optional note. "Follows you" label, "Follow back", and a banner on profiles you've blocked. - Blocked accounts list in Settings, with unblock. Web only: /admin for the admin account: stats, open/resolved reports, resolve, suspend/unsuspend. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
50 lines
1.7 KiB
TypeScript
50 lines
1.7 KiB
TypeScript
import { Link } from 'expo-router';
|
|
import { FlatList, StyleSheet, Text, View } from 'react-native';
|
|
import useSWR from 'swr';
|
|
import type { UserSummaryDTO } from '@musicshare/shared';
|
|
import { Avatar } from '@/components/Avatar';
|
|
import { Body, Button } from '@/components/ui';
|
|
import { api } from '@/lib/api';
|
|
import { showError } from '@/lib/errors';
|
|
import { colors, space } from '@/lib/theme';
|
|
|
|
export default function BlockedScreen() {
|
|
const { data: blocked = [], mutate, isLoading } = useSWR<UserSummaryDTO[]>('/me/blocks');
|
|
const unblock = async (username: string) => {
|
|
try {
|
|
await api.unblock(username);
|
|
await mutate();
|
|
} catch (err) {
|
|
showError(err);
|
|
}
|
|
};
|
|
return (
|
|
<FlatList
|
|
data={blocked}
|
|
keyExtractor={(u) => u.id}
|
|
contentContainerStyle={styles.list}
|
|
ListEmptyComponent={isLoading ? null : <Body muted>You haven't blocked anyone. Block someone from the ⋯ menu on their profile.</Body>}
|
|
renderItem={({ item: u }) => (
|
|
<View style={styles.row}>
|
|
<Link href={`/u/${u.username}`} style={{ flex: 1 }}>
|
|
<View style={styles.person}>
|
|
<Avatar user={u} size={40} />
|
|
<Text style={styles.name} numberOfLines={1}>
|
|
{u.displayName ?? u.username}
|
|
</Text>
|
|
</View>
|
|
</Link>
|
|
<Button small title="Unblock" onPress={() => unblock(u.username)} />
|
|
</View>
|
|
)}
|
|
/>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
list: { padding: space.lg, gap: space.md },
|
|
row: { flexDirection: 'row', alignItems: 'center', gap: space.md },
|
|
person: { flexDirection: 'row', alignItems: 'center', gap: space.md },
|
|
name: { color: colors.text, fontSize: 15, fontWeight: '600', flexShrink: 1 },
|
|
});
|