@flamingo-stack/openframe-frontend-core 0.0.721 → 0.0.722
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/dist/assets/index.cjs +0 -6
- package/dist/assets/index.d.ts +0 -1
- package/dist/assets/index.d.ts.map +1 -1
- package/dist/assets/index.js +1 -5
- package/dist/{chunk-KFOAV77K.js → chunk-5D2K7TFT.js} +1 -1
- package/dist/{chunk-NMPVK53I.cjs → chunk-5K6YKRP5.cjs} +23 -23
- package/dist/{chunk-S63QBBT7.js → chunk-65C4WHG4.js} +5 -5
- package/dist/{chunk-XE2K53GH.cjs → chunk-6HRQLM6T.cjs} +17 -17
- package/dist/{chunk-NPSJE7J3.js → chunk-7UZ2JZJY.js} +2 -2
- package/dist/{chunk-7Z5FMQYR.cjs → chunk-ABSWD4PQ.cjs} +14 -14
- package/dist/{chunk-RCI3PALX.js → chunk-AWWLEYAU.js} +6 -6
- package/dist/{chunk-NJ5TBTJG.js → chunk-BCU6UUNQ.js} +5 -5
- package/dist/{chunk-3LY7V3RF.js → chunk-BUDZWXRG.js} +2 -2
- package/dist/{chunk-UZTOI6FJ.cjs → chunk-DWSBTGKT.cjs} +590 -598
- package/dist/{chunk-QQCFPMAA.js → chunk-DX3W7DPC.js} +1 -1
- package/dist/{chunk-HXB7SEYG.js → chunk-E7NIZXDT.js} +7 -16
- package/dist/{chunk-74N742ZG.js → chunk-ETQVXIYW.js} +4 -4
- package/dist/{chunk-742BGXNX.js → chunk-EWED6ISQ.js} +1 -1
- package/dist/{chunk-LAXKEBDD.js → chunk-F33RMJOL.js} +7 -7
- package/dist/{chunk-QLTXT562.cjs → chunk-FDWYBJYD.cjs} +6 -6
- package/dist/{chunk-6AJIOW3S.cjs → chunk-GAFK3WNT.cjs} +37 -37
- package/dist/{chunk-3R32ZNA2.cjs → chunk-GYSTZD6X.cjs} +35 -35
- package/dist/{chunk-57UTI7ZH.js → chunk-HA2ASGJM.js} +4 -4
- package/dist/{chunk-OKE5BIQH.cjs → chunk-HP4UUBJC.cjs} +8 -8
- package/dist/{chunk-CO3FOZIH.js → chunk-I3UUMRO7.js} +1 -1
- package/dist/{chunk-ZLUFEWZZ.js → chunk-IF72N4PE.js} +6 -6
- package/dist/{chunk-GCK75D7F.cjs → chunk-IITZ6JSC.cjs} +28 -28
- package/dist/{chunk-QYS4YVEP.cjs → chunk-JQ55FFIK.cjs} +31 -31
- package/dist/{chunk-2P2M27EW.js → chunk-KBPGSPXQ.js} +13 -21
- package/dist/{chunk-JDBZ4ZHU.js → chunk-KS3YLYVL.js} +2 -2
- package/dist/{chunk-RJMWJAJO.cjs → chunk-KWBH63GV.cjs} +19 -19
- package/dist/{chunk-IYVFSLWJ.js → chunk-KZQO5EJQ.js} +4 -0
- package/dist/{chunk-IMTY3GRR.cjs → chunk-LV6UJD2K.cjs} +2 -2
- package/dist/{chunk-XXO52RZR.cjs → chunk-MJ4SXOG2.cjs} +18 -18
- package/dist/{chunk-BYVVE7HP.cjs → chunk-NFABAOMD.cjs} +34 -36
- package/dist/{chunk-UXU7Y5BH.js → chunk-NMGWNUKB.js} +3 -3
- package/dist/{chunk-HJMSNOBT.cjs → chunk-P7Y626YJ.cjs} +258 -267
- package/dist/{chunk-RQZQHLNS.cjs → chunk-RVQV6J5K.cjs} +66 -66
- package/dist/{chunk-E6SMMMKV.js → chunk-SZ3CBFSH.js} +9 -9
- package/dist/{chunk-LGKMR7NZ.js → chunk-SZF7ENIW.js} +3 -3
- package/dist/{chunk-F5BPGWGT.cjs → chunk-TKFTWM3V.cjs} +5 -1
- package/dist/{chunk-GYAXV3MZ.js → chunk-UYMTAFHG.js} +4 -6
- package/dist/{chunk-F7ORKKEF.cjs → chunk-VC7COEWF.cjs} +83 -83
- package/dist/{chunk-44Q6MV7C.js → chunk-VSYQJCNR.js} +1 -1
- package/dist/{chunk-O5EHOJZD.cjs → chunk-VU7RU3LL.cjs} +7 -7
- package/dist/{chunk-D2DH6VZL.cjs → chunk-VV5INIQB.cjs} +3 -3
- package/dist/{chunk-I2254QN6.cjs → chunk-VYHCN5NU.cjs} +3 -3
- package/dist/{chunk-ZZEKXGFS.js → chunk-WY4SLTMZ.js} +2 -2
- package/dist/{chunk-LODMNSQY.cjs → chunk-XJYAUIOL.cjs} +11 -11
- package/dist/{chunk-GGBYZKMS.cjs → chunk-XR7NVJXS.cjs} +5 -5
- package/dist/{chunk-VFICDEYJ.cjs → chunk-ZHC6MDGF.cjs} +41 -41
- package/dist/{chunk-5QXE5HMV.js → chunk-ZMHP4Y6M.js} +3 -3
- package/dist/{cloud-tenants-Q4TCJS5Y.js → cloud-tenants-4Z36KWSX.js} +9 -9
- package/dist/{cloud-tenants-HZHKRUIW.cjs → cloud-tenants-5NIC5VXK.cjs} +10 -10
- package/dist/components/case-studies/index.cjs +23 -23
- package/dist/components/case-studies/index.js +10 -10
- package/dist/components/chat/index.cjs +7 -7
- package/dist/components/chat/index.js +6 -6
- package/dist/components/contact/index.cjs +11 -11
- package/dist/components/contact/index.js +10 -10
- package/dist/components/docs/index.cjs +14 -14
- package/dist/components/docs/index.js +13 -13
- package/dist/components/embeds/index.cjs +11 -11
- package/dist/components/embeds/index.js +10 -10
- package/dist/components/faq/index.cjs +11 -11
- package/dist/components/faq/index.js +10 -10
- package/dist/components/features/index.cjs +10 -10
- package/dist/components/features/index.js +9 -9
- package/dist/components/features/remote-session/remote-access-request-card.d.ts.map +1 -1
- package/dist/components/help-center-pages/download-apps-page.d.ts +19 -6
- package/dist/components/help-center-pages/download-apps-page.d.ts.map +1 -1
- package/dist/components/help-center-pages/index.cjs +145 -130
- package/dist/components/help-center-pages/index.d.ts +1 -1
- package/dist/components/help-center-pages/index.d.ts.map +1 -1
- package/dist/components/help-center-pages/index.js +59 -44
- package/dist/components/index.cjs +64 -64
- package/dist/components/index.js +21 -21
- package/dist/components/meeting-scheduler/index.cjs +47 -47
- package/dist/components/meeting-scheduler/index.js +9 -9
- package/dist/components/navigation/index.cjs +13 -13
- package/dist/components/navigation/index.js +12 -12
- package/dist/components/onboarding-guides/index.cjs +13 -13
- package/dist/components/onboarding-guides/index.js +12 -12
- package/dist/components/product-demo/index.cjs +8 -8
- package/dist/components/product-demo/index.js +8 -8
- package/dist/components/related-content/index.cjs +5 -5
- package/dist/components/related-content/index.js +4 -4
- package/dist/components/tickets/index.cjs +14 -14
- package/dist/components/tickets/index.js +13 -13
- package/dist/components/ui/index.cjs +10 -10
- package/dist/components/ui/index.js +9 -9
- package/dist/components/ui/mobile-app-qr.d.ts +13 -15
- package/dist/components/ui/mobile-app-qr.d.ts.map +1 -1
- package/dist/{devices-NOUGVTES.js → devices-2TK3LAKM.js} +9 -9
- package/dist/{devices-6XHUFIC6.cjs → devices-7IKT72DK.cjs} +11 -11
- package/dist/index.cjs +16 -24
- package/dist/index.js +15 -23
- package/dist/{knowledge-2YTUXQ2Z.js → knowledge-3GJ4MU3V.js} +9 -9
- package/dist/{knowledge-ANEKPMO2.cjs → knowledge-HYSEV35H.cjs} +11 -11
- package/dist/{logs-WVQIONUT.js → logs-TATVM62H.js} +9 -9
- package/dist/{logs-U3IB3HUM.cjs → logs-ZX3NADQC.cjs} +18 -18
- package/dist/{policies-UFRN2EQF.cjs → policies-J4FSDKPI.cjs} +14 -14
- package/dist/{policies-66EGDW2H.js → policies-MRRKLF4Q.js} +9 -9
- package/dist/{remote-session-6WEUJZDC.js → remote-session-WCLR47RN.js} +4 -4
- package/dist/{remote-session-A75X5NPC.cjs → remote-session-Z2YCUPHU.cjs} +6 -6
- package/dist/{software-update-2MHKL76D.cjs → software-update-CXUBWYD2.cjs} +14 -14
- package/dist/{software-update-3ZD3MXF3.js → software-update-KM46WUVQ.js} +9 -9
- package/dist/{tickets-board-IFWMIZ35.cjs → tickets-board-R5OQSEEC.cjs} +8 -8
- package/dist/{tickets-board-MYXG3V72.js → tickets-board-U3RF3AO4.js} +3 -3
- package/dist/types/downloads.d.ts +16 -0
- package/dist/types/downloads.d.ts.map +1 -1
- package/dist/utils/format.d.ts +11 -0
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/index.cjs +4 -9
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +4 -7
- package/dist/utils/mobile-app.cjs +0 -9
- package/dist/utils/mobile-app.d.ts +8 -25
- package/dist/utils/mobile-app.d.ts.map +1 -1
- package/dist/utils/mobile-app.js +1 -7
- package/package.json +1 -1
- package/src/assets/index.ts +0 -1
- package/src/components/features/remote-session/remote-access-request-card.tsx +6 -6
- package/src/components/help-center-pages/download-apps-page.tsx +53 -29
- package/src/components/help-center-pages/index.ts +1 -1
- package/src/components/ui/mobile-app-qr.tsx +17 -22
- package/src/types/downloads.ts +17 -0
- package/src/utils/format.ts +15 -0
- package/src/utils/index.ts +1 -1
- package/src/utils/mobile-app.ts +8 -31
- package/dist/assets/mobile-app-qr.d.ts +0 -17
- package/dist/assets/mobile-app-qr.d.ts.map +0 -1
- package/src/assets/mobile-app-qr.ts +0 -19
|
@@ -8,8 +8,14 @@
|
|
|
8
8
|
* when a visitor looks for another system). In a card the installer is THE
|
|
9
9
|
* action (the other architecture is a quieter button) and each package
|
|
10
10
|
* manager is one line under it, with its copy button.
|
|
11
|
-
* - The mobile app: the store badges, and the install QR code beside them.
|
|
12
|
-
*
|
|
11
|
+
* - The mobile app: the store badges, and the install QR code beside them. The
|
|
12
|
+
* code and the address it encodes are the server's (`mobile.install`): the
|
|
13
|
+
* website's install link, which the server answers with a redirect to the
|
|
14
|
+
* phone's store. This page redirects nobody, and with no install link from
|
|
15
|
+
* the server it draws no code.
|
|
16
|
+
* - `web`: the way in for someone who installs nothing (the website's trial
|
|
17
|
+
* signup). The host names the words and the button; the page draws the card,
|
|
18
|
+
* in the frame of every other card on it.
|
|
13
19
|
*
|
|
14
20
|
* DATA: all of it is the server's answer (`DownloadsPublic`), read by
|
|
15
21
|
* `useDownloads` against `endpoint` (default `DOWNLOADS_API_PATH`; embedders
|
|
@@ -26,7 +32,7 @@
|
|
|
26
32
|
* The chrome is the canonical `PageShell` + frozen `PageLayout`.
|
|
27
33
|
*/
|
|
28
34
|
|
|
29
|
-
import {
|
|
35
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
30
36
|
import { useRouter } from '../../embed-shims/next-navigation';
|
|
31
37
|
import { useCopyToClipboard } from '../../hooks/use-copy-to-clipboard';
|
|
32
38
|
import { useDownloads } from '../../hooks/use-downloads';
|
|
@@ -44,7 +50,7 @@ import {
|
|
|
44
50
|
type MobileAppLinks,
|
|
45
51
|
} from '../../types/downloads';
|
|
46
52
|
import { cn } from '../../utils/cn';
|
|
47
|
-
import {
|
|
53
|
+
import { formatUrlForDisplay } from '../../utils/format';
|
|
48
54
|
import { DESKTOP_OSES, type DesktopOs } from '../../utils/visitor-os';
|
|
49
55
|
import { CommandBox } from '../features/command-box';
|
|
50
56
|
import { AppleLogoIcon } from '../icons-v2-generated/brand-logos/apple-logo-icon';
|
|
@@ -78,8 +84,16 @@ export interface DownloadAppsPageProps {
|
|
|
78
84
|
openStoresInNewTab?: boolean;
|
|
79
85
|
/** Called when a visitor downloads an installer or copies an install command (the host's analytics). */
|
|
80
86
|
onDownloadAction?: (event: DownloadActionEvent) => void;
|
|
81
|
-
/** The
|
|
82
|
-
|
|
87
|
+
/** The closing card, under the mobile app: using OpenFrame without installing anything. Default: none. */
|
|
88
|
+
web?: DownloadAppsWebOption;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The "no install" card: the host's words and its own button (the page holds no signup link). */
|
|
92
|
+
export interface DownloadAppsWebOption {
|
|
93
|
+
title: string;
|
|
94
|
+
description: string;
|
|
95
|
+
/** The host's button (the website's trial signup). */
|
|
96
|
+
action: ReactNode;
|
|
83
97
|
}
|
|
84
98
|
|
|
85
99
|
const SECTIONS_CLASS = 'flex flex-col gap-[var(--spacing-system-xlf)]';
|
|
@@ -206,6 +220,24 @@ function SystemCard({
|
|
|
206
220
|
);
|
|
207
221
|
}
|
|
208
222
|
|
|
223
|
+
/** The closing card: one line of what the web offers, and the host's button at the far end. */
|
|
224
|
+
function WebCard({ web }: { web: DownloadAppsWebOption }) {
|
|
225
|
+
return (
|
|
226
|
+
<section
|
|
227
|
+
aria-labelledby="download-web"
|
|
228
|
+
className={cn(CARD_CLASS, 'content-md:flex-row content-md:items-center content-md:justify-between')}
|
|
229
|
+
>
|
|
230
|
+
<div className="flex flex-col gap-[var(--spacing-system-xsf)]">
|
|
231
|
+
<h2 id="download-web" className="m-0 text-ods-text-primary text-h3">
|
|
232
|
+
{web.title}
|
|
233
|
+
</h2>
|
|
234
|
+
<p className="m-0 text-ods-text-secondary text-h4">{web.description}</p>
|
|
235
|
+
</div>
|
|
236
|
+
<div className="shrink-0">{web.action}</div>
|
|
237
|
+
</section>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
209
241
|
/** A section's cards while the server's answer loads: the same grid and card frame, a button's box in each. */
|
|
210
242
|
function CardsSkeleton({ label }: { label: string }) {
|
|
211
243
|
return (
|
|
@@ -248,7 +280,7 @@ export function DownloadAppsPage({
|
|
|
248
280
|
showDesktop = true,
|
|
249
281
|
openStoresInNewTab = true,
|
|
250
282
|
onDownloadAction,
|
|
251
|
-
|
|
283
|
+
web,
|
|
252
284
|
}: DownloadAppsPageProps) {
|
|
253
285
|
const { data, isLoading, error, reload } = useDownloads({ endpoint, initialData });
|
|
254
286
|
// The page adapts to the server's answer: a section exists only for what it names.
|
|
@@ -258,15 +290,6 @@ export function DownloadAppsPage({
|
|
|
258
290
|
// Until the answer lands, a host that offers the desktop app is titled for it.
|
|
259
291
|
const offersDesktop = showDesktop && (!data || systems.length > 0);
|
|
260
292
|
|
|
261
|
-
useEffect(() => {
|
|
262
|
-
// The install QR code arrives with this parameter: a phone or a tablet goes
|
|
263
|
-
// straight on to its store. `replace`, so Back returns to whatever sent the
|
|
264
|
-
// visitor here. Anyone else who opens the page on a phone reads it.
|
|
265
|
-
if (!mobile || !new URLSearchParams(window.location.search).has(DOWNLOAD_PAGE_STORE_PARAM)) return;
|
|
266
|
-
const storeUrl = resolveMobileStoreUrl(mobile, navigator.userAgent, navigator.maxTouchPoints);
|
|
267
|
-
if (storeUrl) window.location.replace(storeUrl);
|
|
268
|
-
}, [mobile]);
|
|
269
|
-
|
|
270
293
|
let body: ReactNode;
|
|
271
294
|
if (error && !data) {
|
|
272
295
|
body = <LoadError message="Could not load the downloads" onRetry={reload} />;
|
|
@@ -298,7 +321,7 @@ export function DownloadAppsPage({
|
|
|
298
321
|
|
|
299
322
|
{mobile && hasMobile && (
|
|
300
323
|
<Section id="download-mobile" title="Mobile app">
|
|
301
|
-
<div className={
|
|
324
|
+
<div className={cardsGridClass(mobile.install ? 2 : 1)}>
|
|
302
325
|
<div className={CARD_CLASS}>
|
|
303
326
|
<CardHeader title={storeCardTitle(mobile)} />
|
|
304
327
|
<p className="m-0 text-ods-text-secondary text-h4">Get alerts and respond to tickets on the go.</p>
|
|
@@ -308,18 +331,19 @@ export function DownloadAppsPage({
|
|
|
308
331
|
openInNewTab={openStoresInNewTab}
|
|
309
332
|
/>
|
|
310
333
|
</div>
|
|
311
|
-
|
|
312
|
-
<div
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
334
|
+
{mobile.install && (
|
|
335
|
+
<div
|
|
336
|
+
className={cn(CARD_CLASS, 'content-md:flex-row content-md:items-center content-md:justify-between')}
|
|
337
|
+
>
|
|
338
|
+
<div className="flex flex-col gap-[var(--spacing-system-sf)]">
|
|
339
|
+
<CardHeader title="Scan with your phone" />
|
|
340
|
+
<p className="m-0 text-ods-text-secondary text-h4">
|
|
341
|
+
Point your camera at the code, or open {formatUrlForDisplay(mobile.install.url)} on your phone.
|
|
342
|
+
</p>
|
|
343
|
+
</div>
|
|
344
|
+
<MobileAppQr install={mobile.install} className="h-[144px] w-[144px] shrink-0" />
|
|
321
345
|
</div>
|
|
322
|
-
|
|
346
|
+
)}
|
|
323
347
|
</div>
|
|
324
348
|
</Section>
|
|
325
349
|
)}
|
|
@@ -336,7 +360,7 @@ export function DownloadAppsPage({
|
|
|
336
360
|
>
|
|
337
361
|
<div className={SECTIONS_CLASS}>
|
|
338
362
|
{body}
|
|
339
|
-
{
|
|
363
|
+
{web && <WebCard web={web} />}
|
|
340
364
|
</div>
|
|
341
365
|
</DownloadAppsChrome>
|
|
342
366
|
);
|
|
@@ -22,7 +22,7 @@ export { ProductReleasesListPage, type ProductReleasesListPageProps } from './pr
|
|
|
22
22
|
export { DeliveryPage, type DeliveryPageProps } from './delivery-page';
|
|
23
23
|
export { OnboardingGuidesCatalogPage, type OnboardingGuidesCatalogPageProps } from './onboarding-guides-catalog-page';
|
|
24
24
|
export { TrustCenterPage, TrustCenterPageSkeleton, type TrustCenterPageProps } from './trust-center-page';
|
|
25
|
-
export { DownloadAppsPage, type DownloadAppsPageProps } from './download-apps-page';
|
|
25
|
+
export { DownloadAppsPage, type DownloadAppsPageProps, type DownloadAppsWebOption } from './download-apps-page';
|
|
26
26
|
|
|
27
27
|
// Existing full-pages re-exported for a single Help Center import site.
|
|
28
28
|
export { FaqDocumentPage, type FaqDocumentPageProps } from '../faq';
|
|
@@ -1,40 +1,35 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { MobileAppInstallLink } from '../../types/downloads';
|
|
2
2
|
import { cn } from '../../utils/cn';
|
|
3
|
-
import { MOBILE_APP_INSTALL_URL } from '../../utils/mobile-app';
|
|
4
3
|
|
|
5
4
|
export interface MobileAppQrProps {
|
|
5
|
+
/** The server's install link: the address and the artwork that encodes it. */
|
|
6
|
+
install: MobileAppInstallLink;
|
|
6
7
|
/** Sizes the code; the caller owns its footprint. */
|
|
7
8
|
className?: string;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
|
-
* The install QR code, drawn inline
|
|
12
|
-
*
|
|
12
|
+
* The install QR code, drawn inline: the modules are the ODS accent colour and
|
|
13
|
+
* the background is transparent, so the code sits directly on whatever surface
|
|
14
|
+
* it is placed on. The artwork and the address it encodes are the server's
|
|
15
|
+
* (`MobileAppInstallLink`): this component holds neither.
|
|
13
16
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* margin
|
|
18
|
-
*
|
|
19
|
-
* That polarity holds only under the dark theme, where `text-ods-bg` is the dark
|
|
20
|
-
* value and `bg-ods-bg-inverted` the light one; both flip under `.theme-light`.
|
|
21
|
-
* Do not render this inside a light-theme scope: an inverted code is silently
|
|
22
|
-
* unscannable rather than visibly broken.
|
|
23
|
-
*
|
|
24
|
-
* The artwork is the asset `MOBILE_APP_QR_PATH` (`assets/mobile-app-qr`), which
|
|
25
|
-
* encodes {@link MOBILE_APP_INSTALL_URL}; the regeneration note lives there.
|
|
17
|
+
* On a dark surface that is a light-on-dark code. Phone cameras read both
|
|
18
|
+
* polarities; what they need is contrast between the accent and the surface, and
|
|
19
|
+
* the quiet zone, which the server bakes into the viewBox as a transparent
|
|
20
|
+
* margin. Place it on a plain surface (a card, the page), never over an image
|
|
21
|
+
* or a surface close to the accent in brightness.
|
|
26
22
|
*/
|
|
27
|
-
export function MobileAppQr({ className }: MobileAppQrProps) {
|
|
23
|
+
export function MobileAppQr({ install, className }: MobileAppQrProps) {
|
|
28
24
|
return (
|
|
29
25
|
<svg
|
|
30
|
-
viewBox={`0 0 ${
|
|
31
|
-
fill="none"
|
|
26
|
+
viewBox={`0 0 ${install.qrViewBoxSize} ${install.qrViewBoxSize}`}
|
|
32
27
|
shapeRendering="crispEdges"
|
|
33
28
|
role="img"
|
|
34
|
-
aria-label={`QR code for ${
|
|
35
|
-
className={cn('h-[120px] w-[120px] text-ods-
|
|
29
|
+
aria-label={`QR code for ${install.url}`}
|
|
30
|
+
className={cn('h-[120px] w-[120px] text-ods-accent', className)}
|
|
36
31
|
>
|
|
37
|
-
<path
|
|
32
|
+
<path fill="currentColor" d={install.qrPath} />
|
|
38
33
|
</svg>
|
|
39
34
|
);
|
|
40
35
|
}
|
package/src/types/downloads.ts
CHANGED
|
@@ -44,10 +44,27 @@ export interface AppDownload {
|
|
|
44
44
|
label: string | null;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* The install link a phone opens to reach its store, and the QR code that
|
|
49
|
+
* encodes it. Both are the server's: the link is an address on the website
|
|
50
|
+
* (answered there with a redirect by device), and the artwork is drawn from it
|
|
51
|
+
* on the server, so the two can never disagree and the lib names no host.
|
|
52
|
+
*/
|
|
53
|
+
export interface MobileAppInstallLink {
|
|
54
|
+
/** The absolute address the code encodes. */
|
|
55
|
+
url: string;
|
|
56
|
+
/** The side of the artwork's square viewBox, quiet zone included. */
|
|
57
|
+
qrViewBoxSize: number;
|
|
58
|
+
/** The dark modules as one SVG path of filled rectangles (`fill` draws them). */
|
|
59
|
+
qrPath: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
47
62
|
/** Where the mobile app is installed from. A store the server names no listing for is null, and is not offered. */
|
|
48
63
|
export interface MobileAppLinks {
|
|
49
64
|
appStoreUrl: string | null;
|
|
50
65
|
googlePlayUrl: string | null;
|
|
66
|
+
/** The install link and its QR code. Absent or null: no code is drawn. */
|
|
67
|
+
install?: MobileAppInstallLink | null;
|
|
51
68
|
}
|
|
52
69
|
|
|
53
70
|
/**
|
package/src/utils/format.ts
CHANGED
|
@@ -1059,3 +1059,18 @@ export function formatWebinarTimeMeta(
|
|
|
1059
1059
|
const time = formatProgramTimeRange(at, { startAt: opts.startAt, withZoneLabel: opts.withZoneLabel });
|
|
1060
1060
|
return [time, duration].filter(Boolean).join(' · ');
|
|
1061
1061
|
}
|
|
1062
|
+
|
|
1063
|
+
/**
|
|
1064
|
+
* A web address as it is shown to a person to read or type, not to follow: no
|
|
1065
|
+
* scheme, no query or fragment, no trailing slash.
|
|
1066
|
+
* `https://www.example.com/mobile?from=qr` reads `example.com/mobile`.
|
|
1067
|
+
*
|
|
1068
|
+
* `www` is dropped by default, because nobody types it; `keepWww` keeps it where
|
|
1069
|
+
* the address is shown as the site's own name.
|
|
1070
|
+
*/
|
|
1071
|
+
export function formatUrlForDisplay(url: string, options: { keepWww?: boolean } = {}): string {
|
|
1072
|
+
return url
|
|
1073
|
+
.replace(options.keepWww ? /^https?:\/\//i : /^https?:\/\/(www\.)?/i, '')
|
|
1074
|
+
.replace(/[?#].*$/, '')
|
|
1075
|
+
.replace(/\/$/, '');
|
|
1076
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -16,7 +16,7 @@ export {
|
|
|
16
16
|
// Number / currency / byte / date formatters live in `./format` (single
|
|
17
17
|
// source of truth). Re-exported here so existing callers that pull from
|
|
18
18
|
// the barrel keep working without changing imports.
|
|
19
|
-
export { formatDate, formatNumber, formatPrice, formatBytes } from './format';
|
|
19
|
+
export { formatDate, formatNumber, formatPrice, formatBytes, formatUrlForDisplay } from './format';
|
|
20
20
|
export { TRIAL_DAYS, TRIAL_LABELS } from './trial';
|
|
21
21
|
export {
|
|
22
22
|
VENDOR_CLASSIFICATIONS,
|
package/src/utils/mobile-app.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The download page's
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* The download page's address, and what a server needs to send a phone on to
|
|
3
|
+
* its store. No link lives here: the store listings, the installer links and
|
|
4
|
+
* the install link the QR code encodes are all the server's (`DownloadsPublic`,
|
|
5
|
+
* read through `useDownloads`).
|
|
6
6
|
*
|
|
7
7
|
* Pure and server-safe; also published as the granular subpath
|
|
8
8
|
* `./utils/mobile-app`, for an Edge proxy and other server-only consumers.
|
|
@@ -13,37 +13,14 @@ import { detectVisitorOs } from './visitor-os';
|
|
|
13
13
|
/** The website's download page: every installer and the mobile app. */
|
|
14
14
|
export const DOWNLOAD_PAGE_PATH = '/download';
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
* On the download page: "send a phone or a tablet on to its store". The install
|
|
18
|
-
* QR code carries it, because whoever scans a code is holding the phone the app
|
|
19
|
-
* goes on. Anyone who opens the page without it reads the page.
|
|
20
|
-
*/
|
|
21
|
-
export const DOWNLOAD_PAGE_STORE_PARAM = 'store';
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* The one URL the install QR code encodes: the website's download page, asking
|
|
25
|
-
* it to send a phone on to its store.
|
|
26
|
-
*
|
|
27
|
-
* Absolute and on the production host on purpose: a code must point at
|
|
28
|
-
* production whichever deployment rendered the page it came from. `MobileAppQr`
|
|
29
|
-
* draws this value as committed path data, so the two change together: see the
|
|
30
|
-
* regeneration note on that component.
|
|
31
|
-
*/
|
|
32
|
-
export const MOBILE_APP_INSTALL_URL = `https://www.flamingo.run${DOWNLOAD_PAGE_PATH}?${DOWNLOAD_PAGE_STORE_PARAM}=1`;
|
|
33
|
-
|
|
34
|
-
/** The download page as it is printed for a human to read or type: no scheme, no `www`, no query. */
|
|
35
|
-
export const MOBILE_APP_INSTALL_HOST_PATH = MOBILE_APP_INSTALL_URL.replace(/^https:\/\/(www\.)?/, '').replace(
|
|
36
|
-
/\?.*$/,
|
|
37
|
-
'',
|
|
38
|
-
);
|
|
39
|
-
|
|
40
16
|
/**
|
|
41
17
|
* The store for a phone or tablet, or `null` for every other system and for a
|
|
42
18
|
* store the server names no listing for (`links` is the server's answer).
|
|
43
19
|
*
|
|
44
|
-
* The server passes the request's User-Agent
|
|
45
|
-
* `navigator.maxTouchPoints`, the only signal that tells
|
|
46
|
-
* desktop site (it calls itself a Macintosh) from a real
|
|
20
|
+
* The server passes the request's User-Agent (the install link's redirect).
|
|
21
|
+
* A browser may also pass `navigator.maxTouchPoints`, the only signal that tells
|
|
22
|
+
* an iPad asking for the desktop site (it calls itself a Macintosh) from a real
|
|
23
|
+
* Mac; the server cannot, so that iPad lands on the download page and its badges.
|
|
47
24
|
*/
|
|
48
25
|
export function resolveMobileStoreUrl(
|
|
49
26
|
links: MobileAppLinks,
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The install QR code's artwork: the modules of `MOBILE_APP_INSTALL_URL`
|
|
3
|
-
* (`utils/mobile-app`) at version 3, ECC level M, as one SVG path. Packaged with
|
|
4
|
-
* the library like the other assets, so every consumer draws the same code with
|
|
5
|
-
* no host-asset serving. `MobileAppQr` (components/ui) is the component that
|
|
6
|
-
* renders it.
|
|
7
|
-
*
|
|
8
|
-
* It changes WITH that URL. To regenerate: with the `qrcode` package,
|
|
9
|
-
* `QRCode.create(url, { errorCorrectionLevel: 'M' })` gives the modules; each
|
|
10
|
-
* row is one `M{x+4} {y+4}.5h{run}` followed by `m{gap} 0h{run}` per further
|
|
11
|
-
* run (the 4 is the quiet zone), and the viewBox side is `size + 8`.
|
|
12
|
-
*/
|
|
13
|
-
/** The side of the artwork's square viewBox: 29 modules plus a 4-module quiet zone on each side. */
|
|
14
|
-
export declare const MOBILE_APP_QR_VIEWBOX_SIZE = 37;
|
|
15
|
-
/** The dark modules, as horizontal strokes one module tall (`stroke` draws them). */
|
|
16
|
-
export declare const MOBILE_APP_QR_PATH = "M4 4.5h7m2 0h2m2 0h1m2 0h1m2 0h2m1 0h7M4 5.5h1m5 0h1m2 0h2m2 0h3m6 0h1m5 0h1M4 6.5h1m1 0h3m1 0h1m1 0h3m1 0h1m1 0h3m2 0h1m2 0h1m1 0h3m1 0h1M4 7.5h1m1 0h3m1 0h1m1 0h1m1 0h1m1 0h6m2 0h1m1 0h1m1 0h3m1 0h1M4 8.5h1m1 0h3m1 0h1m1 0h3m1 0h1m1 0h1m1 0h5m1 0h1m1 0h3m1 0h1M4 9.5h1m5 0h1m1 0h1m1 0h2m1 0h1m1 0h3m4 0h1m5 0h1M4 10.5h7m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h7M12 11.5h1m2 0h2m5 0h3M4 12.5h1m1 0h5m3 0h1m1 0h8m2 0h5M4 13.5h4m4 0h2m2 0h1m3 0h4m1 0h4m3 0h1M6 14.5h1m3 0h3m1 0h1m3 0h4m2 0h1M4 15.5h1m2 0h3m2 0h2m1 0h2m1 0h1m1 0h1m2 0h5m1 0h1m1 0h1M4 16.5h1m4 0h2m1 0h2m1 0h1m1 0h1m2 0h2m1 0h1m5 0h2M16 17.5h3m3 0h5m1 0h1m3 0h1M5 18.5h1m3 0h2m1 0h1m5 0h2m4 0h1m1 0h2m1 0h2M4 19.5h6m1 0h1m3 0h1m2 0h3m1 0h4m1 0h2m2 0h1M8 20.5h1m1 0h1m2 0h2m1 0h2m1 0h1m1 0h1m5 0h1m1 0h2M4 21.5h1m1 0h2m7 0h1m2 0h1m1 0h9m1 0h1m1 0h1M4 22.5h1m3 0h3m2 0h1m1 0h2m2 0h3m3 0h1m1 0h1m2 0h1M4 23.5h1m6 0h4m3 0h2m3 0h2m2 0h2m2 0h1M4 24.5h1m2 0h4m4 0h1m3 0h1m1 0h1m1 0h6m1 0h3M12 25.5h6m2 0h1m3 0h1m3 0h5M4 26.5h7m3 0h8m1 0h2m1 0h1m1 0h3M4 27.5h1m5 0h1m1 0h2m1 0h3m2 0h1m2 0h2m3 0h1M4 28.5h1m1 0h3m1 0h1m1 0h2m2 0h1m1 0h1m2 0h1m2 0h5m1 0h3M4 29.5h1m1 0h3m1 0h1m1 0h2m1 0h2m3 0h4m3 0h1m1 0h4M4 30.5h1m1 0h3m1 0h1m1 0h1m3 0h4m1 0h1m2 0h8M4 31.5h1m5 0h1m4 0h1m1 0h1m1 0h2m1 0h1m1 0h1m1 0h2m1 0h1m1 0h1M4 32.5h7m1 0h1m2 0h2m4 0h1m1 0h1m3 0h4";
|
|
17
|
-
//# sourceMappingURL=mobile-app-qr.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mobile-app-qr.d.ts","sourceRoot":"","sources":["../../src/assets/mobile-app-qr.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,oGAAoG;AACpG,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAE7C,qFAAqF;AACrF,eAAO,MAAM,kBAAkB,uyCACuwC,CAAC"}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The install QR code's artwork: the modules of `MOBILE_APP_INSTALL_URL`
|
|
3
|
-
* (`utils/mobile-app`) at version 3, ECC level M, as one SVG path. Packaged with
|
|
4
|
-
* the library like the other assets, so every consumer draws the same code with
|
|
5
|
-
* no host-asset serving. `MobileAppQr` (components/ui) is the component that
|
|
6
|
-
* renders it.
|
|
7
|
-
*
|
|
8
|
-
* It changes WITH that URL. To regenerate: with the `qrcode` package,
|
|
9
|
-
* `QRCode.create(url, { errorCorrectionLevel: 'M' })` gives the modules; each
|
|
10
|
-
* row is one `M{x+4} {y+4}.5h{run}` followed by `m{gap} 0h{run}` per further
|
|
11
|
-
* run (the 4 is the quiet zone), and the viewBox side is `size + 8`.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/** The side of the artwork's square viewBox: 29 modules plus a 4-module quiet zone on each side. */
|
|
15
|
-
export const MOBILE_APP_QR_VIEWBOX_SIZE = 37;
|
|
16
|
-
|
|
17
|
-
/** The dark modules, as horizontal strokes one module tall (`stroke` draws them). */
|
|
18
|
-
export const MOBILE_APP_QR_PATH =
|
|
19
|
-
'M4 4.5h7m2 0h2m2 0h1m2 0h1m2 0h2m1 0h7M4 5.5h1m5 0h1m2 0h2m2 0h3m6 0h1m5 0h1M4 6.5h1m1 0h3m1 0h1m1 0h3m1 0h1m1 0h3m2 0h1m2 0h1m1 0h3m1 0h1M4 7.5h1m1 0h3m1 0h1m1 0h1m1 0h1m1 0h6m2 0h1m1 0h1m1 0h3m1 0h1M4 8.5h1m1 0h3m1 0h1m1 0h3m1 0h1m1 0h1m1 0h5m1 0h1m1 0h3m1 0h1M4 9.5h1m5 0h1m1 0h1m1 0h2m1 0h1m1 0h3m4 0h1m5 0h1M4 10.5h7m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h7M12 11.5h1m2 0h2m5 0h3M4 12.5h1m1 0h5m3 0h1m1 0h8m2 0h5M4 13.5h4m4 0h2m2 0h1m3 0h4m1 0h4m3 0h1M6 14.5h1m3 0h3m1 0h1m3 0h4m2 0h1M4 15.5h1m2 0h3m2 0h2m1 0h2m1 0h1m1 0h1m2 0h5m1 0h1m1 0h1M4 16.5h1m4 0h2m1 0h2m1 0h1m1 0h1m2 0h2m1 0h1m5 0h2M16 17.5h3m3 0h5m1 0h1m3 0h1M5 18.5h1m3 0h2m1 0h1m5 0h2m4 0h1m1 0h2m1 0h2M4 19.5h6m1 0h1m3 0h1m2 0h3m1 0h4m1 0h2m2 0h1M8 20.5h1m1 0h1m2 0h2m1 0h2m1 0h1m1 0h1m5 0h1m1 0h2M4 21.5h1m1 0h2m7 0h1m2 0h1m1 0h9m1 0h1m1 0h1M4 22.5h1m3 0h3m2 0h1m1 0h2m2 0h3m3 0h1m1 0h1m2 0h1M4 23.5h1m6 0h4m3 0h2m3 0h2m2 0h2m2 0h1M4 24.5h1m2 0h4m4 0h1m3 0h1m1 0h1m1 0h6m1 0h3M12 25.5h6m2 0h1m3 0h1m3 0h5M4 26.5h7m3 0h8m1 0h2m1 0h1m1 0h3M4 27.5h1m5 0h1m1 0h2m1 0h3m2 0h1m2 0h2m3 0h1M4 28.5h1m1 0h3m1 0h1m1 0h2m2 0h1m1 0h1m2 0h1m2 0h5m1 0h3M4 29.5h1m1 0h3m1 0h1m1 0h2m1 0h2m3 0h4m3 0h1m1 0h4M4 30.5h1m1 0h3m1 0h1m1 0h1m3 0h4m1 0h1m2 0h8M4 31.5h1m5 0h1m4 0h1m1 0h1m1 0h2m1 0h1m1 0h1m1 0h2m1 0h1m1 0h1M4 32.5h7m1 0h1m2 0h2m4 0h1m1 0h1m3 0h4';
|