ssr
This commit is contained in:
66
src/app/music/_components/Page.tsx
Normal file
66
src/app/music/_components/Page.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
'use client'
|
||||
|
||||
import * as Card from "~/components/ui/card";
|
||||
import { useTimeLine } from "../../_providers/GsapProvicer";
|
||||
import AnimatedPageTitle from "../../_components/Animated/AnimatedPageTitle";
|
||||
import AnimateTextIn from "../../_components/Animated/AnimateIn";
|
||||
import { ScrollArea } from "~/components/ui/scroll-area";
|
||||
import AnimatePopUp from "../../_components/Animated/AnimatePopUp";
|
||||
import AudioPlayer from "./AudioPlayer";
|
||||
import type { RouterOutputs } from "~/server/routers/_app";
|
||||
|
||||
export default function MusicPage(props: {
|
||||
tracks: RouterOutputs['music']['list'],
|
||||
}) {
|
||||
const { tracks } = props;
|
||||
useTimeLine(tracks)
|
||||
return (
|
||||
<ScrollArea className="px-10 lg:px-0 w-full h-full max-w-4xl mx-auto pt-10">
|
||||
<AnimatedPageTitle position={0}><span>Just Some </span> <span>Music I Made</span> </AnimatedPageTitle>
|
||||
<div className="flex flex-wrap h-fit content-center">
|
||||
<AnimateTextIn once className="flex flex-wrap mr-[1em]" position={0.5}>
|
||||
<div><p className="break-after-avoid mr-[1em]">All works on this page are licensed under:</p></div>
|
||||
<div><a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a></div>
|
||||
</AnimateTextIn>
|
||||
<AnimatePopUp duration={1} ease='elastic.inOut' position={2} once className="items-center content-center">
|
||||
<div className="flex flex-row">
|
||||
<img className="max-w-[1em]" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" alt="" />
|
||||
<img className="max-w-[1em] ml-[1em]" src="https://mirrors.creativecommons.org/presskit/icons/by.svg" alt="" />
|
||||
<img className="max-w-[1em] ml-[1em]" src="https://mirrors.creativecommons.org/presskit/icons/nc.svg" alt="" />
|
||||
<img className="max-w-[1em] ml-[1em]" src="https://mirrors.creativecommons.org/presskit/icons/sa.svg" alt="" />
|
||||
</div>
|
||||
</AnimatePopUp>
|
||||
</div>
|
||||
<div className="pt-10" />
|
||||
{tracks && tracks.map((track, i) => (
|
||||
<div key={track.id}>
|
||||
<Card.AnimatedCard position={i + 1}>
|
||||
<Card.CardHeader>
|
||||
<AnimateTextIn once position={i + 1.2} animation="slide">
|
||||
<Card.CardTitle>{track.title}</Card.CardTitle>
|
||||
</AnimateTextIn>
|
||||
</Card.CardHeader>
|
||||
<Card.CardContent className="flex flex-col gap-3">
|
||||
{track.description && (
|
||||
<p className="text-sm text-muted-foreground gsapant">{track.description}</p>
|
||||
)}
|
||||
<AnimatePopUp duration={2} ease='elastic.inOut' position={i + 1.3} once>
|
||||
<AudioPlayer
|
||||
id={track.id}
|
||||
src={track.streamUrl ?? track.fileUrl}
|
||||
downloadUrl={track.fileUrl}
|
||||
downloadName={track.fileName}
|
||||
/>
|
||||
</AnimatePopUp>
|
||||
</Card.CardContent>
|
||||
</Card.AnimatedCard>
|
||||
<div className="pt-5" />
|
||||
</div>
|
||||
))}
|
||||
{!tracks?.length &&
|
||||
<div className="flex justify-center items-center text-muted-foreground">
|
||||
No music yet.
|
||||
</div>}
|
||||
</ScrollArea>
|
||||
);
|
||||
}
|
||||
@@ -1,67 +1,13 @@
|
||||
'use client'
|
||||
import { trpc } from "~/app/_trpc/Client";
|
||||
import * as Card from "~/components/ui/card";
|
||||
import { useTimeLine } from "../_providers/GsapProvicer";
|
||||
import AnimatedPageTitle from "../_components/Animated/AnimatedPageTitle";
|
||||
import { Spinner } from "~/components/ui/spinner";
|
||||
import AnimateTextIn from "../_components/Animated/AnimateIn";
|
||||
import { ScrollArea } from "~/components/ui/scroll-area";
|
||||
import AnimatePopUp from "../_components/Animated/AnimatePopUp";
|
||||
import AudioPlayer from "./_components/AudioPlayer";
|
||||
export default function MusicPage() {
|
||||
const { data: tracks, isLoading } = trpc.music.list.useQuery();
|
||||
useTimeLine(tracks)
|
||||
return (
|
||||
<ScrollArea className="px-10 lg:px-0 w-full h-full max-w-4xl mx-auto pt-10">
|
||||
<AnimatedPageTitle position={0}><span>Just Some </span> <span>Music I Made</span> </AnimatedPageTitle>
|
||||
<div className="flex flex-wrap h-fit content-center">
|
||||
<AnimateTextIn once className="flex flex-wrap mr-[1em]" position={0.5}>
|
||||
<div><p className="break-after-avoid mr-[1em]">All works on this page are licensed under:</p></div>
|
||||
<div><a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a></div>
|
||||
</AnimateTextIn>
|
||||
<AnimatePopUp duration={1} ease='elastic.inOut' position={2} once className="items-center content-center">
|
||||
<div className="flex flex-row">
|
||||
<img className="max-w-[1em]" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" alt="" />
|
||||
<img className="max-w-[1em] ml-[1em]" src="https://mirrors.creativecommons.org/presskit/icons/by.svg" alt="" />
|
||||
<img className="max-w-[1em] ml-[1em]" src="https://mirrors.creativecommons.org/presskit/icons/nc.svg" alt="" />
|
||||
<img className="max-w-[1em] ml-[1em]" src="https://mirrors.creativecommons.org/presskit/icons/sa.svg" alt="" />
|
||||
</div>
|
||||
</AnimatePopUp>
|
||||
</div>
|
||||
<div className="pt-10" />
|
||||
{tracks && tracks.map((track, i) => (
|
||||
<div key={track.id}>
|
||||
<Card.AnimatedCard position={i + 1}>
|
||||
<Card.CardHeader>
|
||||
<AnimateTextIn once position={i + 1.2} animation="slide">
|
||||
<Card.CardTitle>{track.title}</Card.CardTitle>
|
||||
</AnimateTextIn>
|
||||
</Card.CardHeader>
|
||||
<Card.CardContent className="flex flex-col gap-3">
|
||||
{track.description && (
|
||||
<p className="text-sm text-muted-foreground gsapant">{track.description}</p>
|
||||
)}
|
||||
<AnimatePopUp duration={2} ease='elastic.inOut' position={i + 1.3} once>
|
||||
<AudioPlayer
|
||||
id={track.id}
|
||||
src={track.streamUrl ?? track.fileUrl}
|
||||
downloadUrl={track.fileUrl}
|
||||
downloadName={track.fileName}
|
||||
/>
|
||||
</AnimatePopUp>
|
||||
</Card.CardContent>
|
||||
</Card.AnimatedCard>
|
||||
<div className="pt-5" />
|
||||
</div>
|
||||
))}
|
||||
{!isLoading && !tracks?.length &&
|
||||
<div className="flex justify-center items-center text-muted-foreground">
|
||||
No music yet.
|
||||
</div>
|
||||
}
|
||||
{isLoading && <div className="w-full h-full items-center flex flex-row content-center gap-4 justify-center">
|
||||
<Spinner /> Loading Tracks
|
||||
</div>}
|
||||
</ScrollArea>
|
||||
);
|
||||
import { Suspense } from "react";
|
||||
import { servTrpc as trpc } from "../_trpc/ServerClient";
|
||||
import Page from "./_components/Page";
|
||||
|
||||
export default async function MusicPage() {
|
||||
const tracks = await trpc.music.list();
|
||||
|
||||
return (
|
||||
<Suspense>
|
||||
<Page tracks={tracks} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
111
src/app/projects/_components/Page.tsx
Normal file
111
src/app/projects/_components/Page.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
'use client'
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import * as Card from "~/components/ui/card";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { StackBadge } from "~/components/StackBadge";
|
||||
import { ScrollArea } from "~/components/ui/scroll-area";
|
||||
import AnimatedPageTitle from "../../_components/Animated/AnimatedPageTitle";
|
||||
import AnimateTextIn from "../../_components/Animated/AnimateIn";
|
||||
import { useTimeLine } from "../../_providers/GsapProvicer";
|
||||
import AnimatePopUp from "../../_components/Animated/AnimatePopUp";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import type { RouterOutputs } from "~/server/routers/_app";
|
||||
|
||||
export default function ProjectsPage(props: {
|
||||
projects: RouterOutputs['projectv2']['listWithStack'],
|
||||
descriptions: Record<string, ReactNode>,
|
||||
}) {
|
||||
const { projects, descriptions } = props;
|
||||
useTimeLine(projects)
|
||||
|
||||
if (!projects?.length) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-[200px] text-muted-foreground">
|
||||
No projects yet.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollArea className="px-10 lg:px-0 w-full h-full max-w-4xl mx-auto pt-10">
|
||||
<AnimatedPageTitle position={0}><span>Projects I've Been</span><span> Working on</span> </AnimatedPageTitle>
|
||||
<div className="pt-10" />
|
||||
{projects.map((project, i) => (
|
||||
<div id={project.id} key={i} className="scroll-mt-10">
|
||||
<Card.AnimatedCard position={i + 1.2} key={project.id}>
|
||||
<Card.CardHeader>
|
||||
<div className="flex items-start justify-between gap-2 flex-wrap">
|
||||
<AnimateTextIn once position={i + 1.4} animation="slide"><Card.CardTitle>{project.title}</Card.CardTitle></AnimateTextIn>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{project.sourceType && (
|
||||
<AnimatePopUp position={i + 2} duration={2} once>
|
||||
<Badge variant={project.sourceType === "open" ? "secondary" : "outline"}>
|
||||
{project.sourceType === "open" ? "Open Source" : "Closed Source"}
|
||||
</Badge>
|
||||
</AnimatePopUp>
|
||||
)}
|
||||
{project.releaseStatus && (
|
||||
<Badge variant={project.releaseStatus === "released" ? "default" : "outline"}>
|
||||
{project.releaseStatus === "released" ? "Released" : "Unreleased"}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card.CardHeader>
|
||||
{(project.description || project.sourceLink || project.releaseLink || project.techStack?.stackItems?.length) && (
|
||||
<Card.CardContent className="flex flex-col gap-3">
|
||||
{project.description && (
|
||||
<div className="prose prose-sm dark:prose-invert max-w-none text-muted-foreground">
|
||||
<AnimatePopUp once position={i + 1.4} duration={10}>
|
||||
{descriptions[project.id] ?? project.description}
|
||||
</AnimatePopUp>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-row">
|
||||
{project.techStack?.stackItems && project.techStack.stackItems.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{project.techStack.stackItems.map((item, k) => (
|
||||
<AnimatePopUp key={k} position={(i + 2) + k * 0.5} once> <StackBadge key={item} item={item} /> </AnimatePopUp>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{(project.sourceLink || project.releaseLink) && (
|
||||
<div className="ml-auto flex-col lg:flex-row justify-center gap-5">
|
||||
{project.sourceLink &&
|
||||
<Button variant='outline' className="cursor-pointer mb-3 lg:mb-0 lg:mr-3 min-w-18">
|
||||
<a
|
||||
href={project.sourceLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className='items-center'
|
||||
>
|
||||
Source
|
||||
</a>
|
||||
</Button>
|
||||
}
|
||||
{project.releaseLink &&
|
||||
<Button variant='default' className="cursor-pointer min-w-18 items-center">
|
||||
<a
|
||||
href={project.releaseLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className='items-center'
|
||||
>
|
||||
Live
|
||||
</a>
|
||||
</Button>
|
||||
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card.CardContent>
|
||||
)}
|
||||
</Card.AnimatedCard>
|
||||
<div className="pt-5" />
|
||||
</div>
|
||||
))}
|
||||
</ScrollArea>
|
||||
);
|
||||
}
|
||||
@@ -1,115 +1,40 @@
|
||||
'use client'
|
||||
import { Suspense, type ReactNode } from "react";
|
||||
import { MDXRemote } from "next-mdx-remote/rsc";
|
||||
import rehypeHighlight from "rehype-highlight";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { servTrpc as trpc } from "../_trpc/ServerClient";
|
||||
import { mdxComponents } from "~/components/mdx-components";
|
||||
import Page from "./_components/Page";
|
||||
|
||||
import { trpc } from "~/app/_trpc/Client";
|
||||
import * as Card from "~/components/ui/card";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { StackBadge } from "~/components/StackBadge";
|
||||
import { ScrollArea } from "~/components/ui/scroll-area";
|
||||
import AnimatedPageTitle from "../_components/Animated/AnimatedPageTitle";
|
||||
import AnimateTextIn from "../_components/Animated/AnimateIn";
|
||||
import { useTimeLine } from "../_providers/GsapProvicer";
|
||||
import AnimatePopUp from "../_components/Animated/AnimatePopUp";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { ClientMdx } from "~/components/ClientMdx";
|
||||
export default async function ProjectsPage() {
|
||||
const projects = await trpc.projectv2.listWithStack();
|
||||
|
||||
export default function ProjectsPage() {
|
||||
const { data: projects, isLoading } = trpc.projectv2.listWithStack.useQuery();
|
||||
useTimeLine(projects)
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-[200px] text-muted-foreground">
|
||||
Loading...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!projects?.length) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-[200px] text-muted-foreground">
|
||||
No projects yet.
|
||||
</div>
|
||||
// Render the MDX descriptions on the server so they exist at first paint.
|
||||
// The client tree (which runs the GSAP entrance via useTimeLine) only places
|
||||
// these already-rendered nodes — it never invokes the MDX renderer itself, so
|
||||
// the 'use client' boundary stays intact and the animations no longer play
|
||||
// against an un-rendered fallback.
|
||||
const descriptions: Record<string, ReactNode> = {};
|
||||
for (const project of projects ?? []) {
|
||||
if (!project.description?.trim()) continue;
|
||||
descriptions[project.id] = (
|
||||
<MDXRemote
|
||||
source={project.description}
|
||||
components={mdxComponents}
|
||||
options={{
|
||||
mdxOptions: {
|
||||
format: "md",
|
||||
remarkPlugins: [remarkGfm],
|
||||
rehypePlugins: [rehypeHighlight],
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollArea className="px-10 lg:px-0 w-full h-full max-w-4xl mx-auto pt-10">
|
||||
<AnimatedPageTitle position={0}><span>Projects I've Been</span><span> Working on</span> </AnimatedPageTitle>
|
||||
<div className="pt-10" />
|
||||
{projects.map((project, i) => (
|
||||
<div id={project.id} key={i} className="scroll-mt-10">
|
||||
<Card.AnimatedCard position={i + 1.2} key={project.id}>
|
||||
<Card.CardHeader>
|
||||
<div className="flex items-start justify-between gap-2 flex-wrap">
|
||||
<AnimateTextIn once position={i + 1.4} animation="slide"><Card.CardTitle>{project.title}</Card.CardTitle></AnimateTextIn>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{project.sourceType && (
|
||||
<AnimatePopUp position={i + 2} duration={2} once>
|
||||
<Badge variant={project.sourceType === "open" ? "secondary" : "outline"}>
|
||||
{project.sourceType === "open" ? "Open Source" : "Closed Source"}
|
||||
</Badge>
|
||||
</AnimatePopUp>
|
||||
)}
|
||||
{project.releaseStatus && (
|
||||
<Badge variant={project.releaseStatus === "released" ? "default" : "outline"}>
|
||||
{project.releaseStatus === "released" ? "Released" : "Unreleased"}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card.CardHeader>
|
||||
{(project.description || project.sourceLink || project.releaseLink || project.techStack?.stackItems?.length) && (
|
||||
<Card.CardContent className="flex flex-col gap-3">
|
||||
{project.description && (
|
||||
<div className="prose prose-sm dark:prose-invert max-w-none text-muted-foreground">
|
||||
<AnimatePopUp once position={i + 1.4} duration={10}>
|
||||
<ClientMdx source={project.description} fallback={project.description} />
|
||||
</AnimatePopUp>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-row">
|
||||
{project.techStack?.stackItems && project.techStack.stackItems.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{project.techStack.stackItems.map((item, k) => (
|
||||
<AnimatePopUp key={k} position={(i + 2) + k * 0.5} once> <StackBadge key={item} item={item} /> </AnimatePopUp>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{(project.sourceLink || project.releaseLink) && (
|
||||
<div className="ml-auto flex-col lg:flex-row justify-center gap-5">
|
||||
{project.sourceLink &&
|
||||
<Button variant='outline' className="cursor-pointer mb-3 lg:mb-0 lg:mr-3 min-w-18">
|
||||
<a
|
||||
href={project.sourceLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className='items-center'
|
||||
>
|
||||
Source
|
||||
</a>
|
||||
</Button>
|
||||
}
|
||||
{project.releaseLink &&
|
||||
<Button variant='default' className="cursor-pointer min-w-18 items-center">
|
||||
<a
|
||||
href={project.releaseLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className='items-center'
|
||||
>
|
||||
Live
|
||||
</a>
|
||||
</Button>
|
||||
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card.CardContent>
|
||||
)}
|
||||
</Card.AnimatedCard>
|
||||
<div className="pt-5" />
|
||||
</div>
|
||||
))}
|
||||
</ScrollArea>
|
||||
<Suspense>
|
||||
<Page projects={projects} descriptions={descriptions} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user