diff --git a/packages/app/src/api/platformServer/keys.ts b/packages/app/src/api/platformServer/keys.ts index 0e1b559..3329c6a 100644 --- a/packages/app/src/api/platformServer/keys.ts +++ b/packages/app/src/api/platformServer/keys.ts @@ -2,3 +2,7 @@ export const gamemakerKeys = { gameSettings: (id: string) => ['gameSettings', id] as const, aiWeeklyReports: ['aiWeeklyReports'] as const, } + +export const weeklyReportKeys = { + aiWeeklyReports: (date: string) => ['aiWeeklyReports', date] as const, +} diff --git a/packages/app/src/api/platformServer/weeklyReport.platformApi.ts b/packages/app/src/api/platformServer/weeklyReport.platformApi.ts new file mode 100644 index 0000000..e0d78db --- /dev/null +++ b/packages/app/src/api/platformServer/weeklyReport.platformApi.ts @@ -0,0 +1,43 @@ +import { platformApiRequest } from '../request' + +import { PLATFORM_API_URL, useBetterQuery } from '..' +import { weeklyReportKeys } from './keys' +import { useMutation, useQueryClient } from '@tanstack/react-query' + +export const useGetAiWeeklyReport = (startOfWeek: string, email?: string) => { + const queryFn = () => + platformApiRequest({ + url: `${PLATFORM_API_URL}/ai-weekly-report?email=${email}&startOfWeek=${startOfWeek}`, + method: 'GET', + }) + return useBetterQuery({ + queryKey: weeklyReportKeys.aiWeeklyReports(startOfWeek), + queryFn, + enabled: !!email, + }) +} + +export const useGenerateAiWeeklyReport = () => { + const queryClient = useQueryClient() + let startOfWeek: string + const mutationFn = (body: { + email: string + startOfWeek: string + weeklyReport: CodeClimbers.WeeklyScores + }) => { + startOfWeek = body.startOfWeek + return platformApiRequest({ + url: `${PLATFORM_API_URL}/ai-weekly-report`, + method: 'POST', + body, + }) + } + return useMutation({ + mutationFn, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: weeklyReportKeys.aiWeeklyReports(startOfWeek), + }) + }, + }) +} diff --git a/packages/app/src/assets/agi_2048.png b/packages/app/src/assets/agi_2048.png new file mode 100644 index 0000000..7b16a26 Binary files /dev/null and b/packages/app/src/assets/agi_2048.png differ diff --git a/packages/app/src/components/ContributorsPage.tsx b/packages/app/src/components/ContributorsPage.tsx index 9cc6f80..ae518fa 100644 --- a/packages/app/src/components/ContributorsPage.tsx +++ b/packages/app/src/components/ContributorsPage.tsx @@ -3,8 +3,10 @@ import { SimpleInfoCard, SimpleInfoCardProps } from './common/SimpleInfoCard' import Grid2 from '@mui/material/Unstable_Grid2' import { getContributors } from '../services/contributors.service' import { PlainHeader } from './common/PlainHeader' +import { useTheme } from '@mui/material/styles' export const ContributorsPage = () => { + const theme = useTheme() const contributors = getContributors() const contributorCardData: SimpleInfoCardProps[] = contributors.map( (contributor) => ({ @@ -25,7 +27,10 @@ export const ContributorsPage = () => { {contributorCardData.map((contributor) => ( - + ))} diff --git a/packages/app/src/components/Home/DateHeader.tsx b/packages/app/src/components/Home/DateHeader.tsx index e32afbe..9d1ab84 100644 --- a/packages/app/src/components/Home/DateHeader.tsx +++ b/packages/app/src/components/Home/DateHeader.tsx @@ -62,6 +62,7 @@ const DateHeader = ({ title, }: Props) => { const today = dayjs().startOf(period === 'week' ? 'isoWeek' : period) + const [anchorEl, setAnchorEl] = useState(null) const open = Boolean(anchorEl) const navigate = useNavigate() diff --git a/packages/app/src/components/Home/Extensions/ExtensionsWidget.tsx b/packages/app/src/components/Home/Extensions/ExtensionsWidget.tsx index 342cacf..675874a 100644 --- a/packages/app/src/components/Home/Extensions/ExtensionsWidget.tsx +++ b/packages/app/src/components/Home/Extensions/ExtensionsWidget.tsx @@ -101,7 +101,10 @@ export const ExtensionsWidget = () => { {extensionCardData.map((extension) => ( - + ))} diff --git a/packages/app/src/components/Home/Time/Time.tsx b/packages/app/src/components/Home/Time/Time.tsx index bb50ce9..0336db7 100644 --- a/packages/app/src/components/Home/Time/Time.tsx +++ b/packages/app/src/components/Home/Time/Time.tsx @@ -3,38 +3,100 @@ import Grid2 from '@mui/material/Unstable_Grid2/Grid2' import { Dayjs } from 'dayjs' import { BossImage } from '../../common/Icons/BossImage' -import { useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { WeeklyReportDialog } from '../../common/WeeklyReportDialog' import { NotificationIcon } from '../../common/Icons/NotificationIcon' import { DeepWork } from '../DeepWork' import { CategoryChart } from './CategoryChart' import { useGetCurrentUser } from '../../../api/browser/user.api' +import { useBrowserStorage } from '../../../hooks/useBrowserStorage' type Props = { selectedDate: Dayjs } export const Time = ({ selectedDate }: Props) => { const [isWeeklyReportModalOpen, setIsWeeklyReportModalOpen] = useState(false) + const iconRef = useRef(null) const { data: user } = useGetCurrentUser() + const [dismissedInfo, setDismissedInfo] = useBrowserStorage({ + key: 'weekly-report-dismissed', + value: { + dismissed: false, + dismissedAt: null as number | null, + }, + }) + const [iconPosition, setIconPosition] = useState(null) + const updateIconPosition = () => { + if (!iconRef.current) return + const newPosition = iconRef.current.getBoundingClientRect() + setIconPosition(newPosition) + } + useEffect(() => { + updateIconPosition() + // Add resize event listener + window.addEventListener('resize', updateIconPosition) + // Cleanup + return () => { + window.removeEventListener('resize', updateIconPosition) + } + }, [iconRef]) const WeeklyReportSettings = () => { - const showNotificationIcon = user?.weeklyReportType === '' && !user?.email + const showNotification = + (user?.weeklyReportType === '' && !user?.email) || + !dismissedInfo?.dismissed return ( { setIsWeeklyReportModalOpen(true) + setDismissedInfo({ dismissed: true, dismissedAt: Date.now() }) }} > - {showNotificationIcon && ( - + {showNotification && ( + <> + + theme.palette.background.inverted, + '&::after': { + content: '""', + position: 'absolute', + bottom: -6, + right: 10, + width: 0, + height: 0, + borderLeft: '6px solid transparent', + borderRight: '6px solid transparent', + borderTop: (theme) => + `6px solid ${theme.palette.background.inverted}`, + }, + }} + > + theme.palette.text.inverted }} + > + Hey sport! + + + )} ) diff --git a/packages/app/src/components/PerformanceReviewFax.tsx b/packages/app/src/components/PerformanceReviewFax.tsx index 374481b..ddf86f0 100644 --- a/packages/app/src/components/PerformanceReviewFax.tsx +++ b/packages/app/src/components/PerformanceReviewFax.tsx @@ -1,4 +1,4 @@ -import { Box, Divider, Stack } from '@mui/material' +import { Box, Divider, Stack, Typography } from '@mui/material' import { BossImage } from './common/Icons/BossImage' interface Props { @@ -7,24 +7,38 @@ interface Props { export const PerformanceReviewFax = ({ performanceReview }: Props) => { return ( - + theme.palette.common.white, + color: (theme) => theme.palette.common.black, + p: 6, + }} + > - + -
+            
               From: Timothy Brother
-            
-
Subject: Comments on Your Week
+ + + Subject: Comments on Your Week +
-
+      
         {performanceReview}
-      
+
) } diff --git a/packages/app/src/components/WeeklyReports/AiReportHeader.tsx b/packages/app/src/components/WeeklyReports/AiReportHeader.tsx new file mode 100644 index 0000000..dc2c75c --- /dev/null +++ b/packages/app/src/components/WeeklyReports/AiReportHeader.tsx @@ -0,0 +1,136 @@ +import { Box, Stack, Typography } from '@mui/material' +import { BossImage } from '../common/Icons/BossImage' +import { CodeClimbersIconButton } from '../common/CodeClimbersIconButton' +import CloseIcon from '@mui/icons-material/Close' +import { WeeklyReportDialog } from '../common/WeeklyReportDialog' +import { useGetCurrentUser } from '../../api/browser/user.api' +import { useEffect, useState } from 'react' +import { CodeClimbersButton } from '../common/CodeClimbersButton' + +const getLoadingTexts = (): string => { + return 'Contacting your manager... Choosing excuses... Engaging passive-aggressive behavior... Attempting to sabotage your career... Synergizing with your peers... Scheduling a meeting... Reviewing vieled threats... ' +} + +const useLoadingText = (loading?: boolean) => { + const [loadingTextIndex, setLoadingTextIndex] = useState(0) + + const [loadingText, setLoadingText] = useState('') + + useEffect(() => { + if (!loading) { + return + } + const interval = setInterval(() => { + const index = + loadingTextIndex > getLoadingTexts().length ? 0 : loadingTextIndex + setLoadingText((prevText) => prevText + getLoadingTexts()[index]) + setLoadingTextIndex((prevIndex) => { + const newIndex = prevIndex + 1 + return newIndex >= getLoadingTexts().length ? 0 : newIndex + }) + }, 100) + return () => clearInterval(interval) + }, [loadingTextIndex, loadingText, loading]) + return { + loadingText, + } +} + +export const AiReportHeader = (props: { + showCloseButton?: boolean + aiButton: { + text: string + onClick?: () => void + disabled?: boolean + loading?: boolean + } + openWeeklyReportModal?: boolean +}) => { + const { data: user } = useGetCurrentUser() + const [isWeeklyReportModalOpen, setIsWeeklyReportModalOpen] = useState(false) + const { loadingText } = useLoadingText(props.aiButton.loading) + + return ( + theme.palette.common.white, + color: (theme) => theme.palette.common.black, + justifyContent: 'center', + p: 4, + }} + > + + + + + + From: Timothy Brother + + + Subject: Comments on Your Week + + + + {props.aiButton.loading ? ( + + {loadingText} + + ) : ( + { + if (props.openWeeklyReportModal) { + setIsWeeklyReportModalOpen(true) + } + props.aiButton.onClick?.() + }} + > + {props.aiButton.text} + + )} + + {props.showCloseButton && ( + + + + + + )} + {user && isWeeklyReportModalOpen && ( + setIsWeeklyReportModalOpen(false)} + /> + )} + + ) +} diff --git a/packages/app/src/components/WeeklyReports/BigBrotherReview.tsx b/packages/app/src/components/WeeklyReports/BigBrotherReview.tsx new file mode 100644 index 0000000..e8801f3 --- /dev/null +++ b/packages/app/src/components/WeeklyReports/BigBrotherReview.tsx @@ -0,0 +1,94 @@ +import { Box } from '@mui/material' +import { useGetCurrentUser } from '../../api/browser/user.api' +import { useGetLocalServerWeeklyReport } from '../../api/localServer/report.localapi' +import { + useGenerateAiWeeklyReport, + useGetAiWeeklyReport, +} from '../../api/platformServer/weeklyReport.platformApi' +import { useSelectedWeekDate } from '../../hooks/useSelectedDate' +import { PerformanceReviewFax } from '../PerformanceReviewFax' +import { AiReportHeader } from './AiReportHeader' + +export const BigBrotherReview = () => { + const { selectedDate, isCurrentWeek, isMonthAgo } = useSelectedWeekDate() + + const { data: user } = useGetCurrentUser() + const { data: weeklyScores, isPending } = + useGetLocalServerWeeklyReport(selectedDate) + + const { data: aiWeeklyReport, isPending: isLoadingAiWeeklyReport } = + useGetAiWeeklyReport(selectedDate.toISOString(), user?.email) + const { mutate: generateAiWeeklyReport, isPending: isGeneratingReport } = + useGenerateAiWeeklyReport() + + if (isPending || !weeklyScores) { + return
Loading
+ } + const reportOff = user?.weeklyReportType !== 'ai' + const reportMissing = !aiWeeklyReport + + const getReviewContent = () => { + if (isLoadingAiWeeklyReport) { + return ( + + ) + } + if (reportOff) { + return ( + + ) + } + + if (isCurrentWeek()) { + return ( + + ) + } + + if (isMonthAgo()) { + return ( + + ) + } + + if (reportMissing) { + return ( + { + generateAiWeeklyReport({ + email: user?.email, + startOfWeek: selectedDate.toISOString(), + weeklyReport: weeklyScores, + }) + }, + loading: isGeneratingReport, + }} + /> + ) + } + + return + } + + return ( + + {getReviewContent()} + + ) +} diff --git a/packages/app/src/components/WeeklyReports/DeepWorkScore.tsx b/packages/app/src/components/WeeklyReports/DeepWorkScore.tsx index 86b9376..6b10c6d 100644 --- a/packages/app/src/components/WeeklyReports/DeepWorkScore.tsx +++ b/packages/app/src/components/WeeklyReports/DeepWorkScore.tsx @@ -28,6 +28,7 @@ export const DeepWorkScore = ({ deepWorkScore }: Props) => { }, ] + const hasNoDeepWork = data.length === 0 || data[0].data.length === 0 return ( { sx={{ display: 'flex', flexDirection: 'column', - height: '225px', + height: '250px', }} > - {data.length > 0 ? ( + {!hasNoDeepWork ? ( <> {formatMinutes(deepWorkScore.actual)} avg 5 highest days diff --git a/packages/app/src/components/WeeklyReports/GrowthScore.tsx b/packages/app/src/components/WeeklyReports/GrowthScore.tsx index a37ed45..764ec6b 100644 --- a/packages/app/src/components/WeeklyReports/GrowthScore.tsx +++ b/packages/app/src/components/WeeklyReports/GrowthScore.tsx @@ -37,7 +37,7 @@ export const GrowthScore = ({ growthScore }: Props) => { padding: '20px 30px', display: 'flex', flexDirection: 'column', - height: '225px', + height: '250px', }} > {data.length > 0 ? ( diff --git a/packages/app/src/components/WeeklyReports/ProjectScore.tsx b/packages/app/src/components/WeeklyReports/ProjectScore.tsx index 9092235..e56985e 100644 --- a/packages/app/src/components/WeeklyReports/ProjectScore.tsx +++ b/packages/app/src/components/WeeklyReports/ProjectScore.tsx @@ -40,7 +40,7 @@ export const ProjectScore = ({ projectScore }: Props) => { padding: '20px 30px', display: 'flex', flexDirection: 'column', - height: '225px', + height: '250px', }} > {data.length > 0 ? ( diff --git a/packages/app/src/components/WeeklyReports/ReportsPage.tsx b/packages/app/src/components/WeeklyReports/ReportsPage.tsx index 0d07a7b..0bf1530 100644 --- a/packages/app/src/components/WeeklyReports/ReportsPage.tsx +++ b/packages/app/src/components/WeeklyReports/ReportsPage.tsx @@ -1,7 +1,5 @@ -import dayjs from 'dayjs' import { Box, Divider, Stack, Typography } from '@mui/material' import { DateHeader } from '../Home/DateHeader' -import { useState } from 'react' import { ProjectScore } from './ProjectScore' import { GrowthScore } from './GrowthScore' import { DeepWorkScore } from './DeepWorkScore' @@ -10,16 +8,30 @@ import { Logo } from '../common/Logo/Logo' import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined' import { useGetLocalServerWeeklyReport } from '../../api/localServer/report.localapi' import { CodeClimbersLink } from '../common/CodeClimbersLink' +import { useSelectedWeekDate } from '../../hooks/useSelectedDate' +import { BigBrotherReview } from './BigBrotherReview' +import agi2024 from '../../assets/agi_2048.png' +import { posthog } from 'posthog-js' +import { useGetCurrentUser } from '../../api/browser/user.api' +import { LoadingScreen } from '../LoadingScreen' export const ReportsPage = () => { - const [selectedDate, setSelectedDate] = useState( - dayjs().subtract(1, 'week').startOf('isoWeek'), - ) + const { selectedDate, setSelectedDate } = useSelectedWeekDate() + const { data: weeklyScores, isPending } = useGetLocalServerWeeklyReport(selectedDate) - if (isPending || !weeklyScores) { - return
Loading
+ + const { data: user } = useGetCurrentUser() + + if (isPending || !weeklyScores || !user) { + return } + + if (!weeklyScores || !user) { + return
No weekly scores or no user found
+ } + + const reportOff = user?.weeklyReportType !== 'ai' return ( { period={'week'} title="Reports" /> - - + theme.palette.background.paper, - p: 4, + display: 'flex', + gap: 2, + flexDirection: { + xs: 'column', + lg: 'row', + maxWidth: '1200px', + width: '100%', + }, + alignItems: { + xs: 'center', + lg: 'flex-start', + }, + justifyContent: 'center', }} > - - - - - - {weeklyScores.totalScore.score}/10 - - + + theme.palette.background.paper, + p: 4, + }} + > + + + + + + {weeklyScores.totalScore.score}/10 + + theme.palette.text.secondary, + textAlign: 'center', + height: 'auto', + }} + > + {' '} + theme.palette.text.secondary, + textDecoration: 'none', + }} + > + Overall Score + + + + + + + + + + + + {!reportOff && ( + + theme.palette.text.secondary, - textAlign: 'center', - height: 'auto', + position: 'absolute', + top: 0, + left: 0, + width: '100%', + height: '100%', + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + p: 6, + border: '1px solid transparent', + '&:hover': { + cursor: 'pointer', + border: (theme) => + `1px solid ${theme.palette.primary.main}`, + }, + }} + onClick={() => { + window.open( + 'https://codeclimbers.io/blog/big-brother', + '_blank', + ) + posthog.capture('weekly_report_agi_2048_clicked') }} > - {' '} - theme.palette.text.secondary, - textDecoration: 'none', - }} - > - Overall Score - - - + The Year is 2048 +
After AGI nothing was the same
+
+ +
+ )} + + theme.palette.text.primary }} + > + Submit Feedback + +  on this report -
- - - - -
- +
+
) } diff --git a/packages/app/src/components/WeeklyReports/WeeklyBarGraph.tsx b/packages/app/src/components/WeeklyReports/WeeklyBarGraph.tsx index ca94938..0259930 100644 --- a/packages/app/src/components/WeeklyReports/WeeklyBarGraph.tsx +++ b/packages/app/src/components/WeeklyReports/WeeklyBarGraph.tsx @@ -15,7 +15,7 @@ export const WeeklyBarGraph = (props: Props) => { const length = record.name.length return { ...record, - name: length > 13 ? `${record.name.slice(0, 12)}..` : record.name, + name: length > 11 ? `${record.name.slice(0, 10)}..` : record.name, } }) diff --git a/packages/app/src/components/common/SimpleInfoCard.tsx b/packages/app/src/components/common/SimpleInfoCard.tsx index 043527d..ad1fbc1 100644 --- a/packages/app/src/components/common/SimpleInfoCard.tsx +++ b/packages/app/src/components/common/SimpleInfoCard.tsx @@ -10,6 +10,7 @@ export interface SimpleInfoCardProps { subTitle: string callout: string href?: string + backgroundColor?: string } export const SimpleInfoCard = ({ subjectUrl, @@ -17,6 +18,7 @@ export const SimpleInfoCard = ({ subTitle, callout, href, + backgroundColor, }: SimpleInfoCardProps) => { const isGithubUrl = subjectUrl?.includes('github.com') return ( @@ -37,7 +39,8 @@ export const SimpleInfoCard = ({ sx={{ border: '1px solid', borderColor: 'transparent', - backgroundColor: (theme) => theme.palette.background.paper_raised, + backgroundColor: (theme) => + backgroundColor || theme.palette.background.paper_raised, '&:hover': { borderColor: (theme) => theme.palette.primary.main, }, diff --git a/packages/app/src/components/common/WeeklyReportDialog.tsx b/packages/app/src/components/common/WeeklyReportDialog.tsx index 90067cb..aed6d4d 100644 --- a/packages/app/src/components/common/WeeklyReportDialog.tsx +++ b/packages/app/src/components/common/WeeklyReportDialog.tsx @@ -20,6 +20,7 @@ import { } from '../../api/browser/user.api' import { CodeClimbersLink } from './CodeClimbersLink' import { setFeatureEnabled } from '../../services/feature.service' +import { BossImage } from './Icons/BossImage' interface ReportOption { type: CodeClimbers.WeeklyReportType @@ -28,11 +29,11 @@ interface ReportOption { } const ReportOptions: ReportOption[] = [ - // { - // type: 'ai', - // img: () => , - // name: 'Big Brother Edition', - // }, + { + type: 'ai', + img: () => , + name: 'Big Brother Edition', + }, { type: 'standard', img: () => , @@ -112,9 +113,17 @@ export const WeeklyReportDialog = ({ const [reportOption, setReportOption] = useState(user.weeklyReportType) const [email, setEmail] = useState(user.email || '') + const [emailError, setEmailError] = useState('') const handleOptionClick = (option: CodeClimbers.WeeklyReportType) => { setReportOption(option) + if (!user.id) return + updateUserSettings({ + user_id: user.id, + settings: { + weekly_report_type: option, + }, + }) } const handleSave = () => { @@ -134,7 +143,31 @@ export const WeeklyReportDialog = ({ setFeatureEnabled('weekly-report', reportOption) handleClose() } - const hasNotSelectedItems = reportOption === '' || !email + const validateEmail = (email: string) => { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + return emailRegex.test(email) + } + + const handleEmailChange = (e: React.ChangeEvent) => { + const newEmail = e.target.value + setEmail(newEmail) + if (newEmail && !validateEmail(newEmail)) { + setEmailError('Please enter a valid email address') + } else { + setEmailError('') + } + } + + let errorMessage = + 'Choose an option for a weekly email report of your coding stats.' + const emailInvalid = + (reportOption === 'standard' || reportOption === 'ai') && + (!email || !!emailError) + + if (emailInvalid) { + errorMessage = 'Enter an email to be sent your weekly report.' + } + const isInvalid = reportOption === '' || emailInvalid return ( - {hasNotSelectedItems ? ( + {isInvalid ? ( - - Choose an option for a weekly email report of your coding stats. - + {errorMessage} ) : ( setEmail(e.target.value)} + onChange={handleEmailChange} fullWidth + error={!!emailError} + helperText={emailError} inputProps={{ 'data-lpignore': 'true', 'data-form-type': 'other', @@ -224,6 +257,7 @@ export const WeeklyReportDialog = ({ onClick={handleSave} variant="contained" sx={{ ml: 0 }} + disabled={isInvalid} > Save diff --git a/packages/app/src/config/theme.ts b/packages/app/src/config/theme.ts index 20c6551..4541ef8 100644 --- a/packages/app/src/config/theme.ts +++ b/packages/app/src/config/theme.ts @@ -1,6 +1,5 @@ import '@mui/lab/themeAugmentation' import { ThemeOptions, createTheme } from '@mui/material' -import { TypographyOptions } from '@mui/material/styles/createTypography' export interface GraphColors { blue: string @@ -26,13 +25,27 @@ declare module '@mui/material/styles' { paper_raised: string medium: string border: string + inverted: string } interface TypeText { actionDown?: string + inverted?: string + } + interface TypographyVariants { + monospace: React.CSSProperties + body3: React.CSSProperties + } + + interface TypographyVariantsOptions { + monospace?: React.CSSProperties + body3?: React.CSSProperties } } -interface ExtendedTypographyOptions extends TypographyOptions { - body3: React.CSSProperties +declare module '@mui/material/Typography' { + interface TypographyPropsVariantOverrides { + monospace: true + body3: true + } } const typography = { @@ -63,7 +76,7 @@ const typography = { }, h5: { fontFamily: 'Roboto', - textTransform: 'uppercase', + textTransform: 'uppercase' as const, fontSize: '14px', lineHeight: '120%', fontWeight: 500, @@ -89,7 +102,13 @@ const typography = { lineHeight: '16px', fontWeight: 400, }, -} as ExtendedTypographyOptions + monospace: { + fontFamily: 'monospace', + fontSize: '12px', + lineHeight: '16px', + fontWeight: 400, + }, +} const lightGraphColors: GraphColors = { blue: '#3892F3', @@ -127,12 +146,14 @@ const darkOptions: ThemeOptions = { paper_raised: '#323232', medium: '#3F3F3F', border: '#707070', + inverted: '#F8F8F8', }, primary: { main: '#72B7F9', }, text: { actionDown: '#AEDBFE', + inverted: '#222222', }, graphColors: darkGraphColors, // Much better readability in dark modes and accessibility @@ -169,15 +190,17 @@ const lightOptions: ThemeOptions = { main: '#769E68', }, background: { - default: '#F5F5F5', - paper: '#F8F8F8', - paper_raised: '#E6E6E6', + default: '#F6F7F8', + paper: '#FFFFFF', + paper_raised: '#F6F7F8', + inverted: '#323232', }, graphColors: lightGraphColors, text: { primary: '#222222', //default secondary: '#000000', //strong disabled: '#464646', //weak + inverted: '#FAF7F7', }, }, typography, diff --git a/packages/app/src/hooks/useSelectedDate.ts b/packages/app/src/hooks/useSelectedDate.ts index 90bb5e3..a59edfe 100644 --- a/packages/app/src/hooks/useSelectedDate.ts +++ b/packages/app/src/hooks/useSelectedDate.ts @@ -14,4 +14,28 @@ const useSelectedDate = () => { return { selectedDate, setSelectedDate } } -export { useSelectedDate } +const weekAppStore = create<{ + selectedDate: dayjs.Dayjs + setSelectedDate: (date: dayjs.Dayjs) => void + isCurrentWeek: () => boolean + isMonthAgo: () => boolean +}>((set, get) => ({ + selectedDate: dayjs().startOf('week').subtract(1, 'week').add(1, 'day'), + setSelectedDate: (date) => set({ selectedDate: date }), + isCurrentWeek: () => { + const state = get() + return state.selectedDate.isSame(dayjs().startOf('isoWeek'), 'week') + }, + isMonthAgo: () => { + const state = get() + return state.selectedDate.isBefore(dayjs().subtract(1, 'month'), 'day') + }, +})) + +const useSelectedWeekDate = () => { + const { selectedDate, setSelectedDate, isCurrentWeek, isMonthAgo } = + weekAppStore() + return { selectedDate, setSelectedDate, isCurrentWeek, isMonthAgo } +} + +export { useSelectedDate, useSelectedWeekDate } diff --git a/packages/server/src/common/scheduleTask.service.ts b/packages/server/src/common/scheduleTask.service.ts index c8efead..f59ff61 100644 --- a/packages/server/src/common/scheduleTask.service.ts +++ b/packages/server/src/common/scheduleTask.service.ts @@ -35,25 +35,49 @@ export class ScheduledTaskService { } private async sendWeeklyReport() { - const startOfWeek = dayjs().subtract(1, 'week').startOf('isoWeek') + const startOfWeek = dayjs() + .startOf('week') + .subtract(1, 'week') + .add(1, 'day') const scores = await this.reportService.getWeeklyScores(startOfWeek) const user = await this.userService.getCurrentUser() + if (user.weeklyReportType === 'none') return - try { - const response = await axios.post(`${platformUrl}/weekly-report`, { - email: user.email, - weeklyReport: scores, - startOfWeek: startOfWeek.toISOString(), - }) - - Logger.log(`Weekly report sent successfully for user ${user.email}`) - Logger.log(`Response status: ${response.status}`) - } catch (error) { - Logger.error( - `Error sending weekly report for user ${user.email}:`, - error.message, - ) + if (user.weeklyReportType === 'ai') { + try { + const response = await axios.post(`${platformUrl}/ai-weekly-report`, { + email: user.email, + weeklyReport: scores, + startOfWeek: startOfWeek.toISOString(), + }) + + Logger.log(`Weekly report sent successfully for user ${user.email}`) + Logger.log(`Response status: ${response.status}`) + } catch (error) { + Logger.error( + `Error sending weekly report for user ${user.email}:`, + error.message, + ) + } + } + + if (user.weeklyReportType === 'standard') { + try { + const response = await axios.post(`${platformUrl}/weekly-report`, { + email: user.email, + weeklyReport: scores, + startOfWeek: startOfWeek.toISOString(), + }) + + Logger.log(`Weekly report sent successfully for user ${user.email}`) + Logger.log(`Response status: ${response.status}`) + } catch (error) { + Logger.error( + `Error sending weekly report for user ${user.email}:`, + error.message, + ) + } } Logger.log(scores.totalScore)