Clio Operate Knowledge Base

Search for answers or browse our knowledge base.
Have you tried asking our AI chatbot for help? It is down in the right-hand corner.

Building a Clio Operate mobile app

Was this article helpful?

Overview

The @sharedo/mobile-core npm package provides the components and services you need to build a Clio Operate mobile progressive web app (PWA) in Vue and Vuetify. It handles authentication, API calls, user profile loading, and common UI patterns, so you can focus on features specific to your app.

The sample app and live examples are useful companions throughout this guide.

Before you start

You need:

  • Node.js and npm installed
  • Experience with Vue.js
  • An active Clio Operate instance with administrator access
  • Your Clio Operate instance URL and identity URL

Register your app in Clio Operate

Before building your app, register it in Clio Operate admin to obtain the credentials the authentication flow requires.

  1. Sign in to your Clio Operate instance as an administrator.
  2. Navigate to the app registrations section in Clio Operate admin.
  3. Create a new app registration with these settings:
Setting Value
Flow type Mobile PWA with local forms-based login
Redirect URL https://localhost:8080/oAuthReply
CORS origin https://localhost:8080
  1. Note the client ID and client secret. You will need them in the next step.

The sample app serves from port 8081 rather than 8080 - pick whichever port matches the dev server you run, and make sure the redirect URL and CORS origin in Clio Operate admin agree.

 

Set up the project

Install the package

Run the following command in your project directory:

npm install @sharedo/mobile-core

Configure settings

Create src/app/settings.js with the values from your Clio Operate app registration:

export default {
 identity: "https://your-identity.sharedo.co.uk",
 api: "https://your-instance.sharedo.co.uk",
 clientId: "your-client-id",
 clientSecret: "your-client-secret",
 redirectUri: "https://localhost:8080/oAuthReply"
};

How you load these values is up to you. The @sharedo/mobile-core package itself just accepts a plain config object - it does not read environment variables or remote configuration on its own.

The sample app demonstrates a common pattern you can borrow: it reads VUE_APP_* environment variables at build time for local development, and reads SHAREDO_* application settings from an /api/settings Azure Functions endpoint when hosted on Azure Static Web Apps, falling back to defaults if neither is available. None of this is built into @sharedo/mobile-core; if you want the same behaviour, replicate it in your own settings module.

Initialise the plugin

In main.js, import the plugin and register it with Vue before mounting your app. The example below follows the pattern used in the sample app:

import Vue from 'vue';
import SharedoMobileCore, {
	useVuetify,
	useTipTapVuetify,
	SharedoAuth,
	SharedoProfile
} from '@sharedo/mobile-core';
import '@sharedo/mobile-core/dist/global.css';
import Main from './views/Main.vue';
import NotLoggedIn from './views/NotLoggedIn.vue';
// Load your settings (see Configure settings above)
settings.get().then(function(config) {
	const vuetify = useVuetify({
		theme: {
			options: {
				customProperties: true
			},
			themes: {
				light: {
					primary: '#27aa5e',
					secondary: '#b0bec5',
					accent: '#8c9eff',
					error: '#b71c1c',
				}
			}
		}
	});
	useTipTapVuetify(vuetify);
	Vue.use(SharedoMobileCore, config);
	const notLoggedIn = () => {
		new Vue({
			vuetify,
			render: h => h(NotLoggedIn)
		}).$mount('#app');
	};
	SharedoAuth.initialise(notLoggedIn).then(() => {
		SharedoProfile.loadProfile().then(() => {
			new Vue({
				vuetify,
				render: h => h(Main)
			}).$mount('#app');
		});
	});
});

SharedoAuth.initialise() checks whether the current URL is the OAuth callback path (/oauthreply). If so, it exchanges the authorisation code for tokens and redirects to /. Otherwise, it attempts to load tokens from localStorage. If valid tokens are found, the returned promise resolves. If no tokens are found, it calls the notLoggedIn callback and the promise does not resolve - the main app never mounts. The NotLoggedIn view is responsible for calling SharedoAuth.reallyRedirectToLogin() to redirect the user to the identity server.

Call useTipTapVuetify(vuetify) after creating the Vuetify instance. This registers the TiptapVuetify plugin, which VRichTextEditor requires to function.

Once authentication succeeds, SharedoProfile.loadProfile() fetches the signed-in user's details from the Clio Operate API.

Use the built-in components

Registering the plugin with Vue.use(SharedoMobileCore, config) makes eight components available globally in any template:

Component Purpose
VTopToolbar Top navigation toolbar
VBottomNav Bottom tab navigation
VBannerSharedo Banner notifications
VTrafficLight Status or priority indicator
VDatetimeTrafficLight Date and time status indicator
VRichTextEditor Rich text editing, powered by tiptap-vuetify
VBtnSection Button section layout helper
VEditableExpansionPanelHeader Editable expansion panel header

See the live examples for rendered demonstrations of each component.

VEditableExpansionPanelHeader is registered globally via Vue.use() and is available in templates, but it is not included in the package's named exports. It cannot be imported individually from @sharedo/mobile-core.

 

Use the framework UI services

The plugin registers a $coreUi property on the Vue prototype. Use this.$coreUi inside any component to access the following services.

Toast

Display a brief notification to the user:

