Skip to content

Commit

Permalink
Merge pull request #878 from cornell-dti/countdown
Browse files Browse the repository at this point in the history
Wrapped Countdown Component
  • Loading branch information
rgu0114 authored Nov 11, 2024
2 parents 99ccd34 + 271af1b commit d2f89af
Show file tree
Hide file tree
Showing 8 changed files with 543 additions and 18 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"moment": "^2.29.4",
"moment-timezone": "^0.5.32",
"react": "^17.0.2",
"react-confetti-explosion": "^2.1.2",
"react-datepicker": "1.3.0",
"react-dates": "^18.2.2",
"react-dom": "^17.0.2",
Expand Down
119 changes: 119 additions & 0 deletions src/components/includes/WrappedCountdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import React, { useState, useEffect } from "react";
import "../../styles/WrappedCountdown.scss";
import ConfettiExplosion from "react-confetti-explosion";
import cone from "../../media/wrapped/cone.svg";
import ribbonBall from "../../media/wrapped/ribbonBall.svg";

type WrappedDate = {
launchDate: Date;
startDate: Date;
};
type RemainingTime = {
days: number;
hours: number;
minutes: number;
total: number
};
// Declare interface to takes in the setter setDisplayWrapped as a prop
interface WrappedCountdownProps {
setDisplayWrapped: React.Dispatch<React.SetStateAction<boolean>>; // Define prop type for setter
wrappedDate: WrappedDate;
}
const WrappedCountdown: React.FC<WrappedCountdownProps> = ({ setDisplayWrapped, wrappedDate }) => {
const handleButtonClick = () => {
// Call the setter function to change the state in the parent
setDisplayWrapped(true); // Set to true to show the modal
};
// Helper function to calculate the remaining time in days, hours, and minutes from start date to launch date
const calculateTimeRemaining = (dateProps: WrappedDate): RemainingTime => {
// Calculate the time remaining (in milliseconds) between the launch date and start date
const time = dateProps.launchDate.getTime() - new Date().getTime();
const gap = time < 0 ? 0 : time;
// Calculate days, hours, and minutes remaining
const days = Math.floor(gap / (1000 * 60 * 60 * 24));
const hours = Math.floor((gap / (1000 * 60 * 60)) % 24);
const minutes = Math.floor((gap / (1000 * 60)) % 60);
return { days, hours, minutes, total: gap };
};
// Initialize countdown state using the calculateTimeRemaining function
const [timeRemaining, setTimeRemaining] = useState<RemainingTime>(calculateTimeRemaining(wrappedDate));
const [countDownClicked, setCountDownClicked] = useState<boolean>(false);
const [confettiShown, setConfettiShown] = useState<boolean>(false);
const [isZeroCounter, setIsZeroCounter] = useState<boolean>(false);

// Countdown timer effect
useEffect(() => {
const updatedTime = calculateTimeRemaining(wrappedDate);
setTimeRemaining(updatedTime);

// Stop countdown and set `isZeroCounter` when time reaches zero
if (updatedTime.total <= 0) {
setIsZeroCounter(true);
}
}, [wrappedDate, timeRemaining]);

// Trigger confetti with a delay only the first time `isZeroCounter` becomes true
useEffect(() => {
if (isZeroCounter && !confettiShown) {
setTimeout(() => {
setConfettiShown(true); // Show confetti after a delay
}, 1000); // Delay of 1 second
}
}, [isZeroCounter, confettiShown]);

// Prepend the days, hours, and minutes if they're single digits
const prependZero = (num: number): string => (num < 10 ? `0${num}` : `${num}`);

// Check that today is the start date or dates after the start date, then render the countdown if true
const isStartDate = () => {
const today = new Date();
// To get the Date object with respect to Eastern Time, we must offset
return today.getTime() >= wrappedDate.startDate.getTime();
};

return !isStartDate() ? null : countDownClicked ? (
<div onClick={() => setCountDownClicked(false)}>
<div className="countdownUpdates">
<div className="countdownContainer">
{!isZeroCounter ? (
<>
<div>
<div className="countdownContainer_boxes">{prependZero(timeRemaining.days)}</div>
<p className="counter_sub">DAYS</p>
</div>
<div>
<div className="countdownContainer_boxes">{prependZero(timeRemaining.hours)}</div>
<p className="counter_sub">HOURS</p>
</div>
<div>
<div className="countdownContainer_boxes">{prependZero(timeRemaining.minutes)}</div>
<p className="counter_sub">MINUTES</p>
</div>
<div className="textContainer">
<p className="top">Queue Me In</p>
<p className="bottom">WRAPPED</p>
</div>
</>
) : (
<div className="launch">
<div className="post">
<div className="textContainer">
<p className="top">Queue Me In</p>
<p className="bottom">WRAPPED</p>
</div>
<div className="viewWrap" onClick={handleButtonClick}>
View Now
</div>
</div>
{!confettiShown && <ConfettiExplosion duration={2800} force={0.6} particleCount={200} />}
<img className="cone" src={cone} alt="icon" />
</div>
)}
</div>
</div>
</div>
) : (
<img className="ribbonBall" src={ribbonBall} alt="icon" onClick={() => setCountDownClicked(true)} />
);
};
export default WrappedCountdown;
33 changes: 20 additions & 13 deletions src/components/pages/SplitView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import TopBar from "../includes/TopBar";
import CalendarExportModal from "../includes/CalendarExportModal";
import { RootState } from "../../redux/store";
import { updateCourse, updateSession } from "../../redux/actions/course";
import Browser from '../../media/browser.svg';
import smsNotif from '../../media/smsNotif.svg'
import { addBanner } from '../../redux/actions/announcements';
import Banner from '../includes/Banner';
import FeedbackPrompt from '../includes/FeedbackPrompt';
import Wrapped from '../includes/Wrapped';
import Browser from "../../media/browser.svg";
import smsNotif from "../../media/smsNotif.svg";
import { addBanner } from "../../redux/actions/announcements";
import Banner from "../includes/Banner";
import FeedbackPrompt from "../includes/FeedbackPrompt";
import Wrapped from "../includes/Wrapped";
import WrappedCountdown from "../includes/WrappedCountdown";
import { WRAPPED_START_DATE, WRAPPED_LAUNCH_DATE } from "../../constants";

