mirror of
https://github.com/stefanpejcic/openpanel
synced 2025-06-26 18:28:26 +00:00
fork refine
This commit is contained in:
66
packages/devtools-ui/src/pages/after-login.tsx
Normal file
66
packages/devtools-ui/src/pages/after-login.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
DevToolsContext,
|
||||
DevtoolsEvent,
|
||||
send,
|
||||
} from "@refinedev/devtools-shared";
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
import { useSearchParams, Navigate } from "react-router-dom";
|
||||
import { Authenticated } from "src/components/authenticated";
|
||||
|
||||
export const AfterLogin = () => {
|
||||
return (
|
||||
<Authenticated fallback={<Failure />}>
|
||||
<Success />
|
||||
</Authenticated>
|
||||
);
|
||||
};
|
||||
|
||||
const Failure = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const errorParam = searchParams.get("error");
|
||||
|
||||
return (
|
||||
<Navigate to={`/login${errorParam ? `?error=${errorParam}` : ""}`} />
|
||||
);
|
||||
};
|
||||
|
||||
const Success = () => {
|
||||
const { ws } = React.useContext(DevToolsContext);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (ws) {
|
||||
send(ws, DevtoolsEvent.DEVTOOLS_LOGIN_SUCCESS, {}).then(() => {
|
||||
window.close();
|
||||
});
|
||||
}
|
||||
}, [ws]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"re-flex-1",
|
||||
"re-flex",
|
||||
"re-items-center",
|
||||
"re-justify-center",
|
||||
"re-py-16",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"re-w-full",
|
||||
"re-flex",
|
||||
"re-flex-col",
|
||||
"re-gap-16",
|
||||
"re-justify-center",
|
||||
"re-items-center",
|
||||
"re-text-center",
|
||||
"re-text-gray-300",
|
||||
"re-text-sm",
|
||||
)}
|
||||
>
|
||||
Logged in successfully, you can close this window.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
189
packages/devtools-ui/src/pages/login.tsx
Normal file
189
packages/devtools-ui/src/pages/login.tsx
Normal file
@@ -0,0 +1,189 @@
|
||||
import { LoginFlow } from "@ory/client";
|
||||
import {
|
||||
DevToolsContext,
|
||||
DevtoolsEvent,
|
||||
receive,
|
||||
} from "@refinedev/devtools-shared";
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
|
||||
import { FeatureSlide, FeatureSlideMobile } from "src/components/feature-slide";
|
||||
import { GithubIcon } from "src/components/icons/github";
|
||||
import { GoogleIcon } from "src/components/icons/google";
|
||||
import { LogoIcon } from "src/components/icons/logo";
|
||||
import { ory } from "src/utils/ory";
|
||||
|
||||
export const Login = () => {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"re-min-h-screen re-w-full",
|
||||
"re-grid re-grid-cols-1 large:re-grid-cols-2 re-gap-4",
|
||||
"re-p-4",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"re-flex",
|
||||
"re-justify-center",
|
||||
"re-items-center",
|
||||
"re-rounded-lg",
|
||||
"re-bg-gray-800",
|
||||
"re-hidden large:re-flex",
|
||||
)}
|
||||
>
|
||||
<FeatureSlide className={clsx("re-w-full", "re-max-w-3xl")} />
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
"re-flex",
|
||||
"re-flex-col",
|
||||
"re-items-center",
|
||||
"re-justify-center",
|
||||
)}
|
||||
>
|
||||
<LoginForm className={clsx("re-pt-2 tall:re-pt-0")} />
|
||||
<FeatureSlideMobile
|
||||
className={clsx("re-flex large:re-hidden", "re-mt-12")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const providers = [
|
||||
{
|
||||
name: "github",
|
||||
icon: <GithubIcon className="re-w-6 re-h-6" />,
|
||||
label: "Sign in with GitHub",
|
||||
callback: "",
|
||||
},
|
||||
{
|
||||
name: "google",
|
||||
icon: <GoogleIcon className="re-w-6 re-h-6" />,
|
||||
label: "Sign in with Google",
|
||||
callback: "",
|
||||
},
|
||||
];
|
||||
|
||||
const LoginForm = (props: { className?: string }) => {
|
||||
const { ws } = React.useContext(DevToolsContext);
|
||||
const [searchParams] = useSearchParams();
|
||||
const [flowData, setFlowData] = React.useState<LoginFlow | null>(null);
|
||||
|
||||
const error = searchParams.get("error");
|
||||
|
||||
const generateAuthFlow = React.useCallback(async () => {
|
||||
try {
|
||||
const redirectUrl = `${window.location.origin}/after-login`;
|
||||
|
||||
const { data } = await ory.createNativeLoginFlow({
|
||||
refresh: true,
|
||||
returnTo: redirectUrl,
|
||||
});
|
||||
|
||||
setFlowData(data);
|
||||
} catch (_error) {
|
||||
console.error(_error);
|
||||
}
|
||||
}, [typeof window]);
|
||||
|
||||
React.useEffect(() => {
|
||||
generateAuthFlow();
|
||||
}, [generateAuthFlow]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (ws) {
|
||||
const unsub = receive(
|
||||
ws,
|
||||
DevtoolsEvent.DEVTOOLS_RELOAD_AFTER_LOGIN,
|
||||
() => {
|
||||
window.location.reload();
|
||||
},
|
||||
);
|
||||
return unsub;
|
||||
}
|
||||
return () => 0;
|
||||
}, [ws]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"re-max-w-[336px]",
|
||||
"re-w-full",
|
||||
"re-flex",
|
||||
"re-flex-col",
|
||||
"re-gap-16",
|
||||
"re-justify-center",
|
||||
"re-items-center",
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
<LogoIcon height={60} width={252} />
|
||||
<form
|
||||
className={clsx(
|
||||
"re-w-full",
|
||||
"re-flex",
|
||||
"re-flex-col",
|
||||
"re-items-center",
|
||||
"re-justify-center",
|
||||
"re-gap-4",
|
||||
)}
|
||||
action={flowData?.ui?.action}
|
||||
method={flowData?.ui?.method}
|
||||
target="_blank"
|
||||
>
|
||||
<input
|
||||
type="hidden"
|
||||
name="csrf_token"
|
||||
value={(flowData?.ui.nodes[2].attributes as any)?.value}
|
||||
/>
|
||||
{providers.map(({ name, icon, label }) => (
|
||||
<button
|
||||
key={name}
|
||||
id={name}
|
||||
name="provider"
|
||||
type="submit"
|
||||
value={name}
|
||||
disabled={!flowData}
|
||||
className={clsx(
|
||||
"re-w-full",
|
||||
"re-py-2.5",
|
||||
"re-px-4",
|
||||
"re-bg-gray-0",
|
||||
"re-text-gray-900",
|
||||
"re-text-base",
|
||||
"re-leading-6",
|
||||
"re-font-semibold",
|
||||
"re-gap-4",
|
||||
"re-flex",
|
||||
"re-items-center",
|
||||
"re-justify-center",
|
||||
"re-rounded",
|
||||
)}
|
||||
>
|
||||
{icon}
|
||||
<span>{label}</span>
|
||||
</button>
|
||||
))}
|
||||
{error && (
|
||||
<div
|
||||
className={clsx(
|
||||
"re-bg-alt-yellow",
|
||||
"re-bg-opacity-20",
|
||||
"re-border-2",
|
||||
"re-border-alt-yellow",
|
||||
"re-text-alt-yellow",
|
||||
"re-text-xs",
|
||||
"re-p-3",
|
||||
"re-rounded",
|
||||
)}
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
323
packages/devtools-ui/src/pages/monitor.tsx
Normal file
323
packages/devtools-ui/src/pages/monitor.tsx
Normal file
@@ -0,0 +1,323 @@
|
||||
import React from "react";
|
||||
import clsx from "clsx";
|
||||
import {
|
||||
DevToolsContext,
|
||||
DevtoolsEvent,
|
||||
receive,
|
||||
hooksByScope,
|
||||
RefineHook,
|
||||
Scopes,
|
||||
scopes,
|
||||
} from "@refinedev/devtools-shared";
|
||||
import {
|
||||
Cell,
|
||||
ColumnDef,
|
||||
SortingState,
|
||||
getCoreRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { ResizablePane } from "src/components/resizable-pane";
|
||||
import type { Activity } from "src/interfaces/activity";
|
||||
import { Status } from "src/components/status";
|
||||
import { MonitorDetails } from "src/components/monitor-details";
|
||||
import { TraceList } from "src/components/trace-list";
|
||||
import { MonitorTable } from "src/components/monitor-table";
|
||||
import { Filters, MonitorFilters } from "src/components/monitor-filters";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { getResourceValue } from "src/utils/get-resource-value";
|
||||
import { ResourceValue } from "src/components/resource-value";
|
||||
import { useLocalStorage } from "src/hooks/use-local-storage";
|
||||
|
||||
export const Monitor = () => {
|
||||
const { ws } = React.useContext(DevToolsContext);
|
||||
const [activities, setActivities] = React.useState<Activity[]>([]);
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const highlightParam = searchParams.get("highlight");
|
||||
|
||||
const fetchActivities = React.useCallback(() => {
|
||||
fetch("/api/activities")
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
setActivities(data.data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchActivities();
|
||||
}, [fetchActivities]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!ws) return () => 0;
|
||||
|
||||
const unsub = receive(
|
||||
ws,
|
||||
DevtoolsEvent.DEVTOOLS_ACTIVITY_UPDATE,
|
||||
(payload) => {
|
||||
payload.updatedActivities.forEach((activity) => {
|
||||
setActivities((activities) => {
|
||||
const index = activities.findIndex(
|
||||
(a) => a.identifier === activity.identifier,
|
||||
);
|
||||
|
||||
if (index === -1) {
|
||||
return [...activities, activity];
|
||||
}
|
||||
|
||||
return [
|
||||
...activities.slice(0, index),
|
||||
activity,
|
||||
...activities.slice(index + 1),
|
||||
];
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
return unsub;
|
||||
}, [ws]);
|
||||
|
||||
const columns = React.useMemo(
|
||||
() =>
|
||||
[
|
||||
{
|
||||
header: "Hook",
|
||||
minSize: 90,
|
||||
accessorFn: (activity) => activity.hookName,
|
||||
},
|
||||
{
|
||||
header: "Resource",
|
||||
minSize: 100,
|
||||
accessorFn: getResourceValue,
|
||||
cell: (cell: Cell<Activity, string>) => {
|
||||
return (
|
||||
<ResourceValue
|
||||
resource={cell.getValue() as string}
|
||||
scope={
|
||||
scopes[
|
||||
cell.row.original.hookName as RefineHook
|
||||
]
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Status",
|
||||
minSize: 90,
|
||||
accessorFn: (activity) => activity.status,
|
||||
cell: (cell: Cell<Activity, Activity["status"]>) => (
|
||||
<Status activity={cell.row.original} />
|
||||
),
|
||||
},
|
||||
{
|
||||
header: "Trace",
|
||||
minSize: 280,
|
||||
accessorFn: (activity) =>
|
||||
[
|
||||
...(activity.trace?.map((t) => t.function) ?? []),
|
||||
].reverse(),
|
||||
cell: (cell: Cell<Activity, string[] | undefined>) => {
|
||||
return <TraceList trace={cell.row.original.trace} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Created At",
|
||||
minSize: 100,
|
||||
accessorKey: "createdAt",
|
||||
cell: (cell: Cell<Activity, Activity["createdAt"]>) => {
|
||||
return dayjs(cell.getValue()).format("HH:mm:ss");
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Updated At",
|
||||
minSize: 100,
|
||||
accessorKey: "updatedAt",
|
||||
cell: (cell: Cell<Activity, Activity["updatedAt"]>) => {
|
||||
return dayjs(cell.getValue()).format("HH:mm:ss");
|
||||
},
|
||||
},
|
||||
] as ColumnDef<Activity>[],
|
||||
[],
|
||||
);
|
||||
|
||||
const [filters, setFilters] = useLocalStorage<Filters>({
|
||||
name: "monitor-filters",
|
||||
defaultValue: {
|
||||
hook: [],
|
||||
parent: [],
|
||||
resource: undefined,
|
||||
scope: ["data"],
|
||||
status: [],
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
if (highlightParam) {
|
||||
setFilters({
|
||||
hook: [],
|
||||
resource: undefined,
|
||||
scope: [],
|
||||
status: [],
|
||||
parent: [highlightParam],
|
||||
});
|
||||
}
|
||||
}, [highlightParam]);
|
||||
|
||||
const data = React.useMemo(() => {
|
||||
let filtered = [...activities];
|
||||
|
||||
if (filters.scope && filters.scope.length > 0) {
|
||||
const allowedHooks: RefineHook[] = [];
|
||||
filters.scope.forEach((scope) => {
|
||||
allowedHooks.push(...hooksByScope[scope as Scopes]);
|
||||
});
|
||||
|
||||
filtered = filtered.filter((activity) =>
|
||||
allowedHooks.includes(activity.hookName as RefineHook),
|
||||
);
|
||||
}
|
||||
if (filters.hook && filters.hook.length > 0) {
|
||||
filtered = filtered.filter((activity) =>
|
||||
filters.hook.includes(activity.hookName),
|
||||
);
|
||||
}
|
||||
if (filters.parent && filters.parent.length > 0) {
|
||||
filtered = filtered.filter((activity) => {
|
||||
return activity.trace?.some((trace) => {
|
||||
if (!trace.function) return false;
|
||||
return filters.parent.includes(trace.function);
|
||||
});
|
||||
});
|
||||
}
|
||||
if (filters.resource) {
|
||||
filtered = filtered.filter((activity) => {
|
||||
const resource = getResourceValue(activity);
|
||||
resource.includes(filters.resource as string);
|
||||
});
|
||||
}
|
||||
|
||||
if (filters.status && filters.status.length > 0) {
|
||||
filtered = filtered.filter((activity) =>
|
||||
filters.status.includes(activity.status as any),
|
||||
);
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}, [activities, filters]);
|
||||
|
||||
const [sorting, setSorting] = React.useState<SortingState>([
|
||||
{
|
||||
id: "createdAt",
|
||||
desc: true,
|
||||
},
|
||||
]);
|
||||
|
||||
const tableInstance = useReactTable({
|
||||
columns,
|
||||
data,
|
||||
state: {
|
||||
sorting,
|
||||
},
|
||||
onSortingChange: setSorting,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
});
|
||||
|
||||
const [selectedActivity, setSelectedActivity] = React.useState<
|
||||
string | null
|
||||
>(null);
|
||||
|
||||
const selectedActivityRecord = React.useMemo(
|
||||
() => activities.find((el) => el.identifier === selectedActivity),
|
||||
[activities, selectedActivity],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={clsx("flex-1", "re-h-full", "re-overflow-clip")}>
|
||||
<div
|
||||
className={clsx(
|
||||
"re-flex",
|
||||
"re-flex-col",
|
||||
"re-gap-4",
|
||||
"re-h-full",
|
||||
"re-pb-4",
|
||||
"md:re-pb-6",
|
||||
"lg:re-pb-8",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"re-flex",
|
||||
"re-items-center",
|
||||
"re-justify-between",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"re-flex",
|
||||
"re-items-center",
|
||||
"re-justify-start",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"re-text-gray-0",
|
||||
"re-font-semibold",
|
||||
"re-text-sm",
|
||||
"re-leading-6",
|
||||
)}
|
||||
>
|
||||
Monitor
|
||||
</div>
|
||||
<MonitorFilters
|
||||
filters={filters}
|
||||
activities={activities}
|
||||
onSubmit={(f) => setFilters(f)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className={clsx("re-text-xs", "re-text-gray-300")}>
|
||||
Count: {activities.length}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={clsx("re-flex-1", "re-overflow-hidden")}>
|
||||
<ResizablePane
|
||||
left={
|
||||
<MonitorTable
|
||||
table={tableInstance}
|
||||
columns={columns}
|
||||
onSelect={setSelectedActivity}
|
||||
selected={selectedActivity}
|
||||
/>
|
||||
}
|
||||
right={
|
||||
<MonitorDetails activity={selectedActivityRecord} />
|
||||
}
|
||||
className={clsx("re-h-full")}
|
||||
leftClassName={clsx(
|
||||
"re-rounded-lg",
|
||||
"re-border",
|
||||
"re-border-gray-700",
|
||||
"re-overflow-auto",
|
||||
"re-flex",
|
||||
"re-flex-col",
|
||||
)}
|
||||
rightClassName={clsx(
|
||||
"re-rounded-lg",
|
||||
"re-border",
|
||||
"re-border-gray-700",
|
||||
"re-overflow-auto",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
189
packages/devtools-ui/src/pages/onboarding.tsx
Normal file
189
packages/devtools-ui/src/pages/onboarding.tsx
Normal file
@@ -0,0 +1,189 @@
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Button } from "src/components/button";
|
||||
import { LogoIcon } from "src/components/icons/logo";
|
||||
import { Input } from "src/components/input";
|
||||
import { FeatureSlide, FeatureSlideMobile } from "src/components/feature-slide";
|
||||
import { MeUpdateVariables } from "src/interfaces/api";
|
||||
import { getMe, updateMe } from "src/utils/me";
|
||||
|
||||
export const Onboarding = () => {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"re-min-h-screen re-w-full",
|
||||
"re-grid re-grid-cols-1 large:re-grid-cols-2 re-gap-4",
|
||||
"re-p-4",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"re-flex",
|
||||
"re-justify-center",
|
||||
"re-items-center",
|
||||
"re-rounded-lg",
|
||||
"re-bg-gray-800",
|
||||
"re-hidden large:re-flex",
|
||||
)}
|
||||
>
|
||||
<FeatureSlide className={clsx("re-w-full", "re-max-w-3xl")} />
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
"re-flex",
|
||||
"re-flex-col",
|
||||
"re-items-center",
|
||||
"re-justify-center",
|
||||
)}
|
||||
>
|
||||
<OnboardingForm />
|
||||
<FeatureSlideMobile
|
||||
className={clsx("re-flex large:re-hidden", "re-mt-12")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const inputs = [
|
||||
{
|
||||
name: "name",
|
||||
label: "Your name",
|
||||
required: true,
|
||||
placeholder: "Enter your name",
|
||||
},
|
||||
{
|
||||
name: "jobTitle",
|
||||
label: "Job title",
|
||||
required: true,
|
||||
placeholder: "Please enter your job title",
|
||||
},
|
||||
{
|
||||
name: "company",
|
||||
label: "Company name",
|
||||
required: true,
|
||||
placeholder: "Enter your company name",
|
||||
},
|
||||
] as const;
|
||||
|
||||
const links = [
|
||||
{
|
||||
name: "Privacy Policy",
|
||||
url: "https://refine.dev/privacy-policy/",
|
||||
},
|
||||
{
|
||||
name: "Terms and conditions",
|
||||
url: "https://github.com/refinedev/refine/blob/master/LICENSE",
|
||||
},
|
||||
];
|
||||
|
||||
const OnboardingForm = () => {
|
||||
const [values, setValues] = React.useState<MeUpdateVariables>({
|
||||
name: "",
|
||||
jobTitle: "",
|
||||
company: "",
|
||||
});
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const fetchMe = React.useCallback(() => {
|
||||
return getMe().then((me) => {
|
||||
if (me && typeof me.name === "string") {
|
||||
setValues((p) => ({
|
||||
...p,
|
||||
name: me.name as string,
|
||||
}));
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchMe();
|
||||
}, [fetchMe]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"re-max-w-[336px]",
|
||||
"re-w-full",
|
||||
"re-flex",
|
||||
"re-flex-col",
|
||||
"re-gap-16",
|
||||
"re-justify-center",
|
||||
"re-items-center",
|
||||
)}
|
||||
>
|
||||
<LogoIcon height={60} width={252} />
|
||||
<div
|
||||
className={clsx(
|
||||
"re-flex",
|
||||
"re-flex-col",
|
||||
"re-items-center",
|
||||
"re-justify-center",
|
||||
"re-gap-6",
|
||||
"re-w-full",
|
||||
)}
|
||||
>
|
||||
{inputs.map(({ name, label, required, placeholder }) => (
|
||||
<Input
|
||||
key={name}
|
||||
label={label}
|
||||
required={required}
|
||||
placeholder={placeholder}
|
||||
value={values[name]}
|
||||
onChange={(value) =>
|
||||
setValues((prev) => ({
|
||||
...prev,
|
||||
[name]: value,
|
||||
}))
|
||||
}
|
||||
className="re-w-full"
|
||||
/>
|
||||
))}
|
||||
<div
|
||||
className={clsx(
|
||||
"re-w-full",
|
||||
"re-flex",
|
||||
"re-items-center",
|
||||
"re-justify-end",
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
onClick={() => {
|
||||
updateMe(values).then(() => {
|
||||
navigate("/overview");
|
||||
});
|
||||
}}
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
"re-flex",
|
||||
"re-items-center",
|
||||
"re-justify-between",
|
||||
"re-w-full",
|
||||
)}
|
||||
>
|
||||
{links.map((link) => (
|
||||
<a
|
||||
key={link.name}
|
||||
href={link.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={clsx(
|
||||
"re-text-gray-500",
|
||||
"re-underline",
|
||||
"re-text-xs",
|
||||
)}
|
||||
>
|
||||
{link.name}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
51
packages/devtools-ui/src/pages/overview.tsx
Normal file
51
packages/devtools-ui/src/pages/overview.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import React from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
import { Feed } from "src/components/feed";
|
||||
import { Packages } from "src/components/packages";
|
||||
|
||||
export const Overview = () => {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"flex-1",
|
||||
"re-h-full",
|
||||
"re-overflow-auto",
|
||||
"large:re-overflow-clip",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"re-h-auto",
|
||||
"large:re-h-full",
|
||||
"re-flex",
|
||||
"re-flex-col",
|
||||
"large:re-flex-row",
|
||||
"re-gap-8",
|
||||
"re-w-full",
|
||||
"re-max-w-full",
|
||||
"re-overflow-hidden",
|
||||
"re-px-10",
|
||||
"large:re-px-0",
|
||||
"re-max-w-[640px]",
|
||||
"large:re-max-w-none",
|
||||
"re-mx-auto",
|
||||
"large:re-mx-0",
|
||||
)}
|
||||
>
|
||||
<Feed />
|
||||
<div
|
||||
className={clsx(
|
||||
"re-hidden",
|
||||
"large:re-block",
|
||||
"re-h-auto",
|
||||
"re-w-px",
|
||||
"re-bg-gray-700",
|
||||
"re-flex-shrink-0",
|
||||
)}
|
||||
/>
|
||||
<Packages />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user