Compare commits

..

6 Commits

Author SHA1 Message Date
freakoverse
a23b0a2304 Merge pull request 'new: profile box. fix: landing page latest mods' (#31) from staging into master
All checks were successful
Release to Staging / build_and_release (push) Successful in 45s
Reviewed-on: #31
2024-09-03 10:06:14 +00:00
daniyal
018536e11d fix: sort the mods in by published_at before displaying in latest mods section of landing page
All checks were successful
Release to Staging / build_and_release (push) Successful in 43s
2024-09-03 14:59:23 +05:00
daniyal
c44a28f755 fix: fixed profile picture and bio in profile box
All checks were successful
Release to Staging / build_and_release (push) Successful in 45s
2024-09-03 14:37:54 +05:00
daniyal
8fea6fa27f chore: quick fix
All checks were successful
Release to Staging / build_and_release (push) Successful in 46s
2024-09-03 13:15:47 +05:00
daniyal
fad1ff98b3 feat: implemented logic for profile box
Some checks failed
Release to Staging / build_and_release (push) Failing after 23s
2024-09-03 13:05:37 +05:00
freakoverse
4a7899cfde Update src/styles/cardGames.css
All checks were successful
Release to Staging / build_and_release (push) Successful in 48s
2024-09-02 18:52:21 +00:00
9 changed files with 609 additions and 64 deletions

View File

@ -1,8 +1,66 @@
import { Filter, kinds, nip19, UnsignedEvent, Event } from 'nostr-tools'
import { QRCodeSVG } from 'qrcode.react'
import { useCallback, useState } from 'react'
import { toast } from 'react-toastify'
import {
MetadataController,
RelayController,
UserRelaysType,
ZapController
} from '../controllers'
import { useAppSelector, useDidMount } from '../hooks'
import '../styles/author.css' import '../styles/author.css'
import '../styles/innerPage.css' import '../styles/innerPage.css'
import '../styles/socialPosts.css' import '../styles/socialPosts.css'
import { PaymentRequest, UserProfile } from '../types'
import {
copyTextToClipboard,
formatNumber,
log,
LogType,
unformatNumber,
now
} from '../utils'
import { LoadingSpinner } from './LoadingSpinner'
import { ZapButtons, ZapPresets, ZapQR } from './Zap'
import { getProfilePageRoute } from '../routes'
import { useNavigate } from 'react-router-dom'
type Props = {
pubkey: string
}
export const ProfileSection = ({ pubkey }: Props) => {
const navigate = useNavigate()
const [profile, setProfile] = useState<UserProfile>()
useDidMount(async () => {
const metadataController = await MetadataController.getInstance()
metadataController.findMetadata(pubkey).then((res) => {
setProfile(res)
})
})
const handleCopy = async () => {
copyTextToClipboard(profile?.npub as string).then((isCopied) => {
if (isCopied) {
toast.success('Npub copied to clipboard!')
} else {
toast.error(
'Failed to copy, look into console for more details on error!'
)
}
})
}
if (!profile) return null
const profileRoute = getProfilePageRoute(
nip19.nprofileEncode({
pubkey
})
)
export const ProfileSection = () => {
return ( return (
<div className='IBMSMSplitMainSmallSide'> <div className='IBMSMSplitMainSmallSide'>
<div className='IBMSMSplitMainSmallSideSecWrapper'> <div className='IBMSMSplitMainSmallSideSecWrapper'>
@ -12,7 +70,11 @@ export const ProfileSection = () => {
<div className='IBMSMSMSSS_Author_Top_Left'> <div className='IBMSMSMSSS_Author_Top_Left'>
<a <a
className='IBMSMSMSSS_Author_Top_Left_InsideLinkWrapper' className='IBMSMSMSSS_Author_Top_Left_InsideLinkWrapper'
href='profile.html' href={`#${profileRoute}`}
onClick={(e) => {
e.preventDefault()
navigate(profileRoute)
}}
> >
<div className='IBMSMSMSSS_Author_Top_Left_Inside'> <div className='IBMSMSMSSS_Author_Top_Left_Inside'>
<div className='IBMSMSMSSS_Author_Top_Left_InsidePic'> <div className='IBMSMSMSSS_Author_Top_Left_InsidePic'>
@ -20,8 +82,9 @@ export const ProfileSection = () => {
<div <div
className='IBMSMSMSSS_Author_Top_PP' className='IBMSMSMSSS_Author_Top_PP'
style={{ style={{
background: background: `url('${
"url('assets/img/DEG%20Mods%20Default%20PP.png') center / cover no-repeat" profile.image || ''
}') center / cover no-repeat`
}} }}
></div> ></div>
</div> </div>
@ -29,10 +92,10 @@ export const ProfileSection = () => {
<div className='IBMSMSMSSS_Author_Top_Left_InsideDetails'> <div className='IBMSMSMSSS_Author_Top_Left_InsideDetails'>
<div className='IBMSMSMSSS_Author_TopWrapper'> <div className='IBMSMSMSSS_Author_TopWrapper'>
<p className='IBMSMSMSSS_Author_Top_Name'> <p className='IBMSMSMSSS_Author_Top_Name'>
{author.name} {profile.displayName || profile.name || ''}
</p> </p>
<p className='IBMSMSMSSS_Author_Top_Handle'> <p className='IBMSMSMSSS_Author_Top_Handle'>
{author.handle} {profile.nip05 || ''}
</p> </p>
</div> </div>
</div> </div>
@ -44,13 +107,14 @@ export const ProfileSection = () => {
id='SiteOwnerAddress' id='SiteOwnerAddress'
className='IBMSMSMSSS_Author_Top_Address' className='IBMSMSMSSS_Author_Top_Address'
> >
{author.address} {profile.npub}
</p> </p>
</div> </div>
<div className='IBMSMSMSSS_Author_Top_IconWrapper'> <div className='IBMSMSMSSS_Author_Top_IconWrapper'>
<div <div
id='copySiteOwnerAddress' id='copySiteOwnerAddress'
className='IBMSMSMSSS_Author_Top_IconWrapped' className='IBMSMSMSSS_Author_Top_IconWrapped'
onClick={handleCopy}
> >
<svg <svg
xmlns='http://www.w3.org/2000/svg' xmlns='http://www.w3.org/2000/svg'
@ -60,40 +124,18 @@ export const ProfileSection = () => {
fill='currentColor' fill='currentColor'
className='IBMSMSMSSS_Author_Top_Icon' className='IBMSMSMSSS_Author_Top_Icon'
> >
<path d="M384 96L384 0h-112c-26.51 0-48 21.49-48 48v288c0 26.51 21.49 48 48 48H464c26.51 0 48-21.49 48-48V128h-95.1C398.4 128 384 113.6 384 96zM416 0v96h96L416 0zM192 352V128h-144c-26.51 0-48 21.49-48 48v288c0 26.51 21.49 48 48 48h192c26.51 0 48-21.49 48-48L288 416h-32C220.7 416 192 387.3 192 352z"></path> <path d='M384 96L384 0h-112c-26.51 0-48 21.49-48 48v288c0 26.51 21.49 48 48 48H464c26.51 0 48-21.49 48-48V128h-95.1C398.4 128 384 113.6 384 96zM416 0v96h96L416 0zM192 352V128h-144c-26.51 0-48 21.49-48 48v288c0 26.51 21.49 48 48 48h192c26.51 0 48-21.49 48-48L288 416h-32C220.7 416 192 387.3 192 352z'></path>
</svg>
</div>
<div className='IBMSMSMSSS_Author_Top_IconWrapped IBMSMSMSSS_Author_Top_IconWrappedQR'>
<svg
xmlns='http://www.w3.org/2000/svg'
viewBox='-32 0 512 512'
width='1em'
height='1em'
fill='currentColor'
className='IBMSMSMSSS_Author_Top_Icon'
>
<path d='M144 32C170.5 32 192 53.49 192 80V176C192 202.5 170.5 224 144 224H48C21.49 224 0 202.5 0 176V80C0 53.49 21.49 32 48 32H144zM128 96H64V160H128V96zM144 288C170.5 288 192 309.5 192 336V432C192 458.5 170.5 480 144 480H48C21.49 480 0 458.5 0 432V336C0 309.5 21.49 288 48 288H144zM128 352H64V416H128V352zM256 80C256 53.49 277.5 32 304 32H400C426.5 32 448 53.49 448 80V176C448 202.5 426.5 224 400 224H304C277.5 224 256 202.5 256 176V80zM320 160H384V96H320V160zM352 448H384V480H352V448zM448 480H416V448H448V480zM416 288H448V416H352V384H320V480H256V288H352V320H416V288z'></path>
</svg>
</div>
<div
className='IBMSMSMSSS_Author_Top_IconWrapped'
>
<svg
xmlns='http://www.w3.org/2000/svg'
viewBox='-32 0 512 512'
width='1em'
height='1em'
fill='currentColor'
className='IBMSMSMSSS_Author_Top_Icon'
>
<path d="M240.5 224H352C365.3 224 377.3 232.3 381.1 244.7C386.6 257.2 383.1 271.3 373.1 280.1L117.1 504.1C105.8 513.9 89.27 514.7 77.19 505.9C65.1 497.1 60.7 481.1 66.59 467.4L143.5 288H31.1C18.67 288 6.733 279.7 2.044 267.3C-2.645 254.8 .8944 240.7 10.93 231.9L266.9 7.918C278.2-1.92 294.7-2.669 306.8 6.114C318.9 14.9 323.3 30.87 317.4 44.61L240.5 224z"></path>
</svg> </svg>
</div> </div>
<QRButtonWithPopUp pubkey={profile.pubkey as string} />
<ZapButtonWithPopUp pubkey={profile.pubkey as string} />
</div> </div>
</div> </div>
</div> </div>
<div className='IBMSMSMSSS_Author_Top_Details'> <div className='IBMSMSMSSS_Author_Top_Details'>
<p className='IBMSMSMSSS_Author_Top_Bio'>{author.bio}</p> <p className='IBMSMSMSSS_Author_Top_Bio'>
{profile.bio || profile.about}
</p>
<div <div
id='OwnerFollowLogin' id='OwnerFollowLogin'
className='IBMSMSMSSS_Author_Top_NostrLinks' className='IBMSMSMSSS_Author_Top_NostrLinks'
@ -101,9 +143,7 @@ export const ProfileSection = () => {
></div> ></div>
</div> </div>
</div> </div>
<button className='btn btnMain' type='button'> <FollowButton pubkey={pubkey} />
Follow
</button>
</div> </div>
</div> </div>
<div className='IBMSMSplitMainSmallSideSec'> <div className='IBMSMSplitMainSmallSideSec'>
@ -154,20 +194,6 @@ export const ProfileSection = () => {
) )
} }
interface Author {
name: string
handle: string
address: string
bio: string
}
const author: Author = {
name: 'User name',
handle: 'nip5handle@domain.com',
address: 'npub1address',
bio: `user bio, this is a long string of temporary text that would be replaced with the user bio from their metada address`
}
interface Post { interface Post {
name: string name: string
link: string link: string
@ -193,3 +219,495 @@ const posts: Post[] = [
imageUrl: '/assets/img/DEGMods%20Placeholder%20Img.png' imageUrl: '/assets/img/DEGMods%20Placeholder%20Img.png'
} }
] ]
type QRButtonWithPopUpProps = {
pubkey: string
}
const QRButtonWithPopUp = ({ pubkey }: QRButtonWithPopUpProps) => {
const [isOpen, setIsOpen] = useState(false)
const nprofile = nip19.nprofileEncode({
pubkey
})
const onQrCodeClicked = async () => {
const href = `https://njump.me/${nprofile}`
const a = document.createElement('a')
a.href = href
a.target = '_blank' // Open in a new tab
a.rel = 'noopener noreferrer' // Recommended for security reasons
a.click()
}
return (
<>
<div
className='IBMSMSMSSS_Author_Top_IconWrapped IBMSMSMSSS_Author_Top_IconWrappedQR'
onClick={() => setIsOpen(true)}
>
<svg
xmlns='http://www.w3.org/2000/svg'
viewBox='-32 0 512 512'
width='1em'
height='1em'
fill='currentColor'
className='IBMSMSMSSS_Author_Top_Icon'
>
<path d='M144 32C170.5 32 192 53.49 192 80V176C192 202.5 170.5 224 144 224H48C21.49 224 0 202.5 0 176V80C0 53.49 21.49 32 48 32H144zM128 96H64V160H128V96zM144 288C170.5 288 192 309.5 192 336V432C192 458.5 170.5 480 144 480H48C21.49 480 0 458.5 0 432V336C0 309.5 21.49 288 48 288H144zM128 352H64V416H128V352zM256 80C256 53.49 277.5 32 304 32H400C426.5 32 448 53.49 448 80V176C448 202.5 426.5 224 400 224H304C277.5 224 256 202.5 256 176V80zM320 160H384V96H320V160zM352 448H384V480H352V448zM448 480H416V448H448V480zM416 288H448V416H352V384H320V480H256V288H352V320H416V288z'></path>
</svg>
</div>
{isOpen && (
<div id='PopUpMainQR' className='popUpMain'>
<div className='ContainerMain'>
<div className='popUpMainCardWrapper'>
<div className='popUpMainCard popUpMainCardQR'>
<div className='popUpMainCardTop'>
<div className='popUpMainCardTopInfo'>
<h3>Nostr Address</h3>
</div>
<div
className='popUpMainCardTopClose'
onClick={() => setIsOpen(false)}
>
<svg
xmlns='http://www.w3.org/2000/svg'
viewBox='-96 0 512 512'
width='1em'
height='1em'
fill='currentColor'
style={{ zIndex: 1 }}
>
<path d='M310.6 361.4c12.5 12.5 12.5 32.75 0 45.25C304.4 412.9 296.2 416 288 416s-16.38-3.125-22.62-9.375L160 301.3L54.63 406.6C48.38 412.9 40.19 416 32 416S15.63 412.9 9.375 406.6c-12.5-12.5-12.5-32.75 0-45.25l105.4-105.4L9.375 150.6c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L160 210.8l105.4-105.4c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25l-105.4 105.4L310.6 361.4z'></path>
</svg>
</div>
</div>
<div className='popUpMainCardBottom'>
<QRCodeSVG
className='popUpMainCardBottomQR'
onClick={onQrCodeClicked}
value={nprofile}
height={235}
width={235}
/>
</div>
</div>
</div>
</div>
</div>
)}
</>
)
}
type ZapButtonWithPopUpProps = {
pubkey: string
}
const ZapButtonWithPopUp = ({ pubkey }: ZapButtonWithPopUpProps) => {
const [isOpen, setIsOpen] = useState(false)
const [amount, setAmount] = useState<number>(0)
const [message, setMessage] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
const [paymentRequest, setPaymentRequest] = useState<PaymentRequest>()
const userState = useAppSelector((state) => state.user)
const handleClose = useCallback(() => {
setPaymentRequest(undefined)
setIsLoading(false)
setIsOpen(false)
}, [])
const handleQRExpiry = useCallback(() => {
setPaymentRequest(undefined)
}, [])
const handleAmountChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const unformattedValue = unformatNumber(event.target.value)
setAmount(unformattedValue)
}
const generatePaymentRequest =
useCallback(async (): Promise<PaymentRequest | null> => {
let userHexKey: string
setIsLoading(true)
setLoadingSpinnerDesc('Getting user pubkey')
if (userState.auth && userState.user?.pubkey) {
userHexKey = userState.user.pubkey as string
} else {
userHexKey = (await window.nostr?.getPublicKey()) as string
}
if (!userHexKey) {
setIsLoading(false)
toast.error('Could not get pubkey')
return null
}
setLoadingSpinnerDesc('Getting admin metadata')
const metadataController = await MetadataController.getInstance()
const authorMetadata = await metadataController.findMetadata(pubkey)
if (!authorMetadata?.lud16) {
setIsLoading(false)
toast.error('Lighting address (lud16) is missing in admin metadata!')
return null
}
if (!authorMetadata?.pubkey) {
setIsLoading(false)
toast.error('pubkey is missing in admin metadata!')
return null
}
const zapController = ZapController.getInstance()
setLoadingSpinnerDesc('Creating zap request')
return await zapController
.getLightningPaymentRequest(
authorMetadata.lud16,
amount,
authorMetadata.pubkey as string,
userHexKey,
message
)
.catch((err) => {
toast.error(err.message || err)
return null
})
.finally(() => {
setIsLoading(false)
})
}, [amount, message, userState, pubkey])
const handleSend = useCallback(async () => {
const pr = await generatePaymentRequest()
if (!pr) return
setIsLoading(true)
setLoadingSpinnerDesc('Sending payment!')
const zapController = ZapController.getInstance()
if (await zapController.isWeblnProviderExists()) {
await zapController
.sendPayment(pr.pr)
.then(() => {
toast.success(`Successfully sent ${amount} sats!`)
handleClose()
})
.catch((err) => {
toast.error(err.message || err)
})
} else {
toast.warn('Webln is not present. Use QR code to send zap.')
setPaymentRequest(pr)
}
setIsLoading(false)
}, [amount, handleClose, generatePaymentRequest])
const handleGenerateQRCode = async () => {
const pr = await generatePaymentRequest()
if (!pr) return
setPaymentRequest(pr)
}
return (
<>
<div
className='IBMSMSMSSS_Author_Top_IconWrapped'
onClick={() => setIsOpen(true)}
>
<svg
xmlns='http://www.w3.org/2000/svg'
viewBox='-32 0 512 512'
width='1em'
height='1em'
fill='currentColor'
className='IBMSMSMSSS_Author_Top_Icon'
>
<path d='M240.5 224H352C365.3 224 377.3 232.3 381.1 244.7C386.6 257.2 383.1 271.3 373.1 280.1L117.1 504.1C105.8 513.9 89.27 514.7 77.19 505.9C65.1 497.1 60.7 481.1 66.59 467.4L143.5 288H31.1C18.67 288 6.733 279.7 2.044 267.3C-2.645 254.8 .8944 240.7 10.93 231.9L266.9 7.918C278.2-1.92 294.7-2.669 306.8 6.114C318.9 14.9 323.3 30.87 317.4 44.61L240.5 224z'></path>
</svg>
</div>
{isOpen && (
<div id='PopUpMainZap' className='popUpMain'>
<div className='ContainerMain'>
<div className='popUpMainCardWrapper'>
<div className='popUpMainCard popUpMainCardQR'>
<div className='popUpMainCardTop'>
<div className='popUpMainCardTopInfo'>
<h3>Tip/Zap</h3>
</div>
<div className='popUpMainCardTopClose' onClick={handleClose}>
<svg
xmlns='http://www.w3.org/2000/svg'
viewBox='-96 0 512 512'
width='1em'
height='1em'
fill='currentColor'
style={{ zIndex: 1 }}
>
<path d='M310.6 361.4c12.5 12.5 12.5 32.75 0 45.25C304.4 412.9 296.2 416 288 416s-16.38-3.125-22.62-9.375L160 301.3L54.63 406.6C48.38 412.9 40.19 416 32 416S15.63 412.9 9.375 406.6c-12.5-12.5-12.5-32.75 0-45.25l105.4-105.4L9.375 150.6c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L160 210.8l105.4-105.4c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25l-105.4 105.4L310.6 361.4z' />
</svg>
</div>
</div>
<div className='pUMCB_Zaps'>
<div className='pUMCB_ZapsInside'>
<div className='pUMCB_ZapsInsideAmount'>
<div className='inputLabelWrapperMain'>
<label className='form-label labelMain'>
Amount (Satoshis)
</label>
<input
className='inputMain'
type='text'
inputMode='numeric'
value={amount ? formatNumber(amount) : ''}
onChange={handleAmountChange}
/>
</div>
<div className='pUMCB_ZapsInsideAmountOptions'>
<ZapPresets setAmount={setAmount} />
</div>
</div>
<div className='inputLabelWrapperMain'>
<label className='form-label labelMain'>
Message (optional)
</label>
<input
type='text'
className='inputMain'
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
</div>
<ZapButtons
disabled={!amount}
handleGenerateQRCode={handleGenerateQRCode}
handleSend={handleSend}
/>
{paymentRequest && (
<ZapQR
paymentRequest={paymentRequest}
handleClose={handleClose}
handleQRExpiry={handleQRExpiry}
/>
)}
</div>
</div>
</div>
</div>
</div>
</div>
)}
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
</>
)
}
type FollowButtonProps = {
pubkey: string
}
const FollowButton = ({ pubkey }: FollowButtonProps) => {
const [isFollowing, setIsFollowing] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
const userState = useAppSelector((state) => state.user)
useDidMount(async () => {
if (userState.auth && userState.user?.pubkey) {
const userHexKey = userState.user.pubkey as string
const { isFollowing: isAlreadyFollowing } = await checkIfFollowing(
userHexKey,
pubkey
)
setIsFollowing(isAlreadyFollowing)
}
})
const getUserPubKey = async (): Promise<string | null> => {
if (userState.auth && userState.user?.pubkey) {
return userState.user.pubkey as string
} else {
return (await window.nostr?.getPublicKey()) as string
}
}
const checkIfFollowing = async (
userHexKey: string,
pubkey: string
): Promise<{
isFollowing: boolean
tags: string[][]
}> => {
const filter: Filter = {
kinds: [kinds.Contacts],
authors: [userHexKey]
}
const contactListEvent =
await RelayController.getInstance().fetchEventFromUserRelays(
filter,
userHexKey,
UserRelaysType.Both
)
if (!contactListEvent)
return {
isFollowing: false,
tags: []
}
return {
isFollowing: contactListEvent.tags.some(
(t) => t[0] === 'p' && t[1] === pubkey
),
tags: contactListEvent.tags
}
}
const signAndPublishEvent = async (
unsignedEvent: UnsignedEvent
): Promise<boolean> => {
const signedEvent = await window.nostr
?.signEvent(unsignedEvent)
.then((event) => event as Event)
.catch((err) => {
toast.error('Failed to sign the event!')
log(true, LogType.Error, 'Failed to sign the event!', err)
return null
})
if (!signedEvent) return false
const publishedOnRelays = await RelayController.getInstance().publish(
signedEvent as Event
)
if (publishedOnRelays.length === 0) {
toast.error('Failed to publish event on any relay')
return false
}
toast.success(
`Event published successfully to the following relays\n\n${publishedOnRelays.join(
'\n'
)}`
)
return true
}
const handleFollow = async () => {
setIsLoading(true)
setLoadingSpinnerDesc('Processing follow request')
const userHexKey = await getUserPubKey()
if (!userHexKey) {
setIsLoading(false)
toast.error('Could not get pubkey')
return
}
const { isFollowing: isAlreadyFollowing, tags } = await checkIfFollowing(
userHexKey,
pubkey
)
if (isAlreadyFollowing) {
toast.info('Already following!')
setIsFollowing(true)
setIsLoading(false)
return
}
const unsignedEvent: UnsignedEvent = {
content: '',
created_at: now(),
kind: kinds.Contacts,
pubkey: userHexKey,
tags: [...tags, ['p', pubkey]]
}
setLoadingSpinnerDesc('Signing and publishing follow event')
const success = await signAndPublishEvent(unsignedEvent)
setIsFollowing(success)
setIsLoading(false)
}
const handleUnFollow = async () => {
setIsLoading(true)
setLoadingSpinnerDesc('Processing unfollow request')
const userHexKey = await getUserPubKey()
if (!userHexKey) {
setIsLoading(false)
toast.error('Could not get pubkey')
return
}
const filter: Filter = {
kinds: [kinds.Contacts],
authors: [userHexKey]
}
const contactListEvent =
await RelayController.getInstance().fetchEventFromUserRelays(
filter,
userHexKey,
UserRelaysType.Both
)
if (
!contactListEvent ||
!contactListEvent.tags.some((t) => t[0] === 'p' && t[1] === pubkey)
) {
// could not found target pubkey in user's follow list
// so, just update the status and return
setIsFollowing(false)
setIsLoading(false)
return
}
const unsignedEvent: UnsignedEvent = {
content: '',
created_at: now(),
kind: kinds.Contacts,
pubkey: userHexKey,
tags: contactListEvent.tags.filter(
(t) => !(t[0] === 'p' && t[1] === pubkey)
)
}
setLoadingSpinnerDesc('Signing and publishing unfollow event')
const success = await signAndPublishEvent(unsignedEvent)
setIsFollowing(!success)
setIsLoading(false)
}
return (
<>
<button
className='btn btnMain'
type='button'
onClick={isFollowing ? handleUnFollow : handleFollow}
>
{isFollowing ? 'Un-Follow' : 'Follow'}
</button>
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
</>
)
}

View File

@ -251,6 +251,7 @@ const DisplayLatestMods = () => {
useDidMount(() => { useDidMount(() => {
fetchMods({ source: window.location.host, limit: 4 }) fetchMods({ source: window.location.host, limit: 4 })
.then((res) => { .then((res) => {
res.sort((a, b) => b.published_at - a.published_at)
setLatestMods(res) setLatestMods(res)
}) })
.finally(() => { .finally(() => {

View File

@ -199,7 +199,7 @@ export const InnerModPage = () => {
<Comments /> <Comments />
</div> </div>
</div> </div>
<ProfileSection /> <ProfileSection pubkey={modData.author} />
</div> </div>
</div> </div>
</div> </div>

3
src/pages/profile.tsx Normal file
View File

@ -0,0 +1,3 @@
export const ProfilePage = () => {
return <h1>WIP</h1>
}

View File

@ -17,6 +17,7 @@ import { copyTextToClipboard } from '../utils'
export const SettingsPage = () => { export const SettingsPage = () => {
const location = useLocation() const location = useLocation()
const userState = useAppSelector((state) => state.user)
return ( return (
<div className='InnerBodyMain'> <div className='InnerBodyMain'>
@ -34,7 +35,9 @@ export const SettingsPage = () => {
<PreferencesSetting /> <PreferencesSetting />
)} )}
{location.pathname === appRoutes.settingsAdmin && <AdminSetting />} {location.pathname === appRoutes.settingsAdmin && <AdminSetting />}
<ProfileSection /> {userState.auth && userState.user?.pubkey && (
<ProfileSection pubkey={userState.user.pubkey as string} />
)}
</div> </div>
</div> </div>
</div> </div>
@ -268,9 +271,7 @@ const ProfileSettings = () => {
<path d='M144 32C170.5 32 192 53.49 192 80V176C192 202.5 170.5 224 144 224H48C21.49 224 0 202.5 0 176V80C0 53.49 21.49 32 48 32H144zM128 96H64V160H128V96zM144 288C170.5 288 192 309.5 192 336V432C192 458.5 170.5 480 144 480H48C21.49 480 0 458.5 0 432V336C0 309.5 21.49 288 48 288H144zM128 352H64V416H128V352zM256 80C256 53.49 277.5 32 304 32H400C426.5 32 448 53.49 448 80V176C448 202.5 426.5 224 400 224H304C277.5 224 256 202.5 256 176V80zM320 160H384V96H320V160zM352 448H384V480H352V448zM448 480H416V448H448V480zM416 288H448V416H352V384H320V480H256V288H352V320H416V288z'></path> <path d='M144 32C170.5 32 192 53.49 192 80V176C192 202.5 170.5 224 144 224H48C21.49 224 0 202.5 0 176V80C0 53.49 21.49 32 48 32H144zM128 96H64V160H128V96zM144 288C170.5 288 192 309.5 192 336V432C192 458.5 170.5 480 144 480H48C21.49 480 0 458.5 0 432V336C0 309.5 21.49 288 48 288H144zM128 352H64V416H128V352zM256 80C256 53.49 277.5 32 304 32H400C426.5 32 448 53.49 448 80V176C448 202.5 426.5 224 400 224H304C277.5 224 256 202.5 256 176V80zM320 160H384V96H320V160zM352 448H384V480H352V448zM448 480H416V448H448V480zM416 288H448V416H352V384H320V480H256V288H352V320H416V288z'></path>
</svg> </svg>
</div> </div>
<div <div className='IBMSMSMSSS_Author_Top_IconWrapped'>
className='IBMSMSMSSS_Author_Top_IconWrapped'
>
<svg <svg
xmlns='http://www.w3.org/2000/svg' xmlns='http://www.w3.org/2000/svg'
viewBox='-32 0 512 512' viewBox='-32 0 512 512'
@ -279,14 +280,15 @@ const ProfileSettings = () => {
fill='currentColor' fill='currentColor'
className='IBMSMSMSSS_Author_Top_Icon' className='IBMSMSMSSS_Author_Top_Icon'
> >
<path d="M240.5 224H352C365.3 224 377.3 232.3 381.1 244.7C386.6 257.2 383.1 271.3 373.1 280.1L117.1 504.1C105.8 513.9 89.27 514.7 77.19 505.9C65.1 497.1 60.7 481.1 66.59 467.4L143.5 288H31.1C18.67 288 6.733 279.7 2.044 267.3C-2.645 254.8 .8944 240.7 10.93 231.9L266.9 7.918C278.2-1.92 294.7-2.669 306.8 6.114C318.9 14.9 323.3 30.87 317.4 44.61L240.5 224z"></path> <path d='M240.5 224H352C365.3 224 377.3 232.3 381.1 244.7C386.6 257.2 383.1 271.3 373.1 280.1L117.1 504.1C105.8 513.9 89.27 514.7 77.19 505.9C65.1 497.1 60.7 481.1 66.59 467.4L143.5 288H31.1C18.67 288 6.733 279.7 2.044 267.3C-2.645 254.8 .8944 240.7 10.93 231.9L266.9 7.918C278.2-1.92 294.7-2.669 306.8 6.114C318.9 14.9 323.3 30.87 317.4 44.61L240.5 224z'></path>
</svg> </svg>
</div> </div>
</div> </div>
</div> </div>
<div className='IBMSMSMSSS_Author_Top_Details'> <div className='IBMSMSMSSS_Author_Top_Details'>
<p className='IBMSMSMSSS_Author_Top_Bio'> <p className='IBMSMSMSSS_Author_Top_Bio'>
user bio, this is a long string of temporary text that would be replaced with the user bio from their metada address user bio, this is a long string of temporary text that would
be replaced with the user bio from their metada address
</p> </p>
<div <div
id='OwnerFollowLogin-1' id='OwnerFollowLogin-1'

View File

@ -11,7 +11,7 @@ import { ModDetails } from '../types'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { useState } from 'react' import { useState } from 'react'
import { LoadingSpinner } from '../components/LoadingSpinner' import { LoadingSpinner } from '../components/LoadingSpinner'
import { useDidMount } from '../hooks' import { useAppSelector, useDidMount } from '../hooks'
export const SubmitModPage = () => { export const SubmitModPage = () => {
const location = useLocation() const location = useLocation()
@ -19,6 +19,8 @@ export const SubmitModPage = () => {
const [modData, setModData] = useState<ModDetails>() const [modData, setModData] = useState<ModDetails>()
const [isFetching, setIsFetching] = useState(false) const [isFetching, setIsFetching] = useState(false)
const userState = useAppSelector((state) => state.user)
const title = location.pathname.startsWith('/edit-mod') const title = location.pathname.startsWith('/edit-mod')
? 'Edit Mod' ? 'Edit Mod'
: 'Submit a mod' : 'Submit a mod'
@ -74,7 +76,9 @@ export const SubmitModPage = () => {
)} )}
</div> </div>
</div> </div>
<ProfileSection /> {userState.auth && userState.user?.pubkey && (
<ProfileSection pubkey={userState.user.pubkey as string} />
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,10 +1,13 @@
import { CheckboxField, InputField } from '../components/Inputs' import { CheckboxField, InputField } from '../components/Inputs'
import { ProfileSection } from '../components/ProfileSection' import { ProfileSection } from '../components/ProfileSection'
import { useAppSelector } from '../hooks'
import '../styles/innerPage.css' import '../styles/innerPage.css'
import '../styles/styles.css' import '../styles/styles.css'
import '../styles/write.css' import '../styles/write.css'
export const WritePage = () => { export const WritePage = () => {
const userState = useAppSelector((state) => state.user)
return ( return (
<div className='InnerBodyMain'> <div className='InnerBodyMain'>
<div className='ContainerMain'> <div className='ContainerMain'>
@ -12,7 +15,9 @@ export const WritePage = () => {
<div className='IBMSMSplitMain'> <div className='IBMSMSplitMain'>
<div className='IBMSMSplitMainBigSide'> <div className='IBMSMSplitMainBigSide'>
<div className='IBMSMTitleMain'> <div className='IBMSMTitleMain'>
<h2 className='IBMSMTitleMainHeading'>Write a blog post (WIP)</h2> <h2 className='IBMSMTitleMainHeading'>
Write a blog post (WIP)
</h2>
</div> </div>
<div className='IBMSMSMBS_Write'> <div className='IBMSMSMBS_Write'>
<InputField <InputField
@ -58,7 +63,9 @@ export const WritePage = () => {
</div> </div>
</div> </div>
</div> </div>
<ProfileSection /> {userState.auth && userState.user?.pubkey && (
<ProfileSection pubkey={userState.user.pubkey as string} />
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -4,6 +4,7 @@ import { GamesPage } from '../pages/games'
import { HomePage } from '../pages/home' import { HomePage } from '../pages/home'
import { InnerModPage } from '../pages/innerMod' import { InnerModPage } from '../pages/innerMod'
import { ModsPage } from '../pages/mods' import { ModsPage } from '../pages/mods'
import { ProfilePage } from '../pages/profile'
import { SettingsPage } from '../pages/settings' import { SettingsPage } from '../pages/settings'
import { SubmitModPage } from '../pages/submitMod' import { SubmitModPage } from '../pages/submitMod'
import { WritePage } from '../pages/write' import { WritePage } from '../pages/write'
@ -22,7 +23,8 @@ export const appRoutes = {
settingsProfile: '/settings-profile', settingsProfile: '/settings-profile',
settingsRelays: '/settings-relays', settingsRelays: '/settings-relays',
settingsPreferences: '/settings-preferences', settingsPreferences: '/settings-preferences',
settingsAdmin: '/settings-admin' settingsAdmin: '/settings-admin',
profile: '/profile/:nprofile'
} }
export const getModsInnerPageRoute = (eventId: string) => export const getModsInnerPageRoute = (eventId: string) =>
@ -31,6 +33,9 @@ export const getModsInnerPageRoute = (eventId: string) =>
export const getModsEditPageRoute = (eventId: string) => export const getModsEditPageRoute = (eventId: string) =>
appRoutes.editMod.replace(':naddr', eventId) appRoutes.editMod.replace(':naddr', eventId)
export const getProfilePageRoute = (nprofile: string) =>
appRoutes.profile.replace(':nprofile', nprofile)
export const routes = [ export const routes = [
{ {
path: appRoutes.index, path: appRoutes.index,
@ -87,5 +92,9 @@ export const routes = [
{ {
path: appRoutes.settingsAdmin, path: appRoutes.settingsAdmin,
element: <SettingsPage /> element: <SettingsPage />
},
{
path: appRoutes.profile,
element: <ProfilePage />
} }
] ]

View File

@ -43,4 +43,5 @@
-webkit-line-clamp: 1; -webkit-line-clamp: 1;
font-size: 18px; font-size: 18px;
line-height: 1.5; line-height: 1.5;
text-align: center;
} }