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
9 changes: 9 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { SearchResultsPage } from "./pages/SearchResults";
import { ObjectDetailsPage } from "./pages/ObjectDetails";
import { NotFoundPage } from "./pages/NotFound";
import { TableDetailsPage } from "./pages/TableDetails";
import { CrossmatchResultsPage } from "./pages/CrossmatchResults";

function Layout({ children }: { children: React.ReactNode }) {
return (
Expand Down Expand Up @@ -51,6 +52,14 @@ function App() {
</Layout>
}
/>
<Route
path="/crossmatch"
element={
<Layout>
<CrossmatchResultsPage />
</Layout>
}
/>
<Route
path="*"
element={
Expand Down
6 changes: 5 additions & 1 deletion src/assets/texts.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
"title": {
"catalog.designation": "Designation",
"catalog.icrs": "Celestial coordinates",
"catalog.redshift": "Redshift"
"catalog.redshift": "Redshift",
"crossmatch.status.unprocessed": "Unprocessed",
"crossmatch.status.new": "New",
"crossmatch.status.collided": "Collided",
"crossmatch.status.existing": "Existing"
}
}
26 changes: 26 additions & 0 deletions src/components/ui/badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ReactElement } from "react";
import { Link } from "./link";

interface BadgeProps {
children: React.ReactNode;
className?: string;
href?: string;
}

export function Badge({
children,
className = "",
href,
}: BadgeProps): ReactElement {
const badgeClasses = `inline-block bg-gray-600 rounded px-1.5 py-0.5 text-sm mr-0.5 mb-0.5 ${className}`;

if (href) {
return (
<Link href={href}>
<span className={badgeClasses}>{children}</span>
</Link>
);
}

return <div className={badgeClasses}>{children}</div>;
}
10 changes: 7 additions & 3 deletions src/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import classNames from "classnames";

interface ButtonProps {
children: React.ReactNode;
onClick?: () => void;
onClick?: (event: React.MouseEvent) => void;
className?: string;
type?: "button" | "submit" | "reset";
disabled?: boolean;
Expand All @@ -13,10 +13,14 @@ export function Button(props: ButtonProps): ReactElement {
return (
<button
type={props.type}
onClick={props.onClick}
onClick={(event) => props.onClick?.(event)}
disabled={props.disabled}
className={classNames(
"px-2 py-2 box-border flex items-center font-semibold border-1 border-[#1a1a1a] rounded-lg bg-[#1a1a1a] hover:border-[#646cff] transition-colors duration-300 active:border-white",
"px-2 py-2 box-border flex items-center font-semibold border-1 border-[#1a1a1a] rounded-lg bg-[#1a1a1a] hover:border-[#646cff] transition-colors duration-300 active:border-white cursor-pointer",
{
"opacity-50 cursor-not-allowed hover:border-[#1a1a1a] active:border-[#1a1a1a]":
props.disabled,
},
props.className,
)}
>
Expand Down
40 changes: 40 additions & 0 deletions src/components/ui/dropdown-filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ReactElement } from "react";

interface DropdownFilterOption {
value: string;
label?: string;
}

interface DropdownFilterProps {
title: string;
options: DropdownFilterOption[];
defaultValue: string;
value: string;
onChange: (value: string) => void;
}

export function DropdownFilter({
title,
options,
value,
onChange,
}: DropdownFilterProps): ReactElement {
return (
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">
{title}
</label>
<select
value={value}
onChange={(e) => onChange(e.target.value)}
className="bg-gray-700 border border-gray-600 rounded px-3 py-2 text-white h-10 w-full"
>
{options.map((option) => (
<option key={option.value} value={option.value}>
{option.label || option.value}
</option>
))}
</select>
</div>
);
}
60 changes: 60 additions & 0 deletions src/components/ui/error-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { ReactElement, ReactNode } from "react";
import { Button } from "./button";

interface ErrorPageProps {
title: string;
message: string;
children?: ReactNode;
className?: string;
showLargeText?: boolean;
}

export function ErrorPage({
title,
message,
children,
className = "",
showLargeText = false,
}: ErrorPageProps): ReactElement {
return (
<div
className={`min-h-screen flex items-center justify-center ${className}`}
>
<div className="max-w-md w-full text-center">
<div className="mb-8">
{showLargeText && <h1 className="text-9xl font-bold">404</h1>}
<h2 className="text-2xl font-semibold mb-4">{title}</h2>
<p className="text-gray-400 mb-8">{message}</p>
</div>

{children && (
<div className="flex justify-center gap-4">{children}</div>
)}
</div>
</div>
);
}

export function ErrorPageBackButton({
onClick,
}: {
onClick: () => void;
}): ReactElement {
return (
<Button onClick={onClick} className="px-6 py-3 text-base">
Back
</Button>
);
}

export function ErrorPageHomeButton({
onClick,
}: {
onClick: () => void;
}): ReactElement {
return (
<Button onClick={onClick} className="px-6 py-3 text-base">
Home
</Button>
);
}
19 changes: 19 additions & 0 deletions src/components/ui/link-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ReactElement, ReactNode } from "react";
import { MdOpenInNew } from "react-icons/md";
import { Link } from "./link";

interface LinkButtonProps {
children?: ReactNode;
to: string;
}

export function LinkButton(props: LinkButtonProps): ReactElement {
return (
<div className="font-mono relative flex items-center justify-between gap-2">
<div className="flex items-center">{props.children}</div>
<Link href={props.to} className="flex items-center">
<MdOpenInNew />
</Link>
</div>
);
}
7 changes: 6 additions & 1 deletion src/components/ui/link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@ import { ReactElement } from "react";
interface LinkProps {
children?: ReactElement | string;
href: string;
className?: string;
}

export function Link(props: LinkProps): ReactElement {
const content = props.children ?? props.href;
const baseClass = "text-green-500 hover:text-green-600 transition-colors";
const className = props.className
? `${baseClass} ${props.className}`
: baseClass;
return (
<a
target="_blank"
rel="noopener noreferrer"
href={props.href}
className="text-green-500 hover:text-green-600 transition-colors"
className={className}
>
{content}
</a>
Expand Down
28 changes: 28 additions & 0 deletions src/components/ui/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ReactElement } from "react";

interface LoadingProps {
message?: string;
className?: string;
}

export function Loading({
message = "Loading...",
className = "",
}: LoadingProps): ReactElement {
return (
<div className={`p-8 ${className}`}>
<div className="flex flex-col justify-center items-center h-64 space-y-4">
<div className="relative">
<div className="w-8 h-8 border-4 border-gray-600 border-t-blue-500 rounded-full animate-spin"></div>
<div
className="absolute top-0 left-0 w-8 h-8 border-4 border-transparent border-t-blue-300 rounded-full animate-spin"
style={{ animationDirection: "reverse", animationDuration: "1.5s" }}
></div>
</div>
<p className="text-gray-300 text-lg font-medium animate-pulse">
{message}
</p>
</div>
</div>
);
}
39 changes: 39 additions & 0 deletions src/components/ui/text-filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ReactElement } from "react";

interface TextFieldProps {
title: string;
value: string;
onChange: (value: string) => void;
placeholder?: string;
type?: "text" | "email" | "password" | "number";
onEnter?: () => void;
}

export function TextFilter({
title,
value,
onChange,
placeholder,
type = "text",
onEnter,
}: TextFieldProps): ReactElement {
return (
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">
{title}
</label>
<input
type={type}
value={value}
onChange={(e) => onChange(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && onEnter) {
onEnter();
}
}}
placeholder={placeholder}
className="bg-gray-700 border border-gray-600 rounded px-3 py-2 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent h-10 w-full"
/>
</div>
);
}
Loading