mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-05 17:58:55 +00:00
many updates to profile cards
This commit is contained in:
1 parent
26a7c2ba63
commit
3fac95abf0
20 files changed
+647
-108
No files matched your search
@@ -0,0 +1,3 @@
|
||||
wave {
|
||||
z-index: 2;
|
||||
}
|
||||
@@ -3,15 +3,21 @@ import WaveSurfer from "wavesurfer.js";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import { faPlay, faPause } from "@fortawesome/pro-solid-svg-icons";
|
||||
import Button from "components/common/Button";
|
||||
import "./AudioPlayer.scss";
|
||||
import { useAudioPlayerContext } from "./AudioPlayerContext";
|
||||
import useOnScreen from "hooks/useOnScreen";
|
||||
|
||||
interface AudioPlayerProps {
|
||||
src: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export default function AudioPlayer({ src }: AudioPlayerProps) {
|
||||
export default function AudioPlayer({ src, id }: AudioPlayerProps) {
|
||||
const { currentPlayingId, setCurrentPlayingId } = useAudioPlayerContext();
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const waveformRef = useRef<HTMLDivElement | null>(null);
|
||||
const waveSurferRef = useRef<WaveSurfer | null>(null);
|
||||
const isPlayerOnScreen = useOnScreen(waveformRef); //This stops the audio from playing when the player is not on screen
|
||||
|
||||
useEffect(() => {
|
||||
const audioLink = new BucketConfig().getGcsUrl(src);
|
||||
@@ -38,6 +44,7 @@ export default function AudioPlayer({ src }: AudioPlayerProps) {
|
||||
|
||||
waveSurferRef.current.on("play", () => {
|
||||
setIsPlaying(true);
|
||||
setCurrentPlayingId(id);
|
||||
});
|
||||
|
||||
waveSurferRef.current.on("finish", () => {
|
||||
@@ -51,10 +58,26 @@ export default function AudioPlayer({ src }: AudioPlayerProps) {
|
||||
waveSurferRef.current.destroy();
|
||||
}
|
||||
};
|
||||
}, [src]);
|
||||
}, [src, setCurrentPlayingId, id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentPlayingId !== id && isPlaying) {
|
||||
setIsPlaying(false);
|
||||
waveSurferRef.current?.pause();
|
||||
}
|
||||
}, [currentPlayingId, id, isPlaying]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPlayerOnScreen && isPlaying) {
|
||||
waveSurferRef.current?.pause();
|
||||
}
|
||||
}, [isPlayerOnScreen, isPlaying]);
|
||||
|
||||
const togglePlayPause = () => {
|
||||
if (waveSurferRef.current) {
|
||||
if (!isPlaying) {
|
||||
setCurrentPlayingId(id);
|
||||
}
|
||||
waveSurferRef.current.playPause();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import React, { createContext, useContext, useState } from "react";
|
||||
|
||||
interface AudioPlayerContextProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const AudioPlayerContext = createContext({
|
||||
currentPlayingId: null,
|
||||
setCurrentPlayingId: (id: any) => {},
|
||||
});
|
||||
|
||||
export const useAudioPlayerContext = () => useContext(AudioPlayerContext);
|
||||
|
||||
export default function AudioPlayerProvider({
|
||||
children,
|
||||
}: AudioPlayerContextProps) {
|
||||
const [currentPlayingId, setCurrentPlayingId] = useState(null);
|
||||
|
||||
return (
|
||||
<AudioPlayerContext.Provider
|
||||
value={{ currentPlayingId, setCurrentPlayingId }}
|
||||
>
|
||||
{children}
|
||||
</AudioPlayerContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -15,24 +15,24 @@
|
||||
margin-bottom: 10px;
|
||||
|
||||
&.shadow {
|
||||
box-shadow: 0 0 12px rgba($bg, 0.2);
|
||||
box-shadow: 0 0 12px rgba($bg, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
$colors: (
|
||||
"gray": #e0e0e0,
|
||||
"red": #ff0000,
|
||||
"orange": #ffa500,
|
||||
"yellow": #ffff00,
|
||||
"lime": #00ff00,
|
||||
"green": #008000,
|
||||
"teal": #008080,
|
||||
"turquoise": #40e0d0,
|
||||
"aqua": #00ffff,
|
||||
"blue": #0000ff,
|
||||
"ultramarine": #4169e1,
|
||||
"purple": #800080,
|
||||
"pink": #ffc0cb,
|
||||
"gray": #5b7282,
|
||||
"red": #d91f11,
|
||||
"orange": #e86427,
|
||||
"yellow": #f5c518,
|
||||
"lime": #3c7d0e,
|
||||
"green": #077d55,
|
||||
"teal": #167b7d,
|
||||
"turquoise": #067a91,
|
||||
"aqua": #0073ba,
|
||||
"blue": #186ade,
|
||||
"ultramarine": #535fe8,
|
||||
"purple": #8f49de,
|
||||
"pink": #cc1d92,
|
||||
);
|
||||
|
||||
@each $color, $value in $colors {
|
||||
|
||||
@@ -4,10 +4,19 @@ import "./Badge.scss";
|
||||
interface BadgeProps {
|
||||
label: string;
|
||||
color: string;
|
||||
shadow?: boolean;
|
||||
overlay?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function Badge({ label, color, shadow = false }: BadgeProps) {
|
||||
const badgeClass = `fy-badge badge-${color} ${shadow ? shadow : null} mb-0`;
|
||||
export default function Badge({
|
||||
label,
|
||||
color,
|
||||
overlay = false,
|
||||
className = "",
|
||||
}: BadgeProps) {
|
||||
const badgeClass = `fy-badge badge-${color} ${
|
||||
overlay ? "shadow" : ""
|
||||
} mb-0 ${className}`.trim();
|
||||
|
||||
return <span className={badgeClass}>{label}</span>;
|
||||
}
|
||||
@@ -4,17 +4,17 @@ import Card from "../Card";
|
||||
import AudioPlayer from "components/common/AudioPlayer";
|
||||
import useTimeAgo from "hooks/useTimeAgo";
|
||||
import Badge from "components/common/Badge";
|
||||
import LikeButton from "components/common/LikeButton";
|
||||
|
||||
interface AudioCardProps {
|
||||
data: any;
|
||||
to: string;
|
||||
}
|
||||
|
||||
export default function AudioCard({ data, to }: AudioCardProps) {
|
||||
export default function AudioCard({ data }: AudioCardProps) {
|
||||
const history = useHistory();
|
||||
|
||||
const handleCardClick = () => {
|
||||
history.push(to);
|
||||
history.push(`/media/${data.token}`);
|
||||
};
|
||||
|
||||
const handleInnerClick = (event: any) => {
|
||||
@@ -23,16 +23,28 @@ export default function AudioCard({ data, to }: AudioCardProps) {
|
||||
|
||||
const timeAgo = useTimeAgo(data.created_at);
|
||||
|
||||
const handleLike = async (data: any) => {
|
||||
console.log(`The item is now ${data.isLiked ? "liked" : "not liked"}.`);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card padding={true} onClick={handleCardClick}>
|
||||
<div className="mb-3">
|
||||
<Badge label="Audio" color="teal" />
|
||||
<div className="d-flex align-items-center">
|
||||
<div className="flex-grow-1">
|
||||
<Badge label="Audio" color="teal" />
|
||||
</div>
|
||||
<div onClick={handleInnerClick}>
|
||||
<LikeButton onToggle={handleLike} likeCount={data.likes} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 className="fw-semibold text-white mb-1 mt-3">{data.weight_name}</h6>
|
||||
<p className="fs-7 opacity-75">{timeAgo}</p>
|
||||
</div>
|
||||
|
||||
<div onClick={handleInnerClick}>
|
||||
<AudioPlayer src={data.public_bucket_path} />
|
||||
<AudioPlayer src={data.public_bucket_path} id={data.token} />
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -3,21 +3,29 @@ import { useHistory } from "react-router-dom";
|
||||
import Card from "../Card";
|
||||
import useTimeAgo from "hooks/useTimeAgo";
|
||||
import Badge from "components/common/Badge";
|
||||
import LikeButton from "components/common/LikeButton";
|
||||
|
||||
interface ImageCardProps {
|
||||
data: any;
|
||||
to: string;
|
||||
}
|
||||
|
||||
export default function ImageCard({ data, to }: ImageCardProps) {
|
||||
export default function ImageCard({ data }: ImageCardProps) {
|
||||
const history = useHistory();
|
||||
|
||||
const handleCardClick = () => {
|
||||
history.push(to);
|
||||
history.push(`/media/${data.token}`);
|
||||
};
|
||||
|
||||
const handleInnerClick = (event: any) => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
const timeAgo = useTimeAgo(data.created_at);
|
||||
|
||||
const handleLike = async (data: any) => {
|
||||
console.log(`The item is now ${data.isLiked ? "liked" : "not liked"}.`);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card padding={false} onClick={handleCardClick}>
|
||||
<img
|
||||
@@ -27,7 +35,20 @@ export default function ImageCard({ data, to }: ImageCardProps) {
|
||||
/>
|
||||
<div className="card-img-overlay">
|
||||
<div className="card-img-gradient" />
|
||||
<Badge label="Image" color="ultramarine" />
|
||||
|
||||
<div className="d-flex align-items-center">
|
||||
<div className="flex-grow-1">
|
||||
<Badge label="Image" color="ultramarine" overlay={true} />
|
||||
</div>
|
||||
<div onClick={handleInnerClick}>
|
||||
<LikeButton
|
||||
onToggle={handleLike}
|
||||
likeCount={data.likes}
|
||||
overlay={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card-img-overlay-text">
|
||||
<div>
|
||||
<h6 className="fw-semibold text-white mb-1">{data.weight_name}</h6>
|
||||
|
||||
@@ -5,21 +5,29 @@ import useTimeAgo from "hooks/useTimeAgo";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faPlayCircle } from "@fortawesome/pro-solid-svg-icons";
|
||||
import Badge from "components/common/Badge";
|
||||
import LikeButton from "components/common/LikeButton";
|
||||
|
||||
interface VideoCardProps {
|
||||
data: any;
|
||||
to: string;
|
||||
}
|
||||
|
||||
export default function VideoCard({ data, to }: VideoCardProps) {
|
||||
export default function VideoCard({ data }: VideoCardProps) {
|
||||
const history = useHistory();
|
||||
|
||||
const handleCardClick = () => {
|
||||
history.push(to);
|
||||
history.push(`/media/${data.token}`);
|
||||
};
|
||||
|
||||
const handleInnerClick = (event: any) => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
const timeAgo = useTimeAgo(data.created_at);
|
||||
|
||||
const handleLike = async (data: any) => {
|
||||
console.log(`The item is now ${data.isLiked ? "liked" : "not liked"}.`);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card padding={false} onClick={handleCardClick}>
|
||||
<img
|
||||
@@ -29,7 +37,19 @@ export default function VideoCard({ data, to }: VideoCardProps) {
|
||||
/>
|
||||
<div className="card-img-overlay">
|
||||
<div className="card-img-gradient" />
|
||||
<Badge label="Video" color="purple" />
|
||||
|
||||
<div className="d-flex align-items-center">
|
||||
<div className="flex-grow-1">
|
||||
<Badge label="Video" color="purple" overlay={true} />
|
||||
</div>
|
||||
<div onClick={handleInnerClick}>
|
||||
<LikeButton
|
||||
onToggle={handleLike}
|
||||
likeCount={data.likes}
|
||||
overlay={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<FontAwesomeIcon icon={faPlayCircle} className="card-video-play" />
|
||||
<div className="card-img-overlay-text">
|
||||
<div>
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
@import "scss/colours.scss";
|
||||
|
||||
.favorite-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: lighten($secondary, 3%);
|
||||
color: inherit;
|
||||
border: none;
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
outline: inherit;
|
||||
font-size: 16px;
|
||||
transition: 0.2s;
|
||||
padding: 3px 8px;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
&:hover {
|
||||
background: lighten($secondary, 10%);
|
||||
}
|
||||
|
||||
&:hover .favorite-number {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:hover .icon-default {
|
||||
color: $primary;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.toggled {
|
||||
background-color: rgba($primary, 0.25);
|
||||
|
||||
&:hover {
|
||||
background-color: darken(rgba($primary, 0.25), 5%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.favorite-button .icon-default {
|
||||
color: $white;
|
||||
opacity: 0.7;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.favorite-button .icon-toggled {
|
||||
color: $primary;
|
||||
animation: pulse 0.3s ease;
|
||||
}
|
||||
|
||||
.favorite-button .icon-toggled:hover {
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
.favorite-number {
|
||||
font-size: 14px;
|
||||
font-weight: 400 !important;
|
||||
opacity: 0.8;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.3);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import React, { useState } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faHeart } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { faHeart as faHeartOutline } from "@fortawesome/pro-regular-svg-icons";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import "tippy.js/dist/tippy.css";
|
||||
import "./FavoriteButton.scss";
|
||||
|
||||
interface FavoriteButtonProps {
|
||||
initialToggled?: boolean;
|
||||
onToggle: (toggled: boolean) => Promise<void>;
|
||||
FavoriteCount?: number;
|
||||
}
|
||||
|
||||
export default function FavoriteButton({
|
||||
initialToggled = false,
|
||||
onToggle,
|
||||
FavoriteCount,
|
||||
}: FavoriteButtonProps) {
|
||||
const [isToggled, setIsToggled] = useState(initialToggled);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleClick = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await onToggle(!isToggled);
|
||||
setIsToggled(!isToggled);
|
||||
} catch (error) {
|
||||
console.error("Error calling API", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonClass = isToggled ? "favorite-button toggled" : "favorite-button";
|
||||
const iconClass = isToggled ? "icon-toggled" : "icon-default";
|
||||
const toolTip = isToggled ? "Unfavorite" : "Favorite";
|
||||
|
||||
return (
|
||||
<div className="d-flex gap-2" onClick={handleClick}>
|
||||
<Tippy
|
||||
theme="fakeyou"
|
||||
content={toolTip}
|
||||
hideOnClick={false}
|
||||
trigger="mouseenter"
|
||||
delay={[500, 0]}
|
||||
offset={[0, 12]}
|
||||
>
|
||||
<button
|
||||
onClick={handleClick}
|
||||
disabled={isLoading}
|
||||
className={buttonClass}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isToggled ? faHeart : faHeartOutline}
|
||||
className={`${iconClass} me-2`}
|
||||
/>
|
||||
{FavoriteCount && <p className="favorite-number">{FavoriteCount}</p>}
|
||||
</button>
|
||||
</Tippy>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./FavoriteButton";
|
||||
@@ -0,0 +1,80 @@
|
||||
@import "scss/colours.scss";
|
||||
|
||||
.like-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: rgba(lighten($secondary, 5%), 0.9);
|
||||
color: inherit;
|
||||
border: none;
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
outline: inherit;
|
||||
font-size: 16px;
|
||||
transition: 0.2s;
|
||||
padding: 3px 8px;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
&.shadow {
|
||||
box-shadow: 0 0 12px rgba($bg, 0.4) !important;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 12px rgba($bg, 0.4) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(lighten($secondary, 10%), 0.9);
|
||||
}
|
||||
|
||||
&:hover .like-number {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:hover .icon-default {
|
||||
color: $primary;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.toggled {
|
||||
background-color: rgba(#593a48, 0.9);
|
||||
|
||||
&:hover {
|
||||
background-color: darken(rgba(#593a48, 0.9), 2%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.like-button .icon-default {
|
||||
color: $white;
|
||||
opacity: 0.7;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.like-button .icon-toggled {
|
||||
color: $primary;
|
||||
animation: pulse 0.3s ease;
|
||||
}
|
||||
|
||||
.like-button .icon-toggled:hover {
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
.like-number {
|
||||
font-size: 14px;
|
||||
font-weight: 400 !important;
|
||||
opacity: 0.8;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.3);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import React, { useState } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faHeart } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { faHeart as faHeartOutline } from "@fortawesome/pro-regular-svg-icons";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import "tippy.js/dist/tippy.css";
|
||||
import "./LikeButton.scss";
|
||||
|
||||
interface LikeButtonProps {
|
||||
initialToggled?: boolean;
|
||||
onToggle: (toggled: boolean) => Promise<void>;
|
||||
likeCount?: number;
|
||||
overlay?: boolean;
|
||||
}
|
||||
|
||||
export default function LikeButton({
|
||||
initialToggled = false,
|
||||
onToggle,
|
||||
likeCount,
|
||||
overlay,
|
||||
}: LikeButtonProps) {
|
||||
const [isToggled, setIsToggled] = useState(initialToggled);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleClick = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await onToggle(!isToggled);
|
||||
setIsToggled(!isToggled);
|
||||
} catch (error) {
|
||||
console.error("Error calling API", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonClass = isToggled ? "like-button toggled" : "like-button";
|
||||
const buttonShadow = overlay ? "shadow" : "";
|
||||
const iconClass = isToggled ? "icon-toggled" : "icon-default";
|
||||
const toolTip = isToggled ? "Unlike" : "Like";
|
||||
|
||||
return (
|
||||
<div className="d-flex gap-2" onClick={handleClick}>
|
||||
<Tippy
|
||||
theme="fakeyou"
|
||||
content={toolTip}
|
||||
hideOnClick={false}
|
||||
trigger="mouseenter"
|
||||
delay={[500, 0]}
|
||||
offset={[0, 12]}
|
||||
>
|
||||
<button
|
||||
onClick={handleClick}
|
||||
disabled={isLoading}
|
||||
className={`${buttonClass} ${buttonShadow}`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isToggled ? faHeart : faHeartOutline}
|
||||
className={`${iconClass} me-2`}
|
||||
/>
|
||||
{likeCount && <p className="like-number">{likeCount}</p>}
|
||||
</button>
|
||||
</Tippy>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./LikeButton";
|
||||
@@ -2,11 +2,14 @@ import React, { useEffect, useState } from "react";
|
||||
import { NavLink, useLocation } from "react-router-dom";
|
||||
import { useSpring, a } from "@react-spring/web";
|
||||
import "./Tabs.scss";
|
||||
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
interface TabProps {
|
||||
to: string;
|
||||
label: string;
|
||||
content?: React.ReactNode;
|
||||
icon?: IconDefinition;
|
||||
}
|
||||
|
||||
interface TabsProps {
|
||||
@@ -17,7 +20,7 @@ interface TabContentProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
function Tab({ to, label, onClick }: TabProps & { onClick: () => void }) {
|
||||
function Tab({ to, label, icon, onClick }: TabProps & { onClick: () => void }) {
|
||||
return (
|
||||
<li className="nav-item">
|
||||
<NavLink
|
||||
@@ -26,6 +29,7 @@ function Tab({ to, label, onClick }: TabProps & { onClick: () => void }) {
|
||||
activeClassName="active"
|
||||
onClick={onClick}
|
||||
>
|
||||
{icon && <FontAwesomeIcon icon={icon} className="me-2" />}
|
||||
{label}
|
||||
</NavLink>
|
||||
</li>
|
||||
@@ -70,6 +74,7 @@ function Tabs({ tabs }: TabsProps) {
|
||||
to={tab.to}
|
||||
label={tab.label}
|
||||
onClick={() => handleTabClick(tab.to)}
|
||||
icon={tab.icon}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./useOnScreen";
|
||||
@@ -0,0 +1,35 @@
|
||||
import { useEffect, useState, RefObject } from "react";
|
||||
|
||||
const useOnScreen = (
|
||||
ref: RefObject<HTMLElement>,
|
||||
rootMargin: string = "0px"
|
||||
) => {
|
||||
const [isIntersecting, setIntersecting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const currentRef = ref.current;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
setIntersecting(entry.isIntersecting);
|
||||
},
|
||||
{
|
||||
rootMargin,
|
||||
}
|
||||
);
|
||||
|
||||
if (currentRef) {
|
||||
observer.observe(currentRef);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (currentRef) {
|
||||
observer.unobserve(currentRef);
|
||||
}
|
||||
};
|
||||
}, [ref, rootMargin]);
|
||||
|
||||
return isIntersecting;
|
||||
};
|
||||
|
||||
export default useOnScreen;
|
||||
@@ -34,7 +34,11 @@ import Tippy from "@tippyjs/react";
|
||||
import "tippy.js/dist/tippy.css";
|
||||
|
||||
import { usePrefixedDocumentTitle } from "../../../../../common/UsePrefixedDocumentTitle";
|
||||
import { faCalendarAlt } from "@fortawesome/pro-solid-svg-icons";
|
||||
import {
|
||||
faCalendarAlt,
|
||||
faLayerGroup,
|
||||
faPhotoFilmMusic,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
// import { CommentComponent } from "../../../_common/comments/CommentComponent";
|
||||
import { PosthogClient } from "@storyteller/components/src/analytics/PosthogClient";
|
||||
import { Panel } from "components/common";
|
||||
@@ -154,7 +158,7 @@ function NewProfilePage(this: any, props: Props) {
|
||||
|
||||
// Mods shouldn't edit preferences.
|
||||
const buttonLabel = props.sessionWrapper.userTokenMatches(
|
||||
userData.user_token,
|
||||
userData.user_token
|
||||
)
|
||||
? "Edit Profile & Preferences"
|
||||
: "Edit Profile";
|
||||
@@ -208,7 +212,7 @@ function NewProfilePage(this: any, props: Props) {
|
||||
>
|
||||
<FontAwesomeIcon icon={faCalendarAlt} className="me-2" />
|
||||
<p className="fw-normal">Joined {joinDate}</p>
|
||||
</div>,
|
||||
</div>
|
||||
);
|
||||
|
||||
if (userData.website_url !== undefined && userData.website_url !== null) {
|
||||
@@ -300,7 +304,7 @@ function NewProfilePage(this: any, props: Props) {
|
||||
>
|
||||
<FontAwesomeIcon icon={faDiscord} />
|
||||
</a>
|
||||
</Tippy>,
|
||||
</Tippy>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -351,16 +355,19 @@ function NewProfilePage(this: any, props: Props) {
|
||||
to: `/profile/${username}/media`,
|
||||
label: "Media",
|
||||
content: <MediaTab />,
|
||||
icon: faPhotoFilmMusic,
|
||||
},
|
||||
{
|
||||
to: `/profile/${username}/weights`,
|
||||
label: "Weights",
|
||||
content: <div>Weights</div>,
|
||||
icon: faLayerGroup,
|
||||
},
|
||||
{
|
||||
to: `/profile/${username}/favorites`,
|
||||
label: "Favorites",
|
||||
content: <div>Favorites</div>,
|
||||
icon: faStar,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -9,17 +9,10 @@ import VideoCard from "components/common/Card/VideoCard";
|
||||
import Panel from "components/common/Panel";
|
||||
import { Select } from "components/common/Inputs/Inputs";
|
||||
import { faFilter } from "@fortawesome/pro-solid-svg-icons";
|
||||
import AudioPlayerProvider from "components/common/AudioPlayer/AudioPlayerContext";
|
||||
|
||||
export default function MediaTab() {
|
||||
const [data, setData] = useState(mockData);
|
||||
// const [hasMore, setHasMore] = useState(true);
|
||||
|
||||
// const fetchData = () => {
|
||||
// setTimeout(() => {
|
||||
// setData((prevData) => [...prevData, ...mockData]);
|
||||
// setHasMore(false);
|
||||
// }, 1500);
|
||||
// };
|
||||
|
||||
const options = [
|
||||
{ value: "all", label: "All Media" },
|
||||
@@ -38,33 +31,34 @@ export default function MediaTab() {
|
||||
isSearchable={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<MasonryGrid>
|
||||
{data.map((data, index) => {
|
||||
let card;
|
||||
switch (data.media_type) {
|
||||
case "audio":
|
||||
card = <AudioCard key={index} data={data} to="/" />;
|
||||
break;
|
||||
case "image":
|
||||
card = <ImageCard key={index} data={data} to="/" />;
|
||||
break;
|
||||
case "video":
|
||||
card = <VideoCard key={index} data={data} to="/" />;
|
||||
break;
|
||||
default:
|
||||
card = <div key={index}>Unsupported media type</div>;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="col-12 col-sm-6 col-xl-4 col-xxl-3 grid-item"
|
||||
>
|
||||
{card}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</MasonryGrid>
|
||||
<AudioPlayerProvider>
|
||||
<MasonryGrid>
|
||||
{data.map((data, index) => {
|
||||
let card;
|
||||
switch (data.media_type) {
|
||||
case "audio":
|
||||
card = <AudioCard key={index} data={data} />;
|
||||
break;
|
||||
case "image":
|
||||
card = <ImageCard key={index} data={data} />;
|
||||
break;
|
||||
case "video":
|
||||
card = <VideoCard key={index} data={data} />;
|
||||
break;
|
||||
default:
|
||||
card = <div key={index}>Unsupported media type</div>;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="col-12 col-sm-6 col-xl-4 col-xxl-3 grid-item"
|
||||
>
|
||||
{card}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</MasonryGrid>
|
||||
</AudioPlayerProvider>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
@@ -1,92 +1,192 @@
|
||||
const mockData = [
|
||||
let mockData = [
|
||||
{
|
||||
id: 1,
|
||||
token: "token1",
|
||||
weight_name: "Queen Elizabeth",
|
||||
media_type: "audio",
|
||||
public_bucket_path: "/path/to/audio.mp3",
|
||||
favorites: 150,
|
||||
liked: true,
|
||||
public_bucket_path:
|
||||
"/media/s/p/x/d/2/spxd2xr7nt7zdezyhp61h00kxatr18pf/fakeyou_rvc_spxd2xr7nt7zdezyhp61h00kxatr18pf.wav",
|
||||
likes: 150,
|
||||
isLiked: true,
|
||||
created_at: "2023-08-10T09:00:00.000Z",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
token: "token2",
|
||||
weight_name: "Monster House",
|
||||
media_type: "image",
|
||||
public_bucket_path: "/images/dummy-image.jpg",
|
||||
favorites: 200,
|
||||
liked: false,
|
||||
likes: 200,
|
||||
isLiked: false,
|
||||
created_at: "2023-08-10T09:00:00.000Z",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
token: "token3",
|
||||
weight_name: "Ronaldo",
|
||||
media_type: "audio",
|
||||
public_bucket_path: "/path/to/audio.mp3",
|
||||
favorites: 200,
|
||||
liked: false,
|
||||
public_bucket_path:
|
||||
"/media/s/p/x/d/2/spxd2xr7nt7zdezyhp61h00kxatr18pf/fakeyou_rvc_spxd2xr7nt7zdezyhp61h00kxatr18pf.wav",
|
||||
likes: 200,
|
||||
isLiked: false,
|
||||
created_at: "2023-08-10T09:00:00.000Z",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
token: "token4",
|
||||
weight_name: "Desert",
|
||||
media_type: "video",
|
||||
public_bucket_path: "/images/dummy-video.jpg",
|
||||
favorites: 150,
|
||||
liked: true,
|
||||
likes: 150,
|
||||
isLiked: true,
|
||||
created_at: "2023-08-10T09:00:00.000Z",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
token: "token5",
|
||||
weight_name: "3D Cat",
|
||||
media_type: "image",
|
||||
public_bucket_path: "/images/dummy-image-2.jpg",
|
||||
favorites: 200,
|
||||
liked: false,
|
||||
likes: 200,
|
||||
isLiked: false,
|
||||
created_at: "2023-08-10T09:00:00.000Z",
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
token: "token6",
|
||||
weight_name: "Superman",
|
||||
media_type: "audio",
|
||||
public_bucket_path: "/path/to/image.jpg",
|
||||
favorites: 200,
|
||||
liked: false,
|
||||
public_bucket_path:
|
||||
"/media/s/p/x/d/2/spxd2xr7nt7zdezyhp61h00kxatr18pf/fakeyou_rvc_spxd2xr7nt7zdezyhp61h00kxatr18pf.wav",
|
||||
likes: 200,
|
||||
isLiked: false,
|
||||
created_at: "2023-08-10T09:00:00.000Z",
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
token: "token7",
|
||||
weight_name: "Son Goku",
|
||||
media_type: "audio",
|
||||
public_bucket_path: "/path/to/audio.mp3",
|
||||
favorites: 150,
|
||||
liked: true,
|
||||
public_bucket_path:
|
||||
"/media/s/p/x/d/2/spxd2xr7nt7zdezyhp61h00kxatr18pf/fakeyou_rvc_spxd2xr7nt7zdezyhp61h00kxatr18pf.wav",
|
||||
likes: 150,
|
||||
isLiked: true,
|
||||
created_at: "2023-08-10T09:00:00.000Z",
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
token: "token8",
|
||||
weight_name: "Astronaut in Space",
|
||||
media_type: "image",
|
||||
public_bucket_path: "/images/dummy-image-3.jpg",
|
||||
favorites: 150,
|
||||
liked: true,
|
||||
likes: 150,
|
||||
isLiked: true,
|
||||
created_at: "2023-08-10T09:00:00.000Z",
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
token: "token9",
|
||||
weight_name: "Monokuma",
|
||||
media_type: "audio",
|
||||
public_bucket_path: "/path/to/audio.mp3",
|
||||
favorites: 150,
|
||||
liked: true,
|
||||
public_bucket_path:
|
||||
"/media/s/p/x/d/2/spxd2xr7nt7zdezyhp61h00kxatr18pf/fakeyou_rvc_spxd2xr7nt7zdezyhp61h00kxatr18pf.wav",
|
||||
likes: 150,
|
||||
isLiked: true,
|
||||
created_at: "2023-08-10T09:00:00.000Z",
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
token: "token10",
|
||||
weight_name: "Watery Landscape",
|
||||
media_type: "image",
|
||||
public_bucket_path: "/images/dummy-image-4.jpg",
|
||||
favorites: 150,
|
||||
liked: true,
|
||||
likes: 150,
|
||||
isLiked: true,
|
||||
created_at: "2023-08-10T09:00:00.000Z",
|
||||
},
|
||||
{
|
||||
token: "token1",
|
||||
weight_name: "Queen Elizabeth",
|
||||
media_type: "audio",
|
||||
public_bucket_path:
|
||||
"/media/s/p/x/d/2/spxd2xr7nt7zdezyhp61h00kxatr18pf/fakeyou_rvc_spxd2xr7nt7zdezyhp61h00kxatr18pf.wav",
|
||||
likes: 150,
|
||||
isLiked: true,
|
||||
created_at: "2023-08-10T09:00:00.000Z",
|
||||
},
|
||||
{
|
||||
token: "token2",
|
||||
weight_name: "Monster House",
|
||||
media_type: "image",
|
||||
public_bucket_path: "/images/dummy-image.jpg",
|
||||
likes: 200,
|
||||
isLiked: false,
|
||||
created_at: "2023-08-10T09:00:00.000Z",
|
||||
},
|
||||
{
|
||||
token: "token3",
|
||||
weight_name: "Ronaldo",
|
||||
media_type: "audio",
|
||||
public_bucket_path:
|
||||
"/media/s/p/x/d/2/spxd2xr7nt7zdezyhp61h00kxatr18pf/fakeyou_rvc_spxd2xr7nt7zdezyhp61h00kxatr18pf.wav",
|
||||
likes: 200,
|
||||
isLiked: false,
|
||||
created_at: "2023-08-10T09:00:00.000Z",
|
||||
},
|
||||
{
|
||||
token: "token4",
|
||||
weight_name: "Desert",
|
||||
media_type: "video",
|
||||
public_bucket_path: "/images/dummy-video.jpg",
|
||||
likes: 150,
|
||||
isLiked: true,
|
||||
created_at: "2023-08-10T09:00:00.000Z",
|
||||
},
|
||||
{
|
||||
token: "token5",
|
||||
weight_name: "3D Cat",
|
||||
media_type: "image",
|
||||
public_bucket_path: "/images/dummy-image-2.jpg",
|
||||
likes: 200,
|
||||
isLiked: false,
|
||||
created_at: "2023-08-10T09:00:00.000Z",
|
||||
},
|
||||
{
|
||||
token: "token6",
|
||||
weight_name: "Superman",
|
||||
media_type: "audio",
|
||||
public_bucket_path:
|
||||
"/media/s/p/x/d/2/spxd2xr7nt7zdezyhp61h00kxatr18pf/fakeyou_rvc_spxd2xr7nt7zdezyhp61h00kxatr18pf.wav",
|
||||
likes: 200,
|
||||
isLiked: false,
|
||||
created_at: "2023-08-10T09:00:00.000Z",
|
||||
},
|
||||
{
|
||||
token: "token7",
|
||||
weight_name: "Son Goku",
|
||||
media_type: "audio",
|
||||
public_bucket_path:
|
||||
"/media/s/p/x/d/2/spxd2xr7nt7zdezyhp61h00kxatr18pf/fakeyou_rvc_spxd2xr7nt7zdezyhp61h00kxatr18pf.wav",
|
||||
likes: 150,
|
||||
isLiked: true,
|
||||
created_at: "2023-08-10T09:00:00.000Z",
|
||||
},
|
||||
{
|
||||
token: "token8",
|
||||
weight_name: "Astronaut in Space",
|
||||
media_type: "image",
|
||||
public_bucket_path: "/images/dummy-image-3.jpg",
|
||||
likes: 150,
|
||||
isLiked: true,
|
||||
created_at: "2023-08-10T09:00:00.000Z",
|
||||
},
|
||||
{
|
||||
token: "token9",
|
||||
weight_name: "Monokuma",
|
||||
media_type: "audio",
|
||||
public_bucket_path:
|
||||
"/media/s/p/x/d/2/spxd2xr7nt7zdezyhp61h00kxatr18pf/fakeyou_rvc_spxd2xr7nt7zdezyhp61h00kxatr18pf.wav",
|
||||
likes: 150,
|
||||
isLiked: true,
|
||||
created_at: "2023-08-10T09:00:00.000Z",
|
||||
},
|
||||
{
|
||||
token: "token10",
|
||||
weight_name: "Watery Landscape",
|
||||
media_type: "image",
|
||||
public_bucket_path: "/images/dummy-image-4.jpg",
|
||||
likes: 150,
|
||||
isLiked: true,
|
||||
created_at: "2023-08-10T09:00:00.000Z",
|
||||
},
|
||||
];
|
||||
|
||||
Reference in new issue
Block a user