diff --git a/package.json b/package.json index ffe6e6b..7d75976 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "preview": "vite preview" }, "dependencies": { + "@supabase/supabase-js": "2.110.9", "react": "19.1.1", "react-dom": "19.1.1" }, diff --git a/src/App.jsx b/src/App.jsx index 3992cd6..4595eb8 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,92 +1,438 @@ -import { useEffect, useRef } from 'react' +import { useEffect, useRef, useState } from 'react' +import { supabase } from './supabase.js' + +const ADMIN_EMAIL = 'dev@cod3uchiha.com' +const API_URL = 'https://api.cod3uchiha.com' + +function useHashRoute() { + const read = () => window.location.hash.replace(/^#\/?/, '') || 'home' + const [route, setRoute] = useState(read) + + useEffect(() => { + const onHashChange = () => setRoute(read()) + window.addEventListener('hashchange', onHashChange) + return () => window.removeEventListener('hashchange', onHashChange) + }, []) + + return route +} + +function AuthModal({ open, onClose }) { + const [mode, setMode] = useState('signin') + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [loading, setLoading] = useState(false) + const [message, setMessage] = useState('') + const [error, setError] = useState('') + + useEffect(() => { + if (!open) { + setPassword('') + setMessage('') + setError('') + } + }, [open]) + + if (!open) return null + + async function submit(event) { + event.preventDefault() + setLoading(true) + setMessage('') + setError('') + + try { + if (mode === 'signup') { + const { data, error: signUpError } = await supabase.auth.signUp({ email, password }) + if (signUpError) throw signUpError + if (data.session) { + onClose() + window.location.hash = 'dashboard' + } else { + setMessage('Account created. Check your email to confirm it, then sign in.') + } + } else { + const { error: signInError } = await supabase.auth.signInWithPassword({ email, password }) + if (signInError) throw signInError + onClose() + window.location.hash = 'dashboard' + } + } catch (authError) { + setError(authError.message || 'Authentication failed.') + } finally { + setLoading(false) + } + } + + return ( +
+ +
+ +
+ + + {error &&

{error}

} + {message &&

{message}

} + +
+ + + + + ) +} function Story() { return (

The Uchiha Clan and Coding

-

- The Uchiha Clan is renowned for their exceptional abilities and Sharingan eye technique. In the world of coding, we strive to develop our own unique skills and techniques, just like the Uchiha Clan. -

-

- Programming and developing, like the Sharingan, allow us to perceive and understand the intricacies of technology. With each line of code we write, we unlock new possibilities and shape the digital world around us. -

-

- As the Uchiha Clan sought to protect and advance their abilities, we, as developers, aim to use our coding skills to create innovative solutions, build amazing projects, and contribute to the ever-evolving field of technology. -

+

The Uchiha Clan is renowned for their exceptional abilities and Sharingan eye technique. In the world of coding, we strive to develop our own unique skills and techniques, just like the Uchiha Clan.

+

Programming and developing, like the Sharingan, allow us to perceive and understand the intricacies of technology. With each line of code we write, we unlock new possibilities and shape the digital world around us.

+

As the Uchiha Clan sought to protect and advance their abilities, we, as developers, aim to use our coding skills to create innovative solutions, build amazing projects, and contribute to the ever-evolving field of technology.

) } function AudioControls() { const audioRef = useRef(null) + return ( +
+
+ ) +} + +function Home({ session, openAuth }) { + return ( +
+
+
+ Developer · APIs · Open source +

Code Uchiha

+

Build with the Cod3Uchiha API. Accounts include 15 successful API requests every day across the entire endpoint catalog.

+
+ Browse APIs + {session ? ( + Dashboard + ) : ( + + )} +
+

Browsing the site and endpoint catalog stays public. Login is only needed for account features and API usage.

+
+
+ Uchiha Clan +
+
+ +
+
15successful requests / day
+
Allendpoints share one quota
+
0failed requests counted
+
- const play = () => { - audioRef.current?.play() + + + +
+

Contact me: WhatsApp: +263785028126

+

GitHub: github.com/Cod3Uchiha

+

Telegram: t.me/Code_Uchiha

+
+
+ ) +} + +function Dashboard({ session, openAuth }) { + const [data, setData] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState('') + const [keyName, setKeyName] = useState('Default') + const [newKey, setNewKey] = useState('') + const [working, setWorking] = useState(false) + + async function loadDashboard() { + if (!session) return + setLoading(true) + setError('') + try { + const today = new Date().toISOString().slice(0, 10) + const [profileResult, planResult, usageResult, keysResult, endpointResult] = await Promise.all([ + supabase.from('profiles').select('display_name, plan_id, is_banned, total_requests, created_at').single(), + supabase.from('plans').select('id, name, requests_per_day, requests_per_minute, max_api_keys').eq('id', 'free').single(), + supabase.from('usage_daily').select('request_count, last_request_at').eq('usage_date', today).maybeSingle(), + supabase.from('api_keys').select('id, name, key_prefix, created_at, last_used_at, revoked_at, total_requests').order('created_at', { ascending: false }), + supabase.from('usage_endpoint_daily').select('endpoint, request_count').eq('usage_date', today).order('request_count', { ascending: false }).limit(12) + ]) + + const firstError = [profileResult, planResult, usageResult, keysResult, endpointResult].find((result) => result.error)?.error + if (firstError) throw firstError + + setData({ + profile: profileResult.data, + plan: planResult.data, + usage: usageResult.data, + keys: keysResult.data || [], + endpoints: endpointResult.data || [] + }) + } catch (loadError) { + setError(loadError.message || 'Unable to load dashboard.') + } finally { + setLoading(false) + } } - const stop = () => { - const audio = audioRef.current - if (!audio) return + useEffect(() => { + void loadDashboard() + }, [session?.user?.id]) + + async function createKey() { + setWorking(true) + setNewKey('') + setError('') + try { + const { data: created, error: createError } = await supabase.rpc('create_api_key', { p_name: keyName || 'Default' }) + if (createError) throw createError + setNewKey(created?.api_key || '') + await loadDashboard() + } catch (createError) { + setError(createError.message || 'Unable to create API key.') + } finally { + setWorking(false) + } + } - audio.pause() - audio.currentTime = 0 + async function revokeKey(id) { + setWorking(true) + try { + const { error: revokeError } = await supabase.rpc('revoke_api_key', { p_key_id: id }) + if (revokeError) throw revokeError + await loadDashboard() + } catch (revokeError) { + setError(revokeError.message || 'Unable to revoke API key.') + } finally { + setWorking(false) + } } + if (!session) { + return ( +
+

Dashboard

+

Sign in to view your API quota and manage API keys.

+ +
+ ) + } + + if (loading) return

Loading dashboard…

+ if (!data) return

{error || 'Dashboard unavailable.'}

+ + const used = data.usage?.request_count || 0 + const limit = data.plan?.requests_per_day || 15 + const remaining = Math.max(0, limit - used) + const activeKeys = data.keys.filter((key) => !key.revoked_at) + return ( -
-