;; Notification components (defcomp ~notifications/preview (&key (preview :as string)) (div :class "text-sm text-stone-500 mt-1 truncate" preview)) (defcomp ~notifications/card (&key (cls :as string) avatar (from-name :as string) (from-username :as string) (from-domain :as string) (action-text :as string) preview (time :as string)) (div :class cls (div :class "flex items-start gap-3" avatar (div :class "flex-1" (div :class "text-sm" (span :class "font-semibold" from-name) " " (span :class "text-stone-500" "@" from-username from-domain) " " (span :class "text-stone-600" action-text)) preview (div :class "text-xs text-stone-400 mt-1" time))))) (defcomp ~notifications/list (&key (items :as list)) (div :class "space-y-2" items)) (defcomp ~notifications/page (&key notifs) (h1 :class "text-2xl font-bold mb-6" "Notifications") notifs) ;; Assembled notification card — replaces Python _notification_sx (defcomp ~notifications/from-data (&key (notif :as dict)) (let* ((from-name (or (get notif "from_actor_name") "?")) (from-username (or (get notif "from_actor_username") "")) (from-domain (or (get notif "from_actor_domain") "")) (from-icon (get notif "from_actor_icon")) (ntype (or (get notif "notification_type") "")) (preview (get notif "target_content_preview")) (created (or (get notif "created_at_formatted") "")) (read (get notif "read")) (app-domain (or (get notif "app_domain") "")) (border (if (not read) " border-l-4 border-l-stone-400" "")) (initial (if (and (not from-icon) from-name) (upper (slice from-name 0 1)) "?")) (action-text (cond ((= ntype "follow") (str "followed you" (if (and app-domain (!= app-domain "federation")) (str " on " (escape app-domain)) ""))) ((= ntype "like") "liked your post") ((= ntype "boost") "boosted your post") ((= ntype "mention") "mentioned you") ((= ntype "reply") "replied to your post") (true "")))) (~notifications/card :cls (str "bg-white rounded-lg shadow-sm border border-stone-200 p-4" border) :avatar (~shared:misc/avatar :src from-icon :cls (if from-icon "w-8 h-8 rounded-full" "w-8 h-8 rounded-full bg-stone-300 flex items-center justify-center text-stone-600 font-bold text-xs") :initial (when (not from-icon) initial)) :from-name (escape from-name) :from-username (escape from-username) :from-domain (if from-domain (str "@" (escape from-domain)) "") :action-text action-text :preview (when preview (~notifications/preview :preview (escape preview))) :time created))) ;; Assembled notifications content — replaces Python _notifications_content_sx (defcomp ~notifications/content (&key (notifications :as list)) (~notifications/page :notifs (if (empty? notifications) (~shared:misc/empty-state :message "No notifications yet." :cls "text-stone-500") (~notifications/list :items (map (lambda (n) (~notifications/from-data :notif n)) notifications)))))