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
10 changes: 9 additions & 1 deletion frontend/components/stream-creation/AmountStep.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use client";
import React from "react";
import React, { useRef, useEffect } from "react";

interface AmountStepProps {
value: string;
Expand All @@ -14,6 +14,13 @@ export const AmountStep: React.FC<AmountStepProps> = ({
error,
token,
}) => {
const inputRef = useRef<HTMLInputElement>(null);

// Auto-focus on mount
useEffect(() => {
inputRef.current?.focus();
}, []);

return (
<div className="space-y-4">
<div>
Expand All @@ -32,6 +39,7 @@ export const AmountStep: React.FC<AmountStepProps> = ({
</label>
<div className="relative">
<input
ref={inputRef}
id="amount"
type="number"
step="any"
Expand Down
10 changes: 9 additions & 1 deletion frontend/components/stream-creation/RecipientStep.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use client";
import React from "react";
import React, { useRef, useEffect } from "react";

interface RecipientStepProps {
value: string;
Expand All @@ -12,6 +12,13 @@ export const RecipientStep: React.FC<RecipientStepProps> = ({
onChange,
error,
}) => {
const inputRef = useRef<HTMLInputElement>(null);

// Auto-focus on mount
useEffect(() => {
inputRef.current?.focus();
}, []);

return (
<div className="space-y-4">
<div>
Expand All @@ -30,6 +37,7 @@ export const RecipientStep: React.FC<RecipientStepProps> = ({
Stellar Public Key
</label>
<input
ref={inputRef}
id="recipient"
type="text"
value={value}
Expand Down
10 changes: 9 additions & 1 deletion frontend/components/stream-creation/ScheduleStep.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use client";
import React, { useMemo } from "react";
import React, { useMemo, useRef, useEffect } from "react";

interface ScheduleStepProps {
duration: string;
Expand Down Expand Up @@ -29,6 +29,13 @@ export const ScheduleStep: React.FC<ScheduleStepProps> = ({
amount,
token,
}) => {
const durationInputRef = useRef<HTMLInputElement>(null);

// Auto-focus on mount
useEffect(() => {
durationInputRef.current?.focus();
}, []);

const ratePerSecond = useMemo(() => {
if (!amount || !duration || parseFloat(amount) <= 0 || parseFloat(duration) <= 0) {
return null;
Expand Down Expand Up @@ -91,6 +98,7 @@ export const ScheduleStep: React.FC<ScheduleStepProps> = ({
Duration
</label>
<input
ref={durationInputRef}
id="duration"
type="number"
step="any"
Expand Down
75 changes: 70 additions & 5 deletions frontend/components/stream-creation/StreamCreationWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,26 @@ export const StreamCreationWizard: React.FC<StreamCreationWizardProps> = ({
if (validateStep(currentStep)) {
if (currentStep < STEPS.length) {
setCurrentStep(currentStep + 1);
// Scroll to top when moving to next step
const modal = document.querySelector('.glass-card');
if (modal) {
modal.scrollTo({ top: 0, behavior: 'smooth' });
}
}
} else {
// Scroll to first error if validation fails
const firstError = document.querySelector('[role="alert"]');
if (firstError) {
firstError.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
};

const handleBack = () => {
if (currentStep > 1) {
setCurrentStep(currentStep - 1);
// Scroll to top when going back
window.scrollTo({ top: 0, behavior: 'smooth' });
}
};

Expand All @@ -113,9 +126,14 @@ export const StreamCreationWizard: React.FC<StreamCreationWizardProps> = ({
} catch (error) {
console.error("Failed to create stream:", error);
// Error handling can be added here (e.g., toast notification)
} finally {
setIsSubmitting(false);
}
} else {
// Scroll to first error if validation fails
const firstError = document.querySelector('[role="alert"]');
if (firstError) {
firstError.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
};

Expand Down Expand Up @@ -163,8 +181,27 @@ export const StreamCreationWizard: React.FC<StreamCreationWizardProps> = ({
}
};

// Handle Escape key to close
React.useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
}
};
window.addEventListener('keydown', handleEscape);
return () => window.removeEventListener('keydown', handleEscape);
}, [onClose]);

return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
onClick={(e) => {
// Close on backdrop click
if (e.target === e.currentTarget) {
onClose();
}
}}
>
<div className="glass-card relative z-10 w-full max-w-2xl mx-4 max-h-[90vh] overflow-y-auto rounded-2xl border border-glass-border p-8">
<div className="flex items-center justify-between mb-6">
<h2 className="text-2xl font-bold">Create Payment Stream</h2>
Expand Down Expand Up @@ -192,12 +229,25 @@ export const StreamCreationWizard: React.FC<StreamCreationWizardProps> = ({

<Stepper steps={STEPS} currentStep={currentStep} />

<div className="my-8 min-h-[300px]">{renderStepContent()}</div>
<div className="my-8 min-h-[300px]">
<div className="mb-6 flex items-center justify-between">
<div className="text-sm text-slate-400">
Step {currentStep} of {STEPS.length}
</div>
<div className="text-xs text-slate-500">
{Math.round((currentStep / STEPS.length) * 100)}% complete
</div>
</div>
{renderStepContent()}
</div>

<div className="flex justify-between gap-4 pt-6 border-t border-glass-border">
<div>
{currentStep > 1 && (
<Button variant="outline" onClick={handleBack}>
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
Back
</Button>
)}
Expand All @@ -207,10 +257,25 @@ export const StreamCreationWizard: React.FC<StreamCreationWizardProps> = ({
Cancel
</Button>
{currentStep < STEPS.length ? (
<Button onClick={handleNext}>Next</Button>
<Button onClick={handleNext}>
Next
<svg className="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</Button>
) : (
<Button onClick={handleSubmit} disabled={isSubmitting}>
{isSubmitting ? "Creating..." : "Create Stream"}
{isSubmitting ? (
<>
<svg className="animate-spin -ml-1 mr-2 h-4 w-4" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Creating...
</>
) : (
"Create Stream"
)}
</Button>
)}
</div>
Expand Down
Loading