From f480ced756ef4be1a28fb9c8343b2bad81849cca Mon Sep 17 00:00:00 2001 From: Wanjohi <71614375+wanjohiryan@users.noreply.github.com> Date: Sat, 18 Jan 2025 07:12:47 +0300 Subject: [PATCH] =?UTF-8?q?=E2=AD=90=20feat:=20Connect=20the=20frontend=20?= =?UTF-8?q?to=20the=20API=20(#160)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/www/package.json | 5 +- apps/www/public/images/steam.png | Bin 0 -> 91637 bytes apps/www/src/routes/(auth)/device/index.tsx | 9 - .../src/routes/(auth)/login-test/index.tsx | 54 --- .../routes/(onboarding)/new/deploy/index.tsx | 20 - .../routes/(onboarding)/new/game/index.tsx | 79 ---- .../www/src/routes/(onboarding)/new/index.tsx | 217 ---------- .../routes/{home => (play)/[user]}/index.tsx | 266 +++++++++--- apps/www/src/routes/(play)/layout.tsx | 17 + .../routes/{ => (play)}/play/[id]/index.tsx | 0 .../{ => (public)}/(legal)/privacy/index.tsx | 21 +- .../{ => (public)}/(legal)/terms/index.tsx | 29 +- .../src/routes/{ => (public)}/blog/blog.css | 0 .../blog/gpu-passthru/index.mdx | 0 .../blog/how-nestri-works/index.mdx | 0 .../src/routes/{ => (public)}/blog/index.tsx | 4 +- .../src/routes/{ => (public)}/blog/layout.tsx | 0 .../{ => (public)}/blog/lorem-ipsum/index.mdx | 0 .../routes/{ => (public)}/changelog/index.tsx | 5 +- .../routes/{ => (public)}/contact/index.tsx | 3 +- .../{ => (public)}/fundraiser/index.tsx | 5 +- apps/www/src/routes/{ => (public)}/index.tsx | 47 ++- apps/www/src/routes/(public)/layout.tsx | 51 +++ .../routes/{ => (public)}/pricing/index.tsx | 70 ++-- apps/www/src/routes/(public)/thanks/index.tsx | 85 ++++ apps/www/src/routes/callback/index.tsx | 73 ++++ apps/www/src/routes/layout.tsx | 20 +- bun.lockb | Bin 747432 -> 753888 bytes infra/cli.ts | 10 - infra/dns.ts | 2 +- packages/core/instant.schema.ts | 27 +- packages/core/src/email/index.ts | 20 + packages/core/src/examples.ts | 19 + packages/core/src/profile/index.ts | 32 +- packages/core/src/subscription/index.ts | 205 +++++++++ packages/core/src/team/index.ts | 165 ++++++++ packages/core/src/user/index.ts | 2 +- packages/functions/src/api/index.ts | 8 +- packages/functions/src/api/session.ts | 4 +- packages/functions/src/api/subscription.ts | 132 ++++++ packages/functions/src/api/team.ts | 238 +++++++++++ packages/functions/src/api/user.ts | 46 ++ packages/functions/src/auth.ts | 37 +- packages/ui/globals.css | 170 ++++++-- packages/ui/package.json | 2 +- packages/ui/src/constants.ts | 4 + .../{github-banner.tsx => footer-banner.tsx} | 39 +- packages/ui/src/footer.tsx | 16 +- packages/ui/src/game-store/default.tsx | 89 ++++ packages/ui/src/game-store/index.tsx | 34 ++ packages/ui/src/home-nav-bar.tsx | 394 +++++++++++++----- packages/ui/src/index.ts | 5 +- packages/ui/src/nav-bar.tsx | 26 +- packages/ui/src/react/animate.tsx | 32 ++ packages/ui/src/react/index.ts | 3 +- packages/ui/src/svg.tsx | 11 + 56 files changed, 2109 insertions(+), 743 deletions(-) create mode 100644 apps/www/public/images/steam.png delete mode 100644 apps/www/src/routes/(auth)/device/index.tsx delete mode 100644 apps/www/src/routes/(auth)/login-test/index.tsx delete mode 100644 apps/www/src/routes/(onboarding)/new/deploy/index.tsx delete mode 100644 apps/www/src/routes/(onboarding)/new/game/index.tsx delete mode 100644 apps/www/src/routes/(onboarding)/new/index.tsx rename apps/www/src/routes/{home => (play)/[user]}/index.tsx (59%) create mode 100644 apps/www/src/routes/(play)/layout.tsx rename apps/www/src/routes/{ => (play)}/play/[id]/index.tsx (100%) rename apps/www/src/routes/{ => (public)}/(legal)/privacy/index.tsx (85%) rename apps/www/src/routes/{ => (public)}/(legal)/terms/index.tsx (85%) rename apps/www/src/routes/{ => (public)}/blog/blog.css (100%) rename apps/www/src/routes/{ => (public)}/blog/gpu-passthru/index.mdx (100%) rename apps/www/src/routes/{ => (public)}/blog/how-nestri-works/index.mdx (100%) rename apps/www/src/routes/{ => (public)}/blog/index.tsx (97%) rename apps/www/src/routes/{ => (public)}/blog/layout.tsx (100%) rename apps/www/src/routes/{ => (public)}/blog/lorem-ipsum/index.mdx (100%) rename apps/www/src/routes/{ => (public)}/changelog/index.tsx (99%) rename apps/www/src/routes/{ => (public)}/contact/index.tsx (99%) rename apps/www/src/routes/{ => (public)}/fundraiser/index.tsx (99%) rename apps/www/src/routes/{ => (public)}/index.tsx (83%) create mode 100644 apps/www/src/routes/(public)/layout.tsx rename apps/www/src/routes/{ => (public)}/pricing/index.tsx (94%) create mode 100644 apps/www/src/routes/(public)/thanks/index.tsx create mode 100644 apps/www/src/routes/callback/index.tsx delete mode 100644 infra/cli.ts create mode 100644 packages/core/src/subscription/index.ts create mode 100644 packages/core/src/team/index.ts create mode 100644 packages/functions/src/api/subscription.ts create mode 100644 packages/functions/src/api/team.ts create mode 100644 packages/functions/src/api/user.ts create mode 100644 packages/ui/src/constants.ts rename packages/ui/src/{github-banner.tsx => footer-banner.tsx} (76%) create mode 100644 packages/ui/src/game-store/default.tsx create mode 100644 packages/ui/src/game-store/index.tsx create mode 100644 packages/ui/src/react/animate.tsx create mode 100644 packages/ui/src/svg.tsx diff --git a/apps/www/package.json b/apps/www/package.json index 6d5c76e4..e4236d9e 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -34,11 +34,14 @@ "@builder.io/qwik": "^1.8.0", "@builder.io/qwik-city": "^1.8.0", "@builder.io/qwik-react": "0.5.0", - "@modular-forms/qwik": "^0.27.0", + "@modular-forms/qwik": "^0.29.0", "@nestri/input": "*", "@nestri/libmoq": "*", + "@nestri/sdk": "0.1.0-alpha.11", "@nestri/ui": "*", "@openauthjs/openauth": "^0.2.6", + "@polar-sh/checkout": "^0.1.8", + "@polar-sh/sdk": "^0.21.1", "@qwik-ui/headless": "^0.6.4", "@types/eslint": "8.56.10", "@types/howler": "^2.2.12", diff --git a/apps/www/public/images/steam.png b/apps/www/public/images/steam.png new file mode 100644 index 0000000000000000000000000000000000000000..357c96a0cd60cdbc4844c76155f9a9f76e75e698 GIT binary patch literal 91637 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%0Dw<>?5%t$)@7jObl3OH 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 literal 0 HcmV?d00001 diff --git a/apps/www/src/routes/(auth)/device/index.tsx b/apps/www/src/routes/(auth)/device/index.tsx deleted file mode 100644 index 88c75a05..00000000 --- a/apps/www/src/routes/(auth)/device/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { component$ } from "@builder.io/qwik" - -export default component$(() => { - return ( -
- Device -
- ) -}) \ No newline at end of file diff --git a/apps/www/src/routes/(auth)/login-test/index.tsx b/apps/www/src/routes/(auth)/login-test/index.tsx deleted file mode 100644 index 1b606cc1..00000000 --- a/apps/www/src/routes/(auth)/login-test/index.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { $, component$, useVisibleTask$ } from "@builder.io/qwik"; -import { createClient } from "@openauthjs/openauth/client"; - -function getHashParams(url: URL) { - const urlString = url.toString() - const hash = urlString.substring(urlString.indexOf('#') + 1); // Extract the part after the # - console.log("url", hash) - const params = new URLSearchParams(hash); - const paramsObj = {} as any; - for (const [key, value] of params.entries()) { - paramsObj[key] = decodeURIComponent(value); - } - console.log(paramsObj) - return paramsObj; -} - -function removeURLParams() { - const newURL = window.location.origin + window.location.pathname; // Just origin and path - window.location.replace(newURL); -} - -export default component$(() => { - - const login = $(async () => { - const client = createClient({ - clientID: "www", - issuer: "https://auth.lauryn.dev.nestri.io" - }) - - const { url } = await client.authorize("http://localhost:5173/login-test", "token", { pkce: true }) - window.location.href = url - }) - - // eslint-disable-next-line qwik/no-use-visible-task - useVisibleTask$(async () => { - const urlObj = new URL(window.location.href); - const params = getHashParams(urlObj) - if (params.access_token && params.refresh_token) { - - localStorage.setItem("access_token", params.access_token) - localStorage.setItem("refresh_token", params.refresh_token) - removeURLParams() - } - - - }) - return ( -
- -
- ) -}) \ No newline at end of file diff --git a/apps/www/src/routes/(onboarding)/new/deploy/index.tsx b/apps/www/src/routes/(onboarding)/new/deploy/index.tsx deleted file mode 100644 index f37a8fa6..00000000 --- a/apps/www/src/routes/(onboarding)/new/deploy/index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { component$ } from "@builder.io/qwik"; - -export default component$(() => { - return ( - <> -
-
- Step 2 of 2 -

- You're almost done -

-

- Please follow the steps to configure your game and install it -

-
- -
- - ) -}) \ No newline at end of file diff --git a/apps/www/src/routes/(onboarding)/new/game/index.tsx b/apps/www/src/routes/(onboarding)/new/game/index.tsx deleted file mode 100644 index c2b06112..00000000 --- a/apps/www/src/routes/(onboarding)/new/game/index.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { component$ } from "@builder.io/qwik"; - -const games = [ - { - cover: 'https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/444200/library_600x900_2x.jpg', - release_date: 1478710740000, - compatibility: 'playable', - name: 'World of Tanks Blitz', - appid: 444200 - }, - { - cover: 'https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/1085660/library_600x900_2x.jpg', - release_date: 1569949200000, - compatibility: 'unsupported', - name: 'Destiny 2', - appid: 1085660 - }, - { - cover: 'https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/1172470/library_600x900_2x.jpg', - release_date: 1604548800000, - compatibility: 'playable', - name: 'Apex Legends', - appid: 1172470 - }, - { - cover: 'https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/1229380/library_600x900_2x.jpg', - release_date: 1614870001000, - compatibility: 'perfect', - name: 'Everhood', - appid: 1229380 - }, - { - cover: 'https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/1637320/library_600x900_2x.jpg', - release_date: 1664296270000, - compatibility: 'perfect', - name: 'Dome Keeper', - appid: 1637320 - }, - { - cover: 'https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/2581970/library_600x900_2x.jpg', - release_date: 1698246127000, - compatibility: 'playable', - name: 'Shell Runner - Prelude', - appid: 2581970 - } -] - -export default component$(() => { - return ( - <> -
-
- Step 1 of 2 -

- Let's play something cool -

-

- Choose a game to play from your Steam library -

-
-
    - {games.map((game) => ( -
  • -
    -
    - {game.name} -
    -

    {game.name}

    -
    - Logo -
    -
    -
  • - ))} -
-
- - ) -}) \ No newline at end of file diff --git a/apps/www/src/routes/(onboarding)/new/index.tsx b/apps/www/src/routes/(onboarding)/new/index.tsx deleted file mode 100644 index 98b6ed00..00000000 --- a/apps/www/src/routes/(onboarding)/new/index.tsx +++ /dev/null @@ -1,217 +0,0 @@ -// import { auth } from "@nestri/ui"; -import { Button } from "@nestri/ui/react" -import { buttonVariants } from "@nestri/ui/design"; -// import { type Provider } from "@supabase/supabase-js"; -import { Link } from "@builder.io/qwik-city"; -import { $, component$, useSignal } from "@builder.io/qwik"; - -// type AuthFlowProps = { -// flow: string -// } - -const flow: any = "join" - -export default component$(() => { - const isLoading = useSignal(false); - const isGHLoading = useSignal(false); - const setIsLoading = $((v: boolean) => isLoading.value = v) - const setIsGHLoading = $((v: boolean) => isGHLoading.value = v) - // const location = useLocation(); - - // const authenticateUser = $(async (provider: any) => { - // await auth.openWindow(`${location.url.origin}/api/auth/${provider}`) - // }) - - return ( - <> -
-
- {/* Nestri Logo */} -

