Skip to content

Commit

Permalink
fix: button usage for links
Browse files Browse the repository at this point in the history
Button tags in forms submit the form.
This means clicking the "Buy Bitcoin" link did not open the link but actually submit the form.
According to the HTML spec a button tag must also not be nested into a hyperlink.
  • Loading branch information
bumi committed Jul 26, 2024
1 parent 269903c commit c05f7cc
Show file tree
Hide file tree
Showing 3 changed files with 367 additions and 325 deletions.
26 changes: 25 additions & 1 deletion frontend/src/components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
import { Link } from "react-router-dom";

import ExternalLink from "src/components/ExternalLink";
import { cn } from "src/lib/utils";

const buttonVariants = cva(
Expand Down Expand Up @@ -56,5 +58,27 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
);
Button.displayName = "Button";

interface LinkProps extends ButtonProps {
to: string;
}
const ExternalLinkButton = React.forwardRef<HTMLAnchorElement, LinkProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : ExternalLink;
const _className = cn(buttonVariants({ variant, size, className }));

return <Comp className={_className} ref={ref} {...props} />;
}
);

const LinkButton = React.forwardRef<HTMLAnchorElement, LinkProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : Link;
const _className = cn(buttonVariants({ variant, size, className }));

return <Comp className={_className} ref={ref} {...props} />;
}
);

