Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Admin Features #114

Merged
merged 5 commits into from
Jan 7, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
farisashai marked this conversation as resolved.
Show resolved Hide resolved
}
}
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 @@ -2,6 +2,7 @@ export { default as Button } from './Button';
export { default as Carousel } from './Carousel';
export { default as CommunityLogo } from './CommunityLogo';
export { default as Dropdown } from './Dropdown';
export { default as LinkButton } from './LinkButton';
export { default as PaginationControls } from './PaginationControls';
export { default as SEO } from './SEO';
export { default as Typography } from './Typography';
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 @@ -81,6 +81,15 @@ const config = {
itemRoute: '/store/item/',
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
Loading