Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 20 additions & 0 deletions apps/connect/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/index.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}
14 changes: 14 additions & 0 deletions apps/connect/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Connect</title>
<meta name="description" content="Authenticate with your Geo Account" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
39 changes: 39 additions & 0 deletions apps/connect/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "events",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --force",
"preview": "vite preview"
},
"dependencies": {
"@graphprotocol/grc-20": "^0.11.5",
"@privy-io/react-auth": "^2.13.0",
"@radix-ui/react-avatar": "^1.1.9",
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-slot": "^1.2.2",
"@tanstack/react-query": "^5.75.5",
"@tanstack/react-router": "^1.120.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"effect": "^3.14.20",
"framer-motion": "^12.10.1",
"lucide-react": "^0.508.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"tailwind-merge": "^3.2.0",
"tailwindcss-animate": "^1.0.7",
"vite": "^6.3.5"
},
"devDependencies": {
"@tailwindcss/vite": "^4.1.5",
"@tanstack/router-devtools": "^1.120.2",
"@tanstack/router-plugin": "^1.120.2",
"@types/node": "^22.15.15",
"@types/react": "^19.1.3",
"@types/react-dom": "^19.1.3",
"@vitejs/plugin-react": "^4.4.1",
"tailwindcss": "^4.1.5"
}
}
3 changes: 3 additions & 0 deletions apps/connect/public/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions apps/connect/src/Boot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { PrivyProvider } from '@privy-io/react-auth';
import { RouterProvider, createRouter } from '@tanstack/react-router';
import { routeTree } from './routeTree.gen';

// Create a new router instance
const router = createRouter({ routeTree });

// Register the router instance for type safety
declare module '@tanstack/react-router' {
interface Register {
router: typeof router;
}
}

export function Boot() {
return (
<PrivyProvider
appId="cm4wx6ziv00ngrmfjf9ik36iu"
config={{
loginMethods: ['email', 'wallet', 'google', 'twitter', 'github'],
appearance: {
theme: 'light',
accentColor: '#676FFF',
},
embeddedWallets: {
createOnLogin: 'users-without-wallets',
},
}}
>
<RouterProvider router={router} />
</PrivyProvider>
);
}
31 changes: 31 additions & 0 deletions apps/connect/src/components/logout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { usePrivy } from '@privy-io/react-auth';
import { useRouter } from '@tanstack/react-router';
import { Loader2 } from 'lucide-react';
import { useState } from 'react';
import { Button } from './ui/button';

export function Logout() {
const { logout: privyLogout, ready, authenticated } = usePrivy();
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);
const disconnectWallet = async () => {
setIsLoading(true);
await privyLogout();
router.navigate({
to: '/login',
});
setIsLoading(false);
};

return (
<Button className="home-button" onClick={() => disconnectWallet()} disabled={!ready || !authenticated}>
{isLoading ? (
<>
<Loader2 className="w-4 h-4 animate-spin" /> Logout
</>
) : (
'Logout'
)}
</Button>
);
}
42 changes: 42 additions & 0 deletions apps/connect/src/components/spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use client';

import { cn } from '@/lib/utils';
import { motion } from 'framer-motion';

interface SpinnerProps {
size?: 'sm' | 'md' | 'lg' | 'xl';
color?: 'default' | 'primary' | 'secondary' | 'accent' | 'white';
className?: string;
}

export function Spinner({ size = 'md', color = 'primary', className }: SpinnerProps) {
const sizeClasses = {
sm: 'h-4 w-4 border-2',
md: 'h-6 w-6 border-2',
lg: 'h-8 w-8 border-3',
xl: 'h-12 w-12 border-4',
};

const colorClasses = {
default: 'border-muted-foreground/30 border-t-muted-foreground',
primary: 'border-primary/30 border-t-primary',
secondary: 'border-secondary/30 border-t-secondary',
accent: 'border-accent/30 border-t-accent',
white: 'border-white/30 border-t-white',
};

return (
<motion.div
className={cn('rounded-full animate-spin', sizeClasses[size], colorClasses[color], className)}
animate={{ rotate: 360 }}
transition={{
duration: 1,
ease: 'linear',
repeat: Number.POSITIVE_INFINITY,
}}
aria-label="Loading"
>
<span className="sr-only">Loading...</span>
</motion.div>
);
}
38 changes: 38 additions & 0 deletions apps/connect/src/components/ui/avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import * as AvatarPrimitive from '@radix-ui/react-avatar';
import * as React from 'react';

