Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: edit name in show app screen #503

Merged
merged 4 commits into from
Aug 22, 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
14 changes: 14 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ func (api *api) CreateApp(createAppRequest *CreateAppRequest) (*CreateAppRespons
}

func (api *api) UpdateApp(userApp *db.App, updateAppRequest *UpdateAppRequest) error {
name := updateAppRequest.Name

if name == "" {
return fmt.Errorf("won't update an app to have no name")
}

maxAmount := updateAppRequest.MaxAmountSat
budgetRenewal := updateAppRequest.BudgetRenewal

Expand All @@ -125,6 +131,14 @@ func (api *api) UpdateApp(userApp *db.App, updateAppRequest *UpdateAppRequest) e
}

err = api.db.Transaction(func(tx *gorm.DB) error {
// Update app name if it is not the same
if name != userApp.Name {
err := tx.Model(&db.App{}).Where("id", userApp.ID).Update("name", name).Error
if err != nil {
return err
}
}

// Update existing permissions with new budget and expiry
err := tx.Model(&db.AppPermission{}).Where("app_id", userApp.ID).Updates(map[string]interface{}{
"ExpiresAt": expiresAt,
Expand Down
1 change: 1 addition & 0 deletions api/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ type ListAppsResponse struct {
}

type UpdateAppRequest struct {
Name string `json:"name"`
MaxAmountSat uint64 `json:"maxAmount"`
BudgetRenewal string `json:"budgetRenewal"`
ExpiresAt string `json:"expiresAt"`
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/screens/apps/NewApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { Input } from "src/components/ui/input";
import { Label } from "src/components/ui/label";
import { Separator } from "src/components/ui/separator";
import { useToast } from "src/components/ui/use-toast";
import { useApps } from "src/hooks/useApps";
import { useCapabilities } from "src/hooks/useCapabilities";
import { handleRequestError } from "src/utils/handleRequestError";
import { request } from "src/utils/request"; // build the project for this to appear
Expand All @@ -45,6 +46,7 @@ const NewAppInternal = ({ capabilities }: NewAppInternalProps) => {

const { toast } = useToast();
const navigate = useNavigate();
const { data: apps } = useApps();
const [unsupportedError, setUnsupportedError] = useState<string>();

const queryParams = new URLSearchParams(location.search);
Expand Down Expand Up @@ -180,6 +182,10 @@ const NewAppInternal = ({ capabilities }: NewAppInternalProps) => {
}

try {
if (apps?.some((existingApp) => existingApp.name === appName)) {
throw new Error("A connection with the same name already exists.");
rolznz marked this conversation as resolved.
Show resolved Hide resolved
}

const createAppRequest: CreateAppRequest = {
name: appName,
pubkey,
Expand Down
76 changes: 61 additions & 15 deletions frontend/src/screens/apps/ShowApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import { handleRequestError } from "src/utils/handleRequestError";
import { request } from "src/utils/request"; // build the project for this to appear

import { PencilIcon } from "lucide-react";
import AppAvatar from "src/components/AppAvatar";
import AppHeader from "src/components/AppHeader";
import Loading from "src/components/Loading";
Expand All @@ -37,8 +38,10 @@ import {
CardHeader,
CardTitle,
} from "src/components/ui/card";
import { Input } from "src/components/ui/input";
import { Table, TableBody, TableCell, TableRow } from "src/components/ui/table";
import { useToast } from "src/components/ui/use-toast";
import { useApps } from "src/hooks/useApps";
import { useCapabilities } from "src/hooks/useCapabilities";
import { formatAmount } from "src/lib/utils";

Expand Down Expand Up @@ -74,17 +77,22 @@ function AppInternal({ app, refetchApp, capabilities }: AppInternalProps) {
const { toast } = useToast();
const navigate = useNavigate();
const location = useLocation();
const [editMode, setEditMode] = React.useState(false);
const { data: apps } = useApps();
const [isEditingName, setIsEditingName] = React.useState(false);
const [isEditingPermissions, setIsEditingPermissions] = React.useState(false);

React.useEffect(() => {
const queryParams = new URLSearchParams(location.search);
setEditMode(queryParams.has("edit"));
const editMode = queryParams.has("edit");
setIsEditingName(editMode);
im-adithya marked this conversation as resolved.
Show resolved Hide resolved
setIsEditingPermissions(editMode);
}, [location.search]);

const { deleteApp, isDeleting } = useDeleteApp(() => {
navigate("/apps");
});

const [name, setName] = React.useState(app.name);
const [permissions, setPermissions] = React.useState<AppPermissions>({
scopes: app.scopes,
maxAmount: app.maxAmount,
Expand All @@ -95,7 +103,15 @@ function AppInternal({ app, refetchApp, capabilities }: AppInternalProps) {

const handleSave = async () => {
try {
if (
isEditingName &&
apps?.some((existingApp) => existingApp.name === name)
) {
throw new Error("A connection with the same name already exists.");
im-adithya marked this conversation as resolved.
Show resolved Hide resolved
}

const updateAppRequest: UpdateAppRequest = {
name,
scopes: Array.from(permissions.scopes),
budgetRenewal: permissions.budgetRenewal,
expiresAt: permissions.expiresAt?.toISOString(),
Expand All @@ -111,10 +127,13 @@ function AppInternal({ app, refetchApp, capabilities }: AppInternalProps) {
});

await refetchApp();
setEditMode(false);
toast({ title: "Successfully updated permissions" });
setIsEditingName(false);
setIsEditingPermissions(false);
toast({
title: `Successfully updated ${isEditingName ? "name" : "permissions"}`,
im-adithya marked this conversation as resolved.
Show resolved Hide resolved
});
} catch (error) {
handleRequestError(toast, "Failed to update permissions", error);
handleRequestError(toast, "Failed to update connection", error);
}
};

Expand All @@ -126,12 +145,37 @@ function AppInternal({ app, refetchApp, capabilities }: AppInternalProps) {
title={
<div className="flex flex-row items-center">
<AppAvatar appName={app.name} className="w-10 h-10 mr-2" />
<h2
title={app.name}
className="text-xl font-semibold overflow-hidden text-ellipsis whitespace-nowrap"
>
{app.name}
</h2>
{isEditingName ? (
<div className="flex flex-row gap-2 items-center">
<Input
autoFocus
type="text"
name="name"
value={name}
id="name"
onChange={(e) => setName(e.target.value)}
required
className="text-xl font-semibold w-max max-w-40 md:max-w-fit"
autoComplete="off"
/>
<Button type="button" onClick={handleSave}>
Save
</Button>
</div>
) : (
<div
className="flex flex-row gap-2 items-center cursor-pointer"
onClick={() => setIsEditingName(true)}
>
<h2
title={app.name}
className="text-xl font-semibold overflow-hidden text-ellipsis whitespace-nowrap"
>
{app.name}
</h2>
<PencilIcon className="h-4 w-4 shrink-0 text-muted-foreground" />
</div>
)}
</div>
}
contentRight={
Expand Down Expand Up @@ -216,7 +260,7 @@ function AppInternal({ app, refetchApp, capabilities }: AppInternalProps) {
<div className="flex flex-row justify-between items-center">
Permissions
<div className="flex flex-row gap-2">
{editMode && (
{isEditingPermissions && (
<div className="flex justify-center items-center gap-2">
<Button
type="button"
Expand All @@ -234,11 +278,13 @@ function AppInternal({ app, refetchApp, capabilities }: AppInternalProps) {
</div>
)}

{!editMode && (
{!isEditingPermissions && (
<>
<Button
variant="outline"
onClick={() => setEditMode(!editMode)}
onClick={() =>
setIsEditingPermissions(!isEditingPermissions)
}
>
Edit
</Button>
Expand All @@ -253,7 +299,7 @@ function AppInternal({ app, refetchApp, capabilities }: AppInternalProps) {
capabilities={capabilities}
permissions={permissions}
setPermissions={setPermissions}
readOnly={!editMode}
readOnly={!isEditingPermissions}
isNewConnection={false}
budgetUsage={app.budgetUsage}
/>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ export interface CreateAppResponse {
}

export type UpdateAppRequest = {
name: string;
maxAmount: number;
budgetRenewal: string;
expiresAt: string | undefined;
Expand Down
Loading