Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
80c8e26
docs: add developer portal design document
zenabot27 Feb 27, 2026
9991461
docs: add developer portal implementation plan
zenabot27 Feb 27, 2026
dd8861b
feat(developer): add shadcn UI components for developer portal
zenabot27 Feb 27, 2026
87094c5
feat(developer): add AuthContext and webhook API client
zenabot27 Feb 27, 2026
89b325c
feat(developer): add login, callback, layout, dashboard pages and Aut…
zenabot27 Feb 27, 2026
a2db13e
feat(developer): add API keys management page
zenabot27 Feb 27, 2026
d669e8f
feat(developer): add webhook endpoints management page
zenabot27 Feb 27, 2026
b5e6c82
feat(developer): add subscriptions management page
zenabot27 Feb 27, 2026
0f9c89c
feat(developer): add delivery logs page
zenabot27 Feb 27, 2026
7e46619
feat(developer): add developer portal link to sidebar
zenabot27 Feb 27, 2026
6cf4977
feat(developer): configure GoTrue SMTP for magic link emails
zenabot27 Feb 27, 2026
0832c77
feat(developer): add custom email templates for GoTrue
zenabot27 Feb 27, 2026
c408dc8
feat: add BIMI SVG logo for email branding
zenabot27 Feb 27, 2026
369a181
fix(email): dark theme, OTP code, text-only logo
zenabot27 Feb 27, 2026
6afea8b
fix(email): force dark background for Gmail compatibility
zenabot27 Feb 27, 2026
a59f75f
feat: default login to magic link + OTP, remove password auth
zenabot27 Feb 28, 2026
b28f3fe
feat: add disabled wallet login button with Coming Soon badge
zenabot27 Feb 28, 2026
aa44f6c
merge: resolve conflicts with main, keep magic link + OTP login
zenabot27 Feb 28, 2026
7bdf7ba
feat: separate Developer section in sidebar with divider
zenabot27 Feb 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 62 additions & 5 deletions frontend/app/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ export default function Sidebar() {
return location.pathname.startsWith(path);
};

