frontend/src/components/dashboard/Settings.tsx
2025-08-08 23:20:41 +01:00

259 lines
No EOL
11 KiB
TypeScript

// import { component$ } from "@builder.io/qwik";
// import { useNanostore$ } from "~/hooks/nanostores";
// import { isSettingsOpen, userInfo } from "~/lib/stores";
// import { StereoUser } from "~/lib/types";
import { $, component$, useComputed$, useSignal, useTask$, useVisibleTask$ } from "@builder.io/qwik";
import ky from "ky";
import { useNanostore$ } from "~/hooks/nanostores";
import { isSettingsOpen, userInfo } from "~/lib/stores";
const StorageAndPlan = component$(() => {
const user = useNanostore$(userInfo);
useVisibleTask$(({track}) => {
if (user.value) {
console.log(user.value.global_name);
}
});
const title = useSignal("this is a test");
const description = useSignal("this is a test description");
const color = useSignal("#FF264E");
return (
<div class="flex flex-col gap-2">
<div class="flex flex-col mb-2">
<div class="flex items-center gap-2">
<svg class="w-5 h-5 text-stereo" fill="currentColor" viewBox="0 0 20 20"><path d="M10 2a6 6 0 016 6v2a6 6 0 01-12 0V8a6 6 0 016-6zm0 2a4 4 0 00-4 4v2a4 4 0 008 0V8a4 4 0 00-4-4z"/></svg>
<p class="text-white/80">current plan: <span class="text-stereo font-semibold">pro+</span></p>
</div>
<div class="flex items-center gap-2">
<svg class="w-5 h-5 text-stereo" fill="currentColor" viewBox="0 0 20 20"><path d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm0 2h12v10H4V5zm2 2v6h8V7H6z"/></svg>
<p class="text-white/80">storage used: <span class="font-semibold text-white">3.8</span> / <span class="text-white/50">15 GB</span></p>
</div>
<div class="w-full h-2 bg-white/10 rounded-full overflow-hidden mt-1 mb-2">
<div class="h-full bg-stereo rounded-full w-[calc(3.8/15*100%)]" />
</div>
<p class="text-sm text-white/40">upgrade your plan for more features</p>
</div>
<p class="text-xl">embed editor</p>
{user.value && (
<div class="flex gap-3">
<div class="flex gap-3">
<img
class="rounded-full w-12 h-12"
src={
user.value.id && user.value.avatar
? `https://cdn.discordapp.com/avatars/${user.value.id}/${user.value.avatar}.webp?size=512`
: ""
}
/>
<div class="flex flex-col gap-1">
<div class="flex gap-1 items-center">
<p>{user.value.global_name}</p>
<p class="text-sm opacity-50">{new Date().toLocaleTimeString().split(":").slice(0, 2).join(":")}</p>
</div>
<div class="overflow-clip flex bg-black/25 rounded-lg">
{(title.value || description.value) ? (
<>
<div class="w-1.5" style={{ backgroundColor: color.value }} />
<div class="flex flex-col p-2 gap-2 border-2 border-white/10 border-l-0 rounded-r-lg">
<div class="flex flex-col">
{ title.value && <p>{title.value}</p> }
{ description.value && <p class="text-sm opacity-50">{description.value}</p> }
</div>
<img
class="rounded-sm aspect-[3/2]"
src="https://placehold.co/300x200"
/>
</div>
</>
) : (
<img
class="rounded-sm aspect-[3/2]"
src="https://placehold.co/300x200"
/>
)}
</div>
</div>
</div>
<div class="flex flex-col gap-2">
<div class="flex flex-col">
<p class="text-sm text-white/50">title</p>
<input
type="text"
class="bg-black/40 border border-white/10 rounded-lg px-3 py-2 text-white focus:outline-none focus:border-stereo transition"
placeholder="enter a title..."
value={title.value}
onInput$={(e) => title.value = (e.target as HTMLInputElement).value}
/>
</div>
<div class="flex flex-col">
<p class="text-sm text-white/50">description</p>
<textarea
class="bg-black/40 border border-white/10 rounded-lg px-3 py-2 text-white focus:outline-none focus:border-stereo transition"
placeholder="enter a description..."
rows={3}
value={description.value}
onInput$={(e) => description.value = (e.target as HTMLTextAreaElement).value}
/>
</div>
<div class="flex flex-col">
<p class="text-sm text-white/50">color</p>
<input
type="color"
class="bg-black/40 rounded-lg w-full"
value={color.value}
onInput$={(e) => color.value = (e.target as HTMLInputElement).value}
/>
</div>
</div>
</div>
)}
</div>
);
});
const Integrations = component$(() => {
return (
<div>
<p>manage your api keys and integrations here</p>
</div>
);
});
const PrivacyAndSecurity = component$(() => {
return (
<div>
<p>manage your privacy settings and security options</p>
</div>
);
});
const DangerZone = component$(() => {
const handleLogout = $(() => {
ky.get("/api/auth/logout", { credentials: "include" })
.then(() => {
window.location.href = "/";
});
});
return (
<div>
<p>delete your account and data here</p>
<button
onClick$={handleLogout}
class="text-white bg-stereo hover:bg-stereo/80 transition-colors rounded-lg px-4 py-2"
>
log out
</button>
</div>
);
});
export default component$(() => {
const open = useNanostore$<boolean>(isSettingsOpen);
const visible = useSignal(false);
const shouldRender = useSignal(open.value);
useTask$(({ track }) => {
track(() => open.value);
if (open.value) {
shouldRender.value = true;
setTimeout(() => { visible.value = true; }, 10);
} else {
visible.value = false;
setTimeout(() => { shouldRender.value = false; }, 300);
}
});
const categories = useSignal([
{
name: "storage & plan",
description: "manage your storage and plan details",
component: StorageAndPlan
},
{
name: "api & integrations",
description: "manage your api keys and integrations",
component: Integrations
},
{
name: "privacy & security",
description: "manage your privacy settings and security options",
component: PrivacyAndSecurity
},
{
name: "danger zone",
description: "delete your account and data",
component: DangerZone
}
])
const selectedCategory = useSignal(0);
const SelectedComponent = useComputed$(() => {
return categories.value[selectedCategory.value].component;
});
if (!shouldRender.value) return <></>;
return (
<div
onClick$={() => (open.value = false)}
style={{
opacity: visible.value ? 1 : 0,
}}
class="z-[50] fixed inset-0 flex items-center justify-center bg-black/50 text-white text-6xl backdrop-blur-3xl transition-opacity duration-300"
>
<div
data-aos="fade-up"
data-aos-duration="400"
data-aos-anchor-placement="top-center"
data-aos-easing="ease-out-quad"
style={{
border: "0.5px solid #FF264E",
boxShadow:
"0px 4px 20px 0px rgba(255, 38, 78, 0.08), 0px 8px 12px 0px rgba(0, 0, 0, 0.12), 0px 4px 4px 0px rgba(0, 0, 0, 0.06), 0px 2px 1px 0px rgba(0, 0, 0, 0.04), 0px 4px 8px 0px rgba(255, 38, 78, 0.12) inset, 0px 1px 3px 0px rgba(255, 38, 78, 0.24) inset",
}}
onClick$={e => e.stopPropagation()}
class="flex gap-8 bg-black/30 bg-gradient-to-t from-stereo/20 to-transparent p-8 rounded-3xl shadow-lg w-4/7 h-4/7"
>
<div class="flex flex-col justify-between flex-1/4 border-r-2 border-white/15">
<h1 class="text-2xl">settings</h1>
<div>
{categories.value.map((category, i) => (
<div
key={category.name}
class={[
"group py-2 px-3 transition-colors cursor-pointer rounded-l-xl select-none",
selectedCategory.value === i
? "bg-white/10 text-white"
: "hover:bg-white/5 text-white/75 hover:text-white"
].join(" ")}
onClick$={() => (selectedCategory.value = i)}
>
<h2 class="text-lg">{category.name}</h2>
<p class="text-sm text-white/25">{category.description}</p>
</div>
))}
</div>
</div>
<div class="flex flex-col flex-3/4 text-lg overflow-clip overflow-y-auto">
<h1 class="text-2xl mb-2">{categories.value[selectedCategory.value].name}</h1>
<SelectedComponent.value />
</div>
</div>
</div>
);
});