diff --git a/bun.lock b/bun.lock index 7ade672..8e57ca5 100644 --- a/bun.lock +++ b/bun.lock @@ -4,6 +4,9 @@ "": { "name": "my-qwik-empty-starter", "dependencies": { + "@cloudgakkai/qwik-aos": "github:CloudGakkai/qwik-aos", + "@types/aos": "^3.0.7", + "aos": "^3.0.0-beta.6", "ky": "^1.8.1", "nanostores": "^1.0.1", "tailwind-scrollbar": "^4.0.2", @@ -35,6 +38,8 @@ "@builder.io/qwik-city": ["@builder.io/qwik-city@1.14.1", "", { "dependencies": { "@mdx-js/mdx": "^3", "@types/mdx": "^2", "source-map": "^0.7.4", "svgo": "^3.3", "undici": "*", "valibot": ">=0.36.0 <2", "vfile": "6.0.2", "vite": "^5", "vite-imagetools": "^7", "zod": "3.22.4" } }, "sha512-VsAvk7u2HyyTnL9GhpT+h10t2XAIlxtv6LFL3Xt9/1QZ6lMfGWMcMEAMuZB1Ib+D/oTfu7QRqZngRg3FsrIKyg=="], + "@cloudgakkai/qwik-aos": ["@cloudgakkai/qwik-aos@github:CloudGakkai/qwik-aos#affd212", {}, "CloudGakkai-qwik-aos-affd212"], + "@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="], "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="], @@ -245,6 +250,8 @@ "@trysound/sax": ["@trysound/sax@0.2.0", "", {}, "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA=="], + "@types/aos": ["@types/aos@3.0.7", "", {}, "sha512-sEhyFqvKauUJZDbvAB3Pggynrq6g+2PS4XB3tmUr+mDL1gfDJnwslUC4QQ7/l8UD+LWpr3RxZVR/rHoZrLqZVg=="], + "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], @@ -297,6 +304,8 @@ "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "aos": ["aos@3.0.0-beta.6", "", { "dependencies": { "classlist-polyfill": "^1.2.0", "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1" } }, "sha512-VLWrpq8bfAWcetynVHMMrqdC+89Qq/Ym6UBJbHB4crIwp3RR8uq1dNGgsFzoDl03S43rlVMK+na3r5+oUCZsYw=="], + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], "array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="], @@ -345,6 +354,8 @@ "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + "classlist-polyfill": ["classlist-polyfill@1.2.0", "", {}, "sha512-GzIjNdcEtH4ieA2S8NmrSxv7DfEV5fmixQeyTmqmRmRJPGpRBaSnA2a0VrCjyT8iW8JjEdMbKzDotAJf+ajgaQ=="], + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], "collapse-white-space": ["collapse-white-space@2.1.0", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="], @@ -655,8 +666,12 @@ "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="], + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + "lodash.throttle": ["lodash.throttle@4.1.1", "", {}, "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="], + "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], "magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], diff --git a/package.json b/package.json index 2c28d14..c95d6ca 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,8 @@ "vite-tsconfig-paths": "^4.2.1" }, "dependencies": { + "@types/aos": "^3.0.7", + "aos": "^3.0.0-beta.6", "ky": "^1.8.1", "nanostores": "^1.0.1", "tailwind-scrollbar": "^4.0.2" diff --git a/public/dashboard 1.png b/public/dashboard 1.png new file mode 100644 index 0000000..a6bd762 Binary files /dev/null and b/public/dashboard 1.png differ diff --git a/public/dashboard 2.png b/public/dashboard 2.png new file mode 100644 index 0000000..67532d1 Binary files /dev/null and b/public/dashboard 2.png differ diff --git a/src/components/Controlbar.tsx b/src/components/dashboard/Controlbar.tsx similarity index 96% rename from src/components/Controlbar.tsx rename to src/components/dashboard/Controlbar.tsx index 3aeadc1..45310c1 100644 --- a/src/components/Controlbar.tsx +++ b/src/components/dashboard/Controlbar.tsx @@ -3,8 +3,8 @@ 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 "./Icons"; -import StereoLogo from "./StereoLogo"; +import { SolarUploadLinear, SvgSpinnersBarsRotateFade } from "../misc/Icons"; +import StereoLogo from "../misc/StereoLogo"; export default component$(() => { const loaded = useNanostore$(areFilesLoaded); diff --git a/src/components/Dropzone.tsx b/src/components/dashboard/Dropzone.tsx similarity index 100% rename from src/components/Dropzone.tsx rename to src/components/dashboard/Dropzone.tsx diff --git a/src/components/File.tsx b/src/components/dashboard/File.tsx similarity index 99% rename from src/components/File.tsx rename to src/components/dashboard/File.tsx index c68f603..382f25b 100644 --- a/src/components/File.tsx +++ b/src/components/dashboard/File.tsx @@ -3,7 +3,7 @@ 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 "./Icons"; +import { SolarClipboardAddBold, SolarDownloadMinimalisticBold, SolarTrashBin2Bold } from "../misc/Icons"; type FileProps = { file: StereoFile; diff --git a/src/components/landing/CallToAction.tsx b/src/components/landing/CallToAction.tsx new file mode 100644 index 0000000..1b4fc31 --- /dev/null +++ b/src/components/landing/CallToAction.tsx @@ -0,0 +1,23 @@ +import { component$ } from "@builder.io/qwik"; +import { OAUTH_LINK } from "~/lib/constants"; + +export default component$(() => ( +
+

+ ready to try the stereo experience? +

+

+ join over 100k other people hosting their files with stereo! +

+ + get started + +
+)); \ No newline at end of file diff --git a/src/components/landing/Footer.tsx b/src/components/landing/Footer.tsx new file mode 100644 index 0000000..16fef9d --- /dev/null +++ b/src/components/landing/Footer.tsx @@ -0,0 +1,29 @@ +import { component$ } from "@builder.io/qwik"; +import StereoLogo from "../misc/StereoLogo"; + +export default component$(() => { + return ( +
+
+
+ + + stereo.cat + + + + store all your precious moments with stereo. + + + + copyright © {new Date().getFullYear()} stereo.cat - all rights reserved. + +
+
+ +
+ +
+
+ ) +}) \ No newline at end of file diff --git a/src/components/landing/Hero.tsx b/src/components/landing/Hero.tsx new file mode 100644 index 0000000..298c35b --- /dev/null +++ b/src/components/landing/Hero.tsx @@ -0,0 +1,83 @@ +import { component$, useSignal } from "@builder.io/qwik"; +import { useRelativeMouse } from "~/hooks/mouse"; +import { OAUTH_LINK } from "~/lib/constants"; +import GradientBorder from "../misc/GradientBorder"; + +export default component$(() => { + const ref1 = useSignal(); + const mouse1 = useRelativeMouse(ref1); + + const ref2 = useSignal(); + const mouse2 = useRelativeMouse(ref2); + + return ( +
+
+
+

+ you bring the files, we'll bring the magic. +

+

+ stereo is no-bs file-host inspired by Tixte, that you'll love to use +

+ +
+ + + + + + +
+
+
+
+ ); +}); \ No newline at end of file diff --git a/src/components/landing/Navbar.tsx b/src/components/landing/Navbar.tsx new file mode 100644 index 0000000..85fb716 --- /dev/null +++ b/src/components/landing/Navbar.tsx @@ -0,0 +1,41 @@ +import { component$ } from "@builder.io/qwik"; +import StereoLogo from "../misc/StereoLogo"; + +export default component$(() => { + const items = [ + { text: "Synopsis", href: "#" }, + { text: "Discord", href: "#" }, + { text: "Dashboard", href: "/dashboard", highlighted: true }, + { text: "Pricing", href: "#" }, + { text: "Terms", href: "#" } + ]; + + return ( +
+
+ +
+ +
+ stereo.cat +
+
+ ) +}); \ No newline at end of file diff --git a/src/components/landing/Stats.tsx b/src/components/landing/Stats.tsx new file mode 100644 index 0000000..0da6ea6 --- /dev/null +++ b/src/components/landing/Stats.tsx @@ -0,0 +1,40 @@ +import { component$ } from "@builder.io/qwik"; + +export default component$(() => { + type StatProps = { + stat: string, + description: string; + } + + const Stat = component$(({ stat, description }: StatProps) => ( +
+
+

+ {stat.includes("+") ? stat.substring(0, stat.length - 1) : stat} + {stat.endsWith("+") ? + : ""} +

+
+

{description}

+
+ ) ); + + return ( +
+
+

statistics

+

+ we know what you're thinking, and we have the numbers to prove it. +

+
+
+ + + +
+
+ ) +}); \ No newline at end of file diff --git a/src/components/landing/Testimonials.tsx b/src/components/landing/Testimonials.tsx new file mode 100644 index 0000000..5303576 --- /dev/null +++ b/src/components/landing/Testimonials.tsx @@ -0,0 +1,112 @@ +import { $, component$, useOnDocument, useSignal } from "@builder.io/qwik"; +import ky from "ky"; + +export default component$(() => { + type TestimonialProps = { + nickname: string; + pfp?: string; + id?: string; + quote: string; + }; + + type LanyardResponse = { + data: { + discord_status: "online" | "idle" | "dnd"; + discord_user: { + avatar: string; + } + }, + success: boolean; + } + + const Testimonial = component$(({ nickname, id, quote, pfp }: TestimonialProps) => { + const lanyard = useSignal(); + + useOnDocument("DOMContentLoaded", $(async () => { + if (!id) return; + + try { + const response = await ky.get(`https://api.lanyard.rest/v1/users/${id}`).json(); + lanyard.value = response; + console.log("Lanyard data:", lanyard.value); + } catch (error) { + console.error("Error fetching lanyard data:", error); + } + })); + + return ( +
+
+ +
+ +
+

+

+ {quote} +

+

— {nickname}

+
+
+ ); + }); + + return ( +
+
+

testimonials

+

+ don't just take our word for it, hear it from our users. +

+
+
+ + + + + +
+

+ and many, many more... +

+
+ ); +}); \ No newline at end of file diff --git a/src/components/misc/GradientBorder.tsx b/src/components/misc/GradientBorder.tsx new file mode 100644 index 0000000..4507345 --- /dev/null +++ b/src/components/misc/GradientBorder.tsx @@ -0,0 +1,40 @@ +import { component$, CSSProperties, QwikIntrinsicElements, Slot } from "@builder.io/qwik"; + +type GradientProps = { + size: string; + from: string; + to: string; + direction?: string; +}; + +export default component$((props: GradientProps & QwikIntrinsicElements["div"]) => { + const { + size, + from, + to, + direction, + style: userStyle, + ...rest + } = props; + + const borderStyle: CSSProperties = { + padding: size, + background: `linear-gradient(${direction || "to bottom"}, ${from}, ${to})`, + display: "inline-block", + }; + + // Only spread userStyle if it's an object + const mergedStyle = + userStyle && typeof userStyle === "object" + ? { ...borderStyle, ...userStyle } + : borderStyle; + + return ( +
+ +
+ ); +}); \ No newline at end of file diff --git a/src/components/Icons.tsx b/src/components/misc/Icons.tsx similarity index 100% rename from src/components/Icons.tsx rename to src/components/misc/Icons.tsx diff --git a/src/components/StereoLogo.tsx b/src/components/misc/StereoLogo.tsx similarity index 100% rename from src/components/StereoLogo.tsx rename to src/components/misc/StereoLogo.tsx diff --git a/src/global.css b/src/global.css index 49a69bf..402189e 100644 --- a/src/global.css +++ b/src/global.css @@ -3,6 +3,5 @@ @theme { --font-sans: 'DM Sans', sans-serif; -} - -@plugin 'tailwind-scrollbar'; \ No newline at end of file + --color-stereo: #ff264e; +} \ No newline at end of file diff --git a/src/hooks/dropzone.tsx b/src/hooks/dropzone.ts similarity index 100% rename from src/hooks/dropzone.tsx rename to src/hooks/dropzone.ts diff --git a/src/hooks/mouse.ts b/src/hooks/mouse.ts new file mode 100644 index 0000000..151b8aa --- /dev/null +++ b/src/hooks/mouse.ts @@ -0,0 +1,27 @@ +import { $, Signal, useOnDocument, useStore } from "@builder.io/qwik"; + +export const useMouse = () => { + const pos = useStore({ x: 0, y: 0 }); + + useOnDocument("mousemove", $((event: MouseEvent) => { + const { clientX, clientY } = event; + pos.x = clientX; + pos.y = clientY; + })); + + return pos +} + +export const useRelativeMouse = (ref: Signal) => { + const pos = useStore({ x: 0, y: 0 }); + + useOnDocument("mousemove", $((event: MouseEvent) => { + const el = ref.value; + if (!el) return; + const rect = el.getBoundingClientRect(); + pos.x = event.clientX - rect.left; + pos.y = event.clientY - rect.top; + })); + + return pos; +}; \ No newline at end of file diff --git a/src/routes/dashboard/index.tsx b/src/routes/dashboard/index.tsx index c7e1727..98a3864 100644 --- a/src/routes/dashboard/index.tsx +++ b/src/routes/dashboard/index.tsx @@ -1,9 +1,9 @@ import { component$, useVisibleTask$ } from "@builder.io/qwik"; import { routeLoader$, type DocumentHead } from "@builder.io/qwik-city"; -import Controlbar from "~/components/Controlbar"; +import Controlbar from "~/components/dashboard/Controlbar"; // import Dropzone from "~/components/Dropzone"; -import File from "~/components/File"; -import { SolarUploadLinear, SvgSpinnersBarsRotateFade } from "~/components/Icons"; +import File from "~/components/dashboard/File"; +import { SolarUploadLinear, SvgSpinnersBarsRotateFade } from "~/components/misc/Icons"; import { useNanostore$ } from "~/hooks/nanostores"; import { api } from "~/lib/api"; import { areFilesLoaded, dashboardFiles } from "~/lib/stores"; @@ -12,7 +12,6 @@ import { StereoFile } from "~/lib/types"; export const useAuthCheck = routeLoader$(({ cookie, redirect: r }) => { const jwt = cookie.get("jwt"); if (jwt) return {}; - throw r(302, "/"); }); diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 1858e14..b1dc77d 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -1,18 +1,44 @@ +/* eslint-disable qwik/jsx-img */ import { component$ } from "@builder.io/qwik"; -import { routeLoader$ } from "@builder.io/qwik-city"; -import { OAUTH_LINK } from "~/lib/constants"; +import { DocumentHead, routeLoader$ } from "@builder.io/qwik-city"; +import CallToAction from "~/components/landing/CallToAction"; +import Footer from "~/components/landing/Footer"; +import Hero from "~/components/landing/Hero"; +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 }) => { - const jwt = cookie.get("jwt"); - if (!jwt) return {}; +export const useAuthCheck = routeLoader$(({ cookie, redirect: r, query }) => { + const jwt = cookie.get("jwt"); + const set = Boolean(query.get("jwt_set")); - throw r(302, "/dashboard"); + if (jwt && set) { + throw r(302, "/dashboard"); + } + return {}; }); export default component$(() => { - return ( - - ); -}); \ No newline at end of file + return ( + <> + +
+ + + + +
+