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">&times;</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 &amp; 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">&times;</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>