@ossy/analytics 3.4.0 → 3.5.1
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 +1 -1
- package/package.json +6 -3
- package/src/AnalyticsProductHome.jsx +124 -0
- package/src/AnalyticsSalesPage.jsx +55 -0
- package/src/HeroCover.jsx +93 -0
- package/src/SalesSection.jsx +69 -0
- package/src/analytics-home-content.js +51 -0
- package/src/analytics.page.jsx +19 -114
- package/src/en.translations.json +15 -1
- package/src/sv.translations.json +15 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Platform-wide (ops) insights belong in a future back-office surface — see [ADR
|
|
|
8
8
|
|
|
9
9
|
| Page | Path | Audience |
|
|
10
10
|
|------|------|----------|
|
|
11
|
-
| `analytics/home` | `/analytics` · `/analys` |
|
|
11
|
+
| `analytics/home` | `/analytics` · `/analys` | Signed-in + entitled → product KPIs. Otherwise the public sales page (no KPI loads). |
|
|
12
12
|
|
|
13
13
|
## Actions
|
|
14
14
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/analytics",
|
|
3
3
|
"description": "Analytics module — workspace insight KPIs",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.5.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"module": "./src/index.js",
|
|
@@ -26,11 +26,14 @@
|
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@ossy/event-store": "^3.4.0",
|
|
29
|
-
"@ossy/workspaces": "^3.
|
|
29
|
+
"@ossy/workspaces": "^3.5.1"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
|
+
"@ossy/app": "*",
|
|
33
|
+
"@ossy/authentication": "*",
|
|
32
34
|
"@ossy/design-system": "*",
|
|
33
35
|
"@ossy/resources": "*",
|
|
36
|
+
"@ossy/router-react": "*",
|
|
34
37
|
"@ossy/sdk-react": "*",
|
|
35
38
|
"@ossy/workspaces": "*",
|
|
36
39
|
"react": "*"
|
|
@@ -39,5 +42,5 @@
|
|
|
39
42
|
"@jest/globals": "^30.2.0",
|
|
40
43
|
"jest": "^30.2.0"
|
|
41
44
|
},
|
|
42
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "cafa2d72ae9c4f6197ebec0b5b857919a062ceff"
|
|
43
46
|
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
|
+
import { Text, View, Page, useLocale } from '@ossy/design-system'
|
|
3
|
+
import { usePageViewStats } from './usePageViewStats.js'
|
|
4
|
+
import { useWorkspaceKpis } from './useWorkspaceKpis.js'
|
|
5
|
+
import { useSdk } from '@ossy/sdk-react'
|
|
6
|
+
import { GetWorkspace } from '@ossy/workspaces'
|
|
7
|
+
|
|
8
|
+
function SummaryCard ({ label, value }) {
|
|
9
|
+
return (
|
|
10
|
+
<View
|
|
11
|
+
surface="primary"
|
|
12
|
+
roundness="m"
|
|
13
|
+
inset="m"
|
|
14
|
+
style={{ border: '1px solid var(--separator)' }}
|
|
15
|
+
>
|
|
16
|
+
<Text variant="small" style={{ opacity: 0.75 }}>{label}</Text>
|
|
17
|
+
<Text variant="heading-secondary" as="h2">{value}</Text>
|
|
18
|
+
</View>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Workspace analytics product UI. Only mount when the caller is authenticated
|
|
24
|
+
* so KPI / page-view actions do not run for anonymous sessions.
|
|
25
|
+
*/
|
|
26
|
+
export default function AnalyticsProductHome () {
|
|
27
|
+
const { t } = useLocale()
|
|
28
|
+
const sdk = useSdk()
|
|
29
|
+
const { data: workspace } = sdk.read(GetWorkspace)
|
|
30
|
+
const { from, to } = useMemo(() => {
|
|
31
|
+
const now = Date.now()
|
|
32
|
+
return { from: now - 30 * 24 * 60 * 60 * 1000, to: now }
|
|
33
|
+
}, [])
|
|
34
|
+
|
|
35
|
+
const pageViews = usePageViewStats({
|
|
36
|
+
workspaceId: workspace?.id,
|
|
37
|
+
from,
|
|
38
|
+
to,
|
|
39
|
+
limit: 10,
|
|
40
|
+
})
|
|
41
|
+
const workspaceKpis = useWorkspaceKpis({ workspaceId: workspace?.id })
|
|
42
|
+
|
|
43
|
+
const { status, totalViews, dailyViews, topPaths } = pageViews
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Page
|
|
47
|
+
title="analytics.home.title"
|
|
48
|
+
description="analytics.home.description"
|
|
49
|
+
>
|
|
50
|
+
<View gap="m">
|
|
51
|
+
{(workspaceKpis.status === 'loading' || status === 'loading') && (
|
|
52
|
+
<View surface="primary" roundness="m" inset="m">
|
|
53
|
+
<Text>{t('analytics.home.loading')}</Text>
|
|
54
|
+
</View>
|
|
55
|
+
)}
|
|
56
|
+
|
|
57
|
+
{workspaceKpis.status === 'error' && (
|
|
58
|
+
<View surface="primary" roundness="m" inset="m">
|
|
59
|
+
<Text>{t('analytics.home.workspaceKpisError')}</Text>
|
|
60
|
+
</View>
|
|
61
|
+
)}
|
|
62
|
+
|
|
63
|
+
{workspaceKpis.status === 'success' && (
|
|
64
|
+
<View layout="row" gap="m">
|
|
65
|
+
<SummaryCard label={t('analytics.home.members')} value={`${workspaceKpis.members}`} />
|
|
66
|
+
<SummaryCard label={t('analytics.home.resources')} value={`${workspaceKpis.resources}`} />
|
|
67
|
+
</View>
|
|
68
|
+
)}
|
|
69
|
+
|
|
70
|
+
{status === 'error' && (
|
|
71
|
+
<View surface="primary" roundness="m" inset="m">
|
|
72
|
+
<Text>{t('analytics.home.error')}</Text>
|
|
73
|
+
</View>
|
|
74
|
+
)}
|
|
75
|
+
|
|
76
|
+
{status === 'success' && (
|
|
77
|
+
<>
|
|
78
|
+
<View layout="row" gap="m">
|
|
79
|
+
<SummaryCard label={t('analytics.home.totalPageViews')} value={`${totalViews}`} />
|
|
80
|
+
<SummaryCard label={t('analytics.home.trackedPages')} value={`${topPaths.length}`} />
|
|
81
|
+
<SummaryCard label={t('analytics.home.daysWithTraffic')} value={`${dailyViews.length}`} />
|
|
82
|
+
</View>
|
|
83
|
+
|
|
84
|
+
<View layout="row" gap="m" style={{ alignItems: 'flex-start' }}>
|
|
85
|
+
<View
|
|
86
|
+
surface="primary"
|
|
87
|
+
roundness="m"
|
|
88
|
+
inset="m"
|
|
89
|
+
gap="s"
|
|
90
|
+
style={{ border: '1px solid var(--separator)', flex: 1 }}
|
|
91
|
+
>
|
|
92
|
+
<Text variant="heading-secondary" as="h2" text="analytics.home.topPaths" />
|
|
93
|
+
{topPaths.length === 0 && <Text variant="small" text="analytics.home.noPageViews" />}
|
|
94
|
+
{topPaths.map((item) => (
|
|
95
|
+
<View key={item.path || 'unknown'} layout="row" justifyContent="space-between">
|
|
96
|
+
<Text variant="small">{item.path || t('analytics.home.unknownPath')}</Text>
|
|
97
|
+
<Text variant="small">{item.views}</Text>
|
|
98
|
+
</View>
|
|
99
|
+
))}
|
|
100
|
+
</View>
|
|
101
|
+
|
|
102
|
+
<View
|
|
103
|
+
surface="primary"
|
|
104
|
+
roundness="m"
|
|
105
|
+
inset="m"
|
|
106
|
+
gap="s"
|
|
107
|
+
style={{ border: '1px solid var(--separator)', flex: 1 }}
|
|
108
|
+
>
|
|
109
|
+
<Text variant="heading-secondary" as="h2" text="analytics.home.dailyPageViews" />
|
|
110
|
+
{dailyViews.length === 0 && <Text variant="small" text="analytics.home.noDailyData" />}
|
|
111
|
+
{dailyViews.map((item) => (
|
|
112
|
+
<View key={item.date} layout="row" justifyContent="space-between">
|
|
113
|
+
<Text variant="small">{item.date}</Text>
|
|
114
|
+
<Text variant="small">{item.views}</Text>
|
|
115
|
+
</View>
|
|
116
|
+
))}
|
|
117
|
+
</View>
|
|
118
|
+
</View>
|
|
119
|
+
</>
|
|
120
|
+
)}
|
|
121
|
+
</View>
|
|
122
|
+
</Page>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React, { useState, useCallback } from 'react'
|
|
2
|
+
import { GetWorkspace, EnableService } from '@ossy/workspaces'
|
|
3
|
+
import { useSdk, cacheKey } from '@ossy/sdk-react'
|
|
4
|
+
import { Text, View, Button, Page, useLocale } from '@ossy/design-system'
|
|
5
|
+
import SalesSection from './SalesSection.jsx'
|
|
6
|
+
import { useAnalyticsHomeContent } from './analytics-home-content.js'
|
|
7
|
+
|
|
8
|
+
const ANALYTICS_SERVICE = '@ossy/analytics'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Public sales surface. Anonymous → sign up; signed-in but not entitled → enable.
|
|
12
|
+
* Does not load workspace KPI / page-view actions.
|
|
13
|
+
*/
|
|
14
|
+
export default function AnalyticsSalesPage ({ isAuthenticated }) {
|
|
15
|
+
const { t } = useLocale()
|
|
16
|
+
const { invoke, invalidate } = useSdk()
|
|
17
|
+
const [error, setError] = useState(null)
|
|
18
|
+
const { cover: baseCover, features } = useAnalyticsHomeContent()
|
|
19
|
+
|
|
20
|
+
const handleEnable = useCallback(() => {
|
|
21
|
+
setError(null)
|
|
22
|
+
invoke(EnableService, { service: ANALYTICS_SERVICE })
|
|
23
|
+
.then(() => invalidate(cacheKey(GetWorkspace)))
|
|
24
|
+
.catch(() => setError(t('analytics.sales.enableError')))
|
|
25
|
+
}, [invoke, invalidate, t])
|
|
26
|
+
|
|
27
|
+
const enableAction = {
|
|
28
|
+
...EnableService,
|
|
29
|
+
'data-service': ANALYTICS_SERVICE,
|
|
30
|
+
variant: 'cta',
|
|
31
|
+
label: 'analytics.sales.enable',
|
|
32
|
+
onClick: handleEnable,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const cover = isAuthenticated
|
|
36
|
+
? { ...baseCover, actions: [] }
|
|
37
|
+
: baseCover
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<View data-analytics-status="off">
|
|
41
|
+
<SalesSection cover={cover} features={features} />
|
|
42
|
+
|
|
43
|
+
{isAuthenticated && (
|
|
44
|
+
<Page maxWidth="xl" gap="l">
|
|
45
|
+
<View gap="m" inset="l">
|
|
46
|
+
<Text variant="heading-secondary" as="h2" text="analytics.sales.enable" />
|
|
47
|
+
<Text style={{ maxWidth: '600px' }} text="analytics.sales.enableDescription" />
|
|
48
|
+
<Button {...enableAction} variant="cta" />
|
|
49
|
+
{error && <Text variant="small">{error}</Text>}
|
|
50
|
+
</View>
|
|
51
|
+
</Page>
|
|
52
|
+
)}
|
|
53
|
+
</View>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Button, View, PageSection, Text } from '@ossy/design-system'
|
|
3
|
+
|
|
4
|
+
const Cover = ({
|
|
5
|
+
title,
|
|
6
|
+
titleMaxWidth = '1100px',
|
|
7
|
+
text,
|
|
8
|
+
actions = [],
|
|
9
|
+
}) => (
|
|
10
|
+
<View
|
|
11
|
+
gap="l"
|
|
12
|
+
placeContent="center"
|
|
13
|
+
layout="column"
|
|
14
|
+
justifyContent="center"
|
|
15
|
+
style={{
|
|
16
|
+
height: '100%',
|
|
17
|
+
borderRadius: 'var(--space-m)',
|
|
18
|
+
padding: 'var(--space-xl) var(--space-s)',
|
|
19
|
+
}}
|
|
20
|
+
>
|
|
21
|
+
<View gap="m" alignItems="center">
|
|
22
|
+
<Text as="h1" variant="display" style={{ maxWidth: titleMaxWidth, textWrap: 'balance' }}>
|
|
23
|
+
{title}
|
|
24
|
+
</Text>
|
|
25
|
+
<Text as="h2" variant="heading-tertiary" style={{ maxWidth: '800px' }}>
|
|
26
|
+
{text}
|
|
27
|
+
</Text>
|
|
28
|
+
</View>
|
|
29
|
+
<View gap="m" layout="row" justifyContent="center" style={{ flexWrap: 'wrap' }}>
|
|
30
|
+
{actions.map(({ label, ...props }) => (
|
|
31
|
+
<Button {...props} key={label}>
|
|
32
|
+
{label}
|
|
33
|
+
</Button>
|
|
34
|
+
))}
|
|
35
|
+
</View>
|
|
36
|
+
</View>
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
export function HeroCover ({
|
|
40
|
+
title,
|
|
41
|
+
titleMaxWidth = '1100px',
|
|
42
|
+
text,
|
|
43
|
+
actions = [],
|
|
44
|
+
id = 'analytics-hero-cover',
|
|
45
|
+
surfaceAs = 'div',
|
|
46
|
+
maxWidth = 'l',
|
|
47
|
+
surface = 'hero',
|
|
48
|
+
...props
|
|
49
|
+
}) {
|
|
50
|
+
const { style: passthroughStyle, ...pageSectionProps } = props
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<PageSection
|
|
54
|
+
id={id}
|
|
55
|
+
surfaceAs={surfaceAs}
|
|
56
|
+
maxWidth={maxWidth}
|
|
57
|
+
surface={surface}
|
|
58
|
+
{...pageSectionProps}
|
|
59
|
+
data-rounded
|
|
60
|
+
data-component="@ossy/analytics/cover"
|
|
61
|
+
style={{
|
|
62
|
+
backgroundAttachment: 'scroll',
|
|
63
|
+
boxShadow: 'inset 0 1px 0 color-mix(in srgb, var(--foreground) 6%, transparent)',
|
|
64
|
+
...passthroughStyle,
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
<style href="@ossy/analytics/cover" precedence="high">
|
|
68
|
+
{`
|
|
69
|
+
[data-component="@ossy/analytics/cover"] {
|
|
70
|
+
gap: var(--space-m);
|
|
71
|
+
min-height: min(42vh, 420px);
|
|
72
|
+
height: auto;
|
|
73
|
+
}
|
|
74
|
+
[data-rounded] {
|
|
75
|
+
border-radius: var(--space-m);
|
|
76
|
+
overflow: hidden;
|
|
77
|
+
}
|
|
78
|
+
@media (min-width: 900px) {
|
|
79
|
+
[data-component="@ossy/analytics/cover"] {
|
|
80
|
+
min-height: min(46vh, 520px);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
`}
|
|
84
|
+
</style>
|
|
85
|
+
<Cover
|
|
86
|
+
title={title}
|
|
87
|
+
titleMaxWidth={titleMaxWidth}
|
|
88
|
+
text={text}
|
|
89
|
+
actions={actions}
|
|
90
|
+
/>
|
|
91
|
+
</PageSection>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Text, View, Icon, PageSection, useLocale } from '@ossy/design-system'
|
|
3
|
+
import { HeroCover } from './HeroCover.jsx'
|
|
4
|
+
|
|
5
|
+
export default function SalesSection ({ cover, features }) {
|
|
6
|
+
const { t } = useLocale()
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<PageSection maxWidth="xl" gap="l">
|
|
10
|
+
<View
|
|
11
|
+
gap="l"
|
|
12
|
+
surface="primary"
|
|
13
|
+
style={{ padding: 'var(--space-m) var(--space-l)', height: '100%', overflowY: 'auto' }}
|
|
14
|
+
>
|
|
15
|
+
<HeroCover {...cover} />
|
|
16
|
+
|
|
17
|
+
<View gap="m" inset="s">
|
|
18
|
+
<Text variant="heading-secondary" as="h2">
|
|
19
|
+
{t('analytics.sales.overview')}
|
|
20
|
+
</Text>
|
|
21
|
+
<View gap="m" layout="row-wrap">
|
|
22
|
+
{features.map((x) => (
|
|
23
|
+
<View
|
|
24
|
+
key={x.title}
|
|
25
|
+
roundness="m"
|
|
26
|
+
surface="primary"
|
|
27
|
+
inset="m"
|
|
28
|
+
gap="m"
|
|
29
|
+
style={{
|
|
30
|
+
width: 200,
|
|
31
|
+
border: '1px solid var(--separator)',
|
|
32
|
+
}}
|
|
33
|
+
>
|
|
34
|
+
<View justifyContent="center" alignItems="center">
|
|
35
|
+
<Icon name={x.icon} size="m" />
|
|
36
|
+
</View>
|
|
37
|
+
<Text variant="small" style={{ fontWeight: 'bold', textAlign: 'center' }}>{x.title}</Text>
|
|
38
|
+
<Text variant="small" style={{ textAlign: 'center' }}>{x.text}</Text>
|
|
39
|
+
</View>
|
|
40
|
+
))}
|
|
41
|
+
</View>
|
|
42
|
+
</View>
|
|
43
|
+
|
|
44
|
+
<View gap="m" inset="s">
|
|
45
|
+
<Text variant="heading-secondary" as="h2">
|
|
46
|
+
{t('analytics.sales.features')}
|
|
47
|
+
</Text>
|
|
48
|
+
{features.map((x) => (
|
|
49
|
+
<View
|
|
50
|
+
key={x.title}
|
|
51
|
+
roundness="m"
|
|
52
|
+
gap="m"
|
|
53
|
+
inset="l"
|
|
54
|
+
style={{ border: '1px solid var(--separator)' }}
|
|
55
|
+
>
|
|
56
|
+
<View layout="row" gap="s" alignItems="center">
|
|
57
|
+
<View justifyContent="center" alignItems="center">
|
|
58
|
+
<Icon name={x.icon} size="m" />
|
|
59
|
+
</View>
|
|
60
|
+
<Text variant="heading-tertiary" as="h3">{x.title}</Text>
|
|
61
|
+
</View>
|
|
62
|
+
<Text>{x.text}</Text>
|
|
63
|
+
</View>
|
|
64
|
+
))}
|
|
65
|
+
</View>
|
|
66
|
+
</View>
|
|
67
|
+
</PageSection>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useLocale } from '@ossy/design-system'
|
|
2
|
+
import { useRouter } from '@ossy/router-react'
|
|
3
|
+
import { OpenSignUp } from '@ossy/authentication'
|
|
4
|
+
|
|
5
|
+
const ANALYTICS_SERVICE = '@ossy/analytics'
|
|
6
|
+
|
|
7
|
+
export function useAnalyticsHomeContent () {
|
|
8
|
+
const { t } = useLocale()
|
|
9
|
+
const router = useRouter()
|
|
10
|
+
|
|
11
|
+
const analyticsHome = router.getHref('analytics/home') || '/analytics'
|
|
12
|
+
const signUpHref = (() => {
|
|
13
|
+
const base = router.getHref('sign-up') || router.getHref('@sign-up') || '/sign-up'
|
|
14
|
+
const url = new URL(base, 'http://local.invalid')
|
|
15
|
+
url.searchParams.set('redirect', analyticsHome)
|
|
16
|
+
return `${url.pathname}${url.search}`
|
|
17
|
+
})()
|
|
18
|
+
|
|
19
|
+
const cover = {
|
|
20
|
+
title: t('analytics.home.cover.title'),
|
|
21
|
+
text: t('analytics.home.cover.text'),
|
|
22
|
+
actions: [
|
|
23
|
+
{
|
|
24
|
+
...OpenSignUp,
|
|
25
|
+
variant: 'cta',
|
|
26
|
+
href: signUpHref,
|
|
27
|
+
label: t('analytics.home.cover.ctaPrimary'),
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const features = [
|
|
33
|
+
{
|
|
34
|
+
title: t('analytics.home.features.members.title'),
|
|
35
|
+
icon: 'user',
|
|
36
|
+
text: t('analytics.home.features.members.text'),
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
title: t('analytics.home.features.resources.title'),
|
|
40
|
+
icon: 'folder',
|
|
41
|
+
text: t('analytics.home.features.resources.text'),
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
title: t('analytics.home.features.traffic.title'),
|
|
45
|
+
icon: 'chart',
|
|
46
|
+
text: t('analytics.home.features.traffic.text'),
|
|
47
|
+
},
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
return { cover, features, ANALYTICS_SERVICE }
|
|
51
|
+
}
|
package/src/analytics.page.jsx
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import {
|
|
3
|
-
import { usePageViewStats } from './usePageViewStats.js'
|
|
4
|
-
import { useWorkspaceKpis } from './useWorkspaceKpis.js'
|
|
5
|
-
import { useSdk } from '@ossy/sdk-react'
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useApp } from '@ossy/app/shell'
|
|
6
3
|
import { GetWorkspace } from '@ossy/workspaces'
|
|
4
|
+
import { isServiceEntitled } from '@ossy/workspaces/entitlements'
|
|
5
|
+
import { useSdk } from '@ossy/sdk-react'
|
|
6
|
+
import AnalyticsSalesPage from './AnalyticsSalesPage.jsx'
|
|
7
|
+
import AnalyticsProductHome from './AnalyticsProductHome.jsx'
|
|
7
8
|
|
|
8
9
|
export const metadata = {
|
|
9
10
|
id: 'analytics/home',
|
|
@@ -13,116 +14,20 @@ export const metadata = {
|
|
|
13
14
|
},
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
style={{ border: '1px solid var(--separator)' }}
|
|
23
|
-
>
|
|
24
|
-
<Text variant="small" style={{ opacity: 0.75 }}>{label}</Text>
|
|
25
|
-
<Text variant="heading-secondary" as="h2">{value}</Text>
|
|
26
|
-
</View>
|
|
27
|
-
)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export default function AnalyticsPage() {
|
|
31
|
-
const { t } = useLocale()
|
|
17
|
+
/**
|
|
18
|
+
* Analytics home — product KPIs only when signed in and entitled.
|
|
19
|
+
* Otherwise show the sales page (no KPI / page-view loads).
|
|
20
|
+
*/
|
|
21
|
+
export default function AnalyticsPage () {
|
|
22
|
+
const app = useApp()
|
|
32
23
|
const sdk = useSdk()
|
|
33
|
-
const
|
|
34
|
-
const {
|
|
35
|
-
|
|
36
|
-
return { from: now - 30 * 24 * 60 * 60 * 1000, to: now }
|
|
37
|
-
}, [])
|
|
38
|
-
|
|
39
|
-
const pageViews = usePageViewStats({
|
|
40
|
-
workspaceId: workspace?.id,
|
|
41
|
-
from,
|
|
42
|
-
to,
|
|
43
|
-
limit: 10,
|
|
44
|
-
})
|
|
45
|
-
const workspaceKpis = useWorkspaceKpis({ workspaceId: workspace?.id })
|
|
46
|
-
|
|
47
|
-
const { status, totalViews, dailyViews, topPaths } = pageViews
|
|
48
|
-
|
|
49
|
-
return (
|
|
50
|
-
<Page
|
|
51
|
-
title="analytics.home.title"
|
|
52
|
-
description="analytics.home.description"
|
|
53
|
-
>
|
|
54
|
-
<View gap="m">
|
|
55
|
-
{(workspaceKpis.status === 'loading' || status === 'loading') && (
|
|
56
|
-
<View surface="primary" roundness="m" inset="m">
|
|
57
|
-
<Text>{t('analytics.home.loading')}</Text>
|
|
58
|
-
</View>
|
|
59
|
-
)}
|
|
60
|
-
|
|
61
|
-
{workspaceKpis.status === 'error' && (
|
|
62
|
-
<View surface="primary" roundness="m" inset="m">
|
|
63
|
-
<Text>{t('analytics.home.workspaceKpisError')}</Text>
|
|
64
|
-
</View>
|
|
65
|
-
)}
|
|
66
|
-
|
|
67
|
-
{workspaceKpis.status === 'success' && (
|
|
68
|
-
<View layout="row" gap="m">
|
|
69
|
-
<SummaryCard label={t('analytics.home.members')} value={`${workspaceKpis.members}`} />
|
|
70
|
-
<SummaryCard label={t('analytics.home.resources')} value={`${workspaceKpis.resources}`} />
|
|
71
|
-
</View>
|
|
72
|
-
)}
|
|
73
|
-
|
|
74
|
-
{status === 'error' && (
|
|
75
|
-
<View surface="primary" roundness="m" inset="m">
|
|
76
|
-
<Text>{t('analytics.home.error')}</Text>
|
|
77
|
-
</View>
|
|
78
|
-
)}
|
|
79
|
-
|
|
80
|
-
{status === 'success' && (
|
|
81
|
-
<>
|
|
82
|
-
<View layout="row" gap="m">
|
|
83
|
-
<SummaryCard label={t('analytics.home.totalPageViews')} value={`${totalViews}`} />
|
|
84
|
-
<SummaryCard label={t('analytics.home.trackedPages')} value={`${topPaths.length}`} />
|
|
85
|
-
<SummaryCard label={t('analytics.home.daysWithTraffic')} value={`${dailyViews.length}`} />
|
|
86
|
-
</View>
|
|
24
|
+
const isAuthenticated = !!app?.isAuthenticated
|
|
25
|
+
const { data: workspace } = sdk.read(GetWorkspace, undefined, { enabled: isAuthenticated })
|
|
26
|
+
const entitled = isServiceEntitled(workspace?.services, '@ossy/analytics')
|
|
87
27
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
roundness="m"
|
|
92
|
-
inset="m"
|
|
93
|
-
gap="s"
|
|
94
|
-
style={{ border: '1px solid var(--separator)', flex: 1 }}
|
|
95
|
-
>
|
|
96
|
-
<Text variant="heading-secondary" as="h2" text="analytics.home.topPaths" />
|
|
97
|
-
{topPaths.length === 0 && <Text variant="small" text="analytics.home.noPageViews" />}
|
|
98
|
-
{topPaths.map((item) => (
|
|
99
|
-
<View key={item.path || 'unknown'} layout="row" justifyContent="space-between">
|
|
100
|
-
<Text variant="small">{item.path || t('analytics.home.unknownPath')}</Text>
|
|
101
|
-
<Text variant="small">{item.views}</Text>
|
|
102
|
-
</View>
|
|
103
|
-
))}
|
|
104
|
-
</View>
|
|
28
|
+
if (isAuthenticated && entitled) {
|
|
29
|
+
return <AnalyticsProductHome />
|
|
30
|
+
}
|
|
105
31
|
|
|
106
|
-
|
|
107
|
-
surface="primary"
|
|
108
|
-
roundness="m"
|
|
109
|
-
inset="m"
|
|
110
|
-
gap="s"
|
|
111
|
-
style={{ border: '1px solid var(--separator)', flex: 1 }}
|
|
112
|
-
>
|
|
113
|
-
<Text variant="heading-secondary" as="h2" text="analytics.home.dailyPageViews" />
|
|
114
|
-
{dailyViews.length === 0 && <Text variant="small" text="analytics.home.noDailyData" />}
|
|
115
|
-
{dailyViews.map((item) => (
|
|
116
|
-
<View key={item.date} layout="row" justifyContent="space-between">
|
|
117
|
-
<Text variant="small">{item.date}</Text>
|
|
118
|
-
<Text variant="small">{item.views}</Text>
|
|
119
|
-
</View>
|
|
120
|
-
))}
|
|
121
|
-
</View>
|
|
122
|
-
</View>
|
|
123
|
-
</>
|
|
124
|
-
)}
|
|
125
|
-
</View>
|
|
126
|
-
</Page>
|
|
127
|
-
)
|
|
32
|
+
return <AnalyticsSalesPage isAuthenticated={isAuthenticated} />
|
|
128
33
|
}
|
package/src/en.translations.json
CHANGED
|
@@ -14,5 +14,19 @@
|
|
|
14
14
|
"analytics.home.noPageViews": "No page views yet.",
|
|
15
15
|
"analytics.home.unknownPath": "(unknown path)",
|
|
16
16
|
"analytics.home.dailyPageViews": "Daily page views",
|
|
17
|
-
"analytics.home.noDailyData": "No daily data yet."
|
|
17
|
+
"analytics.home.noDailyData": "No daily data yet.",
|
|
18
|
+
"analytics.home.cover.title": "See how your workspace is used",
|
|
19
|
+
"analytics.home.cover.text": "Members, resources, and page traffic — insight KPIs for the teams building on the platform.",
|
|
20
|
+
"analytics.home.cover.ctaPrimary": "Get started free",
|
|
21
|
+
"analytics.home.features.members.title": "Members",
|
|
22
|
+
"analytics.home.features.members.text": "Track how many people are in the workspace.",
|
|
23
|
+
"analytics.home.features.resources.title": "Resources",
|
|
24
|
+
"analytics.home.features.resources.text": "See how much content and data you store.",
|
|
25
|
+
"analytics.home.features.traffic.title": "Traffic",
|
|
26
|
+
"analytics.home.features.traffic.text": "Page views over the last 30 days, by path and by day.",
|
|
27
|
+
"analytics.sales.overview": "Overview",
|
|
28
|
+
"analytics.sales.features": "Features",
|
|
29
|
+
"analytics.sales.enable": "Enable analytics",
|
|
30
|
+
"analytics.sales.enableDescription": "Activate analytics for your workspace to view member, resource, and traffic KPIs.",
|
|
31
|
+
"analytics.sales.enableError": "Could not enable analytics. Try again or contact support."
|
|
18
32
|
}
|
package/src/sv.translations.json
CHANGED
|
@@ -14,5 +14,19 @@
|
|
|
14
14
|
"analytics.home.noPageViews": "Inga sidvisningar ännu.",
|
|
15
15
|
"analytics.home.unknownPath": "(okänd sökväg)",
|
|
16
16
|
"analytics.home.dailyPageViews": "Dagliga sidvisningar",
|
|
17
|
-
"analytics.home.noDailyData": "Ingen daglig data ännu."
|
|
17
|
+
"analytics.home.noDailyData": "Ingen daglig data ännu.",
|
|
18
|
+
"analytics.home.cover.title": "Se hur workspace används",
|
|
19
|
+
"analytics.home.cover.text": "Medlemmar, resurser och sidtrafik — KPIer för team som bygger på plattformen.",
|
|
20
|
+
"analytics.home.cover.ctaPrimary": "Kom igång gratis",
|
|
21
|
+
"analytics.home.features.members.title": "Medlemmar",
|
|
22
|
+
"analytics.home.features.members.text": "Se hur många personer som finns i workspace.",
|
|
23
|
+
"analytics.home.features.resources.title": "Resurser",
|
|
24
|
+
"analytics.home.features.resources.text": "Följ hur mycket innehåll och data ni lagrar.",
|
|
25
|
+
"analytics.home.features.traffic.title": "Trafik",
|
|
26
|
+
"analytics.home.features.traffic.text": "Sidvisningar de senaste 30 dagarna, per sökväg och dag.",
|
|
27
|
+
"analytics.sales.overview": "Översikt",
|
|
28
|
+
"analytics.sales.features": "Funktioner",
|
|
29
|
+
"analytics.sales.enable": "Aktivera analys",
|
|
30
|
+
"analytics.sales.enableDescription": "Aktivera analys för workspace för att se KPIer för medlemmar, resurser och trafik.",
|
|
31
|
+
"analytics.sales.enableError": "Kunde inte aktivera analys. Försök igen eller kontakta support."
|
|
18
32
|
}
|