Added react-router

This commit is contained in:
Gregory Schier
2023-02-25 18:04:14 -08:00
parent 93105a3e89
commit 83bb18df03
12 changed files with 180 additions and 67 deletions

View File

@@ -9,7 +9,8 @@ import { Sidebar } from './components/Sidebar';
import { UrlBar } from './components/UrlBar';
import { Grid } from './components/Grid';
import { motion } from 'framer-motion';
import { useRequests, useWorkspace, useWorkspaces } from './hooks/useWorkspaces';
import { useRequests } from './hooks/useWorkspaces';
import { useParams } from 'react-router-dom';
interface Response {
url: string;
@@ -21,17 +22,26 @@ interface Response {
headers: Record<string, string>;
}
type Params = {
workspaceId: string;
requestId?: string;
};
function App() {
const { data } = useWorkspace();
console.log('DATA', data);
const p = useParams<Params>();
const workspaceId = p.workspaceId ?? '';
const requestId = p.requestId;
const { data: requests } = useRequests(workspaceId);
const request = requests?.find((r) => r.id === requestId);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const [response, setResponse] = useState<Response | null>(null);
const [url, setUrl] = useState<string>('https://go-server.schier.dev/debug');
const [body, setBody] = useState<string>(`{\n "foo": "bar"\n}`);
const [url, setUrl] = useState<string>(request?.url ?? '');
const [body, setBody] = useState<string>(request?.body ?? '');
const [method, setMethod] = useState<{ label: string; value: string }>({
label: 'GET',
value: 'GET',
label: request?.method ?? 'GET',
value: request?.method ?? 'GET',
});
useEffect(() => {
@@ -44,9 +54,6 @@ function App() {
return () => document.documentElement.removeEventListener('keypress', listener);
}, []);
if (!data) return null;
const { requests, workspace } = data;
async function sendRequest() {
setLoading(true);
setError(null);
@@ -73,7 +80,7 @@ function App() {
return (
<>
<div className="grid grid-cols-[auto_1fr] h-full text-gray-900">
<Sidebar requests={requests ?? []} workspaceId={workspace.id} />
<Sidebar requests={requests ?? []} workspaceId={workspaceId} requestId={requestId} />
<Grid cols={2}>
<VStack className="w-full">
<HStack as={WindowDragRegion} items="center" className="pl-3 pr-1.5">

View File

@@ -1,29 +1,29 @@
import { ButtonHTMLAttributes, forwardRef } from 'react';
import { ButtonHTMLAttributes, ComponentPropsWithoutRef, ElementType } from 'react';
import classnames from 'classnames';
import { Icon } from './Icon';
export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
export interface ButtonProps<T extends ElementType>
extends ButtonHTMLAttributes<HTMLButtonElement> {
color?: 'primary' | 'secondary';
size?: 'xs' | 'sm' | 'md';
justify?: 'start' | 'center';
forDropdown?: boolean;
};
as?: T;
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
{
className,
justify = 'center',
children,
size = 'md',
forDropdown,
color,
...props
}: ButtonProps,
ref,
) {
export function Button<T extends ElementType>({
className,
as,
justify = 'center',
children,
size = 'md',
forDropdown,
color,
...props
}: ButtonProps<T> & Omit<ComponentPropsWithoutRef<T>, keyof ButtonProps<T>>) {
const Component = as || 'button';
return (
<button
ref={ref}
<Component
className={classnames(
className,
'rounded-md flex items-center',
@@ -41,6 +41,6 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
>
{children}
{forDropdown && <Icon icon="triangle-down" className="ml-1 -mr-1" />}
</button>
</Component>
);
});
}

View File

@@ -1,16 +1,9 @@
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import { DropdownMenuRadioGroup } from '@radix-ui/react-dropdown-menu';
import { motion } from 'framer-motion';
import {
CheckIcon,
ChevronRightIcon,
DotFilledIcon,
HamburgerMenuIcon,
} from '@radix-ui/react-icons';
import { forwardRef, HTMLAttributes, ReactNode, useState } from 'react';
import { Button } from './Button';
import { CheckIcon } from '@radix-ui/react-icons';
import { forwardRef, HTMLAttributes, ReactNode } from 'react';
import classnames from 'classnames';
import { HotKey } from './HotKey';
interface DropdownMenuRadioProps {
children: ReactNode;
@@ -232,13 +225,19 @@ function DropdownMenuSeparator({ className, ...props }: DropdownMenu.DropdownMen
);
}
function DropdownMenuTrigger({ className, ...props }: DropdownMenu.DropdownMenuTriggerProps) {
function DropdownMenuTrigger({
children,
className,
...props
}: DropdownMenu.DropdownMenuTriggerProps) {
return (
<DropdownMenu.Trigger
asChild
className={classnames(className, 'focus:outline-none')}
{...props}
/>
>
<>{children}</>
</DropdownMenu.Trigger>
);
}

View File

@@ -1,16 +1,12 @@
import { forwardRef } from 'react';
import { Icon, IconProps } from './Icon';
import { Button, ButtonProps } from './Button';
type Props = Omit<IconProps, 'size'> & ButtonProps;
type Props = Omit<IconProps, 'size'> & ButtonProps<typeof Button>;
export const IconButton = forwardRef<HTMLButtonElement, Props>(function IconButton(
{ icon, spin, ...props }: Props,
ref,
) {
export function IconButton({ icon, spin, ...props }: Props) {
return (
<Button ref={ref} className="group" {...props}>
<Button className="group" {...props}>
<Icon icon={icon} spin={spin} className="text-gray-700 group-hover:text-gray-900" />
</Button>
);
});
}

View File

@@ -3,17 +3,19 @@ import classnames from 'classnames';
import { IconButton } from './IconButton';
import { Button } from './Button';
import useTheme from '../hooks/useTheme';
import { HStack } from './Stacks';
import { HStack, VStack } from './Stacks';
import { WindowDragRegion } from './WindowDragRegion';
import { Request } from '../hooks/useWorkspaces';
import { invoke } from '@tauri-apps/api';
import { Link } from 'react-router-dom';
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
workspaceId: string;
requests: Request[];
requestId?: string;
}
export function Sidebar({ className, workspaceId, requests, ...props }: Props) {
export function Sidebar({ className, requestId, workspaceId, requests, ...props }: Props) {
const { toggleTheme } = useTheme();
return (
<div
@@ -35,11 +37,13 @@ export function Sidebar({ className, workspaceId, requests, ...props }: Props) {
}}
/>
</HStack>
<ul className="mx-2 py-2">
<VStack as="ul" className="py-2" space={1}>
{requests.map((r) => (
<li key={r.id}>
<li key={r.id} className="mx-2">
<Button
className={classnames('w-full', false && 'bg-gray-50')}
as={Link}
to={`/workspaces/${workspaceId}/requests/${r.id}`}
className={classnames('w-full', requestId === r.id && 'bg-gray-50')}
size="sm"
justify="start"
>
@@ -47,7 +51,7 @@ export function Sidebar({ className, workspaceId, requests, ...props }: Props) {
</Button>
</li>
))}
</ul>
</VStack>
</div>
);
}

View File

@@ -27,10 +27,9 @@ export function useWorkspaces(): UseQueryResult<Workspace[]> {
});
}
export function useRequests(): UseQueryResult<Request[]> {
export function useRequests(workspaceId: string): UseQueryResult<Request[]> {
return useQuery('requests', async () => {
const workspaces = (await invoke('workspaces')) as Workspace[];
const requests = (await invoke('requests', { workspaceId: workspaces[0].id })) as Request[];
const requests = (await invoke('requests', { workspaceId })) as Request[];
return requests.map(convertDates);
});
}

View File

@@ -1,29 +1,51 @@
import React from 'react';
import init, { greet } from 'hello';
import ReactDOM from 'react-dom/client';
import App from './App';
import { HelmetProvider } from 'react-helmet-async';
import { MotionConfig } from 'framer-motion';
import init, { greet } from 'hello';
import { invoke } from '@tauri-apps/api';
import { setTheme } from './lib/theme';
import './main.css';
import { QueryClient, QueryClientProvider } from 'react-query';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { Layout } from './pages/Layout';
import { Workspaces } from './pages/Workspaces';
import './main.css';
setTheme();
await init();
greet();
await invoke('load_db');
const queryClient = new QueryClient();
const router = createBrowserRouter([
{
path: '/',
element: <Layout />,
children: [
{
path: '/',
element: <Workspaces />,
},
{
path: '/workspaces/:workspaceId',
element: <App />,
},
{
path: '/workspaces/:workspaceId/requests/:requestId',
element: <App />,
},
],
},
]);
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<MotionConfig transition={{ duration: 0.15 }}>
<HelmetProvider>
<App />
<RouterProvider router={router} />
</HelmetProvider>
</MotionConfig>
</QueryClientProvider>

9
src-web/pages/Layout.tsx Normal file
View File

@@ -0,0 +1,9 @@
import { Outlet } from 'react-router-dom';
export function Layout() {
return (
<div className="w-full h-full">
<Outlet />
</div>
);
}

View File

@@ -0,0 +1,15 @@
import { Link, useParams } from 'react-router-dom';
import { useWorkspaces } from '../hooks/useWorkspaces';
export function Workspaces() {
const workspaces = useWorkspaces();
return (
<ul className="p-12">
{workspaces.data?.map((w) => (
<Link key={w.id} to={`/workspaces/${w.id}`}>
{w.name}
</Link>
))}
</ul>
);
}