feat: improve onboarding

This commit is contained in:
codytseng
2025-12-23 11:51:37 +08:00
parent ef6d44d112
commit f8081b86bf
23 changed files with 157 additions and 66 deletions

View File

@@ -60,39 +60,41 @@ export default function FollowButton({ pubkey }: { pubkey: string }) {
}
return isFollowing ? (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
className="rounded-full min-w-28"
variant={hover ? 'destructive' : 'secondary'}
disabled={updating}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
{updating ? (
<Loader className="animate-spin" />
) : hover ? (
t('Unfollow')
) : (
t('buttonFollowing')
)}
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t('Unfollow')}?</AlertDialogTitle>
<AlertDialogDescription>
{t('Are you sure you want to unfollow this user?')}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t('Cancel')}</AlertDialogCancel>
<AlertDialogAction onClick={handleUnfollow} variant="destructive">
{t('Unfollow')}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<div onClick={(e) => e.stopPropagation()}>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
className="rounded-full min-w-28"
variant={hover ? 'destructive' : 'secondary'}
disabled={updating}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
{updating ? (
<Loader className="animate-spin" />
) : hover ? (
t('Unfollow')
) : (
t('buttonFollowing')
)}
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t('Unfollow')}?</AlertDialogTitle>
<AlertDialogDescription>
{t('Are you sure you want to unfollow this user?')}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t('Cancel')}</AlertDialogCancel>
<AlertDialogAction onClick={handleUnfollow} variant="destructive">
{t('Unfollow')}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
) : (
<Button className="rounded-full min-w-28" onClick={handleFollow} disabled={updating}>
{updating ? <Loader className="animate-spin" /> : t('Follow')}

View File

@@ -589,6 +589,9 @@ export default {
'Write your thoughts about this highlight...': 'اكتب أفكارك حول هذا التمييز...',
'Publish Highlight': 'نشر التمييز',
'Show replies': 'إظهار الردود',
'Hide replies': 'إخفاء الردود'
'Hide replies': 'إخفاء الردود',
'Welcome to Jumble!': 'مرحبًا بك في Jumble!',
'Your feed is empty because you are not following anyone yet. Start by exploring interesting content and following users you like!': 'خلاصتك فارغة لأنك لا تتابع أي شخص بعد. ابدأ باستكشاف محتوى مثير للاهتمام ومتابعة المستخدمين الذين تحبهم!',
'Search Users': 'البحث عن المستخدمين'
}
}

View File

@@ -606,6 +606,9 @@ export default {
'Schreiben Sie Ihre Gedanken zu dieser Markierung...',
'Publish Highlight': 'Markierung Veröffentlichen',
'Show replies': 'Antworten anzeigen',
'Hide replies': 'Antworten ausblenden'
'Hide replies': 'Antworten ausblenden',
'Welcome to Jumble!': 'Willkommen bei Jumble!',
'Your feed is empty because you are not following anyone yet. Start by exploring interesting content and following users you like!': 'Ihr Feed ist leer, weil Sie noch niemandem folgen. Beginnen Sie damit, interessante Inhalte zu erkunden und Benutzern zu folgen, die Ihnen gefallen!',
'Search Users': 'Benutzer suchen'
}
}

View File

@@ -592,6 +592,9 @@ export default {
'Write your thoughts about this highlight...': 'Write your thoughts about this highlight...',
'Publish Highlight': 'Publish Highlight',
'Show replies': 'Show replies',
'Hide replies': 'Hide replies'
'Hide replies': 'Hide replies',
'Welcome to Jumble!': 'Welcome to Jumble!',
'Your feed is empty because you are not following anyone yet. Start by exploring interesting content and following users you like!': 'Your feed is empty because you are not following anyone yet. Start by exploring interesting content and following users you like!',
'Search Users': 'Search Users'
}
}

View File

