feat(frontend): i18n expansion, admin/profile i18n, grid/list view fix, empty state

- Add 5 new locales (hi, ar, ru, ja, ko) — now 14 total
- Admin panel: 117 i18n keys, confirm modal, animated tabs, no inline handlers
- Profile page: 58 i18n keys with data-i18n attributes
- Fix i18n safeT() shadowing bug and translationsLoaded timing
- Fix grid/list view: list header no longer shows in grid mode on login
- Fix classList.toggle hidden sync for view switching across all nav functions
- Revert .hidden important that broke login page rendering
- Add files empty state (no_files + empty_hint) with translations
- Fix language selector dropdown scroll and styling
- Fix admin panel scroll with sticky tabs
This commit is contained in:
Diocrafts
2026-03-09 00:08:34 +01:00
parent f409a9edd7
commit df336da679
43 changed files with 6645 additions and 2043 deletions
+8 -8
View File
@@ -11,7 +11,7 @@ use crate::application::dtos::search_dto::SearchCriteriaDto;
use crate::application::ports::inbound::SearchUseCase;
use crate::application::ports::storage_ports::StorageUsagePort;
use crate::common::di::AppState;
use crate::interfaces::middleware::auth::CurrentUser;
use crate::interfaces::middleware::auth::AuthUser;
/// Build an OCS success response with the given statuscode and data.
fn ocs_ok(statuscode: u16, data: serde_json::Value) -> serde_json::Value {
@@ -45,7 +45,7 @@ pub async fn handle_capabilities_v2(State(state): State<Arc<AppState>>) -> Respo
Json(payload).into_response()
}
pub async fn handle_user_info(State(state): State<Arc<AppState>>, user: CurrentUser) -> Response {
pub async fn handle_user_info(State(state): State<Arc<AppState>>, user: AuthUser) -> Response {
let quota: (i64, i64) = match state.storage_usage_service.as_ref() {
Some(service) => match service.get_user_storage_info(user.id).await {
Ok((used, total)) => (used, total),
@@ -86,7 +86,7 @@ pub async fn handle_user_info(State(state): State<Arc<AppState>>, user: CurrentU
pub async fn handle_user_provisioning_v1(
state: State<Arc<AppState>>,
path: Path<String>,
user: CurrentUser,
user: AuthUser,
) -> Response {
user_provisioning_response(state, path, user, 1).await
}
@@ -95,7 +95,7 @@ pub async fn handle_user_provisioning_v1(
pub async fn handle_user_provisioning_v2(
state: State<Arc<AppState>>,
path: Path<String>,
user: CurrentUser,
user: AuthUser,
) -> Response {
user_provisioning_response(state, path, user, 2).await
}
@@ -105,7 +105,7 @@ pub async fn handle_user_provisioning_v2(
async fn user_provisioning_response(
State(state): State<Arc<AppState>>,
Path(userid): Path<String>,
user: CurrentUser,
user: AuthUser,
ocs_version: u8,
) -> Response {
let statuscode = if ocs_version == 1 { 100 } else { 200 };
@@ -197,7 +197,7 @@ async fn user_provisioning_response(
pub async fn handle_revoke_apppassword(
State(state): State<Arc<AppState>>,
user: CurrentUser,
user: AuthUser,
headers: axum::http::HeaderMap,
) -> Response {
let nextcloud = match state.nextcloud.as_ref() {
@@ -249,7 +249,7 @@ pub async fn handle_recommendations() -> Response {
/// this endpoint and expects a well-formed OCS response rather than a 404.
pub async fn handle_sharees_search(
State(state): State<Arc<AppState>>,
user: CurrentUser,
user: AuthUser,
axum::extract::Query(params): axum::extract::Query<ShareeSearchParams>,
) -> Response {
let search = params.search.unwrap_or_default();
@@ -343,7 +343,7 @@ pub async fn handle_search(
State(state): State<Arc<AppState>>,
Path(provider_id): Path<String>,
axum::extract::Query(params): axum::extract::Query<UnifiedSearchParams>,
user: CurrentUser,
user: AuthUser,
) -> Response {
// Only the "files" provider is supported
if provider_id != "files" {
+2 -2
View File
@@ -15,7 +15,7 @@ use crate::application::ports::file_ports::FileRetrievalUseCase;
use crate::application::ports::storage_ports::FileReadPort;
use crate::application::ports::thumbnail_ports::{ThumbnailPort, ThumbnailSize};
use crate::common::di::AppState;
use crate::interfaces::middleware::auth::CurrentUser;
use crate::interfaces::middleware::auth::AuthUser;
#[derive(Debug, Deserialize)]
pub struct PreviewParams {
@@ -34,7 +34,7 @@ pub struct PreviewParams {
/// - Size selection based on request dimensions and forceIcon param
pub async fn handle_preview(
State(state): State<Arc<AppState>>,
user: CurrentUser,
user: AuthUser,
Query(params): Query<PreviewParams>,
) -> impl IntoResponse {
// Parse the Nextcloud file ID — the NC app may append an instance suffix
+9 -9
View File
@@ -10,7 +10,7 @@ use axum::{
use std::sync::Arc;
use crate::common::di::AppState;
use crate::interfaces::middleware::auth::CurrentUser;
use crate::interfaces::middleware::auth::{AuthUser, CurrentUser};
use crate::interfaces::middleware::rate_limit::{RateLimiter, rate_limit_login};
use crate::interfaces::nextcloud::avatar_handler;
use crate::interfaces::nextcloud::basic_auth_middleware::basic_auth_middleware;
@@ -176,7 +176,7 @@ fn verify_url_user(url_user: &str, auth_user: &CurrentUser) -> Result<(), Respon
async fn handle_dav_files(
State(state): State<Arc<AppState>>,
Path((url_user, subpath)): Path<(String, String)>,
user_ext: CurrentUser,
user_ext: AuthUser,
req: Request<Body>,
) -> Result<Response, Response> {
verify_url_user(&url_user, &user_ext)?;
@@ -188,7 +188,7 @@ async fn handle_dav_files(
async fn handle_dav_files_root(
State(state): State<Arc<AppState>>,
Path(url_user): Path<String>,
user_ext: CurrentUser,
user_ext: AuthUser,
req: Request<Body>,
) -> Result<Response, Response> {
verify_url_user(&url_user, &user_ext)?;
@@ -200,7 +200,7 @@ async fn handle_dav_files_root(
async fn handle_dav_uploads(
State(state): State<Arc<AppState>>,
Path((url_user, upload_id, rest)): Path<(String, String, String)>,
user_ext: CurrentUser,
user_ext: AuthUser,
req: Request<Body>,
) -> Result<Response, Response> {
verify_url_user(&url_user, &user_ext)?;
@@ -212,7 +212,7 @@ async fn handle_dav_uploads(
async fn handle_dav_uploads_root(
State(state): State<Arc<AppState>>,
Path((url_user, upload_id)): Path<(String, String)>,
user_ext: CurrentUser,
user_ext: AuthUser,
req: Request<Body>,
) -> Result<Response, Response> {
verify_url_user(&url_user, &user_ext)?;
@@ -222,7 +222,7 @@ async fn handle_dav_uploads_root(
}
/// Legacy /remote.php/webdav/* — redirect to /remote.php/dav/files/{user}/*
async fn handle_legacy_webdav(Path(subpath): Path<String>, user_ext: CurrentUser) -> Response {
async fn handle_legacy_webdav(Path(subpath): Path<String>, user_ext: AuthUser) -> Response {
let location = format!("/remote.php/dav/files/{}/{}", user_ext.username, subpath);
Response::builder()
.status(StatusCode::MOVED_PERMANENTLY)
@@ -231,7 +231,7 @@ async fn handle_legacy_webdav(Path(subpath): Path<String>, user_ext: CurrentUser
.unwrap()
}
async fn handle_legacy_webdav_root(user_ext: CurrentUser) -> Response {
async fn handle_legacy_webdav_root(user_ext: AuthUser) -> Response {
let location = format!("/remote.php/dav/files/{}/", user_ext.username);
Response::builder()
.status(StatusCode::MOVED_PERMANENTLY)
@@ -243,7 +243,7 @@ async fn handle_legacy_webdav_root(user_ext: CurrentUser) -> Response {
async fn handle_dav_trashbin(
State(state): State<Arc<AppState>>,
Path((url_user, subpath)): Path<(String, String)>,
user_ext: CurrentUser,
user_ext: AuthUser,
req: Request<Body>,
) -> Result<Response, Response> {
verify_url_user(&url_user, &user_ext)?;
@@ -255,7 +255,7 @@ async fn handle_dav_trashbin(
async fn handle_dav_trashbin_root(
State(state): State<Arc<AppState>>,
Path(url_user): Path<String>,
user_ext: CurrentUser,
user_ext: AuthUser,
req: Request<Body>,
) -> Result<Response, Response> {
verify_url_user(&url_user, &user_ext)?;
+2 -2
View File
@@ -12,7 +12,7 @@ use std::sync::Arc;
use crate::application::ports::trash_ports::TrashUseCase;
use crate::common::di::AppState;
use crate::interfaces::errors::AppError;
use crate::interfaces::middleware::auth::CurrentUser;
use crate::interfaces::middleware::auth::{AuthUser, CurrentUser};
use crate::interfaces::nextcloud::webdav_handler::{
format_oc_id, resolve_file_id, resolve_folder_id, write_text_element,
};
@@ -25,7 +25,7 @@ const HEADER_DAV: HeaderName = HeaderName::from_static("dav");
pub async fn handle_nc_trashbin(
state: Arc<AppState>,
req: Request<Body>,
user: CurrentUser,
user: AuthUser,
subpath: String,
) -> Result<Response<Body>, AppError> {
let method = req.method().clone();
+2 -2
View File
@@ -9,7 +9,7 @@ use crate::application::ports::file_ports::{FileRetrievalUseCase, FileUploadUseC
use crate::common::di::AppState;
use crate::common::mime_detect::{filename_from_path, refine_content_type_from_file};
use crate::interfaces::errors::AppError;
use crate::interfaces::middleware::auth::CurrentUser;
use crate::interfaces::middleware::auth::{AuthUser, CurrentUser};
/// Dispatch Nextcloud chunked upload WebDAV requests.
///
@@ -21,7 +21,7 @@ use crate::interfaces::middleware::auth::CurrentUser;
pub async fn handle_nc_uploads(
state: Arc<AppState>,
req: Request<Body>,
user: CurrentUser,
user: AuthUser,
upload_id: String,
rest: String, // chunk name or ".file" or empty
) -> Result<Response<Body>, AppError> {
+2 -2
View File
@@ -22,7 +22,7 @@ use crate::application::ports::trash_ports::TrashUseCase;
use crate::common::di::AppState;
use crate::common::mime_detect::{filename_from_path, refine_content_type};
use crate::interfaces::errors::AppError;
use crate::interfaces::middleware::auth::CurrentUser;
use crate::interfaces::middleware::auth::{AuthUser, CurrentUser};
/// Extension trait to map XML write errors to `String` concisely.
trait XmlResultExt<T> {
@@ -89,7 +89,7 @@ pub fn nc_href(username: &str, subpath: &str) -> String {
pub async fn handle_nc_webdav(
state: Arc<AppState>,
req: Request<Body>,
user: CurrentUser,
user: AuthUser,
subpath: String,
) -> Result<Response<Body>, AppError> {
let method = req.method().clone();