diff --git a/.gitignore b/.gitignore index 5a6759a..fd3dbb5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,30 +4,33 @@ /node_modules /.pnp .pnp.js +.yarn/install-state.gz # testing /coverage +# next.js +/.next/ +/out/ + # production /build -/dist # misc .DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local +*.pem +# debug npm-debug.log* yarn-debug.log* yarn-error.log* -# Environment Variables -.env -.env.build +# local env files +.env*.local + +# vercel .vercel -# Docker local -docker-compose.yml -``` +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/LICENSE b/LICENSE deleted file mode 100644 index a4c29e8..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023 WXYC Chapel Hill - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index 0cbb04d..f6c9a9d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ [See the site in action here!](https://wxyc.github.io/dj-site/) -![Simplified Map of WXYC Card Catalog Frontend](https://dj.wxyc.org/img/FrontendMap.png) ## Description The WXYC Card Catalog, Revised is a React-based revision of the original WXYC card catalog and flowsheet. This repository showcases an improved version of the existing catalog and flowsheet, while maintaining the classic theme and preserving the original look. Notably, the revised version is optimized for performance, resulting in faster loading times. @@ -14,7 +13,7 @@ The WXYC Card Catalog, Revised is a React-based revision of the original WXYC ca - Mail Bin: a digital mail bin is available on every account, so DJs can add to the flowsheet directly from their bin without having to type during their sets. ## Deployment -The application is deployed using github pages. In order to deploy to the gh-pages branch, run `npm run deploy`. If you wish to add a commit message to the branch, use `npm run deploy -- -m "YOUR_MESSAGE_HERE"`. +TBD... ## API Integration The revised catalog leverages services defined in `api-service.js`, which utilizes the popular Axios library to communicate with an AWS API Gateway. This integration allows seamless communication between the front-end application and the API endpoints, enabling data retrieval and manipulation. @@ -29,7 +28,7 @@ The revised catalog leverages services defined in `api-service.js`, which utiliz 1. Clone the repository: `git clone https://github.com/WXYC/dj-site.git` 2. Navigate to the project directory: `cd dj-site` 3. Install dependencies: `npm install` -4. Run the application: `npm start` +4. Run the application: `npm run dev` 5. Access the application locally: Open your web browser and visit `http://localhost:3000` ## Contributing @@ -45,4 +44,4 @@ Contributions to the WXYC Card Catalog, Revised are welcome! If you would like t The WXYC Card Catalog, Revised is released under the [MIT License](LICENSE). Feel free to use, modify, and distribute the code as per the terms of this license. ## Acknowledgments -We would like to express our gratitude to the contributors and maintainers of the original WXYC Card Catalog for their valuable work, which served as the foundation for this revised version. In particular, Tim Ross/Tubafrenzy, who developed the original flowsheet site and maintained the database for years during decades when it was much more difficult to maintain and develop a site like this one. +We would like to express our gratitude to the contributors and maintainers of the original WXYC Card Catalog for their valuable work, which served as the foundation for this revised version. In particular, Tim Ross/Tubafrenzy, who developed the original flowsheet site and maintained the database for years during decades when it was much more difficult to maintain and develop a site like this one. \ No newline at end of file diff --git a/app/api/identity-count/route.ts b/app/api/identity-count/route.ts new file mode 100644 index 0000000..9f7471c --- /dev/null +++ b/app/api/identity-count/route.ts @@ -0,0 +1,13 @@ +/* Core */ +import { useDispatch, verifySession } from "@/lib/redux"; +import { NextResponse } from "next/server"; + +export async function POST(req: Request, res: Response) { + const body = await req.json(); + const { amount = 1 } = body; + + // simulate IO latency + await new Promise((r) => setTimeout(r, 500)); + + return NextResponse.json({ data: amount }); +} \ No newline at end of file diff --git a/app/classic/artist/page.tsx b/app/classic/artist/page.tsx new file mode 100644 index 0000000..fc7ead3 --- /dev/null +++ b/app/classic/artist/page.tsx @@ -0,0 +1,48 @@ +'use client'; + +const ClassicArtistPage = () => { + return ( +
+ + + + + + +
+
+ Rock ME 156 + Melkbelly# of releases: 1
+ + + + + + + + + + + + + + + + + + + +
Library CodeArtistTitle of ReleaseFormatComment(s)
Rock + ME 156/ + 1 + MelkbellyPithcd + + + + +
+
+ ); +} + +export default ClassicArtistPage; \ No newline at end of file diff --git a/app/classic/catalog/page.tsx b/app/classic/catalog/page.tsx new file mode 100644 index 0000000..a1d70d0 --- /dev/null +++ b/app/classic/catalog/page.tsx @@ -0,0 +1,220 @@ +'use client'; +import AuthenticationGuard from "@/app/components/Authentication/AuthenticationGuard"; +import LogoutClassic from "@/app/components/Classic/LogoutClassic"; +import { OrderByOption, OrderDirectionOption, SearchInOption } from "@/app/components/Table/types"; +import { Genre, catalogSlice, getAuthenticatedUser, getCatalogLoading, getGenre, getN, getOrderBy, getOrderDirection, getQuery, getReachedEnd, getResults, getSearchIn, searchCatalog, useDispatch, useSelector } from "@/lib/redux"; +import React, { useCallback, useEffect, useState } from "react"; + +const ClassicCatalogPage = () => { + + const dispatch = useDispatch(); + + const user = useSelector(getAuthenticatedUser); + + // Catalog Search State ---------------------------------------------------- + const loadMore = () => dispatch(catalogSlice.actions.loadMore()); + const loading = useSelector(getCatalogLoading); + const searchString = useSelector(getQuery); + const setSearchString = (value: string) => dispatch(catalogSlice.actions.setQuery(value)); + const genre = useSelector(getGenre); + const setGenre = (value: Genre) => dispatch(catalogSlice.actions.setGenre(value)); + const searchIn = useSelector(getSearchIn); + const setSearchIn = (value: SearchInOption) => dispatch(catalogSlice.actions.setSearchIn(value)); + const releaseList = useSelector(getResults); + const orderBy = useSelector(getOrderBy); + const handleRequestSort = (value: OrderByOption) => dispatch(catalogSlice.actions.setOrderBy(value)); + const orderDirection = useSelector(getOrderDirection); + const setOrderDirection = (value: OrderDirectionOption) => dispatch(catalogSlice.actions.setOrderDirection(value)); + const reachedEndForQuery = useSelector(getReachedEnd); + const n = useSelector(getN); + // ------------------------------------------------------------------------- + const [openResults, setOpenResults] = useState(false); + + const [localSearchString, setLocalSearchString] = useState(searchString); + + const handleSubmit = useCallback((event: React.FormEvent) => { + event.preventDefault(); + if (searchString.length > 0) setOpenResults(true); + console.log(event.currentTarget.localSearchString.value); + setSearchString(localSearchString); + }, [localSearchString, searchString]); + + const [searchTimeout, setSearchTimeout] = useState(undefined); + + useEffect(() => { + if (loading) { + document.body.style.cursor = 'wait'; + } else { + document.body.style.cursor = 'default'; + } + + return () => { + document.body.style.cursor = 'default'; + } + }, [loading]); + + useEffect(() => { + if (reachedEndForQuery) return; + + clearTimeout(searchTimeout); + setSearchTimeout(setTimeout(() => { + dispatch(searchCatalog({ + term: searchString, + medium: searchIn, + genre: genre, + n: n + })); + }, 500)); + }, [searchString, searchIn, genre, n]); + + if (openResults || searchString.length > 0) { + return ( +
+
+ + Welcome, {user?.name ?? `DJ ${user?.djName}`}
+ + +
+ +
+
+ { + e.preventDefault(); + setLocalSearchString(e.target.value); + }} /> +
+
+ +
+ + +
+ + Top Results (53) + +

