-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSnfAgentsPage.jsx
More file actions
171 lines (160 loc) · 6.43 KB
/
Copy pathSnfAgentsPage.jsx
File metadata and controls
171 lines (160 loc) · 6.43 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
import React, { useEffect, useMemo, useState } from 'react';
import { Link } from 'react-router-dom';
import {
UserFocusIcon,
LinkSimpleIcon,
FolderIcon,
} from '@phosphor-icons/react';
import SnfLayout from '../../components/snf/SnfLayout';
import {
SnfPageHeader,
SnfSearchInput,
SnfReveal,
SnfEmpty,
} from '../../components/snf/SnfPrimitives';
import Seo from '../../components/Seo';
import { useSnf } from '../../context/SnfContext';
import { useAchievements } from '../../context/AchievementContext';
import { useAuthors, useBooks, getBooksByAuthor } from '../../hooks/useStoriesData';
const SnfAgentsPage = () => {
const { language, setBreadcrumbs } = useSnf();
const { unlockAchievement } = useAchievements();
const { data: authors, loading } = useAuthors();
const { data: books } = useBooks(language);
const [query, setQuery] = useState('');
useEffect(() => {
setBreadcrumbs([{ label: 'OPERATORS' }]);
unlockAchievement('author_aficionado');
}, [setBreadcrumbs, unlockAchievement]);
const filtered = useMemo(() => {
const term = query.toLowerCase();
return (authors || []).filter(
(a) =>
a.name?.toLowerCase().includes(term) ||
a.alias?.toLowerCase().includes(term),
);
}, [authors, query]);
return (
<SnfLayout>
<Seo
title="Operators | Serfs and Frauds Terminal"
description="The field operators and scribes behind From Serfs and Frauds."
keywords={['serfs and frauds', 'authors', 'operators', 'scribes']}
/>
<SnfPageHeader
fileNo="005"
label="FIELD OPERATORS // SCRIBES"
title={language === 'tr' ? 'AJANLAR' : 'OPERATORS'}
subtitle={
language === 'tr'
? 'Bu raporları dosyalayan saha kâtipleri.'
: 'The field scribes who filed these reports.'
}
icon={<UserFocusIcon size={40} weight="duotone" />}
/>
<div className="max-w-xl mb-10">
<SnfSearchInput
value={query}
onChange={setQuery}
placeholder={
language === 'tr' ? 'ajan ara…' : 'query by name or codename…'
}
/>
</div>
{loading && (
<div className="snf-mono text-sm snf-dim animate-pulse">
decrypting operator IDs…
</div>
)}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">
{filtered.map((a, i) => {
const credits = getBooksByAuthor(books, a.name, a.alias);
return (
<SnfReveal key={`${a.alias}-${i}`} delay={Math.min(i * 0.06, 0.3)}>
<div className="snf-panel snf-panel-bracket h-full p-5 flex flex-col">
<div className="flex items-center gap-2 snf-mono text-[10px] tracking-[0.2em] snf-dim mb-4">
<span className="snf-led" /> OPR-{String(i + 1).padStart(2, '0')}
<span className="snf-divider flex-grow" />
ACTIVE
</div>
<div className="flex items-center gap-4 mb-4">
<div className="relative flex-none">
<div className="w-16 h-16 border border-[rgba(var(--snf-phos-rgb),0.5)] overflow-hidden bg-black/40">
{a.image ? (
<img
src={a.image}
alt={a.name}
loading="lazy"
className="w-full h-full object-cover"
style={{
filter: 'grayscale(0.4) contrast(1.1) saturate(0.9)',
}}
/>
) : (
<UserFocusIcon
size={32}
weight="duotone"
className="snf-phos-text w-full h-full p-3"
/>
)}
</div>
<span className="absolute -bottom-1 -right-1 snf-led snf-led-blink" />
</div>
<div className="min-w-0">
<div className="snf-display text-xl font-bold uppercase tracking-wide text-[var(--snf-phos)] snf-glow-soft leading-none truncate">
{a.alias}
</div>
<div className="snf-mono text-[11px] snf-dim mt-1 truncate">
{language === 'tr' ? 'KİMLİK' : 'ID'}: {a.name}
</div>
</div>
</div>
<div className="snf-divider mb-3" />
<div className="snf-mono text-[10px] tracking-[0.2em] snf-dim mb-2 flex items-center gap-1.5">
<FolderIcon size={12} weight="fill" />
{language === 'tr' ? 'KATKILAR' : 'CREDITS'} [
{String(credits.length).padStart(2, '0')}]
</div>
<div className="flex flex-wrap gap-1.5 flex-grow content-start">
{credits.length > 0 ? (
credits.map((b) => (
<Link
key={b.bookId}
to={`/snf/archive/${b.bookId}`}
className="snf-row px-2 py-1 snf-mono text-[10px] text-[var(--snf-ink)] hover:text-[var(--snf-phos)]"
title={b.bookTitle}
>
VOL_{String(b.bookId).padStart(2, '0')}
</Link>
))
) : (
<span className="snf-mono text-[10px] snf-dim italic">
no filed reports
</span>
)}
</div>
{a.website && (
<a
href={a.website}
target="_blank"
rel="noreferrer"
className="mt-4 snf-mono text-[10px] uppercase tracking-[0.25em] snf-dim hover:text-[var(--snf-phos)] transition-colors flex items-center gap-1.5"
>
<LinkSimpleIcon size={13} weight="bold" />
{language === 'tr' ? 'DIŞ BAĞLANTI' : 'EXTERNAL LINK'}
</a>
)}
</div>
</SnfReveal>
);
})}
{!loading && filtered.length === 0 && (
<SnfEmpty>
{language === 'tr' ? 'Ajan bulunamadı.' : 'No operators found.'}
</SnfEmpty>
)}
</div>
</SnfLayout>
);
};
export default SnfAgentsPage;