Skip to content

Commit 4270304

Browse files
committed
Super MVP-ish annual report page
1 parent ab6a977 commit 4270304

File tree

5 files changed

+258
-19
lines changed

5 files changed

+258
-19
lines changed

src/app.jsx

+2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import Search from './pages/search';
4646
import StatusRoute from './pages/status-route';
4747
import Trending from './pages/trending';
4848
import Welcome from './pages/welcome';
49+
import AnnualReport from './pages/annual-report';
4950
import {
5051
api,
5152
hasInstance,
@@ -546,6 +547,7 @@ function SecondaryRoutes({ isLoggedIn }) {
546547
<Route path="/fh" element={<FollowedHashtags />} />
547548
<Route path="/ft" element={<Filters />} />
548549
<Route path="/catchup" element={<Catchup />} />
550+
<Route path="/annual_report/:year" element={<AnnualReport />} />
549551
</>
550552
)}
551553
<Route path="/:instance?/t/:hashtag" element={<Hashtag />} />

src/components/notification.jsx

+13
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,9 @@ const contentText = {
261261
),
262262
emoji_reaction: emojiText,
263263
'pleroma:emoji_reaction': emojiText,
264+
annual_report: ({ year }) => (
265+
<Trans>Your {year} #Wrapstodon is here!</Trans>
266+
),
264267
};
265268

