Skip to content

Commit ff60148

Browse files
fretchenfretchenCopilot
authored
Mobile (#128)
* Cleaner mobile bar * Improved mobile layout * Update styles.ts * Now we have a metadata line * Clean up the MetaDataLine * Cleaner layout * Improved link list * Better blog button * Cleaner cards * Lint * Clean up * Clean up * Update components.ts * Update EntryList.test.tsx * Update website/components/StarSupport.tsx Co-authored-by: Copilot <[email protected]> * Update website/layouts/LayoutDefault.tsx Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: fretchen <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent fad658f commit ff60148

23 files changed

+931
-389
lines changed

website/components/Card.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import * as React from "react";
22
import { Link } from "./Link";
33
import { CardProps } from "../types/components";
4-
import { card } from "../layouts/styles";
4+
import { baseContentCard } from "../layouts/styles";
55

6-
// Einfache Card-Komponente
6+
// Einheitliche Card-Komponente mit verbesserter mobiler Erfahrung
77
export const Card: React.FC<CardProps> = ({ title, description, link }) => {
88
return (
9-
<div className={card.container}>
10-
<div className={card.content}>
11-
<div className={card.text}>
12-
<h3 className={card.title}>{title}</h3>
13-
<p className={card.description}>{description}</p>
9+
<Link href={link} className={baseContentCard.container}>
10+
<div className={baseContentCard.content}>
11+
<div className={baseContentCard.text}>
12+
<h3 className={baseContentCard.title}>{title}</h3>
13+
<p className={baseContentCard.description}>{description}</p>
1414
</div>
15-
<Link href={link}>Read more →</Link>
1615
</div>
17-
</div>
16+
</Link>
1817
);
1918
};

website/components/EntryList.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ const EntryList: React.FC<EntryListProps> = ({
3030
{displayBlogs.map((blog, index) => {
3131
// Calculates the correct index for links when order is reversed
3232
const linkIndex = reverseOrder ? blogs.length - 1 - index : index;
33+
const entryUrl = `${basePath}/${linkIndex}`;
3334

3435
return (
35-
<div key={linkIndex} className={entryList.entry}>
36+
<Link key={linkIndex} href={entryUrl} className={entryList.entry}>
3637
<div className={entryList.entryContent}>
3738
{/* Large NFT image on the left side of the entire entry */}
3839
{(blog.tokenID || blog.nftMetadata?.imageUrl) && (
@@ -45,20 +46,19 @@ const EntryList: React.FC<EntryListProps> = ({
4546

4647
{/* Text content */}
4748
<div className={entryList.entryText}>
48-
{/* Date */}
49-
{showDate && blog.publishing_date && <p className={entryList.entryDate}>{blog.publishing_date}</p>}
49+
<div className={entryList.entryTextContent}>
50+
{/* Date */}
51+
{showDate && blog.publishing_date && <p className={entryList.entryDate}>{blog.publishing_date}</p>}
5052

51-
{/* Title */}
52-
<h3 className={`${entryList.entryTitle} ${titleClassName || ""}`}>{blog.title}</h3>
53+
{/* Title */}
54+
<h3 className={`${entryList.entryTitle} ${titleClassName || ""}`}>{blog.title}</h3>
5355

54-
{/* Description */}
55-
{blog.description && <p className={entryList.entryDescription}>{blog.description}</p>}
56+
{/* Description */}
57+
{blog.description && <p className={entryList.entryDescription}>{blog.description}</p>}
58+
</div>
5659
</div>
57-
58-
{/* Read more link */}
59-
<Link href={`${basePath}/${linkIndex}`}>Read more →</Link>
6060
</div>
61-
</div>
61+
</Link>
6262
);
6363
})}
6464

website/components/Footer.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import * as React from "react";
2+
import { layout } from "../layouts/styles";
3+
4+
/**
5+
* Footer Component
6+
*
7+
* Displays discrete attribution and footer information
8+
*/
9+
const Footer: React.FC = () => {
10+
return (
11+
<footer className={layout.footer}>
12+
<span className={layout.footerAttribution}>by fretchen</span>
13+
</footer>
14+
);
15+
};
16+
17+
export default Footer;

website/components/Link.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ import React from "react";
22
import { usePageContext } from "vike-react/usePageContext";
33
import { css } from "../styled-system/css";
44

5-
export function Link({ href, children }: { href: string; children: React.ReactNode }) {
5+
export function Link({ href, children, className }: { href: string; children: React.ReactNode; className?: string }) {
66
const pageContext = usePageContext();
77
const { urlPathname } = pageContext;
88
const isActive = href === "/" ? urlPathname === href : urlPathname.startsWith(href);
99

1010
return (
1111
<a
1212
href={href}
13-
className={css({
13+
className={`${css({
1414
// Grundlegende Link-Styles
1515
color: "token(colors.primary)",
1616
@@ -23,7 +23,7 @@ export function Link({ href, children }: { href: string; children: React.ReactNo
2323
_hover: {
2424
backgroundColor: isActive ? "token(colors.border)" : "token(colors.background)",
2525
},
26-
})}
26+
})} ${className || ""}`}
2727
>
2828
{children}
2929
</a>

website/components/MetadataLine.tsx

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import * as React from "react";
2+
import { useSupportAction } from "../hooks/useSupportAction";
3+
import { usePageContext } from "vike-react/usePageContext";
4+
import { metadataLine } from "../layouts/styles";
5+
6+
interface MetadataLineProps {
7+
publishingDate?: string;
8+
showSupport?: boolean;
9+
className?: string;
10+
}
11+
12+
/**
13+
* MetadataLine Component
14+
*
15+
* Displays content metadata in a discrete, natural way:
16+
* "January 15, 2025 • ⭐ 12 supporters"
17+
*
18+
* Integrates support functionality seamlessly with other metadata.
19+
*/
20+
export default function MetadataLine({ publishingDate, showSupport = false, className }: MetadataLineProps) {
21+
const pageContext = usePageContext();
22+
const currentUrl = pageContext.urlPathname;
23+
24+
// Support functionality (only load if needed)
25+
const { supportCount, isLoading, isSuccess, errorMessage, isConnected, handleSupport, isReadPending, readError } =
26+
useSupportAction(showSupport ? currentUrl : "");
27+
28+
// Format publishing date
29+
const formatDate = (dateString: string) => {
30+
try {
31+
const date = new Date(dateString);
32+
return date.toLocaleDateString("en-US", {
33+
year: "numeric",
34+
month: "long",
35+
day: "numeric",
36+
});
37+
} catch {
38+
return dateString; // Fallback to original string if parsing fails
39+
}
40+
};
41+
42+
// Handle support click
43+
const handleSupportClick = (e: React.MouseEvent) => {
44+
e.preventDefault();
45+
if (!isConnected) {
46+
// Could show a connect wallet message
47+
return;
48+
}
49+
handleSupport();
50+
};
51+
52+
// Render support section
53+
const renderSupportSection = () => {
54+
if (!showSupport) return null;
55+
56+
if (isReadPending) {
57+
return "⭐ Loading...";
58+
}
59+
60+
if (readError) {
61+
return "⭐ Error";
62+
}
63+
64+
const count = parseInt(supportCount) || 0;
65+
const supportText = count === 1 ? "supporter" : "supporters";
66+
67+
return (
68+
<button
69+
onClick={handleSupportClick}
70+
disabled={isLoading || !isConnected}
71+
className={metadataLine.supportButton}
72+
title={errorMessage || (isConnected ? "Support this content" : "Connect wallet to support")}
73+
>
74+
{isLoading ? "⭐ Supporting..." : isSuccess ? `★ ${count} ${supportText}` : `⭐ ${count} ${supportText}`}
75+
</button>
76+
);
77+
};
78+
79+
// Build metadata items
80+
const metadataItems = [publishingDate && formatDate(publishingDate), renderSupportSection()].filter(Boolean);
81+
82+
if (metadataItems.length === 0) {
83+
return null;
84+
}
85+
86+
return (
87+
<div className={`${metadataLine.container} ${className || ""}`}>
88+
{metadataItems.map((item, index) => (
89+
<React.Fragment key={index}>
90+
{index > 0 && <span className={metadataLine.separator}></span>}
91+
{item}
92+
</React.Fragment>
93+
))}
94+
</div>
95+
);
96+
}

website/components/Post.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import rehypeKatex from "rehype-katex";
55
import remarkGfm from "remark-gfm";
66
import rehypeRaw from "rehype-raw";
77
import { PostProps } from "../types/components";
8-
import TitleBar from "./TitleBar";
8+
import MetadataLine from "./MetadataLine";
99
import { Link } from "./Link";
1010
import { NFTFloatImage } from "./NFTFloatImage";
11-
import { post } from "../layouts/styles";
11+
import { post, titleBar } from "../layouts/styles";
1212
import "katex/dist/katex.min.css";
1313

1414
import Giscus from "@giscus/react";
@@ -144,8 +144,8 @@ export function Post({
144144

145145
return (
146146
<>
147-
<TitleBar title={title} />
148-
{publishing_date && <p className={post.publishingDate}>Published on: {publishing_date}</p>}
147+
<h1 className={titleBar.title}>{title}</h1>
148+
<MetadataLine publishingDate={publishing_date} showSupport={true} />
149149

150150
{/* Render based on post type */}
151151
{type === "react" && componentPath ? (

website/components/StarSupport.tsx

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import * as React from "react";
2+
import { usePageContext } from "vike-react/usePageContext";
3+
import { useSupportAction } from "../hooks/useSupportAction";
4+
import { starSupport } from "../layouts/styles";
5+
6+
interface StarSupportProps {
7+
variant?: "progress" | "inline";
8+
className?: string;
9+
}
10+
11+
/**
12+
* StarSupport Component
13+
*
14+
* Compact star-based support button with two variants:
15+
* - progress: Integrated with reading progress bar (sticky top)
16+
* - inline: Inline content placement
17+
*/
18+
export default function StarSupport({ variant = "progress", className }: StarSupportProps) {
19+
const pageContext = usePageContext();
20+
const currentUrl = pageContext.urlPathname;
21+
22+
// Reading progress state
23+
const [readingProgress, setReadingProgress] = React.useState(0);
24+
const [isVisible, setIsVisible] = React.useState(false);
25+
const [showTooltip, setShowTooltip] = React.useState(false);
26+
27+
// Support functionality
28+
const { supportCount, isLoading, isSuccess, errorMessage, isConnected, handleSupport } = useSupportAction(currentUrl);
29+
30+
// Reading progress calculation
31+
React.useEffect(() => {
32+
if (variant !== "progress") return;
33+
34+
const calculateProgress = () => {
35+
const scrollTop = window.pageYOffset;
36+
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
37+
const progress = Math.min((scrollTop / docHeight) * 100, 100);
38+
39+
setReadingProgress(progress);
40+
setIsVisible(progress > 5); // Show after 5% scroll
41+
};
42+
43+
calculateProgress();
44+
window.addEventListener("scroll", calculateProgress);
45+
window.addEventListener("resize", calculateProgress);
46+
47+
return () => {
48+
window.removeEventListener("scroll", calculateProgress);
49+
window.removeEventListener("resize", calculateProgress);
50+
};
51+
}, [variant]);
52+
53+
// Handle support click
54+
const handleSupportClick = () => {
55+
setShowTooltip(false);
56+
handleSupport();
57+
};
58+
59+
// Show tooltip on hover if there's an error
60+
const handleMouseEnter = () => {
61+
if (errorMessage) {
62+
setShowTooltip(true);
63+
}
64+
};
65+
66+
const handleMouseLeave = () => {
67+
setShowTooltip(false);
68+
};
69+
70+
// Render star icon
71+
const renderStarIcon = () => {
72+
if (isLoading) {
73+
return "⏳";
74+
}
75+
if (isSuccess) {
76+
return <span className={`${starSupport.starIcon} ${starSupport.starIconFilled}`}></span>;
77+
}
78+
return <span className={starSupport.starIcon}></span>;
79+
};
80+
81+
// Render support button
82+
const renderSupportButton = (buttonClass: string) => (
83+
<div style={{ position: "relative" }}>
84+
<button
85+
onClick={handleSupportClick}
86+
disabled={!isConnected || isLoading}
87+
className={`${buttonClass} ${isSuccess ? starSupport.supportButtonActive : ""}`}
88+
onMouseEnter={handleMouseEnter}
89+
onMouseLeave={handleMouseLeave}
90+
>
91+
{renderStarIcon()}
92+
<span className={starSupport.supportCount}>{isLoading ? "..." : supportCount}</span>
93+
</button>
94+
95+
{/* Tooltip for errors */}
96+
{showTooltip && errorMessage && <div className={starSupport.tooltip}>{errorMessage}</div>}
97+
</div>
98+
);
99+
100+
// Progress variant (sticky top bar)
101+
if (variant === "progress") {
102+
return (
103+
<div className={`${starSupport.progressContainer} ${className || ""}`} data-visible={isVisible}>
104+
<div className={starSupport.progressBar}>
105+
<div className={starSupport.progressFill} style={{ width: `${readingProgress}%` }} />
106+
</div>
107+
108+
{renderSupportButton(starSupport.supportButton)}
109+
</div>
110+
);
111+
}
112+
113+
// Inline variant (content placement)
114+
return (
115+
<div className={`${starSupport.inlineContainer} ${className || ""}`}>
116+
<div className={starSupport.inlineText}>
117+
⭐ Quality content? Support the creator!
118+
{parseInt(supportCount, 10) > 0 && ` ${supportCount} others already did.`}
119+
</div>
120+
121+
{renderSupportButton(starSupport.inlineButton)}
122+
</div>
123+
);
124+
}

website/components/TitleBar.tsx

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)