//const ExternalLinkButton = LinkButton
// eslint-disable-next-line react-refresh/only-export-components
export { Button, buttonVariants };
export { Button, LinkButton, ExternalLinkButton, buttonVariants };
326 changes: 167 additions & 159 deletions frontend/src/screens/channels/IncreaseIncomingCapacity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { Link, useNavigate } from "react-router-dom";
import AppHeader from "src/components/AppHeader";
import ExternalLink from "src/components/ExternalLink";
import Loading from "src/components/Loading";
import { Button } from "src/components/ui/button";
import {
Button,
ExternalLinkButton,
LinkButton,
} from "src/components/ui/button";
import { Checkbox } from "src/components/ui/checkbox";
import { Input } from "src/components/ui/input";
import { Label } from "src/components/ui/label";
Expand Down Expand Up @@ -198,177 +202,181 @@ function NewChannelInternal({
</div>
}
/>
<form
onSubmit={onSubmit}
className="md:max-w-md max-w-full flex flex-col gap-5 flex-1"
>
<div className="grid gap-1.5">
<Label htmlFor="amount">Channel size (sats)</Label>
{order.amount && +order.amount < 200_000 && (
<p className="text-muted-foreground text-xs">
For a smooth experience consider a opening a channel of 200k sats
in size or more.{" "}
<ExternalLink
to="https://guides.getalby.com/user-guide/v/alby-account-and-browser-extension/alby-hub/liquidity"
className="underline"
>
Learn more
</ExternalLink>
</p>
)}
<Input
id="amount"
type="number"
required
min={
showAdvanced
? selectedPeer?.minimumChannelSize || 100000
: undefined
}
value={order.amount}
onChange={(e) => {
setAmount(e.target.value.trim());
}}
/>
<div className="grid grid-cols-3 gap-1.5 text-muted-foreground text-xs">
{presetAmounts.map((amount) => (
<div
key={amount}
className={cn(
"text-center border rounded p-2 cursor-pointer hover:border-muted-foreground",
+(order.amount || "0") === amount &&
"border-primary hover:border-primary"
)}
onClick={() => setAmount(amount.toString())}
>
{formatAmount(amount * 1000, 0)}
</div>
))}
<div className="md:max-w-md max-w-full flex flex-col gap-5 flex-1">
<form onSubmit={onSubmit} className="flex flex-col gap-5 flex-1">
<div className="grid gap-1.5">
<Label htmlFor="amount">Channel size (sats)</Label>
{order.amount && +order.amount < 200_000 && (
<p className="text-muted-foreground text-xs">
For a smooth experience consider a opening a channel of 200k
sats in size or more.{" "}
<ExternalLink
to="https://guides.getalby.com/user-guide/v/alby-account-and-browser-extension/alby-hub/liquidity"
className="underline"
>
Learn more
</ExternalLink>
</p>
)}
<Input
id="amount"
type="number"
required
min={
showAdvanced
? selectedPeer?.minimumChannelSize || 100000
: undefined
}
value={order.amount}
onChange={(e) => {
setAmount(e.target.value.trim());
}}
/>
<div className="grid grid-cols-3 gap-1.5 text-muted-foreground text-xs">
{presetAmounts.map((amount) => (
<div
key={amount}
className={cn(
"text-center border rounded p-2 cursor-pointer hover:border-muted-foreground",
+(order.amount || "0") === amount &&
"border-primary hover:border-primary"
)}
onClick={() => setAmount(amount.toString())}
>
{formatAmount(amount * 1000, 0)}
</div>
))}
</div>
</div>
</div>
{showAdvanced && (
<>
<div className="flex flex-col gap-3">
{selectedPeer && (
<div className="grid gap-1.5">
<Label>Channel peer</Label>
<Select
value={getPeerKey(selectedPeer)}
onValueChange={(value) =>
setSelectedPeer(
channelPeerSuggestions.find(
(x) => getPeerKey(x) === value
)
)
}
>
<SelectTrigger>
<SelectValue placeholder="Select channel peer" />
</SelectTrigger>
<SelectContent>
{channelPeerSuggestions
.filter(
(peer) =>
peer.network === network &&
peer.paymentMethod === order.paymentMethod
{showAdvanced && (
<>
<div className="flex flex-col gap-3">
{selectedPeer && (
<div className="grid gap-1.5">
<Label>Channel peer</Label>
<Select
value={getPeerKey(selectedPeer)}
onValueChange={(value) =>
setSelectedPeer(
channelPeerSuggestions.find(
(x) => getPeerKey(x) === value
)
)
.map((peer) => (
<SelectItem
value={getPeerKey(peer)}
key={getPeerKey(peer)}
>
<div className="flex items-center gap-3">
}
>
<SelectTrigger>
<SelectValue placeholder="Select channel peer" />
</SelectTrigger>
<SelectContent>
{channelPeerSuggestions
.filter(
(peer) =>
peer.network === network &&
peer.paymentMethod === order.paymentMethod
)
.map((peer) => (
<SelectItem
value={getPeerKey(peer)}
key={getPeerKey(peer)}
>
<div className="flex items-center gap-3">
{peer.name !== "Custom" && (
<img
src={peer.image}
className="w-8 h-8 object-contain"
/>
)}
<div>
{peer.name}
<span className="ml-4 text-xs text-muted-foreground">
Min.{" "}
{new Intl.NumberFormat().format(
peer.minimumChannelSize
)}
sats
<span className="mr-10" />
Max.{" "}
{new Intl.NumberFormat().format(
peer.maximumChannelSize
)}{" "}
sats
</span>
<div className="flex items-center gap-3">
{peer.name !== "Custom" && (
<img
src={peer.image}
className="w-8 h-8 object-contain"
/>
)}
<div>
{peer.name}
<span className="ml-4 text-xs text-muted-foreground">
Min.{" "}
{new Intl.NumberFormat().format(
peer.minimumChannelSize
)}
sats
<span className="mr-10" />
Max.{" "}
{new Intl.NumberFormat().format(
peer.maximumChannelSize
)}{" "}
sats
</span>
</div>
</div>
</div>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
{selectedPeer.name === "Custom" && (
<>
<div className="grid gap-1.5"></div>
</>
)}
</div>
</SelectItem>
))}
</SelectContent>
</Select>
{selectedPeer.name === "Custom" && (
<>
<div className="grid gap-1.5"></div>
</>
)}
</div>
)}
</div>
{order.paymentMethod === "lightning" && (
<NewChannelLightning order={order} setOrder={setOrder} />
)}
</div>
{order.paymentMethod === "lightning" && (
<NewChannelLightning order={order} setOrder={setOrder} />
)}

<div className="mt-2 flex items-top space-x-2">
<Checkbox
id="public-channel"
defaultChecked={order.isPublic}
onCheckedChange={() => setPublic(!order.isPublic)}
className="mr-2"
/>
<div className="grid gap-1.5 leading-none">
<Label
htmlFor="public-channel"
className="flex items-center gap-2"
>
Public Channel
</Label>
<p className="text-xs text-muted-foreground">
Only enable if you want to receive keysend payments. (e.g.
podcasting)
</p>
<div className="mt-2 flex items-top space-x-2">
<Checkbox
id="public-channel"
defaultChecked={order.isPublic}
onCheckedChange={() => setPublic(!order.isPublic)}
className="mr-2"
/>
<div className="grid gap-1.5 leading-none">
<Label
htmlFor="public-channel"
className="flex items-center gap-2"
>
Public Channel
</Label>
<p className="text-xs text-muted-foreground">
Only enable if you want to receive keysend payments. (e.g.
podcasting)
</p>
</div>
</div>
</div>
</>
)}
{!showAdvanced && (
<Button
type="button"
variant="link"
className="text-muted-foreground text-xs"
onClick={() => setShowAdvanced((current) => !current)}
>
<ChevronDown className="w-4 h-4 mr-2" />
Advanced Options
</Button>
)}
<Button size="lg">Next</Button>
</>
)}
{!showAdvanced && (
<Button
type="button"
variant="link"
className="text-muted-foreground text-xs"
onClick={() => setShowAdvanced((current) => !current)}
>
<ChevronDown className="w-4 h-4 mr-2" />
Advanced Options
</Button>
)}
<Button size="lg">Next</Button>
</form>

<div className="flex-1 flex flex-col justify-end items-center gap-4">
<p className="mt-32 text-sm text-muted-foreground text-center">
Other options
</p>
<Link to="/channels/outgoing" className="w-full">
<Button className="w-full" variant="secondary">
Increase spending balance
</Button>
</Link>
<ExternalLink to="https://www.getalby.com/topup" className="w-full">
<Button className="w-full" variant="secondary">
Buy Bitcoin
</Button>
</ExternalLink>
<LinkButton
to="/channels/outgoing"
className="w-full"
variant="secondary"
>
Increase spending balance
</LinkButton>
<ExternalLinkButton
to="https://www.getalby.com/topup"
className="w-full"
variant="secondary"
>
Buy Bitcoin
</ExternalLinkButton>
</div>
</form>
</div>
</>
);
}
Expand Down
Loading

0 comments on commit c05f7cc

Please sign in to comment.