@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 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` | Workspace members (entitled package) |
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.0",
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.4.0"
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": "d36b69444268d172bc3e8e1dc77e67afc63f8650"
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
+ }
@@ -1,9 +1,10 @@
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'
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
- function SummaryCard({ label, value }) {
17
- return (
18
- <View
19
- surface="primary"
20
- roundness="m"
21
- inset="m"
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 { data: workspace } = sdk.read(GetWorkspace)
34
- const { from, to } = useMemo(() => {
35
- const now = Date.now()
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
- <View layout="row" gap="m" style={{ alignItems: 'flex-start' }}>
89
- <View
90
- surface="primary"
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
- <View
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
  }
@@ -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
  }
@@ -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
  }