feat(www): Finish up on the onboarding (#210)

Merging this prematurely to make sure the team is on the same boat... like dang! We need to find a better way to do this. 

Plus it has become too big
This commit is contained in:
Wanjohi
2025-03-26 02:21:53 +03:00
committed by GitHub
parent 957eca7794
commit f62fc1fb4b
106 changed files with 6329 additions and 866 deletions

View File

@@ -1,14 +1,18 @@
import * as v from "valibot"
import { styled } from "@macaron-css/solid";
import { Text } from "@nestri/www/ui/text";
import { utility } from "@nestri/www/ui/utility";
import { theme } from "@nestri/www/ui/theme";
import { FormField, Input, Select } from "@nestri/www/ui/form";
import { Container, FullScreen } from "@nestri/www/ui/layout";
import { createForm, required, email, valiForm } from "@modular-forms/solid";
import { Show } from "solid-js";
import { Button } from "@nestri/www/ui";
import { Text } from "@nestri/www/ui/text";
import { styled } from "@macaron-css/solid";
import { theme } from "@nestri/www/ui/theme";
import { useNavigate } from "@solidjs/router";
import { useOpenAuth } from "@openauthjs/solid";
import { utility } from "@nestri/www/ui/utility";
import { useAccount } from "../providers/account";
import { Container, FullScreen } from "@nestri/www/ui/layout";
import { FormField, Input, Select } from "@nestri/www/ui/form";
import { createForm, getValue, setError, valiForm } from "@modular-forms/solid";
// const nameRegex = /^[a-z]+$/
const nameRegex = /^[a-z0-9\-]+$/
const FieldList = styled("div", {
base: {
@@ -33,19 +37,19 @@ const Plan = {
} as const;
const schema = v.object({
plan: v.pipe(
v.enum(Plan),
v.minLength(2,"Please choose a plan"),
planType: v.pipe(
v.enum(Plan, "Choose a valid plan"),
),
display_name: v.pipe(
name: v.pipe(
v.string(),
v.maxLength(32, 'Please use 32 characters at maximum.'),
v.minLength(2, 'Use 2 characters at minimum.'),
v.maxLength(32, 'Use 32 characters at maximum.'),
),
slug: v.pipe(
v.string(),
v.minLength(2, 'Please use 2 characters at minimum.'),
// v.regex(nameRegex, "Use only small letters, no numbers or special characters"),
v.maxLength(48, 'Please use 48 characters at maximum.'),
v.regex(nameRegex, "Use a URL friendly name."),
v.minLength(2, 'Use 2 characters at minimum.'),
v.maxLength(48, 'Use 48 characters at maximum.'),
)
})
@@ -82,11 +86,39 @@ const schema = v.object({
// }
// })
const UrlParent = styled("div", {
base: {
display: "flex",
width: "100%",
}
})
const UrlTitle = styled("span", {
base: {
borderWidth: 1,
borderRight: 0,
display: "flex",
alignItems: "center",
borderStyle: "solid",
color: theme.color.gray.d900,
fontSize: theme.font.size.sm,
padding: `0 ${theme.space[3]}`,
height: theme.input.size.base,
borderColor: theme.color.gray.d400,
borderTopLeftRadius: theme.borderRadius,
borderBottomLeftRadius: theme.borderRadius,
}
})
export function CreateTeamComponent() {
const [form, { Form, Field }] = createForm({
validate: valiForm(schema),
});
const nav = useNavigate();
const auth = useOpenAuth();
const account = useAccount();
return (
<FullScreen>
<Container horizontal="center" style={{ width: "100%", padding: "1rem", }} space="1" >
@@ -95,20 +127,41 @@ export function CreateTeamComponent() {
Create a Team
</Text>
<Text style={{ color: theme.color.gray.d900 }} size="sm">
Choose something that your teammates will recognize
Choose something that your team mates will recognize
</Text>
<Hr />
</Container>
<Form style={{ width: "100%", "max-width": "380px" }}>
<Form style={{ width: "100%", "max-width": "380px" }}
onSubmit={async (data) => {
console.log("submitting");
const result = await fetch(
import.meta.env.VITE_API_URL + "/team",
{
method: "POST",
headers: {
authorization: `Bearer ${await auth.access()}`,
"content-type": "application/json",
},
body: JSON.stringify(data),
},
);
if (!result.ok) {
setError(form, "slug", "Team slug is already taken.");
return;
}
await account.refresh(account.current.email);
await new Promise(resolve => setTimeout(resolve, 1000));
nav(`/${data.slug}`);
}}
>
<FieldList>
<Field type="string" name="slug">
<Field type="string" name="name">
{(field, props) => (
<FormField
label="Team Name"
hint={
field.error
&& field.error
// : "Needs to be lowercase, unique, and URL friendly."
}
color={field.error ? "danger" : "primary"}
>
@@ -120,19 +173,47 @@ export function CreateTeamComponent() {
</FormField>
)}
</Field>
<Field type="string" name="plan">
<Field type="string" name="slug">
{(field, props) => (
<FormField
label="Team Slug"
hint={
field.error
&& field.error
}
color={field.error ? "danger" : "primary"}
>
<UrlParent
data-type='url'
>
<UrlTitle>
nestri.io/
</UrlTitle>
<Input
{...props}
autofocus
placeholder={
getValue(form, "name")?.toString()
.split(" ").join("-")
.toLowerCase() || "janes-team"}
/>
</UrlParent>
</FormField>
)}
</Field>
<Field type="string" name="planType">
{(field, props) => (
<FormField
label="Plan Type"
hint={
field.error
&& field.error
// : "Needs to be lowercase, unique, and URL friendly."
}
color={field.error ? "danger" : "primary"}
>
<Select
{...props}
required
value={field.value}
badges={[
{ label: "BYOG", color: "purple" },
@@ -156,8 +237,10 @@ export function CreateTeamComponent() {
</div>
</Summary>
</Details> */}
<Button color="brand">
Continue
<Button color="brand" disabled={form.submitting} >
<Show when={form.submitting} fallback="Create">
Creating&hellip;
</Show>
</Button>
</FieldList>
</Form>

View File

@@ -0,0 +1,70 @@
import { Show } from "solid-js";
import { A } from "@solidjs/router";
import { Text } from "@nestri/www/ui/text";
import { styled } from "@macaron-css/solid";
import { theme } from "@nestri/www/ui/theme";
import { Header } from "@nestri/www/pages/team/header";
import { FullScreen, Container } from "@nestri/www/ui/layout";
const NotAllowedDesc = styled("div", {
base: {
fontSize: theme.font.size.base,
color: theme.color.gray.d900,
},
});
const HomeLink = styled(A, {
base: {
fontSize: theme.font.size.base,
textUnderlineOffset: 1,
color: theme.color.blue.d900
},
});
interface ErrorScreenProps {
inset?: "none" | "header";
message?: string;
header?: boolean;
}
export function NotFound(props: ErrorScreenProps) {
return (
<>
<Show when={props.header}>
<Header />
</Show>
<FullScreen
inset={props.inset ? props.inset : props.header ? "header" : "none"}
>
<Container space="2.5" horizontal="center">
<Text weight="semibold" spacing="xs" size="3xl">{props.message || "Page not found"}</Text>
<HomeLink href="/">Go back home</HomeLink>
</Container>
</FullScreen>
</>
);
}
export function NotAllowed(props: ErrorScreenProps) {
return (
<>
<Show when={props.header}>
<Header />
</Show>
<FullScreen
inset={props.inset ? props.inset : props.header ? "header" : "none"}
>
<Container space="2.5" horizontal="center">
<Text weight="semibold" spacing="xs" size="3xl">Access not allowed</Text>
<NotAllowedDesc>
You don't have access to this page,&nbsp;
<HomeLink href="/">go back home</HomeLink>.
</NotAllowedDesc>
<NotAllowedDesc>
Public profiles are coming soon
</NotAllowedDesc>
</Container>
</FullScreen>
</>
);
}

View File

@@ -0,0 +1,322 @@
import { A } from "@solidjs/router";
import { Container } from "@nestri/www/ui";
import Avatar from "@nestri/www/ui/avatar";
import { styled } from "@macaron-css/solid";
import { theme } from "@nestri/www/ui/theme";
import { useAccount } from "@nestri/www/providers/account";
import { TeamContext } from "@nestri/www/providers/context";
import { Match, ParentProps, Show, Switch, useContext } from "solid-js";
const PageWrapper = styled("div", {
base: {
minHeight: "100dvh",
// paddingBottom: "4rem",
backgroundColor: theme.color.background.d200
}
})
const NestriLogo = styled("svg", {
base: {
height: 28,
width: 28,
}
})
const NestriLogoBig = styled("svg", {
base: {
height: 38,
width: 38,
}
})
const LineSvg = styled("svg", {
base: {
width: 26,
height: 26,
color: theme.color.grayAlpha.d300
}
})
const LogoName = styled("svg", {
base: {
height: 18,
color: theme.color.d1000.grayAlpha
}
})
const Link = styled(A, {
base: {
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: 2
}
})
const TeamRoot = styled("div", {
base: {
height: 32,
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: 8
}
})
const LogoRoot = styled("div", {
base: {
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
}
})
const TeamLabel = styled("span", {
base: {
letterSpacing: -0.5,
fontSize: theme.font.size.base,
fontFamily: theme.font.family.heading,
fontWeight: theme.font.weight.semibold,
color: theme.color.gray.d900
}
})
const Badge = styled("div", {
base: {
height: 20,
fontSize: 11,
lineHeight: 1,
color: "#FFF",
padding: "0 6px",
letterSpacing: 0.2,
borderRadius: 9999,
alignItems: "center",
display: "inline-flex",
whiteSpace: "pre-wrap",
justifyContent: "center",
fontFeatureSettings: `"tnum"`,
fontVariantNumeric: "tabular-nums",
}
})
const DropIcon = styled("svg", {
base: {
height: 14,
width: 14,
marginLeft: -4,
color: theme.color.grayAlpha.d800
}
})
const AvatarImg = styled("img", {
base: {
height: 32,
width: 32,
borderRadius: 9999
}
})
const RightRoot = styled("div", {
base: {
marginLeft: "auto",
display: "flex",
gap: theme.space["4"],
alignItems: "center",
justifyContent: "center",
}
})
const NavRoot = styled("div", {
base: {
display: "flex",
height: "100%",
alignItems: "center",
justifyContent: "center",
gap: theme.space["4"],
}
})
const NavLink = styled(A, {
base: {
color: "#FFF",
textDecoration: "none",
height: 32,
padding: "0 8px",
display: "flex",
justifyContent: "center",
alignItems: "center",
borderRadius: 8,
gap: theme.space["2"],
lineHeight: 1.5,
fontSize: theme.font.size.sm,
fontWeight: theme.font.weight.regular,
transition: "all 0.3s cubic-bezier(0.4,0,0.2,1)",
// ":hover": {
// color: theme.color.d1000.gray
// }
}
})
const NavWrapper = styled("div", {
base: {
// borderBottom: "1px solid white",
zIndex: 10,
position: "fixed",
// backdropFilter: "saturate(60%) blur(3px)",
height: theme.headerHeight.root,
transition: "all 0.3s cubic-bezier(0.4,0,0.2,1)",
width: "100%",
backgroundColor: "transparent"
}
})
const Background = styled("div", {
base: {
background: theme.color.headerGradient,
zIndex: 1,
height: 180,
width: "100%",
position: "fixed",
pointerEvents: "none"
}
})
const Nav = styled("nav", {
base: {
position: "relative",
padding: "0.75rem 1rem",
zIndex: 200,
width: "100%",
gap: "1.5rem",
display: "flex",
justifyContent: "space-between",
alignItems: "center"
}
})
export function Header(props: { whiteColor?: boolean } & ParentProps) {
const team = useContext(TeamContext)
const account = useAccount()
return (
<PageWrapper>
<NavWrapper style={{ color: props.whiteColor ? "#FFF" : theme.color.d1000.gray }} >
{/* <Background /> */}
<Nav>
<Container space="4" vertical="center">
<Show when={team}
fallback={
<Link href="/">
<NestriLogoBig
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" />
</NestriLogoBig>
<LogoName viewBox="0 0 498.05 70.508" xmlns="http://www.w3.org/2000/svg" height="100%" width="100%" >
<g stroke-line-cap="round" fill-rule="evenodd" font-size="9pt" fill="currentColor">
<path
fill="currentColor"
pathLength="1"
d="M 261.23 41.65 L 212.402 41.65 Q 195.313 41.65 195.313 27.002 L 195.313 14.795 A 17.814 17.814 0 0 1 196.311 8.57 Q 199.443 0.146 212.402 0.146 L 283.203 0.146 L 283.203 14.844 L 217.236 14.844 Q 215.337 14.844 214.945 16.383 A 3.67 3.67 0 0 0 214.844 17.285 L 214.844 24.561 Q 214.844 27.002 217.236 27.002 L 266.113 27.002 Q 283.203 27.002 283.203 41.65 L 283.203 53.857 A 17.814 17.814 0 0 1 282.205 60.083 Q 279.073 68.506 266.113 68.506 L 195.313 68.506 L 195.313 53.809 L 261.23 53.809 A 3.515 3.515 0 0 0 262.197 53.688 Q 263.672 53.265 263.672 51.367 L 263.672 44.092 A 3.515 3.515 0 0 0 263.551 43.126 Q 263.128 41.65 261.23 41.65 Z M 185.547 53.906 L 185.547 68.506 L 114.746 68.506 Q 97.656 68.506 97.656 53.857 L 97.656 14.795 A 17.814 17.814 0 0 1 98.655 8.57 Q 101.787 0.146 114.746 0.146 L 168.457 0.146 Q 185.547 0.146 185.547 14.795 L 185.547 31.885 A 17.827 17.827 0 0 1 184.544 38.124 Q 181.621 45.972 170.174 46.538 A 36.906 36.906 0 0 1 168.457 46.582 L 117.188 46.582 L 117.236 51.465 Q 117.236 53.906 119.629 53.955 L 185.547 53.906 Z M 19.531 14.795 L 19.531 68.506 L 0 68.506 L 0 0.146 L 70.801 0.146 Q 87.891 0.146 87.891 14.795 L 87.891 68.506 L 68.359 68.506 L 68.359 17.236 Q 68.359 14.795 65.967 14.795 L 19.531 14.795 Z M 449.219 68.506 L 430.176 46.533 L 400.391 46.533 L 400.391 68.506 L 380.859 68.506 L 380.859 0.146 L 451.66 0.146 A 24.602 24.602 0 0 1 458.423 0.994 Q 466.007 3.166 468.021 10.907 A 25.178 25.178 0 0 1 468.75 17.236 L 468.75 31.885 A 18.217 18.217 0 0 1 467.887 37.73 Q 465.954 43.444 459.698 45.455 A 23.245 23.245 0 0 1 454.492 46.436 L 473.633 68.506 L 449.219 68.506 Z M 292.969 0 L 371.094 0.098 L 371.094 14.795 L 341.846 14.795 L 341.846 68.506 L 322.266 68.506 L 322.217 14.795 L 292.969 14.844 L 292.969 0 Z M 478.516 0.146 L 498.047 0.146 L 498.047 68.506 L 478.516 68.506 L 478.516 0.146 Z M 400.391 14.844 L 400.391 31.885 L 446.826 31.885 Q 448.726 31.885 449.117 30.345 A 3.67 3.67 0 0 0 449.219 29.443 L 449.219 17.285 Q 449.219 14.844 446.826 14.844 L 400.391 14.844 Z M 117.188 31.836 L 163.574 31.934 Q 165.528 31.895 165.918 30.355 A 3.514 3.514 0 0 0 166.016 29.492 L 166.016 17.236 Q 166.016 15.337 164.476 14.945 A 3.67 3.67 0 0 0 163.574 14.844 L 119.629 14.795 Q 117.188 14.795 117.188 17.188 L 117.188 31.836 Z" />
</g>
</LogoName>
</Link>
}
>
<LogoRoot>
<A href={`/${team!().slug}`} >
<NestriLogo
width={32}
height={32}
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" />
</NestriLogo>
</A>
<LineSvg
height="16"
stroke-linejoin="round"
viewBox="0 0 16 16"
width="16">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M4.01526 15.3939L4.3107 14.7046L10.3107 0.704556L10.6061 0.0151978L11.9849 0.606077L11.6894 1.29544L5.68942 15.2954L5.39398 15.9848L4.01526 15.3939Z" fill="currentColor"></path>
</LineSvg>
<TeamRoot>
<Avatar size={21} name={team!().slug} />
<TeamLabel style={{ color: props.whiteColor ? "#FFF" : theme.color.d1000.gray }}>{team!().name}</TeamLabel>
<Switch>
<Match when={team!().planType === "BYOG"}>
<Badge style={{ "background-color": theme.color.purple.d700 }}>
<span style={{ "line-height": 0 }} >BYOG</span>
</Badge>
</Match>
<Match when={team!().planType === "Hosted"}>
<Badge style={{ "background-color": theme.color.blue.d700 }}>
<span style={{ "line-height": 0 }}>Hosted</span>
</Badge>
</Match>
</Switch>
<DropIcon
xmlns="http://www.w3.org/2000/svg"
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" />
</DropIcon>
</TeamRoot>
</LogoRoot>
</Show>
</Container>
<RightRoot>
<Show when={team}>
<NavRoot>
<NavLink href={`/${team!().slug}/machines`}>
{/* <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M17.5 17.5L22 22m-2-11a9 9 0 1 0-18 0a9 9 0 0 0 18 0" color="currentColor" />
</svg> */}
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24">
<path fill="currentColor" d="M3.441 9.956a4.926 4.926 0 0 0 6.233 7.571l4.256 4.257a.773.773 0 0 0 1.169-1.007l-.075-.087l-4.217-4.218A4.927 4.927 0 0 0 3.44 9.956m13.213 6.545c-.225 1.287-.548 2.456-.952 3.454l.03.028l.124.14c.22.295.344.624.378.952a10.03 10.03 0 0 0 4.726-4.574zM12.25 16.5l2.284 2.287c.202-.6.381-1.268.53-1.992l.057-.294zm-2.936-5.45a3.38 3.38 0 1 1-4.78 4.779a3.38 3.38 0 0 1 4.78-4.78M15.45 10h-3.7a5.94 5.94 0 0 1 .892 5h2.71a26 26 0 0 0 .132-4.512zm1.507 0a28 28 0 0 1-.033 4.42l-.057.58h4.703a10.05 10.05 0 0 0 .258-5zm-2.095-7.593c.881 1.35 1.536 3.329 1.883 5.654l.062.44h4.59a10.03 10.03 0 0 0-6.109-5.958l-.304-.1zm-2.836-.405c-1.277 0-2.561 2.382-3.158 5.839c.465.16.912.38 1.331.658l5.088.001c-.54-3.809-1.905-6.498-3.261-6.498m-2.837.405A10.03 10.03 0 0 0 2.654 8.5h.995a5.92 5.92 0 0 1 3.743-.968c.322-1.858.846-3.47 1.527-4.68l.162-.275z" />
</svg>
{/* Machines */}
</NavLink>
<NavLink href={`/${team!().slug}/machines`}>
<svg style={{ "margin-bottom": "1px" }} xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16">
<g fill="currentColor"><path d="M4 8a1.5 1.5 0 1 1 3 0a1.5 1.5 0 0 1-3 0m7.5-1.5a1.5 1.5 0 1 0 0 3a1.5 1.5 0 0 0 0-3" />
<path d="M0 1.5A.5.5 0 0 1 .5 1h1a.5.5 0 0 1 .5.5V4h13.5a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-.5.5H2v2.5a.5.5 0 0 1-1 0V2H.5a.5.5 0 0 1-.5-.5m5.5 4a2.5 2.5 0 1 0 0 5a2.5 2.5 0 0 0 0-5M9 8a2.5 2.5 0 1 0 5 0a2.5 2.5 0 0 0-5 0" />
<path d="M3 12.5h3.5v1a.5.5 0 0 1-.5.5H3.5a.5.5 0 0 1-.5-.5zm4 1v-1h4v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5" />
</g>
</svg>
</NavLink>
</NavRoot>
</Show>
<div style={{ "margin-bottom": "2px" }} >
<Switch>
<Match when={account.current.avatarUrl} >
<AvatarImg src={account.current.avatarUrl} alt={`${account.current.name}'s avatar`} />
</Match>
<Match when={!account.current.avatarUrl}>
<Avatar size={32} name={`${account.current.name}#${account.current.discriminator}`} />
</Match>
</Switch>
</div>
</RightRoot>
</Nav>
</NavWrapper>
{props.children}
</PageWrapper>
)
}

View File

@@ -0,0 +1,414 @@
import { FullScreen, theme } from "@nestri/www/ui";
import { styled } from "@macaron-css/solid";
import { Header } from "@nestri/www/pages/team/header";
import { useSteam } from "@nestri/www/providers/steam";
import { Modal } from "@nestri/www/ui/modal";
import { createEffect, createSignal, onCleanup } from "solid-js";
import { Text } from "@nestri/www/ui/text"
import { QRCode } from "@nestri/www/ui/custom-qr";
import { globalStyle, keyframes } from "@macaron-css/core";
import { A } from "@solidjs/router";
const EmptyState = styled("div", {
base: {
padding: "0 40px",
display: "flex",
gap: 10,
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
margin: "auto"
}
})
const EmptyStateHeader = styled("h2", {
base: {
textAlign: "center",
fontSize: theme.font.size["2xl"],
fontFamily: theme.font.family.heading,
fontWeight: theme.font.weight.semibold,
letterSpacing: -0.5,
}
})
const EmptyStateSubHeader = styled("p", {
base: {
fontWeight: theme.font.weight.regular,
color: theme.color.gray.d900,
fontSize: theme.font.size["lg"],
textAlign: "center",
maxWidth: 380,
letterSpacing: -0.4,
lineHeight: 1.1,
}
})
const QRWrapper = styled("div", {
base: {
backgroundColor: theme.color.background.d100,
position: "relative",
marginBottom: 20,
textWrap: "balance",
border: `1px solid ${theme.color.gray.d400}`,
display: "flex",
justifyContent: "center",
alignItems: "center",
overflow: "hidden",
borderRadius: 22,
padding: 20,
}
})
const SteamMobileLink = styled(A, {
base: {
textUnderlineOffset: 2,
textDecoration: "none",
color: theme.color.blue.d900,
display: "inline-flex",
justifyContent: "center",
alignItems: "center",
gap: 1,
width: "max-content",
textTransform: "capitalize",
":hover": {
textDecoration: "underline"
}
}
})
const LogoContainer = styled("div", {
base: {
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
}
})
const LogoIcon = styled("svg", {
base: {
zIndex: 6,
position: "absolute",
left: "50%",
top: "50%",
transform: "translate(-50%,-50%)",
overflow: "hidden",
// width: "21%",
// height: "21%",
borderRadius: 17,
// ":before": {
// pointerEvents: "none",
// zIndex: 2,
// content: '',
// position: "absolute",
// inset: 0,
// borderRadius: "inherit",
// boxShadow: "inset 0 0 0 1px rgba(0, 0, 0, 0.02)",
// }
}
})
const LastPlayedWrapper = styled("div", {
base: {
position: "relative",
width: "100%",
justifyContent: "center",
minHeight: 700,
height: "50vw",
maxHeight: 800,
WebkitBoxPack: "center",
display: "flex",
flexDirection: "column",
":after": {
content: "",
pointerEvents: "none",
userSelect: "none",
background: `linear-gradient(to bottom,transparent,${theme.color.background.d200})`,
width: "100%",
left: 0,
position: "absolute",
bottom: -1,
zIndex: 3,
height: 320,
backdropFilter: "blur(2px)",
WebkitBackdropFilter: "blur(1px)",
WebkitMaskImage: `linear-gradient(to top,${theme.color.background.d200} 25%,transparent)`,
maskImage: `linear-gradient(to top,${theme.color.background.d200} 25%,transparent)`
}
}
})
const LastPlayedFader = styled("div", {
base: {
position: "absolute",
width: "100%",
height: "3rem",
backgroundColor: "rgba(0,0,0,.08)",
mixBlendMode: "multiply",
backdropFilter: "saturate(160%) blur(60px)",
WebkitBackdropFilter: "saturate(160%) blur(60px)",
maskImage: "linear-gradient(to top,rgba(0,0,0,.15) 0%,rgba(0,0,0,.65) 57.14%,rgba(0,0,0,.9) 67.86%,#000 79.08%)",
// background: "linear-gradient(rgb(0, 0, 0) 0%, rgba(0, 0, 0, 0.3) 50%, rgba(10, 0, 0, 0.15) 65%, rgba(0, 0, 0, 0.075) 75.5%, rgba(0, 0, 0, 0.035) 82.85%, rgba(0, 0, 0, 0.02) 88%, rgba(0, 0, 0, 0) 100%)",
opacity: 0.6,
// backdropFilter: "blur(16px)",
pointerEvents: "none",
zIndex: 1,
top: 0,
left: 0,
}
})
const BackgroundImage = styled("div", {
base: {
position: "fixed",
inset: 0,
backgroundColor: theme.color.background.d200,
backgroundSize: "cover",
zIndex: 0,
transitionDuration: "0.2s",
transitionTimingFunction: "cubic-bezier(0.4,0,0.2,1)",
transitionProperty: "opacity",
backgroundImage: "url(https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/1203190/ss_97ea9b0b5a6adf3436b31d389cd18d3a647ee4bf.jpg)"
// backgroundImage: "url(https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/3373660/c4993923f605b608939536b5f2521913850b028a/ss_c4993923f605b608939536b5f2521913850b028a.jpg)"
}
})
const LogoBackgroundImage = styled("div", {
base: {
position: "fixed",
top: "2rem",
height: 240,
// width: 320,
aspectRatio: "16 / 9",
left: "50%",
transform: "translate(-50%,0%)",
backgroundSize: "cover",
zIndex: 1,
transitionDuration: "0.2s",
transitionTimingFunction: "cubic-bezier(0.4,0,0.2,1)",
transitionProperty: "opacity",
backgroundImage: "url(https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/1203190/logo_2x.png)"
}
})
const Material = styled("div", {
base: {
backdropFilter: "saturate(160%) blur(60px)",
WebkitBackdropFilter: "saturate(160%) blur(60px)",
backgroundSize: "cover",
backgroundRepeat: "no-repeat",
position: "absolute",
borderRadius: 6,
left: 0,
top: 0,
height: "100%",
width: "100%",
maskImage: "linear-gradient(180deg,rgba(0,0,0,0) 0,rgba(0,0,0,0) 40.82%,rgba(0,0,0,.15) 50%,rgba(0,0,0,.65) 57.14%,rgba(0,0,0,.9) 67.86%,#000 79.08%)",
WebkitMaskImage: "linear-gradient(180deg,rgba(0,0,0,0) 0,rgba(0,0,0,0) 40.82%,rgba(0,0,0,.15) 50%,rgba(0,0,0,.65) 57.14%,rgba(0,0,0,.9) 67.86%,#000 79.08%)"
}
})
const JoeColor = styled("div", {
base: {
backgroundColor: "rgba(0,0,0,.08)",
mixBlendMode: "multiply",
position: "absolute",
borderRadius: 6,
left: 0,
top: 0,
height: "100%",
width: "100%",
maskImage: "linear-gradient(180deg,rgba(0,0,0,0) 0,rgba(0,0,0,0) 40.82%,rgba(0,0,0,.15) 50%,rgba(0,0,0,.65) 57.14%,rgba(0,0,0,.9) 67.86%,#000 79.08%)",
WebkitMaskImage: "linear-gradient(180deg,rgba(0,0,0,0) 0,rgba(0,0,0,0) 40.82%,rgba(0,0,0,.15) 50%,rgba(0,0,0,.65) 57.14%,rgba(0,0,0,.9) 67.86%,#000 79.08%)"
}
})
const GamesContainer = styled("div", {
base: {
width: "100%",
display: "flex",
alignItems: "center",
flexDirection: "column",
zIndex: 3,
backgroundColor: theme.color.background.d200,
}
})
const GamesWrapper = styled("div", {
base: {
maxWidth: "70vw",
width: "100%",
gridTemplateColumns: "repeat(4, minmax(0, 1fr))",
margin: "0 auto",
display: "grid",
marginTop: -80,
columnGap: 12,
rowGap: 10
}
})
const GameImage = styled("img", {
base: {
width: "100%",
height: "100%",
aspectRatio: "460/215",
borderRadius: 10,
}
})
const GameSquareImage = styled("img", {
base: {
width: "100%",
height: "100%",
aspectRatio: "1/1",
borderRadius: 10,
transitionDuration: "0.2s",
transitionTimingFunction: "cubic-bezier(0.4,0,0.2,1)",
transitionProperty: "all",
cursor: "pointer",
border: `2px solid transparent`,
":hover": {
transform: "scale(1.05)",
outline: `2px solid ${theme.color.brand}`
}
}
})
const GameImageCapsule = styled("img", {
base: {
width: "100%",
height: "100%",
aspectRatio: "374/448",
borderRadius: 10,
}
})
const SteamLibrary = styled("div", {
base: {
borderTop: `1px solid ${theme.color.gray.d400}`,
padding: "20px 0",
margin: "20px auto",
width: "100%",
display: "grid",
// backgroundColor: "red",
maxWidth: "70vw",
gridTemplateColumns: "repeat(4, minmax(0, 1fr))",
columnGap: 12,
rowGap: 10,
}
})
const SteamLibraryTitle = styled("h3", {
base: {
textAlign: "left",
fontFamily: theme.font.family.heading,
fontWeight: theme.font.weight.medium,
fontSize: theme.font.size["2xl"],
letterSpacing: -0.7,
gridColumn: "1/-1",
marginBottom: 20,
}
})
export function HomeRoute() {
// const steam = useSteam();
// const [loginUrl, setLoginUrl] = createSignal<string | null>(null);
// const [loginStatus, setLoginStatus] = createSignal<string | null>("Not connected");
// const [userData, setUserData] = createSignal<{ username?: string, steamId?: string } | null>(null);
// createEffect(async () => {
// // Connect to the Steam login stream
// const steamConnection = await steam.client.login.connect();
// // Set up event listeners for different event types
// const urlUnsubscribe = steamConnection.addEventListener('url', (url) => {
// setLoginUrl(url);
// setLoginStatus('Scan QR code with Steam mobile app');
// });
// const loginAttemptUnsubscribe = steamConnection.addEventListener('login-attempt', (data) => {
// setLoginStatus(`Logging in as ${data.username}...`);
// });
// const loginSuccessUnsubscribe = steamConnection.addEventListener('login-success', (data) => {
// setUserData(data);
// setLoginStatus(`Successfully logged in as ${data.username}`);
// });
// const loginUnsuccessfulUnsubscribe = steamConnection.addEventListener('login-unsuccessful', (data) => {
// setLoginStatus(`Login failed: ${data.error}`);
// });
// const loggedOffUnsubscribe = steamConnection.addEventListener('logged-off', (data) => {
// setLoginStatus(`Logged out of Steam: ${data.reason}`);
// setUserData(null);
// });
// onCleanup(() => {
// urlUnsubscribe();
// loginAttemptUnsubscribe();
// loginSuccessUnsubscribe();
// loginUnsuccessfulUnsubscribe();
// loggedOffUnsubscribe();
// steamConnection.disconnect();
// });
// })
return (
<>
<Header whiteColor>
<FullScreen >
<EmptyState
style={{
"--nestri-qr-dot-color": theme.color.d1000.gray,
"--nestri-body-background": theme.color.gray.d100
}}
>
<QRWrapper>
<LogoContainer>
<LogoIcon
xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 16 16">
<g fill="currentColor">
<path d="M.329 10.333A8.01 8.01 0 0 0 7.99 16C12.414 16 16 12.418 16 8s-3.586-8-8.009-8A8.006 8.006 0 0 0 0 7.468l.003.006l4.304 1.769A2.2 2.2 0 0 1 5.62 8.88l1.96-2.844l-.001-.04a3.046 3.046 0 0 1 3.042-3.043a3.046 3.046 0 0 1 3.042 3.043a3.047 3.047 0 0 1-3.111 3.044l-2.804 2a2.223 2.223 0 0 1-3.075 2.11a2.22 2.22 0 0 1-1.312-1.568L.33 10.333Z" /><path d="M4.868 12.683a1.715 1.715 0 0 0 1.318-3.165a1.7 1.7 0 0 0-1.263-.02l1.023.424a1.261 1.261 0 1 1-.97 2.33l-.99-.41a1.7 1.7 0 0 0 .882.84Zm3.726-6.687a2.03 2.03 0 0 0 2.027 2.029a2.03 2.03 0 0 0 2.027-2.029a2.03 2.03 0 0 0-2.027-2.027a2.03 2.03 0 0 0-2.027 2.027m2.03-1.527a1.524 1.524 0 1 1-.002 3.048a1.524 1.524 0 0 1 .002-3.048" />
</g>
</LogoIcon>
</LogoContainer>
<QRCode
uri={"https://github.com/family/connectkit/blob/9a3c16c781d8a60853eff0c4988e22926a3f91ce"}
size={180}
ecl="M"
clearArea={true}
/>
</QRWrapper>
<EmptyStateHeader>Sign in to your Steam account</EmptyStateHeader>
<EmptyStateSubHeader>Use your Steam Mobile App to sign in via QR code.&nbsp;<SteamMobileLink href="https://store.steampowered.com/mobile" target="_blank">Learn More<svg data-testid="geist-icon" height="20" stroke-linejoin="round" viewBox="0 0 16 16" width="20" style="color: currentcolor;"><path fill-rule="evenodd" clip-rule="evenodd" d="M11.5 9.75V11.25C11.5 11.3881 11.3881 11.5 11.25 11.5H4.75C4.61193 11.5 4.5 11.3881 4.5 11.25L4.5 4.75C4.5 4.61193 4.61193 4.5 4.75 4.5H6.25H7V3H6.25H4.75C3.7835 3 3 3.7835 3 4.75V11.25C3 12.2165 3.7835 13 4.75 13H11.25C12.2165 13 13 12.2165 13 11.25V9.75V9H11.5V9.75ZM8.5 3H9.25H12.2495C12.6637 3 12.9995 3.33579 12.9995 3.75V6.75V7.5H11.4995V6.75V5.56066L8.53033 8.52978L8 9.06011L6.93934 7.99945L7.46967 7.46912L10.4388 4.5H9.25H8.5V3Z" fill="currentColor"></path></svg></SteamMobileLink></EmptyStateSubHeader>
</EmptyState>
{/* <LastPlayedWrapper>
<LastPlayedFader />
<LogoBackgroundImage />
<BackgroundImage />
<Material />
<JoeColor />
</LastPlayedWrapper> */}
{/* <GamesContainer>
<GamesWrapper>
<GameSquareImage alt="Assasin's Creed Shadows" src="https://assets-prd.ignimgs.com/2024/05/15/acshadows-1715789601294.jpg" />
<GameSquareImage alt="Assasin's Creed Shadows" src="https://assets-prd.ignimgs.com/2022/09/22/slime-rancher-2-button-02-1663890048548.jpg" />
<GameSquareImage alt="Assasin's Creed Shadows" src="https://assets-prd.ignimgs.com/2023/05/19/cataclismo-button-1684532710313.jpg" />
<GameSquareImage alt="Assasin's Creed Shadows" src="https://assets-prd.ignimgs.com/2024/03/27/marvelrivals-1711557092104.jpg" />
</GamesWrapper>
<SteamLibrary>
<SteamLibraryTitle>Games we think you will like</SteamLibraryTitle>
<GameImageCapsule alt="Assasin's Creed Shadows" src="https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/2625420/hero_capsule.jpg?t=1742853642" />
<GameImageCapsule alt="Assasin's Creed Shadows" src="https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/2486740/hero_capsule.jpg?t=1742596243" />
<GameImageCapsule alt="Assasin's Creed Shadows" src="https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/870780/hero_capsule.jpg?t=1737800535" />
<GameImageCapsule alt="Assasin's Creed Shadows" src="https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/2050650/hero_capsule.jpg?t=1737800535" />
</SteamLibrary>
</GamesContainer> */}
</FullScreen>
</Header>
</>
)
}

View File

@@ -0,0 +1,69 @@
import { HomeRoute } from "./home";
import { useOpenAuth } from "@openauthjs/solid";
import { Route, useParams } from "@solidjs/router";
import { ApiProvider } from "@nestri/www/providers/api";
import { SteamRoute } from "@nestri/www/pages/team/steam";
import { ZeroProvider } from "@nestri/www/providers/zero";
import { TeamContext } from "@nestri/www/providers/context";
import { SteamProvider } from "@nestri/www/providers/steam";
import { createEffect, createMemo, Match, Switch } from "solid-js";
import { NotAllowed, NotFound } from "@nestri/www/pages/not-found";
import { useAccount, useStorage } from "@nestri/www/providers/account";
export const TeamRoute = (
<Route
component={(props) => {
const params = useParams();
const account = useAccount();
const storage = useStorage();
const openauth = useOpenAuth();
const team = createMemo(() =>
account.current.teams.find(
(item) => item.slug === params.teamSlug,
),
);
createEffect(() => {
const t = team();
if (!t) return;
storage.set("team", t.id);
});
createEffect(() => {
const teamSlug = params.teamSlug;
for (const item of Object.values(account.all)) {
for (const team of item.teams) {
if (team.slug === teamSlug && item.id !== openauth.subject!.id) {
openauth.switch(item.email);
}
}
}
})
return (
<Switch>
<Match when={!team()}>
TODO: Add a public page for (other) teams
<NotAllowed header />
</Match>
<Match when={team()}>
<TeamContext.Provider value={() => team()!}>
<ZeroProvider>
<ApiProvider>
<SteamProvider>
{props.children}
</SteamProvider>
</ApiProvider>
</ZeroProvider>
</TeamContext.Provider>
</Match>
</Switch>
)
}}
>
<Route path="" component={HomeRoute} />
<Route path="steam" component={SteamRoute} />
<Route path="*" component={() => <NotFound header />} />
</Route>
)

View File

@@ -0,0 +1,238 @@
import { Header } from "./header"
import { theme } from "@nestri/www/ui";
import { Text } from "@nestri/www/ui";
import { styled } from "@macaron-css/solid";
import { useSteam } from "@nestri/www/providers/steam";
import { createEffect, onCleanup } from "solid-js";
// FIXME: Remove this route, or move it to machines
// The idea has changed, let the user login to Steam from the / route
// Let the machines route remain different from the main page
// Why? It becomes much simpler for routing and onboarding, plus how often will you move to the machines route?
// Now it will be the home page's problem with making sure the user can download and install games on whatever machine they need/want
const Root = styled("div", {
base: {
display: "grid",
gridAutoRows: "1fr",
position: "relative",
gridTemplateRows: "0 auto",
backgroundColor: theme.color.background.d200,
minHeight: `calc(100vh - ${theme.headerHeight.root})`,
gridTemplateColumns: "minmax(24px,1fr) minmax(0,1000px) minmax(24px,1fr)"
},
});
const Section = styled("section", {
base: {
gridColumn: "1/-1",
}
})
const TitleHeader = styled("header", {
base: {
borderBottom: `1px solid ${theme.color.gray.d400}`,
color: theme.color.d1000.gray
}
})
const TitleWrapper = styled("div", {
base: {
width: "calc(1000px + calc(2 * 24px))",
paddingLeft: "24px",
display: "flex",
paddingRight: "24px",
marginLeft: "auto",
marginRight: "auto",
maxWidth: "100%"
}
})
const TitleContainer = styled("div", {
base: {
margin: "40px 0",
display: "flex",
flexDirection: "column",
gap: 16,
width: "100%",
minWidth: 0
}
})
const ButtonContainer = styled("div", {
base: {
display: "flex",
flexDirection: "row",
gap: 16,
margin: "40px 0",
}
})
const Title = styled("h1", {
base: {
lineHeight: "2.5rem",
fontWeight: theme.font.weight.semibold,
letterSpacing: "-0.069375rem",
fontSize: theme.font.size["4xl"],
textTransform: "capitalize"
}
})
const Description = styled("p", {
base: {
fontSize: theme.font.size.sm,
lineHeight: "1.25rem",
fontWeight: theme.font.weight.regular,
letterSpacing: "initial",
color: theme.color.gray.d900
}
})
const QRButton = styled("button", {
base: {
height: 40,
borderRadius: theme.borderRadius,
backgroundColor: theme.color.d1000.gray,
color: theme.color.gray.d100,
fontSize: theme.font.size.sm,
textWrap: "nowrap",
border: "1px solid transparent",
padding: `${theme.space[2]} ${theme.space[4]}`,
letterSpacing: 0.1,
lineHeight: "1.25rem",
fontFamily: theme.font.family.body,
fontWeight: theme.font.weight.medium,
cursor: "pointer",
transitionDelay: "0s, 0s",
transitionDuration: "0.2s, 0.2s",
transitionProperty: "background-color, border",
transitionTimingFunction: "ease-out, ease-out",
display: "inline-flex",
gap: theme.space[2],
alignItems: "center",
justifyContent: "center",
":disabled": {
pointerEvents: "none",
},
":hover": {
background: theme.color.hoverColor
}
}
})
const ButtonText = styled("span", {
base: {
textOverflow: "ellipsis",
whiteSpace: "nowrap",
overflow: "hidden",
}
})
const Body = styled("div", {
base: {
padding: "0 24px",
width: "calc(1000px + calc(2 * 24px))",
minWidth: "calc(100vh - 273px)",
margin: "24px auto"
}
})
const GamesContainer = styled("div", {
base: {
background: theme.color.background.d200,
padding: "32px 16px",
borderRadius: 5,
border: `1px solid ${theme.color.gray.d400}`,
display: "flex",
flexDirection: "column",
alignItems: "center",
height: "calc(100vh - 300px)",
}
})
const EmptyState = styled("div", {
base: {
height: "100%",
width: "100%",
display: "flex",
justifyContent: "center",
alignItems: "center",
gap: theme.space[8],
flexDirection: "column"
}
})
const SteamLogoContainer = styled("div", {
base: {
height: 60,
width: 60,
padding: 4,
borderRadius: 8,
display: "flex",
justifyContent: "center",
alignItems: "center",
backgroundColor: theme.color.background.d200,
border: `1px solid ${theme.color.gray.d400}`,
}
})
export function SteamRoute() {
const steam = useSteam();
createEffect(() => {
// steam.client.loginStream.connect();
// Clean up on component unmount
// onCleanup(() => {
// steam.client.loginStream.disconnect();
// });
});
return (
<>
<Header />
<Root>
<Section>
<TitleHeader>
<TitleWrapper>
<TitleContainer>
<Title>
Steam Library
</Title>
<Description>
{/* Read and write directly to databases and stores from your projects. */}
Install games directly from your Steam account to your Nestri Machine
</Description>
</TitleContainer>
<ButtonContainer>
<QRButton>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 32 32">
<path fill="currentColor" d="M15.974 0C7.573 0 .682 6.479.031 14.714l8.573 3.547a4.5 4.5 0 0 1 2.552-.786c.083 0 .167.005.25.005l3.813-5.521v-.078c0-3.328 2.703-6.031 6.031-6.031s6.036 2.708 6.036 6.036a6.04 6.04 0 0 1-6.036 6.031h-.135l-5.438 3.88c0 .073.005.141.005.214c0 2.5-2.021 4.526-4.521 4.526c-2.177 0-4.021-1.563-4.443-3.635L.583 20.36c1.901 6.719 8.063 11.641 15.391 11.641c8.833 0 15.995-7.161 15.995-16s-7.161-16-15.995-16zm-5.922 24.281l-1.964-.813a3.4 3.4 0 0 0 1.755 1.667a3.404 3.404 0 0 0 4.443-1.833a3.38 3.38 0 0 0 .005-2.599a3.36 3.36 0 0 0-1.839-1.844a3.38 3.38 0 0 0-2.5-.042l2.026.839c1.276.536 1.88 2 1.349 3.276s-2 1.88-3.276 1.349zm15.219-12.406a4.025 4.025 0 0 0-4.016-4.021a4.02 4.02 0 1 0 0 8.042a4.02 4.02 0 0 0 4.016-4.021m-7.026-.005c0-1.672 1.349-3.021 3.016-3.021s3.026 1.349 3.026 3.021c0 1.667-1.359 3.021-3.026 3.021s-3.016-1.354-3.016-3.021" />
</svg>
<ButtonText>
Connect Steam
</ButtonText>
</QRButton>
</ButtonContainer>
</TitleWrapper>
</TitleHeader>
<Body>
<GamesContainer>
<EmptyState>
<SteamLogoContainer>
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<path fill="currentColor" d="M15.974 0C7.573 0 .682 6.479.031 14.714l8.573 3.547a4.5 4.5 0 0 1 2.552-.786c.083 0 .167.005.25.005l3.813-5.521v-.078c0-3.328 2.703-6.031 6.031-6.031s6.036 2.708 6.036 6.036a6.04 6.04 0 0 1-6.036 6.031h-.135l-5.438 3.88c0 .073.005.141.005.214c0 2.5-2.021 4.526-4.521 4.526c-2.177 0-4.021-1.563-4.443-3.635L.583 20.36c1.901 6.719 8.063 11.641 15.391 11.641c8.833 0 15.995-7.161 15.995-16s-7.161-16-15.995-16zm-5.922 24.281l-1.964-.813a3.4 3.4 0 0 0 1.755 1.667a3.404 3.404 0 0 0 4.443-1.833a3.38 3.38 0 0 0 .005-2.599a3.36 3.36 0 0 0-1.839-1.844a3.38 3.38 0 0 0-2.5-.042l2.026.839c1.276.536 1.88 2 1.349 3.276s-2 1.88-3.276 1.349zm15.219-12.406a4.025 4.025 0 0 0-4.016-4.021a4.02 4.02 0 1 0 0 8.042a4.02 4.02 0 0 0 4.016-4.021m-7.026-.005c0-1.672 1.349-3.021 3.016-3.021s3.026 1.349 3.026 3.021c0 1.667-1.359 3.021-3.026 3.021s-3.016-1.354-3.016-3.021" />
</svg>
</SteamLogoContainer>
<Text align="center" style={{ "letter-spacing": "-0.3px" }} size="base" >
{/* After connecting your Steam account, your games will appear here */}
{/* URL: {steam.client.loginStream.loginUrl()} */}
</Text>
</EmptyState>
</GamesContainer>
</Body>
</Section>
</Root>
</>
)
}