mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-07-10 00:00:03 +02:00
Show recent badge awards on badges page
This commit is contained in:
5
.changeset/mighty-pans-reflect.md
Normal file
5
.changeset/mighty-pans-reflect.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"nostrudel": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Show recent badge awards on badges page
|
@ -23,7 +23,7 @@ export default function useUserProfileBadges(pubkey: string, additionalRelays: s
|
|||||||
const badge = badges.find((e) => getEventCoordinate(e) === p.badgeCord);
|
const badge = badges.find((e) => getEventCoordinate(e) === p.badgeCord);
|
||||||
const award = awardEvents.find((e) => e.id === p.awardEventId);
|
const award = awardEvents.find((e) => e.id === p.awardEventId);
|
||||||
|
|
||||||
if (badge && award) final.push({ badge, award });
|
if (badge && award && badge.pubkey === award.pubkey) final.push({ badge, award });
|
||||||
}
|
}
|
||||||
|
|
||||||
return final;
|
return final;
|
||||||
|
@ -19,7 +19,6 @@ import { NostrEvent } from "../../types/nostr-event";
|
|||||||
import { getEventCoordinate } from "../../helpers/nostr/events";
|
import { getEventCoordinate } from "../../helpers/nostr/events";
|
||||||
import { UserAvatarLink } from "../../components/user-avatar-link";
|
import { UserAvatarLink } from "../../components/user-avatar-link";
|
||||||
import { UserLink } from "../../components/user-link";
|
import { UserLink } from "../../components/user-link";
|
||||||
import { ErrorBoundary } from "../../components/error-boundary";
|
|
||||||
import Timestamp from "../../components/timestamp";
|
import Timestamp from "../../components/timestamp";
|
||||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||||
|
|
||||||
@ -70,7 +69,7 @@ function BadgeDetailsPage({ badge }: { badge: NostrEvent }) {
|
|||||||
|
|
||||||
<Flex direction={{ base: "column", lg: "row" }} gap="2">
|
<Flex direction={{ base: "column", lg: "row" }} gap="2">
|
||||||
{image && <Image src={image.src} maxW="3in" mr="2" mb="2" mx={{ base: "auto", lg: "initial" }} />}
|
{image && <Image src={image.src} maxW="3in" mr="2" mb="2" mx={{ base: "auto", lg: "initial" }} />}
|
||||||
<Flex direction="column" gap="2">
|
<Flex direction="column">
|
||||||
<Heading size="md">{getBadgeName(badge)}</Heading>
|
<Heading size="md">{getBadgeName(badge)}</Heading>
|
||||||
<Text>
|
<Text>
|
||||||
Created by: <UserAvatarLink pubkey={badge.pubkey} size="xs" />{" "}
|
Created by: <UserAvatarLink pubkey={badge.pubkey} size="xs" />{" "}
|
||||||
@ -79,15 +78,21 @@ function BadgeDetailsPage({ badge }: { badge: NostrEvent }) {
|
|||||||
<Text>
|
<Text>
|
||||||
Created: <Timestamp timestamp={badge.created_at} />
|
Created: <Timestamp timestamp={badge.created_at} />
|
||||||
</Text>
|
</Text>
|
||||||
{description && <Text pb="2">{description}</Text>}
|
{description && (
|
||||||
|
<>
|
||||||
|
<Heading size="md" mt="2">
|
||||||
|
Description
|
||||||
|
</Heading>
|
||||||
|
<Text pb="2">{description}</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{awards.length > 0 && (
|
{awards.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<IntersectionObserverProvider callback={callback}>
|
<IntersectionObserverProvider callback={callback}>
|
||||||
<Heading size="md">Awarded to</Heading>
|
<Heading size="lg">Awarded to</Heading>
|
||||||
<Divider />
|
|
||||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||||
{awards.map((award) => (
|
{awards.map((award) => (
|
||||||
<>
|
<>
|
||||||
|
@ -1,12 +1,90 @@
|
|||||||
import { Button, Flex, Image, Link, Spacer } from "@chakra-ui/react";
|
import { useRef } from "react";
|
||||||
|
import {
|
||||||
|
AvatarGroup,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Flex,
|
||||||
|
Heading,
|
||||||
|
Image,
|
||||||
|
Link,
|
||||||
|
LinkBox,
|
||||||
|
LinkOverlay,
|
||||||
|
Spacer,
|
||||||
|
Text,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
import { Navigate, Link as RouterLink } from "react-router-dom";
|
import { Navigate, Link as RouterLink } from "react-router-dom";
|
||||||
|
import { Kind } from "nostr-tools";
|
||||||
|
|
||||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
|
||||||
import { ExternalLinkIcon } from "../../components/icons";
|
import { ExternalLinkIcon } from "../../components/icons";
|
||||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||||
|
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||||
|
import PeopleListProvider, { usePeopleListContext } from "../../providers/people-list-provider";
|
||||||
|
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
|
||||||
|
import { useReadRelayUrls } from "../../hooks/use-client-relays";
|
||||||
|
import useSubject from "../../hooks/use-subject";
|
||||||
|
import { NostrEvent, isPTag } from "../../types/nostr-event";
|
||||||
|
import { UserLink } from "../../components/user-link";
|
||||||
|
import { UserAvatar } from "../../components/user-avatar";
|
||||||
|
import { getBadgeAwardBadge, getBadgeImage, getBadgeName } from "../../helpers/nostr/badges";
|
||||||
|
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
||||||
|
import IntersectionObserverProvider, { useRegisterIntersectionEntity } from "../../providers/intersection-observer";
|
||||||
|
import { getEventUID } from "../../helpers/nostr/events";
|
||||||
|
import { getSharableEventAddress } from "../../helpers/nip19";
|
||||||
|
import { UserAvatarLink } from "../../components/user-avatar-link";
|
||||||
|
import Timestamp from "../../components/timestamp";
|
||||||
|
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||||
|
|
||||||
|
function BadgeAwardCard({ award }: { award: NostrEvent }) {
|
||||||
|
const badge = useReplaceableEvent(getBadgeAwardBadge(award));
|
||||||
|
|
||||||
|
// if there is a parent intersection observer, register this card
|
||||||
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
|
useRegisterIntersectionEntity(ref, badge && getEventUID(badge));
|
||||||
|
|
||||||
|
if (!badge) return null;
|
||||||
|
|
||||||
|
const naddr = getSharableEventAddress(badge);
|
||||||
|
return (
|
||||||
|
<Card p="2" variant="outline" gap="2" flexDirection={["column", null, "row"]} ref={ref}>
|
||||||
|
<Flex as={LinkBox} direction="column" overflow="hidden" gap="2" w="40" mx="auto">
|
||||||
|
<Image aspectRatio={1} src={getBadgeImage(badge)?.src ?? ""} w="40" />
|
||||||
|
<Heading size="sm" isTruncated>
|
||||||
|
<LinkOverlay as={RouterLink} to={`/badges/${naddr}`}>
|
||||||
|
{getBadgeName(badge)}
|
||||||
|
</LinkOverlay>
|
||||||
|
</Heading>
|
||||||
|
</Flex>
|
||||||
|
<Flex gap="2" direction="column" flex={1}>
|
||||||
|
<Flex gap="2" alignItems="center">
|
||||||
|
<UserAvatar pubkey={award.pubkey} size="sm" />
|
||||||
|
<UserLink pubkey={award.pubkey} fontWeight="bold" />
|
||||||
|
<Text>Awarded:</Text>
|
||||||
|
<Spacer />
|
||||||
|
<Timestamp timestamp={award.created_at} />
|
||||||
|
</Flex>
|
||||||
|
<Flex gap="2" wrap="wrap">
|
||||||
|
{award.tags.filter(isPTag).map((t) => (
|
||||||
|
<Flex key={t[1]} gap="2" alignItems="center">
|
||||||
|
<UserAvatarLink pubkey={t[1]} size="sm" />
|
||||||
|
<UserLink pubkey={t[1]} fontWeight="bold" />
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function BadgesPage() {
|
function BadgesPage() {
|
||||||
const account = useCurrentAccount()!;
|
const { filter, listId } = usePeopleListContext();
|
||||||
|
const readRelays = useReadRelayUrls();
|
||||||
|
const timeline = useTimelineLoader(`${listId}-lists`, readRelays, {
|
||||||
|
...filter,
|
||||||
|
kinds: [Kind.BadgeAward],
|
||||||
|
});
|
||||||
|
|
||||||
|
const awards = useSubject(timeline.timeline);
|
||||||
|
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VerticalPageLayout>
|
<VerticalPageLayout>
|
||||||
@ -25,11 +103,25 @@ function BadgesPage() {
|
|||||||
Badges
|
Badges
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<Flex gap="2" alignItems="center">
|
||||||
|
<Heading size="lg">Recent awards</Heading>
|
||||||
|
<PeopleListSelection />
|
||||||
|
</Flex>
|
||||||
|
<IntersectionObserverProvider callback={callback}>
|
||||||
|
{awards.map((award) => (
|
||||||
|
<BadgeAwardCard key={award.id} award={award} />
|
||||||
|
))}
|
||||||
|
</IntersectionObserverProvider>
|
||||||
</VerticalPageLayout>
|
</VerticalPageLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BadgesView() {
|
export default function BadgesView() {
|
||||||
const account = useCurrentAccount();
|
// const account = useCurrentAccount();
|
||||||
return account ? <BadgesPage /> : <Navigate to="/lists/browse" />;
|
// return account ? <BadgesPage /> : <Navigate to="/lists/browse" />;
|
||||||
|
return (
|
||||||
|
<PeopleListProvider initList="global">
|
||||||
|
<BadgesPage />
|
||||||
|
</PeopleListProvider>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,7 @@ function Badge({ pubkey, badge, award }: { pubkey: string; badge: NostrEvent; aw
|
|||||||
<Image w="14" h="14" src={getBadgeImage(badge)?.src ?? ""} />
|
<Image w="14" h="14" src={getBadgeImage(badge)?.src ?? ""} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Modal isOpen={modal.isOpen} onClose={modal.onClose}>
|
<Modal isOpen={modal.isOpen} onClose={modal.onClose}>
|
||||||
<ModalOverlay />
|
<ModalOverlay />
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
|
Reference in New Issue
Block a user