Add files via upload
This commit is contained in:
@@ -1,8 +1,21 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link, Outlet } from 'react-router-dom';
|
import { Link, Outlet, useSearchParams } from 'react-router-dom';
|
||||||
import { Navigation } from './Navigation';
|
import { Navigation } from './Navigation';
|
||||||
|
|
||||||
export function Layout() {
|
export function Layout() {
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const isIframe = searchParams.get('iframe') === '1';
|
||||||
|
|
||||||
|
if (isIframe) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-900 text-white">
|
||||||
|
<main className="container mx-auto px-4 py-4 md:py-8">
|
||||||
|
<Outlet />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-900 text-white">
|
<div className="min-h-screen bg-gray-900 text-white">
|
||||||
<Navigation />
|
<Navigation />
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
import { Link, useLocation, useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { Flame, Menu, X } from 'lucide-react';
|
import { Flame, Menu, X } from 'lucide-react';
|
||||||
import { useStore } from '../store/useStore';
|
import { useStore } from '../store/useStore';
|
||||||
|
|
||||||
export function Navigation() {
|
export function Navigation() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
const { user, setUser } = useStore();
|
const { user, setUser } = useStore();
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
|
const isIframe = searchParams.get('iframe') === '1';
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
setUser(null);
|
setUser(null);
|
||||||
setIsMenuOpen(false);
|
setIsMenuOpen(false);
|
||||||
navigate('/login', { replace: true }); // Use replace to prevent going back to dashboard
|
const newPath = '/login' + (isIframe ? '?iframe=1' : '');
|
||||||
|
navigate(newPath, { replace: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMenuClick = () => {
|
const handleMenuClick = () => {
|
||||||
@@ -23,11 +26,16 @@ export function Navigation() {
|
|||||||
setIsMenuOpen(false);
|
setIsMenuOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper function to add iframe parameter to paths
|
||||||
|
const getPath = (path: string) => {
|
||||||
|
return isIframe ? `${path}?iframe=1` : path;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="border-b border-gray-800 bg-gray-900 sticky top-0 z-50">
|
<nav className="border-b border-gray-800 bg-gray-900 sticky top-0 z-50">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<div className="flex items-center justify-between h-16">
|
<div className="flex items-center justify-between h-16">
|
||||||
<Link to="/" className="flex items-center space-x-2" onClick={handleNavigation}>
|
<Link to={getPath('/')} className="flex items-center space-x-2" onClick={handleNavigation}>
|
||||||
{import.meta.env.VITE_LOGO_URL ? (
|
{import.meta.env.VITE_LOGO_URL ? (
|
||||||
<img
|
<img
|
||||||
src={import.meta.env.VITE_LOGO_URL}
|
src={import.meta.env.VITE_LOGO_URL}
|
||||||
@@ -47,7 +55,7 @@ export function Navigation() {
|
|||||||
{user ? (
|
{user ? (
|
||||||
<>
|
<>
|
||||||
<Link
|
<Link
|
||||||
to="/dashboard"
|
to={getPath('/dashboard')}
|
||||||
className="px-4 py-2 rounded-lg hover:bg-gray-800 transition-colors"
|
className="px-4 py-2 rounded-lg hover:bg-gray-800 transition-colors"
|
||||||
>
|
>
|
||||||
Dashboard
|
Dashboard
|
||||||
@@ -61,7 +69,7 @@ export function Navigation() {
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Link
|
<Link
|
||||||
to="/login"
|
to={getPath('/login')}
|
||||||
className="px-4 py-2 bg-orange-500 rounded-lg hover:bg-orange-600 transition-colors"
|
className="px-4 py-2 bg-orange-500 rounded-lg hover:bg-orange-600 transition-colors"
|
||||||
>
|
>
|
||||||
Connect Nostr
|
Connect Nostr
|
||||||
@@ -89,7 +97,7 @@ export function Navigation() {
|
|||||||
{user ? (
|
{user ? (
|
||||||
<>
|
<>
|
||||||
<Link
|
<Link
|
||||||
to="/dashboard"
|
to={getPath('/dashboard')}
|
||||||
onClick={handleNavigation}
|
onClick={handleNavigation}
|
||||||
className="px-4 py-2 rounded-lg hover:bg-gray-800 transition-colors"
|
className="px-4 py-2 rounded-lg hover:bg-gray-800 transition-colors"
|
||||||
>
|
>
|
||||||
@@ -104,7 +112,7 @@ export function Navigation() {
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Link
|
<Link
|
||||||
to="/login"
|
to={getPath('/login')}
|
||||||
onClick={handleNavigation}
|
onClick={handleNavigation}
|
||||||
className="px-4 py-2 bg-orange-500 rounded-lg hover:bg-orange-600 transition-colors text-center"
|
className="px-4 py-2 bg-orange-500 rounded-lg hover:bg-orange-600 transition-colors text-center"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { Shield, AlertTriangle, Zap, Copy, Activity, Users } from 'lucide-react';
|
import { Shield, AlertTriangle, Zap, Copy, Activity, Users, LogOut } from 'lucide-react';
|
||||||
import { useStore } from '../store/useStore';
|
import { useStore } from '../store/useStore';
|
||||||
import { apiService } from '../services/api';
|
import { apiService } from '../services/api';
|
||||||
import { Notification } from '../components/Notification';
|
import { Notification } from '../components/Notification';
|
||||||
@@ -15,6 +15,8 @@ export function Dashboard() {
|
|||||||
const isDemoMode = import.meta.env.VITE_ENABLE_DEMO === 'true';
|
const isDemoMode = import.meta.env.VITE_ENABLE_DEMO === 'true';
|
||||||
const apiUrl = import.meta.env.VITE_API_URL;
|
const apiUrl = import.meta.env.VITE_API_URL;
|
||||||
const { isVisible, message, type, showNotification, hideNotification } = useNotification();
|
const { isVisible, message, type, showNotification, hideNotification } = useNotification();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const isIframe = searchParams.get('iframe') === '1';
|
||||||
|
|
||||||
// Check user authentication and fetch status once
|
// Check user authentication and fetch status once
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -34,7 +36,6 @@ export function Dashboard() {
|
|||||||
npub: userInfo.npub,
|
npub: userInfo.npub,
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// If user is not found (404) or any other error, assume not whitelisted
|
|
||||||
if (error.response?.status === 404 || error) {
|
if (error.response?.status === 404 || error) {
|
||||||
setUser({
|
setUser({
|
||||||
...user,
|
...user,
|
||||||
@@ -48,7 +49,7 @@ export function Dashboard() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
checkUserStatus();
|
checkUserStatus();
|
||||||
}, [user?.pubkey, navigate, setUser, isDemoMode]); // Only run on mount and when these dependencies change
|
}, [user?.pubkey, navigate, setUser, isDemoMode]);
|
||||||
|
|
||||||
// Fetch uptime and active users
|
// Fetch uptime and active users
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -109,6 +110,11 @@ export function Dashboard() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
setUser(null);
|
||||||
|
navigate('/login');
|
||||||
|
};
|
||||||
|
|
||||||
if (loading || !user) {
|
if (loading || !user) {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center items-center min-h-[400px]">
|
<div className="flex justify-center items-center min-h-[400px]">
|
||||||
@@ -223,6 +229,19 @@ export function Dashboard() {
|
|||||||
<p className="text-gray-400">Active Users</p>
|
<p className="text-gray-400">Active Users</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Logout Button (only shown in iframe mode) */}
|
||||||
|
{isIframe && (
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<button
|
||||||
|
onClick={handleLogout}
|
||||||
|
className="flex items-center space-x-2 px-6 py-3 bg-gray-800 hover:bg-gray-700 rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
<LogOut className="h-5 w-5" />
|
||||||
|
<span>Logout</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Notification
|
<Notification
|
||||||
isVisible={isVisible}
|
isVisible={isVisible}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link, useSearchParams } from 'react-router-dom';
|
||||||
import { Flame, Zap, Shield, Globe, Server, Code, Cpu, Copy } from 'lucide-react';
|
import { Flame, Zap, Shield, Globe, Server, Code, Cpu, Copy } from 'lucide-react';
|
||||||
import { Notification } from '../components/Notification';
|
import { Notification } from '../components/Notification';
|
||||||
import { useNotification } from '../hooks/useNotification';
|
import { useNotification } from '../hooks/useNotification';
|
||||||
|
|
||||||
export function Home() {
|
export function Home() {
|
||||||
const { isVisible, message, type, showNotification, hideNotification } = useNotification();
|
const { isVisible, message, type, showNotification, hideNotification } = useNotification();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const isIframe = searchParams.get('iframe') === '1';
|
||||||
|
|
||||||
const copyToClipboard = async (text: string) => {
|
const copyToClipboard = async (text: string) => {
|
||||||
try {
|
try {
|
||||||
@@ -26,7 +28,7 @@ export function Home() {
|
|||||||
A high-performance Nostr relay built by Bitcoiners, for Bitcoiners
|
A high-performance Nostr relay built by Bitcoiners, for Bitcoiners
|
||||||
</p>
|
</p>
|
||||||
<Link
|
<Link
|
||||||
to="/login"
|
to={isIframe ? '/login?iframe=1' : '/login'}
|
||||||
className="inline-block px-8 py-4 bg-orange-500 rounded-lg hover:bg-orange-600 transition-colors font-semibold text-lg"
|
className="inline-block px-8 py-4 bg-orange-500 rounded-lg hover:bg-orange-600 transition-colors font-semibold text-lg"
|
||||||
>
|
>
|
||||||
Get Started
|
Get Started
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { Loader2, Zap } from 'lucide-react';
|
import { Loader2, Zap } from 'lucide-react';
|
||||||
import { useStore } from '../store/useStore';
|
import { useStore } from '../store/useStore';
|
||||||
|
|
||||||
export function Login() {
|
export function Login() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
const { user, setUser } = useStore();
|
const { user, setUser } = useStore();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [pubkeyInput, setPubkeyInput] = useState('');
|
const [pubkeyInput, setPubkeyInput] = useState('');
|
||||||
|
const isIframe = searchParams.get('iframe') === '1';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user) {
|
if (user) {
|
||||||
navigate('/dashboard');
|
navigate(isIframe ? '/dashboard?iframe=1' : '/dashboard');
|
||||||
}
|
}
|
||||||
}, [user, navigate]);
|
}, [user, navigate, isIframe]);
|
||||||
|
|
||||||
const handleExtensionLogin = async () => {
|
const handleExtensionLogin = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@@ -33,9 +35,8 @@ export function Login() {
|
|||||||
throw new Error('No public key found');
|
throw new Error('No public key found');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simply store the pubkey and navigate to dashboard
|
|
||||||
setUser({ pubkey, isWhitelisted: false });
|
setUser({ pubkey, isWhitelisted: false });
|
||||||
navigate('/dashboard');
|
navigate(isIframe ? '/dashboard?iframe=1' : '/dashboard');
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.message === 'Rejected by user') {
|
if (error.message === 'Rejected by user') {
|
||||||
return;
|
return;
|
||||||
@@ -62,9 +63,8 @@ export function Login() {
|
|||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
// Simply store the pubkey and navigate to dashboard
|
|
||||||
setUser({ pubkey: pubkeyInput.trim(), isWhitelisted: false });
|
setUser({ pubkey: pubkeyInput.trim(), isWhitelisted: false });
|
||||||
navigate('/dashboard');
|
navigate(isIframe ? '/dashboard?iframe=1' : '/dashboard');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Login failed:', error);
|
console.error('Login failed:', error);
|
||||||
alert('Failed to login with the provided public key. Please check the format and try again.');
|
alert('Failed to login with the provided public key. Please check the format and try again.');
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import { Copy, CheckCircle } from 'lucide-react';
|
import { Copy, CheckCircle } from 'lucide-react';
|
||||||
import { useStore } from '../store/useStore';
|
import { useStore } from '../store/useStore';
|
||||||
@@ -11,6 +11,7 @@ import { useNotification } from '../hooks/useNotification';
|
|||||||
|
|
||||||
export function Payment() {
|
export function Payment() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
const { user, setUser } = useStore();
|
const { user, setUser } = useStore();
|
||||||
const [invoice, setInvoice] = useState<LightningInvoice | null>(null);
|
const [invoice, setInvoice] = useState<LightningInvoice | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -19,15 +20,16 @@ export function Payment() {
|
|||||||
const { isVisible, message, type, showNotification, hideNotification } = useNotification();
|
const { isVisible, message, type, showNotification, hideNotification } = useNotification();
|
||||||
const [checkingPayment, setCheckingPayment] = useState(false);
|
const [checkingPayment, setCheckingPayment] = useState(false);
|
||||||
const [showSuccess, setShowSuccess] = useState(false);
|
const [showSuccess, setShowSuccess] = useState(false);
|
||||||
|
const isIframe = searchParams.get('iframe') === '1';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
navigate('/login');
|
navigate(isIframe ? '/login?iframe=1' : '/login');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.isWhitelisted) {
|
if (user.isWhitelisted) {
|
||||||
navigate('/dashboard');
|
navigate(isIframe ? '/dashboard?iframe=1' : '/dashboard');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,12 +61,12 @@ export function Payment() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
generateInvoice();
|
generateInvoice();
|
||||||
}, [user, navigate]);
|
}, [user, navigate, isIframe]);
|
||||||
|
|
||||||
const handlePaymentSuccess = async () => {
|
const handlePaymentSuccess = async () => {
|
||||||
setShowSuccess(true);
|
setShowSuccess(true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
navigate('/thank-you');
|
navigate(isIframe ? '/thank-you?iframe=1' : '/thank-you');
|
||||||
}, 1500);
|
}, 1500);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@@ -147,7 +149,6 @@ export function Payment() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="max-w-md mx-auto bg-gray-800 rounded-lg p-8 relative">
|
<div className="max-w-md mx-auto bg-gray-800 rounded-lg p-8 relative">
|
||||||
{/* Success Animation Overlay */}
|
|
||||||
{showSuccess && (
|
{showSuccess && (
|
||||||
<div className="absolute inset-0 bg-gray-900/95 flex items-center justify-center rounded-lg animate-fade-in z-10">
|
<div className="absolute inset-0 bg-gray-900/95 flex items-center justify-center rounded-lg animate-fade-in z-10">
|
||||||
<div className="text-center animate-success-appear">
|
<div className="text-center animate-success-appear">
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { Shield } from 'lucide-react';
|
import { Shield } from 'lucide-react';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
|
|
||||||
export function Terms() {
|
export function Terms() {
|
||||||
const [terms, setTerms] = useState<string>('');
|
const [terms, setTerms] = useState<string>('');
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const isIframe = searchParams.get('iframe') === '1';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchTerms = async () => {
|
const fetchTerms = async () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { CheckCircle, Copy, ArrowRight } from 'lucide-react';
|
import { CheckCircle, Copy, ArrowRight } from 'lucide-react';
|
||||||
import { useStore } from '../store/useStore';
|
import { useStore } from '../store/useStore';
|
||||||
import confetti from 'canvas-confetti';
|
import confetti from 'canvas-confetti';
|
||||||
@@ -8,13 +8,15 @@ import { useNotification } from '../hooks/useNotification';
|
|||||||
|
|
||||||
export function ThankYou() {
|
export function ThankYou() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
const { user } = useStore();
|
const { user } = useStore();
|
||||||
const { isVisible, message, type, showNotification, hideNotification } = useNotification();
|
const { isVisible, message, type, showNotification, hideNotification } = useNotification();
|
||||||
const relayUrl = import.meta.env.VITE_NOSTR_RELAY_URL;
|
const relayUrl = import.meta.env.VITE_NOSTR_RELAY_URL;
|
||||||
|
const isIframe = searchParams.get('iframe') === '1';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
navigate('/login');
|
navigate(isIframe ? '/login?iframe=1' : '/login');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +38,6 @@ export function ThankYou() {
|
|||||||
|
|
||||||
const particleCount = 50 * (timeLeft / duration);
|
const particleCount = 50 * (timeLeft / duration);
|
||||||
|
|
||||||
// Since they fall down, start a bit higher than random
|
|
||||||
confetti({
|
confetti({
|
||||||
...defaults,
|
...defaults,
|
||||||
particleCount,
|
particleCount,
|
||||||
@@ -50,7 +51,7 @@ export function ThankYou() {
|
|||||||
}, 250);
|
}, 250);
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [user, navigate]);
|
}, [user, navigate, isIframe]);
|
||||||
|
|
||||||
const copyToClipboard = async (text: string) => {
|
const copyToClipboard = async (text: string) => {
|
||||||
try {
|
try {
|
||||||
@@ -114,7 +115,7 @@ export function ThankYou() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/dashboard')}
|
onClick={() => navigate(isIframe ? '/dashboard?iframe=1' : '/dashboard')}
|
||||||
className="px-8 py-4 bg-orange-500 rounded-lg hover:bg-orange-600 transition-colors font-semibold"
|
className="px-8 py-4 bg-orange-500 rounded-lg hover:bg-orange-600 transition-colors font-semibold"
|
||||||
>
|
>
|
||||||
Go to Dashboard
|
Go to Dashboard
|
||||||
|
|||||||
Reference in New Issue
Block a user