-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor BuiltByBit notifications and alerts system
- Loading branch information
1 parent
e4319ce
commit 43ca23e
Showing
10 changed files
with
394 additions
and
73 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
import { useEffect, useState } from "react"; | ||
import { Alert, AlertType, ContentType, ContentTypeNames, ContentTypeURLMap } from "./types/alert"; | ||
import { AlertsService } from "./services/alertsService"; | ||
import { showFailureToast, useCachedPromise } from "@raycast/utils"; | ||
import { UserUtils } from "./utils/userUtils"; | ||
import { Color, Icon, MenuBarExtra, open, showToast, Toast } from "@raycast/api"; | ||
import { formatRelativeDate } from "./utils/dateUtils"; | ||
|
||
export default function Command() { | ||
const [enrichedAlerts, setEnrichedAlerts] = useState<Alert[]>([]); | ||
|
||
const { | ||
data: alertResponse, | ||
isLoading, | ||
revalidate, | ||
} = useCachedPromise(() => AlertsService.fetchAlerts(), [], { | ||
keepPreviousData: true, | ||
initialData: [], | ||
}); | ||
|
||
useEffect(() => { | ||
async function enrichAlerts() { | ||
const alerts = Array.isArray(alertResponse) ? alertResponse : []; | ||
if (alerts.length === 0) return; | ||
|
||
// Filter out read alerts first | ||
const unreadAlerts = alerts.filter((alert) => !alert.read); | ||
const enriched = [...unreadAlerts]; | ||
|
||
// Process alerts in batches | ||
const batchSize = 5; | ||
for (let i = 0; i < enriched.length; i += batchSize) { | ||
const batch = enriched.slice(i, i + batchSize); | ||
|
||
await Promise.all( | ||
batch.map(async (alert, index) => { | ||
try { | ||
if (alert.caused_member_id !== 0) { | ||
const username = await UserUtils.idToUsername(alert.caused_member_id); | ||
enriched[i + index] = { ...alert, username }; | ||
} | ||
} catch (error) { | ||
console.error(`Error fetching username for ID ${alert.caused_member_id}:`, error); | ||
} | ||
}), | ||
); | ||
} | ||
setEnrichedAlerts(enriched); | ||
} | ||
enrichAlerts(); | ||
}, [alertResponse]); | ||
|
||
const handleMarkAllAsRead = async () => { | ||
try { | ||
const success = await AlertsService.markAllAsRead(); | ||
if (success) { | ||
await showToast(Toast.Style.Success, "Marked all notifications as read"); | ||
revalidate(); | ||
} else { | ||
await showFailureToast("Failed to mark notifications as read", { | ||
title: "Failed to mark notifications as read", | ||
}); | ||
} | ||
} catch (error) { | ||
await showFailureToast(error, { title: "Error marking notifications as read", message: String(error) }); | ||
} | ||
}; | ||
|
||
const getAlertMessage = (alert: Alert) => { | ||
const username = alert.username || `User #${alert.caused_member_id}`; | ||
const contentTypeName = ContentTypeNames[alert.content_type as ContentType] || "content"; | ||
|
||
switch (alert.alert_type) { | ||
case AlertType.REACTION: | ||
return `${username} reacted to your ${contentTypeName}`; | ||
case AlertType.REPLY: | ||
return `${username} replied to your ${contentTypeName}`; | ||
case AlertType.TICKET_MOVED: | ||
return `Your ticket has been moved`; | ||
default: | ||
return `New notification from ${username}`; | ||
} | ||
}; | ||
|
||
const getContentUrl = (alert: Alert) => { | ||
const baseUrl = ContentTypeURLMap[alert.content_type as ContentType]; | ||
return `${baseUrl}/${alert.content_id}`; | ||
}; | ||
|
||
const handleRefresh = async () => { | ||
await UserUtils.clearCache(); | ||
revalidate(); | ||
}; | ||
|
||
const unreadCount = enrichedAlerts.filter((a) => !a.read).length; | ||
|
||
return ( | ||
<MenuBarExtra | ||
icon={ | ||
unreadCount > 0 | ||
? { source: "../assets/bbb-icon.png" } | ||
: { source: "../assets/bbb-icon.png", tintColor: Color.SecondaryText } | ||
} | ||
title={unreadCount > 0 ? String(unreadCount) : "0"} | ||
isLoading={isLoading} | ||
> | ||
<MenuBarExtra.Section title={unreadCount > 0 ? "Notifications" : "No Unread Notifications"}> | ||
<MenuBarExtra.Item | ||
title="View All Alerts" | ||
shortcut={{ modifiers: ["cmd", "shift"], key: "o" }} | ||
onAction={() => open("https://builtbybit.com/account/alerts")} | ||
/> | ||
|
||
{enrichedAlerts.map((alert, index) => ( | ||
<MenuBarExtra.Item | ||
key={index} | ||
title={getAlertMessage(alert)} | ||
onAction={() => { | ||
open(getContentUrl(alert)); | ||
alert.read = true; | ||
revalidate(); | ||
}} | ||
subtitle={formatRelativeDate(alert.alert_date)} | ||
/> | ||
))} | ||
<MenuBarExtra.Item | ||
title="Mark All as Read" | ||
icon={Icon.CheckCircle} | ||
onAction={handleMarkAllAsRead} | ||
shortcut={{ modifiers: ["cmd"], key: "enter" }} | ||
/> | ||
</MenuBarExtra.Section> | ||
<MenuBarExtra.Section> | ||
<MenuBarExtra.Item | ||
title="Refresh" | ||
icon={Icon.ArrowClockwise} | ||
shortcut={{ modifiers: ["cmd"], key: "r" }} | ||
onAction={handleRefresh} | ||
/> | ||
<MenuBarExtra.Item | ||
title="Clear User Cache" | ||
icon={Icon.Trash} | ||
onAction={async () => { | ||
await UserUtils.clearCache(); | ||
await showToast(Toast.Style.Success, "User cache cleared"); | ||
}} | ||
shortcut={{ modifiers: ["cmd", "shift"], key: "x" }} | ||
/> | ||
</MenuBarExtra.Section> | ||
</MenuBarExtra> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { showFailureToast } from "@raycast/utils"; | ||
import { Alert } from "../types/alert"; | ||
import apiClient from "../utils/constants"; | ||
|
||
export class AlertsService { | ||
public static async fetchAlerts(): Promise<Alert[]> { | ||
try { | ||
console.log("Fetching alerts at", new Date().toISOString()); | ||
const response = await apiClient.get("/alerts"); | ||
console.log(response.data); | ||
return response.data?.data; | ||
} catch (error) { | ||
console.error("Error fetching alerts:", error); | ||
await showFailureToast(error, { title: "Error fetching alerts." }); | ||
throw error; | ||
} | ||
} | ||
|
||
public static async markAllAsRead(): Promise<boolean> { | ||
try { | ||
const response = await apiClient.post("/alerts", { read: true }); | ||
return response.status >= 200 && response.status < 300; | ||
} catch (error) { | ||
console.error("Error marking alerts as read:", error); | ||
await showFailureToast(error, { title: "Error marking alerts as read." }); | ||
throw error; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.