Merge branch 'master' of http://git.dd:3000/AppStd/EnvelopeGenerator
This commit is contained in:
commit
fd61d4431f
@ -1,7 +1,7 @@
|
||||
using DigitalData.Core.Abstractions.Application;
|
||||
using DigitalData.Core.DTO;
|
||||
using EnvelopeGenerator.Application.DTOs;
|
||||
using EnvelopeGenerator.Application.DTOs.EnvelopeHistory;
|
||||
using EnvelopeGenerator.Application.DTOs.Receiver;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using EnvelopeGenerator.Infrastructure.Contracts;
|
||||
using static EnvelopeGenerator.Common.Constants;
|
||||
@ -22,7 +22,7 @@ namespace EnvelopeGenerator.Application.Contracts
|
||||
|
||||
Task<IEnumerable<EnvelopeHistoryDto>> ReadRejectedAsync(int envelopeId, string? userReference = null);
|
||||
|
||||
Task<IEnumerable<ReceiverDto>> ReadRejectingReceivers(int envelopeId);
|
||||
Task<IEnumerable<ReceiverReadDto>> ReadRejectingReceivers(int envelopeId);
|
||||
|
||||
Task<DataResult<long>> RecordAsync(int envelopeId, string userReference, EnvelopeStatus status, string? comment = null);
|
||||
}
|
||||
|
||||
@ -24,6 +24,6 @@ namespace EnvelopeGenerator.Application.Contracts
|
||||
|
||||
Task<DataResult<bool>> IsExisting(string envelopeReceiverId);
|
||||
|
||||
Task<DataResult<IEnumerable<EnvelopeReceiverDto>>> ReadByUsernameAsync(string username);
|
||||
Task<DataResult<IEnumerable<EnvelopeReceiverDto>>> ReadByUsernameAsync(string username, int? min_status = null, int? max_status = null, params int[] ignore_statuses);
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,14 @@
|
||||
using DigitalData.Core.Abstractions.Application;
|
||||
using EnvelopeGenerator.Application.DTOs;
|
||||
using DigitalData.Core.DTO;
|
||||
using EnvelopeGenerator.Application.DTOs.Receiver;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Contracts
|
||||
{
|
||||
public interface IReceiverService : IBasicCRUDService<ReceiverDto, Receiver, int>
|
||||
public interface IReceiverService : ICRUDService<ReceiverCreateDto, ReceiverReadDto, ReceiverUpdateDto, Receiver, int>
|
||||
{
|
||||
public Task<DataResult<ReceiverReadDto>> ReadByAsync(string? emailAddress = null, string? signature = null);
|
||||
|
||||
public Task<Result> DeleteByAsync(string? emailAddress = null, string? signature = null);
|
||||
}
|
||||
}
|
||||
@ -12,7 +12,9 @@ namespace EnvelopeGenerator.Application.DTOs
|
||||
|
||||
public int Status { get; set; }
|
||||
|
||||
public string Uuid { get; set; }
|
||||
public string StatusName { get; set; }
|
||||
|
||||
public string Uuid { get; set; }
|
||||
|
||||
[TemplatePlaceholder("[MESSAGE]")]
|
||||
public string Message { get; set; }
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using DigitalData.Core.DTO;
|
||||
using DigitalData.UserManager.Application.DTOs.User;
|
||||
using EnvelopeGenerator.Application.DTOs.Receiver;
|
||||
using static EnvelopeGenerator.Common.Constants;
|
||||
|
||||
namespace EnvelopeGenerator.Application.DTOs.EnvelopeHistory
|
||||
@ -12,7 +13,7 @@ namespace EnvelopeGenerator.Application.DTOs.EnvelopeHistory
|
||||
DateTime AddedWhen,
|
||||
DateTime? ActionDate,
|
||||
UserCreateDto? Sender,
|
||||
ReceiverDto? Receiver,
|
||||
ReceiverReadDto? Receiver,
|
||||
ReferenceType ReferenceType,
|
||||
string? Comment = null) : BaseDTO<long>(Id);
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using DigitalData.EmailProfilerDispatcher.Abstraction.Attributes;
|
||||
using EnvelopeGenerator.Application.DTOs.Receiver;
|
||||
|
||||
namespace EnvelopeGenerator.Application.DTOs
|
||||
{
|
||||
@ -25,6 +26,6 @@ namespace EnvelopeGenerator.Application.DTOs
|
||||
|
||||
public EnvelopeDto? Envelope { get; set; }
|
||||
|
||||
public ReceiverDto? Receiver { get; set; }
|
||||
public ReceiverReadDto? Receiver { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace EnvelopeGenerator.Application.DTOs.Receiver
|
||||
{
|
||||
public record ReceiverCreateDto([EmailAddress] string EmailAddress)
|
||||
{
|
||||
public string Signature => sha256HexOfMail.Value;
|
||||
|
||||
private readonly Lazy<string> sha256HexOfMail = new(() =>
|
||||
{
|
||||
var bytes_arr = Encoding.UTF8.GetBytes(EmailAddress.ToUpper());
|
||||
var hash_arr = SHA256.HashData(bytes_arr);
|
||||
var hexa_str = BitConverter.ToString(hash_arr);
|
||||
return hexa_str.Replace("-", string.Empty);
|
||||
});
|
||||
|
||||
public DateTime AddedWhen { get; } = DateTime.Now;
|
||||
};
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
using DigitalData.Core.DTO;
|
||||
|
||||
namespace EnvelopeGenerator.Application.DTOs
|
||||
namespace EnvelopeGenerator.Application.DTOs.Receiver
|
||||
{
|
||||
public record ReceiverDto(
|
||||
public record ReceiverReadDto(
|
||||
int Id,
|
||||
string EmailAddress,
|
||||
string Signature,
|
||||
@ -0,0 +1,4 @@
|
||||
namespace EnvelopeGenerator.Application.DTOs.Receiver
|
||||
{
|
||||
public record ReceiverUpdateDto();
|
||||
}
|
||||
@ -19,24 +19,15 @@
|
||||
<PackageReference Include="DigitalData.Core.Infrastructure" Version="1.0.1.1" />
|
||||
<PackageReference Include="DigitalData.EmailProfilerDispatcher" Version="1.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.18" />
|
||||
<PackageReference Include="UserManager.Application" Version="1.0.0" />
|
||||
<PackageReference Include="UserManager.Domain" Version="1.0.0" />
|
||||
<PackageReference Include="UserManager.Infrastructure" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="DigitalData.UserManager.Application">
|
||||
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.UserManager.Application.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="DigitalData.UserManager.Domain">
|
||||
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Domain\bin\Debug\net7.0\DigitalData.UserManager.Domain.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="DigitalData.UserManager.Infrastructure">
|
||||
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.UserManager.Infrastructure.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Resources\Model.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using AutoMapper;
|
||||
using EnvelopeGenerator.Application.DTOs;
|
||||
using EnvelopeGenerator.Application.DTOs.EnvelopeHistory;
|
||||
using EnvelopeGenerator.Application.DTOs.Receiver;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.MappingProfiles
|
||||
@ -21,7 +22,9 @@ namespace EnvelopeGenerator.Application.MappingProfiles
|
||||
CreateMap<EnvelopeHistory, EnvelopeHistoryCreateDto>();
|
||||
CreateMap<EnvelopeReceiver, EnvelopeReceiverDto>();
|
||||
CreateMap<EnvelopeType, EnvelopeTypeDto>();
|
||||
CreateMap<Receiver, ReceiverDto>();
|
||||
CreateMap<Receiver, ReceiverReadDto>();
|
||||
CreateMap<Receiver, ReceiverCreateDto>();
|
||||
CreateMap<Receiver, ReceiverUpdateDto>();
|
||||
CreateMap<UserReceiver, UserReceiverDto>();
|
||||
|
||||
// DTO to Entity mappings
|
||||
@ -36,7 +39,9 @@ namespace EnvelopeGenerator.Application.MappingProfiles
|
||||
CreateMap<EnvelopeHistoryCreateDto, EnvelopeHistory>();
|
||||
CreateMap<EnvelopeReceiverDto, EnvelopeReceiver>();
|
||||
CreateMap<EnvelopeTypeDto, EnvelopeType>();
|
||||
CreateMap<ReceiverDto, Receiver>();
|
||||
CreateMap<ReceiverReadDto, Receiver>();
|
||||
CreateMap<ReceiverCreateDto, Receiver>();
|
||||
CreateMap<ReceiverUpdateDto, Receiver>();
|
||||
CreateMap<UserReceiverDto, UserReceiver>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,10 +145,10 @@
|
||||
<value>Englisch</value>
|
||||
</data>
|
||||
<data name="EnvelopeInfo1" xml:space="preserve">
|
||||
<value>Sie müssen {0} Vorgang unterzeichen. Bitte prüfen Sie die Seite {1}.</value>
|
||||
<value>Sie müssen {0} Vorgang unterzeichen. <span class="highlight highlight-envelope-info-1">Bitte prüfen Sie die Seite {1}</span>.</value>
|
||||
</data>
|
||||
<data name="EnvelopeInfo2" xml:space="preserve">
|
||||
<value>Erstellt am {0} von {1}. Sie können den Absender über <a href="mailto:{2}?subject={3}&body=Sehr%20geehrter%20{4}%20{5},%0A%0A%0A">{6}</a> kontaktieren.</value>
|
||||
<value>Erstellt am {0} von {1}. Sie können den Absender über <span class="highlight highlight-envelope-info-2"><a class="mail-link" href="mailto:{2}?subject={3}&body=Sehr%20geehrter%20{4}%20{5},%0A%0A%0A">{6}</a></span> kontaktieren.</value>
|
||||
</data>
|
||||
<data name="Finalize" xml:space="preserve">
|
||||
<value>Abschließen</value>
|
||||
@ -204,6 +204,9 @@
|
||||
<data name="SignDoc" xml:space="preserve">
|
||||
<value>Dokument unterschreiben</value>
|
||||
</data>
|
||||
<data name="SigningProcessTitle" xml:space="preserve">
|
||||
<value>Titel des Unterzeichnungs-Vorgangs</value>
|
||||
</data>
|
||||
<data name="UnexpectedError" xml:space="preserve">
|
||||
<value>Ein unerwarteter Fehler ist aufgetreten.</value>
|
||||
</data>
|
||||
|
||||
@ -145,10 +145,10 @@
|
||||
<value>English</value>
|
||||
</data>
|
||||
<data name="EnvelopeInfo1" xml:space="preserve">
|
||||
<value>You have to sign {0} process. Please check page {1}.</value>
|
||||
<value>You have to sign {0} process. <span class="highlight highlight-envelope-info-1">Please check page {1}</span>.</value>
|
||||
</data>
|
||||
<data name="EnvelopeInfo2" xml:space="preserve">
|
||||
<value>Created on {0} by {1}. You can contact the sender via <a href="mailto:{2}?subject={3}&body=Dear%20{4}%20{5},%0A%0A%0A">{6}</a>.</value>
|
||||
<value>Created on {0} by {1}. You can contact the sender via <span class="highlight highlight-envelope-info-2"><a class="mail-link" href="mailto:{2}?subject={3}&body=Dear%20{4}%20{5},%0A%0A%0A">{6}</a></span>.</value>
|
||||
</data>
|
||||
<data name="Finalize" xml:space="preserve">
|
||||
<value>Finalize</value>
|
||||
@ -204,6 +204,9 @@
|
||||
<data name="SignDoc" xml:space="preserve">
|
||||
<value>Sign document</value>
|
||||
</data>
|
||||
<data name="SigningProcessTitle" xml:space="preserve">
|
||||
<value>Title of the signing process</value>
|
||||
</data>
|
||||
<data name="UnexpectedError" xml:space="preserve">
|
||||
<value>An unexpected error has occurred.</value>
|
||||
</data>
|
||||
|
||||
@ -8,7 +8,7 @@ using static EnvelopeGenerator.Common.Constants;
|
||||
using EnvelopeGenerator.Application.Resources;
|
||||
using DigitalData.Core.DTO;
|
||||
using EnvelopeGenerator.Application.DTOs.EnvelopeHistory;
|
||||
using EnvelopeGenerator.Application.DTOs;
|
||||
using EnvelopeGenerator.Application.DTOs.Receiver;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Services
|
||||
{
|
||||
@ -67,11 +67,11 @@ namespace EnvelopeGenerator.Application.Services
|
||||
await ReadAsync(envelopeId: envelopeId, userReference: userReference, status: (int)EnvelopeStatus.DocumentRejected, withReceiver:true);
|
||||
|
||||
//TODO: use IQueryable in repository to incerease the performance
|
||||
public async Task<IEnumerable<ReceiverDto>> ReadRejectingReceivers(int envelopeId)
|
||||
public async Task<IEnumerable<ReceiverReadDto>> ReadRejectingReceivers(int envelopeId)
|
||||
{
|
||||
var envelopes = await ReadRejectedAsync(envelopeId);
|
||||
return envelopes is null
|
||||
? Enumerable.Empty<ReceiverDto>()
|
||||
? Enumerable.Empty<ReceiverReadDto>()
|
||||
: envelopes
|
||||
.Where(eh => eh?.Receiver != null)
|
||||
.Select(eh => eh.Receiver!);
|
||||
|
||||
@ -116,9 +116,9 @@ namespace EnvelopeGenerator.Application.Services
|
||||
: Result.Success(code);
|
||||
}
|
||||
|
||||
public async Task<DataResult<IEnumerable<EnvelopeReceiverDto>>> ReadByUsernameAsync(string username)
|
||||
public async Task<DataResult<IEnumerable<EnvelopeReceiverDto>>> ReadByUsernameAsync(string username, int? min_status = null, int? max_status = null, params int[] ignore_statuses)
|
||||
{
|
||||
var er_list = await _repository.ReadByUsernameAsync(username: username);
|
||||
var er_list = await _repository.ReadByUsernameAsync(username: username, min_status: min_status, max_status: max_status, ignore_statuses: ignore_statuses);
|
||||
var dto_list = _mapper.MapOrThrow<IEnumerable<EnvelopeReceiverDto>>(er_list);
|
||||
return Result.Success(dto_list);
|
||||
}
|
||||
|
||||
@ -2,19 +2,39 @@
|
||||
using DigitalData.Core.Application;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using EnvelopeGenerator.Application.Contracts;
|
||||
using EnvelopeGenerator.Application.DTOs;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using EnvelopeGenerator.Infrastructure.Contracts;
|
||||
using EnvelopeGenerator.Application.Resources;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using EnvelopeGenerator.Application.DTOs.Receiver;
|
||||
using DigitalData.Core.DTO;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Services
|
||||
{
|
||||
public class ReceiverService : BasicCRUDService<IReceiverRepository, ReceiverDto, Receiver, int>, IReceiverService
|
||||
public class ReceiverService : CRUDService<IReceiverRepository, ReceiverCreateDto, ReceiverReadDto, ReceiverUpdateDto, Receiver, int>, IReceiverService
|
||||
{
|
||||
public ReceiverService(IReceiverRepository repository, IStringLocalizer<Resource> localizer, IMapper mapper)
|
||||
: base(repository, mapper)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<DataResult<ReceiverReadDto>> ReadByAsync(string? emailAddress = null, string? signature = null)
|
||||
{
|
||||
var rcv = await _repository.ReadByAsync(emailAddress: emailAddress, signature: signature);
|
||||
|
||||
if (rcv is null)
|
||||
return Result.Fail<ReceiverReadDto>();
|
||||
|
||||
return Result.Success(_mapper.MapOrThrow<ReceiverReadDto>(rcv));
|
||||
}
|
||||
|
||||
public async Task<Result> DeleteByAsync(string? emailAddress = null, string? signature = null)
|
||||
{
|
||||
var rcv = await _repository.ReadByAsync(emailAddress: emailAddress, signature: signature);
|
||||
|
||||
if (rcv is null)
|
||||
return Result.Fail();
|
||||
|
||||
return await _repository.DeleteAsync(rcv) ? Result.Success() : Result.Fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -21,6 +21,9 @@ namespace EnvelopeGenerator.Domain.Entities
|
||||
[Column("STATUS")]
|
||||
public int Status { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public string StatusName => ((Constants.EnvelopeStatus)Status).ToString();
|
||||
|
||||
[Required]
|
||||
[Column("ENVELOPE_UUID", TypeName = "nvarchar(36)")]
|
||||
public string Uuid { get; set; }
|
||||
|
||||
@ -10,14 +10,14 @@ namespace EnvelopeGenerator.Domain.Entities
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
[Column("GUID")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
|
||||
[Required, EmailAddress]
|
||||
[Column("EMAIL_ADDRESS", TypeName = "nvarchar(128)")]
|
||||
public string EmailAddress { get; set; }
|
||||
public required string EmailAddress { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("SIGNATURE", TypeName = "nvarchar(64)")]
|
||||
public string Signature { get; set; }
|
||||
public required string Signature { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("ADDED_WHEN", TypeName = "datetime")]
|
||||
|
||||
@ -13,16 +13,11 @@
|
||||
<PackageReference Include="DigitalData.Core.DTO" Version="1.0.0" />
|
||||
<PackageReference Include="DigitalData.Core.Infrastructure" Version="1.0.1.1" />
|
||||
<PackageReference Include="DigitalData.EmailProfilerDispatcher.Abstraction" Version="1.0.0" />
|
||||
<PackageReference Include="UserManager.Domain" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\EnvelopeGenerator.Common\EnvelopeGenerator.Common.vbproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="DigitalData.UserManager.Domain">
|
||||
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Domain\bin\Debug\net7.0\DigitalData.UserManager.Domain.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -31,9 +31,14 @@
|
||||
],
|
||||
"styles": [
|
||||
"@angular/material/prebuilt-themes/indigo-pink.css",
|
||||
"src/styles.scss"
|
||||
"src/styles.scss",
|
||||
"node_modules/bootstrap/dist/css/bootstrap.min.css"
|
||||
],
|
||||
"scripts": [
|
||||
"node_modules/jquery/dist/jquery.min.js",
|
||||
"node_modules/@popperjs/core/dist/umd/popper.min.js",
|
||||
"node_modules/bootstrap/dist/js/bootstrap.min.js"
|
||||
],
|
||||
"scripts": [],
|
||||
"server": "src/main.server.ts",
|
||||
"prerender": true,
|
||||
"ssr": {
|
||||
@ -106,7 +111,7 @@
|
||||
],
|
||||
"scripts": [
|
||||
"node_modules/jquery/dist/jquery.min.js",
|
||||
"node_modules/popper.js/dist/umd/popper.min.js",
|
||||
"node_modules/@popperjs/core/dist/umd/popper.min.js",
|
||||
"node_modules/bootstrap/dist/js/bootstrap.min.js"
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "envelope-generator-ui",
|
||||
"version": "0.0.0",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "envelope-generator-ui",
|
||||
"version": "0.0.0",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@angular/animations": "^17.3.0",
|
||||
"@angular/cdk": "^17.3.10",
|
||||
@ -24,11 +24,12 @@
|
||||
"@generic-ui/hermes": "^0.21.0",
|
||||
"@generic-ui/ngx-grid": "^0.21.0",
|
||||
"@ng-bootstrap/ng-bootstrap": "^16.0.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"bootstrap": "^5.3.3",
|
||||
"express": "^4.18.2",
|
||||
"jquery": "^3.7.1",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"uuid": "^10.0.0",
|
||||
"zone.js": "~0.14.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -39,6 +40,7 @@
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^18.18.0",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"jasmine-core": "~5.1.0",
|
||||
"karma": "~6.4.0",
|
||||
"karma-chrome-launcher": "~3.2.0",
|
||||
@ -4259,6 +4261,7 @@
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
@ -4871,6 +4874,12 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/uuid": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
|
||||
"integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz",
|
||||
@ -8448,6 +8457,11 @@
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
},
|
||||
"node_modules/jquery": {
|
||||
"version": "3.7.1",
|
||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
|
||||
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@ -11617,6 +11631,15 @@
|
||||
"websocket-driver": "^0.7.4"
|
||||
}
|
||||
},
|
||||
"node_modules/sockjs/node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/socks": {
|
||||
"version": "2.8.3",
|
||||
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz",
|
||||
@ -12396,10 +12419,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"dev": true,
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
|
||||
"integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "envelope-generator-ui",
|
||||
"version": "0.0.0",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
@ -27,11 +27,12 @@
|
||||
"@generic-ui/hermes": "^0.21.0",
|
||||
"@generic-ui/ngx-grid": "^0.21.0",
|
||||
"@ng-bootstrap/ng-bootstrap": "^16.0.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"bootstrap": "^5.3.3",
|
||||
"express": "^4.18.2",
|
||||
"jquery": "^3.7.1",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"uuid": "^10.0.0",
|
||||
"zone.js": "~0.14.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -42,6 +43,7 @@
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^18.18.0",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"jasmine-core": "~5.1.0",
|
||||
"karma": "~6.4.0",
|
||||
"karma-chrome-launcher": "~3.2.0",
|
||||
|
||||
@ -2,9 +2,11 @@ import { Routes } from '@angular/router';
|
||||
import { HomeComponent } from '../app/pages/home/home.component'
|
||||
import { authGuard } from './guards/auth.guard'
|
||||
import { EnvelopeComponent } from './pages/envelope/envelope.component';
|
||||
import { EnvelopeCreationComponent } from './pages/envelope-creation/envelope-creation.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
{ path: '', component: HomeComponent },
|
||||
{ path: '', component: EnvelopeComponent, canActivate: [authGuard] },
|
||||
{ path: 'login', component: HomeComponent },
|
||||
{ path: 'envelope', component: EnvelopeComponent, canActivate: [authGuard] }
|
||||
{ path: 'envelope', component: EnvelopeComponent, canActivate: [authGuard] },
|
||||
{ path: 'envelope-creation', component: EnvelopeCreationComponent, canActivate: [authGuard] }
|
||||
];
|
||||
@ -0,0 +1,9 @@
|
||||
<mat-form-field class="example-form-field">
|
||||
<mat-label>{{label}}</mat-label>
|
||||
<input matInput type="text" [(ngModel)]="value">
|
||||
@if (value) {
|
||||
<button matSuffix mat-icon-button aria-label="Clear" (click)="value=''">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
}
|
||||
</mat-form-field>
|
||||
@ -0,0 +1,7 @@
|
||||
.mat-stepper-vertical {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.mat-mdc-form-field {
|
||||
margin-top: 16px;
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ClearableInputComponent } from './clearable-input.component';
|
||||
|
||||
describe('ClearableInputComponent', () => {
|
||||
let component: ClearableInputComponent;
|
||||
let fixture: ComponentFixture<ClearableInputComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ClearableInputComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ClearableInputComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,18 @@
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {MatIconModule} from '@angular/material/icon';
|
||||
import {MatButtonModule} from '@angular/material/button';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {MatInputModule} from '@angular/material/input';
|
||||
import {MatFormFieldModule} from '@angular/material/form-field';
|
||||
|
||||
@Component({
|
||||
selector: 'clearable-input',
|
||||
standalone: true,
|
||||
imports: [MatFormFieldModule, MatInputModule, FormsModule, MatButtonModule, MatIconModule],
|
||||
templateUrl: './clearable-input.component.html',
|
||||
styleUrl: './clearable-input.component.scss'
|
||||
})
|
||||
export class ClearableInputComponent {
|
||||
@Input() public value: string = '';
|
||||
@Input() public label: string = '';
|
||||
}
|
||||
@ -1,13 +1,48 @@
|
||||
<gui-grid
|
||||
[columns]="columns"
|
||||
[source]="source"
|
||||
[columnMenu]="columnMenu"
|
||||
[paging]="paging"
|
||||
[sorting]="sorting"
|
||||
[searching]="searching"
|
||||
[infoPanel]="infoPanel"
|
||||
[localization]="localization"
|
||||
[theme]="theme"
|
||||
[autoResizeWidth] = "true"
|
||||
>
|
||||
</gui-grid>
|
||||
<table #table mat-table [dataSource]="data" class="mat-elevation-z8">
|
||||
|
||||
@for (colId of displayedColumns; track colId) {
|
||||
<ng-container matColumnDef="{{colId}}">
|
||||
<th mat-header-cell *matHeaderCellDef> {{schema[colId].header}} </th>
|
||||
<td mat-cell *matCellDef="let element"> {{schema[colId].field(element)}} </td>
|
||||
</ng-container>
|
||||
}
|
||||
|
||||
<ng-container matColumnDef="expand">
|
||||
<th mat-header-cell *matHeaderCellDef aria-label="row actions"> </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button mat-icon-button aria-label="expand row"
|
||||
(click)="(expandedElement = expandedElement === element ? null : element); $event.stopPropagation()">
|
||||
@if (expandedElement === element) {
|
||||
<mat-icon>keyboard_arrow_up</mat-icon>
|
||||
} @else {
|
||||
<mat-icon>keyboard_arrow_down</mat-icon>
|
||||
}
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Expanded Content Column - The detail row is made up of this one column that spans across all columns -->
|
||||
<ng-container matColumnDef="expandedDetail">
|
||||
<td mat-cell *matCellDef="let element" [attr.colspan]="columnsToDisplayWithExpand.length">
|
||||
<div class="example-element-detail" [@detailExpand]="element == expandedElement ? 'expanded' : 'collapsed'">
|
||||
<div class="example-element-diagram">
|
||||
<div class="example-element-position"> {{"element.position"}} </div>
|
||||
<div class="example-element-symbol"> {{"element.symbol"}} </div>
|
||||
<div class="example-element-name"> {{"element.name"}} </div>
|
||||
<div class="example-element-weight"> {{"element.weight"}} </div>
|
||||
</div>
|
||||
<div class="example-element-description">
|
||||
{{"element.description"}}
|
||||
<span class="example-element-description-attribution"> -- Wikipedia </span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="columnsToDisplayWithExpand"></tr>
|
||||
<tr mat-row *matRowDef="let element; columns: columnsToDisplayWithExpand;" class="example-element-row"
|
||||
[class.example-expanded-row]="expandedElement === element"
|
||||
(click)="expandedElement = expandedElement === element ? null : element">
|
||||
</tr>
|
||||
<!--<tr mat-row *matRowDef="let row; columns: ['expandedDetail']; when: isExpandedRow" class="example-detail-row"></tr>-->
|
||||
</table>
|
||||
@ -0,0 +1,31 @@
|
||||
.example-element-row td {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
.example-element-detail {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.example-element-diagram {
|
||||
min-width: 80px;
|
||||
border: 2px solid black;
|
||||
padding: 8px;
|
||||
font-weight: lighter;
|
||||
margin: 8px 0;
|
||||
height: 104px;
|
||||
}
|
||||
|
||||
.example-element-symbol {
|
||||
font-weight: bold;
|
||||
font-size: 40px;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.example-element-description {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.example-element-description-attribution {
|
||||
opacity: 0.5;
|
||||
}
|
||||
@ -1,132 +1,74 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, Input, ViewChild } from '@angular/core';
|
||||
import { EnvelopeReceiverService } from '../../services/envelope-receiver.service';
|
||||
import { GuiColumn, GuiColumnMenu, GuiGridModule, GuiInfoPanel, GuiLocalization, GuiPaging, GuiPagingDisplay, GuiSearching, GuiSorting, GuiSummaries, GuiTheme } from '@generic-ui/ngx-grid';
|
||||
import { MatTable, MatTableModule } from '@angular/material/table';
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
|
||||
@Component({
|
||||
selector: 'app-envelope-table',
|
||||
standalone: true,
|
||||
imports: [GuiGridModule],
|
||||
imports: [MatTableModule, CommonModule, MatTableModule, MatButtonModule, MatIconModule],
|
||||
templateUrl: './envelope-table.component.html',
|
||||
animations: [
|
||||
trigger('detailExpand', [
|
||||
state('collapsed,void', style({ height: '0px', minHeight: '0' })),
|
||||
state('expanded', style({ height: '*' })),
|
||||
transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
|
||||
]),
|
||||
],
|
||||
styleUrl: './envelope-table.component.scss'
|
||||
})
|
||||
export class EnvelopeTableComponent {
|
||||
|
||||
columnMenu: GuiColumnMenu = {
|
||||
enabled: true,
|
||||
sort: true,
|
||||
columnsManager: true
|
||||
};
|
||||
@Input() data: Array<any> = []
|
||||
|
||||
sorting: GuiSorting = {
|
||||
enabled: true,
|
||||
multiSorting: true
|
||||
};
|
||||
@Input() options?: { min_status?: number; max_status?: number; ignore_status?: number[] }
|
||||
|
||||
paging: GuiPaging = {
|
||||
enabled: true,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 25, 50],
|
||||
pagerTop: true,
|
||||
pagerBottom: true,
|
||||
display: GuiPagingDisplay.ADVANCED
|
||||
};
|
||||
@Input() displayedColumns: string[] = ['title', 'status', 'type', 'privateMessage', 'addedWhen'];
|
||||
|
||||
searching: GuiSearching = {
|
||||
enabled: true
|
||||
};
|
||||
@Input() schema: Record<string, { header: string; field: (element: any) => any; }> = {
|
||||
'title': {
|
||||
header: 'Title',
|
||||
field: (element: any) => element.envelope.title
|
||||
},
|
||||
'status': {
|
||||
header: 'Status',
|
||||
field: (element: any) => element.envelope.statusName
|
||||
},
|
||||
'type': {
|
||||
header: 'Type',
|
||||
field: (element: any) => element.envelope.contractType
|
||||
},
|
||||
'privateMessage': {
|
||||
header: 'Private Message',
|
||||
field: (element: any) => element.privateMessage
|
||||
},
|
||||
'addedWhen': {
|
||||
header: 'Added When',
|
||||
field: (element: any) => element.addedWhen
|
||||
},
|
||||
}
|
||||
|
||||
summaries: GuiSummaries = {
|
||||
enabled: true
|
||||
};
|
||||
columnsToDisplayWithExpand = [...this.displayedColumns, 'expand'];
|
||||
|
||||
infoPanel: GuiInfoPanel = {
|
||||
enabled: true,
|
||||
infoDialog: false,
|
||||
columnsManager: true,
|
||||
schemaManager: true
|
||||
};
|
||||
expandedElement: any | null;
|
||||
|
||||
localization: GuiLocalization = {
|
||||
translationResolver: (key: string, value: string) => EnvelopeTableComponent.Translation[key] ?? value
|
||||
};
|
||||
|
||||
theme: GuiTheme = GuiTheme.FABRIC;
|
||||
|
||||
source: Array<any> = []
|
||||
@ViewChild(MatTable) table!: MatTable<any>;
|
||||
|
||||
constructor(private erService: EnvelopeReceiverService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
this.source = await this.erService.getEnvelopeReceiver();
|
||||
if (this.data.length === 0)
|
||||
this.data = await this.erService.getEnvelopeReceiverAsync(this.options);
|
||||
}
|
||||
|
||||
columns: Array<GuiColumn> = [
|
||||
{
|
||||
header: 'Title',
|
||||
field: er => er.envelope.title
|
||||
},
|
||||
{
|
||||
header: "Status",
|
||||
field: er => er.envelope.status
|
||||
},
|
||||
{
|
||||
header: 'Type',
|
||||
field: er => er.envelope.contractType
|
||||
},
|
||||
{
|
||||
header: 'PrivateMessage',
|
||||
field: 'privateMessage'
|
||||
},
|
||||
{
|
||||
header: 'AddedWhen',
|
||||
field: 'addedWhen'
|
||||
}];
|
||||
public updateTable() {
|
||||
this.table.renderRows();
|
||||
}
|
||||
|
||||
static readonly Translation: { [key: string]: string } = {
|
||||
"sourceEmpty": "There are no items to show.",
|
||||
"pagingItemsPerPage": "Items per page:",
|
||||
"pagingOf": "of",
|
||||
"pagingNextPage": "Next",
|
||||
"pagingPrevPage": "Prev",
|
||||
"pagingNoItems": "There is no items.",
|
||||
"infoPanelShowing": "Showing",
|
||||
"infoPanelItems": "items",
|
||||
"infoPanelOutOf": "out of",
|
||||
"infoPanelThemeMangerTooltipText": "Theme manager",
|
||||
"infoPanelColumnManagerTooltipText": "Column manager",
|
||||
"infoPanelInfoTooltipText": "info",
|
||||
"themeManagerModalTitle": "Theme manager",
|
||||
"themeManagerModalTheme": "Theme:",
|
||||
"themeManagerModalRowColoring": "Row coloring:",
|
||||
"themeManagerModalVerticalGrid": "Vertical grid",
|
||||
"themeManagerModalHorizontalGrid": "HorizontalGrid",
|
||||
"columnManagerModalTitle": "Manage columns",
|
||||
"headerMenuMainTab": "Menu",
|
||||
"headerMenuMainTabColumnSort": "Column sort",
|
||||
"headerMenuMainTabHideColumn": "Hide column",
|
||||
"headerMenuMainTabHighlightColumn": "Highlight",
|
||||
"headerMenuMainTabMoveLeft": "Move left",
|
||||
"headerMenuMainTabMoveRight": "Move right",
|
||||
"headerMenuMainTabColumnSortAscending": "Ascending",
|
||||
"headerMenuMainTabColumnSortDescending": "Descending",
|
||||
"headerMenuMainTabColumnSortNone": "None",
|
||||
"headerMenuFilterTab": "Filter",
|
||||
"headerMenuColumnsTab": "Columns",
|
||||
"summariesCount": "Count",
|
||||
"summariesDist": "Dist",
|
||||
"summariesSum": "Sum",
|
||||
"summariesAvg": "Avg",
|
||||
"summariesMin": "Min",
|
||||
"summariesMax": "Max",
|
||||
"summariesMed": "Med",
|
||||
"summariesTruthy": "Truthy",
|
||||
"summariesFalsy": "Falsy",
|
||||
"summariesDistinctValuesTooltip": "Distinct values",
|
||||
"summariesAverageTooltip": "Average",
|
||||
"summariesMinTooltip": "Min",
|
||||
"summariesMaxTooltip": "Max",
|
||||
"summariesMedTooltip": "Median",
|
||||
"summariesCountTooltip": "Number of items in the grid"
|
||||
isExpandedRow(index: number, row: any): boolean {
|
||||
return (row?.envelopeId === this.expandedElement?.envelopeId) && (row?.receiverId === this.expandedElement?.receiverId);
|
||||
}
|
||||
}
|
||||
@ -1,23 +1,25 @@
|
||||
<!-- src/app/components/login/login.component.html -->
|
||||
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
|
||||
<div class="mb-3">
|
||||
<mat-form-field>
|
||||
<mat-label>Username</mat-label>
|
||||
<input matInput formControlName="username">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<mat-form-field>
|
||||
<mat-label>Enter your password</mat-label>
|
||||
<input matInput [type]="hide ? 'password' : 'text'" formControlName="password">
|
||||
<button type="button" mat-icon-button matSuffix (click)="togglePWVisibility($event)" [attr.aria-label]="'Hide password'"
|
||||
[attr.aria-pressed]="hide">
|
||||
<mat-icon>{{hide ? 'visibility_off' : 'visibility'}}</mat-icon>
|
||||
</button>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<button mat-flat-button color="primary" type="submit">Log In</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="mb-3">
|
||||
<mat-form-field>
|
||||
<mat-label>Username</mat-label>
|
||||
<input matInput formControlName="username">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<mat-form-field>
|
||||
<mat-label>Enter your password</mat-label>
|
||||
<input matInput [type]="hide ? 'password' : 'text'" formControlName="password">
|
||||
<button type="button" mat-icon-button matSuffix (click)="togglePWVisibility($event)"
|
||||
[attr.aria-label]="'Hide password'" [attr.aria-pressed]="hide">
|
||||
<mat-icon>{{hide ? 'visibility_off' : 'visibility'}}</mat-icon>
|
||||
</button>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn" mat-flat-button color="primary">
|
||||
<span class="me-2">Anmeldung</span>
|
||||
<span class="p-0 m-0" [class.spinner-border]="wait4waitRes" [class.spinner-border-sm]="wait4waitRes" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@ -9,7 +9,7 @@ import { Router } from '@angular/router';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
selector: 'login',
|
||||
standalone: true,
|
||||
imports: [MatFormFieldModule, MatInputModule, MatButtonModule, MatIconModule, ReactiveFormsModule],
|
||||
templateUrl: './login.component.html',
|
||||
@ -18,6 +18,7 @@ import { ReactiveFormsModule } from '@angular/forms';
|
||||
export class LoginComponent {
|
||||
hide = true;
|
||||
loginForm: FormGroup;
|
||||
wait4waitRes: boolean = false;
|
||||
|
||||
constructor(private fb: FormBuilder, private authService: AuthService, private router: Router) {
|
||||
this.loginForm = this.fb.group({
|
||||
@ -33,12 +34,14 @@ export class LoginComponent {
|
||||
|
||||
onSubmit() {
|
||||
if (this.loginForm.valid) {
|
||||
this.wait4waitRes = true;
|
||||
this.authService.login(this.loginForm.value).subscribe({
|
||||
next: () => {
|
||||
this.router.navigate(['/envelope']);
|
||||
this.wait4waitRes = false;
|
||||
this.router.navigate(['/envelope']);
|
||||
},
|
||||
error: error => {
|
||||
console.error('Login failed', error);
|
||||
this.wait4waitRes = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,6 +1,40 @@
|
||||
<mat-toolbar color="primary" class="toolbar">
|
||||
<button mat-icon-button>
|
||||
<mat-icon>menu</mat-icon>
|
||||
</button>
|
||||
<span>signFlow</span>
|
||||
</mat-toolbar>
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-lg bg-body-tertiary fs-5">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand fs-2 fw-bold" [routerLink]="['/']">SignFlow</a>
|
||||
<!-- Navbars -->
|
||||
<div *ngIf="isLogedIn()" class="navbar-collapse collapse d-sm-inline-flex justify-content-center"
|
||||
[ngClass]="{ show: isExpanded }">
|
||||
<ul class=" navbar-nav flex-grow">
|
||||
<li class="nav-item" [routerLinkActive]="['link-active']" [routerLinkActiveOptions]="{exact: true}">
|
||||
<button class="btn nav-link d-inline-flex flex-column align-items-center justify-content-center mx-2" (click)="router.navigate(['/'])">
|
||||
<mat-icon>mail</mat-icon>
|
||||
<span class="fs-6">Umschläge</span>
|
||||
</button>
|
||||
<button class="btn nav-link d-inline-flex flex-column align-items-center justify-content-center mx-2" (click)="router.navigate(['/envelope-creation'])">
|
||||
<mat-icon>forward_to_inbox</mat-icon>
|
||||
<span class="fs-6">Neuer Umschlag</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Right menu -->
|
||||
<div class="navbar-collapse justify-content-end me-5">
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse"
|
||||
aria-label="Toggle navigation" [attr.aria-expanded]="isExpanded" (click)="toggle()">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<button class="fs-5 btn d-flex align-items-center ms-2 me-0 pe-0" type="button" (click)="logInOut()">
|
||||
@if(isLogedIn()){
|
||||
<mat-icon>logout</mat-icon>
|
||||
<span class="fs-6 ms-1">Abmelden</span>
|
||||
}
|
||||
@else {
|
||||
<mat-icon>login</mat-icon>
|
||||
<span class="fs-6 ms-1">Anmelden</span>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
@ -1,23 +1,55 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { Component, QueryList, ViewChildren } from '@angular/core';
|
||||
import { Router, RouterModule } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatBadgeModule } from '@angular/material/badge';
|
||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { AuthService } from '../../services/auth.service'
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-navbar',
|
||||
standalone: true,
|
||||
imports: [
|
||||
MatToolbarModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatSidenavModule,
|
||||
MatListModule
|
||||
],
|
||||
imports: [RouterModule, CommonModule, MatIconModule, MatBadgeModule, MatSlideToggleModule, FormsModule, MatButtonModule, MatTooltipModule],
|
||||
selector: 'app-navbar',
|
||||
templateUrl: './navbar.component.html',
|
||||
styleUrl: './navbar.component.scss'
|
||||
styleUrls: ['./navbar.component.scss']
|
||||
})
|
||||
export class NavbarComponent {
|
||||
isLogedIn(): boolean {
|
||||
return this.authService.IsAuthenticated;
|
||||
}
|
||||
|
||||
}
|
||||
async logInOut(): Promise<void> {
|
||||
if (this.isLogedIn())
|
||||
return this.authService.logoutAsync().then(() => {
|
||||
this.router.navigate(['/']);
|
||||
})
|
||||
else
|
||||
this.router.navigate(['/login']);
|
||||
}
|
||||
|
||||
isExpanded = false;
|
||||
|
||||
isChecked = true;
|
||||
|
||||
constructor(public router: Router, public authService: AuthService) {
|
||||
}
|
||||
|
||||
get isDarkTheme(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
collapse() {
|
||||
this.isExpanded = false;
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.isExpanded = !this.isExpanded;
|
||||
}
|
||||
|
||||
@ViewChildren(MatTooltip) tooltips: QueryList<MatTooltip> | undefined;
|
||||
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
<form class="example-form">
|
||||
<mat-form-field class="example-full-width">
|
||||
<mat-label>Email</mat-label>
|
||||
<input type="text" aria-label="Email" matInput [formControl]="control" [matAutocomplete]="auto">
|
||||
@if (text) {
|
||||
<button matSuffix mat-icon-button aria-label="Clear" (click)="text=''">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
}
|
||||
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
|
||||
@for (option of filteredOptions | async; track option) {
|
||||
<mat-option [value]="option">{{option}}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
@ -0,0 +1,7 @@
|
||||
.mat-stepper-vertical {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.mat-mdc-form-field {
|
||||
margin-top: 16px;
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ReceiverInputComponent } from './receiver-input.component';
|
||||
|
||||
describe('ReceiverInputComponent', () => {
|
||||
let component: ReceiverInputComponent;
|
||||
let fixture: ComponentFixture<ReceiverInputComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ReceiverInputComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ReceiverInputComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,66 @@
|
||||
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
||||
import { ReactiveFormsModule, FormControl } from '@angular/forms';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { Observable, map, startWith } from 'rxjs';
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
|
||||
@Component({
|
||||
selector: 'receiver-input',
|
||||
standalone: true,
|
||||
imports: [
|
||||
MatInputModule,
|
||||
MatAutocompleteModule,
|
||||
ReactiveFormsModule,
|
||||
AsyncPipe,
|
||||
MatFormFieldModule, MatInputModule, FormsModule, MatButtonModule, MatIconModule
|
||||
],
|
||||
templateUrl: './receiver-input.component.html',
|
||||
styleUrl: './receiver-input.component.scss'
|
||||
})
|
||||
export class ReceiverInputComponent implements OnInit, OnChanges {
|
||||
ngOnInit(): void {
|
||||
this.setupFiltering();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes['options']) {
|
||||
this.setupFiltering();
|
||||
}
|
||||
}
|
||||
|
||||
private setupFiltering(): void {
|
||||
this.filteredOptions = this.control.valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => this.filter(value || '', this.options)),
|
||||
);
|
||||
}
|
||||
|
||||
control = new FormControl('');
|
||||
filteredOptions!: Observable<string[]>;
|
||||
|
||||
|
||||
@Input() options: string[] = [];
|
||||
@Input() filter: (value: string, options: string[]) => string[] = value => {
|
||||
const filterValue = value.toLowerCase();
|
||||
return this.options.filter(option => option.toLowerCase().includes(filterValue));
|
||||
}
|
||||
|
||||
public get text(): string {
|
||||
return this.control.value || '';
|
||||
}
|
||||
|
||||
public set text(value: string) {
|
||||
this.control.setValue(value)
|
||||
}
|
||||
|
||||
public get lenght(): number {
|
||||
return this.text.length;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
<table mat-table [dataSource]="receiverData" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="email">
|
||||
<th mat-header-cell *matHeaderCellDef> Email </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<receiver-input [options]="receiver_mails" [filter]="receiver_filter"></receiver-input>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef> Anrede Email </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<clearable-input [label]="'Anrede Email'" [value]="element.name"></clearable-input>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="accessCode">
|
||||
<th mat-header-cell *matHeaderCellDef> Zugriffscode </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.accessCode}} </td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ReceiverTableComponent } from './receiver-table.component';
|
||||
|
||||
describe('ReceiverTableComponent', () => {
|
||||
let component: ReceiverTableComponent;
|
||||
let fixture: ComponentFixture<ReceiverTableComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ReceiverTableComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ReceiverTableComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,82 @@
|
||||
import { Component, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { ReceiverService } from '../../services/receiver.service'
|
||||
import { ReceiverInputComponent } from '../receiver-input/receiver-input.component';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { ClearableInputComponent } from '../clearable-input/clearable-input.component'
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
@Component({
|
||||
selector: 'receiver-table',
|
||||
standalone: true,
|
||||
imports: [
|
||||
MatTableModule,
|
||||
FormsModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatAutocompleteModule,
|
||||
ReactiveFormsModule,
|
||||
AsyncPipe,
|
||||
ReceiverInputComponent,
|
||||
MatFormFieldModule, MatInputModule, FormsModule, MatButtonModule, MatIconModule, ClearableInputComponent
|
||||
],
|
||||
templateUrl: './receiver-table.component.html',
|
||||
styleUrl: './receiver-table.component.scss'
|
||||
})
|
||||
export class ReceiverTableComponent implements OnInit {
|
||||
|
||||
constructor(private receiverService: ReceiverService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
this.receiver_mails = await this.receiverService.getReceiverAsync().then((receivers: any[]) => receivers.map(r => r.emailAddress));
|
||||
}
|
||||
|
||||
receiver_filter: (value: string, options: string[]) => string[] = (value, options) => {
|
||||
const filterValue = value.toLowerCase();
|
||||
|
||||
// if added into last row
|
||||
if (value.length > 0 && (this.receiverInputs.at(-1)?.lenght ?? 0) > 0) {
|
||||
this.receiverData.at(-1)!.accessCode = generateAccessCode();
|
||||
this.receiverData.push({ email: "", name: "", accessCode: "" });
|
||||
this.update();
|
||||
}
|
||||
else if (value.length == 0) {
|
||||
for (var i = 0; i < this.receiverInputs.length - 1; i++) {
|
||||
if (this.receiverInputs[i].lenght === 0) {
|
||||
this.receiverData.splice(i, 1);
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return options.filter(option => option.toLowerCase().includes(filterValue));
|
||||
}
|
||||
|
||||
receiver_mails: string[] = [];
|
||||
|
||||
@ViewChildren(ReceiverInputComponent) receiverInputsQueryList!: QueryList<ReceiverInputComponent>;
|
||||
get receiverInputs(): ReceiverInputComponent[] {
|
||||
return this.receiverInputsQueryList?.toArray() ?? [];
|
||||
}
|
||||
|
||||
receiverData: { email: string; name: string; accessCode: string }[] = [
|
||||
{ email: "", name: "", accessCode: "" }
|
||||
];
|
||||
|
||||
displayedColumns: string[] = ['email', 'name', 'accessCode'];
|
||||
|
||||
public update() {
|
||||
this.receiverData = [...this.receiverData];
|
||||
}
|
||||
}
|
||||
|
||||
function generateAccessCode(): string {
|
||||
const uuid = uuidv4();
|
||||
return uuid.replace(/-/g, '').substring(1, 7).toUpperCase();
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
enum Status {
|
||||
Invalid = 0,
|
||||
EnvelopeCreated = 1001,
|
||||
EnvelopeSaved = 1002,
|
||||
EnvelopeQueued = 1003,
|
||||
EnvelopeSent = 1004,
|
||||
EnvelopePartlySigned = 1005,
|
||||
EnvelopeCompletelySigned = 1006,
|
||||
EnvelopeReportCreated = 1007,
|
||||
EnvelopeArchived = 1008,
|
||||
EnvelopeDeleted = 1009,
|
||||
AccessCodeRequested = 2001,
|
||||
AccessCodeCorrect = 2002,
|
||||
AccessCodeIncorrect = 2003,
|
||||
DocumentOpened = 2004,
|
||||
DocumentSigned = 2005,
|
||||
SignatureConfirmed = 2006,
|
||||
MessageInvitationSent = 3001,
|
||||
MessageAccessCodeSent = 3002,
|
||||
MessageConfirmationSent = 3003,
|
||||
MessageDeletionSent = 3004,
|
||||
MessageCompletionSent = 3005
|
||||
}
|
||||
|
||||
export { Status }
|
||||
@ -0,0 +1,56 @@
|
||||
<mat-stepper orientation="vertical" [linear]="isLinear" #stepper>
|
||||
<mat-step>
|
||||
<form>
|
||||
<ng-template matStepLabel>Titel & Vertragstyp</ng-template>
|
||||
<mat-form-field>
|
||||
<mat-label>Titel</mat-label>
|
||||
<input matInput placeholder="Arbeitsvertrag" [formControl]="titelControl" required>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="ms-5">
|
||||
<mat-label>Vertragstyp</mat-label>
|
||||
<mat-select [formControl]="typeControl" required>
|
||||
@for (type of types; track type) {
|
||||
<mat-option [value]="type.value">{{type.viewValue}}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<div>
|
||||
<button mat-button matStepperNext>
|
||||
Next
|
||||
<mat-icon>arrow_forward_ios</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step>
|
||||
<form>
|
||||
<ng-template matStepLabel>Empfänger</ng-template>
|
||||
<receiver-table></receiver-table>
|
||||
<div>
|
||||
<button mat-button matStepperPrevious>Back</button>
|
||||
<button mat-button matStepperNext>
|
||||
Next
|
||||
<mat-icon>arrow_forward_ios</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step>
|
||||
<form>
|
||||
<ng-template matStepLabel>Neues Dokument</ng-template>
|
||||
<input type="file" class="file-input" (change)="onFileSelected($event)" #fileUpload>
|
||||
<div class="file-upload">
|
||||
|
||||
{{fileName || "Noch keine Datei hochgeladen."}}
|
||||
|
||||
<button mat-mini-fab color="primary" class="upload-btn" (click)="fileUpload.click()">
|
||||
<mat-icon>attach_file</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div>
|
||||
<button mat-button matStepperPrevious>Back</button>
|
||||
<button mat-button (click)="stepper.reset()">Reset</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
</mat-stepper>
|
||||
@ -0,0 +1,11 @@
|
||||
.mat-stepper-vertical {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.mat-mdc-form-field {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.file-input {
|
||||
display: none;
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { EnvelopeCreationComponent } from './envelope-creation.component';
|
||||
|
||||
describe('EnvelopeCreationComponent', () => {
|
||||
let component: EnvelopeCreationComponent;
|
||||
let fixture: ComponentFixture<EnvelopeCreationComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [EnvelopeCreationComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(EnvelopeCreationComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,53 @@
|
||||
import { Component, Input, inject } from '@angular/core';
|
||||
import { FormBuilder, Validators, FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatStepperModule } from '@angular/material/stepper';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { ReceiverTableComponent } from "../../components/receiver-table/receiver-table.component";
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
|
||||
@Component({
|
||||
selector: 'app-envelope-creation',
|
||||
standalone: true,
|
||||
imports: [
|
||||
MatButtonModule,
|
||||
MatStepperModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatSelectModule,
|
||||
ReceiverTableComponent,
|
||||
MatIconModule
|
||||
],
|
||||
templateUrl: './envelope-creation.component.html',
|
||||
styleUrl: './envelope-creation.component.scss'
|
||||
})
|
||||
export class EnvelopeCreationComponent {
|
||||
|
||||
@Input() isLinear = false;
|
||||
|
||||
titelControl = new FormControl('', [Validators.required]);
|
||||
|
||||
types: any[] = [
|
||||
{ value: 0, viewValue: 'Mietvertrag' },
|
||||
{ value: 1, viewValue: 'Kaufvertrag' }
|
||||
];
|
||||
typeControl = new FormControl('Mietvertrag', [Validators.required]);
|
||||
|
||||
fileName: string = ''
|
||||
onFileSelected(event: any) {
|
||||
const file: File = event.target.files[0];
|
||||
|
||||
if (file) {
|
||||
|
||||
this.fileName = file.name;
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append("thumbnail", file);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
<div id="table">
|
||||
<mat-tab-group>
|
||||
<mat-tab label="Offene Umschläge">
|
||||
<app-envelope-table></app-envelope-table>
|
||||
<app-envelope-table [options]="{max_status: Status.EnvelopePartlySigned}"></app-envelope-table>
|
||||
</mat-tab>
|
||||
<mat-tab label="Abgeschlossene Umschläge">
|
||||
<app-envelope-table></app-envelope-table>
|
||||
<app-envelope-table [options]="{min_status: Status.EnvelopeCompletelySigned, ignore_status: [Status.EnvelopeDeleted]}"></app-envelope-table>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</div>
|
||||
@ -2,10 +2,9 @@ import { Component } from '@angular/core';
|
||||
import { EnvelopeTableComponent } from "../../components/envelope-table/envelope-table.component";
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { LocalizationService } from '../../services/localization.service';
|
||||
import { firstValueFrom } from 'rxjs/internal/firstValueFrom';
|
||||
|
||||
import { Status } from '../../enums/envelope-const'
|
||||
@Component({
|
||||
selector: 'app-envelope',
|
||||
selector: 'envelope',
|
||||
standalone: true,
|
||||
templateUrl: './envelope.component.html',
|
||||
styleUrl: './envelope.component.scss',
|
||||
@ -13,6 +12,8 @@ import { firstValueFrom } from 'rxjs/internal/firstValueFrom';
|
||||
})
|
||||
export class EnvelopeComponent {
|
||||
|
||||
readonly Status = Status;
|
||||
|
||||
private localizer: any = {};
|
||||
|
||||
constructor(private localizationService: LocalizationService) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<div class="main">
|
||||
<div class="content">
|
||||
<div class="left-side">
|
||||
<app-login></app-login>
|
||||
<login></login>
|
||||
</div>
|
||||
<div class="divider" role="separator" aria-label="Divider"></div>
|
||||
<div class="right-side">
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Observable, firstValueFrom, tap } from 'rxjs';
|
||||
import { API_URL } from '../tokens/index';
|
||||
|
||||
@Injectable({
|
||||
@ -22,7 +22,18 @@ export class AuthService {
|
||||
return this.http.post(`${this.url}/logout`, {});
|
||||
}
|
||||
|
||||
async logoutAsync(): Promise<void> {
|
||||
return await firstValueFrom(this.logout());
|
||||
}
|
||||
|
||||
isAuthenticated(): Observable<boolean> {
|
||||
return this.http.get<boolean>(`${this.url}/check`);
|
||||
return this.http.get<boolean>(`${this.url}/check`).pipe(
|
||||
tap(isAuthenticated => this.#IsAuthenticated = isAuthenticated)
|
||||
);
|
||||
}
|
||||
|
||||
#IsAuthenticated = false;
|
||||
get IsAuthenticated(): boolean {
|
||||
return this.#IsAuthenticated;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { Observable, firstValueFrom } from 'rxjs';
|
||||
import { API_URL } from '../tokens/index';
|
||||
|
||||
@ -7,14 +7,28 @@ import { API_URL } from '../tokens/index';
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class EnvelopeReceiverService {
|
||||
private url: string;
|
||||
protected url: string;
|
||||
|
||||
constructor(private http: HttpClient) {
|
||||
const api_url = inject(API_URL);
|
||||
this.url = `${api_url}/envelopereceiver`;
|
||||
}
|
||||
|
||||
getEnvelopeReceiver(): Promise<any> {
|
||||
return firstValueFrom(this.http.get<any>(this.url));
|
||||
getEnvelopeReceiver(options?: { min_status?: number; max_status?: number; ignore_status?: number[] }): Observable<any> {
|
||||
let params = new HttpParams();
|
||||
if (options) {
|
||||
if (options.min_status)
|
||||
params = params.set('min_status', options.min_status.toString());
|
||||
if (options.max_status)
|
||||
params = params.set('max_status', options.max_status.toString());
|
||||
if (options.ignore_status)
|
||||
params = params.set('ignore_status', options.ignore_status.join(','));
|
||||
}
|
||||
return this.http.get<any>(this.url, { params });
|
||||
}
|
||||
|
||||
|
||||
getEnvelopeReceiverAsync(options?: { min_status?: number; max_status?: number; ignore_status?: number[] }): Promise<any> {
|
||||
return firstValueFrom(this.getEnvelopeReceiver(options));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ReceiverService } from './receiver.service';
|
||||
|
||||
describe('ReceiverService', () => {
|
||||
let service: ReceiverService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(ReceiverService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,58 @@
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { API_URL } from '../tokens';
|
||||
import { Observable, firstValueFrom } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ReceiverService {
|
||||
protected url: string;
|
||||
|
||||
constructor(private http: HttpClient) {
|
||||
const api_url = inject(API_URL);
|
||||
this.url = `${api_url}/receiver`;
|
||||
}
|
||||
|
||||
public getReceiver(options?: { emailAdress?: string; signature?: string; }): Observable<any> {
|
||||
let params = new HttpParams();
|
||||
|
||||
if (options) {
|
||||
if (options.emailAdress)
|
||||
params = params.set('emailAdress', options?.emailAdress);
|
||||
if (options.signature)
|
||||
params = params.set('signature', options?.signature);
|
||||
}
|
||||
|
||||
return this.http.get<any>(this.url, { params });
|
||||
}
|
||||
|
||||
public async getReceiverAsync(options?: { emailAdress?: string; signature?: string; }): Promise<any> {
|
||||
return await firstValueFrom(this.getReceiver(options));
|
||||
}
|
||||
|
||||
public createReceiver(emailAddress: string): Observable<any> {
|
||||
return this.http.post<any>(this.url, { emailAddress: emailAddress });
|
||||
}
|
||||
|
||||
public async createReceiverAsync(emailAddress: string): Promise<any> {
|
||||
return await firstValueFrom(this.createReceiver(emailAddress));
|
||||
}
|
||||
|
||||
public deleteReceiver(options: { id: number, emailAdress?: string; signature?: string; }): Observable<any> {
|
||||
let params = new HttpParams();
|
||||
|
||||
if (options.emailAdress)
|
||||
params = params.set('emailAdress', options?.emailAdress);
|
||||
if (options.signature)
|
||||
params = params.set('signature', options?.signature);
|
||||
if (options.id)
|
||||
params = params.set('id', options?.id.toString());
|
||||
|
||||
return this.http.get<any>(this.url, { params });
|
||||
}
|
||||
|
||||
public async deleteReceiverAsync(options: { id: number, emailAdress?: string; signature?: string; }): Promise<any> {
|
||||
return await firstValueFrom(this.deleteReceiver(options));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="3em" height="2.5em" viewBox="0 0 24 24" style="stroke: #a9a8ad;">
|
||||
<path opacity="0.5" d="M15.9998 2L14.9998 2C12.1714 2 10.7576 2.00023 9.87891 2.87891C9.00023 3.75759 9.00023 5.1718 9.00023 8.00023L9.00023 16.0002C9.00023 18.8287 9.00023 20.2429 9.87891 21.1215C10.7574 22 12.1706 22 14.9976 22L14.9998 22L15.9998 22C18.8282 22 20.2424 22 21.1211 21.1213C21.9998 20.2426 21.9998 18.8284 21.9998 16L21.9998 8L21.9998 7.99998C21.9998 5.17157 21.9998 3.75736 21.1211 2.87868C20.2424 2 18.8282 2 15.9998 2Z" fill="#1C274C"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.25098 11.999C1.25098 11.5848 1.58676 11.249 2.00098 11.249L13.9735 11.249L12.0129 9.56845C11.6984 9.29889 11.662 8.82541 11.9315 8.51092C12.2011 8.19642 12.6746 8.16 12.9891 8.42957L16.4891 11.4296C16.6553 11.5721 16.751 11.7801 16.751 11.999C16.751 12.218 16.6553 12.426 16.4891 12.5685L12.9891 15.5685C12.6746 15.838 12.2011 15.8016 11.9315 15.4871C11.662 15.1726 11.6984 14.6991 12.0129 14.4296L13.9735 12.749L2.00098 12.749C1.58676 12.749 1.25098 12.4132 1.25098 11.999Z" fill="#1C274C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@ -17,10 +17,10 @@ namespace EnvelopeGenerator.GeneratorAPI.Controllers
|
||||
_logger = logger;
|
||||
_erService = envelopeReceiverService;
|
||||
}
|
||||
|
||||
|
||||
[Authorize]
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetEnvelopeReceiver()
|
||||
public async Task<IActionResult> GetEnvelopeReceiver([FromQuery] int? min_status = null, [FromQuery] int? max_status = null, [FromQuery] int[]? ignore_status = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -33,7 +33,9 @@ namespace EnvelopeGenerator.GeneratorAPI.Controllers
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
|
||||
return await _erService.ReadByUsernameAsync(username).ThenAsync(
|
||||
ignore_status ??= Array.Empty<int>();
|
||||
|
||||
return await _erService.ReadByUsernameAsync(username: username, min_status: min_status, max_status: max_status, ignore_statuses: ignore_status).ThenAsync(
|
||||
Success: Ok,
|
||||
Fail: IActionResult (msg, ntc) =>
|
||||
{
|
||||
|
||||
@ -0,0 +1,91 @@
|
||||
using DigitalData.Core.API;
|
||||
using DigitalData.Core.DTO;
|
||||
using EnvelopeGenerator.Application.Contracts;
|
||||
using EnvelopeGenerator.Application.DTOs.Receiver;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace EnvelopeGenerator.GeneratorAPI.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class ReceiverController : CRUDControllerBaseWithErrorHandling<IReceiverService, ReceiverCreateDto, ReceiverReadDto, ReceiverUpdateDto, Receiver, int>
|
||||
{
|
||||
public ReceiverController(ILogger<ReceiverController> logger, IReceiverService service) : base(logger, service)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Get([FromQuery] string? emailAddress = null, [FromQuery] string? signature = null)
|
||||
{
|
||||
if (emailAddress is null && signature is null)
|
||||
return await base.GetAll();
|
||||
|
||||
try
|
||||
{
|
||||
return await _service.ReadByAsync(emailAddress: emailAddress, signature: signature).ThenAsync(
|
||||
Success: Ok,
|
||||
Fail: IActionResult (msg, ntc) =>
|
||||
{
|
||||
_logger.LogNotice(ntc);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
});
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "{Message}", ex.Message);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async override Task<IActionResult> Create(ReceiverCreateDto createDto)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(ModelState);
|
||||
|
||||
return await base.Create(createDto);
|
||||
}
|
||||
|
||||
[HttpDelete]
|
||||
public async Task<IActionResult> Delete([FromQuery] int? id = null, [FromQuery]string? emailAddress = null, [FromQuery] string? signature = null)
|
||||
{
|
||||
if(id is int id_int)
|
||||
return await base.Delete(id_int);
|
||||
|
||||
try
|
||||
{
|
||||
if (emailAddress is not null || signature is not null)
|
||||
return await _service.DeleteByAsync(emailAddress: emailAddress, signature: signature).ThenAsync(
|
||||
Success: Ok,
|
||||
Fail: IActionResult (msg, ntc) =>
|
||||
{
|
||||
_logger.LogNotice(ntc);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
});
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "{Message}", ex.Message);
|
||||
return StatusCode(500);
|
||||
}
|
||||
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
#region REMOVED ENDPOINTS
|
||||
[NonAction]
|
||||
public override Task<IActionResult> GetAll() => base.GetAll();
|
||||
|
||||
[NonAction]
|
||||
public override Task<IActionResult> Delete([FromRoute] int id) => base.Delete(id);
|
||||
|
||||
[NonAction]
|
||||
public override Task<IActionResult> Update(ReceiverUpdateDto updateDto) => base.Update(updateDto);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -17,6 +17,7 @@
|
||||
<PackageReference Include="System.DirectoryServices" Version="7.0.1" />
|
||||
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="7.0.1" />
|
||||
<PackageReference Include="System.DirectoryServices.Protocols" Version="7.0.1" />
|
||||
<PackageReference Include="UserManager.Application" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -30,12 +31,6 @@
|
||||
<ProjectReference Include="..\EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="DigitalData.UserManager.Application">
|
||||
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.UserManager.Application.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="ClientApp\envelope-generator-ui\dist\envelope-generator-ui\browser\chunk-35PBLQEP.js">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="3em" height="2.5em" viewBox="0 0 24 24" style="stroke: #a9a8ad;">
|
||||
<path opacity="0.5" d="M15.9998 2L14.9998 2C12.1714 2 10.7576 2.00023 9.87891 2.87891C9.00023 3.75759 9.00023 5.1718 9.00023 8.00023L9.00023 16.0002C9.00023 18.8287 9.00023 20.2429 9.87891 21.1215C10.7574 22 12.1706 22 14.9976 22L14.9998 22L15.9998 22C18.8282 22 20.2424 22 21.1211 21.1213C21.9998 20.2426 21.9998 18.8284 21.9998 16L21.9998 8L21.9998 7.99998C21.9998 5.17157 21.9998 3.75736 21.1211 2.87868C20.2424 2 18.8282 2 15.9998 2Z" fill="#1C274C"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.25098 11.999C1.25098 11.5848 1.58676 11.249 2.00098 11.249L13.9735 11.249L12.0129 9.56845C11.6984 9.29889 11.662 8.82541 11.9315 8.51092C12.2011 8.19642 12.6746 8.16 12.9891 8.42957L16.4891 11.4296C16.6553 11.5721 16.751 11.7801 16.751 11.999C16.751 12.218 16.6553 12.426 16.4891 12.5685L12.9891 15.5685C12.6746 15.838 12.2011 15.8016 11.9315 15.4871C11.662 15.1726 11.6984 14.6991 12.0129 14.4296L13.9735 12.749L2.00098 12.749C1.58676 12.749 1.25098 12.4132 1.25098 11.999Z" fill="#1C274C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
EnvelopeGenerator.GeneratorAPI/wwwroot/chunk-RQJ2DFZW.js
Normal file
1
EnvelopeGenerator.GeneratorAPI/wwwroot/chunk-RQJ2DFZW.js
Normal file
File diff suppressed because one or more lines are too long
7
EnvelopeGenerator.GeneratorAPI/wwwroot/chunk-VPE7ACHQ.js
Normal file
7
EnvelopeGenerator.GeneratorAPI/wwwroot/chunk-VPE7ACHQ.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
5
EnvelopeGenerator.GeneratorAPI/wwwroot/main-7MFQFZI3.js
Normal file
5
EnvelopeGenerator.GeneratorAPI/wwwroot/main-7MFQFZI3.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -19,6 +19,6 @@ namespace EnvelopeGenerator.Infrastructure.Contracts
|
||||
|
||||
Task<string?> ReadAccessCodeByIdAsync(int envelopeId, int receiverId);
|
||||
|
||||
Task<IEnumerable<EnvelopeReceiver>> ReadByUsernameAsync(string username);
|
||||
Task<IEnumerable<EnvelopeReceiver>> ReadByUsernameAsync(string username, int? min_status = null, int? max_status = null, params int[] ignore_statuses);
|
||||
}
|
||||
}
|
||||
@ -5,5 +5,6 @@ namespace EnvelopeGenerator.Infrastructure.Contracts
|
||||
{
|
||||
public interface IReceiverRepository : ICRUDRepository<Receiver, int>
|
||||
{
|
||||
Task<Receiver?> ReadByAsync(string? emailAddress = null, string? signature = null);
|
||||
}
|
||||
}
|
||||
@ -15,19 +15,12 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.15" />
|
||||
<PackageReference Include="UserManager.Domain" Version="1.0.0" />
|
||||
<PackageReference Include="UserManager.Infrastructure" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="DigitalData.UserManager.Domain">
|
||||
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Domain\bin\Debug\net7.0\DigitalData.UserManager.Domain.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="DigitalData.UserManager.Infrastructure">
|
||||
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Infrastructure\bin\Debug\net7.0\DigitalData.UserManager.Infrastructure.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -49,7 +49,7 @@ namespace EnvelopeGenerator.Infrastructure.Repositories
|
||||
|
||||
public async Task<int> CountAsync(string uuid, string signature) => await ReadWhere(uuid: uuid, signature: signature).CountAsync();
|
||||
|
||||
public IQueryable<EnvelopeReceiver> ReadById(int envelopeId, int receiverId) => _dbSet
|
||||
public IQueryable<EnvelopeReceiver> ReadById(int envelopeId, int receiverId) => _dbSet.AsNoTracking()
|
||||
.Where(er => er.EnvelopeId == envelopeId && er.ReceiverId == receiverId);
|
||||
|
||||
public async Task<EnvelopeReceiver?> ReadByIdAsync(int envelopeId, int receiverId)
|
||||
@ -61,11 +61,20 @@ namespace EnvelopeGenerator.Infrastructure.Repositories
|
||||
.Select(er => er.AccessCode)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
public async Task<IEnumerable<EnvelopeReceiver>> ReadByUsernameAsync(string username) => await _dbSet
|
||||
.AsNoTracking()
|
||||
.Where(er => er.Envelope!.User!.Username == username)
|
||||
.Include(er => er.Envelope)
|
||||
.Include(er => er.Receiver)
|
||||
.ToListAsync();
|
||||
public async Task<IEnumerable<EnvelopeReceiver>> ReadByUsernameAsync(string username, int? min_status = null, int? max_status = null, params int[] ignore_statuses)
|
||||
{
|
||||
var query = _dbSet.AsNoTracking().Where(er => er.Envelope!.User!.Username == username);
|
||||
|
||||
if (min_status is not null)
|
||||
query = query.Where(er => er.Envelope!.Status >= min_status);
|
||||
|
||||
if (max_status is not null)
|
||||
query = query.Where(er => er.Envelope!.Status <= max_status);
|
||||
|
||||
foreach (var ignore_status in ignore_statuses)
|
||||
query = query.Where(er => er.Envelope!.Status != ignore_status);
|
||||
|
||||
return await query.Include(er => er.Envelope).Include(er => er.Receiver).ToListAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
using DigitalData.Core.Infrastructure;
|
||||
using DigitalData.UserManager.Infrastructure.Repositories;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using EnvelopeGenerator.Infrastructure.Contracts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace EnvelopeGenerator.Infrastructure.Repositories
|
||||
{
|
||||
@ -10,5 +10,20 @@ namespace EnvelopeGenerator.Infrastructure.Repositories
|
||||
public ReceiverRepository(EGDbContext dbContext) : base(dbContext)
|
||||
{
|
||||
}
|
||||
|
||||
protected IQueryable<Receiver> ReadBy(string? emailAddress = null, string? signature = null)
|
||||
{
|
||||
IQueryable<Receiver> query = _dbSet.AsNoTracking();
|
||||
|
||||
if(emailAddress is not null)
|
||||
query = query.Where(r => r.EmailAddress == emailAddress);
|
||||
|
||||
if(signature is not null)
|
||||
query = query.Where(r => r.Signature == signature);
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
public async Task<Receiver?> ReadByAsync(string? emailAddress = null, string? signature = null) => await ReadBy(emailAddress, signature).FirstOrDefaultAsync();
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
using EnvelopeGenerator.Application.Contracts;
|
||||
using EnvelopeGenerator.Application.DTOs;
|
||||
using EnvelopeGenerator.Application.DTOs.Receiver;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Web.Controllers.Test
|
||||
{
|
||||
public class TestReceiverController : TestControllerBase<IReceiverService, ReceiverDto, Receiver, int>
|
||||
public class TestReceiverController : TestControllerBase<IReceiverService, ReceiverReadDto, Receiver, int>
|
||||
{
|
||||
public TestReceiverController(ILogger<TestReceiverController> logger, IReceiverService service) : base(logger, service)
|
||||
{
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<PackageId>EnvelopeGenerator.Web</PackageId>
|
||||
<Version>1.0.0.1</Version>
|
||||
<Version>1.0.0.3</Version>
|
||||
<Authors>Digital Data GmbH</Authors>
|
||||
<Company>Digital Data GmbH</Company>
|
||||
<Product>EnvelopeGenerator.Web</Product>
|
||||
@ -13,10 +13,14 @@
|
||||
<PackageTags>digital data envelope generator web</PackageTags>
|
||||
<Description>EnvelopeGenerator.Web is an ASP.NET MVC application developed to manage signing processes. It uses Entity Framework Core (EF Core) for database operations. The user interface for signing processes is developed with Razor View Engine (.cshtml files) and JavaScript under wwwroot, integrated with PSPDFKit. This integration allows users to view and sign documents seamlessly.</Description>
|
||||
<ApplicationIcon>Assets\icon.ico</ApplicationIcon>
|
||||
<AssemblyVersion>1.1.0.1</AssemblyVersion>
|
||||
<FileVersion>1.1.0.1</FileVersion>
|
||||
<AssemblyVersion>1.0.0.3</AssemblyVersion>
|
||||
<FileVersion>1.0.0.3</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Remove="bundleconfig.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Views\Shared\Error.html" />
|
||||
</ItemGroup>
|
||||
@ -27,8 +31,13 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="bundleconfig.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
||||
<PackageReference Include="BuildBundlerMinifier2022" Version="2.9.9" />
|
||||
<PackageReference Include="DigitalData.Core.Abstractions" Version="1.0.1.1" />
|
||||
<PackageReference Include="DigitalData.Core.API" Version="1.0.2.1" />
|
||||
<PackageReference Include="DigitalData.Core.Application" Version="1.0.0" />
|
||||
@ -58,6 +67,9 @@
|
||||
<PackageReference Include="System.DirectoryServices.Protocols" Version="7.0.1" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
||||
<PackageReference Include="UserManager.Application" Version="1.0.0" />
|
||||
<PackageReference Include="UserManager.Domain" Version="1.0.0" />
|
||||
<PackageReference Include="UserManager.Infrastructure" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -82,15 +94,6 @@
|
||||
<Reference Include="DigitalData.Modules.Logging">
|
||||
<HintPath>..\..\DDModules\Logging\bin\Debug\DigitalData.Modules.Logging.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="DigitalData.UserManager.Application">
|
||||
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.UserManager.Application.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="DigitalData.UserManager.Domain">
|
||||
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.UserManager.Domain.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="DigitalData.UserManager.Infrastructure">
|
||||
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.UserManager.Infrastructure.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="GdPicture.NET.14">
|
||||
<HintPath>D:\ProgramFiles\GdPicture.NET 14\Redist\GdPicture.NET (.NET Framework 4.5)\GdPicture.NET.14.dll</HintPath>
|
||||
</Reference>
|
||||
|
||||
@ -16,6 +16,7 @@ using Microsoft.Extensions.Options;
|
||||
using EnvelopeGenerator.Application;
|
||||
using DigitalData.EmailProfilerDispatcher;
|
||||
using EnvelopeGenerator.Infrastructure;
|
||||
using EnvelopeGenerator.Web.Sanitizers;
|
||||
|
||||
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
|
||||
logger.Info("Logging initialized!");
|
||||
@ -126,11 +127,15 @@ try
|
||||
|
||||
builder.Services.AddSingleton(HtmlEncoder.Default);
|
||||
builder.Services.AddSingleton(UrlEncoder.Default);
|
||||
builder.Services.AddSingleton(_ =>
|
||||
builder.Services.AddSanitizer<HtmlSanitizer>();
|
||||
builder.Services.AddSanitizer<HighlightHtmlSanitizer>(s =>
|
||||
{
|
||||
var sanitizer = new HtmlSanitizer();
|
||||
//configure sanitzer
|
||||
return sanitizer;
|
||||
s.AllowedTags.Add("a");
|
||||
s.AllowedAttributes.Add("href");
|
||||
s.AllowedAttributes.Add("class");
|
||||
s.AllowedClasses.Add("highlight");
|
||||
s.AllowedClasses.Add("highlight-envelope-info-1");
|
||||
s.AllowedClasses.Add("highlight-envelope-info-2");
|
||||
});
|
||||
|
||||
// Register the FlagIconCssClass instance as a singleton
|
||||
|
||||
17
EnvelopeGenerator.Web/Sanitizers/DIExtensions.cs
Normal file
17
EnvelopeGenerator.Web/Sanitizers/DIExtensions.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using Ganss.Xss;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace EnvelopeGenerator.Web.Sanitizers
|
||||
{
|
||||
public static class DIExtensions
|
||||
{
|
||||
public static IServiceCollection AddSanitizer<THtmlSanitizer>(this IServiceCollection services, Action<THtmlSanitizer>? optionActions = null)
|
||||
where THtmlSanitizer : HtmlSanitizer => services
|
||||
.AddSingleton(serviceProvider =>
|
||||
{
|
||||
var sanitizer = ActivatorUtilities.CreateInstance<THtmlSanitizer>(serviceProvider);
|
||||
optionActions?.Invoke(sanitizer);
|
||||
return sanitizer;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
using Ganss.Xss;
|
||||
|
||||
namespace EnvelopeGenerator.Web.Sanitizers
|
||||
{
|
||||
public class HighlightHtmlSanitizer : HtmlSanitizer
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -37,23 +37,30 @@
|
||||
<div class="row g-0">
|
||||
<div class="col p-0 m-0">
|
||||
<div class="card-body p-0 m-0 ms-4">
|
||||
<h5 class="card-title p-0 m-0">@($"{envelope?.Title.TrySanitize(_sanitizer)}")</h5>
|
||||
<p class="card-text p-0 m-0">@(string.Format(_localizer[WebKey.EnvelopeInfo1], pages.Count(), stPageIndexes.TrySanitize(_sanitizer)))</p>
|
||||
<p class="card-text p-0 m-0"><small class="text-body-secondary">@Html.Raw(string.Format(_localizer[WebKey.EnvelopeInfo2],
|
||||
envelope?.AddedWhen.ToString(userCulture?.Info?.DateTimeFormat),
|
||||
$"{sender?.Prename} {sender?.Name}".TrySanitize(_sanitizer),
|
||||
sender?.Email.TryEncode(_encoder),
|
||||
envelope?.Title.TryEncode(_encoder),
|
||||
sender?.Prename.TryEncode(_encoder),
|
||||
sender?.Name.TryEncode(_encoder),
|
||||
sender?.Email.TryEncode(_encoder)).TrySanitize(_sanitizer))</small></p>
|
||||
<h5 class="card-title p-0 m-0">
|
||||
<span class="signature-process-title">@($"{_localizer[WebKey.SigningProcessTitle]}: ".TrySanitize(_sanitizer))</span>
|
||||
<span class="signature-process-name">@($"{envelope?.Title}".TrySanitize(_sanitizer))</span>
|
||||
</h5>
|
||||
<p class="card-text p-0 m-0">@Html.Raw(string.Format(_localizer[WebKey.EnvelopeInfo1], pages.Count(), stPageIndexes).TrySanitize(_hlSanitizer))</p>
|
||||
<p class="card-text p-0 m-0">
|
||||
<small class="text-body-secondary">
|
||||
@Html.Raw(string.Format(_localizer[WebKey.EnvelopeInfo2], /* sanitize separately but don't sanitize the URI */
|
||||
envelope?.AddedWhen.ToString(userCulture?.Info?.DateTimeFormat).TrySanitize(_sanitizer),
|
||||
$"{sender?.Prename} {sender?.Name}".TrySanitize(_sanitizer),
|
||||
sender?.Email.TrySanitize(_sanitizer),
|
||||
envelope?.Title.TrySanitize(_sanitizer),
|
||||
sender?.Prename.TrySanitize(_sanitizer),
|
||||
sender?.Name.TrySanitize(_sanitizer),
|
||||
sender?.Email.TrySanitize(_sanitizer)))
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="flex-actio-panel" class="btn-group btn_group position-fixed bottom-0 end-0 d-flex align-items-center" role="group" aria-label="Basic mixed styles example">
|
||||
<div id="flex-action-panel" class="btn-group btn_group position-fixed bottom-0 end-0 d-flex align-items-center" role="group" aria-label="Basic mixed styles example">
|
||||
<button class="btn_complete btn btn-primary" type="button">
|
||||
<svg class="icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 16">
|
||||
<path d="m10.036 8.278 9.258-7.79A1.979 1.979 0 0 0 18 0H2A1.987 1.987 0 0 0 .641.541l9.395 7.737Z" />
|
||||
|
||||
@ -8,12 +8,12 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<title>@ViewData["Title"]</title>
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="~/lib/sweetalert2/sweetalert2.min.css" />
|
||||
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true"/>
|
||||
<link rel="stylesheet" href="~/EnvelopeGenerator.Web.styles.css" asp-append-version="true"/>
|
||||
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
|
||||
<link rel="stylesheet" href="~/EnvelopeGenerator.Web.styles.css" asp-append-version="true" />
|
||||
<link rel="stylesheet" href="~/lib/flag-icons-main/css/flag-icons.min.css" asp-append-version="true" />
|
||||
<link rel="stylesheet" href="~/lib/alertifyjs/css/alertify.min.css" />
|
||||
<link rel="stylesheet" href="~/lib/alertifyjs/css/themes/default.min.css" />
|
||||
@ -28,14 +28,14 @@
|
||||
<script src="~/lib/bootstrap/dist/js/bootstrap.min.js"></script>
|
||||
<script src="~/lib/sweetalert2/sweetalert2.min.js"></script>
|
||||
<script src="~/lib/alertifyjs/alertify.min.js"></script>
|
||||
<script src="~/js/ui.js" asp-append-version="true"></script>
|
||||
<script src="~/js/ui.min.js" asp-append-version="true"></script>
|
||||
<script src="~/js/annotation.js" asp-append-version="true"></script>
|
||||
<script src="~/js/network.js" asp-append-version="true"></script>
|
||||
<script src="~/js/app.js" asp-append-version="true"></script>
|
||||
<script src="~/lib/pspdfkit/pspdfkit.js" asp-append-version="true"></script>
|
||||
<script src="~/js/network.min.js" asp-append-version="true"></script>
|
||||
<script src="~/js/app.min.js" asp-append-version="true"></script>
|
||||
<script src="~/lib/pspdfkit/dist-2024.3.2/pspdfkit.js"></script>
|
||||
<script src="~/lib/bootstrap-cookie-consent-settings-main/bootstrap-cookie-consent-settings.js" asp-append-version="true"></script>
|
||||
<script src="~/js/util.js" asp-append-version="true"></script>
|
||||
<script src="~/js/api-service.js" asp-append-version="true"></script>
|
||||
<script src="~/js/util.min.js" asp-append-version="true"></script>
|
||||
<script src="~/js/api-service.min.js" asp-append-version="true"></script>
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
@{
|
||||
var settings = new JsonSerializerSettings
|
||||
@ -51,7 +51,7 @@
|
||||
<partial name="_CookieConsentPartial" />
|
||||
@RenderBody()
|
||||
</main>
|
||||
<script src="~/js/event-binder.js" asp-append-version="true"></script>
|
||||
<script src="~/js/event-binder.min.js" asp-append-version="true"></script>
|
||||
@Html.AntiForgeryToken()
|
||||
</body>
|
||||
</html>
|
||||
@ -24,10 +24,10 @@ a {
|
||||
}
|
||||
|
||||
.border-top {
|
||||
border-top: 1px solid #e5e5e5;
|
||||
border-top: 0.063rem solid #e5e5e5;
|
||||
}
|
||||
.border-bottom {
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
border-bottom: 0.063rem solid #e5e5e5;
|
||||
}
|
||||
|
||||
.box-shadow {
|
||||
@ -44,5 +44,5 @@ button.accept-policy {
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
line-height: 60px;
|
||||
line-height: 3.75rem;
|
||||
}
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
@using EnvelopeGenerator.Web
|
||||
@using EnvelopeGenerator.Web.Models
|
||||
@using EnvelopeGenerator.Web.Sanitizers
|
||||
@using Microsoft.Extensions.Localization
|
||||
@using EnvelopeGenerator.Application.Resources
|
||||
@inject IStringLocalizer<Resource> _localizer
|
||||
@inject System.Text.Encodings.Web.UrlEncoder _encoder
|
||||
@inject Ganss.Xss.HtmlSanitizer _sanitizer
|
||||
@inject HighlightHtmlSanitizer _hlSanitizer
|
||||
@inject Microsoft.AspNetCore.Http.IHttpContextAccessor _accessor
|
||||
@inject Cultures _cultures
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@ -32,5 +32,6 @@
|
||||
public static readonly string RejectionInfo2 = nameof(RejectionInfo2);
|
||||
public static readonly string RejectionInfo1_ext = nameof(RejectionInfo1_ext);
|
||||
public static readonly string RejectionInfo2_ext = nameof(RejectionInfo2_ext);
|
||||
}
|
||||
public static readonly string SigningProcessTitle = nameof(SigningProcessTitle);
|
||||
}
|
||||
}
|
||||
@ -16,18 +16,17 @@
|
||||
},
|
||||
"AdminPassword": "dd",
|
||||
"PSPDFKitLicenseKey": "SXCtGGY9XA-31OGUXQK-r7c6AkdLGPm2ljuyDr1qu0kkhLvydg-Do-fxpNUF4Rq3fS_xAnZRNFRHbXpE6sQ2BMcCSVTcXVJO6tPviexjpiT-HnrDEySlUERJnnvh-tmeOWprxS6BySPnSILkmaVQtUfOIUS-cUbvvEYHTvQBKbSF8di4XHQFyfv49ihr51axm3NVV3AXwh2EiKL5C5XdqBZ4sQ4O7vXBjM2zvxdPxlxdcNYmiU83uAzw7B83O_jubPzya4CdUHh_YH7Nlp2gP56MeG1Sw2JhMtfG3Rj14Sg4ctaeL9p6AEWca5dDjJ2li5tFIV2fQSsw6A_cowLu0gtMm5i8IfJXeIcQbMC2-0wGv1oe9hZYJvFMdzhTM_FiejM0agemxt3lJyzuyP8zbBSOgp7Si6A85krLWPZptyZBTG7pp7IHboUHfPMxCXqi-zMsqewOJtQBE2mjntU-lPryKnssOpMPfswwQX7QSkJYV5EMqNmEhQX6mEkp2wcqFzMC7bJQew1aO4pOpvChUaMvb1vgRek0HxLag0nwQYX2YrYGh7F_xXJs-8HNwJe8H0-eW4x4faayCgM5rB5772CCCsD9ThZcvXFrjNHHLGJ8WuBUFm6LArvSfFQdii_7j-_sqHMpeKZt26NFgivj1A==",
|
||||
"UseCSPInDev": false,
|
||||
"UseCSPInDev": true,
|
||||
"Content-Security-Policy": [ // The first format parameter {0} will be replaced by the nonce value.
|
||||
"default-src 'self'",
|
||||
"script-src 'self' 'nonce-{0}' 'unsafe-inline' 'unsafe-eval' blob: data:",
|
||||
"script-src 'self' 'nonce-{0}' 'unsafe-eval'",
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
"img-src 'self' data: https: blob:",
|
||||
"font-src 'self'",
|
||||
"connect-src 'self' https://nominatim.openstreetmap.org:* http://localhost:* https://localhost:* ws://localhost:* wss://localhost:* blob:",
|
||||
"frame-src 'self'",
|
||||
"media-src 'self'",
|
||||
"object-src 'self'",
|
||||
"worker-src 'self' blob: data:"
|
||||
"object-src 'self'"
|
||||
],
|
||||
"AllowedOrigins": [ "https://localhost:7202", "https://digitale.unterschrift.wisag.de/" ],
|
||||
"NLog": {
|
||||
@ -119,5 +118,17 @@
|
||||
"AddedWho": "DDEnvelopGenerator",
|
||||
"ReminderTypeId": 202377,
|
||||
"EmailAttmt1": ""
|
||||
}
|
||||
},
|
||||
"Regexes": [
|
||||
{
|
||||
"Pattern": "/^\\p{L}+(?:([\\ \\-\\']|(\\.\\ ))\\p{L}+)*$/u",
|
||||
"Name": "City",
|
||||
"Platforms": [ ".NET" ]
|
||||
},
|
||||
{
|
||||
"Pattern": "/^[a-zA-Z\\u0080-\\u024F]+(?:([\\ \\-\\']|(\\.\\ ))[a-zA-Z\\u0080-\\u024F]+)*$/",
|
||||
"Name": "City",
|
||||
"Platforms": [ "javascript" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
62
EnvelopeGenerator.Web/bundleconfig.json
Normal file
62
EnvelopeGenerator.Web/bundleconfig.json
Normal file
@ -0,0 +1,62 @@
|
||||
[
|
||||
{
|
||||
"outputFileName": "wwwroot/js/app.min.js",
|
||||
"inputFiles": [
|
||||
"wwwroot/js/app.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outputFileName": "wwwroot/js/api-service.min.js",
|
||||
"inputFiles": [
|
||||
"wwwroot/js/api-service.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outputFileName": "wwwroot/js/error-space.min.js",
|
||||
"inputFiles": [
|
||||
"wwwroot/js/error-space.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outputFileName": "wwwroot/js/event-binder.min.js",
|
||||
"inputFiles": [
|
||||
"wwwroot/js/event-binder.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outputFileName": "wwwroot/js/network.min.js",
|
||||
"inputFiles": [
|
||||
"wwwroot/js/network.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outputFileName": "wwwroot/js/ui.min.js",
|
||||
"inputFiles": [
|
||||
"wwwroot/js/ui.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outputFileName": "wwwroot/js/util.min.js",
|
||||
"inputFiles": [
|
||||
"wwwroot/js/util.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outputFileName": "wwwroot/css/error-space.min.css",
|
||||
"inputFiles": [
|
||||
"wwwroot/css/error-space.css"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outputFileName": "wwwroot/css/site.min.css",
|
||||
"inputFiles": [
|
||||
"wwwroot/css/site.css"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outputFileName": "wwwroot/lib/bootstrap-cookie-consent-settings-main/bootstrap-cookie-consent-settings.min.js",
|
||||
"inputFiles": [
|
||||
"wwwroot/lib/bootstrap-cookie-consent-settings-main/bootstrap-cookie-consent-settings.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
1
EnvelopeGenerator.Web/bundleconfig.json.bindings
Normal file
1
EnvelopeGenerator.Web/bundleconfig.json.bindings
Normal file
@ -0,0 +1 @@
|
||||
///<binding BeforeBuild='Stylesheets, JavaScript, wwwroot/js/app.min.js' />
|
||||
1
EnvelopeGenerator.Web/wwwroot/css/error-space.min.css
vendored
Normal file
1
EnvelopeGenerator.Web/wwwroot/css/error-space.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -71,8 +71,8 @@ body {
|
||||
.page {
|
||||
margin-top: 3rem;
|
||||
background: white;
|
||||
border-radius: 5px;
|
||||
box-shadow: rgba(9, 30, 66, 0.25) 0px 4px 8px -2px, rgba(9, 30, 66, 0.08) 0px 0px 0px 1px;
|
||||
border-radius: 0.313rem;
|
||||
box-shadow: rgba(9, 30, 66, 0.25) 0rem 0.25rem 0.5rem -0.125rem, rgba(9, 30, 66, 0.08) 0rem 0rem 0rem 0.063rem;
|
||||
max-width: 40rem;
|
||||
}
|
||||
|
||||
@ -83,8 +83,8 @@ body {
|
||||
|
||||
.page header .icon {
|
||||
display: inline-block;
|
||||
border-radius: 100px;
|
||||
padding: 15px;
|
||||
border-radius: 6.25rem;
|
||||
padding: 0.938rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
@ -128,7 +128,7 @@ body {
|
||||
|
||||
.envelope {
|
||||
display: block;
|
||||
border: 1px solid #eee;
|
||||
border: 0.063rem solid #eee;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
@ -160,7 +160,7 @@ footer#page-footer {
|
||||
.sender-card img {
|
||||
height: 7vh;
|
||||
background-color: rgb(209, 207, 207);
|
||||
border-radius: 50px;
|
||||
border-radius: 3.125rem;
|
||||
}
|
||||
|
||||
.envelope-message {
|
||||
@ -183,14 +183,14 @@ footer#page-footer {
|
||||
|
||||
/* CSS for custom class to increase dropdown height */
|
||||
.increase-dropdown-height {
|
||||
min-height: 400px; /* Optional, larger value for increased height */
|
||||
min-height: 25rem; /* Optional, larger value for increased height */
|
||||
}
|
||||
|
||||
|
||||
/* Adjusting select2 width to fit within a specific form size */
|
||||
.dropdown-flag .select2-container {
|
||||
width: 100% !important; /* Makes the container full width */
|
||||
max-width: 180px; /* Suitable maximum width for the form */
|
||||
max-width: 11.25rem; /* Suitable maximum width for the form */
|
||||
}
|
||||
|
||||
.lang-item {
|
||||
@ -201,7 +201,30 @@ footer#page-footer {
|
||||
min-width: 4vw;
|
||||
}
|
||||
|
||||
/* Additional styles for better mobile responsiveness */
|
||||
.highlight {
|
||||
font-weight: 700;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.signature-process-title, .signature-process-name {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
|
||||
.mail-link {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.mail-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#flex-action-panel {
|
||||
z-index: 1050;
|
||||
}
|
||||
|
||||
/* styles for mobile responsiveness */
|
||||
@media (max-width: 767px) {
|
||||
.navbar {
|
||||
flex-direction: column;
|
||||
@ -209,12 +232,13 @@ footer#page-footer {
|
||||
}
|
||||
|
||||
.navbar-toggler {
|
||||
transform: scale(0.75);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 0.5rem;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
@ -255,4 +279,29 @@ footer#page-footer {
|
||||
.page section {
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
font-weight: 700;
|
||||
font-size: 0.5rem;
|
||||
}
|
||||
|
||||
.signature-process-title, .signature-process-name {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
}
|
||||
@media (max-height: 850px) {
|
||||
.collapse .card-text, .collapsing .card-text {
|
||||
font-size: 0.5rem; /* Font size reduced */
|
||||
margin: 0rem;
|
||||
padding: 0rem;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
font-weight: 700;
|
||||
font-size: 0.5rem;
|
||||
}
|
||||
|
||||
.signature-process-title, .signature-process-name {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
}
|
||||
1
EnvelopeGenerator.Web/wwwroot/css/site.min.css
vendored
Normal file
1
EnvelopeGenerator.Web/wwwroot/css/site.min.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
#app{background:#808080;width:100vw;height:80vh}.btn-group{margin-right:10vw;margin-bottom:10vh}.btn_refresh,.btn_reject,.btn_complete{height:2.5rem}.btn_complete .icon,.btn_reject .icon,.btn_refresh .icon{width:1.1rem}.btn_complete span,.btn_reject span,.btn_refresh span{vertical-align:middle}.button-finish{transition:background-color linear 300ms;background-color:#059669;color:#fff;border-left:0}.button-finish:hover,.button-finish:focus,.button-finish:active{background-color:#10b981;color:#fff}.button-reject{transition:background-color linear 300ms;background-color:#d97706;color:#fff;border-left:0}.button-reject:hover,.button-reject:focus,.button-reject:active{background-color:#f59e0b;color:#fff}.button-reset{transition:background-color linear 300ms;background-color:#2563eb;color:#fff;border-left:0}.button-reset:hover,.button-reset:focus,.button-reset:active{background-color:#3b82f6;color:#fff}body{background-color:#bbb}.page{margin-top:3rem;background:#fff;border-radius:.313rem;box-shadow:rgba(9,30,66,.25) 0 .25rem .5rem -.125rem,rgba(9,30,66,.08) 0 0 0 .063rem;max-width:40rem}.page section{max-width:30rem;margin:0 auto}.page header .icon{display:inline-block;border-radius:6.25rem;padding:.938rem;margin-bottom:2rem}.page header .icon.admin{background-color:#331904;color:#fecba1}.page header .icon.locked{background-color:#ffc107;color:#000}.page header .icon.signed{background-color:#146c43;color:#fff}.page header .icon.rejected{background-color:#e4d8d5;color:#fff}.page .form{max-width:30rem;margin:2rem auto;display:flex;gap:1rem}#form-access-code>.input,#form-admin-password>.input{flex-grow:1}#page-admin header .icon{background-color:#331904;color:#fecba1}.envelope{display:block;border:.063rem solid #eee;margin-bottom:1rem;padding:.5rem}footer#page-footer{color:#333;max-width:40rem;margin-top:1rem;font-size:.85rem}footer#page-footer a,footer#page-footer a:link,footer#page-footer a:hover,footer#page-footer a:visited,footer#page-footer a:focus{color:#444}.sender-card{background-color:transparent;border:0}.sender-card .row{height:7vh}.sender-card img{height:7vh;background-color:#d1cfcf;border-radius:3.125rem}.envelope-message{font-family:'Roboto',sans-serif}.none-display{display:none}.dropdown-flag img,.img-flag{width:30%;height:70%}.dropdown-flag{height:75%;width:75%}.increase-dropdown-height{min-height:25rem}.dropdown-flag .select2-container{width:100%!important;max-width:11.25rem}.lang-item{font-size:.85rem}#langDropdownMenuButton{min-width:4vw}.highlight{font-weight:700;font-size:.85rem}.signature-process-title,.signature-process-name{font-size:1.125rem}.mail-link{color:#000;text-decoration:none}.mail-link:hover{text-decoration:underline}#flex-action-panel{z-index:1050}@media(max-width:767px){.navbar{flex-direction:column;align-items:flex-start}.navbar-toggler{transform:scale(.75);padding:0}.navbar-brand{font-size:.5rem;text-align:center;overflow:hidden;text-overflow:ellipsis}.collapse .card-text,.collapsing .card-text{font-size:.6rem;margin:0;padding:0}.sender-card .card-body{padding:.5rem}.btn_group{position:fixed;flex-direction:row;bottom:.5rem;right:.5rem}.img-fluid{width:1.2rem;height:100%;display:none}img{max-width:4rem}.page{margin-top:1rem;max-width:90%;padding:.5rem}.page section{max-width:90%}.highlight{font-weight:700;font-size:.5rem}.signature-process-title,.signature-process-name{font-size:.7rem}}@media(max-height:850px){.collapse .card-text,.collapsing .card-text{font-size:.5rem;margin:0;padding:0}.highlight{font-weight:700;font-size:.5rem}.signature-process-title,.signature-process-name{font-size:.7rem}}
|
||||
Binary file not shown.
@ -62,7 +62,7 @@
|
||||
backgroundColor: PSPDFKit.Color.DarkBlue,
|
||||
blendMode: 'multiply',
|
||||
boundingBox: new PSPDFKit.Geometry.Rect({
|
||||
width: width * 0.75,
|
||||
width: width * 1.55,
|
||||
height: height / 2,
|
||||
top: top + height + 25 + date_place_top_shift,
|
||||
left: left + width * 1.30,
|
||||
@ -77,7 +77,7 @@
|
||||
const formFieldDate = new PSPDFKit.FormFields.TextFormField({
|
||||
name: id_date,
|
||||
annotationIds: PSPDFKit.Immutable.List([annotation_date.id]),
|
||||
value: locale_date_dd_mm_yyyy(),
|
||||
value: detailedCurrentDate(),
|
||||
readOnly: true
|
||||
})
|
||||
|
||||
@ -108,6 +108,8 @@
|
||||
|
||||
this.markFieldAsRequired(formFieldCity);
|
||||
|
||||
this.markFieldAsCity(formFieldCity);
|
||||
|
||||
/**
|
||||
* Date, post code and place label part
|
||||
*/
|
||||
@ -307,6 +309,7 @@
|
||||
return inch * 72
|
||||
}
|
||||
|
||||
//required
|
||||
static #requiredFieldNames = new Array()
|
||||
|
||||
static markFieldAsRequired(formField) {
|
||||
@ -316,4 +319,15 @@
|
||||
static isFieldRequired(formField) {
|
||||
return this.#requiredFieldNames.includes(formField.name)
|
||||
}
|
||||
|
||||
//city
|
||||
static #cityFieldNames = new Array()
|
||||
|
||||
static markFieldAsCity(formField) {
|
||||
this.#cityFieldNames.push(formField.name)
|
||||
}
|
||||
|
||||
static isCityField(formField){
|
||||
return this.#cityFieldNames.includes(formField.name)
|
||||
}
|
||||
}
|
||||
2
EnvelopeGenerator.Web/wwwroot/js/api-service.min.js
vendored
Normal file
2
EnvelopeGenerator.Web/wwwroot/js/api-service.min.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
class Content{static get JSON(){return"application/json"}}class API{static get REJECT_URL(){return`/api/envelope/reject`}static get REJECT_REDIR_URL(){return`/envelopekey/${API.ENV_KEY}/rejected`}static __XSRF_TOKEN
|
||||
static get XSRF_TOKEN(){return API.__XSRF_TOKEN??=document.getElementsByName("__RequestVerificationToken")[0].value,API.__XSRF_TOKEN}static get ENV_KEY(){return ENV_KEY??document.querySelector('meta[name="env-key"]').getAttribute("content")}}const submitForm=async n=>await fetch(n.action,{method:n.method,body:new FormData(n),headers:{"X-Requested-With":"XMLHttpRequest"}}),createRequest=async(n,t,i,r)=>fetch(t,{credentials:"include",method:n,headers:{"Content-Type":r,"X-XSRF-TOKEN":API.XSRF_TOKEN},body:JSON.stringify(i)}),createPost=(n,t,i)=>createRequest("POST",n,t,i),rejectEnvelope=n=>createPost(API.REJECT_URL,n,Content.JSON),redirect=n=>window.location.href=n,redirRejected=()=>redirect(API.REJECT_REDIR_URL);
|
||||
@ -170,6 +170,8 @@ class App {
|
||||
async handleFinish(event) {
|
||||
const iJSON = await this.Instance.exportInstantJSON()
|
||||
const iFormFieldValues = await iJSON.formFieldValues;
|
||||
|
||||
//check required
|
||||
const iReqFields = iFormFieldValues.filter(f => Annotation.isFieldRequired(f))
|
||||
const hasEmptyReq = iReqFields.some(f => (f.value === undefined || f.value === null || f.value === ""))
|
||||
|
||||
@ -182,6 +184,20 @@ class App {
|
||||
return false;
|
||||
}
|
||||
|
||||
//check city
|
||||
const city_regex = new RegExp("^[a-zA-Z\\u0080-\\u024F]+(?:([\\ \\-\\']|(\\.\\ ))[a-zA-Z\\u0080-\\u024F]+)*$")
|
||||
const iCityFields = iFormFieldValues.filter(f => Annotation.isCityField(f))
|
||||
for(var f of iCityFields)
|
||||
if(!city_regex.test(f.value)){
|
||||
Swal.fire({
|
||||
title: 'Warnung',
|
||||
text: `Bitte überprüfen Sie die eingegebene Ortsangabe "${f.value}" auf korrekte Formatierung. Beispiele für richtige Formate sind: München, Île-de-France, Sauðárkrókur, San Francisco, St. Catharines usw.`,
|
||||
icon: 'warning',
|
||||
})
|
||||
return false;
|
||||
}
|
||||
|
||||
//check # of signature
|
||||
const validationResult = await this.validateAnnotations(this.signatureCount)
|
||||
if (validationResult === false) {
|
||||
Swal.fire({
|
||||
|
||||
File diff suppressed because one or more lines are too long
1
EnvelopeGenerator.Web/wwwroot/js/app.min.js
vendored
Normal file
1
EnvelopeGenerator.Web/wwwroot/js/app.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
const ActionType={Created:0,Saved:1,Sent:2,EmailSent:3,Delivered:4,Seen:5,Signed:6,Rejected:7};class App{constructor(n,t,i,r,u,f){this.container=f??`#${this.constructor.name.toLowerCase()}`;this.envelopeKey=n;this.Network=new Network;this.Instance=null;this.currentDocument=null;this.currentReceiver=null;this.signatureCount=0;this.envelopeReceiver=t;this.documentBytes=i;this.licenseKey=r;this.locale=u}async init(){this.currentDocument=this.envelopeReceiver.envelope.documents[0];this.currentReceiver=this.envelopeReceiver.receiver;const n=this.documentBytes;if(n.fatal||n.error)return Swal.fire({title:"Fehler",text:"Dokument konnte nicht geladen werden!",icon:"error"});const t=this.documentBytes;this.Instance=await UI.loadPSPDFKit(t,this.container,this.licenseKey,this.locale);UI.configurePSPDFKit(this.Instance,this.handleClick.bind(this));this.Instance.addEventListener("annotations.load",this.handleAnnotationsLoad.bind(this));this.Instance.addEventListener("annotations.change",this.handleAnnotationsChange.bind(this));this.Instance.addEventListener("annotations.create",this.handleAnnotationsCreate.bind(this));this.Instance.addEventListener("annotations.willChange",()=>{Comp.ActPanel.Toggle()});try{this.signatureCount=this.currentDocument.elements.length;await Annotation.createAnnotations(this.currentDocument,this.Instance);const n=await this.Network.openDocument(this.envelopeKey);if(n.fatal||n.error)return Swal.fire({title:"Fehler",text:"Umschlag konnte nicht geöffnet werden!",icon:"error"})}catch(i){}[...document.getElementsByClassName("btn_refresh")].forEach(n=>n.addEventListener("click",()=>this.handleClick("RESET")));[...document.getElementsByClassName("btn_complete")].forEach(n=>n.addEventListener("click",()=>this.handleClick("FINISH")))}handleAnnotationsLoad(n){n.toJS()}handleAnnotationsChange(){}async handleAnnotationsCreate(n){const t=n.toJS()[0],i=!!t.formFieldName,r=!!t.isSignature;if(i===!1&&r===!0){const r=t.boundingBox.left-20,u=t.boundingBox.top-20,n=150,i=75,f=new Date,e=await Annotation.createAnnotationFrameBlob(this.envelopeReceiver.name,this.currentReceiver.signature,f,n,i),o=await fetch(e),s=await o.blob(),h=await this.Instance.createAttachment(s),c=Annotation.createImageAnnotation(new PSPDFKit.Geometry.Rect({left:r,top:u,width:n,height:i}),t.pageIndex,h);this.Instance.create(c)}}async handleClick(n){let t=!1;switch(n){case"RESET":t=await this.handleReset(null);t.isConfirmed&&Swal.fire({title:"Erfolg",text:"Dokument wurde zurückgesetzt",icon:"info"});break;case"FINISH":t=await this.handleFinish(null);t==!0&&(window.location.href=`/EnvelopeKey/${this.envelopeKey}/Success`);break;case"REJECT":alert("Dokument abgelent!")}}async handleFinish(){const n=await this.Instance.exportInstantJSON(),t=await n.formFieldValues,r=t.filter(n=>Annotation.isFieldRequired(n)),u=r.some(n=>n.value===undefined||n.value===null||n.value==="");if(u)return Swal.fire({title:"Warnung",text:"Bitte füllen Sie alle Standortinformationen vollständig aus!",icon:"warning"}),!1;const f=new RegExp("^[a-zA-Z\\u0080-\\u024F]+(?:([\\ \\-\\']|(\\.\\ ))[a-zA-Z\\u0080-\\u024F]+)*$"),e=t.filter(n=>Annotation.isCityField(n));for(var i of e)if(!f.test(i.value))return Swal.fire({title:"Warnung",text:`Bitte überprüfen Sie die eingegebene Ortsangabe "${i.value}" auf korrekte Formatierung. Beispiele für richtige Formate sind: München, Île-de-France, Sauðárkrókur, San Francisco, St. Catharines usw.`,icon:"warning"}),!1;const o=await this.validateAnnotations(this.signatureCount);return o===!1?(Swal.fire({title:"Warnung",text:"Es wurden nicht alle Signaturfelder ausgefüllt!",icon:"warning"}),!1):Swal.fire({title:localized.confirmation,html:`<div class="text-start fs-6 p-0 m-0">${localized.sigAgree}</div>`,icon:"question",showCancelButton:!0,confirmButtonColor:"#3085d6",cancelButtonColor:"#d33",confirmButtonText:localized.finalize,cancelButtonText:localized.back}).then(async t=>{if(t.isConfirmed){try{await this.Instance.save()}catch(i){return Swal.fire({title:"Fehler",text:"Umschlag konnte nicht signiert werden!",icon:"error"}),!1}try{const i=await n,t=await this.Network.postEnvelope(this.envelopeKey,this.currentDocument.id,i);return t.fatal?(Swal.fire({title:"Fehler",text:"Umschlag konnte nicht signiert werden!",icon:"error"}),!1):t.error?(Swal.fire({title:"Warnung",text:"Umschlag ist nicht mehr verfügbar.",icon:"warning"}),!1):!0}catch(i){return!1}}else return!1})}async validateAnnotations(n){const t=await Annotation.getAnnotations(this.Instance),i=t.map(n=>n.toJS()).filter(n=>n.isSignature);return n>i.length?!1:!0}async handleReset(){const n=await Swal.fire({title:"Sind sie sicher?",text:"Wollen Sie das Dokument und alle erstellten Signaturen zurücksetzen?",icon:"question",showCancelButton:!0});if(n.isConfirmed){const n=await Annotation.deleteAnnotations(this.Instance)}return n}}
|
||||
1
EnvelopeGenerator.Web/wwwroot/js/error-space.min.js
vendored
Normal file
1
EnvelopeGenerator.Web/wwwroot/js/error-space.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
function drawVisor(){const t=document.getElementById("visor"),n=t.getContext("2d");n.beginPath();n.moveTo(5,45);n.bezierCurveTo(15,64,45,64,55,45);n.lineTo(55,20);n.bezierCurveTo(55,15,50,10,45,10);n.lineTo(15,10);n.bezierCurveTo(15,10,5,10,5,20);n.lineTo(5,45);n.fillStyle="#2f3640";n.strokeStyle="#f5f6fa";n.fill();n.stroke()}function animate(){requestAnimationFrame(animate);ctx.clearRect(0,0,innerWidth,innerHeight);ctx.beginPath();ctx.moveTo(130,170);ctx.bezierCurveTo(250,y1,345,y2,400,y3);ctx.strokeStyle="white";ctx.lineWidth=8;ctx.stroke();y1===100&&(y1Forward=!0);y1===300&&(y1Forward=!1);y2===100&&(y2Forward=!0);y2===310&&(y2Forward=!1);y3===100&&(y3Forward=!0);y3===317&&(y3Forward=!1);y1Forward?y1+=1:y1-=1;y2Forward?y2+=1:y2-=1;y3Forward?y3+=1:y3-=1}const cordCanvas=document.getElementById("cord"),ctx=cordCanvas.getContext("2d");let y1=160,y2=100,y3=100,y1Forward=!0,y2Forward=!1,y3Forward=!0;drawVisor();animate();
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user