force rewrited code

This commit is contained in:
Krzysztof Cieślik
2026-04-15 17:38:49 +02:00
parent 6bbb24e633
commit 9a7cf8518d
16 changed files with 3167 additions and 109 deletions

18
frontend/Dockerfile Normal file
View 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"]

View File

@@ -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",

View File

@@ -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",

View File

@@ -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 />