Skip to content
Merged
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
126 changes: 126 additions & 0 deletions ui/src/components/WorkosOrgSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import * as React from 'react'
import { useRouter } from '@tanstack/react-router'
import { getWidgetsAuthToken } from '@/authkit/serverFunctions'
import { useToast } from '@/hooks/use-toast'
import { OrganizationSwitcher, WorkOsWidgets } from '@workos-inc/widgets'
import { DropdownMenu } from '@radix-ui/themes'

import '@workos-inc/widgets/styles.css'
import '@radix-ui/themes/styles.css'

type WorkosOrgSwitcherProps = {
userId: string
organisationId: string
label?: string
redirectTo?: string
/**
* If true, wraps the switcher in WorkOsWidgets provider which applies a full-page layout.
* Leave false for compact embedding in headers/navs to avoid large whitespace.
*/
wrapWithProvider?: boolean
/**
* When true, injects a default extra group with a Settings item in the switcher dropdown.
*/
showSettingsItem?: boolean
}

export default function WorkosOrgSwitcher({
userId,
organisationId,
label = 'My Orgs',
redirectTo = '/dashboard/units',
wrapWithProvider = false,
showSettingsItem = false,
}: WorkosOrgSwitcherProps) {
const router = useRouter()
const { toast } = useToast()
const [authToken, setAuthToken] = React.useState<string | null>(null)
const [error, setError] = React.useState<string | null>(null)
const [loading, setLoading] = React.useState(true)

const handleSwitchToOrganization = async (organizationId: string) => {
try {
const res = await fetch('/api/auth/workos/switch-org', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ organizationId, pathname: redirectTo }),
})
const data = await res.json()
if (!data?.redirectUrl) return
const url: string = data.redirectUrl
const isInternal = url.startsWith('/')
if (isInternal) {
await router.navigate({ to: url })
router.invalidate()
} else {
throw new Error('Cannot redirect to external URL')
}
} catch (e: any) {
toast({
title: 'Failed to switch organization',
description: e?.message ?? 'Failed to switch organization',
variant: 'destructive',
})
console.error('Failed to switch organization', e)
}
}

React.useEffect(() => {
(async () => {
try {
const token = await getWidgetsAuthToken({ data: { userId, organizationId: organisationId } })
setAuthToken(token)
setLoading(false)
} catch (e: any) {
setError(e?.message ?? 'Failed to get WorkOS token')
setLoading(false)
}
})()
}, [userId, organisationId])

if (loading) return <p>Loading WorkOS…</p>
if (error) return <p className="text-red-600">Error: {error}</p>
if (!authToken) return <p>Could not load WorkOS token.</p>

const extraMenu = showSettingsItem ? (
<>
<DropdownMenu.Separator />
<DropdownMenu.Group>
<DropdownMenu.Item onClick={() => router.navigate({ to: '/dashboard/settings/user' })}>
Settings
</DropdownMenu.Item>
</DropdownMenu.Group>
</>
) : null

if (wrapWithProvider) {
return (
<WorkOsWidgets
// Reset WorkOS full-page layout styles so it fits inside the sidebar
style={{ minHeight: 'auto', height: 'auto', padding: 0, display: 'contents' } as any}
>
<div className="w-full">
<OrganizationSwitcher
authToken={authToken}
organizationLabel={label}
switchToOrganization={({ organizationId }) => handleSwitchToOrganization(organizationId)}
>
{extraMenu}
</OrganizationSwitcher>
</div>
</WorkOsWidgets>
)
}

return (
<OrganizationSwitcher
authToken={authToken}
organizationLabel={label}
switchToOrganization={({ organizationId }) => handleSwitchToOrganization(organizationId)}
>
{extraMenu}
</OrganizationSwitcher>
)
}


1 change: 1 addition & 0 deletions ui/src/components/WorkosSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import '@workos-inc/widgets/styles.css';
import '@radix-ui/themes/styles.css';
import CreateOrganizationBtn from './CreateOrganisationButtonWOS';
import WorkosOrgSwitcher from './WorkosOrgSwitcher';


type LoaderData = {
Expand Down
3 changes: 3 additions & 0 deletions ui/src/lib/env.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import { createServerFn } from "@tanstack/react-start"


// !IMPORTANT: DO NOT ADD ANYTHING SENSITIVE HERE. THIS IS USED ON THE CLIENT SIDE.
export type Env = {
PUBLIC_URL: string
PUBLIC_HOSTNAME: string
STATESMAN_BACKEND_URL: string
WORKOS_REDIRECT_URI: string
}

export const getPublicServerConfig = createServerFn({ method: 'GET' })
Expand All @@ -16,5 +18,6 @@ export const getPublicServerConfig = createServerFn({ method: 'GET' })
PUBLIC_URL: process.env.PUBLIC_URL ?? '',
PUBLIC_HOSTNAME: process.env.PUBLIC_URL?.replace('https://', '').replace('http://', '') ?? '',
STATESMAN_BACKEND_URL: process.env.STATESMAN_BACKEND_URL ?? '',
WORKOS_REDIRECT_URI: process.env.WORKOS_REDIRECT_URI ?? '',
} as Env
})
22 changes: 17 additions & 5 deletions ui/src/routes/_authenticated/_dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,45 @@ import { getSignInUrl } from '../../authkit/serverFunctions';
import { SidebarProvider, Sidebar, SidebarHeader, SidebarContent, SidebarGroup, SidebarGroupLabel, SidebarGroupContent, SidebarMenu, SidebarMenuItem, SidebarMenuButton, SidebarTrigger } from '@/components/ui/sidebar';
import { Link } from '@tanstack/react-router';
import { GitBranch, Folders, Waves, Settings, CreditCard, LogOut, Cuboid} from 'lucide-react';
import WorkosOrgSwitcher from '@/components/WorkosOrgSwitcher';
import { WorkOsWidgets } from '@workos-inc/widgets';

