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
8 changes: 7 additions & 1 deletion .env.exemple
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
NEXT_PUBLIC_BACKEND_URL=http://127.0.0.1:8000
NEXT_PUBLIC_BACKEND_URL=http://127.0.0.1:8000
# https://hyperion-3.dev.eclair.ec-lyon.fr
NEXT_PUBLIC_FRONTEND_URL=http://localhost:3000
# https://raid-registering.myecl.fr
NEXT_PUBLIC_REDIRECT_URL=http://localhost:3000/login

NEXT_PUBLIC_CLIENT_ID="RaidRegistering"
2 changes: 1 addition & 1 deletion next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { withPlausibleProxy } from "next-plausible";

const nextConfig = {
output: 'export',
output: "export",
webpack: (config, { isServer }) => {
config.resolve.alias.canvas = false;
return config;
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"next": "14.2.3",
"next-plausible": "^3.12.0",
"next-themes": "^0.3.0",
"oauth4webapi": "^2.10.4",
"react": "^18",
"react-circular-progressbar": "^2.1.0",
"react-currency-input-field": "^3.8.0",
Expand Down
9 changes: 8 additions & 1 deletion src/api/hyperionContext.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { QueryKey, UseQueryOptions } from "@tanstack/react-query";
import { QueryOperation } from "./hyperionComponents";
import { useToken } from "../hooks/useToken";

export type HyperionContext = {
fetcherOptions: {
Expand All @@ -11,6 +12,7 @@ export type HyperionContext = {
* Query params to inject in the fetcher
*/
queryParams?: {};
getToken?: () => Promise<string | null | undefined>;
};
queryOptions: {
/**
Expand Down Expand Up @@ -41,8 +43,13 @@ export function useHyperionContext<
"queryKey" | "queryFn"
>,
): HyperionContext {

const { getToken } = useToken();

return {
fetcherOptions: {},
fetcherOptions: {
getToken
},
queryOptions: {},
queryKeyFn,
};
Expand Down
8 changes: 8 additions & 0 deletions src/api/hyperionFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export async function hyperionFetch<
pathParams,
queryParams,
signal,
getToken,
}: HyperionFetcherOptions<
TBody,
THeaders,
Expand All @@ -59,6 +60,13 @@ export async function hyperionFetch<
delete requestHeaders["Content-Type"];
}

if (getToken) {
const token = await getToken();
if (token) {
requestHeaders["Authorization"]= `Bearer ${token}`;
}
}

const response = await window.fetch(
`${baseUrl}${resolveUrl(url, queryParams, pathParams)}`,
{
Expand Down
7 changes: 4 additions & 3 deletions src/app/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ import {
import MyECLButton from "../../components/login/MyECLButton";
import Link from "next/link";

const Login = () => {
function Login() {
return (
<div className="flex [&>div]:w-full h-screen">
<Card className="rounded-xl border bg-card text-card-foreground shadow max-w-[700px] m-auto text-zinc-700">
<CardHeader>
<CardTitle>Se connecter</CardTitle>
<CardDescription>
Si vous possédez déjà un compte MyECL, vous pouvez vous connecter avec.
Si vous possédez déjà un compte MyECL, vous pouvez vous connecter
avec.
</CardDescription>
</CardHeader>
<CardContent>
Expand All @@ -42,6 +43,6 @@ const Login = () => {
</Card>
</div>
);
};
}

export default Login;
6 changes: 3 additions & 3 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { EmptyParticipantCard } from "../components/home/participantView/EmptyPa
import { ParticipantCard } from "../components/home/participantView/ParicipantCard";
import { TeamCard } from "../components/home/teamCard/TeamCard";
import { TopBar } from "../components/home/TopBar";
import { useAuth } from "../hooks/useAuth";
import { useRouter, useSearchParams } from "next/navigation";
import { useTeam } from "../hooks/useTeam";
import { CreateParticipant } from "../components/home/CreateParticipant";
Expand All @@ -20,12 +19,13 @@ import { toast } from "../components/ui/use-toast";
import { StatusDialog } from "../components/custom/StatusDialog";
import { Button } from "../components/ui/button";
import { RegisteringCompleteDialog } from "../components/home/RegisteringCompleteDialog";
import { useTokenStore } from "../stores/token";

const Home = () => {
const { isTokenQueried, token } = useAuth();
const { me, isFetched, refetch } = useParticipant();
const { me: user, isAdmin } = useUser();
const { team, createTeam, refetchTeam, isLoading: isTeamLoading } = useTeam();
const { token } = useTokenStore();
const [isOpened, setIsOpened] = useState(false);
const [isEndDialogOpened, setIsEndDialogOpened] = useState(true);
const searchParams = useSearchParams();
Expand All @@ -47,7 +47,7 @@ const Home = () => {
router.replace("/");
}

if (isTokenQueried && token === null) {
if (token === null) {
router.replace("/login");
}

Expand Down
4 changes: 1 addition & 3 deletions src/app/recover/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ const RecoverPage = () => {
return showRecover ? (
<AskMail onCodeReceived={() => setShowRecover(false)} />
) : (
<RecoverPassword onCodeNotReceived={
() => setShowRecover(true)
}/>
<RecoverPassword onCodeNotReceived={() => setShowRecover(true)} />
);
};

Expand Down
32 changes: 16 additions & 16 deletions src/components/custom/PdfViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,21 @@ export const PdfViewer = ({ file, width }: PdfViewerProps) => {
// get the width of the parent element
const maxWidth = self?.innerWidth ?? width ?? 550;
return (
<Document
file={file}
onLoadSuccess={onDocumentLoadSuccess}
options={options}
loading={<Skeleton className="w-full h-80" />}
>
{Array.from(new Array(numPages), (el, index) => (
<Page
key={`page_${index + 1}`}
pageNumber={index + 1}
renderTextLayer={false}
className="max-w-full aspect-auto"
width={width}
/>
))}
</Document>
<Document
file={file}
onLoadSuccess={onDocumentLoadSuccess}
options={options}
loading={<Skeleton className="w-full h-80" />}
>
{Array.from(new Array(numPages), (el, index) => (
<Page
key={`page_${index + 1}`}
pageNumber={index + 1}
renderTextLayer={false}
className="max-w-full aspect-auto"
width={width}
/>
))}
</Document>
);
};
2 changes: 1 addition & 1 deletion src/components/home/RegisteringCompleteDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const RegisteringCompleteDialog = ({
toast({
title: "Erreur",
description: "Impossible de télécharger le fichier",
variant: "destructive"
variant: "destructive",
});
setIsFileLoading(false);
return;
Expand Down
12 changes: 10 additions & 2 deletions src/components/home/userSheet/logoutButton.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { useAuth } from "@/src/hooks/useAuth";
import { HiLogout } from "react-icons/hi";
import { Button } from "../../ui/button";
import { useRouter } from "next/navigation";
import { useTokenStore } from "@/src/stores/token";

export const LogoutButton = () => {
const { logout } = useAuth();
const { setToken, setRefreshToken } = useTokenStore();
const router = useRouter();

function logout() {
setToken(null);
setRefreshToken(null);
router.push("/");
}

return (
<Button
Expand Down
115 changes: 89 additions & 26 deletions src/components/login/MyECLButton.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,114 @@
"use client";

import { useAuth } from "../../hooks/useAuth";
import { useRouter, useSearchParams } from "next/navigation";
import { useCodeVerifierStore } from "@/src/stores/codeVerifier";
import { useEffect, useState } from "react";
import { useState } from "react";
import * as auth from "oauth4webapi";
import { LoadingButton } from "../custom/LoadingButton";
import { useTokenStore } from "@/src/stores/token";

const Login = () => {
const { token, isTokenExpired, login, isLoading, getTokenFromRequest } =
useAuth();
const MyECLButton = () => {
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);
const { setCodeVerifier, codeVerifier } = useCodeVerifierStore();
const searchParams = useSearchParams();
const code = searchParams.get("code");
const { codeVerifier } = useCodeVerifierStore();
const [isLoggingIn, setIsLoggingIn] = useState(false);
const issuerUrl = new URL(process.env.NEXT_PUBLIC_BACKEND_URL ?? "");
const { token, setToken, setRefreshToken } = useTokenStore();

useEffect(() => {
async function getIssuer() {
return auth
.discoveryRequest(issuerUrl, { algorithm: "oauth2" })
.then((response) => auth.processDiscoveryResponse(issuerUrl, response));
}
const redirectUri = process.env.NEXT_PUBLIC_FRONTEND_URL + "/login" ?? "";
const client: auth.Client = {
client_id: process.env.NEXT_PUBLIC_CLIENT_ID ?? "",
token_endpoint_auth_method: "none",
};

if (code && !isLoading && typeof window !== "undefined" && codeVerifier) {
login(new URL(window.location.href));
router.replace("/login");
}

async function login(url: URL) {
setIsLoading(true);
const hyperionIssuer = await getIssuer();
const params = auth.validateAuthResponse(
hyperionIssuer,
client,
url,
auth.skipStateCheck,
);
if (auth.isOAuth2Error(params)) {
console.error("Error Response", params);
throw new Error(); // Handle OAuth 2.0 redirect error
}

const response = await auth.authorizationCodeGrantRequest(
hyperionIssuer,
client,
params,
redirectUri,
codeVerifier ?? "",
);

const result = await auth.processAuthorizationCodeOAuth2Response(
hyperionIssuer,
client,
response,
);
if (auth.isOAuth2Error(result)) {
console.error("Error Response", result);
setIsLoading(false);
throw new Error(); // Handle OAuth 2.0 response body error
}
setToken(result.access_token);
setRefreshToken(result.refresh_token ?? "");
setIsLoading(false);
}

async function openSSO() {
const hyperionIssuer = await getIssuer();

const generatedCodeVerifier = auth.generateRandomCodeVerifier();
setCodeVerifier(generatedCodeVerifier);
const codeChallenge = await auth.calculatePKCECodeChallenge(
generatedCodeVerifier,
);
const codeChallengeMethod = "S256";

const authorizationUrl = new URL(hyperionIssuer.authorization_endpoint!);
authorizationUrl.searchParams.set("client_id", client.client_id);
authorizationUrl.searchParams.set("redirect_uri", redirectUri);
authorizationUrl.searchParams.set("response_type", "code");
authorizationUrl.searchParams.set("scope", "API");
authorizationUrl.searchParams.set("code_challenge", codeChallenge);
authorizationUrl.searchParams.set(
"code_challenge_method",
codeChallengeMethod,
);
if (
code &&
typeof window !== "undefined" &&
!isLoading &&
codeVerifier !== undefined &&
!isLoggingIn
hyperionIssuer.code_challenge_methods_supported?.includes("S256") !== true
) {
setIsLoggingIn(true);
login(code, () => {
router.replace("/");
});
const state = auth.generateRandomState();
authorizationUrl.searchParams.set("state", state);
}
}, [code, isLoading, codeVerifier, login, router, isLoggingIn]);

if (token !== null && !isTokenExpired()) {
router.replace("/");
router.push(authorizationUrl.href);
}

function connectMyECL(e: React.MouseEvent<HTMLButtonElement>) {
e.preventDefault();
getTokenFromRequest();
if (token !== null) {
router.replace("/");
}

return (
<LoadingButton
isLoading={isLoading}
onClick={connectMyECL}
onClick={openSSO}
label="Se connecter"
/>
);
};

export default Login;
export default MyECLButton;
10 changes: 5 additions & 5 deletions src/components/register/CreateAccountFormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ interface CreateAccountFormFieldProps {
form: any;
label: string;
name: string;
render: (field: ControllerRenderProps<FieldValues, string>) => React.ReactNode;
render: (
field: ControllerRenderProps<FieldValues, string>,
) => React.ReactNode;
}

export const CreateAccountFormField = ({
form,
label,
name,
render,
render,
}: CreateAccountFormFieldProps) => {
return (
<FormField
Expand All @@ -27,9 +29,7 @@ export const CreateAccountFormField = ({
render={({ field }) => (
<FormItem className="grid gap-2">
<FormLabel>{label}</FormLabel>
<FormControl>
{render(field)}
</FormControl>
<FormControl>{render(field)}</FormControl>
<FormMessage />
</FormItem>
)}
Expand Down
Loading