import { cn } from '@/lib/utils';

const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn('relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full', className)}
{...props}
/>
));
Avatar.displayName = AvatarPrimitive.Root.displayName;

const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image ref={ref} className={cn('aspect-square h-full w-full', className)} {...props} />
));
AvatarImage.displayName = AvatarPrimitive.Image.displayName;

const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn('flex h-full w-full items-center justify-center rounded-full bg-muted', className)}
{...props}
/>
));
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;

export { Avatar, AvatarImage, AvatarFallback };
48 changes: 48 additions & 0 deletions apps/connect/src/components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Slot } from '@radix-ui/react-slot';
import { type VariantProps, cva } from 'class-variance-authority';
import * as React from 'react';

import { cn } from '@/lib/utils';

const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground shadow-sm hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90',
outline: 'border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground',
secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-9 px-4 py-2',
sm: 'h-8 rounded-md px-3 text-xs',
lg: 'h-10 rounded-md px-8',
icon: 'h-9 w-9',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
);

export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
},
);
Button.displayName = 'Button';

// eslint-disable-next-line react-refresh/only-export-components
export { Button, buttonVariants };
43 changes: 43 additions & 0 deletions apps/connect/src/components/ui/card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as React from 'react';

import { cn } from '@/lib/utils';

const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
<div ref={ref} className={cn('rounded-xl border bg-card text-card-foreground shadow-sm', className)} {...props} />
));
Card.displayName = 'Card';

const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />
),
);
CardHeader.displayName = 'CardHeader';

const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h3 ref={ref} className={cn('font-semibold leading-none tracking-tight', className)} {...props} />
),
);
CardTitle.displayName = 'CardTitle';

const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => (
<p ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
),
);
CardDescription.displayName = 'CardDescription';

const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />,
);
CardContent.displayName = 'CardContent';

const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} />
),
);
CardFooter.displayName = 'CardFooter';

export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
23 changes: 23 additions & 0 deletions apps/connect/src/components/ui/input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as React from 'react';

import { cn } from '@/lib/utils';

// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}

const Input = React.forwardRef<HTMLInputElement, InputProps>(({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
ref={ref}
{...props}
/>
);
});
Input.displayName = 'Input';

export { Input };
39 changes: 39 additions & 0 deletions apps/connect/src/components/ui/modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useEffect } from 'react';

type ModalProps = {
isOpen: boolean;
onOpenChange: (open: boolean) => void;
children: React.ReactNode;
};

export function Modal({ isOpen, onOpenChange, children }: ModalProps) {
useEffect(() => {
const handleEsc = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
onOpenChange(false);
}
};

if (isOpen) {
document.addEventListener('keydown', handleEsc);
document.body.style.overflow = 'hidden';
}

return () => {
document.removeEventListener('keydown', handleEsc);
document.body.style.overflow = 'unset';
};
}, [isOpen, onOpenChange]);

if (!isOpen) return null;

return (
<div className="fixed inset-0 z-50">
{/* biome-ignore lint/a11y/useKeyWithClickEvents: Modal has keyboard support via Escape key */}
<div className="fixed inset-0 bg-black/50" onClick={() => onOpenChange(false)} />
<div className="fixed left-[50%] top-[50%] z-50 translate-x-[-50%] translate-y-[-50%] max-h-[90vh] w-[90vw] max-w-2xl">
<div className="bg-white rounded shadow-lg max-h-[90vh] overflow-y-auto">{children}</div>
</div>
</div>
);
}
Loading
Loading