export const Route = createFileRoute('/_authenticated/_dashboard')({
component: DashboardComponent,
loader: async ({ context }) => {
const { user, organisationName } = context;
return { user, organisationName };
const { user, organisationName, organisationId, publicServerConfig } = context;
return { user, organisationName, organisationId, publicServerConfig };
},
});

function DashboardComponent() {
const { user, organisationName } = Route.useLoaderData();
const { user, organisationName, organisationId, publicServerConfig } = Route.useLoaderData();
const workosEnabled = publicServerConfig.WORKOS_REDIRECT_URI !== '';
const location = useLocation();
return (
<SidebarProvider>
<WorkOsWidgets
style={{ display: 'contents', minHeight: 'auto', height: 'auto' } as any}
theme={{ panelBackground: 'solid', radius: 'none' } as any}
>
<div className="flex h-screen w-full">
<Sidebar>
<SidebarHeader className="text-center">
<h2 className="text-xl font-bold mb-2">🌮 OpenTACO</h2>
<div className="px-4">
<div className="h-[1px] bg-border mb-2" />
<h3>
{!workosEnabled && <h3>
<Link
to="/dashboard/settings/user"
className="text-sm text-muted-foreground hover:text-primary transition-colors duration-200"
>
{organisationName}
</Link>
</h3>

</h3>}
<div className="mt-2" />
{workosEnabled && <WorkosOrgSwitcher userId={user?.id || ''} organisationId={organisationId || ''} showSettingsItem />}

<div className="h-[1px] bg-border mt-2" />
</div>
</SidebarHeader>
Expand Down Expand Up @@ -106,6 +117,7 @@ function DashboardComponent() {
</div>
</main>
</div>
</WorkOsWidgets>
</SidebarProvider>
)
};
107 changes: 63 additions & 44 deletions ui/src/routes/_authenticated/_dashboard/dashboard/repos.index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,51 +43,70 @@ function RouteComponent() {
<CardDescription>List of repositories Connected to digger and their latest runs</CardDescription>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Type</TableHead>
<TableHead>Name</TableHead>
<TableHead>URL</TableHead>
{/* <TableHead>Latest Run</TableHead> */}
<TableHead>Details</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{repos.map((repo : Repo) => {
const Icon = iconMap[repo.vcs]
return (
<TableRow key={repo.id}>
<TableCell>
<Icon className="h-5 w-5" />
</TableCell>
<TableCell>{repo.name}</TableCell>
<TableCell>
<a
href={repo.repo_url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:underline"
>
{repo.repo_url}
</a>
</TableCell>
{/* <TableCell>{repo.latestRun}</TableCell> */}
<TableCell>
<Button variant="ghost" asChild>
<Link to="/dashboard/repos/$repoId" params={{ repoId: String(repo.id) }}>
View Details <ExternalLink className="ml-2 h-4 w-4" />
</Link>
</Button>
</TableCell>
{repos.length === 0 ? (
<div className="text-center py-12">
<div className="inline-flex h-12 w-12 items-center justify-center rounded-full bg-primary/10 mb-4">
<Github className="h-6 w-6 text-primary" />
</div>
<h2 className="text-lg font-semibold mb-2">No Repositories Connected</h2>
<p className="text-muted-foreground max-w-sm mx-auto mb-6">
Connect your first repository to start running Terraform with Digger.
</p>
<Button asChild>
<Link to="/dashboard/onboarding">
Connect your first repository <PlusCircle className="ml-2 h-4 w-4" />
</Link>
</Button>
</div>
) : (
<>
<Table>
<TableHeader>
<TableRow>
<TableHead>Type</TableHead>
<TableHead>Name</TableHead>
<TableHead>URL</TableHead>
{/* <TableHead>Latest Run</TableHead> */}
<TableHead>Details</TableHead>
</TableRow>
)
})}
</TableBody>
</Table>
<div className="mt-4">
<ConnectMoreRepositoriesButton />
</div>
</TableHeader>
<TableBody>
{repos.map((repo : Repo) => {
const Icon = iconMap[repo.vcs]
return (
<TableRow key={repo.id}>
<TableCell>
<Icon className="h-5 w-5" />
</TableCell>
<TableCell>{repo.name}</TableCell>
<TableCell>
<a
href={repo.repo_url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:underline"
>
{repo.repo_url}
</a>
</TableCell>
{/* <TableCell>{repo.latestRun}</TableCell> */}
<TableCell>
<Button variant="ghost" asChild>
<Link to="/dashboard/repos/$repoId" params={{ repoId: String(repo.id) }}>
View Details <ExternalLink className="ml-2 h-4 w-4" />
</Link>
</Button>
</TableCell>
</TableRow>
)
})}
</TableBody>
</Table>
<div className="mt-4">
<ConnectMoreRepositoriesButton />
</div>
</>
)}
</CardContent>
</Card>
<Outlet />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ function RouteComponent() {
ID: {unit.id}
</CardDescription>
<CardDescription>
Version {unit.version} • Last updated {formatDate(unit.updated)} • {formatBytes(unit.size)}
Last updated {formatDate(unit.updated)} • {formatBytes(unit.size)}
</CardDescription>
</CardHeader>
</Card>
Expand Down
Loading
Loading