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
2 changes: 1 addition & 1 deletion course-matrix/frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/img/logo.png" />
<link rel="icon" type="image/png" href="/img/course-matrix-logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Course Matrix </title>
</head>
Expand Down
34 changes: 34 additions & 0 deletions course-matrix/frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions course-matrix/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-switch": "^1.1.3",
"@radix-ui/react-toast": "^1.2.6",
"@radix-ui/react-tooltip": "^1.1.8",
"@reduxjs/toolkit": "^2.5.1",
"@schedule-x/drag-and-drop": "^2.21.1",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added course-matrix/frontend/public/img/grey-avatar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions course-matrix/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import SignupPage from "./pages/Signup/SignUpPage";
import AuthRoute from "./components/auth-route";
import SignupSuccessfulPage from "./pages/Signup/SignupSuccessfulPage";
import LoginRoute from "./components/login-route";
import { Toaster } from "./components/ui/toaster";

/**
* App Component
Expand Down Expand Up @@ -44,6 +45,7 @@ function App() {
element={<AuthRoute component={Dashboard} />}
/>
</Routes>
<Toaster />
</div>
);
}
Expand Down
2 changes: 1 addition & 1 deletion course-matrix/frontend/src/components/UserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export function UserMenu({ setOpen }: UserMenuProps) {
{username}
<Avatar>
{/* Avatar Image is the profile picture of the user. The default avatar is used as a placeholder for now. */}
<AvatarImage src="/img/default-avatar.png" />
<AvatarImage src="/img/grey-avatar.png" className="h-18 w-18" />
{/* Avatar Fallback is the initials of the user. Avatar Fallback will be used if Avatar Image fails to load */}
<AvatarFallback>{initials}</AvatarFallback>
</Avatar>
Expand Down
16 changes: 16 additions & 0 deletions course-matrix/frontend/src/components/imagePlaceholder.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const ImagePlaceholder = () => {
const blurredImagePlaceholder =
"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAAAAAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAAIAAgDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAhEAACAQMDBQAAAAAAAAAAAAABAgMABAUGIWEREiMxUf/EABUBAQEAAAAAAAAAAAAAAAAAAAMF/8QAGhEAAgIDAAAAAAAAAAAAAAAAAQIAAxEhMf/aAAwDAQACEQMRAD8ASlWzQwxoGsuragA8nk6c+ONWNeOixw2aRm2AMbs5JAJJPk9aUpAJViJIGenZEwn/2Q==";

return (
<div
className="absolute inset-0 rounded-lg bg-gray-200"
style={{
backgroundImage: `url(${blurredImagePlaceholder})`,
backgroundSize: "cover",
filter: "blur(8px)",
transform: "scale(1.1)",
}}
></div>
);
};
25 changes: 19 additions & 6 deletions course-matrix/frontend/src/components/logo.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
import logoImg from "/img/logo.png";
import { useState, useEffect } from "react";
import logoImg from "/img/course-matrix-logo.png";
import { ImagePlaceholder } from "./imagePlaceholder";

const Logo = () => {
const [imageLoaded, setImageLoaded] = useState(false);

return (
<>
<div className="p-2 flex gap-2 items-center font-medium">
<img
src={logoImg}
alt="profile-img"
className="rounded-lg aspect-square object-cover w-8"
></img>
<div className="relative rounded-lg w-8 h-8">
{!imageLoaded && <ImagePlaceholder />}

<img
src={logoImg}
alt="Course Matrix logo"
className={`rounded-lg aspect-square object-cover w-8 h-8 transition-opacity duration-300 ${
imageLoaded ? "opacity-100" : "opacity-0"
}`}
loading="lazy"
onLoad={() => setImageLoaded(true)}
/>
</div>

<div className="flex items-center gap-1">
<div>Course</div>
<div className="text-green-500">Matrix</div>
Expand Down
127 changes: 127 additions & 0 deletions course-matrix/frontend/src/components/ui/toast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import * as React from "react";
import * as ToastPrimitives from "@radix-ui/react-toast";
import { cva, type VariantProps } from "class-variance-authority";
import { X } from "lucide-react";

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

const ToastProvider = ToastPrimitives.Provider;

const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Viewport
ref={ref}
className={cn(
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
className,
)}
{...props}
/>
));
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;

const toastVariants = cva(
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
{
variants: {
variant: {
default: "border bg-background text-foreground",
destructive:
"destructive group border-destructive bg-destructive text-destructive-foreground",
},
},
defaultVariants: {
variant: "default",
},
},
);

const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => {
return (
<ToastPrimitives.Root
ref={ref}
className={cn(toastVariants({ variant }), className)}
{...props}
/>
);
});
Toast.displayName = ToastPrimitives.Root.displayName;

const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Action
ref={ref}
className={cn(
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
className,
)}
{...props}
/>
));
ToastAction.displayName = ToastPrimitives.Action.displayName;

const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Close
ref={ref}
className={cn(
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
className,
)}
toast-close=""
{...props}
>
<X className="h-4 w-4" />
</ToastPrimitives.Close>
));
ToastClose.displayName = ToastPrimitives.Close.displayName;

const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Title
ref={ref}
className={cn("text-sm font-semibold", className)}
{...props}
/>
));
ToastTitle.displayName = ToastPrimitives.Title.displayName;

const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Description
ref={ref}
className={cn("text-sm opacity-90", className)}
{...props}
/>
));
ToastDescription.displayName = ToastPrimitives.Description.displayName;

type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;

type ToastActionElement = React.ReactElement<typeof ToastAction>;

export {
type ToastProps,
type ToastActionElement,
ToastProvider,
ToastViewport,
Toast,
ToastTitle,
ToastDescription,
ToastClose,
ToastAction,
};
33 changes: 33 additions & 0 deletions course-matrix/frontend/src/components/ui/toaster.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useToast } from "@/hooks/use-toast";
import {
Toast,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from "@/components/ui/toast";

export function Toaster() {
const { toasts } = useToast();

return (
<ToastProvider>
{toasts.map(function ({ id, title, description, action, ...props }) {
return (
<Toast key={id} {...props}>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && (
<ToastDescription>{description}</ToastDescription>
)}
</div>
{action}
<ToastClose />
</Toast>
);
})}
<ToastViewport />
</ToastProvider>
);
}
Loading