Skip to content

Commit

Permalink
admin: add mailing list
Browse files Browse the repository at this point in the history
  • Loading branch information
NickSavage committed Jan 3, 2025
1 parent 281ca63 commit 739ad4f
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 21 deletions.
4 changes: 2 additions & 2 deletions go-backend/handlers/mail.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ type Subscriber struct {

func (s *Handler) GetAllSubscribers() ([]models.MailingList, error) {
query := `
SELECT id, email, welcome_email_sent, created_at, updated_at
SELECT id, email, welcome_email_sent, created_at, updated_at, subscribed
FROM mailing_list
ORDER BY id DESC
`
Expand All @@ -62,7 +62,7 @@ func (s *Handler) GetAllSubscribers() ([]models.MailingList, error) {
var subscribers []models.MailingList
for rows.Next() {
var sub models.MailingList
if err := rows.Scan(&sub.ID, &sub.Email, &sub.WelcomeEmailSent, &sub.CreatedAt, &sub.UpdatedAt); err != nil {
if err := rows.Scan(&sub.ID, &sub.Email, &sub.WelcomeEmailSent, &sub.CreatedAt, &sub.UpdatedAt, &sub.Subscribed); err != nil {
log.Printf("Error scanning subscriber row: %v", err)
return nil, fmt.Errorf("error scanning subscriber row: %v", err)
}
Expand Down
1 change: 1 addition & 0 deletions go-backend/models/mailing_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ type MailingList struct {
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
WelcomeEmailSent bool `json:"welcome_email_sent"`
Subscribed bool `json:"subscribed"`
}
26 changes: 26 additions & 0 deletions zettelkasten-front/src/api/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,29 @@ export function addToMailingList(email: string): Promise<{ email: string }> {
}
});
}

export interface MailingListSubscriber {
id: number;
email: string;
welcome_email_sent: boolean;
subscribed: boolean;
created_at: string;
updated_at: string;
}

export function getMailingListSubscribers(): Promise<MailingListSubscriber[]> {
const url = `${base_url}/mailing-list`;
let token = localStorage.getItem("token");

return fetch(url, {
headers: { Authorization: `Bearer ${token}` },
})
.then(checkStatus)
.then((response) => {
if (response) {
return response.json() as Promise<MailingListSubscriber[]>;
} else {
return Promise.reject(new Error("Response is undefined"));
}
});
}
74 changes: 74 additions & 0 deletions zettelkasten-front/src/pages/admin/AdminMailingListPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, { useState, useEffect } from "react";
import { getMailingListSubscribers, MailingListSubscriber } from "../../api/users";

export function AdminMailingListPage() {
const [subscribers, setSubscribers] = useState<MailingListSubscriber[]>([]);

useEffect(() => {
const fetchSubscribers = async () => {
try {
const data = await getMailingListSubscribers();
setSubscribers(data);
} catch (error) {
console.error("Error fetching subscribers:", error);
}
};
fetchSubscribers();
}, []);

return (
<div className="container mx-auto px-4">
<h1 className="text-2xl font-bold mb-4">Mailing List Subscribers</h1>
<div className="overflow-x-auto">
<table className="min-w-full bg-white shadow-md rounded">
<thead className="bg-gray-800 text-white">
<tr>
<th className="py-2 px-4 text-left">ID</th>
<th className="py-2 px-4 text-left">Email</th>
<th className="py-2 px-4 text-left">Status</th>
<th className="py-2 px-4 text-left">Welcome Email</th>
<th className="py-2 px-4 text-left">Created At</th>
<th className="py-2 px-4 text-left">Updated At</th>
</tr>
</thead>
<tbody>
{subscribers.map((subscriber) => (
<tr key={subscriber.id} className="border-b hover:bg-gray-100">
<td className="py-2 px-4">{subscriber.id}</td>
<td className="py-2 px-4">{subscriber.email}</td>
<td className="py-2 px-4">
<span
className={`px-2 py-1 rounded text-sm ${
subscriber.subscribed
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
}`}
>
{subscriber.subscribed ? "Subscribed" : "Unsubscribed"}
</span>
</td>
<td className="py-2 px-4">
<span
className={`px-2 py-1 rounded text-sm ${
subscriber.welcome_email_sent
? "bg-blue-100 text-blue-800"
: "bg-yellow-100 text-yellow-800"
}`}
>
{subscriber.welcome_email_sent ? "Sent" : "Pending"}
</span>
</td>
<td className="py-2 px-4">
{new Date(subscriber.created_at).toLocaleString()}
</td>
<td className="py-2 px-4">
{new Date(subscriber.updated_at).toLocaleString()}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}
40 changes: 21 additions & 19 deletions zettelkasten-front/src/pages/admin/AdminPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { useAuth } from "../../contexts/AuthContext";
import { useNavigate, Link } from "react-router-dom";

import { AdminUserIndex } from "./AdminUserIndex";

import { AdminUserDetailPage } from "./AdminUserDetailPage";
import { AdminEditUserPage } from "./AdminEditUserPage";
import { AdminMailingListPage } from "./AdminMailingListPage";

import { Routes, Route } from "react-router-dom";

Expand All @@ -27,24 +27,26 @@ export function Admin() {
}

return (
<div className="flex h-screen">
<div className="flex flex-1">
<div className="w-64 bg-gray-800 text-white p-4">
<ul>
<li>
<Link to="/admin" className="block py-2 px-4 hover:bg-gray-700">Index</Link>
<Link to="/app" className="block py-2 px-4 hover:bg-gray-700">Back to App</Link>
</li>
</ul>
</div>
<div className="flex-1 p-4">
<Routes>
<Route path="/" element={<AdminUserIndex />} />
<Route path="user/:id" element={<AdminUserDetailPage />} />
<Route path="user/:id/edit" element={<AdminEditUserPage />} />
</Routes>
<div className="flex h-screen">
<div className="flex flex-1">
<div className="w-64 bg-gray-800 text-white p-4">
<ul>
<li>
<Link to="/admin" className="block py-2 px-4 hover:bg-gray-700">Users</Link>
<Link to="/admin/mailing-list" className="block py-2 px-4 hover:bg-gray-700">Mailing List</Link>
<Link to="/app" className="block py-2 px-4 hover:bg-gray-700">Back to App</Link>
</li>
</ul>
</div>
<div className="flex-1 p-4">
<Routes>
<Route path="/" element={<AdminUserIndex />} />
<Route path="user/:id" element={<AdminUserDetailPage />} />
<Route path="user/:id/edit" element={<AdminEditUserPage />} />
<Route path="mailing-list" element={<AdminMailingListPage />} />
</Routes>
</div>
</div>
</div>
</div>
</div>
);
}

0 comments on commit 739ad4f

Please sign in to comment.