Skip to content

Commit

Permalink
Frontend cleanup (#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
david-c-smith authored Nov 30, 2023
1 parent e16a4d5 commit ab25e4d
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 154 deletions.
53 changes: 53 additions & 0 deletions src/components/FixtureRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {useNavigate} from 'react-router-dom';
import {Fixture} from '../lib/api/fixtures';

interface FixtureRowProps {
fixture?: Fixture;
}

function FixtureRow({fixture}: FixtureRowProps) {
const navigate = useNavigate();

if (!fixture) {
return null;
}

return (
<>
<button
className="btn btn-secondary d-flex align-items-center border w-100 shadow-sm text-muted mb-2"
key={fixture.id}
onClick={() => navigate(`/fixtures/${fixture.id}`)}
>
<div className="d-flex justify-content-between h-100 w-100">
<div className="d-flex flex-column">
<div className="d-flex align-items-center">
<img
className="me-2"
src={fixture.teams.home.logo}
alt="Home team logo"
style={{maxWidth: '20px'}}
></img>
<div>{fixture.teams.home.name}</div>
</div>
<div className="d-flex align-items-center">
<img
className="me-2"
src={fixture.teams.away.logo}
alt="Away team logo"
style={{maxWidth: '20px'}}
></img>
<div>{fixture.teams.away.name}</div>
</div>
</div>
<div className="d-flex flex-column align-items-start">
<div>{new Date(fixture.date).toDateString()}</div>
<div>{new Date(fixture.date).toLocaleTimeString()}</div>
</div>
</div>
</button>
</>
);
}

export default FixtureRow;
70 changes: 13 additions & 57 deletions src/components/FixturesList.tsx
Original file line number Diff line number Diff line change
@@ -1,64 +1,20 @@
import {useNavigate} from 'react-router-dom';
import {Fixture} from '../lib/api/fixtures';
import {League} from '../lib/api/leagues';

function leaguesForFixtures(fixtures: Fixture[], leagues: League[]): League[] {
const leaguesForFixtures = new Set<League>();
fixtures.forEach((fixture) => {
const league = leagues.find((league) => league.id === fixture.leagueId);
if (league) {
leaguesForFixtures.add(league);
}
});

return Array.from(leaguesForFixtures);
}

interface FixtureRowProps {
fixture?: Fixture;
}

export function FixtureRow({fixture}: FixtureRowProps) {
const navigate = useNavigate();

if (!fixture) {
return null;
}

return (
<>
<button
className="btn btn-secondary d-flex align-items-center border w-100 shadow-sm text-muted mb-2"
key={fixture.id}
onClick={() => navigate(`/fixtures/${fixture.id}`)}
>
<div className="d-flex justify-content-between h-100 w-100">
<div className="d-flex flex-column">
<div className="d-flex align-items-center">
<img className="me-2" src={fixture.teams.home.logo} style={{maxWidth: '20px'}}></img>
<div>{fixture.teams.home.name}</div>
</div>
<div className="d-flex align-items-center">
<img className="me-2" src={fixture.teams.away.logo} style={{maxWidth: '20px'}}></img>
<div>{fixture.teams.away.name}</div>
</div>
</div>
<div className="d-flex flex-column align-items-start">
<div>{new Date(fixture.date).toDateString()}</div>
<div>{new Date(fixture.date).toLocaleTimeString()}</div>
</div>
</div>
</button>
</>
);
}
import FixtureRow from './FixtureRow';

interface FixturesListProps {
fixtures?: Fixture[];
leagues?: League[];
}

export function FixturesList({fixtures, leagues}: FixturesListProps) {
function leaguesForFixtures(fixtures: Fixture[], leagues: League[]): League[] {
const uniqueLeagueIds = new Set(fixtures.map((fixture) => fixture.leagueId));
const filteredLeagues = leagues.filter((league) => uniqueLeagueIds.has(league.id));

return filteredLeagues;
}

function FixturesList({fixtures, leagues}: FixturesListProps) {
if (!fixtures || !leagues) {
return null;
}
Expand All @@ -70,19 +26,19 @@ export function FixturesList({fixtures, leagues}: FixturesListProps) {
{filteredLeagues.map((league) => (
<div key={league.id} className="w-100">
<div className="d-flex mb-3 mt-4 align-items-center">
<img src={league.logo} className="me-2" height={25}></img>
<img src={league.logo} alt="logo" className="me-2" height={25}></img>
<div>{league.name}</div>
</div>
<>
{fixtures.map(
(fixture) =>
fixture.leagueId === league.id && (
<FixtureRow key={fixture.id} fixture={fixture}></FixtureRow>
)
fixture.leagueId === league.id && <FixtureRow key={fixture.id} fixture={fixture} />
)}
</>
</div>
))}
</div>
);
}

export default FixturesList;
20 changes: 9 additions & 11 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
import {useEffect, useState} from 'react';

import _logoBlack from '../assets/top90logo-black.avif';
import _logoWhite from '../assets/top90logo-white.avif';
import logoBlack from '../assets/top90logo-black.avif';
import logoWhite from '../assets/top90logo-white.avif';

interface HeaderProps {
selectedTheme: string;
onClick?: () => void;
}

export function Header({selectedTheme, onClick}: HeaderProps) {
const [logo, setLogo] = useState(_logoBlack);
const DARK = 'dark';

function getLogo(theme: string) {
if (theme === 'dark') {
return _logoWhite;
}
return _logoBlack;
}
function Header({selectedTheme, onClick}: HeaderProps) {
const [logo, setLogo] = useState(logoBlack);

useEffect(() => {
setLogo(getLogo(selectedTheme));
const logoToDisplay = selectedTheme === DARK ? logoWhite : logoBlack;
setLogo(logoToDisplay);
}, [selectedTheme]);

return (
Expand All @@ -28,3 +24,5 @@ export function Header({selectedTheme, onClick}: HeaderProps) {
</div>
);
}

export default Header;
15 changes: 12 additions & 3 deletions src/components/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,19 @@ function Select({
const currentOption = options?.find((option) => option.value == value);
const [searchText, setSearchText] = useState('');

const dropdownId = `dropdown-${Math.random().toString(36).substring(7)}`;

return (
<div className="form-group text-muted" style={{minWidth: '0px', width: '100%'}}>
<label className="form-label" htmlFor="select">
<label className="form-label" htmlFor={dropdownId}>
{label}
</label>
<div className="d-flex">
<div className="dropdown mr-1 w-100">
<button
type="button"
className="btn btn-secondary dropdown-toggle w-100 text-left shadow-sm rounded-pill border-none text-start text-muted overflow-hidden"
id="dropdown"
id={dropdownId}
data-bs-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
Expand All @@ -52,7 +54,8 @@ function Select({
<div
className="dropdown-menu overflow-auto w-100 pt-0 shadow"
style={{maxHeight: '300px'}}
aria-labelledby="dropdown"
aria-labelledby={dropdownId}
role="menu"
>
{showSearchInput && (
<div style={{padding: '7px 5px'}}>
Expand All @@ -64,13 +67,17 @@ function Select({
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
autoComplete="off"
aria-label="Search"
role="search"
/>
</div>
)}
{showAllOption && (
<button
className={`dropdown-item ${!currentOption ? 'active' : ''}`}
onClick={() => onChange(0)}
aria-selected={!currentOption}
role="option"
>
All
</button>
Expand All @@ -94,6 +101,8 @@ function Select({
}`}
key={option.value}
onClick={() => onChange(option.value)}
aria-selected={currentOption && currentOption.value === option.value}
role="option"
>
{option.displayName}
</button>
Expand Down
80 changes: 38 additions & 42 deletions src/components/Video.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import {useEffect, useState} from 'react';
import {API_BASE_URL} from '../lib/api/core';
import {Goal} from '../lib/api/goals';

import {useEffect, useState} from 'react';
import {cloudfrontEnabled} from '../lib/utils';

const defaultButtonText = 'Copy Link';
const clickedButtonText = 'Link Copied';
const DEFAULT_BUTTON_TEXT = 'Copy Link';
const CLICKED_BUTTON_TEXT = 'Link Copied';
const CLOUDFRONT_BASE_URL = 'https://s3-redditsoccergoals.top90.io/';
const REDDIT_COMMENTS_BASE_URL = 'https://www.reddit.com/r/soccer/comments/';

export function Video({goal}: {goal: Goal}) {
const [buttonText, setButtonText] = useState(defaultButtonText);
function Video({goal}: {goal: Goal}) {
const [buttonText, setButtonText] = useState(DEFAULT_BUTTON_TEXT);
const [timer, setTimer] = useState<ReturnType<typeof setTimeout>>();
const [disableButton, setDisableButton] = useState(false);

Expand All @@ -22,68 +23,63 @@ export function Video({goal}: {goal: Goal}) {
const goalUrl = `${API_BASE_URL}/message_preview/${goal.id}`;
navigator.clipboard.writeText(goalUrl);

setButtonText(clickedButtonText);
setButtonText(CLICKED_BUTTON_TEXT);
setDisableButton(true);

const timeout = setTimeout(() => {
setButtonText(defaultButtonText);
setButtonText(DEFAULT_BUTTON_TEXT);
setDisableButton(false);
}, 3000);

setTimer(timeout);
}

function numDaysAgo(date: Date) {
function formatDateAgo(date: Date): string {
const now = new Date();
const timeDifference = now.getTime() - date.getTime();
const dayDifference = timeDifference / (1000 * 3600 * 24);

return Math.trunc(dayDifference);
const hourDifference = timeDifference / (1000 * 3600);
const minuteDifference = timeDifference / (1000 * 60);

if (dayDifference >= 1) {
const numDays = Math.trunc(dayDifference);
return numDays === 1 ? '1 day ago' : `${numDays} days ago`;
} else if (hourDifference >= 1) {
const numHours = Math.trunc(hourDifference);
return numHours === 1 ? '1 hour ago' : `${numHours} hours ago`;
} else if (minuteDifference >= 1) {
const numMinutes = Math.trunc(minuteDifference);
return numMinutes === 1 ? '1 minute ago' : `${numMinutes} minutes ago`;
} else {
return 'Just now';
}
}

function goToRedditPost(postId: string) {
window.open(`https://www.reddit.com/r/soccer/comments/${postId}`, '_blank');
function navigateToRedditPost(postId: string) {
window.open(`${REDDIT_COMMENTS_BASE_URL}${postId}`, '_blank');
}

function postId(fullName: string) {
function getPostId(fullName: string) {
return fullName.substring(3, fullName.length);
}

function daysAgoText(num: number) {
if (num < 1) {
return '';
} else if (num === 1) {
return '1 day ago';
} else {
return num + ' days ago';
}
}

function cloudfrontUrl(s3Key: string) {
return 'https://s3-redditsoccergoals.top90.io/' + s3Key;
function getCloudfrontUrl(s3Key: string) {
return `${CLOUDFRONT_BASE_URL}${s3Key}`;
}

function thumbnailUrl(goal: Goal) {
if (cloudfrontEnabled()) {
return cloudfrontUrl(goal.thumbnailS3Key);
}

return goal.thumbnailPresignedUrl;
function getThumbnailUrl(goal: Goal) {
return cloudfrontEnabled() ? getCloudfrontUrl(goal.thumbnailS3Key) : goal.thumbnailPresignedUrl;
}

function videoUrl(goal: Goal) {
if (cloudfrontEnabled()) {
return cloudfrontUrl(goal.s3ObjectKey);
}

return goal.presignedUrl;
function getVideoUrl(goal: Goal) {
return cloudfrontEnabled() ? getCloudfrontUrl(goal.s3ObjectKey) : goal.presignedUrl;
}

return (
<div key={goal.redditPostTitle}>
<h6>{goal.redditPostTitle}</h6>
<video poster={thumbnailUrl(goal)} className="shadow-sm w-100" controls muted={true}>
<source src={videoUrl(goal)} type="video/mp4"></source>
<video poster={getThumbnailUrl(goal)} className="shadow-sm w-100" controls muted={true}>
<source src={getVideoUrl(goal)} type="video/mp4"></source>
</video>
<div className="d-flex justify-content-between align-items-center">
<div>
Expand All @@ -95,14 +91,14 @@ export function Video({goal}: {goal: Goal}) {
{buttonText}
</button>
<button
onClick={() => goToRedditPost(postId(goal.redditFullname))}
onClick={() => navigateToRedditPost(getPostId(goal.redditFullname))}
className="btn btn-outline-secondary btn-sm border-0"
>
Comments
</button>
</div>
<div style={{fontSize: '14px'}} className="text-muted me-2">
{daysAgoText(numDaysAgo(new Date(goal.createdAt)))}
{formatDateAgo(new Date(goal.redditPostCreatedAt))}
</div>
</div>
</div>
Expand Down
Loading

0 comments on commit ab25e4d

Please sign in to comment.