Skip to content

Commit

Permalink
Merge pull request #362 from NIAEFEUP/fix/exchange-ui
Browse files Browse the repository at this point in the history
Improving Exchange UI
  • Loading branch information
thePeras authored Jan 21, 2025
2 parents 148e6c4 + 5797d26 commit 2204fa3
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 87 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-hover-card": "^1.1.4",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-scroll-area": "^1.0.5",
Expand Down
58 changes: 31 additions & 27 deletions src/components/auth/HeaderProfileDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,62 @@
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar"
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuSeparator } from "../ui/dropdown-menu"
import { DropdownMenuSeparator } from "../ui/dropdown-menu"
import { Button } from "../ui/button";
import { ChevronRightIcon } from "@heroicons/react/24/solid";
import { ArrowRightStartOnRectangleIcon } from "@heroicons/react/24/solid";
import { useContext, useState } from "react";
import { ClipLoader } from "react-spinners";
import SessionContext from "../../contexts/SessionContext";
import authService from "../../api/services/authService";
import studentInfoService from "../../api/services/studentInfo";
import { HoverCard, HoverCardContent, HoverCardTrigger } from "../ui/hover-card";
import ScheduleContext from "../../contexts/ScheduleContext";

export const HeaderProfileDropdown = () => {
const [loggingOut, setLoggingOut] = useState(false);

const { user, setSignedIn } = useContext(SessionContext);
const { setExchangeSchedule } = useContext(ScheduleContext);

const logout = async () => {
setLoggingOut(true);
await authService.logout(user.token, setSignedIn, setLoggingOut);
setExchangeSchedule([]);
await authService.logout(user.token, setSignedIn, setLoggingOut);
}

return <DropdownMenu>
<DropdownMenuTrigger className="w-full">
return <HoverCard>
<HoverCardTrigger className="w-fit">
<Avatar className="border shadow-sm">
<AvatarImage src={studentInfoService.getStudentPictureUrl(user?.username)} />
<AvatarFallback>{user ? user.name.charAt(0) : ""}</AvatarFallback>
</Avatar>
</DropdownMenuTrigger>
<DropdownMenuContent className="p-4 m-4">
</HoverCardTrigger>
<HoverCardContent className="w-44 p-4 mx-4">
<div className="flex flex-col">
<article className="flex flex-col">
<p className="text-md font-bold">{user?.name}</p>
<p className="text-sm">{user?.username}</p>
</article>
<DropdownMenuSeparator className="my-2" />
<Button
variant="ghost"
className="w-full flex flex-row justify-between"
onClick={async () => {
await logout();
}}
>
<div>
<ClipLoader
className="w-full h-2"
loading={loggingOut}
aria-label="Loading Spinner"
data-testid="loader"
/>
</div>
{!loggingOut && <span>Sair</span>}
<ChevronRightIcon className="w-5 h-5" />
</Button>
{loggingOut ?
<ClipLoader
className="w-2 h-2 mx-auto"
loading={true}
aria-label="Loading Spinner"
data-testid="loader"
/>
:
<Button
variant="secondary"
className="w-full flex flex-row justify-center gap-2"
onClick={logout}
>
<ArrowRightStartOnRectangleIcon className="w-5 h-5" />
{!loggingOut && <span>Sair</span>}
</Button>
}

</div>
</DropdownMenuContent>
</DropdownMenu>
</HoverCardContent>
</HoverCard>
}


8 changes: 3 additions & 5 deletions src/components/auth/LoginButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@ type Props = {
export const LoginButton = ({ expanded = false }: Props) => {
return <Button
variant={`${expanded ? "default" : "ghost"}`}
onClick={() => window.location.href = api.OIDC_LOGIN_URL}
>
< a href={`${api.OIDC_LOGIN_URL}`
} className="flex flex-row gap-1" >
{expanded && "Entrar"}
<a href={api.OIDC_LOGIN_URL} className="flex flex-row gap-1" >
<ArrowLeftEndOnRectangleIcon className="w-5 h-5" />
</a >
{expanded && "Entrar"}
</a>
</Button >

}
15 changes: 0 additions & 15 deletions src/components/exchange/ExchangeSidebar.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion src/components/layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const navigation = [
location: getPath(config.paths.planner),
icon: <RectangleStackIcon className="h-5 w-5" />,
wip: false,
},{
}, {
title: 'Trocas',
location: getPath(config.paths.exchange),
icon: <ArrowsRightLeftIcon className="h-5 w-5" />,
Expand Down
4 changes: 2 additions & 2 deletions src/components/ui/avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const Avatar = React.forwardRef<
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full border",
className
)}
{...props}
Expand All @@ -24,7 +24,7 @@ const AvatarImage = React.forwardRef<
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
className={cn("object-cover w-full h-full absolute top-0 left-0 ", className)}
{...props}
/>
))
Expand Down
27 changes: 27 additions & 0 deletions src/components/ui/hover-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as React from "react"
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"

import { cn } from '../../utils'

const HoverCard = HoverCardPrimitive.Root

const HoverCardTrigger = HoverCardPrimitive.Trigger

const HoverCardContent = React.forwardRef<
React.ElementRef<typeof HoverCardPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<HoverCardPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-64 rounded-md border border-slate-200 bg-white p-4 text-slate-950 shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-slate-800 dark:bg-slate-950 dark:text-slate-50",
className
)}
{...props}
/>
))
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName

