mirror of
https://github.com/lumina-rocks/lumina.git
synced 2026-06-04 01:31:13 +02:00
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:
@@ -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>
|
||||
);
|
||||
|
||||
10
app/page.tsx
10
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 (
|
||||
<>
|
||||
|
||||
0
components/ProfilePageComponent.tsx
Normal file
0
components/ProfilePageComponent.tsx
Normal 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>
|
||||
)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
67
components/providers.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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: [
|
||||
|
||||
@@ -6,6 +6,7 @@ const config = {
|
||||
|
||||
const nextConfig = {
|
||||
output: 'standalone',
|
||||
reactStrictMode: true,
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user