@shoppexio/storefront-react 0.2.1 → 0.4.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/dist/account.css +4 -8
  3. package/dist/account.js +2387 -942
  4. package/dist/account.js.map +1 -1
  5. package/dist/{chunk-ZEFZPUAW.js → chunk-NBOIS53R.js} +2 -1
  6. package/dist/chunk-NBOIS53R.js.map +1 -0
  7. package/dist/index.d.ts +243 -29
  8. package/dist/index.js +221 -77
  9. package/dist/index.js.map +1 -1
  10. package/eject/account/account-availability.ts +4 -1
  11. package/eject/account/account-downloads.ts +89 -1
  12. package/eject/account/account-referrals.ts +249 -7
  13. package/eject/account/account-reseller.ts +6 -140
  14. package/eject/account/dashboard.tsx +1 -0
  15. package/eject/account/downloads.tsx +73 -4
  16. package/eject/account/hosted-page.ts +45 -0
  17. package/eject/account/messages/ar.json +94 -31
  18. package/eject/account/messages/de.json +94 -31
  19. package/eject/account/messages/en.json +94 -31
  20. package/eject/account/messages/es.json +94 -31
  21. package/eject/account/messages/fi.json +94 -31
  22. package/eject/account/messages/fr.json +94 -31
  23. package/eject/account/messages/hi.json +94 -31
  24. package/eject/account/messages/it.json +94 -31
  25. package/eject/account/messages/lt.json +94 -31
  26. package/eject/account/messages/nl.json +94 -31
  27. package/eject/account/messages/pl.json +94 -31
  28. package/eject/account/messages/pt.json +94 -31
  29. package/eject/account/messages/ru.json +94 -31
  30. package/eject/account/messages/tr.json +94 -31
  31. package/eject/account/messages/uk.json +94 -31
  32. package/eject/account/messages/zh.json +94 -31
  33. package/eject/account/orders.tsx +141 -0
  34. package/eject/account/overview.tsx +6 -0
  35. package/eject/account/referrals.tsx +444 -156
  36. package/eject/account/reseller.tsx +59 -306
  37. package/eject/account/rewards.tsx +43 -6
  38. package/eject/account/settings.tsx +98 -2
  39. package/eject/account/shared.tsx +42 -3
  40. package/eject/account/subscriptions.tsx +75 -16
  41. package/package.json +7 -4
  42. package/dist/chunk-ZEFZPUAW.js.map +0 -1
@@ -19,7 +19,9 @@ import { useState } from 'react';
19
19
  import { Link } from 'react-router-dom';
20
20
  import { accountOrderHref } from '@shoppexio/storefront-react';
