Handle identity token reset on 2004
This commit is contained in:
@@ -1,14 +1,15 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||||
import { tokens, useToastController, Toast, ToastTitle } from '@fluentui/react-components';
|
import { tokens, useToastController, Toast, ToastTitle, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, Button, Input, Text } from '@fluentui/react-components';
|
||||||
import { MainLayout } from './layouts/MainLayout';
|
import { MainLayout } from './layouts/MainLayout';
|
||||||
import About from './components/About';
|
import About from './components/About';
|
||||||
import CreatePost from './components/CreatePost';
|
import CreatePost from './components/CreatePost';
|
||||||
import PostCard from './components/PostCard';
|
import PostCard from './components/PostCard';
|
||||||
import ImageViewer from './components/ImageViewer';
|
import ImageViewer from './components/ImageViewer';
|
||||||
import { fetchArticles, type Article } from './api';
|
import { fetchArticles, reset_identity_token, type Article } from './api';
|
||||||
import { useLayout } from './context/LayoutContext';
|
import { useLayout } from './context/LayoutContext';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
import { Eye24Regular, EyeOff24Regular, Copy24Regular } from '@fluentui/react-icons';
|
||||||
|
|
||||||
const Home: React.FC<{ onPreviewImage: (src: string, alt?: string) => void }> = ({ onPreviewImage }) => {
|
const Home: React.FC<{ onPreviewImage: (src: string, alt?: string) => void }> = ({ onPreviewImage }) => {
|
||||||
const { refreshTrigger } = useLayout();
|
const { refreshTrigger } = useLayout();
|
||||||
@@ -154,6 +155,19 @@ function App() {
|
|||||||
setImageViewer({ open: true, src, alt });
|
setImageViewer({ open: true, src, alt });
|
||||||
};
|
};
|
||||||
const closeImageViewer = () => setImageViewer({ open: false });
|
const closeImageViewer = () => setImageViewer({ open: false });
|
||||||
|
const [identityDialogOpen, setIdentityDialogOpen] = useState(false);
|
||||||
|
const [identityToken, setIdentityToken] = useState('');
|
||||||
|
const [identityVisible, setIdentityVisible] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handler = () => {
|
||||||
|
setIdentityToken(localStorage.getItem('identity_token') || '');
|
||||||
|
setIdentityVisible(false);
|
||||||
|
setIdentityDialogOpen(true);
|
||||||
|
};
|
||||||
|
window.addEventListener('identity_invalid', handler as EventListener);
|
||||||
|
return () => window.removeEventListener('identity_invalid', handler as EventListener);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
@@ -162,10 +176,70 @@ function App() {
|
|||||||
path="/"
|
path="/"
|
||||||
element={
|
element={
|
||||||
<MainLayout
|
<MainLayout
|
||||||
imageViewer={
|
overlays={
|
||||||
imageViewer.open && imageViewer.src ? (
|
<>
|
||||||
|
{imageViewer.open && imageViewer.src ? (
|
||||||
<ImageViewer src={imageViewer.src!} alt={imageViewer.alt} onClose={closeImageViewer} />
|
<ImageViewer src={imageViewer.src!} alt={imageViewer.alt} onClose={closeImageViewer} />
|
||||||
) : null
|
) : null}
|
||||||
|
<Dialog open={identityDialogOpen} onOpenChange={(_, data) => setIdentityDialogOpen(!!data.open)}>
|
||||||
|
<DialogSurface>
|
||||||
|
<DialogBody>
|
||||||
|
<DialogTitle>Identity_token无效,是否重置?</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Text block style={{ color: tokens.colorPaletteRedForeground1 }}>
|
||||||
|
注意:重置Identity_token后,你将以一个全新的匿名身份继续使用本站,你将无法再修改或删除使用旧 identity 发布的内容!
|
||||||
|
</Text>
|
||||||
|
<div style={{ marginTop: tokens.spacingVerticalS }}>
|
||||||
|
<Text block>当前Identity_token:</Text>
|
||||||
|
<div style={{ display: 'flex', gap: tokens.spacingHorizontalS, alignItems: 'center', marginTop: tokens.spacingVerticalXS }}>
|
||||||
|
<Input
|
||||||
|
type={identityVisible ? 'text' : 'password'}
|
||||||
|
value={identityToken}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
appearance="secondary"
|
||||||
|
icon={identityVisible ? <EyeOff24Regular /> : <Eye24Regular />}
|
||||||
|
onClick={() => setIdentityVisible(v => !v)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
appearance="secondary"
|
||||||
|
icon={<Copy24Regular />}
|
||||||
|
disabled={!identityVisible}
|
||||||
|
onClick={() => {
|
||||||
|
if (!identityVisible || !identityToken) return;
|
||||||
|
navigator.clipboard.writeText(identityToken).catch(() => {});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
appearance="secondary"
|
||||||
|
onClick={() => setIdentityDialogOpen(false)}
|
||||||
|
>
|
||||||
|
否
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
appearance="primary"
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
const token = await reset_identity_token();
|
||||||
|
setIdentityToken(token);
|
||||||
|
setIdentityDialogOpen(false);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to reset identity token:', err);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
是
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</DialogBody>
|
||||||
|
</DialogSurface>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,6 +117,26 @@ export const get_id_token = async (): Promise<string> => {
|
|||||||
return token;
|
return token;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const notifyInvalidIdentity = () => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.dispatchEvent(new CustomEvent('identity_invalid'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePostApiCode = (json: any) => {
|
||||||
|
if (json && json.code === 2004) {
|
||||||
|
notifyInvalidIdentity();
|
||||||
|
throw new Error(json.data || 'Identity token invalid');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const reset_identity_token = async (): Promise<string> => {
|
||||||
|
const token = await fetch_id_token();
|
||||||
|
localStorage.removeItem('identity_token');
|
||||||
|
localStorage.setItem('identity_token', token);
|
||||||
|
return token;
|
||||||
|
};
|
||||||
|
|
||||||
export interface CreatePostResponse {
|
export interface CreatePostResponse {
|
||||||
code: number;
|
code: number;
|
||||||
data: any;
|
data: any;
|
||||||
@@ -156,7 +176,9 @@ export const createPost = async (content: string): Promise<CreatePostResponse> =
|
|||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await response.json();
|
const json = await response.json();
|
||||||
|
handlePostApiCode(json);
|
||||||
|
return json;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to create post:', error);
|
console.error('Failed to create post:', error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -286,6 +308,7 @@ export const postComment = async (commentData: PostCommentRequest): Promise<Post
|
|||||||
}
|
}
|
||||||
|
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
|
handlePostApiCode(json);
|
||||||
if (json.code === 1001 && json.data?.id !== undefined) {
|
if (json.code === 1001 && json.data?.id !== undefined) {
|
||||||
return { id: Number(json.data.id) };
|
return { id: Number(json.data.id) };
|
||||||
}
|
}
|
||||||
@@ -320,6 +343,7 @@ export const reportPost = async (reportData: { id: number; title: string; conten
|
|||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
|
handlePostApiCode(json);
|
||||||
if (json.code === 1001 && json.data?.id !== undefined) {
|
if (json.code === 1001 && json.data?.id !== undefined) {
|
||||||
return { id: Number(json.data.id) };
|
return { id: Number(json.data.id) };
|
||||||
}
|
}
|
||||||
@@ -346,6 +370,7 @@ export const uploadImage = async (file: File): Promise<string> => {
|
|||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
|
handlePostApiCode(json);
|
||||||
if (json.code === 1001 && typeof json.data === 'string') {
|
if (json.code === 1001 && typeof json.data === 'string') {
|
||||||
return json.data;
|
return json.data;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ const useStyles = makeStyles({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const LayoutContent = ({ toasterId, imageViewer }: { toasterId: string; imageViewer?: ReactNode }) => {
|
const LayoutContent = ({ toasterId, overlays }: { toasterId: string; overlays?: ReactNode }) => {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const { isDarkMode } = useLayout();
|
const { isDarkMode } = useLayout();
|
||||||
|
|
||||||
@@ -71,17 +71,17 @@ const LayoutContent = ({ toasterId, imageViewer }: { toasterId: string; imageVie
|
|||||||
</div>
|
</div>
|
||||||
<Footer />
|
<Footer />
|
||||||
<Toaster toasterId={toasterId} position="top-end" />
|
<Toaster toasterId={toasterId} position="top-end" />
|
||||||
{imageViewer}
|
{overlays}
|
||||||
</div>
|
</div>
|
||||||
</FluentProvider>
|
</FluentProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MainLayout = ({ imageViewer }: { imageViewer?: ReactNode }) => {
|
export const MainLayout = ({ overlays }: { overlays?: ReactNode }) => {
|
||||||
const toasterId = useId('toaster');
|
const toasterId = useId('toaster');
|
||||||
return (
|
return (
|
||||||
<LayoutProvider toasterId={toasterId}>
|
<LayoutProvider toasterId={toasterId}>
|
||||||
<LayoutContent toasterId={toasterId} imageViewer={imageViewer} />
|
<LayoutContent toasterId={toasterId} overlays={overlays} />
|
||||||
</LayoutProvider>
|
</LayoutProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user