misc changes

This commit is contained in:
Braydon 2024-09-20 04:44:00 -04:00
parent 71079fb9a6
commit 4d66133171
11 changed files with 131 additions and 103 deletions

@ -10,7 +10,7 @@ import UserSettingsHeader from "@/components/dashboard/user/user-settings-header
* @return the page jsx * @return the page jsx
*/ */
const UserBillingPage = (): ReactElement => ( const UserBillingPage = (): ReactElement => (
<main className="w-[47rem] p-10 flex flex-col gap-5"> <main className="w-[47rem] px-10 py-7 flex flex-col gap-5">
<UserSettingsHeader title="Billing" /> <UserSettingsHeader title="Billing" />
{/* Content */} {/* Content */}

@ -14,7 +14,7 @@ import UserSettingsHeader from "@/components/dashboard/user/user-settings-header
* @return the page jsx * @return the page jsx
*/ */
const UserProfilePage = (): ReactElement => ( const UserProfilePage = (): ReactElement => (
<main className="w-[47rem] p-10 flex flex-col gap-5"> <main className="w-[47rem] px-10 py-7 flex flex-col gap-5">
<UserSettingsHeader title="My Profile" /> <UserSettingsHeader title="My Profile" />
{/* Content */} {/* Content */}

@ -12,7 +12,7 @@ import DevicesSetting from "@/components/dashboard/user/settings/device/devices-
* @return the page jsx * @return the page jsx
*/ */
const UserSettingsPage = (): ReactElement => ( const UserSettingsPage = (): ReactElement => (
<main className="w-[47rem] p-10 flex flex-col gap-5"> <main className="w-[47rem] px-10 py-7 flex flex-col gap-5">
<UserSettingsHeader title="Settings" /> <UserSettingsHeader title="Settings" />
{/* Content */} {/* Content */}

@ -37,20 +37,24 @@ const OrganizationProvider = ({ children }: { children: ReactNode }) => {
* Fetch the organizations for the logged in user. * Fetch the organizations for the logged in user.
*/ */
const fetchOrganizations = useCallback(async () => { const fetchOrganizations = useCallback(async () => {
let selectedOrganization: string | null = localStorage.getItem(
"selected-organization"
);
const { data, error } = await apiRequest<Organization[]>({ const { data, error } = await apiRequest<Organization[]>({
endpoint: "/organization/@me", endpoint: "/organization/@me",
session, session,
}); });
const selectedOrgSnowflake: string | null = localStorage.getItem(
"selected-organization"
);
const organizations: Organization[] = data as Organization[]; const organizations: Organization[] = data as Organization[];
if (!selectedOrganization && organizations.length > 0) { let selected: Organization | undefined;
selectedOrganization = organizations[0].snowflake; if (!selected && organizations.length > 0) {
selected = organizations[0];
} else {
selected = organizations.find(
(organization: Organization) =>
organization.snowflake === selectedOrgSnowflake
);
} }
storeRef.current storeRef.current?.getState().update(organizations, selected);
?.getState()
.update(selectedOrganization || undefined, organizations);
}, [session]); }, [session]);
useEffect(() => { useEffect(() => {

@ -13,15 +13,15 @@ export const OrganizationContext = createContext<OrganizationStore | null>(
* The props in this store. * The props in this store.
*/ */
export type OrganizationStoreProps = { export type OrganizationStoreProps = {
/**
* The currently selected organization.
*/
selected: string | undefined;
/** /**
* The organization's the user has. * The organization's the user has.
*/ */
organizations: Organization[]; organizations: Organization[];
/**
* The currently selected organization.
*/
selected: Organization | undefined;
}; };
/** /**
@ -31,12 +31,12 @@ export type OrganizationState = OrganizationStoreProps & {
/** /**
* Update the state. * Update the state.
* *
* @param selected the selected organization
* @param organizations the user's organizations * @param organizations the user's organizations
* @param selected the selected organization
*/ */
update: ( update: (
selected: string | undefined, organizations: Organization[],
organizations: Organization[] selected: Organization | undefined
) => void; ) => void;
/** /**
@ -44,7 +44,7 @@ export type OrganizationState = OrganizationStoreProps & {
* *
* @param selected the selected organization * @param selected the selected organization
*/ */
setSelected: (selected: string | undefined) => void; setSelected: (selected: Organization | undefined) => void;
}; };
/** /**
@ -62,9 +62,11 @@ const createOrganizationStore = () => {
}; };
return createStore<OrganizationState>()((set) => ({ return createStore<OrganizationState>()((set) => ({
...defaultProps, ...defaultProps,
update: (selected: string | undefined, organizations: Organization[]) => update: (
set(() => ({ selected, organizations })), organizations: Organization[],
setSelected: (selected: string | undefined) => selected: Organization | undefined
) => set(() => ({ organizations, selected })),
setSelected: (selected: Organization | undefined) =>
set(() => ({ selected })), set(() => ({ selected })),
})); }));
}; };

@ -29,36 +29,14 @@ import OrganizationLogo from "@/components/org/organization-logo";
* @return the selector jsx * @return the selector jsx
*/ */
const OrganizationSelector = (): ReactElement => { const OrganizationSelector = (): ReactElement => {
const selectedOrganization: string | undefined = useOrganizationContext(
(state: OrganizationState) => state.selected
);
const setSelectedOrganization = useOrganizationContext(
(state) => state.setSelected
);
const organizations: Organization[] = useOrganizationContext( const organizations: Organization[] = useOrganizationContext(
(state: OrganizationState) => state.organizations (state: OrganizationState) => state.organizations
); );
const selected: Organization | undefined = useOrganizationContext(
(state: OrganizationState) => state.selected
);
const setSelected = useOrganizationContext((state) => state.setSelected);
const [open, setOpen] = useState<boolean>(false); const [open, setOpen] = useState<boolean>(false);
const [selected, setSelected] = useState<Organization | undefined>();
// Set the selected organization
useEffect(() => {
const toSelect: Organization | undefined = organizations?.find(
(organization: Organization) => {
return organization.snowflake === selectedOrganization;
}
);
// Update the state for this page
setSelected(
toSelect ||
(organizations?.length > 0 ? organizations[0] : undefined)
);
// Update the state for all pages
if (!toSelect && organizations?.length > 0) {
setSelectedOrganization(organizations[0].snowflake);
}
}, [organizations, selectedOrganization, setSelectedOrganization]);
/** /**
* Handle selecting an organization. * Handle selecting an organization.
@ -68,7 +46,6 @@ const OrganizationSelector = (): ReactElement => {
const selectOrganization = (organization: Organization) => { const selectOrganization = (organization: Organization) => {
setOpen(false); setOpen(false);
setSelected(organization); setSelected(organization);
setSelectedOrganization(organization.snowflake);
localStorage.setItem("selected-organization", organization.snowflake); localStorage.setItem("selected-organization", organization.snowflake);
}; };
@ -122,8 +99,7 @@ const OrganizationSelector = (): ReactElement => {
size="sm" size="sm"
/> />
{organization.name} {organization.name}
{organization.snowflake === {organization === selected && (
selectedOrganization && (
<CheckIcon className="absolute right-3.5 w-4 h-4" /> <CheckIcon className="absolute right-3.5 w-4 h-4" />
)} )}
</CommandItem> </CommandItem>

@ -17,6 +17,7 @@ import {
import { useOrganizationContext } from "@/app/provider/organization-provider"; import { useOrganizationContext } from "@/app/provider/organization-provider";
import { OrganizationState } from "@/app/store/organization-store"; import { OrganizationState } from "@/app/store/organization-store";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { Organization } from "@/app/types/org/organization";
const links: SidebarLink[] = [ const links: SidebarLink[] = [
{ {
@ -27,32 +28,32 @@ const links: SidebarLink[] = [
{ {
name: "Status Pages", name: "Status Pages",
icon: <ClipboardIcon />, icon: <ClipboardIcon />,
href: "/dashboard/{org}/status-pages", href: "/dashboard/status-pages",
}, },
{ {
name: "Automations", name: "Automations",
icon: <WrenchIcon />, icon: <WrenchIcon />,
href: "/dashboard/{org}/automations", href: "/dashboard/automations",
}, },
{ {
name: "Incidents", name: "Incidents",
icon: <FireIcon />, icon: <FireIcon />,
href: "/dashboard/{org}/incidents", href: "/dashboard/incidents",
}, },
{ {
name: "Insights", name: "Insights",
icon: <ChartBarSquareIcon />, icon: <ChartBarSquareIcon />,
href: "/dashboard/{org}/insights", href: "/dashboard/insights",
}, },
{ {
name: "Audit Logs", name: "Audit Logs",
icon: <PencilSquareIcon />, icon: <PencilSquareIcon />,
href: "/dashboard/{org}/audit", href: "/dashboard/audit",
}, },
{ {
name: "Settings", name: "Settings",
icon: <Cog6ToothIcon />, icon: <Cog6ToothIcon />,
href: "/dashboard/{org}/settings", href: "/dashboard/settings",
}, },
]; ];
@ -63,18 +64,18 @@ const links: SidebarLink[] = [
* @return the links jsx * @return the links jsx
*/ */
const Links = (): ReactElement => { const Links = (): ReactElement => {
const selectedOrganization: string | undefined = useOrganizationContext( const selectedOrganization: Organization | undefined =
(state: OrganizationState) => state.selected useOrganizationContext((state: OrganizationState) => state.selected);
);
const path: string = usePathname(); const path: string = usePathname();
return ( return (
<div className="mt-3.5 w-full flex flex-col gap-0.5 select-none"> <div className="mt-3.5 w-full flex flex-col gap-0.5 select-none">
{links.map((link: SidebarLink, index: number) => { {links
const href: string = link.href.replace( .filter(
"{org}", (link: SidebarLink, index: number) =>
selectedOrganization as string index === 0 || (index > 0 && selectedOrganization)
); )
const active: boolean = path.startsWith(href); .map((link: SidebarLink, index: number) => {
const active: boolean = path.startsWith(link.href);
return ( return (
<SimpleTooltip <SimpleTooltip
key={index} key={index}
@ -86,9 +87,11 @@ const Links = (): ReactElement => {
"px-3 py-2 flex gap-2 items-center text-sm rounded-lg hover:bg-zinc-800 transition-all transform-gpu", "px-3 py-2 flex gap-2 items-center text-sm rounded-lg hover:bg-zinc-800 transition-all transform-gpu",
active && "font-medium bg-zinc-800" active && "font-medium bg-zinc-800"
)} )}
href={href} href={link.href}
> >
<div className="relative w-5 h-5">{link.icon}</div> <div className="relative w-5 h-5">
{link.icon}
</div>
{link.name} {link.name}
</Link> </Link>
</SimpleTooltip> </SimpleTooltip>

@ -34,7 +34,7 @@ const Device = ({
setTimeSinceFirstLogin( setTimeSinceFirstLogin(
DateTime.fromISO(device.firstLogin.toString()).toRelative() DateTime.fromISO(device.firstLogin.toString()).toRelative()
); );
}, 60000); }, 1000);
return () => clearInterval(interval); return () => clearInterval(interval);
}, [device.firstLogin]); }, [device.firstLogin]);

@ -9,6 +9,7 @@ import { Device as DeviceType } from "@/app/types/user/device";
import { apiRequest } from "@/lib/api"; import { apiRequest } from "@/lib/api";
import Device from "@/components/dashboard/user/settings/device/device"; import Device from "@/components/dashboard/user/settings/device/device";
import { DateTime } from "luxon"; import { DateTime } from "luxon";
import DevicesSkeleton from "@/components/dashboard/user/settings/device/devices-skeleton";
/** /**
* The setting that allows a * The setting that allows a
@ -52,23 +53,34 @@ const DevicesSetting = (): ReactElement => {
{/* Setting */} {/* Setting */}
<div className="w-[27.7rem] flex flex-col gap-2"> <div className="w-[27.7rem] flex flex-col gap-2">
{devices {!devices ? (
?.sort( <>
{Array.from({ length: 4 }, (_, index) => (
<DevicesSkeleton key={index} index={index} />
))}
</>
) : (
devices
.sort(
(a: DeviceType, b: DeviceType) => (a: DeviceType, b: DeviceType) =>
DateTime.fromISO( DateTime.fromISO(
b.firstLogin.toString() b.firstLogin.toString()
).toMillis() - ).toMillis() -
DateTime.fromISO(a.firstLogin.toString()).toMillis() DateTime.fromISO(
a.firstLogin.toString()
).toMillis()
) )
.map((device: DeviceType, index: number) => ( .map((device: DeviceType, index: number) => (
<Device <Device
key={index} key={index}
device={device} device={device}
current={ current={
session?.snowflake === device.sessionSnowflake session?.snowflake ===
device.sessionSnowflake
} }
/> />
))} ))
)}
</div> </div>
</div> </div>
); );

@ -0,0 +1,16 @@
import { ReactElement } from "react";
import { Skeleton } from "@/components/ui/skeleton";
/**
* The skeleton to indicate the
* loading of the user's devices.
*
* @param index the skeleton index
* @return the skeleton jsx
*/
const DevicesSkeleton = ({ index }: { index: number }): ReactElement => (
<div style={{ opacity: 0.5 - 0.14 * index }}>
<Skeleton className="h-[4rem] rounded-lg" />
</div>
);
export default DevicesSkeleton;

@ -0,0 +1,15 @@
import { cn } from "@/lib/utils";
function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-zinc-300/10", className)}
{...props}
/>
);
}
export { Skeleton };