Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 71 additions & 139 deletions apps/react-three-org/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,61 @@
import { useEffect, useMemo, useState } from 'react'
import { Loading } from './loading.js'
import { useQuery } from '@tanstack/react-query'
import { PackageCard } from '@/components/package-card'
import { ProjectConfigurator } from '@/components/project-configurator'
import { NavBar } from '@/components/nav-bar'
import { packages, tools, PackageIDs, ToolIDs } from '@/lib/packages'
import { BackgroundAnimation } from '@/components/background-animation'
import { CogIcon, PackageIcon } from 'lucide-react'
import { PackageIDs, packages, ToolIDs, tools } from '@/lib/packages'
import { useState } from 'react'
import { ProjectConfigurator, FilterType, GithubRepo, SelectBox } from './components/redesign-ui'
import { Toaster } from 'sonner'
import { SelectionSection } from './components/selection-section.js'
import { Hero } from './components/redesign-ui/hero'
import { ListItem } from './components/redesign-ui/list-item'
import { useIsMobile } from './hooks/use-mobile'

const searchParams = new URLSearchParams(location.search)
const sessionAccessTokenKey = 'access_token'
const sessionAccessToken = sessionStorage.getItem(sessionAccessTokenKey)

export function App() {
const [state, setState] = useState(() => searchParams.get('state'))
const [selectedPackages, setSelectedPackages] = useState<PackageIDs[]>([])
const [selectedTools, setSelectedTools] = useState<ToolIDs[]>(['triplex'])
const allPackagesSelected = packages.every((pkg) => selectedPackages.includes(pkg.id))
const allToolsSelected = tools.every((tool) => selectedTools.includes(tool.id))

const isMobile = useIsMobile()

if (state != null) {
return <GithubRepo state={state} />
}

return (
<main className="relative min-h-screen flex flex-col pb-36">
<BackgroundAnimation />
<div className="container mx-auto px-4 py-8 z-10 flex-1">
<div className="flex flex-col items-center justify-center mb-6 mt-8">
<h1 className="text-5xl md:text-7xl font-bold text-center mb-4 tracking-tighter">React Three</h1>
<p className="text-lg md:text-xl text-white/70 text-center max-w-2xl mb-6">
Building 3D experiences with the React Three Ecosystem
</p>
<NavBar />
</div>

{/* Visual separator using space instead of a border */}
<div className="mb-10 mt-8"></div>

<SelectionSection
value={selectedPackages}
icon={PackageIcon}
label="packages"
onChange={setSelectedPackages}
options={packages}
<main className="min-h-screen text-redesign-white font-mono relative">
<Hero />
<div className="w-full xl:grid xl:grid-cols-12 xl:gap-4 relative">
<FilterType
selectedPackages={selectedPackages}
selectedTools={selectedTools}
onSelectAllPackages={(selected) => {
setSelectedPackages(selected ? packages.map((pkg) => pkg.id) : [])
}}
onSelectAllTools={(selected) => {
setSelectedTools(selected ? tools.map((tool) => tool.id) : [])
}}
/>

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
<div className="xl:col-start-3 xl:col-end-13 divide-y divide-redesign-gray">
{isMobile && (
<div className="xl:hidden border-b border-redesign-gray">
<button
className="w-full p-4 flex gap-4 justify-between"
onClick={() => {
setSelectedPackages(allPackagesSelected ? [] : packages.map((pkg) => pkg.id))
}}
aria-pressed={allPackagesSelected}
aria-label="All packages"
>
<p role="heading" aria-level={3}>
/ ALL PACKAGES
</p>
<SelectBox selected={allPackagesSelected} />
</button>
</div>
)}
{packages.map((pkg) => (
<PackageCard
<ListItem
key={pkg.id}
package={pkg}
isSelected={selectedPackages.includes(pkg.id)}
Expand All @@ -59,22 +66,26 @@ export function App() {
}}
/>
))}
</div>

{/* Visual separator using space instead of a border */}
<div className="mb-10 mt-8"></div>

<SelectionSection
label="tools"
value={selectedTools}
icon={CogIcon}
onChange={setSelectedTools}
options={tools}
/>

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
{isMobile && (
<div className="xl:hidden border-b border-redesign-gray">
<button
className="w-full p-4 flex gap-4 justify-between"
onClick={() => {
setSelectedTools(allToolsSelected ? [] : tools.map((tool) => tool.id))
}}
aria-pressed={allToolsSelected}
aria-label="All tools"
>
<p role="heading" aria-level={3}>
/ ALL TOOLS
</p>
<SelectBox selected={allToolsSelected} />
</button>
</div>
)}
{tools.map((pkg) => (
<PackageCard
<ListItem
key={pkg.id}
package={pkg}
isSelected={selectedTools.includes(pkg.id)}
Expand All @@ -86,20 +97,19 @@ export function App() {
/>
))}
</div>

<ProjectConfigurator
createGithubRepo={() => {
const integrations: any = {}
const selections = [...selectedPackages, ...selectedTools]
for (const integration of selections) {
integrations[integration] = true
}

setState(btoa(JSON.stringify(integrations)))
}}
selections={[...selectedPackages, ...selectedTools]}
/>
</div>
<ProjectConfigurator
createGithubRepo={() => {
const integrations: any = {}
const selections = [...selectedPackages, ...selectedTools]
for (const integration of selections) {
integrations[integration] = true
}

setState(btoa(JSON.stringify(integrations)))
}}
selections={[...selectedPackages, ...selectedTools]}
/>
<Toaster
position="top-right"
toastOptions={{
Expand All @@ -118,81 +128,3 @@ export function App() {
</main>
)
}

function GithubRepo({ state }: { state: string }) {
const code = useMemo(() => searchParams.get('code'), [])
const {
isPending: isPendingAccessToken,
error: errorAccessToken,
data: accessTokenData,
} = useQuery({
retry: false,
enabled: sessionAccessToken == null,
queryKey: ['oauth', code],
queryFn: async () => {
if (code == null) {
//promise never
return new Promise<{ token: string }>(() => {})
}
const response = await fetch(new URL(`/oauth?code=${code}`, import.meta.env.VITE_SERVER_URL))
if (!response.ok) {
throw new Error(response.statusText)
}
return response.json() as any as { token: string }
},
})
useEffect(() => {
if (accessTokenData == null) {
return
}
sessionStorage.setItem(sessionAccessTokenKey, accessTokenData.token)
}, [accessTokenData?.token])
const accessToken = sessionAccessToken ?? accessTokenData?.token
const {
data: repoData,
isPending: isPendingRepo,
error: repoError,
} = useQuery({
retry: false,
enabled: accessToken != null,
queryKey: ['repo', accessToken],
queryFn: async () => {
const response = await fetch(new URL('/repo', import.meta.env.VITE_SERVER_URL), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ token: accessToken!, ...JSON.parse(atob(decodeURIComponent(state))) }),
})
if (!response.ok) {
throw new Error(response.statusText)
}
return response.json() as any as { url: string }
},
})
useEffect(() => {
if (repoData?.url == null) {
return
}
setTimeout(() => (location.href = repoData.url), 1000)
}, [repoData?.url])
if (code == null) {
location.href = `https://github.com/login/oauth/authorize?client_id=${
import.meta.env.VITE_CLIENT_ID
}&redirect_uri=${import.meta.env.VITE_REDIRECT_URL}&state=${state}&scope=user%20repo%20workflow`
return null
}
if (sessionAccessToken == null && isPendingAccessToken) {
return <Loading text="Loggin In" />
}
if (errorAccessToken != null) {
return errorAccessToken.message
}
if (isPendingRepo) {
return <Loading text="Creating Repository" />
}
if (repoError) {
return repoError.message
}
return <Loading text="Forwarding to Repository" />
}
59 changes: 59 additions & 0 deletions apps/react-three-org/src/components/redesign-ui/filter-type.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { type Package, PackageIDs, packages, ToolIDs, tools } from '@/lib/packages'
import { SelectBox } from './select-box'
import { useIsMobile } from '@/hooks/use-mobile'

