force rewrited code
This commit is contained in:
18
frontend/Dockerfile
Normal file
18
frontend/Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
||||
FROM node:18-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package management files
|
||||
# Try to catch package-lock.json if it exists
|
||||
COPY package.json package-lock.json* ./
|
||||
|
||||
# Install dependencies (ignoring optional ones if they fail in alpine)
|
||||
RUN npm install
|
||||
|
||||
# Copy application source code
|
||||
COPY . .
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
# Start development server
|
||||
CMD ["npm", "start"]
|
||||
16
frontend/package-lock.json
generated
16
frontend/package-lock.json
generated
@@ -8,6 +8,7 @@
|
||||
"name": "text-editor",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.6.0",
|
||||
"@tiptap/extension-character-count": "^3.20.1",
|
||||
"@tiptap/extension-image": "^3.20.1",
|
||||
"@tiptap/extension-link": "^3.20.1",
|
||||
@@ -3378,6 +3379,21 @@
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/api": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.6.0.tgz",
|
||||
"integrity": "sha512-rqI++FWClU5I2UBp4HXFvl+sBWkdigBkxnpJDQUWttNyG7IZP4FwQGhTNL5EOw0vI8i6eSAJ5frLqO7n7jbJdg==",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.6.0",
|
||||
"npm": ">= 6.6.0",
|
||||
"yarn": ">= 1.19.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/tauri"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/core": {
|
||||
"version": "3.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.20.1.tgz",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.6.0",
|
||||
"@tiptap/extension-character-count": "^3.20.1",
|
||||
"@tiptap/extension-image": "^3.20.1",
|
||||
"@tiptap/extension-link": "^3.20.1",
|
||||
|
||||
@@ -1,12 +1,162 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import TextEditor from './TextEditor';
|
||||
import './App.css';
|
||||
|
||||
const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
|
||||
|
||||
function App() {
|
||||
const [appState, setAppState] = useState({
|
||||
loading: true,
|
||||
initialized: false,
|
||||
authenticated: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const [password, setPassword] = useState('');
|
||||
const [isRecovery, setIsRecovery] = useState(false);
|
||||
const [recoveryKey, setRecoveryKey] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
checkStatus();
|
||||
}, []);
|
||||
|
||||
const checkStatus = async () => {
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/status`);
|
||||
const data = await res.json();
|
||||
setAppState({
|
||||
...appState,
|
||||
loading: false,
|
||||
initialized: data.is_initialized,
|
||||
error: null,
|
||||
});
|
||||
} catch (err) {
|
||||
setAppState({
|
||||
...appState,
|
||||
loading: false,
|
||||
error: 'Nie można połączyć z backendem.',
|
||||
initialized: false,
|
||||
authenticated: false
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleInit = async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/init`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ password }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
setRecoveryKey(data.recovery_key);
|
||||
setAppState({ ...appState, initialized: true, error: null });
|
||||
setPassword('');
|
||||
} else {
|
||||
setAppState({ ...appState, error: data.detail || 'Błąd inicjalizacji' });
|
||||
}
|
||||
} catch (err) {
|
||||
setAppState({ ...appState, error: 'Błąd połączenia' });
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogin = async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ password, is_recovery: isRecovery }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok && data.status === 'success') {
|
||||
setAppState({ ...appState, authenticated: true, error: null });
|
||||
} else {
|
||||
// If not initialized (e.g. wiped database), reload state
|
||||
if (res.status === 404) {
|
||||
checkStatus();
|
||||
return;
|
||||
}
|
||||
setAppState({ ...appState, error: data.detail || 'Błędne hasło/klucz' });
|
||||
}
|
||||
} catch (err) {
|
||||
setAppState({ ...appState, error: 'Błąd połączenia' });
|
||||
}
|
||||
};
|
||||
|
||||
if (appState.loading) return <div style={{ padding: '20px', textAlign: 'center' }}>Ładowanie zabezpieczeń...</div>;
|
||||
|
||||
// Ekran tworzenia konta
|
||||
if (!appState.initialized) {
|
||||
return (
|
||||
<div style={{ maxWidth: '400px', margin: '100px auto', fontFamily: 'sans-serif', textAlign: 'center', padding: '20px', border: '1px solid #ccc', borderRadius: '8px' }}>
|
||||
<h2>Witamy w Archivium</h2>
|
||||
<p>Utwórz pierwsze hasło do swojego sejfu.</p>
|
||||
<form onSubmit={handleInit} style={{ display: 'flex', flexDirection: 'column', gap: '15px', marginTop: '20px' }}>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="Główne hasło (min. 8 znaków)"
|
||||
required
|
||||
minLength={8}
|
||||
style={{ padding: '10px', fontSize: '16px' }}
|
||||
/>
|
||||
<button type="submit" style={{ padding: '10px', background: '#282c34', color: '#fff', border: 'none', cursor: 'pointer', fontSize: '16px' }}>Zainicjuj Baze</button>
|
||||
</form>
|
||||
{appState.error && <p style={{ color: 'red', marginTop: '15px' }}>{appState.error}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Ekran po inicjalizacji pokazujący wygenerowany klucz odzyskiwania
|
||||
if (recoveryKey) {
|
||||
return (
|
||||
<div style={{ maxWidth: '500px', margin: '100px auto', fontFamily: 'sans-serif', textAlign: 'center', border: '2px solid red', padding: '30px', borderRadius: '8px' }}>
|
||||
<h2 style={{ color: 'red', marginTop: 0 }}>⚠️ ZAPISZ TEN KLUCZ</h2>
|
||||
<p>To jedyny moment, w którym go widzisz. Użyjesz go do odzyskania dostępu, jeśli zapomnisz hasła.</p>
|
||||
<code style={{ display: 'block', padding: '15px', background: '#eee', fontSize: '18px', margin: '20px 0', wordBreak: 'break-all' }}>
|
||||
{recoveryKey}
|
||||
</code>
|
||||
<button onClick={() => setRecoveryKey(null)} style={{ padding: '10px 20px', background: '#282c34', color: '#fff', cursor: 'pointer', border: 'none', fontSize: '16px' }}>Zapisany, przejdź do logowania</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Ekran logowania
|
||||
if (!appState.authenticated) {
|
||||
return (
|
||||
<div style={{ maxWidth: '400px', margin: '100px auto', fontFamily: 'sans-serif', textAlign: 'center', padding: '20px', border: '1px solid #ccc', borderRadius: '8px' }}>
|
||||
<h2>Logowanie</h2>
|
||||
<p style={{ color: '#666', marginBottom: '20px' }}>Wprowadź hasło aby odblokować sejf.</p>
|
||||
<form onSubmit={handleLogin} style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>
|
||||
<input
|
||||
type={isRecovery ? "text" : "password"}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder={isRecovery ? "Wklej klucz odzyskiwania" : "Twoje hasło"}
|
||||
required
|
||||
style={{ padding: '10px', fontSize: '16px' }}
|
||||
/>
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: '10px', justifyContent: 'center', cursor: 'pointer' }}>
|
||||
<input type="checkbox" checked={isRecovery} onChange={(e) => setIsRecovery(e.target.checked)} />
|
||||
Zaloguj kluczem odzyskiwania
|
||||
</label>
|
||||
<button type="submit" style={{ padding: '10px', background: '#282c34', color: '#fff', border: 'none', cursor: 'pointer', fontSize: '16px' }}>Odblokuj</button>
|
||||
</form>
|
||||
{appState.error && <p style={{ color: 'red', marginTop: '15px' }}>{appState.error}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Główny widok (Text Editor) - po zalogowaniu
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header" style={{ minHeight: 'auto', padding: '20px', backgroundColor: '#282c34', color: 'white' }}>
|
||||
<h1>Edytor tekstowy</h1>
|
||||
<header className="App-header" style={{ minHeight: 'auto', padding: '15px 30px', backgroundColor: '#282c34', color: 'white', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<h1 style={{ margin: 0, fontSize: '22px' }}>Archivium</h1>
|
||||
<button onClick={() => { setAppState({...appState, authenticated: false}); setPassword(''); }} style={{ padding: '8px 15px', background: '#dc3545', color: '#fff', border: 'none', borderRadius: '4px', cursor: 'pointer' }}>Wyloguj</button>
|
||||
</header>
|
||||
<main style={{ padding: '20px' }}>
|
||||
<TextEditor />
|
||||
|
||||
Reference in New Issue
Block a user