mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-15 02:05:37 +02:00
⭐ feat: Update website, API, and infra (#164)
>Adds `maitred` in charge of handling automated game installs, updates,
and even execution.
>Not only that, we have the hosted stuff here
>- [x] AWS Task on ECS GPUs
>- [ ] Add a service to listen for game starts and stops
(docker-compose.yml)
>- [x] Add a queue for requesting a game to start
>- [x] Fix up the play/watch UI
>TODO:
>- Add a README
>- Add an SST docs
Edit:
- This adds a new landing page, updates the homepage etc etc
>I forgot what the rest of the updated stuff are 😅
This commit is contained in:
135
packages/ui/src/home/friends.tsx
Normal file
135
packages/ui/src/home/friends.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import { cn } from "../design";
|
||||
import Avatar from "../avatar";
|
||||
import type Nestri from "@nestri/sdk"
|
||||
import { Tooltip } from '@qwik-ui/headless';
|
||||
import { useNavigate } from "@builder.io/qwik-city";
|
||||
import { $, component$, useOnDocument, useSignal, type QRL } from "@builder.io/qwik";
|
||||
|
||||
type Props = {
|
||||
getActiveUsers$: QRL<() => Promise<Nestri.Users.UserListResponse.Data[] | undefined>>
|
||||
getSession$: QRL<(profileID: string) => Promise<Nestri.Users.UserSessionResponse | undefined>>
|
||||
}
|
||||
|
||||
const skeletonCrew = new Array(3).fill(0)
|
||||
|
||||
export const HomeFriendsSection = component$(({ getActiveUsers$, getSession$ }: Props) => {
|
||||
const activeUsers = useSignal<Nestri.Users.UserListResponse.Data[] | undefined>()
|
||||
const nav = useNavigate()
|
||||
|
||||
useOnDocument("load", $(async () => {
|
||||
const sessionUserData = sessionStorage.getItem("active_user_data")
|
||||
if (!sessionUserData) {
|
||||
const users = await getActiveUsers$()
|
||||
sessionStorage.setItem("active_user_data", JSON.stringify(users))
|
||||
activeUsers.value = users
|
||||
} else {
|
||||
activeUsers.value = JSON.parse(sessionUserData)
|
||||
}
|
||||
}))
|
||||
|
||||
const onWatch = $(async (profileID: string) => {
|
||||
const session = await getSession$(profileID)
|
||||
await nav(`/play/${session?.data.id}`)
|
||||
})
|
||||
|
||||
return (
|
||||
<div class="gap-2 w-full flex-col flex">
|
||||
<hr class="border-none h-[1.5px] dark:bg-gray-700 bg-gray-300 w-full" />
|
||||
<div class="flex flex-col justify-center py-2 px-3 items-start w-full ">
|
||||
<div class="text-gray-600/70 dark:text-gray-400/70 leading-none flex justify-between items-center w-full py-1">
|
||||
<span class="text-xl text-gray-700 dark:text-gray-300 leading-none font-bold font-title flex gap-2 items-center pb-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0 size-5" viewBox="0 0 20 20"><path fill="currentColor" d="M2.049 9.112a8.001 8.001 0 1 1 9.718 8.692a1.5 1.5 0 0 0-.206-1.865l-.01-.01q.244-.355.47-.837a9.3 9.3 0 0 0 .56-1.592H9.744q.17-.478.229-1h2.82A15 15 0 0 0 13 10c0-.883-.073-1.725-.206-2.5H7.206l-.05.315a4.5 4.5 0 0 0-.971-.263l.008-.052H3.46q-.112.291-.198.595c-.462.265-.873.61-1.213 1.017m9.973-4.204C11.407 3.59 10.657 3 10 3s-1.407.59-2.022 1.908A9.3 9.3 0 0 0 7.42 6.5h5.162a9.3 9.3 0 0 0-.56-1.592M6.389 6.5c.176-.743.407-1.422.683-2.015c.186-.399.401-.773.642-1.103A7.02 7.02 0 0 0 3.936 6.5zm9.675 7H13.61a10.5 10.5 0 0 1-.683 2.015a6.6 6.6 0 0 1-.642 1.103a7.02 7.02 0 0 0 3.778-3.118m-2.257-1h2.733c.297-.776.46-1.62.46-2.5s-.163-1.724-.46-2.5h-2.733c.126.788.193 1.63.193 2.5s-.067 1.712-.193 2.5m2.257-6a7.02 7.02 0 0 0-3.778-3.118c.241.33.456.704.642 1.103c.276.593.507 1.272.683 2.015zm-7.76 7.596a3.5 3.5 0 1 0-.707.707l2.55 2.55a.5.5 0 0 0 .707-.707zM8 12a2.5 2.5 0 1 1-5 0a2.5 2.5 0 0 1 5 0" /></svg>
|
||||
Find people to play with
|
||||
</span>
|
||||
</div>
|
||||
<ul class="list-none ml-4 relative w-[calc(100%-1rem)]">
|
||||
{activeUsers.value ? (
|
||||
activeUsers.value.slice(0, 3).map((user, key) => (
|
||||
<div key={`user-${key}`} >
|
||||
<div class="gap-3.5 hover:bg-gray-200 dark:hover:bg-gray-800 text-left outline-none group rounded-lg px-3 [transition:all_0.3s_cubic-bezier(0.4,0,0.2,1)] flex items-center w-full">
|
||||
<div class="relative [&>svg]:size-[80px] [&>img]:size-[80px] min-w-[80px]">
|
||||
{user.avatarUrl ?
|
||||
(<img height={52} width={52} draggable={false} class="[transition:all_0.3s_cubic-bezier(0.4,0,0.2,1)] ring-2 ring-gray-200 dark:ring-gray-800 select-none rounded-full aspect-square w-[80px]" src={user.avatarUrl} alt={user.username} />) :
|
||||
(<Avatar name={`${user.username}#${user.discriminator}`} />)}
|
||||
</div>
|
||||
<div class={cn("w-full h-[100px] overflow-hidden pr-2 border-b-2 border-gray-400/70 dark:border-gray-700/70 flex gap-2 items-center", key == 2 && "border-none")}>
|
||||
<div class="flex-col">
|
||||
<span class="font-medium tracking-tighter text-gray-700 dark:text-gray-300 max-w-full text-lg truncate leading-none flex [&>svg]:size-5 [&>svg]:dark:text-[#12ECFA] ">
|
||||
{`${user.username}`} <p class="hidden group-hover:block text-gray-600/70 dark:text-gray-400/70 transition-all duration-200 ease-in">{` #${user.discriminator}`}</p>
|
||||
{/* {user.status && (user.status == "active" && (<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M10.277 16.515c.005-.11.186-.154.24-.058c.254.45.686 1.111 1.176 1.412s1.276.386 1.792.408c.11.005.153.186.057.24c-.45.254-1.11.686-1.411 1.176s-.386 1.276-.408 1.792c-.005.11-.187.153-.24.057c-.254-.45-.686-1.11-1.177-1.411c-.49-.301-1.276-.386-1.791-.408c-.11-.005-.154-.187-.058-.24c.45-.254 1.111-.686 1.412-1.177c.3-.49.386-1.276.408-1.791"/><path fill="currentColor" d="M18.492 15.515c-.009-.11-.2-.156-.258-.062c-.172.283-.42.623-.697.793s-.692.236-1.022.262c-.11.008-.156.2-.062.257c.282.172.623.42.793.697s.236.693.262 1.023c.008.11.2.155.257.061c.172-.282.42-.623.697-.792s.693-.237 1.023-.262c.11-.009.155-.2.061-.258c-.282-.172-.623-.42-.792-.697s-.237-.692-.262-1.022" opacity=".5"/><path fill="currentColor" d="m14.703 4.002l-.242-.306c-.937-1.183-1.405-1.775-1.95-1.688c-.544.088-.805.796-1.326 2.213l-.135.366c-.148.403-.222.604-.364.752s-.336.225-.724.38l-.353.141l-.247.1c-1.2.48-1.804.753-1.882 1.283c-.082.565.49 1.049 1.634 2.016l.296.25c.326.275.488.413.581.6c.094.187.107.403.133.835l.024.393c.094 1.52.14 2.28.635 2.542c.494.262 1.108-.147 2.336-.966l.318-.212c.349-.233.523-.35.723-.381s.401.024.806.136l.367.102c1.423.394 2.134.591 2.521.188c.388-.403.195-1.14-.19-2.613l-.1-.381c-.109-.419-.164-.628-.134-.835s.142-.389.366-.752l.203-.33c.785-1.276 1.178-1.914.924-2.426c-.255-.51-.988-.557-2.454-.648l-.38-.024c-.416-.026-.624-.039-.805-.135s-.314-.264-.58-.6"/><path fill="currentColor" d="M8.835 13.326C6.698 14.37 4.919 16.024 4.248 18c-.752-4.707.292-7.747 1.965-9.637c.144.295.332.539.5.73c.35.396.852.82 1.362 1.251l.367.31l.17.145c.005.064.01.14.015.237l.03.485c.04.655.08 1.294.178 1.805" opacity=".5"/></svg>))} */}
|
||||
</span>
|
||||
<div class="flex items-center gap-2 w-full cursor-pointer px-1 rounded-md">
|
||||
<div class={cn("font-normal capitalize w-full text-gray-600/70 dark:text-gray-400/70 truncate flex gap-1 items-center", (user.status && user.status == "active") && "dark:text-[#50e3c2]")}>
|
||||
<div class={cn("size-3 rounded-full block", user.status ? (user.status == "active" ? "bg-[#50e3c2]" : user.status == "idle" ? "bg-[#ff990a]" : "hidden") : "hidden")} />
|
||||
{user.status ? (user.status == "active" ? "Playing Steam" : user.status) : "Offline"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-auto relative flex gap-2 justify-center h-full items-center">
|
||||
{user.status && (user.status == "active" &&
|
||||
(
|
||||
<Tooltip.Root gutter={12} flip={false} placement="top" >
|
||||
<Tooltip.Trigger onClick$={async () => { await onWatch(user.id) }} type="button" class="bg-gray-200 group-hover:bg-gray-300 dark:group-hover:bg-gray-700 transition-all duration-200 ease-in text-gray-600 dark:text-gray-400 dark:bg-gray-800 [&>svg]:size-5 p-2 rounded-full" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M4.75 5.5c.427 0 .791.163 1.075.45c-.145-.778-.37-1.415-.64-1.894C4.72 3.236 4.216 3 3.75 3s-.97.237-1.434 1.056C1.835 4.906 1.5 6.25 1.5 8s.335 3.094.816 3.944c.463.82.967 1.056 1.434 1.056s.97-.237 1.434-1.056c.272-.48.496-1.116.64-1.895a1.47 1.47 0 0 1-1.074.451C3.674 10.5 3 9.47 3 8s.674-2.5 1.75-2.5M7.5 8c0 3.822-1.445 6.5-3.75 6.5S0 11.822 0 8s1.445-6.5 3.75-6.5S7.5 4.178 7.5 8m6.825 2.05c-.145.778-.37 1.415-.64 1.894c-.464.82-.968 1.056-1.435 1.056s-.97-.237-1.434-1.056C10.335 11.094 10 9.75 10 8s.335-3.094.816-3.944C11.279 3.236 11.783 3 12.25 3s.97.237 1.434 1.056c.272.48.496 1.116.64 1.895A1.47 1.47 0 0 0 13.25 5.5c-1.076 0-1.75 1.03-1.75 2.5s.674 2.5 1.75 2.5a1.47 1.47 0 0 0 1.075-.45M16 8c0 3.822-1.445 6.5-3.75 6.5S8.5 11.822 8.5 8s1.445-6.5 3.75-6.5S16 4.178 16 8" clip-rule="evenodd" /></svg>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Panel class="tooltip capitalize text-sm relative rounded-md dark:bg-gray-700 bg-gray-300 py-2 px-3 text-gray-700 dark:text-gray-300" aria-label="More options">
|
||||
Watch stream
|
||||
<div class="-bottom-2 left-1/2 -translate-x-1/2 absolute dark:text-gray-700 text-gray-300" >
|
||||
<svg width="15" height="10" viewBox="0 0 30 10" preserveAspectRatio="none" fill="currentColor"><polygon points="0,0 30,0 15,10"></polygon></svg>
|
||||
</div>
|
||||
</Tooltip.Panel>
|
||||
</Tooltip.Root>
|
||||
))}
|
||||
<Tooltip.Root gutter={12} flip={false} placement="top" >
|
||||
<Tooltip.Trigger disabled type="button" class="bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed group-hover:bg-gray-300 dark:group-hover:bg-gray-700 transition-all duration-200 ease-in text-gray-600 dark:text-gray-400 dark:bg-gray-800 [&>svg]:size-5 p-2 rounded-full" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0-8 0m8 12h6m-3-3v6M6 21v-2a4 4 0 0 1 4-4h4" /></svg>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Panel class="tooltip capitalize text-sm relative rounded-md dark:bg-gray-700 bg-gray-300 py-2 px-3 text-gray-700 dark:text-gray-300" aria-label="More options">
|
||||
Invite
|
||||
<div class="-bottom-2 left-1/2 -translate-x-1/2 absolute dark:text-gray-700 text-gray-300" >
|
||||
<svg width="15" height="10" viewBox="0 0 30 10" preserveAspectRatio="none" fill="currentColor"><polygon points="0,0 30,0 15,10"></polygon></svg>
|
||||
</div>
|
||||
</Tooltip.Panel>
|
||||
</Tooltip.Root>
|
||||
<Tooltip.Root gutter={12} flip={false} placement="top" >
|
||||
<Tooltip.Trigger disabled type="button" class="bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed group-hover:bg-gray-300 dark:group-hover:bg-gray-700 transition-all duration-200 ease-in text-gray-600 dark:text-gray-400 dark:bg-gray-800 [&>svg]:size-5 p-2 rounded-full" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2s-2 .9-2 2s.9 2 2 2m0 2c-1.1 0-2 .9-2 2s.9 2 2 2s2-.9 2-2s-.9-2-2-2m0 6c-1.1 0-2 .9-2 2s.9 2 2 2s2-.9 2-2s-.9-2-2-2" /></svg>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Panel class="tooltip capitalize text-sm relative rounded-md dark:bg-gray-700 bg-gray-300 py-2 px-3 text-gray-700 dark:text-gray-300" aria-label="More options">
|
||||
more
|
||||
<div class="-bottom-2 left-1/2 -translate-x-1/2 absolute dark:text-gray-700 text-gray-300" >
|
||||
<svg width="15" height="10" viewBox="0 0 30 10" preserveAspectRatio="none" fill="currentColor"><polygon points="0,0 30,0 15,10"></polygon></svg>
|
||||
</div>
|
||||
</Tooltip.Panel>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) :
|
||||
(
|
||||
skeletonCrew.map((_, key) => (
|
||||
<div key={`skeleton-friend-${key}`} >
|
||||
<div class="gap-3.5 text-left animate-pulse outline-none group rounded-lg px-3 [transition:all_0.3s_cubic-bezier(0.4,0,0.2,1)] flex items-center w-full">
|
||||
<div class="relative w-max">
|
||||
<div class="size-20 rounded-full bg-gray-200 dark:bg-gray-800" />
|
||||
</div>
|
||||
<div class={cn("w-full h-[100px] overflow-hidden pr-2 border-b-2 border-gray-400/70 dark:border-gray-700/70 flex gap-2 items-center", key == 2 && "border-none")}>
|
||||
<div class="flex-col w-[80%] gap-2 flex">
|
||||
<span class="font-medium tracking-tighter bg-gray-200 dark:bg-gray-800 rounded-md h-6 w-2/3 max-w-full text-lg font-title truncate leading-none block" />
|
||||
<div class="flex items-center gap-2 w-full h-6 bg-gray-200 dark:bg-gray-800 rounded-md" />
|
||||
</div>
|
||||
<div class="bg-gray-200 dark:bg-gray-800 h-7 w-16 ml-auto rounded-md" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)
|
||||
}
|
||||
<div class="[border:1px_dashed_theme(colors.gray.300)] dark:[border:1px_dashed_theme(colors.gray.800)] [mask-image:linear-gradient(rgb(0,0,0)_0%,_rgb(0,0,0)_calc(100%-120px),_transparent_100%)] bottom-0 top-0 -left-[0.4625rem] absolute" />
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
102
packages/ui/src/home/games.tsx
Normal file
102
packages/ui/src/home/games.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import type Nestri from "@nestri/sdk";
|
||||
import { useNavigate } from "@builder.io/qwik-city";
|
||||
import { $, component$, useOnDocument, useSignal, type QRL } from "@builder.io/qwik";
|
||||
|
||||
type Props = {
|
||||
getUserSubscription$: QRL<() => Promise<"Free" | "Pro" | undefined>>
|
||||
createSession$: QRL<() => Promise<Nestri.Tasks.TaskSessionResponse.Data | undefined>>
|
||||
}
|
||||
|
||||
const skeletonGames = new Array(6).fill(0)
|
||||
|
||||
export const HomeGamesSection = component$(({ getUserSubscription$ }: Props) => { //createSession$
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const nav = useNavigate()
|
||||
const creatingSession = useSignal(false)
|
||||
const userSubscription = useSignal<"Free" | "Pro" | undefined>()
|
||||
|
||||
useOnDocument("load", $(async () => {
|
||||
const userSub = sessionStorage.getItem("subscription_data")
|
||||
if (userSub) {
|
||||
userSubscription.value = JSON.parse(userSub)
|
||||
} else {
|
||||
const subscription = await getUserSubscription$()
|
||||
sessionStorage.setItem("subscription_data", JSON.stringify(subscription))
|
||||
userSubscription.value = subscription
|
||||
}
|
||||
}))
|
||||
|
||||
const onClick = $(async () => {
|
||||
console.log("clicked")
|
||||
// creatingSession.value = true
|
||||
// const sessionID = await createSession$()
|
||||
// if (sessionID) {
|
||||
// creatingSession.value = false
|
||||
// await nav(`/play/${sessionID.id}`)
|
||||
// }
|
||||
});
|
||||
|
||||
|
||||
return (
|
||||
<div class="gap-2 w-full flex-col flex">
|
||||
<hr class="border-none h-[1.5px] dark:bg-gray-700 bg-gray-300 w-full" />
|
||||
<div class="text-gray-600/70 dark:text-gray-400/70 text-sm leading-none flex justify-start py-2 px-3 items-end">
|
||||
<span class="text-xl text-gray-700 dark:text-gray-300 leading-none font-bold font-title flex gap-2 ">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0 size-5" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 22c-.818 0-1.6-.33-3.163-.99C4.946 19.366 3 18.543 3 17.16V7m9 15c.818 0 1.6-.33 3.163-.99C19.054 19.366 21 18.543 21 17.16V7m-9 15V11.355M8.326 9.691L5.405 8.278C3.802 7.502 3 7.114 3 6.5s.802-1.002 2.405-1.778l2.92-1.413C10.13 2.436 11.03 2 12 2s1.871.436 3.674 1.309l2.921 1.413C20.198 5.498 21 5.886 21 6.5s-.802 1.002-2.405 1.778l-2.92 1.413C13.87 10.564 12.97 11 12 11s-1.871-.436-3.674-1.309M6 12l2 1m9-9L7 9" color="currentColor" /></svg>
|
||||
Your Games
|
||||
</span>
|
||||
{/* {userSubscription.value ? (
|
||||
<button disabled={userSubscription.value === "Free"} class="disabled:opacity-50 disabled:cursor-not-allowed ml-auto flex gap-1 items-center cursor-pointer [&:not(:disabled)]:hover:text-gray-800 dark:[&:not(:disabled)]:hover:text-gray-200 transition-all duration-200 outline-none">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0 size-5" viewBox="0 0 256 256"><path fill="currentColor" d="M248 128a87.34 87.34 0 0 1-17.6 52.81a8 8 0 1 1-12.8-9.62A71.34 71.34 0 0 0 232 128a72 72 0 0 0-144 0a8 8 0 0 1-16 0a88 88 0 0 1 3.29-23.88C74.2 104 73.1 104 72 104a48 48 0 0 0 0 96h24a8 8 0 0 1 0 16H72a64 64 0 1 1 9.29-127.32A88 88 0 0 1 248 128m-69.66 42.34L160 188.69V128a8 8 0 0 0-16 0v60.69l-18.34-18.35a8 8 0 0 0-11.32 11.32l32 32a8 8 0 0 0 11.32 0l32-32a8 8 0 0 0-11.32-11.32" /></svg>
|
||||
<span>Install a game</span>
|
||||
</button>
|
||||
) : (
|
||||
<div class="ml-auto h-4 w-28 rounded-md bg-gray-200 dark:gray-800 animate-pulse" />
|
||||
)} */}
|
||||
</div>
|
||||
<ul class="relative py-3 w-full list-none after:pointer-events-none after:select-none after:w-full after:h-[120px] after:fixed after:z-10 after:backdrop-blur-[1px] after:bg-gradient-to-b after:from-transparent after:to-gray-200 dark:after:to-gray-800 after:[-webkit-mask-image:linear-gradient(to_top,theme(colors.gray.200)_25%,transparent)] dark:after:[-webkit-mask-image:linear-gradient(to_top,theme(colors.gray.800)_25%,transparent)] after:left-0 after:-bottom-[1px]">
|
||||
{userSubscription.value ? (
|
||||
<div class="flex flex-col items-center justify-center gap-6 px-6 py-20 w-full" >
|
||||
<div class="relative flex items-center justify-center overflow-hidden rounded-[22px] p-[2px] before:absolute before:left-[-50%] before:top-[-50%] before:z-[-2] before:h-[200%] before:w-[200%] before:animate-[bgRotate_1.15s_linear_infinite] before:bg-[conic-gradient(from_0deg,transparent_0%,#ff4f01_10%,#ff4f01_25%,transparent_35%)] before:content-[''] after:absolute after:inset-[2px] after:z-[-1] after:content-['']" >
|
||||
<div class="flex items-center justify-center rounded-[20px] bg-gray-200 dark:bg-gray-800 p-1">
|
||||
<div class="flex items-center justify-center rounded-2xl bg-[#F5F5F5] p-1 dark:bg-[#171717]">
|
||||
<div class="flex h-[64px] w-[64px] items-center justify-center rounded-xl bg-gray-100 dark:bg-gray-900">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" class="h-8 w-8 shrink-0 dark:text-gray-700 text-gray-300" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M11.968 2C6.767 2 2.4 6.045 2.048 11.181l5.329 2.216c.45-.322.995-.45 1.573-.45h.128l2.344-3.5v-.031a3.74 3.74 0 0 1 3.756-3.756c2.087 0 3.788 1.67 3.788 3.756a3.74 3.74 0 0 1-3.756 3.756h-.096l-3.403 2.44v.128a2.863 2.863 0 0 1-2.857 2.857c-1.349 0-2.536-.995-2.761-2.247l-3.724-1.637C3.557 18.886 7.44 22 11.968 22c5.49-.032 9.984-4.494 9.984-10.016S17.457 2 11.968 2" /><path fill="currentColor" d="m8.276 17.152l-1.22-.481c.225.45.578.867 1.092 1.027c1.027.45 2.311-.032 2.76-1.123a2.07 2.07 0 0 0 0-1.638a2.26 2.26 0 0 0-1.123-1.187c-.514-.225-1.027-.193-1.54-.033l1.251.546c.77.353 1.188 1.252.867 2.023c-.353.802-1.252 1.155-2.087.866m9.502-7.736c0-1.349-1.124-2.536-2.536-2.536c-1.349 0-2.536 1.123-2.536 2.536c0 1.412 1.188 2.536 2.536 2.536s2.536-1.156 2.536-2.536m-4.366 0c0-1.027.867-1.862 1.862-1.862c1.027 0 1.862.867 1.862 1.862c0 1.027-.867 1.862-1.862 1.862c-1.027.032-1.862-.835-1.862-1.862" /></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center gap-1">
|
||||
<span class="select-none text-center text-gray-700 dark:text-gray-300 font-title text-xl font-semibold sm:font-medium">Waiting for your first game install</span>
|
||||
<p class="text-center text-base font-medium text-gray-600 dark:text-gray-400 sm:font-regular">Once you have installed a game on your machine, it should appear here</p>
|
||||
</div>
|
||||
<button
|
||||
onClick$={onClick}
|
||||
// disabled={userSubscription.value === "Free"}
|
||||
disabled
|
||||
class="flex gap-2 h-[48px] disabled:cursor-not-allowed disabled:opacity-50 max-w-[360px] w-full select-none items-center justify-center rounded-full bg-primary-500 text-base font-semibold text-white transition-all duration-200 ease-out [&:not(:disabled)]:hover:ring-2 [&:not(:disabled)]:hover:ring-gray-600 dark:[&:not(:disabled)]:hover:ring-gray-400 [&:not(:disabled)]:focus:scale-95 [&:not(:disabled)]:active:scale-95 sm:font-medium">
|
||||
{creatingSession.value &&
|
||||
<div style={{ "--spinner-color": "#FFF" }} data-component="spinner">
|
||||
<div>
|
||||
{new Array(12).fill(0).map((i, k) => (
|
||||
<div key={k} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<span> {creatingSession.value ? "Launching Steam" : "Launch Steam"}</span>
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div class="grid sm:grid-cols-3 grid-cols-2 gap-2 gap-y-3 w-full animate-pulse" >
|
||||
{skeletonGames.map((_, key) => (
|
||||
<div key={`skeleton-game-${key}`} class="w-full gap-2 flex flex-col" >
|
||||
<div class="bg-gray-200 dark:bg-gray-800 w-full aspect-square rounded-2xl" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</ul >
|
||||
</div >
|
||||
)
|
||||
})
|
||||
131
packages/ui/src/home/machines.tsx
Normal file
131
packages/ui/src/home/machines.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
/* eslint-disable qwik/jsx-img */
|
||||
import { cn } from "../design";
|
||||
import { MotionComponent } from "../react";
|
||||
import { $, component$, useOnDocument, useSignal, type QRL } from "@builder.io/qwik";
|
||||
import { Link } from "@builder.io/qwik-city";
|
||||
|
||||
type Props = {
|
||||
getUserSubscription$: QRL<() => Promise<"Free" | "Pro" | undefined>>
|
||||
}
|
||||
|
||||
export const HomeMachineSection = component$(({ getUserSubscription$ }: Props) => {
|
||||
const isHovered = useSignal(false)
|
||||
const userSubscription = useSignal<"Free" | "Pro" | undefined>()
|
||||
|
||||
useOnDocument("load", $(async () => {
|
||||
const userSub = sessionStorage.getItem("subscription_data")
|
||||
if (userSub) {
|
||||
userSubscription.value = JSON.parse(userSub)
|
||||
} else {
|
||||
const subscription = await getUserSubscription$()
|
||||
sessionStorage.setItem("subscription_data", JSON.stringify(subscription))
|
||||
userSubscription.value = subscription
|
||||
}
|
||||
}))
|
||||
|
||||
return (
|
||||
<div class="flex flex-col gap-6 w-full py-4">
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
{userSubscription.value ? (
|
||||
<>
|
||||
{userSubscription.value == "Pro" ? (
|
||||
<div class="w-full animate-fade-in opacity-0 transition-all duration-200 ease-in" >
|
||||
<Link href="/machine" class="w-full border-gray-400/70 dark:border-gray-700/70 hover:ring-2 hover:ring-[#8f8f8f] dark:hover:ring-[#707070] outline-none group transition-all duration-200 border-[2px] h-14 rounded-xl px-4 gap-2 flex items-center justify-between overflow-hidden bg-white dark:bg-black hover:bg-gray-300/70 dark:hover:bg-gray-700/70 disabled:opacity-50">
|
||||
<div class="py-2 w-2/3 flex truncate group">
|
||||
<div class="flex items-center justify-between group gap-2">
|
||||
<div class="flex items-center w-auto gap-2 overflow-hidden">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-auto select-none text-nowrap font-medium transition-colors duration-200 ease-out group-hover:text-black text-black/80 dark:group-hover:text-white dark:text-white/80">Steam Machine</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* <div class="select-none flex gap-1 items-center overflow-hidden rounded-md px-2 py-1 text-xs font-medium transition-colors duration-200 ease-out" > */}
|
||||
<div class={cn("select-none overflow-hidden flex gap-1 uppercase items-center rounded-md px-2 py-1 text-xs font-medium transition-colors duration-200 ease-out", userSubscription.value === "Pro" ? "bg-[#0CCE6B]/30 text-[#0CCE6B] group-hover:bg-[#0CCE6B]/40" : " bg-[#EE0048]/30 text-[#EE0048] hover:bg-[#EE0048]/40")}>
|
||||
<div class={cn("size-2 rounded-full", userSubscription.value == "Pro" ? "bg-[#0CCE6B]" : "bg-[#EE0048]")} />
|
||||
<span>{userSubscription.value == "Pro" ? "Online" : "Error"}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
"--cutout-avatar-percentage-visible": 0.2,
|
||||
"--head-margin-percentage": 0.1,
|
||||
"--size": "3rem"
|
||||
}}
|
||||
class="relative h-full flex w-[20%] justify-end">
|
||||
<img draggable={false} alt="game" width={256} height={256} src="/images/steam.png" class="h-12 shadow-lg shadow-gray-900 ring-gray-400/70 ring-1 bg-black w-12 translate-y-4 rotate-[14deg] rounded-lg object-cover transition-transform sm:h-16 sm:w-16 group-hover:scale-110" />
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<div class="w-full animate-fade-in opacity-0 transition-all duration-200 ease-in relative">
|
||||
<MotionComponent
|
||||
client:load
|
||||
as="span"
|
||||
initial={{ x: 80, y: 0, rotate: 0 }}
|
||||
animate={{
|
||||
x: isHovered.value ? 5 : 80,
|
||||
y: isHovered.value ? -20 : 0,
|
||||
rotate: isHovered.value ? 720 : 0
|
||||
}}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 200,
|
||||
damping: 15,
|
||||
}}
|
||||
class="absolute text-[#99CCFF] flex items-center justify-center z-[1]">
|
||||
<svg height="16" class="size-10" stroke-linejoin="round" viewBox="0 0 16 16" width="16" color="currentColor">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.49999 0H6.49999L6.22628 1.45975C6.1916 1.64472 6.05544 1.79299 5.87755 1.85441C5.6298 1.93996 5.38883 2.04007 5.15568 2.15371C4.98644 2.2362 4.78522 2.22767 4.62984 2.12136L3.40379 1.28249L1.28247 3.40381L2.12135 4.62986C2.22766 4.78524 2.23619 4.98646 2.1537 5.15569C2.04005 5.38885 1.93995 5.62981 1.8544 5.87756C1.79297 6.05545 1.6447 6.19162 1.45973 6.2263L0 6.5V9.5L1.45973 9.7737C1.6447 9.80838 1.79297 9.94455 1.8544 10.1224C1.93995 10.3702 2.04006 10.6112 2.1537 10.8443C2.23619 11.0136 2.22767 11.2148 2.12136 11.3702L1.28249 12.5962L3.40381 14.7175L4.62985 13.8786C4.78523 13.7723 4.98645 13.7638 5.15569 13.8463C5.38884 13.9599 5.6298 14.06 5.87755 14.1456C6.05544 14.207 6.1916 14.3553 6.22628 14.5403L6.49999 16H9.49999L9.77369 14.5403C9.80837 14.3553 9.94454 14.207 10.1224 14.1456C10.3702 14.06 10.6111 13.9599 10.8443 13.8463C11.0135 13.7638 11.2147 13.7723 11.3701 13.8786L12.5962 14.7175L14.7175 12.5962L13.8786 11.3701C13.7723 11.2148 13.7638 11.0135 13.8463 10.8443C13.9599 10.6112 14.06 10.3702 14.1456 10.1224C14.207 9.94455 14.3553 9.80839 14.5402 9.7737L16 9.5V6.5L14.5402 6.2263C14.3553 6.19161 14.207 6.05545 14.1456 5.87756C14.06 5.62981 13.9599 5.38885 13.8463 5.1557C13.7638 4.98647 13.7723 4.78525 13.8786 4.62987L14.7175 3.40381L12.5962 1.28249L11.3701 2.12137C11.2148 2.22768 11.0135 2.2362 10.8443 2.15371C10.6111 2.04007 10.3702 1.93996 10.1224 1.85441C9.94454 1.79299 9.80837 1.64472 9.77369 1.45974L9.49999 0ZM8 11C9.65685 11 11 9.65685 11 8C11 6.34315 9.65685 5 8 5C6.34315 5 5 6.34315 5 8C5 9.65685 6.34315 11 8 11Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</MotionComponent>
|
||||
<MotionComponent
|
||||
as="span"
|
||||
client:load
|
||||
initial={{ x: 80, y: 0, rotate: 0 }}
|
||||
animate={{
|
||||
x: isHovered.value ? 100 : 20,
|
||||
y: isHovered.value ? 35 : 0,
|
||||
rotate: isHovered.value ? 720 : 0
|
||||
}}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 200,
|
||||
damping: 15,
|
||||
}}
|
||||
class="absolute text-[#99CCFF] flex items-center justify-center z-[1]">
|
||||
<svg height="16" class="size-10" stroke-linejoin="round" viewBox="0 0 16 16" width="16" color="currentColor">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.49999 0H6.49999L6.22628 1.45975C6.1916 1.64472 6.05544 1.79299 5.87755 1.85441C5.6298 1.93996 5.38883 2.04007 5.15568 2.15371C4.98644 2.2362 4.78522 2.22767 4.62984 2.12136L3.40379 1.28249L1.28247 3.40381L2.12135 4.62986C2.22766 4.78524 2.23619 4.98646 2.1537 5.15569C2.04005 5.38885 1.93995 5.62981 1.8544 5.87756C1.79297 6.05545 1.6447 6.19162 1.45973 6.2263L0 6.5V9.5L1.45973 9.7737C1.6447 9.80838 1.79297 9.94455 1.8544 10.1224C1.93995 10.3702 2.04006 10.6112 2.1537 10.8443C2.23619 11.0136 2.22767 11.2148 2.12136 11.3702L1.28249 12.5962L3.40381 14.7175L4.62985 13.8786C4.78523 13.7723 4.98645 13.7638 5.15569 13.8463C5.38884 13.9599 5.6298 14.06 5.87755 14.1456C6.05544 14.207 6.1916 14.3553 6.22628 14.5403L6.49999 16H9.49999L9.77369 14.5403C9.80837 14.3553 9.94454 14.207 10.1224 14.1456C10.3702 14.06 10.6111 13.9599 10.8443 13.8463C11.0135 13.7638 11.2147 13.7723 11.3701 13.8786L12.5962 14.7175L14.7175 12.5962L13.8786 11.3701C13.7723 11.2148 13.7638 11.0135 13.8463 10.8443C13.9599 10.6112 14.06 10.3702 14.1456 10.1224C14.207 9.94455 14.3553 9.80839 14.5402 9.7737L16 9.5V6.5L14.5402 6.2263C14.3553 6.19161 14.207 6.05545 14.1456 5.87756C14.06 5.62981 13.9599 5.38885 13.8463 5.1557C13.7638 4.98647 13.7723 4.78525 13.8786 4.62987L14.7175 3.40381L12.5962 1.28249L11.3701 2.12137C11.2148 2.22768 11.0135 2.2362 10.8443 2.15371C10.6111 2.04007 10.3702 1.93996 10.1224 1.85441C9.94454 1.79299 9.80837 1.64472 9.77369 1.45974L9.49999 0ZM8 11C9.65685 11 11 9.65685 11 8C11 6.34315 9.65685 5 8 5C6.34315 5 5 6.34315 5 8C5 9.65685 6.34315 11 8 11Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</MotionComponent>
|
||||
<div class="hover:border-[#99CCFF]/70 border-gray-300 dark:border-gray-700 bg-white dark:bg-black z-[5] relative w-full transition-all duration-300 border-[2px] h-14 rounded-xl px-4 gap-2 flex items-center justify-between overflow-hidden outline-none disabled:opacity-50">
|
||||
<span class="p-2 pl-0 text-gray-800/70 dark:text-gray-200/70 leading-none shrink truncate flex text-start items-center gap-2">
|
||||
<a
|
||||
onMouseEnter$={() => isHovered.value = true}
|
||||
onMouseLeave$={() => isHovered.value = false}
|
||||
href="https://polar.sh/nestri" class="dark:text-white text-black border-b border-[#99CCFF] py-0.5">Upgrade to Pro</a> to get a machine
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<button class="w-full animate-fade-in opacity-0 transition-all duration-200 ease-in">
|
||||
<div class="border-gray-400/70 w-full dark:border-gray-700/70 transition-all border-dashed duration-200 border-[2px] h-14 rounded-xl px-4 gap-2 flex items-center justify-between overflow-hidden outline-none disabled:opacity-50">
|
||||
<span class="p-2 pl-0 text-gray-600/70 dark:text-gray-400/70 leading-none shrink truncate flex text-start items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0 size-5" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M11.505 2h-1.501c-3.281 0-4.921 0-6.084.814a4.5 4.5 0 0 0-1.106 1.105C2 5.08 2 6.72 2 10s0 4.919.814 6.081a4.5 4.5 0 0 0 1.106 1.105C5.083 18 6.723 18 10.004 18h4.002c3.28 0 4.921 0 6.084-.814a4.5 4.5 0 0 0 1.105-1.105c.63-.897.772-2.08.805-4.081m-8-6h4m0 0h4m-4 0V2m0 4v4m-7 5h2m-1 3v4m-4 0h8" color="currentColor" /></svg>
|
||||
Add your machine
|
||||
<div class="select-none text-[#ff990a] bg-[#ff990a]/30 h-max uppercase overflow-hidden rounded-md px-2 py-1 text-xs transition-colors duration-200 ease-out font-semibold font-title">
|
||||
<span>Soon</span>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</>
|
||||
) :
|
||||
new Array(2).fill(0).map((_, key) => (
|
||||
<div class="w-full animate-pulse" key={`skeleton-machine-${key}`}>
|
||||
<div class="rounded-xl bg-gray-200 dark:bg-gray-800 h-14 w-full" />
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div >
|
||||
)
|
||||
})
|
||||
393
packages/ui/src/home/nav-bar.tsx
Normal file
393
packages/ui/src/home/nav-bar.tsx
Normal file
@@ -0,0 +1,393 @@
|
||||
import { cn } from "../design";
|
||||
import Avatar from "../avatar"
|
||||
import type Nestri from "@nestri/sdk"
|
||||
import { MotionComponent } from "../react";
|
||||
import { Dropdown, Modal } from '@qwik-ui/headless';
|
||||
import { useLocation, useNavigate } from "@builder.io/qwik-city";
|
||||
import { disablePageScroll, enablePageScroll } from '@fluejs/noscroll';
|
||||
import { $, component$, type QRL, useOnDocument, useSignal } from "@builder.io/qwik";
|
||||
|
||||
type Props = {
|
||||
getUserProfile$: QRL<() => Promise<Nestri.Users.UserRetrieveResponse.Data | undefined>>
|
||||
}
|
||||
|
||||
|
||||
export const HomeNavBar = component$(({ getUserProfile$ }: Props) => {
|
||||
const nav = useNavigate()
|
||||
const inviteEmail = useSignal('');
|
||||
const location = useLocation().url
|
||||
const inviteName = useSignal('');
|
||||
const isHolding = useSignal(false);
|
||||
const newTeamName = useSignal('');
|
||||
const isNewTeam = useSignal(false);
|
||||
const hasScrolled = useSignal(false);
|
||||
const isNewMember = useSignal(false);
|
||||
const showInviteSuccess = useSignal(false);
|
||||
const userProfile = useSignal<Nestri.Users.UserRetrieveResponse.Data | undefined>()
|
||||
|
||||
const onDialogOpen = $((open: boolean) => {
|
||||
if (open) {
|
||||
disablePageScroll()
|
||||
} else {
|
||||
enablePageScroll()
|
||||
}
|
||||
})
|
||||
|
||||
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 handleLogout = $(async() => {
|
||||
await nav(`/auth/logout`)
|
||||
});
|
||||
|
||||
const handleLogoutAnimationComplete = $(() => {
|
||||
if (isHolding.value) {
|
||||
console.log("fucking hell")
|
||||
|
||||
// isDeleting.value = true;
|
||||
// Reset the holding state
|
||||
isHolding.value = false;
|
||||
handleLogout();
|
||||
}
|
||||
});
|
||||
|
||||
useOnDocument("load", $(async () => {
|
||||
const currentProfile = await getUserProfile$()
|
||||
userProfile.value = currentProfile
|
||||
}))
|
||||
|
||||
return (
|
||||
<>
|
||||
<nav class={cn("fixed w-screen justify-between top-0 z-50 px-2 sm:px-6 text-xs sm:text-sm leading-[1] text-gray-950/70 dark:text-gray-50/70 h-[66px] before:backdrop-blur-[15px] before:absolute before:-z-[1] before:top-0 before:left-0 before:w-full before:h-full flex items-center", hasScrolled.value && "shadow-[0_2px_20px_1px] shadow-gray-300 dark:shadow-gray-700")} >
|
||||
<div class="flex flex-row justify-center relative items-center top-0 bottom-0">
|
||||
<div class="flex-shrink-0 gap-2 flex justify-center items-center">
|
||||
<svg
|
||||
class="size-8 "
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 12.8778 9.7377253"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="m 2.093439,1.7855532 h 8.690922 V 2.2639978 H 2.093439 Z m 0,2.8440874 h 8.690922 V 5.1080848 H 2.093439 Z m 0,2.8440866 h 8.690922 V 7.952172 H 2.093439 Z"
|
||||
style="font-size:12px;fill:#ff4f01;fill-opacity:1;fill-rule:evenodd;stroke:#ff4f01;stroke-width:1.66201;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="relative z-[5] items-center flex">
|
||||
<hr class="dark:bg-gray-700/70 bg-gray-400/70 w-0.5 rounded-md mx-3 rotate-[16deg] h-7 border-none" />
|
||||
{userProfile.value ? (
|
||||
<Dropdown.Root class="animate-fade-in opacity-0 transition-all duration-200 ease-in" onOpenChange$={onDialogOpen}>
|
||||
<Dropdown.Trigger class="text-sm [&>svg:first-child]:size-5 rounded-full h-8 focus:bg-gray-300/70 dark:focus:bg-gray-700/70 focus:ring-[#8f8f8f] dark:focus:ring-[#707070] focus:ring-2 outline-none dark:text-gray-400 text-gray-600 gap-2 px-3 cursor-pointer inline-flex transition-all duration-150 items-center hover:bg-gray-300/70 dark:hover:bg-gray-700/70 ">
|
||||
<Avatar name={`${userProfile.value.username}'s Games`} />
|
||||
<span class="truncate shrink max-w-[20ch]">{`${userProfile.value.username}'s Games`}</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M72.61 83.06a8 8 0 0 1 1.73-8.72l48-48a8 8 0 0 1 11.32 0l48 48A8 8 0 0 1 176 88H80a8 8 0 0 1-7.39-4.94M176 168H80a8 8 0 0 0-5.66 13.66l48 48a8 8 0 0 0 11.32 0l48-48A8 8 0 0 0 176 168" /></svg>
|
||||
</Dropdown.Trigger>
|
||||
<Dropdown.Popover
|
||||
class="bg-[hsla(0,0%,100%,.5)] dark:bg-[hsla(0,0%,100%,.026)] min-w-[160px] max-w-[240px] backdrop-blur-md rounded-lg py-1 px-2 border border-[#e8e8e8] dark:border-[#2e2e2e] [box-shadow:0_8px_30px_rgba(0,0,0,.12)]">
|
||||
<Dropdown.RadioGroup class="w-full flex overflow-hidden flex-col gap-1 [&_*]:w-full [&_[data-checked]]:bg-[rgba(0,0,0,.071)] dark:[&_[data-checked]]:bg-[hsla(0,0%,100%,.077)] [&_[data-checked]]:rounded-md [&_[data-checked]]:text-[#171717] [&_[data-checked]_svg]:block cursor-pointer [&_[data-highlighted]]:text-[#171717] dark:[&_[data-checked]]:text-[#ededed] dark:[&_[data-highlighted]]:text-[#ededed] [&_[data-highlighted]]:bg-[rgba(0,0,0,.071)] dark:[&_[data-highlighted]]:bg-[hsla(0,0%,100%,.077)] [&_[data-highlighted]]:rounded-md">
|
||||
<Dropdown.RadioItem
|
||||
value={`${userProfile.value.username}'s Games`}
|
||||
class="leading-none text-sm items-center flex px-2 h-8 rounded-md outline-none relative select-none w-full"
|
||||
>
|
||||
<span class="w-full max-w-[20ch] flex items-center gap-2 truncate [&>svg]:size-5 text-[#6f6f6f] dark:text-[#a0a0a0]">
|
||||
<Avatar class="flex-shrink-0 rounded-full" name={`${userProfile.value.username}'s Games`} />
|
||||
{`${userProfile.value.username}'s Games`}
|
||||
</span>
|
||||
<span class="py-1 px-1 text-primary-500 [&>svg]:size-5 [&>svg]:hidden !w-max" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="" viewBox="0 0 24 24"><path fill="currentColor" d="m10 13.6l5.9-5.9q.275-.275.7-.275t.7.275t.275.7t-.275.7l-6.6 6.6q-.3.3-.7.3t-.7-.3l-2.6-2.6q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275z" /></svg>
|
||||
</span>
|
||||
</Dropdown.RadioItem>
|
||||
</Dropdown.RadioGroup>
|
||||
<Dropdown.Separator class="w-full dark:bg-[#2e2e2e] bg-[#e8e8e8] border-0 h-[1px] my-1" />
|
||||
<Dropdown.Group class="flex flex-col gap-1 w-full">
|
||||
<Dropdown.Item
|
||||
onClick$={() => isNewTeam.value = true}
|
||||
class={cn("leading-none w-full text-sm items-center text-[#6f6f6f] dark:text-[#a0a0a0] hover:text-[#171717] dark:hover:text-[#ededed] hover:bg-[rgba(0,0,0,.071)] dark:hover:bg-[hsla(0,0%,100%,.077)] flex px-2 gap-2 h-8 rounded-md cursor-pointer outline-none relative", "opacity-50 pointer-events-none !cursor-not-allowed")}
|
||||
>
|
||||
<span class="w-full max-w-[20ch] flex items-center gap-2 truncate overflow-visible [&>svg]:size-5">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v14m-7-7h14" /></svg>
|
||||
New Team
|
||||
</span>
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
// onClick$={() => isNewMember.value = true}
|
||||
class={cn("leading-none w-full text-sm items-center text-[#6f6f6f] dark:text-[#a0a0a0] hover:text-[#171717] dark:hover:text-[#ededed] hover:bg-[rgba(0,0,0,.071)] dark:hover:bg-[hsla(0,0%,100%,.077)] flex px-2 gap-2 h-8 rounded-md cursor-pointer outline-none relative", "opacity-50 pointer-events-none !cursor-not-allowed")}
|
||||
>
|
||||
<span class="w-full max-w-[20ch] flex items-center gap-2 truncate overflow-visible [&>svg]:size-5">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0-8 0m8 12h6m-3-3v6M6 21v-2a4 4 0 0 1 4-4h4" /></svg>
|
||||
Send an invite
|
||||
</span>
|
||||
</Dropdown.Item>
|
||||
<button
|
||||
onPointerDown$={handlePointerDown}
|
||||
onPointerUp$={handlePointerUp}
|
||||
onPointerLeave$={handlePointerUp}
|
||||
onKeyDown$={(e) => e.key === "Enter" && handlePointerDown()}
|
||||
onKeyUp$={(e) => e.key === "Enter" && handlePointerUp()}
|
||||
disabled={true}
|
||||
class={cn("leading-none relative overflow-hidden transition-all duration-200 text-sm group items-center text-red-500 [&>*]:w-full [&>qwik-react]:absolute [&>qwik-react]:h-full [&>qwik-react]:left-0 hover:text-[#171717] dark:hover:text-[#ededed] hover:bg-[rgba(0,0,0,.071)] dark:hover:bg-[hsla(0,0%,100%,.077)] flex px-2 gap-2 h-8 rounded-md cursor-pointer outline-none select-none w-full", "opacity-50 pointer-events-none !cursor-not-allowed")}>
|
||||
<MotionComponent
|
||||
client:load
|
||||
class="absolute left-0 top-0 bottom-0 bg-red-500 opacity-50 w-full h-full rounded-md"
|
||||
initial={{ scaleX: 0 }}
|
||||
animate={{
|
||||
scaleX: isHolding.value ? 1 : 0
|
||||
}}
|
||||
style={{
|
||||
transformOrigin: 'left',
|
||||
}}
|
||||
transition={{
|
||||
duration: isHolding.value ? 2 : 0.5,
|
||||
ease: "linear"
|
||||
}}
|
||||
/>
|
||||
<span class="w-full max-w-[20ch] flex items-center gap-2 truncate overflow-visible [&>svg]:size-5 ">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m19.5 5.5l-.402 6.506M4.5 5.5l.605 10.025c.154 2.567.232 3.85.874 4.774c.317.456.726.842 1.2 1.131c.671.41 1.502.533 2.821.57m10-7l-7 7m7 0l-7-7M3 5.5h18m-4.944 0l-.683-1.408c-.453-.936-.68-1.403-1.071-1.695a2 2 0 0 0-.275-.172C13.594 2 13.074 2 12.035 2c-1.066 0-1.599 0-2.04.234a2 2 0 0 0-.278.18c-.395.303-.616.788-1.058 1.757L8.053 5.5" color="currentColor" /></svg>
|
||||
<span class="group-hover:hidden">Delete Team</span>
|
||||
<span class="hidden group-hover:block">Hold to delete</span>
|
||||
</span>
|
||||
</button>
|
||||
</Dropdown.Group>
|
||||
</Dropdown.Popover>
|
||||
</Dropdown.Root>) : (<div class="h-6 w-40 rounded-md bg-gray-200 dark:bg-gray-800 animate-pulse" />)}
|
||||
{location.pathname == "/machine" &&
|
||||
(<>
|
||||
<hr class="dark:bg-gray-700/70 bg-gray-400/70 w-0.5 rounded-md mx-3 rotate-[16deg] h-7 border-none" />
|
||||
{userProfile.value ? (
|
||||
<div class="text-sm [&>svg:first-child]:size-5 rounded-full h-8 focus:bg-gray-300/70 dark:focus:bg-gray-700/70 focus:ring-[#8f8f8f] dark:focus:ring-[#707070] focus:ring-2 outline-none dark:text-gray-400 text-gray-600 gap-2 px-3 cursor-pointer inline-flex transition-all duration-150 items-center hover:bg-gray-300/70 dark:hover:bg-gray-700/70 ">
|
||||
<span class="truncate shrink max-w-[20ch]">{`Steam Machine`}</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M72.61 83.06a8 8 0 0 1 1.73-8.72l48-48a8 8 0 0 1 11.32 0l48 48A8 8 0 0 1 176 88H80a8 8 0 0 1-7.39-4.94M176 168H80a8 8 0 0 0-5.66 13.66l48 48a8 8 0 0 0 11.32 0l48-48A8 8 0 0 0 176 168" /></svg>
|
||||
</div>
|
||||
) : (<div class="h-6 w-40 rounded-md bg-gray-200 dark:bg-gray-800 animate-pulse" />)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="gap-4 flex flex-row justify-center h-full items-center">
|
||||
{userProfile.value ?
|
||||
(<Dropdown.Root class="animate-fade-in opacity-0 transition-all duration-200 ease-in" onOpenChange$={onDialogOpen}>
|
||||
<Dropdown.Trigger class="focus:bg-gray-300/70 dark:focus:bg-gray-700/70 focus:ring-[#8f8f8f] dark:focus:ring-[#707070] text-gray-600 dark:text-gray-400 [&>svg:first-child]:size-5 text-sm focus:ring-2 outline-none rounded-full transition-all flex items-center duration-150 select-none cursor-pointer hover:bg-gray-300/70 dark:hover:bg-gray-700/70 gap-1 px-3 h-8" >
|
||||
{userProfile.value.avatarUrl ? (<img src={userProfile.value.avatarUrl} height={20} width={20} class="size-6 rounded-full" alt="Avatar" />) : (<Avatar name={`${userProfile.value.username}#${userProfile.value.discriminator}`} />)}
|
||||
<span class="truncate shrink max-w-[20ch] sm:flex hidden">{`${userProfile.value.username}#${userProfile.value.discriminator}`}</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:block hidden" width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M72.61 83.06a8 8 0 0 1 1.73-8.72l48-48a8 8 0 0 1 11.32 0l48 48A8 8 0 0 1 176 88H80a8 8 0 0 1-7.39-4.94M176 168H80a8 8 0 0 0-5.66 13.66l48 48a8 8 0 0 0 11.32 0l48-48A8 8 0 0 0 176 168" /></svg>
|
||||
</Dropdown.Trigger>
|
||||
<Dropdown.Popover
|
||||
class="bg-[hsla(0,0%,100%,.5)] dark:bg-[hsla(0,0%,100%,.026)] min-w-[160px] max-w-[240px] backdrop-blur-md rounded-lg py-1 px-2 border border-[#e8e8e8] dark:border-[#2e2e2e] [box-shadow:0_8px_30px_rgba(0,0,0,.12)]">
|
||||
<Dropdown.Group class="flex flex-col gap-1">
|
||||
<Dropdown.Item
|
||||
onClick$={() => window.location.href = "mailto:feedback@nestri.io"}
|
||||
class="leading-none text-sm items-center text-[#6f6f6f] dark:text-[#a0a0a0] hover:text-[#171717] dark:hover:text-[#ededed] hover:bg-[rgba(0,0,0,.071)] dark:hover:bg-[hsla(0,0%,100%,.077)] flex px-2 gap-2 h-8 rounded-md cursor-pointer outline-none relative select-none "
|
||||
>
|
||||
<span class="w-full max-w-[20ch] flex items-center gap-2 truncate overflow-visible [&>svg]:size-5 ">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0" viewBox="0 0 24 24"><path fill="currentColor" d="M22 8.5a6.5 6.5 0 0 0-11.626-3.993A9.5 9.5 0 0 1 19.5 14q0 .165-.006.33l.333.088a1.3 1.3 0 0 0 1.592-1.591l-.128-.476c-.103-.385-.04-.791.125-1.153A6.5 6.5 0 0 0 22 8.5" /><path fill="currentColor" fill-rule="evenodd" d="M18 14a8 8 0 0 1-11.45 7.22a1.67 1.67 0 0 0-1.15-.13l-1.227.329a1.3 1.3 0 0 1-1.591-1.592L2.91 18.6a1.67 1.67 0 0 0-.13-1.15A8 8 0 1 1 18 14M6.5 15a1 1 0 1 0 0-2a1 1 0 0 0 0 2m3.5 0a1 1 0 1 0 0-2a1 1 0 0 0 0 2m3.5 0a1 1 0 1 0 0-2a1 1 0 0 0 0 2" clip-rule="evenodd" /></svg>
|
||||
Send Feedback
|
||||
</span>
|
||||
</Dropdown.Item>
|
||||
{/* <button
|
||||
onPointerDown$={handlePointerDown}
|
||||
onPointerUp$={handlePointerUp}
|
||||
onPointerLeave$={handlePointerUp}
|
||||
onKeyDown$={(e) => e.key === "Enter" && handlePointerDown()}
|
||||
onKeyUp$={(e) => e.key === "Enter" && handlePointerUp()}
|
||||
class="leading-none relative overflow-hidden transition-all duration-200 text-sm group items-center text-red-500 [&>*]:w-full [&>qwik-react]:absolute [&>qwik-react]:h-full [&>qwik-react]:left-0 hover:text-[#171717] dark:hover:text-[#ededed] hover:bg-[rgba(0,0,0,.071)] dark:hover:bg-[hsla(0,0%,100%,.077)] flex px-2 gap-2 h-8 rounded-md cursor-pointer outline-none select-none w-full">
|
||||
<MotionComponent
|
||||
client:load
|
||||
class="absolute left-0 top-0 bottom-0 bg-red-500 opacity-50 w-full h-full rounded-md"
|
||||
initial={{ scaleX: 0 }}
|
||||
animate={{
|
||||
scaleX: isHolding.value ? 1 : 0
|
||||
}}
|
||||
style={{
|
||||
transformOrigin: 'left',
|
||||
}}
|
||||
transition={{
|
||||
duration: isHolding.value ? 2 : 0.5,
|
||||
ease: "linear"
|
||||
}}
|
||||
onAnimationComplete$={handleLogoutAnimationComplete}
|
||||
/>
|
||||
<span class="w-full max-w-[20ch] flex items-center gap-2 truncate overflow-visible [&>svg]:size-5 ">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0" viewBox="0 0 24 24"><g fill="none"><path fill="currentColor" fill-rule="evenodd" d="M10.138 1.815A3 3 0 0 1 14 4.688v14.624a3 3 0 0 1-3.862 2.873l-6-1.8A3 3 0 0 1 2 17.512V6.488a3 3 0 0 1 2.138-2.873zM15 4a1 1 0 0 1 1-1h3a3 3 0 0 1 3 3v1a1 1 0 1 1-2 0V6a1 1 0 0 0-1-1h-3a1 1 0 0 1-1-1m6 12a1 1 0 0 1 1 1v1a3 3 0 0 1-3 3h-3a1 1 0 1 1 0-2h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1M9 11a1 1 0 1 0 0 2h.001a1 1 0 1 0 0-2z" clip-rule="evenodd" /><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 12h5m0 0l-2-2m2 2l-2 2" /></g></svg>
|
||||
<span class="group-hover:hidden">Log out</span>
|
||||
<span class="hidden group-hover:block">Hold to logout</span>
|
||||
</span>
|
||||
</button> */}
|
||||
<button
|
||||
onPointerDown$={handlePointerDown}
|
||||
onPointerUp$={handlePointerUp}
|
||||
onPointerLeave$={handlePointerUp}
|
||||
onKeyDown$={(e) => e.key === "Enter" && handlePointerDown()}
|
||||
onKeyUp$={(e) => e.key === "Enter" && handlePointerUp()}
|
||||
class={cn("leading-none relative overflow-hidden transition-all duration-200 text-sm group items-center text-red-500 [&>*]:w-full [&>qwik-react]:absolute [&>qwik-react]:h-full [&>qwik-react]:left-0 hover:text-[#171717] dark:hover:text-[#ededed] hover:bg-[rgba(0,0,0,.071)] dark:hover:bg-[hsla(0,0%,100%,.077)] flex px-2 gap-2 h-8 rounded-md cursor-pointer outline-none select-none w-full")}>
|
||||
<MotionComponent
|
||||
client:load
|
||||
class="absolute left-0 top-0 bottom-0 bg-red-500 opacity-50 w-full h-full rounded-md"
|
||||
initial={{ scaleX: 0 }}
|
||||
animate={{
|
||||
scaleX: isHolding.value ? 1 : 0
|
||||
}}
|
||||
style={{
|
||||
transformOrigin: 'left',
|
||||
}}
|
||||
transition={{
|
||||
duration: isHolding.value ? 2 : 0.5,
|
||||
ease: "linear"
|
||||
}}
|
||||
onAnimationComplete$={handleLogoutAnimationComplete}
|
||||
/>
|
||||
<span class="w-full max-w-[20ch] flex items-center gap-2 truncate overflow-visible [&>svg]:size-5 ">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0" viewBox="0 0 24 24"><g fill="none"><path fill="currentColor" fill-rule="evenodd" d="M10.138 1.815A3 3 0 0 1 14 4.688v14.624a3 3 0 0 1-3.862 2.873l-6-1.8A3 3 0 0 1 2 17.512V6.488a3 3 0 0 1 2.138-2.873zM15 4a1 1 0 0 1 1-1h3a3 3 0 0 1 3 3v1a1 1 0 1 1-2 0V6a1 1 0 0 0-1-1h-3a1 1 0 0 1-1-1m6 12a1 1 0 0 1 1 1v1a3 3 0 0 1-3 3h-3a1 1 0 1 1 0-2h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1M9 11a1 1 0 1 0 0 2h.001a1 1 0 1 0 0-2z" clip-rule="evenodd" /><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 12h5m0 0l-2-2m2 2l-2 2" /></g></svg>
|
||||
<span class="group-hover:hidden">Log out</span>
|
||||
<span class="hidden group-hover:block">Hold to logout</span>
|
||||
</span>
|
||||
</button>
|
||||
</Dropdown.Group>
|
||||
{/* <Dropdown.Separator class="w-full dark:bg-[#2e2e2e] bg-[#e8e8e8] border-0 h-[1px] my-1" />
|
||||
<Dropdown.Group class="flex flex-col gap-1">
|
||||
<Dropdown.Item
|
||||
class="leading-none transition-all duration-200 text-sm group items-center text-red-500 hover:text-[#171717] dark:hover:text-[#ededed] hover:bg-[rgba(0,0,0,.071)] dark:hover:bg-[hsla(0,0%,100%,.077)] flex px-2 gap-2 h-8 rounded-md cursor-pointer outline-none relative select-none "
|
||||
>
|
||||
<span class="w-full max-w-[20ch] flex items-center gap-2 truncate overflow-visible [&>svg]:size-5 ">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M13 22H6.59c-1.545 0-2.774-.752-3.877-1.803c-2.26-2.153 1.45-3.873 2.865-4.715a10.67 10.67 0 0 1 7.922-1.187m3-7.795a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0M16 22l3-3m0 0l3-3m-3 3l-3-3m3 3l3 3" color="currentColor" /></svg>
|
||||
<span class="group-hover:hidden">Leave Team</span>
|
||||
<span class="hidden group-hover:block">Hold to leave</span>
|
||||
</span>
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Group> */}
|
||||
</Dropdown.Popover>
|
||||
</Dropdown.Root>) :
|
||||
(<div class="flex gap-2 justify-center items-center animate-pulse " >
|
||||
<div class="h-6 w-20 rounded-md bg-gray-200 dark:bg-gray-800 right-4" />
|
||||
<div class="size-8 rounded-full bg-gray-200 dark:bg-gray-800 right-4" />
|
||||
</div>)
|
||||
}
|
||||
</div>
|
||||
</nav>
|
||||
<Modal.Root bind:show={isNewTeam} class="w-full">
|
||||
<Modal.Panel
|
||||
class="dark:bg-black bg-white [box-shadow:0_8px_30px_rgba(0,0,0,.12)]
|
||||
dark:backdrop:bg-[#0009] backdrop:bg-[#b3b5b799] backdrop:backdrop-grayscale-[.3] max-h-[75vh] rounded-xl
|
||||
backdrop-blur-md modal max-w-[400px] w-full border dark:border-gray-800 border-gray-200">
|
||||
<form preventdefault:submit onSubmit$={handleAddTeam}>
|
||||
<main class="size-full flex flex-col relative py-4 px-5">
|
||||
<div class="dark:text-white text-black">
|
||||
<h3 class="font-semibold text-2xl tracking-tight mb-1 font-title">Create a team</h3>
|
||||
<div class="text-sm dark:text-gray-200/70 text-gray-800/70" >
|
||||
Continue to start playing with on Pro with increased usage, additional security features, and support
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 flex flex-col gap-3" >
|
||||
<div>
|
||||
<label for="name" class="text-sm dark:text-gray-200 text-gray-800 pb-2 pt-1" >
|
||||
Team Name
|
||||
</label>
|
||||
<input
|
||||
//@ts-expect-error
|
||||
onInput$={(e) => 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" />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer class="dark:text-gray-200/70 text-gray-800/70 dark:bg-gray-900 bg-gray-100 ring-1 ring-gray-200 dark:ring-gray-800 select-none flex gap-2 items-center justify-between w-full bottom-0 left-0 py-3 px-5 text-sm leading-none">
|
||||
<Modal.Close class="rounded-lg [transition:all_0.3s_cubic-bezier(0.4,0,0.2,1)] py-3 px-4 hover:bg-gray-200 dark:hover:bg-gray-800 flex items-center justify-center">
|
||||
Cancel
|
||||
</Modal.Close>
|
||||
<button
|
||||
type="submit"
|
||||
class="flex items-center justify-center gap-2 border-2 border-gray-300 dark:border-gray-700 rounded-lg bg-gray-200 dark:bg-gray-800 py-3 px-4 hover:bg-gray-300 dark:hover:bg-gray-700 [transition:all_0.3s_cubic-bezier(0.4,0,0.2,1)]" >
|
||||
Continue
|
||||
</button>
|
||||
</footer>
|
||||
</form>
|
||||
</Modal.Panel>
|
||||
</Modal.Root >
|
||||
<Modal.Root bind:show={isNewMember} class="w-full">
|
||||
<Modal.Panel
|
||||
class="dark:bg-black bg-white [box-shadow:0_8px_30px_rgba(0,0,0,.12)]
|
||||
dark:backdrop:bg-[#0009] backdrop:bg-[#b3b5b799] backdrop:backdrop-grayscale-[.3] max-h-[75vh] rounded-xl
|
||||
backdrop-blur-md modal max-w-[400px] w-full border dark:border-gray-800 border-gray-200">
|
||||
<form preventdefault:submit onSubmit$={handleInvite}>
|
||||
|
||||
<main class="size-full flex flex-col relative py-4 px-5">
|
||||
<div class="dark:text-white text-black">
|
||||
<h3 class="font-semibold text-2xl tracking-tight mb-1 font-title">Send an invite</h3>
|
||||
<div class="text-sm dark:text-gray-200/70 text-gray-800/70" >
|
||||
Friends will receive an email allowing them to join this team
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 flex flex-col gap-3" >
|
||||
<div>
|
||||
<label for="name" class="text-sm dark:text-gray-200 text-gray-800 pb-2 pt-1" >
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
value={inviteName.value}
|
||||
//@ts-expect-error
|
||||
onInput$={(e) => 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" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="email" class="text-sm dark:text-gray-200 text-gray-800 pb-2 pt-1" >
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
value={inviteEmail.value}
|
||||
//@ts-expect-error
|
||||
onInput$={(e) => 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" />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer class="dark:text-gray-200/70 text-gray-800/70 dark:bg-gray-900 bg-gray-100 ring-1 ring-gray-200 dark:ring-gray-800 select-none flex gap-2 items-center justify-between w-full bottom-0 left-0 py-3 px-5 text-sm leading-none">
|
||||
<Modal.Close class="rounded-lg [transition:all_0.3s_cubic-bezier(0.4,0,0.2,1)] py-3 px-4 hover:bg-gray-200 dark:hover:bg-gray-800 flex items-center justify-center">
|
||||
Cancel
|
||||
</Modal.Close>
|
||||
<button type="submit" class="flex items-center justify-center gap-2 border-2 border-gray-300 dark:border-gray-700 rounded-lg bg-gray-200 dark:bg-gray-800 py-3 px-4 hover:bg-gray-300 dark:hover:bg-gray-700 [transition:all_0.3s_cubic-bezier(0.4,0,0.2,1)]" >
|
||||
Send an invite
|
||||
</button>
|
||||
</footer>
|
||||
</form>
|
||||
</Modal.Panel>
|
||||
</Modal.Root>
|
||||
</>
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user