mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-05 09:48:44 +00:00
Merge branch 'master' into bflat-fy-new-profile
This commit is contained in:
47 files changed
+67
-2564
No files matched your search
@@ -15,7 +15,6 @@
|
||||
"@fortawesome/pro-regular-svg-icons": "^6.2.1",
|
||||
"@fortawesome/pro-solid-svg-icons": "^6.2.1",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@react-spring/types": "^9.7.3",
|
||||
"@react-spring/web": "^9.7.3",
|
||||
"@storyteller/components": "0.1.0",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
@@ -27,11 +26,8 @@
|
||||
"@types/google.analytics": "^0.0.42",
|
||||
"@types/gtag.js": "^0.0.12",
|
||||
"@types/howler": "^2.1.2",
|
||||
"@types/imagesloaded": "^4.1.6",
|
||||
"@types/infinite-scroll": "^4.0.3",
|
||||
"@types/jest": "^24.9.1",
|
||||
"@types/js-cookie": "^3.0.1",
|
||||
"@types/masonry-layout": "^4.2.7",
|
||||
"@types/node": "^12.12.47",
|
||||
"@types/react": "^16.9.36",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
@@ -46,10 +42,7 @@
|
||||
"i18next": "^21.6.11",
|
||||
"i18next-browser-languagedetector": "^7.1.0",
|
||||
"i18next-http-backend": "^2.2.1",
|
||||
"imagesloaded": "^5.0.0",
|
||||
"infinite-scroll": "^4.0.1",
|
||||
"js-cookie": "^3.0.1",
|
||||
"masonry-layout": "^4.2.2",
|
||||
"posthog-js": "^1.73.1",
|
||||
"pretty-num": "^0.5.2",
|
||||
"query-string": "^8.1.0",
|
||||
@@ -59,7 +52,6 @@
|
||||
"react-dom": "^16.13.1",
|
||||
"react-drag-drop-files": "^2.3.9",
|
||||
"react-i18next": "^11.15.4",
|
||||
"react-infinite-scroll-component": "^6.1.0",
|
||||
"react-range": "^1.8.14",
|
||||
"react-reveal": "^1.2.2",
|
||||
"react-router-dom": "^5.2.0",
|
||||
@@ -70,7 +62,6 @@
|
||||
"react-wavesurfer.js": "^0.0.8",
|
||||
"sass": "^1.57.1",
|
||||
"typescript": "^3.7.5",
|
||||
"universal-cookie": "^6.1.1",
|
||||
"uuid": "^8.3.2",
|
||||
"wavesurfer.js": "^6.4.0",
|
||||
"zustand": "^4.3.9"
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 82 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 93 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 62 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 170 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 174 KiB |
@@ -1,3 +0,0 @@
|
||||
wave {
|
||||
z-index: 2;
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
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, 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);
|
||||
|
||||
if (!waveSurferRef.current && waveformRef.current) {
|
||||
const wavesurferInstance = WaveSurfer.create({
|
||||
container: waveformRef.current,
|
||||
height: 38,
|
||||
responsive: true,
|
||||
waveColor: "#cbcbcb",
|
||||
progressColor: "#fc8481",
|
||||
cursorColor: "#fc6b68",
|
||||
cursorWidth: 2,
|
||||
normalize: true,
|
||||
});
|
||||
|
||||
waveSurferRef.current = wavesurferInstance;
|
||||
|
||||
waveSurferRef.current.load(audioLink);
|
||||
|
||||
waveSurferRef.current.on("pause", () => {
|
||||
setIsPlaying(false);
|
||||
});
|
||||
|
||||
waveSurferRef.current.on("play", () => {
|
||||
setIsPlaying(true);
|
||||
setCurrentPlayingId(id);
|
||||
});
|
||||
|
||||
waveSurferRef.current.on("finish", () => {
|
||||
waveSurferRef.current?.pause();
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
// Cleanup when component unmounts
|
||||
if (waveSurferRef.current) {
|
||||
waveSurferRef.current.destroy();
|
||||
}
|
||||
};
|
||||
}, [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();
|
||||
}
|
||||
};
|
||||
|
||||
const playButtonIcon = isPlaying ? faPause : faPlay;
|
||||
|
||||
return (
|
||||
<div className="d-flex gap-3 align-items-center">
|
||||
<Button
|
||||
square={true}
|
||||
icon={playButtonIcon}
|
||||
onClick={togglePlayPause}
|
||||
small={true}
|
||||
/>
|
||||
<div ref={waveformRef} className="w-100" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from "./AudioPlayer";
|
||||
@@ -1,27 +0,0 @@
|
||||
@import "scss/colours.scss";
|
||||
|
||||
.fy-badge {
|
||||
font-weight: 600;
|
||||
border-radius: 0.35rem;
|
||||
font-size: 13px;
|
||||
padding: 4px 8px;
|
||||
transition: 0.2s;
|
||||
-moz-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
height: 100%;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&.shadow {
|
||||
box-shadow: 0 0 12px rgba($bg, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
@each $color, $value in $badge-colors {
|
||||
.badge-#{$color} {
|
||||
background-color: $value;
|
||||
color: contrast($value) !important;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import React from "react";
|
||||
import "./Badge.scss";
|
||||
|
||||
interface BadgeProps {
|
||||
label: string;
|
||||
color: string;
|
||||
overlay?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
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>;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from "./Badge";
|
||||
@@ -1,101 +0,0 @@
|
||||
import React from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
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";
|
||||
import FavoriteButton from "components/common/FavoriteButton";
|
||||
import Button from "components/common/Button";
|
||||
|
||||
interface AudioCardProps {
|
||||
data: any;
|
||||
type: "media" | "weights";
|
||||
}
|
||||
|
||||
export default function AudioCard({ data, type }: AudioCardProps) {
|
||||
const history = useHistory();
|
||||
|
||||
const handleCardClick = () => {
|
||||
if (type === "media") {
|
||||
history.push(`/media/${data.token}`);
|
||||
} else if (type === "weights") {
|
||||
history.push(`/weights/${data.token}`);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInnerClick = (event: any) => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
const handleUseModel = () => {
|
||||
if (type === "weights") {
|
||||
history.push(`/weights/${data.token}`);
|
||||
}
|
||||
};
|
||||
|
||||
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}>
|
||||
{type === "media" && (
|
||||
<>
|
||||
<div className="mb-3">
|
||||
<div className="d-flex align-items-center">
|
||||
<div className="flex-grow-1">
|
||||
<Badge label="Audio" color="teal" />
|
||||
</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} id={data.token} />
|
||||
</div>
|
||||
|
||||
<div className="mt-3" onClick={handleInnerClick}>
|
||||
<LikeButton onToggle={handleLike} likeCount={data.likes} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{type === "weights" && (
|
||||
<>
|
||||
<div>
|
||||
<div className="d-flex align-items-center">
|
||||
<div className="flex-grow-1">
|
||||
<Badge label="RVC" color="orange" />
|
||||
</div>
|
||||
<div onClick={handleInnerClick}>
|
||||
<FavoriteButton
|
||||
onToggle={handleLike}
|
||||
favoriteCount={data.likes}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="d-flex align-items-center mt-3">
|
||||
<div className="flex-grow-1">
|
||||
<h6 className="fw-semibold text-white mb-1">
|
||||
{data.weight_name}
|
||||
</h6>
|
||||
<p className="fs-7 opacity-75">{timeAgo}</p>
|
||||
</div>
|
||||
<div onClick={handleInnerClick}>
|
||||
<Button label="Use" small={true} onClick={handleUseModel} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from "./AudioCard";
|
||||
@@ -1,85 +0,0 @@
|
||||
@import "scss/colours.scss";
|
||||
|
||||
.card {
|
||||
background-color: $panel-inner;
|
||||
border: 1px solid $panel-inner-border;
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
transition: 0.2s;
|
||||
position: relative;
|
||||
|
||||
.card-img {
|
||||
width: 100%;
|
||||
transition: 0.4s ease;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
min-height: 300px;
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.card-video {
|
||||
width: 100%;
|
||||
transition: 0.4s ease;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
min-height: 240px;
|
||||
max-height: 240px;
|
||||
}
|
||||
|
||||
.card-img-overlay {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-video-play {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 50px;
|
||||
color: rgba($white, 0.5);
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.card-img-gradient {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 40%;
|
||||
background: linear-gradient(180deg, transparent, darken($bg, 5%));
|
||||
}
|
||||
|
||||
.card-img-overlay-text {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 1rem;
|
||||
color: $white;
|
||||
text-shadow: 0 0 10px black;
|
||||
}
|
||||
|
||||
&.card-clickable {
|
||||
&:hover {
|
||||
background-color: lighten($panel-inner, 2%);
|
||||
cursor: pointer;
|
||||
border: 1px solid lighten($panel-inner-border, 15%);
|
||||
}
|
||||
|
||||
&:hover > .card-img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
&:hover > .card-video {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
&:hover .card-video-play {
|
||||
color: rgba($white, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import React from "react";
|
||||
import "./Card.scss";
|
||||
|
||||
interface CardProps {
|
||||
padding?: boolean;
|
||||
children?: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export default function Card({ padding, children, onClick }: CardProps) {
|
||||
return (
|
||||
<div
|
||||
className={`card ${padding ? "p-3" : ""} ${
|
||||
onClick ? "card-clickable" : ""
|
||||
}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
import React from "react";
|
||||
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";
|
||||
import Button from "components/common/Button";
|
||||
import FavoriteButton from "components/common/FavoriteButton";
|
||||
|
||||
interface ImageCardProps {
|
||||
data: any;
|
||||
type: "media" | "weights";
|
||||
}
|
||||
|
||||
export default function ImageCard({ data, type }: ImageCardProps) {
|
||||
const history = useHistory();
|
||||
|
||||
const handleCardClick = () => {
|
||||
if (type === "media") {
|
||||
history.push(`/media/${data.token}`);
|
||||
} else if (type === "weights") {
|
||||
history.push(`/weights/${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}>
|
||||
{type === "media" && (
|
||||
<>
|
||||
<img
|
||||
src={data.public_bucket_path}
|
||||
alt={data.weight_name}
|
||||
className="card-img"
|
||||
/>
|
||||
<div className="card-img-overlay">
|
||||
<div className="card-img-gradient" />
|
||||
|
||||
<div className="d-flex align-items-center">
|
||||
<div className="flex-grow-1">
|
||||
<Badge label="Image" color="ultramarine" overlay={true} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card-img-overlay-text">
|
||||
<div>
|
||||
<p className="fs-7 opacity-75 mb-0">{timeAgo}</p>
|
||||
</div>
|
||||
<div onClick={handleInnerClick} className="mt-2">
|
||||
<LikeButton
|
||||
onToggle={handleLike}
|
||||
likeCount={data.likes}
|
||||
overlay={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{type === "weights" && (
|
||||
<>
|
||||
<img
|
||||
src={data.public_bucket_path}
|
||||
alt={data.weight_name}
|
||||
className="card-img"
|
||||
/>
|
||||
<div className="card-img-overlay">
|
||||
<div className="card-img-gradient" />
|
||||
|
||||
<div className="d-flex align-items-center">
|
||||
<div className="flex-grow-1">
|
||||
<Badge label="LORA" color="pink" overlay={true} />
|
||||
</div>
|
||||
<div onClick={handleInnerClick}>
|
||||
<FavoriteButton
|
||||
onToggle={handleLike}
|
||||
favoriteCount={data.likes}
|
||||
overlay={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card-img-overlay-text">
|
||||
<div className="d-flex align-items-center mt-3">
|
||||
<div className="flex-grow-1">
|
||||
<h6 className="fw-semibold text-white mb-1">
|
||||
{data.weight_name}
|
||||
</h6>
|
||||
<p className="fs-7 opacity-75">{timeAgo}</p>
|
||||
</div>
|
||||
<div onClick={handleInnerClick}>
|
||||
<Button label="Use" small={true} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from "./ImageCard";
|
||||
@@ -1,37 +0,0 @@
|
||||
import React from "react";
|
||||
import Card from "../Card";
|
||||
import Skeleton from "components/common/Skeleton";
|
||||
|
||||
interface SkeletonCardProps {}
|
||||
|
||||
export default function SkeletonCard(props: SkeletonCardProps) {
|
||||
return (
|
||||
<div className="col-12 col-sm-6 col-xl-4">
|
||||
<Card padding={true}>
|
||||
<div className="mb-3">
|
||||
<div className="d-flex align-items-center">
|
||||
<div className="flex-grow-1">
|
||||
<Skeleton type="short" />
|
||||
</div>
|
||||
<div>
|
||||
<Skeleton type="short" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 className="fw-semibold text-white mb-1 mt-3">
|
||||
<Skeleton type="medium" />
|
||||
</h6>
|
||||
<p className="fs-7 opacity-75">
|
||||
<Skeleton type="short" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 className="mb-0">
|
||||
<Skeleton type="full" />
|
||||
</h2>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from "./SkeletonCard";
|
||||
@@ -1,105 +0,0 @@
|
||||
import React from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import Card from "../Card";
|
||||
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;
|
||||
type: "media" | "weights";
|
||||
}
|
||||
|
||||
export default function VideoCard({ data, type }: VideoCardProps) {
|
||||
const history = useHistory();
|
||||
|
||||
const handleCardClick = () => {
|
||||
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}>
|
||||
{type === "media" && (
|
||||
<>
|
||||
<img
|
||||
src={data.public_bucket_path}
|
||||
alt={data.weight_name}
|
||||
className="card-video"
|
||||
/>
|
||||
<div className="card-img-overlay">
|
||||
<div className="card-img-gradient" />
|
||||
|
||||
<div className="d-flex align-items-center">
|
||||
<div className="flex-grow-1">
|
||||
<Badge label="Video" color="purple" overlay={true} />
|
||||
</div>
|
||||
</div>
|
||||
<FontAwesomeIcon icon={faPlayCircle} className="card-video-play" />
|
||||
<div className="card-img-overlay-text">
|
||||
<div>
|
||||
<h6 className="fw-semibold text-white mb-1">
|
||||
{data.weight_name}
|
||||
</h6>
|
||||
<p className="fs-7 opacity-75">{timeAgo}</p>
|
||||
<div className="mt-2" onClick={handleInnerClick}>
|
||||
<LikeButton
|
||||
onToggle={handleLike}
|
||||
likeCount={data.likes}
|
||||
overlay={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{type === "weights" && (
|
||||
<>
|
||||
<img
|
||||
src={data.public_bucket_path}
|
||||
alt={data.weight_name}
|
||||
className="card-video"
|
||||
/>
|
||||
<div className="card-img-overlay">
|
||||
<div className="card-img-gradient" />
|
||||
|
||||
<div className="d-flex align-items-center">
|
||||
<div className="flex-grow-1">
|
||||
<Badge label="Video" color="purple" overlay={true} />
|
||||
</div>
|
||||
</div>
|
||||
<FontAwesomeIcon icon={faPlayCircle} className="card-video-play" />
|
||||
<div className="card-img-overlay-text">
|
||||
<div>
|
||||
<h6 className="fw-semibold text-white mb-1">
|
||||
{data.weight_name}
|
||||
</h6>
|
||||
<p className="fs-7 opacity-75">{timeAgo}</p>
|
||||
<div className="mt-2" onClick={handleInnerClick}>
|
||||
<LikeButton
|
||||
onToggle={handleLike}
|
||||
likeCount={data.likes}
|
||||
overlay={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from "./VideoCard";
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from "./Card";
|
||||
@@ -1,83 +0,0 @@
|
||||
@import "scss/colours.scss";
|
||||
|
||||
.favorite-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 .favorite-number {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:hover .icon-default {
|
||||
color: map-get($badge-colors, "yellow");
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.toggled {
|
||||
background-color: darken(rgba(map-get($badge-colors, "yellow"), 0.9), 32%);
|
||||
|
||||
&:hover {
|
||||
background-color: darken(
|
||||
rgba(map-get($badge-colors, "yellow"), 0.9),
|
||||
25%
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.favorite-button .icon-default {
|
||||
color: $white;
|
||||
opacity: 0.7;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.favorite-button .icon-toggled {
|
||||
color: map-get($badge-colors, "yellow");
|
||||
animation: pulse 0.3s ease;
|
||||
}
|
||||
|
||||
.favorite-button .icon-toggled:hover {
|
||||
color: map-get($badge-colors, "yellow");
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faBookmark } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { faBookmark as faBookmarkOutline } 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;
|
||||
overlay?: boolean;
|
||||
}
|
||||
|
||||
export default function FavoriteButton({
|
||||
initialToggled = false,
|
||||
onToggle,
|
||||
favoriteCount,
|
||||
overlay,
|
||||
}: 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 buttonShadow = overlay ? "shadow" : "";
|
||||
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} ${buttonShadow}`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isToggled ? faBookmark : faBookmarkOutline}
|
||||
className={`${iconClass} me-2`}
|
||||
/>
|
||||
{favoriteCount && <p className="favorite-number">{favoriteCount}</p>}
|
||||
</button>
|
||||
</Tippy>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from "./FavoriteButton";
|
||||
@@ -1,80 +0,0 @@
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
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]}
|
||||
placement="bottom"
|
||||
>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from "./LikeButton";
|
||||
@@ -1,56 +0,0 @@
|
||||
import Masonry from "masonry-layout"; //Refer to: https://github.com/desandro/masonry
|
||||
import imagesLoaded from "imagesloaded"; //Refer to: https://github.com/desandro/imagesloaded
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
interface MasonryGridProps {
|
||||
children: React.ReactNode;
|
||||
onLayoutComplete?: () => void;
|
||||
gridRef: React.RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export default function MasonryGrid({
|
||||
children,
|
||||
onLayoutComplete,
|
||||
gridRef,
|
||||
}: MasonryGridProps) {
|
||||
useEffect(() => {
|
||||
let masonryInstance: Masonry | null = null;
|
||||
|
||||
const updateLayout = () => {
|
||||
//Needs this type guard to check that it is not undefined.
|
||||
if (masonryInstance && typeof masonryInstance.layout === "function") {
|
||||
masonryInstance.layout();
|
||||
}
|
||||
};
|
||||
|
||||
if (gridRef.current) {
|
||||
masonryInstance = new Masonry(gridRef.current, {
|
||||
itemSelector: ".grid-item",
|
||||
percentPosition: true,
|
||||
});
|
||||
|
||||
//Needs this or images will overflow masonry card. It checks if image is loaded then after it is loaded, it resizes the card.
|
||||
imagesLoaded(gridRef.current, function () {
|
||||
updateLayout();
|
||||
onLayoutComplete?.();
|
||||
});
|
||||
}
|
||||
|
||||
//Clean up
|
||||
return () => {
|
||||
if (masonryInstance && typeof masonryInstance.destroy === "function") {
|
||||
masonryInstance.destroy();
|
||||
}
|
||||
};
|
||||
}, [gridRef, onLayoutComplete]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={gridRef}
|
||||
className="row gy-3 gx-3"
|
||||
data-masonry='{"percentPosition": true}'
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -19,7 +19,7 @@ export default function Panel({
|
||||
}: PanelProps) {
|
||||
return (
|
||||
<div
|
||||
className={`${clear ? "panel-clear" : "panel"} ${
|
||||
className={`panel ${clear && "panel-clear"} ${
|
||||
padding ? "p-3 py-4 p-md-4" : ""
|
||||
}
|
||||
${mb ? "mb-4" : ""} ${mt ? "mt-4" : ""} ${className || ""}`}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
@import "scss/colours.scss";
|
||||
|
||||
.nav-tabs {
|
||||
border-bottom: 2px solid #ffffff15;
|
||||
|
||||
.nav-link {
|
||||
border: none;
|
||||
padding: 1rem 1.4rem;
|
||||
border-bottom: 4px solid transparent;
|
||||
color: rgba($white, 0.5);
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
transition: 0.2s;
|
||||
|
||||
&:focus {
|
||||
border-bottom: 4px solid transparent;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: rgba($white, 0.75);
|
||||
border-bottom: 4px solid rgba($white, 0.75);
|
||||
}
|
||||
|
||||
&:focus:hover {
|
||||
color: rgba($white, 0.75);
|
||||
border-bottom: 4px solid rgba($white, 0.75);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $white !important;
|
||||
background-color: transparent;
|
||||
border-bottom: 4px solid $primary !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
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 {
|
||||
tabs: TabProps[];
|
||||
}
|
||||
|
||||
interface TabContentProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
function Tab({ to, label, icon, onClick }: TabProps & { onClick: () => void }) {
|
||||
return (
|
||||
<li className="nav-item">
|
||||
<NavLink
|
||||
to={to}
|
||||
className="nav-link fs-6 px-3 px-lg-4"
|
||||
activeClassName="active"
|
||||
onClick={onClick}
|
||||
>
|
||||
{icon && <FontAwesomeIcon icon={icon} className="me-2" />}
|
||||
{label}
|
||||
</NavLink>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
function TabContent({ children }: TabContentProps) {
|
||||
return <div className="tab-content">{children}</div>;
|
||||
}
|
||||
|
||||
function Tabs({ tabs }: TabsProps) {
|
||||
const location = useLocation();
|
||||
const currentPath = location.pathname;
|
||||
const initialTab = tabs.find((tab) => tab.to === currentPath) || tabs[0];
|
||||
const [activeTab, setActiveTab] = useState(initialTab.to);
|
||||
|
||||
const [fade, setFade] = useSpring(() => ({
|
||||
opacity: 1,
|
||||
from: { opacity: 0 },
|
||||
config: { duration: 50 },
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
setFade({ opacity: 1 });
|
||||
}, [activeTab, setFade]);
|
||||
|
||||
useEffect(() => {
|
||||
setActiveTab(currentPath);
|
||||
}, [currentPath]);
|
||||
|
||||
const handleTabClick = (tabTo: string) => {
|
||||
if (activeTab === tabTo) {
|
||||
return;
|
||||
}
|
||||
setFade({ opacity: 0 });
|
||||
setTimeout(() => setActiveTab(tabTo), 50);
|
||||
};
|
||||
|
||||
return (
|
||||
<nav>
|
||||
<ul className="nav nav-tabs">
|
||||
{tabs.map((tab) => (
|
||||
<Tab
|
||||
key={tab.to}
|
||||
to={tab.to}
|
||||
label={tab.label}
|
||||
onClick={() => handleTabClick(tab.to)}
|
||||
icon={tab.icon}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
<TabContent>
|
||||
{tabs.map((tab) => (
|
||||
<a.div
|
||||
key={tab.to}
|
||||
style={fade}
|
||||
className={`tab-pane fade ${
|
||||
activeTab === tab.to ? "show active" : ""
|
||||
}`}
|
||||
>
|
||||
{tab.content}
|
||||
</a.div>
|
||||
))}
|
||||
</TabContent>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
export default Tabs;
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from "./Tabs";
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from "./useOnScreen";
|
||||
@@ -1,35 +0,0 @@
|
||||
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;
|
||||
@@ -2,6 +2,7 @@
|
||||
$primary: #e66462;
|
||||
$secondary: #39394c;
|
||||
$bg: #1a1a27;
|
||||
$panel: #242433;
|
||||
$input: #39394c;
|
||||
$input-placeholder: #aeaeb3;
|
||||
$danger: #8f4951;
|
||||
@@ -11,9 +12,8 @@ $success: #40ad48;
|
||||
$warning: darken(#f7941c, 5%);
|
||||
$accent: #80cfd3;
|
||||
$workflow: $primary;
|
||||
$panel: #242433;
|
||||
$panel-inner: lighten($panel, 4%);
|
||||
$panel-inner-border: lighten($panel, 8%);
|
||||
|
||||
// Light mode colours
|
||||
|
||||
$primary-lm: #000;
|
||||
|
||||
@@ -59,19 +59,3 @@ $danger-dark: darken($danger, 10%);
|
||||
|
||||
$accent-light: desaturate(lighten($accent, 20%), 15%);
|
||||
$accent-dark: saturate(darken($accent, 15%), 10%);
|
||||
|
||||
$badge-colors: (
|
||||
"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,
|
||||
);
|
||||
@@ -148,7 +148,7 @@ styles with important tags:
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1280px !important;
|
||||
max-width: 1200px !important;
|
||||
}
|
||||
|
||||
.container,
|
||||
@@ -296,15 +296,20 @@ styles with important tags:
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-radius: 0px;
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(0 0 0 / 16%),
|
||||
0 70px 65px rgb(0 0 0 / 8%),
|
||||
0 30px 30px rgb(0 0 0 / 7%),
|
||||
0 15px 15px rgb(0 0 0 / 5%),
|
||||
0 10px 8px rgb(0 0 0 / 3%),
|
||||
0 4px 4px rgb(0 0 0 / 2%),
|
||||
box-shadow: 0 0 0 1px rgba(0 0 0 / 16%), 0 70px 65px rgb(0 0 0 / 8%),
|
||||
0 30px 30px rgb(0 0 0 / 7%), 0 15px 15px rgb(0 0 0 / 5%),
|
||||
0 10px 8px rgb(0 0 0 / 3%), 0 4px 4px rgb(0 0 0 / 2%),
|
||||
0 2px 2px rgb(0 0 0 / 1%) !important;
|
||||
|
||||
&.panel-clear {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-color: transparent !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.video-bg {
|
||||
background-color: $black;
|
||||
}
|
||||
@@ -351,15 +356,6 @@ styles with important tags:
|
||||
}
|
||||
}
|
||||
|
||||
.panel-clear {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-color: transparent !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.menu {
|
||||
border-radius: 0.5rem !important;
|
||||
color: $white;
|
||||
@@ -1024,13 +1020,9 @@ styles with important tags:
|
||||
background-color: $panel !important;
|
||||
min-height: 220px;
|
||||
height: 100%;
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(0 0 0 / 30%),
|
||||
0 70px 65px rgb(0 0 0 / 10%),
|
||||
0 30px 30px rgb(0 0 0 / 8%),
|
||||
0 15px 15px rgb(0 0 0 / 6%),
|
||||
0 10px 8px rgb(0 0 0 / 4%),
|
||||
0 4px 4px rgb(0 0 0 / 2%),
|
||||
box-shadow: 0 0 0 1px rgba(0 0 0 / 30%), 0 70px 65px rgb(0 0 0 / 10%),
|
||||
0 30px 30px rgb(0 0 0 / 8%), 0 15px 15px rgb(0 0 0 / 6%),
|
||||
0 10px 8px rgb(0 0 0 / 4%), 0 4px 4px rgb(0 0 0 / 2%),
|
||||
0 2px 2px rgb(0 0 0 / 1%) !important;
|
||||
}
|
||||
|
||||
@@ -1039,13 +1031,9 @@ styles with important tags:
|
||||
border-radius: 0.5rem;
|
||||
background-color: $panel !important;
|
||||
min-height: 650px;
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(0 0 0 / 30%),
|
||||
0 70px 65px rgb(0 0 0 / 10%),
|
||||
0 30px 30px rgb(0 0 0 / 8%),
|
||||
0 15px 15px rgb(0 0 0 / 6%),
|
||||
0 10px 8px rgb(0 0 0 / 4%),
|
||||
0 4px 4px rgb(0 0 0 / 2%),
|
||||
box-shadow: 0 0 0 1px rgba(0 0 0 / 30%), 0 70px 65px rgb(0 0 0 / 10%),
|
||||
0 30px 30px rgb(0 0 0 / 8%), 0 15px 15px rgb(0 0 0 / 6%),
|
||||
0 10px 8px rgb(0 0 0 / 4%), 0 4px 4px rgb(0 0 0 / 2%),
|
||||
0 2px 2px rgb(0 0 0 / 1%) !important;
|
||||
}
|
||||
|
||||
@@ -1645,18 +1633,18 @@ textarea {
|
||||
padding-left: var(--bs-gutter-x, 1.75rem);
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
max-width: 1280px;
|
||||
max-width: 1200px;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.panel {
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.panel-clear {
|
||||
padding-left: 0rem;
|
||||
padding-right: 0rem;
|
||||
&.panel-clear {
|
||||
padding-left: 0rem;
|
||||
padding-right: 0rem;
|
||||
}
|
||||
}
|
||||
|
||||
ul.patrons-list {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ModerationIpBanListFc } from "./pages/moderation/moderation_ip_ban_list
|
||||
import { ModerationViewIpBanFc } from "./pages/moderation/moderation_view_ip_ban/ModerationViewIpBanFc";
|
||||
import { LipsyncEditor } from "./pages/lipsync";
|
||||
import { ProfileEditFc } from "./pages/profile/profile_edit/ProfileEditFc";
|
||||
import { ProfilePage } from "./pages/profile/profile_view/ProfilePage";
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
|
||||
import { SignupPage } from "./pages/signup/SignupPage";
|
||||
@@ -95,8 +96,6 @@ import { VoiceDesignerVoiceEditPage } from "./pages/voice_designer/VoiceDesigner
|
||||
import VoiceDesignerUseVoicePage from "./pages/voice_designer/VoiceDesignerUseVoicePage";
|
||||
import { PasswordResetEmailPage } from "./pages/password_reset/PasswordResetEmailPage";
|
||||
import { PasswordResetVerificationPage } from "./pages/password_reset/PasswordResetVerificationPage";
|
||||
import { NewProfilePage } from "./pages/profile/profile_view/NewProfilePage";
|
||||
import { ProfilePage } from "./pages/profile/profile_view/ProfilePage";
|
||||
|
||||
interface Props {
|
||||
sessionWrapper: SessionWrapper;
|
||||
@@ -306,16 +305,6 @@ class PageContainer extends React.Component<
|
||||
/>
|
||||
</Route>
|
||||
|
||||
{/* New Profile Page */}
|
||||
<Route path="/dev-profile/:username">
|
||||
<NewProfilePage
|
||||
sessionWrapper={this.props.sessionWrapper}
|
||||
sessionSubscriptionsWrapper={
|
||||
this.props.sessionSubscriptionsWrapper
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route path="/signup">
|
||||
<SignupPage
|
||||
querySessionCallback={() => {}}
|
||||
@@ -650,39 +639,42 @@ class PageContainer extends React.Component<
|
||||
</Route>
|
||||
|
||||
{/* Route for initial voice creation */}
|
||||
<Route exact path="/voice-designer/create">
|
||||
<VoiceDesignerFormPage
|
||||
{...{
|
||||
enqueueInferenceJob: this.props.enqueueInferenceJob,
|
||||
sessionWrapper: this.props.sessionWrapper,
|
||||
sessionSubscriptionsWrapper:
|
||||
this.props.sessionSubscriptionsWrapper,
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/voice-designer/create"
|
||||
>
|
||||
<VoiceDesignerFormPage {...{
|
||||
enqueueInferenceJob: this.props.enqueueInferenceJob,
|
||||
sessionWrapper: this.props.sessionWrapper,
|
||||
sessionSubscriptionsWrapper:
|
||||
this.props.sessionSubscriptionsWrapper
|
||||
}}/>
|
||||
</Route>
|
||||
|
||||
{/* Route for editing the dataset details */}
|
||||
<Route exact path="/voice-designer/dataset/:dataset_token/edit">
|
||||
<VoiceDesignerFormPage
|
||||
{...{
|
||||
enqueueInferenceJob: this.props.enqueueInferenceJob,
|
||||
sessionWrapper: this.props.sessionWrapper,
|
||||
sessionSubscriptionsWrapper:
|
||||
this.props.sessionSubscriptionsWrapper,
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/voice-designer/dataset/:dataset_token/edit"
|
||||
>
|
||||
<VoiceDesignerFormPage {...{
|
||||
enqueueInferenceJob: this.props.enqueueInferenceJob,
|
||||
sessionWrapper: this.props.sessionWrapper,
|
||||
sessionSubscriptionsWrapper:
|
||||
this.props.sessionSubscriptionsWrapper
|
||||
}}/>
|
||||
</Route>
|
||||
|
||||
{/* Route for handling dataset token for uploading samples */}
|
||||
<Route exact path="/voice-designer/dataset/:dataset_token/upload">
|
||||
<VoiceDesignerFormPage
|
||||
{...{
|
||||
enqueueInferenceJob: this.props.enqueueInferenceJob,
|
||||
sessionWrapper: this.props.sessionWrapper,
|
||||
sessionSubscriptionsWrapper:
|
||||
this.props.sessionSubscriptionsWrapper,
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/voice-designer/dataset/:dataset_token/upload"
|
||||
>
|
||||
<VoiceDesignerFormPage {...{
|
||||
enqueueInferenceJob: this.props.enqueueInferenceJob,
|
||||
sessionWrapper: this.props.sessionWrapper,
|
||||
sessionSubscriptionsWrapper:
|
||||
this.props.sessionSubscriptionsWrapper
|
||||
}}/>
|
||||
</Route>
|
||||
|
||||
<Route path="/voice-designer/voice/:voice_token/edit">
|
||||
@@ -703,16 +695,14 @@ class PageContainer extends React.Component<
|
||||
</Route>
|
||||
|
||||
<Route path="/voice-designer">
|
||||
<VoiceDesignerMainPage
|
||||
{...{
|
||||
sessionWrapper: this.props.sessionWrapper,
|
||||
sessionSubscriptionsWrapper:
|
||||
this.props.sessionSubscriptionsWrapper,
|
||||
enqueueInferenceJob: this.props.enqueueInferenceJob,
|
||||
inferenceJobs: this.props.inferenceJobs,
|
||||
inferenceJobsByCategory: this.props.inferenceJobsByCategory,
|
||||
}}
|
||||
/>
|
||||
<VoiceDesignerMainPage {...{
|
||||
sessionWrapper:this.props.sessionWrapper,
|
||||
sessionSubscriptionsWrapper:
|
||||
this.props.sessionSubscriptionsWrapper,
|
||||
enqueueInferenceJob: this.props.enqueueInferenceJob,
|
||||
inferenceJobs: this.props.inferenceJobs,
|
||||
inferenceJobsByCategory: this.props.inferenceJobsByCategory
|
||||
}} />
|
||||
</Route>
|
||||
|
||||
<Route path="/generate-speech">
|
||||
|
||||
@@ -1,430 +0,0 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Gravatar } from "@storyteller/components/src/elements/Gravatar";
|
||||
import { Link, Redirect, useLocation } from "react-router-dom";
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faDiscord,
|
||||
faGithub,
|
||||
faTwitch,
|
||||
faTwitter,
|
||||
} from "@fortawesome/free-brands-svg-icons";
|
||||
import {
|
||||
faBan,
|
||||
faGear,
|
||||
faGlobe,
|
||||
faDollarSign,
|
||||
faStar,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import {
|
||||
GetUserByUsername,
|
||||
GetUserByUsernameIsErr,
|
||||
GetUserByUsernameIsOk,
|
||||
User,
|
||||
UserLookupError,
|
||||
} from "@storyteller/components/src/api/user/GetUserByUsername";
|
||||
import { format } from "date-fns";
|
||||
import { WebUrl } from "../../../../../common/WebUrl";
|
||||
|
||||
import Tippy from "@tippyjs/react";
|
||||
import "tippy.js/dist/tippy.css";
|
||||
|
||||
import { usePrefixedDocumentTitle } from "../../../../../common/UsePrefixedDocumentTitle";
|
||||
import {
|
||||
faBookmark,
|
||||
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";
|
||||
import Container from "components/common/Container";
|
||||
import Tabs from "components/common/Tabs";
|
||||
import MediaTab from "./tabs/MediaTab";
|
||||
import WeightsTab from "./tabs/WeightsTab";
|
||||
import BookmarksTab from "./tabs/BookmarksTab";
|
||||
|
||||
interface Props {
|
||||
sessionWrapper: SessionWrapper;
|
||||
sessionSubscriptionsWrapper: SessionSubscriptionsWrapper;
|
||||
}
|
||||
|
||||
function NewProfilePage(this: any, props: Props) {
|
||||
const { username }: { username: string } = useParams();
|
||||
const { pathname } = useLocation();
|
||||
PosthogClient.recordPageview();
|
||||
|
||||
const [userData, setUserData] = useState<User | undefined>(undefined);
|
||||
const [notFoundState, setNotFoundState] = useState<boolean>(false);
|
||||
|
||||
const getUser = useCallback(async (username) => {
|
||||
const response = await GetUserByUsername(username);
|
||||
if (GetUserByUsernameIsOk(response)) {
|
||||
setUserData(response);
|
||||
} else if (GetUserByUsernameIsErr(response)) {
|
||||
switch (response) {
|
||||
case UserLookupError.NotFound:
|
||||
setNotFoundState(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
getUser(username);
|
||||
}, [username, getUser]);
|
||||
|
||||
const documentTitle =
|
||||
userData?.display_name === undefined
|
||||
? undefined
|
||||
: `${userData.display_name}`;
|
||||
usePrefixedDocumentTitle(documentTitle);
|
||||
|
||||
if (
|
||||
pathname === `/dev-profile/${username}` ||
|
||||
pathname === `/dev-profile/${username}/`
|
||||
) {
|
||||
return <Redirect to={`/dev-profile/${username}/media`} />;
|
||||
}
|
||||
|
||||
if (notFoundState) {
|
||||
return (
|
||||
<div className="container py-5">
|
||||
<div className="py-5">
|
||||
<h1 className="fw-semibold text-center mb-4">User not found</h1>
|
||||
<div className="text-center">
|
||||
<Link className="btn btn-primary" to="/">
|
||||
Back to main
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!userData) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
let userEmailHash = userData.email_gravatar_hash;
|
||||
|
||||
let editProfileButton = undefined;
|
||||
|
||||
let banUserButton = undefined;
|
||||
|
||||
let upgradeButton = undefined;
|
||||
|
||||
if (props.sessionWrapper.isLoggedIn()) {
|
||||
if (props.sessionWrapper.userTokenMatches(userData.user_token)) {
|
||||
if (!props.sessionSubscriptionsWrapper.hasPaidFeatures()) {
|
||||
const upgradeLinkUrl = WebUrl.pricingPage();
|
||||
|
||||
upgradeButton = (
|
||||
<>
|
||||
<Link className="btn btn-primary" to={upgradeLinkUrl}>
|
||||
<FontAwesomeIcon icon={faStar} className="me-2" />
|
||||
<span>Upgrade</span>
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (props.sessionWrapper.canBanUsers()) {
|
||||
const currentlyBanned = userData.maybe_moderator_fields?.is_banned;
|
||||
const banLinkUrl = WebUrl.userProfileBanPage(userData.username);
|
||||
const buttonLabel = currentlyBanned ? "Unban" : "Ban";
|
||||
const banButtonCss = currentlyBanned
|
||||
? "btn btn-secondary"
|
||||
: "btn btn-destructive";
|
||||
|
||||
banUserButton = (
|
||||
<>
|
||||
<Link className={banButtonCss} to={banLinkUrl}>
|
||||
<FontAwesomeIcon icon={faBan} className="me-2" />
|
||||
{buttonLabel}
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
let profileButtonsMobile = <span />;
|
||||
|
||||
if (props.sessionWrapper.canEditUserProfile(userData.username)) {
|
||||
const editLinkUrl = WebUrl.userProfileEditPage(userData.username);
|
||||
|
||||
// Mods shouldn't edit preferences.
|
||||
const buttonLabel = props.sessionWrapper.userTokenMatches(
|
||||
userData.user_token
|
||||
)
|
||||
? "Edit Profile & Preferences"
|
||||
: "Edit Profile";
|
||||
|
||||
editProfileButton = (
|
||||
<>
|
||||
<Link className={"btn btn-secondary"} to={editLinkUrl}>
|
||||
<FontAwesomeIcon icon={faGear} className="me-2" />
|
||||
{buttonLabel}
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
|
||||
profileButtonsMobile = (
|
||||
<div className="container d-flex d-lg-none mb-4">
|
||||
<div className="d-flex w-100 gap-3 justify-content-center flex-column flex-md-row">
|
||||
{upgradeButton}
|
||||
{editProfileButton}
|
||||
{banUserButton}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let profileDesc = (
|
||||
<div className="mt-3 text-center text-lg-start opacity-50">
|
||||
No profile description.
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!!userData.profile_rendered_html) {
|
||||
profileDesc = (
|
||||
<div
|
||||
className="mt-3 text-center text-lg-start"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: userData.profile_rendered_html || "",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let profileRows: Array<JSX.Element> = [];
|
||||
let profileJoinDate: Array<JSX.Element> = [];
|
||||
|
||||
const createdAt = new Date(userData.created_at);
|
||||
const joinDate = format(createdAt, "LLLL y");
|
||||
profileJoinDate.push(
|
||||
<div
|
||||
key="created"
|
||||
className="d-flex align-items-center justify-content-center justify-content-lg-start"
|
||||
>
|
||||
<FontAwesomeIcon icon={faCalendarAlt} className="me-2" />
|
||||
<p className="fw-normal">Joined {joinDate}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (userData.website_url !== undefined && userData.website_url !== null) {
|
||||
let websiteUrl = <span>{userData.website_url}</span>;
|
||||
if (
|
||||
userData.website_url?.startsWith("http://") ||
|
||||
userData.website_url?.startsWith("https://")
|
||||
) {
|
||||
websiteUrl = (
|
||||
<Tippy
|
||||
content="Website"
|
||||
hideOnClick
|
||||
placement="bottom"
|
||||
theme="fakeyou"
|
||||
arrow={false}
|
||||
>
|
||||
<a
|
||||
href={userData.website_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<FontAwesomeIcon icon={faGlobe} />
|
||||
</a>
|
||||
</Tippy>
|
||||
);
|
||||
}
|
||||
|
||||
profileRows.push(<div key="website">{websiteUrl}</div>);
|
||||
}
|
||||
|
||||
if (userData.twitch_username) {
|
||||
let twitchUrl = `https://twitch.com/${userData.twitch_username}`;
|
||||
let twitchLink = (
|
||||
<Tippy
|
||||
content="Twitch"
|
||||
hideOnClick
|
||||
placement="bottom"
|
||||
theme="fakeyou"
|
||||
arrow={false}
|
||||
>
|
||||
<a href={twitchUrl} target="_blank" rel="noopener noreferrer nofollow">
|
||||
<FontAwesomeIcon icon={faTwitch} />
|
||||
</a>
|
||||
</Tippy>
|
||||
);
|
||||
|
||||
profileRows.push(<div key="twitch">{twitchLink}</div>);
|
||||
}
|
||||
|
||||
if (userData.twitter_username) {
|
||||
let twitterUrl = `https://twitter.com/${userData.twitter_username}`;
|
||||
let twitterLink = (
|
||||
<Tippy
|
||||
content="Twitter"
|
||||
hideOnClick
|
||||
placement="bottom"
|
||||
theme="fakeyou"
|
||||
arrow={false}
|
||||
>
|
||||
<a href={twitterUrl} target="_blank" rel="noopener noreferrer nofollow">
|
||||
<FontAwesomeIcon icon={faTwitter} />
|
||||
</a>
|
||||
</Tippy>
|
||||
);
|
||||
profileRows.push(<div key="twitter">{twitterLink}</div>);
|
||||
}
|
||||
|
||||
if (userData.discord_username) {
|
||||
profileRows.push(
|
||||
<Tippy
|
||||
allowHTML
|
||||
content={
|
||||
<div className="text-center">
|
||||
Discord
|
||||
<br />
|
||||
<p className="fw-semibold fs-6">{userData.discord_username}</p>
|
||||
</div>
|
||||
}
|
||||
interactive
|
||||
hideOnClick
|
||||
placement="bottom"
|
||||
theme="fakeyou"
|
||||
arrow={false}
|
||||
>
|
||||
<a
|
||||
// eslint-disable-next-line no-script-url
|
||||
href="javascript:;"
|
||||
// onClick={copyToClipboard(userData.discord_username)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faDiscord} />
|
||||
</a>
|
||||
</Tippy>
|
||||
);
|
||||
}
|
||||
|
||||
if (userData.github_username) {
|
||||
let githubUrl = `https://github.com/${userData.github_username}`;
|
||||
let githubLink = (
|
||||
<Tippy
|
||||
content="GitHub"
|
||||
hideOnClick
|
||||
placement="bottom"
|
||||
theme="fakeyou"
|
||||
arrow={false}
|
||||
>
|
||||
<a
|
||||
href={githubUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
className="fw-normal"
|
||||
>
|
||||
<FontAwesomeIcon icon={faGithub} />
|
||||
</a>
|
||||
</Tippy>
|
||||
);
|
||||
profileRows.push(<div key="github">{githubLink}</div>);
|
||||
}
|
||||
|
||||
if (userData.cashapp_username) {
|
||||
// NB: URL includes a dollar sign
|
||||
let cashAppUrl = `https://cash.me/$${userData.cashapp_username}`;
|
||||
let cashAppLink = (
|
||||
<Tippy
|
||||
content="CashApp"
|
||||
hideOnClick
|
||||
placement="bottom"
|
||||
theme="fakeyou"
|
||||
arrow={false}
|
||||
>
|
||||
<a href={cashAppUrl} target="_blank" rel="noopener noreferrer nofollow">
|
||||
<FontAwesomeIcon icon={faDollarSign} />
|
||||
</a>
|
||||
</Tippy>
|
||||
);
|
||||
profileRows.push(<div key="cashapp">{cashAppLink}</div>);
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
to: `/dev-profile/${username}/media`,
|
||||
label: "Media",
|
||||
content: <MediaTab />,
|
||||
icon: faPhotoFilmMusic,
|
||||
},
|
||||
{
|
||||
to: `/dev-profile/${username}/weights`,
|
||||
label: "Weights",
|
||||
content: <WeightsTab />,
|
||||
icon: faLayerGroup,
|
||||
},
|
||||
{
|
||||
to: `/dev-profile/${username}/bookmarks`,
|
||||
label: "Bookmarks",
|
||||
content: <BookmarksTab />,
|
||||
icon: faBookmark,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Container type="panel">
|
||||
<Panel clear={true}>
|
||||
<div className="d-flex flex-column flex-lg-row py-4">
|
||||
<div className="mb-3 me-lg-4 mb-lg-0">
|
||||
<div className="border-3 d-none d-lg-block">
|
||||
<Gravatar
|
||||
size={150}
|
||||
username={userData.display_name}
|
||||
email_hash={userEmailHash}
|
||||
avatarIndex={userData.default_avatar_index}
|
||||
backgroundIndex={userData.default_avatar_color_index}
|
||||
/>
|
||||
</div>
|
||||
<div className="border-3 text-center d-lg-none">
|
||||
<Gravatar
|
||||
size={100}
|
||||
username={userData.display_name}
|
||||
email_hash={userEmailHash}
|
||||
avatarIndex={userData.default_avatar_index}
|
||||
backgroundIndex={userData.default_avatar_color_index}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-flex flex-column w-100">
|
||||
<div className="d-flex">
|
||||
<h1 className="fw-bold mb-0 flex-grow-1 text-center text-lg-start">
|
||||
{userData.display_name}
|
||||
</h1>
|
||||
<div className="gap-3 d-none d-lg-flex">
|
||||
{banUserButton}
|
||||
{upgradeButton}
|
||||
{editProfileButton}
|
||||
</div>
|
||||
</div>
|
||||
<div className="opacity-75 mt-1">{profileJoinDate}</div>
|
||||
<div>{profileDesc}</div>
|
||||
<div className="d-flex mt-3 gap-4 gap-lg-3 profile-social-icons justify-content-center justify-content-lg-start">
|
||||
{profileRows}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
{profileButtonsMobile}
|
||||
|
||||
<Panel mb={true}>
|
||||
<Tabs tabs={tabs} />
|
||||
</Panel>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export { NewProfilePage };
|
||||
@@ -1,58 +0,0 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import MasonryGrid from "components/common/MasonryGrid/MasonryGrid";
|
||||
import AudioCard from "components/common/Card/AudioCard";
|
||||
import ImageCard from "components/common/Card/ImageCard";
|
||||
import VideoCard from "components/common/Card/VideoCard";
|
||||
import Panel from "components/common/Panel";
|
||||
import AudioPlayerProvider from "components/common/AudioPlayer/AudioPlayerContext";
|
||||
import SkeletonCard from "components/common/Card/SkeletonCard";
|
||||
import mockWeightsData from "./mockWeightsData";
|
||||
|
||||
export default function BookmarksTab() {
|
||||
const [data, setData] = useState(mockWeightsData);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const gridContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
return (
|
||||
<Panel clear={true} padding={true}>
|
||||
<AudioPlayerProvider>
|
||||
{isLoading ? (
|
||||
<div className="row gx-3 gy-3">
|
||||
{Array.from({ length: 6 }).map((_, index) => (
|
||||
<SkeletonCard key={index} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<MasonryGrid
|
||||
gridRef={gridContainerRef}
|
||||
onLayoutComplete={() => console.log("Layout complete!")}
|
||||
>
|
||||
{data.map((data, index) => {
|
||||
let card;
|
||||
switch (data.media_type) {
|
||||
case "audio":
|
||||
card = <AudioCard key={index} data={data} type="weights" />;
|
||||
break;
|
||||
case "image":
|
||||
card = <ImageCard key={index} data={data} type="weights" />;
|
||||
break;
|
||||
case "video":
|
||||
card = <VideoCard key={index} data={data} type="weights" />;
|
||||
break;
|
||||
default:
|
||||
card = <div key={index}>Unsupported media type</div>;
|
||||
}
|
||||
return (
|
||||
<div key={index} className="col-12 col-sm-6 col-xl-4 grid-item">
|
||||
{card}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</MasonryGrid>
|
||||
)}
|
||||
</AudioPlayerProvider>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import MasonryGrid from "components/common/MasonryGrid/MasonryGrid";
|
||||
import mockMediaData from "./mockMediaData";
|
||||
import AudioCard from "components/common/Card/AudioCard";
|
||||
import ImageCard from "components/common/Card/ImageCard";
|
||||
import VideoCard from "components/common/Card/VideoCard";
|
||||
import Panel from "components/common/Panel";
|
||||
import { Select } from "components/common/Inputs/Inputs";
|
||||
import {
|
||||
faArrowDownWideShort,
|
||||
faFilter,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import AudioPlayerProvider from "components/common/AudioPlayer/AudioPlayerContext";
|
||||
import SkeletonCard from "components/common/Card/SkeletonCard";
|
||||
|
||||
export default function MediaTab() {
|
||||
const [data, setData] = useState(mockMediaData);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const gridContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const filterOptions = [
|
||||
{ value: "all", label: "All Media" },
|
||||
{ value: "images", label: "Images" },
|
||||
{ value: "audio", label: "Audio" },
|
||||
{ value: "video", label: "Video" },
|
||||
];
|
||||
|
||||
const sortOptions = [
|
||||
{ value: "newest", label: "Newest" },
|
||||
{ value: "oldest", label: "Oldest" },
|
||||
{ value: "mostliked", label: "Most Liked" },
|
||||
];
|
||||
|
||||
return (
|
||||
<Panel clear={true} padding={true}>
|
||||
<div className="d-flex gap-2 mb-3">
|
||||
<Select
|
||||
icon={faArrowDownWideShort}
|
||||
options={sortOptions}
|
||||
defaultValue={sortOptions[0]}
|
||||
isSearchable={false}
|
||||
/>
|
||||
<Select
|
||||
icon={faFilter}
|
||||
options={filterOptions}
|
||||
defaultValue={filterOptions[0]}
|
||||
isSearchable={false}
|
||||
/>
|
||||
</div>
|
||||
<AudioPlayerProvider>
|
||||
{isLoading ? (
|
||||
<div className="row gx-3 gy-3">
|
||||
{Array.from({ length: 6 }).map((_, index) => (
|
||||
<SkeletonCard key={index} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<MasonryGrid
|
||||
gridRef={gridContainerRef}
|
||||
onLayoutComplete={() => console.log("Layout complete!")}
|
||||
>
|
||||
{data.map((data, index) => {
|
||||
let card;
|
||||
switch (data.media_type) {
|
||||
case "audio":
|
||||
card = <AudioCard key={index} data={data} type="media" />;
|
||||
break;
|
||||
case "image":
|
||||
card = <ImageCard key={index} data={data} type="media" />;
|
||||
break;
|
||||
case "video":
|
||||
card = <VideoCard key={index} data={data} type="media" />;
|
||||
break;
|
||||
default:
|
||||
card = <div key={index}>Unsupported media type</div>;
|
||||
}
|
||||
return (
|
||||
<div key={index} className="col-12 col-sm-6 col-xl-4 grid-item">
|
||||
{card}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</MasonryGrid>
|
||||
)}
|
||||
</AudioPlayerProvider>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import MasonryGrid from "components/common/MasonryGrid/MasonryGrid";
|
||||
import mockWeightsData from "./mockWeightsData";
|
||||
import AudioCard from "components/common/Card/AudioCard";
|
||||
import ImageCard from "components/common/Card/ImageCard";
|
||||
import VideoCard from "components/common/Card/VideoCard";
|
||||
import Panel from "components/common/Panel";
|
||||
import { Select } from "components/common/Inputs/Inputs";
|
||||
import {
|
||||
faArrowDownWideShort,
|
||||
faFilter,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import SkeletonCard from "components/common/Card/SkeletonCard";
|
||||
|
||||
export default function WeightsTab() {
|
||||
const [data, setData] = useState(mockWeightsData);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const gridContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const filterOptions = [
|
||||
{ value: "all", label: "All Weights" },
|
||||
{ value: "tts", label: "Text to Speech" },
|
||||
{ value: "vc", label: "Voice to Voice" },
|
||||
{ value: "vd", label: "Voice Designer" },
|
||||
];
|
||||
|
||||
const sortOptions = [
|
||||
{ value: "newest", label: "Newest" },
|
||||
{ value: "oldest", label: "Oldest" },
|
||||
{ value: "mostliked", label: "Most Favorited" },
|
||||
];
|
||||
|
||||
return (
|
||||
<Panel clear={true} padding={true}>
|
||||
<div className="d-flex gap-2 mb-3">
|
||||
<Select
|
||||
icon={faArrowDownWideShort}
|
||||
options={sortOptions}
|
||||
defaultValue={sortOptions[0]}
|
||||
isSearchable={false}
|
||||
/>
|
||||
<Select
|
||||
icon={faFilter}
|
||||
options={filterOptions}
|
||||
defaultValue={filterOptions[0]}
|
||||
isSearchable={false}
|
||||
/>
|
||||
</div>
|
||||
{isLoading ? (
|
||||
<div className="row gx-3 gy-3">
|
||||
{Array.from({ length: 6 }).map((_, index) => (
|
||||
<SkeletonCard key={index} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<MasonryGrid
|
||||
gridRef={gridContainerRef}
|
||||
onLayoutComplete={() => console.log("Layout complete!")}
|
||||
>
|
||||
{data.map((data, index) => {
|
||||
let card;
|
||||
switch (data.media_type) {
|
||||
case "audio":
|
||||
card = <AudioCard key={index} data={data} type="weights" />;
|
||||
break;
|
||||
case "image":
|
||||
card = <ImageCard key={index} data={data} type="weights" />;
|
||||
break;
|
||||
case "video":
|
||||
card = <VideoCard key={index} data={data} type="weights" />;
|
||||
break;
|
||||
default:
|
||||
card = <div key={index}>Unsupported media type</div>;
|
||||
}
|
||||
return (
|
||||
<div key={index} className="col-12 col-sm-6 col-xl-4 grid-item">
|
||||
{card}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</MasonryGrid>
|
||||
)}
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
@@ -1,384 +0,0 @@
|
||||
let mockMediaData = [
|
||||
{
|
||||
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",
|
||||
},
|
||||
{
|
||||
token: "token11",
|
||||
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: "token12",
|
||||
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: "token13",
|
||||
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: "token14",
|
||||
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: "token15",
|
||||
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: "token16",
|
||||
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: "token17",
|
||||
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: "token18",
|
||||
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: "token19",
|
||||
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: "token20",
|
||||
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",
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
{
|
||||
token: "token11",
|
||||
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: "token12",
|
||||
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: "token13",
|
||||
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: "token14",
|
||||
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: "token15",
|
||||
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: "token16",
|
||||
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: "token17",
|
||||
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: "token18",
|
||||
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: "token19",
|
||||
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: "token20",
|
||||
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",
|
||||
},
|
||||
];
|
||||
|
||||
export default mockMediaData;
|
||||
@@ -1,66 +0,0 @@
|
||||
let mockWeightsData = [
|
||||
{
|
||||
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: "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",
|
||||
},
|
||||
];
|
||||
|
||||
// Randomize the order of the array
|
||||
for (let i = mockWeightsData.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[mockWeightsData[i], mockWeightsData[j]] = [
|
||||
mockWeightsData[j],
|
||||
mockWeightsData[i],
|
||||
];
|
||||
}
|
||||
|
||||
// Update the 'token' property to start at 1
|
||||
mockWeightsData.forEach((item, index) => {
|
||||
item.token = `token${index + 1}`;
|
||||
});
|
||||
|
||||
export default mockWeightsData;
|
||||
@@ -4420,7 +4420,6 @@ __metadata:
|
||||
"@fortawesome/pro-regular-svg-icons": ^6.2.1
|
||||
"@fortawesome/pro-solid-svg-icons": ^6.2.1
|
||||
"@fortawesome/react-fontawesome": ^0.2.0
|
||||
"@react-spring/types": ^9.7.3
|
||||
"@react-spring/web": ^9.7.3
|
||||
"@storyteller/components": 0.1.0
|
||||
"@testing-library/jest-dom": ^4.2.4
|
||||
@@ -4432,11 +4431,8 @@ __metadata:
|
||||
"@types/google.analytics": ^0.0.42
|
||||
"@types/gtag.js": ^0.0.12
|
||||
"@types/howler": ^2.1.2
|
||||
"@types/imagesloaded": ^4.1.6
|
||||
"@types/infinite-scroll": ^4.0.3
|
||||
"@types/jest": ^24.9.1
|
||||
"@types/js-cookie": ^3.0.1
|
||||
"@types/masonry-layout": ^4.2.7
|
||||
"@types/node": ^12.12.47
|
||||
"@types/react": ^16.9.36
|
||||
"@types/react-autocomplete": ^1.8.6
|
||||
@@ -4455,10 +4451,7 @@ __metadata:
|
||||
i18next: ^21.6.11
|
||||
i18next-browser-languagedetector: ^7.1.0
|
||||
i18next-http-backend: ^2.2.1
|
||||
imagesloaded: ^5.0.0
|
||||
infinite-scroll: ^4.0.1
|
||||
js-cookie: ^3.0.1
|
||||
masonry-layout: ^4.2.2
|
||||
posthog-js: ^1.73.1
|
||||
pretty-num: ^0.5.2
|
||||
query-string: ^8.1.0
|
||||
@@ -4468,7 +4461,6 @@ __metadata:
|
||||
react-dom: ^16.13.1
|
||||
react-drag-drop-files: ^2.3.9
|
||||
react-i18next: ^11.15.4
|
||||
react-infinite-scroll-component: ^6.1.0
|
||||
react-range: ^1.8.14
|
||||
react-reveal: ^1.2.2
|
||||
react-router-dom: ^5.2.0
|
||||
@@ -4479,7 +4471,6 @@ __metadata:
|
||||
react-wavesurfer.js: ^0.0.8
|
||||
sass: ^1.57.1
|
||||
typescript: ^3.7.5
|
||||
universal-cookie: ^6.1.1
|
||||
uuid: ^8.3.2
|
||||
wavesurfer.js: ^6.4.0
|
||||
zustand: ^4.3.9
|
||||
@@ -5260,34 +5251,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/imagesloaded@npm:^4.1.6":
|
||||
version: 4.1.6
|
||||
resolution: "@types/imagesloaded@npm:4.1.6"
|
||||
dependencies:
|
||||
"@types/jquery": "*"
|
||||
checksum: adfea961f0295e173ac4f81a58d13b62247576aef057f010da6b651e4191e86d52c97d4386d154c73ca8fd71d05b3bb29da8f9f3e20fda2c677af94a122feb6d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/infinite-scroll@npm:^4.0.3":
|
||||
version: 4.0.3
|
||||
resolution: "@types/infinite-scroll@npm:4.0.3"
|
||||
dependencies:
|
||||
"@types/isotope-layout": "*"
|
||||
"@types/jquery": "*"
|
||||
"@types/masonry-layout": "*"
|
||||
"@types/packery": "*"
|
||||
checksum: c7c9a2db7eb6a26c646a63c36fb1b04b381b9bcbae1b5fbc97d36f05eea36d6b17749b52cb906be578087aa203bfce72541b175fd0529df7c71961a7e7608668
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/isotope-layout@npm:*":
|
||||
version: 3.0.12
|
||||
resolution: "@types/isotope-layout@npm:3.0.12"
|
||||
checksum: 7ae477fad4017a2e7ebbd0b5202e26d6febc58bc0604688d99d2ff822157d48eab77c8a89d942afc21ad1b0d51967491d07c9619d2e5722b626964c2c03e71b9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1":
|
||||
version: 2.0.4
|
||||
resolution: "@types/istanbul-lib-coverage@npm:2.0.4"
|
||||
@@ -5342,15 +5305,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/jquery@npm:*":
|
||||
version: 3.5.29
|
||||
resolution: "@types/jquery@npm:3.5.29"
|
||||
dependencies:
|
||||
"@types/sizzle": "*"
|
||||
checksum: 5e959762d6f7050b07b4387b6507a308113384566a77cfc4f8d0f54c2fb0a79f6bc8c057706c6aa4840cde56f32ad0e5814fb53c5f078c5db9e01670a1ecd535
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/js-cookie@npm:^3.0.1":
|
||||
version: 3.0.2
|
||||
resolution: "@types/js-cookie@npm:3.0.2"
|
||||
@@ -5372,15 +5326,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/masonry-layout@npm:*, @types/masonry-layout@npm:^4.2.7":
|
||||
version: 4.2.7
|
||||
resolution: "@types/masonry-layout@npm:4.2.7"
|
||||
dependencies:
|
||||
"@types/jquery": "*"
|
||||
checksum: ef12ab8dc278ce0c96239f8542715d87b856db2414dd57c4256bb232f389247522d7f73ddd2bfe717a6c7a5e27ac5ec4dbc44ab95786ee49f97c38d97087cd71
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/mime@npm:^1":
|
||||
version: 1.3.2
|
||||
resolution: "@types/mime@npm:1.3.2"
|
||||
@@ -5430,15 +5375,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/packery@npm:*":
|
||||
version: 1.4.37
|
||||
resolution: "@types/packery@npm:1.4.37"
|
||||
dependencies:
|
||||
"@types/jquery": "*"
|
||||
checksum: 8350af4392e89988f4d8ec57827b47503be54365f5486c34ef36af3c0f48f1dc18332805eec7d04b06a6c22c2ef7db6cae03bc840058a9972d08fca21b0bc957
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/parse-json@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "@types/parse-json@npm:4.0.0"
|
||||
@@ -5602,13 +5538,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/sizzle@npm:*":
|
||||
version: 2.3.8
|
||||
resolution: "@types/sizzle@npm:2.3.8"
|
||||
checksum: 2ac62443dc917f5f903cbd9afc51c7d6cc1c6569b4e1a15faf04aea5b13b486e7f208650014c3dc4fed34653eded3e00fe5abffe0e6300cbf0e8a01beebf11a6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/sockjs@npm:^0.3.33":
|
||||
version: 0.3.33
|
||||
resolution: "@types/sockjs@npm:0.3.33"
|
||||
@@ -9431,13 +9360,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"desandro-matches-selector@npm:^2.0.0":
|
||||
version: 2.0.2
|
||||
resolution: "desandro-matches-selector@npm:2.0.2"
|
||||
checksum: 30979e6b45d7720d259d4db11ec026fcbee242deee5ec0944c265d7e386a7358ab29f7417cbc3631830d0dbe6e827940dee31f789ed865dba22880d209fc9f8e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"destroy@npm:~1.0.4":
|
||||
version: 1.0.4
|
||||
resolution: "destroy@npm:1.0.4"
|
||||
@@ -10467,20 +10389,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ev-emitter@npm:^1.0.0":
|
||||
version: 1.1.1
|
||||
resolution: "ev-emitter@npm:1.1.1"
|
||||
checksum: 3dd78a7620701ef8095794f611dd251bdd9023badf322b3d6fa5e10a660c50e83eec5ee5bbc7654682635cf7b8858c94e2c77e1d10bdc188a2b0593436403476
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ev-emitter@npm:^2.1.0, ev-emitter@npm:^2.1.2":
|
||||
version: 2.1.2
|
||||
resolution: "ev-emitter@npm:2.1.2"
|
||||
checksum: 1102823ef040684869256100e19a6c6aeeecd422d09a267d7c74e77023136eede2ee86ee475ad1019d30d1f7917557d74542d5665e9f91c398bd5814260af6b3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eventemitter3@npm:^3.1.0":
|
||||
version: 3.1.2
|
||||
resolution: "eventemitter3@npm:3.1.2"
|
||||
@@ -10961,22 +10869,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fizzy-ui-utils@npm:^2.0.0":
|
||||
version: 2.0.7
|
||||
resolution: "fizzy-ui-utils@npm:2.0.7"
|
||||
dependencies:
|
||||
desandro-matches-selector: ^2.0.0
|
||||
checksum: 001e54effa22c4f62728b90a25e8eb5477057ab53d0184e1c3231bbdfc7c34e278418fbcaf7d63c75ede823d82e61245e668ce8cba90e1c5699de960188c654f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fizzy-ui-utils@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "fizzy-ui-utils@npm:3.0.0"
|
||||
checksum: 73b53e5dc89e033bad3eb9cf111d220502afae083c84fbc842d9453202673284de270f7bbe6cc77a806678e62cf8068da1b4280f0d3a1bf1310110248dfa96cd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"flat-cache@npm:^3.0.4":
|
||||
version: 3.0.4
|
||||
resolution: "flat-cache@npm:3.0.4"
|
||||
@@ -12301,15 +12193,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"imagesloaded@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "imagesloaded@npm:5.0.0"
|
||||
dependencies:
|
||||
ev-emitter: ^2.1.2
|
||||
checksum: aec1826e866bcf2f92fc533b78e431597096c32dda3cf6de060f9dc2bdf1e39bb600eda244a7280ed09a7d12462fb63cf01e723799d35a86a326592e2f23751b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"immer@npm:^9.0.7":
|
||||
version: 9.0.12
|
||||
resolution: "immer@npm:9.0.12"
|
||||
@@ -12367,16 +12250,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"infinite-scroll@npm:^4.0.1":
|
||||
version: 4.0.1
|
||||
resolution: "infinite-scroll@npm:4.0.1"
|
||||
dependencies:
|
||||
ev-emitter: ^2.1.0
|
||||
fizzy-ui-utils: ^3.0.0
|
||||
checksum: e6000c0b82eace9df15ffb007447ecd12b806df60a41c6a06cdc0f931ae419e360be1e713f02e1047e088c75e0060198b25d19de6c3f88bd5fe0f987e326050a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"inflight@npm:^1.0.4":
|
||||
version: 1.0.6
|
||||
resolution: "inflight@npm:1.0.6"
|
||||
@@ -14508,16 +14381,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"masonry-layout@npm:^4.2.2":
|
||||
version: 4.2.2
|
||||
resolution: "masonry-layout@npm:4.2.2"
|
||||
dependencies:
|
||||
get-size: ^2.0.2
|
||||
outlayer: ^2.1.0
|
||||
checksum: b947029b8fdfb384d17d7749d80a4f00f88d885acea04d77e1fefd36827cc2a345fd40d9ccddf720ed9323e133d8ea498125eece91d2c8517f69fd4e9e1bbdf4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"md5.js@npm:^1.3.4":
|
||||
version: 1.3.5
|
||||
resolution: "md5.js@npm:1.3.5"
|
||||
@@ -15845,17 +15708,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"outlayer@npm:^2.1.0":
|
||||
version: 2.1.1
|
||||
resolution: "outlayer@npm:2.1.1"
|
||||
dependencies:
|
||||
ev-emitter: ^1.0.0
|
||||
fizzy-ui-utils: ^2.0.0
|
||||
get-size: ^2.0.2
|
||||
checksum: a8b69d07bad6b498ea1956f89a4bae6d38414d81a5feeae6af06cd8206a1bf47db82f664dc3ccd13b8d2c5886a6cded4a4642d0fafcbbd80f68873b7eb9f4153
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"p-finally@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "p-finally@npm:1.0.0"
|
||||
@@ -17808,17 +17660,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-infinite-scroll-component@npm:^6.1.0":
|
||||
version: 6.1.0
|
||||
resolution: "react-infinite-scroll-component@npm:6.1.0"
|
||||
dependencies:
|
||||
throttle-debounce: ^2.1.0
|
||||
peerDependencies:
|
||||
react: ">=16.0.0"
|
||||
checksum: 3708398934366df907dbad215247ebc1033221957ce7e32289ea31750cce70aa16513e2d03743e06c8b868ac7c542d12d5dbb6c830fd408433a4762f3cb5ecfb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-is@npm:^16.12.0, react-is@npm:^16.13.1, react-is@npm:^16.6.0, react-is@npm:^16.7.0, react-is@npm:^16.8.4":
|
||||
version: 16.13.1
|
||||
resolution: "react-is@npm:16.13.1"
|
||||
@@ -20383,13 +20224,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"throttle-debounce@npm:^2.1.0":
|
||||
version: 2.3.0
|
||||
resolution: "throttle-debounce@npm:2.3.0"
|
||||
checksum: 6d90aa2ddb294f8dad13d854a1cfcd88fdb757469669a096a7da10f515ee466857ac1e750649cb9da931165c6f36feb448318e7cb92570f0a3679d20e860a925
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"through2@npm:^2.0.0":
|
||||
version: 2.0.5
|
||||
resolution: "through2@npm:2.0.5"
|
||||
|
||||
Reference in new issue
Block a user