mirror of
https://github.com/wasp-lang/open-saas.git
synced 2025-11-21 02:27:07 +01:00
add notification to admin dash if no stats generated yet (#346)
* add notification if no stats generated yet * Update main.wasp * fix app_diff * Update AnalyticsDashboardPage.tsx
This commit is contained in:
@@ -25,8 +25,10 @@
|
|||||||
+ "prettier-plugin-tailwindcss": "0.5.11",
|
+ "prettier-plugin-tailwindcss": "0.5.11",
|
||||||
+ "react": "^18.2.0",
|
+ "react": "^18.2.0",
|
||||||
+ "react-apexcharts": "1.4.1",
|
+ "react-apexcharts": "1.4.1",
|
||||||
|
+ "react-dom": "^18.2.0",
|
||||||
+ "react-hot-toast": "^2.4.1",
|
+ "react-hot-toast": "^2.4.1",
|
||||||
+ "react-icons": "4.11.0",
|
+ "react-icons": "4.11.0",
|
||||||
|
+ "react-router-dom": "^6.26.2",
|
||||||
+ "stripe": "11.15.0",
|
+ "stripe": "11.15.0",
|
||||||
+ "tailwind-merge": "^2.2.1",
|
+ "tailwind-merge": "^2.2.1",
|
||||||
+ "vanilla-cookieconsent": "^3.0.1",
|
+ "vanilla-cookieconsent": "^3.0.1",
|
||||||
@@ -9013,7 +9015,6 @@
|
|||||||
+ "version": "18.3.1",
|
+ "version": "18.3.1",
|
||||||
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||||
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||||
+ "peer": true,
|
|
||||||
+ "dependencies": {
|
+ "dependencies": {
|
||||||
+ "loose-envify": "^1.1.0",
|
+ "loose-envify": "^1.1.0",
|
||||||
+ "scheduler": "^0.23.2"
|
+ "scheduler": "^0.23.2"
|
||||||
@@ -9327,7 +9328,6 @@
|
|||||||
+ "version": "0.23.2",
|
+ "version": "0.23.2",
|
||||||
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||||
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||||
+ "peer": true,
|
|
||||||
+ "dependencies": {
|
+ "dependencies": {
|
||||||
+ "loose-envify": "^1.1.0"
|
+ "loose-envify": "^1.1.0"
|
||||||
+ }
|
+ }
|
||||||
|
|||||||
@@ -7,17 +7,12 @@
|
|||||||
import { useQuery, getDailyStats } from 'wasp/client/operations';
|
import { useQuery, getDailyStats } from 'wasp/client/operations';
|
||||||
import TotalSignupsCard from './TotalSignupsCard';
|
import TotalSignupsCard from './TotalSignupsCard';
|
||||||
import TotalPageViewsCard from './TotalPageViewsCard';
|
import TotalPageViewsCard from './TotalPageViewsCard';
|
||||||
@@ -7,21 +9,68 @@
|
@@ -10,12 +12,54 @@
|
||||||
import RevenueAndProfitChart from './RevenueAndProfitChart';
|
import { useRedirectHomeUnlessUserIsAdmin } from '../../useRedirectHomeUnlessUserIsAdmin';
|
||||||
import SourcesTable from './SourcesTable';
|
|
||||||
import DefaultLayout from '../../layout/DefaultLayout';
|
|
||||||
-import { useRedirectHomeUnlessUserIsAdmin } from '../../useRedirectHomeUnlessUserIsAdmin'
|
|
||||||
+import { useRedirectHomeUnlessUserIsAdmin } from '../../useRedirectHomeUnlessUserIsAdmin';
|
|
||||||
|
|
||||||
const Dashboard = ({ user }: { user: AuthUser }) => {
|
const Dashboard = ({ user }: { user: AuthUser }) => {
|
||||||
- useRedirectHomeUnlessUserIsAdmin({ user })
|
|
||||||
+ const [isDemoInfoVisible, setIsDemoInfoVisible] = useState(false);
|
+ const [isDemoInfoVisible, setIsDemoInfoVisible] = useState(false);
|
||||||
+ useRedirectHomeUnlessUserIsAdmin({ user });
|
useRedirectHomeUnlessUserIsAdmin({ user });
|
||||||
|
|
||||||
const { data: stats, isLoading, error } = useQuery(getDailyStats);
|
const { data: stats, isLoading, error } = useQuery(getDailyStats);
|
||||||
|
|
||||||
@@ -48,7 +43,6 @@
|
|||||||
+
|
+
|
||||||
return (
|
return (
|
||||||
<DefaultLayout user={user}>
|
<DefaultLayout user={user}>
|
||||||
+ {/* Floating Demo Announcement */}
|
|
||||||
+ {isDemoInfoVisible && (
|
+ {isDemoInfoVisible && (
|
||||||
+ <div className='fixed z-999 bottom-0 mb-2 left-1/2 -translate-x-1/2 lg:mb-4 bg-gray-700 rounded-full px-3.5 py-2 text-sm text-white duration-300 ease-in-out hover:bg-gray-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-indigo-600'>
|
+ <div className='fixed z-999 bottom-0 mb-2 left-1/2 -translate-x-1/2 lg:mb-4 bg-gray-700 rounded-full px-3.5 py-2 text-sm text-white duration-300 ease-in-out hover:bg-gray-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-indigo-600'>
|
||||||
+ <div className='px-4 flex flex-row gap-2 items-center my-1'>
|
+ <div className='px-4 flex flex-row gap-2 items-center my-1'>
|
||||||
@@ -65,21 +59,10 @@
|
|||||||
+ </div>
|
+ </div>
|
||||||
+ </div>
|
+ </div>
|
||||||
+ )}
|
+ )}
|
||||||
|
<div className='relative'>
|
||||||
|
<div className={`${!stats ? 'opacity-25' : ''}`}>
|
||||||
<div className='grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6 xl:grid-cols-4 2xl:gap-7.5'>
|
<div className='grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6 xl:grid-cols-4 2xl:gap-7.5'>
|
||||||
<TotalPageViewsCard
|
@@ -36,7 +80,7 @@
|
||||||
totalPageViews={stats?.dailyStats.totalViews}
|
|
||||||
prevDayViewsChangePercent={stats?.dailyStats.prevDayViewsChangePercent}
|
|
||||||
/>
|
|
||||||
- <TotalRevenueCard dailyStats={stats?.dailyStats} weeklyStats={stats?.weeklyStats} isLoading={isLoading} />
|
|
||||||
+ <TotalRevenueCard
|
|
||||||
+ dailyStats={stats?.dailyStats}
|
|
||||||
+ weeklyStats={stats?.weeklyStats}
|
|
||||||
+ isLoading={isLoading}
|
|
||||||
+ />
|
|
||||||
<TotalPayingUsersCard dailyStats={stats?.dailyStats} isLoading={isLoading} />
|
|
||||||
<TotalSignupsCard dailyStats={stats?.dailyStats} isLoading={isLoading} />
|
|
||||||
</div>
|
|
||||||
@@ -30,7 +79,7 @@
|
|
||||||
<RevenueAndProfitChart weeklyStats={stats?.weeklyStats} isLoading={isLoading} />
|
<RevenueAndProfitChart weeklyStats={stats?.weeklyStats} isLoading={isLoading} />
|
||||||
|
|
||||||
<div className='col-span-12 xl:col-span-8'>
|
<div className='col-span-12 xl:col-span-8'>
|
||||||
@@ -87,4 +70,4 @@
|
|||||||
+ <SourcesTable sources={sortedSources} />
|
+ <SourcesTable sources={sortedSources} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DefaultLayout>
|
</div>
|
||||||
|
|||||||
@@ -19,7 +19,13 @@
|
|||||||
</WaspRouterLink>
|
</WaspRouterLink>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex lg:hidden'>
|
<div className='flex lg:hidden'>
|
||||||
@@ -61,8 +60,8 @@
|
@@ -56,13 +55,13 @@
|
||||||
|
</div>
|
||||||
|
<div className='hidden lg:flex lg:gap-x-12'>{renderNavigationItems(navigationItems)}</div>
|
||||||
|
<div className='hidden lg:flex lg:flex-1 gap-3 justify-end items-center'>
|
||||||
|
- <ul className='flex justify-center items-center gap-2 sm:gap-4'>
|
||||||
|
+ <ul className='ml-4 flex justify-center items-center gap-2 sm:gap-4'>
|
||||||
|
<DarkModeSwitcher />
|
||||||
</ul>
|
</ul>
|
||||||
{isUserLoading ? null : !user ? (
|
{isUserLoading ? null : !user ? (
|
||||||
<WaspRouterLink to={routes.LoginRoute.to} className='text-sm font-semibold leading-6 ml-3'>
|
<WaspRouterLink to={routes.LoginRoute.to} className='text-sm font-semibold leading-6 ml-3'>
|
||||||
|
|||||||
@@ -7,21 +7,30 @@ import TotalRevenueCard from './TotalRevenueCard';
|
|||||||
import RevenueAndProfitChart from './RevenueAndProfitChart';
|
import RevenueAndProfitChart from './RevenueAndProfitChart';
|
||||||
import SourcesTable from './SourcesTable';
|
import SourcesTable from './SourcesTable';
|
||||||
import DefaultLayout from '../../layout/DefaultLayout';
|
import DefaultLayout from '../../layout/DefaultLayout';
|
||||||
import { useRedirectHomeUnlessUserIsAdmin } from '../../useRedirectHomeUnlessUserIsAdmin'
|
import { useRedirectHomeUnlessUserIsAdmin } from '../../useRedirectHomeUnlessUserIsAdmin';
|
||||||
|
import { cn } from '../../../client/cn';
|
||||||
|
|
||||||
const Dashboard = ({ user }: { user: AuthUser }) => {
|
const Dashboard = ({ user }: { user: AuthUser }) => {
|
||||||
useRedirectHomeUnlessUserIsAdmin({ user })
|
useRedirectHomeUnlessUserIsAdmin({ user });
|
||||||
|
|
||||||
const { data: stats, isLoading, error } = useQuery(getDailyStats);
|
const { data: stats, isLoading, error } = useQuery(getDailyStats);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DefaultLayout user={user}>
|
<DefaultLayout user={user}>
|
||||||
|
<div className='relative'>
|
||||||
|
<div className={cn({
|
||||||
|
'opacity-25': !stats,
|
||||||
|
})}>
|
||||||
<div className='grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6 xl:grid-cols-4 2xl:gap-7.5'>
|
<div className='grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6 xl:grid-cols-4 2xl:gap-7.5'>
|
||||||
<TotalPageViewsCard
|
<TotalPageViewsCard
|
||||||
totalPageViews={stats?.dailyStats.totalViews}
|
totalPageViews={stats?.dailyStats.totalViews}
|
||||||
prevDayViewsChangePercent={stats?.dailyStats.prevDayViewsChangePercent}
|
prevDayViewsChangePercent={stats?.dailyStats.prevDayViewsChangePercent}
|
||||||
/>
|
/>
|
||||||
<TotalRevenueCard dailyStats={stats?.dailyStats} weeklyStats={stats?.weeklyStats} isLoading={isLoading} />
|
<TotalRevenueCard
|
||||||
|
dailyStats={stats?.dailyStats}
|
||||||
|
weeklyStats={stats?.weeklyStats}
|
||||||
|
isLoading={isLoading}
|
||||||
|
/>
|
||||||
<TotalPayingUsersCard dailyStats={stats?.dailyStats} isLoading={isLoading} />
|
<TotalPayingUsersCard dailyStats={stats?.dailyStats} isLoading={isLoading} />
|
||||||
<TotalSignupsCard dailyStats={stats?.dailyStats} isLoading={isLoading} />
|
<TotalSignupsCard dailyStats={stats?.dailyStats} isLoading={isLoading} />
|
||||||
</div>
|
</div>
|
||||||
@@ -33,6 +42,21 @@ const Dashboard = ({ user }: { user: AuthUser }) => {
|
|||||||
<SourcesTable sources={stats?.dailyStats?.sources} />
|
<SourcesTable sources={stats?.dailyStats?.sources} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!stats && (
|
||||||
|
<div className='absolute inset-0 flex items-start justify-center bg-white/50 dark:bg-boxdark-2/50'>
|
||||||
|
<div className='rounded-lg bg-white p-8 shadow-lg dark:bg-boxdark'>
|
||||||
|
<p className='text-2xl font-bold text-boxdark dark:text-white'>
|
||||||
|
No daily stats generated yet
|
||||||
|
</p>
|
||||||
|
<p className='mt-2 text-sm text-bodydark2'>
|
||||||
|
Stats will appear here once the daily stats job has run
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</DefaultLayout>
|
</DefaultLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ type DailyStatsValues = {
|
|||||||
weeklyStats: DailyStatsWithSources[];
|
weeklyStats: DailyStatsWithSources[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDailyStats: GetDailyStats<void, DailyStatsValues> = async (_args, context) => {
|
export const getDailyStats: GetDailyStats<void, DailyStatsValues | undefined> = async (_args, context) => {
|
||||||
if (!context.user?.isAdmin) {
|
if (!context.user?.isAdmin) {
|
||||||
throw new HttpError(401);
|
throw new HttpError(401);
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,8 @@ export const getDailyStats: GetDailyStats<void, DailyStatsValues> = async (_args
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!dailyStats) {
|
if (!dailyStats) {
|
||||||
throw new HttpError(204, 'No daily stats generated yet.');
|
console.log('\x1b[34mNote: No daily stats have been generated by the dailyStatsJob yet. \x1b[0m');
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const weeklyStats = await context.entities.DailyStats.findMany({
|
const weeklyStats = await context.entities.DailyStats.findMany({
|
||||||
|
|||||||
Reference in New Issue
Block a user