Initial version
This commit is contained in:
257
public/index.html
Normal file
257
public/index.html
Normal file
@ -0,0 +1,257 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Encrypted Todo List</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>Encrypted Todo List</h1>
|
||||
</div>
|
||||
|
||||
<div id="user-setup" class="container">
|
||||
<h3 class="section-title">Create/Login User</h3>
|
||||
<input type="text" id="userId" placeholder="Enter user ID" />
|
||||
<button onclick="createUser()">Create User</button>
|
||||
</div>
|
||||
|
||||
<div id="todo-app" style="display: none">
|
||||
<div class="container">
|
||||
<h3 class="section-title">Add New Todo</h3>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
id="todoText"
|
||||
placeholder="Enter todo item"
|
||||
/>
|
||||
<button onclick="addTodo()">Add Todo</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<h3 class="section-title">Your Encrypted Todos</h3>
|
||||
<div class="toggle-container">
|
||||
<span>Show Decrypted</span>
|
||||
<label class="toggle-switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="viewToggle"
|
||||
onchange="toggleView()"
|
||||
/>
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
<span>Show Encrypted</span>
|
||||
</div>
|
||||
|
||||
<div id="todos"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentUser = null;
|
||||
let allTodos = [];
|
||||
let showEncrypted = false;
|
||||
let allUsers = [];
|
||||
|
||||
async function createUser() {
|
||||
const userId = document.getElementById("userId").value;
|
||||
if (!userId) return;
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/users", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ userId }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
currentUser = userId;
|
||||
document.getElementById("user-setup").style.display =
|
||||
"none";
|
||||
document.getElementById("todo-app").style.display =
|
||||
"block";
|
||||
loadUsers();
|
||||
loadTodos();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert(error.error);
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Error creating user: " + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadUsers() {
|
||||
try {
|
||||
const response = await fetch("/api/users");
|
||||
allUsers = await response.json();
|
||||
} catch (error) {
|
||||
console.error("Error loading users:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function addTodo() {
|
||||
const text = document.getElementById("todoText").value;
|
||||
if (!text) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/users/${currentUser}/todos`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ text }),
|
||||
},
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
document.getElementById("todoText").value = "";
|
||||
loadTodos();
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Error adding todo: " + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTodos() {
|
||||
try {
|
||||
// Get the decrypted todos
|
||||
const response = await fetch(
|
||||
`/api/users/${currentUser}/todos`,
|
||||
);
|
||||
const todos = await response.json();
|
||||
|
||||
// Get the raw encrypted todos
|
||||
const encryptedResponse = await fetch(
|
||||
`/api/users/${currentUser}/todos/encrypted`,
|
||||
);
|
||||
const encryptedTodos = await encryptedResponse.json();
|
||||
|
||||
// Create a map of encrypted todos by ID
|
||||
const encryptedMap = {};
|
||||
encryptedTodos.forEach((todo) => {
|
||||
encryptedMap[todo.id] = todo.encrypted;
|
||||
});
|
||||
|
||||
// Combine the data
|
||||
allTodos = todos.map((todo) => ({
|
||||
...todo,
|
||||
encrypted: encryptedMap[todo.id] || {
|
||||
body: "Encryption data not available",
|
||||
},
|
||||
}));
|
||||
|
||||
renderTodos();
|
||||
} catch (error) {
|
||||
console.error("Error loading todos:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function renderTodos() {
|
||||
const todosDiv = document.getElementById("todos");
|
||||
|
||||
if (allTodos.length === 0) {
|
||||
todosDiv.innerHTML = "<p>No todos yet. Add one above.</p>";
|
||||
return;
|
||||
}
|
||||
|
||||
todosDiv.innerHTML = allTodos
|
||||
.map(
|
||||
(todo) => `
|
||||
<div class="todo-item">
|
||||
<div class="todo-content">
|
||||
${
|
||||
showEncrypted
|
||||
? `<div class="encrypted-data">
|
||||
<strong>Type:</strong> ${todo.encrypted.type}<br>
|
||||
<strong>Encrypted Data:</strong><br>${todo.encrypted.body}
|
||||
</div>`
|
||||
: `<div>
|
||||
<span>${todo.text}</span>
|
||||
${todo.sharedBy ? `<span class="shared-badge">Shared by ${todo.sharedBy}</span>` : ""}
|
||||
</div>`
|
||||
}
|
||||
</div>
|
||||
<div class="todo-actions">
|
||||
${
|
||||
!todo.sharedBy
|
||||
? `
|
||||
<div class="share-container">
|
||||
<select id="share-${todo.id}" class="share-select">
|
||||
<option value="">Share with...</option>
|
||||
${allUsers
|
||||
.filter((user) => user !== currentUser)
|
||||
.map(
|
||||
(user) =>
|
||||
`<option value="${user}">${user}</option>`,
|
||||
)
|
||||
.join("")}
|
||||
</select>
|
||||
<button onclick="shareTodo('${todo.id}')">Share</button>
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
<button onclick="deleteTodo('${todo.id}')">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
function toggleView() {
|
||||
showEncrypted = document.getElementById("viewToggle").checked;
|
||||
renderTodos();
|
||||
}
|
||||
|
||||
async function shareTodo(todoId) {
|
||||
const selectElement = document.getElementById(
|
||||
`share-${todoId}`,
|
||||
);
|
||||
const recipientId = selectElement.value;
|
||||
|
||||
if (!recipientId) {
|
||||
alert("Please select a user to share with");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/users/${currentUser}/todos/${todoId}/share`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ recipientId }),
|
||||
},
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
alert(`Todo shared with ${recipientId}`);
|
||||
selectElement.value = "";
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert(error.error);
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Error sharing todo: " + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteTodo(todoId) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/users/${currentUser}/todos/${todoId}`,
|
||||
{ method: "DELETE" },
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
loadTodos();
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Error deleting todo: " + error.message);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
153
public/styles.css
Normal file
153
public/styles.css
Normal file
@ -0,0 +1,153 @@
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.todo-item {
|
||||
background: #f5f5f5;
|
||||
margin: 10px 0;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.todo-content {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.todo-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 8px 12px;
|
||||
margin: 5px;
|
||||
background-color: #4a6ea9;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #3a5a89;
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
width: 300px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.toggle-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
height: 34px;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.toggle-switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ccc;
|
||||
transition: 0.4s;
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
transition: 0.4s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: #4a6ea9;
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
.encrypted-data {
|
||||
font-family: monospace;
|
||||
word-break: break-all;
|
||||
color: #666;
|
||||
font-size: 0.9em;
|
||||
background-color: #f0f0f0;
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
max-height: 100px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
margin-right: 10px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.share-container {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.shared-badge {
|
||||
background-color: #4a6ea9;
|
||||
color: white;
|
||||
padding: 3px 8px;
|
||||
border-radius: 10px;
|
||||
font-size: 0.8em;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.container {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 10px;
|
||||
margin-top: 0;
|
||||
}
|
Reference in New Issue
Block a user