diff --git a/sites/org/pages/admin/index.mjs b/sites/org/pages/admin/index.mjs index 3dfb673ad80..155950600df 100644 --- a/sites/org/pages/admin/index.mjs +++ b/sites/org/pages/admin/index.mjs @@ -16,6 +16,23 @@ import { SearchIcon } from 'shared/components/icons.mjs' // Translation namespaces used on this page const namespaces = nsMerge(pageNs, authNs) +const AdminMenu = () => ( + +) + const AdminPage = ({ page }) => { const { t } = useTranslation(namespaces) const backend = useBackend() @@ -39,25 +56,26 @@ const AdminPage = ({ page }) => { return ( -

- Other admin links: - -

-
Search users
-
- 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" + /> + +
+ + + + + + + + + + {hits.map((hit, i) => ( + + + + + + ))} + +
EmailLanguageUnsubscribe
+ {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 */