@@ -602,6 +602,9 @@ export default {
'Escribe tus pensamientos sobre este resaltado...',
'Publish Highlight': 'Publicar Resaltado',
'Show replies': 'Mostrar respuestas',
'Hide replies': 'Ocultar respuestas'
'Hide replies': 'Ocultar respuestas',
'Welcome to Jumble!': '¡Bienvenido a Jumble!',
'Your feed is empty because you are not following anyone yet. Start by exploring interesting content and following users you like!': 'Tu feed está vacío porque aún no sigues a nadie. ¡Comienza explorando contenido interesante y siguiendo a los usuarios que te gusten!',
'Search Users': 'Buscar Usuarios'
}
}

View File

@@ -595,6 +595,9 @@ export default {
'Write your thoughts about this highlight...': 'نظرات خود را درباره این برجسته‌سازی بنویسید...',
'Publish Highlight': 'انتشار برجسته‌سازی',
'Show replies': 'نمایش پاسخ‌ها',
'Hide replies': 'پنهان کردن پاسخ‌ها'
'Hide replies': 'پنهان کردن پاسخ‌ها',
'Welcome to Jumble!': 'به Jumble خوش آمدید!',
'Your feed is empty because you are not following anyone yet. Start by exploring interesting content and following users you like!': 'فید شما خالی است زیرا هنوز کسی را دنبال نمی‌کنید. با کاوش محتوای جالب و دنبال کردن کاربرانی که دوست دارید شروع کنید!',
'Search Users': 'جستجوی کاربران'
}
}

View File

@@ -604,6 +604,9 @@ export default {
'Write your thoughts about this highlight...': 'Écrivez vos pensées sur ce surlignage...',
'Publish Highlight': 'Publier le Surlignage',
'Show replies': 'Afficher les réponses',
'Hide replies': 'Masquer les réponses'
'Hide replies': 'Masquer les réponses',
'Welcome to Jumble!': 'Bienvenue sur Jumble !',
'Your feed is empty because you are not following anyone yet. Start by exploring interesting content and following users you like!': 'Votre flux est vide car vous ne suivez personne pour le moment. Commencez par explorer du contenu intéressant et suivez les utilisateurs que vous aimez !',
'Search Users': 'Rechercher des utilisateurs'
}
}

View File

@@ -596,6 +596,9 @@ export default {
'Write your thoughts about this highlight...': 'इस हाइलाइट के बारे में अपने विचार लिखें...',
'Publish Highlight': 'हाइलाइट प्रकाशित करें',
'Show replies': 'जवाब दिखाएं',
'Hide replies': 'जवाब छुपाएं'
'Hide replies': 'जवाब छुपाएं',
'Welcome to Jumble!': 'Jumble में आपका स्वागत है!',
'Your feed is empty because you are not following anyone yet. Start by exploring interesting content and following users you like!': 'आपका फ़ीड खाली है क्योंकि आप अभी तक किसी को फ़ॉलो नहीं कर रहे हैं। दिलचस्प सामग्री का अन्वेषण करके और अपनी पसंद के उपयोगकर्ताओं को फ़ॉलो करके शुरू करें!',
'Search Users': 'उपयोगकर्ता खोजें'
}
}

View File

@@ -590,6 +590,9 @@ export default {
'Write your thoughts about this highlight...': 'Írd le a gondolataidat erről a kiemelésről...',
'Publish Highlight': 'Kiemelés Közzététele',
'Show replies': 'Válaszok megjelenítése',
'Hide replies': 'Válaszok elrejtése'
'Hide replies': 'Válaszok elrejtése',
'Welcome to Jumble!': 'Üdvözlünk a Jumble-ban!',
'Your feed is empty because you are not following anyone yet. Start by exploring interesting content and following users you like!': 'A hírcsatornád üres, mert még nem követsz senkit. Kezdd el érdekes tartalmak felfedezésével és kövesd azokat a felhasználókat, akik tetszenek!',
'Search Users': 'Felhasználók keresése'
}
}

View File

