227 lines
5.8 KiB
TypeScript
227 lines
5.8 KiB
TypeScript
|
|
import {
|
||
|
|
DeliveryTruck01Icon,
|
||
|
|
Mail01Icon,
|
||
|
|
Message01Icon,
|
||
|
|
PackageIcon,
|
||
|
|
} from "@hugeicons/core-free-icons"
|
||
|
|
import type { QueryFunctionContext } from "@tanstack/react-query"
|
||
|
|
|
||
|
|
import type { Notification } from "@workspace/blocks/notifications"
|
||
|
|
|
||
|
|
const RANDOM_USER_API_URL =
|
||
|
|
"https://randomuser.me/api/1.4/?results=4&inc=name,email,picture&nat=au,ca,de,fr,gb,nl,nz,us&noinfo"
|
||
|
|
|
||
|
|
interface RandomUser {
|
||
|
|
email: string
|
||
|
|
name: {
|
||
|
|
first: string
|
||
|
|
last: string
|
||
|
|
}
|
||
|
|
picture: {
|
||
|
|
medium: string
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const HOUR_IN_MILLISECONDS = 60 * 60 * 1000
|
||
|
|
|
||
|
|
function getCreatedAt(hoursAgo: number) {
|
||
|
|
return new Date(Date.now() - hoursAgo * HOUR_IN_MILLISECONDS).toISOString()
|
||
|
|
}
|
||
|
|
|
||
|
|
export const fallbackNotifications: readonly Notification[] = [
|
||
|
|
{
|
||
|
|
id: "friend-request-deja-brady",
|
||
|
|
title: "Deja Brady sent you a friend request",
|
||
|
|
createdAt: getCreatedAt(11),
|
||
|
|
category: "Communication",
|
||
|
|
isRead: false,
|
||
|
|
media: {
|
||
|
|
kind: "avatar",
|
||
|
|
src: "https://pub-c5e31b5cdafb419fb247a8ac2e78df7a.r2.dev/public/assets/images/mock/avatar/avatar-2.webp",
|
||
|
|
alt: "Deja Brady",
|
||
|
|
},
|
||
|
|
actions: [
|
||
|
|
{ id: "accept", label: "Accept" },
|
||
|
|
{ id: "decline", label: "Decline", intent: "neutral" },
|
||
|
|
],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "mention-minimal-ui",
|
||
|
|
title: "Jayvon Hull mentioned you in Minimal UI",
|
||
|
|
createdAt: getCreatedAt(24),
|
||
|
|
category: "Project UI",
|
||
|
|
to: "/workspace/tasks",
|
||
|
|
isRead: false,
|
||
|
|
media: {
|
||
|
|
kind: "avatar",
|
||
|
|
src: "https://pub-c5e31b5cdafb419fb247a8ac2e78df7a.r2.dev/public/assets/images/mock/avatar/avatar-3.webp",
|
||
|
|
alt: "Jayvon Hull",
|
||
|
|
},
|
||
|
|
actions: [{ id: "view", label: "View", intent: "neutral" }],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "order-placed",
|
||
|
|
title: "Your order is placed",
|
||
|
|
description: "The order is confirmed and waiting for shipping.",
|
||
|
|
createdAt: getCreatedAt(24 * 6),
|
||
|
|
category: "Order",
|
||
|
|
isRead: true,
|
||
|
|
media: {
|
||
|
|
kind: "icon",
|
||
|
|
icon: PackageIcon,
|
||
|
|
tone: "warning",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "order-shipping",
|
||
|
|
title: "Delivery in progress",
|
||
|
|
description: "Your order has left the warehouse and is being shipped.",
|
||
|
|
createdAt: getCreatedAt(24 * 7),
|
||
|
|
category: "Order",
|
||
|
|
isRead: true,
|
||
|
|
media: {
|
||
|
|
kind: "icon",
|
||
|
|
icon: DeliveryTruck01Icon,
|
||
|
|
tone: "success",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "unread-messages",
|
||
|
|
title: "You have 5 unread messages",
|
||
|
|
createdAt: getCreatedAt(24 * 8),
|
||
|
|
category: "Communication",
|
||
|
|
to: "/workspace/tasks",
|
||
|
|
isRead: true,
|
||
|
|
media: {
|
||
|
|
kind: "icon",
|
||
|
|
icon: Message01Icon,
|
||
|
|
tone: "info",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "new-mail",
|
||
|
|
title: "You have new mail",
|
||
|
|
createdAt: getCreatedAt(24 * 9),
|
||
|
|
category: "Communication",
|
||
|
|
isRead: true,
|
||
|
|
media: {
|
||
|
|
kind: "icon",
|
||
|
|
icon: Mail01Icon,
|
||
|
|
tone: "destructive",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
]
|
||
|
|
|
||
|
|
export const notificationsQueryKey = ["notifications"] as const
|
||
|
|
|
||
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||
|
|
return typeof value === "object" && value !== null
|
||
|
|
}
|
||
|
|
|
||
|
|
function isRandomUser(value: unknown): value is RandomUser {
|
||
|
|
if (!isRecord(value) || !isRecord(value.name) || !isRecord(value.picture)) {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
typeof value.email === "string" &&
|
||
|
|
typeof value.name.first === "string" &&
|
||
|
|
typeof value.name.last === "string" &&
|
||
|
|
typeof value.picture.medium === "string"
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
function getUserName(user: RandomUser) {
|
||
|
|
return `${user.name.first} ${user.name.last}`
|
||
|
|
}
|
||
|
|
|
||
|
|
function getAvatarMedia(user: RandomUser) {
|
||
|
|
return {
|
||
|
|
alt: getUserName(user),
|
||
|
|
kind: "avatar" as const,
|
||
|
|
src: user.picture.medium,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function createRandomUserNotifications(
|
||
|
|
users: readonly RandomUser[]
|
||
|
|
): readonly Notification[] {
|
||
|
|
const [friend, mention, commenter, collaborator] = users
|
||
|
|
|
||
|
|
if (!friend || !mention || !commenter || !collaborator) {
|
||
|
|
throw new Error("Random User API returned too few users")
|
||
|
|
}
|
||
|
|
|
||
|
|
return [
|
||
|
|
{
|
||
|
|
id: `friend-request:${friend.email}`,
|
||
|
|
title: `${getUserName(friend)} sent you a friend request`,
|
||
|
|
createdAt: getCreatedAt(11),
|
||
|
|
category: "Communication",
|
||
|
|
isRead: false,
|
||
|
|
media: getAvatarMedia(friend),
|
||
|
|
actions: [
|
||
|
|
{ id: "accept", label: "Accept" },
|
||
|
|
{ id: "decline", label: "Decline", intent: "neutral" },
|
||
|
|
],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: `mention:${mention.email}`,
|
||
|
|
title: `${getUserName(mention)} mentioned you in Minimal UI`,
|
||
|
|
createdAt: getCreatedAt(24),
|
||
|
|
category: "Project UI",
|
||
|
|
to: "/workspace/tasks",
|
||
|
|
isRead: false,
|
||
|
|
media: getAvatarMedia(mention),
|
||
|
|
actions: [{ id: "view", label: "View", intent: "neutral" }],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: `comment:${commenter.email}`,
|
||
|
|
title: `${getUserName(commenter)} commented on your task`,
|
||
|
|
description: "Can you review the latest changes?",
|
||
|
|
createdAt: getCreatedAt(24 * 2),
|
||
|
|
category: "Project UI",
|
||
|
|
to: "/workspace/tasks",
|
||
|
|
isRead: false,
|
||
|
|
media: getAvatarMedia(commenter),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: `collaborator:${collaborator.email}`,
|
||
|
|
title: `${getUserName(collaborator)} joined your workspace`,
|
||
|
|
createdAt: getCreatedAt(24 * 4),
|
||
|
|
category: "Team",
|
||
|
|
isRead: true,
|
||
|
|
media: getAvatarMedia(collaborator),
|
||
|
|
},
|
||
|
|
...fallbackNotifications.slice(2),
|
||
|
|
]
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function fetchMockNotifications({
|
||
|
|
signal,
|
||
|
|
}: QueryFunctionContext): Promise<readonly Notification[]> {
|
||
|
|
try {
|
||
|
|
const response = await fetch(RANDOM_USER_API_URL, { signal })
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
throw new Error(`Random User API responded with ${response.status}`)
|
||
|
|
}
|
||
|
|
|
||
|
|
const payload: unknown = await response.json()
|
||
|
|
|
||
|
|
if (!isRecord(payload) || !Array.isArray(payload.results)) {
|
||
|
|
throw new Error("Random User API returned an invalid response")
|
||
|
|
}
|
||
|
|
|
||
|
|
const users = payload.results.filter(isRandomUser)
|
||
|
|
|
||
|
|
return createRandomUserNotifications(users)
|
||
|
|
} catch (error) {
|
||
|
|
if (signal.aborted) {
|
||
|
|
throw error
|
||
|
|
}
|
||
|
|
|
||
|
|
return fallbackNotifications
|
||
|
|
}
|
||
|
|
}
|