const navItems: Array<{ label: string; path: string; icon: typeof Home; disabled?: boolean }> = [
type NavItem = { label: string; path: string; icon: typeof Home; disabled?: boolean };

const explorerItems: NavItem[] = [
{ label: 'Home Page', path: '/', icon: Home },
{ label: 'Analytics', path: '/analytics', icon: BarChart3 },
{ label: 'Transactions', path: '/txs', icon: ArrowRightLeft },
Expand All @@ -30,8 +32,11 @@ export default function Sidebar() {
{ label: 'Scheduled Txs', path: '/scheduled', icon: Clock },
{ label: 'Nodes', path: '/nodes', icon: Globe },
{ label: 'Indexing Status', path: '/stats', icon: Layers },
];

const developerItems: NavItem[] = [
{ label: 'API Docs', path: '/api-docs', icon: FileText },
{ label: 'Developer', path: '/developer', icon: Code2 },
{ label: 'Developer Portal', path: '/developer', icon: Code2 },
];

return (
Expand Down Expand Up @@ -64,7 +69,7 @@ export default function Sidebar() {

{/* Nav Items */}
<nav className="flex-1 px-3 space-y-1 overflow-y-auto">
{navItems.map((item) => (
{explorerItems.map((item) => (
<Link
key={item.label}
to={item.disabled ? '#' : item.path}
Expand All @@ -79,6 +84,28 @@ export default function Sidebar() {
<span className="text-sm font-medium">{item.label}</span>
</Link>
))}

{/* Developer Section */}
<div className="pt-4 mt-4 border-t border-zinc-200 dark:border-white/10">
<span className="px-4 text-[10px] font-semibold uppercase tracking-wider text-zinc-400 dark:text-zinc-600">Developer</span>
<div className="mt-2 space-y-1">
{developerItems.map((item) => (
<Link
key={item.label}
to={item.disabled ? '#' : item.path}
onClick={(e) => {
if (item.disabled) e.preventDefault();
else closeMobileMenu();
}}
className={`flex items-center space-x-3 px-4 py-3 rounded-sm transition-colors ${item.disabled ? 'opacity-40 cursor-not-allowed' : 'hover:bg-zinc-100 dark:hover:bg-white/5'
} ${isActive(item.path) && !item.disabled ? 'text-nothing-green bg-nothing-green/10' : 'text-zinc-600 dark:text-zinc-400'}`}
>
<item.icon className="w-5 h-5 shrink-0" />
<span className="text-sm font-medium">{item.label}</span>
</Link>
))}
</div>
</div>
</nav>

{/* Footer */}
Expand Down Expand Up @@ -125,12 +152,12 @@ export default function Sidebar() {

{/* Navigation */}
<nav className="flex-1 px-4 space-y-2 overflow-y-auto custom-scrollbar overflow-x-hidden">
{navItems.map((item) => (
{explorerItems.map((item) => (
<Link
key={item.label}
to={item.disabled ? '#' : item.path}
className={`flex items-center ${isCollapsed ? 'justify-center px-2' : 'space-x-3 px-4'} py-3 rounded-sm transition-all duration-200 group relative
${item.disabled ? 'opacity-40 cursor-not-allowed' : 'hover:bg-zinc-100 dark:hover:bg-white/5'}
${item.disabled ? 'opacity-40 cursor-not-allowed' : 'hover:bg-zinc-100 dark:hover:bg-white/5'}
${isActive(item.path) && !item.disabled
? 'bg-nothing-green/10 text-nothing-green border-r-2 border-nothing-green'
: 'text-zinc-600 dark:text-zinc-400'}`}
Expand All @@ -146,6 +173,36 @@ export default function Sidebar() {
)}
</Link>
))}

{/* Developer Section */}
<div className={`pt-4 mt-2 border-t border-zinc-200 dark:border-white/10 ${isCollapsed ? '' : ''}`}>
{!isCollapsed && (
<span className="px-4 text-[10px] font-semibold uppercase tracking-wider text-zinc-400 dark:text-zinc-600">Developer</span>
)}
<div className={`${isCollapsed ? '' : 'mt-2'} space-y-2`}>
{developerItems.map((item) => (
<Link
key={item.label}
to={item.disabled ? '#' : item.path}
className={`flex items-center ${isCollapsed ? 'justify-center px-2' : 'space-x-3 px-4'} py-3 rounded-sm transition-all duration-200 group relative
${item.disabled ? 'opacity-40 cursor-not-allowed' : 'hover:bg-zinc-100 dark:hover:bg-white/5'}
${isActive(item.path) && !item.disabled
? 'bg-nothing-green/10 text-nothing-green border-r-2 border-nothing-green'
: 'text-zinc-600 dark:text-zinc-400'}`}
onClick={(e) => item.disabled && e.preventDefault()}
title={isCollapsed ? item.label : ''}
>
<item.icon className={`h-5 w-5 shrink-0 ${isActive(item.path) && !item.disabled ? 'text-nothing-green' : 'text-zinc-500 dark:text-zinc-500 group-hover:text-zinc-900 dark:group-hover:text-zinc-300'}`} />

{!isCollapsed && (
<span className={`text-sm font-medium tracking-wide whitespace-nowrap ${isActive(item.path) && !item.disabled ? 'text-zinc-900 dark:text-white' : ''}`}>
{item.label}
</span>
)}
</Link>
))}
</div>
</div>
</nav>

{/* Footer / Controls */}
Expand Down
10 changes: 10 additions & 0 deletions frontend/app/contexts/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface AuthContextValue extends AuthState {
signUp: (email: string, password: string) => Promise<void>;
signIn: (email: string, password: string) => Promise<void>;
sendMagicLink: (email: string) => Promise<void>;
verifyOtp: (email: string, token: string) => Promise<void>;
handleCallback: (hash: string) => void;
signOut: () => void;
}
Expand Down Expand Up @@ -231,6 +232,14 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
await gotruePost('/magiclink', { email });
}, []);

const verifyOtp = useCallback(
async (email: string, token: string) => {
const data = await gotruePost('/verify', { type: 'magiclink', token, redirect_to: '', email });
applyTokenResponse(data);
},
[applyTokenResponse],
);

const handleCallback = useCallback(
(hash: string) => {
const params = new URLSearchParams(hash.replace(/^#/, ''));
Expand All @@ -257,6 +266,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
signUp,
signIn,
sendMagicLink,
verifyOtp,
handleCallback,
signOut,
}}
Expand Down
Loading
Loading