chore(git): merge pull request #123 from fixes-120-117-84-104 into staging
All checks were successful
Release to Staging / build_and_release (push) Successful in 51s
All checks were successful
Release to Staging / build_and_release (push) Successful in 51s
Reviewed-on: #123
This commit is contained in:
commit
df0c64e2c9
@ -1,3 +1,4 @@
|
||||
import { Dots } from 'components/Spinner'
|
||||
import { useReactions } from 'hooks'
|
||||
import { Addressable } from 'types'
|
||||
|
||||
@ -19,15 +20,13 @@ export const Reactions = ({ addressable }: ReactionsProps) => {
|
||||
aTag: addressable.aTag
|
||||
})
|
||||
|
||||
if (!isDataLoaded) return null
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`IBMSMSMBSS_Details_Card IBMSMSMBSS_D_CReactUp ${
|
||||
hasReactedPositively ? 'IBMSMSMBSS_D_CRUActive' : ''
|
||||
}`}
|
||||
onClick={() => handleReaction(true)}
|
||||
onClick={isDataLoaded ? () => handleReaction(true) : undefined}
|
||||
>
|
||||
<div className='IBMSMSMBSS_Details_CardVisual'>
|
||||
<svg
|
||||
@ -41,7 +40,9 @@ export const Reactions = ({ addressable }: ReactionsProps) => {
|
||||
<path d='M0 190.9V185.1C0 115.2 50.52 55.58 119.4 44.1C164.1 36.51 211.4 51.37 244 84.02L256 96L267.1 84.02C300.6 51.37 347 36.51 392.6 44.1C461.5 55.58 512 115.2 512 185.1V190.9C512 232.4 494.8 272.1 464.4 300.4L283.7 469.1C276.2 476.1 266.3 480 256 480C245.7 480 235.8 476.1 228.3 469.1L47.59 300.4C17.23 272.1 .0003 232.4 .0003 190.9L0 190.9z'></path>
|
||||
</svg>
|
||||
</div>
|
||||
<p className='IBMSMSMBSS_Details_CardText'>{likesCount}</p>
|
||||
<p className='IBMSMSMBSS_Details_CardText'>
|
||||
{isDataLoaded ? likesCount : <Dots />}
|
||||
</p>
|
||||
<div className='IBMSMSMBSSCL_CAElementLoadWrapper'>
|
||||
<div className='IBMSMSMBSSCL_CAElementLoad'></div>
|
||||
</div>
|
||||
@ -50,7 +51,7 @@ export const Reactions = ({ addressable }: ReactionsProps) => {
|
||||
className={`IBMSMSMBSS_Details_Card IBMSMSMBSS_D_CReactDown ${
|
||||
hasReactedNegatively ? 'IBMSMSMBSS_D_CRDActive' : ''
|
||||
}`}
|
||||
onClick={() => handleReaction()}
|
||||
onClick={isDataLoaded ? () => handleReaction() : undefined}
|
||||
>
|
||||
<div className='IBMSMSMBSS_Details_CardVisual'>
|
||||
<svg
|
||||
@ -64,7 +65,9 @@ export const Reactions = ({ addressable }: ReactionsProps) => {
|
||||
<path d='M512 440.1C512 479.9 479.7 512 439.1 512H71.92C32.17 512 0 479.8 0 440c0-35.88 26.19-65.35 60.56-70.85C43.31 356 32 335.4 32 312C32 272.2 64.25 240 104 240h13.99C104.5 228.2 96 211.2 96 192c0-35.38 28.56-64 63.94-64h16C220.1 128 256 92.12 256 48c0-17.38-5.784-33.35-15.16-46.47C245.8 .7754 250.9 0 256 0c53 0 96 43 96 96c0 11.25-2.288 22-5.913 32h5.879C387.3 128 416 156.6 416 192c0 19.25-8.59 36.25-22.09 48H408C447.8 240 480 272.2 480 312c0 23.38-11.38 44.01-28.63 57.14C485.7 374.6 512 404.3 512 440.1z'></path>
|
||||
</svg>
|
||||
</div>
|
||||
<p className='IBMSMSMBSS_Details_CardText'>{disLikesCount}</p>
|
||||
<p className='IBMSMSMBSS_Details_CardText'>
|
||||
{isDataLoaded ? disLikesCount : <Dots />}
|
||||
</p>
|
||||
<div className='IBMSMSMBSSCL_CAElementLoadWrapper'>
|
||||
<div className='IBMSMSMBSSCL_CAElementLoad'></div>
|
||||
</div>
|
||||
|
9
src/components/Spinner.tsx
Normal file
9
src/components/Spinner.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import styles from '../styles/dotsSpinner.module.scss'
|
||||
|
||||
export const Spinner = () => (
|
||||
<div className='spinner'>
|
||||
<div className='spinnerCircle'></div>
|
||||
</div>
|
||||
)
|
||||
|
||||
export const Dots = () => <span className={styles.loading}></span>
|
@ -1,4 +1,5 @@
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk'
|
||||
import { Dots, Spinner } from 'components/Spinner'
|
||||
import { ZapPopUp } from 'components/Zap'
|
||||
import { formatDate } from 'date-fns'
|
||||
import {
|
||||
@ -59,6 +60,15 @@ export const Comments = ({ addressable, setCommentCount }: Props) => {
|
||||
author: AuthorFilterEnum.All_Comments
|
||||
})
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
useEffect(() => {
|
||||
// Initial loading to indicate comments fetching (stop after 5 seconds)
|
||||
const t = window.setTimeout(() => setIsLoading(false), 5000)
|
||||
return () => {
|
||||
window.clearTimeout(t)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setCommentCount(commentEvents.length)
|
||||
}, [commentEvents, setCommentCount])
|
||||
@ -175,8 +185,18 @@ export const Comments = ({ addressable, setCommentCount }: Props) => {
|
||||
return true
|
||||
}
|
||||
|
||||
const handleDiscoveredClick = () => {
|
||||
setVisible(commentEvents)
|
||||
}
|
||||
const [visible, setVisible] = useState<CommentEvent[]>([])
|
||||
useEffect(() => {
|
||||
if (isLoading) {
|
||||
setVisible(commentEvents)
|
||||
}
|
||||
}, [commentEvents, isLoading])
|
||||
|
||||
const comments = useMemo(() => {
|
||||
let filteredComments = commentEvents
|
||||
let filteredComments = visible
|
||||
if (filterOptions.author === AuthorFilterEnum.Creator_Comments) {
|
||||
filteredComments = filteredComments.filter(
|
||||
(comment) => comment.pubkey === addressable.author
|
||||
@ -190,14 +210,28 @@ export const Comments = ({ addressable, setCommentCount }: Props) => {
|
||||
}
|
||||
|
||||
return filteredComments
|
||||
}, [commentEvents, filterOptions, addressable.author])
|
||||
}, [visible, filterOptions.author, filterOptions.sort, addressable.author])
|
||||
|
||||
const discoveredCount = commentEvents.length - visible.length
|
||||
return (
|
||||
<div className='IBMSMSMBSSCommentsWrapper'>
|
||||
<h4 className='IBMSMSMBSSTitle'>Comments</h4>
|
||||
<div className='IBMSMSMBSSComments'>
|
||||
{/* Hide comment form if aTag is missing */}
|
||||
{!!addressable.aTag && <CommentForm handleSubmit={handleSubmit} />}
|
||||
<div>
|
||||
{isLoading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<button
|
||||
type='button'
|
||||
className='btnMain'
|
||||
onClick={discoveredCount ? handleDiscoveredClick : undefined}
|
||||
>
|
||||
<span>Load {discoveredCount} discovered comments</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<Filter
|
||||
filterOptions={filterOptions}
|
||||
setFilterOptions={setFilterOptions}
|
||||
@ -356,12 +390,12 @@ const Comment = (props: CommentEvent) => {
|
||||
</div>
|
||||
<div className='IBMSMSMBSSCL_CommentTopDetailsWrapper'>
|
||||
<div className='IBMSMSMBSSCL_CommentTopDetails'>
|
||||
<a className='IBMSMSMBSSCL_CTD_Name' href='profile.html'>
|
||||
<Link className='IBMSMSMBSSCL_CTD_Name' to={profileRoute}>
|
||||
{profile?.displayName || profile?.name || ''}{' '}
|
||||
</a>
|
||||
<a className='IBMSMSMBSSCL_CTD_Address' href='profile.html'>
|
||||
</Link>
|
||||
<Link className='IBMSMSMBSSCL_CTD_Address' to={profileRoute}>
|
||||
{hexToNpub(props.pubkey)}
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div className='IBMSMSMBSSCL_CommentActionsDetails'>
|
||||
<a className='IBMSMSMBSSCL_CADTime'>
|
||||
@ -447,15 +481,13 @@ const Reactions = (props: Event) => {
|
||||
eTag: props.id
|
||||
})
|
||||
|
||||
if (!isDataLoaded) return null
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`IBMSMSMBSSCL_CAElement IBMSMSMBSSCL_CAEUp ${
|
||||
hasReactedPositively ? 'IBMSMSMBSSCL_CAEUpActive' : ''
|
||||
}`}
|
||||
onClick={() => handleReaction(true)}
|
||||
onClick={isDataLoaded ? () => handleReaction(true) : undefined}
|
||||
>
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
@ -467,7 +499,9 @@ const Reactions = (props: Event) => {
|
||||
>
|
||||
<path d='M0 190.9V185.1C0 115.2 50.52 55.58 119.4 44.1C164.1 36.51 211.4 51.37 244 84.02L256 96L267.1 84.02C300.6 51.37 347 36.51 392.6 44.1C461.5 55.58 512 115.2 512 185.1V190.9C512 232.4 494.8 272.1 464.4 300.4L283.7 469.1C276.2 476.1 266.3 480 256 480C245.7 480 235.8 476.1 228.3 469.1L47.59 300.4C17.23 272.1 .0003 232.4 .0003 190.9L0 190.9z'></path>
|
||||
</svg>
|
||||
<p className='IBMSMSMBSSCL_CAElementText'>{likesCount}</p>
|
||||
<p className='IBMSMSMBSSCL_CAElementText'>
|
||||
{isDataLoaded ? likesCount : <Dots />}
|
||||
</p>
|
||||
<div className='IBMSMSMBSSCL_CAElementLoadWrapper'>
|
||||
<div className='IBMSMSMBSSCL_CAElementLoad'></div>
|
||||
</div>
|
||||
@ -476,7 +510,7 @@ const Reactions = (props: Event) => {
|
||||
className={`IBMSMSMBSSCL_CAElement IBMSMSMBSSCL_CAEDown ${
|
||||
hasReactedNegatively ? 'IBMSMSMBSSCL_CAEDownActive' : ''
|
||||
}`}
|
||||
onClick={() => handleReaction()}
|
||||
onClick={isDataLoaded ? () => handleReaction() : undefined}
|
||||
>
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
@ -488,7 +522,9 @@ const Reactions = (props: Event) => {
|
||||
>
|
||||
<path d='M512 440.1C512 479.9 479.7 512 439.1 512H71.92C32.17 512 0 479.8 0 440c0-35.88 26.19-65.35 60.56-70.85C43.31 356 32 335.4 32 312C32 272.2 64.25 240 104 240h13.99C104.5 228.2 96 211.2 96 192c0-35.38 28.56-64 63.94-64h16C220.1 128 256 92.12 256 48c0-17.38-5.784-33.35-15.16-46.47C245.8 .7754 250.9 0 256 0c53 0 96 43 96 96c0 11.25-2.288 22-5.913 32h5.879C387.3 128 416 156.6 416 192c0 19.25-8.59 36.25-22.09 48H408C447.8 240 480 272.2 480 312c0 23.38-11.38 44.01-28.63 57.14C485.7 374.6 512 404.3 512 440.1z'></path>
|
||||
</svg>
|
||||
<p className='IBMSMSMBSSCL_CAElementText'>{disLikesCount}</p>
|
||||
<p className='IBMSMSMBSSCL_CAElementText'>
|
||||
{isDataLoaded ? disLikesCount : <Dots />}
|
||||
</p>
|
||||
<div className='IBMSMSMBSSCL_CAElementLoadWrapper'>
|
||||
<div className='IBMSMSMBSSCL_CAElementLoad'></div>
|
||||
</div>
|
||||
|
@ -21,7 +21,8 @@ import {
|
||||
log,
|
||||
LogType,
|
||||
npubToHex,
|
||||
orderEventsChronologically
|
||||
orderEventsChronologically,
|
||||
timeout
|
||||
} from 'utils'
|
||||
|
||||
type FetchModsOptions = {
|
||||
@ -241,8 +242,11 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
hexKey: string,
|
||||
userRelaysType: UserRelaysType
|
||||
): Promise<NDKEvent[]> => {
|
||||
// Find the user's relays.
|
||||
const relayUrls = await getRelayListForUser(hexKey, ndk)
|
||||
// Find the user's relays (10s timeout).
|
||||
const relayUrls = await Promise.race([
|
||||
getRelayListForUser(hexKey, ndk),
|
||||
timeout(10000)
|
||||
])
|
||||
.then((ndkRelayList) => {
|
||||
if (ndkRelayList) return ndkRelayList[userRelaysType]
|
||||
return [] // Return an empty array if ndkRelayList is undefined
|
||||
|
@ -5,7 +5,7 @@ import { Event, kinds, UnsignedEvent } from 'nostr-tools'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
import { UserRelaysType } from 'types'
|
||||
import { abbreviateNumber, log, LogType, now } from 'utils'
|
||||
import { abbreviateNumber, log, LogType, now, timeout } from 'utils'
|
||||
|
||||
type UseReactionsParams = {
|
||||
pubkey: string
|
||||
@ -32,7 +32,11 @@ export const useReactions = (params: UseReactionsParams) => {
|
||||
filter['#e'] = [params.eTag]
|
||||
}
|
||||
|
||||
fetchEventsFromUserRelays(filter, params.pubkey, UserRelaysType.Read)
|
||||
// 1 minute timeout
|
||||
Promise.race([
|
||||
fetchEventsFromUserRelays(filter, params.pubkey, UserRelaysType.Read),
|
||||
timeout(60000)
|
||||
])
|
||||
.then((events) => {
|
||||
setReactionEvents(events)
|
||||
})
|
||||
|
@ -36,6 +36,7 @@ import 'swiper/css'
|
||||
import 'swiper/css/navigation'
|
||||
import 'swiper/css/pagination'
|
||||
import { LoadingSpinner } from 'components/LoadingSpinner'
|
||||
import { Spinner } from 'components/Spinner'
|
||||
|
||||
export const HomePage = () => {
|
||||
const navigate = useNavigate()
|
||||
@ -310,14 +311,6 @@ const DisplayLatestMods = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const Spinner = () => {
|
||||
return (
|
||||
<div className='spinner'>
|
||||
<div className='spinnerCircle'></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const DisplayLatestBlogs = () => {
|
||||
const [blogs, setBlogs] = useState<Partial<BlogCardDetails>[]>()
|
||||
const { fetchEvents } = useNDKContext()
|
||||
|
@ -14,17 +14,11 @@ import {
|
||||
useNDKContext,
|
||||
useNSFWList
|
||||
} from 'hooks'
|
||||
import { kinds, nip19, UnsignedEvent } from 'nostr-tools'
|
||||
import { kinds, UnsignedEvent } from 'nostr-tools'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import {
|
||||
useParams,
|
||||
Navigate,
|
||||
Link,
|
||||
useLoaderData,
|
||||
useNavigation
|
||||
} from 'react-router-dom'
|
||||
import { Link, useLoaderData, useNavigation } from 'react-router-dom'
|
||||
import { toast } from 'react-toastify'
|
||||
import { appRoutes, getProfilePageRoute } from 'routes'
|
||||
import { appRoutes } from 'routes'
|
||||
import {
|
||||
BlogCardDetails,
|
||||
FilterOptions,
|
||||
@ -38,8 +32,6 @@ import {
|
||||
copyTextToClipboard,
|
||||
DEFAULT_FILTER_OPTIONS,
|
||||
extractBlogCardDetails,
|
||||
log,
|
||||
LogType,
|
||||
now,
|
||||
npubToHex,
|
||||
scrollIntoView,
|
||||
@ -52,23 +44,11 @@ import { BlogCard } from 'components/BlogCard'
|
||||
|
||||
export const ProfilePage = () => {
|
||||
const {
|
||||
profilePubkey,
|
||||
profile,
|
||||
isBlocked: _isBlocked,
|
||||
isOwnProfile
|
||||
} = useLoaderData() as ProfilePageLoaderResult
|
||||
// Try to decode nprofile parameter
|
||||
const { nprofile } = useParams()
|
||||
let profilePubkey: string | undefined
|
||||
try {
|
||||
const value = nprofile
|
||||
? nip19.decode(nprofile as `nprofile1${string}`)
|
||||
: undefined
|
||||
profilePubkey = value?.data.pubkey
|
||||
} catch (error) {
|
||||
// Silently ignore and redirect to home or logged in user
|
||||
log(true, LogType.Error, 'Failed to decode nprofile.', error)
|
||||
}
|
||||
|
||||
const scrollTargetRef = useRef<HTMLDivElement>(null)
|
||||
const { ndk, publish, fetchEventFromUserRelays, fetchMods } = useNDKContext()
|
||||
const userState = useAppSelector((state) => state.user)
|
||||
@ -292,22 +272,6 @@ export const ProfilePage = () => {
|
||||
profilePubkey
|
||||
)
|
||||
|
||||
// Redirect route
|
||||
let profileRoute = appRoutes.home
|
||||
if (!nprofile && userState.auth && userState.user) {
|
||||
// Redirect to user's profile is no profile is linked
|
||||
const userHexKey = npubToHex(userState.user.npub as string)
|
||||
|
||||
if (userHexKey) {
|
||||
profileRoute = getProfilePageRoute(
|
||||
nip19.nprofileEncode({
|
||||
pubkey: userHexKey
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
if (!profilePubkey) return <Navigate to={profileRoute} replace={true} />
|
||||
|
||||
return (
|
||||
<div className='InnerBodyMain'>
|
||||
<div className='ContainerMain'>
|
||||
|
@ -4,9 +4,10 @@ import { LoaderFunctionArgs, redirect } from 'react-router-dom'
|
||||
import { appRoutes, getProfilePageRoute } from 'routes'
|
||||
import { store } from 'store'
|
||||
import { MuteLists, UserProfile } from 'types'
|
||||
import { log, LogType } from 'utils'
|
||||
import { log, LogType, npubToHex } from 'utils'
|
||||
|
||||
export interface ProfilePageLoaderResult {
|
||||
profilePubkey: string
|
||||
profile: UserProfile
|
||||
isBlocked: boolean
|
||||
isOwnProfile: boolean
|
||||
@ -24,10 +25,25 @@ export const profileRouteLoader =
|
||||
const { nprofile } = params
|
||||
let profilePubkey: string | undefined
|
||||
try {
|
||||
const value = nprofile
|
||||
? nip19.decode(nprofile as `nprofile1${string}`)
|
||||
: undefined
|
||||
profilePubkey = value?.data.pubkey
|
||||
// Decode if it starts with nprofile1
|
||||
if (nprofile?.startsWith('nprofile1')) {
|
||||
const value = nprofile
|
||||
? nip19.decode(nprofile as `nprofile1${string}`)
|
||||
: undefined
|
||||
profilePubkey = value?.data.pubkey
|
||||
} else if (nprofile?.startsWith('npub1')) {
|
||||
// Try to get hex from the npub and encode it to nprofile
|
||||
const value = npubToHex(nprofile)
|
||||
if (value) {
|
||||
return redirect(
|
||||
getProfilePageRoute(
|
||||
nip19.nprofileEncode({
|
||||
pubkey: value
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently ignore and redirect to home or logged in user
|
||||
log(true, LogType.Error, 'Failed to decode nprofile.', error)
|
||||
@ -57,6 +73,7 @@ export const profileRouteLoader =
|
||||
|
||||
// Empty result
|
||||
const result: ProfilePageLoaderResult = {
|
||||
profilePubkey: profilePubkey,
|
||||
profile: {},
|
||||
isBlocked: false,
|
||||
isOwnProfile: false,
|
||||
|
18
src/styles/dotsSpinner.module.scss
Normal file
18
src/styles/dotsSpinner.module.scss
Normal file
@ -0,0 +1,18 @@
|
||||
.loading::after {
|
||||
content: '.';
|
||||
animation: dots 1.5s steps(4, end) infinite;
|
||||
}
|
||||
|
||||
@keyframes dots {
|
||||
0%,
|
||||
20% {
|
||||
content: '.\00a0\00a0';
|
||||
}
|
||||
40% {
|
||||
content: '..\00a0';
|
||||
}
|
||||
60%,
|
||||
100% {
|
||||
content: '...';
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user