mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-14 05:42:32 +00:00
profile image and time update
This commit is contained in:
1 parent
ba31bf47f9
commit
a7bd1d97c2
10 files changed
+135
-28
No files matched your search
@@ -33,7 +33,7 @@
|
||||
"axios": "^0.21.0",
|
||||
"bulma": "^0.9.0",
|
||||
"craco": "^0.0.3",
|
||||
"date-fns": "^2.22.1",
|
||||
"date-fns": "2.22.1",
|
||||
"framer-motion": "^6.5.1",
|
||||
"howler": "^2.2.0",
|
||||
"i18next": "^21.6.11",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
.fy-accordion-item {
|
||||
border: 1px solid rgba($white, 0.12);
|
||||
border-radius: 5px;
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import React, {
|
||||
useState,
|
||||
useRef,
|
||||
useEffect,
|
||||
createContext,
|
||||
useContext,
|
||||
} from "react";
|
||||
import "./Accordion.scss";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faChevronDown } from "@fortawesome/pro-solid-svg-icons";
|
||||
@@ -14,50 +20,62 @@ interface AccordionItemProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
interface AccordionContextType {
|
||||
openItems: string[];
|
||||
toggleItem: (title: string) => void;
|
||||
}
|
||||
|
||||
const AccordionContext = createContext<AccordionContextType | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
function AccordionItem({
|
||||
title,
|
||||
defaultOpen = false,
|
||||
children,
|
||||
}: AccordionItemProps) {
|
||||
const [isOpen, setIsOpen] = useState(defaultOpen);
|
||||
const { openItems, toggleItem } = useContext(
|
||||
AccordionContext
|
||||
) as AccordionContextType;
|
||||
const isOpen = openItems.includes(title);
|
||||
|
||||
const [contentHeight, setContentHeight] = useState<number | undefined>(
|
||||
defaultOpen ? undefined : 0
|
||||
isOpen ? undefined : 0
|
||||
);
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && contentRef.current) {
|
||||
setContentHeight(contentRef.current.scrollHeight);
|
||||
} else {
|
||||
setContentHeight(0);
|
||||
if (contentRef.current) {
|
||||
setContentHeight(isOpen ? contentRef.current.scrollHeight : 0);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const heightProps = useSpring({
|
||||
height: `${contentHeight}px`,
|
||||
from: {
|
||||
height: defaultOpen ? "auto" : "0px",
|
||||
config: { tension: 300, friction: 25 },
|
||||
},
|
||||
height: isOpen ? `${contentHeight}px` : "0px",
|
||||
config: { tension: 300, friction: 25, clamp: true },
|
||||
});
|
||||
|
||||
const contentOpacityProps = useSpring({
|
||||
opacity: isOpen ? 1 : 0,
|
||||
config: { tension: 300, friction: 25 },
|
||||
config: { tension: 300, friction: 25, clamp: true },
|
||||
});
|
||||
|
||||
const handleClick = () => {
|
||||
toggleItem(title);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fy-accordion-item">
|
||||
<div
|
||||
className={`fy-accordion-header p-3 ${isOpen ? "open" : ""}`}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{title}
|
||||
<span className={`caret-icon ${isOpen ? "caret-rotated" : ""}`}>
|
||||
<FontAwesomeIcon icon={faChevronDown} />
|
||||
</span>
|
||||
</div>
|
||||
<animated.div style={heightProps}>
|
||||
<animated.div style={{ ...heightProps, overflow: "hidden" }}>
|
||||
<animated.div
|
||||
ref={contentRef}
|
||||
style={contentOpacityProps}
|
||||
@@ -71,7 +89,29 @@ function AccordionItem({
|
||||
}
|
||||
|
||||
function Accordion({ children }: AccordionProps) {
|
||||
return <div className="d-flex flex-column gap-3">{children}</div>;
|
||||
const [openItems, setOpenItems] = useState<string[]>([]);
|
||||
|
||||
const toggleItem = (title: string) => {
|
||||
if (openItems.includes(title)) {
|
||||
setOpenItems(openItems.filter((item) => item !== title));
|
||||
} else {
|
||||
setOpenItems([...openItems, title]);
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize openItems based on defaultOpen props of AccordionItem children
|
||||
useEffect(() => {
|
||||
const defaultOpenItems = React.Children.toArray(children)
|
||||
.filter((child: any) => child.props.defaultOpen)
|
||||
.map((child: any) => child.props.title);
|
||||
setOpenItems(defaultOpenItems);
|
||||
}, [children]);
|
||||
|
||||
return (
|
||||
<AccordionContext.Provider value={{ openItems, toggleItem }}>
|
||||
<div className="d-flex flex-column gap-3">{children}</div>
|
||||
</AccordionContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
Accordion.Item = AccordionItem;
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
@import "scss/colours.scss";
|
||||
|
||||
table {
|
||||
font-size: 14px;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 8px 16px !important;
|
||||
min-width: 124px !important;
|
||||
}
|
||||
|
||||
.no-outer-border {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./useTimeAgo";
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
function useTimeAgo(timestamp: string) {
|
||||
const [timeAgo, setTimeAgo] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
const calculateTimeAgo = () => {
|
||||
const now = new Date();
|
||||
const then = new Date(timestamp);
|
||||
|
||||
const seconds = Math.floor((now.getTime() - then.getTime()) / 1000);
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const days = Math.floor(hours / 24);
|
||||
const months = Math.floor(days / 30);
|
||||
const years = Math.floor(months / 12);
|
||||
|
||||
if (years > 0) {
|
||||
setTimeAgo(`${years} ${years === 1 ? "year" : "years"} ago`);
|
||||
} else if (months > 0) {
|
||||
setTimeAgo(`${months} ${months === 1 ? "month" : "months"} ago`);
|
||||
} else if (days > 0) {
|
||||
setTimeAgo(`${days} ${days === 1 ? "day" : "days"} ago`);
|
||||
} else if (hours > 0) {
|
||||
setTimeAgo(`${hours} ${hours === 1 ? "hour" : "hours"} ago`);
|
||||
} else if (minutes > 0) {
|
||||
setTimeAgo(`${minutes} ${minutes === 1 ? "minute" : "minutes"} ago`);
|
||||
} else {
|
||||
setTimeAgo(`${seconds} ${seconds === 1 ? "second" : "seconds"} ago`);
|
||||
}
|
||||
};
|
||||
|
||||
calculateTimeAgo();
|
||||
|
||||
// Update the time difference every minute
|
||||
const intervalId = setInterval(calculateTimeAgo, 60000);
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
}, [timestamp]);
|
||||
|
||||
return timeAgo;
|
||||
}
|
||||
|
||||
export default useTimeAgo;
|
||||
@@ -148,7 +148,7 @@ styles with important tags:
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1080px !important;
|
||||
max-width: 1200px !important;
|
||||
}
|
||||
|
||||
.container,
|
||||
|
||||
@@ -12,6 +12,8 @@ export default interface MediaData {
|
||||
color_index: number;
|
||||
};
|
||||
};
|
||||
model_used: string;
|
||||
model_link: string;
|
||||
creator_set_visibility: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
|
||||
@@ -14,6 +14,7 @@ import { faCircleExclamation } from "@fortawesome/pro-solid-svg-icons";
|
||||
import Accordion from "components/common/Accordion";
|
||||
import DataTable from "components/common/DataTable";
|
||||
import { Gravatar } from "@storyteller/components/src/elements/Gravatar";
|
||||
import useTimeAgo from "hooks/useTimeAgo";
|
||||
|
||||
interface MediaPageProps {
|
||||
sessionWrapper: SessionWrapper;
|
||||
@@ -35,9 +36,11 @@ let dummyMediaData = {
|
||||
color_index: 3,
|
||||
},
|
||||
},
|
||||
model_used: "Ash Ketchum",
|
||||
model_link: "/tts/TM:6g1mfb9b6fb8",
|
||||
creator_set_visibility: "public",
|
||||
created_at: "2023-10-12T07:49:53Z",
|
||||
updated_at: "2023-10-12T07:49:53Z",
|
||||
created_at: "2023-05-12T07:49:53Z",
|
||||
updated_at: "2023-05-12T07:49:53Z",
|
||||
};
|
||||
|
||||
export default function MediaPage({ sessionWrapper }: MediaPageProps) {
|
||||
@@ -46,6 +49,8 @@ export default function MediaPage({ sessionWrapper }: MediaPageProps) {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<boolean>(false);
|
||||
|
||||
const timeCreated = useTimeAgo(mediaData?.created_at || "");
|
||||
|
||||
// Simulate API call
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
@@ -88,12 +93,12 @@ export default function MediaPage({ sessionWrapper }: MediaPageProps) {
|
||||
/>
|
||||
<Panel padding={true}>
|
||||
<div className="row">
|
||||
<div className="col-12 col-md-8">
|
||||
<div className="col-12 col-xl-8">
|
||||
<h1 className="mb-0">
|
||||
<Skeleton />
|
||||
</h1>
|
||||
</div>
|
||||
<div className="col-12 col-md-4">
|
||||
<div className="col-12 col-xl-4">
|
||||
<h1 className="mb-0">
|
||||
<Skeleton />
|
||||
</h1>
|
||||
@@ -123,7 +128,10 @@ export default function MediaPage({ sessionWrapper }: MediaPageProps) {
|
||||
{ property: "Type", value: mediaData.media_type },
|
||||
{ property: "Created at", value: mediaData.created_at },
|
||||
{ property: "Visibility", value: mediaData.creator_set_visibility },
|
||||
{ property: "Model Used", value: "Model Name" },
|
||||
{
|
||||
property: "Model",
|
||||
value: mediaData.model_used,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -134,13 +142,13 @@ export default function MediaPage({ sessionWrapper }: MediaPageProps) {
|
||||
/>
|
||||
<Panel padding={true}>
|
||||
<div className="row g-4">
|
||||
<div className="col-12 col-lg-8">
|
||||
<div className="col-12 col-xl-8">
|
||||
{renderMediaComponent(mediaData)}
|
||||
</div>
|
||||
<div className="col-12 col-lg-4">
|
||||
<div className="d-flex">
|
||||
<div className="col-12 col-xl-4">
|
||||
<div className="d-flex mb-3 gap-2">
|
||||
<Gravatar
|
||||
size={50}
|
||||
size={48}
|
||||
username={mediaData.maybe_creator_user.display_name}
|
||||
avatarIndex={
|
||||
mediaData.maybe_creator_user.default_avatar.image_index
|
||||
@@ -151,10 +159,12 @@ export default function MediaPage({ sessionWrapper }: MediaPageProps) {
|
||||
/>
|
||||
<div className="d-flex flex-column">
|
||||
<Link
|
||||
className="fw-medium"
|
||||
to={`/profile/${mediaData.maybe_creator_user.display_name}`}
|
||||
>
|
||||
{mediaData.maybe_creator_user.display_name}
|
||||
</Link>
|
||||
{timeCreated}
|
||||
</div>
|
||||
</div>
|
||||
<Accordion>
|
||||
|
||||
@@ -4570,6 +4570,7 @@ __metadata:
|
||||
dependencies:
|
||||
"@react-spring/types": ^9.7.3
|
||||
craco: ^0.0.3
|
||||
date-fns: 2.22.1
|
||||
lerna: ^4.0.0
|
||||
yarn: ^1.22.19
|
||||
languageName: unknown
|
||||
@@ -8966,6 +8967,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"date-fns@npm:2.22.1":
|
||||
version: 2.22.1
|
||||
resolution: "date-fns@npm:2.22.1"
|
||||
checksum: 7ff97cd605af50c02f341687c2cafd218839a1aace67965374989855a13f76dc4fe52e0e38c343c1ad1f8399787cb6839a0b14a669c44b30550c287300b1bb50
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"date-fns@npm:^2.22.1":
|
||||
version: 2.28.0
|
||||
resolution: "date-fns@npm:2.28.0"
|
||||
|
||||
Reference in new issue
Block a user