@@ -601,6 +601,9 @@ export default {
'Scrivi i tuoi pensieri su questa evidenziazione...',
'Publish Highlight': 'Pubblica Evidenziazione',
'Show replies': 'Mostra risposte',
'Hide replies': 'Nascondi risposte'
'Hide replies': 'Nascondi risposte',
'Welcome to Jumble!': 'Benvenuto su Jumble!',
'Your feed is empty because you are not following anyone yet. Start by exploring interesting content and following users you like!': 'Il tuo feed è vuoto perché non stai ancora seguendo nessuno. Inizia esplorando contenuti interessanti e seguendo gli utenti che ti piacciono!',
'Search Users': 'Cerca Utenti'
}
}

View File

@@ -596,6 +596,9 @@ export default {
'このハイライトについての考えを書いてください...',
'Publish Highlight': 'ハイライトを公開',
'Show replies': '返信を表示',
'Hide replies': '返信を非表示'
'Hide replies': '返信を非表示',
'Welcome to Jumble!': 'Jumbleへようこそ',
'Your feed is empty because you are not following anyone yet. Start by exploring interesting content and following users you like!': 'まだ誰もフォローしていないため、フィードが空です。興味深いコンテンツを探索して、好きなユーザーをフォローしてみましょう!',
'Search Users': 'ユーザーを検索'
}
}

View File

@@ -594,6 +594,9 @@ export default {
'Write your thoughts about this highlight...': '이 하이라이트에 대한 생각을 작성하세요...',
'Publish Highlight': '하이라이트 게시',
'Show replies': '답글 표시',
'Hide replies': '답글 숨기기'
'Hide replies': '답글 숨기기',
'Welcome to Jumble!': 'Jumble에 오신 것을 환영합니다!',
'Your feed is empty because you are not following anyone yet. Start by exploring interesting content and following users you like!': '아직 아무도 팔로우하지 않아서 피드가 비어 있습니다. 흥미로운 콘텐츠를 탐색하고 마음에 드는 사용자를 팔로우해보세요!',
'Search Users': '사용자 검색'
}
}

View File

@@ -602,6 +602,9 @@ export default {
'Napisz swoje przemyślenia na temat tego wyróżnienienia...',
'Publish Highlight': 'Opublikuj wyróżnienie',
'Show replies': 'Pokaż odpowiedzi',
'Hide replies': 'Ukryj odpowiedzi'
'Hide replies': 'Ukryj odpowiedzi',
'Welcome to Jumble!': 'Witamy w Jumble!',
'Your feed is empty because you are not following anyone yet. Start by exploring interesting content and following users you like!': 'Twój kanał jest pusty, ponieważ jeszcze nikogo nie obserwujesz. Zacznij od odkrywania ciekawych treści i obserwowania użytkowników, którzy Ci się podobają!',
'Search Users': 'Szukaj użytkowników'
}
}

View File

@@ -597,6 +597,9 @@ export default {
'Escreva seus pensamentos sobre este destaque...',
'Publish Highlight': 'Publicar Destaque',
'Show replies': 'Mostrar respostas',
'Hide replies': 'Ocultar respostas'
'Hide replies': 'Ocultar respostas',
'Welcome to Jumble!': 'Bem-vindo ao Jumble!',
'Your feed is empty because you are not following anyone yet. Start by exploring interesting content and following users you like!': 'Seu feed está vazio porque você ainda não está seguindo ninguém. Comece explorando conteúdo interessante e seguindo usuários que você gosta!',
'Search Users': 'Buscar Usuários'
}
}

View File

@@ -600,6 +600,9 @@ export default {
'Escreva os seus pensamentos sobre este destaque...',
'Publish Highlight': 'Publicar Destaque',
'Show replies': 'Mostrar respostas',
'Hide replies': 'Ocultar respostas'
'Hide replies': 'Ocultar respostas',
'Welcome to Jumble!': 'Bem-vindo ao Jumble!',
'Your feed is empty because you are not following anyone yet. Start by exploring interesting content and following users you like!': 'O seu feed está vazio porque ainda não está a seguir ninguém. Comece por explorar conteúdo interessante e siga utilizadores de que gosta!',
'Search Users': 'Procurar Utilizadores'
}
}

