Skip to content
This repository has been archived by the owner on Sep 2, 2024. It is now read-only.

Commit

Permalink
chore: merge app connection flows (#377)
Browse files Browse the repository at this point in the history
  • Loading branch information
im-adithya authored Jun 7, 2024
1 parent 376f8db commit c1e4c1c
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 315 deletions.
4 changes: 0 additions & 4 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ import { BackupNode } from "src/screens/BackupNode";
import { BackupNodeSuccess } from "src/screens/BackupNodeSuccess";
import { Intro } from "src/screens/Intro";
import AlbyAuthRedirect from "src/screens/alby/AlbyAuthRedirect";
import AppConnect from "src/screens/appstore/AppConnect";
import AppDetail from "src/screens/appstore/AppDetail";
import { CurrentChannelOrder } from "src/screens/channels/CurrentChannelOrder";
import { Success } from "src/screens/onboarding/Success";
import Peers from "src/screens/peers/Peers";
Expand Down Expand Up @@ -79,8 +77,6 @@ function App() {
</Route>
<Route path="appstore" element={<DefaultRedirect />}>
<Route index element={<AppStore />} />
<Route path=":id" element={<AppDetail />} />
<Route path=":id/connect" element={<AppConnect />} />
</Route>
<Route path="apps" element={<DefaultRedirect />}>
<Route path="new" element={<NewApp />} />
Expand Down
41 changes: 23 additions & 18 deletions frontend/src/components/Permissions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,20 @@ interface PermissionsProps {
initialPermissions: AppPermissions;
onPermissionsChange: (permissions: AppPermissions) => void;
budgetUsage?: number;
isEditing: boolean;
isNew?: boolean;
canEditPermissions: boolean;
isNewConnection?: boolean;
}

const Permissions: React.FC<PermissionsProps> = ({
initialPermissions,
onPermissionsChange,
isEditing,
isNew,
canEditPermissions,
isNewConnection,
budgetUsage,
}) => {
const [permissions, setPermissions] = React.useState(initialPermissions);
const [days, setDays] = useState(isNew ? 0 : -1);
const [expireOptions, setExpireOptions] = useState(!isNew);
const [days, setDays] = useState(isNewConnection ? 0 : -1);
const [expireOptions, setExpireOptions] = useState(!isNewConnection);

useEffect(() => {
setPermissions(initialPermissions);
Expand All @@ -54,7 +54,7 @@ const Permissions: React.FC<PermissionsProps> = ({
};

const handleRequestMethodChange = (requestMethod: PermissionType) => {
if (!isEditing) {
if (!canEditPermissions) {
return;
}

Expand Down Expand Up @@ -109,7 +109,7 @@ const Permissions: React.FC<PermissionsProps> = ({
className={cn(
"w-full",
rm == "pay_invoice" ? "order-last" : "",
!isEditing && !permissions.requestMethods.has(rm)
!canEditPermissions && !permissions.requestMethods.has(rm)
? "hidden"
: ""
)}
Expand All @@ -119,19 +119,22 @@ const Permissions: React.FC<PermissionsProps> = ({
<RequestMethodIcon
className={cn(
"text-muted-foreground w-4 mr-3",
isEditing ? "hidden" : ""
canEditPermissions ? "hidden" : ""
)}
/>
)}
<Checkbox
id={rm}
className={cn("mr-2", !isEditing ? "hidden" : "")}
className={cn(
"mr-2",
!canEditPermissions ? "hidden" : ""
)}
onCheckedChange={() => handleRequestMethodChange(rm)}
checked={permissions.requestMethods.has(rm)}
/>
<Label
htmlFor={rm}
className={`${isEditing && "cursor-pointer"}`}
className={`${canEditPermissions && "cursor-pointer"}`}
>
{nip47PermissionDescriptions[rm]}
</Label>
Expand All @@ -141,23 +144,23 @@ const Permissions: React.FC<PermissionsProps> = ({
className={cn(
"pt-2 pb-2 pl-5 ml-2.5 border-l-2 border-l-primary",
!permissions.requestMethods.has(rm)
? isEditing
? canEditPermissions
? "pointer-events-none opacity-30"
: "hidden"
: ""
)}
>
{isEditing ? (
{canEditPermissions ? (
<>
<div className="flex flex-row gap-2 items-center text-muted-foreground mb-3 text-sm capitalize">
<p> Budget Renewal:</p>
{!isEditing ? (
{!canEditPermissions ? (
permissions.budgetRenewal
) : (
<Select
value={permissions.budgetRenewal}
onValueChange={handleBudgetRenewalChange}
disabled={!isEditing}
disabled={!canEditPermissions}
>
<SelectTrigger className="w-[150px]">
<SelectValue
Expand Down Expand Up @@ -209,7 +212,7 @@ const Permissions: React.FC<PermissionsProps> = ({
})}
</div>
</>
) : isNew ? (
) : isNewConnection ? (
<>
<p className="text-muted-foreground text-sm">
<span className="capitalize">
Expand Down Expand Up @@ -254,7 +257,9 @@ const Permissions: React.FC<PermissionsProps> = ({
</ul>
</div>

{(isNew ? !permissions.expiresAt || days : isEditing) ? (
{(
isNewConnection ? !permissions.expiresAt || days : canEditPermissions
) ? (
<>
{!expireOptions && (
<Button
Expand All @@ -270,7 +275,7 @@ const Permissions: React.FC<PermissionsProps> = ({
{expireOptions && (
<div className="mt-5">
<p className="font-medium text-sm mb-2">Connection expiration</p>
{!isNew && (
{!isNewConnection && (
<p className="mb-2 text-muted-foreground text-sm">
Expires:{" "}
{permissions.expiresAt &&
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/SuggestedApps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { SuggestedApp, suggestedApps } from "./SuggestedAppData";

function SuggestedAppCard({ id, title, description, logo }: SuggestedApp) {
return (
<Link to={`/appstore/${id}`}>
<Link to={`/apps/new?app=${id}`}>
<Card>
<CardContent className="pt-6">
<div className="flex gap-3 items-center">
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/ui/carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ const CarouselDots = React.forwardRef<
React.HTMLAttributes<HTMLDivElement>
>((props, ref) => {
const { api } = useCarousel();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, setUpdateState] = React.useState(false);
const toggleUpdateState = React.useCallback(
() => setUpdateState((prevState) => !prevState),
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/ui/loading-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,5 @@ const LoadingButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
);
LoadingButton.displayName = "LoadingButton";

// eslint-disable-next-line react-refresh/only-export-components
export { LoadingButton, buttonVariants };
4 changes: 3 additions & 1 deletion frontend/src/components/ui/use-toast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,9 @@ function toast({ ...props }: Toast) {
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss();
if (!open) {
dismiss();
}
},
},
});
Expand Down
164 changes: 109 additions & 55 deletions frontend/src/screens/apps/AppCreated.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,66 @@
import { DialogDescription, DialogTrigger } from "@radix-ui/react-dialog";
import { CopyIcon, QrCode } from "lucide-react";
import { useEffect } from "react";
import { Navigate, useLocation } from "react-router-dom";
import { CopyIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { Link, Navigate, useLocation, useNavigate } from "react-router-dom";

import AppHeader from "src/components/AppHeader";
import ExternalLink from "src/components/ExternalLink";
import Loading from "src/components/Loading";
import QRCode from "src/components/QRCode";
import { suggestedApps } from "src/components/SuggestedAppData";
import { Button } from "src/components/ui/button";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "src/components/ui/dialog";
Card,
CardContent,
CardHeader,
CardTitle,
} from "src/components/ui/card";
import { useToast } from "src/components/ui/use-toast";
import { useApp } from "src/hooks/useApp";
import { copyToClipboard } from "src/lib/clipboard";
import { CreateAppResponse } from "src/types";

export default function AppCreated() {
const { state } = useLocation();
const { search, state } = useLocation();
const navigate = useNavigate();
const { toast } = useToast();

const queryParams = new URLSearchParams(search);
const appId = queryParams.get("app") ?? "";
const appstoreApp = suggestedApps.find((app) => app.id === appId);
console.info(appstoreApp, appId);

const [timeout, setTimeout] = useState(false);
const createAppResponse = state as CreateAppResponse;
const pairingUri = createAppResponse.pairingUri;
const { data: app } = useApp(createAppResponse.pairingPublicKey, true);

const copy = () => {
copyToClipboard(pairingUri);
toast({ title: "Copied to clipboard." });
};

useEffect(() => {
const timeoutId = window.setTimeout(() => {
setTimeout(true);
}, 10000);

return () => window.clearTimeout(timeoutId);
}, []);

useEffect(() => {
if (app?.lastEventAt) {
toast({
title: "Connection established!",
description: "You can now use the app with your Alby Hub.",
});
navigate("/apps");
}
}, [app?.lastEventAt, navigate, toast]);

useEffect(() => {
if (appstoreApp) {
return;
}
// dispatch a success event which can be listened to by the opener or by the app that embedded the webview
// this gives those apps the chance to know the user has enabled the connection
const nwcEvent = new CustomEvent("nwc:success", { detail: {} });
Expand All @@ -36,58 +76,72 @@ export default function AppCreated() {
"*"
);
}
}, []);
}, [appstoreApp]);

if (!createAppResponse) {
return <Navigate to="/apps/new" />;
}

const pairingUri = createAppResponse.pairingUri;

const copy = () => {
copyToClipboard(pairingUri);
toast({ title: "Copied to clipboard." });
};

return (
<div className="w-full max-w-screen-sm mx-auto mt-6 md:px-4 ph-no-capture">
<h2 className="font-bold text-2xl font-headline mb-2 text-center">
🚀 Almost there!
</h2>
<div className="font-medium text-muted-foreground text-center mb-6">
Complete the last step of the setup by pasting or scanning your
connection's pairing secret in the desired app to finalise the
connection.
</div>

<div className="flex flex-col items-center gap-3">
<Button size="lg" onClick={copy}>
<CopyIcon className="w-4 h-4 mr-2" />
Copy pairing secret
</Button>
<Dialog>
<DialogTrigger asChild>
<Button variant="secondary">
<QrCode className="w-4 h-4 mr-2" />
QR-Code
</Button>
</DialogTrigger>

<DialogContent>
<DialogHeader>
<DialogTitle>Scan QR Code</DialogTitle>
<DialogDescription>
Open the app you want to pair and scan this QR code to connect.
</DialogDescription>
</DialogHeader>
<div className="flex flex-row justify-center p-3">
<a href={pairingUri} target="_blank">
<QRCode value={pairingUri} />
</a>
<>
<AppHeader
title={`Connect to ${createAppResponse.name}`}
description="Configure wallet permissions for the app and follow instructions to finalise the connection"
/>
<div className="flex flex-col gap-3 ph-no-capture">
<div>
<p>
1. Open{" "}
{appstoreApp ? (
<ExternalLink
className="font-semibold underline"
to={appstoreApp.to}
>
{appstoreApp.title}
</ExternalLink>
) : (
"the app you wish to connect"
)}{" "}
and look for a way to attach a wallet (most apps provide this option
in settings)
</p>
<p>2. Scan or paste the connection secret</p>
</div>
<Card className="max-w-sm">
<CardHeader>
<CardTitle className="text-center">Connection Secret</CardTitle>
</CardHeader>
<CardContent className="flex flex-col items-center gap-5">
<div className="flex flex-row items-center gap-2 text-sm">
<Loading className="w-4 h-4" />
<p>Waiting for app to connect</p>
</div>
{timeout && (
<div className="text-sm flex flex-col gap-2 items-center text-center">
Connecting is taking longer than usual.
<Link to={`/apps/${app?.nostrPubkey}`}>
<Button variant="secondary">Continue anyway</Button>
</Link>
</div>
)}
<a href={pairingUri} target="_blank" className="relative">
<QRCode value={pairingUri} className="w-full" />
{appstoreApp && (
<img
src={appstoreApp.logo}
className="absolute w-12 h-12 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-muted p-1 rounded-xl"
/>
)}
</a>
<div>
<Button onClick={copy} variant="outline">
<CopyIcon className="w-4 h-4 mr-2" />
Copy pairing secret
</Button>
</div>
</DialogContent>
</Dialog>
</CardContent>
</Card>
</div>
</div>
</>
);
}
Loading

0 comments on commit c1e4c1c

Please sign in to comment.