WIP
This commit is contained in:
@@ -1,63 +1,63 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
import { Controller } from "@hotwired/stimulus";
|
||||
|
||||
// Connects to data-controller="password-validator"
|
||||
export default class extends Controller {
|
||||
static targets = ["input", "requirementType", "blockLine"]
|
||||
static targets = ["input", "requirementType", "blockLine"];
|
||||
|
||||
connect() {
|
||||
this.validate()
|
||||
this.validate();
|
||||
}
|
||||
|
||||
validate() {
|
||||
const password = this.inputTarget.value
|
||||
let requirementsMet = 0
|
||||
const password = this.inputTarget.value;
|
||||
let requirementsMet = 0;
|
||||
|
||||
// Check each requirement and count how many are met
|
||||
const lengthValid = password.length >= 8
|
||||
const caseValid = /[A-Z]/.test(password) && /[a-z]/.test(password)
|
||||
const numberValid = /\d/.test(password)
|
||||
const specialValid = /[!@#$%^&*(),.?":{}|<>]/.test(password)
|
||||
const lengthValid = password.length >= 8;
|
||||
const caseValid = /[A-Z]/.test(password) && /[a-z]/.test(password);
|
||||
const numberValid = /\d/.test(password);
|
||||
const specialValid = /[!@#$%^&*(),.?":{}|<>]/.test(password);
|
||||
|
||||
// Update individual requirement text
|
||||
this.validateRequirementText("length", lengthValid)
|
||||
this.validateRequirementText("case", caseValid)
|
||||
this.validateRequirementText("number", numberValid)
|
||||
this.validateRequirementText("special", specialValid)
|
||||
this.validateRequirementText("length", lengthValid);
|
||||
this.validateRequirementText("case", caseValid);
|
||||
this.validateRequirementText("number", numberValid);
|
||||
this.validateRequirementText("special", specialValid);
|
||||
|
||||
// Count total requirements met
|
||||
if (lengthValid) requirementsMet++
|
||||
if (caseValid) requirementsMet++
|
||||
if (numberValid) requirementsMet++
|
||||
if (specialValid) requirementsMet++
|
||||
if (lengthValid) requirementsMet++;
|
||||
if (caseValid) requirementsMet++;
|
||||
if (numberValid) requirementsMet++;
|
||||
if (specialValid) requirementsMet++;
|
||||
|
||||
// Update block lines sequentially
|
||||
this.updateBlockLines(requirementsMet)
|
||||
this.updateBlockLines(requirementsMet);
|
||||
}
|
||||
|
||||
validateRequirementText(type, isValid) {
|
||||
this.requirementTypeTargets.forEach(target => {
|
||||
this.requirementTypeTargets.forEach((target) => {
|
||||
if (target.dataset.requirementType === type) {
|
||||
if (isValid) {
|
||||
target.classList.remove("text-secondary")
|
||||
target.classList.add("text-green-600")
|
||||
target.classList.remove("text-secondary");
|
||||
target.classList.add("text-green-600");
|
||||
} else {
|
||||
target.classList.remove("text-green-600")
|
||||
target.classList.add("text-secondary")
|
||||
target.classList.remove("text-green-600");
|
||||
target.classList.add("text-secondary");
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
updateBlockLines(requirementsMet) {
|
||||
// Update block lines sequentially based on total requirements met
|
||||
this.blockLineTargets.forEach((line, index) => {
|
||||
if (index < requirementsMet) {
|
||||
line.classList.remove("bg-gray-200")
|
||||
line.classList.add("bg-green-600")
|
||||
line.classList.remove("bg-gray-200");
|
||||
line.classList.add("bg-green-600");
|
||||
} else {
|
||||
line.classList.remove("bg-green-600")
|
||||
line.classList.add("bg-gray-200")
|
||||
line.classList.remove("bg-green-600");
|
||||
line.classList.add("bg-gray-200");
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
import { Controller } from "@hotwired/stimulus";
|
||||
|
||||
// Connects to data-controller="password-visibility"
|
||||
export default class extends Controller {
|
||||
static targets = ["input", "showIcon", "hideIcon"]
|
||||
static targets = ["input", "showIcon", "hideIcon"];
|
||||
|
||||
connect() {
|
||||
// Initially hide the eye-off icon
|
||||
this.hideIconTarget.classList.add("hidden")
|
||||
this.hideIconTarget.classList.add("hidden");
|
||||
}
|
||||
|
||||
toggle() {
|
||||
const input = this.inputTarget
|
||||
const type = input.type === "password" ? "text" : "password"
|
||||
input.type = type
|
||||
|
||||
this.showIconTarget.classList.toggle("hidden")
|
||||
this.hideIconTarget.classList.toggle("hidden")
|
||||
const input = this.inputTarget;
|
||||
const type = input.type === "password" ? "text" : "password";
|
||||
input.type = type;
|
||||
|
||||
this.showIconTarget.classList.toggle("hidden");
|
||||
this.hideIconTarget.classList.toggle("hidden");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ export default class extends Controller {
|
||||
"deleteProfileImage",
|
||||
"input",
|
||||
"clearBtn",
|
||||
"uploadText",
|
||||
"changeText",
|
||||
"cameraIcon"
|
||||
];
|
||||
|
||||
clearFileInput() {
|
||||
@@ -17,6 +20,9 @@ export default class extends Controller {
|
||||
this.attachedImageTarget.classList.add("hidden");
|
||||
this.previewImageTarget.classList.add("hidden");
|
||||
this.deleteProfileImageTarget.value = "1";
|
||||
this.uploadTextTarget.classList.remove("hidden");
|
||||
this.changeTextTarget.classList.add("hidden");
|
||||
this.cameraIconTarget.classList.remove("!hidden");
|
||||
}
|
||||
|
||||
showFileInputPreview(event) {
|
||||
@@ -28,7 +34,9 @@ export default class extends Controller {
|
||||
this.previewImageTarget.classList.remove("hidden");
|
||||
this.clearBtnTarget.classList.remove("hidden");
|
||||
this.deleteProfileImageTarget.value = "0";
|
||||
|
||||
this.uploadTextTarget.classList.add("hidden");
|
||||
this.changeTextTarget.classList.remove("hidden");
|
||||
this.cameraIconTarget.classList.add("!hidden");
|
||||
this.previewImageTarget.querySelector("img").src =
|
||||
URL.createObjectURL(file);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<div class="bg-gray-25 h-screen flex flex-col justify-between">
|
||||
<div class="bg-gray-25 min-h-screen flex flex-col justify-between">
|
||||
<%= render "onboardings/header" %>
|
||||
|
||||
<div class="grow max-w-lg w-full mx-auto bg-gray-25 flex flex-col justify-center">
|
||||
<div class="grow max-w-lg w-full mx-auto bg-gray-25 flex flex-col justify-center md:py-0 py-6 px-4 md:px-0">
|
||||
<div>
|
||||
<div class="space-y-1 mb-6">
|
||||
<h1 class="text-2xl font-medium"><%= t(".title") %></h1>
|
||||
<h1 class="text-2xl font-medium md:text-2xl"><%= t(".title") %></h1>
|
||||
<p class="text-secondary text-sm"><%= t(".subtitle") %></p>
|
||||
</div>
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
<%= form.hidden_field :onboarded_at, value: Time.current if @invitation %>
|
||||
|
||||
<div class="space-y-4 mb-4">
|
||||
<p class="text-secondary text-xs"><%= t(".profile_image") %></p>
|
||||
<p class="text-secondary text-xs hidden md:block"><%= t(".profile_image") %></p>
|
||||
<%= render "settings/user_avatar_field", form: form, user: @user %>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center gap-4 mb-4">
|
||||
<%= form.text_field :first_name, placeholder: t(".first_name"), label: t(".first_name"), container_class: "bg-container w-1/2", required: true %>
|
||||
<%= form.text_field :last_name, placeholder: t(".last_name"), label: t(".last_name"), container_class: "bg-container w-1/2", required: true %>
|
||||
<div class="flex flex-col md:flex-row md:justify-between md:items-center md:gap-4 space-y-4 md:space-y-0 mb-4">
|
||||
<%= form.text_field :first_name, placeholder: t(".first_name"), label: t(".first_name"), container_class: "bg-container md:w-1/2 w-full", required: true %>
|
||||
<%= form.text_field :last_name, placeholder: t(".last_name"), label: t(".last_name"), container_class: "bg-container md:w-1/2 w-full", required: true %>
|
||||
</div>
|
||||
<% unless @invitation %>
|
||||
<div class="space-y-4 mb-4">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<%# locals: (form:, user:) %>
|
||||
|
||||
<div class="flex items-center gap-4" data-controller="profile-image-preview">
|
||||
<div class="relative flex justify-center items-center bg-gray-50 w-24 h-24 rounded-full border-alpha-black-300 border border-dashed">
|
||||
<div class="flex md:flex-row flex-col md:items-center items-center gap-4" data-controller="profile-image-preview">
|
||||
<div class="relative flex justify-center items-center bg-gray-50 size-26 md:size-24 rounded-full border-alpha-black-300 border border-dashed">
|
||||
|
||||
<%# The image preview once user has uploaded a new file %>
|
||||
<div data-profile-image-preview-target="previewImage" class="h-full w-full flex justify-center items-center hidden">
|
||||
@@ -34,13 +34,23 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="md:text-left text-center">
|
||||
<%= form.hidden_field :delete_profile_image, value: "0", data: { profile_image_preview_target: "deleteProfileImage" } %>
|
||||
|
||||
<p class="mb-3"><%= t(".accepted_formats") %></p>
|
||||
<%= form.label :profile_image, class: "btn btn--outline inline-block", data: { profile_image_preview_target: "uploadButton" } do %>
|
||||
|
||||
<%= form.label :profile_image, t(".choose"),
|
||||
class: "btn btn--outline inline-block" %>
|
||||
<%= lucide_icon "camera", class: "w-5 h-5 mr-2 inline-block", data: { profile_image_preview_target: "cameraIcon" } %>
|
||||
<span data-profile-image-preview-target="uploadText" class="<%= user.profile_image.attached? ? 'hidden' : '' %>" aria-hidden="<%= user.profile_image.attached? ? 'true' : 'false' %>">
|
||||
<%= t(".choose") %> <span class="text-secondary"><%= t(".choose_label") %></span>
|
||||
</span>
|
||||
<span data-profile-image-preview-target="changeText" class="<%= user.profile_image.attached? ? '' : 'hidden' %>" aria-hidden="<%= user.profile_image.attached? ? 'false' : 'true' %>">
|
||||
<%= t(".change") %>
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
|
||||
<p class="mt-2 text-xs text-secondary"><%= t(".accepted_formats") %></p>
|
||||
|
||||
<%= form.file_field :profile_image,
|
||||
accept: "image/png, image/jpeg",
|
||||
class: "hidden px-3 py-2 bg-gray-50 text-primary rounded-md text-sm font-medium",
|
||||
|
||||
@@ -97,4 +97,6 @@ en:
|
||||
previous: Back
|
||||
user_avatar_field:
|
||||
accepted_formats: JPG or PNG. 5MB max.
|
||||
choose: Choose
|
||||
choose: Upload photo
|
||||
choose_label: (optional)
|
||||
change: Change photo
|
||||
|
||||
Reference in New Issue
Block a user