Skip to content

Commit

Permalink
Admin Features (#114)
Browse files Browse the repository at this point in the history
* Show Admin Link in Mobile Nav

* Create all Available Actions

* Delete admin store page

* Update src/pages/admin/milestone.tsx

Co-authored-by: Alex Zhang <[email protected]>

---------

Co-authored-by: Alex Zhang <[email protected]>
  • Loading branch information
farisashai and alexzhang1618 authored Jan 7, 2024
1 parent dd7eaae commit 26bead4
Show file tree
Hide file tree
Showing 19 changed files with 477 additions and 57 deletions.
10 changes: 8 additions & 2 deletions src/components/auth/SignInTitle/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@ import styles from './style.module.scss';

interface SignInTitleProps {
text: string;
description?: string;
}

const SignInTitle = ({ text }: SignInTitleProps) => {
return <h1 className={styles.title}>{text}</h1>;
const SignInTitle = ({ text, description }: SignInTitleProps) => {
return (
<>
<h1 className={styles.title}>{text}</h1>
{description ? <h2 className={styles.subtitle}>{description}</h2> : null}
</>
);
};

export default SignInTitle;
10 changes: 10 additions & 0 deletions src/components/auth/SignInTitle/style.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,13 @@
margin: 0.5rem 0;
text-align: center;
}

.subtitle {
align-items: center;
color: var(--theme-text-on-background-1);
font-size: 1.25rem;
font-style: normal;
font-weight: 700;
margin-bottom: 0.5rem;
text-align: center;
}
1 change: 1 addition & 0 deletions src/components/auth/SignInTitle/style.module.scss.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export type Styles = {
subtitle: string;
title: string;
};

Expand Down
29 changes: 29 additions & 0 deletions src/components/common/LinkButton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { URL } from '@/lib/types';
import Link from 'next/link';
import { PropsWithChildren } from 'react';
import style from './style.module.scss';

interface IProps {
variant?: 'primary' | 'secondary';
destructive?: boolean;
href: URL;
size?: 'default' | 'small';
}

const LinkButton = (props: PropsWithChildren<IProps>) => {
const { variant = 'primary', destructive = false, href, size = 'default', children } = props;

return (
<Link
className={style.button}
data-variant={variant}
data-destructive={destructive}
data-size={size}
href={href}
>
{children}
</Link>
);
};

export default LinkButton;
62 changes: 62 additions & 0 deletions src/components/common/LinkButton/style.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
@use '../../../styles/vars.scss' as vars;

.button {
align-items: center;
background-color: #62b0ff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25);
color: #fff;
cursor: pointer;
display: flex;
height: 2rem;
justify-content: center;
padding: 4px 1rem;
transition: 0.3s ease-in-out transform;

width: fit-content;

&[data-size='default'] {
height: 2.5rem;
}

&[data-size='small'] {
height: 2rem;
}

&[data-variant='primary'] {
background-color: #62b0ff;
border: 1px solid #fff;
color: #fff;

&[data-destructive='true'] {
background-color: #ef626c;
border: 1px solid #fff;
color: #fff;
}
}

&[data-variant='secondary'] {
background-color: #fff;
border: 1px solid #62b0ff;
color: #62b0ff;

&[data-destructive='true'] {
background-color: #fff;
border: 1px solid #ef626c;
color: #ef626c;
}
}

&:hover {
transform: scale(1.04);

&:disabled {
cursor: wait;
transform: scale(1);
}
}

&:disabled {
background-color: vars.$disabled !important;
}
}
9 changes: 9 additions & 0 deletions src/components/common/LinkButton/style.module.scss.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export type Styles = {
button: string;
};

export type ClassNames = keyof Styles;

declare const styles: Styles;

export default styles;
1 change: 1 addition & 0 deletions src/components/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export { default as Carousel } from './Carousel';
export { default as CommunityLogo } from './CommunityLogo';
export { default as Cropper } from './Cropper';
export { default as Dropdown } from './Dropdown';
export { default as LinkButton } from './LinkButton';
export { default as Modal } from './Modal';
export { default as PaginationControls } from './PaginationControls';
export { default as SEO } from './SEO';
Expand Down
10 changes: 8 additions & 2 deletions src/components/layout/Navbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const Navbar = ({ user }: NavbarProps) => {
);
}

