File size: 14,396 Bytes
ed79486 73566f6 0dcd2ca 73566f6 0dcd2ca 73566f6 ed79486 73566f6 ed79486 73566f6 e94898e ed79486 73566f6 e94898e ed79486 73566f6 ed79486 e94898e ed79486 73566f6 e94898e ed79486 b1efa3a 73566f6 ed79486 73566f6 ed79486 73566f6 ed79486 73566f6 ed79486 e94898e ed79486 73566f6 ed79486 e94898e ed79486 73566f6 ed79486 73566f6 ed79486 73566f6 ed79486 73566f6 ed79486 73566f6 ed79486 73566f6 ed79486 73566f6 ed79486 6562178 73566f6 463dbc3 0dcd2ca ed79486 0dcd2ca ed79486 0dcd2ca ed79486 463dbc3 73566f6 b1efa3a 73566f6 ed79486 73566f6 ed79486 73566f6 ed79486 73566f6 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
<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">
<!-- FRONT: sign-up on main panel, image side on left -->
<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>
<!-- aria-live region for screen readers (visually hidden) -->
<div class="sr-only" aria-live="assertive" *ngIf="submitted && invalidFieldsMessage">{{ invalidFieldsMessage }}</div>
<!-- Extend form to include terms, reCAPTCHA, and submit button -->
<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'))" />
<!-- Name errors intentionally hidden until Create Account and per user request no name error texts -->
</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'))" />
<!-- Last name errors intentionally hidden until Create Account and per user request no name error texts -->
</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>
<!-- wrap input and eye in a positioned wrapper so the eye is anchored to the input only -->
<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" />
<!-- local eye toggle button (sign-up only) -->
<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>
<!-- wrap confirm input and eye similarly -->
<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" />
<!-- local eye toggle for confirm (sign-up only) -->
<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>
<!--2FA opt-in and method selection -->
<div class="form-row">
<div class="form-field twofa-field">
<!-- single row: checkbox + methods -->
<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>
<!-- Email-specific display: show account email (plain text) -->
<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>
<!-- SMS-specific input -->
<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>
<!-- reCAPTCHA -->
<div id="recaptcha-container" class="recaptcha-container"></div>
<small *ngIf="recaptchaError" class="error">{{ recaptchaError }}</small>
<!-- Submit -->
<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>
<!-- Terms & Privacy moved outside form group, aligned with2FA -->
<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>
<!-- Google Sign-In button -->
<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>
<!-- Detached overlay/modal so layout doesn't shift -->
<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>
<!-- Floating Info Popup -->
<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>
|