diff --git a/src/app.jsx b/src/app.jsx
index 8d95cc34..e4b89603 100644
--- a/src/app.jsx
+++ b/src/app.jsx
@@ -46,6 +46,7 @@ import Search from './pages/search';
import StatusRoute from './pages/status-route';
import Trending from './pages/trending';
import Welcome from './pages/welcome';
+import AnnualReport from './pages/annual-report';
import {
api,
hasInstance,
@@ -546,6 +547,7 @@ function SecondaryRoutes({ isLoggedIn }) {
} />
} />
} />
+ } />
>
)}
} />
diff --git a/src/components/notification.jsx b/src/components/notification.jsx
index dc864bbf..6c497643 100644
--- a/src/components/notification.jsx
+++ b/src/components/notification.jsx
@@ -261,6 +261,9 @@ const contentText = {
),
emoji_reaction: emojiText,
'pleroma:emoji_reaction': emojiText,
+ annual_report: ({ year }) => (
+ Your {year} #Wrapstodon is here!
+ ),
};
// account_suspension, domain_block, user_domain_block
@@ -312,6 +315,7 @@ function Notification({
report,
event,
moderation_warning,
+ annualReport,
// Client-side grouped notification
_ids,
_accounts,
@@ -409,6 +413,10 @@ function Notification({
emoji: notification.emoji,
emojiURL,
});
+ } else if (type === 'annual_report') {
+ text = text({
+ ...notification.annualReport,
+ });
} else {
text = text({
account: account ? (
@@ -527,6 +535,11 @@ function Notification({
)}
+ {type === 'annual_report' && (
+
+ View #Wrapstodon
+
+ )}
>
)}
{_accounts?.length > 1 && (
diff --git a/src/locales/en.po b/src/locales/en.po
index 238dc806..06003a63 100644
--- a/src/locales/en.po
+++ b/src/locales/en.po
@@ -963,7 +963,7 @@ msgid "Nothing to show"
msgstr ""
#: src/components/generic-accounts.jsx:145
-#: src/components/notification.jsx:438
+#: src/components/notification.jsx:446
#: src/pages/accounts.jsx:41
#: src/pages/search.jsx:317
#: src/pages/search.jsx:350
@@ -1472,74 +1472,82 @@ msgstr ""
msgid "Moderation warning"
msgstr ""
-#: src/components/notification.jsx:269
+#: src/components/notification.jsx:265
+msgid "Your {year} #Wrapstodon is here!"
+msgstr "Your {year} #Wrapstodon is here!"
+
+#: src/components/notification.jsx:272
msgid "An admin from <0>{from}0> has suspended <1>{targetName}1>, which means you can no longer receive updates from them or interact with them."
msgstr ""
-#: src/components/notification.jsx:275
+#: src/components/notification.jsx:278
msgid "An admin from <0>{from}0> has blocked <1>{targetName}1>. Affected followers: {followersCount}, followings: {followingCount}."
msgstr ""
-#: src/components/notification.jsx:281
+#: src/components/notification.jsx:284
msgid "You have blocked <0>{targetName}0>. Removed followers: {followersCount}, followings: {followingCount}."
msgstr ""
-#: src/components/notification.jsx:289
+#: src/components/notification.jsx:292
msgid "Your account has received a moderation warning."
msgstr ""
-#: src/components/notification.jsx:290
+#: src/components/notification.jsx:293
msgid "Your account has been disabled."
msgstr ""
-#: src/components/notification.jsx:291
+#: src/components/notification.jsx:294
msgid "Some of your posts have been marked as sensitive."
msgstr ""
-#: src/components/notification.jsx:292
+#: src/components/notification.jsx:295
msgid "Some of your posts have been deleted."
msgstr ""
-#: src/components/notification.jsx:293
+#: src/components/notification.jsx:296
msgid "Your posts will be marked as sensitive from now on."
msgstr ""
-#: src/components/notification.jsx:294
+#: src/components/notification.jsx:297
msgid "Your account has been limited."
msgstr ""
-#: src/components/notification.jsx:295
+#: src/components/notification.jsx:298
msgid "Your account has been suspended."
msgstr ""
-#: src/components/notification.jsx:369
+#: src/components/notification.jsx:373
msgid "[Unknown notification type: {type}]"
msgstr ""
-#: src/components/notification.jsx:434
+#: src/components/notification.jsx:442
#: src/components/status.jsx:1036
#: src/components/status.jsx:1046
msgid "Boosted/Liked by…"
msgstr ""
-#: src/components/notification.jsx:435
+#: src/components/notification.jsx:443
msgid "Liked by…"
msgstr ""
-#: src/components/notification.jsx:436
+#: src/components/notification.jsx:444
msgid "Boosted by…"
msgstr ""
-#: src/components/notification.jsx:437
+#: src/components/notification.jsx:445
msgid "Followed by…"
msgstr ""
-#: src/components/notification.jsx:508
-#: src/components/notification.jsx:524
+#: src/components/notification.jsx:516
+#: src/components/notification.jsx:532
msgid "Learn more <0/>"
msgstr ""
-#: src/components/notification.jsx:756
+#: src/components/notification.jsx:540
+msgid "View #Wrapstodon"
+msgstr "View #Wrapstodon"
+
+#: src/components/notification.jsx:769
#: src/components/status.jsx:267
msgid "Read more →"
msgstr ""
@@ -2260,6 +2268,7 @@ msgid "Failed to load history"
msgstr ""
#: src/components/status.jsx:3006
+#: src/pages/annual-report.jsx:44
msgid "Loading…"
msgstr ""
@@ -2401,6 +2410,7 @@ msgid "Login required."
msgstr "Login required."
#: src/compose.jsx:90
+#: src/pages/annual-report.jsx:132
#: src/pages/http-route.jsx:91
#: src/pages/login.jsx:270
msgid "Go home"
diff --git a/src/pages/annual-report.css b/src/pages/annual-report.css
new file mode 100644
index 00000000..be765301
--- /dev/null
+++ b/src/pages/annual-report.css
@@ -0,0 +1,77 @@
+#annual-report-page {
+ .report {
+ background-color: var(--bg-color);
+ border: 16px ridge var(--bg-faded-color);
+ box-shadow: 0 0 0 2px var(--bg-color);
+ padding: 16px;
+ margin: 80px auto;
+ max-width: var(--main-width);
+ font-family: var(--monospace-font);
+ font-variant-numeric: slashed-zero;
+ font-feature-settings: 'ss01';
+ font-variant-numeric: tabular-nums;
+ min-height: 80vh;
+
+ h1 {
+ margin: 0;
+ padding: 0;
+ }
+
+ dt {
+ font-weight: bold;
+ font-size: larger;
+ }
+
+ dd {
+ margin: 0 0 2em;
+ padding: 0;
+ overflow: auto;
+ }
+
+ table {
+ width: 100%;
+
+ td, th {
+ vertical-align: top;
+ }
+
+ th {
+ font-weight: normal;
+ text-align: start;
+ color: var(--text-insignificant-color);
+ text-transform: uppercase;
+ }
+
+ tr > * {
+ border-top: 1px dashed var(--outline-color);
+ }
+ }
+
+ .report-topStatuses {
+ dt {
+ font-size: var(--text-size);
+ }
+
+ dd {
+ margin-block-end: 1em;
+
+ > a {
+ display: block;
+ color: inherit;
+ text-decoration: none;
+ border: 2px dashed var(--outline-stronger-color);
+
+ &:is(:hover, :focus) {
+ border-color: var(--text-color);
+ }
+ }
+
+ .status {
+ pointer-events: none;
+ font-size: calc(var(--text-size) * .8);
+ }
+ }
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/pages/annual-report.jsx b/src/pages/annual-report.jsx
new file mode 100644
index 00000000..ab7d1c75
--- /dev/null
+++ b/src/pages/annual-report.jsx
@@ -0,0 +1,137 @@
+import { t, Trans } from '@lingui/macro';
+
+import './annual-report.css';
+
+import { useEffect, useState } from 'preact/hooks';
+import { useParams } from 'react-router-dom';
+
+import Link from '../components/link';
+import Loader from '../components/loader';
+import NameText from '../components/name-text';
+import Status from '../components/status';
+import { api } from '../utils/api';
+import useTitle from '../utils/useTitle';
+
+export default function AnnualReport() {
+ const params = useParams();
+ const { year } = params;
+ useTitle(year ? `Annual Report: ${year}` : 'Annual Report');
+ const { masto, instance } = api();
+ const [results, setResults] = useState(null);
+ const [uiState, setUIState] = useState('default');
+
+ useEffect(() => {
+ if (year) {
+ (async () => {
+ setUIState('loading');
+ const results = await masto.v1.annualReports.$select(year).fetch();
+ console.log('REPORT', results);
+ setResults(results);
+ setUIState('default');
+ })();
+ }
+ }, [year]);
+
+ const { accounts, annualReports, statuses } = results || {};
+ const report = annualReports?.find((report) => report.year == year)?.data;
+
+ return (
+
+
+
{year} #Wrapstodon
+ {uiState === 'loading' && (
+
+ Loading…
+
+ )}
+ {!!report && (
+
+ {Object.entries(report).map(([key, value]) => (
+ <>
+ - {key}
+ -
+ {Array.isArray(value) ? (
+
+
+
+ {Object.keys(value[0]).map((key) => (
+ {key} |
+ ))}
+
+
+
+ {value.map((item) => (
+
+ {Object.entries(item).map(([k, value]) => (
+
+ {value && /(accountId)/i.test(k) &&
+ /^(mostRebloggedAccounts|commonlyInteractedWithAccounts)$/i.test(
+ key,
+ ) ? (
+ a.id === value,
+ )}
+ showAvatar
+ />
+ ) : (
+ value
+ )}
+ |
+ ))}
+
+ ))}
+
+
+ ) : typeof value === 'object' ? (
+ /^(topStatuses)$/i.test(key) ? (
+
+ {Object.entries(value).map(([k, value]) => (
+ <>
+ - {k}
+ -
+ {value &&
+
+ s.id === value)}
+ size="s"
+ readOnly
+ />
+ }
+
+ >
+ ))}
+
+ ) : (
+
+
+ {Object.entries(value).map(([k, value]) => (
+
+ {k} |
+ {value} |
+
+ ))}
+
+
+ )
+ ) : typeof value === 'string' ? (
+ value
+ ) : (
+ // Last resort
+ JSON.stringify(value, null, 2)
+ )}
+
+ >
+ ))}
+
+ )}
+
+
+
+
+ Go home
+
+
+
+ );
+}