Skip to content

Commit

Permalink
it all started the day i was born
Browse files Browse the repository at this point in the history
  • Loading branch information
alexzhang1618 committed Jan 20, 2024
1 parent ff6cd3f commit fbac2bc
Show file tree
Hide file tree
Showing 102 changed files with 3,776 additions and 567 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
8 changes: 8 additions & 0 deletions src/components/common/Carousel/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { PropsWithChildren } from 'react';
import styles from './style.module.scss';

const Carousel = ({ children }: PropsWithChildren) => {
return <div className={styles.slider}>{children}</div>;
};

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

.slider {
display: flex;
gap: 2rem;
margin: 0 -1rem;
overflow-x: auto;
padding: 1rem;
scrollbar-color: var(--theme-primary-2) var(--theme-primary-1);

&::-webkit-scrollbar {
background-color: var(--theme-primary-1);
border: 1rem solid var(--theme-background);
border-radius: 3rem;
height: 2.75rem;
}

&::-webkit-scrollbar-thumb {
background-color: var(--theme-primary-2);
border: 1rem solid var(--theme-background);
border-radius: 3rem;
}
}
9 changes: 9 additions & 0 deletions src/components/common/Carousel/style.module.scss.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export type Styles = {
slider: string;
};

export type ClassNames = keyof Styles;

declare const styles: Styles;

export default styles;
29 changes: 11 additions & 18 deletions src/components/common/CommunityLogo/index.tsx
Original file line number Diff line number Diff line change
@@ -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 <Image src={HackLogo} width={size} alt="ACM Hack Logo" />;
case 'ai':
return <Image src={AILogo} width={size} alt="ACM AI Logo" />;
case 'cyber':
return <Image src={CyberLogo} width={size} alt="ACM Cyber Logo" />;
case 'design':
return <Image src={DesignLogo} width={size} alt="ACM Design Logo" />;
default:
return <Image src={ACMLogo} width={size} alt="ACM General Logo" />;
}
const formattedName = capitalize(community) as Community;

if (!Object.values(Community).includes(formattedName))
return <Image src={communityLogos.General} width={size} alt="ACM General Logo" />;

return (
<Image src={communityLogos[formattedName]} width={size} alt={`ACM ${formattedName} Logo`} />
);
};

export default CommunityLogo;
197 changes: 197 additions & 0 deletions src/components/common/Cropper/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import Modal from '@/components/common/Modal';
import { useObjectUrl } from '@/lib/utils';
import Image from 'next/image';
import { PointerEvent, useCallback, useRef, useState } from 'react';
import styles from './style.module.scss';

/** Height of the preview square. */
const HEIGHT = 200;

/** Promisified version of `HTMLCanvasElement.toBlob` */
function toBlob(canvas: HTMLCanvasElement, type?: string, quality?: number): Promise<Blob | null> {
return new Promise(resolve => {
canvas.toBlob(resolve, type, quality);
});
}

type DragState = {
pointerId: number;
offsetX: number;
offsetY: number;
initLeft: number;
initTop: number;
};

interface CropperProps {
file: Blob | null;
aspectRatio: number;
circle?: boolean;
maxFileHeight: number;
maxSize?: number;
onCrop: (file: Blob) => void;
onClose: (reason: 'invalid-image' | 'cannot-compress' | null) => void;
}

const Cropper = ({
file,
aspectRatio,
circle,
maxFileHeight,
maxSize = Infinity,
onCrop,
onClose,
}: CropperProps) => {
const WIDTH = HEIGHT * aspectRatio;

const url = useObjectUrl(file);
const image = useRef<HTMLImageElement>(null);
const [loaded, setLoaded] = useState(file);
const [left, setLeft] = useState(0);
const [top, setTop] = useState(0);
const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0);
const [scale, setScale] = useState(1);
const dragState = useRef<DragState | null>(null);

const handlePointerEnd = (e: PointerEvent<HTMLElement>) => {
if (dragState.current?.pointerId === e.pointerId) {
dragState.current = null;
}
};
// onError callback needs to be memoized lest next/image reload the image
const handleImageError = useCallback(() => {
if (file !== null) {
onClose('invalid-image');
}
}, [file, onClose]);

