initial commit
This commit is contained in:
5
client/src/Series/Events.tsx/Event/Event.css
Normal file
5
client/src/Series/Events.tsx/Event/Event.css
Normal file
@@ -0,0 +1,5 @@
|
||||
.event-list-item {
|
||||
background-color: transparent !important;
|
||||
color: inherit;
|
||||
border: 0.15rem solid var(--group-info-color);
|
||||
}
|
||||
70
client/src/Series/Events.tsx/Event/Event.tsx
Normal file
70
client/src/Series/Events.tsx/Event/Event.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { ListGroup } from "react-bootstrap";
|
||||
import "./Event.css";
|
||||
|
||||
export class EventObj {
|
||||
event_id: number;
|
||||
location: string;
|
||||
time: string; // ISO 8601 formatted date-time string
|
||||
ticket_url?: string;
|
||||
map_url?: string;
|
||||
|
||||
constructor(
|
||||
event_id: number,
|
||||
location: string,
|
||||
time: string,
|
||||
ticket_url?: string,
|
||||
map_url?: string,
|
||||
) {
|
||||
this.event_id = event_id;
|
||||
this.location = location;
|
||||
this.time = time;
|
||||
this.ticket_url = ticket_url;
|
||||
this.map_url = map_url;
|
||||
}
|
||||
}
|
||||
|
||||
interface EventProps {
|
||||
event: EventObj;
|
||||
}
|
||||
|
||||
function Event(props: EventProps) {
|
||||
const event = props.event;
|
||||
const date = new Date(event.time);
|
||||
const dateString = date.toLocaleDateString(undefined, {
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
weekday: "long",
|
||||
});
|
||||
const location = event.map_url ? (
|
||||
<>
|
||||
<a href={event.map_url} target="_blank" rel="noreferrer">
|
||||
{event.location}
|
||||
</a>
|
||||
</>
|
||||
) : (
|
||||
event.location
|
||||
);
|
||||
const tickets = event.ticket_url ? (
|
||||
<>
|
||||
|{" "}
|
||||
<a href={event.ticket_url} target="_blank" rel="noreferrer">
|
||||
Tickets
|
||||
</a>
|
||||
</>
|
||||
) : null;
|
||||
const timeString = date.toLocaleTimeString(undefined, {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<ListGroup.Item className="event-list-item">
|
||||
<p>
|
||||
{dateString} {timeString.toLowerCase()} | {location} {tickets}
|
||||
</p>
|
||||
</ListGroup.Item>
|
||||
);
|
||||
}
|
||||
|
||||
export default Event;
|
||||
3
client/src/Series/Events.tsx/Events.css
Normal file
3
client/src/Series/Events.tsx/Events.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.event-list {
|
||||
margin-top: 2.5rem;
|
||||
}
|
||||
23
client/src/Series/Events.tsx/Events.tsx
Normal file
23
client/src/Series/Events.tsx/Events.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ListGroup } from "react-bootstrap";
|
||||
import Event, { EventObj } from "./Event/Event";
|
||||
import "./Events.css";
|
||||
|
||||
interface EventsProps {
|
||||
events: EventObj[];
|
||||
}
|
||||
|
||||
function Events(props: EventsProps) {
|
||||
const events = props.events;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListGroup className="event-list">
|
||||
{events.map((event) => (
|
||||
<Event key={event.event_id} event={event} />
|
||||
))}
|
||||
</ListGroup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Events;
|
||||
90
client/src/Series/Series/Series.tsx
Normal file
90
client/src/Series/Series/Series.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Card, Col, Container, Row } from "react-bootstrap";
|
||||
import { EventSeriesObj } from "../SeriesList";
|
||||
import Events from "../Events.tsx/Events";
|
||||
import SeriesPoster from "./SeriesPoster";
|
||||
import EditButton from "../../Buttons/EditButton/EditButton";
|
||||
import DeleteButton from "../../Buttons/DeleteButton/DeleteButton";
|
||||
import { useEffect, useState } from "react";
|
||||
import { faEdit } from "@fortawesome/free-solid-svg-icons";
|
||||
import EditModal from "../../EditModals/EditModal";
|
||||
import EventForm from "../../Forms/Event/EventForm";
|
||||
|
||||
interface SeriesProps {
|
||||
series: EventSeriesObj;
|
||||
setSeriesList: React.Dispatch<React.SetStateAction<EventSeriesObj[]>>;
|
||||
seriesList: EventSeriesObj[];
|
||||
token: string;
|
||||
}
|
||||
|
||||
function Series(props: SeriesProps) {
|
||||
const [series, setSeries] = useState<EventSeriesObj>(props.series);
|
||||
const [modalShow, setModalShow] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setSeries(props.series);
|
||||
}, [props.series]);
|
||||
|
||||
const EditableSeries = (
|
||||
<Container>
|
||||
<Row>
|
||||
<Col>
|
||||
<Container className="text-center">
|
||||
<EditButton setModalShow={setModalShow} faIcon={faEdit} />
|
||||
<EditModal
|
||||
show={modalShow}
|
||||
onHide={() => setModalShow(false)}
|
||||
title="Edit Concert Series"
|
||||
entity={props.series}
|
||||
form={
|
||||
<EventForm
|
||||
setModalShow={setModalShow}
|
||||
setSeriesList={props.setSeriesList}
|
||||
seriesList={props.seriesList}
|
||||
series={series}
|
||||
isNewSeries={false}
|
||||
setSeries={setSeries}
|
||||
token={props.token}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Container>
|
||||
</Col>
|
||||
|
||||
<Col>
|
||||
<Container className="text-center">
|
||||
<DeleteButton
|
||||
series={series}
|
||||
setSeriesList={props.setSeriesList}
|
||||
seriesList={props.seriesList}
|
||||
token={props.token}
|
||||
/>
|
||||
</Container>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
);
|
||||
|
||||
return (
|
||||
<Row id={`series-${series.series_id}-row`}>
|
||||
<Col>
|
||||
<SeriesPoster
|
||||
series={series}
|
||||
setSeries={setSeries}
|
||||
token={props.token}
|
||||
/>
|
||||
</Col>
|
||||
<Col>
|
||||
<Container>
|
||||
<Card>
|
||||
<h4>{series.name}</h4>
|
||||
{props.token && EditableSeries}
|
||||
<p>{series.description}</p>
|
||||
<Events events={series.events} />
|
||||
</Card>
|
||||
</Container>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
export default Series;
|
||||
57
client/src/Series/Series/SeriesPoster.tsx
Normal file
57
client/src/Series/Series/SeriesPoster.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { Container, Image } from "react-bootstrap";
|
||||
import { EventSeriesObj } from "../SeriesList";
|
||||
import cld from "../../Cld/CloudinaryConfig";
|
||||
import { useState } from "react";
|
||||
import EditButton from "../../Buttons/EditButton/EditButton";
|
||||
import { faUpload } from "@fortawesome/free-solid-svg-icons";
|
||||
import EditModal from "../../EditModals/EditModal";
|
||||
import PosterUploadForm from "../../Forms/PosterUpload/PosterUploadForm";
|
||||
|
||||
interface SeriesPosterProps {
|
||||
series: EventSeriesObj;
|
||||
setSeries: React.Dispatch<React.SetStateAction<EventSeriesObj>>;
|
||||
token: string;
|
||||
}
|
||||
|
||||
function SeriesPoster(props: SeriesPosterProps) {
|
||||
const series = props.series;
|
||||
const imgUrl = cld.image(series.poster_id).toURL();
|
||||
const imgSrc = imgUrl ? imgUrl : undefined;
|
||||
const [modalShow, setModalShow] = useState(false);
|
||||
|
||||
const EditablePoster = (
|
||||
<Container className="">
|
||||
<EditButton
|
||||
setModalShow={setModalShow}
|
||||
faIcon={faUpload}
|
||||
actionName=" Poster"
|
||||
/>
|
||||
<EditModal
|
||||
show={modalShow}
|
||||
onHide={() => setModalShow(false)}
|
||||
title="Edit Poster"
|
||||
entity={props.series}
|
||||
form={
|
||||
<PosterUploadForm
|
||||
series={series}
|
||||
currentPoster={imgSrc}
|
||||
setModalShow={setModalShow}
|
||||
setSeries={props.setSeries}
|
||||
token={props.token}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{series.poster_id ? (
|
||||
<Image src={imgSrc} alt={series.name} className="img-fluid" />
|
||||
) : null}
|
||||
{props.token && EditablePoster}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default SeriesPoster;
|
||||
19
client/src/Series/SeriesList.css
Normal file
19
client/src/Series/SeriesList.css
Normal file
@@ -0,0 +1,19 @@
|
||||
.events-title {
|
||||
color: var(--group-info-color);
|
||||
margin-bottom: 6rem;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
height: 4px; /* This controls the thickness of the divider */
|
||||
background: rgba(124, 1, 68); /* This sets the color of the divider */
|
||||
margin-top: 10rem;
|
||||
margin-bottom: 6rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
#events {
|
||||
padding-top: 70px;
|
||||
margin-top: -70px;
|
||||
margin-bottom: 400px;
|
||||
}
|
||||
106
client/src/Series/SeriesList.tsx
Normal file
106
client/src/Series/SeriesList.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import { Col, Container } from "react-bootstrap";
|
||||
import { EventObj } from "./Events.tsx/Event/Event";
|
||||
import Series from "./Series/Series";
|
||||
import "./SeriesList.css";
|
||||
import EditButton from "../Buttons/EditButton/EditButton";
|
||||
import { useState } from "react";
|
||||
import { faAdd } from "@fortawesome/free-solid-svg-icons";
|
||||
import EditModal from "../EditModals/EditModal";
|
||||
import EventForm from "../Forms/Event/EventForm";
|
||||
|
||||
export class EventSeriesObj {
|
||||
series_id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
events: EventObj[];
|
||||
poster_id?: string; // Cloudinary public ID
|
||||
|
||||
constructor(
|
||||
series_id: number,
|
||||
name: string,
|
||||
description: string,
|
||||
events: EventObj[],
|
||||
poster_id?: string,
|
||||
) {
|
||||
this.series_id = series_id;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.events = events;
|
||||
this.poster_id = poster_id;
|
||||
}
|
||||
}
|
||||
|
||||
interface SeriesListProps {
|
||||
seriesList: EventSeriesObj[];
|
||||
setSeriesList: React.Dispatch<React.SetStateAction<EventSeriesObj[]>>;
|
||||
token: string;
|
||||
}
|
||||
|
||||
function SeriesList(props: SeriesListProps) {
|
||||
const seriesList = props.seriesList;
|
||||
const [modalShow, setModalShow] = useState(false);
|
||||
|
||||
const AddableSeries = (
|
||||
<Container className="text-end">
|
||||
<EditButton
|
||||
setModalShow={setModalShow}
|
||||
faIcon={faAdd}
|
||||
actionName=" Event"
|
||||
/>
|
||||
<EditModal
|
||||
show={modalShow}
|
||||
onHide={() => setModalShow(false)}
|
||||
title="Add Concert Series"
|
||||
form={
|
||||
<EventForm
|
||||
setModalShow={setModalShow}
|
||||
setSeriesList={props.setSeriesList}
|
||||
seriesList={props.seriesList}
|
||||
isNewSeries={true}
|
||||
token={props.token}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
|
||||
if (seriesList.length === 0) {
|
||||
return (
|
||||
<section id="events">
|
||||
<Col>
|
||||
<Container>
|
||||
<h3 className="display-3 text-end events-title">Upcoming Events</h3>
|
||||
{props.token && AddableSeries}
|
||||
</Container>
|
||||
<Container>
|
||||
<h3 className="display-4 text-center events-title">Stay tuned!</h3>
|
||||
</Container>
|
||||
</Col>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section id="events">
|
||||
<Col>
|
||||
<Container>
|
||||
<h3 className="display-3 text-end events-title">Upcoming Events</h3>
|
||||
{props.token && AddableSeries}
|
||||
</Container>
|
||||
{seriesList.map((series, idx) => (
|
||||
<Container key={series.series_id}>
|
||||
<Series
|
||||
series={series}
|
||||
setSeriesList={props.setSeriesList}
|
||||
seriesList={props.seriesList}
|
||||
token={props.token}
|
||||
/>
|
||||
{idx < seriesList.length - 1 && <hr className="series-divider" />}
|
||||
</Container>
|
||||
))}
|
||||
</Col>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default SeriesList;
|
||||
Reference in New Issue
Block a user