generic avatar component

This commit is contained in:
Braydon 2024-09-20 07:36:48 -04:00
parent 1501e6dea4
commit 020cd4c5ac
8 changed files with 187 additions and 112 deletions

@ -17,11 +17,12 @@ const OrganizationLayout = ({
}: Readonly<{ }: Readonly<{
children: ReactNode; children: ReactNode;
}>): ReactElement => { }>): ReactElement => {
const selectedOrganization: Organization | undefined = const organization: Organization | undefined = useOrganizationContext(
useOrganizationContext((state: OrganizationState) => state.selected); (state: OrganizationState) => state.selected
);
return ( return (
<div className="h-full"> <div className="h-full">
{selectedOrganization ? children : <NoOrganizationSelected />} {organization ? children : <NoOrganizationSelected />}
</div> </div>
); );
}; };

@ -1,9 +1,11 @@
import { ReactElement } from "react"; import { ReactElement } from "react";
import DashboardHeader from "@/components/dashboard/dashboard-header"; import DashboardHeader from "@/components/dashboard/dashboard-header";
import StatusPageList from "@/components/dashboard/org/status-page/status-page-list";
const StatusPagesPage = (): ReactElement => ( const StatusPagesPage = (): ReactElement => (
<main className="flex flex-col gap-3"> <main className="h-full flex flex-col gap-10">
<DashboardHeader title="Status Pages" /> <DashboardHeader title="Status Pages" />
<StatusPageList />
</main> </main>
); );
export default StatusPagesPage; export default StatusPagesPage;

@ -25,7 +25,7 @@ const NotFoundPage = (): ReactElement => (
<div className="flex flex-col justify-center"> <div className="flex flex-col justify-center">
<div className="flex flex-col gap-0.5 pointer-events-none"> <div className="flex flex-col gap-0.5 pointer-events-none">
<h1 className="text-3xl font-bold">Wrong Door!</h1> <h1 className="text-3xl font-bold">Wrong Door!</h1>
<p className="max-w-72 text-lg opacity-75"> <p className="max-w-64 text-lg opacity-75">
The page you were looking for could not be found. The page you were looking for could not be found.
</p> </p>
</div> </div>

@ -0,0 +1,40 @@
"use client";
import { ReactElement } from "react";
import { Button } from "@/components/ui/button";
import SimpleTooltip from "@/components/simple-tooltip";
import { Organization } from "@/app/types/org/organization";
import { useOrganizationContext } from "@/app/provider/organization-provider";
import { OrganizationState } from "@/app/store/organization-store";
import { StatusPage as StatusPageType } from "@/app/types/page/status-page";
import StatusPage from "@/components/dashboard/org/status-page/status-page";
/**
* A list of status pages for the
* currently selected {@link Organization}.
*
* @constructor
*/
const StatusPageList = (): ReactElement => {
const organization: Organization | undefined = useOrganizationContext(
(state: OrganizationState) => state.selected
);
return (
<div className="flex flex-col gap-5">
{/* Create */}
<SimpleTooltip content="Create a new status page">
<Button className="w-24" variant="outline" size="sm" disabled>
Create
</Button>
</SimpleTooltip>
{/* Status Pages */}
{organization?.statusPages.map(
(statusPage: StatusPageType, index: number) => (
<StatusPage key={index} statusPage={statusPage} />
)
)}
</div>
);
};
export default StatusPageList;

@ -0,0 +1,17 @@
import { ReactElement } from "react";
import { StatusPage as StatusPageType } from "@/app/types/page/status-page";
/**
* The status page for an organization.
*
* @param statusPage
* @constructor
*/
const StatusPage = ({
statusPage,
}: {
statusPage: StatusPageType;
}): ReactElement => (
<div className="p-3.5 w-72 border rounded-lg">sdfdssdfsfs</div>
);
export default StatusPage;

@ -0,0 +1,84 @@
import * as React from "react";
import { ReactElement } from "react";
import { cva } from "class-variance-authority";
import Image from "next/image";
import InitialsAvatar from "react-initials-avatar";
import { cn } from "@/lib/utils";
/**
* The variants of the avatar.
*/
const avatarVariants = cva("relative rounded-full", {
variants: {
size: {
sm: "w-6 h-6",
default: "w-11 h-11",
},
},
defaultVariants: {
size: "default",
},
});
/**
* The props for the avatar.
*/
export type GenericAvatarProps = {
/**
* The image URL of the
* avatar to display, if any.
*/
image?: string | undefined;
/**
* The alt text for the image.
*/
imageAlt?: string;
/**
* The backup text to extract initials
* from if the avatar image is unavailable.
*/
initialsText: string;
/**
* The size of the avatar.
*/
size?: "sm" | "default";
/**
* The optional class name to apply to the logo.
*/
className?: string;
};
/**
* An avatar for a user.
*
* @param props the avatar props
* @return the avatar jsx
*/
const GenericAvatar = ({
image,
imageAlt,
initialsText,
size,
className,
}: GenericAvatarProps): ReactElement => (
<div className={cn(avatarVariants({ size, className }))}>
{image ? (
<Image
className="rounded-full"
src={image}
alt={imageAlt ?? ""}
fill
/>
) : (
<InitialsAvatar
className="w-full h-full flex justify-center items-center bg-muted rounded-full"
name={initialsText}
/>
)}
</div>
);
export default GenericAvatar;

@ -1,25 +1,7 @@
import * as React from "react"; import * as React from "react";
import { ReactElement } from "react"; import { ReactElement } from "react";
import { cva } from "class-variance-authority";
import Image from "next/image";
import InitialsAvatar from "react-initials-avatar";
import { cn } from "@/lib/utils";
import { Organization } from "@/app/types/org/organization"; import { Organization } from "@/app/types/org/organization";
import GenericAvatar from "@/components/generic-avatar";
/**
* The variants of the logo.
*/
const logoVariants = cva("relative rounded-full", {
variants: {
size: {
sm: "w-5 h-5",
default: "w-10 h-10",
},
},
defaultVariants: {
size: "default",
},
});
/** /**
* The props for this component. * The props for this component.
@ -29,45 +11,30 @@ type OrganizationLogoProps = {
* The organization to show the logo for. * The organization to show the logo for.
*/ */
organization: Organization; organization: Organization;
} & Omit<
/** React.ComponentProps<typeof GenericAvatar>,
* The size of the logo. "image" | "imageAlt" | "initialsText"
*/ >;
size?: "sm" | "default";
/**
* The optional class name to apply to the logo.
*/
className?: string;
};
/** /**
* A logo for an organization. * The logo for an organization.
* *
* @param organization the organization * @param organization the org to show the logo for
* @param size the size * @param props additional props
* @param className additional class names * @return the logo jsx
* @return the organization jsx
*/ */
const OrganizationLogo = ({ const OrganizationLogo = ({
organization, organization,
size, ...props
className,
}: OrganizationLogoProps): ReactElement => ( }: OrganizationLogoProps): ReactElement => (
<div className={cn(logoVariants({ size, className }))}> <GenericAvatar
{organization.logo ? ( image={
<Image organization.logo &&
className="rounded-full" `${process.env.NEXT_PUBLIC_CDN_ENDPOINT}/organizations/${organization.logo}.webp`
src={`${process.env.NEXT_PUBLIC_CDN_ENDPOINT}/organizations/${organization.logo}.webp`} }
alt={`${organization.name}'s Logo`} imageAlt={`${organization.name}'s Logo`}
fill initialsText={organization.name}
/> {...props}
) : ( />
<InitialsAvatar
className="-translate-y-0.5 w-[130%] h-[130%] flex justify-center items-center bg-muted rounded-full"
name={organization.name}
/>
)}
</div>
); );
export default OrganizationLogo; export default OrganizationLogo;

