Sample PRO
Let's go through the code generated by the FireCMS CLI after a PRO project is created. FireCMS is at its core a React library, so the generated code is a React application. The code is structured in a way that you can easily understand and modify it to fit your needs.
Firebase Setup
The first step involves initializing Firebase using the provided configuration. This is necessary for all Firebase-related operations throughout the app.
You can find the firebaseConfig
after creating a new webapp in the Firebase console.
const {
firebaseApp,
firebaseConfigLoading,
configError
} = useInitialiseFirebase({
firebaseConfig
});
This snippet sets up Firebase, checks for loading status, and handles configuration errors.
Data Source and Storage Source
Your users will need to interact with data and files, so you need to set up data and storage sources. FireCMS provides a Firestore delegate and Firebase storage source for these operations.
const firestoreDelegate = useFirestoreDelegate({
firebaseApp
});
const storageSource = useFirebaseStorageSource({
firebaseApp
});
You are free to define your own data source and storage source, but these are the default ones provided by FireCMS. Feel free to reach out to us if you need help setting up your own data source or storage source.
Collection Configuration Plugin
The collection editor plugin allows you to include a UI for editing collection configurations. You can choose where
the config is stored, and pass the configuration to the plugin. We include a controller that saves the configuration
in your Firestore database. The default path is __FIRECMS/config/collections
.
The controller includes a few methods you can use in your own components to manage the collection configuration.
const collectionConfigController = useFirestoreCollectionsConfigController({
firebaseApp
});
You are free to define your collections in code, or use the UI to define them. You can also allow the modification in the UI of the collections defined in code. You can then merge the collections defined in code with the ones defined in the UI.
const collectionsBuilder = useCallback(() => {
// Here we define a sample collection in code.
const collections = [
productsCollection
// Your collections here
];
// You can merge collections defined in the collection editor (UI) with your own collections
return mergeCollections(collections, collectionConfigController.collections ?? []);
}, [collectionConfigController.collections]);
In order to add the collection editor plugin, you need to include it in the list of plugins passed to the FireCMS
component.
const collectionEditorPlugin = useCollectionEditorPlugin({
collectionConfigController
});
Authorization Management
Managing user authentication and permissions is critical for security and proper access control. FireCMS
provides an Authenticator
interface that you can implement to define your own authentication logic.
You can validate the user's access to the main view based on their authentication status and permissions.
const myAuthenticator: Authenticator<FirebaseUserWrapper> = useCallback(async ({
user,
authController
}) => {
if (user?.email?.includes("flanders")) {
// You can throw an error to prevent access
throw Error("Stupid Flanders!");
}
const idTokenResult = await user?.firebaseUser?.getIdTokenResult();
const userIsAdmin = idTokenResult?.claims.admin || user?.email?.endsWith("@firecms.co");
console.log("Allowing access to", user);
// we allow access to every user in this case
return true;
}, []);
const {
authLoading,
canAccessMainView,
notAllowedError
} = useValidateAuthenticator({
authController,
authenticator: myAuthenticator,
dataSourceDelegate: firestoreDelegate,
storageSource
});
(if you use the user management system, you can use the authenticator
provided by the UserManagement
controller. See below)
User Management
FireCMS PRO includes a user management system that allows you to define roles and permissions for users. The
UserManagement
interface provides methods to define roles and permissions, as well as a loading state to manage.
We include a controller that stores user roles and permissions in Firestore. You are free to define your own user
management system.
const userManagement = useBuildUserManagement({
dataSourceDelegate: firestoreDelegate,
});
then build the user management plugin and include it in the list of plugins passed to the FireCMS
component.
const userManagementPlugin = useUserManagementPlugin({ userManagement });
You can delegate all the authentication logic to the user management system, by using the authenticator
provided by
the UserManagement
controller.
const {
authLoading,
canAccessMainView,
notAllowedError
} = useValidateAuthenticator({
authController,
disabled: userManagement.loading,
authenticator: userManagement.authenticator,
dataSourceDelegate: firestoreDelegate,
storageSource
});
The Auth Controller
The AuthController
is the controller in charge of managing authentication. It provides methods to sign in, sign out,
and get the current user. You can also define roles for users. You can access this controller from within your components
using the useAuthController
hook.
const authController: FirebaseAuthController = useFirebaseAuthController({
firebaseApp,
signInOptions: ["google.com", "password"], // you can pick many more options
loading: userManagement.loading,
defineRolesFor: userManagement.defineRolesFor
});
In this case we are hooking the AuthController
to the UserManagement
controller, so we can define roles for users
based on the user management system.
Mode Controller & User Config Persistence
Adjusting UI preferences, like theme mode, enhances user experience.
const modeController = useBuildModeController();
const userConfigPersistence = useBuildLocalConfigurationPersistence();
These controllers enable theme mode toggling and local storage of user preferences.
Navigation Controller
The internal navigation controller manages the app's navigation, leveraging the collections and permissions setup.
Here you can define your collections, views, and admin views.
You can also pass the authController
and dataSourceDelegate
to the NavigationController
. Optionally, you can
define the collection permissions in the UserManagement
controller.
const collectionsBuilder = useCallback(() => {
// Here we define a sample collection in code.
const collections = [
productsCollection
// Your collections here
];
// You can merge collections defined in the collection editor (UI) with your own collections
return mergeCollections(collections, collectionConfigController.collections ?? []);
}, [collectionConfigController.collections]);
// Here you define your custom top-level views
const views: CMSView[] = useMemo(() => ([{
path: "example",
name: "Example CMS view",
view: <ExampleCMSView/>
}]), []);
const navigationController = useBuildNavigationController({
collections: collectionsBuilder,
views,
authController,
dataSourceDelegate: firestoreDelegate,
adminViews: userManagementAdminViews,
collectionPermissions: userManagement.collectionPermissions
});
This controller leverages the built collections and permissions setup to manage navigation efficiently.
Wiring it all up
Once you have all the controllers set up, you can pass them to the
FireCMS
component, along with the plugins you want to use. The FireCMS
component will handle the rest, and
render the main view or the login view based on the user's authentication status.
Note how you can customize the main view based on the user's authentication status and permissions. The default login view is a Firebase login view, but you can define your own login view.
The SideDialogs
component is used to render the lateral dialogs, like the entity detail view.
The NavigationRoutes
component is used to render the main navigation routes. It uses react-router-dom
to handle
the routing, but you are free to replace it with your own routing system.
return (
<SnackbarProvider>
<ModeControllerProvider value={modeController}>
<FireCMS
navigationController={navigationController}
authController={authController}
userConfigPersistence={userConfigPersistence}
dataSourceDelegate={firestoreDelegate}
storageSource={storageSource}
plugins={[dataEnhancementPlugin, importPlugin, exportPlugin, userManagementPlugin, collectionEditorPlugin]}
>
{({
context,
loading
}) => {
let component;
if (loading || authLoading) {
component = <CircularProgressCenter size={"large"}/>;
} else {
if (!canAccessMainView) {
component = (
<FirebaseLoginView
allowSkipLogin={false}
signInOptions={signInOptions}
firebaseApp={firebaseApp}
authController={authController}
notAllowedError={notAllowedError}/>
);
} else {
component = (
<Scaffold autoOpenDrawer={false}>
<AppBar title={"My amazing CMS"}/>
<Drawer/>
<NavigationRoutes/>
<SideDialogs/>
</Scaffold>
);
}
}
return component;
}}
</FireCMS>
</ModeControllerProvider>
</SnackbarProvider>
);
Find more details about the main components in the Main Components section.