PluginProbe
Extendify / 0.5.0
Extendify v0.5.0
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / src / state / User.js

User.js in Extendify 0.5.0, at src/state/User.js

187 lines 7.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { sample } from 'lodash'
2 import create from 'zustand'
3 import { persist } from 'zustand/middleware'
4 import { User } from '@extendify/api/User'
5
6 const storage = {
7 getItem: async () => await User.getData(),
8 setItem: async (_name, value) => await User.setData(value),
9 removeItem: async () => await User.deleteData(),
10 }
11
12 const isGlobalLibraryEnabled = () =>
13 window.extendifyData.sitesettings === null ||
14 window.extendifyData?.sitesettings?.state?.enabled
15
16 // Keep track of active tests as some might be active
17 // but never rendered.
18 const activeTests = {
19 ['notice-position']: '0001',
20 ['main-button-text']: '0002',
21 ['import-counter-color']: '0003',
22 }
23
24 export const useUserStore = create(
25 persist(
26 (set, get) => ({
27 _hasHydrated: false,
28 firstLoadedOn: new Date().toISOString(),
29 email: '',
30 apiKey: '',
31 uuid: '',
32 group: 0,
33 sdkPartner: '',
34 noticesDismissedAt: {},
35 modalNoticesDismissedAt: {},
36 imports: 0, // total imports over time
37 runningImports: 0, // timed imports, resets to 0 every month
38 allowedImports: 0, // Max imports the Extendify service allows
39 freebieImports: 0, // Various free imports from actions (rewards)
40 entryPoint: 'not-set',
41 enabled: isGlobalLibraryEnabled(),
42 canInstallPlugins: false,
43 canActivatePlugins: false,
44 participatingTestsGroups: {},
45 preferredOptions: {
46 taxonomies: {},
47 type: '',
48 search: '',
49 },
50 preferredOptionsHistory: {
51 siteType: [],
52 },
53 incrementImports: () => {
54 // If the user has freebie imports, use those first
55 const freebieImports =
56 Number(get().freebieImports) > 0
57 ? Number(get().freebieImports) - 1
58 : Number(get().freebieImports)
59 // If they don't, then increment the running imports
60 const runningImports =
61 Number(get().runningImports) + +(freebieImports < 1)
62 set({
63 imports: Number(get().imports) + 1,
64 runningImports,
65 freebieImports,
66 })
67 },
68 giveFreebieImports: (amount) => {
69 set({ freebieImports: get().freebieImports + amount })
70 },
71 totalAvailableImports: () => {
72 return (
73 Number(get().allowedImports) + Number(get().freebieImports)
74 )
75 },
76 testGroup(testKey, groupOptions) {
77 if (!get().uuid) return
78 if (!Object.keys(activeTests).includes(testKey)) return
79 const groups = get().participatingTestsGroups
80 // If the test is already in the group, don't add it again
81 if (!groups[testKey]) {
82 set({
83 participatingTestsGroups: Object.assign({}, groups, {
84 [testKey]: sample(groupOptions),
85 }),
86 })
87 }
88 return groups[testKey]
89 },
90 activeTestGroups() {
91 return Object.entries(get().participatingTestsGroups)
92 .filter(([key]) => Object.keys(activeTests).includes(key))
93 .reduce((obj, [key, value]) => {
94 obj[key] = value
95 return obj
96 }, {})
97 },
98 activeTestGroupsUtmValue() {
99 const active = Object.entries(get().activeTestGroups())
100 .map(([key, value]) => {
101 return `${activeTests[key]}=${value}`
102 }, '')
103 .join(':')
104 return encodeURIComponent(active)
105 },
106 hasAvailableImports: () => {
107 return get().apiKey
108 ? true
109 : Number(get().runningImports) <
110 Number(get().totalAvailableImports())
111 },
112 remainingImports: () => {
113 const remaining =
114 Number(get().totalAvailableImports()) -
115 Number(get().runningImports)
116 // If they have no allowed imports, this might be a first load
117 // where it's just fetching templates (and/or their max alllowed)
118 if (!get().allowedImports) {
119 return null
120 }
121 return remaining > 0 ? remaining : 0
122 },
123 updatePreferredSiteType: (value) => {
124 get().updatePreferredOption('siteType', value)
125 if (!value?.slug || value.slug === 'unknown') return
126 const current = get().preferredOptionsHistory?.siteType ?? []
127
128 // If the site type isn't already included, prepend it
129 if (!current.find((t) => t.slug === value.slug)) {
130 const siteType = [value, ...current]
131 set({
132 preferredOptionsHistory: Object.assign(
133 {},
134 get().preferredOptionsHistory,
135 { siteType: siteType.slice(0, 3) },
136 ),
137 })
138 }
139 },
140 updatePreferredOption: (option, value) => {
141 // If the option doesn't exist, assume it's a taxonomy
142 if (
143 !Object.prototype.hasOwnProperty.call(
144 get().preferredOptions,
145 option,
146 )
147 ) {
148 value = Object.assign(
149 {},
150 get().preferredOptions?.taxonomies ?? {},
151 { [option]: value },
152 )
153 option = 'taxonomies'
154 }
155
156 set({
157 preferredOptions: {
158 ...Object.assign({}, get().preferredOptions, {
159 [option]: value,
160 }),
161 },
162 })
163 },
164 // Will mark a modal or footer notice
165 markNoticeSeen: (key, type) => {
166 set({
167 [`${type}DismissedAt`]: {
168 ...get()[`${type}DismissedAt`],
169 [key]: new Date().toISOString(),
170 },
171 })
172 },
173 }),
174 {
175 name: 'extendify-user',
176 getStorage: () => storage,
177 onRehydrateStorage: () => () => {
178 useUserStore.setState({ _hasHydrated: true })
179 },
180 partialize: (state) => {
181 delete state._hasHydrated
182 return state
183 },
184 },
185 ),
186 )
187