@ossy/workspaces 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # @ossy/workspaces
2
+
3
+ Workspaces **feature package** for the Ossy platform — provides workspace creation, selection, user management, and invitation pages that are auto-discovered by `@ossy/app` when the package is installed.
4
+
5
+ ## What's included
6
+
7
+ ### Pages
8
+
9
+ | File | Page id | Default paths |
10
+ |---|---|---|
11
+ | `Invitations.page.jsx` | `workspace/invitations` | `/invitations` (en), `/inbjudningar` (sv) |
12
+ | `Invite.page.jsx` | `workspace/invitations/add` | `/invitations/add` (en), `/inbjudningar/lagg-till` (sv) |
13
+ | `Users.page.jsx` | `workspace/users` | `/users` (en), `/anvandare` (sv) |
14
+ | `WorkspaceSettings.page.jsx` | `workspace/settings` | `/settings` (en), `/installningar` (sv) |
15
+
16
+ ### Components
17
+
18
+ | Export | Description |
19
+ |---|---|
20
+ | `CreateWorkspacePage` | Form to create a new workspace |
21
+ | `SelectWorkspacePage` | List and switch between workspaces |
22
+ | `GeneralSettings` | Workspace details and services settings |
23
+ | `Invitations` | List pending workspace invitations |
24
+ | `Invite` | Invite a user to the workspace by email |
25
+ | `Users` | List and manage workspace members |
26
+ | `AuthenticationGuard` | Wraps children, shows error for unauthenticated users |
27
+ | `patchUserWorkspaceId` | Utility to update the active workspace cookie |
28
+ | `Definition` | Feature metadata (id, actions, views) |
29
+
30
+ ## Usage
31
+
32
+ ```bash
33
+ npm install @ossy/workspaces
34
+ npm run build
35
+ ```
36
+
37
+ `@ossy/app` discovers the pages automatically by reading the `"ossy": { "src": "./src" }` field in this package's `package.json`.
38
+
39
+ ## Peer dependencies
40
+
41
+ | Package | Version |
42
+ |---|---|
43
+ | `@ossy/design-system` | `>=1.0.0` |
44
+ | `@ossy/router-react` | `>=1.0.0` |
45
+ | `@ossy/sdk-react` | `>=1.0.0` |
46
+ | `react` | `>=19.0.0 <20.0.0` |
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@ossy/workspaces",
3
+ "description": "Workspaces feature package — create, select, and manage workspaces, users, and invitations",
4
+ "version": "1.1.0",
5
+ "type": "module",
6
+ "main": "./src/index.js",
7
+ "module": "./src/index.js",
8
+ "exports": {
9
+ ".": "./src/index.js"
10
+ },
11
+ "author": "Ossy <yourfriends@ossy.se> (https://ossy.se)",
12
+ "license": "MIT",
13
+ "ossy": {
14
+ "src": "./src"
15
+ },
16
+ "peerDependencies": {
17
+ "@ossy/design-system": ">=1.0.0",
18
+ "@ossy/router-react": ">=1.0.0",
19
+ "@ossy/sdk-react": ">=1.0.0",
20
+ "react": ">=19.0.0 <20.0.0"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public",
24
+ "registry": "https://registry.npmjs.org"
25
+ },
26
+ "files": [
27
+ "/src",
28
+ "README.md"
29
+ ],
30
+ "gitHead": "eabc1ddca6f83721efa11f044422ab8628929c88"
31
+ }
@@ -0,0 +1,50 @@
1
+ import React from 'react'
2
+ import { AuthenticationStatus, useAuthentication } from '@ossy/sdk-react'
3
+ import { Switch, View, Guide, Icon } from '@ossy/design-system'
4
+
5
+ export const AuthenticationGuard = ({ children }) => {
6
+ const { status } = useAuthentication()
7
+
8
+ return (
9
+ <Switch on={status}>
10
+
11
+ <Switch.Case match={[AuthenticationStatus.NotAuthenticated]}>
12
+ <View layout="off-center" style={{ height: '100%' }}>
13
+ <Guide
14
+ style={{ width: '100vw', maxWidth: '300px' }}
15
+ slot="content"
16
+ title="Not Authenticated"
17
+ text="You need to login to view this page"
18
+ />
19
+ </View>
20
+ </Switch.Case>
21
+
22
+ <Switch.Case match={[AuthenticationStatus.AuthenticationError]}>
23
+ <View layout="off-center" style={{ height: '100%' }}>
24
+ <Guide
25
+ style={{ width: '100vw', maxWidth: '300px' }}
26
+ slot="content"
27
+ title="Error"
28
+ text="An error occurred while trying to authenticate, try again in a few minutes"
29
+ />
30
+ </View>
31
+ </Switch.Case>
32
+
33
+ <Switch.Case match={[AuthenticationStatus.Verifying, AuthenticationStatus.NoInitialized]}>
34
+ <View layout="off-center" style={{ height: '100%' }}>
35
+ <Icon
36
+ name="Cached"
37
+ style={{ width: '64px', height: '64px' }}
38
+ className="rotate fill-50"
39
+ slot="content"
40
+ />
41
+ </View>
42
+ </Switch.Case>
43
+
44
+ <Switch.Case match={[AuthenticationStatus.Authenticated]}>
45
+ {children}
46
+ </Switch.Case>
47
+
48
+ </Switch>
49
+ )
50
+ }
@@ -0,0 +1,70 @@
1
+ import React, { useState } from 'react'
2
+ import { useWorkspaces } from '@ossy/sdk-react'
3
+ import {
4
+ Button,
5
+ useInputValue,
6
+ Title,
7
+ Input,
8
+ View
9
+ } from '@ossy/design-system'
10
+ import { useRouter } from '@ossy/router-react'
11
+ import { AuthenticationGuard } from './AuthenticationGuard.jsx'
12
+ import { patchUserWorkspaceId } from './patchUserWorkspaceId.js'
13
+
14
+ export const CreateWorkspacePage = () => {
15
+ const router = useRouter()
16
+ const [error, setError] = useState()
17
+ const [workspaceName, setWorkspaceName] = useInputValue()
18
+ const { createWorkspace } = useWorkspaces()
19
+
20
+ const onCreateWorkspace = workspaceName => {
21
+ createWorkspace(workspaceName)
22
+ .then((workspace) =>
23
+ patchUserWorkspaceId(workspace.id).then((res) => {
24
+ if (!res.ok) throw new Error('Could not select the new workspace')
25
+ window.location.assign(
26
+ router.getHref({ id: '@storage/home', params: { workspaceId: workspace.id } })
27
+ )
28
+ })
29
+ )
30
+ .catch(err => { setError(err.message) })
31
+ }
32
+
33
+ return (
34
+ <AuthenticationGuard>
35
+ <View gap="m">
36
+
37
+ <title>Create workspace</title>
38
+ <Title variant="primary" className="stack-m">Create a new workspace</Title>
39
+
40
+ <Input
41
+ id="workspaceName"
42
+ className={error ? 'stack-s' : 'stack-m'}
43
+ type="text"
44
+ placeholder="Workspace name"
45
+ required
46
+ onChange={setWorkspaceName}
47
+ style={{ width: '100%' }}
48
+ />
49
+
50
+ <div className={error ? 'stack-m' : ''}>{error}</div>
51
+
52
+ <View gap="m" layout="row" justifyContent="flex-end">
53
+
54
+ <Button
55
+ variant="link"
56
+ id="cancel"
57
+ href={() => router.back()}>Cancel
58
+ </Button>
59
+
60
+ <Button
61
+ id="createWorkspace"
62
+ variant="cta"
63
+ onClick={() => onCreateWorkspace(workspaceName)}>Create
64
+ </Button>
65
+
66
+ </View>
67
+ </View>
68
+ </AuthenticationGuard>
69
+ )
70
+ }
@@ -0,0 +1,23 @@
1
+ export const Definition = {
2
+ id: 'workspaces',
3
+ title: 'Workspaces',
4
+ description: 'Workspace lifecycle, members, invitations, and services.',
5
+ module: {
6
+ id: 'workspaces',
7
+ enabled: true
8
+ },
9
+ statuses: ['beta'],
10
+ actions: [
11
+ 'workspaces.create',
12
+ 'workspaces.invite-user',
13
+ 'workspaces.enable-service',
14
+ 'workspaces.disable-service'
15
+ ],
16
+ views: [
17
+ 'workspaces.get-all',
18
+ 'workspaces.get',
19
+ 'workspaces.get-current',
20
+ 'workspaces.get-users'
21
+ ],
22
+ tasks: []
23
+ }
@@ -0,0 +1,87 @@
1
+ import React, { useState } from 'react'
2
+ import { useWorkspace, useUser, AsyncStatus } from '@ossy/sdk-react'
3
+ import { Switch, Title, Button, View, Text } from '@ossy/design-system'
4
+
5
+ const formatDate = (timestamp) => {
6
+ if (!timestamp) return ''
7
+ const date = new Date(timestamp)
8
+ return date.toLocaleDateString()
9
+ }
10
+
11
+ const FlowStage = {
12
+ View: 'View',
13
+ Edit: 'Edit'
14
+ }
15
+
16
+ const availableServices = [
17
+ '@ossy/jobs/visual-content-descriptors',
18
+ '@ossy/jobs/resize-common-web',
19
+ '@ossy/resumes',
20
+ '@ossy/consultancy',
21
+ '@ossy/apps',
22
+ '@ossy/domains',
23
+ ]
24
+
25
+ export const GeneralSettings = () => {
26
+ const { workspace } = useWorkspace()
27
+ const { status: userStatus, user } = useUser()
28
+ const [flowStage, setFlowStage] = useState(FlowStage.View)
29
+
30
+ const services = workspace?.services || {}
31
+
32
+ const isWorkspaceCreator =
33
+ userStatus === AsyncStatus.Success &&
34
+ Boolean(workspace?.createdBy) &&
35
+ Boolean(user?.id) &&
36
+ workspace.createdBy === user.id
37
+
38
+ return (
39
+ <View gap="l" inset="m">
40
+ <View layout="row" gap="s" justifyContent="space-between">
41
+ <Title>Workspace details</Title>
42
+
43
+ <View>
44
+ <Switch on={flowStage}>
45
+ <Switch.Case match={[FlowStage.View]}>
46
+ <Button prefix={{ size: 's', name: 'pen' }} variant="cta" disabled>
47
+ Edit
48
+ </Button>
49
+ </Switch.Case>
50
+ </Switch>
51
+ </View>
52
+ </View>
53
+
54
+ <View gap="s" style={{ height: '100%' }}>
55
+ <View layout="row" gap="s">
56
+ <Text style={{ fontWeight: 'bold' }}>Id:</Text>
57
+ <Text>{workspace.id}</Text>
58
+ </View>
59
+ <View layout="row" gap="s">
60
+ <Text style={{ fontWeight: 'bold' }}>Name:</Text>
61
+ <Text>{workspace.name}</Text>
62
+ </View>
63
+ <View layout="row" gap="s">
64
+ <Text style={{ fontWeight: 'bold' }}>Created:</Text>
65
+ <Text>{formatDate(workspace?.created)}</Text>
66
+ </View>
67
+ </View>
68
+
69
+ {isWorkspaceCreator && (
70
+ <View gap="s">
71
+ <Title variant="secondary">Workspace services</Title>
72
+ <Text variant="small" style={{ maxWidth: '42rem' }}>
73
+ Turn features on or off for this workspace.
74
+ </Text>
75
+ {availableServices.map((x) => (
76
+ <View key={x}>
77
+ <Text>
78
+ {x} : {services[x] !== false ? 'On' : 'Off'}
79
+ </Text>
80
+ </View>
81
+ ))}
82
+ </View>
83
+ )}
84
+
85
+ </View>
86
+ )
87
+ }
@@ -0,0 +1,44 @@
1
+ import React from 'react'
2
+ import { useWorkspace } from '@ossy/sdk-react'
3
+ import { Title, Text, Button, View, Icon2 } from '@ossy/design-system'
4
+ import { useRouter } from '@ossy/router-react'
5
+
6
+ export const Invitations = () => {
7
+ const router = useRouter()
8
+ const { workspace } = useWorkspace()
9
+ const invitations = workspace?.invitations || []
10
+
11
+ return (
12
+ <View gap="m">
13
+
14
+ <View inset="s" gap="s">
15
+
16
+ <View layout="row" justifyContent="space-between" alignItems="center">
17
+ <Title>Invitations</Title>
18
+ <Button variant="cta" href={router.getHref('@workspace/invitations/add')} prefix="user-add">
19
+ Invite user
20
+ </Button>
21
+ </View>
22
+
23
+ <Text style={{ maxWidth: '400px' }}>
24
+ Invite users to your workspace to collaborate.
25
+ </Text>
26
+
27
+ </View>
28
+
29
+ <View gap="xs">
30
+ {invitations.map(invite => (
31
+ <View layout="row" roundness="s" alignItems="center" inset="m" gap="m" selectable key={invite.email}>
32
+ <Icon2 name="mail" />
33
+ <View>
34
+ <Text variant="small" as="span">{invite.email}</Text>
35
+ </View>
36
+ </View>
37
+ ))}
38
+ </View>
39
+
40
+ {invitations.length === 0 && <Text>No invitations</Text>}
41
+
42
+ </View>
43
+ )
44
+ }
@@ -0,0 +1,23 @@
1
+ import React from 'react'
2
+ import { View } from '@ossy/design-system'
3
+ import { Invitations } from './Invitations.jsx'
4
+
5
+ export const metadata = {
6
+ id: 'workspace/invitations',
7
+ path: {
8
+ sv: '/inbjudningar',
9
+ en: '/invitations',
10
+ },
11
+ }
12
+
13
+ export const InvitationsPage = () => {
14
+ return (
15
+ <View layout="off-center-m" style={{ height: '100%' }}>
16
+ <View slot="content" surface="primary" roundness="m" inset="m">
17
+ <Invitations />
18
+ </View>
19
+ </View>
20
+ )
21
+ }
22
+
23
+ export default InvitationsPage
package/src/Invite.jsx ADDED
@@ -0,0 +1,105 @@
1
+ import React, { useState, useRef } from 'react'
2
+ import { useWorkspace } from '@ossy/sdk-react'
3
+ import { useRouter } from '@ossy/router-react'
4
+ import {
5
+ useInputValue,
6
+ Button,
7
+ Title,
8
+ Input,
9
+ Switch,
10
+ Guide,
11
+ Text,
12
+ View
13
+ } from '@ossy/design-system'
14
+
15
+ const FlowStage = {
16
+ EnterEmail: 'EnterEmail',
17
+ Success: 'Success',
18
+ Error: 'Error'
19
+ }
20
+
21
+ export const Invite = () => {
22
+ const router = useRouter()
23
+ const { inviteUser } = useWorkspace()
24
+ const [flowStage, setFlowStage] = useState(FlowStage.EnterEmail)
25
+ const [email, setEmail] = useInputValue('')
26
+ const formRef = useRef()
27
+
28
+ const sendInvite = event => {
29
+ event.preventDefault()
30
+ formRef.current.reportValidity() && inviteUser(email)
31
+ .then(() => setFlowStage(FlowStage.Success))
32
+ .catch(() => setFlowStage(FlowStage.Error))
33
+ }
34
+
35
+ return (
36
+ <Switch on={flowStage}>
37
+
38
+ <title>Invite user</title>
39
+
40
+ <Switch.Case match={[FlowStage.EnterEmail]}>
41
+ <View gap="m">
42
+ <Title variant="primary">Invite user</Title>
43
+ <Text>
44
+ Invite a user to manage and contribute content to this workspace
45
+ </Text>
46
+ <form
47
+ ref={formRef}
48
+ onSubmit={sendInvite}
49
+ style={{ width: '100%', display: 'flex', flexDirection: 'column' }}
50
+ >
51
+ <View gap="m">
52
+
53
+ <Input
54
+ id="registerField"
55
+ className="stack-m"
56
+ type="email"
57
+ placeholder="Email address"
58
+ required
59
+ value={email}
60
+ onChange={e => setEmail(e.target.value)}
61
+ />
62
+
63
+ <View gap="m" layout="row" justifyContent="flex-end">
64
+
65
+ <Button
66
+ variant="link"
67
+ id="cancelButton"
68
+ href={() => router.back()}>Cancel
69
+ </Button>
70
+
71
+ <Button
72
+ variant="cta"
73
+ id="registerButton"
74
+ type="submit"
75
+ onClick={sendInvite}>Send invite
76
+ </Button>
77
+ </View>
78
+ </View>
79
+ </form>
80
+ </View>
81
+ </Switch.Case>
82
+
83
+ <Switch.Case match={[FlowStage.Success]}>
84
+ <Guide
85
+ title="Success"
86
+ titleVariant="primary"
87
+ >
88
+ <Text>
89
+ We sent them an invitation over email.
90
+ When they have accepted they will show up in the list of users.
91
+ <br/>
92
+ ✌️
93
+ </Text>
94
+ </Guide>
95
+ </Switch.Case>
96
+
97
+ <Switch.Case match={[FlowStage.Error]}>
98
+ <Text>
99
+ Something went wrong, try again in a couple of minutes
100
+ </Text>
101
+ </Switch.Case>
102
+
103
+ </Switch>
104
+ )
105
+ }
@@ -0,0 +1,23 @@
1
+ import React from 'react'
2
+ import { View } from '@ossy/design-system'
3
+ import { Invite } from './Invite.jsx'
4
+
5
+ export const metadata = {
6
+ id: 'workspace/invitations/add',
7
+ path: {
8
+ sv: '/inbjudningar/lagg-till',
9
+ en: '/invitations/add',
10
+ },
11
+ }
12
+
13
+ export const InvitePage = () => {
14
+ return (
15
+ <View layout="off-center-m" style={{ height: '100%' }}>
16
+ <View slot="content" surface="primary" roundness="m" inset="m">
17
+ <Invite />
18
+ </View>
19
+ </View>
20
+ )
21
+ }
22
+
23
+ export default InvitePage
@@ -0,0 +1,143 @@
1
+ import React from 'react'
2
+ import { useWorkspaces, AsyncStatus } from '@ossy/sdk-react'
3
+ import { Title, Text, Switch, Button, View, Guide, Icon2, Stack } from '@ossy/design-system'
4
+ import { useRouter } from '@ossy/router-react'
5
+ import { AuthenticationGuard } from './AuthenticationGuard.jsx'
6
+ import { patchUserWorkspaceId } from './patchUserWorkspaceId.js'
7
+
8
+ const PageFlow = {
9
+ Error: AsyncStatus.Error,
10
+ Loading: AsyncStatus.Loading,
11
+ Success: AsyncStatus.Success,
12
+ NoWorkspacesCreated: 'NoWorkspacesCreated'
13
+ }
14
+
15
+ export const SelectWorkspacePage = () => {
16
+ const router = useRouter()
17
+ const { status: asyncStatus, workspaces } = useWorkspaces()
18
+
19
+ const pageFlow = (asyncStatus === AsyncStatus.Success && workspaces.length === 0)
20
+ ? PageFlow.NoWorkspacesCreated
21
+ : asyncStatus
22
+
23
+ const setWorkspace = workspaceId => {
24
+ patchUserWorkspaceId(workspaceId).then((res) => {
25
+ if (!res.ok) return
26
+ window.location.assign(router.getHref('@home/home'))
27
+ })
28
+ }
29
+
30
+ return (
31
+ <AuthenticationGuard>
32
+
33
+ <title>Select workspace</title>
34
+
35
+ <Switch on={pageFlow}>
36
+ <Switch.Case match={[PageFlow.Success]}>
37
+ <div style={{
38
+ display: 'flex',
39
+ justifyContent: 'space-between',
40
+ alignItems: 'center',
41
+ marginBottom: 'var(--space-m)',
42
+ }}>
43
+ <Title variant="primary">
44
+ My workspaces
45
+ </Title>
46
+ <Button
47
+ prefix="math-plus"
48
+ variant="cta"
49
+ href={router.getHref('@profile/workspaces/create')}
50
+ className="mobile:d-none">
51
+ New workspace
52
+ </Button>
53
+ </div>
54
+ </Switch.Case>
55
+ </Switch>
56
+
57
+ <Switch on={pageFlow}>
58
+
59
+ <Switch.Case match={[PageFlow.Error]}>
60
+ <View layout="off-center" style={{ height: '100%' }}>
61
+ <Guide
62
+ slot="content"
63
+ style={{ width: '100vw', maxWidth: '500px' }}
64
+ title="We could not load your workspaces"
65
+ text={`
66
+ Something went wrong when loading your workspaces.
67
+ Maybe your session have expired, or we messed up somehow.
68
+ Try starting over from our home page.
69
+ `}
70
+ actions={[{
71
+ variant: 'cta',
72
+ href: '/',
73
+ label: 'Go to our home page'
74
+ }]}
75
+ />
76
+ </View>
77
+ </Switch.Case>
78
+
79
+ <Switch.Case match={[PageFlow.Loading]}>
80
+ <Icon2
81
+ slot="content"
82
+ name="spinner"
83
+ style={{ width: '64px', height: '64px' }}
84
+ className="rotate"
85
+ />
86
+ <Text>Loading...</Text>
87
+ </Switch.Case>
88
+
89
+ <Switch.Case match={[PageFlow.Success]}>
90
+ <View gap="m">
91
+
92
+ <Text style={{ maxWidth: '400px' }}>
93
+ Workspaces are separate environments where you can manage resources, templates, services and billing.
94
+ </Text>
95
+
96
+ <Stack gap="xs" style={{ overflowY: 'auto' }}>
97
+ {workspaces.map(workspace => (
98
+ <Stack.Item
99
+ key={workspace.id}
100
+ layout="row"
101
+ style={{ alignItems: 'center' }}
102
+ gap="m"
103
+ inset="m"
104
+ roundness="s"
105
+ selectable
106
+ onClick={() => setWorkspace(workspace.id)}
107
+ >
108
+ <View>
109
+ <Text style={{ fontWeight: 'bold' }}>{workspace.name}</Text>
110
+ <Text variant="small" as="span">{workspace.id}</Text>
111
+ </View>
112
+ <div style={{ flexGrow: '1' }} />
113
+ <Button prefix="more-vertical-alt" variant="command" style={{ cursor: 'not-allowed' }} />
114
+ </Stack.Item>
115
+ ))}
116
+ </Stack>
117
+ </View>
118
+ </Switch.Case>
119
+
120
+ <Switch.Case match={[PageFlow.NoWorkspacesCreated]}>
121
+ <View layout="vertical-top" style={{ flexGrow: '1' }}>
122
+ <Guide
123
+ slot="content"
124
+ style={{ padding: 'var(--space-xl) var(--space-m)', width: '100vw', maxWidth: '500px' }}
125
+ title="🎉 Let's get the party started 🎉"
126
+ text={`
127
+ To get started we need to create a workspace.
128
+ A workspace is an encapsulation of resources, resource templates,
129
+ billing information, and what users have access to these things.
130
+ `}
131
+ actions={[{
132
+ variant: 'cta',
133
+ label: 'Create a workspace',
134
+ href: router.getHref('@profile/workspaces/create')
135
+ }]}
136
+ />
137
+ </View>
138
+ </Switch.Case>
139
+
140
+ </Switch>
141
+ </AuthenticationGuard>
142
+ )
143
+ }
package/src/Users.jsx ADDED
@@ -0,0 +1,47 @@
1
+ import React from 'react'
2
+ import { useUsers, AsyncStatus } from '@ossy/sdk-react'
3
+ import { Text, View, Icon2, Title, Button } from '@ossy/design-system'
4
+ import { useRouter } from '@ossy/router-react'
5
+
6
+ export const Users = () => {
7
+ const router = useRouter()
8
+ const { users = [], status } = useUsers()
9
+
10
+ if (status === AsyncStatus.Loading) return <>Loading</>
11
+
12
+ return (
13
+ <View gap="m">
14
+
15
+ <View inset="s" gap="s">
16
+
17
+ <View layout="row" justifyContent="space-between" alignItems="center">
18
+ <Title>Users</Title>
19
+ <Button variant="cta" href={router.getHref('@workspace/invitations/add')} prefix="user-add">
20
+ Invite user
21
+ </Button>
22
+ </View>
23
+
24
+ <Text style={{ maxWidth: '400px' }}>
25
+ Manage users in your workspace.
26
+ </Text>
27
+
28
+ </View>
29
+
30
+ <View gap="xs">
31
+ {users.map(user => (
32
+ <View layout="row" roundness="s" alignItems="center" inset="m" gap="m" selectable key={user.id}>
33
+ <Icon2 name={user.type === 'Bot' ? 'bot' : 'user'} />
34
+ <View>
35
+ <View layout="row" gap="s">
36
+ <Text as="span" style={{ fontWeight: 'bold' }}>{user.firstName}</Text>
37
+ <Text as="span" style={{ fontWeight: 'bold' }}>{user.lastName}</Text>
38
+ </View>
39
+ <Text variant="small" as="span">{user.email}</Text>
40
+ </View>
41
+ </View>
42
+ ))}
43
+ </View>
44
+
45
+ </View>
46
+ )
47
+ }
@@ -0,0 +1,21 @@
1
+ import React from 'react'
2
+ import { View } from '@ossy/design-system'
3
+ import { Users } from './Users.jsx'
4
+
5
+ export const metadata = {
6
+ id: 'workspace/users',
7
+ path: {
8
+ sv: '/anvandare',
9
+ en: '/users',
10
+ },
11
+ }
12
+
13
+ export const UsersPage = () => {
14
+ return (
15
+ <View gap="m" surface="primary" style={{ padding: 'var(--space-m) var(--space-l)', height: '100%', overflowY: 'auto' }}>
16
+ <Users />
17
+ </View>
18
+ )
19
+ }
20
+
21
+ export default UsersPage
@@ -0,0 +1,23 @@
1
+ import React from 'react'
2
+ import { View } from '@ossy/design-system'
3
+ import { GeneralSettings } from './GeneralSettings.jsx'
4
+
5
+ export const metadata = {
6
+ id: 'workspace/settings',
7
+ path: {
8
+ sv: '/installningar',
9
+ en: '/settings',
10
+ },
11
+ }
12
+
13
+ export const WorkspaceSettingsPage = () => {
14
+ return (
15
+ <View layout="off-center-m" style={{ height: '100%' }}>
16
+ <View slot="content" surface="primary" roundness="m" inset="m">
17
+ <GeneralSettings />
18
+ </View>
19
+ </View>
20
+ )
21
+ }
22
+
23
+ export default WorkspaceSettingsPage
package/src/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { CreateWorkspacePage } from './CreateWorkspacePage.jsx'
2
+ export { SelectWorkspacePage } from './SelectWorkspacePage.jsx'
3
+ export { GeneralSettings } from './GeneralSettings.jsx'
4
+ export { Invitations } from './Invitations.jsx'
5
+ export { Invite } from './Invite.jsx'
6
+ export { Users } from './Users.jsx'
7
+ export { AuthenticationGuard } from './AuthenticationGuard.jsx'
8
+ export { patchUserWorkspaceId } from './patchUserWorkspaceId.js'
9
+ export { Definition } from './Definition.js'
@@ -0,0 +1,14 @@
1
+ function patchUserAppSettings(partial) {
2
+ return fetch('/@ossy/users/me/app-settings', {
3
+ method: 'PATCH',
4
+ body: JSON.stringify(partial),
5
+ headers: {
6
+ 'Content-Type': 'application/json',
7
+ },
8
+ credentials: 'same-origin',
9
+ })
10
+ }
11
+
12
+ export function patchUserWorkspaceId(workspaceId) {
13
+ return patchUserAppSettings({ workspaceId })
14
+ }