Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improving Exchange UI #362

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
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
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;
Loading