126 lines
3.7 KiB
TypeScript
126 lines
3.7 KiB
TypeScript
import { defineRouteConfig } from "@medusajs/admin-sdk"
|
|
import { EnvelopeSolid } from "@medusajs/icons"
|
|
import { Badge, Button, Container, Heading, Table, Text } from "@medusajs/ui"
|
|
import { useEffect, useState } from "react"
|
|
|
|
type Subscriber = {
|
|
id: string
|
|
email: string
|
|
status: "subscribed" | "unsubscribed"
|
|
source: string | null
|
|
created_at: string
|
|
}
|
|
|
|
const PAGE_SIZE = 25
|
|
|
|
const NewsletterPage = () => {
|
|
const [subscribers, setSubscribers] = useState<Subscriber[]>([])
|
|
const [count, setCount] = useState(0)
|
|
const [offset, setOffset] = useState(0)
|
|
const [isLoading, setIsLoading] = useState(true)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
useEffect(() => {
|
|
setIsLoading(true)
|
|
fetch(
|
|
`/admin/newsletter/subscribers?limit=${PAGE_SIZE}&offset=${offset}`,
|
|
{ credentials: "include" }
|
|
)
|
|
.then((res) => {
|
|
if (!res.ok) throw new Error(`Request failed (${res.status})`)
|
|
return res.json()
|
|
})
|
|
.then((data) => {
|
|
setSubscribers(data.subscribers ?? [])
|
|
setCount(data.count ?? 0)
|
|
setError(null)
|
|
})
|
|
.catch((err) => setError(err.message))
|
|
.finally(() => setIsLoading(false))
|
|
}, [offset])
|
|
|
|
const pageCount = Math.max(Math.ceil(count / PAGE_SIZE), 1)
|
|
const page = Math.floor(offset / PAGE_SIZE) + 1
|
|
|
|
return (
|
|
<Container className="divide-y p-0">
|
|
<div className="flex items-center justify-between px-6 py-4">
|
|
<div>
|
|
<Heading>Newsletter</Heading>
|
|
<Text size="small" className="text-ui-fg-subtle">
|
|
{count} subscriber{count === 1 ? "" : "s"} signed up from the
|
|
storefront footer.
|
|
</Text>
|
|
</div>
|
|
<Button variant="secondary" asChild>
|
|
<a href="/admin/newsletter/subscribers?format=csv" download>
|
|
Export CSV
|
|
</a>
|
|
</Button>
|
|
</div>
|
|
|
|
{error && (
|
|
<Text className="px-6 py-4 text-ui-fg-error">
|
|
Could not load subscribers: {error}
|
|
</Text>
|
|
)}
|
|
|
|
<Table>
|
|
<Table.Header>
|
|
<Table.Row>
|
|
<Table.HeaderCell>Email</Table.HeaderCell>
|
|
<Table.HeaderCell>Status</Table.HeaderCell>
|
|
<Table.HeaderCell>Source</Table.HeaderCell>
|
|
<Table.HeaderCell>Date</Table.HeaderCell>
|
|
</Table.Row>
|
|
</Table.Header>
|
|
<Table.Body>
|
|
{!isLoading && subscribers.length === 0 && (
|
|
<Table.Row>
|
|
<Table.Cell>No subscribers yet.</Table.Cell>
|
|
<Table.Cell />
|
|
<Table.Cell />
|
|
<Table.Cell />
|
|
</Table.Row>
|
|
)}
|
|
{subscribers.map((s) => (
|
|
<Table.Row key={s.id}>
|
|
<Table.Cell>{s.email}</Table.Cell>
|
|
<Table.Cell>
|
|
<Badge
|
|
size="2xsmall"
|
|
color={s.status === "subscribed" ? "green" : "grey"}
|
|
>
|
|
{s.status}
|
|
</Badge>
|
|
</Table.Cell>
|
|
<Table.Cell>{s.source ?? "-"}</Table.Cell>
|
|
<Table.Cell>
|
|
{new Date(s.created_at).toLocaleString()}
|
|
</Table.Cell>
|
|
</Table.Row>
|
|
))}
|
|
</Table.Body>
|
|
</Table>
|
|
|
|
<Table.Pagination
|
|
count={count}
|
|
pageSize={PAGE_SIZE}
|
|
pageIndex={page - 1}
|
|
pageCount={pageCount}
|
|
canPreviousPage={offset > 0}
|
|
canNextPage={offset + PAGE_SIZE < count}
|
|
previousPage={() => setOffset(Math.max(offset - PAGE_SIZE, 0))}
|
|
nextPage={() => setOffset(offset + PAGE_SIZE)}
|
|
/>
|
|
</Container>
|
|
)
|
|
}
|
|
|
|
export const config = defineRouteConfig({
|
|
label: "Newsletter",
|
|
icon: EnvelopeSolid,
|
|
})
|
|
|
|
export default NewsletterPage
|