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.
- Sign in to your Clio Operate instance as an administrator.
- Navigate to the app registrations section in Clio Operate admin.
- 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 |
- 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-coreConfigure 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();Banner and action sheet
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 viewUtilities
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 yourNotLoggedInview to start the sign-in flow. -
SharedoAuth.redirectToLogin()- redirects to/pleaseSignIn(an internal route). Called automatically bySharedoFetchwhen token refresh fails.