orionplace-react 0.3.1 → 0.5.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 +39 -4
- package/bin/orionplace-react.mjs +38 -7
- package/package.json +1 -1
- package/runtime/marketplace.mjs +12 -0
- package/src/install.mjs +5 -4
- package/src/project-readme.mjs +3 -0
- package/src/service.mjs +186 -0
- package/templates/app/StorefrontShell.tsx +8 -0
- package/templates/app/_storefront/PrivacyPage.tsx +15 -0
- package/templates/app/_storefront/storefrontRoutes.tsx +3 -0
- package/templates/app/privacy/page.tsx +9 -0
- package/templates/components/orionplace/blog/BlogArticle.README.md +16 -0
- package/templates/components/orionplace/blog/BlogArticle.module.css +5 -0
- package/templates/components/orionplace/blog/BlogArticle.tsx +60 -1
- package/templates/components/orionplace/legal/CookieNotice.README.md +42 -0
- package/templates/components/orionplace/legal/CookieNotice.module.css +48 -0
- package/templates/components/orionplace/legal/CookieNotice.tsx +82 -0
- package/templates/components/orionplace/legal/StorefrontPrivacy.README.md +49 -0
- package/templates/components/orionplace/legal/StorefrontPrivacy.module.css +166 -0
- package/templates/components/orionplace/legal/StorefrontPrivacy.tsx +83 -0
- package/templates/components/orionplace/products/ProductReviewImageViewer.README.md +13 -2
- package/templates/components/orionplace/products/ProductReviewImageViewer.module.css +10 -1
- package/templates/components/orionplace/products/ProductReviewImageViewer.tsx +61 -9
- package/templates/components/orionplace/storefront/StorefrontFooter.tsx +3 -0
- package/templates/components/orionplace/subscriptions/StorefrontTariff.README.md +13 -0
- package/templates/components/orionplace/subscriptions/StorefrontTariff.tsx +17 -6
- package/templates/components/orionplace/subscriptions/StorefrontTariffs.README.md +13 -0
- package/templates/components/orionplace/subscriptions/StorefrontTariffs.tsx +19 -2
- package/templates/components/orionplace/tariffs/TariffCard.tsx +8 -1
- package/templates/components/orionplace/tariffs/TariffCardPrices.README.md +10 -2
- package/templates/components/orionplace/tariffs/TariffCardPrices.tsx +12 -2
- package/templates/hooks/useOrionplaceTariffPurchase.ts +60 -0
- package/templates/lib/orionplace/blogImageViewer.ts +84 -0
- package/templates/lib/orionplace/blogLabels.ts +4 -0
- package/templates/lib/orionplace/footerLabels.ts +2 -0
- package/templates/lib/orionplace/pageMetadata.ts +1 -0
- package/templates/lib/orionplace/pageSeo.ts +10 -0
- package/templates/lib/orionplace/privacyLabels.ts +198 -0
- package/templates/lib/orionplace/reviewLabels.ts +4 -0
- package/templates/lib/orionplace/tariffLabels.ts +2 -0
- package/templates/lib/orionplace/tariffs.ts +49 -0
- package/templates/stores/orionplaceCatalog.ts +18 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Texts of the privacy page (/privacy) and the cookie notice.
|
|
3
|
+
*
|
|
4
|
+
* The page describes what the storefront itself does: the account, orders,
|
|
5
|
+
* the two platform cookies and the notice's own storage mark. A store that
|
|
6
|
+
* adds analytics, chat widgets or other cookies lists them in the cookies
|
|
7
|
+
* section here. The operator is the store's company from the platform's
|
|
8
|
+
* contact details; `{company}` in a text is replaced with its name.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export interface PrivacySection {
|
|
12
|
+
id: string;
|
|
13
|
+
title: string;
|
|
14
|
+
text: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PrivacyLabels {
|
|
18
|
+
title: string;
|
|
19
|
+
lead: string;
|
|
20
|
+
contents: string;
|
|
21
|
+
operator: string;
|
|
22
|
+
operatorUnknown: string;
|
|
23
|
+
sections: PrivacySection[];
|
|
24
|
+
cookie: {
|
|
25
|
+
label: string;
|
|
26
|
+
text: string;
|
|
27
|
+
link: string;
|
|
28
|
+
ok: string;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Local storage mark: the visitor has dismissed the cookie notice. */
|
|
33
|
+
export const COOKIE_NOTICE_STORAGE_KEY = 'orionplace-cookie-notice-seen';
|
|
34
|
+
|
|
35
|
+
const ru: PrivacyLabels = {
|
|
36
|
+
title: 'Политика обработки персональных данных',
|
|
37
|
+
lead: 'Какие данные магазин получает, зачем, кому их передаёт и как долго хранит.',
|
|
38
|
+
contents: 'Содержание',
|
|
39
|
+
operator: 'Оператор персональных данных',
|
|
40
|
+
operatorUnknown: 'Реквизиты оператора указаны на странице «Контакты».',
|
|
41
|
+
sections: [
|
|
42
|
+
{
|
|
43
|
+
id: 'operator',
|
|
44
|
+
title: '1. Кто обрабатывает данные',
|
|
45
|
+
text: [
|
|
46
|
+
'Оператор персональных данных — {company} (далее — Магазин). Реквизиты Магазина указаны в конце страницы.',
|
|
47
|
+
'Обработка ведётся в соответствии с Федеральным законом № 152-ФЗ «О персональных данных».',
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'data',
|
|
52
|
+
title: '2. Какие данные',
|
|
53
|
+
text: [
|
|
54
|
+
'Аккаунт: номер телефона, а если вы их укажете — имя, адрес электронной почты, дата рождения и фото.',
|
|
55
|
+
'Заказы: товары, адреса доставки, имя и телефон получателя, суммы, способ и статус оплаты, возвраты. Данные карты вводятся на странице платёжного провайдера; Магазин номера карт не получает.',
|
|
56
|
+
'Обращения, отзывы и комментарии, избранное, подписки на товары и история поиска.',
|
|
57
|
+
'История входов: дата и время, IP-адрес, браузер и тип устройства.',
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'purposes',
|
|
62
|
+
title: '3. Зачем',
|
|
63
|
+
text: [
|
|
64
|
+
'Чтобы вы могли войти, оформить, оплатить, получить и вернуть заказ, получить ответ на обращение и опубликовать отзыв; чтобы защищать аккаунт и выполнять требования закона. Письма о новинках и акциях приходят, только если вы их включили.',
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: 'recipients',
|
|
69
|
+
title: '4. Кому передаются данные',
|
|
70
|
+
text: [
|
|
71
|
+
'Только тем, без кого заказ не выполнить, и в нужном объёме: службам доставки, платёжным провайдерам, операторам связи (для кодов подтверждения) и платформе, на серверах которой работает Магазин. Данные не продаются.',
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'storage',
|
|
76
|
+
title: '5. Сколько хранятся',
|
|
77
|
+
text: [
|
|
78
|
+
'Пока существует аккаунт. Записи о заказах и платежах хранятся столько, сколько требует закон о бухгалтерском учёте.',
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'rights',
|
|
83
|
+
title: '6. Ваши права',
|
|
84
|
+
text: [
|
|
85
|
+
'В профиле можно изменить свои данные, отключить письма и удалить аккаунт. Также можно узнать, какие данные обрабатываются, потребовать их уточнить или удалить и отозвать согласие: напишите в поддержку или на странице «Контакты».',
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 'cookies',
|
|
90
|
+
title: '7. Cookie и локальное хранилище',
|
|
91
|
+
text: [
|
|
92
|
+
'Сайт использует только необходимые для работы cookie: sessionid сохраняет вход в аккаунт, csrftoken защищает формы от подделки запросов.',
|
|
93
|
+
'В локальном хранилище браузера сохраняется отметка о том, что вы видели уведомление о cookie. Рекламных и аналитических cookie нет. Если запретить cookie, вход и оформление заказа не будут работать.',
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 'changes',
|
|
98
|
+
title: '8. Изменение политики',
|
|
99
|
+
text: [
|
|
100
|
+
'Новая редакция действует с момента публикации на этой странице.',
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
cookie: {
|
|
105
|
+
label: 'Уведомление о cookie',
|
|
106
|
+
text: 'Сайт использует только необходимые cookie: для входа в аккаунт и защиты форм.',
|
|
107
|
+
link: 'Подробнее',
|
|
108
|
+
ok: 'Понятно',
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const en: PrivacyLabels = {
|
|
113
|
+
title: 'Privacy policy',
|
|
114
|
+
lead: 'Which data the store receives, why, who it shares it with and how long it keeps it.',
|
|
115
|
+
contents: 'Contents',
|
|
116
|
+
operator: 'Data controller',
|
|
117
|
+
operatorUnknown: 'The controller’s details are on the Contact page.',
|
|
118
|
+
sections: [
|
|
119
|
+
{
|
|
120
|
+
id: 'operator',
|
|
121
|
+
title: '1. Who processes the data',
|
|
122
|
+
text: [
|
|
123
|
+
'The data controller is {company} (the Store). The Store’s details are at the end of this page.',
|
|
124
|
+
'Data is processed under Russian Federal Law No. 152-FZ “On Personal Data”.',
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: 'data',
|
|
129
|
+
title: '2. Which data',
|
|
130
|
+
text: [
|
|
131
|
+
'Account: phone number and, if you add them, name, email address, date of birth and photo.',
|
|
132
|
+
'Orders: products, delivery addresses, recipient name and phone, amounts, payment method and status, refunds. Card details are entered on the payment provider’s page; the Store does not receive card numbers.',
|
|
133
|
+
'Support requests, reviews and comments, wishlist, product subscriptions and search history.',
|
|
134
|
+
'Sign-in history: date and time, IP address, browser and device type.',
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: 'purposes',
|
|
139
|
+
title: '3. Why',
|
|
140
|
+
text: [
|
|
141
|
+
'To let you sign in, place, pay for, receive and return orders, get answers to requests and publish reviews; to protect the account and meet legal duties. News and promotion emails come only if you turned them on.',
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: 'recipients',
|
|
146
|
+
title: '4. Who receives the data',
|
|
147
|
+
text: [
|
|
148
|
+
'Only those needed to fulfil an order, and only as much as they need: delivery services, payment providers, telecom operators (for confirmation codes) and the platform whose servers run the Store. The data is not sold.',
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: 'storage',
|
|
153
|
+
title: '5. How long it is kept',
|
|
154
|
+
text: [
|
|
155
|
+
'While the account exists. Order and payment records are kept as long as accounting law requires.',
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: 'rights',
|
|
160
|
+
title: '6. Your rights',
|
|
161
|
+
text: [
|
|
162
|
+
'In the profile you can change your details, turn emails off and delete the account. You can also ask which data is processed, ask to correct or delete it and withdraw consent: write to support or use the Contact page.',
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: 'cookies',
|
|
167
|
+
title: '7. Cookies and local storage',
|
|
168
|
+
text: [
|
|
169
|
+
'The site uses only the cookies it needs to work: sessionid keeps you signed in, csrftoken protects forms from request forgery.',
|
|
170
|
+
'The browser’s local storage keeps the fact that you have seen the cookie notice. There are no advertising or analytics cookies. If you block cookies, signing in and checking out do not work.',
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: 'changes',
|
|
175
|
+
title: '8. Changes to this policy',
|
|
176
|
+
text: ['A new version applies from its publication on this page.'],
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
cookie: {
|
|
180
|
+
label: 'Cookie notice',
|
|
181
|
+
text: 'The site uses only the cookies it needs: to sign you in and to protect forms.',
|
|
182
|
+
link: 'Learn more',
|
|
183
|
+
ok: 'OK',
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export function privacyLabels(locale: string, company = ''): PrivacyLabels {
|
|
188
|
+
const copy = locale.toLowerCase().startsWith('ru') ? ru : en;
|
|
189
|
+
const name = company || (copy === ru ? 'Магазин' : 'the Store');
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
...copy,
|
|
193
|
+
sections: copy.sections.map((section) => ({
|
|
194
|
+
...section,
|
|
195
|
+
text: section.text.map((line) => line.replaceAll('{company}', name)),
|
|
196
|
+
})),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
@@ -47,6 +47,8 @@ const en = {
|
|
|
47
47
|
less: 'Less',
|
|
48
48
|
gallery: 'Product images',
|
|
49
49
|
previousImage: 'Previous image',
|
|
50
|
+
zoomIn: 'Show at full size',
|
|
51
|
+
zoomOut: 'Fit to screen',
|
|
50
52
|
nextImage: 'Next image',
|
|
51
53
|
thumbnailsBack: 'Show previous thumbnails',
|
|
52
54
|
thumbnailsForward: 'Show next thumbnails',
|
|
@@ -124,6 +126,8 @@ const ru: typeof en = {
|
|
|
124
126
|
less: 'Свернуть',
|
|
125
127
|
gallery: 'Фотографии товара',
|
|
126
128
|
previousImage: 'Предыдущая фотография',
|
|
129
|
+
zoomIn: 'Показать в полный размер',
|
|
130
|
+
zoomOut: 'Вписать в экран',
|
|
127
131
|
nextImage: 'Следующая фотография',
|
|
128
132
|
thumbnailsBack: 'Показать предыдущие миниатюры',
|
|
129
133
|
thumbnailsForward: 'Показать следующие миниатюры',
|
|
@@ -9,6 +9,7 @@ const labels = {
|
|
|
9
9
|
adminOnly:
|
|
10
10
|
'This plan is assigned by the store administrator. It cannot be subscribed to online.',
|
|
11
11
|
closed: 'New subscriptions to this plan are closed.',
|
|
12
|
+
trialUsed: 'You have already used the trial of this subscription.',
|
|
12
13
|
subscribe: 'Subscribe',
|
|
13
14
|
tryTrial: 'Try',
|
|
14
15
|
perPeriod: 'for {days}',
|
|
@@ -56,6 +57,7 @@ const labels = {
|
|
|
56
57
|
adminOnly:
|
|
57
58
|
'Этот тариф подключает администратор магазина. Самостоятельное подключение недоступно.',
|
|
58
59
|
closed: 'Новые подключения к этому тарифу закрыты.',
|
|
60
|
+
trialUsed: 'Пробный период этой подписки вы уже использовали.',
|
|
59
61
|
subscribe: 'Подключить',
|
|
60
62
|
tryTrial: 'Попробовать',
|
|
61
63
|
perPeriod: 'за {days}',
|
|
@@ -133,6 +133,55 @@ export function tariffSubscribeRequest(
|
|
|
133
133
|
};
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
/**
|
|
137
|
+
* The subscription records the customer already had a period of, paid or
|
|
138
|
+
* trial, read from the `subscriptions_get` response. The backend gives a trial
|
|
139
|
+
* only to a customer new to the record, so the plans of these records offer
|
|
140
|
+
* none.
|
|
141
|
+
*/
|
|
142
|
+
export function tariffUsedSubscriptions(page: {
|
|
143
|
+
data: { subscription: number | null; tariff: number | null }[];
|
|
144
|
+
tariffs: { id: number; subscription: number }[];
|
|
145
|
+
}) {
|
|
146
|
+
const ids = page.data.map(
|
|
147
|
+
(item) =>
|
|
148
|
+
item.subscription ??
|
|
149
|
+
page.tariffs.find((tariff) => tariff.id === item.tariff)?.subscription,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
return [...new Set(ids.filter((id): id is number => typeof id === 'number'))];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Whether the plan offers its trial: it has trial days and the customer is
|
|
157
|
+
* not known to have had its subscription record (`used`, see
|
|
158
|
+
* `tariffUsedSubscriptions`). A guest and a customer whose records are not
|
|
159
|
+
* loaded yet see the trial; the backend refuses it to a returning customer.
|
|
160
|
+
*/
|
|
161
|
+
export function tariffTrialOpen(
|
|
162
|
+
tariff: ISubscribeTariff,
|
|
163
|
+
used: readonly number[] = [],
|
|
164
|
+
) {
|
|
165
|
+
return tariff.trial_days > 0 && !used.includes(tariff.subscription);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The plan choice to open again after sign-in: a paid period as it is, a
|
|
170
|
+
* trial only once the customer's records are loaded (`used`) and the
|
|
171
|
+
* customer is new to the record.
|
|
172
|
+
*/
|
|
173
|
+
export function tariffResumable(
|
|
174
|
+
selection: ITariffSelection | undefined,
|
|
175
|
+
tariff: ISubscribeTariff | undefined,
|
|
176
|
+
used: readonly number[] | undefined,
|
|
177
|
+
) {
|
|
178
|
+
if (!selection || selection.price !== null) {
|
|
179
|
+
return selection;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return tariff && used && tariffTrialOpen(tariff, used) ? selection : undefined;
|
|
183
|
+
}
|
|
184
|
+
|
|
136
185
|
export interface TariffFeature {
|
|
137
186
|
key: string;
|
|
138
187
|
label: string;
|
|
@@ -23,9 +23,16 @@ export interface OrionplaceCatalogState {
|
|
|
23
23
|
tariffs: IAppLoadCommon['tariffs'] | null;
|
|
24
24
|
/** Every plan the storefront sells; the subscriptions page reads it. */
|
|
25
25
|
storeTariffs: IAppLoadCommon['tariffs'] | null;
|
|
26
|
+
/**
|
|
27
|
+
* The subscription records the signed-in customer already had, for the
|
|
28
|
+
* profile that loaded them (see `tariffUsedSubscriptions`). A new
|
|
29
|
+
* Application Load keeps them; another profile ignores them.
|
|
30
|
+
*/
|
|
31
|
+
usedSubscriptions?: { profile: number; ids: number[] };
|
|
26
32
|
setCatalogTree: (tree: CatalogTree) => void;
|
|
27
33
|
setTariffs: (tariffs: Tariffs) => void;
|
|
28
34
|
setStoreTariffs: (tariffs: Tariffs) => void;
|
|
35
|
+
setUsedSubscriptions: (profile: number, ids: number[]) => void;
|
|
29
36
|
getTariff: (id: number) => Tariffs['data'][number] | undefined;
|
|
30
37
|
getCatalogCategory: (id: number) => ICategory | undefined;
|
|
31
38
|
getCatalogProducts: (
|
|
@@ -81,6 +88,10 @@ export function createOrionplaceCatalogStore(
|
|
|
81
88
|
});
|
|
82
89
|
},
|
|
83
90
|
|
|
91
|
+
setUsedSubscriptions(profile, ids) {
|
|
92
|
+
set({ usedSubscriptions: { profile, ids } });
|
|
93
|
+
},
|
|
94
|
+
|
|
84
95
|
getTariff(id) {
|
|
85
96
|
return get().tariffs?.data.find((tariff) => tariff.id === id);
|
|
86
97
|
},
|
|
@@ -131,6 +142,13 @@ export function createOrionplaceCatalogStore(
|
|
|
131
142
|
}));
|
|
132
143
|
}
|
|
133
144
|
|
|
145
|
+
/** The records a profile had; undefined until they are loaded. */
|
|
146
|
+
export const selectUsedSubscriptions =
|
|
147
|
+
(profile: number | undefined) => (state: OrionplaceCatalogState) =>
|
|
148
|
+
state.usedSubscriptions && state.usedSubscriptions.profile === profile
|
|
149
|
+
? state.usedSubscriptions.ids
|
|
150
|
+
: undefined;
|
|
151
|
+
|
|
134
152
|
export const selectCatalogRoots = (state: OrionplaceCatalogState) =>
|
|
135
153
|
navigationCatalogCategories(state.catalog);
|
|
136
154
|
|