|
|
<section class="signup-popup"> |
|
|
<div class="ai-particle-bg"></div> |
|
|
<div class="signup-header"> |
|
|
<div class="signup-logo"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="auth-card"> |
|
|
<div class="card-inner"> |
|
|
|
|
|
<div class="card-front"> |
|
|
<button class="signin-close" type="button" (click)="closePopup()" aria-label="Close">×</button> |
|
|
|
|
|
<div class="card-content"> |
|
|
<div class="side-panel side-left"> |
|
|
<div class="signup-panel-left"> |
|
|
|
|
|
</div> |
|
|
<div class="main-panel" [class.pwd-mismatch]="submitted && pwdMismatch"> |
|
|
<h2 class="signup-title center-title">Create An Account</h2> |
|
|
|
|
|
|
|
|
<div class="sr-only" aria-live="assertive" *ngIf="submitted && invalidFieldsMessage">{{ invalidFieldsMessage }}</div> |
|
|
|
|
|
|
|
|
<form [formGroup]="form" (ngSubmit)="submit()" class="create-form" novalidate autocomplete="off"> |
|
|
<div class="form-row"> |
|
|
<div class="form-field"> |
|
|
<label for="firstName">First Name |
|
|
<span class="label-cross" *ngIf="submitted && controlHasError('name')" aria-hidden="true">✖</span> |
|
|
</label> |
|
|
<input id="firstName" type="text" placeholder="First Name" formControlName="name" [attr.aria-invalid]="controlHasError('name')" [class.input-invalid]="submitted && (controlHasError('name') || nameHasDigits('name'))" /> |
|
|
|
|
|
</div> |
|
|
<div class="form-field"> |
|
|
<label for="lastName">Last Name |
|
|
<span class="label-cross" *ngIf="submitted && controlHasError('lastName')" aria-hidden="true">✖</span> |
|
|
</label> |
|
|
<input id="lastName" type="text" placeholder="Last Name" formControlName="lastName" [attr.aria-invalid]="controlHasError('lastName')" [class.input-invalid]="submitted && (controlHasError('lastName') || nameHasDigits('lastName'))" /> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
<div class="form-row"> |
|
|
<div class="form-field email-field" [class.email-invalid]="submitted && controlHasError('email')"> |
|
|
<label for="email">Email |
|
|
</label> |
|
|
<input id="email" type="text" placeholder="email@gmail.com" formControlName="email" [attr.aria-invalid]="controlHasError('email')" [class.input-invalid]="submitted && controlHasError('email')" autocomplete="off" autocapitalize="off" spellcheck="false" /> |
|
|
<small *ngIf="submitted && controlHasError('email','required')" class="error">Email is required.</small> |
|
|
<small *ngIf="submitted && controlHasError('email','pattern')" class="error">Enter a valid email/phone.</small> |
|
|
<small *ngIf="submitted && controlHasError('email','emailExists')" class="error">Email already exists.</small> |
|
|
</div> |
|
|
<div class="form-field role-field-wrapper"> |
|
|
<label for="roleGroup">Role Group |
|
|
<span class="label-cross" *ngIf="submitted && controlHasError('roleGroup')" aria-hidden="true">✖</span> |
|
|
<button type="button" class="info-btn" (click)="showInfo = true" aria-label="Role info">i</button> |
|
|
</label> |
|
|
<select id="roleGroup" formControlName="roleGroup" (change)="onRoleGroupChange($any($event.target).value)" aria-label="Role group" [class.input-invalid]="submitted && controlHasError('roleGroup')"> |
|
|
<option value="">-- Select role group --</option> |
|
|
<option *ngFor="let g of roleGroups" [value]="g.key">{{ g.label }}</option> |
|
|
</select> |
|
|
<small *ngIf="submitted && controlHasError('roleGroup','required')" class="error">Please select a role group.</small> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="form-row"> |
|
|
<div class="form-field password-field"> |
|
|
<label for="password">Create Password |
|
|
</label> |
|
|
|
|
|
<div class="input-with-eye"> |
|
|
<input id="password" [type]="showPassword ? 'text' : 'password'" placeholder="••••••••" formControlName="password" (keydown)="onPasswordKey($event)" (keyup)="onPasswordKey($event)" [class.input-invalid]="submitted && controlHasError('password')" autocomplete="new-password" autocapitalize="off" spellcheck="false" /> |
|
|
|
|
|
<button type="button" class="eye-toggle variant-signup" aria-label="Show password" [attr.aria-pressed]="showPassword" (click)="togglePasswordVisibility()"> |
|
|
<i class="fa-solid" [ngClass]="showPassword ? 'fa-eye' : 'fa-eye-slash'" aria-hidden="true"></i> |
|
|
</button> |
|
|
</div> |
|
|
<div *ngIf="capsLockOn" class="caps-lock-warning">Caps Lock is on</div> |
|
|
<small *ngIf="submitted && controlHasError('password','required')" class="error">Password is required.</small> |
|
|
<small *ngIf="submitted && form.get('password')?.hasError('passwordPolicy')" class="policy-info"> |
|
|
Required at least 8 characters using letters, numbers, and special character. |
|
|
</small> |
|
|
</div> |
|
|
|
|
|
<div class="form-field password-field"> |
|
|
<label for="confirmPassword">Confirm Password |
|
|
</label> |
|
|
|
|
|
<div class="input-with-eye" [class.password-mismatch]="submitted && pwdMismatch" [class.confirm-cross]="submitted && pwdMismatch"> |
|
|
<input id="confirmPassword" [type]="showConfirmPassword ? 'text' : 'password'" placeholder="••••••••" formControlName="confirmPassword" (keydown)="onPasswordKey($event)" (keyup)="onPasswordKey($event)" [class.input-invalid]="submitted && (controlHasError('confirmPassword') || showPwdMismatch())" autocomplete="new-password" autocapitalize="off" spellcheck="false" /> |
|
|
|
|
|
<button type="button" class="eye-toggle variant-signup" aria-label="Show confirm password" [attr.aria-pressed]="showConfirmPassword" (click)="toggleConfirmPasswordVisibility()"> |
|
|
<i class="fa-solid" [ngClass]="showConfirmPassword ? 'fa-eye' : 'fa-eye-slash'" aria-hidden="true"></i> |
|
|
</button> |
|
|
</div> |
|
|
<div *ngIf="capsLockOn" class="caps-lock-warning">Caps Lock is on</div> |
|
|
<small *ngIf="submitted && controlHasError('confirmPassword','required')" class="error">Confirm password is required.</small> |
|
|
<small *ngIf="submitted && showPwdMismatch()" class="error">Passwords do not match.</small> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="form-row"> |
|
|
<div class="form-field twofa-field"> |
|
|
|
|
|
<div class="twofa-row"> |
|
|
<div class="form-checkbox twofa-checkbox"> |
|
|
<input type="checkbox" id="twoFAOptIn" formControlName="twoFAOptIn" /> |
|
|
<label for="twoFAOptIn">Enable Two-Factor Authentication (2FA)</label> |
|
|
</div> |
|
|
|
|
|
<div *ngIf="form.get('twoFAOptIn')?.value" class="twofa-methods"> |
|
|
<label class="inline-control plain-control"><input type="radio" formControlName="twoFAMethod" value="email" /> Email</label> |
|
|
<label class="inline-control plain-control"><input type="radio" formControlName="twoFAMethod" value="sms" /> SMS</label> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div *ngIf="form.get('twoFAOptIn')?.value && form.get('twoFAMethod')?.value === 'email'" class="twofa-email-display"> |
|
|
<label class="twofa-email-label">2FA will be sent to</label> |
|
|
<div class="twofa-email-value">{{ form.get('email')?.value || '—' }}</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div *ngIf="form.get('twoFAOptIn')?.value && form.get('twoFAMethod')?.value === 'sms'" class="twofa-sms-options"> |
|
|
<label for="twoFAPhone">Phone number for SMS2FA</label> |
|
|
<input id="twoFAPhone" type="tel" placeholder="+1234567890" formControlName="twoFAPhone" (input)="formatPhone($event)" inputmode="tel" autocomplete="tel" [class.input-invalid]="submitted && form.get('twoFAPhone')?.invalid" /> |
|
|
<small *ngIf="submitted && form.get('twoFAPhone')?.invalid" class="error">Enter a valid phone number for SMS 2FA.</small> |
|
|
</div> |
|
|
|
|
|
<small *ngIf="twoFAError" class="error twofa-error">{{ twoFAError }}</small> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="recaptcha-container" class="recaptcha-container"></div> |
|
|
<small *ngIf="recaptchaError" class="error">{{ recaptchaError }}</small> |
|
|
|
|
|
|
|
|
<button class="create-btn ai-pulse" type="submit" [disabled]="loading || !isFormFilled()"> |
|
|
<ng-container *ngIf="!loading; else creatingAccount"> |
|
|
Create Account |
|
|
</ng-container> |
|
|
<ng-template #creatingAccount> |
|
|
<span class="spinner"></span> Creating Account... |
|
|
</ng-template> |
|
|
</button> |
|
|
</form> |
|
|
|
|
|
|
|
|
<div [formGroup]="form"> |
|
|
<div class="form-checkbox"> |
|
|
<input type="checkbox" id="terms" formControlName="terms" /> |
|
|
<label for="terms">Agree to our <a href="/legal/terms" target="_blank" rel="_noopener noreferrer">Terms & Conditions</a> and <a href="/legal/privacy" target="_blank" rel="_noopener noreferrer">Privacy Policy</a>.</label> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="google-signup-row"> |
|
|
<div id="google-signup-btn-div"> |
|
|
<div class="g-signin2" data-width="240" data-height="50" data-longtitle="true"></div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="create-footer"><b>Version 1.0.0 | © Pykara Technologies</b></div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="role-help-overlay" *ngIf="showRoleInfo" (click)="hideRoleInfo()"> |
|
|
<div class="role-help-modal" role="dialog" aria-label="Role descriptions" (click)="$event.stopPropagation()"> |
|
|
<button type="button" class="role-help-close" aria-label="Close role info" (click)="hideRoleInfo()">×</button> |
|
|
<h3 class="role-help-title">Role Information</h3> |
|
|
<ul class="role-help-list"> |
|
|
<li><strong>Law Enforcement</strong><br/>Investigator, Supervisor — for users handling case investigation, interviews, evidence review, and workflow supervision. These roles may require admin approval or identity verification.</li> |
|
|
<li><strong>Legal</strong><br/>Lawyer — for legal professionals involved in reviewing case summaries, preparing legal notes, analyzing evidence, or assisting in compliance processes.</li> |
|
|
<li><strong>Administration</strong><br/>Admin — for users who manage system settings, user access, roles, case assignments, and overall application operations.</li> |
|
|
<li><strong>General Access</strong><br/>Other — for users who require limited or basic access, such as support staff, trainees, or external collaborators.</li> |
|
|
</ul> |
|
|
<p class="role-help-tip">If you are not sure which role to choose, select <strong>Other</strong>, or contact: <a href="mailto:support@pykara.ai">support@pykara.ai</a></p> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div *ngIf="showInfo" class="info-popup-bg" (click)="showInfo = false"> |
|
|
<div class="info-popup" (click)="$event.stopPropagation()"> |
|
|
<button class="info-close" type="button" (click)="showInfo = false" aria-label="Close">×</button> |
|
|
<div class="info-title">Role Information</div> |
|
|
|
|
|
<div class="info-text"> |
|
|
<ul> |
|
|
<li><strong>Law Enforcement</strong><br/>Investigator, Supervisor — for users handling case investigation, interviews, evidence review, and workflow supervision. These roles may require admin approval or identity verification.</li> |
|
|
<li><strong>Legal</strong><br/>Lawyer — for legal professionals involved in reviewing case summaries, preparing legal notes, analyzing evidence, or assisting in compliance processes.</li> |
|
|
<li><strong>Administration</strong><br/>Admin — for users who manage system settings, user access, roles, case assignments, and overall application operations.</li> |
|
|
<li><strong>General Access</strong><br/>Other — for users who require limited or basic access, such as support staff, trainees, or external collaborators.</li> |
|
|
</ul> |
|
|
<p>If you are not sure which role to choose, select <strong>Other</strong>, or contact: <a href="mailto:info@pykara.ai">info@pykara.ai</a></p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|