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
13 changes: 13 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,16 @@ button {
.slim-scrollbar::-webkit-scrollbar-track {
background-color: transparent;
}

/* Hide arrows in Chrome, Safari, Edge, Opera */
input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}

/* Hide arrows in Firefox */
input[type='number'] {
-moz-appearance: textfield;
appearance: textfield;
}
202 changes: 202 additions & 0 deletions components/flows/back-project/back-project-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
'use client';

import type React from 'react';
import { useState } from 'react';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Checkbox } from '@/components/ui/checkbox';
import { ArrowLeft, Check, Copy } from 'lucide-react';
import { BoundlessButton } from '@/components/buttons';

interface BackProjectFormProps {
onSubmit: (data: {
amount: string;
currency: string;
token: string;
network: string;
walletAddress: string;
keepAnonymous: boolean;
}) => void;
isLoading?: boolean;
}

const QUICK_AMOUNTS = [10, 20, 30, 50, 100, 500, 1000];

export function BackProjectForm({
onSubmit,
isLoading = false,
}: BackProjectFormProps) {
const [amount, setAmount] = useState('');
const [currency] = useState('USDT');
const [token, setToken] = useState('');
const [network, setNetwork] = useState('Stella / Soroban');
const [walletAddress] = useState('GDS3...GB7');
const [keepAnonymous, setKeepAnonymous] = useState(false);

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit({
amount,
currency,
token,
network,
walletAddress,
keepAnonymous,
});
};

const handleQuickAmount = (quickAmount: number) => {
setAmount(quickAmount.toString());
};

const handleCopyAddress = async (e: React.MouseEvent) => {
e.preventDefault();
try {
await navigator.clipboard.writeText(walletAddress);
// Could add toast notification here instead of state
} catch (err) {
// Fallback for browsers that don't support clipboard API
const textArea = document.createElement('textarea');
console.error(err);

textArea.value = walletAddress;
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
} catch (copyErr) {
console.error('Failed to copy address:', copyErr);
}
document.body.removeChild(textArea);
}
};

const isFormValid = amount && currency && token && walletAddress;

return (
<div className='text-white'>
<div className='mb-5 flex gap-10 '>
<ArrowLeft className='cursor-pointer' />
<h1 className='text-white text-lg font-semibold'>Back Project</h1>
</div>
<div className='w-[500px] flex flex-col gap-3 pt-3 pb-6'>
<div className='space-y-2'>
<p className='text-orange-400 text-sm'>
Funds will be held in escrow and released only upon milestone
approvals.
</p>
</div>

<div className='flex flex-col gap-1'>
<label className='text-xs text-card font-medium'>
Amount <span className='text-red-500'>*</span>
</label>
<div className='w-full h-12 flex items-center gap-3 p-4 rounded-[12px] bg-stepper-foreground border border-stepper-border'>
<span className='text-card text-sm'>{currency}</span>
<input
value={amount}
onChange={e => setAmount(e.target.value)}
type='number'
className='w-full bg-transparent font-normal text-base text-placeholder focus:outline-none'
placeholder='1000'
disabled={isLoading}
/>
</div>
<p className='text-card/60 text-xs'>min. amount: $10</p>

<div className='flex flex-wrap gap-2 mt-2'>
{QUICK_AMOUNTS.map(quickAmount => (
<button
key={quickAmount}
type='button'
onClick={() => handleQuickAmount(quickAmount)}
className='px-3 py-1 text-sm bg-stepper-foreground border border-stepper-border text-card rounded-[8px] hover:bg-stepper-foreground/80 transition-colors'
disabled={isLoading}
>
${quickAmount}
</button>
))}
</div>
</div>

<div className='flex flex-col gap-1'>
<label className='text-xs text-card font-medium'>
Select Token <span className='text-red-500'>*</span>
</label>
<Select value={token} onValueChange={setToken} disabled={isLoading}>
<SelectTrigger className='w-full !h-12 flex items-center !gap-3 p-4 rounded-[12px] bg-stepper-foreground border border-stepper-border focus:ring-0'>
<SelectValue placeholder='Select' />
</SelectTrigger>
<SelectContent className='max-h-[200px] bg-background rounded-[12px] font-normal text-base text-placeholder border border-stepper-border overflow-y-auto'>
<SelectItem value='XLM'>XLM</SelectItem>
<SelectItem value='USDT'>USDT</SelectItem>
<SelectItem value='USDC'>USDC</SelectItem>
</SelectContent>
</Select>
</div>

<div className='flex flex-col gap-1'>
<label className='text-xs text-card font-medium'>
Network <span className='text-red-500'>*</span>
</label>
<div className='w-full h-12 flex items-center gap-3 p-4 rounded-[12px] bg-stepper-foreground border border-stepper-border'>
<input
value={network}
onChange={e => setNetwork(e.target.value)}
type='text'
className='w-full bg-transparent font-normal text-base text-placeholder focus:outline-none'
disabled={isLoading}
/>
</div>
</div>

<div className='flex flex-col gap-1'>
<label className='text-xs text-card font-medium'>
Wallet Address <span className='text-red-500'>*</span>
</label>
<BoundlessButton
type='button'
onClick={handleCopyAddress}
className='w-full h-12 flex items-center justify-center gap-3 p-4 rounded-[12px] bg-blue-600 hover:bg-blue-700 text-white transition-colors'
disabled={isLoading}
>
<Check className='w-4 h-4' />
<span className='font-normal text-base'>{walletAddress}</span>
<Copy className='w-4 h-4' />
</BoundlessButton>
</div>

<div className='flex items-center gap-2 mt-2'>
<Checkbox
id='anonymous'
checked={keepAnonymous}
onCheckedChange={checked => setKeepAnonymous(checked as boolean)}
disabled={isLoading}
className='border-stepper-border data-[state=checked]:bg-primary data-[state=checked]:border-primary'
/>
<label htmlFor='anonymous' className='text-card text-sm font-normal'>
Keep me anonymous
</label>
</div>

<BoundlessButton
type='button'
disabled={!isFormValid || isLoading}
onClick={handleSubmit}
className={`w-full mt-4 h-12 text-base font-medium transition-colors ${
isFormValid && !isLoading
? 'bg-primary text-background border border-primary hover:bg-primary/90'
: 'bg-stepper-foreground text-card/30 border border-stepper-border cursor-not-allowed'
}`}
>
Confirm Contribution
</BoundlessButton>
</div>
</div>
);
}
111 changes: 111 additions & 0 deletions components/flows/back-project/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
'use client';

