From 0d29d599376a02ac74941407c644918b5327f5e8 Mon Sep 17 00:00:00 2001 From: grngxd <36968271+grngxd@users.noreply.github.com> Date: Sat, 21 Jun 2025 16:15:33 +0100 Subject: [PATCH 1/2] change oauth link --- README.md | 9 ++++++--- src/routes/dashboard/index.tsx | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dbab72d..ec869d0 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # stereo.cat frontend -written in typescript with qwik +written in typescript with qwik & bun -## running in dev env -``` +## development +https://bun.sh/docs/installation + +```bash git clone https://git.iwakura.rip/stereo.cat/frontend.git git submodule update --init --recursive bun install @@ -11,4 +13,5 @@ bun dev ``` ## disclaimer + All graphic assets belonging to stereo.cat may not be used in unofficial instances, forks or versions of our software. Please replace them if you are hosting our software yourself, they can be found in the ``public`` folder in this repository. More information (like the full license) can be found [here](https://git.iwakura.rip/stereo.cat/public) diff --git a/src/routes/dashboard/index.tsx b/src/routes/dashboard/index.tsx index 98a3864..c2572c9 100644 --- a/src/routes/dashboard/index.tsx +++ b/src/routes/dashboard/index.tsx @@ -6,13 +6,14 @@ 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"; import { areFilesLoaded, dashboardFiles } from "~/lib/stores"; import { StereoFile } from "~/lib/types"; export const useAuthCheck = routeLoader$(({ cookie, redirect: r }) => { const jwt = cookie.get("jwt"); if (jwt) return {}; - throw r(302, "/"); + throw r(302, OAUTH_LINK); }); export default component$(() => { 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 2/2] 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,