View File

@@ -601,6 +601,9 @@ export default {
'Write your thoughts about this highlight...': 'Напишите свои мысли об этом выделении...',
'Publish Highlight': 'Опубликовать Выделение',
'Show replies': 'Показать ответы',
'Hide replies': 'Скрыть ответы'
'Hide replies': 'Скрыть ответы',
'Welcome to Jumble!': 'Добро пожаловать в Jumble!',
'Your feed is empty because you are not following anyone yet. Start by exploring interesting content and following users you like!': 'Ваша лента пуста, потому что вы еще ни на кого не подписаны. Начните с изучения интересного контента и подписки на понравившихся пользователей!',
'Search Users': 'Поиск пользователей'
}
}

View File

@@ -588,6 +588,9 @@ export default {
'Write your thoughts about this highlight...': 'เขียนความคิดของคุณเกี่ยวกับไฮไลท์นี้...',
'Publish Highlight': 'เผยแพร่ไฮไลท์',
'Show replies': 'แสดงการตอบกลับ',
'Hide replies': 'ซ่อนการตอบกลับ'
'Hide replies': 'ซ่อนการตอบกลับ',
'Welcome to Jumble!': 'ยินดีต้อนรับสู่ Jumble!',
'Your feed is empty because you are not following anyone yet. Start by exploring interesting content and following users you like!': 'ฟีดของคุณว่างเปล่าเพราะคุณยังไม่ได้ติดตามใครเลย เริ่มต้นด้วยการสำรวจเนื้อหาที่น่าสนใจและติดตามผู้ใช้ที่คุณชอบ!',
'Search Users': 'ค้นหาผู้ใช้'
}
}

View File

@@ -575,6 +575,9 @@ export default {
'Special Follow': '特別關注',
'Unfollow Special': '取消特別關注',
'Personal Feeds': '個人訂閱',
'Relay Feeds': '中繼訂閱'
'Relay Feeds': '中繼訂閱',
'Welcome to Jumble!': '歡迎來到 Jumble',
'Your feed is empty because you are not following anyone yet. Start by exploring interesting content and following users you like!': '你的動態是空的,因為你還沒有關注任何人。開始探索有趣的內容並關注你喜歡的用戶吧!',
'Search Users': '搜尋用戶'
}
}

View File

@@ -581,6 +581,9 @@ export default {
'Write your thoughts about this highlight...': '写下你对这段高亮的想法...',
'Publish Highlight': '发布高亮',
'Show replies': '显示回复',
'Hide replies': '隐藏回复'
'Hide replies': '隐藏回复',
'Welcome to Jumble!': '欢迎来到 Jumble',
'Your feed is empty because you are not following anyone yet. Start by exploring interesting content and following users you like!': '你的动态是空的,因为你还没有关注任何人。开始探索有趣的内容并关注你喜欢的用户吧!',
'Search Users': '搜索用户'
}
}

View File

