diff --git a/.dockerignore b/.dockerignore index 45b7c22b..444995d0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,7 @@ **/bin/ **/obj/ **/.playwright/ +.git/ .vs/ .github/ # Exclure uniquement les dossiers de sortie de compilation @@ -13,4 +14,5 @@ test/*/obj/ # Exclure les caches lourds **/.playwright/ +.git/ .vs/ diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 48d3b4d1..ae9780df 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -59,7 +59,7 @@ jobs: # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@v4 # Add any setup steps before running the `github/codeql-action/init` action. # This includes steps like installing compilers or runtimes (`actions/setup-node` diff --git a/.vscode/launch.json b/.vscode/launch.json index 76dc08d5..c374bc6b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,7 +14,7 @@ "name": "Yavsc.Org", "type": "dotnet", "request": "launch", - "projectPath": "${workspaceFolder}/src/Yavsc.Org/Yavsc.Org.csproj", + "projectPath": "${workspaceFolder}/src/Yavsc.Org/Yavsc.Org.csproj" }, { "name": "Yavsc.Blogs", diff --git a/.vscode/mcp.json b/.vscode/mcp.json deleted file mode 100644 index 7ca6ed4b..00000000 --- a/.vscode/mcp.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "servers": { - "openclaw": { - "type": "stdio", - "command": "/home/paul/.nvm/versions/node/v22.23.0/bin/node", - "args": [ - "/home/paul/Workspace/tools/openclaw-mcp-server.js" - ] - } - } -} diff --git a/.vscode/settings.json b/.vscode/settings.json index 6e22cb5e..0a4785b9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -26,17 +26,5 @@ "cSpell.language": "fr,en", "makefile.configureOnOpen": false, "search.useGlobalIgnoreFiles": true, - "search.useParentIgnoreFiles": true, - "chat.mcp.serverSampling": { - "yavsc/.vscode/mcp.json: openclaw": { - "allowedModels": [ - "copilot/auto", - "copilotcli/claude-haiku-4.5", - "copilotcli/gpt-4.1", - "copilotcli/gpt-5-mini", - "copilotcli/mai-code-1-flash-picker", - "copilotcli/gpt-5.3-codex" - ] - } - } + "search.useParentIgnoreFiles": true } diff --git a/Directory.Packages.props b/Directory.Packages.props index 021c5e53..b1ed6926 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,11 +2,20 @@ true + - - - + + + @@ -15,7 +24,7 @@ - + diff --git a/Dockerfile b/Dockerfile index 88b11683..e534f8ad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -48,7 +48,7 @@ COPY . . # (3) Source NuGet interne (Letsencrypt, certificat auto-signé côté # serveur, justifié par build privé). -RUN dotnet nuget add source https://isn.pschneider.fr/api/v3/index.json --allow-insecure-connections +RUN dotnet nuget add source https://isn.pschneider.fr/v3/index.json --allow-insecure-connections # (4) Restore RUN dotnet restore diff --git a/Dockerfile.backend b/Dockerfile.backend index 76ad9ea0..86df9ccd 100644 --- a/Dockerfile.backend +++ b/Dockerfile.backend @@ -26,7 +26,7 @@ COPY src/PostIt/PostIt.Desktop/*.csproj ./src/PostIt/PostIt.Desktop/ COPY . . # 3. Restauration des dépendances avec vos workloads actifs -RUN dotnet nuget add source https://isn.pschneider.fr/api/v3/index.json +RUN dotnet nuget add source https://isn.pschneider.fr/v3/index.json --allow-insecure-connections # 4. Restauration des dépendances pour tous les projets RUN dotnet restore diff --git a/Makefile b/Makefile index 2683c542..a4922a3d 100644 --- a/Makefile +++ b/Makefile @@ -10,8 +10,8 @@ include .env all: dotnet build --nologo -clean: - dotnet clean -c $(CONFIG) +clean: + dotnet clean src/Yavsc/bin/output/wwwroot: dotnet --project src/Yavsc.Org/Yavsc.Org.csproj publish @@ -31,7 +31,7 @@ src/Yavsc.Server/bin/$(CONFIG)/$(FRAMEWORK)/Yavsc.Server.dll: src/Yavsc/bin/$(CONFIG)/$(FRAMEWORK)/Yavsc.dll: dotnet build -p:Configuration=$(CONFIG) --project src/Yavsc.Org/Yavsc.Org.csproj -$(DESTDIR): +$(DESTDIR): mkdir $(DESTDIR) install: $(DESTDIR) diff --git a/README.md b/README.md index d1ed912a..1f1e7ce5 100644 --- a/README.md +++ b/README.md @@ -152,13 +152,6 @@ d'abord `appsettings-org.json` du serveur ; sinon, laisse-le en place. (utilisateur, mot de passe, hôte, base). Privilégier `dotnet user-secrets` ou des variables d'environnement `ASPNETCORE_*` plutôt qu'un mot de passe en clair dans le fichier. -- Au démarrage, Yavsc.Org applique automatiquement ses migrations EF - Core. Sur cette base de code, EF Core 10 peut encore lever un - `PendingModelChangesWarning` malgré des migrations et snapshots déjà - alignés ; ce faux positif est ignoré sur les contextes PostgreSQL pour - éviter un démarrage inutilement en mode dégradé. Si une erreur de - migration apparaît encore en production, elle doit être traitée comme - une vraie divergence de schéma ou de connexion. - `Smtp.*` — hôte, port, identifiants SMTP pour l'envoi d'e-mails transactionnels. - `Authentication.PayPal.*` et `Authentication.Google.*` — clés d'API diff --git a/doc/dev-tracking/client-editor-overhaul.md b/doc/dev-tracking/client-editor-overhaul.md new file mode 100644 index 00000000..0231e6aa --- /dev/null +++ b/doc/dev-tracking/client-editor-overhaul.md @@ -0,0 +1,278 @@ +# Client editor overhaul — Yavsc.Org administration + +## Goal + +Bring the OAuth2 client administration UI (`/Client/Edit/{id}` and friends) +in Yavsc.Org to feature parity with the IdentityServer8 `Client` entity +model. Today the editor only exposes a handful of scalar fields and a few +single-line inputs for collections; the bulk of the entity and its +related collections are unreachable from the UI. + +## Inventory — current state + +### Properties exposed by `Views/Client/Edit.cshtml` + +| Field | Type | Notes | +| ------------------------ | ----------- | ---------------------------------- | +| `ClientId` | string | hidden, identifier | +| `Enabled` | bool | checkbox | +| `ClientName` | string | display name | +| `FrontChannelLogoutUri` | string | only front-channel, no back-channel | +| `RedirectUris` | collection | rendered as a single text input | +| `IdentityTokenLifetime` | int | seconds | +| `AbsoluteRefreshTokenLifetime` | int | seconds | +| `ClientSecrets` | collection | rendered as a single text input | +| `AccessTokenType` | enum | dropdown (custom `SetAppTypesInputValues`) | + +### Properties of `IdentityServer8.EntityFramework.Entities.Client` **NOT** in the editor + +Core scalars (16 fields missing): + +- `Description` +- `ClientUri` +- `LogoUri` +- `RequireConsent` +- `RequirePkce` +- `RequireRequestObject` +- `RequireClientSecret` +- `AllowPlainTextPkce` +- `AllowOfflineAccess` +- `AllowRememberConsent` +- `AlwaysIncludeUserClaimsInIdToken` +- `AlwaysSendClientClaims` +- `AuthorizationCodeLifetime` +- `BackChannelLogoutUri` +- `BackChannelLogoutSessionRequired` +- `CibaLifetime` +- `ClientClaimsPrefix` +- `ConsentLifetime` +- `Created` +- `DeviceCodeLifetime` +- `EnableLocalLogin` +- `Enabled` +- `FrontChannelLogoutSessionRequired` +- `IncludeJwtId` +- `LastAccessed` +- `LogoUri` +- `NonEditable` +- `PairwiseSubjectSalt` +- `PollingInterval` +- `ProtocolType` +- `RefreshTokenExpiration` +- `RefreshTokenUsage` +- `SlidingRefreshTokenLifetime` +- `UpdateAccessTokenClaimsOnRefresh` +- `Updated` +- `UserCodeType` +- `UserSsoLifetime` + +Collections (8 missing — currently either not exposed at all, or jammed +into a single-line text input that doesn't work for an IEnumerable): + +- `AllowedGrantTypes` → `ClientGrantType` (GrantType) +- `AllowedScopes` → `ClientScope` (Scope) +- `RedirectUris` → `ClientRedirectUri` (RedirectUri) — exposed but broken +- `PostLogoutRedirectUris` → `ClientPostLogoutRedirectUri` (PostLogoutRedirectUri) +- `AllowedCorsOrigins` → `ClientCorsOrigin` (Origin) +- `IdentityProviderRestrictions` → `ClientIdPRestriction` (Provider) +- `Claims` → `ClientClaim` (Type, Value) +- `Properties` → `ClientProperty` (Key, Value) +- `ClientSecrets` → `ClientSecret` (Type, Value, Description, Created, Expiration) — exposed but broken +- `AllowedSigningAlgorithms` → scalar string collection on Client itself + +## Pages to add + +Pattern: one Razor page per collection under +`Views/Client/Edit{Collection}.cshtml`. Each page lists existing rows, +offers an "Add" form with the relevant fields, and a per-row +remove button. The main `Edit.cshtml` becomes a hub page with links +to each subpage plus the scalar fields it already has. + +| Page | Route | Form fields | +| ------------------------------------- | ------------------------------------------ | ------------------------------------------------- | +| `Edit.cshtml` | `GET /Client/Edit/{id}` (existing) | scalar fields + nav links | +| `EditRedirectUris.cshtml` | `GET /Client/EditRedirectUris/{id}` | `RedirectUri` | +| `EditPostLogoutRedirectUris.cshtml` | `GET /Client/EditPostLogoutRedirectUris/{id}` | `PostLogoutRedirectUri` | +| `EditScopes.cshtml` | `GET /Client/EditScopes/{id}` | `Scope` (with select of known scopes) | +| `EditGrantTypes.cshtml` | `GET /Client/EditGrantTypes/{id}` | `GrantType` (with select of known types) | +| `EditCorsOrigins.cshtml` | `GET /Client/EditCorsOrigins/{id}` | `Origin` | +| `EditIdPRestrictions.cshtml` | `GET /Client/EditIdPRestrictions/{id}` | `Provider` | +| `EditClaims.cshtml` | `GET /Client/EditClaims/{id}` | `Type`, `Value` | +| `EditProperties.cshtml` | `GET /Client/EditProperties/{id}` | `Key`, `Value` | +| `EditSecrets.cshtml` (replacement) | `GET /Client/EditSecrets/{id}` | `Type`, `Value`, `Description`, `Expiration` | + +Partial view `_EditableList.cshtml` factored once and consumed by all +of the above. + +## Controller actions to add + +For each collection `Foo`: + +- `GET EditFoo(int id)` — load the client, render the page +- `POST AddFoo(int id, …)` — append a row, redirect to `EditFoo` +- `POST RemoveFoo(int id, int rowId)` — delete a row, redirect + +## Verification + +- `dotnet build src/Yavsc.Org/Yavsc.Org.csproj` → 0 errors +- No tests in `Yavsc.Org.Tests` exercise the controller today (per + `find … -name "ClientController*" -not -path "*/bin/*"`). Smoke-test + by logging in as admin, hitting `/Client/Edit/1`, then each + `Edit*/1` page, and verifying the add/remove POSTs. +- Existing seed flow (`MigratePostItClientToPublic` in + `HostingExtensions.cs`) must keep working — the editor changes are + additive, not destructive. + +## Out of scope + +- Tests (no MVC test infrastructure currently exists for this controller) +- Migration of existing collection fields (the broken `RedirectUris` + text input will simply be replaced by the new subpage) +- Per-collection authorization policies (the controller is already + `[Authorize("AdministratorOnly")]`) +- Client cloning / templating / JSON import-export + +## Status + +2026-06-21 16:04 — kickoff. Inventory done. Pages not yet started. + +2026-06-21 16:11 — first delivery, **build does not compile by design** +(per Paul: "Tu peux même me laisser un travail qui ne compile +pas"). The structural work is done; the residual errors are easy +fixes Paul will do in a debug session. + +Files added (working tree, not yet committed): + +- `src/Yavsc.Org/Controllers/Administration/ClientController.Collections.cs` + — partial class with the per-collection GET / Add / Remove actions. +- `src/Yavsc.Org/Views/Client/EditRedirectUris.cshtml` +- `src/Yavsc.Org/Views/Client/EditPostLogoutRedirectUris.cshtml` +- `src/Yavsc.Org/Views/Client/EditScopes.cshtml` +- `src/Yavsc.Org/Views/Client/EditGrantTypes.cshtml` +- `src/Yavsc.Org/Views/Client/EditCorsOrigins.cshtml` +- `src/Yavsc.Org/Views/Client/EditIdPRestrictions.cshtml` +- `src/Yavsc.Org/Views/Client/EditClaims.cshtml` +- `src/Yavsc.Org/Views/Client/EditProperties.cshtml` +- `src/Yavsc.Org/Views/Client/EditSecrets.cshtml` +- `src/Yavsc.Org/Views/Client/_EditableStringList.cshtml` + — partial consumed by the single-string-field collection pages. + +Files modified: + +- `src/Yavsc.Org/Controllers/Administration/ClientController.cs` + — `class` → `partial class`; the `Edit(int id)` GET now uses + `LoadClientAsync` to load all navigations (so the new Edit.cshtml + can render counts in its nav links). +- `src/Yavsc.Org/Views/Client/Edit.cshtml` + — significantly enriched: nav links to the 9 sub-pages, all the + scalar fields split into fieldsets (Security, Logout, Tokens, + Device / CIBA, Tokens-extra), ClientId / Id hidden. + +### Known residual compile errors (4 errors total) + +Paul is fixing these in a debug session. The structure is sound; the +errors are missing properties on the `Client` entity, a Razor +nullable quirk, and a `Localizer` injection miss. + +1. `Edit.cshtml:249` — `PairwiseSubjectSalt` doesn't exist on + `IdentityServer8.EntityFramework.Entities.Client`. **Fix**: drop + the field from Edit.cshtml; IdentityServer8 likely uses a + different property name (e.g. on a related entity) or doesn't + expose it. +2. `Edit.cshtml:221` — `CibaLifetime` doesn't exist on `Client`. + **Fix**: same as above. CIBA flow may be configured elsewhere + (resource-level) or via a different property. +3. `ClientController.Collections.cs` lines 181, 217, 253, 304 — + `Localizer` is not available in the partial class. **Fix**: inject + `IStringLocalizer` via the constructor, or + inline the strings ("BothTypeAndValueRequired", "KeyRequired", + "ValueRequired", "SecretValueRequired"). +4. `EditSecrets.cshtml:44` — `s.Expiration?.ToString("u")` on a + `DateTime?`. **Fix**: just `s.Expiration?.ToString("u")` works + if you write `s.Expiration.Value.ToString("u")`, or use + `(s.Expiration is null ? "" : s.Expiration.Value.ToString("u"))`, + or `s.Expiration?.ToString("u") ?? string.Empty`. + +### Suggested next session + +Once the 4 compile errors are fixed and the pages render: + +1. Smoke test by logging in as admin, hitting `/Client/Edit/1`, + then each `Edit*/1` page, and verifying add/remove POSTs. +2. Add a confirmation prompt (or 2-step form) for Remove actions — + removing a Redirect URI is destructive and one click is too easy. +3. Wire up some collection-level validation (e.g. redirect URI must + be a valid URL) at the controller level. +4. Add tests — the project doesn't have MVC test infrastructure + today; consider adding a `Yavsc.Org.Tests` project that drives + the controller via `WebApplicationFactory`. + + +## Test bootstrap notes (session of 2026-06-21 17:00+) + +When adding new integration tests against `WebServerFixture`: + +1. **Skip `/Account/Login` roundtrip.** The fixture ships without + `MapRazorPages()` (commented out in `HostingExtensions.ConfigurePipeline`), + so `/Identity/Account/Login` is 404, and the custom + `/Account/Login` route requires a complex antiforgery dance. + Instead, build a `ClaimsPrincipal` for the test user via + `UserManager` + `IUserClaimsPrincipalFactory`, + then call `IAuthenticationService.SignInAsync` on a synthetic + `DefaultHttpContext` and replay the resulting `Set-Cookie` header + into the test `HttpClient`. See + `ClientControllerCollectionTests.IssueIdentityCookie`. + +2. **Create the `Administrator` role before assigning it.** ASP.NET + Identity stores roles in `AspNetRoles`; there is no automatic seed. + The constant name is `YavscConstants.AdminGroupName` = `"Administrator"`. + Use `RoleManager.CreateAsync(new IdentityRole("Administrator"))` + before `AddToRoleAsync`. + +3. **Use `InMemory` connection string to bypass the prod signing-cert + requirement.** `HostingExtensions.AddIdentityServer` requires a + PEM cert unless `builder.Environment.IsDevelopment()` OR + `UsesInMemoryProvider(connectionString)`. The fixture already + uses `InMemory`, so `AddDeveloperSigningCredential()` is called + automatically — but only after we wired this check in (see + commit history). + +4. **Field-name gotchas** (from disassembling HigginsSoft + IdentityServer8.EntityFramework.Entities.Client 8.0.5-preview-net9): + - `PairWiseSubjectSalt` (capital W on "Wise"), not `PairwiseSubjectSalt`. + - `CibaLifetime` and `PollingInterval` do NOT exist on `Client` in + this version. + - `ConsentLifetime` and `UserSsoLifetime` are `int?`. + +5. **`MapStaticAssets()` fails on test projects.** Calling + `MapStaticAssets()` resolves a manifest file + (`.staticwebassets.endpoints.json`) that test projects + don't produce. Skip when `WebRootPath` points at the test + assembly directory. + +6. **Routing 404 on /Client/Edit/{id} via WebServerFixture.** As of + this session, the GET endpoint returns 404 even with admin + header. The route mapping is intact + (`MapDefaultControllerRoute()`), so this is likely an MVC + convention routing issue with the + `Controllers/Administration/` subdirectory. To investigate + next session: log middleware pipeline or hit `/Client` index + first to see if any Client route resolves. + +7. **`MapStaticAssets()` is unconditional in prod, but blocks tests.** + `WebApplication.CreateBuilder` defaults `ContentRootPath` to + `AppContext.BaseDirectory`. In test runs that resolves to + `src/Yavsc.Org.Tests/bin/Debug/net10.0/`, where + `Yavsc.Org.Tests.staticwebassets.endpoints.json` doesn't exist + (it's generated only by projects with the Web SDK). The + `app.MapStaticAssets()` call inside `ConfigurePipeline` then + throws and the fixture fails to start — taking every test in + the `[Collection("Yavsc Server")]` down with it. + + This is a pre-existing fragility of the WebServerFixture that + the new test work surfaced. Fixing it cleanly requires either: + (a) moving the test project to the Web SDK so it produces its + own manifest, (b) copying the manifest at build time via an + MSBuild target, or (c) routing `MapStaticAssets` through an + assembly-resolution fallback. None attempted in this session — + recorded for next session. diff --git a/src/PostIt.Tests/FakeAuthorizingBrowser.cs b/src/PostIt.Tests/FakeAuthorizingBrowser.cs index 4748425a..10311500 100644 --- a/src/PostIt.Tests/FakeAuthorizingBrowser.cs +++ b/src/PostIt.Tests/FakeAuthorizingBrowser.cs @@ -10,7 +10,7 @@ namespace PostIt.Tests; /// URL emitted by OidcClient, extracts its state, and returns a /// BrowserResult that mimics the OIDC redirect-with-code callback. /// -/// The paired 's token endpoint accepts +/// The paired 's token endpoint accepts /// any authorization code, so we don't need to mint a real one here. /// public sealed class FakeAuthorizingBrowser diff --git a/src/PostIt.Tests/LoginPageViewModelTests.cs b/src/PostIt.Tests/LoginPageViewModelTests.cs index 7a8b579f..e469c011 100644 --- a/src/PostIt.Tests/LoginPageViewModelTests.cs +++ b/src/PostIt.Tests/LoginPageViewModelTests.cs @@ -14,7 +14,7 @@ public class LoginPageViewModelTests // short-circuits the system browser. The authority signs its // access_token with RS256; the fake browser captures the redirect // URI so the authority can complete the token exchange. - using var authority = await OIDCStubAuthority.StartAsync(); + using var authority = await OidcStubAuthority.StartAsync(); var browser = new FakeAuthorizingBrowser(authority.LoopbackRedirectUri); var settings = new PostIt.Settings @@ -96,7 +96,7 @@ public class LoginPageViewModelTests // double slash before /.well-known/openid-configuration. The // stub advertises itself without the trailing slash; OidcClient // must bridge. - using var authority = await OIDCStubAuthority.StartAsync(); + using var authority = await OidcStubAuthority.StartAsync(); var browser = new FakeAuthorizingBrowser(authority.LoopbackRedirectUri); var settings = new PostIt.Settings @@ -254,4 +254,4 @@ public class LoginPageViewModelTests "https://yavsc.example.com/.well-known/openid-configuration", vm.StatusMessage); } -} +} \ No newline at end of file diff --git a/src/PostIt.Tests/OidcStubAuthority.cs b/src/PostIt.Tests/OidcStubAuthority.cs index 3c6552fb..552db6b0 100644 --- a/src/PostIt.Tests/OidcStubAuthority.cs +++ b/src/PostIt.Tests/OidcStubAuthority.cs @@ -20,7 +20,7 @@ namespace PostIt.Tests; /// the browser intercepts the authorize redirect, the server completes /// the token exchange. /// -public sealed class OIDCStubAuthority : IAsyncDisposable, IDisposable +public sealed class OidcStubAuthority : IAsyncDisposable, IDisposable { private readonly HttpListener _listener; private readonly RSA _rsa; @@ -30,7 +30,7 @@ public sealed class OIDCStubAuthority : IAsyncDisposable, IDisposable public string Issuer { get; } public string LoopbackRedirectUri { get; } - private OIDCStubAuthority(HttpListener listener, RSA rsa, string kid, string issuer, string loopback) + private OidcStubAuthority(HttpListener listener, RSA rsa, string kid, string issuer, string loopback) { _listener = listener; _rsa = rsa; @@ -39,7 +39,7 @@ public sealed class OIDCStubAuthority : IAsyncDisposable, IDisposable LoopbackRedirectUri = loopback; } - public static async Task StartAsync() + public static async Task StartAsync() { // Pick a free loopback port. var port = GetFreePort(); @@ -53,7 +53,7 @@ public sealed class OIDCStubAuthority : IAsyncDisposable, IDisposable var rsa = RSA.Create(2048); var kid = "test-key-1"; - var authority = new OIDCStubAuthority(listener, rsa, kid, prefix.TrimEnd('/'), loopback); + var authority = new OidcStubAuthority(listener, rsa, kid, prefix.TrimEnd('/'), loopback); _ = Task.Run(() => authority.AcceptLoopAsync(authority._cts.Token)); return authority; } diff --git a/src/PostIt.Tests/SignaturePadControlTests.cs b/src/PostIt.Tests/SignaturePadControlTests.cs deleted file mode 100644 index 691ae547..00000000 --- a/src/PostIt.Tests/SignaturePadControlTests.cs +++ /dev/null @@ -1,188 +0,0 @@ -using System; -using System.Linq; -using PostIt.Controls; -using PostIt.Models; -using Xunit; - -namespace PostIt.Tests; - -/// -/// Targeted tests for and -/// . -/// -/// The control exposes internal test hooks so we can drive -/// the buffer without standing up a headless XAML tree just to -/// deliver synthetic pointer events. The headless surface is used -/// only to assert that the control's pointer handlers are wired -/// when a template is applied; see -/// . -/// -public class SignaturePadControlTests -{ - // --- SignaturePadData (pure) --------------------------------------- - - [Fact] - public void Data_empty_array_is_empty() - { - var d = new SignaturePadData(Array.Empty()); - Assert.True(d.IsEmpty); - Assert.Equal(0, d.StrokeCount); - } - - [Fact] - public void Data_single_dot_is_one_stroke_with_k_equals_one() - { - var d = new SignaturePadData(new[] { 1, 5_000, 5_000 }); - Assert.False(d.IsEmpty); - Assert.Equal(1, d.StrokeCount); - } - - [Fact] - public void Data_two_strokes_are_independent() - { - var d = new SignaturePadData(new[] - { - 2, 100, 100, 200, 200, - 1, 9_000, 9_000, - }); - Assert.Equal(2, d.StrokeCount); - } - - [Fact] - public void Data_malformed_payload_does_not_throw_on_read() - { - // k=0 at the head would underflow the walker. The reader - // short-circuits instead of throwing. - var d = new SignaturePadData(new[] { 0, 1, 2, 3 }); - Assert.Equal(0, d.StrokeCount); - } - - [Fact] - public void Data_constructor_rejects_null() - { - Assert.Throws(() => new SignaturePadData(null!)); - } - - // --- SignaturePadControl (buffer / events) ------------------------- - - [Fact] - public void New_control_has_empty_buffer() - { - var pad = new SignaturePadControl(); - Assert.Empty(pad.Strokes); - Assert.True(pad.Snapshot().IsEmpty); - } - - [Fact] - public void Snapshot_returns_a_distinct_array_each_call() - { - var pad = new SignaturePadControl(); - pad.AppendPointForTest(1_000, 2_000); - pad.AppendPointForTest(3_000, 4_000); - pad.SealStrokeForTest(); - - var first = pad.Snapshot(); - var second = pad.Snapshot(); - - // Distinct array instances — the consumer of the first - // snapshot can hold onto it after the control mutates. - Assert.NotSame(first.Strokes, second.Strokes); - // Same logical content (no mutation in between). - Assert.Equal(first.Strokes, second.Strokes); - - pad.AppendPointForTest(5_000, 6_000); - pad.SealStrokeForTest(); - - var third = pad.Snapshot(); - Assert.NotEqual(first.Strokes, third.Strokes); - } - - [Fact] - public void Clear_empties_buffer_and_raises_redraw() - { - var pad = new SignaturePadControl(); - pad.AppendPointForTest(1, 1); - pad.SealStrokeForTest(); - Assert.NotEmpty(pad.Strokes); - - int redraws = 0; - pad.RedrawRequested += (_, _) => redraws++; - pad.Clear(); - - Assert.Empty(pad.Strokes); - Assert.True(pad.Snapshot().IsEmpty); - Assert.Equal(1, redraws); - } - - [Fact] - public void SealStrokeForTest_raises_redraw() - { - var pad = new SignaturePadControl(); - int redraws = 0; - pad.RedrawRequested += (_, _) => redraws++; - pad.AppendPointForTest(1, 1); - pad.AppendPointForTest(2, 2); - pad.SealStrokeForTest(); - Assert.Equal(1, redraws); - } - - [Fact] - public void SealStrokeForTest_with_no_pending_points_is_a_no_op() - { - var pad = new SignaturePadControl(); - int redraws = 0; - pad.RedrawRequested += (_, _) => redraws++; - pad.SealStrokeForTest(); - Assert.Equal(0, redraws); - } - - [Fact] - public void Two_sealed_strokes_produce_two_length_prefixes() - { - var pad = new SignaturePadControl(); - // Stroke 0: one point. - pad.AppendPointForTest(1_000, 1_000); - pad.SealStrokeForTest(); - // Stroke 1: two points. - pad.AppendPointForTest(2_000, 2_000); - pad.AppendPointForTest(3_000, 3_000); - pad.SealStrokeForTest(); - - var s = pad.Strokes; - // Layout: [k0, x0, y0, k1, x1, y1, x2, y2] - Assert.Equal(1, s[0]); - Assert.Equal(1_000, s[1]); - Assert.Equal(1_000, s[2]); - Assert.Equal(2, s[3]); - Assert.Equal(2_000, s[4]); - Assert.Equal(2_000, s[5]); - Assert.Equal(3_000, s[6]); - Assert.Equal(3_000, s[7]); - } - - [Fact] - public void StrokeCompleted_fires_on_seal() - { - var pad = new SignaturePadControl(); - int events = 0; - pad.StrokeCompleted += (_, _) => events++; - pad.AppendPointForTest(1, 1); - pad.SealStrokeForTest(); - pad.AppendPointForTest(2, 2); - pad.SealStrokeForTest(); - Assert.Equal(2, events); - } - - [Fact] - public void StrokeCompleted_carries_a_snapshot_with_k_count() - { - var pad = new SignaturePadControl(); - SignaturePadData? captured = null; - pad.StrokeCompleted += (_, d) => captured = d; - pad.AppendPointForTest(1, 1); - pad.AppendPointForTest(2, 2); - pad.SealStrokeForTest(); - Assert.NotNull(captured); - Assert.Equal(1, captured!.StrokeCount); - } -} diff --git a/src/PostIt.Tests/SignaturePageViewModelTests.cs b/src/PostIt.Tests/SignaturePageViewModelTests.cs deleted file mode 100644 index 37f17a58..00000000 --- a/src/PostIt.Tests/SignaturePageViewModelTests.cs +++ /dev/null @@ -1,163 +0,0 @@ -using System; -using System.IO; -using System.Text.Json; -using System.Threading.Tasks; -using PostIt.Controls; -using PostIt.ViewModels; -using Xunit; - -namespace PostIt.Tests; - -/// -/// Tests for : the contract -/// between the page's view model and the . -/// The view (XAML + code-behind rendering) is not tested here — the -/// control is render-agnostic, and the rendering is plain Polyline -/// reconstruction that we'll exercise manually in PostIt.Desktop. -/// -public class SignaturePageViewModelTests -{ - [Fact] - public void Default_constructor_uses_default_dimensions() - { - var vm = new SignaturePageViewModel(); - Assert.Equal(SignaturePageViewModel.DefaultWidth, vm.Width); - Assert.Equal(SignaturePageViewModel.DefaultHeight, vm.Height); - } - - [Fact] - public void Constructor_rejects_non_positive_dimensions() - { - Assert.Throws( - () => new SignaturePageViewModel(0, 100)); - Assert.Throws( - () => new SignaturePageViewModel(100, 0)); - Assert.Throws( - () => new SignaturePageViewModel(-1, 100)); - } - - [Fact] - public void Attach_then_Detach_is_idempotent() - { - var vm = new SignaturePageViewModel(); - var pad = new SignaturePadControl(); - vm.Attach(pad); - vm.Detach(); - // Second detach is a no-op: must not throw. - vm.Detach(); - } - - [Fact] - public void Attach_rejects_null() - { - var vm = new SignaturePageViewModel(); - Assert.Throws(() => vm.Attach(null!)); - } - - [Fact] - public void StrokeCompleted_updates_status_and_counts() - { - var vm = new SignaturePageViewModel(); - var pad = new SignaturePadControl(); - vm.Attach(pad); - - // Drive the control via the test hooks so we don't depend - // on Avalonia pointer events. - pad.AppendPointForTest(1_000, 1_000); - pad.AppendPointForTest(2_000, 2_000); - pad.SealStrokeForTest(); - - Assert.Equal(1, vm.StrokeCount); - Assert.Equal(2, vm.PointCount); - Assert.Contains("1 trait", vm.StatusMessage); - } - - [Fact] - public void Clear_resets_counts_and_buffer() - { - var vm = new SignaturePageViewModel(); - var pad = new SignaturePadControl(); - vm.Attach(pad); - - pad.AppendPointForTest(1, 1); - pad.SealStrokeForTest(); - Assert.Equal(1, vm.StrokeCount); - - vm.Clear(); - - Assert.Equal(0, vm.StrokeCount); - Assert.Equal(0, vm.PointCount); - Assert.Empty(pad.Strokes); - Assert.Contains("Effacé", vm.StatusMessage); - } - - [Fact] - public async Task CaptureAsync_on_empty_buffer_reports_and_writes_nothing() - { - var vm = new SignaturePageViewModel(); - var pad = new SignaturePadControl(); - vm.Attach(pad); - - await vm.CaptureAsync(); - - Assert.Contains("Rien", vm.StatusMessage); - Assert.Null(vm.LastCapturedPath); - } - - [Fact] - public async Task CaptureAsync_writes_a_yavsc_signature_v1_file() - { - // The VM uses Environment.SpecialFolder.LocalApplicationData, - // which we cannot redirect per-call without a constructor - // seam. We test the produced file's structure rather than - // its text formatting, because System.Text.Json's pretty- - // printer is not part of the contract we're locking down. - var vm = new SignaturePageViewModel(); - var pad = new SignaturePadControl(); - vm.Attach(pad); - - pad.AppendPointForTest(1_000, 2_000); - pad.AppendPointForTest(3_000, 4_000); - pad.SealStrokeForTest(); - - await vm.CaptureAsync(); - - Assert.NotNull(vm.LastCapturedPath); - Assert.True(File.Exists(vm.LastCapturedPath!), $"file missing: {vm.LastCapturedPath}"); - - using var doc = JsonDocument.Parse(File.ReadAllText(vm.LastCapturedPath!)); - var root = doc.RootElement; - - Assert.Equal("yavsc.signature/v1", root.GetProperty("format").GetString()); - Assert.Equal(10_000, root.GetProperty("coordinateMax").GetInt32()); - Assert.Equal(1, root.GetProperty("strokeCount").GetInt32()); - - var strokes = root.GetProperty("strokes"); - Assert.Equal(JsonValueKind.Array, strokes.ValueKind); - // [k=2, x0, y0, x1, y1] - Assert.Equal(5, strokes.GetArrayLength()); - Assert.Equal(2, strokes[0].GetInt32()); // k (2 points) - Assert.Equal(1_000, strokes[1].GetInt32()); // x0 - Assert.Equal(2_000, strokes[2].GetInt32()); // y0 - Assert.Equal(3_000, strokes[3].GetInt32()); // x1 - Assert.Equal(4_000, strokes[4].GetInt32()); // y1 - } - - [Fact] - public async Task CaptureAsync_creates_directory_if_missing() - { - var vm = new SignaturePageViewModel(); - var pad = new SignaturePadControl(); - vm.Attach(pad); - pad.AppendPointForTest(1, 1); - pad.SealStrokeForTest(); - - // The directory must exist after the call (CreateDirectory - // in the VM handles this). - await vm.CaptureAsync(); - - var dir = Path.GetDirectoryName(vm.LastCapturedPath!); - Assert.NotNull(dir); - Assert.True(Directory.Exists(dir), $"directory missing: {dir}"); - } -} diff --git a/src/PostIt.Tests/YavscApiClientTests.cs b/src/PostIt.Tests/YavscApiClientTests.cs index 03e2fa3f..1617c1b6 100644 --- a/src/PostIt.Tests/YavscApiClientTests.cs +++ b/src/PostIt.Tests/YavscApiClientTests.cs @@ -20,7 +20,7 @@ namespace PostIt.Tests; /// End-to-end coverage of : silent /// refresh on a near-expiry access token, 401-driven refresh + retry, /// and persistence of the token bundle via . -/// Uses the project's for the IdP and +/// Uses the project's for the IdP and /// a tiny in-process HTTP listener for the API server side. /// public class YavscApiClientTests @@ -45,7 +45,7 @@ public class YavscApiClientTests // in-memory access token as expired and re-run a call. The // refresh path must rotate the refresh token transparently // and the API call must succeed with the new token. - using var authority = await OIDCStubAuthority.StartAsync(); + using var authority = await OidcStubAuthority.StartAsync(); using var apiServer = new StubApiServer(); await apiServer.StartAsync(); @@ -63,7 +63,7 @@ public class YavscApiClientTests var reloaded = new YavscApiClient(settings, new TokenStore(tokensPath)); var posts = await reloaded.CallAsync>( - HttpMethod.Get, "posts", TestContext.Current.CancellationToken); + HttpMethod.Get, "posts"); Assert.NotNull(posts); Assert.NotEmpty(posts); @@ -85,7 +85,7 @@ public class YavscApiClientTests { // API server returns 401 on the first request, 200 on the next. // YavscApiClient must refresh, then retry exactly once. - using var authority = await OIDCStubAuthority.StartAsync(); + using var authority = await OidcStubAuthority.StartAsync(); using var apiServer = new StubApiServer(forceFirstRequest: true); await apiServer.StartAsync(); @@ -97,7 +97,7 @@ public class YavscApiClientTests settings, authority, tokensPath); var posts = await client.CallAsync>( - HttpMethod.Get, "posts", TestContext.Current.CancellationToken); + HttpMethod.Get, "posts"); Assert.NotEmpty(posts); Assert.Equal(2, apiServer.RequestCount); @@ -125,15 +125,14 @@ public class YavscApiClientTests var client = new YavscApiClient(settings, new TokenStore(Path.Combine( Path.GetTempPath(), $"postit-tests-noop-{Guid.NewGuid():N}.json"))); - await Assert.ThrowsAsync( - () => - client.CallAsync(HttpMethod.Get, "posts", TestContext.Current.CancellationToken)); + await Assert.ThrowsAsync(() => + client.CallAsync(HttpMethod.Get, "posts")); } [Fact] public async Task HasValidSession_is_true_after_login() { - using var authority = await OIDCStubAuthority.StartAsync(); + using var authority = await OidcStubAuthority.StartAsync(); using var apiServer = new StubApiServer(); await apiServer.StartAsync(); @@ -155,7 +154,7 @@ public class YavscApiClientTests // --- helpers -------------------------------------------------------- - private static PostIt.Settings BuildSettings(OIDCStubAuthority authority, string apiBaseUrl) => new() + private static PostIt.Settings BuildSettings(OidcStubAuthority authority, string apiBaseUrl) => new() { Authentication = new AuthenticationSettings { @@ -168,7 +167,7 @@ public class YavscApiClientTests }; private static async Task LoginAndPersistAsync( - PostIt.Settings settings, OIDCStubAuthority authority, string tokensPath) + PostIt.Settings settings, OidcStubAuthority authority, string tokensPath) { var browser = new FakeAuthorizingBrowser(authority.LoopbackRedirectUri); var client = new YavscApiClient(settings, new TokenStore(tokensPath)); @@ -182,7 +181,7 @@ public class YavscApiClientTests /// /// YavscApiClient.LoginInteractiveAsync delegates to /// Platform.CreateBrowser. We can't override that static cleanly - /// from XUnit.v3, so we rebuild the call by re-routing the + /// from xunit.v3, so we rebuild the call by re-routing the /// Platform.CreateBrowser delegate for the duration of the call. /// private static async Task LoginWithBrowserAsync( @@ -215,7 +214,7 @@ public class YavscApiClientTests File.WriteAllText(tokensPath, JsonSerializer.Serialize(record)); } - // --- OIDCLoginPhase progress tests --------------------------------- + // --- OidcLoginPhase progress tests --------------------------------- /// /// Collecting Progress is documented to capture reports @@ -226,7 +225,7 @@ public class YavscApiClientTests [Fact] public async Task LoginInteractiveAsync_reports_Discovering_then_Success() { - using var authority = await OIDCStubAuthority.StartAsync(); + using var authority = await OidcStubAuthority.StartAsync(); using var apiServer = new StubApiServer(); await apiServer.StartAsync(); @@ -235,18 +234,18 @@ public class YavscApiClientTests var client = new YavscApiClient(settings, new TokenStore(tokensPath)); var browser = new FakeAuthorizingBrowser(authority.LoopbackRedirectUri); - var reported = new System.Collections.Generic.List(); - var progress = new SyncProgress(reported); + var reported = new System.Collections.Generic.List(); + var progress = new SyncProgress(reported); try { await LoginWithBrowserAsync(client, browser.CreateBrowser(), progress); // SyncProgress captures reports synchronously — no flush needed. - Assert.Contains(OIDCLoginPhase.Discovering, reported); - Assert.Contains(OIDCLoginPhase.OpeningBrowser, reported); - Assert.Contains(OIDCLoginPhase.ExchangingCode, reported); - Assert.Equal(OIDCLoginPhase.Success, Last(reported)); + Assert.Contains(OidcLoginPhase.Discovering, reported); + Assert.Contains(OidcLoginPhase.OpeningBrowser, reported); + Assert.Contains(OidcLoginPhase.ExchangingCode, reported); + Assert.Equal(OidcLoginPhase.Success, Last(reported)); } finally { @@ -257,24 +256,24 @@ public class YavscApiClientTests [Fact] public async Task LoginInteractiveAsync_reports_Error_when_browser_missing() { - using var authority = await OIDCStubAuthority.StartAsync(); + using var authority = await OidcStubAuthority.StartAsync(); using var apiServer = new StubApiServer(); await apiServer.StartAsync(); var settings = BuildSettings(authority, apiServer.BaseUrl); var client = new YavscApiClient(settings, new TokenStore(TokensPath())); - var reported = new System.Collections.Generic.List(); - var progress = new SyncProgress(reported); + var reported = new System.Collections.Generic.List(); + var progress = new SyncProgress(reported); var original = Platform.CreateBrowser; try { Platform.CreateBrowser = () => null; // simulate no browser wired up await Assert.ThrowsAsync( - () => client.LoginInteractiveAsync(progress, TestContext.Current.CancellationToken)); + () => client.LoginInteractiveAsync(progress)); // SyncProgress captures reports synchronously — no flush needed. - Assert.Equal(OIDCLoginPhase.Error, Last(reported)); + Assert.Equal(OidcLoginPhase.Error, Last(reported)); } finally { @@ -285,7 +284,7 @@ public class YavscApiClientTests [Fact] public async Task TrySilentLoginAsync_returns_false_when_no_bundle_on_disk() { - using var authority = await OIDCStubAuthority.StartAsync(); + using var authority = await OidcStubAuthority.StartAsync(); using var apiServer = new StubApiServer(); await apiServer.StartAsync(); @@ -294,7 +293,7 @@ public class YavscApiClientTests // Tokens file deliberately doesn't exist. var client = new YavscApiClient(settings, new TokenStore(tokensPath)); - var ok = await client.TrySilentLoginAsync(null, TestContext.Current.CancellationToken); + var ok = await client.TrySilentLoginAsync(); Assert.False(ok); Assert.False(client.HasValidSession); } @@ -302,7 +301,7 @@ public class YavscApiClientTests [Fact] public async Task TrySilentLoginAsync_returns_true_when_access_token_still_valid() { - using var authority = await OIDCStubAuthority.StartAsync(); + using var authority = await OidcStubAuthority.StartAsync(); using var apiServer = new StubApiServer(); await apiServer.StartAsync(); @@ -315,7 +314,7 @@ public class YavscApiClientTests { await LoginWithBrowserAsync(client, browser.CreateBrowser()); // Login fresh → access token is far from expiry. - var ok = await client.TrySilentLoginAsync(null, TestContext.Current.CancellationToken); + var ok = await client.TrySilentLoginAsync(); Assert.True(ok); Assert.True(client.HasValidSession); } @@ -328,7 +327,7 @@ public class YavscApiClientTests [Fact] public async Task TrySilentLoginAsync_returns_true_when_refresh_succeeds() { - using var authority = await OIDCStubAuthority.StartAsync(); + using var authority = await OidcStubAuthority.StartAsync(); using var apiServer = new StubApiServer(); await apiServer.StartAsync(); @@ -352,13 +351,13 @@ public class YavscApiClientTests // matches the disk: access expired, refresh still good. var client = new YavscApiClient(settings, store); - var reported = new System.Collections.Generic.List(); - var progress = new SyncProgress(reported); + var reported = new System.Collections.Generic.List(); + var progress = new SyncProgress(reported); - var ok = await client.TrySilentLoginAsync(progress, TestContext.Current.CancellationToken); + var ok = await client.TrySilentLoginAsync(progress); Assert.True(ok, "silent refresh should succeed via the stub authority."); - Assert.Contains(OIDCLoginPhase.ExchangingCode, reported); - Assert.Equal(OIDCLoginPhase.Success, Last(reported)); + Assert.Contains(OidcLoginPhase.ExchangingCode, reported); + Assert.Equal(OidcLoginPhase.Success, Last(reported)); } finally { @@ -431,7 +430,7 @@ public class YavscApiClientTests /// overload stays for tests that don't care about phase events. /// private static async Task LoginWithBrowserAsync( - YavscApiClient client, IBrowser browser, IProgress? progress = null) + YavscApiClient client, IBrowser browser, IProgress? progress = null) { var original = Platform.CreateBrowser; try diff --git a/src/PostIt/PostIt/App.axaml.cs b/src/PostIt/PostIt/App.axaml.cs index 134ae42f..65ce8b26 100644 --- a/src/PostIt/PostIt/App.axaml.cs +++ b/src/PostIt/PostIt/App.axaml.cs @@ -63,7 +63,6 @@ public partial class App : Application services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); // ViewModels services.AddSingleton(settings); @@ -73,7 +72,6 @@ public partial class App : Application services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); // Persistent session banner: one instance for the lifetime of // the app so the same VM survives page navigation. diff --git a/src/PostIt/PostIt/Controls/SignaturePadControl.cs b/src/PostIt/PostIt/Controls/SignaturePadControl.cs deleted file mode 100644 index 87949d30..00000000 --- a/src/PostIt/PostIt/Controls/SignaturePadControl.cs +++ /dev/null @@ -1,204 +0,0 @@ -using System; -using System.Collections.Generic; -using Avalonia; -using Avalonia.Controls.Primitives; -using Avalonia.Input; -using PostIt.Models; - -namespace PostIt.Controls; - -/// -/// Pointer-driven capture surface that records a signature as a list -/// of strokes, each stroke being a length-prefixed sequence of (x, y) -/// coordinates normalised to [0, CoordinateMax]. -/// -/// The control is render-agnostic: it does not draw anything. The -/// host view templates a (typically a -/// Border) as PART_CaptureArea for pointer capture, -/// and binds a separate visual layer (e.g. a Canvas) to -/// for redraw. Keeping the control headless of -/// rendering makes it usable from a headless test where no -/// composition happens. -/// -/// Wire format (see ): -/// int[] = [k0, x0, y0, ..., k1, x0, y0, ...] -/// with x, y ∈ [0, 10_000]. -/// -/// Threading: pointer events are dispatched on the UI thread, which -/// is the only thread that ever mutates . The -/// buffer is safe to read from any thread as long as no read -/// straddles a pointer event — for cross-thread transfer use -/// , which copies. -/// -public class SignaturePadControl : TemplatedControl -{ - /// - /// Styled property pointing at the - /// that receives pointer events. Set it in the control's - /// template (PART_CaptureArea). - /// - public static readonly StyledProperty CaptureAreaProperty = - AvaloniaProperty.Register(nameof(CaptureArea)); - - public InputElement? CaptureArea - { - get => GetValue(CaptureAreaProperty); - set => SetValue(CaptureAreaProperty, value); - } - - /// - /// Captured strokes in wire form. Exposed as a read-only view - /// over the internal buffer. The buffer only mutates on the UI - /// thread, between pointer events. - /// - public IReadOnlyList Strokes => _strokes; - - /// - /// Raised when the user finishes a stroke (pointer release). - /// The argument is a snapshot of the buffer at release time. - /// - public event EventHandler? StrokeCompleted; - - /// - /// Raised when the buffer changes: at the end of every stroke - /// and on . Mid-stroke points do not raise - /// this event (pointer-move is too dense); bind a separate - /// visual layer if you need a live preview. - /// - public event EventHandler? RedrawRequested; - - private readonly List _strokes = new(capacity: 256); - private int _pendingPoints; // number of (x, y) pairs awaiting a length prefix - private bool _capturing; - - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) - { - base.OnApplyTemplate(e); - - if (CaptureArea is { } previous) - { - previous.PointerPressed -= OnCapturePressed; - previous.PointerMoved -= OnCaptureMoved; - previous.PointerReleased -= OnCaptureReleased; - } - - if (CaptureArea is { } area) - { - area.PointerPressed += OnCapturePressed; - area.PointerMoved += OnCaptureMoved; - area.PointerReleased += OnCaptureReleased; - } - } - - private void OnCapturePressed(object? sender, PointerPressedEventArgs e) - { - if (!e.GetCurrentPoint(CaptureArea).Properties.IsLeftButtonPressed) return; - e.Pointer.Capture(CaptureArea); - _capturing = true; - _pendingPoints = 0; - AppendPoint(e.GetPosition(CaptureArea)); - } - - private void OnCaptureMoved(object? sender, PointerEventArgs e) - { - if (!_capturing) return; - AppendPoint(e.GetPosition(CaptureArea)); - } - - private void OnCaptureReleased(object? sender, PointerReleasedEventArgs e) - { - if (!_capturing) return; - AppendPoint(e.GetPosition(CaptureArea)); - _capturing = false; - - if (_pendingPoints == 0) - { - // Press + immediate release without movement yields no - // point at all (the press fired AppendPoint, so this - // branch is unreachable — kept for clarity if a future - // change skips the press append). - return; - } - - // Seal the current stroke by inserting its length at the - // head of its slice. The slice is the trailing - // 2 * _pendingPoints entries. - int sliceStart = _strokes.Count - 2 * _pendingPoints; - _strokes.Insert(sliceStart, _pendingPoints); - _pendingPoints = 0; - - StrokeCompleted?.Invoke(this, Snapshot()); - RedrawRequested?.Invoke(this, EventArgs.Empty); - } - - private void AppendPoint(Point p) - { - var (nx, ny) = Normalise(p); - _strokes.Add(nx); - _strokes.Add(ny); - _pendingPoints++; - } - - private (int x, int y) Normalise(Point p) - { - if (CaptureArea is null) return (0, 0); - var bounds = CaptureArea.Bounds; - double w = bounds.Width; - double h = bounds.Height; - if (w <= 0 || h <= 0) return (0, 0); - int nx = (int)Math.Round(Math.Clamp(p.X / w, 0.0, 1.0) * SignaturePadData.CoordinateMax); - int ny = (int)Math.Round(Math.Clamp(p.Y / h, 0.0, 1.0) * SignaturePadData.CoordinateMax); - return (nx, ny); - } - - /// - /// Forget every captured stroke. Raises . - /// - public void Clear() - { - _strokes.Clear(); - _pendingPoints = 0; - _capturing = false; - RedrawRequested?.Invoke(this, EventArgs.Empty); - } - - /// - /// Defensive copy of the current buffer wrapped in a - /// . Cheap; call only when the - /// view needs to ship the data off (e.g. to a backend). - /// - public SignaturePadData Snapshot() => new(_strokes.ToArray()); - - // --- Test-only surface (visible to PostIt.Tests) ------------------- - - /// - /// Test hook: append a single normalised point without going - /// through the pointer pipeline. Does not raise - /// . - /// - internal void AppendPointForTest(int x, int y) - { - _strokes.Add(x); - _strokes.Add(y); - _pendingPoints++; - } - - /// - /// Test hook: seal the currently-pending stroke with a length - /// prefix. Mirrors what does at - /// pointer release time, including the - /// and - /// events, so test scenarios observe the same notification - /// contract as production. Idempotent: a second call without - /// intermediate appends is a no-op. - /// - internal void SealStrokeForTest() - { - if (_pendingPoints == 0) return; - int sliceStart = _strokes.Count - 2 * _pendingPoints; - _strokes.Insert(sliceStart, _pendingPoints); - _pendingPoints = 0; - StrokeCompleted?.Invoke(this, Snapshot()); - RedrawRequested?.Invoke(this, EventArgs.Empty); - } -} diff --git a/src/PostIt/PostIt/Models/SignaturePadData.cs b/src/PostIt/PostIt/Models/SignaturePadData.cs deleted file mode 100644 index 11568eac..00000000 --- a/src/PostIt/PostIt/Models/SignaturePadData.cs +++ /dev/null @@ -1,103 +0,0 @@ -namespace PostIt.Models; - -/// -/// Serialized form of a signature captured by -/// . -/// -/// Wire format (length-prefixed, normalised): -/// -/// int[] = [k0, x00, y00, x01, y01, ..., x0_{k0-1}, y0_{k0-1}, -/// k1, x10, y10, x11, y11, ..., x1_{k1-1}, y1_{k1-1}, -/// ...] -/// -/// -/// k_i — number of (x, y) pairs in stroke i. -/// x, y — coordinates normalised to [0, CoordinateMax] -/// (inclusive) on the control's client area. -/// is 10_000 by default — a 4-decimal fixed-point fraction of -/// the surface, which is enough to discriminate 0.01% of the diagonal -/// on any reasonable screen and stays well inside int. -/// Total array length is even: each stroke contributes -/// 1 + 2 * k_i integers, and 1 + 2k is always odd. -/// Sum of 1 + 2k_i over strokes is therefore odd * N, which -/// is odd when N is odd and even when N is even — so the overall -/// "size pair" property is not enforced, only the per-stroke shape -/// is. If the consumer needs a strictly even total, pad the last -/// stroke with a duplicate terminal point (or use -/// to drop the array entirely). -/// -/// -/// Empty signature (no strokes) is represented by an empty array -/// (length 0). A single dot — pen down + pen up at the same point — -/// is a single stroke with k = 1: [1, x, y]. -/// -public sealed class SignaturePadData -{ - /// - /// Upper bound of normalised coordinates. 10_000 means a - /// surface unit is represented as 0.0001 of the whole. - /// - public const int CoordinateMax = 10_000; - - /// - /// Raw payload. See for the layout. - /// Never null; an empty array means "no strokes". - /// - public int[] Strokes { get; } - - public SignaturePadData(int[] strokes) - { - if (strokes is null) throw new System.ArgumentNullException(nameof(strokes)); - Strokes = strokes; - } - - /// True if no stroke has been captured. - public bool IsEmpty => Strokes.Length == 0; - - /// - /// Number of distinct strokes (pen-down / pen-up cycles). - /// Returns 0 when is true. - /// - public int StrokeCount - { - get - { - if (Strokes.Length == 0) return 0; - int n = 0; - int i = 0; - while (i < Strokes.Length) - { - int k = Strokes[i]; - // Defensive: a malformed entry is treated as 0 so we - // never throw on read. The capture side never produces - // these, this is only for robustness on the wire. - if (k <= 0) return n; - i += 1 + 2 * k; - n++; - } - return n; - } - } - - /// - /// Total number of (x, y) pairs across all strokes. Useful - /// for sanity-checks and for displaying capture density - /// without re-walking the wire format. - /// - public int PointCount - { - get - { - int n = 0; - int i = 0; - while (i < Strokes.Length) - { - int k = Strokes[i]; - if (k <= 0) break; - n += k; - i += 1 + 2 * k; - } - return n; - } - } -} diff --git a/src/PostIt/PostIt/Services/OidcLoginPhase.cs b/src/PostIt/PostIt/Services/OidcLoginPhase.cs index c4787489..0add3d76 100644 --- a/src/PostIt/PostIt/Services/OidcLoginPhase.cs +++ b/src/PostIt/PostIt/Services/OidcLoginPhase.cs @@ -12,7 +12,7 @@ namespace PostIt.Services; /// The set is deliberately small: each value is a milestone an /// operator can grep for in logs / StatusMessage, not a heartbeat. /// -public enum OIDCLoginPhase +public enum OidcLoginPhase { /// No login in flight (or login has settled). Idle, diff --git a/src/PostIt/PostIt/Services/YavscApiClient.cs b/src/PostIt/PostIt/Services/YavscApiClient.cs index 0a42773b..6710ebb8 100644 --- a/src/PostIt/PostIt/Services/YavscApiClient.cs +++ b/src/PostIt/PostIt/Services/YavscApiClient.cs @@ -91,15 +91,15 @@ public class YavscApiClient : IAsyncDisposable /// for the human /// text (URLs, error detail). public async Task LoginInteractiveAsync( - IProgress? progress = null, + IProgress? progress = null, CancellationToken ct = default) { - progress?.Report(OIDCLoginPhase.Discovering); + progress?.Report(OidcLoginPhase.Discovering); var browser = Platform.CreateBrowser?.Invoke(); if (browser is null) { - progress?.Report(OIDCLoginPhase.Error); + progress?.Report(OidcLoginPhase.Error); throw new InvalidOperationException("No browser is available on this platform."); } @@ -114,20 +114,20 @@ public class YavscApiClient : IAsyncDisposable // the moment we ask the browser to open (covers the entire // user-driven window including the AwaitingCallback wait), and // the moment we trade the code for tokens. - progress?.Report(OIDCLoginPhase.OpeningBrowser); + progress?.Report(OidcLoginPhase.OpeningBrowser); var result = await client.LoginAsync(new LoginRequest(), ct).ConfigureAwait(false); if (result.IsError) { - progress?.Report(OIDCLoginPhase.Error); + progress?.Report(OidcLoginPhase.Error); throw new InvalidOperationException($"OIDC login failed: {result.Error}"); } - progress?.Report(OIDCLoginPhase.ExchangingCode); + progress?.Report(OidcLoginPhase.ExchangingCode); if (string.IsNullOrEmpty(result.RefreshToken)) { - progress?.Report(OIDCLoginPhase.Error); + progress?.Report(OidcLoginPhase.Error); throw new InvalidOperationException( "Missing refresh_token — vérifie le scope 'offline_access'."); } @@ -139,7 +139,7 @@ public class YavscApiClient : IAsyncDisposable IdToken: result.IdentityToken); _store.Save(_tokens); - progress?.Report(OIDCLoginPhase.Success); + progress?.Report(OidcLoginPhase.Success); } /// @@ -152,7 +152,7 @@ public class YavscApiClient : IAsyncDisposable /// phase and returns false so the UI can keep going. /// public async Task TrySilentLoginAsync( - IProgress? progress = null, + IProgress? progress = null, CancellationToken ct = default) { if (!HasValidSession) return false; @@ -161,7 +161,7 @@ public class YavscApiClient : IAsyncDisposable // Access token still has plenty of life — nothing to do. if (_tokens.AccessTokenExpiresAt - DateTimeOffset.UtcNow > RefreshSkew) { - progress?.Report(OIDCLoginPhase.Success); + progress?.Report(OidcLoginPhase.Success); return true; } @@ -172,19 +172,19 @@ public class YavscApiClient : IAsyncDisposable // the user back to the login page. try { - progress?.Report(OIDCLoginPhase.ExchangingCode); + progress?.Report(OidcLoginPhase.ExchangingCode); await ForceRefreshAsync(ct).ConfigureAwait(false); - progress?.Report(OIDCLoginPhase.Success); + progress?.Report(OidcLoginPhase.Success); return true; } catch (RefreshFailedException) { - progress?.Report(OIDCLoginPhase.Idle); + progress?.Report(OidcLoginPhase.Idle); return false; } catch { - progress?.Report(OIDCLoginPhase.Idle); + progress?.Report(OidcLoginPhase.Idle); return false; } } @@ -205,16 +205,6 @@ public class YavscApiClient : IAsyncDisposable return dto!; } - /// - /// Call a JSON endpoint with no request body while still allowing a - /// positional cancellation token argument. - /// - public Task CallAsync( - HttpMethod method, - string path, - CancellationToken ct) - => CallAsync(method, path, body: null, ct); - /// Call an endpoint that returns no useful body (DELETE, etc.). public async Task CallAsync( HttpMethod method, @@ -226,16 +216,6 @@ public class YavscApiClient : IAsyncDisposable response.EnsureSuccessStatusCode(); } - /// - /// Call an endpoint with no request body while still allowing a - /// positional cancellation token argument. - /// - public Task CallAsync( - HttpMethod method, - string path, - CancellationToken ct) - => CallAsync(method, path, body: null, ct); - private async Task SendAsync( HttpMethod method, string path, object? body, CancellationToken ct) { diff --git a/src/PostIt/PostIt/ViewLocator.cs b/src/PostIt/PostIt/ViewLocator.cs index e6d0e91a..9a05aa84 100644 --- a/src/PostIt/PostIt/ViewLocator.cs +++ b/src/PostIt/PostIt/ViewLocator.cs @@ -29,7 +29,6 @@ public class ViewLocator : IDataTemplate SettingsPageViewModel => _services.GetRequiredService(), LoginPageViewModel => _services.GetRequiredService(), HomePageViewModel => _services.GetRequiredService(), - SignaturePageViewModel => _services.GetRequiredService(), _ => new TextBlock { Text = $"No view for {data.GetType().Name}" } }; } diff --git a/src/PostIt/PostIt/ViewModels/LoginPageViewModel.cs b/src/PostIt/PostIt/ViewModels/LoginPageViewModel.cs index a6ed595d..009d67f2 100644 --- a/src/PostIt/PostIt/ViewModels/LoginPageViewModel.cs +++ b/src/PostIt/PostIt/ViewModels/LoginPageViewModel.cs @@ -105,8 +105,8 @@ public partial class LoginPageViewModel : ViewModelBase /// callback hand-off: when AwaitingCallback never resolves, /// the OS never re-launched PostIt with the postit:// URL. /// - private OIDCLoginPhase _phase = OIDCLoginPhase.Idle; - public OIDCLoginPhase Phase + private OidcLoginPhase _phase = OidcLoginPhase.Idle; + public OidcLoginPhase Phase { get => _phase; private set @@ -122,13 +122,13 @@ public partial class LoginPageViewModel : ViewModelBase /// public string PhaseLabel => _phase switch { - OIDCLoginPhase.Idle => "En attente", - OIDCLoginPhase.Discovering => "Découverte OIDC…", - OIDCLoginPhase.OpeningBrowser => "Ouverture du navigateur…", - OIDCLoginPhase.AwaitingCallback => "En attente du callback postit://…", - OIDCLoginPhase.ExchangingCode => "Échange du code contre les jetons…", - OIDCLoginPhase.Success => "Connecté", - OIDCLoginPhase.Error => "Erreur", + OidcLoginPhase.Idle => "En attente", + OidcLoginPhase.Discovering => "Découverte OIDC…", + OidcLoginPhase.OpeningBrowser => "Ouverture du navigateur…", + OidcLoginPhase.AwaitingCallback => "En attente du callback postit://…", + OidcLoginPhase.ExchangingCode => "Échange du code contre les jetons…", + OidcLoginPhase.Success => "Connecté", + OidcLoginPhase.Error => "Erreur", _ => _phase.ToString(), }; @@ -275,7 +275,7 @@ public partial class LoginPageViewModel : ViewModelBase // The progress sink drives Phase / PhaseLabel; StatusMessage // keeps the text detail (URLs, error messages). Same // underlying flow, two views. - var progress = new Progress(p => Phase = p); + var progress = new Progress(p => Phase = p); await LoginInteractiveCoreAsync(_api, progress); IsBusy = false; @@ -299,7 +299,7 @@ public partial class LoginPageViewModel : ViewModelBase /// private async Task LoginInteractiveCoreAsync( YavscApiClient api, - IProgress? progress = null) + IProgress? progress = null) { var original = Platform.CreateBrowser; try diff --git a/src/PostIt/PostIt/ViewModels/SignaturePageViewModel.cs b/src/PostIt/PostIt/ViewModels/SignaturePageViewModel.cs deleted file mode 100644 index b4b37974..00000000 --- a/src/PostIt/PostIt/ViewModels/SignaturePageViewModel.cs +++ /dev/null @@ -1,185 +0,0 @@ -using System; -using System.IO; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using PostIt.Controls; -using PostIt.Models; - -namespace PostIt.ViewModels; - -/// -/// Backing state for . -/// -/// The page exists to produce a -/// (length-prefixed normalised int[]) from a human signature drawn -/// with the mouse (Desktop) or finger (touch / Android). The page -/// is a recipient of an external trigger — a SignalR push from -/// Yavsc.Org telling PostIt "a devis has been sent, sign here" — -/// so it intentionally has no first-class entry point in -/// . The only "open" affordance today is a -/// dev-only shortcut on the blog editor, marked for removal once -/// the SignalR handler lands. -/// -/// Output path is the platform-friendly per-user data directory -/// (XDG_DATA_HOME / AppData / NSDocumentDirectory on iOS). Files -/// are JSON, one per capture, named -/// signature-{yyyyMMdd-HHmmssfff}.json. This is a stop-gap -/// until the Yavsc.Org endpoint exists; the contract there will -/// be POST /api/signature/{devisId} with this same payload. -/// -public partial class SignaturePageViewModel : ViewModelBase -{ - /// - /// Default capture surface, in DIPs. 3:1 ratio matches a - /// signature line at the bottom of an A4 contract. - /// - public const double DefaultWidth = 600; - public const double DefaultHeight = 200; - - [ObservableProperty] - public partial string StatusMessage { get; set; } = "Prêt."; - - [ObservableProperty] - public partial int StrokeCount { get; set; } - - [ObservableProperty] - public partial int PointCount { get; set; } - - [ObservableProperty] - public partial string? LastCapturedPath { get; set; } - - public double Width { get; } - public double Height { get; } - - private SignaturePadControl? _control; - - public override bool CanNavigateNext - { - get => false; - protected set { _ = value; } - } - - public override bool CanNavigatePrevious - { - get => true; - protected set { _ = value; } - } - - public SignaturePageViewModel() - : this(DefaultWidth, DefaultHeight) - { - } - - public SignaturePageViewModel(double width, double height) - { - if (width <= 0) throw new ArgumentOutOfRangeException(nameof(width)); - if (height <= 0) throw new ArgumentOutOfRangeException(nameof(height)); - Width = width; - Height = height; - } - - /// - /// Bind a freshly-constructed (or re-templated) control to this - /// VM. Called from the view's code-behind once the control has - /// been added to the visual tree and its template applied (so - /// is wired). - /// - public void Attach(SignaturePadControl control) - { - if (control is null) throw new ArgumentNullException(nameof(control)); - Detach(); - _control = control; - _control.RedrawRequested += OnRedraw; - _control.StrokeCompleted += OnStrokeCompleted; - RefreshCounts(); - } - - public void Detach() - { - if (_control is null) return; - _control.RedrawRequested -= OnRedraw; - _control.StrokeCompleted -= OnStrokeCompleted; - _control = null; - } - - private void OnStrokeCompleted(object? sender, SignaturePadData data) - { - StatusMessage = $"Trait terminé. {data.StrokeCount} trait(s)."; - RefreshCounts(); - } - - private void OnRedraw(object? sender, EventArgs e) => RefreshCounts(); - - private void RefreshCounts() - { - if (_control is null) return; - var snap = _control.Snapshot(); - StrokeCount = snap.StrokeCount; - PointCount = snap.PointCount; - } - - [RelayCommand] - public void Clear() - { - _control?.Clear(); - StatusMessage = "Effacé."; - RefreshCounts(); - } - - [RelayCommand] - public async Task CaptureAsync() - { - if (_control is null) - { - StatusMessage = "Contrôle non attaché."; - return; - } - - var data = _control.Snapshot(); - if (data.IsEmpty) - { - StatusMessage = "Rien à capturer."; - return; - } - - try - { - var path = WriteCapture(data); - LastCapturedPath = path; - StatusMessage = $"Capture enregistrée: {path}"; - } - catch (Exception ex) - { - StatusMessage = $"Erreur: {ex.Message}"; - } - await Task.CompletedTask; - } - - private static string WriteCapture(SignaturePadData data) - { - var dir = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "PostIt", "signatures"); - Directory.CreateDirectory(dir); - - var fileName = $"signature-{DateTime.UtcNow:yyyyMMdd-HHmmssfff}.json"; - var path = Path.Combine(dir, fileName); - - var payload = new - { - format = "yavsc.signature/v1", - coordinateMax = SignaturePadData.CoordinateMax, - capturedAtUtc = DateTime.UtcNow, - strokes = data.Strokes, - strokeCount = data.StrokeCount, - }; - File.WriteAllText( - path, - JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true }), - Encoding.UTF8); - return path; - } -} diff --git a/src/PostIt/PostIt/Views/MainPage.axaml b/src/PostIt/PostIt/Views/MainPage.axaml index c09c2f7a..b9e0308d 100644 --- a/src/PostIt/PostIt/Views/MainPage.axaml +++ b/src/PostIt/PostIt/Views/MainPage.axaml @@ -35,17 +35,6 @@ - \ No newline at end of file + diff --git a/src/Yavsc.Org/Views/Account/ForgotPasswordConfirmation.cshtml b/src/Yavsc.Org/Views/Account/ForgotPasswordConfirmation.cshtml index 4faaf2d0..d1e50615 100644 --- a/src/Yavsc.Org/Views/Account/ForgotPasswordConfirmation.cshtml +++ b/src/Yavsc.Org/Views/Account/ForgotPasswordConfirmation.cshtml @@ -1,7 +1,5 @@ @model string -@{ - ViewBag.Title = Localizer["Account"]; -}

