refactor: clean up unused imports and improve file loading logic

This commit is contained in:
grngxd 2025-07-31 12:29:37 +01:00
parent 4f54880400
commit 31a8d62f1d
4 changed files with 105 additions and 67 deletions

View file

@ -2,7 +2,7 @@ import { $, component$, useOnDocument, useSignal } from "@builder.io/qwik";
import ky from "ky";
export default component$(() => {
type TestimonialProps = {
type Testimonial = {
nickname: string;
pfp?: string;
id?: string;
@ -19,7 +19,31 @@ export default component$(() => {
success: boolean;
}
const Testimonial = component$(({ nickname, id, quote, pfp }: TestimonialProps) => {
// Array of testimonial objects
const testimonials: Testimonial[] = [
{
nickname: "grng",
id: "829372486780715018",
quote: "stereo is the best file host I've ever used, it's fast, reliable, and the interface is so clean and easy to use. I love it!"
},
{
nickname: "hexlocation",
pfp: "https://git.iwakura.rip/avatars/38bbf57a26f2891c59102582240386a4e2fa52b3999374673e0f4c4249ed4149?size=512",
quote: "I've been using stereo for a while now, and I can't imagine going back to any other file host. It's just that good!"
},
{
nickname: "typed",
pfp: "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fclipartcraft.com%2Fimages%2Ffire-emoji-transparent-snapchat-4.png&f=1&nofb=1&ipt=d59b80eec4d535f7f10b618a0daa9c0689a408643eaa6c1a054c0a03e7ca1835",
quote: "stereo.cat saved my house from a fire, because when I was signing up I realized the email I had to log into needed my phone for 2fa, so when I went to the kitchen and grabbed my phone I noticed the stove was on, I thankfully turned it off. Thank you stereo.cat"
},
{
nickname: "starlo",
pfp: "https://cdn.discordapp.com/avatars/962173926849519716/04af851a9954ba623b5d2eb5dd189785.webp?size=512",
quote: "I was expecting that stereo would be a greater website for storing my images, and I wasn't wrong and my doubts are always correct, it's more cooler than I expected 🔥"
}
];
const Testimonial = component$(({ nickname, id, quote, pfp }: Testimonial) => {
const lanyard = useSignal<LanyardResponse>();
useOnDocument("DOMContentLoaded", $(async () => {
@ -86,23 +110,9 @@ export default component$(() => {
</p>
</div>
<div class="flex flex-col gap-6">
<Testimonial
nickname="grng"
id="829372486780715018"
quote="stereo is the best file host I've ever used, it's fast, reliable, and the interface is so clean and easy to use. I love it!"
/>
<Testimonial
pfp="https://git.iwakura.rip/avatars/38bbf57a26f2891c59102582240386a4e2fa52b3999374673e0f4c4249ed4149?size=512"
nickname="hexlocation"
quote="I've been using stereo for a while now, and I can't imagine going back to any other file host. It's just that good!"
/>
<Testimonial
pfp="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fclipartcraft.com%2Fimages%2Ffire-emoji-transparent-snapchat-4.png&f=1&nofb=1&ipt=d59b80eec4d535f7f10b618a0daa9c0689a408643eaa6c1a054c0a03e7ca1835"
nickname="typed"
quote="stereo.cat saved my house from a fire, because when I was signing up I realized the email I had to log into needed my phone for 2fa, so when I went to the kitchen and grabbed my phone I noticed the stove was on, I thankfully turned it off. Thank you stereo.cat"
/>
{testimonials.map((t, i) => (
<Testimonial key={i} {...t} />
))}
</div>
<p class="text-xl text-white/50">
and many, many more...

7
src/lib/misc.ts Normal file
View file

@ -0,0 +1,7 @@
export function debounce<T extends (...args: any[]) => void>(fn: T, delay = 200) {
let timer: ReturnType<typeof setTimeout> | null = null;
return (...args: Parameters<T>) => {
if (timer) clearTimeout(timer);
timer = setTimeout(async () => await fn(...args), delay);
};
}

View file

@ -1,36 +1,28 @@
import { $, component$, Signal, useSignal, useTask$, useVisibleTask$ } from "@builder.io/qwik";
/* eslint-disable qwik/no-use-visible-task */
import { component$, Signal, useSignal, useTask$, useVisibleTask$ } from "@builder.io/qwik";
import { routeLoader$, type DocumentHead } from "@builder.io/qwik-city";
import Actionbar from "~/components/dashboard/Actionbar";
import Settings from "~/components/dashboard/Settings";
import Titlebar from "~/components/dashboard/Titlebar";
// import Dropzone from "~/components/Dropzone";
import { useNanostore$ } from "~/hooks/nanostores";
import { api } from "~/lib/api";
import { areFilesLoaded, dashboardFiles } from "~/lib/stores";
import { debounce } from "~/lib/misc";
import { StereoFile } from "~/lib/types";
export const useAuthCheck = routeLoader$(({ cookie, redirect: r }) => {
export const useAuthenticated = routeLoader$(({ cookie, redirect: r }) => {
const jwt = cookie.get("jwt");
if (jwt) return {};
throw r(302, "/api/auth/login");
});
export default component$(() => {
const files = useNanostore$<StereoFile[]>(dashboardFiles);
const loaded = useNanostore$<boolean>(areFilesLoaded);
useVisibleTask$(async () => {
loaded.value = false;
files.value = await api.list();
loaded.value = true;
});
return (
<>
<Settings />
<div class="flex flex-col w-full h-screen p-8 gap-6 bg-gradient-to-b from-stereo/20 to-transparent justify-self-end">
<Titlebar />
<Files files={files} loaded={loaded} />
<Files />
<Actionbar />
</div>
</>
@ -45,11 +37,8 @@ const formatSize = (bytes: number) => {
}
const Files = component$<{
files: Signal<StereoFile[]>;
loaded: Signal<boolean>;
}>(({ files }) => {
const File = component$(({ file }: { file: StereoFile }) => {
const Files = component$(() => {
const File = component$(({ file }: { file: StereoFile }) => {
const Preview = component$(() => {
type FileType = "image" | "video" | "audio" | "other";
const fileType: Signal<FileType> = useSignal<FileType>("other");
@ -90,7 +79,7 @@ const Files = component$<{
)}
{fileType.value === "other" && (
<div class="w-full min-h-30 flex items-center justify-center bg-gray-200 text-gray-500">
<p>Unsupported file type</p>
<p>unsupported file type</p>
</div>
)}
</div>
@ -119,26 +108,68 @@ const Files = component$<{
});
const loadingMore = useSignal(false);
const hasMore = useSignal(true);
const sentinel = useSignal<HTMLDivElement>();
const onScroll = $(async (e: Event) => {
const element = e.target as HTMLElement;
if (!element) return;
const files = useSignal<StereoFile[]>([]);
const page = useSignal(1);
const isAtBottom = element.scrollHeight - element.scrollTop - element.clientHeight < threshold;
if (isAtBottom) {
console.log("Loading more files...");
}
});
// TODO: make it load enough images to fill the viewport instead
useVisibleTask$(({ cleanup }) => {
if (!sentinel.value) return;
return (
<div class="px-2 mb-6 flex-grow overflow-y-auto mask-clip-content rounded-3xl" onScroll$={onScroll}>
<div class="w-full columns-1 md:columns-2 lg:columns-4 gap-2 space-y-2">
{files.value.map((file) => (
<File key={file.ID} file={file} />
))}
</div>
</div>
);
const observer = new IntersectionObserver((entries) => {
const entry = entries[0];
if (
entry.isIntersecting &&
!loadingMore.value &&
hasMore.value
) {
loadingMore.value = true;
console.log("Loading more files...");
debounce(async () => {
const newFiles = await api.list(page.value, 4); // gotta be a multiple of 4 so pages dont look weird
if (newFiles.length === 0) {
hasMore.value = false;
if (sentinel.value) observer.unobserve(sentinel.value);
} else {
files.value = [...files.value, ...newFiles];
page.value++;
}
loadingMore.value = false;
}, 50)();
}
});
observer.observe(sentinel.value);
cleanup(() => {
if (observer && sentinel.value) observer.unobserve(sentinel.value);
});
});
return (
<div class="px-2 mb-6 flex-grow overflow-y-auto mask-clip-content rounded-3xl">
<div class="w-full columns-1 md:columns-2 lg:columns-4 gap-2 space-y-2 relative">
{files.value.map((file) => (
<File key={file.ID} file={file} />
))}
{hasMore.value && (
<div
ref={sentinel}
class="absolute bottom-0 left-0 h-56 flex items-center justify-center w-full bg-blue-500"
>
{/* {loadingMore.value && (
loading spinner?
)} */}
</div>
)}
</div>
</div>
);
});
export const head: DocumentHead = {

View file

@ -1,6 +1,6 @@
/* eslint-disable qwik/jsx-img */
import { component$ } from "@builder.io/qwik";
import { DocumentHead, routeLoader$ } from "@builder.io/qwik-city";
import { DocumentHead } from "@builder.io/qwik-city";
import CallToAction from "~/components/landing/CallToAction";
import Footer from "~/components/landing/Footer";
import Hero from "~/components/landing/Hero";
@ -8,16 +8,6 @@ import Navbar from "~/components/landing/Navbar";
import Stats from "~/components/landing/Stats";
import Testimonials from "~/components/landing/Testimonials";
export const useAuthCheck = routeLoader$(({ cookie, redirect: r, query }) => {
const jwt = cookie.get("jwt");
const set = Boolean(query.get("jwt_set"));
if (jwt && set) {
throw r(302, "/api/auth/login");
}
return {};
});
export default component$(() => {
return (
<>