const isAdmin = PermissionService.canViewAdminPage().includes(user.accessType);
const isAdmin = PermissionService.canViewAdminPage.includes(user.accessType);

return (
<header className={styles.header} ref={headerRef}>
Expand Down Expand Up @@ -126,11 +126,17 @@ const Navbar = ({ user }: NavbarProps) => {
<ShopIcon className={styles.iconLink} />
Store
</Link>
{isAdmin ? (
<Link className={styles.mobileNavItem} href={config.admin.homeRoute}>
<SettingsIcon color="var(--theme-text-on-background-1)" className={styles.iconLink} />
Admin Settings
</Link>
) : null}
<div>
<ThemeToggle />
</div>
<hr className={styles.wainbow} />
</div>
<hr className={styles.wainbow} />
</header>
);
};
Expand Down
15 changes: 7 additions & 8 deletions src/components/layout/Navbar/style.module.scss
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
@use 'src/styles/vars.scss' as vars;

$wainbow-height: 20rem;

.header {
background-color: var(--theme-background);
height: vars.$header-height;
Expand Down Expand Up @@ -119,19 +117,19 @@ $wainbow-height: 20rem;
font-size: 20px;
font-weight: 700;
gap: 0.25rem;
height: $wainbow-height;
height: fit-content;
justify-content: center;
line-height: 300%;
margin-top: #{$wainbow-height * -1};

padding: 0 1rem 1rem;
padding: 0 1rem;
position: relative;
transition: margin-top 0.3s cubic-bezier(0, 0.4, 0.1, 1), background-color 0.3s ease;
transform: translateY(calc(-100% + 0.25rem));
transition: transform 0.3s cubic-bezier(0, 0.4, 0.1, 1), background-color 0.3s ease;
width: 100vw;
z-index: -1;

&[data-open='true'] {
margin-top: 0;
transform: translateY(0%);
}

.mobileNavItem {
Expand All @@ -158,7 +156,8 @@ $wainbow-height: 20rem;
.wainbow {
background: vars.$wainbow;
height: 0.25rem;
width: 100vw;
margin: 0 -1rem;
width: calc(100% + 2rem);
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ const config = {
},
admin: {
homeRoute: '/admin',
awardPoints: '/admin/points',
grantPastAttendance: '/admin/attendance',
awardMilestone: '/admin/milestone',
viewResumes: '/admin/resumes',
store: {
items: '/admin/store/items',
pickupEvents: '/admin/store/pickupEvents',
homeRoute: '/admin/store',
},
events: {
homeRoute: '/admin/event',
editRoute: '/admin/event/edit',
Expand Down
32 changes: 14 additions & 18 deletions src/lib/services/PermissionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,26 @@ import { UserAccessType } from '@/lib/types/enums';
/**
* Wrapper class to manage permissions by helping setting restrictions and validating permissions across the application
*/
export const canEditMerchItems = (): UserAccessType[] => {
return [UserAccessType.ADMIN, UserAccessType.MERCH_STORE_MANAGER];
};
export const canEditMerchItems = [UserAccessType.ADMIN, UserAccessType.MERCH_STORE_MANAGER];

export const canManageEvents = (): UserAccessType[] => {
return [UserAccessType.ADMIN, UserAccessType.MARKETING];
};
export const canManageEvents = [UserAccessType.ADMIN, UserAccessType.MARKETING];

export const canViewAdminPage = (): UserAccessType[] => {
return [
UserAccessType.ADMIN,
UserAccessType.MARKETING,
UserAccessType.MERCH_STORE_MANAGER,
UserAccessType.MERCH_STORE_DISTRIBUTOR,
];
};
export const canAwardPoints = [UserAccessType.ADMIN];

// will add sponsorship role here soon
export const canViewResumes = [UserAccessType.ADMIN];

export const canViewAdminPage = [
UserAccessType.ADMIN,
UserAccessType.MARKETING,
UserAccessType.MERCH_STORE_MANAGER,
UserAccessType.MERCH_STORE_DISTRIBUTOR,
];

/**
* @returns Array of all possible user access types
*/
export const allUserTypes = (): UserAccessType[] => {
const values = Object.values(UserAccessType) as UserAccessType[];
return values;
};
export const allUserTypes = () => Object.values(UserAccessType) as UserAccessType[];

/**
* @param types to exclude from array
Expand Down
89 changes: 89 additions & 0 deletions src/pages/admin/attendance.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { SignInButton, SignInFormItem, SignInTitle } from '@/components/auth';
import { VerticalForm } from '@/components/common';
import { config } from '@/lib';
import withAccessType from '@/lib/hoc/withAccessType';
import { PermissionService, ValidationService } from '@/lib/services';
import type { GetServerSideProps, NextPage } from 'next';
import { SubmitHandler, useForm } from 'react-hook-form';
import { AiOutlineMail } from 'react-icons/ai';
import { VscLock } from 'react-icons/vsc';

interface FormValues {
email: string;
description: string;
points: number;
}
const AwardPointsPage: NextPage = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormValues>();

const onSubmit: SubmitHandler<FormValues> = () => {
// TODO
};

return (
<VerticalForm onEnterPress={handleSubmit(onSubmit)}>
<SignInTitle
text="Retroactive Attendance"
description="Mark members as attended for past events"
/>
<SignInFormItem
icon={<AiOutlineMail />}
element="input"
name="email"
type="email"
placeholder="User Email ([email protected])"
formRegister={register('email', {
validate: email => {
const validation = ValidationService.isValidEmail(email);
return validation.valid || validation.error;
},
})}
error={errors.email}
/>
<SignInFormItem
icon={<AiOutlineMail />}
element="input"
name="description"
type="text"
placeholder="Description"
formRegister={register('description', {
required: 'Required',
})}
error={errors.description}
/>
<SignInFormItem
icon={<VscLock />}
name="points"
element="input"
type="number"
placeholder="Point Value"
formRegister={register('points', {
required: 'Required',
})}
error={errors.points}
/>
<SignInButton
type="button"
display="button1"
text="Award Points"
onClick={handleSubmit(onSubmit)}
/>
</VerticalForm>
);
};

export default AwardPointsPage;

const getServerSidePropsFunc: GetServerSideProps = async () => ({
props: {},
});

export const getServerSideProps = withAccessType(
getServerSidePropsFunc,
PermissionService.canAwardPoints,
config.admin.homeRoute
);
2 changes: 1 addition & 1 deletion src/pages/admin/event/create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ const getServerSidePropsFunc: GetServerSideProps = async ({ req, res, query }) =

export const getServerSideProps = withAccessType(
getServerSidePropsFunc,
PermissionService.canManageEvents(),
PermissionService.canManageEvents,
config.admin.homeRoute
);
2 changes: 1 addition & 1 deletion src/pages/admin/event/edit/[uuid].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,6 @@ const getServerSidePropsFunc: GetServerSideProps = async ({ params, req, res })

export const getServerSideProps = withAccessType(
getServerSidePropsFunc,
PermissionService.canManageEvents(),
PermissionService.canManageEvents,
config.admin.homeRoute
);
2 changes: 1 addition & 1 deletion src/pages/admin/event/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,6 @@ const getServerSidePropsFunc: GetServerSideProps = async () => {

export const getServerSideProps = withAccessType(
getServerSidePropsFunc,
PermissionService.canManageEvents(),
PermissionService.canManageEvents,
config.admin.homeRoute
);
Loading

1 comment on commit 26bead4

@vercel
Copy link

@vercel vercel bot commented on 26bead4 Jan 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.