- Add multi-language support for registration and user menu components
- Refactor `TextEditor` to include book-closing functionality with updated toolbar buttons - Replace `generateHTML` with a lightweight custom TipTap-to-HTML renderer - Update and lock `esbuild` and related dependencies to latest versions
This commit is contained in:
@@ -2,9 +2,11 @@ import {useContext, useEffect, useRef, useState} from "react";
|
||||
import {SessionContext} from "@/context/SessionContext";
|
||||
import NoPicture from "@/components/NoPicture";
|
||||
import System from "@/lib/models/System";
|
||||
import {useTranslations} from "next-intl";
|
||||
|
||||
export default function UserMenu() {
|
||||
const {session} = useContext(SessionContext);
|
||||
const t = useTranslations();
|
||||
|
||||
const profileMenuRef: React.RefObject<HTMLDivElement | null> = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -32,15 +34,8 @@ export default function UserMenu() {
|
||||
|
||||
async function handleLogout(): Promise<void> {
|
||||
System.removeCookie("token");
|
||||
|
||||
// Si dans Electron, utiliser IPC pour logout
|
||||
if (window.electron) {
|
||||
await window.electron.removeToken();
|
||||
window.electron.logout();
|
||||
} else {
|
||||
// Fallback web
|
||||
document.location.href = "https://eritors.com/login";
|
||||
}
|
||||
await window.electron.removeToken();
|
||||
window.electron.logout();
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -64,11 +59,11 @@ export default function UserMenu() {
|
||||
<a href="https://eritors.com/settings"
|
||||
className="group flex items-center gap-3 px-4 py-2.5 text-text-primary hover:bg-secondary/50 transition-all hover:pl-5">
|
||||
<span
|
||||
className="text-sm font-medium group-hover:text-primary transition-colors">Paramètres</span>
|
||||
className="text-sm font-medium group-hover:text-primary transition-colors">{t('userMenu.settings')}</span>
|
||||
</a>
|
||||
<a onClick={handleLogout} href="#"
|
||||
className="group flex items-center gap-3 px-4 py-2.5 text-error hover:bg-error/10 transition-all hover:pl-5 rounded-b-xl">
|
||||
<span className="text-sm font-medium">Déconnexion</span>
|
||||
<span className="text-sm font-medium">{t('userMenu.logout')}</span>
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -15,7 +15,8 @@ import {
|
||||
faListOl,
|
||||
faListUl,
|
||||
faParagraph,
|
||||
faUnderline
|
||||
faUnderline,
|
||||
faXmark
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import {EditorContext} from "@/context/EditorContext";
|
||||
import {ChapterContext} from '@/context/ChapterContext';
|
||||
@@ -135,8 +136,8 @@ export default function TextEditor() {
|
||||
const t = useTranslations();
|
||||
const {lang} = useContext<LangContextProps>(LangContext)
|
||||
const {editor} = useContext(EditorContext);
|
||||
const {chapter} = useContext(ChapterContext);
|
||||
const {book} = useContext(BookContext);
|
||||
const {chapter, setChapter} = useContext(ChapterContext);
|
||||
const {book, setBook} = useContext(BookContext);
|
||||
const {errorMessage, successMessage} = useContext(AlertContext);
|
||||
const {session} = useContext(SessionContext);
|
||||
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
|
||||
@@ -146,6 +147,7 @@ export default function TextEditor() {
|
||||
const [showGhostWriter, setShowGhostWriter] = useState<boolean>(false);
|
||||
const [showUserSettings, setShowUserSettings] = useState<boolean>(false);
|
||||
const [isSaving, setIsSaving] = useState<boolean>(false);
|
||||
const [isClosing, setIsClosing] = useState<boolean>(false);
|
||||
const [editorSettings, setEditorSettings] = useState<EditorDisplaySettings>(DEFAULT_EDITOR_SETTINGS);
|
||||
const [editorClasses, setEditorClasses] = useState<EditorClasses>({
|
||||
base: 'text-lg font-serif leading-normal',
|
||||
@@ -351,6 +353,14 @@ export default function TextEditor() {
|
||||
setShowDraftCompanion(false);
|
||||
setShowGhostWriter(false);
|
||||
}, []);
|
||||
|
||||
const handleCloseBook: () => Promise<void> = useCallback(async (): Promise<void> => {
|
||||
setIsClosing(true);
|
||||
await saveContent();
|
||||
setBook && setBook(null);
|
||||
setChapter && setChapter(undefined);
|
||||
setIsClosing(false);
|
||||
}, [saveContent, setBook, setChapter]);
|
||||
|
||||
useEffect((): void => {
|
||||
if (!editor) return;
|
||||
@@ -454,25 +464,25 @@ export default function TextEditor() {
|
||||
return (
|
||||
<div className="flex flex-col flex-1 w-full h-full">
|
||||
<div
|
||||
className={`flex justify-between gap-3 border-b border-secondary/30 px-4 py-3 bg-gradient-to-b from-dark-background/80 to-dark-background/50 backdrop-blur-sm transition-opacity duration-300 shadow-md ${editorSettings.focusMode ? 'opacity-70 hover:opacity-100' : ''}`}>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
className={`flex justify-between gap-2 lg:gap-3 border-b border-secondary/30 px-2 lg:px-4 py-2 lg:py-3 bg-gradient-to-b from-dark-background/80 to-dark-background/50 backdrop-blur-sm transition-opacity duration-300 shadow-md overflow-x-auto ${editorSettings.focusMode ? 'opacity-70 hover:opacity-100' : ''}`}>
|
||||
<div className="flex gap-1 flex-shrink-0">
|
||||
{toolbarButtons.map((button: ToolbarButton, index: number) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={button.action}
|
||||
className={`group flex items-center px-3 py-2 rounded-lg transition-all duration-200 ${button.isActive ? 'bg-primary text-text-primary shadow-md shadow-primary/30 scale-105' : 'text-muted hover:text-text-primary hover:bg-secondary/50 hover:shadow-sm hover:scale-105'}`}
|
||||
className={`group flex items-center px-2 lg:px-3 py-1.5 lg:py-2 rounded-lg transition-all duration-200 flex-shrink-0 ${button.isActive ? 'bg-primary text-text-primary shadow-md shadow-primary/30 scale-105' : 'text-muted hover:text-text-primary hover:bg-secondary/50 hover:shadow-sm hover:scale-105'}`}
|
||||
>
|
||||
<FontAwesomeIcon icon={button.icon} className={'w-4 h-4 transition-transform duration-200 group-hover:scale-110'}/>
|
||||
<FontAwesomeIcon icon={button.icon} className={'w-3.5 h-3.5 lg:w-4 lg:h-4 transition-transform duration-200 group-hover:scale-110'}/>
|
||||
{
|
||||
button.label &&
|
||||
<span className="ml-2 text-sm font-medium">
|
||||
<span className="ml-1 lg:ml-2 text-xs lg:text-sm font-medium">
|
||||
{t(`textEditor.toolbar.${button.label}`)}
|
||||
</span>
|
||||
}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2 flex-shrink-0">
|
||||
<CollapsableButton
|
||||
showCollapsable={showUserSettings}
|
||||
text={t("textEditor.preferences")}
|
||||
@@ -495,13 +505,30 @@ export default function TextEditor() {
|
||||
icon={faLayerGroup}
|
||||
/>
|
||||
)}
|
||||
<SubmitButtonWLoading
|
||||
callBackAction={saveContent}
|
||||
isLoading={isSaving}
|
||||
text={t("textEditor.save")}
|
||||
loadingText={t("textEditor.saving")}
|
||||
icon={faFloppyDisk}
|
||||
/>
|
||||
<button
|
||||
onClick={saveContent}
|
||||
disabled={isSaving}
|
||||
className={`group py-2.5 px-3 lg:px-5 rounded-lg font-semibold transition-all flex items-center justify-center gap-2 relative overflow-hidden ${
|
||||
isSaving
|
||||
? 'bg-secondary cursor-not-allowed opacity-75'
|
||||
: 'bg-secondary/80 hover:bg-secondary shadow-md hover:shadow-lg hover:shadow-primary/20 hover:scale-105 border border-secondary/50 hover:border-primary/30'
|
||||
}`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faFloppyDisk} className="w-4 h-4 transition-transform group-hover:scale-110 text-primary" />
|
||||
<span className="hidden lg:inline text-sm text-primary">{isSaving ? t("textEditor.saving") : t("textEditor.save")}</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleCloseBook}
|
||||
disabled={isClosing}
|
||||
className={`group py-2.5 px-3 lg:px-5 rounded-lg font-semibold transition-all flex items-center justify-center gap-2 relative overflow-hidden ${
|
||||
isClosing
|
||||
? 'bg-secondary/30 cursor-not-allowed opacity-75'
|
||||
: 'bg-secondary/30 text-muted hover:text-text-primary hover:bg-secondary hover:shadow-sm hover:scale-105'
|
||||
}`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="w-4 h-4 transition-transform duration-200 group-hover:scale-110" />
|
||||
<span className="hidden lg:inline text-sm">{t("textEditor.close")}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faLock, faWifi, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faLock, faWifi, faEye, faEyeSlash, faSignOutAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import System from '@/lib/models/System';
|
||||
|
||||
interface OfflinePinVerifyProps {
|
||||
onSuccess: (userId: string) => void;
|
||||
@@ -59,6 +60,12 @@ export default function OfflinePinVerify({ onSuccess, onCancel }: OfflinePinVeri
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogout = async () => {
|
||||
System.removeCookie("token");
|
||||
await window.electron.removeToken();
|
||||
window.electron.logout();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background">
|
||||
<div className="bg-tertiary rounded-2xl p-6 max-w-md w-full mx-4 shadow-2xl">
|
||||
@@ -118,6 +125,14 @@ export default function OfflinePinVerify({ onSuccess, onCancel }: OfflinePinVeri
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="flex-1 px-4 py-2 text-error hover:bg-error/10 rounded-lg transition-all flex items-center justify-center gap-2"
|
||||
disabled={isLoading}
|
||||
>
|
||||
<FontAwesomeIcon icon={faSignOutAlt} className="w-4 h-4" />
|
||||
<span className="text-sm font-medium">{t('userMenu.logout')}</span>
|
||||
</button>
|
||||
{onCancel && (
|
||||
<button
|
||||
onClick={onCancel}
|
||||
|
||||
Reference in New Issue
Block a user