return (
<Modal title="Edit image" open={loaded === file && file !== null} onClose={() => onClose(null)}>
<div
className={styles.cropWrapper}
onPointerDown={e => {
if (!dragState.current) {
dragState.current = {
pointerId: e.pointerId,
offsetX: e.clientX,
offsetY: e.clientY,
initLeft: left,
initTop: top,
};
e.currentTarget.setPointerCapture(e.pointerId);
}
}}
onPointerMove={e => {
const state = dragState.current;
if (state?.pointerId === e.pointerId) {
const left = state.initLeft + e.clientX - state.offsetX;
const top = state.initTop + e.clientY - state.offsetY;
setLeft(Math.max(Math.min(left, 0), WIDTH - width * scale));
setTop(Math.max(Math.min(top, 0), HEIGHT - height * scale));
}
}}
onPointerUp={handlePointerEnd}
onPointerCancel={handlePointerEnd}
>
{url && (
<Image
src={url}
alt="Selected file"
className={styles.image}
style={{ transform: `translate(${left}px, ${top}px)` }}
width={width * scale}
height={height * scale}
onLoad={e => {
if (e.currentTarget.naturalHeight > 0) {
const ratio = e.currentTarget.naturalWidth / e.currentTarget.naturalHeight;
const height = Math.max(HEIGHT, WIDTH / ratio);
const width = height * ratio;
setWidth(width);
setHeight(height);
// Default to centering the image
setLeft((WIDTH - width) / 2);
setTop((HEIGHT - height) / 2);
setScale(1);
setLoaded(file);
}
}}
onError={handleImageError}
draggable={false}
ref={image}
/>
)}
<div
className={`${styles.frame} ${circle ? styles.circle : ''}`}
style={{ aspectRatio: `${aspectRatio}` }}
/>
</div>
<div className={styles.controls}>
<label className={styles.zoomWrapper}>
Zoom:&nbsp;
<input
type="range"
className={styles.zoom}
min={1}
max={2}
step="any"
value={scale}
onChange={e => {
const newScale = +e.currentTarget.value;
const newLeft = WIDTH / 2 - ((WIDTH / 2 - left) / scale) * newScale;
const newTop = HEIGHT / 2 - ((HEIGHT / 2 - top) / scale) * newScale;
setScale(newScale);
setLeft(Math.max(Math.min(newLeft, 0), WIDTH - width * newScale));
setTop(Math.max(Math.min(newTop, 0), HEIGHT - height * newScale));
}}
/>
</label>
<button
type="submit"
className={styles.upload}
onClick={async () => {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
if (!context || !image.current) {
return;
}
const sourceHeight = image.current.naturalHeight * (HEIGHT / (height * scale));
const fileHeight = Math.min(maxFileHeight, sourceHeight);
canvas.width = fileHeight * aspectRatio;
canvas.height = fileHeight;
context.drawImage(
image.current,
image.current.naturalWidth * (-left / (width * scale)),
image.current.naturalHeight * (-top / (height * scale)),
sourceHeight * aspectRatio,
sourceHeight,
0,
0,
fileHeight * aspectRatio,
fileHeight
);
const blob = await toBlob(canvas);
if (blob && blob.size <= maxSize) {
onCrop(blob);
return;
}
// Try compressing as JPG with various qualities, in parallel due to
// eslint(no-await-in-loop)
const blobs = await Promise.all(
[1, 0.9, 0.7, 0.4, 0].map(quality => toBlob(canvas, 'image/jpeg', quality))
);
const firstSmallEnough = blobs.find(blob => blob && blob.size <= maxSize);
if (firstSmallEnough) {
onCrop(firstSmallEnough);
} else {
onClose('cannot-compress');
}
}}
>
Apply
</button>
</div>
</Modal>
);
};

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

$margin: 40px;
$height: 200px;

.cropWrapper {
border-radius: 1rem;
cursor: grab;
overflow: hidden;
position: relative;
touch-action: none;
user-select: none;
z-index: 0;

.image {
margin: $margin;
position: absolute;
z-index: -1;
}

.frame {
border-radius: 0.5rem;
box-shadow: 0 0 0 3px var(--theme-text-on-background-1),
0 0 0 (($height + $margin) * sqrt(0.5)) var(--theme-accent-line-1-transparent);
height: $height;
margin: $margin;

&.circle {
border-radius: 50%;
}
}
}

.controls {
align-items: center;
display: flex;
gap: 1rem;
margin-top: 1rem;

.zoomWrapper {
align-items: center;
display: flex;
flex: auto;

.zoom {
flex: auto;
width: 0;
}
}

.upload {
background-color: var(--theme-primary-2);
border-radius: 0.5rem;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
color: var(--theme-background);
font-size: 1rem;
font-weight: bold;
height: 2.5rem;
width: 6.5rem;
}
}
Loading

0 comments on commit fbac2bc

Please sign in to comment.