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 @@
+
+
+
+
+
{{ isHintTaken ? 'Hint' : 'Take Hint?' }}
+
+ Are you sure you want to spend {{ selectedHint.points }} points to view this hint?
+
+
+ {{ hintStates[selectedHint.id] && hintStates[selectedHint.id].description }}
+
+
+
+
+
+
+
+
+
+
+ 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 @@
-
+
-
+ See Challenges
+
-
+ Register Now
+

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