Skip to content

Commit

Permalink
chore: further changes
Browse files Browse the repository at this point in the history
  • Loading branch information
im-adithya committed Jul 12, 2024
1 parent 03b0518 commit 2310386
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 152 deletions.
13 changes: 10 additions & 3 deletions frontend/src/components/ExpirySelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const daysFromNow = (date?: Date) => {

interface ExpiryProps {
value?: Date | undefined;
onChange: (expiryDays: number) => void;
onChange: (expiryDate?: Date) => void;
}

const ExpirySelect: React.FC<ExpiryProps> = ({ value, onChange }) => {
Expand All @@ -41,7 +41,13 @@ const ExpirySelect: React.FC<ExpiryProps> = ({ value, onChange }) => {
key={expiry}
onClick={() => {
setCustomExpiry(false);
onChange(expiryOptions[expiry]);
let date;
if (expiryOptions[expiry]) {
date = new Date();
date.setDate(date.getUTCDate() + expiryOptions[expiry]);
date.setHours(23, 59, 59);
}
onChange(date);
setExpiryDays(expiryOptions[expiry]);
}}
className={cn(
Expand Down Expand Up @@ -83,8 +89,9 @@ const ExpirySelect: React.FC<ExpiryProps> = ({ value, onChange }) => {
if (!date) {
return;
}
date.setHours(23, 59, 59);
setCustomExpiry(true);
onChange(daysFromNow(date));
onChange(date);
setExpiryDays(daysFromNow(date));
}}
initialFocus
Expand Down
44 changes: 19 additions & 25 deletions frontend/src/components/Permissions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ const Permissions: React.FC<PermissionsProps> = ({
}) => {
const [permissions, setPermissions] = React.useState(initialPermissions);

const [isScopesEditable, setScopesEditable] = React.useState(
isNewConnection ? !initialPermissions.scopes.size : canEditPermissions
);
const [isBudgetAmountEditable, setBudgetAmountEditable] = React.useState(
isNewConnection
? Number.isNaN(initialPermissions.maxAmount)
Expand All @@ -44,25 +47,23 @@ const Permissions: React.FC<PermissionsProps> = ({
isNewConnection ? !initialPermissions.expiresAt : canEditPermissions
);

// this is triggered when changes are saved in show app
// triggered when changes are saved in show app
React.useEffect(() => {
if (isNewConnection || canEditPermissions) {
return;
}
setPermissions(initialPermissions);
}, [initialPermissions]);
}, [canEditPermissions, isNewConnection, initialPermissions]);

// this is triggered when edit mode is called
// triggered when edit mode is toggled in show app
React.useEffect(() => {
if (isNewConnection) {
return;
}
setBudgetAmountEditable(
isNewConnection
? Number.isNaN(initialPermissions.maxAmount)
: canEditPermissions
);
setExpiryEditable(
isNewConnection ? !initialPermissions.expiresAt : canEditPermissions
);
}, [canEditPermissions, initialPermissions, isNewConnection]);
setScopesEditable(canEditPermissions);
setBudgetAmountEditable(canEditPermissions);
setExpiryEditable(canEditPermissions);
}, [canEditPermissions, isNewConnection]);

const [showBudgetOptions, setShowBudgetOptions] = React.useState(
isNewConnection ? !!permissions.maxAmount : true
Expand Down Expand Up @@ -101,23 +102,16 @@ const Permissions: React.FC<PermissionsProps> = ({
[handlePermissionsChange]
);

const handleExpiryDaysChange = React.useCallback(
(expiryDays: number) => {
if (!expiryDays) {
handlePermissionsChange({ expiresAt: undefined });
return;
}
const currentDate = new Date();
currentDate.setDate(currentDate.getUTCDate() + expiryDays);
currentDate.setHours(23, 59, 59);
handlePermissionsChange({ expiresAt: currentDate });
const handleExpiryChange = React.useCallback(
(expiryDate?: Date) => {
handlePermissionsChange({ expiresAt: expiryDate });
},
[handlePermissionsChange]
);

return (
<div className="max-w-lg">
{canEditPermissions ? (
{isScopesEditable ? (
<Scopes
capabilities={capabilities}
scopes={permissions.scopes}
Expand All @@ -127,7 +121,7 @@ const Permissions: React.FC<PermissionsProps> = ({
<>
<p className="text-sm font-medium mb-2">Scopes</p>
<div className="flex flex-col gap-1">
{[...initialPermissions.scopes].map((rm) => {
{[...permissions.scopes].map((rm) => {
const PermissionIcon = iconMap[rm];
return (
<div
Expand Down Expand Up @@ -226,7 +220,7 @@ const Permissions: React.FC<PermissionsProps> = ({
{showExpiryOptions && (
<ExpirySelect
value={permissions.expiresAt}
onChange={handleExpiryDaysChange}
onChange={handleExpiryChange}
/>
)}
</>
Expand Down
179 changes: 104 additions & 75 deletions frontend/src/components/Scopes.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import React from "react";
import React, { useEffect } from "react";
import { Checkbox } from "src/components/ui/checkbox";
import { Label } from "src/components/ui/label";
import { cn } from "src/lib/utils";
import {
NIP_47_MAKE_INVOICE_METHOD,
NIP_47_GET_BALANCE_METHOD,
NIP_47_GET_INFO_METHOD,
NIP_47_LIST_TRANSACTIONS_METHOD,
NIP_47_LOOKUP_INVOICE_METHOD,
NIP_47_MULTI_PAY_INVOICE_METHOD,
NIP_47_MULTI_PAY_KEYSEND_METHOD,
NIP_47_NOTIFICATIONS_PERMISSION,
NIP_47_PAY_INVOICE_METHOD,
NIP_47_PAY_KEYSEND_METHOD,
ReadOnlyScope,
SCOPE_GROUP_CUSTOM,
SCOPE_GROUP_ONLY_RECEIVE,
SCOPE_GROUP_SEND_RECEIVE,
SCOPE_GROUP_FULL_ACCESS,
SCOPE_GROUP_READ_ONLY,
Scope,
ScopeGroupType,
WalletCapabilities,
Expand All @@ -18,63 +25,87 @@ import {
scopeGroupTitle,
} from "src/types";

// TODO: this runs everytime, use useEffect
const scopeGrouper = (scopes: Set<Scope>) => {
if (
scopes.size === 2 &&
scopes.has(NIP_47_MAKE_INVOICE_METHOD) &&
scopes.has(NIP_47_PAY_INVOICE_METHOD)
) {
return "send_receive";
} else if (scopes.size === 1 && scopes.has(NIP_47_MAKE_INVOICE_METHOD)) {
return "only_receive";
}
return "custom";
};

const validScopeGroups = (capabilities: WalletCapabilities) => {
const scopeGroups = [SCOPE_GROUP_CUSTOM];
if (capabilities.scopes.includes(NIP_47_MAKE_INVOICE_METHOD)) {
scopeGroups.unshift(SCOPE_GROUP_ONLY_RECEIVE);
if (capabilities.scopes.includes(NIP_47_PAY_INVOICE_METHOD)) {
scopeGroups.unshift(SCOPE_GROUP_SEND_RECEIVE);
}
}
return scopeGroups;
};

interface ScopesProps {
capabilities: WalletCapabilities;
scopes: Set<Scope>;
onScopeChange: (scopes: Set<Scope>) => void;
}

const isSetEqual = (setA: Set<string>, setB: Set<string>) =>
setA.size === setB.size && [...setA].every((value) => setB.has(value));

const Scopes: React.FC<ScopesProps> = ({
capabilities,
scopes,
onScopeChange,
}) => {
const [scopeGroup, setScopeGroup] = React.useState(scopeGrouper(scopes));
const scopeGroups = validScopeGroups(capabilities);
const fullAccessScopes: Set<Scope> = React.useMemo(() => {
const scopes: Scope[] = capabilities.methods as Scope[];
if (capabilities.notificationTypes.length) {
scopes.push(NIP_47_NOTIFICATIONS_PERMISSION);
}
return new Set(
scopes
.map((scope) =>
[
NIP_47_PAY_KEYSEND_METHOD,
NIP_47_MULTI_PAY_INVOICE_METHOD,
NIP_47_MULTI_PAY_KEYSEND_METHOD,
].includes(scope)
? NIP_47_PAY_INVOICE_METHOD
: scope
)
.filter((scope, i, self) => self.indexOf(scope) === i)
);
}, [capabilities]);

const readOnlyScopes: Set<ReadOnlyScope> = React.useMemo(() => {
const scopes: Scope[] = capabilities.methods as Scope[];
if (capabilities.notificationTypes.length) {
scopes.push(NIP_47_NOTIFICATIONS_PERMISSION);
}
return new Set(
scopes.filter((method): method is ReadOnlyScope =>
[
NIP_47_GET_BALANCE_METHOD,
NIP_47_GET_INFO_METHOD,
NIP_47_LOOKUP_INVOICE_METHOD,
NIP_47_LIST_TRANSACTIONS_METHOD,
NIP_47_NOTIFICATIONS_PERMISSION,
].includes(method)
)
);
}, [capabilities]);

const [scopeGroup, setScopeGroup] = React.useState<ScopeGroupType>(() => {
if (!scopes.size || isSetEqual(scopes, fullAccessScopes)) {
return SCOPE_GROUP_FULL_ACCESS;
} else if (isSetEqual(scopes, readOnlyScopes)) {
return SCOPE_GROUP_READ_ONLY;
}
return SCOPE_GROUP_CUSTOM;
});

// we need scopes to be empty till this point for isScopesEditable
// and once this component is mounted we set it to all scopes
useEffect(() => {
// stop setting scopes on re-renders
if (!scopes.size && scopeGroup != SCOPE_GROUP_CUSTOM) {
onScopeChange(fullAccessScopes);
}
}, [fullAccessScopes, onScopeChange, scopeGroup, scopes]);

// TODO: EDITABLE PROP
const handleScopeGroupChange = (scopeGroup: ScopeGroupType) => {
setScopeGroup(scopeGroup);
switch (scopeGroup) {
case "send_receive":
onScopeChange(
new Set([NIP_47_PAY_INVOICE_METHOD, NIP_47_MAKE_INVOICE_METHOD])
);
case SCOPE_GROUP_FULL_ACCESS:
onScopeChange(fullAccessScopes);
break;
case "only_receive":
onScopeChange(new Set([NIP_47_MAKE_INVOICE_METHOD]));
case SCOPE_GROUP_READ_ONLY:
onScopeChange(readOnlyScopes);
break;
default: {
const newSet = new Set(capabilities.scopes);
if (capabilities.notificationTypes.length) {
newSet.add(NIP_47_NOTIFICATIONS_PERMISSION);
}
onScopeChange(newSet);
onScopeChange(new Set());
break;
}
}
Expand All @@ -92,39 +123,37 @@ const Scopes: React.FC<ScopesProps> = ({

return (
<div className="mb-6">
{scopeGroups.length > 1 && (
<div className="flex flex-col w-full mb-6">
<p className="font-medium text-sm mb-2">Choose wallet permissions</p>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
{(scopeGroups as ScopeGroupType[]).map((sg, index) => {
if (
scopeGroup == SCOPE_GROUP_SEND_RECEIVE &&
!capabilities.scopes.includes(NIP_47_PAY_INVOICE_METHOD)
) {
return;
}
const ScopeGroupIcon = scopeGroupIconMap[sg];
return (
<div
key={index}
className={`flex flex-col items-center border-2 rounded cursor-pointer ${scopeGroup == sg ? "border-primary" : "border-muted"} p-4`}
onClick={() => {
handleScopeGroupChange(sg);
}}
>
<ScopeGroupIcon className="mb-2" />
<p className="text-sm font-medium">{scopeGroupTitle[sg]}</p>
<p className="text-[10px] text-muted-foreground text-nowrap">
{scopeGroupDescriptions[sg]}
</p>
</div>
);
})}
</div>
<div className="flex flex-col w-full mb-6">
<p className="font-medium text-sm mb-2">Choose wallet permissions</p>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
{(
[
SCOPE_GROUP_FULL_ACCESS,
SCOPE_GROUP_READ_ONLY,
SCOPE_GROUP_CUSTOM,
] as ScopeGroupType[]
).map((sg, index) => {
const ScopeGroupIcon = scopeGroupIconMap[sg];
return (
<div
key={index}
className={`flex flex-col items-center border-2 rounded cursor-pointer ${scopeGroup == sg ? "border-primary" : "border-muted"} p-4`}
onClick={() => {
handleScopeGroupChange(sg);
}}
>
<ScopeGroupIcon className="mb-2" />
<p className="text-sm font-medium">{scopeGroupTitle[sg]}</p>
<p className="text-[10px] text-muted-foreground text-nowrap">
{scopeGroupDescriptions[sg]}
</p>
</div>
);
})}
</div>
)}
</div>

{(scopeGroup == "custom" || scopeGroups.length == 1) && (
{scopeGroup == "custom" && (
<>
<p className="font-medium text-sm">Authorize the app to:</p>
<ul className="flex flex-col w-full mt-3">
Expand All @@ -134,7 +163,7 @@ const Scopes: React.FC<ScopesProps> = ({
key={index}
className={cn(
"w-full",
rm == "pay_invoice" ? "order-last" : ""
rm == NIP_47_PAY_INVOICE_METHOD ? "order-last" : ""
)}
>
<div className="flex items-center mb-2">
Expand Down
11 changes: 8 additions & 3 deletions frontend/src/components/connections/AppCardConnectionInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import { Link } from "react-router-dom";
import { Button } from "src/components/ui/button";
import { Progress } from "src/components/ui/progress";
import { formatAmount } from "src/lib/utils";
import { App, BudgetRenewalType } from "src/types";
import {
App,
BudgetRenewalType,
NIP_47_MAKE_INVOICE_METHOD,
NIP_47_PAY_INVOICE_METHOD,
} from "src/types";

type AppCardConnectionInfoProps = {
connection: App;
Expand Down Expand Up @@ -69,7 +74,7 @@ export function AppCardConnectionInfo({
</div>
</div>
</>
) : connection.scopes.indexOf("pay_invoice") > -1 ? (
) : connection.scopes.indexOf(NIP_47_PAY_INVOICE_METHOD) > -1 ? (
<>
<div className="flex flex-row justify-between">
<div className="mb-2">
Expand Down Expand Up @@ -97,7 +102,7 @@ export function AppCardConnectionInfo({
<CircleCheck className="w-4 h-4" />
Share wallet information
</div>
{connection.scopes.indexOf("make_invoice") > -1 && (
{connection.scopes.indexOf(NIP_47_MAKE_INVOICE_METHOD) > -1 && (
<div className="flex flex-row items-center gap-2">
<CircleCheck className="w-4 h-4" />
Create Invoices
Expand Down
Loading

0 comments on commit 2310386

Please sign in to comment.