+ Narrow by...
+
+ + + +
+ + +

+  Displaying {releaseList.length} results +matching text query {searchString}

+  {!reachedEndForQuery && (Load more)} +   + +

+ + + + + + + + + {releaseList.map((row) => ( + + + + + + + + ))} +
Library CodeArtist NameTitle Of ReleaseFormat
{row.album.artist.genre}{row.album.artist.lettercode} {row.album.artist.numbercode}/{row.album.release} + {row.album.artist.name} + {row.album.title}{row.album.format}
+
+ +
+ ) + } else { + return ( +
+ +
+
+ + + + + + + + + + + + + + + + + + + +
+ Search the   + WXYC logo +   Library: +
{ + e.preventDefault(); + setLocalSearchString(e.target.value); + }} />
+      + +
Program last modified: February 2, 2024.
56,000+ total releases in this database.
+
+
+ +

 

+ +
+ Tips for searching the WXYC Library: +

Look up whatever you want!

+
+
+ ); + } +} + +export default ClassicCatalogPage; diff --git a/src/CLASSIC_VIEW/CLASSIC_Flowsheet.js b/app/classic/flowsheet/page.tsx similarity index 62% rename from src/CLASSIC_VIEW/CLASSIC_Flowsheet.js rename to app/classic/flowsheet/page.tsx index a2f7cf0..293fe77 100644 --- a/src/CLASSIC_VIEW/CLASSIC_Flowsheet.js +++ b/app/classic/flowsheet/page.tsx @@ -1,32 +1,79 @@ -import React, { useRef, useState } from "react"; -import { useLive } from "../services/flowsheet/live-context"; -import { useFlowsheet } from "../services/flowsheet/flowsheet-context"; -import { useAuth } from "../services/authentication/authentication-context"; +'use client'; -import NorthIcon from '@mui/icons-material/North'; -import SouthIcon from '@mui/icons-material/South'; +import { Album, FlowSheetEntry, FlowSheetEntryProps, Rotation, authenticationSlice, flowSheetSlice, getAuthenticatedUser, getEntries, getIsLive, isLive, join, leave, loadFlowsheet, processingLive, pushToEntries, useDispatch } from "@/lib/redux"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { useSelector } from "react-redux"; -const CLASSIC_Flowsheet = ({ logout }) => { - const [releaseType, setReleaseType] = useState("libraryRelease"); - const [rotationType, setRotationType] = useState("heavy"); +import { Box } from "@mui/joy"; - const [notification, setNotification] = useState(''); +const ClassicFlowsheetPage = (): JSX.Element => { + + const dispatch = useDispatch(); + + const [fontSizeSetting, setFontSizeSetting] = useState(2); + + useEffect(() => { + // make the html data-attribute reflect the font size setting + document.documentElement.setAttribute('data-classic-font-size', fontSizeSetting.toString()); + }, [fontSizeSetting]); + + const logout = (e: any) => dispatch(authenticationSlice.actions.logout()); + const user = useSelector(getAuthenticatedUser); + + useEffect(() => { + dispatch(loadFlowsheet()); + }, []); - const { live, goLive, goOff } = useLive(); - const { entries, addToEntries, removeFromEntries, switchEntry } = useFlowsheet(); - const { user } = useAuth(); + useEffect(() => { + dispatch(getIsLive(user?.djId)); + }, [user?.djId]); - const formRef = useRef(); + const live = useSelector(isLive); + const intermediate = useSelector(processingLive); + const goLive = useCallback(() => { + + if (!user?.djId) { + return; + } + + dispatch(join({ + dj_id: user?.djId, + })); + + }, [user?.djId]); + + const goOff = useCallback(() => { + + if (!user?.djId) { + return; + } + + dispatch(leave({ + dj_id: user?.djId, + })); + + }, [user?.djId]); + + const entries = useSelector(getEntries); + const addToEntries = (entry: FlowSheetEntry) => dispatch(pushToEntries(entry)); + const removeFromEntries = (id: number) => dispatch(flowSheetSlice.actions.removeFromEntries(id)); + const switchEntry = (id1: number, id2: number) => dispatch(flowSheetSlice.actions.switchEntry({ id1, id2 })); + + const formRef = useRef(null); + + const [rotationType, setRotationType] = useState("H"); + const [notification, setNotification] = useState(''); const OpenHelp = () => { console.log("Help!"); }; - const toggleReleaseType = (event) => { + const [releaseType, setReleaseType] = useState("libraryRelease"); + const toggleReleaseType = (event: any) => { setReleaseType(event.target.value); }; - const validate = (e) => { + const validate = (e: any) => { e.preventDefault(); // Now get all form elements switch(releaseType) { @@ -38,8 +85,8 @@ const CLASSIC_Flowsheet = ({ logout }) => { label: e.target.label.value, album: e.target.album.value, }; - addToEntries({ message: "", ...libraryRelease }); - formRef.current.reset(); + pushToEntries({ message: undefined, ...libraryRelease }); + formRef.current?.reset(); break; default: console.log("Non-configured release type!"); @@ -47,7 +94,7 @@ const CLASSIC_Flowsheet = ({ logout }) => { } }; - const toggleRotationDropdowns = (event) => { + const toggleRotationDropdowns = (event: any) => { console.log(event.target.value); }; @@ -75,9 +122,9 @@ const CLASSIC_Flowsheet = ({ logout }) => { console.log("Autofill composer on change!"); }; - const addTalkset = (e) => { + const addTalkset = (e: any) => { e.preventDefault(); - addToEntries({ message: "Talkset" }); + pushToEntries({ message: "Talkset" }); }; const addBreakpoint = () => { @@ -85,11 +132,11 @@ const CLASSIC_Flowsheet = ({ logout }) => { }; return live ? ( - <> - + +
-
+ Add a Talkset! {" "} @@ -119,14 +166,14 @@ const CLASSIC_Flowsheet = ({ logout }) => { onSubmit={validate} ref={formRef} > - +
- +
@@ -158,7 +205,7 @@ const CLASSIC_Flowsheet = ({ logout }) => {
{releaseType == "rotationRelease" ? ( - +
{ @@ -176,21 +223,21 @@ const CLASSIC_Flowsheet = ({ logout }) => { L {" "} S  @@ -202,22 +249,22 @@ const CLASSIC_Flowsheet = ({ logout }) => { (Choose 'H' for Heavy, 'M' for Medium, 'L' for Light, or 'S' for Singles) - {rotationType == "heavy" && ( + {rotationType == "H" && ( )} - {rotationType == "medium" && ( + {rotationType == "M" && ( )} - {rotationType == "light" && ( + {rotationType == "L" && ( )} - {rotationType == "singles" && ( + {rotationType == "S" && ( @@ -226,14 +273,14 @@ const CLASSIC_Flowsheet = ({ logout }) => {
) : ( - +
- @@ -241,10 +288,10 @@ const CLASSIC_Flowsheet = ({ logout }) => { - - {releaseType == "otherRelease" && ( @@ -263,8 +310,8 @@ const CLASSIC_Flowsheet = ({ logout }) => { - )} - - + - + - + - + - + - +
-
- +
+
+ Leave the 'Artist' field blank unless the record/CD is a compilation or split release.{" "} - +
ARTIST: + @@ -254,8 +301,8 @@ const CLASSIC_Flowsheet = ({ logout }) => { SONG: - + +
COMPOSER: - + +    @@ -279,25 +326,25 @@ const CLASSIC_Flowsheet = ({ logout }) => {
-
- +
+
+ 'Release' and 'Label' are optional fields but listeners may be interested in this information.{" "} - +
Release:  - +     Label:  - +    
@@ -313,83 +360,70 @@ const CLASSIC_Flowsheet = ({ logout }) => {
)} - {(notification.length > 0) && (<>
+ {(notification.length > 0) && (<>
{notification} 

)} -
+
-
- Date of Show: 6/7/23 + + Date of Show: 6/7/23
- Hours: 12:00 AM - 3:00 AM + Hours: 12:00 AM - 3:00 AM
- - Aa - - - Aa - - - Aa - - - Aa - - - Aa - - - Aa - - + {[...Array(7)].map((_, index) => ( + + ))}
- Disc Jockey: {user.name}  + Disc Jockey: {user?.name} 
- +
- + - { - entries.map((entry, index) => ((entry.message == "") ? ( - + entries.map((entry: FlowSheetEntry, index) => ((entry.message == "" || !entry.message) ? ( + - - - - - + + + + - - @@ -431,9 +468,9 @@ const CLASSIC_Flowsheet = ({ logout }) => { ) : (() => { let type = entry.message.includes("left") || entry.message.includes("joined"); // true for starting/leaving show, false for other messages return type ? ( - - - + + ) : ( - - - + + - -
Playlist Req.ArtistArtist Song Release Label + Move Up
or Down? @@ -397,33 +431,36 @@ const CLASSIC_Flowsheet = ({ logout }) => {
Edit/Delete
{entry.artist}{entry.title}{entry.album}{entry.label} + {entry?.song?.album?.artist.name}{entry?.song?.title}{entry?.song?.album?.title}{(() => { + let album = entry?.song?.album as Album + return album?.label + })()} { e.preventDefault(); if (entry.id === 0) return; switchEntry(entry.id - 1, entry.id); }}> - + + { e.preventDefault(); if (entry.id === entries.length - 1) return; switchEntry(entry.id + 1, entry.id); }}> - + + Edit   { e.preventDefault(); removeFromEntries(entry.id); }}>Delete
{entry.message} +
--- {entry.message} --- { e.preventDefault(); @@ -445,19 +482,19 @@ const CLASSIC_Flowsheet = ({ logout }) => {
{entry.message} +
{entry.message} - + + - + + { e.preventDefault(); @@ -473,10 +510,10 @@ const CLASSIC_Flowsheet = ({ logout }) => {
- + ) : (
- +

Welcome, DJ Turncoat

@@ -164,11 +200,11 @@ export const AddDJsPopup = ({callback, style }) => { Enter List - + Enter a list of usernames and emails separated by commas.
Every new entry should be on a new line.
- The usernames must be unique and cannot be changed.
- We recommend using the DJ's first name, last name, initials, or some combination of the three. + The usernames must be unique and cannot be changed.
+ We recommend using the DJ's onyen, first name, last name, initials, or some combination thereof.