diff --git a/.eslintrc.json b/.eslintrc.json
index 9b07d282..74ecd12f 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -38,5 +38,10 @@
}
]
},
- "ignorePatterns": "src/**/*.d.ts"
+ "ignorePatterns": [
+ "src/**/*.d.ts",
+ "**/public/sw.js",
+ "**/public/workbox-*.js",
+ "**/public/worker-*.js"
+ ]
}
diff --git a/.gitignore b/.gitignore
index b947a678..885edd4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,3 +36,11 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
+
+# PWA files
+**/public/sw.js
+**/public/workbox-*.js
+**/public/worker-*.js
+**/public/sw.js.map
+**/public/workbox-*.js.map
+**/public/worker-*.js.map
\ No newline at end of file
diff --git a/next.config.js b/next.config.js
index 704d5d37..bb725fef 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,6 +1,13 @@
const env = process.env.NODE_ENV;
const isDevelopment = env !== 'production';
+const withPWA = require('next-pwa')({
+ dest: 'public',
+ register: true,
+ skipWaiting: true,
+ disable: isDevelopment,
+});
+
/** @type {import('next').NextConfig} */
const nextConfig = {
eslint: {
@@ -37,4 +44,4 @@ const nextConfig = {
},
};
-module.exports = nextConfig;
+module.exports = withPWA(nextConfig);
diff --git a/package.json b/package.json
index d3480b4e..1247b198 100644
--- a/package.json
+++ b/package.json
@@ -36,6 +36,7 @@
"ics": "^3.7.2",
"luxon": "^3.3.0",
"next": "^13.2.5-canary.30",
+ "next-pwa": "^5.6.0",
"next-themes": "^0.2.1",
"react": "18.2.0",
"react-dom": "18.2.0",
diff --git a/public/icon-192x192.png b/public/icon-192x192.png
new file mode 100644
index 00000000..749be3be
Binary files /dev/null and b/public/icon-192x192.png differ
diff --git a/public/icon-256x256.png b/public/icon-256x256.png
new file mode 100644
index 00000000..fe18f759
Binary files /dev/null and b/public/icon-256x256.png differ
diff --git a/public/icon-384x384.png b/public/icon-384x384.png
new file mode 100644
index 00000000..f7ff182a
Binary files /dev/null and b/public/icon-384x384.png differ
diff --git a/public/icon-512x512.png b/public/icon-512x512.png
new file mode 100644
index 00000000..1c7ad55d
Binary files /dev/null and b/public/icon-512x512.png differ
diff --git a/public/manifest.json b/public/manifest.json
index 95e7adcc..3566a7f2 100644
--- a/public/manifest.json
+++ b/public/manifest.json
@@ -1,15 +1,32 @@
{
- "short_name": "ACM Membership Portal",
- "name": "Association for Computing Machinery at UC San Diego Student Membership Portal",
+ "theme_color": "#ffffff",
+ "background_color": "#ffffff",
+ "display": "standalone",
+ "scope": "/",
+ "start_url": "/",
+ "name": "ACM at UCSD Membership Portal",
+ "short_name": "ACM Portal",
+ "description": "Association for Computing Machinery at UC San Diego Student Membership Portal",
"icons": [
{
- "src": "favicon.ico",
- "sizes": "64x64 32x32 16x16",
- "type": "image/x-icon"
+ "src": "/icon-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/icon-256x256.png",
+ "sizes": "256x256",
+ "type": "image/png"
+ },
+ {
+ "src": "/icon-384x384.png",
+ "sizes": "384x384",
+ "type": "image/png"
+ },
+ {
+ "src": "/icon-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
}
- ],
- "start_url": ".",
- "display": "standalone",
- "theme_color": "#000000",
- "background_color": "#ffffff"
+ ]
}
diff --git a/src/components/common/Carousel/index.tsx b/src/components/common/Carousel/index.tsx
index 92a4c3a8..600a4d2c 100644
--- a/src/components/common/Carousel/index.tsx
+++ b/src/components/common/Carousel/index.tsx
@@ -1,11 +1,7 @@
-import type { ReactNode } from 'react';
+import type { PropsWithChildren } from 'react';
import styles from './style.module.scss';
-interface CarouselProps {
- children: ReactNode[];
-}
-
-const Carousel = ({ children }: CarouselProps) => {
+const Carousel = ({ children }: PropsWithChildren) => {
return
{children}
;
};
diff --git a/src/components/common/CommunityLogo/index.tsx b/src/components/common/CommunityLogo/index.tsx
index 7338220c..205ca002 100644
--- a/src/components/common/CommunityLogo/index.tsx
+++ b/src/components/common/CommunityLogo/index.tsx
@@ -1,29 +1,22 @@
+import { communityLogos } from '@/lib/constants/communityLogos';
+import { Community } from '@/lib/types/enums';
+import { capitalize } from '@/lib/utils';
import Image from 'next/image';
-import AILogo from '@/public/assets/acm-logos/communities/ai.png';
-import CyberLogo from '@/public/assets/acm-logos/communities/cyber.png';
-import DesignLogo from '@/public/assets/acm-logos/communities/design.png';
-import HackLogo from '@/public/assets/acm-logos/communities/hack.png';
-import ACMLogo from '@/public/assets/acm-logos/general/light-mode.png';
-
interface CommunityLogoProps {
community: string;
size: number;
}
const CommunityLogo = ({ community, size }: CommunityLogoProps) => {
- switch (community.toLowerCase()) {
- case 'hack':
- return ;
- case 'ai':
- return ;
- case 'cyber':
- return ;
- case 'design':
- return ;
- default:
- return ;
- }
+ const formattedName = capitalize(community) as Community;
+
+ if (!Object.values(Community).includes(formattedName))
+ return ;
+
+ return (
+
+ );
};
export default CommunityLogo;
diff --git a/src/components/common/Dropdown/index.tsx b/src/components/common/Dropdown/index.tsx
index fbe4d317..68703e65 100644
--- a/src/components/common/Dropdown/index.tsx
+++ b/src/components/common/Dropdown/index.tsx
@@ -7,10 +7,12 @@ interface Option {
label: string;
}
+export const DIVIDER = '----';
+
interface DropdownProps {
name: string;
ariaLabel: string;
- options: (Option | '---')[];
+ options: (Option | typeof DIVIDER)[];
value: string;
onChange: (value: string) => void;
}
@@ -30,7 +32,7 @@ const Dropdown = ({ name, ariaLabel, options, value, onChange }: DropdownProps)
const optionButtons: ReactNode[] = [];
let dividers = 0;
options.forEach(option => {
- if (option === '---') {
+ if (option === DIVIDER) {
optionButtons.push(
);
dividers += 1;
} else {
@@ -78,7 +80,7 @@ const Dropdown = ({ name, ariaLabel, options, value, onChange }: DropdownProps)
aria-label={ariaLabel}
>
{options.map(option =>
- option !== '---' ? (
+ option !== DIVIDER ? (
diff --git a/src/components/events/EventDisplay/style.module.scss b/src/components/events/EventDisplay/style.module.scss
index 641eb607..50cadbe5 100644
--- a/src/components/events/EventDisplay/style.module.scss
+++ b/src/components/events/EventDisplay/style.module.scss
@@ -1,5 +1,7 @@
.container {
- display: flex;
- flex-wrap: wrap;
- gap: 1rem;
+ column-gap: 1rem;
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
+ place-items: center;
+ row-gap: 2rem;
}
diff --git a/src/components/home/Hero/style.module.scss b/src/components/home/Hero/style.module.scss
index 77973d2c..974f55a3 100644
--- a/src/components/home/Hero/style.module.scss
+++ b/src/components/home/Hero/style.module.scss
@@ -132,6 +132,7 @@
}
.image {
+ margin: 0 auto;
max-height: 1080px;
max-width: 1920px;
object-fit: cover;
diff --git a/src/components/layout/Navbar/index.tsx b/src/components/layout/Navbar/index.tsx
index e073b796..10bb55f3 100644
--- a/src/components/layout/Navbar/index.tsx
+++ b/src/components/layout/Navbar/index.tsx
@@ -2,7 +2,7 @@ import ThemeToggle from '@/components/common/ThemeToggle';
import { config } from '@/lib';
import { useWindowSize } from '@/lib/hooks/useWindowSize';
import { PermissionService } from '@/lib/services';
-import type { PrivateProfile } from '@/lib/types/apiResponses';
+import { UserAccessType } from '@/lib/types/enums';
import LightModeLogo from '@/public/assets/acm-logos/general/light-mode.png';
import CalendarIcon from '@/public/assets/icons/calendar-icon.svg';
import HomeIcon from '@/public/assets/icons/home-icon.svg';
@@ -16,9 +16,9 @@ import { memo, useCallback, useEffect, useRef, useState } from 'react';
import styles from './style.module.scss';
interface NavbarProps {
- user?: PrivateProfile;
+ accessType?: UserAccessType;
}
-const Navbar = ({ user }: NavbarProps) => {
+const Navbar = ({ accessType }: NavbarProps) => {
const size = useWindowSize();
const headerRef = useRef(null);
@@ -47,7 +47,7 @@ const Navbar = ({ user }: NavbarProps) => {
if (!isMobile) setMenuOpen(false);
}, [isMobile]);
- if (!user) {
+ if (!accessType) {
return (