feat: community leaders
continuous-integration/drone/push Build is failing Details

This commit is contained in:
Kieran 2023-12-18 15:57:00 +00:00
parent 457cba32a7
commit 7523b41610
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
14 changed files with 192 additions and 11 deletions

View File

@ -16,7 +16,8 @@
"subscriptions": true,
"deck": true,
"zapPool": true,
"notificationGraph": true
"notificationGraph": true,
"communityLeaders": true
},
"signUp": {
"moderation": true,
@ -26,6 +27,9 @@
"bypassImgProxyError": false,
"preferLargeMedia": true
},
"communityLeaders": {
"list": "naddr1qq4xc6tnw3ez6vp58y6rywpjxckngdtyxukngwr9vckkze33vcknzcnrxcenje35xqmn2cczyp3lucccm3v9s087z6qslpkap8schltk427zfgqgrn3g2menq5zw6qcyqqq82vqprpmhxue69uhhyetvv9ujuumwdae8gtnnda3kjctv7rajfl"
},
"noteCreatorToast": true,
"hideFromNavbar": ["/graph"],
"deckSubKind": 1,

View File

@ -16,7 +16,8 @@
"subscriptions": false,
"deck": true,
"zapPool": true,
"notificationGraph": false
"notificationGraph": false,
"communityLeaders": false
},
"signUp": {
"moderation": false,

View File

@ -58,6 +58,10 @@ declare const CONFIG: {
deck: boolean;
zapPool: boolean;
notificationGraph: boolean;
communityLeaders: boolean;
};
defaultPreferences: {
checkSigs: boolean;
};
signUp: {
moderation: boolean;
@ -67,6 +71,9 @@ declare const CONFIG: {
bypassImgProxyError: boolean;
preferLargeMedia: boolean;
};
communityLeaders?: {
list: string;
};
// Filter urls from nav sidebar
hideFromNavbar: Array<string>;
// Limit deck to certain subscriber tier

View File

@ -0,0 +1,54 @@
export default function AwardIcon({ size }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 62 62" fill="none" className="award">
<defs>
<linearGradient
id="paint0_linear_2660_40043"
x1="31"
y1="3.57143"
x2="31"
y2="58.4286"
gradientUnits="userSpaceOnUse">
<stop stop-color="#5B2CB3" />
<stop offset="1" stop-color="#811EFF" />
</linearGradient>
<linearGradient
id="paint1_linear_2660_40043"
x1="15.5594"
y1="24.305"
x2="46.433"
y2="24.305"
gradientUnits="userSpaceOnUse">
<stop stop-color="#AC88FF" />
<stop offset="1" stop-color="#7234FF" />
</linearGradient>
</defs>
<g id="award-02">
<rect x="1.85713" y="1.85714" width="58.2857" height="58.2857" rx="29.1429" fill="#AC88FF" fill-opacity="0.2" />
<rect
x="1.85713"
y="1.85714"
width="58.2857"
height="58.2857"
rx="29.1429"
stroke="url(#paint0_linear_2660_40043)"
strokeWidth="3.42857"
/>
<path
id="Solid"
d="M23.2006 52.4983L22.5639 50.9066L23.2006 52.4983L30.9963 49.38L38.7919 52.4983C39.8813 52.934 41.116 52.801 42.0876 52.1432C43.0592 51.4854 43.6412 50.3885 43.6412 49.2151V38.1015C46.467 35.038 48.1957 30.9408 48.1957 26.4427C48.1957 16.9437 40.4952 9.24329 30.9963 9.24329C21.4973 9.24329 13.7968 16.9437 13.7968 26.4427C13.7968 30.9408 15.5255 35.038 18.3513 38.1015V49.2151C18.3513 50.3885 18.9333 51.4854 19.9049 52.1432C20.8765 52.801 22.1112 52.934 23.2006 52.4983ZM27.2967 43.2429L25.4234 43.9922V42.7187C26.0332 42.9275 26.6584 43.1029 27.2967 43.2429ZM34.6958 43.2429C35.3341 43.1029 35.9593 42.9275 36.5691 42.7187V43.9922L34.6958 43.2429Z"
fill="url(#paint1_linear_2660_40043)"
stroke="#251250"
strokeWidth="3.42857"
strokeLinecap="round"
/>
<path
id="Ellipse 1595"
d="M24.2557 14.6002C17.7766 18.3409 15.5567 26.6257 19.2974 33.1049L42.7604 19.5585C39.0196 13.0794 30.7348 10.8595 24.2557 14.6002Z"
fill="white"
fill-opacity="0.1"
/>
</g>
</svg>
);
}

View File

@ -0,0 +1,48 @@
import { useState } from "react";
import { FormattedMessage } from "react-intl";
import AwardIcon from "./Award";
import Modal from "../Modal";
import { Link } from "react-router-dom";
import CloseButton from "../Button/CloseButton";
export function LeaderBadge() {
const [showModal, setShowModal] = useState(false);
return (
<>
<div
className="flex gap-1 p-1 pr-2 items-center border border-[#5B2CB3] rounded-full"
onClick={e => {
e.preventDefault();
e.stopPropagation();
setShowModal(true);
}}>
<AwardIcon size={16} />
<div className="text-xs font-medium text-[#AC88FF]">
<FormattedMessage defaultMessage="Community Leader" id="7YkSA2" />
</div>
</div>
{showModal && (
<Modal onClose={() => setShowModal(false)} id="leaders">
<div className="flex flex-col gap-4 items-center relative">
<CloseButton className="absolute right-2 top-2" onClick={() => setShowModal(false)} />
<AwardIcon size={80} />
<div className="text-3xl font-semibold">
<FormattedMessage defaultMessage="Community Leader" id="7YkSA2" />
</div>
<p className="text-secondary">
<FormattedMessage
defaultMessage="Community leaders are individuals who grow the nostr ecosystem by being active in their local communities and helping onboard new users. Anyone can become a community leader, but few hold the current honorary title."
id="f1OxTe"
/>
</p>
<Link to="/settings/invite">
<button className="primary">
<FormattedMessage defaultMessage="Become a leader" id="M6C/px" />
</button>
</Link>
</div>
</Modal>
)}
</>
);
}

View File

@ -336,6 +336,7 @@ export function NoteInner(props: NoteProps) {
subHeader={replyTag() ?? undefined}
link={opt?.canClick === undefined ? undefined : ""}
showProfileCard={options.showProfileCard ?? true}
showBadges={true}
/>
<div className="info">
{props.context}

View File

@ -19,10 +19,6 @@ a.pfp {
text-decoration: none;
}
.pfp .username {
font-weight: 600;
}
.pfp .profile-name {
max-width: stretch;
max-width: -webkit-fill-available;

View File

@ -10,6 +10,8 @@ import DisplayName from "./DisplayName";
import { ProfileLink } from "./ProfileLink";
import { ProfileCard } from "./ProfileCard";
import FollowDistanceIndicator from "@/Element/User/FollowDistanceIndicator";
import { useCommunityLeader } from "@/Hooks/useCommunityLeaders";
import { LeaderBadge } from "@/Element/CommunityLeaders/LeaderBadge";
export interface ProfileImageProps {
pubkey: HexKey;
@ -27,6 +29,7 @@ export interface ProfileImageProps {
showFollowDistance?: boolean;
icons?: ReactNode;
showProfileCard?: boolean;
showBadges?: boolean;
}
export default function ProfileImage({
@ -43,9 +46,11 @@ export default function ProfileImage({
showFollowDistance = true,
icons,
showProfileCard = false,
showBadges = false,
}: ProfileImageProps) {
const user = useUserProfile(profile ? "" : pubkey) ?? profile;
const [isHovering, setIsHovering] = useState(false);
const leader = useCommunityLeader(pubkey);
const hoverTimeoutRef = useRef<NodeJS.Timeout | null>(null);
@ -88,8 +93,9 @@ export default function ProfileImage({
</div>
{showUsername && (
<div className="f-ellipsis">
<div className="flex g4 username">
<div className="flex gap-2 items-center font-medium">
{overrideUsername ? overrideUsername : <DisplayName pubkey={pubkey} user={user} />}
{leader && showBadges && CONFIG.features.communityLeaders && <LeaderBadge />}
</div>
<div className="subheader">{subHeader}</div>
</div>

View File

@ -0,0 +1,41 @@
import { ExternalStore, unwrap } from "@snort/shared";
import { EventKind, parseNostrLink } from "@snort/system";
import { useLinkList } from "./useLists";
import { useEffect, useSyncExternalStore } from "react";
class CommunityLeadersStore extends ExternalStore<Array<string>> {
#leaders: Array<string> = [];
setLeaders(arr: Array<string>) {
this.#leaders = arr;
this.notifyChange();
}
takeSnapshot(): string[] {
return [...this.#leaders];
}
}
const LeadersStore = new CommunityLeadersStore();
export function useCommunityLeaders() {
const link = parseNostrLink(unwrap(CONFIG.communityLeaders).list);
const list = useLinkList("leaders", rb => {
rb.withFilter().kinds([EventKind.FollowSet]).link(link);
});
useEffect(() => {
console.debug("CommunityLeaders", list);
LeadersStore.setLeaders(list.map(a => a.id));
}, [list]);
}
export function useCommunityLeader(pubkey?: string) {
const store = useSyncExternalStore(
c => LeadersStore.hook(c),
() => LeadersStore.snapshot(),
);
return pubkey && store.includes(pubkey);
}

View File

@ -17,6 +17,7 @@ import ErrorBoundary from "@/Element/ErrorBoundary";
import Footer from "@/Pages/Layout/Footer";
import { Header } from "@/Pages/Layout/Header";
import CloseButton from "@/Element/Button/CloseButton";
import { useCommunityLeaders } from "@/Hooks/useCommunityLeaders";
export default function Index() {
const location = useLocation();
@ -25,6 +26,9 @@ export default function Index() {
useTheme();
useLoginRelays();
useLoginFeed();
if (CONFIG.features.communityLeaders) {
useCommunityLeaders();
}
const hideHeaderPaths = ["/login", "/new"];
const shouldHideFooter = location.pathname.startsWith("/messages/");

View File

@ -166,10 +166,6 @@
border: none;
}
.qr-modal .pfp .username {
align-items: center;
}
.qr-modal canvas {
border-radius: 10px;
}

View File

@ -1,3 +1,4 @@
import { LeaderBadge } from "@/Element/CommunityLeaders/LeaderBadge";
import Copy from "@/Element/Copy";
import SnortApi from "@/External/SnortApi";
import useEventPublisher from "@/Hooks/useEventPublisher";
@ -37,6 +38,16 @@ export function ReferralsPage() {
<div className="border border-zinc-900 rounded-2xl px-3 py-2">
<Copy text={`https://${window.location.host}?ref=${refCode}`} maxSize={Number.MAX_VALUE} />
</div>
<h2>
<FormattedMessage defaultMessage="Become a leader" id="M6C/px" />
</h2>
<div className="flex">
<LeaderBadge />
</div>
<p>
<FormattedMessage defaultMessage="Coming soon" id="e61Jf3" />
</p>
</>
);
}

View File

@ -264,6 +264,9 @@
"7UOvbT": {
"defaultMessage": "Offline"
},
"7YkSA2": {
"defaultMessage": "Community Leader"
},
"7hp70g": {
"defaultMessage": "NIP-05"
},
@ -682,6 +685,9 @@
"M3Oirc": {
"defaultMessage": "Debug Menus"
},
"M6C/px": {
"defaultMessage": "Become a leader"
},
"MBAYRO": {
"defaultMessage": "Shows \"Copy ID\" and \"Copy Event JSON\" in the context menu on each message"
},
@ -1140,6 +1146,9 @@
"egib+2": {
"defaultMessage": "{n,plural,=1{& {n} other} other{& {n} others}}"
},
"f1OxTe": {
"defaultMessage": "Community leaders are individuals who grow the nostr ecosystem by being active in their local communities and helping onboard new users. Anyone can become a community leader, but few hold the current honorary title."
},
"fBI91o": {
"defaultMessage": "Zap"
},

View File

@ -87,6 +87,7 @@
"7+Domh": "Notes",
"712i26": "Proxy uses HODL invoices to forward the payment, which hides the pubkey of your node",
"7UOvbT": "Offline",
"7YkSA2": "Community Leader",
"7hp70g": "NIP-05",
"8/vBbP": "Reposts ({n})",
"89q5wc": "Confirm Reposts",
@ -225,6 +226,7 @@
"Lw+I+J": "{n,plural,=0{{name} zapped} other{{name} & {n} others zapped}}",
"LwYmVi": "Zaps on this note will be split to the following users.",
"M3Oirc": "Debug Menus",
"M6C/px": "Become a leader",
"MBAYRO": "Shows \"Copy ID\" and \"Copy Event JSON\" in the context menu on each message",
"MI2jkA": "Not available:",
"MP54GY": "Wallet password",
@ -375,6 +377,7 @@
"eSzf2G": "A single zap of {nIn} sats will allocate {nOut} sats to the zap pool.",
"eXT2QQ": "Group Chat",
"egib+2": "{n,plural,=1{& {n} other} other{& {n} others}}",
"f1OxTe": "Community leaders are individuals who grow the nostr ecosystem by being active in their local communities and helping onboard new users. Anyone can become a community leader, but few hold the current honorary title.",
"fBI91o": "Zap",
"fBlba3": "Thanks for using {site}, please consider donating if you can.",
"fOksnD": "Can't vote because LNURL service does not support zaps",