Check your mail box!

+ diff --git a/src/Yavsc.Org/Views/Account/LoggedOut.cshtml b/src/Yavsc.Org/Views/Account/LoggedOut.cshtml index 82c653c7..dc9fbf7a 100644 --- a/src/Yavsc.Org/Views/Account/LoggedOut.cshtml +++ b/src/Yavsc.Org/Views/Account/LoggedOut.cshtml @@ -1,8 +1,4 @@ -@{ - ViewBag.Title = Localizer["Account"]; -} - -@model LoggedOutViewModel +@model LoggedOutViewModel @{ // set this so the layout rendering sees an anonymous user diff --git a/src/Yavsc.Org/Views/Account/Login.cshtml b/src/Yavsc.Org/Views/Account/Login.cshtml index 54df2e3e..b0ee8e88 100644 --- a/src/Yavsc.Org/Views/Account/Login.cshtml +++ b/src/Yavsc.Org/Views/Account/Login.cshtml @@ -1,7 +1,4 @@ @model LoginViewModel -@{ - ViewBag.Title = Localizer["Account"]; -} - \ No newline at end of file + diff --git a/src/Yavsc.Org/Views/Account/Logout.cshtml b/src/Yavsc.Org/Views/Account/Logout.cshtml index 893807f7..b49dee09 100644 --- a/src/Yavsc.Org/Views/Account/Logout.cshtml +++ b/src/Yavsc.Org/Views/Account/Logout.cshtml @@ -1,8 +1,4 @@ -@{ - ViewBag.Title = Localizer["Account"]; -} - -@model LogoutViewModel +@model LogoutViewModel
diff --git a/src/Yavsc.Org/Views/Account/Register.cshtml b/src/Yavsc.Org/Views/Account/Register.cshtml index 92a7f34e..388b2ecd 100644 --- a/src/Yavsc.Org/Views/Account/Register.cshtml +++ b/src/Yavsc.Org/Views/Account/Register.cshtml @@ -1,11 +1,8 @@ @model RegisterModel -@{ - ViewBag.Title = Localizer["Account"]; -}
@Html.EditorForModel() -
\ No newline at end of file + diff --git a/src/Yavsc.Org/Views/Account/ResetPassword.cshtml b/src/Yavsc.Org/Views/Account/ResetPassword.cshtml index 0cdbb2e3..5f64c727 100644 --- a/src/Yavsc.Org/Views/Account/ResetPassword.cshtml +++ b/src/Yavsc.Org/Views/Account/ResetPassword.cshtml @@ -1,7 +1,4 @@ @model ResetPasswordViewModel -@{ - ViewBag.Title = Localizer["Account"]; -}

Your email : @Model.Email

@@ -18,4 +15,4 @@ -
\ No newline at end of file + diff --git a/src/Yavsc.Org/Views/Account/Signin.cshtml b/src/Yavsc.Org/Views/Account/Signin.cshtml index 871c0c74..079882b3 100644 --- a/src/Yavsc.Org/Views/Account/Signin.cshtml +++ b/src/Yavsc.Org/Views/Account/Signin.cshtml @@ -1,7 +1,4 @@ @model SignInModel -@{ - ViewBag.Title = Localizer["Account"]; -} -
\ No newline at end of file +
diff --git a/src/Yavsc.Org/Views/Administration/Haircut.cshtml b/src/Yavsc.Org/Views/Administration/Haircut.cshtml index 0165665c..a59ab801 100644 --- a/src/Yavsc.Org/Views/Administration/Haircut.cshtml +++ b/src/Yavsc.Org/Views/Administration/Haircut.cshtml @@ -1,7 +1,4 @@ @model HaircutAdminViewModel -@{ - ViewBag.Title = Localizer["Administration"]; -} Gestion des couleurs diff --git a/src/Yavsc.Org/Views/Administration/Role.cshtml b/src/Yavsc.Org/Views/Administration/Role.cshtml index e541f75a..68a5710f 100644 --- a/src/Yavsc.Org/Views/Administration/Role.cshtml +++ b/src/Yavsc.Org/Views/Administration/Role.cshtml @@ -21,19 +21,7 @@ @foreach (var user in Model.Users) { - @if (user.UserId==User.GetUserId()) { - You - } - @if (SiteSettings.Value.Admin.EMail == user.Email) { - Admin - } - @if (SiteSettings.Value.Owner.EMail == user.Email) { - Owner - } - @if (!String.IsNullOrWhiteSpace(user.Avatar)) - { - avatar - } + avatar @user.UserName <@(user.Email)> diff --git a/src/Yavsc.Org/Views/ApiScope/Create.cshtml b/src/Yavsc.Org/Views/ApiScope/Create.cshtml index 8297251d..5d3b9b8b 100644 --- a/src/Yavsc.Org/Views/ApiScope/Create.cshtml +++ b/src/Yavsc.Org/Views/ApiScope/Create.cshtml @@ -1,9 +1,20 @@ @model IdentityServer8.EntityFramework.Entities.ApiScope + @{ - ViewBag.Title = Localizer["ApiScope"]; + Layout = null; } + + + + + + Create + + +

ApiScope

+
@@ -53,3 +64,6 @@ + + + diff --git a/src/Yavsc.Org/Views/ApiScope/Delete.cshtml b/src/Yavsc.Org/Views/ApiScope/Delete.cshtml index abb3d26b..166e6110 100644 --- a/src/Yavsc.Org/Views/ApiScope/Delete.cshtml +++ b/src/Yavsc.Org/Views/ApiScope/Delete.cshtml @@ -1,8 +1,18 @@ @model Yavsc.Models.YavscApiScope + @{ - ViewBag.Title = Localizer["ApiScope"]; + Layout = null; } + + + + + + Delete + + +

Are you sure you want to delete this?

YavscApiScope

@@ -51,9 +61,12 @@ @Html.DisplayFor(model => model.ShowInDiscoveryDocument) - + | Back to List +
+ + diff --git a/src/Yavsc.Org/Views/ApiScope/Details.cshtml b/src/Yavsc.Org/Views/ApiScope/Details.cshtml index 9275a114..6c904ce3 100644 --- a/src/Yavsc.Org/Views/ApiScope/Details.cshtml +++ b/src/Yavsc.Org/Views/ApiScope/Details.cshtml @@ -1,8 +1,17 @@ @model Yavsc.Models.YavscApiScope + @{ - ViewBag.Title = Localizer["ApiScope"]; + Layout = null; } + + + + + + Details + +

YavscApiScope

@@ -56,3 +65,5 @@ Edit | Back to List
+ + diff --git a/src/Yavsc.Org/Views/ApiScope/Edit.cshtml b/src/Yavsc.Org/Views/ApiScope/Edit.cshtml index 1466435b..34b111d4 100644 --- a/src/Yavsc.Org/Views/ApiScope/Edit.cshtml +++ b/src/Yavsc.Org/Views/ApiScope/Edit.cshtml @@ -1,8 +1,17 @@ @model Yavsc.Models.YavscApiScope + @{ - ViewBag.Title = Localizer["ApiScope"]; + Layout = null; } + + + + + + Edit + +

YavscApiScope


@@ -56,3 +65,6 @@ + + + diff --git a/src/Yavsc.Org/Views/ApiScope/Index.cshtml b/src/Yavsc.Org/Views/ApiScope/Index.cshtml index 2c64b5c1..2d3bd6c7 100644 --- a/src/Yavsc.Org/Views/ApiScope/Index.cshtml +++ b/src/Yavsc.Org/Views/ApiScope/Index.cshtml @@ -1,10 +1,17 @@ @model IEnumerable @{ - ViewBag.Title = Localizer["ApiScope"]; - + Layout = null; } + + + + + + Index + +

Create New

@@ -68,3 +75,5 @@ } + + diff --git a/src/Yavsc.Org/Views/Client/Create.cshtml b/src/Yavsc.Org/Views/Client/Create.cshtml index 3bdb03ab..76fece36 100644 --- a/src/Yavsc.Org/Views/Client/Create.cshtml +++ b/src/Yavsc.Org/Views/Client/Create.cshtml @@ -1,7 +1,4 @@ @model Client -@{ - ViewBag.Title = Localizer["Client"]; -}

@Localizer["Create"]

@@ -80,3 +77,4 @@ + diff --git a/src/Yavsc.Org/Views/Client/Delete.cshtml b/src/Yavsc.Org/Views/Client/Delete.cshtml index f2e68d8e..bba5a1b5 100644 --- a/src/Yavsc.Org/Views/Client/Delete.cshtml +++ b/src/Yavsc.Org/Views/Client/Delete.cshtml @@ -1,7 +1,4 @@ @model Client -@{ - ViewBag.Title = Localizer["Client"]; -}

@Localizer["Delete"]

@@ -66,4 +63,4 @@ @Localizer["Back to List"]
-
\ No newline at end of file + diff --git a/src/Yavsc.Org/Views/Client/Details.cshtml b/src/Yavsc.Org/Views/Client/Details.cshtml index 770042be..b39a3f70 100644 --- a/src/Yavsc.Org/Views/Client/Details.cshtml +++ b/src/Yavsc.Org/Views/Client/Details.cshtml @@ -2,12 +2,7 @@ @using Microsoft.AspNetCore.Html @using System.Text -@{ - ViewBag.Title = Localizer["Client"]; -} - @functions { - // Lifetimes in IdentityServer are stored as seconds (int). Render them // in a human-friendly way: "5 min", "2 h", "30 d", "—" for 0/negative. // Zero typically means "use the global default" — show it as such so diff --git a/src/Yavsc.Org/Views/Client/Edit.cshtml b/src/Yavsc.Org/Views/Client/Edit.cshtml index 5b9e107d..a1245061 100644 --- a/src/Yavsc.Org/Views/Client/Edit.cshtml +++ b/src/Yavsc.Org/Views/Client/Edit.cshtml @@ -1,7 +1,4 @@ @model Client -@{ - ViewBag.Title = Localizer["Client"]; -}

@Localizer["Edit"]

@@ -306,4 +303,4 @@ \ No newline at end of file + diff --git a/src/Yavsc.Org/Views/Client/EditClaims.cshtml b/src/Yavsc.Org/Views/Client/EditClaims.cshtml index 9877019b..bf156b09 100644 --- a/src/Yavsc.Org/Views/Client/EditClaims.cshtml +++ b/src/Yavsc.Org/Views/Client/EditClaims.cshtml @@ -1,8 +1,4 @@ @model IEnumerable -@{ - ViewBag.Title = Localizer["Client"]; -} - @{ ViewData["Title"] = "Edit Client Claims"; var clientId = Model.FirstOrDefault()?.ClientId ?? 0; @@ -65,4 +61,4 @@

Back to client -

\ No newline at end of file +

diff --git a/src/Yavsc.Org/Views/Client/EditCorsOrigins.cshtml b/src/Yavsc.Org/Views/Client/EditCorsOrigins.cshtml index 7e613b35..035c130a 100644 --- a/src/Yavsc.Org/Views/Client/EditCorsOrigins.cshtml +++ b/src/Yavsc.Org/Views/Client/EditCorsOrigins.cshtml @@ -1,8 +1,4 @@ @model IEnumerable -@{ - ViewBag.Title = Localizer["Client"]; -} - @{ ViewData["Title"] = "Edit Allowed CORS Origins"; ViewData["addAction"] = "AddCorsOrigin"; @@ -27,4 +23,4 @@

Back to client -

\ No newline at end of file +

diff --git a/src/Yavsc.Org/Views/Client/EditGrantTypes.cshtml b/src/Yavsc.Org/Views/Client/EditGrantTypes.cshtml index 139d779a..6f1a1739 100644 --- a/src/Yavsc.Org/Views/Client/EditGrantTypes.cshtml +++ b/src/Yavsc.Org/Views/Client/EditGrantTypes.cshtml @@ -1,8 +1,4 @@ @model IEnumerable -@{ - ViewBag.Title = Localizer["Client"]; -} - @{ ViewData["Title"] = "Edit Allowed Grant Types"; ViewData["addAction"] = "AddGrantType"; @@ -28,4 +24,4 @@

Back to client -

\ No newline at end of file +

diff --git a/src/Yavsc.Org/Views/Client/EditIdPRestrictions.cshtml b/src/Yavsc.Org/Views/Client/EditIdPRestrictions.cshtml index 5f9d1160..a0032598 100644 --- a/src/Yavsc.Org/Views/Client/EditIdPRestrictions.cshtml +++ b/src/Yavsc.Org/Views/Client/EditIdPRestrictions.cshtml @@ -1,8 +1,4 @@ @model IEnumerable -@{ - ViewBag.Title = Localizer["Client"]; -} - @{ ViewData["Title"] = "Edit Identity Provider Restrictions"; ViewData["addAction"] = "AddIdPRestriction"; @@ -25,4 +21,4 @@

Back to client -

\ No newline at end of file +

diff --git a/src/Yavsc.Org/Views/Client/EditPostLogoutRedirectUris.cshtml b/src/Yavsc.Org/Views/Client/EditPostLogoutRedirectUris.cshtml index 9282d298..f80d2953 100644 --- a/src/Yavsc.Org/Views/Client/EditPostLogoutRedirectUris.cshtml +++ b/src/Yavsc.Org/Views/Client/EditPostLogoutRedirectUris.cshtml @@ -1,8 +1,4 @@ @model IEnumerable -@{ - ViewBag.Title = Localizer["Client"]; -} - @{ ViewData["Title"] = "Edit Post-logout Redirect URIs"; ViewData["addAction"] = "AddPostLogoutRedirectUri"; @@ -26,4 +22,4 @@

Back to client -

\ No newline at end of file +

diff --git a/src/Yavsc.Org/Views/Client/EditProperties.cshtml b/src/Yavsc.Org/Views/Client/EditProperties.cshtml index 6017bc34..e067d487 100644 --- a/src/Yavsc.Org/Views/Client/EditProperties.cshtml +++ b/src/Yavsc.Org/Views/Client/EditProperties.cshtml @@ -1,8 +1,4 @@ @model IEnumerable -@{ - ViewBag.Title = Localizer["Client"]; -} - @{ ViewData["Title"] = "Edit Client Properties"; var clientId = Model.FirstOrDefault()?.ClientId ?? 0; @@ -66,4 +62,4 @@

Back to client -

\ No newline at end of file +

diff --git a/src/Yavsc.Org/Views/Client/EditRedirectUris.cshtml b/src/Yavsc.Org/Views/Client/EditRedirectUris.cshtml index 4a6467a2..fe936624 100644 --- a/src/Yavsc.Org/Views/Client/EditRedirectUris.cshtml +++ b/src/Yavsc.Org/Views/Client/EditRedirectUris.cshtml @@ -1,8 +1,4 @@ @model IEnumerable -@{ - ViewBag.Title = Localizer["Client"]; -} - @{ ViewData["Title"] = "Edit Redirect URIs"; ViewData["addAction"] = "AddRedirectUri"; @@ -26,4 +22,4 @@

Back to client -

\ No newline at end of file +

diff --git a/src/Yavsc.Org/Views/Client/EditScopes.cshtml b/src/Yavsc.Org/Views/Client/EditScopes.cshtml index 62978f5c..0e9e0608 100644 --- a/src/Yavsc.Org/Views/Client/EditScopes.cshtml +++ b/src/Yavsc.Org/Views/Client/EditScopes.cshtml @@ -1,8 +1,4 @@ @model IEnumerable -@{ - ViewBag.Title = Localizer["Client"]; -} - @{ ViewData["Title"] = "Edit Allowed Scopes"; ViewData["addAction"] = "AddScope"; @@ -26,4 +22,4 @@

Back to client -

\ No newline at end of file +

diff --git a/src/Yavsc.Org/Views/Client/EditSecrets.cshtml b/src/Yavsc.Org/Views/Client/EditSecrets.cshtml index e7ce748d..61a23cb3 100644 --- a/src/Yavsc.Org/Views/Client/EditSecrets.cshtml +++ b/src/Yavsc.Org/Views/Client/EditSecrets.cshtml @@ -1,8 +1,4 @@ @model IEnumerable -@{ - ViewBag.Title = Localizer["Client"]; -} - @{ ViewData["Title"] = "Edit Client Secrets"; var clientId = Model.FirstOrDefault()?.ClientId ?? 0; @@ -84,4 +80,4 @@

Back to client Regenerate a single secret -

\ No newline at end of file +

diff --git a/src/Yavsc.Org/Views/Client/Index.cshtml b/src/Yavsc.Org/Views/Client/Index.cshtml index 347ee263..f91f71dc 100644 --- a/src/Yavsc.Org/Views/Client/Index.cshtml +++ b/src/Yavsc.Org/Views/Client/Index.cshtml @@ -1,10 +1,6 @@ @using IdentityServer8.Models; @model IEnumerable -@{ - ViewBag.Title = Localizer["Client"]; -} -

@Localizer["Index"]

@@ -14,7 +10,7 @@ @foreach (var item in Model) {

- +
Identifier
and activation
@@ -40,7 +36,7 @@
- +
Urls
@@ -60,7 +56,7 @@
@Html.DisplayNameFor(model => model.AbsoluteRefreshTokenLifetime)
@Html.DisplayFor(model => item.AbsoluteRefreshTokenLifetime)
- +
@@ -82,7 +78,7 @@
@Enum.GetName(typeof(AccessTokenType), item.AccessTokenType) - +
@@ -90,6 +86,6 @@ Edit Details Delete - + } diff --git a/src/Yavsc.Org/Views/Client/RegenerateSecret.cshtml b/src/Yavsc.Org/Views/Client/RegenerateSecret.cshtml index cc1c1b4f..b0fd554d 100644 --- a/src/Yavsc.Org/Views/Client/RegenerateSecret.cshtml +++ b/src/Yavsc.Org/Views/Client/RegenerateSecret.cshtml @@ -1,7 +1,4 @@ @model Client -@{ - ViewBag.Title = Localizer["Client"]; -}

@Localizer["Regenerate client secret"]

@@ -39,4 +36,4 @@ @Html.AntiForgeryToken() @Localizer["Cancel"] - \ No newline at end of file + diff --git a/src/Yavsc.Org/Views/Client/ShowSecret.cshtml b/src/Yavsc.Org/Views/Client/ShowSecret.cshtml index 3d8ff78a..5256e327 100644 --- a/src/Yavsc.Org/Views/Client/ShowSecret.cshtml +++ b/src/Yavsc.Org/Views/Client/ShowSecret.cshtml @@ -1,7 +1,3 @@ -@{ - ViewBag.Title = Localizer["Client"]; -} - @{ var secret = ViewBag.NewClientSecret as string; var expiresAt = ViewBag.NewClientSecretExpiresAt as string; diff --git a/src/Yavsc.Org/Views/CommandForms/Index.cshtml b/src/Yavsc.Org/Views/CommandForms/Index.cshtml index 975f131f..ede4b71d 100644 --- a/src/Yavsc.Org/Views/CommandForms/Index.cshtml +++ b/src/Yavsc.Org/Views/CommandForms/Index.cshtml @@ -1,10 +1,10 @@ @model IEnumerable @{ - ViewBag.Title = Localizer["CommandForms"]; + ViewBag.Title = "Index"; } -

@ViewBag.Title

+

Index

Create New @@ -22,7 +22,7 @@ - + @foreach (var item in Model) { diff --git a/src/Yavsc.Org/Views/Device/Success.cshtml b/src/Yavsc.Org/Views/Device/Success.cshtml index 7ba766d3..050dd91c 100644 --- a/src/Yavsc.Org/Views/Device/Success.cshtml +++ b/src/Yavsc.Org/Views/Device/Success.cshtml @@ -1,10 +1,7 @@ -@{ - ViewBag.Title = Localizer["Device"]; -}

Success

You have successfully authorized the device

-
\ No newline at end of file + diff --git a/src/Yavsc.Org/Views/Device/UserCodeCapture.cshtml b/src/Yavsc.Org/Views/Device/UserCodeCapture.cshtml index 6357c752..6d412613 100644 --- a/src/Yavsc.Org/Views/Device/UserCodeCapture.cshtml +++ b/src/Yavsc.Org/Views/Device/UserCodeCapture.cshtml @@ -1,7 +1,4 @@ @model string -@{ - ViewBag.Title = Localizer["Device"]; -}
@@ -23,4 +20,4 @@
- \ No newline at end of file + diff --git a/src/Yavsc.Org/Views/Device/UserCodeConfirmation.cshtml b/src/Yavsc.Org/Views/Device/UserCodeConfirmation.cshtml index ef60ba1c..e1d3b196 100644 --- a/src/Yavsc.Org/Views/Device/UserCodeConfirmation.cshtml +++ b/src/Yavsc.Org/Views/Device/UserCodeConfirmation.cshtml @@ -1,7 +1,4 @@ @model DeviceAuthorizationViewModel -@{ - ViewBag.Title = Localizer["Device"]; -}
@@ -108,4 +105,4 @@
- \ No newline at end of file + diff --git a/src/Yavsc.Org/Views/Diagnostics/Index.cshtml b/src/Yavsc.Org/Views/Diagnostics/Index.cshtml index b7bf7a36..e939c0d8 100644 --- a/src/Yavsc.Org/Views/Diagnostics/Index.cshtml +++ b/src/Yavsc.Org/Views/Diagnostics/Index.cshtml @@ -1,9 +1,5 @@ @model DiagnosticsViewModel -@{ - ViewBag.Title = Localizer["Diagnostics"]; -} -

Authentication Cookie

@@ -26,7 +22,7 @@
- +
diff --git a/src/Yavsc.Org/Views/Do/Yavsc.Models.Booking.MusicianSettingsEditor.fr.cshtml b/src/Yavsc.Org/Views/Do/Yavsc.Models.Booking.MusicianSettingsEditor.fr.cshtml index 2bfa0ae5..0b9b6b32 100644 --- a/src/Yavsc.Org/Views/Do/Yavsc.Models.Booking.MusicianSettingsEditor.fr.cshtml +++ b/src/Yavsc.Org/Views/Do/Yavsc.Models.Booking.MusicianSettingsEditor.fr.cshtml @@ -1,7 +1,4 @@ @model Instrumentation -@{ - ViewBag.Title = Localizer["Do"]; -}

@Model.Tool.Name

@@ -9,4 +6,4 @@ Remove Instrument
-
\ No newline at end of file + diff --git a/src/Yavsc.Org/Views/FrontOffice/BillAcquitment.cshtml b/src/Yavsc.Org/Views/FrontOffice/BillAcquitment.cshtml index 8777a809..e69de29b 100644 --- a/src/Yavsc.Org/Views/FrontOffice/BillAcquitment.cshtml +++ b/src/Yavsc.Org/Views/FrontOffice/BillAcquitment.cshtml @@ -1,4 +0,0 @@ -@{ - ViewBag.Title = Localizer["FrontOffice"]; -} - diff --git a/src/Yavsc.Org/Views/FrontOffice/BillValidation.cshtml b/src/Yavsc.Org/Views/FrontOffice/BillValidation.cshtml index 8777a809..e69de29b 100644 --- a/src/Yavsc.Org/Views/FrontOffice/BillValidation.cshtml +++ b/src/Yavsc.Org/Views/FrontOffice/BillValidation.cshtml @@ -1,4 +0,0 @@ -@{ - ViewBag.Title = Localizer["FrontOffice"]; -} - diff --git a/src/Yavsc.Org/Views/FrontOffice/EstimateClientValidation.cshtml b/src/Yavsc.Org/Views/FrontOffice/EstimateClientValidation.cshtml index 8777a809..e69de29b 100644 --- a/src/Yavsc.Org/Views/FrontOffice/EstimateClientValidation.cshtml +++ b/src/Yavsc.Org/Views/FrontOffice/EstimateClientValidation.cshtml @@ -1,4 +0,0 @@ -@{ - ViewBag.Title = Localizer["FrontOffice"]; -} - diff --git a/src/Yavsc.Org/Views/FrontOffice/EstimateProValidation.cshtml b/src/Yavsc.Org/Views/FrontOffice/EstimateProValidation.cshtml index 8777a809..e69de29b 100644 --- a/src/Yavsc.Org/Views/FrontOffice/EstimateProValidation.cshtml +++ b/src/Yavsc.Org/Views/FrontOffice/EstimateProValidation.cshtml @@ -1,4 +0,0 @@ -@{ - ViewBag.Title = Localizer["FrontOffice"]; -} - diff --git a/src/Yavsc.Org/Views/FrontOffice/Index.cshtml b/src/Yavsc.Org/Views/FrontOffice/Index.cshtml index ed4c9038..e563532d 100644 --- a/src/Yavsc.Org/Views/FrontOffice/Index.cshtml +++ b/src/Yavsc.Org/Views/FrontOffice/Index.cshtml @@ -13,7 +13,7 @@
EstimateToSignAsPro
-
@Html.DisplayFor(m=>m.EstimateToHonorAsProCount) +
@Html.DisplayFor(m=>m.EstimateToSignAsProCount)
EstimateToSignAsCli @@ -21,6 +21,11 @@
@Html.DisplayFor(m=>m.EstimateToSignAsCliCount)
+
BillToSignAsPro +
+
@Html.DisplayFor(m=>m.BillToSignAsProCount) +
+
BillToSignAsCli
@Html.DisplayFor(m=>m.BillToSignAsCliCount) diff --git a/src/Yavsc.Org/Views/FrontOffice/Yavsc.Models.Haircut.BrusherProfile.cshtml b/src/Yavsc.Org/Views/FrontOffice/Yavsc.Models.Haircut.BrusherProfile.cshtml index 9b22e772..5fe3cc17 100644 --- a/src/Yavsc.Org/Views/FrontOffice/Yavsc.Models.Haircut.BrusherProfile.cshtml +++ b/src/Yavsc.Org/Views/FrontOffice/Yavsc.Models.Haircut.BrusherProfile.cshtml @@ -1,7 +1,4 @@ @model Yavsc.Models.Haircut.BrusherProfile -@{ - ViewBag.Title = Localizer["FrontOffice"]; -}
Disponibilités diff --git a/src/Yavsc.Org/Views/FrontOffice/Yavsc.Models.Musical.Profiles.Instrumentation.cshtml b/src/Yavsc.Org/Views/FrontOffice/Yavsc.Models.Musical.Profiles.Instrumentation.cshtml index 1dd077bc..f4b2ec80 100644 --- a/src/Yavsc.Org/Views/FrontOffice/Yavsc.Models.Musical.Profiles.Instrumentation.cshtml +++ b/src/Yavsc.Org/Views/FrontOffice/Yavsc.Models.Musical.Profiles.Instrumentation.cshtml @@ -1,8 +1,5 @@ @model Yavsc.Models.Musical.Profiles.Instrumentation -@{ - ViewBag.Title = Localizer["FrontOffice"]; -} @if (Model == null) {

NoInstrument

@@ -20,4 +17,4 @@
-} \ No newline at end of file +} diff --git a/src/Yavsc.Org/Views/FrontOffice/Yavsc.Models.Workflow.Profiles.FormationSettings.cshtml b/src/Yavsc.Org/Views/FrontOffice/Yavsc.Models.Workflow.Profiles.FormationSettings.cshtml index 906f130a..139c4eb5 100644 --- a/src/Yavsc.Org/Views/FrontOffice/Yavsc.Models.Workflow.Profiles.FormationSettings.cshtml +++ b/src/Yavsc.Org/Views/FrontOffice/Yavsc.Models.Workflow.Profiles.FormationSettings.cshtml @@ -1,8 +1,4 @@ @model Yavsc.Models.Workflow.Profiles.FormationSettings -@{ - ViewBag.Title = Localizer["FrontOffice"]; -} - @if (Model != null && Model.CoWorking != null) {
Partenariat
@@ -14,4 +10,4 @@ }
-} \ No newline at end of file +} diff --git a/src/Yavsc.Org/Views/Grants/Index.cshtml b/src/Yavsc.Org/Views/Grants/Index.cshtml index 7fc509e4..a60f2261 100644 --- a/src/Yavsc.Org/Views/Grants/Index.cshtml +++ b/src/Yavsc.Org/Views/Grants/Index.cshtml @@ -1,9 +1,5 @@ @model GrantsViewModel -@{ - ViewBag.Title = Localizer["Grants"]; -} -

Client Application Permissions

@@ -43,13 +39,13 @@
- +
    @if (grant.Description != null) {
  • @grant.Description -
  • + }
  • @grant.Created.ToString("yyyy-MM-dd") @@ -88,4 +84,4 @@
} } -
+ \ No newline at end of file diff --git a/src/Yavsc.Org/Views/HairCutCommand/BrusherProfileScript.cshtml b/src/Yavsc.Org/Views/HairCutCommand/BrusherProfileScript.cshtml index e26397e5..7458e15c 100644 --- a/src/Yavsc.Org/Views/HairCutCommand/BrusherProfileScript.cshtml +++ b/src/Yavsc.Org/Views/HairCutCommand/BrusherProfileScript.cshtml @@ -1,7 +1,4 @@ @model BrusherProfile -@{ - ViewBag.Title = Localizer["HairCutCommand"]; -} \ No newline at end of file + diff --git a/src/Yavsc.Org/Views/HairCutCommand/CommandConfirmation.cshtml b/src/Yavsc.Org/Views/HairCutCommand/CommandConfirmation.cshtml index 4031b7f0..ecdd8876 100644 --- a/src/Yavsc.Org/Views/HairCutCommand/CommandConfirmation.cshtml +++ b/src/Yavsc.Org/Views/HairCutCommand/CommandConfirmation.cshtml @@ -54,7 +54,7 @@
@Html.DisplayNameFor(m => m.Location)
@if (Model.Location == null) { -

Pas de lieu convenu ...

+

Pas de lieu convenu ...

} else { @Html.DisplayFor(m => m.Location) @@ -62,7 +62,7 @@
Notification
-
@if (ViewBag.GooglePayload !=null) +
@if (ViewBag.GooglePayload !=null) { @if (ViewBag.GooglePayload.success>0) {

GCM Notifications sent

@@ -85,10 +85,10 @@
@await Component.InvokeAsync("Bill", Model)
- -
@Html.DisplayNameFor(m=>m.Regularization)
+ +
@Html.DisplayNameFor(m=>m.Regularisation)
@await Component.InvokeAsync("PayPalButton", Model)
- + diff --git a/src/Yavsc.Org/Views/Home/About.cshtml b/src/Yavsc.Org/Views/Home/About.cshtml index 39062cb2..dbde9bc5 100755 --- a/src/Yavsc.Org/Views/Home/About.cshtml +++ b/src/Yavsc.Org/Views/Home/About.cshtml @@ -1,7 +1,4 @@ @using System.Diagnostics -@{ - ViewBag.Title = Localizer["Home"]; -}

@SiteSettings.Value.Title - À Propos

@@ -70,4 +67,4 @@ Il a accès à la connaissance des journées connues comme libres des artistes p De plus, le droit de retrait est permanent et sa mise en oeuvre immédiate. Les artistes comme les clients peuvent demander leur désinscription, qui désactive immédiatement les publications associées à leurs informations, et programme la suppression complète de ces dites informations dans les quinze jours à compter de la demande, sauf demande contradictoire. L'opération est annulable, jusqu'à deux semaines après sa programmation. - \ No newline at end of file + diff --git a/src/Yavsc.Org/Views/Home/About.pt.cshtml b/src/Yavsc.Org/Views/Home/About.pt.cshtml index cca60a94..85b4d291 100755 --- a/src/Yavsc.Org/Views/Home/About.pt.cshtml +++ b/src/Yavsc.Org/Views/Home/About.pt.cshtml @@ -1,7 +1,3 @@ -@{ - ViewBag.Title = Localizer["Home"]; -} -

@SiteSettings.Value.Title - objetivo

diff --git a/src/Yavsc.Org/Views/Home/AboutIdentityServer.cshtml b/src/Yavsc.Org/Views/Home/AboutIdentityServer.cshtml index b77f2032..b34a19c8 100644 --- a/src/Yavsc.Org/Views/Home/AboutIdentityServer.cshtml +++ b/src/Yavsc.Org/Views/Home/AboutIdentityServer.cshtml @@ -1,7 +1,4 @@ @using System.Diagnostics -@{ - ViewBag.Title = Localizer["Home"]; -} @{ var version = FileVersionInfo.GetVersionInfo(typeof(IdentityServer8.Hosting.IdentityServerMiddleware).Assembly.Location).ProductVersion.Split('+').First(); @@ -32,4 +29,4 @@ and ready to use samples. - \ No newline at end of file + diff --git a/src/Yavsc.Org/Views/Home/Basket.cshtml b/src/Yavsc.Org/Views/Home/Basket.cshtml index 120b96d3..d9882ae3 100644 --- a/src/Yavsc.Org/Views/Home/Basket.cshtml +++ b/src/Yavsc.Org/Views/Home/Basket.cshtml @@ -1,7 +1,3 @@ -@{ - ViewBag.Title = Localizer["Home"]; -} - @Model BasketView
    diff --git a/src/Yavsc.Org/Views/Home/Contact.cshtml b/src/Yavsc.Org/Views/Home/Contact.cshtml index 2488e2a0..d7ae2fa7 100755 --- a/src/Yavsc.Org/Views/Home/Contact.cshtml +++ b/src/Yavsc.Org/Views/Home/Contact.cshtml @@ -1,9 +1,5 @@ @using Microsoft.IdentityModel.Protocols.Configuration @model SiteSettings -@{ - ViewBag.Title = Localizer["Home"]; -} -

    Contact

    @Model.Owner.Name

    @@ -13,4 +9,4 @@
    Support: @(Model.Admin.Name)<@(Model.Admin.EMail)>
    Marketing: @(Model.Owner.Name)<@(Model.Owner.EMail)> -
    \ No newline at end of file + diff --git a/src/Yavsc.Org/Views/Home/Privacy.cshtml b/src/Yavsc.Org/Views/Home/Privacy.cshtml index 34ae1803..2f76522b 100644 --- a/src/Yavsc.Org/Views/Home/Privacy.cshtml +++ b/src/Yavsc.Org/Views/Home/Privacy.cshtml @@ -1,6 +1,3 @@ -@{ - ViewBag.Title = Localizer["Home"]; -} = La confidentialité @@ -13,4 +10,4 @@ ne sont transmis à personne. Seul le système et son link:Contact[possesseur] o De plus, le droit de retrait est permanent et sa mise en oeuvre link:/Account/Delete[immédiate]. - \ No newline at end of file + diff --git a/src/Yavsc.Org/Views/Manage/DoDirectCredit.cshtml b/src/Yavsc.Org/Views/Manage/DoDirectCredit.cshtml index b6a21b88..c51ad586 100644 --- a/src/Yavsc.Org/Views/Manage/DoDirectCredit.cshtml +++ b/src/Yavsc.Org/Views/Manage/DoDirectCredit.cshtml @@ -1,9 +1,5 @@ @model DoDirectCreditViewModel -@{ - ViewBag.Title = Localizer["Manage"]; -} -
    @@ -128,4 +124,4 @@ -
    \ No newline at end of file + diff --git a/src/Yavsc.Org/Views/Manage/ProfileEMailUsage.cshtml b/src/Yavsc.Org/Views/Manage/ProfileEMailUsage.cshtml index 95e70fb0..008897fe 100644 --- a/src/Yavsc.Org/Views/Manage/ProfileEMailUsage.cshtml +++ b/src/Yavsc.Org/Views/Manage/ProfileEMailUsage.cshtml @@ -1,7 +1,4 @@ @model Yavsc.ViewModels.Manage.ProfileEMailUsageViewModel -@{ - ViewBag.Title = Localizer["Manage"]; -}
    @@ -20,4 +17,4 @@ -
    \ No newline at end of file + diff --git a/src/Yavsc.Org/Views/Manage/SetActivity.cshtml b/src/Yavsc.Org/Views/Manage/SetActivity.cshtml index 48690359..41866484 100644 --- a/src/Yavsc.Org/Views/Manage/SetActivity.cshtml +++ b/src/Yavsc.Org/Views/Manage/SetActivity.cshtml @@ -1,5 +1,5 @@ -@model PerformerProfile -@{ ViewBag.Title = "Your performer profile"; } +@model PerformerProfile +@{ ViewBag.Title = "Your performer profile"; } @section header {