Skip to content

Commit

Permalink
Add helpful components
Browse files Browse the repository at this point in the history
  • Loading branch information
tiago-bacelar committed Feb 1, 2024
1 parent 38ee4c0 commit 0b8a390
Show file tree
Hide file tree
Showing 23 changed files with 592 additions and 77 deletions.
26 changes: 26 additions & 0 deletions components/AttendeeMention/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { IAttendee, useAuth } from "@context/Auth";
import { ROLES } from "@lib/user";
import Link from "next/link";

interface Mention {
nickname: string;
name: string;
}

export default function AttendeeMention({ attendee }: { attendee: Mention }) {
const { user } = useAuth();
const isSelf =
user &&
user.type === ROLES.ATTENDEE &&
(user as IAttendee).nickname === attendee.nickname;

return (
<Link href={`/attendees/${attendee.nickname}`}>
<span
className={`font-ibold hover:underline ${isSelf ? "text-quinary" : ""}`}
>
{attendee.name}
</span>
</Link>
);
}
61 changes: 36 additions & 25 deletions components/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,50 @@ import { Dialog, Transition } from "@headlessui/react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faBars, faTimes } from "@fortawesome/free-solid-svg-icons";

import { useAuth } from "@context/Auth";
import { IStaff, IUser, useAuth } from "@context/Auth";
import { ROLES } from "@lib/user";

// FIXME: normalize user type between moonstone and safira
const basePaths = {
[ROLES.SPONSOR]: "sponsor",
[ROLES.ATTENDEE]: "attendee",
[ROLES.ADMIN]: "admin",
[ROLES.STAFF]: "staff",
};

const roleNavigations = {
[ROLES.SPONSOR]: ["scanner", "visitors"],
[ROLES.ATTENDEE]: [
"profile",
"wheel",
"badgedex",
"leaderboard",
"store",
"inventory",
"identifier",
],
[ROLES.STAFF]: ["leaderboard", "badges", "prizes", "identifier", "cv"],
[ROLES.ADMIN]: [
"leaderboard",
"badges",
"prizes",
"identifier",
"badgehistory",
"redeemhistory",
"spotlight",
],
};
const roleNavigation = (user: IUser) => {
switch (user.type) {
case ROLES.SPONSOR:
return ["scanner", "visitors"];

case ROLES.ATTENDEE:
return [
"profile",
"wheel",
"badgedex",
"leaderboard",
"store",
"inventory",
"identifier",
];

case ROLES.STAFF:
return [
"leaderboard",
"badges",
"prizes",
"identifier",
"cv",
...((user as IStaff).is_admin ? [
"badgehistory",
"redeemhistory",
"spotlight",
] : [])
];

default:
throw new Error(`Unknown USER TYPE: ${user.type}`);
}
}

