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

dash: show user status #447

Merged
merged 2 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
29 changes: 14 additions & 15 deletions dash/components/src/Computers/ComputerCard.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
import React from 'react';
import cx from 'classnames';
import { Button } from '@shared/components';
import type { ChildComputerStatus } from '@dash/types';
import UserStatus from '../UserStatus';

interface Props {
name?: string;
id: string;
modelTitle: string;
modelIdentifier: string;
onlineUser?: string;
user?: {
name: string;
status: ChildComputerStatus;
};
}

const ComputerCard: React.FC<Props> = ({
name,
modelTitle,
onlineUser,
user,
modelIdentifier,
id,
}) => (
<div className="border-[0.5px] border-slate-200 rounded-3xl shadow-lg shadow-slate-300/50 bg-white">
<div className="p-6 flex justify-between items-center gap-4">
<div>
{user?.name && (
<h3 className="text-sm font-semibold text-slate-500">
{user.name} is using{!name && ` the`}
</h3>
)}
<h2 className="text-2xl font-bold">{name || modelTitle}</h2>
{name && <h3 className="text-sm text-slate-500">{modelTitle}</h3>}
</div>
Expand All @@ -31,18 +40,8 @@ const ComputerCard: React.FC<Props> = ({
/>
</div>
</div>
<div className="p-4 bg-slate-50 rounded-b-3xl flex justify-between items-center">
<div className="flex justify-end items-center gap-2 ml-2">
<div
className={cx(
`w-3 h-3 rounded-full`,
onlineUser ? `bg-green-400` : `bg-slate-100 shadow-inner`,
)}
/>
<span className="text-xs text-slate-400">
{onlineUser ? `${onlineUser} is online` : `offline`}
</span>
</div>
<div className="py-3 pl-3 pr-5 border-t border-slate-200 rounded-b-3xl flex justify-between items-center @container">
<UserStatus status={user?.status || { case: `offline` }} />
<Button type="link" to={`/computers/${id}`} color="tertiary" size="small">
<i className="fa-solid fa-pen mr-2" />
Edit
Expand Down
36 changes: 3 additions & 33 deletions dash/components/src/Computers/EditComputer.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React from 'react';
import cx from 'classnames';
import { Button, SelectMenu, TextInput } from '@shared/components';
import { Link } from 'react-router-dom';
import Badge from '@shared/components/src/Badge';
import type { ChildComputerStatus, ReleaseChannel } from '@dash/types';
import PageHeading from '../PageHeading';
import UserStatus from '../UserStatus';

interface Props {
name: string;
Expand Down Expand Up @@ -118,9 +117,9 @@ const EditComputer: React.FC<Props> = ({
</div>
<div className="border border-slate-200 mt-4 sm:mt-6 rounded-3xl bg-white p-6 sm:p-8">
<h3 className="text-xl font-bold text-slate-900">Children using this computer:</h3>
<div className="mt-6">
<div className="mt-4 flex flex-col gap-2">
{users.map((user) => (
<ComputerUser key={user.id} {...user} />
<UserStatus key={user.id} {...user} />
))}
</div>
</div>
Expand All @@ -142,32 +141,3 @@ const EditComputer: React.FC<Props> = ({
);

export default EditComputer;

interface ComputerUserProps {
name: string;
id: string;
status: ChildComputerStatus;
}

const ComputerUser: React.FC<ComputerUserProps> = ({ name, id, status }) => {
const isOnline = status.case !== `offline`;
return (
<Link
to={`/children/${id}`}
className="flex justify-between items-center odd:bg-slate-50 hover:bg-slate-100 transition-[background-color] duration-100 px-4 py-3 rounded-xl"
>
<h4 className="text-slate-700 font-semibold sm:text-lg">{name}</h4>
<div className="flex items-center gap-2">
<span className={cx(`text-sm`, isOnline ? `text-slate-600` : `text-slate-400`)}>
{isOnline ? `online` : `offline`}
</span>
<div
className={cx(
`w-3 h-3 rounded-full`,
isOnline ? `bg-green-400` : ` shadow-inner bg-slate-100`,
)}
/>
</div>
</Link>
);
};
2 changes: 1 addition & 1 deletion dash/components/src/Computers/ListComputers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const ListComputers: React.FC<Props> = ({ devices }) => (
id={device.id}
modelTitle={device.modelTitle}
modelIdentifier={device.modelIdentifier}
onlineUser={onlineUser?.name}
user={onlineUser}
/>
);
})}
Expand Down
4 changes: 2 additions & 2 deletions dash/components/src/Dashboard/UserActivityWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ const UserActivityWidget: React.FC<Props> = ({ userActivity, className }) => {
);

return (
<DashboardWidget className={cx(`!p-0 !sm:p-0`, className)}>
<div className="p-3 sm:p-4 pb-2 sm:pb-2">
<DashboardWidget className={cx(`!p-0 !sm:p-0 flex flex-col`, className)}>
<div className="p-3 sm:p-4 pb-2 sm:pb-2 flex-grow">
<WidgetTitle icon="binoculars" text="Activity" />
<div className="mb-4 flex flex-col space-y-2">
{writable(userActivity)
Expand Down
28 changes: 7 additions & 21 deletions dash/components/src/Dashboard/UsersOverviewWidget.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from 'react';
import cx from 'classnames';
import { Button, Badge } from '@shared/components';
import { Button } from '@shared/components';
import type { GetDashboardWidgets } from '@dash/types';
import UserStatus from '../UserStatus';
import DashboardWidget from './DashboardWidget';
import WidgetTitle from './WidgetTitle';

Expand All @@ -22,9 +23,11 @@ const UsersOverview: React.FC<Props> = ({ className, users }) => {
All children
</Button>
</div>
{users.map((user) => (
<UserOverview key={user.id} {...user} />
))}
<div className="flex flex-col gap-2 @container">
{users.map((user) => (
<UserStatus key={user.id} {...user} />
))}
</div>
</DashboardWidget>
);
return (
Expand All @@ -45,20 +48,3 @@ const UsersOverview: React.FC<Props> = ({ className, users }) => {
};

export default UsersOverview;

const UserOverview: React.FC<User> = ({ status, name }) => (
<div className="flex justify-between items-center rounded-xl py-4 px-4 even:bg-slate-50/50">
<h3 className="font-medium text-slate-900">{name}</h3>
{status.case !== `offline` ? (
<Badge size="large" className="!px-4" type="green">
<i className="mr-2 fa-solid fa-circle text-green-400 text-sm scale-50" />
online
</Badge>
) : (
<Badge size="large" className="!px-4" type="yellow">
<i className="mr-2 fa-solid fa-circle text-yellow-400 text-sm scale-50" />
offline
</Badge>
)}
</div>
);
104 changes: 104 additions & 0 deletions dash/components/src/UserStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React from 'react';
import cx from 'classnames';
import { relativeTime } from '@dash/datetime';
import type { ChildComputerStatus } from '@dash/types';

interface Props {
name?: string;
status: ChildComputerStatus;
}

const UserStatus: React.FC<Props> = ({ status, name }) => {
const description: {
color: string;
gradient?: string;
primaryText: string;
secondaryText?: string;
} = (() => {
switch (status.case) {
case `filterSuspended`:
return {
color: `bg-yellow-500`,
gradient: `[background:radial-gradient(#eab30820_0%,transparent_70%)]`,
primaryText: `Filter suspended`,
secondaryText: status.resuming && `Resuming ${relativeTime(status.resuming)}`,
};
case `downtime`:
return {
color: `bg-purple-500`,
gradient: `[background:radial-gradient(#a855f720_0%,transparent_70%)]`,
primaryText: `In downtime`,
secondaryText: status.ending && `Ending ${relativeTime(status.ending)}`,
};
case `downtimePaused`:
return {
color: `bg-yellow-500`,
gradient: `[background:radial-gradient(#eab30820_0%,transparent_70%)]`,
primaryText: `Downtime paused`,
secondaryText: status.resuming && `Resuming ${relativeTime(status.resuming)}`,
};
case `offline`:
return {
color: `bg-slate-200`,
primaryText: `Offline`,
};
case `filterOff`:
return {
color: `bg-red-500`,
gradient: `[background:radial-gradient(#ef444420_0%,transparent_70%)]`,
primaryText: `Filter off`,
};
case `filterOn`:
return {
color: `bg-green-500`,
gradient: `[background:radial-gradient(#22c55e20_0%,transparent_70%)]`,
primaryText: `Filter on`,
};
}
})();

return (
<div
className={cx(
`border-[0.5px] border-slate-300 relative overflow-hidden bg-gradient-to-b from-white to-slate-50/50 shadow shadow-slate-300/30`,
name
? `px-4 py-2 rounded-2xl`
: `px-2 @sm:px-3 py-1.5 @sm:py-2 rounded-xl @sm:rounded-2xl`,
)}
>
<div
className={cx(
`w-[600px] h-40 absolute -left-[300px] -bottom-20`,
description.gradient,
)}
/>
{name && <h3 className="text-lg font-semibold">{name}</h3>}
<div
className={cx(
`flex`,
name ? `items-start gap-2` : `items-center gap-2 @sm:gap-3`,
)}
>
<div
className={cx(
`w-2 h-2 rounded-full shrink-0`,
description.color,
name && `mt-1.5`,
)}
/>
<div className="flex flex-col">
<span className="font-medium text-black/60 text-xs @sm:text-sm shrink-0 whitespace-nowrap">
{description.primaryText}
</span>
{description.secondaryText && (
<span className="text-[11px] @sm:text-xs text-black/40 leading-[1]">
{description.secondaryText}
</span>
)}
</div>
</div>
</div>
);
};

export default UserStatus;
12 changes: 4 additions & 8 deletions dash/components/src/Users/EditUser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import KeychainCard from '../Keychains/KeychainCard';
import { ConfirmDeleteEntity } from '../Modal';
import PageHeading from '../PageHeading';
import TimeInput from '../Forms/TimeInput';
import BetaBadge from '../BetaBadge';
import EmptyState from '../EmptyState';
import AddKeychainDrawer from './AddKeychainDrawer';
import ConnectDeviceModal from './ConnectDeviceModal';
Expand Down Expand Up @@ -207,7 +206,7 @@ const EditUser: React.FC<Props> = ({
<h2 className="mt-5 text-lg font-bold text-slate-700">
{devices.length} {inflect(`computer`, devices.length)}:
</h2>
<div className="flex flex-col max-w-3xl">
<div className="flex flex-col max-w-3xl -mx-2 xs:mx-0">
{devices.map((userDevice) => (
<div key={userDevice.id} className="flex items-center mt-3">
<UserDevice
Expand All @@ -217,11 +216,11 @@ const EditUser: React.FC<Props> = ({
deviceId={userDevice.deviceId}
name={userDevice.name}
status={userDevice.status}
className="flex-grow mr-3"
className="flex-grow mr-1 xs:mr-3"
/>
<button
onClick={() => deleteDevice.start(userDevice.id)}
className="transition-colors duration-100 flex justify-center items-center w-10 h-10 rounded-full hover:bg-slate-100 cursor-pointer text-slate-500 hover:text-red-500"
className="transition-colors duration-100 flex justify-center items-center w-6 xs:w-10 h-6 xs:h-10 rounded-full hover:bg-slate-200/50 cursor-pointer text-slate-500 hover:text-red-500"
>
<i className="fa fa-trash" />
</button>
Expand Down Expand Up @@ -351,10 +350,7 @@ const EditUser: React.FC<Props> = ({
{/* blocked apps */}
{blockedApps && (
<div className="mt-12 max-w-3xl mb-12">
<div className="flex items-center gap-2">
<h2 className="text-lg font-bold text-slate-700">Blocked apps{` `}</h2>
<BetaBadge />
</div>
<h2 className="text-lg font-bold text-slate-700">Blocked apps{` `}</h2>
{blockedApps.length === 0 ? (
<div className="flex flex-col items-center justify-center p-8 bg-slate-100 mt-2 rounded-2xl shadow-inner">
<NoSymbolIcon className="w-8 h-8 text-slate-300" strokeWidth={2} />
Expand Down
2 changes: 1 addition & 1 deletion dash/components/src/Users/ListUsers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const Users: React.FC<Props> = ({
<div className="mt-8 flex flex-col">
{users.length > 0 ? (
<>
<div className="mb-16 grid grid-cols-1 lg+:grid-cols-2 gap-10 lg+:gap-8 xl:gap-10 2xl:grid-cols-3">
<div className="mb-16 grid grid-cols-1 xl:grid-cols-2 gap-10 lg+:gap-8 xl:gap-10 2xl:grid-cols-3">
{users.map((user) => (
<UserCard
key={user.id}
Expand Down
4 changes: 2 additions & 2 deletions dash/components/src/Users/UserCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const UserCard: React.FC<Props> = ({
className="rounded-3xl border-[0.5px] border-slate-200 flex flex-col justify-between shadow-lg shadow-slate-300/50 bg-white sm:min-w-[400px]"
data-test="user-card"
>
<div className="p-6">
<div className="p-4 xs:p-6">
<div className="flex justify-between items-center">
<h2 className="text-3xl font-extrabold text-slate-700 mr-3">{name}</h2>
<div className="flex items-center space-x-4">
Expand Down Expand Up @@ -72,7 +72,7 @@ const UserCard: React.FC<Props> = ({
{inflect(`computer`, devices.length)}:
</p>
</div>
<div className="flex flex-col mt-3 space-y-3 pt-3">
<div className="flex flex-col mt-3 gap-3 pt-3">
{devices.map((userDevice) => (
<UserDevice
key={userDevice.id}
Expand Down
Loading