use team redis keys to resolve team gated features
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
'use client';
|
||||
import { Button, Column, Icon, Tab, TabList, TabPanel, Tabs, Text } from '@umami/react-zen';
|
||||
import { type Key, useState } from 'react';
|
||||
import { SessionModal } from '@/app/(main)/websites/[websiteId]/sessions/SessionModal';
|
||||
import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls';
|
||||
import { Panel } from '@/components/common/Panel';
|
||||
import { useMessages, useSubscription } from '@/components/hooks';
|
||||
import { useMessages, useSubscription, useWebsite } from '@/components/hooks';
|
||||
import { Video } from '@/components/icons';
|
||||
import { getItem, setItem } from '@/lib/storage';
|
||||
import { Button, Column, Icon, Tab, TabList, TabPanel, Tabs, Text } from '@umami/react-zen';
|
||||
import { type Key, useState } from 'react';
|
||||
import { ReplayModal } from './ReplayModal';
|
||||
import { ReplaysDataTable } from './ReplaysDataTable';
|
||||
import { SavedReplaysDataTable } from './SavedReplaysDataTable';
|
||||
@@ -15,15 +15,16 @@ const KEY_NAME = 'umami.replays.tab';
|
||||
|
||||
export function ReplaysPage({ websiteId }: { websiteId: string }) {
|
||||
const [tab, setTab] = useState(getItem(KEY_NAME) || 'replays');
|
||||
const website = useWebsite();
|
||||
const { t, labels, messages } = useMessages();
|
||||
const { hasFeature, hasSubscription, cloudMode } = useSubscription();
|
||||
const { hasFeature, cloudMode } = useSubscription(website?.teamId);
|
||||
|
||||
const handleSelect = (value: Key) => {
|
||||
setItem(KEY_NAME, value);
|
||||
setTab(value);
|
||||
};
|
||||
|
||||
if (cloudMode && hasSubscription && !hasFeature('replays')) {
|
||||
if (cloudMode && !hasFeature('replays')) {
|
||||
return (
|
||||
<Column gap="3">
|
||||
<Panel>
|
||||
|
||||
@@ -23,7 +23,7 @@ interface ReplayConfig {
|
||||
export function WebsiteReplaySettings({ websiteId }: { websiteId: string }) {
|
||||
const website = useWebsite();
|
||||
const { t, labels, messages } = useMessages();
|
||||
const { hasFeature, cloudMode } = useSubscription();
|
||||
const { hasFeature, cloudMode } = useSubscription(website?.teamId);
|
||||
const { mutateAsync, touch, toast, isPending } = useUpdateQuery(`/websites/${websiteId}`);
|
||||
const [enabled, setEnabled] = useState(website?.replayEnabled ?? false);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { fetchAccount } from '@/lib/load';
|
||||
import { fetchAccount, fetchTeam } from '@/lib/load';
|
||||
import { parseRequest } from '@/lib/request';
|
||||
import { json } from '@/lib/response';
|
||||
import { getAllUserTeams } from '@/queries/prisma';
|
||||
@@ -20,9 +20,29 @@ export async function POST(request: Request) {
|
||||
user.subscription = {
|
||||
isPro: account.isPro || false,
|
||||
isBusiness: account.isBusiness || false,
|
||||
isNoBilling: account.isNoBilling || false,
|
||||
hasSubscription: account.hasSubscription || false,
|
||||
};
|
||||
}
|
||||
|
||||
const teamsWithSubscription = await Promise.all(
|
||||
teams.map(async (team: any) => {
|
||||
const teamAccount = await fetchTeam(team.id);
|
||||
return {
|
||||
...team,
|
||||
subscription: teamAccount
|
||||
? {
|
||||
isPro: teamAccount.isPro || false,
|
||||
isBusiness: teamAccount.isBusiness || false,
|
||||
isNoBilling: teamAccount.isNoBilling || false,
|
||||
hasSubscription: teamAccount.hasSubscription || false,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
return json({ ...user, teams: teamsWithSubscription });
|
||||
}
|
||||
|
||||
return json({ ...user, teams });
|
||||
|
||||
@@ -4,7 +4,7 @@ import { z } from 'zod';
|
||||
import { secret } from '@/lib/crypto';
|
||||
import { getClientInfo, hasBlockedIp } from '@/lib/detect';
|
||||
import { parseToken } from '@/lib/jwt';
|
||||
import { fetchAccount } from '@/lib/load';
|
||||
import { fetchAccount, fetchTeam } from '@/lib/load';
|
||||
import { parseRequest } from '@/lib/request';
|
||||
import { badRequest, forbidden, json, serverError } from '@/lib/response';
|
||||
import { getWebsite } from '@/queries/prisma';
|
||||
@@ -62,9 +62,13 @@ export async function POST(request: Request) {
|
||||
}
|
||||
|
||||
if (process.env.CLOUD_MODE) {
|
||||
const account = await fetchAccount(website.userId);
|
||||
const account = website.teamId
|
||||
? await fetchTeam(website.teamId)
|
||||
: website.userId
|
||||
? await fetchAccount(website.userId)
|
||||
: null;
|
||||
|
||||
if (!account?.isBusiness) {
|
||||
if (!account?.isBusiness && !account?.isNoBilling) {
|
||||
return forbidden({ message: 'Business subscription required.' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useConfig } from './useConfig';
|
||||
export interface Subscription {
|
||||
isPro: boolean;
|
||||
isBusiness: boolean;
|
||||
isNoBilling: boolean;
|
||||
hasSubscription: boolean;
|
||||
}
|
||||
|
||||
@@ -16,18 +17,24 @@ export type FeatureName = keyof typeof FEATURES;
|
||||
const defaultSubscription: Subscription = {
|
||||
isPro: false,
|
||||
isBusiness: false,
|
||||
isNoBilling: false,
|
||||
hasSubscription: false,
|
||||
};
|
||||
|
||||
export function useSubscription() {
|
||||
export function useSubscription(teamId?: string | null) {
|
||||
const { user } = useApp();
|
||||
const config = useConfig();
|
||||
|
||||
const subscription: Subscription = user?.subscription || defaultSubscription;
|
||||
const ownSubscription: Subscription = user?.subscription || defaultSubscription;
|
||||
const teamSubscription: Subscription | null = teamId
|
||||
? user?.teams?.find((t: any) => t.id === teamId)?.subscription ?? null
|
||||
: null;
|
||||
|
||||
const subscription: Subscription = teamSubscription || ownSubscription;
|
||||
const cloudMode = config?.cloudMode || false;
|
||||
|
||||
function hasFeature(feature: FeatureName): boolean {
|
||||
if (!cloudMode) {
|
||||
if (!cloudMode || subscription.isNoBilling) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,3 +44,9 @@ export async function fetchAccount(userId: string) {
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
export async function fetchTeam(teamId: string) {
|
||||
const team = await redis.client.get(`team:${teamId}`);
|
||||
|
||||
return team;
|
||||
}
|
||||
|
||||
@@ -100,6 +100,13 @@ export async function getAllUserTeams(userId: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function getTeamOwner(teamId: string) {
|
||||
return prisma.client.teamUser.findFirst({
|
||||
where: { teamId, role: ROLES.teamOwner },
|
||||
select: { userId: true },
|
||||
});
|
||||
}
|
||||
|
||||
export async function createTeam(data: Prisma.TeamCreateInput, userId: string): Promise<any> {
|
||||
const { id } = data;
|
||||
const { client, transaction } = prisma;
|
||||
|
||||
Reference in New Issue
Block a user