Files
FamilyPlanner/frontend/src/screens/CalendarOAuthCallbackScreen.js
philippe fdd72c1135 Initial commit: Family Planner application
Complete family planning application with:
- React frontend with TypeScript
- Node.js/Express backend with TypeScript
- Python ingestion service for document processing
- Planning ingestion service with LLM integration
- Shared UI components and type definitions
- OAuth integration for calendar synchronization
- Comprehensive documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-14 10:43:33 +02:00

152 lines
6.0 KiB
JavaScript

import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useEffect, useMemo, useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import styled from "styled-components";
import { completeCalendarOAuth } from "../services/api-client";
import { consumePendingOAuthState, peekPendingOAuthState, storeOAuthResult } from "../utils/calendar-oauth";
const Container = styled.main `
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: radial-gradient(circle at top, rgba(32, 45, 112, 0.55), rgba(9, 13, 28, 0.95));
color: #f4f5ff;
padding: 32px;
`;
const Card = styled.div `
max-width: 520px;
width: 100%;
border-radius: 20px;
border: 1px solid rgba(126, 136, 180, 0.3);
background: rgba(16, 22, 52, 0.88);
padding: 32px;
display: flex;
flex-direction: column;
gap: 18px;
box-shadow: 0 32px 68px rgba(0, 0, 0, 0.45);
`;
const Title = styled.h1 `
margin: 0;
font-size: 1.6rem;
`;
const Message = styled.p `
margin: 0;
line-height: 1.6;
color: rgba(197, 202, 240, 0.85);
`;
const Details = styled.pre `
margin: 0;
padding: 12px;
border-radius: 12px;
background: rgba(9, 13, 28, 0.75);
border: 1px solid rgba(126, 136, 180, 0.25);
font-size: 0.85rem;
color: rgba(197, 202, 240, 0.75);
white-space: pre-wrap;
word-break: break-word;
`;
const Button = styled.button `
align-self: flex-start;
padding: 10px 18px;
border-radius: 12px;
border: 1px solid rgba(85, 98, 255, 0.5);
background: rgba(85, 98, 255, 0.25);
color: #f4f5ff;
font-weight: 600;
cursor: pointer;
transition: background 0.2s ease, transform 0.15s ease;
&:hover {
background: rgba(85, 98, 255, 0.35);
transform: translateY(-1px);
}
`;
const providerLabel = (provider) => (provider === "google" ? "Google" : "Outlook");
export const CalendarOAuthCallbackScreen = () => {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const [status, setStatus] = useState("processing");
const [statusMessage, setStatusMessage] = useState("Initialisation de la connexion securisee...");
const [details, setDetails] = useState({});
const stateParam = searchParams.get("state") ?? "";
const codeParam = searchParams.get("code") ?? undefined;
const errorParam = searchParams.get("error") ?? undefined;
const providerParam = (searchParams.get("provider") ?? undefined);
const pendingInfo = useMemo(() => (stateParam ? peekPendingOAuthState(stateParam) : null), [stateParam]);
useEffect(() => {
const execute = async () => {
if (!stateParam) {
setStatus("error");
setStatusMessage("Parametre 'state' manquant. Impossible de finaliser la connexion.");
return;
}
const inferredProvider = providerParam ?? pendingInfo?.provider ?? "google";
const payload = {
provider: inferredProvider,
state: stateParam,
code: codeParam,
error: errorParam,
profileId: pendingInfo?.profileId
};
try {
const response = await completeCalendarOAuth(payload);
storeOAuthResult({ success: response?.success, provider: inferredProvider, state: stateParam });
if (window.opener) {
window.opener.postMessage({
type: "fp-calendar-oauth",
success: response?.success ?? !errorParam,
state: stateParam,
provider: inferredProvider,
email: response?.email,
label: response?.label
}, window.location.origin);
}
setDetails({
provider: providerLabel(inferredProvider),
state: stateParam,
profileId: pendingInfo?.profileId,
result: response
});
if (response?.success && !errorParam) {
setStatus("success");
setStatusMessage("Connexion validee ! Vous pouvez revenir sur Family Planner.");
}
else {
setStatus("error");
setStatusMessage("La connexion n'a pas pu etre finalisee. Merci de reessayer.");
}
}
catch (error) {
setStatus("error");
setStatusMessage("Une erreur est survenue pendant la finalisation de l'autorisation.");
setDetails({
provider: providerLabel(inferredProvider),
state: stateParam,
error: String(error)
});
if (window.opener) {
window.opener.postMessage({
type: "fp-calendar-oauth",
success: false,
state: stateParam,
provider: inferredProvider,
error: String(error)
}, window.location.origin);
}
}
finally {
if (!window.opener) {
consumePendingOAuthState(stateParam);
}
setTimeout(() => window.close(), 2500);
}
};
void execute();
}, [codeParam, errorParam, pendingInfo, providerParam, stateParam]);
return (_jsx(Container, { children: _jsxs(Card, { children: [_jsx(Title, { children: status === "processing"
? "Connexion a l'agenda..."
: status === "success"
? "Agenda connecte !"
: "Connexion interrompue" }), _jsx(Message, { children: statusMessage }), Object.keys(details).length > 0 ? _jsx(Details, { children: JSON.stringify(details, null, 2) }) : null, status !== "processing" ? (_jsx(Button, { type: "button", onClick: () => navigate("/", { replace: true }), children: "Retourner au tableau de bord" })) : null] }) }));
};