interface FilterTypeProps {
selectedPackages: PackageIDs[]
selectedTools: ToolIDs[]
onSelectAllPackages: (selected: boolean) => void
onSelectAllTools: (selected: boolean) => void
}

export const FilterType = ({
selectedPackages,
selectedTools,
onSelectAllPackages,
onSelectAllTools,
}: FilterTypeProps) => {
const allPackagesSelected = packages.every((pkg) => selectedPackages.includes(pkg.id))
const allToolsSelected = tools.every((tool) => selectedTools.includes(tool.id))

return (
<div className="hidden col-span-2 xl:block xl:sticky top-4 left-0 px-4 xl:px-6" aria-label="Filter options">
<div className="col-start-1 col-end-3 h-full w-full pt-4 ml-6 md:pt-6 md:ml-0">
<div className="w-full sticky top-4 flex flex-col gap-4">
<h2 className="sr-only">Filter Options</h2>
<p role="heading" aria-level={3}>
/ SELECT ALL
</p>

<div className="flex flex-col gap-4 pl-4" role="group" aria-labelledby="packages-group">
<button
className="flex items-center gap-4"
onClick={() => onSelectAllPackages(!allPackagesSelected)}
aria-pressed={allPackagesSelected}
aria-label="Select all packages"
id="packages-group"
>
<SelectBox selected={allPackagesSelected} />
<p>Packages</p>
</button>
</div>

<div className="flex flex-col gap-4 pl-4" role="group" aria-labelledby="tools-group">
<button
className="flex items-center gap-4"
onClick={() => onSelectAllTools(!allToolsSelected)}
aria-pressed={allToolsSelected}
aria-label="Select all tools"
id="tools-group"
>
<SelectBox selected={allToolsSelected} />
<p>Tools</p>
</button>
</div>
</div>
</div>
</div>
)
}
33 changes: 33 additions & 0 deletions apps/react-three-org/src/components/redesign-ui/filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { packages, tools } from '@/lib/packages'
import { Package } from '@/lib/packages'
import { SelectBox } from './select-box'

//TODO

export const Filter = ({
handleSelectAll,
selectedPackages,
visibleTypes,
}: {
handleSelectAll: () => void
selectedPackages: Package[]
visibleTypes: { packages: boolean; tools: boolean }
}) => {
const allVisible = [...(visibleTypes.packages ? packages : []), ...(visibleTypes.tools ? tools : [])]
const allSelected = allVisible.length > 0 && selectedPackages.length === allVisible.length

return (
<div className="col-span-full">
<div className="w-full items-center justify-between py-4 border-y border-redesign-gray px-4 xl:px-6 flex-wrap hidden md:flex">
<p>SELECT WHAT POWERS YOUR PROJECT.</p>
<div className="flex items-center gap-10">
<p>A - Z</p>
<div className="flex items-center gap-4">
<SelectBox selected={allSelected} />
<p>SELECT ALL</p>
</div>
</div>
</div>
</div>
)
}
Loading