Skip to content

Commit 29434eb

Browse files
authored
Merge pull request #34 from cherryontech/feat/dashboard
Feat(dashboard): integrate firestore in dashboard, add detox summary / progress overview logic, create task modal button variant, add link to community
2 parents 7c4cdb9 + d45e008 commit 29434eb

10 files changed

Lines changed: 310 additions & 109 deletions

File tree

.prettierignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
dist
2-
node_modules
2+
node_modules
3+
Button.jsx

public/empty.svg

Lines changed: 29 additions & 0 deletions
Loading

src/App.css

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
button:not(.toggle-btn):not(.delete-btn):focus-visible {
1+
button:not(.toggle-btn):focus-visible {
22
outline: solid 4px var(--color-persianblue);
33
outline-offset: 8px;
44
}
5-
button:not(.toggle-btn):not(.delete-btn):hover {
5+
button:not(.toggle-btn):hover {
66
background-color: var(--color-zinc);
77
color: white;
88
cursor: pointer;
99
}
10-
button:not(.toggle-btn):not(.delete-btn):active {
10+
button:not(.toggle-btn):active {
1111
background-color: var(--color-persianblue);
1212
color: white;
1313
}
@@ -18,3 +18,8 @@ button.active {
1818
button.active:hover {
1919
background-color: var(--color-persianblue);
2020
}
21+
/* style for focus for dashboard tile link */
22+
.dash-tile-link:focus-visible {
23+
outline: solid 4px var(--color-persianblue);
24+
outline-offset: 8px;
25+
}

src/components/Button.jsx

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// prettier-ignore-file
12
import { useNavigate } from 'react-router-dom';
23
import { tv } from 'tailwind-variants/lite';
34

@@ -10,39 +11,32 @@ const buttonVariants = tv({
1011
sm: 'h-12 w-[162px] text-base font-semibold rounded-lg lg:h-14 lg:w-[220px] lg:text-xl',
1112
md: 'h-11 w-[218px] text-base font-semibold rounded-lg lg:h-14 lg:w-[286px] lg:text-xl xl:h-16 xl:w-[327px] xl:text-2xl',
1213
lg: 'h-11 w-[345px] text-base font-medium rounded-md lg:w-[501px]',
13-
circ: 'h-11 w-11 text-2xl rounded-full',
14+
xl: 'h-fit w-full md:w-[450px] text-sm sm:text-base font-medium rounded-md lg:w-[501px] 2xl:w-full p-3 justify-start',
15+
circ: 'h-11 w-11 text-2xl rounded-full'
1416
},
1517
color: {
1618
primary: 'bg-eerie text-white border-1 border-eerie',
1719
secondary: 'bg-white text-eerie border-1 border-eerie',
18-
gradient: 'bg-gradient-to-b from-electricgreen to-persianblue text-white',
20+
gradient: 'bg-gradient-to-b from-electricgreen to-persianblue text-white'
1921
},
2022
},
2123
// default button styles if no specified props
2224
defaultVariants: {
2325
size: 'sm',
24-
color: 'primary',
26+
color: 'primary'
2527
},
2628
// conditional style cases for specific prop combinations
2729
compoundVariants: [
2830
// remove drop shadow and lower font weight for user selection button
2931
{
3032
color: 'secondary',
3133
size: 'md',
32-
className: 'drop-shadow-none !font-normal',
34+
className: 'drop-shadow-none !font-normal'
3335
},
3436
],
3537
});
3638

37-
export default function Button({
38-
size,
39-
color,
40-
label,
41-
onClick,
42-
isActive,
43-
to,
44-
...props
45-
}) {
39+
export default function Button({ size, color, label, onClick, isActive, to, title, subtitle, ...props }) {
4640
let navigate = useNavigate();
4741

4842
function handleClick() {
@@ -54,16 +48,18 @@ export default function Button({
5448
}
5549

5650
return (
57-
<button
58-
onClick={handleClick}
59-
className={
60-
isActive
61-
? `${buttonVariants({ size, color })} active`
62-
: buttonVariants({ size, color })
63-
}
64-
{...props}
51+
<button
52+
onClick={handleClick}
53+
className={isActive ? `${buttonVariants({ size, color })} active` : buttonVariants({ size, color })} {...props}
6554
>
66-
{label}
55+
{title && subtitle ? (
56+
<div className='text-left'>
57+
<h3 className='font-semibold'>{title}</h3>
58+
<p>{subtitle}</p>
59+
</div>
60+
) : (
61+
label
62+
)}
6763
</button>
6864
);
6965
}

src/components/Dashboard/DashboardTile.jsx

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { tv } from "tailwind-variants/lite";
2+
import ProgressDay from "./ProgressDay";
23

34
const tileVariants = tv({
45
base: 'font-poppins flex flex-col justify-start py-6 px-4 rounded-lg shadow-[0_4px_15px_rgba(0,0,0,0.25)]',
@@ -15,6 +16,7 @@ const tileVariants = tv({
1516
type: {
1617
regular: '',
1718
data: '',
19+
progress: ''
1820
},
1921
},
2022
});
@@ -30,15 +32,43 @@ const imgVariants = tv({
3032
},
3133
});
3234

33-
export default function DashboardTile({ type, size, span, title, subtitle, imgSource, altText, dataSentence }){
35+
export default function DashboardTile({ type, size, span, title, subtitle, imgSource, altText, dataSentence, progressDays, onClick, onKeyDown }){
3436
return (
35-
<div className={tileVariants({ type, size, span })}>
37+
// if an onClick prop exists, set a cursor pointer, add a role of button, and make it open to tab nav
38+
<div
39+
className={`${tileVariants({ type, size, span })} ${onClick ? 'cursor-pointer dash-tile-link': ''}`}
40+
onClick={onClick}
41+
role={onClick ? 'button': undefined}
42+
tabIndex={onClick ? 0 : undefined}
43+
onKeyDown={onKeyDown}
44+
>
3645
{type == "data" ? (
3746
<div className="h-full flex flex-col gap-8 text-left">
3847
<h2 className="text-lg md:text-[20px] font-medium">{title}</h2>
3948
<p className="text-3xl md:text-4xl">{dataSentence}</p>
4049
<p className="text-base">{subtitle}</p>
4150
</div>
51+
) : type == "progress" ? (
52+
<div className="h-full flex flex-col gap-2 text-center">
53+
<h2 className="text-[20px] font-medium">{title}</h2>
54+
<p className="text-base font-normal">{subtitle}</p>
55+
{ progressDays.length > 0 ? (
56+
/* div for progress map */
57+
<div className="h-full w-full sm:w-2/3 self-center justify-evenly flex flex-col">
58+
{ progressDays.map(day => (
59+
<ProgressDay
60+
// key for react render
61+
key={day}
62+
// pass the day prop so it'll render in component
63+
day={day}
64+
/>
65+
))}
66+
</div>
67+
) : (
68+
// image for empty state - no completed challenges yet
69+
<img src={imgSource} alt={altText} className={imgVariants({ size })}/>
70+
)}
71+
</div>
4272
) : (
4373
<div className="flex flex-col gap-2 text-center">
4474
<h2 className="text-[20px] font-medium">{title}</h2>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { FaCircle, FaCircleCheck } from "react-icons/fa6"
2+
3+
export default function ProgressDay({ day }) {
4+
return (
5+
<div className="w-full flex flex-row justify-between items-center text-xl">
6+
<div className="flex flex-row gap-3 items-center">
7+
<p>Journal</p>
8+
<FaCircle className="text-[8px]"/>
9+
<p>Day {day}</p>
10+
</div>
11+
<FaCircleCheck className="text-green text-xl shrink-0"/>
12+
</div>
13+
)
14+
}

src/components/Dashboard/TaskModal.jsx

Lines changed: 60 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,107 @@
11
import { useEffect, useState } from 'react';
22
import Button from "../Button";
33
import { IoClose } from "react-icons/io5";
4+
import { useNavigate } from 'react-router-dom';
5+
import { db } from '../../firebase';
6+
import { doc, updateDoc } from 'firebase/firestore';
47

5-
export default function TaskModal({ isOpen, onClose}) {
8+
export default function TaskModal({ isOpen, onClose, user}) {
9+
console.log('task modal is open', isOpen);
10+
const navigate = useNavigate();
611
const [selectedTask, setSelectedTask] = useState([]);
712

8-
// log the selectedTask after the array changes to update automatically
9-
useEffect(() => {
13+
// log the selectedTask after the array changes to update automatically
14+
useEffect(() => {
1015
console.log('Updated Tasks:', selectedTask);
1116
}, [selectedTask]);
1217

1318
function toggleTask(challenge) {
1419
// if the clicked challenge is not selectedTask, setSelectedTask
15-
if (!selectedTask.includes(challenge)) {
16-
// log the selection
17-
console.log(`${challenge} button selected!`);
18-
setSelectedTask([...selectedTask, challenge]);
19-
// if the clicked challenge is already in selectedTask, filter it out of selectedTask
20-
} else {
21-
// log the deselection
22-
console.log(`${challenge} button deselected!`);
23-
setSelectedTask(selectedTask.filter(item => item !== challenge));
20+
if (!selectedTask.includes(challenge)) {
21+
// log the selection
22+
console.log(`${challenge} button selected!`);
23+
setSelectedTask([...selectedTask, challenge]);
24+
// if the clicked challenge is already in selectedTask, filter it out of selectedTask
25+
} else {
26+
// log the deselection
27+
console.log(`${challenge} button deselected!`);
28+
setSelectedTask(selectedTask.filter(item => item !== challenge));
29+
}
30+
}
31+
32+
// function to save selected tasks to firestore and local storage
33+
async function saveTasks(){
34+
if (!user){
35+
console.log('User not authenticated yet')
36+
return;
37+
}
38+
const userDocRef = doc(db, "users", user.uid);
39+
try {
40+
await updateDoc(userDocRef, {
41+
challengesSelected: selectedTask
42+
});
43+
44+
console.log("User saved these tasks:", selectedTask);
45+
46+
// then route to the challenges page
47+
navigate("/challenges");
48+
} catch (error) {
49+
console.error("Error while saving user tasks to firestore", error);
50+
console.log("Error sending tasks to database");
51+
}
2452
}
25-
}
2653

2754
if (!isOpen) return null;
2855

2956
return (
30-
<div className="fixed inset-0 flex justify-center items-center bg-gray-400/30 z-20">
31-
<div className="flex flex-col justify-center items-center gap-10 p-8 h-fit rounded-[20px] bg-gradient-to-b from-nyanza to-celeste opacity-100">
57+
<div className="fixed inset-0 flex justify-center items-center bg-gray-400/30 z-20 h-[100vh]" aria-modal="true" role='dialog' aria-labelledby='modal-title'>
58+
<div className="flex flex-col justify-start items-center gap-3 md:gap-6 p-4 lg:p-10 lg:pb-6 max-h-full sm:max-h-[90vh] md:max-h-[95vh] lg:max-h-[100vh] xl:h-fit w-[90vw] md:w-[70vw] lg:w-[55vw] rounded-[20px] bg-gradient-to-b from-nyanza to-celeste opacity-100 overflow-y-auto">
3259
<button onClick={onClose} className='toggle-btn place-self-start active:outline-4 active:outline-persianblue focus-visible:outline-4 focus-visible:outline-persianblue'><IoClose className='text-2xl'/></button>
33-
<h2 className="font-poppins text-2xl font-bold">Detox Challenge Options</h2>
60+
<h2 className="font-poppins text-base lg:text-2xl font-bold" id='modal-title'>Detox Challenge Options</h2>
3461
{/* buttons */}
35-
<div className="flex flex-col gap-4 text-left">
62+
<div className="flex flex-col gap-4 md:gap-3 text-left w-full items-center">
3663
<Button
37-
size="lg"
64+
size="xl"
3865
color="secondary"
39-
label="Practice Saying No: This task helps you set boundaries in & outside the office."
66+
title="Practice Saying No:"
67+
subtitle="This task helps you set boundaries in & outside the office."
4068
onClick={() => toggleTask('Practice Saying No: This task helps you set boundaries in & outside the office.')}
4169
isActive={selectedTask.includes('Practice Saying No: This task helps you set boundaries in & outside the office.')}
4270
/>
4371
<Button
44-
size="lg"
72+
size="xl"
4573
color="secondary"
46-
label="Read Positive Affirmations: Are you struggling with impostor syndrome, anxiety, & self-doubt? Then this task is for you."
74+
title="Read Positive Affirmations:"
75+
subtitle="Are you struggling with impostor syndrome, anxiety, & self-doubt? Then this task is for you."
4776
onClick={() => toggleTask('Read Positive Affirmations: Are you struggling with impostor syndrome, anxiety, & self-doubt? Then this task is for you.')}
4877
isActive={selectedTask.includes('Read Positive Affirmations: Are you struggling with impostor syndrome, anxiety, & self-doubt? Then this task is for you.')}
4978
/>
5079
<Button
51-
size="lg"
80+
size="xl"
5281
color="secondary"
53-
label="Read a Book: When is the last time you sat down and read a good book?"
82+
title="Read a Book:"
83+
subtitle="When is the last time you sat down and read a good book?"
5484
onClick={() => toggleTask('Read a Book: When is the last time you sat down and read a good book?')}
5585
isActive={selectedTask.includes('Read a Book: When is the last time you sat down and read a good book?')}
5686
/>
5787
<Button
58-
size="lg"
88+
size="xl"
5989
color="secondary"
60-
label="Sleep 7 to 9 Hours: Your quality of sleep greatly affects your emotions and ability to process information."
90+
title="Sleep 7 to 9 Hours:"
91+
subtitle="Your quality of sleep greatly affects your emotions and ability to process information."
6192
onClick={() => toggleTask('Sleep 7 to 9 Hours: Your quality of sleep greatly affects your emotions and ability to process information.')}
6293
isActive={selectedTask.includes('Sleep 7 to 9 Hours: Your quality of sleep greatly affects your emotions and ability to process information.')}
6394
/>
6495
<Button
65-
size="lg"
96+
size="xl"
6697
color="secondary"
67-
label="Journal Entry: Respond to journal prompts or share your thoughts and feelings each day."
98+
title="Journal Entry:"
99+
subtitle="Respond to journal prompts or share your thoughts and feelings each day."
68100
onClick={() => toggleTask('Journal Entry: Respond to journal prompts or share your thoughts and feelings each day.')}
69101
isActive={selectedTask.includes('Journal Entry: Respond to journal prompts or share your thoughts and feelings each day.')}
70102
/>
71103
</div>
72-
<Button size="sm" to="/journal" label="Save" />
104+
<div className='shrink-0'><Button size="sm" onClick={saveTasks} label="Save"/></div>
73105
</div>
74106
</div>
75107
);

src/firebase.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Import the functions you need from the SDKs you need
22
import { initializeApp } from 'firebase/app';
33
import { getFirestore } from 'firebase/firestore';
4-
import { getAuth } from 'firebase/auth';
4+
import { browserLocalPersistence, getAuth, setPersistence } from 'firebase/auth';
55

66
// TODO: Add SDKs for Firebase products that you want to use
77
// https://firebase.google.com/docs/web/setup#available-libraries
@@ -19,6 +19,7 @@ const firebaseConfig = {
1919
// Initialize Firebase
2020
const app = initializeApp(firebaseConfig);
2121
const auth = getAuth(app);
22+
setPersistence(auth, browserLocalPersistence);
2223
// Initialize Cloud Firestore and get a reference to the service
2324
const db = getFirestore(app);
2425
export { db, auth };

0 commit comments

Comments
 (0)