@ -1,73 +1,37 @@
import * as React from "react"; import * as React from "react";
import { ReactElement } from "react"; import { ReactElement } from "react";
import { cva } from "class-variance-authority";
import Image from "next/image";
import InitialsAvatar from "react-initials-avatar";
import { cn } from "@/lib/utils";
import { User } from "@/app/types/user/user"; import { User } from "@/app/types/user/user";
import GenericAvatar from "@/components/generic-avatar";
/** /**
* The variants of the avatar. * The props for the avatar.
*/
const avatarVariants = cva("relative rounded-full", {
variants: {
size: {
sm: "w-6 h-6",
default: "w-11 h-11",
},
},
defaultVariants: {
size: "default",
},
});
/**
* The props for this component.
*/ */
type UserAvatarProps = { type UserAvatarProps = {
/** /**
* The user to show the avatar for. * The user to show the avatar for.
*/ */
user: User; user: User;
} & Omit<
/** React.ComponentProps<typeof GenericAvatar>,
* The size of the avatar. "image" | "imageAlt" | "initialsText"
*/ >;
size?: "sm" | "default";
/**
* The optional class name to apply to the logo.
*/
className?: string;
};
/** /**
* An avatar for a user. * The avatar for a user.
* *
* @param user the user * @param user the user to show the avatar for
* @param size the size * @param props additional props
* @param className additional class names
* @return the avatar jsx * @return the avatar jsx
*/ */
const UserAvatar = ({ const UserAvatar = ({ user, ...props }: UserAvatarProps): ReactElement => (
user, <GenericAvatar
size, image={
className, user.avatar &&
}: UserAvatarProps): ReactElement => ( `${process.env.NEXT_PUBLIC_CDN_ENDPOINT}/avatars/${user.avatar}.webp`
<div className={cn(avatarVariants({ size, className }))}> }
{user.avatar ? ( imageAlt={`${user.username}'s Avatar`}
<Image initialsText={user.username}
className="rounded-full" {...props}
src={`${process.env.NEXT_PUBLIC_CDN_ENDPOINT}/avatars/${user.avatar}.webp`} />
alt={`${user.username}'s Avatar`}
fill
/>
) : (
<InitialsAvatar
className="w-full h-full flex justify-center items-center bg-muted rounded-full"
name={user.username}
/>
)}
</div>
); );
export default UserAvatar; export default UserAvatar;