21
21
  import {
22
+ accessRequestTicket,
22
23
  buildDownloadGroups,
24
+ describeFileAccess,
23
25
  isEmptyDownloadGroups,
24
26
  licenseExpiryDate,
25
27
  offersRenewal,
@@ -50,6 +52,8 @@ export function AccountDownloads() {
50
52
  const [notice, setNotice] = useState<string | null>(null);
51
53
  const [actionError, setActionError] = useState<string | null>(null);
52
54
  const [resettingUniqid, setResettingUniqid] = useState<string | null>(null);
55
+ const [askingId, setAskingId] = useState<string | null>(null);
56
+ const [askedIds, setAskedIds] = useState<ReadonlySet<string>>(() => new Set());
53
57
 
54
58
  if (state.status === 'loading') {
55
59
  return <AccountRowsSkeleton rows={4} label={t('downloads.loading')} />;
@@ -108,6 +112,29 @@ export function AccountDownloads() {
108
112
  reload();
109
113
  };
110
114
 
115
+ /*
116
+ * "Ask for access" is a support ticket on the order, through the same
117
+ * `createTicket` the Support panel uses, so the shop answers it where it
118
+ * answers everything else. One request in flight; a row that asked once says
119
+ * so instead of offering a second ticket.
120
+ */
121
+ const askForAccess = async (file: FileDownload, reason: string | null) => {
122
+ if (askingId !== null) return;
123
+ setAskingId(file.id);
124
+ setNotice(null);
125
+ setActionError(null);
126
+ const result = await callCustomerApi((sdk) => sdk.createTicket(accessRequestTicket(file, reason)));
127
+ setAskingId(null);
128
+
129
+ if (!result.success) {
130
+ setActionError(result.message?.trim() || t('downloads.accessRequestFailed'));
131
+ return;
132
+ }
133
+
134
+ setAskedIds((current) => new Set(current).add(file.id));
135
+ setNotice(t('downloads.accessRequestDone'));
136
+ };
137
+
111
138
  return (
112
139
  <div className="sxa:space-y-8">
113
140
  {notice ? <AccountNotice message={notice} /> : null}
@@ -137,7 +164,13 @@ export function AccountDownloads() {
137
164
  <ul className="sxa:mt-4 sxa:space-y-3">
138
165
  {groups.files.map((file) => (
139
166
  <li key={file.id}>
140
- <FileRow file={file} />
167
+ <FileRow
168
+ file={file}
169
+ asking={askingId === file.id}
170
+ asked={askedIds.has(file.id)}
171
+ disabled={askingId !== null}
172
+ onAskForAccess={(reason) => void askForAccess(file, reason)}
173
+ />
141
174
  </li>
142
175
  ))}
143
176
  </ul>
@@ -267,7 +300,19 @@ function LicenseRow({
267
300
  );
268
301
  }
269
302
 
270
- function FileRow({ file }: { file: FileDownload }) {
303
+ function FileRow({
304
+ file,
305
+ asking,
306
+ asked,
307
+ disabled,
308
+ onAskForAccess,
309
+ }: {
310
+ file: FileDownload;
311
+ asking: boolean;
312
+ asked: boolean;
313
+ disabled: boolean;
314
+ onAskForAccess: (reason: string | null) => void;
315
+ }) {
271
316
  /*
272
317
  * A plain link, not a fetch. The path is same-origin under `/api/customer/`,
273
318
  * so the browser sends the session cookie the Shoppex worker owns — there is
@@ -279,21 +324,45 @@ function FileRow({ file }: { file: FileDownload }) {
279
324
  * because a dead link dressed as a button is worse than a plain refusal.
280
325
  */
281
326
  const href = safeExternalHref(file.href);
327
+ /*
328
+ * What is left and until when, or why the file closed. A file the server
329
+ * refuses offers no Download: the link would only answer with that refusal.
330
+ * A missing `access` (older backend) is a file with nothing to say.
331
+ */
332
+ const access = file.access ? describeFileAccess(file.access) : null;
282
333
 
283
334
  return (
284
335
  <AccountCard className="sxa:flex sxa:flex-wrap sxa:items-center sxa:gap-4 sxa:px-5 sxa:py-4">
285
336
  <BoxIcon className="sxa:h-4 sxa:w-4 sxa:shrink-0 sxa:text-muted" />
286
337
  <div className="sxa:min-w-0 sxa:flex-1">
287
338
  <p className="sxa:truncate sxa:text-small sxa:font-semibold">{file.label}</p>
288
- <p className="sxa:mt-1 sxa:truncate sxa:text-micro sxa:text-muted">{file.product_title}</p>
339
+ <p className="sxa:mt-1 sxa:text-micro sxa:text-muted" data-file-access={file.access?.state}>
340
+ {[file.product_title, access?.text].filter(Boolean).join(' · ')}
341
+ </p>
289
342
  </div>
290
- {href === null ? (
343
+ {access?.refused ? null : href === null ? (
291
344
  <span className="sx-account-chip sxa:meta-chip sxa:meta-chip--neutral">{t('common.unavailable')}</span>
292
345
  ) : (
293
346
  <a href={href} download className="sx-account-button sxa:card-button sxa:card-button--add sxa:shrink-0">
294
347
  {t('downloads.download')}
295
348
  </a>
296
349
  )}
350
+ {access?.canAsk ? (
351
+ asked ? (
352
+ <p role="status" className="sxa:shrink-0 sxa:text-micro sxa:text-muted">{t('downloads.accessRequested')}</p>
353
+ ) : (
354
+ <button
355
+ type="button"
356
+ onClick={() => onAskForAccess(access.text)}
357
+ disabled={disabled}
358
+ aria-busy={asking || undefined}
359
+ className="sx-account-button sxa:card-button sxa:card-button--add sxa:shrink-0 sxa:disabled:cursor-not-allowed sxa:disabled:opacity-50"
360
+ >
361
+ {asking ? <BusySpinner className="sxa:mr-1.5 sxa:h-3.5 sxa:w-3.5" /> : null}
362
+ {asking ? t('downloads.askingAccess') : t('downloads.askAccess')}
363
+ </button>
364
+ )
365
+ ) : null}
297
366
  </AccountCard>
298
367
  );
299
368
  }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The way out of the account to a hosted buyer page on the checkout host
3
+ * (design Batch 7, H2 and A1): `{checkout}/{page}/{shopId}`.
4
+ *
5
+ * Adding funds, changing a card and handing out a reseller key happen there,
6
+ * behind the page's own email code, because the JavaScript on a storefront
7
+ * origin may be the merchant's. So the link carries only the shop, the page to
8
+ * come back to and the page's display hints; never an email or a token. The
9
+ * hosted page reads and checks everything again on the server, so a forged
10
+ * hint can at most open the wrong segment.
11
+ *
12
+ * The same rule as `hostedPageUrl` in
13
+ * `packages/storefront-commerce/src/sx/hosted-page-url.ts`, which the theme
14
+ * lane uses. This package does not depend on storefront-commerce, so it keeps
15
+ * its own copy; the two must build the same URL.
16
+ */
17
+
18
+ export type HostedPage = 'topup' | 'reseller' | 'billing';
19
+
20
+ /** `return` is the account page the buyer comes back to; every other entry is a display hint. */
21
+ export type HostedPageQuery = { return: string } & Record<string, string>;
22
+
23
+ export function hostedPageUrl(
24
+ checkoutBaseUrl: string,
25
+ page: HostedPage,
26
+ shopId: string,
27
+ query: HostedPageQuery,
28
+ ): string {
29
+ const url = new URL(`/${page}/${encodeURIComponent(shopId)}`, checkoutBaseUrl);
30
+ for (const [name, value] of Object.entries(query)) url.searchParams.set(name, value);
31
+ return url.toString();
32
+ }
33
+
34
+ /**
35
+ * The link to `page` for the shop this storefront serves, back to the page the
36
+ * buyer is on now. Null when the worker injected no `shopId` or no
37
+ * `checkoutBaseUrl`: without both there is no page to link to, and the caller
38
+ * renders nothing rather than a guessed host.
39
+ */
40
+ export function hostedPageHref(page: HostedPage, hints: Record<string, string>): string | null {
41
+ const shopId = typeof window.shopId === 'string' ? window.shopId.trim() : '';
42
+ const checkoutBaseUrl = typeof window.checkoutBaseUrl === 'string' ? window.checkoutBaseUrl.trim() : '';
43
+ if (!shopId || !checkoutBaseUrl) return null;
44
+ return hostedPageUrl(checkoutBaseUrl, page, shopId, { ...hints, return: window.location.href });
45
+ }
@@ -11,6 +11,7 @@
11
11
  },
12
12
  "account": {
13
13
  "title": "حسابك",
14
+ "secureLink": "تُفتح صفحة Shoppex آمنة.",
14
15
  "sections": "أقسام الحساب",
15
16
  "signOut": "تسجيل الخروج",
16
17
  "signingOut": "جارٍ تسجيل الخروج…",
@@ -107,6 +108,7 @@
107
108
  "statTickets": "تذاكر الدعم",
108
109
  "walletLabel": "رصيد المتجر",
109
110
  "walletNote": "يُطبَّق عند الدفع",
111
+ "topUp": "شحن الرصيد",
110
112
  "recentOrders": "أحدث الطلبات",
111
113
  "allOrders": "كل الطلبات"
112
114
  },
@@ -159,7 +161,20 @@
159
161
  "warrantyClaimSending": "جارٍ الإرسال…",
160
162
  "warrantyClaimDone": "وصل طلبك إلى المتجر. سيردّون عبر البريد الإلكتروني.",
161
163
  "warrantyClaimFailed": "تعذّر إرسال طلبك.",
162
- "warrantyClaimTooLong": "لا يمكن أن يتجاوز طلبك {max} حرف."
164
+ "warrantyClaimTooLong": "لا يمكن أن يتجاوز طلبك {max} حرف.",
165
+ "invoicePdf": "تنزيل الفاتورة (PDF)",
166
+ "invoicePdfLoading": "جارٍ التحضير…",
167
+ "invoicePdfFailed": "تعذّر تنزيل هذه الفاتورة. يُرجى المحاولة مرة أخرى.",
168
+ "reviewTitle": "تقييمك",
169
+ "reviewPrompt": "ما رأيك في {product}؟",
170
+ "reviewPromptGeneric": "ما رأيك في عملية الشراء؟",
171
+ "reviewPositive": "إيجابي",
172
+ "reviewNeutral": "محايد",
173
+ "reviewNegative": "سلبي",
174
+ "reviewEdit": "تعديل التقييم",
175
+ "reviewWrite": "اكتب تقييمًا",
176
+ "reviewAutomated": "تم نشر تقييم إيجابي تلقائي لهذا الطلب. يمكنك استبداله بتقييمك.",
177
+ "reviewFallbackLabel": "تقييم"
163
178
  },
164
179
  "downloads": {
165
180
  "loading": "جارٍ تحميل تنزيلاتك",
@@ -183,7 +198,25 @@
183
198
  "renewAction": "فتح المتجر",
184
199
  "brokenTitle": "تعذّر عرض قائمة تنزيلاتك",
185
200
  "brokenBody": "أرسل المتجر عنصر ترخيص ({id}) بدون الترخيص نفسه، لذا ستكون هذه القائمة ناقصة لما تذكره.",
186
- "brokenFileBody": "أرسل المتجر عنصر ملف ({id}) دون أي شيء يمكن تنزيله، لذا ستذكر هذه القائمة ملفًا لا تستطيع تسليمه."
201
+ "brokenFileBody": "أرسل المتجر عنصر ملف ({id}) دون أي شيء يمكن تنزيله، لذا ستذكر هذه القائمة ملفًا لا تستطيع تسليمه.",
202
+ "accessLeftOne": "تبقّى {count} تنزيل",
203
+ "accessLeftOther": "تبقّى {count} من التنزيلات",
204
+ "accessUntil": "حتى {date}",
205
+ "accessLimitReached": "لقد استخدمت جميع التنزيلات",
206
+ "accessLimitReachedOne": "لقد استخدمت التنزيل الوحيد المتاح",
207
+ "accessLimitReachedOther": "لقد استخدمت جميع التنزيلات البالغة {count}",
208
+ "accessExpired": "انتهت التنزيلات في {date}",
209
+ "accessExpiredUndated": "انتهت التنزيلات",
210
+ "accessRevoked": "تم إيقاف التنزيلات لهذا الطلب",
211
+ "accessRefunded": "انتهت التنزيلات لأنه تم استرداد مبلغ هذا الطلب",
212
+ "askAccess": "طلب الوصول",
213
+ "askingAccess": "جارٍ الإرسال…",
214
+ "accessRequested": "تم إرسال الطلب",
215
+ "accessRequestDone": "تم إرسال الطلب. سيرد المتجر في قسم الدعم.",
216
+ "accessRequestFailed": "تعذّر إرسال طلبك. يُرجى المحاولة مرة أخرى.",
217
+ "accessRequestTitle": "الوصول إلى {file}",
218
+ "accessRequestMessage": "أرغب في تنزيل {file} ({product}) من هذا الطلب مرة أخرى.",
219
+ "accessRequestReason": "تعرض صفحة الحساب: {reason}."
187
220
  },
188
221
  "subscriptions": {
189
222
  "loading": "جارٍ تحميل اشتراكاتك",
@@ -208,7 +241,6 @@
208
241
  "paymentMethod": "طريقة الدفع",
209
242
  "cardOnFile": "{brand} المنتهية بالأرقام {last4}",
210
243
  "noPaymentMethod": "لا توجد طريقة محفوظة",
211
- "paymentMethodElsewhere": "رابط تغيير بيانات الدفع يصلك من رسائل المتجر نفسها. لا يُعرض هنا، لأن أي عنصر في هذه الصفحة قد يلتقطه.",
212
244
  "pause": "إيقاف مؤقت",
213
245
  "pausing": "جارٍ الإيقاف…",
214
246
  "resume": "استئناف",
@@ -227,7 +259,12 @@
227
259
  "historyLoading": "جارٍ تحميل سجل الفواتير",
228
260
  "historyEmptyTitle": "لا يوجد سجل فواتير بعد",
229
261
  "historyEmptyBody": "تظهر فواتير التجديد هنا بعد الدورة الأولى.",
230
- "trial": "تنتهي الفترة التجريبية في {date}"
262
+ "trial": "تنتهي الفترة التجريبية في {date}",
263
+ "renewalDue": "توجد دفعة تجديد مستحقة لهذا الاشتراك.",
264
+ "payNow": "ادفع الآن",
265
+ "updateCard": "تحديث البطاقة",
266
+ "pastDueCardTitle": "فشلت دفعتك الأخيرة",
267
+ "pastDueCardBody": "حدّث بطاقتك للحفاظ على هذا الاشتراك. تُفتح صفحة Shoppex آمنة."
231
268
  },
232
269
  "favorites": {
233
270
  "loading": "جارٍ تحميل مفضلتك",
@@ -308,6 +345,18 @@
308
345
  "primary": "أساسي",
309
346
  "commission": "عمولة {percent}%",
310
347
  "buyerDiscount": "خصم {percent}% للمشتري",
348
+ "yourRates": "أسعارك",
349
+ "ratesEveryProduct": "{percent}% على كل منتج",
350
+ "ratesMostProducts": "{percent}% على معظم المنتجات",
351
+ "ratesProductEarnsNothing": "{product} لا يحقق عمولة",
352
+ "ratesProductsEarnNothing": "{products} لا تحقق عمولة",
353
+ "ratesCustomRateProducts": "بعض المنتجات لها نسبة خاصة بها",
354
+ "ratesMoreProducts": "{count} أخرى",
355
+ "ratesCap": "حتى {amount} لكل طلب",
356
+ "ratesMinimum": "الطلبات الأقل من {amount} لا تحقق عمولة",
357
+ "ratesBindingDays": "يبقى المشترون لك لمدة {days} يومًا",
358
+ "ratesBindingForever": "يبقى المشترون لك دائمًا",
359
+ "ratesBindingDetail": "بعد أول طلب مدفوع عبر رابطك",
311
360
  "awaitingApproval": "بانتظار موافقة المتجر على هذا الرابط.",
312
361
  "clicks": "النقرات",
313
362
  "sales": "المبيعات",
@@ -316,6 +365,16 @@
316
365
  "commissionsEmptyTitle": "لا توجد عمولات بعد",
317
366
  "commissionsEmptyBody": "تظهر هنا مبيعات الإحالة المعتمدة.",
318
367
  "commissionFor": "الطلب {id}",
368
+ "ledgerWaitingPayment": "قيد الانتظار",
369
+ "ledgerEarned": "مكتسبة",
370
+ "ledgerInPayoutRequest": "في طلب صرف",
371
+ "ledgerPaidOut": "تم الدفع",
372
+ "ledgerStoreCredit": "رصيد المتجر",
373
+ "ledgerSplit": "مقسّم",
374
+ "ledgerVoided": "باطل",
375
+ "paidOutPart": "تم دفع {amount}",
376
+ "storeCreditPart": "{amount} رصيد المتجر",
377
+ "ofTotal": "من {total}",
319
378
  "balanceTitle": "حركة الرصيد",
320
379
  "balanceEmptyTitle": "لا توجد حركة رصيد بعد",
321
380
  "balanceEmptyBody": "تظهر هنا التحويلات والدفعات.",
@@ -324,7 +383,23 @@
324
383
  "claimAdjustment": "تسوية",
325
384
  "differentCurrency": "رصيد إحالاتك محفوظ بعملة {currency}.",
326
385
  "convertedLabel": "حُوِّل إلى رصيد المتجر",
327
- "paidLabel": "تم الدفع"
386
+ "paidLabel": "تم الدفع",
387
+ "newLink": "رابط جديد",
388
+ "linkLabel": "الاسم",
389
+ "linkLabelPlaceholder": "حملة يوتيوب",
390
+ "linkCode": "الرمز",
391
+ "linkCommission": "العمولة المطلوبة (%)",
392
+ "linkDiscount": "خصم المشتري المطلوب (%)",
393
+ "linkRatePlaceholder": "افتراضي المتجر",
394
+ "linkRatesHint": "اترك النسبتين فارغتين لاستخدام نسب المتجر. الرابط الذي يطلب نسبًا خاصة ينتظر موافقة المتجر.",
395
+ "createLink": "إنشاء الرابط",
396
+ "creatingLink": "جارٍ الإنشاء…",
397
+ "linkCreated": "رابطك جاهز للمشاركة.",
398
+ "linkRequested": "رابطك بانتظار موافقة المتجر.",
399
+ "linkFailed": "تعذّر إنشاء رابطك. يُرجى المحاولة مرة أخرى.",
400
+ "linkRequired": "أدخل اسمًا ورمزًا لرابطك.",
401
+ "linkRateInvalid": "أدخل كل نسبة كقيمة مئوية بين 0 و100 بمنزلتين عشريتين على الأكثر، مثل 12.5.",
402
+ "linksEmptyCanCreate": "أنشئ رابطًا برمزك الخاص."
328
403
  },
329
404
  "support": {
330
405
  "loading": "جارٍ تحميل تذاكرك",
@@ -393,15 +468,15 @@
393
468
  "signOutOthersDone": "تم تسجيل الخروج من كل الأجهزة الأخرى.",
394
469
  "devicesFailed": "تعذّر تحميل أجهزتك.",
395
470
  "signOutDeviceFailed": "تعذّر تسجيل الخروج من هذا الجهاز. يُرجى المحاولة مرة أخرى.",
396
- "deviceOn": "{browser} على {os}"
397
- },
398
- "walletTopup": {
399
- "title": "إضافة رصيد المتجر",
400
- "minimum": "الحد الأدنى {min}",
401
- "bounds": "من {min} إلى {max}",
402
- "bonus": "أضف {amount} على الأقل واحصل على {percent}% إضافية",
403
- "amount": "المبلغ",
404
- "submit": "المتابعة إلى الدفع"
471
+ "deviceOn": "{browser} على {os}",
472
+ "orderEmailsTitle": "رسائل الطلبات",
473
+ "orderEmailsLoading": "جارٍ تحميل إعدادات رسائل الطلبات",
474
+ "orderCreated": "تأكيدات الطلبات",
475
+ "orderCreatedNote": "رسالة عند إتمام طلب.",
476
+ "orderCompleted": "إيصالات الطلبات",
477
+ "orderCompletedNote": "الإيصالات وإشعارات اكتمال الطلب. الرسالة التي تسلّم منتجاتك تُرسل دائمًا.",
478
+ "paymentIssue": "مشكلات الدفع",
479
+ "paymentIssueNote": "رسالة عندما تحتاج عملية دفع إلى انتباهك."
405
480
  },
406
481
  "reseller": {
407
482
  "disabledTitle": "برنامج الموزعين غير متاح",
@@ -429,19 +504,12 @@
429
504
  "walletTitle": "رصيد الجملة",
430
505
  "balance": "الرصيد",
431
506
  "held": "محجوز",
507
+ "topUp": "إضافة رصيد",
432
508
  "catalogTitle": "كتالوج الجملة",
433
509
  "search": "البحث عن منتجات",
434
510
  "catalogEmptyTitle": "لم يتم العثور على منتجات جملة",
435
511
  "catalogEmptyBody": "جرّب بحثًا آخر أو عد بعد أن يضيف المتجر منتجات.",
436
512
  "volumePricing": "أسعار الكميات متاحة",
437
- "supplierBacked": "ينفذه المورّد",
438
- "add": "إضافة",
439
- "basketTitle": "سلة الجملة",
440
- "basketEmpty": "أضف منتجًا من كتالوج الجملة لبدء الطلب.",
441
- "quantity": "الكمية",
442
- "basketNeedsQuote": "احسب سعر هذه السلة لرؤية الإجمالي الحالي قبل الطلب.",
443
- "quote": "حساب سعر السلة",
444
- "place": "تنفيذ الطلب",
445
513
  "ordersTitle": "طلبات الجملة",
446
514
  "ordersEmptyTitle": "لا توجد طلبات جملة بعد",
447
515
  "ordersEmptyBody": "تظهر هنا الطلبات المدفوعة من رصيد الجملة.",
@@ -449,17 +517,12 @@
449
517
  "lineQuantity": "الكمية {count}",
450
518
  "serials": "الأرقام التسلسلية المسلّمة",
451
519
  "apiKeysTitle": "مفاتيح API للموزع",
452
- "apiKeyName": "اسم المفتاح",
453
- "createApiKey": "إنشاء مفتاح",
454
520
  "apiKeysEmpty": "لم تنشئ أي مفاتيح API للموزع.",
455
- "orderElsewhere": "تُقدَّم طلبات الجملة عبر واجهة برمجة الموزّعين، لا من هذه الصفحة.",
456
- "keysElsewhere": "يصدر المتجر مفاتيح واجهة البرمجة. اطلب منه مفتاحًا.",
457
- "apiKeyCreatedTitle": "انسخ هذا المفتاح الآن",
458
- "apiKeyCreatedBody": "يظهر هذا المفتاح النصي مرة واحدة ولا يمكن استعادته لاحقًا.",
459
- "dismissKey": "حفظت المفتاح",
521
+ "manageKeys": "إدارة المفاتيح",
522
+ "delivery": "رابط التسليم",
523
+ "deliveryHint": "لحقل Dynamic Delivery في متجرك. تُفتح صفحة Shoppex آمنة.",
460
524
  "lastUsed": "آخر استخدام: {date}",
461
- "neverUsed": "لم يُستخدم",
462
- "revokeApiKey": "إلغاء"
525
+ "neverUsed": "لم يُستخدم"
463
526
  }
464
527
  }
465
528
 
@@ -11,6 +11,7 @@
11
11
  },
12
12
  "account": {
13
13
  "title": "Dein Konto",
14
+ "secureLink": "Öffnet eine sichere Shoppex-Seite.",
14
15
  "sections": "Kontobereiche",
15
16
  "signOut": "Abmelden",
16
17
  "signingOut": "Wird abgemeldet…",
@@ -107,6 +108,7 @@
107
108
  "statTickets": "Support-Anfragen",
108
109
  "walletLabel": "Shop-Guthaben",
109
110
  "walletNote": "Wird an der Kasse verrechnet",
111
+ "topUp": "Aufladen",
110
112
  "recentOrders": "Letzte Bestellungen",
111
113
  "allOrders": "Alle Bestellungen"
112
114
  },
@@ -159,7 +161,20 @@
159
161
  "warrantyClaimSending": "Wird gesendet…",
160
162
  "warrantyClaimDone": "Der Shop hat deinen Fall erhalten. Die Antwort kommt per E-Mail.",
161
163
  "warrantyClaimFailed": "Dein Fall konnte nicht gesendet werden.",
162
- "warrantyClaimTooLong": "Dein Fall darf höchstens {max} Zeichen lang sein."
164
+ "warrantyClaimTooLong": "Dein Fall darf höchstens {max} Zeichen lang sein.",
165
+ "invoicePdf": "Rechnung herunterladen (PDF)",
166
+ "invoicePdfLoading": "Wird vorbereitet…",
167
+ "invoicePdfFailed": "Wir konnten diese Rechnung nicht herunterladen. Bitte versuch es erneut.",
168
+ "reviewTitle": "Deine Bewertung",
169
+ "reviewPrompt": "Wie war {product}?",
170
+ "reviewPromptGeneric": "Wie war dein Kauf?",
171
+ "reviewPositive": "Positiv",
172
+ "reviewNeutral": "Neutral",
173
+ "reviewNegative": "Negativ",
174
+ "reviewEdit": "Bewertung bearbeiten",
175
+ "reviewWrite": "Bewertung schreiben",
176
+ "reviewAutomated": "Für diese Bestellung wurde automatisch eine positive Bewertung veröffentlicht. Du kannst sie durch deine eigene ersetzen.",
177
+ "reviewFallbackLabel": "Bewertung"
163
178
  },
164
179
  "downloads": {
165
180
  "loading": "Deine Downloads werden geladen",
@@ -183,7 +198,25 @@
183
198
  "renewAction": "Shop öffnen",
184
199
  "brokenTitle": "Deine Downloads konnten nicht aufgelistet werden",
185
200
  "brokenBody": "Der Shop hat einen Lizenzeintrag ({id}) ohne die zugehörige Lizenz geschickt. In dieser Liste würde also genau das fehlen, was sie benennt.",
186
- "brokenFileBody": "Der Shop hat einen Dateieintrag ({id}) ohne Datei zum Herunterladen geschickt. Die Liste würde also eine Datei nennen, die sie nicht herausgeben kann."
201
+ "brokenFileBody": "Der Shop hat einen Dateieintrag ({id}) ohne Datei zum Herunterladen geschickt. Die Liste würde also eine Datei nennen, die sie nicht herausgeben kann.",
202
+ "accessLeftOne": "Noch {count} Download",
203
+ "accessLeftOther": "Noch {count} Downloads",
204
+ "accessUntil": "bis {date}",
205
+ "accessLimitReached": "Du hast alle Downloads aufgebraucht",
206
+ "accessLimitReachedOne": "Du hast deinen einzigen Download genutzt",
207
+ "accessLimitReachedOther": "Du hast alle {count} Downloads genutzt",
208
+ "accessExpired": "Downloads endeten am {date}",
209
+ "accessExpiredUndated": "Die Downloads sind beendet",
210
+ "accessRevoked": "Downloads wurden für diese Bestellung deaktiviert",
211
+ "accessRefunded": "Downloads endeten, weil diese Bestellung erstattet wurde",
212
+ "askAccess": "Zugriff anfragen",
213
+ "askingAccess": "Wird gesendet…",
214
+ "accessRequested": "Anfrage gesendet",
215
+ "accessRequestDone": "Anfrage gesendet. Der Shop antwortet im Support.",
216
+ "accessRequestFailed": "Deine Anfrage konnte nicht gesendet werden. Bitte versuche es noch einmal.",
217
+ "accessRequestTitle": "Zugriff auf {file}",
218
+ "accessRequestMessage": "Ich möchte {file} ({product}) aus dieser Bestellung erneut herunterladen.",
219
+ "accessRequestReason": "Im Kundenkonto steht: {reason}."
187
220
  },
188
221
  "subscriptions": {
189
222
  "loading": "Deine Abos werden geladen",
@@ -208,7 +241,6 @@
208
241
  "paymentMethod": "Zahlungsmethode",
209
242
  "cardOnFile": "{brand} endet auf {last4}",
210
243
  "noPaymentMethod": "Keine hinterlegt",
211
- "paymentMethodElsewhere": "Den Link zum Ändern deiner Zahlungsdaten bekommst du in den E-Mails des Shops. Hier wird er nicht ausgegeben, weil alles auf dieser Seite ihn abgreifen könnte.",
212
244
  "pause": "Pausieren",
213
245
  "pausing": "Wird pausiert…",
214
246
  "resume": "Fortsetzen",
@@ -227,7 +259,12 @@
227
259
  "historyLoading": "Abrechnungsverlauf wird geladen",
228
260
  "historyEmptyTitle": "Noch kein Abrechnungsverlauf",
229
261
  "historyEmptyBody": "Verlängerungsrechnungen erscheinen hier nach dem ersten Zyklus.",
230
- "trial": "Testphase endet am {date}"
262
+ "trial": "Testphase endet am {date}",
263
+ "renewalDue": "Für dieses Abo ist eine Verlängerungszahlung fällig.",
264
+ "payNow": "Jetzt bezahlen",
265
+ "updateCard": "Karte aktualisieren",
266
+ "pastDueCardTitle": "Deine letzte Zahlung ist fehlgeschlagen",
267
+ "pastDueCardBody": "Aktualisiere deine Karte, damit dein Abo weiterläuft. Öffnet eine sichere Shoppex-Seite."
231
268
  },
232
269
  "favorites": {
233
270
  "loading": "Deine Favoriten werden geladen",
@@ -308,6 +345,18 @@
308
345
  "primary": "Primär",
309
346
  "commission": "{percent}% Provision",
310
347
  "buyerDiscount": "{percent}% Käuferrabatt",
348
+ "yourRates": "Deine Konditionen",
349
+ "ratesEveryProduct": "{percent}% auf jedes Produkt",
350
+ "ratesMostProducts": "{percent}% auf die meisten Produkte",
351
+ "ratesProductEarnsNothing": "{product} bringt keine Provision",
352
+ "ratesProductsEarnNothing": "{products} bringen keine Provision",
353
+ "ratesCustomRateProducts": "Einige Produkte haben einen eigenen Satz",
354
+ "ratesMoreProducts": "{count} weitere",
355
+ "ratesCap": "Bis zu {amount} pro Bestellung",
356
+ "ratesMinimum": "Bestellungen unter {amount} bringen keine Provision",
357
+ "ratesBindingDays": "Käufer bleiben {days} Tage bei dir",
358
+ "ratesBindingForever": "Käufer bleiben dauerhaft bei dir",
359
+ "ratesBindingDetail": "Nach ihrer ersten bezahlten Bestellung über deinen Link",
311
360
  "awaitingApproval": "Wartet auf die Freigabe durch den Shop.",
312
361
  "clicks": "Klicks",
313
362
  "sales": "Verkäufe",
@@ -316,6 +365,16 @@
316
365
  "commissionsEmptyTitle": "Noch keine Provisionen",
317
366
  "commissionsEmptyBody": "Freigegebene Empfehlungsverkäufe erscheinen hier.",
318
367
  "commissionFor": "Bestellung {id}",
368
+ "ledgerWaitingPayment": "Ausstehend",
369
+ "ledgerEarned": "Verdient",
370
+ "ledgerInPayoutRequest": "In Auszahlungsanfrage",
371
+ "ledgerPaidOut": "Ausgezahlt",
372
+ "ledgerStoreCredit": "Shop-Guthaben",
373
+ "ledgerSplit": "Aufgeteilt",
374
+ "ledgerVoided": "Annulliert",
375
+ "paidOutPart": "{amount} ausgezahlt",
376
+ "storeCreditPart": "{amount} Shop-Guthaben",
377
+ "ofTotal": "von {total}",
319
378
  "balanceTitle": "Guthabenbewegungen",
320
379
  "balanceEmptyTitle": "Noch keine Guthabenbewegungen",
321
380
  "balanceEmptyBody": "Umwandlungen und Auszahlungen erscheinen hier.",
@@ -324,7 +383,23 @@
324
383
  "claimAdjustment": "Korrektur",
325
384
  "differentCurrency": "Dein Empfehlungsguthaben wird in {currency} geführt.",
326
385
  "convertedLabel": "In Shop-Guthaben umgewandelt",
327
- "paidLabel": "Ausgezahlt"
386
+ "paidLabel": "Ausgezahlt",
387
+ "newLink": "Neuer Link",
388
+ "linkLabel": "Name",
389
+ "linkLabelPlaceholder": "YouTube-Kampagne",
390
+ "linkCode": "Code",
391
+ "linkCommission": "Gewünschte Provision (%)",
392
+ "linkDiscount": "Gewünschter Käuferrabatt (%)",
393
+ "linkRatePlaceholder": "Standard des Shops",
394
+ "linkRatesHint": "Lass beide Sätze leer, um die des Shops zu nutzen. Ein Link mit eigenen Sätzen wartet auf die Freigabe durch den Shop.",
395
+ "createLink": "Link erstellen",
396
+ "creatingLink": "Wird erstellt…",
397
+ "linkCreated": "Dein Link ist bereit zum Teilen.",
398
+ "linkRequested": "Dein Link wartet auf die Freigabe durch den Shop.",
399
+ "linkFailed": "Wir konnten deinen Link nicht erstellen. Bitte versuch es erneut.",
400
+ "linkRequired": "Gib einen Namen und einen Code für deinen Link ein.",
401
+ "linkRateInvalid": "Gib jeden Satz als Prozentwert zwischen 0 und 100 mit höchstens zwei Nachkommastellen ein, zum Beispiel 12,5.",
402
+ "linksEmptyCanCreate": "Erstell einen mit deinem eigenen Code."
328
403
  },
329
404
  "support": {
330
405
  "loading": "Deine Anfragen werden geladen",
@@ -393,15 +468,15 @@
393
468
  "signOutOthersDone": "Alle anderen Geräte wurden abgemeldet.",
394
469
  "devicesFailed": "Deine Geräte konnten nicht geladen werden.",
395
470
  "signOutDeviceFailed": "Dieses Gerät konnte nicht abgemeldet werden. Bitte versuche es noch einmal.",
396
- "deviceOn": "{browser} auf {os}"
397
- },
398
- "walletTopup": {
399
- "title": "Guthaben aufladen",
400
- "minimum": "Mindestens {min}",
401
- "bounds": "Von {min} bis {max}",
402
- "bonus": "Lade mindestens {amount} auf und erhalte {percent}% extra",
403
- "amount": "Betrag",
404
- "submit": "Weiter zur Zahlung"
471
+ "deviceOn": "{browser} auf {os}",
472
+ "orderEmailsTitle": "Bestell-E-Mails",
473
+ "orderEmailsLoading": "Deine Bestell-E-Mails werden geladen",
474
+ "orderCreated": "Bestellbestätigungen",
475
+ "orderCreatedNote": "Eine E-Mail, wenn du eine Bestellung aufgibst.",
476
+ "orderCompleted": "Bestellbelege",
477
+ "orderCompletedNote": "Belege und Abschlussmeldungen. Die E-Mail, die deine Artikel liefert, wird immer gesendet.",
478
+ "paymentIssue": "Zahlungsprobleme",
479
+ "paymentIssueNote": "Eine E-Mail, wenn eine Zahlung deine Aufmerksamkeit braucht."
405
480
  },
406
481
  "reseller": {
407
482
  "disabledTitle": "Das Reseller-Programm ist nicht verfügbar",
@@ -429,19 +504,12 @@
429
504
  "walletTitle": "Großhandelsguthaben",
430
505
  "balance": "Guthaben",
431
506
  "held": "Reserviert",
507
+ "topUp": "Guthaben aufladen",
432
508
  "catalogTitle": "Großhandelskatalog",
433
509
  "search": "Produkte suchen",
434
510
  "catalogEmptyTitle": "Keine Großhandelsprodukte gefunden",
435
511
  "catalogEmptyBody": "Versuche eine andere Suche oder komm wieder, nachdem der Shop Produkte hinzugefügt hat.",
436
512
  "volumePricing": "Mengenpreise verfügbar",
437
- "supplierBacked": "Vom Lieferanten erfüllt",
438
- "add": "Hinzufügen",
439
- "basketTitle": "Großhandelswarenkorb",
440
- "basketEmpty": "Füge ein Produkt aus dem Großhandelskatalog hinzu, um eine Bestellung zu starten.",
441
- "quantity": "Menge",
442
- "basketNeedsQuote": "Lass diesen Warenkorb berechnen, um vor der Bestellung den aktuellen Gesamtbetrag zu sehen.",
443
- "quote": "Warenkorb berechnen",
444
- "place": "Bestellung aufgeben",
445
513
  "ordersTitle": "Großhandelsbestellungen",
446
514
  "ordersEmptyTitle": "Noch keine Großhandelsbestellungen",
447
515
  "ordersEmptyBody": "Bestellungen, die mit deinem Großhandelsguthaben bezahlt wurden, erscheinen hier.",
@@ -449,17 +517,12 @@
449
517
  "lineQuantity": "Menge {count}",
450
518
  "serials": "Gelieferte Seriennummern",
451
519
  "apiKeysTitle": "Reseller-API-Schlüssel",
452
- "apiKeyName": "Schlüsselname",
453
- "createApiKey": "Schlüssel erstellen",
454
520
  "apiKeysEmpty": "Du hast noch keine Reseller-API-Schlüssel erstellt.",
455
- "orderElsewhere": "Großhandelsbestellungen laufen über die Reseller-API, nicht über diese Seite.",
456
- "keysElsewhere": "API-Schlüssel vergibt der Shop. Frag dort danach.",
457
- "apiKeyCreatedTitle": "Kopiere diesen Schlüssel jetzt",
458
- "apiKeyCreatedBody": "Dieser Klartextschlüssel wird nur einmal angezeigt und kann später nicht wiederhergestellt werden.",
459
- "dismissKey": "Ich habe ihn gespeichert",
521
+ "manageKeys": "Schlüssel verwalten",
522
+ "delivery": "Liefer-URL",
523
+ "deliveryHint": "Für das Feld „Dynamic Delivery“ in deinem Shop. Öffnet eine sichere Shoppex-Seite.",
460
524
  "lastUsed": "Zuletzt verwendet: {date}",
461
- "neverUsed": "Nie verwendet",
462
- "revokeApiKey": "Widerrufen"
525
+ "neverUsed": "Nie verwendet"
463
526
  }
464
527
  }
465
528