-
setQ(evt.target.value)}
- className="input w-full input-bordered flex flex-row"
- type="text"
- placeholder="Username, ID, or E-mail address"
- />
-
+
+
+
Search users
+
+ setQ(evt.target.value)}
+ className="input w-full input-bordered flex flex-row"
+ type="text"
+ placeholder="Username, ID, or E-mail address"
+ />
+
+
+ {loading ?
:
}
+
+
- {loading ?
:
}
)
diff --git a/sites/org/pages/admin/subscribers.mjs b/sites/org/pages/admin/subscribers.mjs
new file mode 100644
index 00000000000..503e86d3231
--- /dev/null
+++ b/sites/org/pages/admin/subscribers.mjs
@@ -0,0 +1,113 @@
+// Dependencies
+import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
+import { nsMerge, getSearchParam } from 'shared/utils.mjs'
+// Hooks
+import { useTranslation } from 'next-i18next'
+import { useState, useEffect } from 'react'
+import { useBackend } from 'shared/hooks/use-backend.mjs'
+// Components
+import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
+import { AuthWrapper, ns as authNs } from 'shared/components/wrappers/auth/index.mjs'
+import { SearchIcon } from 'shared/components/icons.mjs'
+
+// Translation namespaces used on this page
+const ns = nsMerge(pageNs, authNs)
+
+const SubscribersPage = ({ page }) => {
+ const { t } = useTranslation(ns)
+ const [subscribers, setSubscribers] = useState()
+ const [q, setQ] = useState()
+ const [hits, setHits] = useState([])
+ const backend = useBackend()
+
+ const loadSubscribers = async () => {
+ const result = await backend.adminLoadSubscribers()
+ if (result.success) setSubscribers(result.data.subscribers)
+ }
+
+ const search = async () => {
+ if (!subscribers) await loadSubscribers()
+ const found = []
+ for (const lang in subscribers) {
+ found.push(
+ ...subscribers[lang]
+ .filter((sub) => sub.email.toLowerCase().includes(q.toLowerCase()))
+ .map((sub) => ({ ...sub, lang }))
+ )
+ }
+ setHits(found)
+ }
+
+ const unsubscribe = async (ehash) => {
+ await backend.newsletterUnsubscribe(ehash)
+ await loadSubscribers()
+ await search()
+ }
+
+ return (
+
+
+ {subscribers ? (
+ <>
+ Search subscribers
+
+ setQ(evt.target.value)}
+ className="input w-full input-bordered flex flex-row"
+ type="text"
+ placeholder="Username, ID, or E-mail address"
+ />
+
+
+
+
+
+ Email |
+ Language |
+ Unsubscribe |
+
+
+
+ {hits.map((hit, i) => (
+
+
+ {hit.email}
+ |
+ {hit.lang.toUpperCase()} |
+
+
+ |
+
+ ))}
+
+
+ >
+ ) : (
+
+ )}
+
+
+ )
+}
+
+export default SubscribersPage
+
+export async function getStaticProps({ locale }) {
+ return {
+ props: {
+ ...(await serverSideTranslations(locale, ns)),
+ page: {
+ locale,
+ path: ['admin', 'subscribers'],
+ },
+ },
+ }
+}
diff --git a/sites/shared/hooks/use-backend.mjs b/sites/shared/hooks/use-backend.mjs
index 3ebb7c32792..67572f8d85e 100644
--- a/sites/shared/hooks/use-backend.mjs
+++ b/sites/shared/hooks/use-backend.mjs
@@ -597,6 +597,14 @@ Backend.prototype.adminImpersonateUser = async function (id) {
return responseHandler(await api.get(`/admin/impersonate/${id}/jwt`, this.auth))
}
+/*
+ * Load newsletter subscribers (admin method)
+ */
+Backend.prototype.adminLoadSubscribers = async function () {
+ console.log(this.auth)
+ return responseHandler(await api.get(`/admin/subscribers/jwt`, this.auth))
+}
+
/*
* Verify an admin account while impersonating another user
*/