From 5d2ebd1a79256ed14815df45a62da465d5de48a8 Mon Sep 17 00:00:00 2001 From: SomyaChawla0250 Date: Wed, 15 Jan 2025 01:32:49 +0530 Subject: [PATCH 01/10] minor fix --- src/views/Landing.vue | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/views/Landing.vue b/src/views/Landing.vue index b76d141..297a4ec 100644 --- a/src/views/Landing.vue +++ b/src/views/Landing.vue @@ -26,8 +26,8 @@ + +
import FlagService from "../api/userAPI"; import Button from "@/components/Button.vue"; +import HintsService from "../api/admin/hintsAPI"; import { CONFIG } from "@/config/config"; + export default { name: "ChallCard", props: ["challDetails", "tag", "isPreview"], @@ -114,16 +140,130 @@ export default { showSuccess: false, showFail: false, link: false, - copyText: "Click to Copy" + copyText: "Click to Copy", + hintStates: {}, + loadingHints: true }; }, - state: { - disable: false, - isModalVisible: false, - assetLinks: false, - additionalLinks: false + computed: { + loadedHints() { + if (!this.challDetails || !this.challDetails.hints) return []; + return this.challDetails.hints.filter(hint => this.hintStates[hint.id]); + }, + takenHints() { + if (!this.challDetails || !this.challDetails.hints) return []; + return this.challDetails.hints.filter( + hint => this.hintStates[hint.id] && this.hintStates[hint.id].taken + ); + } + }, + watch: { + challDetails: { + immediate: true, + handler(newVal) { + console.log("Challenge details changed:", JSON.stringify(newVal, null, 2)); + if (newVal && newVal.hints) { + console.log("Found hints in watcher:", JSON.stringify(newVal.hints, null, 2)); + this.loadHints(); + } + } + }, + 'challDetails.hints': { + async handler(newHints) { + if (newHints) { + await this.loadHints(); + } + }, + immediate: true + } }, methods: { + async loadHints() { + if (!this.challDetails.hints) return; + + try { + for (const hint of this.challDetails.hints) { + try { + const response = await HintsService.getHintStatus(hint.id); + const description = response.data.Description || response.data.description; + const points = response.data.Points || response.data.points; + + this.$set(this.hintStates, hint.id, { + description: description, + points: points, + taken: description !== "Hint is not taken yet" + }); + } catch (error) { + console.error("Error loading hint:", error); + + this.$set(this.hintStates, hint.id, { + description: "Not enough hint points!", + points: hint.points, + taken: false + }); + } + } + } catch (error) { + console.error("Error loading hints:", error); + } + }, + async handleHint(hintId) { + // If hint is already taken, just show it + if (this.hintStates[hintId] && this.hintStates[hintId].taken) { + this.$vToastify.setSettings({ theme: "beast-success" }); + this.$vToastify.success(this.hintStates[hintId].description, "Hint"); + return; + } + + try { + // Try to take the hint + const response = await HintsService.takeHint(hintId); + + if (!response || !response.data) { + this.$vToastify.setSettings({ theme: "beast-error" }); + this.$vToastify.error("Error fetching hint", "Error"); + return; + } + + const description = response.data.message; + + // Store and show the hint + if (description) { + this.$set(this.hintStates, hintId, { + description: description, + taken: true + }); + + this.$vToastify.setSettings({ theme: "beast-success" }); + this.$vToastify.success(description, "Hint"); + } else { + this.$vToastify.setSettings({ theme: "beast-error" }); + this.$vToastify.error("Error fetching hint", "Error"); + } + } catch (error) { + // For 403 errors (not enough points) + if (error.response && error.response.status === 403) { + const message = error.response.data && error.response.data.error + ? error.response.data.error + : "You don't have enough points to take this hint"; + + this.$vToastify.setSettings({ theme: "beast-error" }); + this.$vToastify.error(message, "Error"); + return; + } + + // For other errors with response data + if (error.response && error.response.data && error.response.data.error) { + this.$vToastify.setSettings({ theme: "beast-error" }); + this.$vToastify.error(error.response.data.error, "Error"); + return; + } + + // Fallback error message + this.$vToastify.setSettings({ theme: "beast-error" }); + this.$vToastify.error("Error fetching hint", "Error"); + } + }, getUrl(port) { let url = CONFIG.webRoot; let ncurl = CONFIG.ncRoot; @@ -148,37 +288,27 @@ export default { this.submitFlag(); } }, - submitFlag() { - FlagService.submitFlag(this.challDetails.id, this.flag).then(Response => { - this.$vToastify.setSettings({ - position: "center-right", - theme: "beast-success" - }); - if (Response.data.success) { + async submitFlag() { + try { + const response = await FlagService.submitFlag( + this.challDetails.id, + this.flag + ); + if (response.data.success) { this.showSuccess = true; - this.$vToastify.success("Flag submitted successfully", "Success"); + setTimeout(() => { + this.showSuccess = false; + }, 2000); + this.$emit("flag-submitted"); } else { this.showFail = true; - this.$vToastify.setSettings({ - theme: "beast-error" - }); - this.$vToastify.error( - Response.data.error ? Response.data.error : Response.data.message, - "Error" - ); + setTimeout(() => { + this.showFail = false; + }, 2000); } - }); - var self = this; - setTimeout(function() { - if (self.showSuccess) { - self.$router.go(); - } else { - self.$emit("updateChallenges"); - } - self.flag = ""; - self.showSuccess = false; - self.showFail = false; - }, 3000); + } catch (error) { + console.error(error); + } }, copyUrl(text) { navigator.permissions.query({ name: "clipboard-write" }).then(result => { @@ -206,18 +336,16 @@ export default { this.isModalVisible = false; } }, - watch: { - challDetails() { - this.flag = ""; - } - }, - mounted() { + async mounted() { + console.log("Component mounted, challDetails:", JSON.stringify(this.challDetails, null, 2)); if ( this.challDetails.category === "service" || this.challDetails.category === "xinetd" ) this.link = false; else this.link = true; + + await this.loadHints(); } }; diff --git a/src/styles/modules/_challCard.scss b/src/styles/modules/_challCard.scss index 257e37c..91b01f1 100644 --- a/src/styles/modules/_challCard.scss +++ b/src/styles/modules/_challCard.scss @@ -294,3 +294,71 @@ letter-spacing: 0.1rem; cursor: pointer; } + +.challCard-hints { + margin: 20px 0; + padding: 16px; + background: #f8f9fa; + border-radius: 8px; + border: 1px solid #e9ecef; + + .hint-buttons { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-top: 12px; + } + + .hint-button { + flex: 1; + min-width: 200px; + padding: 12px 16px; + border-radius: 8px; + border: none; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + background: #2c3e50; + color: white; + + &.hint-taken { + background: linear-gradient(135deg, #27ae60, #2ecc71); + } + + &:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); + opacity: 0.9; + } + + &.hint-loading { + background-color: #95a5a6; + cursor: not-allowed; + opacity: 0.7; + } + + .hint-button-content { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + } + + .hint-number { + font-weight: 600; + font-size: 15px; + } + + .hint-points { + font-size: 14px; + opacity: 0.8; + } + } + + .link-heading { + font-size: 18px; + font-weight: 600; + color: #2c3e50; + margin-bottom: 8px; + } +} From 267726fbf4e32b758e5ab6df77742c396e24e9c0 Mon Sep 17 00:00:00 2001 From: SomyaChawla0250 Date: Tue, 4 Feb 2025 13:40:07 +0530 Subject: [PATCH 03/10] fix: re-routing issue --- src/views/Landing.vue | 8 +++----- src/views/Register.vue | 1 + 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/views/Landing.vue b/src/views/Landing.vue index 297a4ec..3d80315 100644 --- a/src/views/Landing.vue +++ b/src/views/Landing.vue @@ -23,8 +23,8 @@ v-if="isLoggedIn()" to="/challenges" > -
+ + + +
this.hintStates[hint.id] && this.hintStates[hint.id].taken ); + }, + isHintTaken() { + return this.selectedHint && + this.hintStates[this.selectedHint.id] && + this.hintStates[this.selectedHint.id].taken; } }, watch: { @@ -207,16 +240,26 @@ export default { console.error("Error loading hints:", error); } }, - async handleHint(hintId) { - // If hint is already taken, just show it - if (this.hintStates[hintId] && this.hintStates[hintId].taken) { - this.$vToastify.setSettings({ theme: "beast-success" }); - this.$vToastify.success(this.hintStates[hintId].description, "Hint"); - return; - } + async handleHint(hint) { + if (!hint) return; + + // Show modal for both taken and untaken hints + this.selectedHint = hint; + this.showConfirmModal = true; + }, + + closeConfirmModal() { + this.showConfirmModal = false; + this.selectedHint = null; + }, + async confirmHint() { + if (!this.selectedHint) return; + + const hintId = this.selectedHint.id; + this.showConfirmModal = false; + try { - // Try to take the hint const response = await HintsService.takeHint(hintId); if (!response || !response.data) { @@ -227,21 +270,19 @@ export default { const description = response.data.message; - // Store and show the hint if (description) { this.$set(this.hintStates, hintId, { description: description, taken: true }); - - this.$vToastify.setSettings({ theme: "beast-success" }); - this.$vToastify.success(description, "Hint"); + + // Show the hint in modal + this.showConfirmModal = true; } else { this.$vToastify.setSettings({ theme: "beast-error" }); this.$vToastify.error("Error fetching hint", "Error"); } } catch (error) { - // For 403 errors (not enough points) if (error.response && error.response.status === 403) { const message = error.response.data && error.response.data.error ? error.response.data.error @@ -252,14 +293,12 @@ export default { return; } - // For other errors with response data if (error.response && error.response.data && error.response.data.error) { this.$vToastify.setSettings({ theme: "beast-error" }); this.$vToastify.error(error.response.data.error, "Error"); return; } - // Fallback error message this.$vToastify.setSettings({ theme: "beast-error" }); this.$vToastify.error("Error fetching hint", "Error"); } diff --git a/src/styles/modules/_challCard.scss b/src/styles/modules/_challCard.scss index 91b01f1..e603060 100644 --- a/src/styles/modules/_challCard.scss +++ b/src/styles/modules/_challCard.scss @@ -354,11 +354,88 @@ opacity: 0.8; } } +} - .link-heading { - font-size: 18px; - font-weight: 600; +// Modal styles +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.6); + display: flex; + justify-content: center; + align-items: center; + z-index: 9999; +} + +.modal-content { + background: white; + padding: 24px; + border-radius: 8px; + max-width: 400px; + width: 90%; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + position: relative; + z-index: 10000; + + h3 { color: #2c3e50; - margin-bottom: 8px; + margin: 0 0 16px 0; + font-size: 20px; + font-weight: 600; + } + + p { + color: #4a5568; + margin: 0 0 24px 0; + font-size: 16px; + line-height: 1.5; + + &.hint-text { + background: #f8f9fa; + padding: 16px; + border-radius: 6px; + border: 1px solid #e9ecef; + font-family: $primary-font-family; + white-space: pre-wrap; + } + } + + .modal-actions { + display: flex; + justify-content: flex-end; + gap: 12px; + + button { + min-width: 100px; + padding: 10px 20px; + border-radius: 6px; + font-weight: 500; + font-size: 14px; + cursor: pointer; + transition: all 0.2s; + + &.modal-cancel { + background: #e2e8f0; + color: #4a5568; + border: none; + + &:hover { + background: #cbd5e0; + } + } + + &.modal-confirm { + background: $theme-color-dark-orange; + color: white; + border: none; + + &:hover { + opacity: 0.9; + } + } + } } } From 1b18801df74826f7b2e4acff9bfd80ae167b458e Mon Sep 17 00:00:00 2001 From: SomyaChawla0250 Date: Tue, 4 Feb 2025 16:55:27 +0530 Subject: [PATCH 05/10] minor fix --- src/components/LeaderboardGraph.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/LeaderboardGraph.vue b/src/components/LeaderboardGraph.vue index 18dc32a..3a66029 100644 --- a/src/components/LeaderboardGraph.vue +++ b/src/components/LeaderboardGraph.vue @@ -3,7 +3,7 @@ :chartData="this.lineGraphData()" :options="this.lineGraphOptions" class="lineGraph" - :height="150" + :height="250" v-if="this.users.length > 0 && this.scoreSeries.length > 0" /> From 6e9e931495c69eb1d885e54f9b70a805f15e1d0d Mon Sep 17 00:00:00 2001 From: ayu-ch Date: Tue, 4 Feb 2025 18:31:06 +0530 Subject: [PATCH 06/10] Integrates max attempts Signed-off-by: ayu-ch --- src/components/ChallCard.vue | 22 ++++------- src/components/Stats.vue | 22 +++++++++-- src/styles/modules/_challCard.scss | 60 ++++++++++++++++++++++++++++++ src/utils/challenges.js | 7 +--- src/views/Challenges.vue | 6 ++- 5 files changed, 93 insertions(+), 24 deletions(-) diff --git a/src/components/ChallCard.vue b/src/components/ChallCard.vue index 10528ff..83d4b78 100644 --- a/src/components/ChallCard.vue +++ b/src/components/ChallCard.vue @@ -16,6 +16,9 @@ | {{ challDetails.solves.length }} Solves + + | {{ challDetails.previous_tries }}/{{ challDetails.failSolveLimit }} attempts +
@@ -126,7 +129,7 @@
@@ -147,6 +150,9 @@ />
+
+ Maximum attempts reached +
@@ -359,20 +365,6 @@ export default { setTimeout(() => { this.copyText = "Click to Copy"; }, 1000); - }, - isDisabled: function() { - let flag = document.getElementById("flag-input").value; - if (flag != "") { - this.disable = true; - } else { - this.disable = false; - } - }, - showModal() { - this.isModalVisible = true; - }, - closeModal() { - this.isModalVisible = false; } }, async mounted() { diff --git a/src/components/Stats.vue b/src/components/Stats.vue index f15e2b6..2f8ee7b 100644 --- a/src/components/Stats.vue +++ b/src/components/Stats.vue @@ -9,11 +9,11 @@

Total

-

{{ details.challenges.length }}

+

{{ getSolvedCount }}

Solved

-

{{ total - details.challenges.length }}

+

{{ total - getSolvedCount }}

Unsolved

@@ -23,6 +23,22 @@ diff --git a/src/styles/modules/_challCard.scss b/src/styles/modules/_challCard.scss index e603060..90a3b03 100644 --- a/src/styles/modules/_challCard.scss +++ b/src/styles/modules/_challCard.scss @@ -79,6 +79,11 @@ line-height: 1.125rem; align-items: center; color: $theme-color-grey39; + + .attempts-counter { + color: $theme-color-grey57; + font-size: 0.875rem; + } } .challCard-challDesc { @@ -275,6 +280,61 @@ } } } + + .challCard-submit { + margin-top: 1rem; + padding: 1rem; + border-top: 1px solid #e0e0e0; + + &-container { + display: flex; + gap: 1rem; + } + + &-input { + flex: 1; + padding: 0.5rem 1rem; + border: 1px solid #e0e0e0; + border-radius: 4px; + font-family: $primary-font-family; + font-size: 0.875rem; + + &:focus { + outline: none; + border-color: #007bff; + } + } + + &-button { + padding: 0.5rem 1rem; + border: none; + border-radius: 4px; + background-color: #007bff; + color: white; + font-family: $primary-font-family; + font-size: 0.875rem; + cursor: pointer; + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + &:not(:disabled):hover { + background-color: #0056b3; + } + } + } + + .challCard-maxed { + text-align: center; + color: #dc3545; + font-family: $primary-font-family; + font-size: 0.875rem; + margin-top: 1rem; + padding: 1rem; + border-top: 1px solid #e0e0e0; + } } .preview-challcard { diff --git a/src/utils/challenges.js b/src/utils/challenges.js index bbc71d6..fb94d48 100644 --- a/src/utils/challenges.js +++ b/src/utils/challenges.js @@ -8,11 +8,8 @@ export const getChallenges = async (getUserSolves, username) => { if (getUserSolves) { let userData = await UserService.getUserByUsername(username); challenges.forEach(challenge => { - if ( - userData.data.challenges.find(el => { - return el.id === challenge.id; - }) - ) { + // Check if the current user has solved this challenge + if (challenge.solves && challenge.solves.some(solve => solve.username === username)) { challenge.isSolved = true; } }); diff --git a/src/views/Challenges.vue b/src/views/Challenges.vue index 4e137e5..d4885a8 100644 --- a/src/views/Challenges.vue +++ b/src/views/Challenges.vue @@ -12,7 +12,11 @@
- +
Date: Tue, 4 Feb 2025 23:13:53 +0530 Subject: [PATCH 07/10] wip: register page --- src/views/Register.vue | 198 ++++++++++++++--------------------------- 1 file changed, 66 insertions(+), 132 deletions(-) diff --git a/src/views/Register.vue b/src/views/Register.vue index 480773e..8d8b754 100644 --- a/src/views/Register.vue +++ b/src/views/Register.vue @@ -9,106 +9,46 @@
- +
- +
-
{{ this.UsernameErr }}
+
{{ UsernameErr }}
- + +
-
{{ this.EmailErr }}
+
{{ EmailErr }}
+
+
+
+ + +
+ +
{{ OtpErr }}
- +
-
{{ this.PassLen }}
+
{{ PassLen }}
- +
-
{{ this.PassErr }}
+
{{ PassErr }}
-
@@ -134,76 +74,70 @@ export default { email: "", password: "", confirmPassword: "", - status: false, - UsernameErr: false, - EmailErr: false, - PassLen: false, - PassErr: false, + otp: "", + otpVerified: false, + UsernameErr: "", + EmailErr: "", + PassLen: "", + PassErr: "", + OtpErr: "", registered: false, - errorIcon: "error-white", - tickIcon: "tick-white" }; }, + computed: { + canRegister() { + return ( + this.uname && + this.username && + this.password && + this.confirmPassword && + this.email && + !this.PassErr && + !this.PassLen && + !this.UsernameErr && + !this.EmailErr && + this.password === this.confirmPassword && + this.otpVerified + ); + } + }, methods: { - validateUsername(e) { - if (this.username.length > 12) { - this.UsernameErr = "Username should be 12 characters max"; - } else { - this.UsernameErr = false; - } + validateUsername() { + this.UsernameErr = this.username.length > 12 ? "Username should be 12 characters max" : ""; }, - validateEmail(e) { - const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - if (re.test(this.email)) { - this.EmailErr = false; - } else { - this.EmailErr = "Invalid Email"; - } + validateEmail() { + const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + this.EmailErr = re.test(this.email) ? "" : "Invalid Email"; }, - validatePassword(e) { - if (this.password.length < 8) { - this.PassLen = "Password should be atleast of 8 characters"; - } else { - this.PassLen = false; + validatePassword() { + this.PassLen = this.password.length < 8 ? "Password should be at least 8 characters" : ""; + }, + comparePassword() { + this.PassErr = this.password !== this.confirmPassword ? "Passwords don't match" : ""; + }, + async sendOTP() { + if (!this.EmailErr) { + this.$vToastify.success("OTP sent to your email", "Success"); } }, - comparePassword(e) { - if (this.password !== this.confirmPassword) { - this.PassErr = "Passwords don't match"; + verifyOTP() { + if (this.otp === "123456") { + this.otpVerified = true; + this.OtpErr = ""; + this.$vToastify.success("Email verified successfully", "Success"); } else { - this.PassErr = false; + this.OtpErr = "Invalid OTP"; } }, - sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); - }, async register() { - if ( - !this.PassErr && - !this.UsernameErr && - !this.EmailErr && - !this.PassLen && - !this.registered - ) { - const registerResponse = await RegisterUser.registerUser( - this.uname, - this.username, - this.email, - this.password - ); + if (this.canRegister && !this.registered) { + const registerResponse = await RegisterUser.registerUser(this.uname, this.username, this.email, this.password); if (registerResponse.status !== 200) { - this.$vToastify.setSettings({ - theme: "beast-error" - }); this.$vToastify.error(registerResponse.data.message, "Error"); } else { - this.$vToastify.setSettings({ - theme: "beast-success" - }); this.$vToastify.success("Registered Successfully", "Success"); this.registered = true; - await this.sleep(3000); - this.$router.push("/login"); + setTimeout(() => this.$router.push("/login"), 3000); } } } From 3773fdcedf70931625c560aadf05bca79d351447 Mon Sep 17 00:00:00 2001 From: Arshdeep54 Date: Wed, 5 Feb 2025 03:03:09 +0530 Subject: [PATCH 08/10] fix register form with email verification Signed-off-by: Arshdeep54 --- src/styles/modules/_auth.scss | 197 ++++++++++++++++++++++++++++++++-- src/views/Register.vue | 166 +++++++++++++++++++++------- 2 files changed, 315 insertions(+), 48 deletions(-) diff --git a/src/styles/modules/_auth.scss b/src/styles/modules/_auth.scss index de5abb8..9d557a1 100644 --- a/src/styles/modules/_auth.scss +++ b/src/styles/modules/_auth.scss @@ -1,5 +1,7 @@ .auth { width: 100%; + display: flex; + flex-direction: column; .auth-subheading { @extend %font-style-basic; @@ -12,7 +14,7 @@ } .auth-image { - width: 60%; + width: 100%; } .auth-container { @@ -20,6 +22,7 @@ flex-direction: row; align-items: center; text-align: center; + .heading { font-family: $primary-font-family; font-weight: bold; @@ -27,6 +30,10 @@ line-height: 2.75rem; color: $theme-color-grey39; text-align: left; + + .switch { + color: $theme-color-light-orange; + } } } @@ -43,18 +50,190 @@ .form { width: 35%; - .inputField { - width: 100%; + .info { + position: relative; + margin-bottom: 1rem; + + .inputField { + width: 100%; + margin-top: 1rem; + padding: 0.75rem; + border: 1px solid $theme-color-light-grey-d7; + border-radius: 4px; + font-size: 1rem; + + &:focus { + outline: none; + border-color: $theme-color-light-orange; + } + } + + .verify-button { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + background: $theme-color-light-orange; + color: $theme-color-white; + border: none; + padding: 0.5rem 1rem; + border-radius: 4px; + cursor: pointer; + + &:disabled { + background: $theme-color-light-grey-d7; + cursor: not-allowed; + } + } + + .verification-message { + margin-bottom: 1rem; + color: $theme-color-grey39; + } + } + + .text-field-error { + font-size: 0.9rem; + display: flex; + flex-direction: row; + align-items: center; + gap: 5px; + color: $theme-color-red; + margin-top: 0.5rem; + + .errImg { + width: 16px; + height: 16px; + } + } + + .form-actions { + margin-top: 2rem; + + .next-arrow { + display: flex; + align-items: center; + gap: 0.5rem; + cursor: pointer; + color: $theme-color-light-orange; + + &.disabled { + color: $theme-color-light-grey-d7; + cursor: not-allowed; + } + + .next-text { + font-weight: 500; + } + } + } + + .timer-container { margin-top: 1rem; + text-align: center; + color: $theme-color-grey57; + + .resend-button { + background: none; + border: none; + color: $theme-color-light-orange; + cursor: pointer; + text-decoration: underline; + } } } + + .auth-image-container { + width: 60%; + display: flex; + justify-content: center; + align-items: center; + } } - .text-field-error { - font-size: 0.9rem; - display: flex; - flex-direction: row; - align-items: center; - gap: 5px; + .progress-container { + margin-top: 2rem; + + .progress-steps { + display: flex; + align-items: center; + justify-content: space-between; + position: relative; + + .step-item { + display: flex; + flex-direction: column; + align-items: center; + position: relative; + z-index: 1; + + &.active { + .step-circle { + background: $theme-color-white; + border-color: $theme-color-light-orange; + color: $theme-color-light-orange; + } + + .step-title { + color: $theme-color-light-orange; + } + } + + &.completed { + .step-circle { + background: $theme-color-light-orange; + border-color: $theme-color-light-orange; + color: $theme-color-white; + } + } + + .step-circle { + width: 30px; + height: 30px; + border-radius: 50%; + background: $theme-color-white; + border: 2px solid $theme-color-light-grey-d7; + color: $theme-color-light-grey-d7; + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; + transition: all 0.3s ease; + } + + .step-title { + margin-top: 0.5rem; + color: $theme-color-grey57; + font-size: 0.875rem; + } + } + + .step-line { + flex: 1; + height: 2px; + background: $theme-color-light-grey-d7; + margin: 0 1rem; + position: relative; + top: -15px; + z-index: 0; + + &.active { + background: $theme-color-light-orange; + } + } + } + } + + .register-button-container { + margin-top: 2rem; + + .register-button { + width: 100%; + + &:disabled { + opacity: 0.7; + cursor: not-allowed; + } + } } } diff --git a/src/views/Register.vue b/src/views/Register.vue index 8d8b754..11d1e72 100644 --- a/src/views/Register.vue +++ b/src/views/Register.vue @@ -8,49 +8,97 @@
-
- -
-
- -
- -
{{ UsernameErr }}
+
+
+
-
-
- - -
- -
{{ EmailErr }}
+
+ +
+ +
{{ UsernameErr }}
+
+
+
+
+ Next + + + +
-
- - -
- -
{{ OtpErr }}
+ +
+
+
Setup Email
+ + +
+ +
{{ EmailErr }}
+
+
+
+
+ Please enter the OTP sent to {{ email }} +
+ + +
+ +
{{ OtpErr }}
+
+
+ Resend OTP in {{ timer }} seconds + +
-
- -
- -
{{ PassLen }}
+ +
+
+ +
+ +
{{ PassLen }}
+
+
+
+ +
+ +
{{ PassErr }}
+
+
+
+
-
- -
- -
{{ PassErr }}
+ +
+
+
+
1
+
Basic Info
+
+
+
+
2
+
Setup Email
+
+
+
+
3
+
Set Password
+
-
+
+
-
@@ -59,6 +107,7 @@ import RegisterUser from "../api/admin/authAPI.js"; import ErrorBox from "../components/ErrorBox"; import Button from "@/components/Button.vue"; + export default { name: "register", components: { @@ -67,6 +116,7 @@ export default { }, data() { return { + currentStep: 1, msg: null, icon: null, uname: "", @@ -75,6 +125,7 @@ export default { password: "", confirmPassword: "", otp: "", + otpSent: false, otpVerified: false, UsernameErr: "", EmailErr: "", @@ -82,26 +133,45 @@ export default { PassErr: "", OtpErr: "", registered: false, + timer: 0, + timerInterval: null, }; }, computed: { + canProceedToEmail() { + return this.uname && this.username && !this.UsernameErr; + }, + canProceedToOTP() { + return this.email && !this.EmailErr; + }, canRegister() { return ( - this.uname && - this.username && this.password && this.confirmPassword && - this.email && !this.PassErr && !this.PassLen && - !this.UsernameErr && - !this.EmailErr && this.password === this.confirmPassword && this.otpVerified ); + }, + progressWidth() { + return `${((this.currentStep - 1) / 2) * 100}%`; } }, methods: { + startTimer() { + this.timer = 60; + if (this.timerInterval) { + clearInterval(this.timerInterval); + } + this.timerInterval = setInterval(() => { + if (this.timer > 0) { + this.timer--; + } else { + clearInterval(this.timerInterval); + } + }, 1000); + }, validateUsername() { this.UsernameErr = this.username.length > 12 ? "Username should be 12 characters max" : ""; }, @@ -116,7 +186,9 @@ export default { this.PassErr = this.password !== this.confirmPassword ? "Passwords don't match" : ""; }, async sendOTP() { - if (!this.EmailErr) { + if (this.canProceedToOTP) { + this.otpSent = true; + this.startTimer(); this.$vToastify.success("OTP sent to your email", "Success"); } }, @@ -125,10 +197,21 @@ export default { this.otpVerified = true; this.OtpErr = ""; this.$vToastify.success("Email verified successfully", "Success"); + this.currentStep = 3; + if (this.timerInterval) { + clearInterval(this.timerInterval); + } } else { this.OtpErr = "Invalid OTP"; } }, + proceedToEmail() { + if (this.canProceedToEmail) { + this.currentStep = 2; + } else { + this.$vToastify.error("Please fill all required fields correctly", "Error"); + } + }, async register() { if (this.canRegister && !this.registered) { const registerResponse = await RegisterUser.registerUser(this.uname, this.username, this.email, this.password); @@ -141,6 +224,11 @@ export default { } } } + }, + beforeDestroy() { + if (this.timerInterval) { + clearInterval(this.timerInterval); + } } }; From 773ea66f32744e86917f9ab35bff6804b58bcdbf Mon Sep 17 00:00:00 2001 From: Arshdeep54 Date: Wed, 5 Feb 2025 18:54:14 +0530 Subject: [PATCH 09/10] fix: change register flow , and add email check Signed-off-by: Arshdeep54 --- src/styles/modules/_auth.scss | 198 ++++++++++++++++++++--- src/views/Login.vue | 6 +- src/views/Register.vue | 286 +++++++++++++++++++++++++--------- 3 files changed, 393 insertions(+), 97 deletions(-) diff --git a/src/styles/modules/_auth.scss b/src/styles/modules/_auth.scss index 9d557a1..831ff21 100644 --- a/src/styles/modules/_auth.scss +++ b/src/styles/modules/_auth.scss @@ -52,43 +52,144 @@ .info { position: relative; - margin-bottom: 1rem; + margin-bottom: 1.25rem; + + .step-title { + font-size: 1.1rem; + font-weight: 500; + color: $theme-color-grey39; + margin-bottom: 0.5rem; + } .inputField { width: 100%; + max-width: 320px; + height: 12px; + margin-top: 0.5rem; + padding: 0.75rem 1rem; + border: 1px solid $theme-color-light-grey-d7; + border-radius: 4px; + font-size: 0.95rem; + transition: all 0.2s ease; + background-color: #fff; + + &:focus { + outline: none; + border-color: $theme-color-light-orange; + box-shadow: 0 0 0 2px rgba($theme-color-light-orange, 0.1); + } + + &::placeholder { + color: $theme-color-grey57; + font-size: 0.95rem; + } + } + + .verification-message { + font-size: 0.85rem; + color: $theme-color-grey57; + margin-top: 0.5rem; + margin-bottom: 0.5rem; + display: flex; + align-items: center; + gap: 0.5rem; + + &::before { + content: "ⓘ"; + color: $theme-color-light-orange; + } + } + + .timer-container { margin-top: 1rem; - padding: 0.75rem; + text-align: center; + font-size: 0.9rem; + color: $theme-color-grey39; + + .resend-button { + background: none; + border: none; + color: $theme-color-light-orange; + cursor: pointer; + font-size: 0.9rem; + padding: 0; + text-decoration: underline; + + &:hover { + color: darken($theme-color-light-orange, 10%); + } + } + } + } + + .email-input-container, + .otp-input-container { + position: relative; + display: flex; + align-items: center; + gap: 1rem; + margin-top: 0.5rem; + max-width: 330px; + + .inputField { + flex: 1; + height: 12px; + margin-top: 0; + padding: 0.75rem 1rem; border: 1px solid $theme-color-light-grey-d7; border-radius: 4px; - font-size: 1rem; + font-size: 0.95rem; + transition: all 0.2s ease; &:focus { outline: none; border-color: $theme-color-light-orange; + box-shadow: 0 0 0 2px rgba($theme-color-light-orange, 0.1); + } + + &:read-only { + background-color: $theme-color-light-grey-d7; + cursor: not-allowed; } } - .verify-button { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); + .otp-button { + min-width: 110px; + height: 42px; background: $theme-color-light-orange; color: $theme-color-white; border: none; - padding: 0.5rem 1rem; border-radius: 4px; + font-size: 0.95rem; + font-weight: 500; cursor: pointer; + transition: all 0.3s ease; + + &:hover:not(:disabled) { + background: darken($theme-color-light-orange, 5%); + transform: translateY(-1px); + } &:disabled { background: $theme-color-light-grey-d7; cursor: not-allowed; } - } - .verification-message { - margin-bottom: 1rem; - color: $theme-color-grey39; + &.verify { + background: $theme-color-white; + border: 1px solid $theme-color-light-orange; + color: $theme-color-light-orange; + + &:hover:not(:disabled) { + background: rgba($theme-color-light-orange, 0.1); + } + + &:disabled { + border-color: $theme-color-light-grey-d7; + color: $theme-color-light-grey-d7; + background: $theme-color-white; + } + } } } @@ -109,21 +210,78 @@ .form-actions { margin-top: 2rem; + max-width: 320px; - .next-arrow { - display: flex; + .next-button { + display: inline-flex; align-items: center; - gap: 0.5rem; + padding: 0.3rem 1rem; + background: $theme-color-white; + border: 1px solid $theme-color-light-orange; + border-radius: 4px; cursor: pointer; - color: $theme-color-light-orange; + transition: all 0.3s ease; + + .next-text { + margin-right: 0.75rem; + color: $theme-color-light-orange; + font-weight: 500; + font-size: 0.95rem; + } + + svg { + color: $theme-color-light-orange; + transition: transform 0.3s ease; + } + + &:hover { + box-shadow: 0 4px 12px rgba($theme-color-light-orange, 0.2); + transform: translateY(-2px); + + svg { + transform: translateX(4px); + } + } &.disabled { - color: $theme-color-light-grey-d7; + opacity: 0.6; cursor: not-allowed; + &:hover { + box-shadow: none; + transform: none; + svg { + transform: none; + } + } } + } + } - .next-text { - font-weight: 500; + .register-button-container { + margin-top: 2rem; + width: 100%; + max-width: 320px; + + .register-button { + width: 100%; + height: 42px; + font-size: 0.95rem; + font-weight: 500; + background: #f2f2f2; + color: $theme-color-grey39; + border: none; + border-radius: 4px; + cursor: pointer; + transition: all 0.3s ease; + + &:hover:not(:disabled) { + background: darken(#f2f2f2, 5%); + } + + &:disabled { + background: #f2f2f2; + color: $theme-color-grey57; + cursor: not-allowed; } } } diff --git a/src/views/Login.vue b/src/views/Login.vue index 9329883..ca05875 100644 --- a/src/views/Login.vue +++ b/src/views/Login.vue @@ -36,8 +36,10 @@ text="Login" />
- -
+
+ +
+
diff --git a/src/views/Register.vue b/src/views/Register.vue index 11d1e72..39c3ee8 100644 --- a/src/views/Register.vue +++ b/src/views/Register.vue @@ -2,7 +2,7 @@
- Register for n00b + Register for play
@@ -10,82 +10,190 @@
- -
-
- -
- -
{{ UsernameErr }}
-
-
-
-
- Next - - - -
-
-
- -
-
Setup Email
- - +
{{ EmailErr }}
+
Please enter the OTP sent to {{ email }}
- - +
+ + +
{{ OtpErr }}
Resend OTP in {{ timer }} seconds - + +
+
+ +
+
+ Next + + + +
+
+
+ +
+
Basic Info
+
+ +
+
+ +
+ +
{{ UsernameErr }}
+
+
+
+
+ Next + + +
- +
{{ PassLen }}
- +
{{ PassErr }}
-
-
+
1
-
Basic Info
+
Setup Email
-
+
2
-
Setup Email
+
Basic Info
@@ -94,7 +202,6 @@
-
@@ -104,46 +211,46 @@ From 60d945c0a461c5957ae9537edc7e735622610232 Mon Sep 17 00:00:00 2001 From: SomyaChawla0250 Date: Wed, 5 Feb 2025 20:21:32 +0530 Subject: [PATCH 10/10] integrated OTP --- src/api/admin/otpAPI.js | 31 +++++++++++++++++++++++++++++++ src/views/Register.vue | 28 +++++++++++++++++++--------- 2 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 src/api/admin/otpAPI.js diff --git a/src/api/admin/otpAPI.js b/src/api/admin/otpAPI.js new file mode 100644 index 0000000..d3170ca --- /dev/null +++ b/src/api/admin/otpAPI.js @@ -0,0 +1,31 @@ +import axiosInstance from "../axiosInstance.js"; + +export default { + async sendOTP(email) { + let bodyFormData = new FormData(); + bodyFormData.append("email", email); + + const response = await axiosInstance({ + method: "post", + url: `/auth/send-otp`, + data: bodyFormData + }); + + return response; +}, + +async verifyOTP(email,otp) { + let bodyFormData = new FormData(); + bodyFormData.append("email", email); + bodyFormData.append("otp", otp); + + const response = await axiosInstance({ + method: "post", + url: `/auth/verify-otp`, + data: bodyFormData + }); + + return response; +} + +}; diff --git a/src/views/Register.vue b/src/views/Register.vue index 39c3ee8..fc70974 100644 --- a/src/views/Register.vue +++ b/src/views/Register.vue @@ -212,6 +212,7 @@