diff --git a/k-notes-frontend/Dockerfile b/k-notes-frontend/Dockerfile
index 0ad66b1..5a4d512 100644
--- a/k-notes-frontend/Dockerfile
+++ b/k-notes-frontend/Dockerfile
@@ -19,9 +19,15 @@ COPY nginx.conf /etc/nginx/conf.d/default.conf
# Create script to generate env-config.js from environment variables
RUN echo '#!/bin/sh' > /docker-entrypoint.d/40-env-config.sh && \
- echo 'echo "window.env = {" > /usr/share/nginx/html/env-config.js' >> /docker-entrypoint.d/40-env-config.sh && \
- echo 'if [ -n "$API_URL" ]; then echo " API_URL: \"$API_URL\"," >> /usr/share/nginx/html/env-config.js; fi' >> /docker-entrypoint.d/40-env-config.sh && \
- echo 'echo "};" >> /usr/share/nginx/html/env-config.js' >> /docker-entrypoint.d/40-env-config.sh && \
+ echo 'cat > /usr/share/nginx/html/env-config.js << EOF' >> /docker-entrypoint.d/40-env-config.sh && \
+ echo '// Runtime environment configuration' >> /docker-entrypoint.d/40-env-config.sh && \
+ echo '// Generated at container startup' >> /docker-entrypoint.d/40-env-config.sh && \
+ echo 'window.env = {' >> /docker-entrypoint.d/40-env-config.sh && \
+ echo 'EOF' >> /docker-entrypoint.d/40-env-config.sh && \
+ echo 'if [ -n "$API_URL" ]; then' >> /docker-entrypoint.d/40-env-config.sh && \
+ echo ' echo " API_URL: \"$API_URL\"," >> /usr/share/nginx/html/env-config.js' >> /docker-entrypoint.d/40-env-config.sh && \
+ echo 'fi' >> /docker-entrypoint.d/40-env-config.sh && \
+ echo 'echo "};\" >> /usr/share/nginx/html/env-config.js' >> /docker-entrypoint.d/40-env-config.sh && \
chmod +x /docker-entrypoint.d/40-env-config.sh
EXPOSE 80
diff --git a/k-notes-frontend/index.html b/k-notes-frontend/index.html
index 1fc05a9..f7162a7 100644
--- a/k-notes-frontend/index.html
+++ b/k-notes-frontend/index.html
@@ -7,7 +7,7 @@
-
+
K-Notes
diff --git a/k-notes-frontend/public/env-config.js b/k-notes-frontend/public/env-config.js
new file mode 100644
index 0000000..682cfcb
--- /dev/null
+++ b/k-notes-frontend/public/env-config.js
@@ -0,0 +1,5 @@
+// Default environment configuration for development
+// This file is replaced at Docker container startup with runtime values
+window.env = {
+ // API_URL will be injected by Docker, or can be overridden via localStorage
+};
diff --git a/k-notes-frontend/public/locales/de/translation.json b/k-notes-frontend/public/locales/de/translation.json
index 503103a..5321e3d 100644
--- a/k-notes-frontend/public/locales/de/translation.json
+++ b/k-notes-frontend/public/locales/de/translation.json
@@ -2,6 +2,9 @@
"{{count}} selected_one": "{{count}} ausgewählt",
"{{count}} selected_other": "{{count}} ausgewählt",
"Add a new note to your collection.": "Füge eine neue Notiz zu deiner Sammlung hinzu.",
+ "API Configuration": "API-Konfiguration",
+ "API URL reset to default. Please reload the page.": "API-URL auf Standard zurückgesetzt. Bitte Seite neu laden.",
+ "API URL updated successfully. Please reload the page.": "API-URL erfolgreich aktualisiert. Bitte Seite neu laden.",
"Archive": "Archiv",
"Archived {{count}} note_one": "{{count}} Notiz archiviert",
"Archived {{count}} note_other": "{{count}} Notizen archiviert",
@@ -9,11 +12,15 @@
"Are you sure you want to delete {{count}} note?_other": "Möchtest du wirklich {{count}} Notizen löschen?",
"Are you sure?": "Bist du sicher?",
"Backend URL": "Backend-URL",
+ "Choose your preferred language": "Wähle deine bevorzugte Sprache",
"Color": "Farbe",
"Configure the application settings.": "Konfiguriere die Anwendungseinstellungen.",
+ "Configure the backend API URL for this application": "Konfiguriere die Backend-API-URL für diese Anwendung",
"Content": "Inhalt",
"Create": "Erstellen",
"Create Note": "Notiz erstellen",
+ "Current API URL": "Aktuelle API-URL",
+ "Custom API URL": "Benutzerdefinierte API-URL",
"Data Management": "Datenverwaltung",
"Delete": "Löschen",
"Delete tag \"{{name}}\"? Notes will keep their content.": "Tag \"{{name}}\" löschen? Notizen behalten ihren Inhalt.",
@@ -29,9 +36,10 @@
"Import Data": "Daten importieren",
"Import failed": "Import fehlgeschlagen",
"Import successful. Reloading...": "Import erfolgreich. Wird neu geladen...",
- "Invalid URL": "Ungültige URL",
+ "Invalid URL format. Please enter a valid URL.": "Ungültiges URL-Format. Bitte gib eine gültige URL ein.",
"K-Notes": "K-Notes",
"Language": "Sprache",
+ "Leave empty to use the default or Docker-injected URL": "Leer lassen, um die Standard- oder Docker-injizierte URL zu verwenden",
"List View": "Listenansicht",
"New Note": "Neue Notiz",
"No archived notes yet": "Noch keine archivierten Notizen",
@@ -42,16 +50,24 @@
"Note created": "Notiz erstellt",
"Note title": "Notiztitel",
"Note updated": "Notiz aktualisiert",
- "Notes": "Notizen",
"Others": "Andere",
"Pin this note": "Diese Notiz anheften",
"Pinned": "Angeheftet",
+ "Please enter a URL": "Bitte gib eine URL ein",
+ "Reload": "Neu laden",
"Rename": "Umbenennen",
+ "Reset to Default": "Auf Standard zurücksetzen",
+ "Save": "Speichern",
"Save changes": "Änderungen speichern",
- "Saving...": "Speichern...",
+ "Saving": {
+ "": {
+ "": {
+ "": ""
+ }
+ }
+ },
"Search your notes...": "Durchsuche deine Notizen...",
"Settings": "Einstellungen",
- "Settings saved. Please refresh the page.": "Einstellungen gespeichert. Bitte aktualisiere die Seite.",
"Tag deleted": "Tag gelöscht",
"Tag renamed": "Tag umbenannt",
"Tags": "Tags",
diff --git a/k-notes-frontend/public/locales/en/translation.json b/k-notes-frontend/public/locales/en/translation.json
index 9697ac1..58311e9 100644
--- a/k-notes-frontend/public/locales/en/translation.json
+++ b/k-notes-frontend/public/locales/en/translation.json
@@ -2,6 +2,9 @@
"{{count}} selected_one": "{{count}} selected",
"{{count}} selected_other": "{{count}} selected",
"Add a new note to your collection.": "Add a new note to your collection.",
+ "API Configuration": "API Configuration",
+ "API URL reset to default. Please reload the page.": "API URL reset to default. Please reload the page.",
+ "API URL updated successfully. Please reload the page.": "API URL updated successfully. Please reload the page.",
"Archive": "Archive",
"Archived {{count}} note_one": "Archived {{count}} notes",
"Archived {{count}} note_other": "Archived {{count}} notes",
@@ -9,11 +12,15 @@
"Are you sure you want to delete {{count}} note?_other": "Are you sure you want to delete {{count}} notes?",
"Are you sure?": "Are you sure?",
"Backend URL": "Backend URL",
+ "Choose your preferred language": "Choose your preferred language",
"Color": "Color",
"Configure the application settings.": "Configure the application settings.",
+ "Configure the backend API URL for this application": "Configure the backend API URL for this application",
"Content": "Content",
"Create": "Create",
"Create Note": "Create Note",
+ "Current API URL": "Current API URL",
+ "Custom API URL": "Custom API URL",
"Data Management": "Data Management",
"Delete": "Delete",
"Delete tag \"{{name}}\"? Notes will keep their content.": "Delete tag \"{{name}}\"? Notes will keep their content.",
@@ -29,9 +36,10 @@
"Import Data": "Import Data",
"Import failed": "Import failed",
"Import successful. Reloading...": "Import successful. Reloading...",
- "Invalid URL": "Invalid URL",
+ "Invalid URL format. Please enter a valid URL.": "Invalid URL format. Please enter a valid URL.",
"K-Notes": "K-Notes",
"Language": "Language",
+ "Leave empty to use the default or Docker-injected URL": "Leave empty to use the default or Docker-injected URL",
"List View": "List View",
"New Note": "New Note",
"No archived notes yet": "No archived notes yet",
@@ -45,7 +53,11 @@
"Others": "Others",
"Pin this note": "Pin this note",
"Pinned": "Pinned",
+ "Please enter a URL": "Please enter a URL",
+ "Reload": "Reload",
"Rename": "Rename",
+ "Reset to Default": "Reset to Default",
+ "Save": "Save",
"Save changes": "Save changes",
"Saving": {
"": {
@@ -56,7 +68,6 @@
},
"Search your notes...": "Search your notes...",
"Settings": "Settings",
- "Settings saved. Please refresh the page.": "Settings saved. Please refresh the page.",
"Tag deleted": "Tag deleted",
"Tag renamed": "Tag renamed",
"Tags": "Tags",
diff --git a/k-notes-frontend/public/locales/es/translation.json b/k-notes-frontend/public/locales/es/translation.json
index 42aee1b..44a9a97 100644
--- a/k-notes-frontend/public/locales/es/translation.json
+++ b/k-notes-frontend/public/locales/es/translation.json
@@ -1,23 +1,34 @@
{
"{{count}} selected_one": "{{count}} seleccionado",
+ "{{count}} selected_many": "",
"{{count}} selected_other": "{{count}} seleccionados",
"Add a new note to your collection.": "Añade una nueva nota a tu colección.",
+ "API Configuration": "Configuración de API",
+ "API URL reset to default. Please reload the page.": "URL de API restablecido a predeterminado. Recarga la página.",
+ "API URL updated successfully. Please reload the page.": "URL de API actualizado correctamente. Recarga la página.",
"Archive": "Archivar",
"Archived {{count}} note_one": "{{count}} nota archivada",
+ "Archived {{count}} note_many": "",
"Archived {{count}} note_other": "{{count}} notas archivadas",
"Are you sure you want to delete {{count}} note?_one": "¿Estás seguro de que quieres eliminar {{count}} nota?",
+ "Are you sure you want to delete {{count}} note?_many": "",
"Are you sure you want to delete {{count}} note?_other": "¿Estás seguro de que quieres eliminar {{count}} notas?",
"Are you sure?": "¿Estás seguro?",
"Backend URL": "URL del backend",
+ "Choose your preferred language": "Elige tu idioma preferido",
"Color": "Color",
"Configure the application settings.": "Configura los ajustes de la aplicación.",
+ "Configure the backend API URL for this application": "Configura la URL de API del backend para esta aplicación",
"Content": "Contenido",
"Create": "Crear",
"Create Note": "Crear nota",
+ "Current API URL": "URL de API actual",
+ "Custom API URL": "URL de API personalizado",
"Data Management": "Gestión de datos",
"Delete": "Eliminar",
"Delete tag \"{{name}}\"? Notes will keep their content.": "¿Eliminar etiqueta \"{{name}}\"? Las notas conservarán su contenido.",
"Deleted {{count}} note_one": "{{count}} nota eliminada",
+ "Deleted {{count}} note_many": "",
"Deleted {{count}} note_other": "{{count}} notas eliminadas",
"Edit Note": "Editar nota",
"Export Data": "Exportar datos",
@@ -29,9 +40,10 @@
"Import Data": "Importar datos",
"Import failed": "Importación fallida",
"Import successful. Reloading...": "Importación exitosa. Recargando...",
- "Invalid URL": "URL inválida",
+ "Invalid URL format. Please enter a valid URL.": "Formato de URL inválido. Por favor, introduce una URL válida.",
"K-Notes": "K-Notes",
"Language": "Idioma",
+ "Leave empty to use the default or Docker-injected URL": "Dejar vacío para usar la URL predeterminada o inyectada por Docker",
"List View": "Vista de lista",
"New Note": "Nueva nota",
"No archived notes yet": "Aún no hay notas archivadas",
@@ -42,16 +54,24 @@
"Note created": "Nota creada",
"Note title": "Título de la nota",
"Note updated": "Nota actualizada",
- "Notes": "Notas",
"Others": "Otros",
"Pin this note": "Fijar esta nota",
"Pinned": "Fijadas",
+ "Please enter a URL": "Por favor, introduce una URL",
+ "Reload": "Recargar",
"Rename": "Renombrar",
+ "Reset to Default": "Restablecer a predeterminado",
+ "Save": "Guardar",
"Save changes": "Guardar cambios",
- "Saving...": "Guardando...",
+ "Saving": {
+ "": {
+ "": {
+ "": ""
+ }
+ }
+ },
"Search your notes...": "Busca tus notas...",
"Settings": "Configuración",
- "Settings saved. Please refresh the page.": "Configuración guardada. Por favor, actualiza la página.",
"Tag deleted": "Etiqueta eliminada",
"Tag renamed": "Etiqueta renombrada",
"Tags": "Etiquetas",
diff --git a/k-notes-frontend/public/locales/fr/translation.json b/k-notes-frontend/public/locales/fr/translation.json
index 9ee2788..2347601 100644
--- a/k-notes-frontend/public/locales/fr/translation.json
+++ b/k-notes-frontend/public/locales/fr/translation.json
@@ -1,23 +1,34 @@
{
"{{count}} selected_one": "{{count}} sélectionné",
+ "{{count}} selected_many": "",
"{{count}} selected_other": "{{count}} sélectionnés",
"Add a new note to your collection.": "Ajoute une nouvelle note à ta collection.",
+ "API Configuration": "Configuration de l'API",
+ "API URL reset to default. Please reload the page.": "URL de l'API réinitialisée par défaut. Veuillez recharger la page.",
+ "API URL updated successfully. Please reload the page.": "URL de l'API mise à jour avec succès. Veuillez recharger la page.",
"Archive": "Archive",
"Archived {{count}} note_one": "{{count}} note archivée",
+ "Archived {{count}} note_many": "",
"Archived {{count}} note_other": "{{count}} notes archivées",
"Are you sure you want to delete {{count}} note?_one": "Es-tu sûr de vouloir supprimer {{count}} note ?",
+ "Are you sure you want to delete {{count}} note?_many": "",
"Are you sure you want to delete {{count}} note?_other": "Es-tu sûr de vouloir supprimer {{count}} notes ?",
"Are you sure?": "Es-tu sûr ?",
"Backend URL": "URL du backend",
+ "Choose your preferred language": "Choisis ta langue préférée",
"Color": "Couleur",
"Configure the application settings.": "Configure les paramètres de l'application.",
+ "Configure the backend API URL for this application": "Configure l'URL de l'API backend pour cette application",
"Content": "Contenu",
"Create": "Créer",
"Create Note": "Créer une note",
+ "Current API URL": "URL de l'API actuelle",
+ "Custom API URL": "URL de l'API personnalisée",
"Data Management": "Gestion des données",
"Delete": "Supprimer",
"Delete tag \"{{name}}\"? Notes will keep their content.": "Supprimer l'étiquette \"{{name}}\" ? Les notes conserveront leur contenu.",
"Deleted {{count}} note_one": "{{count}} note supprimée",
+ "Deleted {{count}} note_many": "",
"Deleted {{count}} note_other": "{{count}} notes supprimées",
"Edit Note": "Modifier la note",
"Export Data": "Exporter les données",
@@ -29,9 +40,10 @@
"Import Data": "Importer les données",
"Import failed": "Échec de l'importation",
"Import successful. Reloading...": "Importation réussie. Rechargement...",
- "Invalid URL": "URL invalide",
+ "Invalid URL format. Please enter a valid URL.": "Format d'URL invalide. Veuillez entrer une URL valide.",
"K-Notes": "K-Notes",
"Language": "Langue",
+ "Leave empty to use the default or Docker-injected URL": "Laisser vide pour utiliser l'URL par défaut ou injectée par Docker",
"List View": "Vue en liste",
"New Note": "Nouvelle note",
"No archived notes yet": "Pas encore de notes archivées",
@@ -42,16 +54,24 @@
"Note created": "Note créée",
"Note title": "Titre de la note",
"Note updated": "Note mise à jour",
- "Notes": "Notes",
"Others": "Autres",
"Pin this note": "Épingler cette note",
"Pinned": "Épinglées",
+ "Please enter a URL": "Veuillez entrer une URL",
+ "Reload": "Recharger",
"Rename": "Renommer",
+ "Reset to Default": "Réinitialiser par défaut",
+ "Save": "Enregistrer",
"Save changes": "Enregistrer les modifications",
- "Saving...": "Enregistrement...",
+ "Saving": {
+ "": {
+ "": {
+ "": ""
+ }
+ }
+ },
"Search your notes...": "Recherche dans tes notes...",
"Settings": "Paramètres",
- "Settings saved. Please refresh the page.": "Paramètres enregistrés. Veuillez actualiser la page.",
"Tag deleted": "Étiquette supprimée",
"Tag renamed": "Étiquette renommée",
"Tags": "Étiquettes",
diff --git a/k-notes-frontend/public/locales/pl/translation.json b/k-notes-frontend/public/locales/pl/translation.json
index 3f83ddc..db9f3b3 100644
--- a/k-notes-frontend/public/locales/pl/translation.json
+++ b/k-notes-frontend/public/locales/pl/translation.json
@@ -4,6 +4,9 @@
"{{count}} selected_many": "{{count}} zaznaczonych",
"{{count}} selected_other": "{{count}} zaznaczonych",
"Add a new note to your collection.": "Dodaj nową notatkę do swojej kolekcji.",
+ "API Configuration": "Konfiguracja API",
+ "API URL reset to default. Please reload the page.": "URL API zresetowano do domyślnego. Przeładuj stronę.",
+ "API URL updated successfully. Please reload the page.": "URL API zaktualizowano pomyślnie. Przeładuj stronę.",
"Archive": "Archiwum",
"Archived {{count}} note_one": "Zarchiwizowano {{count}} notatkę",
"Archived {{count}} note_few": "Zarchiwizowano {{count}} notatki",
@@ -15,11 +18,15 @@
"Are you sure you want to delete {{count}} note?_other": "Czy na pewno chcesz usunąć {{count}} notatek?",
"Are you sure?": "Czy jesteś pewien?",
"Backend URL": "Adres URL backendu",
+ "Choose your preferred language": "Wybierz preferowany język",
"Color": "Kolor",
"Configure the application settings.": "Skonfiguruj ustawienia aplikacji.",
+ "Configure the backend API URL for this application": "Skonfiguruj URL API backendu dla tej aplikacji",
"Content": "Treść",
"Create": "Utwórz",
"Create Note": "Utwórz notatkę",
+ "Current API URL": "Aktualny URL API",
+ "Custom API URL": "Własny URL API",
"Data Management": "Zarządzanie danymi",
"Delete": "Usuń",
"Delete tag \"{{name}}\"? Notes will keep their content.": "Usunąć tag \"{{name}}\"? Notatki zachowają swoją treść.",
@@ -37,9 +44,10 @@
"Import Data": "Importuj dane",
"Import failed": "Import nie powiódł się",
"Import successful. Reloading...": "Import zakończony sukcesem. Przeładowywanie...",
- "Invalid URL": "Nieprawidłowy adres URL",
+ "Invalid URL format. Please enter a valid URL.": "Nieprawidłowy format URL. Podaj prawidłowy URL.",
"K-Notes": "K-Notes",
"Language": "Język",
+ "Leave empty to use the default or Docker-injected URL": "Pozostaw puste, aby użyć domyślnego lub wstrzykniętego przez Docker URL",
"List View": "Widok listy",
"New Note": "Nowa notatka",
"No archived notes yet": "Jeszcze nie ma zarchiwizowanych notatek",
@@ -53,7 +61,11 @@
"Others": "Inne",
"Pin this note": "Przypnij tę notatkę",
"Pinned": "Przypięte",
+ "Please enter a URL": "Proszę podać URL",
+ "Reload": "Przeładuj",
"Rename": "Zmień nazwę",
+ "Reset to Default": "Przywróć domyślne",
+ "Save": "Zapisz",
"Save changes": "Zapisz zmiany",
"Saving": {
"": {
@@ -64,7 +76,6 @@
},
"Search your notes...": "Szukaj swoich notatek...",
"Settings": "Ustawienia",
- "Settings saved. Please refresh the page.": "Ustawienia zapisane. Odśwież stronę.",
"Tag deleted": "Tag usunięty",
"Tag renamed": "Nazwę tagu zmieniono",
"Tags": "Tagi",
@@ -75,4 +86,4 @@
"Update": "Aktualizuj",
"work, todo, ideas": "praca, zadania, pomysły",
"Your notes will appear here. Click + to create one.": "Twoje notatki pojawią się tutaj. Kliknij +, aby utworzyć notatkę."
-}
+}
\ No newline at end of file
diff --git a/k-notes-frontend/src/App.tsx b/k-notes-frontend/src/App.tsx
index 9006712..c482d5f 100644
--- a/k-notes-frontend/src/App.tsx
+++ b/k-notes-frontend/src/App.tsx
@@ -1,5 +1,6 @@
import { Routes, Route, Navigate } from "react-router-dom";
import { ProtectedRoute, PublicRoute } from "@/components/auth-guard";
+import SettingsPage from "@/pages/settings";
import LoginPage from "@/pages/login";
import RegisterPage from "@/pages/register";
import DashboardPage from "@/pages/dashboard";
@@ -22,6 +23,7 @@ function App() {
}>
} />
} />
+ } />
diff --git a/k-notes-frontend/src/components/app-sidebar.tsx b/k-notes-frontend/src/components/app-sidebar.tsx
index 0815bde..32b8a6b 100644
--- a/k-notes-frontend/src/components/app-sidebar.tsx
+++ b/k-notes-frontend/src/components/app-sidebar.tsx
@@ -10,7 +10,6 @@ import {
SidebarMenuItem,
} from "@/components/ui/sidebar"
import { Link, useLocation, useSearchParams, useNavigate } from "react-router-dom"
-import { SettingsDialog } from "@/components/settings-dialog"
import { useState } from "react"
import { useTags, useDeleteTag, useRenameTag } from "@/hooks/use-notes"
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
@@ -149,7 +148,6 @@ function TagItem({ tag, isActive }: TagItemProps) {
export function AppSidebar() {
const location = useLocation();
const [searchParams] = useSearchParams();
- const [settingsOpen, setSettingsOpen] = useState(false);
const [tagsOpen, setTagsOpen] = useState(true);
const { t } = useTranslation();
@@ -176,9 +174,11 @@ export function AppSidebar() {
))}
- setSettingsOpen(true)} tooltip={t("Settings")}>
-
- {t("Settings")}
+
+
+
+ {t("Settings")}
+
@@ -222,7 +222,6 @@ export function AppSidebar() {
-
>
)
}
diff --git a/k-notes-frontend/src/components/settings-dialog.tsx b/k-notes-frontend/src/components/settings-dialog.tsx
index 23ad4f7..12cdddf 100644
--- a/k-notes-frontend/src/components/settings-dialog.tsx
+++ b/k-notes-frontend/src/components/settings-dialog.tsx
@@ -1,13 +1,12 @@
-import { useRef, useState, useEffect } from "react";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
-import { toast } from "sonner";
-import { api } from "@/lib/api";
import { Separator } from "@/components/ui/separator";
import { useTranslation } from "react-i18next";
import { LanguageSwitcher } from "@/components/language-switcher";
+import { useApiUrl } from "@/hooks/use-api-url";
+import { useDataManagement } from "@/hooks/use-data-management";
interface SettingsDialogProps {
open: boolean;
@@ -16,64 +15,13 @@ interface SettingsDialogProps {
}
export function SettingsDialog({ open, onOpenChange, dataManagementEnabled }: SettingsDialogProps) {
- const [url, setUrl] = useState("http://localhost:3000");
const { t } = useTranslation();
-
- useEffect(() => {
- const stored = localStorage.getItem("k_notes_api_url");
- if (stored) {
- setUrl(stored);
- }
- }, [open]);
+ const { apiUrl, setApiUrl, saveApiUrl } = useApiUrl();
+ const { fileInputRef, exportData, importData, triggerImport } = useDataManagement();
const handleSave = () => {
- try {
- // Basic validation
- new URL(url);
- // Remove trailing slash if present
- const cleanUrl = url.replace(/\/$/, "");
- localStorage.setItem("k_notes_api_url", cleanUrl);
- toast.success(t("Settings saved. Please refresh the page."));
+ if (saveApiUrl(apiUrl)) {
onOpenChange(false);
- window.location.reload();
- } catch (e) {
- toast.error(t("Invalid URL"));
- }
- };
-
- const fileInputRef = useRef(null);
-
- const handleExport = async () => {
- try {
- const blob = await api.exportData();
- const url = window.URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = `k-notes-backup-${new Date().toISOString().split('T')[0]}.json`;
- document.body.appendChild(a);
- a.click();
- window.URL.revokeObjectURL(url);
- document.body.removeChild(a);
- toast.success(t("Export successful"));
- } catch (e) {
- toast.error(t("Export failed"));
- }
- };
-
- const handleImport = async (e: React.ChangeEvent) => {
- const file = e.target.files?.[0];
- if (!file) return;
-
- try {
- const text = await file.text();
- const data = JSON.parse(text);
- await api.importData(data);
- toast.success(t("Import successful. Reloading..."));
- onOpenChange(false);
- window.location.reload();
- } catch (e) {
- console.error(e);
- toast.error(t("Import failed"));
}
};
@@ -93,8 +41,8 @@ export function SettingsDialog({ open, onOpenChange, dataManagementEnabled }: Se
setUrl(e.target.value)}
+ value={apiUrl}
+ onChange={(e) => setApiUrl(e.target.value)}
className="col-span-3"
placeholder="http://localhost:3000"
/>
@@ -115,10 +63,10 @@ export function SettingsDialog({ open, onOpenChange, dataManagementEnabled }: Se
-
diff --git a/k-notes-frontend/src/hooks/use-api-url.ts b/k-notes-frontend/src/hooks/use-api-url.ts
new file mode 100644
index 0000000..6217875
--- /dev/null
+++ b/k-notes-frontend/src/hooks/use-api-url.ts
@@ -0,0 +1,63 @@
+import { useState, useEffect } from "react";
+import { getBaseUrl } from "@/lib/api";
+import { toast } from "sonner";
+import { useTranslation } from "react-i18next";
+
+export function useApiUrl() {
+ const { t } = useTranslation();
+ const [apiUrl, setApiUrl] = useState("");
+ const [currentApiUrl, setCurrentApiUrl] = useState("");
+
+ useEffect(() => {
+ const url = getBaseUrl();
+ setCurrentApiUrl(url);
+ setApiUrl(localStorage.getItem("k_notes_api_url") || "");
+ }, []);
+
+ const saveApiUrl = (url: string) => {
+ const trimmedUrl = url.trim();
+
+ if (!trimmedUrl) {
+ toast.error(t("Please enter a URL"));
+ return false;
+ }
+
+ try {
+ new URL(trimmedUrl);
+ const cleanUrl = trimmedUrl.replace(/\/$/, "");
+ localStorage.setItem("k_notes_api_url", cleanUrl);
+ toast.success(t("API URL updated successfully. Please reload the page."), {
+ action: {
+ label: t("Reload"),
+ onClick: () => window.location.reload(),
+ },
+ });
+ setCurrentApiUrl(cleanUrl);
+ return true;
+ } catch {
+ toast.error(t("Invalid URL format. Please enter a valid URL."));
+ return false;
+ }
+ };
+
+ const resetApiUrl = () => {
+ localStorage.removeItem("k_notes_api_url");
+ setApiUrl("");
+ const defaultUrl = window.env?.API_URL || "http://localhost:3000";
+ setCurrentApiUrl(defaultUrl);
+ toast.success(t("API URL reset to default. Please reload the page."), {
+ action: {
+ label: t("Reload"),
+ onClick: () => window.location.reload(),
+ },
+ });
+ };
+
+ return {
+ apiUrl,
+ setApiUrl,
+ currentApiUrl,
+ saveApiUrl,
+ resetApiUrl,
+ };
+}
diff --git a/k-notes-frontend/src/hooks/use-data-management.ts b/k-notes-frontend/src/hooks/use-data-management.ts
new file mode 100644
index 0000000..4e304a1
--- /dev/null
+++ b/k-notes-frontend/src/hooks/use-data-management.ts
@@ -0,0 +1,53 @@
+import { useRef } from "react";
+import { api } from "@/lib/api";
+import { toast } from "sonner";
+import { useTranslation } from "react-i18next";
+
+export function useDataManagement() {
+ const { t } = useTranslation();
+ const fileInputRef = useRef(null);
+
+ const exportData = async () => {
+ try {
+ const blob = await api.exportData();
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `k-notes-backup-${new Date().toISOString().split('T')[0]}.json`;
+ document.body.appendChild(a);
+ a.click();
+ window.URL.revokeObjectURL(url);
+ document.body.removeChild(a);
+ toast.success(t("Export successful"));
+ } catch (e) {
+ toast.error(t("Export failed"));
+ }
+ };
+
+ const importData = async (e: React.ChangeEvent) => {
+ const file = e.target.files?.[0];
+ if (!file) return;
+
+ try {
+ const text = await file.text();
+ const data = JSON.parse(text);
+ await api.importData(data);
+ toast.success(t("Import successful. Reloading..."));
+ setTimeout(() => window.location.reload(), 1000);
+ } catch (e) {
+ console.error(e);
+ toast.error(t("Import failed"));
+ }
+ };
+
+ const triggerImport = () => {
+ fileInputRef.current?.click();
+ };
+
+ return {
+ fileInputRef,
+ exportData,
+ importData,
+ triggerImport,
+ };
+}
diff --git a/k-notes-frontend/src/pages/settings.tsx b/k-notes-frontend/src/pages/settings.tsx
new file mode 100644
index 0000000..e4afdee
--- /dev/null
+++ b/k-notes-frontend/src/pages/settings.tsx
@@ -0,0 +1,125 @@
+import { useTranslation } from "react-i18next";
+import { useApiUrl } from "@/hooks/use-api-url";
+import { useDataManagement } from "@/hooks/use-data-management";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { LanguageSwitcher } from "@/components/language-switcher";
+import { Settings as SettingsIcon, RotateCcw, Save, Download, Upload } from "lucide-react";
+
+export default function SettingsPage() {
+ const { t } = useTranslation();
+ const { apiUrl, setApiUrl, currentApiUrl, saveApiUrl, resetApiUrl } = useApiUrl();
+ const { fileInputRef, exportData, importData, triggerImport } = useDataManagement();
+
+ return (
+
+
+
+
{t("Settings")}
+
+
+ {/* API Configuration */}
+
+
+
+ {t("API Configuration")}
+
+ {t("Configure the backend API URL for this application")}
+
+
+
+
+
+
+ {currentApiUrl}
+
+
+
+
+
+
setApiUrl(e.target.value)}
+ className="font-mono"
+ />
+
+ {t("Leave empty to use the default or Docker-injected URL")}
+
+
+
+
+ saveApiUrl(apiUrl)} className="flex items-center gap-2">
+
+ {t("Save")}
+
+
+
+ {t("Reset to Default")}
+
+
+
+
+
+ {/* Language Settings */}
+
+
+ {t("Language")}
+
+ {t("Choose your preferred language")}
+
+
+
+
+
+
+
+ {/* Data Management */}
+
+
+ {t("Data Management")}
+
+ {t("Export your notes for backup or import from a JSON file.")}
+
+
+
+
+
+
+ {t("Export Data")}
+
+
+
+ {t("Import Data")}
+
+
+
+
+
+
+ );
+}