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
22 changes: 22 additions & 0 deletions components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/styles/index.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {}
}
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,21 @@
"preview": "vite preview"
},
"dependencies": {
"@radix-ui/react-slot": "^1.2.4",
"@tailwindcss/vite": "^4.0.9",
"axios": "^1.8.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.562.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-icons": "^5.5.0",
"react-query": "^3.39.3",
"react-router": "^7.2.0",
"tailwindcss": "^4.0.9"
"tailwind-merge": "^3.4.0",
"tailwindcss": "^4.0.9",
"tailwindcss-animate": "^1.0.7",
"tw-animate-css": "^1.4.0"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
Expand Down
19 changes: 11 additions & 8 deletions src/components/dashboard/DashboardHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { DASHBOARD_LINKS, DashboardLinkType } from "@/constants/dashboard";
import { IoArrowBack } from "react-icons/io5";
import { MdOutlineRefresh } from "react-icons/md";
import { Link } from "react-router";
import { Button } from "@/components/ui/button";

interface DashboardHeaderProps {
username: string;
Expand Down Expand Up @@ -41,20 +42,22 @@ function DashboardHeaderToolbar({ onRefresh }: { onRefresh: () => void }) {
return (
<div className="flex flex-row justify-between items-center mb-8 text-gray-500">
<nav>
<Link to="/" className="flex flex-row gap-2 items-center text-sm">
<IoArrowBack size={20} />
Back to change user
</Link>
<Button variant="ghost" asChild className="hover:bg-transparent">
<Link to="/" className="flex flex-row gap-2 items-center text-sm">
<IoArrowBack size={20} />
Back to change user
</Link>
</Button>
</nav>
<button
type="button"
<Button
variant="ghost"
onClick={onRefresh}
aria-label="Refresh progress data"
className="flex flex-row gap-2 items-center text-sm cursor-pointer"
className="hover:bg-transparent"
>
<MdOutlineRefresh size={20} />
Refresh
</button>
</Button>
</div>
);
}
Expand Down
44 changes: 26 additions & 18 deletions src/components/dashboard/ExerciseTable.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { BASE_URL, ExerciseStatus } from "@/constants";
import { Exercise } from "@/types/exercise";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";

interface ExerciseTableProps {
exercises: Exercise[];
Expand Down Expand Up @@ -48,24 +56,24 @@ function ExerciseContextLabel({ exercise }: { exercise: Exercise }) {

function ExerciseTable({ exercises, progress }: ExerciseTableProps) {
return (
<table className="table-fixed w-full bg-white border border-gray-300 rounded-sm">
<thead>
<tr>
<th className="bg-emerald-700 text-white border border-emerald-800 px-4 py-2 text-left">
<Table className="table-fixed bg-white border border-gray-300 rounded-sm">
<TableHeader>
<TableRow className="hover:bg-transparent border-0">
<TableHead className="bg-emerald-700 text-white border border-emerald-800 px-4 py-2 text-left font-bold text-lg">
Exercise
</th>
<th className="bg-emerald-700 text-white border border-emerald-800 px-4 py-2 text-left w-40">
</TableHead>
<TableHead className="bg-emerald-700 text-white border border-emerald-800 px-4 py-2 text-left w-40 font-bold text-lg">
Status
</th>
</tr>
</thead>
<tbody>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{exercises.map((exercise) => {
const status = getStatusDisplay(progress.get(exercise.identifier));
const lessonPath = exercise.detour?.lesson.path ?? exercise.parentLesson.path;
return (
<tr key={exercise.key}>
<td className="border border-gray-300 px-4 py-2 text-left">
<TableRow key={exercise.key}>
<TableCell className="border border-gray-300 px-4 py-2">
<a
target="_blank"
rel="noopener noreferrer"
Expand All @@ -74,16 +82,16 @@ function ExerciseTable({ exercises, progress }: ExerciseTableProps) {
<code className="underline text-blue-800">{exercise.identifier}</code>
</a>
<ExerciseContextLabel exercise={exercise} />
</td>
<td className="border border-gray-300 px-4 py-2 text-left">
</TableCell>
<TableCell className="border border-gray-300 px-4 py-2 text-left">
<span className="mr-2">{status.icon}</span>
{status.text}
</td>
</tr>
</TableCell>
</TableRow>
);
})}
</tbody>
</table>
</TableBody>
</Table>
);
}

Expand Down
51 changes: 28 additions & 23 deletions src/components/dashboard/StatusMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,55 @@
import { Link } from "react-router";
import { Button } from "@/components/ui/button";

type ButtonVariant = "error" | "primary";
type StatusVariant = "destructive" | "default";

interface StatusMessageProps extends React.PropsWithChildren {
buttonText: string;
buttonHref: string;
variant?: ButtonVariant;
variant?: StatusVariant;
external?: boolean;
}

const variantStyles: Record<ButtonVariant, string> = {
error: "border-red-700 bg-red-700 text-white",
primary: "border-blue-800 bg-blue-800 text-white",
};

const textStyles: Record<ButtonVariant, string> = {
error: "text-red-700",
primary: "",
const textStyles: Record<StatusVariant, string> = {
destructive: "text-red-700",
default: "",
};

function StatusMessage({
children,
buttonText,
buttonHref,
variant = "error",
variant = "default",
external = false,
}: StatusMessageProps) {
const buttonClasses = `hover:cursor-pointer border-1 ${variantStyles[variant]} rounded-sm px-4 py-2 font-semibold`;

return (
<div className="text-center">
<div className={textStyles[variant]}>{children}</div>
<div className="mt-4">
{external ? (
<a
href={buttonHref}
target="_blank"
rel="noopener noreferrer"
className={buttonClasses}
<Button
asChild
className="rounded-sm font-semibold"
variant={variant}
>
{buttonText}
</a>
<a
href={buttonHref}
target="_blank"
rel="noopener noreferrer"
>
{buttonText}
</a>
</Button>
) : (
<Link to={buttonHref} className={buttonClasses}>
{buttonText}
</Link>
<Button
asChild
className="rounded-sm font-semibold"
variant={variant}
>
<Link to={buttonHref}>
{buttonText}
</Link>
</Button>
)}
</div>
</div>
Expand Down
57 changes: 57 additions & 0 deletions src/components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-white shadow-sm hover:bg-destructive/90",
outline:
"border border-outline hover:bg-outline hover:text-outline-foreground hover:border-outline",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)

export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"

export { Button, buttonVariants }
Loading
Loading