From f47a1b22262161de5f72a64c38983d3564add7b2 Mon Sep 17 00:00:00 2001 From: grngxd <36968271+grngxd@users.noreply.github.com> Date: Sat, 21 Jun 2025 17:02:52 +0100 Subject: [PATCH] templating dashboard & add user route to api client --- src/components/dashboard/Controlbar.tsx | 88 ----------- src/components/dashboard/File.tsx | 199 ------------------------ src/components/dashboard/OSBar.tsx | 9 ++ src/components/dashboard/TitleBar.tsx | 9 ++ src/lib/api.ts | 1 + src/lib/stores.ts | 5 +- src/lib/types.ts | 8 + src/routes/dashboard/index.tsx | 57 +++---- src/routes/layout.tsx | 11 +- 9 files changed, 58 insertions(+), 329 deletions(-) delete mode 100644 src/components/dashboard/Controlbar.tsx delete mode 100644 src/components/dashboard/File.tsx create mode 100644 src/components/dashboard/OSBar.tsx create mode 100644 src/components/dashboard/TitleBar.tsx diff --git a/src/components/dashboard/Controlbar.tsx b/src/components/dashboard/Controlbar.tsx deleted file mode 100644 index 45310c1..0000000 --- a/src/components/dashboard/Controlbar.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { $, component$, noSerialize, NoSerialize, useSignal, useVisibleTask$ } from "@builder.io/qwik"; -import { useNanostore$ } from "~/hooks/nanostores"; -import { api } from "~/lib/api"; -import { areFilesLoaded, dashboardFiles } from "~/lib/stores"; -import { StereoFile } from "~/lib/types"; -import { SolarUploadLinear, SvgSpinnersBarsRotateFade } from "../misc/Icons"; -import StereoLogo from "../misc/StereoLogo"; - -export default component$(() => { - const loaded = useNanostore$(areFilesLoaded); - const files = useNanostore$(dashboardFiles); - const fileInputRef = useSignal(); - const uploadingFiles = useSignal | undefined>(); - const now = useSignal(new Date()); - - useVisibleTask$(() => { - const interval = setInterval(() => { - now.value = new Date(); - }, 500); - return () => clearInterval(interval); - }); - - const uploadFiles = $(async () => { - if (!uploadingFiles.value) { - console.error("No file(s) selected for upload."); - return; - } - - try { - const ufiles = uploadingFiles.value as File[]; - - for (const file of ufiles) { - const name = file.name.replace(/[^a-zA-Z0-9_.-]/g, "_"); - const f = new File([file], name, { type: file.type }); - - await api.upload(f); - } - - files.value = await api.list(); - } catch (error) { - console.error("Error uploading file:", error); - } - }) - return ( -
- { - uploadingFiles.value = noSerialize(Object.values((e.target as HTMLInputElement).files || {})); - await uploadFiles(); - }} - multiple - /> - -
- {/* TODO: replace this button with a modal with options like settings log out etc */} - - -

|

- - -
-

{now.value.toLocaleTimeString()}

-
- ) -}) \ No newline at end of file diff --git a/src/components/dashboard/File.tsx b/src/components/dashboard/File.tsx deleted file mode 100644 index 50c1e02..0000000 --- a/src/components/dashboard/File.tsx +++ /dev/null @@ -1,199 +0,0 @@ -import { $, component$, Signal, useSignal, useTask$ } from "@builder.io/qwik"; -import { useNanostore$ } from "~/hooks/nanostores"; -import { api } from "~/lib/api"; -import { dashboardFiles } from "~/lib/stores"; -import { StereoFile } from "~/lib/types"; -import { SolarClipboardAddBold, SolarDownloadMinimalisticBold, SolarTrashBin2Bold } from "../misc/Icons"; - -type FileProps = { - file: StereoFile; -} - -const formatSize = (bytes: number) => { - if (bytes < 1024) return `${bytes} B`; - if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; - if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; - return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`; -} - -export default component$(({ file }: FileProps) => { - const files = useNanostore$(dashboardFiles); - - const deleteFile = $(async (id: string) => { - if (!confirm("Are you sure you want to delete this file?")) return; - await api.delete(id); - files.value = await api.list(); - }); - - const addFileToClipboard = $(async () => { - const response = await api.file(file.Name); - const data = await response.blob(); - let mime = data.type || "application/octet-stream"; - let clip; - - if (navigator.clipboard && window.ClipboardItem) { - if (mime === "image/jpeg" || mime === "image/jpg") { - const img = document.createElement("img"); - img.src = URL.createObjectURL(data); - await new Promise((res) => (img.onload = res)); - const canvas = document.createElement("canvas"); - canvas.width = img.width; - canvas.height = img.height; - const ctx = canvas.getContext("2d"); - ctx?.drawImage(img, 0, 0); - const png = await new Promise((resolve) => - canvas.toBlob((b) => resolve(b!), "image/png") - ); - mime = "image/png"; - clip = new ClipboardItem({ [mime]: png }); - } else { - clip = new ClipboardItem({ [mime]: data }); - } - - try { - await navigator.clipboard.write([clip]); - alert("File added to clipboard successfully!"); - } catch (error) { - console.error("Failed to add file to clipboard:", error); - alert("Failed to add file to clipboard. Please try again."); - } - } else { - alert("Clipboard API not supported in this browser."); - } - }); - - return ( -
-
- -
- -
-
- - -
- -
-
-

- { file.Name || "Untitled" } -

-
- { formatSize(file.Size) } - - Uploaded on { new Date(file.CreatedAt).toLocaleDateString() } -
-
-
-
- ) -}) - -const FilePreview = component$(({ file }: FileProps) => { - type FileType = - | "image" - | "video" - | "audio" - | "other"; - - const type: Signal = useSignal("other"); - const extension = file.Name.split('.').pop()?.toLowerCase() || ""; - - useTask$(async () => { - if ( - ["png", "jpg", "jpeg", "gif"] - .includes(extension)) type.value = "image"; - - else if ( - ["mp4", "webm", "ogg", "avi", "mov", "mkv"] - .includes(extension)) type.value = "video"; - else if ( - ["mp3", "wav", "ogg", "flac", "aac"] - .includes(extension)) type.value = "audio"; - - else type.value = "other"; - }); - - switch (type.value) { - case "image": - return ( -
- {file.Name} -
- ); - case "video": - return ( -
- -
- ); - case "audio": - return ( -
- -
- ); - case "other": - default: - return ( -
-

- Preview not available -

-
- ); - } -}); \ No newline at end of file diff --git a/src/components/dashboard/OSBar.tsx b/src/components/dashboard/OSBar.tsx new file mode 100644 index 0000000..4607fcf --- /dev/null +++ b/src/components/dashboard/OSBar.tsx @@ -0,0 +1,9 @@ +import { component$ } from "@builder.io/qwik"; + +export default component$(() => { + return ( +
+ a +
+ ) +}) \ No newline at end of file diff --git a/src/components/dashboard/TitleBar.tsx b/src/components/dashboard/TitleBar.tsx new file mode 100644 index 0000000..8ae4d02 --- /dev/null +++ b/src/components/dashboard/TitleBar.tsx @@ -0,0 +1,9 @@ +import { component$ } from "@builder.io/qwik"; + +export default component$(() => { + return ( +
+

whats on the agenda today, @hexlocation?

+
+ ) +}) \ No newline at end of file diff --git a/src/lib/api.ts b/src/lib/api.ts index c75a9b8..bc5c2e6 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -15,4 +15,5 @@ export const api = { return await client.post('upload', { body: formData }); }, delete: async (uid: string) => await client.delete(uid).json(), + me: async () => (await client.get('auth/me').json() as any).user, } \ No newline at end of file diff --git a/src/lib/stores.ts b/src/lib/stores.ts index 444b668..8fa59e2 100644 --- a/src/lib/stores.ts +++ b/src/lib/stores.ts @@ -1,5 +1,6 @@ import { atom } from "nanostores"; -import { StereoFile } from "./types"; +import { StereoFile, StereoUser } from "./types"; export const areFilesLoaded = atom(false); -export const dashboardFiles = atom([]); \ No newline at end of file +export const dashboardFiles = atom([]); +export const userInfo = atom({} as StereoUser); \ No newline at end of file diff --git a/src/lib/types.ts b/src/lib/types.ts index 861bacc..72bec61 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -5,4 +5,12 @@ export type StereoFile = { Size: number; CreatedAt: string; Mime: string; +} + +export type StereoUser = { + id: string; + username: string; + blacklisted: boolean; + email: string; + created_at: string; } \ No newline at end of file diff --git a/src/routes/dashboard/index.tsx b/src/routes/dashboard/index.tsx index c2572c9..c2db24e 100644 --- a/src/routes/dashboard/index.tsx +++ b/src/routes/dashboard/index.tsx @@ -1,9 +1,8 @@ -import { component$, useVisibleTask$ } from "@builder.io/qwik"; +import { component$, Signal, useVisibleTask$ } from "@builder.io/qwik"; import { routeLoader$, type DocumentHead } from "@builder.io/qwik-city"; -import Controlbar from "~/components/dashboard/Controlbar"; +import OSBar from "~/components/dashboard/OSBar"; +import TitleBar from "~/components/dashboard/TitleBar"; // import Dropzone from "~/components/Dropzone"; -import File from "~/components/dashboard/File"; -import { SolarUploadLinear, SvgSpinnersBarsRotateFade } from "~/components/misc/Icons"; import { useNanostore$ } from "~/hooks/nanostores"; import { api } from "~/lib/api"; import { OAUTH_LINK } from "~/lib/constants"; @@ -23,50 +22,30 @@ export default component$(() => { useVisibleTask$(async () => { loaded.value = false; files.value = await api.list(); - console.log("Files loaded:", files.value); loaded.value = true; }); return ( <> - {/* */} - - {!loaded.value ? ( -
-

-

loading your files...

- please wait... -
- ) : ( - files.value.length === 0 ? ( -
-

{ - [ - "┻━┻︵ \\(°□°)/ ︵ ┻━┻", - "┻━┻︵ヽ(`Д´)ノ︵ ┻━┻", - "ʕノ•ᴥ•ʔノ ︵ ┻━┻", - "(╯°Д°)╯︵ /(.□ . \\)", - "┬─┬ ︵ /(.□. \\)", - "(/ .□.)\\ ︵╰(゜Д゜)╯︵ /(.□. \\)" - ].sort(() => Math.random() - 0.5)[0] - }

-

you haven't uploaded any files yet!

- click the button to get started -
- ) - : ( -
- {files.value.map((file) => ( - - ))} -
- ) - )} - + + + ); }); +const Files = component$<{ + files: Signal; + loaded: Signal; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +}>(({ files, loaded }) => { + return ( +
+ b +
+ ); +}); + export const head: DocumentHead = { title: "Welcome to Qwik", meta: [ diff --git a/src/routes/layout.tsx b/src/routes/layout.tsx index fe816c8..c7a9b0f 100644 --- a/src/routes/layout.tsx +++ b/src/routes/layout.tsx @@ -1,9 +1,18 @@ import { $, component$, Slot, useOnDocument } from '@builder.io/qwik'; import AOS from 'aos'; import 'aos/dist/aos.css'; +import { useNanostore$ } from '~/hooks/nanostores'; +import { api } from '~/lib/api'; +import { userInfo } from '~/lib/stores'; +import { StereoUser } from '~/lib/types'; export default component$(() => { - useOnDocument("DOMContentLoaded", $(() => { + const info = useNanostore$(userInfo); + + useOnDocument("DOMContentLoaded", $(async () => { + info.value = await api.me() + console.log(info.value); + AOS.init({ once: true, duration: 1000,