Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Button } from "@/components/ui/button"
import { UTCDate } from "@date-fns/utc"
import type { JSX } from "react"
import { Link } from "src/i18n/navigation"
import DeveloperStats from "src/components/user/DeveloperStats"

type DocusaurusFeed = {
version: string
Expand Down Expand Up @@ -131,7 +132,13 @@ const News = ({ feed }: { feed: DocusaurusFeed }) => {
)
}

const DeveloperApps = ({ locale }: { locale: string }) => {
const DeveloperApps = ({
locale,
topContent,
}: {
locale: string
topContent?: JSX.Element
}) => {
const t = useTranslations()
const user = useUserContext()

Expand All @@ -143,6 +150,7 @@ const DeveloperApps = ({ locale }: { locale: string }) => {
<UserApps
locale={locale}
variant="dev"
topContent={topContent}
customButtons={
(!IS_PRODUCTION ||
user.info?.permissions.some((a) => a === Permission.moderation)) && (
Expand Down Expand Up @@ -182,7 +190,7 @@ const DeveloperPortalClient = ({
</h1>
<div className="space-y-12 w-full">
<News feed={feed} />
<DeveloperApps locale={locale} />
<DeveloperApps locale={locale} topContent={<DeveloperStats />} />
<InviteCode locale={locale} />
<AcceptingPayment />
</div>
Expand Down
5 changes: 4 additions & 1 deletion frontend/public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,9 @@
},
"developer-portal": {
"title": "Developer Portal",
"runtime-version": "Runtime: {version}"
"runtime-version": "Runtime: {version}",
"downloads-last-week": "Downloads (last 7 days)",
"no-downloads-last-week": "No downloads last week",
"some-downloads-last-week": "Nice — great work this week!"
}
}
3 changes: 3 additions & 0 deletions frontend/src/components/application/Collection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface Props {
showId?: boolean
showRuntime?: boolean
customButtons?: JSX.Element
topContent?: JSX.Element
}

const Header = ({
Expand Down Expand Up @@ -69,6 +70,7 @@ const ApplicationCollection: FunctionComponent<Props> = ({
showId = false,
showRuntime = false,
customButtons,
topContent,
}) => {
const t = useTranslations()
const searchParams = useSearchParams()
Expand Down Expand Up @@ -114,6 +116,7 @@ const ApplicationCollection: FunctionComponent<Props> = ({
refresh={refresh}
customButtons={customButtons}
/>
{topContent}

<div className="grid grid-cols-1 justify-around gap-4 md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-3">
{applications.map((app) => (
Expand Down
74 changes: 74 additions & 0 deletions frontend/src/components/user/DeveloperStats.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { FunctionComponent, useMemo } from "react"
import { useTranslations } from "next-intl"
import { useUserContext } from "../../context/user-info"
import { useQueries } from "@tanstack/react-query"
import { getStatsForAppStatsAppIdGet } from "src/codegen/stats/stats"
import { Card, CardContent, CardHeader } from "@/components/ui/card"
import { Skeleton } from "@/components/ui/skeleton"

const DeveloperStats: FunctionComponent = () => {
const t = useTranslations()
const user = useUserContext()

const appIds: string[] = user.info?.dev_flatpaks || []

const queries = useQueries({
queries: appIds.map((id) => ({
queryKey: ["stats", id],
queryFn: () => getStatsForAppStatsAppIdGet(id),
enabled: !!user.info && !!id,
staleTime: 1000 * 60 * 60 * 24, // 1 day
})),
})

const loading = queries.some((q) => q.isFetching || q.isLoading)

const aggregated = useMemo(() => {
const totalLast7 = queries.reduce((acc, q) => {
const v = q.data?.data?.installs_last_7_days ?? 0
return acc + v
}, 0)

return totalLast7
}, [queries])

if (user.loading || !user.info) {
return null
}

if (!appIds || appIds.length === 0) {
return null
}

return loading ? (
<Card>
<CardHeader className="flex items-center flex-col">
<Skeleton className="h-6 w-48" />
</CardHeader>
<CardContent>
<div className="flex flex-col items-center gap-3">
<Skeleton className="h-10 w-36 rounded-md" />
<Skeleton className="h-4 w-56 rounded-md" />
</div>
</CardContent>
</Card>
) : (
<Card className="p-4">
<h3 className="my-4 mt-0 text-xl font-semibold text-center">
{t("developer-portal.downloads-last-week")}
</h3>
<div className="flex flex-col items-center text-center">
<div className="text-3xl font-bold text-flathub-celestial-blue">
{aggregated.toLocaleString()}
</div>
<span className="text-flathub-sonic-silver text-base font-medium mt-1">
{aggregated === 0
? t("developer-portal.no-downloads-last-week")
: t("developer-portal.some-downloads-last-week")}
</span>
</div>
</Card>
)
}

export default DeveloperStats
5 changes: 5 additions & 0 deletions frontend/src/components/user/UserApps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ interface Props {
variant: "dev" | "owned" | "invited"
customButtons?: JSX.Element
locale: string
topContent?: JSX.Element
}

const UserApps: FunctionComponent<Props> = ({
variant,
customButtons,
locale,
topContent,
}) => {
const t = useTranslations()
const user = useUserContext()
Expand Down Expand Up @@ -96,6 +98,9 @@ const UserApps: FunctionComponent<Props> = ({
showId
showRuntime
link={link}
// forward topContent to the underlying Collection component
// so it renders below the title/header
{...(topContent ? { topContent } : {})}
/>
<Pagination currentPage={page} pages={pages} onClick={setPage} />
</>
Expand Down