Skip to content

Commit

Permalink
Merge pull request #348 from cstenglein/app-open-info
Browse files Browse the repository at this point in the history
add tooltip with login information for installed apps
  • Loading branch information
cstenglein authored Jun 12, 2022
2 parents 8cd71be + 6b60dda commit 065ee79
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 60 deletions.
5 changes: 0 additions & 5 deletions src/assets/info.svg

This file was deleted.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 25 additions & 7 deletions src/components/Apps/AppCard/AppCard.test.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,35 @@
import { render, screen, act } from "@testing-library/react";
import { render, screen } from "@testing-library/react";
import { I18nextProvider } from "react-i18next";
import i18n from "../../../i18n/test_config";
import { AppCard } from "./AppCard";
import { AppStatus, AuthMethod } from "../../../models/app-status";
import { App } from "../../../models/app.model";
import { AppCard, Props } from "./AppCard";

const app = {
const app: App = {
id: "123",
description: "Hi",
name: "d",
author: "Me",
name: "someApp",
repository: "http://example.com",
version: "1.0.0",
};

const basicProps = {
const appStatus: AppStatus = {
id: "123",
installed: false,
address: "",
authMethod: AuthMethod.NONE,
details: "",
hiddenService: "",
httpsForced: "0",
httpsSelfsigned: "0",
error: "",
};

const basicProps: Props = {
installingApp: null,
app: app,
appInfo: app,
appStatusInfo: appStatus,
installed: false,
onInstall: () => {},
onOpenDetails: () => {},
};
Expand Down
59 changes: 47 additions & 12 deletions src/components/Apps/AppCard/AppCard.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { FC, useState, useEffect } from "react";
import Tooltip from "rc-tooltip";
import { FC, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { ReactComponent as InfoIcon } from "../../../assets/info.svg";
import { ReactComponent as InfoIcon } from "../../../assets/information-circle.svg";
import { ReactComponent as LinkIcon } from "../../../assets/link.svg";
import { ReactComponent as LockIcon } from "../../../assets/lock-open.svg";
import { ReactComponent as PlusIcon } from "../../../assets/plus.svg";
import AppIcon from "../../../container/AppIcon/AppIcon";
import { AppStatus, AuthMethod } from "../../../models/app-status";
import { App } from "../../../models/app.model";

import ButtonWithSpinner from "../../Shared/ButtonWithSpinner/ButtonWithSpinner";

type Props = {
app: App;
export type Props = {
appInfo: App;
appStatusInfo: AppStatus;
installed: boolean;
installingApp: any | null;
address?: string;
Expand All @@ -18,15 +23,15 @@ type Props = {
};

export const AppCard: FC<Props> = ({
app,
appInfo,
appStatusInfo,
installed,
installingApp,
onInstall,
onOpenDetails,
address,
hiddenService,
}) => {
const { id, name } = app;
const { id, name } = appInfo;
const { t } = useTranslation();
const [isInstallWaiting, setInstallWaiting] = useState(false);

Expand All @@ -39,6 +44,27 @@ export const AppCard: FC<Props> = ({
onInstall(id);
};

const tooltipContent = (
<div>
{appStatusInfo.httpsForced === "1" &&
appStatusInfo.httpsSelfsigned === "1" && (
<h2 className="pb-5">{t("apps.selfsigned_cert")}</h2>
)}
{appStatusInfo.authMethod === AuthMethod.NONE && (
<h2>{t("apps.login_no_pass")}</h2>
)}
{appStatusInfo.authMethod === AuthMethod.PASSWORD_B && (
<h2>{t("apps.login_pass_b")}</h2>
)}
{appStatusInfo.authMethod === AuthMethod.USER_ADMIN_PASSWORD_B && (
<h2>{t("apps.login_admin_pass_b")}</h2>
)}
{appStatusInfo.authMethod === AuthMethod.USER_DEFINED && (
<h2>{t("apps.login_userdef")}</h2>
)}
</div>
);

return (
<div className="bd-card transition-colors dark:bg-gray-800">
<div className="mt-2 flex h-4/6 w-full flex-row items-center">
Expand All @@ -47,11 +73,20 @@ export const AppCard: FC<Props> = ({
<AppIcon appId={id} />
</div>
{/* Content */}
<div className="flex w-3/4 flex-col items-start justify-center text-xl">
<div>{name}</div>
<div className="overflow-ellipsis text-base text-gray-500 dark:text-gray-200">
<div className="relative flex w-3/4 flex-col items-start justify-center text-xl">
<h4>{name}</h4>
{installed && (
<Tooltip
trigger={["click", "hover"]}
overlay={tooltipContent}
placement="top"
>
<LockIcon className="absolute top-0 right-0 h-6 w-6" />
</Tooltip>
)}
<p className="overflow-ellipsis text-base text-gray-500 dark:text-gray-200">
{t(`appInfo.${id}.shortDescription`)}
</div>
</p>
</div>
</div>
<div className="flex h-2/6 flex-row gap-2 py-2">
Expand Down Expand Up @@ -103,7 +138,7 @@ export const AppCard: FC<Props> = ({
)}
<button
className="flex w-1/2 items-center justify-center rounded p-2 shadow-md hover:bg-gray-300 dark:bg-gray-500 dark:hover:bg-gray-300 dark:hover:text-black"
onClick={() => onOpenDetails(app)}
onClick={() => onOpenDetails(appInfo)}
>
<InfoIcon />
&nbsp;{t("apps.info")}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Home/TransactionCard/TransactionCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { FC, useContext, useState } from "react";
import { useTranslation } from "react-i18next";
import { ReactComponent as ArrowDownIcon } from "../../../assets/arrow-down.svg";
import { ReactComponent as InfoCircleIcon } from "../../../assets/info-circle.svg";
import { ReactComponent as InfoCircleIcon } from "../../../assets/information-circle.svg";
import { ReactComponent as ClosedLockIcon } from "../../../assets/lock-closed.svg";
import Message from "../../../container/Message/Message";
import { Transaction } from "../../../models/transaction.model";
Expand Down
7 changes: 6 additions & 1 deletion src/i18n/langs/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,12 @@
"install_success": "{{appName}} installed successfully",
"install_failure": "Installation of {{appName}} failed: {{details}}",
"uninstall_success": "Successfully uninstalled {{appName}}",
"uninstall_failure": "Uninstall of {{appName}} failed: {{details}}"
"uninstall_failure": "Uninstall of {{appName}} failed: {{details}}",
"selfsigned_cert": "This app uses a self-signed HTTPS certificate",
"login_admin_pass_b": "Login with User 'admin' & Password B",
"login_pass_b": "Login with Password B",
"login_no_pass": "Login without Password",
"login_userdef": "Login with user-defined Password"
},
"settings": {
"change_pw_a": "Change password A",
Expand Down
13 changes: 12 additions & 1 deletion src/models/app-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@ export interface AppStatus {
id: string;
installed: boolean;
address?: string;
httpsForced?: "0" | "1";
httpsSelfsigned?: "0" | "1";
hiddenService?: string;
extra?: any;
authMethod?: AuthMethod;
details?: unknown;
error: string;
}

export enum AuthMethod {
NONE = "none",
USER_DEFINED = "userdefined",
PASSWORD_B = "password_b",
USER_ADMIN_PASSWORD_B = "user_admin_password_b",
}
3 changes: 3 additions & 0 deletions src/models/app.model.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export interface App {
id: string;
name: string;
author: string;
version: string;
repository: string;
}
48 changes: 17 additions & 31 deletions src/pages/Apps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,32 +25,16 @@ export const Apps: FC = () => {
}, []);

// on every render sort installed & uninstalled app keys
const installedAppsData = appStatus.filter((app: AppStatus) => {
const installedApps = appStatus.filter((app: AppStatus) => {
return app.installed;
});
const installedApps = Array.from(installedAppsData, (app: AppStatus) => {
return app.id;
});
const notInstalledAppsData = appStatus.filter((app: AppStatus) => {
const notInstalledApps = appStatus.filter((app: AppStatus) => {
return !app.installed;
});
const notInstalledApps = Array.from(
notInstalledAppsData,
(app: AppStatus) => {
return app.id;
}
);

// in case no App data received yet .. show loading
// in case no App data received yet => show loading
const isLoading = appStatus.length === 0;

const getAppStatus: any = (id: string, datafield: string) => {
// return AppStatus from SSE context if available (always the latest updates)
const fromSSE: any = appStatus.find((a) => a.id === id);
if (fromSSE && fromSSE[datafield]) return fromSSE[datafield];
return null;
};

const installHandler = (id: string) => {
instance.post(`apps/install/${id}`).catch((err) => {
toast.error(err.response.data.detail || err.response.data);
Expand All @@ -74,13 +58,14 @@ export const Apps: FC = () => {
};

if (showDetails && app) {
const appInfos = appStatus.find((a) => a.id === app.id)!;
return (
<AppInfo
app={app}
installingApp={installingApp}
onInstall={() => installHandler(app.id)}
onUninstall={() => uninstallHandler(app.id)}
installed={getAppStatus(app.id, "installed")}
installed={appInfos?.installed}
onClose={closeDetailsHandler}
/>
);
Expand All @@ -99,18 +84,18 @@ export const Apps: FC = () => {
<h2 className="w-full px-5 pt-8 pb-5 text-xl font-bold dark:text-gray-200">
{t("apps.installed")}
</h2>
{/* TODO: make address / hiddenService faster */}
{installedApps.map((appId: string) => {
{installedApps.map((appStatus: AppStatus) => {
return (
<article className="w-full p-3 lg:w-1/3" key={appId}>
<article className="w-full p-3 lg:w-1/3" key={appStatus.id}>
<AppCard
app={availableApps.get(appId)!}
appInfo={availableApps.get(appStatus.id)!}
appStatusInfo={appStatus}
installed={true}
installingApp={null}
onInstall={() => installHandler(appId)}
onInstall={() => installHandler(appStatus.id)}
onOpenDetails={openDetailsHandler}
address={getAppStatus(appId, "address")}
hiddenService={getAppStatus(appId, "hiddenService")}
address={appStatus.address}
hiddenService={appStatus.hiddenService}
/>
</article>
);
Expand All @@ -120,14 +105,15 @@ export const Apps: FC = () => {
<h2 className="block w-full px-5 pt-8 pb-5 text-xl font-bold dark:text-gray-200 ">
{t("apps.available")}
</h2>
{notInstalledApps.map((appId: string) => {
{notInstalledApps.map((appStatus: AppStatus) => {
return (
<article className="w-full p-3 lg:w-1/3" key={appId}>
<article className="w-full p-3 lg:w-1/3" key={appStatus.id}>
<AppCard
app={availableApps.get(appId)!}
appInfo={availableApps.get(appStatus.id)!}
appStatusInfo={appStatus}
installed={false}
installingApp={installingApp}
onInstall={() => installHandler(appId)}
onInstall={() => installHandler(appStatus.id)}
onOpenDetails={openDetailsHandler}
/>
</article>
Expand Down

0 comments on commit 065ee79

Please sign in to comment.