diff --git a/frontend/app/auth/signin/page.tsx b/frontend/app/auth/signin/page.tsx index 74c1541..3c1e3ea 100644 --- a/frontend/app/auth/signin/page.tsx +++ b/frontend/app/auth/signin/page.tsx @@ -12,6 +12,8 @@ import { useToast } from '@/components/ui/ToastProvider'; import { useStellarWalletAuth } from '@/hooks/useStellarWalletAuth'; import { useAuth } from '@/hooks/useAuth'; import { WalletType } from '@/lib/stellar/types'; +import WalletModal, { WalletType as ModalWalletType } from '@/components/ui/WalletModal'; +import { useWalletModal } from '@/hooks/useWalletModal'; const SignInPage = () => { const router = useRouter(); @@ -24,6 +26,7 @@ const SignInPage = () => { clearError, } = useStellarWalletAuth(); const { loginSuccess, loginFailure, setLoading } = useAuth(); + const { isOpen: isWalletModalOpen, openModal: openWalletModal, closeModal: closeWalletModal } = useWalletModal(); const [formData, setFormData] = useState({ username: '', password: '' @@ -197,41 +200,45 @@ const SignInPage = () => { window.location.href = "http://localhost:3000/auth/google-authentication"; }; - const handleWalletLogin = async () => { + const handleWalletSelect = async (walletType: ModalWalletType) => { + closeWalletModal(); + + if (walletType !== 'freighter') { + showInfo('Coming Soon', `${walletType.charAt(0).toUpperCase() + walletType.slice(1)} wallet support is coming soon!`); + return; + } + clearError(); try { await connectAndLogin('freighter' as WalletType); - - // Success - show toast and redirect showSuccess('Login Successful', 'Welcome back!'); router.push('/dashboard'); } catch (error) { console.error("Wallet Connection Error:", error); - // Error handling with user-friendly messages const isErrorWithCode = (e: unknown): e is { code?: string; message?: string } => { return typeof e === 'object' && e !== null; }; if (isErrorWithCode(error)) { - if (error?.code === 'WALLET_NOT_INSTALLED') { - showError( - 'Wallet Not Installed', - 'Please install Freighter wallet from freighter.app to continue' - ); - } else if (error?.code === 'USER_REJECTED') { - showWarning('Request Cancelled', 'You cancelled the wallet request'); - } else if (error?.code === 'NONCE_EXPIRED') { - showError('Authentication Expired', 'Please try again'); - } else if (error?.code === 'INVALID_SIGNATURE') { - showError('Authentication Failed', 'Invalid signature or expired nonce'); - } else if (error?.code === 'NETWORK_ERROR') { - showError('Network Error', 'Unable to connect to server. Please try again.'); - } else { - showError('Login Failed', error?.message || 'An unexpected error occurred'); + if (error?.code === 'WALLET_NOT_INSTALLED') { + showError( + 'Wallet Not Installed', + 'Please install Freighter wallet from freighter.app to continue' + ); + } else if (error?.code === 'USER_REJECTED') { + showWarning('Request Cancelled', 'You cancelled the wallet request'); + } else if (error?.code === 'NONCE_EXPIRED') { + showError('Authentication Expired', 'Please try again'); + } else if (error?.code === 'INVALID_SIGNATURE') { + showError('Authentication Failed', 'Invalid signature or expired nonce'); + } else if (error?.code === 'NETWORK_ERROR') { + showError('Network Error', 'Unable to connect to server. Please try again.'); + } else { + showError('Login Failed', error?.message || 'An unexpected error occurred'); + } } } - } }; return ( @@ -325,7 +332,7 @@ const SignInPage = () => { + + {/* Title */} +

+ Connect a wallet to continue +

+ + {/* Wallet list */} +
+ {WALLETS.map((wallet) => ( +
+ onSelect(wallet.id)} + /> +
+ ))} +
+ + + ); +}; + +export default WalletModal; diff --git a/frontend/components/ui/wallet/WalletIcons.tsx b/frontend/components/ui/wallet/WalletIcons.tsx new file mode 100644 index 0000000..2e94944 --- /dev/null +++ b/frontend/components/ui/wallet/WalletIcons.tsx @@ -0,0 +1,110 @@ +import React from 'react'; + +export const PhantomIcon: React.FC<{ className?: string }> = ({ className }) => ( + +); + +export const MetaMaskIcon: React.FC<{ className?: string }> = ({ className }) => ( + +); + +export const CoinbaseIcon: React.FC<{ className?: string }> = ({ className }) => ( + +); + +export const TrustIcon: React.FC<{ className?: string }> = ({ className }) => ( + +); + +export const FreighterIcon: React.FC<{ className?: string }> = ({ className }) => ( + +); diff --git a/frontend/components/ui/wallet/WalletOption.tsx b/frontend/components/ui/wallet/WalletOption.tsx new file mode 100644 index 0000000..6c36a95 --- /dev/null +++ b/frontend/components/ui/wallet/WalletOption.tsx @@ -0,0 +1,32 @@ +'use client'; + +import React from 'react'; + +export interface WalletOptionProps { + name: string; + icon: React.ReactNode; + isDetected: boolean; + onClick: () => void; +} + +const WalletOption: React.FC = ({ name, icon, isDetected, onClick }) => { + return ( + + ); +}; + +export default WalletOption; diff --git a/frontend/hooks/useReducedMotionCheck.ts b/frontend/hooks/useReducedMotionCheck.ts index 1e49bce..95f27e3 100644 --- a/frontend/hooks/useReducedMotionCheck.ts +++ b/frontend/hooks/useReducedMotionCheck.ts @@ -1,4 +1,4 @@ -import { usePrefersReducedMotion } from 'framer-motion'; +import { usePrefersReducedMotion } from '@/lib/animations/hooks'; const useReducedMotionCheck = () => { const prefersReducedMotion = usePrefersReducedMotion(); diff --git a/frontend/hooks/useWalletModal.ts b/frontend/hooks/useWalletModal.ts new file mode 100644 index 0000000..a3792dd --- /dev/null +++ b/frontend/hooks/useWalletModal.ts @@ -0,0 +1,12 @@ +'use client'; + +import { useState, useCallback } from 'react'; + +export function useWalletModal() { + const [isOpen, setIsOpen] = useState(false); + + const openModal = useCallback(() => setIsOpen(true), []); + const closeModal = useCallback(() => setIsOpen(false), []); + + return { isOpen, openModal, closeModal }; +} diff --git a/frontend/lib/animations/hooks.ts b/frontend/lib/animations/hooks.ts index 4284fc7..4006f32 100644 --- a/frontend/lib/animations/hooks.ts +++ b/frontend/lib/animations/hooks.ts @@ -5,7 +5,7 @@ import { useReducedMotion } from 'framer-motion'; * Hook to determine if animations should be disabled based on user preferences. */ export const usePrefersReducedMotion = (): boolean => { - return useReducedMotion(); + return useReducedMotion() ?? false; }; /**