Skip to content

Commit 41f8a3d

Browse files
jen-cuiarath7
andauthored
207 Welcome Wizard for a first-time user (#213)
Co-authored-by: Ananya Rath <60801863+arath7@users.noreply.github.com> Co-authored-by: Ananya Rath <rath.ananya@gmail.com>
1 parent 3aad407 commit 41f8a3d

11 files changed

Lines changed: 486 additions & 14 deletions

File tree

packages/api/src/models/user.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const userSchema = new Schema<IUser>({
3434
verified: { type: Boolean, required: true, default: false },
3535
favoriteBatteries: [{ type: Schema.Types.ObjectId, ref: "Battery" }],
3636
recentBatteries: [{ type: Schema.Types.ObjectId, ref: "Battery" }],
37+
welcomeWizardStep: { type: Number, required: true, default: 0 },
3738
});
3839

3940
userSchema.methods.verifyPassword = function (password: string) {

packages/api/src/services/auth.service.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,7 @@ export const getUsers = async (): APIResponse<IUser[]> => {
130130
export const updateUser = async (req: any): APIResponse<IUser> => {
131131
const user = req.user;
132132
if (!user) throw new HttpError(401, "Unauthorized");
133-
134-
const { firstName, lastName, email, password } =
133+
const { firstName, lastName, email, password, welcomeWizardStep } =
135134
req.body as Partial<IUser> & {
136135
password?: string;
137136
};
@@ -141,6 +140,8 @@ export const updateUser = async (req: any): APIResponse<IUser> => {
141140
if (typeof email === "string") user.email = email;
142141
if (typeof password === "string" && password.length > 0)
143142
user.password = password;
143+
if (typeof welcomeWizardStep === "number")
144+
user.welcomeWizardStep = welcomeWizardStep;
144145

145146
await user.save();
146147
return [200, user];

packages/shared/types/models/user.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface CreateUser {
1818
favoriteBatteries?: Types.ObjectId[];
1919
recentStudyIds?: string[] | null;
2020
recentBatteries?: Types.ObjectId[];
21+
welcomeWizardStep: number;
2122
}
2223

2324
export interface IUser extends Required<CreateUser> {

packages/ui/public/hearing.svg

Lines changed: 3 additions & 0 deletions
Loading

packages/ui/public/training.svg

Lines changed: 11 additions & 0 deletions
Loading

packages/ui/src/components/AuthForm.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ function submit() {
6868
v-if="!hasPasswordConfirm"
6969
class="px-8"
7070
@click="router.push('/signup')"
71-
>Signup</SecondaryButton
71+
>Sign Up</SecondaryButton
7272
>
7373
</header>
7474
<ElCard

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

Lines changed: 264 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
<script setup lang="ts">
2-
import { ref } from "vue";
2+
import { ref, reactive } from "vue";
33
import { useAuthStore } from "../../stores/auth";
44
import { useRouter } from "vue-router";
55
import MyStudiesItem from "./components/MyStudiesItem.vue";
66
import StudyDetailsSidebar from "./components/StudyDetailsSideBar.vue";
77
import { useQuery, useMutation, useQueryClient } from "@tanstack/vue-query";
8+
import authAPI from "@/api/auth";
89
import studiesAPI from "@/api/studies";
910
import AppButton from "@/components/ui/AppButton.vue";
1011
import RecentStudies from "./components/RecentStudies.vue";
12+
import { ElDialog, ElForm, ElFormItem, ElInput, ElTag } from "element-plus";
13+
import { Plus } from "@element-plus/icons-vue";
1114
1215
const router = useRouter();
1316
const authStore = useAuthStore();
@@ -33,6 +36,18 @@ const { mutate, isLoading } = useMutation({
3336
3437
const selectedStudyId = ref<string | null>(null);
3538
const showSidebar = ref<boolean>(false);
39+
const createStudyPopup = ref(false);
40+
const { data: currentUser } = useQuery({
41+
queryKey: ["user"],
42+
queryFn: authAPI.getCurrentUser,
43+
});
44+
45+
const tagsIcon = {
46+
Cognitive: "/brain.svg",
47+
Hearing: "/hearing.svg",
48+
Training: "/training.svg",
49+
Vision: "/vision.svg",
50+
};
3651
3752
const openSidebar = (studyId: string) => {
3853
selectedStudyId.value = studyId;
@@ -43,8 +58,40 @@ const handleStudyDeleted = async () => {
4358
await refetch();
4459
await queryClient.invalidateQueries({ queryKey: ["studies", "recent"] });
4560
};
46-
</script>
4761
62+
const firstStudyForm = reactive({
63+
title: "",
64+
description: "",
65+
tags: [] as string[],
66+
serverCode: "",
67+
});
68+
69+
const createFirstStudy = async () => {
70+
createStudyPopup.value = false;
71+
const createdStudyId = await studiesAPI.createStudy();
72+
const study = await studiesAPI.getStudy(createdStudyId);
73+
await studiesAPI.saveStudy(createdStudyId, {
74+
...study,
75+
name: firstStudyForm.title,
76+
description: firstStudyForm.description,
77+
variants: study.variants.map((variant, index) =>
78+
index === 0
79+
? {
80+
...variant,
81+
serverCode: firstStudyForm.serverCode,
82+
tags: firstStudyForm.tags,
83+
}
84+
: variant
85+
),
86+
});
87+
router.push({ name: "study", params: { id: createdStudyId } });
88+
};
89+
90+
const updateWizardSteps = async (step: number) => {
91+
await authAPI.updateCurrentUser({ welcomeWizardStep: step });
92+
await queryClient.invalidateQueries(["user"]);
93+
};
94+
</script>
4895
<template>
4996
<div
5097
v-loading="isLoading"
@@ -53,8 +100,203 @@ const handleStudyDeleted = async () => {
53100
<div class="flex items-center">
54101
<h1 class="text-3xl font-bold mb-4 -mt-4">My Studies</h1>
55102
<div class="flex-1"></div>
56-
<AppButton class="mb-4 -mt-4" @click="mutate">+ New</AppButton>
103+
<el-popover
104+
title="Create a new Study"
105+
placement="left-start"
106+
width="308px"
107+
popper-style="border-radius: 10px; z-index: 1500"
108+
:hide-after="0"
109+
:visible="currentUser?.welcomeWizardStep == 0"
110+
trigger="manual"
111+
>
112+
<template #default>
113+
Press the “New” button to create a new study
114+
<div
115+
style="
116+
display: flex;
117+
justify-content: space-between;
118+
align-items: center;
119+
margin-top: 8px;
120+
"
121+
>
122+
<el-button
123+
class="close-tip"
124+
style="
125+
width: 59px;
126+
height: 36px;
127+
border-radius: 10px;
128+
border-width: 1px;
129+
border-color: #c3bcb5;
130+
background-color: #fffdfd;
131+
font-size: 14px;
132+
font-weight: 700;
133+
"
134+
@click="
135+
updateWizardSteps((currentUser?.welcomeWizardStep ?? 0) + 1)
136+
"
137+
>Close</el-button
138+
>
139+
<div style="font-weight: 500; font-size: 14px">1/3</div>
140+
<el-button
141+
type="text"
142+
style="font-size: 14px; font-weight: 500; color: #8a7f75"
143+
@click="updateWizardSteps(3)"
144+
>Skip all tips</el-button
145+
>
146+
</div>
147+
</template>
148+
<template #reference>
149+
<AppButton
150+
class="mb-4 -mt-4"
151+
@click="
152+
if (currentUser?.welcomeWizardStep == 1) createStudyPopup = true;
153+
else mutate();
154+
"
155+
>+ New</AppButton
156+
>
157+
</template>
158+
</el-popover>
159+
<div
160+
v-if="currentUser?.welcomeWizardStep == 0"
161+
style="position: fixed; inset: 0; background: rgba(0, 0, 0, 0.5)"
162+
></div>
57163
</div>
164+
<ElDialog
165+
v-model="createStudyPopup"
166+
width="545px"
167+
style="border-radius: 30px; background-color: #fffdfd"
168+
>
169+
<ElForm
170+
:model="firstStudyForm"
171+
label-position="top"
172+
style="margin-right: 30px; margin-left: 30px"
173+
>
174+
<ElFormItem label="Title">
175+
<template #label>
176+
<span
177+
class="font-bold text-base text-black"
178+
style="font-size: 16px"
179+
>
180+
Title
181+
</span>
182+
</template>
183+
<ElInput
184+
v-model="firstStudyForm.title"
185+
style="height: 44px; border-radius: 10px"
186+
/>
187+
</ElFormItem>
188+
<h1
189+
class="inline-block border-b-2 border-red-500 pb-1 mb-4 font-bold text-black"
190+
style="font-size: 14px"
191+
>
192+
Details
193+
</h1>
194+
<ElFormItem label="Description">
195+
<template #label>
196+
<span class="font-bold text-base text-black"> Description </span>
197+
</template>
198+
<ElInput
199+
v-model="firstStudyForm.description"
200+
type="textarea"
201+
style="height: 88px"
202+
:rows="4"
203+
resize="none"
204+
/>
205+
</ElFormItem>
206+
<ElFormItem label="Condition Server Code">
207+
<template #label>
208+
<span class="font-bold text-base text-black">
209+
Condition Server Code
210+
</span>
211+
</template>
212+
<ElInput
213+
v-model="firstStudyForm.serverCode"
214+
style="width: 165px; border-radius: 10px; background-color: #fffdfd"
215+
/>
216+
</ElFormItem>
217+
<ElFormItem label="Tags">
218+
<template #label>
219+
<span class="font-bold text-base text-black"> Tags </span>
220+
</template>
221+
<ElTag
222+
v-for="tag in firstStudyForm.tags"
223+
:key="tag"
224+
closable
225+
style="
226+
margin-right: 4px;
227+
border-color: #ffb9aa;
228+
background-color: #ffede9;
229+
color: #ba3b2a;
230+
"
231+
@close="
232+
firstStudyForm.tags = firstStudyForm.tags.filter((t) => t !== tag)
233+
"
234+
>
235+
<img
236+
:src="tagsIcon[tag as keyof typeof tagsIcon]"
237+
class="inline-block mr-2"
238+
style="width: 12px; height: 12px"
239+
/>{{ tag }}
240+
</ElTag>
241+
<el-dropdown trigger="click">
242+
<el-button
243+
style="
244+
width: 20px;
245+
height: 20px;
246+
padding: 0;
247+
border-radius: 6px;
248+
background-color: #fcf9f7;
249+
"
250+
>
251+
<template #icon>
252+
<Plus />
253+
</template>
254+
</el-button>
255+
<template #dropdown>
256+
<el-dropdown-menu>
257+
<el-dropdown-item
258+
v-for="tag in Object.keys(tagsIcon)"
259+
:key="tag"
260+
>
261+
<el-checkbox
262+
:model-value="firstStudyForm.tags.includes(tag)"
263+
class="tag-checkbox"
264+
@change="
265+
(checked: boolean) => {
266+
if (checked) {
267+
firstStudyForm.tags.push(tag);
268+
} else {
269+
firstStudyForm.tags = firstStudyForm.tags.filter(
270+
(t) => t !== tag
271+
);
272+
}
273+
}
274+
"
275+
>
276+
{{ tag }}
277+
</el-checkbox>
278+
</el-dropdown-item>
279+
</el-dropdown-menu>
280+
</template>
281+
</el-dropdown>
282+
</ElFormItem>
283+
</ElForm>
284+
<template #footer>
285+
<el-button
286+
style="
287+
width: 105px;
288+
height: 36px;
289+
border-radius: 10px;
290+
border-color: #c3bcb5;
291+
font-weight: 700;
292+
"
293+
class="block mx-auto w-fit"
294+
@click="createFirstStudy"
295+
>Create Study</el-button
296+
>
297+
</template>
298+
</ElDialog>
299+
58300
<h1 class="mt-4">Recents</h1>
59301
<RecentStudies />
60302

@@ -102,3 +344,22 @@ const handleStudyDeleted = async () => {
102344
@close="showSidebar = false"
103345
/>
104346
</template>
347+
348+
<style scoped>
349+
:deep(.el-input__wrapper),
350+
:deep(.el-textarea__inner) {
351+
border-radius: 10px !important;
352+
background-color: #fffdfd !important;
353+
}
354+
:deep(.el-tag__close) {
355+
color: #ba3b2a !important;
356+
background-color: transparent !important;
357+
}
358+
:deep(.el-dialog__footer .el-button:hover) {
359+
color: inherit !important;
360+
background-color: #f5f5f5 !important;
361+
}
362+
:deep(.tag-checkbox.is-checked .el-checkbox__label) {
363+
color: inherit !important;
364+
}
365+
</style>

0 commit comments

Comments
 (0)