Self-hosted, federated location sharing app and server that prioritizes user privacy and security
end-to-end-encryption location-sharing privacy self-hosted federated

oansetasnote sATN<e

Changed files
+186
app
+116
app/src/settings-page/settings.css
··· 1 + body { 2 + font-family: system-ui, sans-serif; 3 + background: #f9fafb; 4 + display: flex; 5 + align-items: center; 6 + justify-content: center; 7 + height: 100vh; 8 + margin: 0; 9 + } 10 + 11 + .card { 12 + max-width: 90%; 13 + background: white; 14 + border: 1px solid #d1d5db; 15 + border-radius: 8px; 16 + padding: 1.5rem; 17 + box-sizing: border-box; 18 + } 19 + 20 + .header { 21 + text-align: center; 22 + margin-bottom: 1.5rem; 23 + } 24 + 25 + .icon-circle { 26 + width: 64px; 27 + height: 64px; 28 + background: #dbeafe; 29 + border-radius: 50%; 30 + display: flex; 31 + align-items: center; 32 + justify-content: center; 33 + margin: 0 auto 1rem; 34 + } 35 + 36 + .icon-circle img { 37 + width: 32px; 38 + height: 32px; 39 + } 40 + 41 + h1 { 42 + font-size: 1.5rem; 43 + } 44 + 45 + p { 46 + font-size: 0.9rem; 47 + color: #6b7280; 48 + } 49 + 50 + .actions { 51 + display: flex; 52 + flex-direction: column; 53 + gap: 1rem; 54 + } 55 + 56 + label { 57 + display: block; 58 + font-size: 0.85rem; 59 + font-weight: 600; 60 + margin-bottom: 0.25rem; 61 + } 62 + 63 + input { 64 + width: 100%; 65 + padding: 0.5rem 0.75rem; 66 + border: 1px solid #d1d5db; 67 + border-radius: 4px; 68 + font-size: 0.95rem; 69 + box-sizing: border-box; 70 + } 71 + 72 + input:focus { 73 + outline: none; 74 + border-color: #2563eb; 75 + } 76 + 77 + button { 78 + width: 100%; 79 + padding: 0.6rem; 80 + font-size: 0.95rem; 81 + border-radius: 4px; 82 + cursor: pointer; 83 + /*transition: background 0.2s ease;*/ 84 + display: flex; 85 + align-items: center; 86 + justify-content: center; 87 + } 88 + 89 + .btn-primary { 90 + background: #2563eb; 91 + color: white; 92 + border: none; 93 + } 94 + 95 + .btn-primary:hover { 96 + background: #1d4ed8; 97 + } 98 + 99 + .btn-qr { 100 + background: white; 101 + gap: 0.5rem; 102 + border: 1px solid #d1d5db; 103 + } 104 + 105 + .btn-qr:hover { 106 + background: #f3f4f6; 107 + } 108 + 109 + .btn-qr img { 110 + width: 16px; 111 + height: 16px; 112 + } 113 + 114 + .hint { 115 + font-size: 0.75rem; 116 + }
+26
app/src/settings-page/settings.html
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <script type="module" src="./settings.ts"></script> 6 + <link rel="stylesheet" href="./settings.css" /> 7 + </head> 8 + 9 + <body> 10 + <div class="card"> 11 + <!-- x-data connects this element to the settingsPageState Alpine component, enabling its data (serverAddress and signupKey) and functions (signup and scanQR) to work within it :) --> 12 + <!-- TODO: make this a form instead? --> 13 + <div class="actions" x-data="settingsPageState"> 14 + <h3>Settings</h3> 15 + 16 + <button class="btn-secondary" @click="goto('home')"> 17 + Back to Home 18 + </button> 19 + 20 + <button class="btn-secondary" @click="resetStore()"> 21 + Reset Store & go to Sign Up 22 + </button> 23 + </div> 24 + </div> 25 + </body> 26 + </html>
+16
app/src/settings-page/settings.ts
··· 1 + import Alpine from "alpinejs"; 2 + import { Store } from "../utils/store.ts"; 3 + import { goto } from "../utils/tools.ts"; 4 + 5 + Alpine.data("settingsPageState", () => ({ 6 + resetStore() { 7 + Store.reset(); 8 + alert("Store reset"); 9 + goto("signup"); 10 + }, 11 + goto(newLocation: string) { 12 + goto(newLocation); 13 + }, 14 + })); 15 + 16 + Alpine.start();
+28
app/src/utils/tools.ts
··· 1 + export function goto(newLocation: string) { 2 + window.location.href = 3 + "/src/" + newLocation + "-page/" + newLocation + ".html"; 4 + } 5 + 6 + export function toggleStyle(classNames: string | string[], newClass: string) { 7 + if (typeof classNames === "string") { 8 + for ( 9 + let i = 0; 10 + i < document.getElementsByClassName(classNames).length; 11 + i++ 12 + ) { 13 + document.getElementsByClassName(classNames)[i].classList.toggle(newClass); 14 + } 15 + } else { 16 + for (let i = 0; i < classNames.length; i++) { 17 + for ( 18 + let j = 0; 19 + j < document.getElementsByClassName(classNames[i]).length; 20 + j++ 21 + ) { 22 + document 23 + .getElementsByClassName(classNames[i]) 24 + [j].classList.toggle(newClass); 25 + } 26 + } 27 + } 28 + }