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

chore: merge app connection flows #377

Merged
merged 5 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
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
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
167 changes: 108 additions & 59 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
im-adithya marked this conversation as resolved.
Show resolved Hide resolved
const nwcEvent = new CustomEvent("nwc:success", { detail: {} });
Expand All @@ -36,63 +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"
<>
<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}
>
<QRCode value={pairingUri} />
</a>
{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 >
</>
);
}
39 changes: 27 additions & 12 deletions frontend/src/screens/apps/NewApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { useToast } from "src/components/ui/use-toast";
import { handleRequestError } from "src/utils/handleRequestError";
import { request } from "src/utils/request"; // build the project for this to appear
import Permissions from "../../components/Permissions";
import { suggestedApps } from "../../components/SuggestedAppData";

const NewApp = () => {
const location = useLocation();
Expand All @@ -28,11 +29,17 @@ const NewApp = () => {
const navigate = useNavigate();

const queryParams = new URLSearchParams(location.search);
const nameParam = (queryParams.get("name") || queryParams.get("c")) ?? "";

const appId = queryParams.get("app") ?? "";
const app = suggestedApps.find((app) => app.id === appId);

const nameParam = app
? app.title
: (queryParams.get("name") || queryParams.get("c")) ?? "";
const pubkey = queryParams.get("pubkey") ?? "";
const returnTo = queryParams.get("return_to") ?? "";

const [appName, setAppName] = useState(() => nameParam);
const [appName, setAppName] = useState(nameParam);

const budgetRenewalParam = queryParams.get(
"budget_renewal"
Expand Down Expand Up @@ -102,7 +109,7 @@ const NewApp = () => {
window.location.href = createAppResponse.returnTo;
return;
}
navigate("/apps/created", {
navigate(`/apps/created${app ? `?app=${app.id}` : ""}`, {
state: createAppResponse,
});
toast({ title: "App created" });
Expand All @@ -117,12 +124,20 @@ const NewApp = () => {
title={nameParam ? `Connect to ${appName}` : "Connect a new app"}
description="Configure wallet permissions for the app and follow instructions to finalise the connection"
/>
<form onSubmit={handleSubmit} acceptCharset="UTF-8" className="flex flex-col items-start gap-5 max-w-lg">
<form
onSubmit={handleSubmit}
acceptCharset="UTF-8"
className="flex flex-col items-start gap-5 max-w-lg"
>
{app && (
<div className="flex flex-row items-center gap-3">
<img src={app.logo} className="h-12 w-12" />
<h2 className="font-semibold text-lg">{app.title}</h2>
</div>
)}
{!nameParam && (
<div className="w-full grid gap-1.5">
<Label htmlFor="name">
Name
</Label>
<Label htmlFor="name">Name</Label>
im-adithya marked this conversation as resolved.
Show resolved Hide resolved
<Input
readOnly={!!nameParam}
type="text"
Expand All @@ -131,7 +146,8 @@ const NewApp = () => {
id="name"
onChange={(e) => setAppName(e.target.value)}
required
autoComplete="off" />
autoComplete="off"
/>
<p className="text-xs text-muted-foreground">
Name of the app or purpose of the connection
</p>
Expand All @@ -143,14 +159,13 @@ const NewApp = () => {
initialPermissions={permissions}
onPermissionsChange={setPermissions}
isEditing={!reqMethodsParam}
isNew />
isNew
im-adithya marked this conversation as resolved.
Show resolved Hide resolved
/>
</div>

<Separator />

<Button type="submit">
{pubkey ? "Connect" : "Next"}
</Button>
<Button type="submit">{pubkey ? "Connect" : "Next"}</Button>
</form>
</>
);
Expand Down
Loading
Loading