import { useState } from 'react';
import { BoundlessButton } from '@/components/buttons';
import { ProjectSubmissionSuccess } from '@/components/project';
import BoundlessSheet from '@/components/sheet/boundless-sheet';
import { ProjectSubmissionLoading } from '@/components/flows/back-project/project-submission-loading';
import { BackProjectForm } from './back-project-form';

type BackProjectState = 'form' | 'loading' | 'success';

interface BackProjectData {
amount: string;
currency: string;
token: string;
network: string;
walletAddress: string;
keepAnonymous: boolean;
}

const BackProject = () => {
const [isSheetOpen, setIsSheetOpen] = useState(false);
const [backProjectState, setBackProjectState] =
useState<BackProjectState>('form');

const handleBackProject = (data: BackProjectData) => {
setBackProjectState('loading');
console.log(data);

// Simulate API call
setTimeout(() => {
setBackProjectState('success');
}, 2000);
};

// const handleContinue = () => {
// setIsSheetOpen(false)
// setBackProjectState("form")
// }

// const handleViewHistory = () => {
// // Navigate to history page or open history modal
// setIsSheetOpen(false)
// // TODO: Implement backing history modal or navigation
// }

// const handleBack = () => {
// if (backProjectState === "success") {
// setBackProjectState("form")
// }
// }

const renderSheetContent = () => {
if (backProjectState === 'success') {
return (
<div className='absolute inset-0 flex flex-col items-center justify-center w-full h-full'>
<div className='flex items-center mb-4 w-full max-w-[500px]'>
{/* <Button variant="ghost" size="icon" onClick={handleBack} className="text-white hover:bg-gray-800">
<ArrowLeft className="w-5 h-5" />
</Button> */}
</div>
<ProjectSubmissionSuccess
title="You've Backed this Campaign!"
description="Your funds have been securely locked in escrow. You'll be notified when milestones are completed."
linkSection='You can see funding history for this campaign'
linkName='Here'
url='/'
// onContinue={handleContinue}
// onViewHistory={handleViewHistory}
/>
</div>
);
}

return (
<div className='relative flex flex-col items-center justify-start min-h-full px-4'>
<BackProjectForm
onSubmit={handleBackProject}
isLoading={backProjectState === 'loading'}
/>

{backProjectState === 'loading' && (
<div className='absolute inset-0 flex items-center justify-center w-full h-full bg-black/50 backdrop-blur-sm'>
<ProjectSubmissionLoading />
</div>
)}
</div>
);
};

return (
<div className='min-h-screen space-x-2 w-full text-white bg-black'>
<BoundlessSheet
open={isSheetOpen}
setOpen={setIsSheetOpen}
// title="Back Project"
showCloseButton={true}
contentClassName={`h-[100vh]`}
className='mx-4'
>
{renderSheetContent()}
</BoundlessSheet>

<BoundlessButton onClick={() => setIsSheetOpen(true)}>
Back Project
</BoundlessButton>
</div>
);
};

export default BackProject;
15 changes: 15 additions & 0 deletions components/flows/back-project/project-submission-loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export function ProjectSubmissionLoading() {
return (
<div className='flex flex-col items-center justify-center p-8 space-y-6'>
<div className='relative'>
{/* Outer spinning ring */}
<div className='w-16 h-16 border-4 border-white/20 rounded-full'></div>
{/* Inner spinning arc */}
<div className='absolute top-0 left-0 w-16 h-16 border-4 border-transparent border-t-white rounded-full animate-spin'></div>
</div>
<p className='text-white text-lg font-medium'>
Processing your contribution...
</p>
</div>
);
}
Loading
Loading