This commit is contained in:
neo773
2025-04-12 00:06:11 +05:30
parent dd09d8d7ab
commit cbaaee1257
6 changed files with 75 additions and 55 deletions

View File

@@ -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");
}
})
});
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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);
}

View 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">

View File

@@ -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",

View File

@@ -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