export { HoverCard, HoverCardTrigger, HoverCardContent }
91 changes: 54 additions & 37 deletions src/pages/Exchange.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
import { useContext, useEffect, useState } from "react";
import { ClassDescriptor } from "../@types";
import { LoginButton } from "../components/auth/LoginButton";
import { ExchangeSidebar } from "../components/exchange/ExchangeSidebar";
import ExchangeSchedule from "../components/exchange/schedule/ExchangeSchedule";
import ScheduleContext from "../contexts/ScheduleContext";
import SessionContext from "../contexts/SessionContext";
import useSchedule from "../hooks/useSchedule";
import useStudentCourseUnits from "../hooks/useStudentCourseUnits";
import '../styles/exchange.css';
import { CreateRequest } from "../components/exchange/requests/issue/CreateRequest";
import { ViewRequests } from "../components/exchange/requests/view/ViewRequests";
import { FaceFrownIcon, ShieldExclamationIcon } from "@heroicons/react/24/outline";
import { Schedule } from "../components/planner";

const ExchangeGuard = ({ children }: { children: React.ReactNode }) => {
return (
<article className="flex flex-col mx-auto w-full gap-4">
<h1 className="text-center text-3xl font-bold">
Trocas de Turmas
</h1>
{children}
</article>
);
}

const ExchangePage = () => {
const [loads, setLoads] = useState<number>(-1);
Expand All @@ -40,36 +33,60 @@ const ExchangePage = () => {
}
}, [schedule]);

return <>
{signedIn ?
<ScheduleContext.Provider value={{ originalExchangeSchedule, exchangeSchedule, loadingSchedule, setExchangeSchedule, enrolledCourseUnits }}>
{
user?.eligible_exchange ?
<div className="grid w-cfull grid-cols-12 gap-x-4 gap-y-4 px-4 py-4">
{/* Schedule Preview */}
<div className="lg:min-h-adjusted order-1 col-span-12 min-h-min rounded bg-lightest px-3 py-3 dark:bg-dark lg:col-span-9 2xl:px-5 2xl:py-5">
<div className="h-full w-full">
<ExchangeSchedule />
</div>
</div>
const [creatingRequest, setCreatingRequest] = useState<boolean>(false);

<ExchangeSidebar />
</div>
: <ExchangeGuard>
<p className="text-center">Não tens nenhuma inscrição numa disciplina com um período de trocas ativo.</p>
</ExchangeGuard>
}
</ScheduleContext.Provider>
: <ExchangeGuard>
<p className="text-center">
Tens de iniciar sessão para acederes a esta funcionalidade.
</p>
<div className="justify-center mx-auto">
if (!signedIn) return <ScheduleContext.Provider value={{ originalExchangeSchedule, exchangeSchedule, loadingSchedule, setExchangeSchedule, enrolledCourseUnits }}>
<div className="grid w-cfull grid-cols-12 gap-x-4 gap-y-4 px-4 py-4">
<div className="lg:min-h-adjusted order-1 col-span-12 min-h-min rounded bg-lightest px-3 py-3 dark:bg-dark lg:col-span-9 2xl:px-5 2xl:py-5">
<div className="h-full w-full">
<Schedule
classes={[]}
slots={[]}
/>
</div>
</div>

<div className="lg:min-h-adjusted order-2 col-span-12 flex min-h-min flex-col justify-between rounded bg-lightest px-3 py-3 dark:bg-dark lg:col-span-3 2xl:px-4 2xl:py-4">
<div className="flex flex-col items-center justify-center gap-4 h-full">
<ShieldExclamationIcon className="w-12 h-12" />
<p className="text-center">Tens de iniciar sessão para começares a trocar de turmas</p>
<LoginButton expanded={true} />
</div>
</div>
</div>
</ScheduleContext.Provider>

return <ScheduleContext.Provider value={{ originalExchangeSchedule, exchangeSchedule, loadingSchedule, setExchangeSchedule, enrolledCourseUnits }}>
{
<div className="grid w-cfull grid-cols-12 gap-x-4 gap-y-4 px-4 py-4">
{/* Schedule Preview */}
<div className="lg:min-h-adjusted order-1 col-span-12 min-h-min rounded bg-lightest px-3 py-3 dark:bg-dark lg:col-span-9 2xl:px-5 2xl:py-5">
<div className="h-full w-full">
<ExchangeSchedule />
</div>
</div>

</ExchangeGuard>}
</>
<div className="lg:min-h-adjusted order-2 col-span-12 flex min-h-min flex-col justify-between rounded bg-lightest px-3 py-3 dark:bg-dark lg:col-span-3 2xl:px-4 2xl:py-4">
{user?.eligible_exchange ?
<div>
{creatingRequest
? <CreateRequest setCreatingRequest={setCreatingRequest} />
: <ViewRequests setCreatingRequest={setCreatingRequest} />}
</div>
:
<div className="flex flex-col items-center justify-center gap-4 h-full">
<FaceFrownIcon className="w-12 h-12" />
<p className="text-center">Nenhuma das tuas unidades curriculares dá para trocar a turma no TTS</p>
{/* TODO: Open the send feedback modal with something already written
<p className="text-center">Gostavas de utilizar esta funcionalidade no teu curso?</p>
<Button onClick={() => { }}>Sim!</Button>
*/}
</div>
}
</div>
</div>
}
</ScheduleContext.Provider>
}

export default ExchangePage;

0 comments on commit 2204fa3

Please sign in to comment.