diff --git a/app/layout.tsx b/app/layout.tsx index a4527a3..ffdc6a5 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,74 +1,27 @@ -'use client'; - import "./globals.css"; -import { NostrProvider } from "nostr-react"; -import { ThemeProvider } from "@/components/theme-provider"; -import { TopNavigation } from "@/components/headerComponents/TopNavigation"; -import BottomBar from "@/components/BottomBar"; import { Inter } from "next/font/google"; -import { Toaster } from "@/components/ui/toaster" -import Umami from "@/components/Umami"; -import { useEffect, useState } from "react"; +import type { Metadata } from "next"; +import ClientProviders from "@/components/providers"; const inter = Inter({ subsets: ["latin"] }); +export const metadata: Metadata = { + title: "LUMINA", + description: + "An effortless, enjoyable, and innovative way to capture, enhance, and share moments with everyone, decentralized and boundless.", + icons: { icon: "/favicon.ico" }, + manifest: "/manifest.json", +}; + export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { - const [relayUrls, setRelayUrls] = useState([ - "wss://relay.nostr.band", - "wss://relay.damus.io", - ]); - - useEffect(() => { - // Load custom relays from localStorage - try { - const customRelays = JSON.parse(localStorage.getItem("customRelays") || "[]"); - if (customRelays.length > 0) { - // Remove trailing slashes from any relay URLs - const sanitizedRelays = customRelays.map((relay: string) => - relay.endsWith('/') ? relay.slice(0, -1) : relay - ); - - setRelayUrls(prevRelays => { - // Combine default relays with custom relays, removing duplicates - const allRelays = [...prevRelays, ...sanitizedRelays]; - return Array.from(new Set(allRelays)); // Remove duplicates - }); - } - } catch (error) { - console.error("Error loading custom relays:", error); - } - }, []); - return ( - - - - LUMINA - - - - -
- - - - {children} - -
- -
+ {children} ); diff --git a/app/page.tsx b/app/page.tsx index 3659973..22c8e53 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,17 +1,9 @@ -"use client"; - import { Search } from "@/components/Search"; import { TrendingImagesNew } from "@/components/TrendingImagesNew"; import { GeyserFundDonation } from "@/components/GeyserFundDonation"; -import { useEffect } from "react"; export default function Home() { - useEffect(() => { - document.title = `LUMINA`; - }, []); - - // Check for environment variable - Next.js exposes public env vars with NEXT_PUBLIC_ prefix - const showGeyserFund = process.env.NEXT_PUBLIC_SHOW_GEYSER_FUND === 'true'; + const showGeyserFund = process.env.NEXT_PUBLIC_SHOW_GEYSER_FUND === "true"; return ( <> diff --git a/components/ProfilePageComponent.tsx b/components/ProfilePageComponent.tsx new file mode 100644 index 0000000..e69de29 diff --git a/components/Search.tsx b/components/Search.tsx index 58f374f..85fe3bf 100644 --- a/components/Search.tsx +++ b/components/Search.tsx @@ -1,3 +1,5 @@ +"use client"; + import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { queryProfile } from "nostr-tools/nip05" @@ -18,20 +20,17 @@ export function Search() { let value = inputValue.trim(); value = value.replaceAll('nostr:', ''); - if (value.startsWith('npub')) { // npub Search - // window.location.href = `/profile/${inputValue}`; + if (value.startsWith('npub')) { router.push(`/profile/${value}`); - } else if (value.startsWith('#')) { // Hashtag Search - // window.location.href = `/tag/${inputValue.replaceAll('#', '')}`; + } else if (value.startsWith('#')) { router.push(`/tag/${value.replaceAll('#', '')}`); - } else if(value.includes('@')) { // NIP-05 Search - // if inputValue starts with @, then add a "_" at the beginning + } else if(value.includes('@')) { if(value.startsWith('@')) { setInputValue('_' + value); } let profile = await queryProfile(value); - if(profile?.pubkey !== undefined) { // Only redirect if profile is found + if(profile?.pubkey !== undefined) { router.push(`/profile/${nip19.npubEncode(profile?.pubkey)}`); } } else { @@ -55,9 +54,8 @@ export function Search() { onChange={(e) => setInputValue(e.target.value)} onKeyDown={handleKeyDown} /> - {/* */} ) diff --git a/components/TrendingImageNew.tsx b/components/TrendingImageNew.tsx index 5e4f70e..620d457 100644 --- a/components/TrendingImageNew.tsx +++ b/components/TrendingImageNew.tsx @@ -1,3 +1,5 @@ +"use client"; + import React, { useState } from 'react'; import { useProfile } from "nostr-react"; import { nip19 } from "nostr-tools"; @@ -29,10 +31,7 @@ const TrendingImageNew: React.FC = ({ event }) => { pubkey: event.pubkey, }); - // Check if the event has nsfw or sexy tags const hasNsfwTag = hasNsfwContent(event.tags); - - // State to control image blur const [showSensitiveContent, setShowSensitiveContent] = useState(false); const npubShortened = (() => { @@ -44,7 +43,6 @@ const TrendingImageNew: React.FC = ({ event }) => { const title = userData?.username || userData?.display_name || userData?.name || userData?.npub || npubShortened; const text = event.content.replaceAll('\n', ' '); - // Get image URL from imeta tags const imageUrl = event.tags.find(tag => tag[0] === 'imeta' && tag[1]?.startsWith('url ')) ?.slice(1)[0]?.replace('url ', ''); @@ -52,7 +50,6 @@ const TrendingImageNew: React.FC = ({ event }) => { const hrefNote = `/note/${nip19.noteEncode(event.id)}`; const profileImageSrc = userData?.picture || "https://robohash.org/" + event.pubkey; - // Toggle sensitive content visibility const toggleSensitiveContent = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); diff --git a/components/TrendingImagesNew.tsx b/components/TrendingImagesNew.tsx index e0dec93..d9b7a5f 100644 --- a/components/TrendingImagesNew.tsx +++ b/components/TrendingImagesNew.tsx @@ -1,3 +1,5 @@ +"use client"; + import React, { useState, useEffect } from 'react'; import TrendingImage from '@/components/TrendingImageNew'; import { Spinner } from '@/components/spinner'; @@ -6,7 +8,6 @@ export function TrendingImagesNew() { const [events, setEvents] = useState([]); useEffect(() => { - // TODO: Fetch trending images from luminas own relay via http call fetch('https://relay.lumina.rocks/api/trending/kind20') .then(res => res.json()) .then(data => setEvents(data.trending)) @@ -20,7 +21,7 @@ export function TrendingImagesNew() {

Currently Trending

{events && events.length > 0 ? ( - events.map((event, index) => ( + events.map((event) => ( )) ) : ( diff --git a/components/providers.tsx b/components/providers.tsx new file mode 100644 index 0000000..27cb2c3 --- /dev/null +++ b/components/providers.tsx @@ -0,0 +1,67 @@ +"use client"; + +import { ReactNode, useEffect, useState } from "react"; +import { NostrProvider } from "nostr-react"; +import { ThemeProvider } from "@/components/theme-provider"; +import { TopNavigation } from "@/components/headerComponents/TopNavigation"; +import BottomBar from "@/components/BottomBar"; +import { Toaster } from "@/components/ui/toaster"; +import Umami from "@/components/Umami"; + +type ClientProvidersProps = { + children: ReactNode; +}; + +export default function ClientProviders({ children }: ClientProvidersProps) { + const [relayUrls, setRelayUrls] = useState([ + "wss://relay.nostr.band", + "wss://relay.damus.io", + ]); + + useEffect(() => { + try { + const stored = localStorage.getItem("customRelays"); + const customRelays: unknown = stored ? JSON.parse(stored) : []; + if (Array.isArray(customRelays) && customRelays.length > 0) { + const sanitizedRelays = customRelays + .filter((r): r is string => typeof r === "string") + .map((relay) => (relay.endsWith("/") ? relay.slice(0, -1) : relay)); + setRelayUrls((prev) => Array.from(new Set([...prev, ...sanitizedRelays]))); + } + } catch (error) { + console.error("Error loading custom relays:", error); + } + }, []); + + return ( + + +
+ + + + {children} + +
+ +
+ ); +} diff --git a/config/site.ts b/config/site.ts index 6def428..d2d7e69 100644 --- a/config/site.ts +++ b/config/site.ts @@ -1,11 +1,8 @@ -// Import package information dynamically -import packageInfo from "../package.json" - export type SiteConfig = typeof siteConfig export const siteConfig = { name: "LUMINA", - version: packageInfo.version, // Use the version from package.json + version: process.env.NEXT_PUBLIC_APP_VERSION ?? "0.0.0", description: "A beautiful Nostr client for images.", mainNav: [ diff --git a/next.config.mjs b/next.config.mjs index ea9ca17..ee2a99e 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -6,6 +6,7 @@ const config = { const nextConfig = { output: 'standalone', + reactStrictMode: true, images: { remotePatterns: [ {