diff --git a/components/wrapped/WrappedContainer.tsx b/components/wrapped/WrappedContainer.tsx
new file mode 100644
index 0000000..516d15f
--- /dev/null
+++ b/components/wrapped/WrappedContainer.tsx
@@ -0,0 +1,22 @@
+import React, { ReactNode } from "react";
+
+type WrappedContainerProps = {
+ children: ReactNode;
+ bg?: string;
+ text?: string;
+};
+
+export const WrappedContainer = ({
+ children,
+ bg = "bg-card",
+ text = "text-lightGray",
+}: WrappedContainerProps) => {
+ return (
+
+ {children}
+
+ );
+}
\ No newline at end of file
diff --git a/components/wrapped/WrappedPlayer.ts b/components/wrapped/WrappedPlayer.ts
new file mode 100644
index 0000000..39de078
--- /dev/null
+++ b/components/wrapped/WrappedPlayer.ts
@@ -0,0 +1,29 @@
+import EventEmitter from "events";
+import SpotifyFramePlayer from "./spotify/FramePlayer";
+import { SLIDES, Slide } from "./slides";
+
+export default class WrappedPlayer extends EventEmitter {
+ public currentSlide: Slide | null = null;
+
+ constructor(public spotifyPlayer: SpotifyFramePlayer | null = null) {
+ super();
+ }
+
+ public async play(): Promise {
+ for (let i = 0; i < SLIDES.length; i++) {
+ const slide = SLIDES[i];
+ this.currentSlide = slide;
+
+ if (this.currentSlide.spotify && this.spotifyPlayer) {
+ await this.spotifyPlayer.playSong(this.currentSlide.spotify);
+ }
+
+ this.emit("update");
+ await this.wait(slide.duration);
+ }
+ }
+
+ private wait(ms: number): Promise {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+ }
+}
diff --git a/components/wrapped/WrappedPlayerComponent.tsx b/components/wrapped/WrappedPlayerComponent.tsx
new file mode 100644
index 0000000..7889102
--- /dev/null
+++ b/components/wrapped/WrappedPlayerComponent.tsx
@@ -0,0 +1,57 @@
+import React, { useEffect, useState } from "react";
+import { CSSTransition, TransitionGroup } from "react-transition-group";
+import { Loader2 } from "lucide-react";
+import WrappedPlayer from "./WrappedPlayer";
+import { WrappedContainer } from "./WrappedContainer";
+import SpotifyFramePlayer from "./spotify/FramePlayer";
+
+type WrappedPlayerComponentProps = {
+ spotify: SpotifyFramePlayer | null;
+ team: any;
+};
+
+const LoadingPlayerComponent: React.FC = () => (
+
+
+
+);
+
+const WrappedPlayerComponent: React.FC = ({
+ spotify,
+ ...props
+}) => {
+ const [player] = useState(() => new WrappedPlayer(spotify));
+ const [, setForceUpdate] = useState(0);
+
+ useEffect(() => {
+ const forceUpdateHandler = () => setForceUpdate((prev) => prev + 1);
+
+ player.on("update", forceUpdateHandler);
+ player.play();
+
+ return () => {
+ player.off("update", forceUpdateHandler);
+ };
+ }, [player]);
+
+ useEffect(() => {
+ player.spotifyPlayer = spotify;
+ }, [player, spotify]);
+
+ const Component = player.currentSlide?.component || LoadingPlayerComponent;
+
+ return (
+
+
+
+
+
+ );
+};
+
+export default WrappedPlayerComponent;
diff --git a/components/wrapped/slides/FirstSlide.tsx b/components/wrapped/slides/FirstSlide.tsx
new file mode 100644
index 0000000..9e72bdd
--- /dev/null
+++ b/components/wrapped/slides/FirstSlide.tsx
@@ -0,0 +1,9 @@
+import { WrappedContainer } from "../WrappedContainer";
+
+export const FirstSlide = () => {
+ return (
+
+ This is the First Slide
+
+ );
+};
diff --git a/components/wrapped/slides/IntroSlide.tsx b/components/wrapped/slides/IntroSlide.tsx
new file mode 100644
index 0000000..f17d634
--- /dev/null
+++ b/components/wrapped/slides/IntroSlide.tsx
@@ -0,0 +1,21 @@
+import { WrappedContainer } from "../WrappedContainer";
+
+export const IntroSlide = (props: any) => {
+ return (
+
+
+ Did someone say {props.team} Wrap?
+
+
+ Get ready to see season highlights, match videos, & so much more!
+
+
+
+
+ );
+};
diff --git a/components/wrapped/slides/SecondSlide.tsx b/components/wrapped/slides/SecondSlide.tsx
new file mode 100644
index 0000000..47b1e9f
--- /dev/null
+++ b/components/wrapped/slides/SecondSlide.tsx
@@ -0,0 +1,9 @@
+import { WrappedContainer } from "../WrappedContainer";
+
+export const SecondSlide = () => {
+ return (
+
+ This is the Second Slide
+
+ );
+};
diff --git a/components/wrapped/slides/ThirdSlide.tsx b/components/wrapped/slides/ThirdSlide.tsx
new file mode 100644
index 0000000..d5a9064
--- /dev/null
+++ b/components/wrapped/slides/ThirdSlide.tsx
@@ -0,0 +1,9 @@
+import { WrappedContainer } from "../WrappedContainer";
+
+export const ThirdSlide = () => {
+ return (
+
+ This is the Third Slide
+
+ );
+};
diff --git a/components/wrapped/slides/index.tsx b/components/wrapped/slides/index.tsx
new file mode 100644
index 0000000..0a23685
--- /dev/null
+++ b/components/wrapped/slides/index.tsx
@@ -0,0 +1,31 @@
+import { FirstSlide } from "./FirstSlide";
+import { SecondSlide } from "./SecondSlide";
+import { ThirdSlide } from "./ThirdSlide";
+
+export interface Slide {
+ name: string;
+ component: React.FC;
+ duration: number;
+ spotify?: string;
+}
+
+export const SLIDES: Slide[] = [
+ {
+ name: "First Slide",
+ component: FirstSlide,
+ duration: 6000,
+ spotify: "spotify:track:0RiRZpuVRbi7oqRdSMwhQY",
+ },
+ {
+ name: "Second Slide",
+ component: SecondSlide,
+ duration: 6000,
+ spotify: "spotify:track:47Mf1u67oqXtWBAaUHUSi7",
+ },
+ {
+ name: "Third Slide",
+ component: ThirdSlide,
+ duration: 6000,
+ spotify: "spotify:track:14zDy9P7qf0oBDOtHMNoKV",
+ },
+];
diff --git a/components/wrapped/spotify/FramePlayer.ts b/components/wrapped/spotify/FramePlayer.ts
new file mode 100644
index 0000000..ba5ef36
--- /dev/null
+++ b/components/wrapped/spotify/FramePlayer.ts
@@ -0,0 +1,181 @@
+import EventEmitter from "events";
+
+export default class SpotifyFramePlayer extends EventEmitter {
+ public embedController: EmbedController | null = null;
+ public canPlaySongs = false;
+
+ private currentIFrame: HTMLIFrameElement | null = null;
+ private previousIFrame: HTMLIFrameElement | null = null;
+
+ public loadLibrary(): Promise {
+ return new Promise((resolve) => {
+ const script = document.createElement("script");
+ script.src = "https://open.spotify.com/embed-podcast/iframe-api/v1";
+ script.async = true;
+ document.body.appendChild(script);
+
+ const timeout = setTimeout(() => {
+ resolve();
+ }, 7000);
+
+ window.onSpotifyIframeApiReady = (IFrameAPI: SpotifyIframeApi) => {
+ const element = document.getElementById("spotify")!;
+ const options = {
+ uri: "spotify:track:0RiRZpuVRbi7oqRdSMwhQY",
+ width: 300,
+ height: 80,
+ theme: "dark",
+ };
+
+ IFrameAPI.createController(element, options, (EmbedController) => {
+ this.embedController = EmbedController;
+
+ const enablePlayback = () => {
+ if (this.canPlaySongs) return;
+
+ this.canPlaySongs = true;
+ this.embedController!.removeListener("ready", enablePlayback);
+ this.emit("ready");
+
+ clearTimeout(timeout);
+ resolve();
+ };
+
+ const defaultIframe = document
+ .getElementById("spotify-wrapper")
+ ?.querySelector("iframe");
+
+ if (defaultIframe) {
+ defaultIframe.style.opacity = "0";
+ defaultIframe.style.position = "absolute";
+ defaultIframe.style.top = "-1000px";
+ }
+
+ this.embedController!.addListener("ready", enablePlayback);
+ });
+ };
+ });
+ }
+
+ public playSong(uri: string): Promise {
+ return new Promise(async (resolve) => {
+ if (!this.canPlaySongs) return resolve();
+ if (!this.embedController) return resolve();
+
+ const playTimeout = setTimeout(() => {
+ this.canPlaySongs = false;
+ resolve();
+ }, 6000);
+
+ const container = document.getElementById("spotify-wrapper");
+ this.previousIFrame = this.currentIFrame;
+ const frameElement = document.createElement("div");
+
+ if (container?.firstChild) {
+ container!.insertBefore(frameElement, container.firstChild);
+ } else {
+ container!.appendChild(frameElement);
+ }
+
+ const oembed = await fetch(`https://open.spotify.com/oembed?url=${uri}`)
+ .then((response) => response.json())
+ .catch(() => {
+ return {
+ html: "",
+ };
+ });
+
+ if (!oembed.html) {
+ this.destroyPreviousIFrame();
+ resolve();
+ return;
+ }
+
+ oembed.html = oembed.html.replace("encrypted-media; ", "");
+ frameElement.innerHTML = oembed.html;
+
+ this.setupNewIframe(frameElement);
+
+ await this.waitForIframe();
+
+ this.embedController.loadUri(uri);
+ this.embedController.resume();
+
+ setTimeout(() => this.destroyPreviousIFrame(), 1000);
+ await this.waitForSpotify();
+
+ this.currentIFrame!.style.opacity = "1";
+ clearTimeout(playTimeout);
+ resolve();
+ });
+ }
+
+ private setupNewIframe(frameElement: HTMLDivElement): void {
+ const iframe = frameElement.querySelector("iframe")!;
+ this.embedController!.iframeElement = iframe;
+ this.currentIFrame = iframe;
+ this.currentIFrame!.style.opacity = "0";
+ this.currentIFrame!.setAttribute("width", "300");
+ this.currentIFrame!.setAttribute("height", "80");
+ }
+
+ private waitForIframe(): Promise {
+ return new Promise((resolve) => {
+ const checkIfReady = (e: MessageEvent) => {
+ if (
+ e.source === this.currentIFrame?.contentWindow &&
+ e.data.type === "ready"
+ ) {
+ window.removeEventListener("message", checkIfReady);
+ resolve();
+ }
+ };
+
+ window.addEventListener("message", checkIfReady);
+ });
+ }
+
+ private destroyPreviousIFrame(): void {
+ if (this.previousIFrame) {
+ this.previousIFrame.remove();
+ this.previousIFrame = null;
+ }
+ }
+
+ private waitForSpotify(): Promise {
+ return new Promise((resolve) => {
+ let hasResolved = false;
+
+ const checkIfReady = (state: {
+ data: {
+ isBuffering: boolean;
+ duration: number;
+ position: number;
+ };
+ }) => {
+ if (hasResolved) {
+ return;
+ }
+
+ if (
+ !state.data.isBuffering &&
+ state.data.duration > 0 &&
+ state.data.position > 0
+ ) {
+ this.embedController?.removeListener("playback_update", checkIfReady);
+ hasResolved = true;
+ resolve();
+ }
+ };
+
+ this.embedController?.addListener("playback_update", checkIfReady);
+ });
+ }
+
+ public async pause(): Promise {
+ if (!this.canPlaySongs) return;
+ if (!this.embedController) return;
+
+ this.embedController.pause();
+ }
+}
diff --git a/components/wrapped/spotify/SpotifyPlayer.tsx b/components/wrapped/spotify/SpotifyPlayer.tsx
new file mode 100644
index 0000000..3d14480
--- /dev/null
+++ b/components/wrapped/spotify/SpotifyPlayer.tsx
@@ -0,0 +1,9 @@
+import React from "react";
+
+export const SpotifyPlayer = () => {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/components/wrapped/spotify/spotify.d.ts b/components/wrapped/spotify/spotify.d.ts
new file mode 100644
index 0000000..7a5d716
--- /dev/null
+++ b/components/wrapped/spotify/spotify.d.ts
@@ -0,0 +1,28 @@
+interface EmbedController {
+ loadUri(uri: string): void;
+ play(): void;
+ playFromStart(): void;
+ togglePlay(): void;
+ pause(): void;
+ resume(): void;
+ seek(seconds: number): void;
+ destroy(): void;
+
+ addListener(event: string, callback: (state: any) => void): void;
+ once(event: string, callback: () => void): void;
+ removeListener(event: string, callback: (state: any) => void): void;
+
+ iframeElement: HTMLIFrameElement;
+}
+
+interface SpotifyIframeApi {
+ createController(
+ element: HTMLElement,
+ options: { uri: string; width?: number; height?: number },
+ callback: (embedController: EmbedController) => void
+ ): void;
+}
+
+interface Window {
+ onSpotifyIframeApiReady: (iframeAPI: SpotifyIframeApi) => void;
+}
diff --git a/package.json b/package.json
index 9a1cb7f..d480c41 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"@types/node": "20.2.5",
"@types/react": "18.2.8",
"@types/react-dom": "18.2.4",
+ "@types/react-transition-group": "^4.4.6",
"@vercel/analytics": "^1.0.1",
"autoprefixer": "10.4.14",
"axios": "^1.4.0",
@@ -37,6 +38,7 @@
"framer-motion": "^10.12.16",
"haversine-distance": "^1.2.1",
"lodash": "^4.17.21",
+ "lucide-react": "^0.236.0",
"next": "13.4.4",
"next-api-handler": "^0.4.10",
"next-auth": "^4.22.1",
@@ -51,6 +53,7 @@
"react-google-autocomplete": "^2.7.3",
"react-hot-toast": "^2.4.1",
"react-icons": "^4.9.0",
+ "react-transition-group": "^4.4.5",
"sharp": "^0.32.1",
"tailwindcss": "3.3.2",
"typescript": "5.0.4",
diff --git a/pages/team/[team]/wrap.tsx b/pages/team/[team]/wrap.tsx
index 7fe2cc2..c580fc0 100644
--- a/pages/team/[team]/wrap.tsx
+++ b/pages/team/[team]/wrap.tsx
@@ -1,9 +1,40 @@
import { GetServerSideProps, GetServerSidePropsContext } from "next";
-import db from "@/lib/db";
-import { Match } from "@prisma/client";
+import WrappedPlayerComponent from "@/components/wrapped/WrappedPlayerComponent";
+// import db from "@/lib/db";
+// import { Match } from "@prisma/client";
+import { useRouter } from "next/router";
+import { SpotifyPlayer } from "@/components/wrapped/spotify/SpotifyPlayer";
+import SpotifyFramePlayer from "@/components/wrapped/spotify/FramePlayer";
+import { useEffect, useState } from "react";
+import { IntroSlide } from "@/components/wrapped/slides/IntroSlide";
-export default function LiveFieldViewPage({ teamMatches }: any) {
- return Testing
;
+export default function TeamWrapPage() {
+ const router = useRouter();
+ const { team } = router.query;
+ const [spotify, setSpotify] = useState(null);
+ const [page, setPage] = useState("main");
+
+ useEffect(() => {
+ const loadSpotify = async () => {
+ const getSpotify = new SpotifyFramePlayer();
+ await getSpotify.loadLibrary();
+ setSpotify(getSpotify);
+ };
+
+ if (!spotify) loadSpotify();
+ });
+
+ return (
+ <>
+
+
+ {page === "main" && }
+
+ {page === "ready" && (
+
+ )}
+ >
+ );
}
export const getServerSideProps: GetServerSideProps = async (
@@ -11,45 +42,45 @@ export const getServerSideProps: GetServerSideProps = async (
): Promise<{ props: any }> => {
const { team }: any = context.params;
- const teamMatches = await db.match.findMany({
- where: {
- event_key: {
- contains: "2023",
- },
- OR: [
- {
- alliances: {
- path: ["red", "team_keys"],
- array_contains: `frc${team}`,
- },
- },
- {
- alliances: {
- path: ["blue", "team_keys"],
- array_contains: `frc${team}`,
- },
- },
- ],
- },
- });
+ // const teamMatches = await db.match.findMany({
+ // where: {
+ // event_key: {
+ // contains: "2023",
+ // },
+ // OR: [
+ // {
+ // alliances: {
+ // path: ["red", "team_keys"],
+ // array_contains: `frc${team}`,
+ // },
+ // },
+ // {
+ // alliances: {
+ // path: ["blue", "team_keys"],
+ // array_contains: `frc${team}`,
+ // },
+ // },
+ // ],
+ // },
+ // });
- const highestScoringMatches: Match[] = teamMatches
- .sort((a: any, b: any) => {
- const scoreA = a.alliances.red.score + a.alliances.blue.score;
- const scoreB = b.alliances.red.score + b.alliances.blue.score;
- return scoreB - scoreA;
- })
- .slice(0, 2);
+ // const highestScoringMatches: Match[] = teamMatches
+ // .sort((a: any, b: any) => {
+ // const scoreA = a.alliances.red.score + a.alliances.blue.score;
+ // const scoreB = b.alliances.red.score + b.alliances.blue.score;
+ // return scoreB - scoreA;
+ // })
+ // .slice(0, 2);
return {
props: {
- teamMatches: JSON.parse(
- JSON.stringify(
- highestScoringMatches,
- (key: string, value) =>
- typeof value === "bigint" ? value.toString() : value // return everything else unchanged
- )
- ),
+ // teamMatches: JSON.parse(
+ // JSON.stringify(
+ // highestScoringMatches,
+ // (key: string, value) =>
+ // typeof value === "bigint" ? value.toString() : value // return everything else unchanged
+ // )
+ // ),
},
};
};
diff --git a/styles/globals.css b/styles/globals.css
index 212d2be..dfa8a22 100644
--- a/styles/globals.css
+++ b/styles/globals.css
@@ -87,6 +87,28 @@ a:hover {
}
}
+.fade-enter {
+ transform: translateY(100%);
+}
+
+.fade-enter-active {
+ transform: translateY(0%);
+ transition: transform 300ms;
+}
+
+.fade-exit {
+ position: absolute;
+ top: 0;
+ left: 0;
+
+ transform: translateY(0%);
+}
+
+.fade-exit-active {
+ transform: translateY(-100%);
+ transition: transform 300ms;
+}
+
::-webkit-scrollbar {
width: 2px;
}
diff --git a/yarn.lock b/yarn.lock
index 80528a1..ca9fa5d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1905,7 +1905,7 @@
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
-"@babel/runtime@^7.11.2", "@babel/runtime@^7.8.4":
+"@babel/runtime@^7.11.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
version "7.22.3"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.3.tgz#0a7fce51d43adbf0f7b517a71f4c3aaca92ebcbb"
integrity sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==
@@ -2641,6 +2641,13 @@
dependencies:
"@types/react" "*"
+"@types/react-transition-group@^4.4.6":
+ version "4.4.6"
+ resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.6.tgz#18187bcda5281f8e10dfc48f0943e2fdf4f75e2e"
+ integrity sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==
+ dependencies:
+ "@types/react" "*"
+
"@types/react@*", "@types/react@18.2.8":
version "18.2.8"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.8.tgz#a77dcffe4e9af148ca4aa8000c51a1e8ed99e2c8"
@@ -3680,6 +3687,14 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
+dom-helpers@^5.0.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
+ integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
+ dependencies:
+ "@babel/runtime" "^7.8.7"
+ csstype "^3.0.2"
+
dot-prop@^5.1.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
@@ -5328,6 +5343,11 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
+lucide-react@^0.236.0:
+ version "0.236.0"
+ resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.236.0.tgz#156270061d68f81b4bda0daa03c3487dbfe403b8"
+ integrity sha512-himeKF7nVgOQ1BNcyBgk41E4/rcbmI6Zw8Q4o57nlynsFvIBA/DMacFpzKYdcyBReUj8jf08xTnCGyn/niLvwQ==
+
lunr@^2.3.9:
version "2.3.9"
resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1"
@@ -6126,7 +6146,7 @@ prisma@^4.15.0:
dependencies:
"@prisma/engines" "4.15.0"
-prop-types@^15.5.0, prop-types@^15.8.1:
+prop-types@^15.5.0, prop-types@^15.6.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -6242,6 +6262,16 @@ react-is@^16.13.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
+react-transition-group@^4.4.5:
+ version "4.4.5"
+ resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
+ integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==
+ dependencies:
+ "@babel/runtime" "^7.5.5"
+ dom-helpers "^5.0.1"
+ loose-envify "^1.4.0"
+ prop-types "^15.6.2"
+
react@18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"