From 5d2ebd1a79256ed14815df45a62da465d5de48a8 Mon Sep 17 00:00:00 2001 From: SomyaChawla0250 Date: Wed, 15 Jan 2025 01:32:49 +0530 Subject: [PATCH 1/9] 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 4/9] 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 43ea5fd575776dc049a3834a6b6922bbdb2b0a27 Mon Sep 17 00:00:00 2001 From: sukhman Date: Tue, 4 Feb 2025 16:24:58 +0530 Subject: [PATCH 6/9] Add Dynamic links --- src/components/ChallCard.vue | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/components/ChallCard.vue b/src/components/ChallCard.vue index 5c5a12e..efceb2e 100644 --- a/src/components/ChallCard.vue +++ b/src/components/ChallCard.vue @@ -271,9 +271,18 @@ export default { this.challDetails.category === "service" || this.challDetails.category === "xinetd" ) { - return `nc ${ncurl} ${port}`; + if (this.challDetails.deployedLink == "localhost" || this.challDetails.deployedLink == "") { + return `nc ${ncurl} ${port}`; + } else { + return `nc ${this.challDetails.deployedLink} ${port}`; + } + } + + if (this.challDetails.deployedLink == "localhost" || this.challDetails.deployedLink == "") { + return `${CONFIG.webRoot}:${port}`; + } else { + return `http://${this.challDetails.deployedLink}:${port}`; } - return `${CONFIG.webRoot}:${port}`; }, getStaticUrl(asset) { let url = CONFIG.staticRoot; From 1b18801df74826f7b2e4acff9bfd80ae167b458e Mon Sep 17 00:00:00 2001 From: SomyaChawla0250 Date: Tue, 4 Feb 2025 16:55:27 +0530 Subject: [PATCH 7/9] 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 8/9] 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, 11 Feb 2025 00:26:55 +0530 Subject: [PATCH 9/9] Fix challenge card's input Signed-off-by: ayu-ch --- src/components/ChallCard.vue | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/ChallCard.vue b/src/components/ChallCard.vue index 83d4b78..67189b7 100644 --- a/src/components/ChallCard.vue +++ b/src/components/ChallCard.vue @@ -16,8 +16,8 @@ | {{ challDetails.solves.length }} Solves - - | {{ challDetails.previous_tries }}/{{ challDetails.failSolveLimit }} attempts + + | {{ challDetails.previous_tries }}/{{ challDetails.maxAttemptLimit }} attempts
@@ -129,12 +129,11 @@
-
+
Maximum attempts reached