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
*/
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" />
{/* Content */}

@ -14,7 +14,7 @@ import UserSettingsHeader from "@/components/dashboard/user/user-settings-header
* @return the page jsx
*/
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" />
{/* Content */}

@ -12,7 +12,7 @@ import DevicesSetting from "@/components/dashboard/user/settings/device/devices-
* @return the page jsx
*/
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" />
{/* Content */}

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

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

@ -29,36 +29,14 @@ import OrganizationLogo from "@/components/org/organization-logo";
* @return the selector jsx
*/
const OrganizationSelector = (): ReactElement => {
const selectedOrganization: string | undefined = useOrganizationContext(
(state: OrganizationState) => state.selected
);
const setSelectedOrganization = useOrganizationContext(
(state) => state.setSelected
);
const organizations: Organization[] = useOrganizationContext(
(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 [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.
@ -68,7 +46,6 @@ const OrganizationSelector = (): ReactElement => {
const selectOrganization = (organization: Organization) => {
setOpen(false);
setSelected(organization);
setSelectedOrganization(organization.snowflake);
localStorage.setItem("selected-organization", organization.snowflake);
};
@ -122,8 +99,7 @@ const OrganizationSelector = (): ReactElement => {
size="sm"
/>
{organization.name}
{organization.snowflake ===
selectedOrganization && (
{organization === selected && (
<CheckIcon className="absolute right-3.5 w-4 h-4" />
)}
</CommandItem>

@ -17,6 +17,7 @@ import {
import { useOrganizationContext } from "@/app/provider/organization-provider";
import { OrganizationState } from "@/app/store/organization-store";
import { usePathname } from "next/navigation";
import { Organization } from "@/app/types/org/organization";
const links: SidebarLink[] = [
{
@ -27,32 +28,32 @@ const links: SidebarLink[] = [
{
name: "Status Pages",
icon: <ClipboardIcon />,
href: "/dashboard/{org}/status-pages",
href: "/dashboard/status-pages",
},
{
name: "Automations",
icon: <WrenchIcon />,
href: "/dashboard/{org}/automations",
href: "/dashboard/automations",
},
{
name: "Incidents",
icon: <FireIcon />,
href: "/dashboard/{org}/incidents",
href: "/dashboard/incidents",
},
{
name: "Insights",
icon: <ChartBarSquareIcon />,
href: "/dashboard/{org}/insights",
href: "/dashboard/insights",
},
{
name: "Audit Logs",
icon: <PencilSquareIcon />,
href: "/dashboard/{org}/audit",
href: "/dashboard/audit",
},
{
name: "Settings",
icon: <Cog6ToothIcon />,
href: "/dashboard/{org}/settings",
href: "/dashboard/settings",
},
];
@ -63,37 +64,39 @@ const links: SidebarLink[] = [
* @return the links jsx
*/
const Links = (): ReactElement => {
const selectedOrganization: string | undefined = useOrganizationContext(
(state: OrganizationState) => state.selected
);
const selectedOrganization: Organization | undefined =
useOrganizationContext((state: OrganizationState) => state.selected);
const path: string = usePathname();
return (
<div className="mt-3.5 w-full flex flex-col gap-0.5 select-none">
{links.map((link: SidebarLink, index: number) => {
const href: string = link.href.replace(
"{org}",
selectedOrganization as string
);
const active: boolean = path.startsWith(href);
return (
<SimpleTooltip
key={index}
content={`Visit ${link.name}`}
side="right"
>
<Link
className={cn(
"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"
)}
href={href}
{links
.filter(
(link: SidebarLink, index: number) =>
index === 0 || (index > 0 && selectedOrganization)
)
.map((link: SidebarLink, index: number) => {
const active: boolean = path.startsWith(link.href);
return (
<SimpleTooltip
key={index}
content={`Visit ${link.name}`}
side="right"
>
<div className="relative w-5 h-5">{link.icon}</div>
{link.name}
</Link>
</SimpleTooltip>
);
})}
<Link
className={cn(
"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"
)}
href={link.href}
>
<div className="relative w-5 h-5">
{link.icon}
</div>
{link.name}
</Link>
</SimpleTooltip>
);
})}
</div>
);
};

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

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