// String shorthand
this.$coreUi.toast('Record saved');
// With options
this.$coreUi.toast({
	message: 'Record saved',
	color: 'success',
	icon: 'mdi-check',
	dismissable: true,
	timeout: 3000
});

Message box

Display a dialogue with configurable action buttons:

this.$coreUi.messageBox({
	title: 'Confirm delete',
	message: 'This action cannot be undone.',
	btns: [{
			text: 'Delete',
			color: 'error',
			handler: () => this.deleteItem()
		},
		{
			text: 'Cancel',
			color: 'default',
			handler: () => {}
		}
	]
});

Dialog

Render any Vue component as a dialogue overlay. Pass props in the second argument and an event handler object in the third:

import SampleForm from './components/SampleForm.vue';
this.$coreUi.dialog(
	SampleForm, {
		reference: 'REF-001',
		title: 'My item'
	}, {
		closing: (result) => {
			console.log('Dialog closed with:', result);
		}
	}
);

Inside the dialogue component, emit close with an optional result to dismiss the overlay:

this.$emit('close', 'saved');

To show a loading indicator while performing async work inside a dialogue, use CoreUi.loading() and dismiss it when the operation is complete:

import { CoreUi } from '@sharedo/mobile-core';
const l = CoreUi.loading();
await saveData();
l.dismiss();
this.$emit('close', 'saved');

Loading

Show a full-screen loading overlay independently of a dialogue:

const l = this.$coreUi.loading();
await performWork();
l.dismiss();

this.$coreUi.banner displays a persistent banner notification. this.$coreUi.actionSheet displays a bottom sheet with a list of options. See the live examples for usage.

Make authenticated API calls

SharedoFetch is an authenticated HTTP wrapper. Import it from the package and call it from your services or components:

import {
	SharedoFetch
} from '@sharedo/mobile-core';
// GET
const data = await SharedoFetch.get(`${config.api}/api/your/endpoint`);
// POST
const result = await SharedoFetch.post(
	`${config.api}/api/your/endpoint`, {
		field: 'value'
	}
);
// PUT
await SharedoFetch.put(`${config.api}/api/your/endpoint`, payload);
// DELETE
await SharedoFetch.delete(`${config.api}/api/your/endpoint`);

SharedoFetch automatically attaches the Bearer token from the active session. If a request returns a 401, it attempts to refresh the token and retries the request. If the refresh fails, SharedoAuth.redirectToLogin() is called, redirecting the user to /pleaseSignIn.

To request a specific response type, pass an options object instead of a plain URL string:

const blob = await SharedoFetch.get({
 url: `${config.api}/api/file/123`,
 responseType: 'blob'
});

Supported responseType values are json (default), text, and blob.

For requests that need a non-JSON body (for example, form-encoded data), SharedoFetch.rawPost(url, body) sends the body untouched while still handling authentication.

Access the user profile

After SharedoProfile.loadProfile() resolves, the profile is available anywhere in the app via the static profile accessor:

import { SharedoProfile } from '@sharedo/mobile-core';
const { userId, name, persona, globalPermissions } = SharedoProfile.profile;

Use globalPermissions to conditionally show or hide features based on what the signed-in user is permitted to do.

Work with the types tree

The types tree represents the Clio Operate type hierarchy. Load it once after authentication and use it to inspect type relationships across your app:

import { SharedoTypesTree } from '@sharedo/mobile-core';
const tree = await SharedoTypesTree.load();
// Check whether a type descends from a given parent
const isTask = tree.isDerivedFrom(workItem.type, ['Task']);
// Check for true descendants only (excludes exact matches)
const isSubTask = tree.isDerivedFrom(workItem.type, ['Task'], true);
// Find a specific type node by system name
const taskType = tree.find('Task');

Add logout

Call SharedoAuth.logout() to sign the user out. This revokes both the access and refresh tokens and clears them from localStorage:

import { SharedoAuth } from '@sharedo/mobile-core';
await SharedoAuth.logout();
// Redirect to your NotLoggedIn view

Utilities

Three utility functions are exported from the package:

Utility Purpose
ContactUrlGenerator Generates a mailto: or tel: URL for a contact record
MapUrlGenerator Generates a map URL for a given address, picking the best provider for the device
debounce Debounces a function call

Both URL generators use a getFor(...) factory followed by .generate():

import { ContactUrlGenerator, MapUrlGenerator, debounce } from '@sharedo/mobile-core';
const contactUrl = ContactUrlGenerator.getFor(contact)?.generate();
const mapUrl = MapUrlGenerator.getFor(location).generate();

ContactUrlGenerator currently recognises email, mobile, and direct-line contact types and returns null for anything else.

Undocumented exports

The following are exported from @sharedo/mobile-core but are not yet documented in this article:

  • useRouter - creates a VueRouter instance
  • useStore - creates a Vuex store instance
  • InstallPrompt - service for handling PWA installation prompts
  • SharedoFetch.rawPost - sends a POST with an untouched body (use when you need form-encoded or pre-serialised payloads)
  • SharedoAuth.reallyRedirectToLogin() - redirects to the identity server's authorisation endpoint. Call this from your NotLoggedIn view to start the sign-in flow.
  • SharedoAuth.redirectToLogin() - redirects to /pleaseSignIn (an internal route). Called automatically by SharedoFetch when token refresh fails.

Was this article helpful?

Related Articles

Related articles in the knowledge base