@@ -1,28 +1,69 @@
import NormalFeed from '@/components/NormalFeed'
import { useFeed } from '@/providers/FeedProvider'
import { Button } from '@/components/ui/button'
import { usePrimaryPage } from '@/PageManager'
import { useFollowList } from '@/providers/FollowListProvider'
import { useNostr } from '@/providers/NostrProvider'
import client from '@/services/client.service'
import { TFeedSubRequest } from '@/types'
import { useEffect, useState } from 'react'
import { Compass, Search, UserPlus } from 'lucide-react'
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
export default function FollowingFeed() {
const { t } = useTranslation()
const { pubkey } = useNostr()
const { feedInfo } = useFeed()
const { followingSet } = useFollowList()
const { navigate } = usePrimaryPage()
const [subRequests, setSubRequests] = useState<TFeedSubRequest[]>([])
const [hasFollowings, setHasFollowings] = useState<boolean | null>(null)
const initializedRef = useRef(false)
useEffect(() => {
if (initializedRef.current) return
async function init() {
if (feedInfo?.feedType !== 'following' || !pubkey) {
if (!pubkey) {
setSubRequests([])
setHasFollowings(null)
return
}
const followings = await client.fetchFollowings(pubkey)
setHasFollowings(followings.length > 0)
setSubRequests(await client.generateSubRequestsForPubkeys([pubkey, ...followings], pubkey))
if (followings.length) {
initializedRef.current = true
}
}
init()
}, [feedInfo?.feedType, pubkey])
}, [pubkey, followingSet])
// Show empty state when user has no followings
if (hasFollowings === false && subRequests.length > 0) {
return (
<div className="flex flex-col items-center justify-center min-h-[60vh] px-6 text-center">
<UserPlus size={64} className="text-muted-foreground mb-4" strokeWidth={1.5} />
<h2 className="text-2xl font-semibold mb-2">{t('Welcome to Jumble!')}</h2>
<p className="text-muted-foreground mb-6 max-w-md">
{t(
'Your feed is empty because you are not following anyone yet. Start by exploring interesting content and following users you like!'
)}
</p>
<div className="flex flex-col sm:flex-row gap-3 w-full max-w-md">
<Button size="lg" onClick={() => navigate('explore')} className="w-full">
<Compass className="size-5" />
{t('Explore')}
</Button>
<Button size="lg" variant="outline" onClick={() => navigate('search')} className="w-full">
<Search className="size-5" />
{t('Search Users')}
</Button>
</div>
</div>
)
}
return <NormalFeed subRequests={subRequests} isMainFeed />
}

View File

@@ -1,5 +1,4 @@
import NormalFeed from '@/components/NormalFeed'
import { useFeed } from '@/providers/FeedProvider'
import { useNostr } from '@/providers/NostrProvider'
import { usePinnedUsers } from '@/providers/PinnedUsersProvider'
import client from '@/services/client.service'
@@ -8,7 +7,6 @@ import { useEffect, useRef, useState } from 'react'
export default function PinnedFeed() {
const { pubkey } = useNostr()
const { feedInfo } = useFeed()
const { pinnedPubkeySet } = usePinnedUsers()
const [subRequests, setSubRequests] = useState<TFeedSubRequest[]>([])
const initializedRef = useRef(false)
@@ -17,7 +15,7 @@ export default function PinnedFeed() {
if (initializedRef.current) return
async function init() {
if (feedInfo?.feedType !== 'pinned' || !pubkey || pinnedPubkeySet.size === 0) {
if (!pubkey || pinnedPubkeySet.size === 0) {
setSubRequests([])
return
}
@@ -28,7 +26,7 @@ export default function PinnedFeed() {
}
init()
}, [feedInfo?.feedType, pubkey, pinnedPubkeySet])
}, [pubkey, pinnedPubkeySet])
return <NormalFeed subRequests={subRequests} isMainFeed />
}

View File

@@ -5,7 +5,7 @@ import relayInfoService from '@/services/relay-info.service'
import { useEffect, useState } from 'react'
export default function RelaysFeed() {
const { feedInfo, relayUrls } = useFeed()
const { relayUrls } = useFeed()
const [isReady, setIsReady] = useState(false)
const [areAlgoRelays, setAreAlgoRelays] = useState(false)
@@ -22,10 +22,6 @@ export default function RelaysFeed() {
return null
}
if (!feedInfo || (feedInfo.feedType !== 'relay' && feedInfo.feedType !== 'relays')) {
return null
}
return (
<NormalFeed
subRequests={[{ urls: relayUrls, filter: {} }]}

View File

@@ -199,7 +199,7 @@ function WelcomeGuide() {
<div className="flex flex-col sm:flex-row gap-3 w-full max-w-md">
<Button size="lg" className="w-full" onClick={() => navigate('explore')}>
<Compass className="size-5" />
{t('Explore Relays')}
{t('Explore')}
</Button>
<Button size="lg" className="w-full" variant="outline" onClick={() => checkLogin()}>