feat: enhance layout and configuration

* Added reactStrictMode to next.config.mjs for improved performance.
* Refactored RootLayout to use ClientProviders for better structure and removed unused state management for relay URLs.
* Introduced metadata for the application in RootLayout.
* Cleaned up Home component by removing unnecessary useEffect for document title.
* Added new ProfilePageComponent and providers.tsx for better component organization.
* Updated TrendingImagesNew and Search components for improved functionality and code clarity.
This commit is contained in:
2025-08-07 23:34:40 +02:00
parent 7092821880
commit fdc1d3bc02
9 changed files with 93 additions and 87 deletions

View File

@@ -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<string[]>([
"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 (
<html lang="en">
<head>
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
<link rel="manifest" href="/manifest.json" />
<title>LUMINA</title>
<meta name="description" content="An effortless, enjoyable, and innovative way to capture, enhance, and share moments with everyone, decentralized and boundless." />
</head>
<body className={inter.className}>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem
disableTransitionOnChange
themes={["light", "dark", "purple-light", "purple-dark", "vintage-light", "vintage-dark", "neo-brutalism-light", "neo-brutalism-dark", "nature-light", "nature-dark", "system"]}
>
<Umami />
<div className="main-content pb-14">
<NostrProvider relayUrls={relayUrls} debug={false}>
<TopNavigation />
<Toaster />
{children}
</NostrProvider>
</div>
<BottomBar />
</ThemeProvider>
<ClientProviders>{children}</ClientProviders>
</body>
</html>
);

View File

@@ -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 (
<>

View File

View File

@@ -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}
/>
{/* <Button type="submit" onClick={calculateAndRedirect}>Search</Button> */}
<Button type="submit" onClick={calculateAndRedirect}>
{isLoading ? <ReloadIcon className="mr-2 h-4 w-4 animate-spin" /> : 'Search'} {/* Spinner-Komponente anzeigen, wenn geladen wird */}
{isLoading ? <ReloadIcon className="mr-2 h-4 w-4 animate-spin" /> : 'Search'}
</Button>
</div>
)

View File

@@ -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<TrendingImageNewProps> = ({ 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<TrendingImageNewProps> = ({ 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<TrendingImageNewProps> = ({ 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();

View File

@@ -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<any[]>([]);
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() {
<h1 className="text-3xl font-bold">Currently Trending</h1>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mt-6">
{events && events.length > 0 ? (
events.map((event, index) => (
events.map((event) => (
<TrendingImage key={event.id} event={event} />
))
) : (

67
components/providers.tsx Normal file
View File

@@ -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<string[]>([
"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 (
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem
disableTransitionOnChange
themes={[
"light",
"dark",
"purple-light",
"purple-dark",
"vintage-light",
"vintage-dark",
"neo-brutalism-light",
"neo-brutalism-dark",
"nature-light",
"nature-dark",
"system",
]}
>
<Umami />
<div className="main-content pb-14">
<NostrProvider relayUrls={relayUrls} debug={false}>
<TopNavigation />
<Toaster />
{children}
</NostrProvider>
</div>
<BottomBar />
</ThemeProvider>
);
}

View File

@@ -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: [

View File

@@ -6,6 +6,7 @@ const config = {
const nextConfig = {
output: 'standalone',
reactStrictMode: true,
images: {
remotePatterns: [
{