diff --git a/src/api/admin/hintsAPI.js b/src/api/admin/hintsAPI.js new file mode 100644 index 0000000..cd969d5 --- /dev/null +++ b/src/api/admin/hintsAPI.js @@ -0,0 +1,17 @@ +import axiosInstance from "../axiosInstance.js"; + +export default { + async getHintStatus(hintId) { + return await axiosInstance({ + method: "get", + url: `/api/info/hint/${hintId}` + }); + }, + + async takeHint(hintId) { + return await axiosInstance({ + method: "post", + url: `/api/info/hint/${hintId}` + }); + } +}; 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/api/axiosInstance.js b/src/api/axiosInstance.js index 7b6609d..2f4362b 100644 --- a/src/api/axiosInstance.js +++ b/src/api/axiosInstance.js @@ -32,6 +32,12 @@ axiosInstance.interceptors.response.use( "/auth/register", "/api/submit/challenge" ]; + + // For hint errors, pass through the error response + if (error.response && error.response.config.url.includes("/api/info/hint")) { + return Promise.reject(error); + } + if (!error.response) { router.push("/error/networkerror"); } else if (ignoreErrorPagesPath.includes(error.response.config.url)) { @@ -49,7 +55,6 @@ axiosInstance.interceptors.response.use( default: return error; } - Promise.reject(error); } } diff --git a/src/components/ChallCard.vue b/src/components/ChallCard.vue index 488c671..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 +
@@ -76,7 +79,57 @@
+ +
+ +
+
+ + + + +
@@ -97,13 +150,18 @@ />
+
+ Maximum attempts reached +
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" /> 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/_auth.scss b/src/styles/modules/_auth.scss index de5abb8..831ff21 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,348 @@ .form { width: 35%; - .inputField { + .info { + position: relative; + 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; + 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: 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; + } + } + + .otp-button { + min-width: 110px; + height: 42px; + background: $theme-color-light-orange; + color: $theme-color-white; + border: none; + 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; + } + + &.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; + } + } + } + } + + .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; + max-width: 320px; + + .next-button { + display: inline-flex; + align-items: center; + padding: 0.3rem 1rem; + background: $theme-color-white; + border: 1px solid $theme-color-light-orange; + border-radius: 4px; + cursor: pointer; + 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 { + opacity: 0.6; + cursor: not-allowed; + &:hover { + box-shadow: none; + transform: none; + svg { + transform: none; + } + } + } + } + } + + .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; + } + } + } + + .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/styles/modules/_challCard.scss b/src/styles/modules/_challCard.scss index 257e37c..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 { @@ -294,3 +354,148 @@ 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; + } + } +} + +// 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: 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; + } + } + } + } +} 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 @@
- +
-
@@ -55,13 +55,11 @@ import moment from "moment-timezone"; import LoginUser from "../api/admin/authAPI.js"; export default { name: "home", - components: { - Button - }, + props: ["fetchedData", "configs"], data() { return { - configData: {} + configData: {}, }; }, beforeCreate() { @@ -69,7 +67,7 @@ export default { }, mounted() { if (!this.fetchedData) { - ConfigApiService.getConfigs().then(response => { + ConfigApiService.getConfigs().then((response) => { this.configData = response.data; }); } else { @@ -88,8 +86,8 @@ export default { return moment(time, "HH:mm:ss UTC: Z, DD MMMM YYYY, dddd").format( "HH:mm:ss UTC: Z, Do MMMM YYYY, dddd" ); - } - } + }, + }, }; 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 e697afd..fc70974 100644 --- a/src/views/Register.vue +++ b/src/views/Register.vue @@ -2,188 +2,359 @@
- Register for n00b + Register for play
-
- -
-
- -
- -
{{ this.UsernameErr }}
+
+
+
Setup Email
+ +
+ +
{{ EmailErr }}
+
+
+ +
+
+ Please enter the OTP sent to {{ email }} +
+
+ + +
+
+ +
{{ OtpErr }}
+
+
+ Resend OTP in {{ timer }} seconds + +
+
+ +
+
+ Next + + + +
-
- -
- -
{{ this.EmailErr }}
+ +
+
Basic Info
+
+ +
+
+ +
+ +
{{ UsernameErr }}
+
+
+
+
+ Next + + + +
-
- -
- -
{{ this.PassLen }}
+ +
+
+ +
+ +
{{ PassLen }}
+
+
+
+ +
+ +
{{ PassErr }}
+
+
+
+
-
- -
- -
{{ this.PassErr }}
+ +
+
+
+
1
+
Setup Email
+
+
+
+
2
+
Basic Info
+
+
+
+
3
+
Set Password
+
-
- +
+ +