266269
// account_suspension, domain_block, user_domain_block
@@ -312,6 +315,7 @@ function Notification({
312315
report,
313316
event,
314317
moderation_warning,
318+
annualReport,
315319
// Client-side grouped notification
316320
_ids,
317321
_accounts,
@@ -409,6 +413,10 @@ function Notification({
409413
emoji: notification.emoji,
410414
emojiURL,
411415
});
416+
} else if (type === 'annual_report') {
417+
text = text({
418+
...notification.annualReport,
419+
});
412420
} else {
413421
text = text({
414422
account: account ? (
@@ -527,6 +535,11 @@ function Notification({
527535
</a>
528536
</div>
529537
)}
538+
{type === 'annual_report' && (
539+
<div>
540+
<Link to={`/annual_report/${annualReport?.year}`}><Trans>View #Wrapstodon</Trans></Link>
541+
</div>
542+
)}
530543
</>
531544
)}
532545
{_accounts?.length > 1 && (

src/locales/en.po

+29-19
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/pages/annual-report.css

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#annual-report-page {
2+
.report {
3+
background-color: var(--bg-color);
4+
border: 16px ridge var(--bg-faded-color);
5+
box-shadow: 0 0 0 2px var(--bg-color);
6+
padding: 16px;
7+
margin: 80px auto;
8+
max-width: var(--main-width);
9+
font-family: var(--monospace-font);
10+
font-variant-numeric: slashed-zero;
11+
font-feature-settings: 'ss01';
12+
font-variant-numeric: tabular-nums;
13+
min-height: 80vh;
14+
15+
h1 {
16+
margin: 0;
17+
padding: 0;
18+
}
19+
20+
dt {
21+
font-weight: bold;
22+
font-size: larger;
23+
}
24+
25+
dd {
26+
margin: 0 0 2em;
27+
padding: 0;
28+
overflow: auto;
29+
}
30+
31+
table {
32+
width: 100%;
33+
34+
td, th {
35+
vertical-align: top;
36+
}
37+
38+
th {
39+
font-weight: normal;
40+
text-align: start;
41+
color: var(--text-insignificant-color);
42+
text-transform: uppercase;
43+
}
44+
45+
tr > * {
46+
border-top: 1px dashed var(--outline-color);
47+
}
48+
}
49+
50+
.report-topStatuses {
51+
dt {
52+
font-size: var(--text-size);
53+
}
54+
55+
dd {
56+
margin-block-end: 1em;
57+
58+
> a {
59+
display: block;
60+
color: inherit;
61+
text-decoration: none;
62+
border: 2px dashed var(--outline-stronger-color);
63+
64+
&:is(:hover, :focus) {
65+
border-color: var(--text-color);
66+
}
67+
}
68+
69+
.status {
70+
pointer-events: none;
71+
font-size: calc(var(--text-size) * .8);
72+
}
73+
}
74+
75+
}
76+
}
77+
}

src/pages/annual-report.jsx

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { t, Trans } from '@lingui/macro';
2+
3+
import './annual-report.css';
4+
5+
import { useEffect, useState } from 'preact/hooks';
6+
import { useParams } from 'react-router-dom';
7+
8+
import Link from '../components/link';
9+
import Loader from '../components/loader';
10+
import NameText from '../components/name-text';
11+
import Status from '../components/status';
12+
import { api } from '../utils/api';
13+
import useTitle from '../utils/useTitle';
14+
15+
export default function AnnualReport() {
16+
const params = useParams();
17+
const { year } = params;
18+
useTitle(year ? `Annual Report: ${year}` : 'Annual Report');
19+
const { masto, instance } = api();
20+
const [results, setResults] = useState(null);
21+
const [uiState, setUIState] = useState('default');
22+
23+
useEffect(() => {
24+
if (year) {
25+
(async () => {
26+
setUIState('loading');
27+
const results = await masto.v1.annualReports.$select(year).fetch();
28+
console.log('REPORT', results);
29+
setResults(results);
30+
setUIState('default');
31+
})();
32+
}
33+
}, [year]);
34+
35+
const { accounts, annualReports, statuses } = results || {};
36+
const report = annualReports?.find((report) => report.year == year)?.data;
37+
38+
return (
39+
<div id="annual-report-page" class="deck-container" tabIndex="-1">
40+
<div class="report">
41+
<h1>{year} #Wrapstodon</h1>
42+
{uiState === 'loading' && (
43+
<p>
44+
<Loader abrupt /> <Trans>Loading…</Trans>
45+
</p>
46+
)}
47+
{!!report && (
48+
<dl>
49+
{Object.entries(report).map(([key, value]) => (
50+
<>
51+
<dt>{key}</dt>
52+
<dd class={`report-${key}`}>
53+
{Array.isArray(value) ? (
54+
<table>
55+
<thead>
56+
<tr>
57+
{Object.keys(value[0]).map((key) => (
58+
<th>{key}</th>
59+
))}
60+
</tr>
61+
</thead>
62+
<tbody>
63+
{value.map((item) => (
64+
<tr>
65+
{Object.entries(item).map(([k, value]) => (
66+
<td>
67+
{value && /(accountId)/i.test(k) &&
68+
/^(mostRebloggedAccounts|commonlyInteractedWithAccounts)$/i.test(
69+
key,
70+
) ? (
71+
<NameText
72+
account={accounts?.find(
73+
(a) => a.id === value,
74+
)}
75+
showAvatar
76+
/>
77+
) : (
78+
value
79+
)}
80+
</td>
81+
))}
82+
</tr>
83+
))}
84+
</tbody>
85+
</table>
86+
) : typeof value === 'object' ? (
87+
/^(topStatuses)$/i.test(key) ? (
88+
<dl>
89+
{Object.entries(value).map(([k, value]) => (
90+
<>
91+
<dt>{k}</dt>
92+
<dd>
93+
{value &&
94+
<Link to={`/${instance}/s/${value}`}>
95+
<Status
96+
status={statuses?.find((s) => s.id === value)}
97+
size="s"
98+
readOnly
99+
/>
100+
</Link>}
101+
</dd>
102+
</>
103+
))}
104+
</dl>
105+
) : (
106+
<table>
107+
<tbody>
108+
{Object.entries(value).map(([k, value]) => (
109+
<tr>
110+
<th>{k}</th>
111+
<td>{value}</td>
112+
</tr>
113+
))}
114+
</tbody>
115+
</table>
116+
)
117+
) : typeof value === 'string' ? (
118+
value
119+
) : (
120+
// Last resort
121+
JSON.stringify(value, null, 2)
122+
)}
123+
</dd>
124+
</>
125+
))}
126+
</dl>
127+
)}
128+
</div>
129+
<hr />
130+
<p style={{ textAlign: 'center' }}>
131+
<Link to="/">
132+
<Trans>Go home</Trans>
133+
</Link>
134+
</p>
135+
</div>
136+
);
137+
}

0 commit comments

Comments
 (0)