Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/api/src/models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const userSchema = new Schema<IUser>({
verified: { type: Boolean, required: true, default: false },
favoriteBatteries: [{ type: Schema.Types.ObjectId, ref: "Battery" }],
recentBatteries: [{ type: Schema.Types.ObjectId, ref: "Battery" }],
welcomeWizardStep: { type: Number, required: true, default: 0 },
});

userSchema.methods.verifyPassword = function (password: string) {
Expand Down
5 changes: 3 additions & 2 deletions packages/api/src/services/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,7 @@ export const getUsers = async (): APIResponse<IUser[]> => {
export const updateUser = async (req: any): APIResponse<IUser> => {
const user = req.user;
if (!user) throw new HttpError(401, "Unauthorized");

const { firstName, lastName, email, password } =
const { firstName, lastName, email, password, welcomeWizardStep } =
req.body as Partial<IUser> & {
password?: string;
};
Expand All @@ -141,6 +140,8 @@ export const updateUser = async (req: any): APIResponse<IUser> => {
if (typeof email === "string") user.email = email;
if (typeof password === "string" && password.length > 0)
user.password = password;
if (typeof welcomeWizardStep === "number")
user.welcomeWizardStep = welcomeWizardStep;

await user.save();
return [200, user];
Expand Down
1 change: 1 addition & 0 deletions packages/shared/types/models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface CreateUser {
favoriteBatteries?: Types.ObjectId[];
recentStudyIds?: string[] | null;
recentBatteries?: Types.ObjectId[];
welcomeWizardStep: number;
}

export interface IUser extends Required<CreateUser> {
Expand Down
3 changes: 3 additions & 0 deletions packages/ui/public/hearing.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions packages/ui/public/training.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
263 changes: 260 additions & 3 deletions packages/ui/src/pages/MyStudiesPage/MyStudiesPage.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
<script setup lang="ts">
import { ref } from "vue";
import { ref, reactive, computed } from "vue";
import { useAuthStore } from "../../stores/auth";
import { useRouter } from "vue-router";
import MyStudiesItem from "./components/MyStudiesItem.vue";
import StudyDetailsSidebar from "./components/StudyDetailsSideBar.vue";
import { useQuery, useMutation, useQueryClient } from "@tanstack/vue-query";
import authAPI from "@/api/auth";
import studiesAPI from "@/api/studies";
import AppButton from "@/components/ui/AppButton.vue";
import RecentStudies from "./components/RecentStudies.vue";
import { ElDialog, ElForm, ElFormItem, ElInput, ElTag } from "element-plus";
import { Plus } from "@element-plus/icons-vue";
const router = useRouter();
const authStore = useAuthStore();
Expand All @@ -33,6 +36,17 @@
const selectedStudyId = ref<string | null>(null);
const showSidebar = ref<boolean>(false);
const createStudyPopup = ref(false);
const welcomeWizardStep = computed(
() => authStore.currentUser?.welcomeWizardStep ?? 0
);
const tagsIcon = {
Cognitive: "/brain.svg",
Hearing: "/hearing.svg",
Training: "/training.svg",
Vision: "/vision.svg",
};
const openSidebar = (studyId: string) => {
selectedStudyId.value = studyId;
Expand All @@ -43,8 +57,39 @@
await refetch();
await queryClient.invalidateQueries({ queryKey: ["studies", "recent"] });
};
</script>
const firstStudyForm = reactive({
title: "",
description: "",
tags: [] as string[],
serverCode: "",
});
const createFirstStudy = async () => {
const createdStudyId = await studiesAPI.createStudy();
const study = await studiesAPI.getStudy(createdStudyId);
await studiesAPI.saveStudy(createdStudyId, {
...study,
name: firstStudyForm.title,
description: firstStudyForm.description,
variants: study.variants.map((variant, index) =>
index === 0
? {
...variant,
serverCode: firstStudyForm.serverCode,
tags: firstStudyForm.tags,
}
: variant
),
});
router.push({ name: "study", params: { id: createdStudyId } });
};
const updateWizardSteps = (step: number) => {
authAPI.updateCurrentUser({ welcomeWizardStep: step });
if (authStore.currentUser) authStore.currentUser.welcomeWizardStep = step;
};
</script>
<template>
<div
v-loading="isLoading"
Expand All @@ -53,8 +98,201 @@
<div class="flex items-center">
<h1 class="text-3xl font-bold mb-4 -mt-4">My Studies</h1>
<div class="flex-1"></div>
<AppButton class="mb-4 -mt-4" @click="mutate">+ New</AppButton>
<el-popover
title="Create a new Study"
placement="left-start"
width="308px"
popper-style="border-radius: 10px;"
:hide-after="0"
:visible="welcomeWizardStep == 0"
trigger="manual"
>
<template #default>
Press the “New” button to create a new study
<div
style="
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 8px;
"
>
<el-button
class="close-tip"
style="
width: 59px;
height: 36px;
border-radius: 10px;
border-width: 1px;
border-color: #c3bcb5;
background-color: #fffdfd;
font-size: 14px;
font-weight: 700;
"
@click="updateWizardSteps(welcomeWizardStep + 1)"
>Close</el-button
>
<div style="font-weight: 500; font-size: 14px">1/3</div>
<el-button
type="text"
style="font-size: 14px; font-weight: 500; color: #8a7f75"
@click="updateWizardSteps(3)"
>Skip all tips</el-button
>
</div>
</template>
<template #reference>
<AppButton
class="mb-4 -mt-4"
@click="
if (welcomeWizardStep == 1) createStudyPopup = true;
else mutate();
"
>+ New</AppButton
>
</template>
</el-popover>
<div
v-if="welcomeWizardStep == 0"
style="position: fixed; inset: 0; background: rgba(0, 0, 0, 0.5)"
></div>
</div>
<ElDialog
v-model="createStudyPopup"
width="545px"
style="border-radius: 30px; background-color: #fffdfd"
>
<ElForm
:model="firstStudyForm"
label-position="top"
style="margin-right: 30px; margin-left: 30px"
>
<ElFormItem label="Title">
<template #label>
<span
class="font-bold text-base text-black"
style="font-size: 16px"
>
Title
</span>
</template>
<ElInput
v-model="firstStudyForm.title"
style="height: 44px; border-radius: 10px"
/>
</ElFormItem>
<h1
class="inline-block border-b-2 border-red-500 pb-1 mb-4 font-bold text-black"
style="font-size: 14px"
>
Details
</h1>
<ElFormItem label="Description">
<template #label>
<span class="font-bold text-base text-black"> Description </span>
</template>
<ElInput
v-model="firstStudyForm.description"
type="textarea"
style="height: 88px"
:rows="4"
resize="none"
/>
</ElFormItem>
<ElFormItem label="Condition Server Code">
<template #label>
<span class="font-bold text-base text-black">
Condition Server Code
</span>
</template>
<ElInput
v-model="firstStudyForm.serverCode"
style="width: 165px; border-radius: 10px; background-color: #fffdfd"
/>
</ElFormItem>
<ElFormItem label="Tags">
<template #label>
<span class="font-bold text-base text-black"> Tags </span>
</template>
<ElTag
v-for="tag in firstStudyForm.tags"
:key="tag"
closable
style="
margin-right: 4px;
border-color: #ffb9aa;
background-color: #ffede9;
color: #ba3b2a;
"
@close="
firstStudyForm.tags = firstStudyForm.tags.filter((t) => t !== tag)
"
>
<img
:src="tagsIcon[tag as keyof typeof tagsIcon]"
class="inline-block mr-2"
style="width: 12px; height: 12px"
/>{{ tag }}
</ElTag>
<el-dropdown trigger="click">
<el-button
style="
width: 20px;
height: 20px;
padding: 0;
border-radius: 6px;
background-color: #fcf9f7;
"
>
<template #icon>
<Plus />
</template>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="tag in Object.keys(tagsIcon)"
:key="tag"
>
<el-checkbox
:model-value="firstStudyForm.tags.includes(tag)"
@change="
(checked: boolean) => {
if (checked) {
firstStudyForm.tags.push(tag);
} else {
firstStudyForm.tags = firstStudyForm.tags.filter(
(t) => t !== tag
);
}
}
"
class="tag-checkbox"

Check warning on line 270 in packages/ui/src/pages/MyStudiesPage/MyStudiesPage.vue

View workflow job for this annotation

GitHub Actions / ESLint

packages/ui/src/pages/MyStudiesPage/MyStudiesPage.vue#L270

Attribute "class" should go before "@change" (vue/attributes-order)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix the warning here

>
{{ tag }}
</el-checkbox>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</ElFormItem>
</ElForm>
<template #footer>
<el-button
style="
width: 105px;
height: 36px;
border-radius: 10px;
border-color: #c3bcb5;
font-weight: 700;
"
class="block mx-auto w-fit"
@click="createFirstStudy"
>Create Study</el-button
>
</template>
</ElDialog>

<h1 class="mt-4">Recents</h1>
<RecentStudies />

Expand Down Expand Up @@ -98,3 +336,22 @@
@close="showSidebar = false"
/>
</template>

<style scoped>
:deep(.el-input__wrapper),
:deep(.el-textarea__inner) {
border-radius: 10px !important;
background-color: #fffdfd !important;
}
:deep(.el-tag__close) {
color: #ba3b2a !important;
background-color: transparent !important;
}
:deep(.el-dialog__footer .el-button:hover) {
color: inherit !important;
background-color: #f5f5f5 !important;
}
:deep(.tag-checkbox.is-checked .el-checkbox__label) {
color: inherit !important;
}
</style>
Loading