diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/AuthService.cs b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/AuthService.cs
index 12db705b..c5e74eef 100644
--- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/AuthService.cs
+++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/AuthService.cs
@@ -1,5 +1,4 @@
using System.Net;
-using System.Net.Http;
using System.Net.Http.Json;
namespace EnvelopeGenerator.Server.Client.Services;
@@ -10,6 +9,7 @@ public enum SenderLoginResult { Success, InvalidCredentials, Error }
public class AuthService(IHttpClientFactory httpClientFactory)
{
+ private HttpClient CreateDefaultClient() => httpClientFactory.CreateClient("EnvelopeGenerator.Server");
///
/// Checks whether the current user holds a valid receiver token for the given envelope key.
@@ -17,7 +17,7 @@ public class AuthService(IHttpClientFactory httpClientFactory)
///
public async Task CheckEnvelopeAccessAsync(string envelopeKey, CancellationToken cancel = default)
{
- using var http = httpClientFactory.CreateClient("EnvelopeGenerator.Server");
+ using var http = CreateDefaultClient();
var response = await http.GetAsync($"/api/auth/check/envelope/{Uri.EscapeDataString(envelopeKey)}", cancel);
return response.StatusCode == HttpStatusCode.OK;
}
@@ -29,9 +29,11 @@ public class AuthService(IHttpClientFactory httpClientFactory)
///
public async Task LoginEnvelopeReceiverAsync(string envelopeKey, string accessCode, CancellationToken cancel = default)
{
- using var http = httpClientFactory.CreateClient("EnvelopeGenerator.Server");
- var form = new MultipartFormDataContent();
- form.Add(new StringContent(accessCode), "AccessCode");
+ using var http = CreateDefaultClient();
+ var form = new MultipartFormDataContent
+ {
+ { new StringContent(accessCode), "AccessCode" }
+ };
var response = await http.PostAsync(
$"/api/Auth/envelope-receiver/{Uri.EscapeDataString(envelopeKey)}",
@@ -52,7 +54,7 @@ public class AuthService(IHttpClientFactory httpClientFactory)
///
public async Task LogoutEnvelopeReceiverAsync(string envelopeKey, CancellationToken cancel = default)
{
- using var http = httpClientFactory.CreateClient("EnvelopeGenerator.Server");
+ using var http = CreateDefaultClient();
var response = await http.PostAsync(
$"/api/auth/logout/envelope/{Uri.EscapeDataString(envelopeKey)}",
null, cancel);
@@ -66,7 +68,7 @@ public class AuthService(IHttpClientFactory httpClientFactory)
///
public async Task LoginSenderAsync(string username, string password, CancellationToken cancel = default)
{
- using var http = httpClientFactory.CreateClient("EnvelopeGenerator.Server");
+ using var http = CreateDefaultClient();
var requestBody = new { username, password };
var response = await http.PostAsJsonAsync(
diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/CultureService.cs b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/CultureService.cs
new file mode 100644
index 00000000..a7b6a7b6
--- /dev/null
+++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/CultureService.cs
@@ -0,0 +1,74 @@
+using System.Globalization;
+using Microsoft.JSInterop;
+
+namespace EnvelopeGenerator.Server.Client.Services;
+
+///
+/// Service for managing application culture/localization.
+///
+public class CultureService
+{
+ private readonly IJSRuntime _jsRuntime;
+ private const string CULTURE_KEY = "AppCulture";
+
+ public CultureService(IJSRuntime jsRuntime)
+ {
+ _jsRuntime = jsRuntime;
+ }
+
+ ///
+ /// Gets the list of supported cultures.
+ ///
+ public static CultureInfo[] SupportedCultures { get; } = new[]
+ {
+ new CultureInfo("de-DE"),
+ new CultureInfo("en-US"),
+ new CultureInfo("fr-FR")
+ };
+
+ ///
+ /// Sets the application culture and stores it in localStorage.
+ ///
+ public async Task SetCultureAsync(string culture)
+ {
+ if (!SupportedCultures.Any(c => c.Name == culture))
+ throw new ArgumentException($"Culture '{culture}' is not supported.", nameof(culture));
+
+ await _jsRuntime.InvokeVoidAsync("localStorage.setItem", CULTURE_KEY, culture);
+ }
+
+ ///
+ /// Gets the stored culture from localStorage.
+ ///
+ public async Task GetCultureAsync()
+ {
+ try
+ {
+ return await _jsRuntime.InvokeAsync("localStorage.getItem", CULTURE_KEY);
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Initializes the culture from localStorage or browser settings.
+ ///
+ public async Task InitializeCultureAsync()
+ {
+ var storedCulture = await GetCultureAsync();
+
+ if (!string.IsNullOrEmpty(storedCulture) &&
+ SupportedCultures.Any(c => c.Name == storedCulture))
+ {
+ return new CultureInfo(storedCulture);
+ }
+
+ // Fallback to browser culture or default
+ var browserCulture = CultureInfo.CurrentCulture.Name;
+ var matchedCulture = SupportedCultures.FirstOrDefault(c => c.Name == browserCulture);
+
+ return matchedCulture ?? SupportedCultures[0]; // Default to German
+ }
+}
diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/DocReceiverElementService.cs b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/DocReceiverElementService.cs
new file mode 100644
index 00000000..b64e242e
--- /dev/null
+++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/DocReceiverElementService.cs
@@ -0,0 +1,24 @@
+using System.Net.Http.Json;
+using System.Text.Json;
+using EnvelopeGenerator.Server.Client.Models;
+using EnvelopeGenerator.Server.Client.Options;
+using Microsoft.Extensions.Options;
+
+namespace EnvelopeGenerator.Server.Client.Services;
+
+public class DocReceiverElementService(HttpClient http, IOptions apiOptions)
+{
+ private static readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.Web);
+
+ public async Task> GetAsync(string envelopeKey, CancellationToken cancel = default)
+ {
+ var url = $"{apiOptions.Value.BaseUrl}/api/DocReceiverElement/{Uri.EscapeDataString(envelopeKey)}";
+ var response = await http.GetAsync(url, cancel);
+
+ if (!response.IsSuccessStatusCode)
+ throw new HttpRequestException($"Failed to retrieve signatures for envelope {envelopeKey}: {response.StatusCode} {response.ReasonPhrase}");
+
+ var result = await response.Content.ReadFromJsonAsync>(_jsonOptions, cancel);
+ return result ?? [];
+ }
+}
diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/EnvelopeService.cs b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/EnvelopeService.cs
new file mode 100644
index 00000000..1acfa669
--- /dev/null
+++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/EnvelopeService.cs
@@ -0,0 +1,72 @@
+using System.Net.Http.Json;
+using System.Text.Json;
+using EnvelopeGenerator.Server.Client.Models;
+using EnvelopeGenerator.Server.Client.Options;
+using Microsoft.AspNetCore.WebUtilities;
+using Microsoft.Extensions.Options;
+
+namespace EnvelopeGenerator.Server.Client.Services;
+
+///
+/// Retrieves s from the API.
+///
+public class EnvelopeService
+{
+ private readonly HttpClient _http;
+ private readonly ApiOptions _apiOptions;
+ private static readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.Web);
+
+ public EnvelopeService(HttpClient http, IOptions apiOptions)
+ {
+ _http = http;
+ _apiOptions = apiOptions.Value;
+ }
+
+ ///
+ /// Fetches envelopes from the API with optional filters.
+ ///
+ /// Thrown when the API request fails.
+ public async Task?> GetAsync(
+ int? id = null,
+ string? uuid = null,
+ bool? onlyActive = null,
+ bool? onlyCompleted = null,
+ CancellationToken cancel = default)
+ {
+ var baseUrl = $"{_apiOptions.BaseUrl}/api/Envelope";
+ var queryParams = new Dictionary();
+
+ if (id.HasValue)
+ {
+ queryParams["Id"] = id.Value.ToString();
+ }
+ if (!string.IsNullOrEmpty(uuid))
+ {
+ queryParams["Uuid"] = uuid;
+ }
+ if (onlyActive.HasValue)
+ {
+ queryParams["OnlyActive"] = onlyActive.Value.ToString();
+ }
+ if (onlyCompleted.HasValue)
+ {
+ queryParams["OnlyCompleted"] = onlyCompleted.Value.ToString();
+ }
+
+ var url = QueryHelpers.AddQueryString(baseUrl, queryParams);
+
+ var response = await _http.GetAsync(url, cancel);
+
+ if (!response.IsSuccessStatusCode)
+ {
+ var statusCode = (int)response.StatusCode;
+ var reasonPhrase = response.ReasonPhrase ?? "Unknown error";
+ throw new HttpRequestException(
+ $"Failed to load envelopes. Status: {statusCode} ({reasonPhrase})",
+ null,
+ response.StatusCode);
+ }
+
+ return await response.Content.ReadFromJsonAsync>(_jsonOptions, cancel);
+ }
+}