Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions backend/src/handlers/notifications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Router } from "express";
import platformAPIClient from "../services/platformAPIClient";

export default function mountNotificationEndpoints(router: Router) {
router.post("/send", async (req, res) => {
try {
const { notifications } = req.body || {};

if (!Array.isArray(notifications) || notifications.length === 0) {
return res
.status(400)
.json({ message: "notifications array is required" });
}

// Forward to Platform API: POST /v2/in_app_notifications/notify
const response = await platformAPIClient.post(
"/v2/in_app_notifications/notify",
{ notifications }
);

return res.status(200).json(response.data);
} catch (err: any) {
const status = err?.response?.status || 500;
const data = err?.response?.data || {
message: "Failed to send notifications",
};
return res.status(status).json(data);
}
});
}
7 changes: 7 additions & 0 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import mountUserEndpoints from './handlers/users';
// have no problem here)
// https://stackoverflow.com/questions/65108033/property-user-does-not-exist-on-type-session-partialsessiondata#comment125163548_65381085
import "./types/session";
import mountNotificationEndpoints from './handlers/notifications';

const dbName = env.mongo_db_name;
const mongoUri = `mongodb://${env.mongo_host}/${dbName}`;
Expand Down Expand Up @@ -81,6 +82,12 @@ const userRouter = express.Router();
mountUserEndpoints(userRouter);
app.use('/user', userRouter);


// Notification endpoints under /notifications:
const notificationRouter = express.Router();
mountNotificationEndpoints(notificationRouter);
app.use("/notifications", notificationRouter);

// Hello World page to check everything works:
app.get('/', async (_, res) => {
res.status(200).send({ message: "Hello, World!" });
Expand Down
13 changes: 11 additions & 2 deletions frontend/src/Shop/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { User } from "../";
interface Props {
onSignIn: () => void;
onSignOut: () => void;
user: User | null
onSendTestNotification: () => void;
user: User | null;
}

const headerStyle: CSSProperties = {
Expand All @@ -27,7 +28,15 @@ export default function Header(props: Props) {
<button onClick={props.onSignIn}>Sign in</button>
) : (
<div>
@{props.user.username} <button type="button" onClick={props.onSignOut}>Sign out</button>
@{props.user.username}{" "}
<button type="button" onClick={props.onSignOut}>
Sign out
</button>
{props.user.roles.includes("core_team") && (
<button onClick={props.onSendTestNotification}>
Send Test Notification to yourself
</button>
)}
</div>
)}
</div>
Expand Down
21 changes: 18 additions & 3 deletions frontend/src/Shop/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ type AuthResult = {
accessToken: string,
user: {
uid: string,
username: string
username: string,
roles: string[],
}
};

Expand Down Expand Up @@ -58,7 +59,7 @@ export default function Shop() {
const [showModal, setShowModal] = useState<boolean>(false);

const signIn = async () => {
const scopes = ['username', 'payments'];
const scopes = ['username', 'payments', 'roles', 'in_app_notifications'];
const authResult: AuthResult = await window.Pi.authenticate(scopes, onIncompletePaymentFound);
signInUser(authResult);
setUser(authResult.user);
Expand All @@ -69,6 +70,20 @@ export default function Shop() {
signOutUser();
}

const onSendTestNotification = () => {
const notification = {
title: "Test Notification",
body: "This is a test notification",
user_uid: user?.uid,
subroute: "/shop"
};
axiosClient.post(
"/notifications/send",
{ notifications: [notification] },
config
);
};

const signInUser = (authResult: AuthResult) => {
axiosClient.post('/user/signin', {authResult});
return setShowModal(false);
Expand Down Expand Up @@ -127,7 +142,7 @@ export default function Shop() {

return (
<>
<Header user={user} onSignIn={signIn} onSignOut={signOut}/>
<Header user={user} onSignIn={signIn} onSignOut={signOut} onSendTestNotification={onSendTestNotification}/>

<ProductCard
name="Apple Pie"
Expand Down