- {flow == "login" ? "Login" : "Join"} -

- {flow == "join" ? ( -

- Already have an account? - Login - -

- ) : ( -

- Don't have an account yet? - Join - -

- )} -
- {/* await authenticateUser("discord")} - size="md" - class="w-full gap-4 from-[#5865F2] to-[#4445e7] [--btn-border-color:#3836cc] dark:border-[#8093f9]/75"> - - - - - - - - Continue with Discord - - */} - - - - - - - - - - Link your Steam account - -
- - - - - - - - - - Link your Epic Games account - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Link your GOG.com account - -
- - - {flow == "join" ? ( -

- By creating an account, you agree to our
- Terms of Service - and Privacy Policy - -

- ) : ( -
- )} -
-
- - ) -}) \ No newline at end of file diff --git a/apps/www/src/routes/home/index.tsx b/apps/www/src/routes/(play)/[user]/index.tsx similarity index 59% rename from apps/www/src/routes/home/index.tsx rename to apps/www/src/routes/(play)/[user]/index.tsx index 1993ed6e..217927a8 100644 --- a/apps/www/src/routes/home/index.tsx +++ b/apps/www/src/routes/(play)/[user]/index.tsx @@ -1,8 +1,10 @@ -import { Avatar } from "@nestri/ui"; -// import { } from "@qwik-ui/headless"; -import { component$ } from "@builder.io/qwik"; -import { HomeNavBar, Modal, SimpleFooter } from "@nestri/ui"; import { cn } from "@nestri/ui/design"; +import type Nestri from "@nestri/sdk"; +import { Modal } from "@qwik-ui/headless"; +import { Avatar, Icons } from "@nestri/ui"; +import { Link, routeLoader$ } from "@builder.io/qwik-city"; +import { HomeNavBar, SimpleFooter, GameStoreButton } from "@nestri/ui"; +import { component$, useSignal, useStore, useVisibleTask$ } from "@builder.io/qwik"; const games = [ { @@ -52,60 +54,96 @@ const games = [ ] +export const useCurrentProfile = routeLoader$(async ({ sharedMap }) => { + const res = sharedMap.get("profile") as Nestri.Users.UserRetrieveResponse.Data | null + + return res + // return { + // avatarUrl: undefined, + // discriminator: 47, + // username: "WanjohiRyan" + // } +}) +//bg-blue-100 rounded-lg p-4 min-w-16 text-center +const TimeUnit = ({ value, label }: { value: number, label: string }) => ( +
+
+ {new Array(2).fill(0).map((_, key) => { + const [digitOne, digitTwo] = value.toString().padStart(2, '0') + return ( +
+
+
9
+
0
+
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
0
+
+
+ ) + })} +
+
{label}
+
+); + +const random = Math.floor(100 * Math.random()) + export default component$(() => { + const profile = useCurrentProfile() + const isNewPerson = useSignal(false) + const targetDate = new Date('2025-01-29T23:59:00Z'); + + const timeLeft = useStore({ + days: 0, + hours: 0, + minutes: 0, + seconds: 0 + }) + + // eslint-disable-next-line qwik/no-use-visible-task + useVisibleTask$(() => { + isNewPerson.value = true + + const calculateTimeLeft = () => { + const difference = targetDate.getTime() - new Date().getTime(); + + if (difference > 0) { + const days = Math.floor(difference / (1000 * 60 * 60 * 24)); + const hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + const minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60)); + const seconds = Math.floor((difference % (1000 * 60)) / 1000); + + timeLeft.days = days + timeLeft.hours = hours + timeLeft.minutes = minutes + timeLeft.seconds = seconds + } + }; + + calculateTimeLeft(); + + const timer = setInterval(calculateTimeLeft, 1000); + + return () => clearInterval(timer); + }) + return ( -
- +
+ {profile.value && }
- - - + - + Add another Linux machine @@ -137,7 +175,7 @@ export default component$(() => {
-
+

@@ -176,15 +214,15 @@ export default component$(() => { maskImage: `url('data:image/svg+xml,'),url('data:image/svg+xml,')` }} > - +
))}
- +
-

{`${Math.floor(Math.random() * 100)} people are currently playing this game`}

+

{`${(key + 1) * random} people are currently playing this game`}

@@ -210,7 +248,7 @@ export default component$(() => { -
+
@@ -287,13 +325,13 @@ export default component$(() => { {game.name}
-
-
+
{ Delta Force is a first-person shooter which offers players both a single player campaign based on the movie Black Hawk Down, but also large-scale PvP multiplayer action. The game was formerly known as Delta Force: Hawk Ops.

@@ -341,8 +379,112 @@ export default component$(() => { ))}
- + + + +
+
+ +
+
+
+
+ + + + + + +
+
Nestri needs your help
+
+ {profile.value && profile.value.avatarUrl ? (Avatar) : ()} + + {profile.value && profile.value.username} + +
+
+
+
+
What's wrong?
+
+
+
+
+
1
+
We're almost ready to launch Nestri, but server costs are our biggest hurdle right now.
+
+
+
2
+
As a bootstrapped startup (yeah, just a few passionate developers!), we're reaching out to our early believers.
+
+
+
3
+
Your early access subscription will directly fund our initial server infrastructure, helping us bring self-hosted cloud gaming to life.
+
+
+
+
+
+ What you get +
+
+
+
+ +
+
+ Schedule 1-on-1 calls with the Founders +
+
+
+
+ +
+
+ Keep your special early supporter pricing forever +
+
+
+
+ +
+
+ Priority feature requests +
+
+
+
+
+
Full access in
+
+ + + + +
+
+
+
+ {/**https://sandbox-api.polar.sh/v1/checkout-links/polar_cl_3Kf9mOEl8We2ZnmYr0tolrFPfHiPvlC71XgZy4Jd2ni/redirect */} + Get early supporter price +
+ +
+
+
+
+
+
+
+
+ + + {/*
*/} ) }) \ No newline at end of file diff --git a/apps/www/src/routes/(play)/layout.tsx b/apps/www/src/routes/(play)/layout.tsx new file mode 100644 index 00000000..ac31b8b1 --- /dev/null +++ b/apps/www/src/routes/(play)/layout.tsx @@ -0,0 +1,17 @@ +import type Nestri from "@nestri/sdk" +import { component$, Slot } from "@builder.io/qwik"; +import { type RequestHandler } from "@builder.io/qwik-city"; + +export const onRequest: RequestHandler = async ({ url, redirect, sharedMap }) => { + const currentProfile = sharedMap.get("profile") as Nestri.Users.UserRetrieveResponse.Data | null + + if (!currentProfile) { + throw redirect(308, `${url.origin}`) + } +} + +export default component$(() => { + return ( + + ) +}) \ No newline at end of file diff --git a/apps/www/src/routes/play/[id]/index.tsx b/apps/www/src/routes/(play)/play/[id]/index.tsx similarity index 100% rename from apps/www/src/routes/play/[id]/index.tsx rename to apps/www/src/routes/(play)/play/[id]/index.tsx diff --git a/apps/www/src/routes/(legal)/privacy/index.tsx b/apps/www/src/routes/(public)/(legal)/privacy/index.tsx similarity index 85% rename from apps/www/src/routes/(legal)/privacy/index.tsx rename to apps/www/src/routes/(public)/(legal)/privacy/index.tsx index e7443dca..5b83f9ee 100644 --- a/apps/www/src/routes/(legal)/privacy/index.tsx +++ b/apps/www/src/routes/(public)/(legal)/privacy/index.tsx @@ -1,26 +1,14 @@ /* eslint-disable qwik/no-react-props */ -import { Title, Text} from "@nestri/ui/react"; -import { buttonVariants, cn } from "@nestri/ui/design"; -import { component$ } from "@builder.io/qwik"; import { Link } from "@builder.io/qwik-city"; +import { Title, Text } from "@nestri/ui/react"; +import { component$ } from "@builder.io/qwik"; +import { buttonVariants } from "@nestri/ui/design"; export default component$(() => { return (
- {/**Gradient to hide the ending of the checkered bg at the bottom*/} - {/*
*/} - -
- +
Nestri's Privacy Policy @@ -177,6 +165,7 @@ export default component$(() => { <Text align="center" className="pt-3"> 💖 Thank you for trusting Nestri with your data and gaming experience.💖 <br /> + <br /> We are committed to safeguarding your personal information and ensuring your privacy. </Text> </div> </section> diff --git a/apps/www/src/routes/(legal)/terms/index.tsx b/apps/www/src/routes/(public)/(legal)/terms/index.tsx similarity index 85% rename from apps/www/src/routes/(legal)/terms/index.tsx rename to apps/www/src/routes/(public)/(legal)/terms/index.tsx index 988067b5..4dc2923b 100644 --- a/apps/www/src/routes/(legal)/terms/index.tsx +++ b/apps/www/src/routes/(public)/(legal)/terms/index.tsx @@ -1,24 +1,13 @@ /* eslint-disable qwik/no-react-props */ -import { Title, Text } from "@nestri/ui/react"; -import { buttonVariants, cn } from "@nestri/ui/design"; -import { component$ } from "@builder.io/qwik"; import { Link } from "@builder.io/qwik-city"; +import { Title, Text } from "@nestri/ui/react"; +import { component$ } from "@builder.io/qwik"; +import { buttonVariants } from "@nestri/ui/design"; export default component$(() => { return ( <div class="w-screen relative" > - {/**Gradient to hide the ending of the checkered bg at the bottom*/} - {/* <div class="absolute inset-0 dark:[background:radial-gradient(60.1852%_65%_at_50%_52%,rgba(255,255,255,0)_41.4414%,theme(colors.gray.950,0.7)_102%)] [background:radial-gradient(60.1852%_65%_at_50%_52%,rgba(255,255,255,0)_41.4414%,theme(colors.gray.50,0.7)_102%)] h-screen w-screen overflow-hidden max-w-[100vw] top-0 left-0 right-0 select-none" /> */} - <nav class="w-full h-[70px] lg:flex hidden sticky top-0 z-50 py-4 justify-center items-center" > - <div class="w-full left-1/2 relative -translate-x-[40%]"> - <Link href="/" class={cn(buttonVariants.outlined({ intent: "neutral", size: "md" }), "w-max")}> - <svg xmlns="http://www.w3.org/2000/svg" class="size-[20px] -rotate-90" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="1.5"><path stroke-linejoin="round" d="m17 9.5l-5-5l-5 5" /><path d="M12 4.5v10c0 1.667-1 5-5 5" opacity=".5" /></g></svg> - {/* <svg xmlns="http://www.w3.org/2000/svg" class="size-[20px]" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10" opacity=".5" /><path stroke-linecap="round" stroke-linejoin="round" d="m15.5 9l-3 3l3 3m-4-6l-3 3l3 3" /></g></svg> */} - Go Back - </Link> - </div> - </nav> - <section class="px-4 relative lg:-top-[70px]" > + <section class="px-4 relative" > <div class="mx-auto select-text max-w-xl overflow-x-hidden py-8 [&_h1]:text-3xl flex relative gap-4 w-full flex-col" > <Title className="py-4 text-4xl" > Nestri's Terms of Service @@ -30,7 +19,7 @@ export default component$(() => { </Text> <Text> - Welcome to Nestri and thank you for using our services. We are an innovative cloud gaming platform that offers both self-hosted and hosted versions for gamers without GPUs. + Welcome to Nestri and thank you for using our services. We are an innovative cloud gaming platform that offers both self-hosted and hosted versions for gamers to play online with friends and family. <br /> <br /> By using Nestri, you agree to these Terms of Service ("Terms"). If you have any questions, feel free to contact us. @@ -38,11 +27,11 @@ export default component$(() => { <Title>Who We Are - Nestri is an open-source cloud gaming platform that lets you play games on your own terms — invite friends to join your gaming sessions, share your game library, and take even more control by hosting your own gaming server. Our hosted version is perfect for those who need GPU support, providing seamless gaming experiences for everyone. + Nestri is an open-source cloud gaming platform that lets you play games on your own terms — invite friends to join your gaming sessions, share your game library, and take even more control by hosting your own gaming server. Our hosted version is perfect for those who need GPU support. Privacy and Security - We take your privacy and security very seriously. We adhere to stringent data protection laws and ensure that all data, including games downloaded from Steam on behalf of the user, is encrypted. We also collect and log IP addresses to avoid abuse and ensure the security of our services. For more details, please review our + We take your privacy and security very seriously. We adhere to stringent data protection laws and ensure that all data, is secured. We also collect and log IP addresses to avoid abuse and ensure the security of our services. For more details, please review our @@ -59,7 +48,7 @@ export default component$(() => {

Check the list here.
@@ -176,7 +165,7 @@ export default component$(() => { .
- 💖 Thank you for choosing Nestri for your cloud gaming needs! 💖 + 💖 Thank you for choosing Nestri for your cloud gaming needs! 💖
diff --git a/apps/www/src/routes/blog/blog.css b/apps/www/src/routes/(public)/blog/blog.css similarity index 100% rename from apps/www/src/routes/blog/blog.css rename to apps/www/src/routes/(public)/blog/blog.css diff --git a/apps/www/src/routes/blog/gpu-passthru/index.mdx b/apps/www/src/routes/(public)/blog/gpu-passthru/index.mdx similarity index 100% rename from apps/www/src/routes/blog/gpu-passthru/index.mdx rename to apps/www/src/routes/(public)/blog/gpu-passthru/index.mdx diff --git a/apps/www/src/routes/blog/how-nestri-works/index.mdx b/apps/www/src/routes/(public)/blog/how-nestri-works/index.mdx similarity index 100% rename from apps/www/src/routes/blog/how-nestri-works/index.mdx rename to apps/www/src/routes/(public)/blog/how-nestri-works/index.mdx diff --git a/apps/www/src/routes/blog/index.tsx b/apps/www/src/routes/(public)/blog/index.tsx similarity index 97% rename from apps/www/src/routes/blog/index.tsx rename to apps/www/src/routes/(public)/blog/index.tsx index 4b3bec86..0e0b18ba 100644 --- a/apps/www/src/routes/blog/index.tsx +++ b/apps/www/src/routes/(public)/blog/index.tsx @@ -1,6 +1,5 @@ -import { component$ } from "@builder.io/qwik" import { Link } from "@builder.io/qwik-city" -import { NavBar } from "@nestri/ui" +import { component$ } from "@builder.io/qwik" import { MotionComponent, TitleSection, transition } from "@nestri/ui/react" const blogs = [ @@ -15,7 +14,6 @@ const blogs = [ export default component$(() => { return (
- { return ( <> - { return ( <> - { return ( <> - {
-
+
) }) diff --git a/apps/www/src/routes/index.tsx b/apps/www/src/routes/(public)/index.tsx similarity index 83% rename from apps/www/src/routes/index.tsx rename to apps/www/src/routes/(public)/index.tsx index 7b8aefb7..2c01f495 100644 --- a/apps/www/src/routes/index.tsx +++ b/apps/www/src/routes/(public)/index.tsx @@ -1,8 +1,9 @@ -import { component$ } from "@builder.io/qwik"; -import { type DocumentHead } from "@builder.io/qwik-city"; -import { HeroSection, MotionComponent, transition } from "@nestri/ui/react" -import { NavBar, Footer } from "@nestri/ui" +import { Footer } from "@nestri/ui" import { cn } from "@nestri/ui/design"; +import { component$ } from "@builder.io/qwik"; +import { HeroSection, MotionComponent, transition } from "@nestri/ui/react" +import { type RequestHandler, type DocumentHead } from "@builder.io/qwik-city"; + const tags = [ { @@ -40,25 +41,49 @@ const games = [ "https://assets-prd.ignimgs.com/2023/03/22/keyart-wide-1-1679503853654-1679505306655.jpeg", "https://assets-prd.ignimgs.com/2022/11/09/coffee-talk-episode-1-button-fin-1668033710468.jpg", "https://assets-prd.ignimgs.com/2022/06/15/stalker2chornobyl-1655253282275.jpg", - "https://assets-prd.ignimgs.com/2022/05/24/call-of-duty-modern-warfare-2-button-02-1653417394041.jpg", + "https://assets-prd.ignimgs.com/2023/05/27/alanwake2-1685200534966.jpg", "https://assets-prd.ignimgs.com/2023/02/16/apexrevelry-1676588335122.jpg" ] +export const onGet: RequestHandler = async ({ request, send }) => { + const userAgent = request.headers.get('user-agent') || '' + const isCurl = userAgent.toLowerCase().includes('curl'); + + //TODO: + if (isCurl) { + const response = new Response( + `#!/bin/bash + +echo "Not yet ready 😅\n" + +echo "Consider joining our Discord channel (https://discord.com/invite/Y6etn3qKZ3) for the latest updates \n" + `, { + status: 200, + headers: { + 'Content-Type': 'text/plain', + 'Content-Disposition': 'attachment; filename="nestri.sh"' + } + }) + send(response) + } +}; + // FIXME: Change up the copy -//TODO: Use a db to query all this -//TODO: Add the search modal -// TODO: Add the game modal +//TODO: Add the demo video/gif export default component$(() => { + return (
-
- +
@@ -254,20 +274,20 @@ export default component$(() => {
- +
-

Family

+

Pro

@@ -329,7 +349,7 @@ export default component$(() => { class="overflow-hidden absolute cursor-pointer z-30 top-0 left-0 opacity-0 h-full w-full" />
-
+

{convertToTitle(priceValue.value)}

@@ -455,14 +475,14 @@ export default component$(() => {
- +
1 Feature is in development @@ -483,15 +503,15 @@ export default component$(() => {

Looking for something else? Use Nestri as your own on our servers or yours. Flexible licensing and white-glove onboarding included.

- +
- Organization Account · Security Restrictions · Custom Events · Single Sign On · Advanced Integrations · Additional APIs · Custom-Built Features · - Organization Account · Security Restrictions · Custom Events · Single Sign On · Advanced Integrations · Additional APIs · Custom-Built Features · + Organization Account · Security Restrictions · Custom Parties · Single Sign On · Advanced Integrations · Additional APIs · Custom-Built Features · + Organization Account · Security Restrictions · Custom Parties · Single Sign On · Advanced Integrations · Additional APIs · Custom-Built Features ·
diff --git a/apps/www/src/routes/(public)/thanks/index.tsx b/apps/www/src/routes/(public)/thanks/index.tsx new file mode 100644 index 00000000..d7e2f89d --- /dev/null +++ b/apps/www/src/routes/(public)/thanks/index.tsx @@ -0,0 +1,85 @@ +import Nestri from '@nestri/sdk'; +import { routeLoader$ } from '@builder.io/qwik-city'; +import { component$, useVisibleTask$ } from '@builder.io/qwik'; + +// function getCookie(cname: string) { +// const name = cname + "="; +// const ca = document.cookie.split(';'); +// for (let i = 0; i < ca.length; i++) { +// let c = ca[i]; +// while (c.charAt(0) == ' ') { +// c = c.substring(1); +// } +// if (c.indexOf(name) == 0) { +// return c.substring(name.length, c.length); +// } +// } +// return ""; +// } + +// function getParams(url: URL) { +// const urlString = url.toString() +// const hash = urlString.substring(urlString.indexOf('?') + 1); // Extract the part after the # +// const params = new URLSearchParams(hash); +// const paramsObj = {} as any; +// for (const [key, value] of params.entries()) { +// paramsObj[key] = decodeURIComponent(value); +// } +// console.log(paramsObj) +// return paramsObj; +// } + +//FIXME: There is an issue where the cookie cannot be found, tbh, i dunno what drugs Qwik is on +export const useSubscribe = routeLoader$(async ({ url, cookie }) => { + const access = cookie.get("access_token") + if (access) { + const bearerToken = access.value + console.log("bearerToken", bearerToken) + + const nestriClient = new Nestri({ + bearerToken, + baseURL: "https://api.nestri.io" + }) + + const checkout_id = url.searchParams.get("checkout") + + if (checkout_id) { + console.log("checkout", checkout_id) + + await nestriClient.subscriptions.create({ + checkoutID: checkout_id + }) + + return "okey" + } + } +}) + +export default component$(() => { + const subscribe = useSubscribe() + // eslint-disable-next-line qwik/no-use-visible-task + useVisibleTask$(async () => { + console.log("subscribe", subscribe) + }) + // const bearerToken = getCookie("access_token") + // // console.log("bearerToken", bearerToken) + // const nestriClient = new Nestri({ + // bearerToken, + // baseURL: "https://api.lauryn.dev.nestri.io" + // }) + // const urlObj = new URL(window.location.href) + // const checkout_id = getParams(urlObj).checkout + + // if (checkout_id) { + // await nestriClient.subscriptions.create({ + // checkoutID: checkout_id + // }) + // } + // }) + + return ( +
+

Thank you, now check your email for more details

+
+ ) +}) \ No newline at end of file diff --git a/apps/www/src/routes/callback/index.tsx b/apps/www/src/routes/callback/index.tsx new file mode 100644 index 00000000..fdea0732 --- /dev/null +++ b/apps/www/src/routes/callback/index.tsx @@ -0,0 +1,73 @@ +import Nestri from "@nestri/sdk"; +import { component$, useVisibleTask$ } from "@builder.io/qwik"; +import { createClient } from "@openauthjs/openauth/client"; +import { routeLoader$, useNavigate, type CookieOptions } from "@builder.io/qwik-city"; + +export const useLoggedIn = routeLoader$(async ({ query, url, cookie }) => { + const code = query.get("code") + if (code) { + const redirect_uri = url.origin + "/callback" + const cookieOptions: CookieOptions = { + path: "/", + sameSite: "lax", + secure: false, // Only send cookies over HTTPS + //FIXME: This causes weird issues in Qwik + httpOnly: true, // Prevent JavaScript access to cookies + expires: new Date(Date.now() + 24 * 10 * 60 * 60 * 1000), // expires in like 10 days + } + + const client = createClient({ + clientID: "www", + issuer: "https://auth.nestri.io" + }) + + const tokens = await client.exchange(code, redirect_uri) + if (!tokens.err) { + const access_token = tokens.tokens.access + const refresh_token = tokens.tokens.refresh + + cookie.set("access_token", access_token, cookieOptions) + cookie.set("refresh_token", refresh_token, cookieOptions) + + const bearerToken = access_token + + const nestriClient = new Nestri({ + bearerToken, + baseURL: "https://api.nestri.io" + }) + + //TODO: Use subjects instead + const currentProfile = await nestriClient.users.retrieve() + const username = currentProfile.data.username + return username + } + } +}) + +export default component$(() => { + const username = useLoggedIn() + const navigate = useNavigate(); + + // eslint-disable-next-line qwik/no-use-visible-task + useVisibleTask$(() => { + if (username.value) { + setTimeout(async () => { + await navigate(`${window.location.origin}/${username.value}`) + }, 500); + } + }) + + return ( +
+ +
+
+ {new Array(12).fill(0).map((i, k) => ( +
+ ))} +
+
+ We are confirming your identity... +
+ ) +}) \ No newline at end of file diff --git a/apps/www/src/routes/layout.tsx b/apps/www/src/routes/layout.tsx index 38176ed0..59b6e6e3 100644 --- a/apps/www/src/routes/layout.tsx +++ b/apps/www/src/routes/layout.tsx @@ -1,6 +1,7 @@ -import { component$, Slot } from "@builder.io/qwik"; +import Nestri from "@nestri/sdk"; import { NavProgress } from "@nestri/ui"; -import type { DocumentHead, RequestHandler } from "@builder.io/qwik-city"; +import { component$, Slot } from "@builder.io/qwik"; +import { type DocumentHead, type RequestHandler } from "@builder.io/qwik-city"; export const onGet: RequestHandler = async ({ cacheControl }) => { // Control caching for this request for best performance and to reduce hosting costs: @@ -13,6 +14,21 @@ export const onGet: RequestHandler = async ({ cacheControl }) => { }); }; +export const onRequest: RequestHandler = async ({ cookie, sharedMap }) => { + const access = cookie.get("access_token") + if (access) { + const bearerToken = access.value + + const nestriClient = new Nestri({ + bearerToken, + baseURL: "https://api.nestri.io" + }) + + const currentProfile = await nestriClient.users.retrieve() + sharedMap.set("profile", currentProfile.data) + } +} + export default component$(() => { return ( <> diff --git a/bun.lockb b/bun.lockb index f07d1f0ad8947fe30338a9f99eb580f7c02324f2..87c2a0ab3b7fb8f02805de5fbfe0b9e36a96df14 100755 GIT binary patch delta 35886 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 NkdMJ%7<<_)`ad63*FFFM delta 31538 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 _C8xoHwylq}uq(kP(3S?DhDhCU|ivcs(9 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 L7gIITedhijKzVhm diff --git a/infra/cli.ts b/infra/cli.ts deleted file mode 100644 index 0b5f2728..00000000 --- a/infra/cli.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { auth, urls } from "./api" - - -// export const cmd = new sst.x.DevCommand("Cmd", { -// link: [urls, auth], -// dev: { -// autostart: true, -// command: "cd packages/cmd && go run main.go" -// } -// }) \ No newline at end of file diff --git a/infra/dns.ts b/infra/dns.ts index 2dd0569e..e23d3a8e 100644 --- a/infra/dns.ts +++ b/infra/dns.ts @@ -1,6 +1,6 @@ export const domain = { - production: "prod.nestri.io", //temporary use until we go into the real production + production: "nestri.io", dev: "dev.nestri.io", }[$app.stage] || $app.stage + ".dev.nestri.io"; diff --git a/packages/core/instant.schema.ts b/packages/core/instant.schema.ts index 3b28c460..a841847c 100644 --- a/packages/core/instant.schema.ts +++ b/packages/core/instant.schema.ts @@ -14,11 +14,17 @@ const _schema = i.schema({ profiles: i.entity({ avatarUrl: i.string().optional(), username: i.string().indexed(), - ownerID: i.string().unique().indexed(), updatedAt: i.date(), createdAt: i.date(), discriminator: i.string().indexed() }), + teams: i.entity({ + name: i.string(), + slug: i.string().unique().indexed(), + deletedAt: i.date().optional().indexed(), + updatedAt: i.date(), + createdAt: i.date(), + }), games: i.entity({ name: i.string(), steamID: i.number().unique().indexed(), @@ -29,12 +35,31 @@ const _schema = i.schema({ endedAt: i.date().optional().indexed(), public: i.boolean().indexed(), }), + subscriptions: i.entity({ + checkoutID: i.string(), + // quantity: i.number(), + // frequency: i.string(), + canceledAt: i.date(), + // next: i.date() + }) }, links: { + UserSubscriptions: { + forward: { on: "subscriptions", has: "one", label: "owner" }, + reverse: { on: "$users", has: "many", label: "subscriptions" } + }, UserProfiles: { forward: { on: "profiles", has: "one", label: "owner" }, reverse: { on: "$users", has: "one", label: "profile" } }, + TeamsOwned: { + forward: { on: "teams", has: "one", label: "owner" }, + reverse: { on: "$users", has: "many", label: "teamsOwned" }, + }, + TeamsJoined: { + forward: { on: "teams", has: "many", label: "members" }, + reverse: { on: "$users", has: "many", label: "teamsJoined" }, + }, UserMachines: { forward: { on: "machines", has: "one", label: "owner" }, reverse: { on: "$users", has: "many", label: "machines" } diff --git a/packages/core/src/email/index.ts b/packages/core/src/email/index.ts index b552aa33..769f1b31 100644 --- a/packages/core/src/email/index.ts +++ b/packages/core/src/email/index.ts @@ -22,4 +22,24 @@ export namespace Email { console.log("error sending email", error) } } + + export async function sendWelcome( + to: string, + name: string, + ) { + + try { + await Client().sendTransactionalEmail( + { + transactionalId: "cm61jrbbx02twlstfwfcywt5u", + email: to, + dataVariables: { + name + } + } + ); + } catch (error) { + console.log("error sending email", error) + } + } } \ No newline at end of file diff --git a/packages/core/src/examples.ts b/packages/core/src/examples.ts index 17b2c130..5a99b1f3 100644 --- a/packages/core/src/examples.ts +++ b/packages/core/src/examples.ts @@ -14,6 +14,25 @@ export module Examples { updatedAt: '2025-01-09T01:56:23.902Z' } + export const Subscription = { + id: "0bfcb712-df13-4454-81a8-fbee66eddca4", + checkoutID: "0bfcb712-df43-4454-81a8-fbee66eddca4", + // productID: "0bfcb712-df43-4454-81a8-fbee66eddca4", + // quantity: 1, + // frequency: "monthly" as const, + // next: '2025-01-09T01:56:23.902Z', + canceledAt: '2025-02-09T01:56:23.902Z' + } + + export const Team = { + id: "0bfcb712-df13-4454-81a8-fbee66eddca4", + owner: true, + name: "Jane Doe's Games", + slug: "jane-does-games", + createdAt: '2025-01-04T11:56:23.902Z', + updatedAt: '2025-01-09T01:56:23.902Z' + } + export const Machine = { id: "0bfcb712-df13-4454-81a8-fbee66eddca4", hostname: "DESKTOP-EUO8VSF", diff --git a/packages/core/src/profile/index.ts b/packages/core/src/profile/index.ts index f28bdea3..1d20fb99 100644 --- a/packages/core/src/profile/index.ts +++ b/packages/core/src/profile/index.ts @@ -5,6 +5,7 @@ import { Examples } from "../examples"; import databaseClient from "../database"; import { groupBy, map, pipe, values } from "remeda" import { id as createID } from "@instantdb/admin"; +import { useCurrentUser } from "../actor"; export module Profiles { const MAX_ATTEMPTS = 50; @@ -124,10 +125,6 @@ export module Profiles { export const create = fn(z.object({ username: z.string(), customDiscriminator: z.string().optional(), avatarUrl: z.string().optional(), owner: z.string() }), async (input) => { const username = sanitizeUsername(input.username); - // if (!username || username.length < 2 || username.length > 32) { - // // throw new Error('Invalid username length'); - // } - const db = databaseClient() const id = createID() const now = new Date().toISOString() @@ -150,6 +147,7 @@ export module Profiles { } } } + const res = await db.query(query) const profiles = res.profiles if (profiles.length != 0) { @@ -176,7 +174,6 @@ export module Profiles { avatarUrl: input.avatarUrl, createdAt: now, updatedAt: now, - ownerID: input.owner, discriminator, }).link({ owner: input.owner }) ) @@ -214,7 +211,7 @@ export module Profiles { profiles: { $: { where: { - ownerID + owner: ownerID } }, } @@ -227,6 +224,27 @@ export module Profiles { return null } - return profiles + const profile = pipe( + profiles, + groupBy(x => x.id), + values(), + map((group): Info => ({ + id: group[0].id, + username: group[0].username, + createdAt: group[0].createdAt, + updatedAt: group[0].updatedAt, + avatarUrl: group[0].avatarUrl, + discriminator: group[0].discriminator + })) + ) + + return profile[0] + } + + export const getCurrentProfile = async () => { + const user = useCurrentUser() + const currentProfile = await getProfile(user.id); + + return currentProfile } }; \ No newline at end of file diff --git a/packages/core/src/subscription/index.ts b/packages/core/src/subscription/index.ts new file mode 100644 index 00000000..44b567c4 --- /dev/null +++ b/packages/core/src/subscription/index.ts @@ -0,0 +1,205 @@ +import { z } from "zod"; +import databaseClient from "../database" +import { fn } from "../utils"; +import { groupBy, map, pipe, values } from "remeda" +import { Common } from "../common"; +import { Examples } from "../examples"; +import { useCurrentUser } from "../actor"; +import { id as createID } from "@instantdb/admin"; +import { Email } from "../email"; +import { Profiles } from "../profile"; + +export const SubscriptionFrequency = z.enum([ + "fixed", + "daily", + "weekly", + "monthly", + "yearly", +]); + +export type SubscriptionFrequency = z.infer; + +export namespace Subscriptions { + export const Info = z + .object({ + id: z.string().openapi({ + description: Common.IdDescription, + example: Examples.Subscription.id, + }), + checkoutID: z.string().openapi({ + description: "The polar.sh checkout id", + example: Examples.Subscription.checkoutID, + }), + // productID: z.string().openapi({ + // description: "ID of the product being subscribed to.", + // example: Examples.Subscription.productID, + // }), + // quantity: z.number().int().openapi({ + // description: "Quantity of the subscription.", + // example: Examples.Subscription.quantity, + // }), + // frequency: SubscriptionFrequency.openapi({ + // description: "Frequency of the subscription.", + // example: Examples.Subscription.frequency, + // }), + // next: z.string().or(z.number()).openapi({ + // description: "Next billing date for the subscription.", + // example: Examples.Subscription.next, + // }), + canceledAt: z.string().or(z.number()).optional().openapi({ + description: "Cancelled date for the subscription.", + example: Examples.Subscription.canceledAt, + }), + }) + .openapi({ + ref: "Subscription", + description: "Subscription to a Nestri product.", + example: Examples.Subscription, + }); + + export type Info = z.infer; + + export const list = async () => { + const db = databaseClient() + const user = useCurrentUser() + + const query = { + subscriptions: { + $: { + where: { + owner: user.id, + canceledAt: { $isNull: true } + } + }, + } + } + + const res = await db.query(query) + + const response = res.subscriptions + if (!response || response.length === 0) { + return null + } + + const result = pipe( + response, + groupBy(x => x.id), + values(), + map((group): Info => ({ + id: group[0].id, + // next: group[0].next, + // frequency: group[0].frequency as any, + // quantity: group[0].quantity, + // productID: group[0].productID, + checkoutID: group[0].checkoutID, + })) + ) + + return result + } + + export const create = fn(Info.omit({ id: true, canceledAt: true }), async (input) => { + // const id = createID() + const id = createID() + const db = databaseClient() + const user = useCurrentUser() + + //Use the polar.sh ID + await db.transact(db.tx.subscriptions[id]!.update({ + // next: input.next, + // frequency: input.frequency, + // quantity: input.quantity, + checkoutID: input.checkoutID, + }).link({ owner: user.id })) + const res = await db.auth.getUser({ id: user.id }) + const profile = await Profiles.getProfile(user.id) + if (profile) { + await Email.sendWelcome(res.email, profile.username) + } + + }) + + export const remove = fn(z.string(), async (id) => { + const db = databaseClient() + + await db.transact(db.tx.subscriptions[id]!.update({ + canceledAt: new Date().toString() + })) + }) + + export const fromID = fn(z.string(), async (id) => { + const db = databaseClient() + const user = useCurrentUser() + const query = { + subscriptions: { + $: { + where: { + id, + //Make sure they can only get subscriptions they own + owner: user.id, + canceledAt: { $isNull: true } + } + }, + } + } + + const res = await db.query(query) + + const response = res.subscriptions + if (!response || response.length === 0) { + return null + } + + const result = pipe( + response, + groupBy(x => x.id), + values(), + map((group): Info => ({ + id: group[0].id, + checkoutID: group[0].checkoutID, + // next: group[0].next, + // frequency: group[0].frequency as any, + // quantity: group[0].quantity, + // productID: group[0].productID, + })) + ) + + return result[0] + }) + + export const fromCheckoutID = fn(z.string(), async (id) => { + const db = databaseClient() + const user = useCurrentUser() + const query = { + subscriptions: { + $: { + where: { + id, + //Make sure they can only get subscriptions they own + checkoutID: id, + canceledAt: { $isNull: true } + } + }, + } + } + + const res = await db.query(query) + + const response = res.subscriptions + if (!response || response.length === 0) { + return null + } + + const result = pipe( + response, + groupBy(x => x.id), + values(), + map((group): Info => ({ + id: group[0].id, + checkoutID: group[0].checkoutID, + })) + ) + + return result[0] + }) +} \ No newline at end of file diff --git a/packages/core/src/team/index.ts b/packages/core/src/team/index.ts new file mode 100644 index 00000000..e3a7df3f --- /dev/null +++ b/packages/core/src/team/index.ts @@ -0,0 +1,165 @@ +import { z } from "zod"; +import databaseClient from "../database" +import { fn } from "../utils"; +import { groupBy, map, pipe, values } from "remeda" +import { Common } from "../common"; +import { Examples } from "../examples"; +import { useCurrentUser } from "../actor"; +import { id as createID } from "@instantdb/admin"; + +export namespace Teams { + export const Info = z + .object({ + id: z.string().openapi({ + description: Common.IdDescription, + example: Examples.Team.id, + }), + name: z.string().openapi({ + description: "Name of the team", + example: Examples.Team.name, + }), + createdAt: z.string().or(z.number()).openapi({ + description: "The time when this team was first created", + example: Examples.Team.createdAt, + }), + updatedAt: z.string().or(z.number()).openapi({ + description: "The time when this team was last edited", + example: Examples.Team.updatedAt, + }), + owner: z.boolean().openapi({ + description: "Whether this team is owned by this user", + example: Examples.Team.owner, + }), + slug: z.string().openapi({ + description: "This is the unique name identifier for the team", + example: Examples.Team.slug + }) + }) + .openapi({ + ref: "Team", + description: "A group of users sharing the same machines for gaming.", + example: Examples.Team, + }); + + export type Info = z.infer; + + export const list = async () => { + const db = databaseClient() + const user = useCurrentUser() + + const query = { + teams: { + $: { + where: { + members: user.id, + deletedAt: { $isNull: true } + } + }, + } + } + + const res = await db.query(query) + + const teams = res.teams + if (!teams || teams.length === 0) { + return null + } + + const result = pipe( + teams, + groupBy(x => x.id), + values(), + map((group): Info => ({ + id: group[0].id, + name: group[0].name, + createdAt: group[0].createdAt, + updatedAt: group[0].updatedAt, + slug: group[0].slug, + //@ts-expect-error + owner: group[0].owner === user.id + })) + ) + + return result + } + + + export const fromSlug = fn(z.string(), async (slug) => { + const db = databaseClient() + + const query = { + teams: { + $: { + where: { + slug, + deletedAt: { $isNull: true } + } + }, + } + } + + const res = await db.query(query) + + const teams = res.teams + if (!teams || teams.length === 0) { + return null + } + + const result = pipe( + teams, + groupBy(x => x.id), + values(), + map((group): Info => ({ + id: group[0].id, + name: group[0].name, + createdAt: group[0].createdAt, + slug: group[0].slug, + updatedAt: group[0].updatedAt, + //@ts-expect-error + owner: group[0].owner === user.id + })) + ) + + return result[0] + }) + + export const create = fn(Info.pick({ name: true, slug: true }), async (input) => { + const id = createID() + const db = databaseClient() + const user = useCurrentUser() + const now = new Date().toISOString() + + await db.transact(db.tx.teams[id]!.update({ + name: input.name, + slug: input.slug, + createdAt: now, + updatedAt: now, + }).link({ owner: user.id, members: user.id })) + + return id + }) + + export const remove = fn(z.string(), async (id) => { + const db = databaseClient() + const now = new Date().toISOString() + + await db.transact(db.tx.teams[id]!.update({ + deletedAt: now + })) + + return "ok" + }) + + export const invite = fn(z.object({email:z.string(), id: z.string()}), async (input) => { + //TODO: + // const db = databaseClient() + // const now = new Date().toISOString() + + // await db.transact(db.tx.teams[id]!.update({ + // deletedAt: now + // })) + + return "ok" + }) + +} \ No newline at end of file diff --git a/packages/core/src/user/index.ts b/packages/core/src/user/index.ts index 12c4babd..278fbbcc 100644 --- a/packages/core/src/user/index.ts +++ b/packages/core/src/user/index.ts @@ -4,7 +4,7 @@ import { fn } from "../utils"; import { Common } from "../common"; import { Examples } from "../examples"; -export module User { +export module Users { export const Info = z .object({ id: z.string().openapi({ diff --git a/packages/functions/src/api/index.ts b/packages/functions/src/api/index.ts index f48e20ea..5a709fb0 100644 --- a/packages/functions/src/api/index.ts +++ b/packages/functions/src/api/index.ts @@ -1,12 +1,15 @@ import "zod-openapi/extend"; import { Resource } from "sst"; import { ZodError } from "zod"; +import { UserApi } from "./user"; import { GameApi } from "./game"; +import { TeamApi } from "./team"; import { logger } from "hono/logger"; import { subjects } from "../subjects"; import { SessionApi } from "./session"; import { MachineApi } from "./machine"; import { openAPISpecs } from "hono-openapi"; +import { SubscriptionApi } from "./subscription"; import { VisibleError } from "@nestri/core/error"; import { ActorContext } from '@nestri/core/actor'; import { Hono, type MiddlewareHandler } from "hono"; @@ -81,9 +84,12 @@ app .use(auth); const routes = app + .route("/users", UserApi.route) + .route("/teams", TeamApi.route) .route("/games", GameApi.route) - .route("/machines", MachineApi.route) .route("/sessions", SessionApi.route) + .route("/machines", MachineApi.route) + .route("/subscriptions", SubscriptionApi.route) .onError((error, c) => { console.warn(error); if (error instanceof VisibleError) { diff --git a/packages/functions/src/api/session.ts b/packages/functions/src/api/session.ts index 976db274..ff2d87af 100644 --- a/packages/functions/src/api/session.ts +++ b/packages/functions/src/api/session.ts @@ -166,11 +166,11 @@ export module SessionApi { }, ) .post( - "/:id", + "/", describeRoute({ tags: ["Session"], summary: "Create a new gaming session for this user", - description: "Creates a new gaming session for the currently authenticated user, enabling them to play a game", + description: "Create a new gaming session for the currently authenticated user, enabling them to play a game", responses: { 200: { content: { diff --git a/packages/functions/src/api/subscription.ts b/packages/functions/src/api/subscription.ts new file mode 100644 index 00000000..ca23d500 --- /dev/null +++ b/packages/functions/src/api/subscription.ts @@ -0,0 +1,132 @@ +import { z } from "zod"; +import { Hono } from "hono"; +import { Result } from "../common"; +import { describeRoute } from "hono-openapi"; +import { Examples } from "@nestri/core/examples"; +import { validator, resolver } from "hono-openapi/zod"; +import { Subscriptions } from "@nestri/core/subscription/index"; +import { Email } from "@nestri/core/email/index"; + +export module SubscriptionApi { + export const route = new Hono() + .get( + "/", + describeRoute({ + tags: ["Subscription"], + summary: "List subscriptions", + description: "List the subscriptions associated with the current user.", + responses: { + 200: { + content: { + "application/json": { + schema: Result( + Subscriptions.Info.array().openapi({ + description: "List of subscriptions.", + example: [Examples.Subscription], + }), + ), + }, + }, + description: "List of subscriptions.", + }, + 404: { + content: { + "application/json": { + schema: resolver(z.object({ error: z.string() })), + }, + }, + description: "No subscriptions found for this user", + }, + }, + }), + async (c) => { + const data = await Subscriptions.list(); + if (!data) return c.json({ error: "No subscriptions found for this user" }, 404); + return c.json({ data }, 200); + }, + ) + .post( + "/", + describeRoute({ + tags: ["Subscription"], + summary: "Subscribe", + description: "Create a subscription for the current user.", + responses: { + 200: { + content: { + "application/json": { + schema: Result(z.literal("ok")), + }, + }, + description: "Subscription was created successfully.", + }, + 400: { + content: { + "application/json": { + schema: resolver(z.object({ error: z.string() })), + }, + }, + description: "Subscription already exists.", + }, + }, + }), + validator( + "json", + z.object({ + checkoutID: Subscriptions.Info.shape.id.openapi({ + description: "The checkout id information.", + example: Examples.Subscription.id, + }) + }), + ), + async (c) => { + const body = c.req.valid("json"); + const data = await Subscriptions.fromCheckoutID(body.checkoutID) + if (data) return c.json({ error: "Subscription already exists" }) + await Subscriptions.create(body); + return c.json({ data: "ok" as const }, 200); + }, + ) + .delete( + "/:id", + describeRoute({ + tags: ["Subscription"], + summary: "Cancel", + description: "Cancel a subscription for the current user.", + responses: { + 200: { + content: { + "application/json": { + schema: Result(z.literal("ok")), + }, + }, + description: "Subscription was cancelled successfully.", + }, + 404: { + content: { + "application/json": { + schema: resolver(z.object({ error: z.string() })), + }, + }, + description: "Subscription not found.", + }, + }, + }), + validator( + "param", + z.object({ + id: Subscriptions.Info.shape.id.openapi({ + description: "ID of the subscription to cancel.", + example: Examples.Subscription.id, + }), + }), + ), + async (c) => { + const param = c.req.valid("param"); + const subscription = await Subscriptions.fromID(param.id); + if (!subscription) return c.json({ error: "Subscription not found" }, 404); + await Subscriptions.remove(param.id); + return c.json({ data: "ok" as const }, 200); + }, + ); +} \ No newline at end of file diff --git a/packages/functions/src/api/team.ts b/packages/functions/src/api/team.ts new file mode 100644 index 00000000..d8f9a5ad --- /dev/null +++ b/packages/functions/src/api/team.ts @@ -0,0 +1,238 @@ +import { z } from "zod"; +import { Hono } from "hono"; +import { Result } from "../common"; +import { describeRoute } from "hono-openapi"; +import { Teams } from "@nestri/core/team/index"; +import { Users } from "@nestri/core/user/index"; +import { Examples } from "@nestri/core/examples"; +import { validator, resolver } from "hono-openapi/zod"; + +export module TeamApi { + export const route = new Hono() + .get( + "/", + //FIXME: Add a way to filter through query params + describeRoute({ + tags: ["Team"], + summary: "Retrieve all teams", + description: "Returns a list of all teams which the authenticated user is part of", + responses: { + 200: { + content: { + "application/json": { + schema: Result( + Teams.Info.array().openapi({ + description: "A list of teams associated with the user", + example: [Examples.Team], + }), + ), + }, + }, + description: "Successfully retrieved the list teams", + }, + 404: { + content: { + "application/json": { + schema: resolver(z.object({ error: z.string() })), + }, + }, + description: "No teams found for the authenticated user", + }, + }, + }), + async (c) => { + const teams = await Teams.list(); + if (!teams) return c.json({ error: "No teams found for this user" }, 404); + return c.json({ data: teams }, 200); + }, + ) + .get( + "/:slug", + describeRoute({ + tags: ["Team"], + summary: "Retrieve a team by slug", + description: "Fetch detailed information about a specific team using its unique slug", + responses: { + 404: { + content: { + "application/json": { + schema: resolver(z.object({ error: z.string() })), + }, + }, + description: "No team found matching the provided slug", + }, + 200: { + content: { + "application/json": { + schema: Result( + Teams.Info.openapi({ + description: "Detailed information about the requested team", + example: Examples.Team, + }), + ), + }, + }, + description: "Successfully retrieved the team information", + }, + }, + }), + validator( + "param", + z.object({ + slug: Teams.Info.shape.slug.openapi({ + description: "The unique slug used to identify the team", + example: Examples.Team.slug, + }), + }), + ), + async (c) => { + const params = c.req.valid("param"); + const team = await Teams.fromSlug(params.slug); + if (!team) return c.json({ error: "Team not found" }, 404); + return c.json({ data: team }, 200); + }, + ) + .post( + "/", + describeRoute({ + tags: ["Team"], + summary: "Create a team", + description: "Create a new team for the currently authenticated user, enabling them to invite and play a game together with friends", + responses: { + 200: { + content: { + "application/json": { + schema: Result(z.literal("ok")) + }, + }, + description: "Team successfully created", + }, + 404: { + content: { + "application/json": { + schema: resolver(z.object({ error: z.string() })), + }, + }, + description: "A team with this slug already exists", + }, + }, + }), + validator( + "json", + z.object({ + slug: Teams.Info.shape.slug.openapi({ + description: "The unique name to be used with this team", + example: Examples.Team.slug + }), + name: Teams.Info.shape.name.openapi({ + description: "The human readable name to give this team", + example: Examples.Team.name + }) + }) + ), + async (c) => { + const params = c.req.valid("json") + const team = await Teams.fromSlug(params.slug) + if (team) return c.json({ error: "A team with this slug already exists" }, 404); + const res = await Teams.create(params) + return c.json({ data: res }, 200); + }, + ) + .delete( + "/:slug", + describeRoute({ + tags: ["Team"], + summary: "Delete a team", + description: "This endpoint allows a user to delete a team, by providing it's unique slug", + responses: { + 200: { + content: { + "application/json": { + schema: Result(z.literal("ok")), + }, + }, + description: "The team was successfully deleted.", + }, + 404: { + content: { + "application/json": { + schema: resolver(z.object({ error: z.string() })), + }, + }, + description: "A team with this slug does not exist", + }, + 401: { + content: { + "application/json": { + schema: resolver(z.object({ error: z.string() })), + }, + }, + description: "Your are not authorized to delete this team", + }, + } + }), + validator( + "param", + z.object({ + slug: Teams.Info.shape.slug.openapi({ + description: "The unique slug of the team to be deleted. ", + example: Examples.Team.slug, + }), + }), + ), + async (c) => { + const params = c.req.valid("param"); + const team = await Teams.fromSlug(params.slug) + if (!team) return c.json({ error: "Team not found" }, 404); + if (!team.owner) return c.json({ error: "Your are not authorised to delete this team" }, 401) + const res = await Teams.remove(team.id); + return c.json({ data: res }, 200); + }, + ) + .post( + "/:slug/invite/:email", + describeRoute({ + tags: ["Team"], + summary: "Invite a user to a team", + description: "Invite a user to a team owned by the current user", + responses: { + 200: { + content: { + "application/json": { + schema: Result(z.literal("ok")), + }, + }, + description: "User successfully invited", + }, + 404: { + content: { + "application/json": { + schema: resolver(z.object({ error: z.string() })), + }, + }, + description: "The game with the specified Steam ID was not found", + }, + } + }), + validator( + "param", + z.object({ + slug: Teams.Info.shape.slug.openapi({ + description: "The unique slug of the team the user wants to invite ", + example: Examples.Team.slug, + }), + email: Users.Info.shape.email.openapi({ + description: "The email of the user to invite", + example: Examples.User.email + }) + }), + ), + async (c) => { + const params = c.req.valid("param"); + const team = await Teams.fromSlug(params.slug) + if (!team) return c.json({ error: "Team not found" }, 404); + if (!team.owner) return c.json({ error: "Your are not authorized to delete this team" }, 401) + return c.json({ data: "ok" }, 200); + }, + ) +} \ No newline at end of file diff --git a/packages/functions/src/api/user.ts b/packages/functions/src/api/user.ts new file mode 100644 index 00000000..d89f55af --- /dev/null +++ b/packages/functions/src/api/user.ts @@ -0,0 +1,46 @@ +import { z } from "zod"; +import { Hono } from "hono"; +import { Result } from "../common"; +import { describeRoute } from "hono-openapi"; +import { Examples } from "@nestri/core/examples"; +import { Profiles } from "@nestri/core/profile/index"; +import { validator, resolver } from "hono-openapi/zod"; + +export module UserApi { + export const route = new Hono() + .get( + "/@me", + describeRoute({ + tags: ["User"], + summary: "Retrieve current user profile", + description: "Returns the current authenticate user's profile", + responses: { + 200: { + content: { + "application/json": { + schema: Result( + Profiles.Info.openapi({ + description: "The profile for this user", + example: Examples.Profile, + }), + ), + }, + }, + description: "Successfully retrieved the user's profile", + }, + 404: { + content: { + "application/json": { + schema: resolver(z.object({ error: z.string() })), + }, + }, + description: "No user profile found", + }, + }, + }), async (c) => { + const profile = await Profiles.getCurrentProfile(); + if (!profile) return c.json({ error: "No profile found for this user" }, 404); + return c.json({ data: profile }, 200); + }, + ) +} \ No newline at end of file diff --git a/packages/functions/src/auth.ts b/packages/functions/src/auth.ts index c4b68c2b..27629a23 100644 --- a/packages/functions/src/auth.ts +++ b/packages/functions/src/auth.ts @@ -6,7 +6,11 @@ import { import { Select } from "./ui/select"; import { subjects } from "./subjects" import { PasswordUI } from "./ui/password" +import { Email } from "@nestri/core/email/index" +import { Users } from "@nestri/core/user/index" import { authorizer } from "@openauthjs/openauth" +import { Profiles } from "@nestri/core/profile/index" +import { handleDiscord, handleGithub } from "./utils"; import { type CFRequest } from "@nestri/core/types" import { GithubAdapter } from "./ui/adapters/github"; import { DiscordAdapter } from "./ui/adapters/discord"; @@ -14,9 +18,6 @@ import { Machines } from "@nestri/core/machine/index" import { PasswordAdapter } from "./ui/adapters/password" import { type Adapter } from "@openauthjs/openauth/adapter/adapter" import { CloudflareStorage } from "@openauthjs/openauth/storage/cloudflare" -import { handleDiscord, handleGithub } from "./utils"; -import { User } from "@nestri/core/user/index" -import { Profiles } from "@nestri/core/profile/index" interface Env { CloudflareAuthKV: KVNamespace } @@ -89,7 +90,7 @@ export default { PasswordUI({ sendCode: async (email, code) => { console.log("email & code:", email, code) - // await Email.send(email, code) + await Email.send(email, code) }, }), ), @@ -149,8 +150,8 @@ export default { if (value.provider === "password") { const email = value.email const username = value.username - const token = await User.create(email) - const usr = await User.fromEmail(email); + const token = await Users.create(email) + const usr = await Users.fromEmail(email); const exists = await Profiles.getProfile(usr.id) if(username && !exists){ await Profiles.create({ owner: usr.id, username }) @@ -168,19 +169,17 @@ export default { if (value.provider === "github") { const access = value.tokenset.access; user = await handleGithub(access) - // console.log("user", user) } if (value.provider === "discord") { const access = value.tokenset.access user = await handleDiscord(access) - // console.log("user", user) } if (user) { try { - const token = await User.create(user.primary.email) - const usr = await User.fromEmail(user.primary.email); + const token = await Users.create(user.primary.email) + const usr = await Users.fromEmail(user.primary.email); const exists = await Profiles.getProfile(usr.id) console.log("exists",exists) if (!exists) { @@ -198,23 +197,7 @@ export default { } - // if (email) { - // console.log("email", email) - // // value.username && console.log("username", value.username) - - // } - - // if (email) { - // const token = await User.create(email); - // const user = await User.fromEmail(email); - - // return await ctx.subject("user", { - // accessToken: token, - // userID: user.id - // }); - // } - - throw new Error("This is not implemented yet"); + throw new Error("Something went seriously wrong"); }, }).fetch(request, env, ctx) } diff --git a/packages/ui/globals.css b/packages/ui/globals.css index 11c0f5b2..71d300b5 100644 --- a/packages/ui/globals.css +++ b/packages/ui/globals.css @@ -260,6 +260,7 @@ transition: transform 0.5s cubic-bezier(0.32, 0.72, 0, 1); animation-name: slideFromRight; } + .modal::backdrop, .modal-sheet::backdrop { animation-duration: 0.5s; @@ -280,7 +281,7 @@ } .modal[data-closing]::backdrop, -.modal-sheet[data-closing]::backdrop{ +.modal-sheet[data-closing]::backdrop { animation-duration: 0.5s; animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1); touch-action: none; @@ -298,7 +299,7 @@ animation-name: modalIn; } -.modal[data-closing]{ +.modal[data-closing] { animation-duration: 0.5s; animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1); touch-action: none; @@ -325,37 +326,138 @@ @keyframes fadeIn { from { - opacity: 0; + opacity: 0; } - to { - opacity: 1; - } - } - - @keyframes fadeOut { - to { - opacity: 0; - } - } - - @keyframes modalIn { - from { - opacity: 0; - scale: 0.9; - } - to { - opacity: 1; - scale: 1; - } - } - - @keyframes modalOut { - to { - opacity: 0; - scale: 0.9; - } - } - /* button, a { - @apply outline-none hover:[box-shadow:0_0_0_2px_#fcfcfc,0_0_0_4px_#8f8f8f] dark:hover:[box-shadow:0_0_0_2px_#161616,0_0_0_4px_#707070] focus:[box-shadow:0_0_0_2px_#fcfcfc,0_0_0_4px_#8f8f8f] dark:focus:[box-shadow:0_0_0_2px_#161616,0_0_0_4px_#707070] [transition:all_0.3s_cubic-bezier(0.4,0,0.2,1)] - } */ \ No newline at end of file + to { + opacity: 1; + } +} + +@keyframes fadeOut { + to { + opacity: 0; + } +} + +@keyframes modalIn { + from { + opacity: 0; + scale: 0.9; + } + + to { + opacity: 1; + scale: 1; + } +} + +@keyframes modalOut { + to { + opacity: 0; + scale: 0.9; + } +} + +[data-component="spinner"] { + --spinner-size: 20px; + --spinner-color: #000; + + @media (prefers-color-scheme: dark) { + --spinner-color: #FFF; + } + + height: var(--spinner-size, 20px); + width: var(--spinner-size, 20px); + margin-left: calc(var(--spinner-size, 20px)*-1px); + /* display: none; */ + + &>div { + position: relative; + top: 50%; + left: 50%; + height: var(--spinner-size, 20px); + width: var(--spinner-size, 20px); + } + + &>div>div { + animation: spin 1.2s linear infinite; + background: var(--spinner-color); + border-radius: 9999px; + height: 8%; + left: -10%; + position: absolute; + top: -3.9%; + width: 24%; + } + + &>div>div:first-child { + animation-delay: -1.2s; + transform: rotate(.0001deg) translate(146%); + } + + &>div>div:nth-child(2) { + animation-delay: -1.1s; + transform: rotate(30deg) translate(146%); + } + + &>div>div:nth-child(3) { + animation-delay: -1s; + transform: rotate(60deg) translate(146%); + } + + &>div>div:nth-child(4) { + animation-delay: -.9s; + transform: rotate(90deg) translate(146%); + } + + &>div>div:nth-child(5) { + animation-delay: -.8s; + transform: rotate(120deg) translate(146%); + } + + &>div>div:nth-child(6) { + animation-delay: -.7s; + transform: rotate(150deg) translate(146%); + } + + &>div>div:nth-child(7) { + animation-delay: -.6s; + transform: rotate(180deg) translate(146%); + } + + &>div>div:nth-child(8) { + animation-delay: -.5s; + transform: rotate(210deg) translate(146%); + } + + &>div>div:nth-child(9) { + animation-delay: -.4s; + transform: rotate(240deg) translate(146%); + } + + &>div>div:nth-child(10) { + animation-delay: -.3s; + transform: rotate(270deg) translate(146%); + } + + &>div>div:nth-child(11) { + animation-delay: -.2s; + transform: rotate(300deg) translate(146%); + } + + &>div>div:nth-child(12) { + animation-delay: -.1s; + transform: rotate(330deg) translate(146%); + } +} + +@keyframes spin { + 0% { + opacity: 1; + } + + 100% { + opacity: .15; + } +} \ No newline at end of file diff --git a/packages/ui/package.json b/packages/ui/package.json index 9ba953d9..1167b37d 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -28,7 +28,7 @@ "@fontsource/bricolage-grotesque": "^5.0.7", "@fontsource/geist-mono": "^5.1.0", "@fontsource/geist-sans": "^5.1.0", - "@modular-forms/qwik": "0.26.1", + "@modular-forms/qwik": "^0.29.0", "@nestri/core": "*", "@qwik-ui/headless": "^0.6.4", "@types/eslint": "^8.56.5", diff --git a/packages/ui/src/constants.ts b/packages/ui/src/constants.ts new file mode 100644 index 00000000..43ec81f7 --- /dev/null +++ b/packages/ui/src/constants.ts @@ -0,0 +1,4 @@ +export const CONSTANTS = { + githubLink: "https://github.com/nestrilabs/nestri#start", + enterpriseContact: "mailto:enterprise@nestri.io" +} \ No newline at end of file diff --git a/packages/ui/src/github-banner.tsx b/packages/ui/src/footer-banner.tsx similarity index 76% rename from packages/ui/src/github-banner.tsx rename to packages/ui/src/footer-banner.tsx index d23486cf..42736223 100644 --- a/packages/ui/src/github-banner.tsx +++ b/packages/ui/src/footer-banner.tsx @@ -1,23 +1,26 @@ -import { component$, useSignal, useVisibleTask$ } from "@builder.io/qwik"; -import { MotionComponent, transition } from "@nestri/ui/react"; import Book from "./book"; +import { CONSTANTS } from "./constants"; +import { Link } from "@builder.io/qwik-city"; +import { MotionComponent, transition } from "@nestri/ui/react"; +import { component$, useSignal, useVisibleTask$ } from "@builder.io/qwik"; -export const GithubBanner = component$(() => { - const buttonRef = useSignal() - const bookRef = useSignal() +export const FooterBanner = component$(() => { + const docsLinkRef = useSignal() + const bookRef = useSignal() + // eslint-disable-next-line qwik/no-use-visible-task useVisibleTask$(() => { - buttonRef.value?.addEventListener("mouseenter", () => { + docsLinkRef.value?.addEventListener("mouseenter", () => { bookRef.value?.classList.add('flip') }) - buttonRef.value?.addEventListener("mouseleave", () => { + docsLinkRef.value?.addEventListener("mouseleave", () => { bookRef.value?.classList.remove('flip') }) return () => { - buttonRef.value?.removeEventListener("mouseenter", () => { + docsLinkRef.value?.removeEventListener("mouseenter", () => { bookRef.value?.classList.add('flip') }) - buttonRef.value?.removeEventListener("mouseleave", () => { + docsLinkRef.value?.removeEventListener("mouseleave", () => { bookRef.value?.classList.remove('flip') }) } @@ -41,36 +44,36 @@ export const GithubBanner = component$(() => {

Ready to start playing?
- Dive into the documentation or unlock premium features with Nestri Family + Dive into the documentation or unlock premium features with Nestri Pro

- - +
- +
diff --git a/packages/ui/src/footer.tsx b/packages/ui/src/footer.tsx index c3d4bfaa..a31fe384 100644 --- a/packages/ui/src/footer.tsx +++ b/packages/ui/src/footer.tsx @@ -2,7 +2,7 @@ import { component$ } from "@builder.io/qwik"; import { Link } from "@builder.io/qwik-city"; import { MotionComponent, transition } from "@nestri/ui/react" -import { GithubBanner } from "./github-banner"; +import { FooterBanner } from "./footer-banner"; const socialMedia = [ { @@ -30,13 +30,13 @@ const socialMedia = [ ] type Props = { - showGH?: boolean + showBanner?: boolean } -export const Footer = component$(({ showGH = true }: Props) => { +export const Footer = component$(({ showBanner = true }: Props) => { return ( <> - {showGH && } + {showBanner && }
{

Docs

Pricing - Changelog + About Us

Company

- Blog - Contact Us + {/* Blog */} + {/* Contact Us */} +

Blog

+

Contact Us

Open Startup

diff --git a/packages/ui/src/game-store/default.tsx b/packages/ui/src/game-store/default.tsx new file mode 100644 index 00000000..10879cc2 --- /dev/null +++ b/packages/ui/src/game-store/default.tsx @@ -0,0 +1,89 @@ +import { Modal } from "@qwik-ui/headless"; +import { MotionComponent } from "../react"; +import { component$, type QRL } from "@builder.io/qwik"; + +// const FadeScale = { +// initial: { +// opacity: 0, +// scale: 0.85, +// }, +// animate: { +// opacity: 1, +// scale: 1, +// }, +// exit: { +// opacity: 0, +// scale: 0.85, +// }, +// transition: { +// type: "spring", +// duration: 0.35, +// bounce: 0.1 +// } +// }; + +type StoreSelectProps = { + onSteamPress$: QRL<() => void> +} + +export const StoreSelect = component$(({ onSteamPress$ }: StoreSelectProps) => { + return ( + // + <> +
+ + + +
+
+
+

+
+ +
+ Connect Game Store +

+
+
+ +
+ Epic Games + +
+
+ GOG.com + + + +
+
+ Amazon Games + + + +
+
+
+ +
+
+ + //
+ ) +}) + +export const SteamLoad = component$(() => { + return ( + +
+ +
+
+ ) +}) \ No newline at end of file diff --git a/packages/ui/src/game-store/index.tsx b/packages/ui/src/game-store/index.tsx new file mode 100644 index 00000000..2ed3fa2d --- /dev/null +++ b/packages/ui/src/game-store/index.tsx @@ -0,0 +1,34 @@ +import { SteamLoad, StoreSelect } from "./default"; +import { Modal } from "@qwik-ui/headless"; +import { $, component$, useSignal } from "@builder.io/qwik"; + +export default component$(() => { + const storeSelect = useSignal(true) + return ( + + +
+

DESKTOP-EUO8VSF

+
+
+ game +
+
+ +
+ {storeSelect.value ? { console.log("clicked") })} /> : } +
+
+
+ ) +}) \ No newline at end of file diff --git a/packages/ui/src/home-nav-bar.tsx b/packages/ui/src/home-nav-bar.tsx index cee4bac8..ae30c859 100644 --- a/packages/ui/src/home-nav-bar.tsx +++ b/packages/ui/src/home-nav-bar.tsx @@ -1,20 +1,31 @@ import { cn } from "./design"; import Avatar from "./avatar" -import { Dropdown } from '@qwik-ui/headless'; +import { MotionComponent } from "./react"; +import { Dropdown, Modal } from '@qwik-ui/headless'; import { disablePageScroll, enablePageScroll } from '@fluejs/noscroll'; import { $, component$, useOnDocument, useSignal } from "@builder.io/qwik"; +type Props = { + avatarUrl?: string; + discriminator: string | number; + username: string; +} -export const HomeNavBar = component$(() => { +export const HomeNavBar = component$(({ avatarUrl, username, discriminator }: Props) => { const hasScrolled = useSignal(false); + const defaultTeam = `${username}'s Games` + const selectedTeam = useSignal(defaultTeam) + const isNewTeam = useSignal(false); + const isNewMember = useSignal(false); + const isHolding = useSignal(false); + const showInviteSuccess = useSignal(false); + const newTeamName = useSignal(''); + const inviteName = useSignal(''); + const inviteEmail = useSignal(''); + const teams = useSignal([ + { name: defaultTeam } + ]); - const actions = [ - { label: "Hell Diver's Europe", disabled: false }, - { label: "WanjohiRyan's Games", disabled: false }, - { label: "CyberPunk Marathon", disabled: false }, - { label: "Emulation Hackers", disabled: true }, - { label: "testing-123", disabled: false }, - ]; const onDialogOpen = $((open: boolean) => { if (open) { @@ -24,111 +35,213 @@ export const HomeNavBar = component$(() => { } }) + const handlePointerDown = $(() => { + isHolding.value = true + }); + + const handlePointerUp = $(() => { + isHolding.value = false + }); + + const handleAddTeam = $((e: any) => { + e.preventDefault(); + if (newTeamName.value.trim()) { + teams.value = [...teams.value, { name: newTeamName.value.trim() }]; + // selectedTeam.value = newTeamName.value.trim() + newTeamName.value = ''; + isNewTeam.value = false; + } + }); + + const handleInvite = $((e: any) => { + e.preventDefault(); + if (inviteName.value && inviteEmail.value) { + // Here you would typically make an API call to send the invitation + console.log('Sending invite to:', { name: inviteName.value, email: inviteEmail.value }); + inviteName.value = ''; + inviteEmail.value = ''; + isNewMember.value = false; + showInviteSuccess.value = true; + setTimeout(() => { + showInviteSuccess.value = false; + }, 3000); + } + }); + useOnDocument( 'scroll', $(() => { hasScrolled.value = window.scrollY > 0; }) ); - // + + const handleDeleteTeam = $(() => { + // Only delete if it's not the default team + if (selectedTeam.value !== defaultTeam) { + teams.value = teams.value.filter(team => team.name !== selectedTeam.value); + selectedTeam.value = defaultTeam; + } + }); + + const handleDeleteAnimationComplete = $(() => { + if (isHolding.value) { + // isDeleting.value = true; + // Reset the holding state + isHolding.value = false; + handleDeleteTeam(); + } + }); + return ( - + + +
+
+
+

Create a team

+
+ Continue to start playing with on Pro with increased usage, additional security features, and support +
+
+
+
+ + newTeamName.value = e.target!.value} + required value={newTeamName.value} id="name" type="text" placeholder="Enter team name" class="[transition:all_0.3s_cubic-bezier(0.4,0,0.2,1)] w-full bg-transparent px-2 py-3 h-10 border text-black dark:text-white dark:border-gray-700/70 border-gray-300/70 rounded-md text-sm outline-none leading-none focus:ring-gray-300 dark:focus:ring-gray-700 focus:ring-2" /> +
+
+
+
+ + Cancel + + +
+
+
+
+ + +
+ +
+
+

Send an invite

+
+ Friends will receive an email allowing them to join this team +
+
+
+
+ + inviteName.value = e.target!.value} + id="name" type="text" placeholder="Jane Doe" class="[transition:all_0.3s_cubic-bezier(0.4,0,0.2,1)] w-full bg-transparent px-2 py-3 h-10 border text-black dark:text-white dark:border-gray-700/70 border-gray-300/70 rounded-md text-sm outline-none leading-none focus:ring-gray-300 dark:focus:ring-gray-700 focus:ring-2" /> +
+
+ + inviteEmail.value = e.target!.value} + id="email" type="email" placeholder="jane@doe.com" class="[transition:all_0.3s_cubic-bezier(0.4,0,0.2,1)] w-full px-2 bg-transparent py-3 h-10 border text-black dark:text-white dark:border-gray-700/70 border-gray-300/70 rounded-md text-sm outline-none leading-none focus:ring-gray-300 dark:focus:ring-gray-700 focus:ring-2" /> +
+
+
+
+ + Cancel + + +
+
+
+
+ ) }) \ No newline at end of file diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 1d930d73..18097385 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -9,9 +9,12 @@ export * from "./footer" export * from "./card" export * from "./router-head" export * from "./team-counter" +export * from "./constants" +export * from "./svg" export * as auth from "./popup" export * as Modal from "./modal" export { default as Book } from "./book" export { default as Portal } from "./portal" export { default as Avatar } from "./avatar" -export { default as SimpleFooter } from "./simple-footer" \ No newline at end of file +export { default as SimpleFooter } from "./simple-footer" +export { default as GameStoreButton } from "./game-store" \ No newline at end of file diff --git a/packages/ui/src/nav-bar.tsx b/packages/ui/src/nav-bar.tsx index 8d43a3bf..7332f9df 100644 --- a/packages/ui/src/nav-bar.tsx +++ b/packages/ui/src/nav-bar.tsx @@ -4,8 +4,8 @@ import { buttonVariants, cn } from "./design"; const navLinks = [ { - name: "Changelog", - href: "/changelog" + name: "About Us", + href: "/about" }, { name: "Pricing", @@ -13,11 +13,15 @@ const navLinks = [ }, { name: "Login", - href: "/login" } ] -export const NavBar = component$(() => { +type Props = { + link?: string +} + + +export const NavBar = component$(({ link }: Props) => { const location = useLocation() const hasScrolled = useSignal(false); @@ -30,8 +34,16 @@ export const NavBar = component$(() => { ); return ( -