Skip to content

Commit

Permalink
Added license check
Browse files Browse the repository at this point in the history
  • Loading branch information
fgatti675 committed Oct 7, 2024
1 parent 88ae21b commit f68251f
Show file tree
Hide file tree
Showing 16 changed files with 76 additions and 42 deletions.
1 change: 1 addition & 0 deletions examples/example_pro/src/FirestoreApp/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ export function App() {
<SnackbarProvider>
<ModeControllerProvider value={modeController}>
<FireCMS
apiKey={import.meta.env.VITE_FIRECMS_API_KEY as string}
navigationController={navigationController}
authController={authController}
entityLinkBuilder={({ entity }) => `https://console.firebase.google.com/project/${firebaseApp.options.projectId}/firestore/data/${entity.path}/${entity.id}`}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
DeleteConfirmationDialog,
ConfirmationDialog,
PluginHomePageActionsProps,
useAuthController,
useSnackbarController
Expand Down Expand Up @@ -76,7 +76,7 @@ export function HomePageEditorCollectionAction({
</IconButton>}
</div>

<DeleteConfirmationDialog
<ConfirmationDialog
open={deleteRequested}
onAccept={deleteCollection}
onCancel={() => setDeleteRequested(false)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import equal from "react-fast-compare"
import { Formex, FormexController, getIn, useCreateFormex } from "@firecms/formex";
import {
DEFAULT_FIELD_CONFIGS,
DeleteConfirmationDialog,
ConfirmationDialog,
getFieldConfig,
getFieldId,
isPropertyBuilder,
Expand Down Expand Up @@ -583,11 +583,11 @@ function PropertyEditFormFields({
</div>

{onDelete &&
<DeleteConfirmationDialog open={deleteDialogOpen}
onAccept={() => onDelete(values?.id, propertyNamespace)}
onCancel={() => setDeleteDialogOpen(false)}
title={<div>Delete this property?</div>}
body={
<ConfirmationDialog open={deleteDialogOpen}
onAccept={() => onDelete(values?.id, propertyNamespace)}
onCancel={() => setDeleteDialogOpen(false)}
title={<div>Delete this property?</div>}
body={
<div> This will <b>not delete any
data</b>, only modify the
collection.</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";
import {
DeleteConfirmationDialog,
ConfirmationDialog,
EntityCollection,
EntityCustomView,
resolveEntityView,
Expand Down Expand Up @@ -211,8 +211,8 @@ export function SubcollectionsEditTab({
<div style={{ height: "52px" }}/>

{subcollectionToDelete &&
<DeleteConfirmationDialog open={Boolean(subcollectionToDelete)}
onAccept={() => {
<ConfirmationDialog open={Boolean(subcollectionToDelete)}
onAccept={() => {
const props = {
id: subcollectionToDelete,
parentCollectionIds: [...(parentCollectionIds ?? []), collection.id]
Expand All @@ -223,20 +223,20 @@ export function SubcollectionsEditTab({
setSubcollections(subcollections?.filter(e => e.id !== subcollectionToDelete))
});
}}
onCancel={() => setSubcollectionToDelete(undefined)}
title={<>Delete this subcollection?</>}
body={<> This will <b>not
onCancel={() => setSubcollectionToDelete(undefined)}
title={<>Delete this subcollection?</>}
body={<> This will <b>not
delete any data</b>, only
the collection in the CMS</>}/>}
{viewToDelete &&
<DeleteConfirmationDialog open={Boolean(viewToDelete)}
onAccept={() => {
<ConfirmationDialog open={Boolean(viewToDelete)}
onAccept={() => {
setFieldValue("entityViews", values.entityViews?.filter(e => e !== viewToDelete));
setViewToDelete(undefined);
}}
onCancel={() => setViewToDelete(undefined)}
title={<>Remove this view?</>}
body={<>This will <b>not
onCancel={() => setViewToDelete(undefined)}
title={<>Remove this view?</>}
body={<>This will <b>not
delete any data</b>, only
the view in the CMS</>}/>}

Expand Down
8 changes: 6 additions & 2 deletions packages/firecms_cloud/src/components/settings/common.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { ProductPrice, Subscription, SubscriptionStatus } from "../../types/subscriptions";
import { ProductPrice, SubscriptionStatus } from "../../types/subscriptions";
import { ProjectSubscriptionPlan } from "../../types/projects";

export function getPriceString(price: ProductPrice) {

if (price.billing_scheme === "tiered") {
return "Tiered pricing"
const firstFlatPrice = price.tiers.find(p => p.flat_amount);
if (firstFlatPrice)
return "Starting at " + formatPrice(firstFlatPrice.flat_amount as number, price.currency);
else
return "Billing in " + price.currency;
}

return formatPrice(price.unit_amount, price.currency) + " user/" + price.interval;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function PlansComparisonDialog({

const navigationController = useNavigationController();
const navigate = useNavigate();
const goToSettings = () => {
const goToSubscriptions = () => {
onClose();
navigate(navigationController.basePath + "/settings");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@ export interface SubscriptionPriceSelectParams {
selectedPrice?: ProductPrice;
setSelectedPrice: (price?: ProductPrice) => void;
largePriceLabel: boolean;
fullWidth?: boolean;
}

export function SubscriptionPriceSelect({
productPrices,
setSelectedPrice,
largePriceLabel,
selectedPrice
selectedPrice,
fullWidth = true
}: SubscriptionPriceSelectParams) {


return (productPrices ?? [])?.length > 1
? <>
<Select
Expand All @@ -25,7 +28,7 @@ export function SubscriptionPriceSelect({
onChange={(e) => {
setSelectedPrice((productPrices ?? []).find(price => price.id === e.target.value));
}}
className={"w-full"}
className={fullWidth ? "w-full" : "w-fit"}
position={"item-aligned"}
// label={"Choose pricing plan"}
renderValue={(value) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function useLicensesForUserController(): LicensesController {
if (!firestore || !userId) return;
const licensesRef = collection(firestore, LICENSES_COLLECTION);

return onSnapshot(query(licensesRef, where("created_by", "==", userId)),
return onSnapshot(query(licensesRef, where("created_by", "==", userId), where("archived", "==", false)),
{
next:
async (snapshot) => {
Expand Down
1 change: 1 addition & 0 deletions packages/firecms_cloud/src/types/pro_license.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { SubscriptionStatus } from "../index";

export type ProLicense = {
id: string;
archived: boolean;
status: SubscriptionStatus;
licensed_users: number;
firebase_project_ids: string[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";

import { Button, Dialog, DialogActions, DialogContent, LoadingButton, Typography } from "@firecms/ui";

export function DeleteConfirmationDialog({
export function ConfirmationDialog({
open,
onAccept,
onCancel,
Expand Down
2 changes: 1 addition & 1 deletion packages/firecms_core/src/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export * from "./NotFoundPage";

export * from "./VirtualTable";
export * from "./ErrorBoundary";
export * from "./DeleteConfirmationDialog";
export * from "./ConfirmationDialog";

export * from "./FireCMSLogo";

Expand Down
21 changes: 15 additions & 6 deletions packages/firecms_core/src/core/FireCMS.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export function FireCMS<UserType extends User, EC extends EntityCollection>(prop
entityViews,
components,
navigationController,
apiKey
} = props;

useLocaleConfig(locale);
Expand Down Expand Up @@ -82,7 +83,15 @@ export function FireCMS<UserType extends User, EC extends EntityCollection>(prop
onAnalyticsEvent
}), []);

const accessResponse = useProjectLog(authController, dataSourceDelegate, plugins);
const accessResponse = useProjectLog({
apiKey,
authController,
dataSourceDelegate,
plugins
});
if (accessResponse?.message) {
console.warn(accessResponse.message);
}

if (navigationController.navigationLoadingError) {
return (
Expand All @@ -106,16 +115,16 @@ export function FireCMS<UserType extends User, EC extends EntityCollection>(prop

if (accessResponse?.blocked) {
return (
<CenteredView maxWidth={"md"} fullScreen={true}>
<Typography variant={"h4"}>
Access blocked
<CenteredView maxWidth={"md"} fullScreen={true} className={"flex flex-col gap-2"}>
<Typography variant={"h4"} gutterBottom>
License needed
</Typography>
<Typography>
This app has been blocked. Please reach out at <a
You need a valid license to use FireCMS PRO. Please reach out at <a
href={"mailto:[email protected]"}>[email protected]</a> for more information.
</Typography>
{accessResponse?.message &&
<Typography>Response from the server: {accessResponse?.message}</Typography>}
<Typography>{accessResponse?.message}</Typography>}
</CenteredView>
);
}
Expand Down
21 changes: 16 additions & 5 deletions packages/firecms_core/src/hooks/useProjectLog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export type AccessResponse = {
message?: string;
}

async function makeRequest(authController: AuthController, dataSourceKey: string, pluginKeys: string[] | undefined) {
async function makeRequest(authController: AuthController, dataSourceKey: string, pluginKeys: string[] | undefined, apiKey?: string): Promise<AccessResponse> {
let idToken: string | null;
try {
idToken = await authController.getAuthToken();
Expand All @@ -25,6 +25,7 @@ async function makeRequest(authController: AuthController, dataSourceKey: string
Authorization: `Basic ${idToken}`
},
body: JSON.stringify({
apiKey,
email: authController.user?.email ?? null,
datasource: dataSourceKey,
plugins: pluginKeys
Expand All @@ -35,16 +36,26 @@ async function makeRequest(authController: AuthController, dataSourceKey: string
});
}

export function useProjectLog(authController: AuthController,
dataSourceDelegate: DataSourceDelegate,
plugins?: FireCMSPlugin<any, any, any>[]): AccessResponse | null {
export interface UseProjectLogParams {
apiKey?: string;
authController: AuthController;
dataSourceDelegate: DataSourceDelegate;
plugins?: FireCMSPlugin<any, any, any>[];
}

export function useProjectLog({
authController,
dataSourceDelegate,
plugins,
apiKey
}: UseProjectLogParams): AccessResponse | null {
const [accessResponse, setAccessResponse] = useState<AccessResponse | null>(null);
const accessedUserRef = useRef<string | null>(null);
const dataSourceKey = dataSourceDelegate.key;
const pluginKeys = plugins?.map(plugin => plugin.key);
useEffect(() => {
if (authController.user && authController.user.uid !== accessedUserRef.current && !authController.initialLoading) {
makeRequest(authController, dataSourceKey, pluginKeys).then(setAccessResponse);
makeRequest(authController, dataSourceKey, pluginKeys, apiKey).then(setAccessResponse);
accessedUserRef.current = authController.user.uid;
}
}, [authController, dataSourceKey, pluginKeys]);
Expand Down
5 changes: 5 additions & 0 deletions packages/firecms_core/src/types/firecms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ export type FireCMSProps<UserType extends User, EC extends EntityCollection> = {
loading: boolean;
}) => React.ReactNode;

/**
* If you have a custom API key, you can use it here.
*/
apiKey?: string;

/**
* Record of custom form fields to be used in the CMS.
* You can use the key to reference the custom field in
Expand Down
4 changes: 2 additions & 2 deletions packages/user_management/src/components/roles/RolesTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
Tooltip,
Typography
} from "@firecms/ui";
import { DeleteConfirmationDialog, Role } from "@firecms/core";
import { ConfirmationDialog, Role } from "@firecms/core";
import { useUserManagement } from "../../hooks";
import { RoleChip } from "./RoleChip";
import { DEFAULT_ROLES } from "./default_roles";
Expand Down Expand Up @@ -115,7 +115,7 @@ export function RolesTable({

</Table>

<DeleteConfirmationDialog
<ConfirmationDialog
open={Boolean(roleToBeDeleted)}
loading={deleteInProgress}
onAccept={() => {
Expand Down
4 changes: 2 additions & 2 deletions packages/user_management/src/components/users/UsersTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as locales from "date-fns/locale";

import {
defaultDateFormat,
DeleteConfirmationDialog, Role,
ConfirmationDialog, Role,
useAuthController,
useCustomizationController, User,
useSnackbarController
Expand Down Expand Up @@ -149,7 +149,7 @@ export function UsersTable({ onUserClicked }: {
</TableBody>
</Table>

<DeleteConfirmationDialog
<ConfirmationDialog
open={Boolean(userToBeDeleted)}
loading={deleteInProgress}
onAccept={() => {
Expand Down

0 comments on commit f68251f

Please sign in to comment.