type LayoutProps = {
title?: string;
Expand All @@ -54,7 +65,7 @@ export default function Layout({ title, description, children }: LayoutProps) {
const router = useRouter();

const currentHref = router.asPath;
const links = roleNavigations[user.type];
const links = roleNavigation(user);
const basePath = basePaths[user.type];

const openNavbar = () => {
Expand Down
22 changes: 8 additions & 14 deletions components/Navbar/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ const navigation = [
{ name: "FAQs", slug: "/faqs" },
];

const userNavigation = (type) => {
switch (type) {
const userNavigation = (user) => {
switch (user.type) {
case USER.ROLES.ATTENDEE:
return [{ name: "Dashboard", slug: "/attendee/profile" }];
case USER.ROLES.STAFF:
Expand All @@ -31,23 +31,17 @@ const userNavigation = (type) => {
{ name: "Give Badges", slug: "/staff/badges" },
{ name: "Give Prizes", slug: "/staff/prizes" },
{ name: "Upload CV", slug: "/staff/cv" },
...(user.is_admin ? [
{ name: "Manage Spotlight", slug: "/staff/spotlight" },
] : [])
];
case USER.ROLES.SPONSOR:
return [
{ name: "Scanner", slug: "/sponsor/scanner" },
{ name: "Visitors", slug: "/sponsor/visitors" },
];
case USER.ROLES.ADMIN:
return [
{ name: "Leaderboard", slug: "/admin/leaderboard" },
{ name: "Give Badges", slug: "/admin/badges" },
{ name: "Give Prizes", slug: "/admin/prizes" },
{ name: "History", slug: "/admin/badgehistory" },
{ name: "Activate Spotlight", slug: "/admin/spotlight" },
];

default:
throw new Error(`Unknown USER TYPE: ${type}`);
throw new Error(`Unknown USER TYPE: ${user.type}`);
}
};

Expand Down Expand Up @@ -130,7 +124,7 @@ export default function Navbar({ bgColor, fgColor, button, children }) {
>
<Menu.Items className="absolute right-0 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
{user &&
userNavigation(user.type).map((item) => (
userNavigation(user).map((item) => (
<Menu.Item key={item.name}>
<Link
href={item.slug}
Expand Down Expand Up @@ -189,7 +183,7 @@ export default function Navbar({ bgColor, fgColor, button, children }) {
))}
{isAuthenticated &&
user &&
userNavigation(user.type).map((item) => (
userNavigation(user).map((item) => (
<Disclosure.Button
key={item.slug}
as="a"
Expand Down
32 changes: 32 additions & 0 deletions components/PaginatedSearch/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ReactElement, useEffect, useState } from "react";

export default function PaginatedSearch<Elem>({
limit,
fetchElems,
showElems,
children
} : {
limit: number,
fetchElems: (query: string, offset: number, limit: number) => Promise<Elem[]> | Elem[],
showElems: (elems : Elem[]) => ReactElement
children?: ReactElement
}) {
const [ query, updateQuery ] = useState<string>("");
const [ offset, updateOffset ] = useState<number>(0);
const [ elems, updateElems ] = useState<Elem[]>([]);

useEffect(() => {
(async () => {
const fetched = await fetchElems(query, offset, limit);
updateElems(fetched);
})();
}, [query, offset]);

return (
<>
{ /*TODO: search bar and pagination*/ }
{ children }
{ showElems(elems) }
</>
);
}
61 changes: 61 additions & 0 deletions components/Table/TableColumn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { FunctionComponent, Key, ReactNode, createElement } from "react";

export enum Align {
Left,
Center,
Right
}

export function alignToCSS(align: Align): string {
switch (+align) {
case Align.Left: return "text-left justify-self-left";
case Align.Center: return "text-center justify-self-center";
case Align.Right: return "text-right justify-self-right";
}
}

export type TableColumnProps<T> = {
key?: Key;
header?: string;
headerAlign?: Align;
elemAlign?: Align;
elemPadding?: number;
colSpan?: number;
getter?: (e: T) => ReactNode;
};

type ResolvedTableColumnProps<T> = {
key: Key;
header: string;
headerAlign: Align;
elemAlign: Align;
elemPadding: number;
colSpan: number;
getter: (e: T) => ReactNode;
};

export function resolveProps<T>(props: TableColumnProps<T>): ResolvedTableColumnProps<T> {
return {
key: Math.random(),
header: "",
headerAlign: Align.Center,
elemAlign: Align.Center,
elemPadding: 0,
colSpan: 1,
getter: (x) => x as ReactNode,
...props
};
}

export function printHeader<T>(props : TableColumnProps<T>) {
return (
<div key={props.key} className={`col-span-${props.colSpan} ${alignToCSS(props.headerAlign)}`}>
{ props.header }
</div>
);
}


export default function TableColumn<T>(props : TableColumnProps<T>) {
return createElement<TableColumnProps<T>>(TableColumn<T>, props);
}
39 changes: 39 additions & 0 deletions components/Table/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Key, ReactElement } from "react";
import TableColumn, { Align, TableColumnProps, alignToCSS, printHeader, resolveProps } from "./TableColumn";

export { TableColumn, Align };

type TableProps<T> = {
children: ReactElement<TableColumnProps<T>>[];
elems: T[];
elemKey?: (elem: T) => Key;
};

export default function Table<T>({ children, elems, elemKey } : TableProps<T>) {
const cols = children.map((c) => resolveProps(c.props));
const totalColSpan = cols.reduce((sum, c) => sum + c.colSpan, 0);
const anyHasHeader = cols.reduce((or, c) => c.header || or, false);

const printElem = (elem: T, isFirst: boolean, isLast: boolean) => {
const border = isLast ? "" : "boprder-b-solid border-b-2";
const key = elemKey ? elemKey(elem) : Math.random();

return <div key={key} className={`w-full py-4 ${border} grid grid-cols-${totalColSpan} items-center`}>
{ cols.map((c) =>
<div key={c.key} className={`col-span-${c.colSpan} ${alignToCSS(c.elemAlign)} px-${c.elemPadding}`}>
{ c.getter(elem) }
</div>)
}
</div>;
}

return <>
{
anyHasHeader &&
<div className={`w-full py-4 select-none text-iregular font-iregular grid grid-cols-${totalColSpan} items-center`}>
{ cols.map(printHeader) }
</div>
}
{ elems.map((e, i) => printElem(e, i === 0, i === elems.length - 1)) }
</>;
}
16 changes: 14 additions & 2 deletions context/Auth/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,21 @@ export interface IPrize {
not_redeemed: number;
}

export interface IRedeemable {
id: number;
image: string;
name: string;
not_redeemed: number;
price: number;
quantity: number;
}

export interface IAbstractUser {
email: string;
type: string;
}

export interface IAttendee extends IAbstractUser {
export interface IPublicAttendee {
avatar: string | null;
badge_count: number;
badges: IBadge[];
Expand All @@ -44,12 +53,15 @@ export interface IAttendee extends IAbstractUser {
name: string;
nickname: string;
prizes: IPrize[];
redeemables: IPrize[];
redeemables: IRedeemable[];
token_balance: number;
}

export interface IAttendee extends IAbstractUser, IPublicAttendee {}

export interface IStaff extends IAbstractUser {
id: number;
is_admin: boolean;
}

export interface ISponsor extends IAbstractUser {
Expand Down
2 changes: 2 additions & 0 deletions context/Auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export type {
IBadge,
IPrize,
IRedeemable,
IAbstractUser,
IPublicAttendee,
IAttendee,
IStaff,
ISponsor,
Expand Down
22 changes: 5 additions & 17 deletions context/Auth/withAuth.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ export function withAuth(WrappedComponent) {
"/staff/leaderboard",
"/staff/cv",
"/attendees/[uuid]",
...(user.is_admin ? [
"/staff/badgehistory",
"/staff/redeemhistory",
"/staff/spotlight",
] : [])
].includes(router.pathname)
) {
router.replace("/404");
Expand All @@ -62,23 +67,6 @@ export function withAuth(WrappedComponent) {
return null;
}
break;
case USER.ROLES.ADMIN:
if (
![
"/admin/badges",
"/admin/prizes",
"/admin/prizes/[uuid]",
"/admin/identifier",
"/admin/leaderboard",
"/admin/badgehistory",
"/admin/redeemhistory",
"/admin/spotlight",
"/attendees/[uuid]",
].includes(router.pathname)
) {
return router.replace("/404");
}
break;
}

return <WrappedComponent {...props} />;
Expand Down
Loading

0 comments on commit 0b8a390

Please sign in to comment.