diff --git a/backend/app/controllers/stats_controller.rb b/backend/app/controllers/stats_controller.rb index a25af348..53099f3b 100644 --- a/backend/app/controllers/stats_controller.rb +++ b/backend/app/controllers/stats_controller.rb @@ -8,6 +8,17 @@ def index contribution_amount_from_secondary_donors = SecondaryDonation.sum(:amount) @total_contribution_amount = contribution_amount_from_primary_donor + contribution_amount_from_secondary_donors + @total_redemption_count = redemptions.count + @total_charities_supported = CampaignCharity.distinct.count(:charity_id) + end + + def steps + redemptions = Redemption.includes(:coupon).all + + contribution_amount_from_primary_donor = redemptions.map { |r| r.coupon.denomination }.sum + contribution_amount_from_secondary_donors = SecondaryDonation.sum(:amount) + @total_contribution_amount = contribution_amount_from_primary_donor + contribution_amount_from_secondary_donors + @total_redemption_count = redemptions.count end end diff --git a/backend/app/views/stats/index.json.jbuilder b/backend/app/views/stats/index.json.jbuilder index ea5da4f4..7151cbc4 100644 --- a/backend/app/views/stats/index.json.jbuilder +++ b/backend/app/views/stats/index.json.jbuilder @@ -2,3 +2,4 @@ json.totalContributionAmount @total_contribution_amount json.totalRedemptionCount @total_redemption_count +json.totalCharitiesSupported @total_charities_supported diff --git a/backend/app/views/stats/steps.json.jbuilder b/backend/app/views/stats/steps.json.jbuilder new file mode 100644 index 00000000..ea5da4f4 --- /dev/null +++ b/backend/app/views/stats/steps.json.jbuilder @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +json.totalContributionAmount @total_contribution_amount +json.totalRedemptionCount @total_redemption_count diff --git a/backend/config/routes.rb b/backend/config/routes.rb index da563493..aabffeb0 100644 --- a/backend/config/routes.rb +++ b/backend/config/routes.rb @@ -9,7 +9,11 @@ token_validations: 'auth/token_validations' } - get 'stats', to: 'stats#index' + resources :stats, only: %i[index] do + collection do + get 'steps' + end + end resources :campaigns do collection do diff --git a/frontend/frontendApis/stats.ts b/frontend/frontendApis/stats.ts index d00ad3fd..390331eb 100644 --- a/frontend/frontendApis/stats.ts +++ b/frontend/frontendApis/stats.ts @@ -1,13 +1,17 @@ import { ApiPromise } from '../types/api'; -import { SummaryData } from '../types/summary'; +import { LandingPageStatsData, StepsStatsData } from '../types/summary'; import BaseAPI from './base'; class StatsAPI extends BaseAPI { static STATS_URL = 'stats'; - public getSummaryStats(): ApiPromise { + public getLandingPageStats(): ApiPromise { return this.get(StatsAPI.STATS_URL); } + + public getStepsStats(): ApiPromise { + return this.get(`${StatsAPI.STATS_URL}/steps`); + } } export default StatsAPI; diff --git a/frontend/pages/index.tsx b/frontend/pages/index.tsx index 432e7fe2..8f129e31 100644 --- a/frontend/pages/index.tsx +++ b/frontend/pages/index.tsx @@ -6,7 +6,7 @@ import GitHubIcon from '@mui/icons-material/GitHub'; import InstagramIcon from '@mui/icons-material/Instagram'; import LocalActivityIcon from '@mui/icons-material/LocalActivity'; import VolunteerActivismIcon from '@mui/icons-material/VolunteerActivism'; -import { Avatar, Grid, Tooltip, Typography, useMediaQuery } from '@mui/material'; +import { Avatar, Grid, Skeleton, Tooltip, Typography, useMediaQuery } from '@mui/material'; import { Box, Stack, useTheme } from '@mui/system'; import type { NextPage } from 'next'; import Head from 'next/head'; @@ -14,7 +14,10 @@ import Link from 'next/link'; import { useRouter } from 'next/router'; import { ReactNode } from 'react'; import Typed from 'react-typed'; +import useSWR from 'swr'; import Button from '../components/generic/Button'; +import api from '../frontendApis'; +import StatsAPI from '../frontendApis/stats'; import { callToActionIconAvatarSx, callToActionIconSx, @@ -69,12 +72,14 @@ import { statisticsMainTextSx, statisticsSectionSx, } from '../styles/indexStyles'; +import { LandingPageStatsData } from '../types/summary'; +import { Nullable } from '../types/utils'; import { log } from '../utils/analytics'; import { theme } from '../utils/theme'; import { combineSxProps } from '../utils/types'; interface StatisticItemProps { - statistic: string; + statistic?: Nullable; icon: ReactNode; description: string; } @@ -85,7 +90,11 @@ const StatisticItem = ({ statistic, icon, description }: StatisticItemProps) => {icon} - {statistic} + {statistic ? ( + {statistic} + ) : ( + + )} {description} @@ -155,17 +164,11 @@ const SectionHeader = ({ title, subtitle = [] }: SectionHeaderProps) => ( ); -const statistics: StatisticItemProps[] = [ - { - statistic: '$1600', - icon: , - description: 'Raised for charities', - }, - { statistic: '160', icon: , description: 'Coupons distributed' }, - { statistic: '8', icon: , description: 'Charities supported' }, -]; - const Home: NextPage = () => { + const { data: stats } = useSWR>(StatsAPI.STATS_URL, () => + api.stats.getLandingPageStats().then((r) => r.payload), + ); + const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); const router = useRouter(); @@ -207,6 +210,24 @@ const Home: NextPage = () => { }, ]; + const statistics: StatisticItemProps[] = [ + { + statistic: stats && `$${stats.totalContributionAmount}`, + icon: , + description: 'Raised for charities', + }, + { + statistic: stats?.totalRedemptionCount, + icon: , + description: 'Coupons redeemed', + }, + { + statistic: stats?.totalCharitiesSupported, + icon: , + description: 'Charities supported', + }, + ]; + return ( diff --git a/frontend/pages/stats.tsx b/frontend/pages/stats.tsx index b619c34c..7e17277e 100644 --- a/frontend/pages/stats.tsx +++ b/frontend/pages/stats.tsx @@ -1,19 +1,19 @@ -import { Skeleton, Stack, Typography, Box } from '@mui/material'; +import { Box, Skeleton, Stack, Typography } from '@mui/material'; +import Head from 'next/head'; import useSWR from 'swr'; +import AnimatedNumber from '../components/AnimatedNumber'; +import GlassCard from '../components/GlassCard'; import api from '../frontendApis'; import StatsAPI from '../frontendApis/stats'; -import { rootSx, numberSx, ctaSx, leftSectionSx } from '../styles/statsStyles'; -import { SummaryData } from '../types/summary'; +import { ctaSx, leftSectionSx, numberSx, rootSx } from '../styles/statsStyles'; +import { StepsStatsData } from '../types/summary'; import { Nullable } from '../types/utils'; import { theme } from '../utils/theme'; -import GlassCard from '../components/GlassCard'; -import AnimatedNumber from '../components/AnimatedNumber'; -import Head from 'next/head'; function Stats() { - const { data: stats } = useSWR>( + const { data: stats } = useSWR>( StatsAPI.STATS_URL, - () => api.stats.getSummaryStats().then((r) => r.payload), + () => api.stats.getStepsStats().then((r) => r.payload), { refreshInterval: 5000, refreshWhenHidden: true }, ); @@ -23,7 +23,7 @@ function Stats() { return ( - STePs Statistics + STePS Statistics diff --git a/frontend/types/summary.ts b/frontend/types/summary.ts index 091cb40b..1a195503 100644 --- a/frontend/types/summary.ts +++ b/frontend/types/summary.ts @@ -1,4 +1,10 @@ -export type SummaryData = { +export type LandingPageStatsData = { + totalContributionAmount: number; + totalRedemptionCount: number; + totalCharitiesSupported: number; +}; + +export type StepsStatsData = { totalContributionAmount: number; totalRedemptionCount: number; };