Let visitors run their own company
Goal
Let the people who sign in to your portal belong to a company — start one, invite colleagues into it, see who is there, change what they may do, and leave — all from a screen you draw.
Before you start
- A portal that asks visitors to sign in. See Ask visitors to sign in.
- Email sending, set up by whoever runs your Parsware installation. An invitation is a message with a link in it; without email nobody can be invited.
Why these are the platform's and not your Custom API
Everything else your portal reads and writes goes through a Custom API you wrote. A company does not, and it is worth knowing why: who belongs to which company is kept by the platform's identity service, which accepts a visitor's session only from the portal's own server. Your Custom API does not run there — it runs where your tables are — so no script you could write would be allowed to invite anybody.
So the platform gives you the six calls, and you draw the screen. There is no People page built into the platform: the member list, the Invite box and the role picker are yours to lay out, the way the rest of your portal is.
The six calls
| Call | What it does |
|---|---|
startCompany(name) | Starts a company with this visitor as its first Owner |
invitePerson(email) / invitePerson(email, role) | Emails an invitation. Owners only. Member when no role is given |
loadCompanyPeople(into) / loadCompanyPeople(into, companyInto) | Puts the people into a state field, and the company into a second one |
changePersonRole(person, role) | Changes what a colleague may do |
removePerson(person) | Removes a colleague, or withdraws an outstanding invitation |
leaveCompany() | This visitor leaves |
The roles are Member, Billing and Owner, and each may do everything the one before it may. Only Owners invite, change roles and remove people; the platform decides that, not your screen.
Steps
-
Draw a text box and bind its value to a state field — say
state.text. -
Draw a button and give its click handler:
startCompany(getState("text"));createRecord("par_client", { par_name: getState("text") });loadCompanyPeople("people", "company"); -
Draw a second button for inviting:
invitePerson(getState("text"));loadCompanyPeople("people", "company");notify("Invitation sent."); -
In the screen's screenLoad, put
loadCompanyPeople("people", "company");so the page opens with the list already filled.
startCompany does two things: it creates the company and re-points this visitor's session at
it. Only after that does portalCustomerId() answer anything — which is what the Custom API
behind createRecord reads to give your own record the company's id.
Do it the other way round and there is no id to give the record. Your API never receives one from the page, and should not: the platform decides it, which is what stops a visitor claiming somebody else's company.
What lands in your state fields
loadCompanyPeople("people", "company") fills two fields:
-
state.people— one row per colleague and per outstanding invitation:Field What it is idmember:…orinvitation:…— pass it straight back toremovePersonnameTheir name, or the address an invitation went to emailThe address they sign in with roleMember,BillingorOwnerstatusMemberorInvitedisYouWhether this row is the person reading the page -
state.company—{ name, role, canManage }. Bind a caption tostate.company.name, and put your Invite box's visible binding onstate.company.canManageso an ordinary member is not shown a button they will be refused.
Somebody who is in no company gets an empty list and an empty company, which is not an error — it is what your "start a company" form is for.
A Remove button that works on both kinds of row
Repeat a table over state.people and put one button in the row:
removePerson(row.id);
loadCompanyPeople("people", "company");
That removes a colleague or withdraws an invitation, because the row's id says which it is. You
do not write that condition — and you should not, because the two are different acts to the
platform and only it knows the rules for each.
An invitation's role cannot be changed. changePersonRole on an invitation: row is refused in
words: withdraw it and invite them again.
What a refusal looks like
The platform's own sentence appears beside the button somebody pressed — "Only an Owner can invite people", "The last Owner cannot leave the company", "That address is already in this company".
You do not need to write those messages and should not try to pre-empt them: the rules belong to the platform, and a screen that guessed would eventually guess wrong.
Somebody who is the only Owner of a company must make a colleague an Owner first. That is deliberate: a company with records behind it is not left with nobody who can administer it by one person pressing a button.
Where an invitation lands
On your portal's front door, like a confirmation link and a password-reset link. Somebody who is signed in joins straight away; somebody who is not sees the sign-in form with You have been invited above it, and the invitation is still waiting after they sign up and confirm their address. See Ask visitors to sign in.
What this is for
A company is what a data policy narrows records by. So these two pages fit together: this one is how somebody comes to have a company, and that one is how the rows of a table come to belong to it.