// Also update in the main LESS file
const MOBILE_BREAKPOINT = 920;
Expand Down Expand Up @@ -115,15 +117,15 @@ const SplitView = ({
}, [courseHook, updateCourse]);
useEffect(() => {
updateSession(sessionHook);
}, [sessionHook, updateSession])
}, [sessionHook, updateSession]);

useEffect(() => {
if (user && user.wrapped) {
setDisplayWrapped(true);
} else {
setDisplayWrapped(false);
}
}, [user])
}, [user]);

// Handle browser back button
history.listen((location) => {
Expand Down Expand Up @@ -194,6 +196,9 @@ const SplitView = ({
}
}, [addBanner, user]);

const start = new Date(WRAPPED_START_DATE);
const launch = new Date(WRAPPED_LAUNCH_DATE);

return (
<>
<LeaveQueue setShowModal={setShowModal} showModal={showModal} removeQuestion={removeQuestion} />
Expand Down Expand Up @@ -258,8 +263,8 @@ const SplitView = ({
<div className="warningArea">
<div>&#9888;</div>
<div>
Please make sure to enable browser notifications in your system
settings.
Please make sure to enable browser notifications in your system
settings.
</div>
</div>
)}
Expand All @@ -270,16 +275,18 @@ const SplitView = ({
<Loader active={true} content="Loading" />
))}
<ProductUpdates />
<WrappedCountdown
setDisplayWrapped={setDisplayWrapped}
wrappedDate={{ launchDate: launch, startDate: start }}
/>
{displayFeedbackPrompt ? (
<FeedbackPrompt
onClose={submitFeedback(removedQuestionId, course, session.sessionId)}
closeFeedbackPrompt={() => setDisplayFeedbackPrompt(false)}
/>
) : null}

{displayWrapped ? (
<Wrapped user={user} onClose={() => setDisplayWrapped(false)} />
) : null}
{displayWrapped ? <Wrapped user={user} onClose={() => setDisplayWrapped(false)} /> : null}
</>
);
};
Expand Down
4 changes: 4 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ export const ALL_SEMESTERS = ['SP20', 'FA20', 'SP21', 'FA21', 'SP22', 'FA22',

export const START_DATE = '2024-08-19'
export const END_DATE = '2024-12-20'

// These are the start date and launch date for QMI Wrap for the current semester
export const WRAPPED_START_DATE = "2024-11-12T00:00:00";
export const WRAPPED_LAUNCH_DATE = "2024-11-22T00:00:00";
22 changes: 22 additions & 0 deletions src/media/wrapped/cone.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions src/media/wrapped/ribbonBall.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
128 changes: 128 additions & 0 deletions src/styles/WrappedCountdown.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
.countdownUpdates {
position: fixed;
bottom: 35px;
right: 41px;
}
.countdownContainer {
padding: 20px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 413px;
height: 139px;
border-radius: 12px;
background: linear-gradient(63deg, #668ae9 16.65%, #6db9ea 83.35%); /* Gradient background */
}
.countdownContainer_boxes {
color: #5599db;
font-family: Roboto;
font-size: 50px;
font-style: normal;
font-weight: 400;
line-height: normal;
justify-content: center;
align-items: center;
text-align: center;
display: flex;
width: 63px;
height: 76px;
border-radius: 5px;
background: #fff;
}
.textContainer {
align-items: center;
}

.ribbonBall {
position: fixed;
bottom: 36px;
right: 46.23px;
width: 68px;
height: 68px;
border-width: 100px;
cursor: pointer;
animation: rotation 1.3s ease-in-out infinite;
}

@keyframes rotation {

0% {
transform: rotate(15deg);
}
15% {
transform: rotate(-15deg);
}
25% {
transform: rotate(0deg);
}
}

.top {
margin: 0;
color: #fff;
font-family: Roboto;
font-size: 24.079px;
font-style: normal;
font-weight: 700;
line-height: normal;
}
.bottom {
margin: 0;
font-family: Roboto;
font-size: 24.079px;
font-style: normal;
font-weight: 400;
line-height: normal;
background: linear-gradient(180deg, #fff 0%, #fff 100%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.counter_sub {
margin-top: 10px;
color: #fff;
font-family: Roboto;
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: normal;
letter-spacing: -0.6px;
}
.viewWrap {
cursor: pointer;
margin-top: 7px;
width: 154.619px;
height: 37.669px;
border-radius: 5px;
background: #fff;
color: #5599db;
font-family: Roboto;
font-size: 20px;
font-style: normal;
font-weight: 500;
line-height: normal;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}

.post {
display: flex;
flex-direction: column;
}

.cone {
z-index:10;
margin-top: 40px;
margin-right: 25px;
}

.launch{
margin-left: 20px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
Loading

0 comments on commit d2f89af

Please sign in to comment.