@phosra/connect 0.1.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 +313 -0
- package/package.json +56 -0
- package/src/core/controller.ts +186 -0
- package/src/core/index.ts +19 -0
- package/src/core/types.ts +122 -0
- package/src/core/useConnect.ts +68 -0
- package/src/index.ts +4 -0
- package/src/native/ConnectFlow.native.tsx +205 -0
- package/src/native/PhosraConnect.native.tsx +719 -0
- package/src/native/assets.native.tsx +98 -0
- package/src/native/index.ts +24 -0
- package/src/native/openAuthorizeUrl.native.ts +56 -0
- package/src/web/ConnectFlow.tsx +156 -0
- package/src/web/PhosraConnect.tsx +286 -0
- package/src/web/assets.tsx +84 -0
- package/src/web/connect.css +358 -0
- package/src/web/index.ts +10 -0
- package/src/web/openAuthorizeUrl.ts +81 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
// Real Phosra + OCSS marks, inlined as React SVG (verbatim geometry from
|
|
4
|
+
// web/public/mark.svg, web/public/logo.svg, and apps/ocss-review OcssMark.tsx).
|
|
5
|
+
// Inlined (not <img>) so they tint via `fill`/`stroke`, need no asset loader, and
|
|
6
|
+
// ship as source into the PCA's bundle.
|
|
7
|
+
|
|
8
|
+
/** The 12-path Phosra "spark" sunburst (viewBox 0 0 66 73). */
|
|
9
|
+
export function Spark({ size = 24, fill = 'currentColor', ...rest }: { size?: number; fill?: string } & React.SVGProps<SVGSVGElement>) {
|
|
10
|
+
return (
|
|
11
|
+
<svg width={size} height={size} viewBox="0 0 66 73" fill={fill} aria-hidden="true" {...rest}>
|
|
12
|
+
<path d="M32.5152 53.8069C29.9079 59.9238 29.9079 66.0408 32.5152 72.1577C35.1225 66.0408 35.1225 59.9238 32.5152 53.8069Z" />
|
|
13
|
+
<path d="M32.5152 0C29.9079 6.11695 29.9079 12.2339 32.5152 18.3508C35.1225 12.2339 35.1225 6.11695 32.5152 0Z" />
|
|
14
|
+
<path d="M40.5035 51.4317C41.0019 58.1757 43.7583 63.4731 48.7727 67.324C48.2743 60.5801 45.5179 55.2826 40.5035 51.4317Z" />
|
|
15
|
+
<path d="M16.2576 4.8338C16.756 11.5778 19.5124 16.8752 24.5268 20.7261C24.0284 13.9822 21.272 8.68471 16.2576 4.8338Z" />
|
|
16
|
+
<path d="M46.3512 44.9432C49.8218 50.5071 54.596 53.5656 60.6739 54.1187C57.2033 48.5547 52.4291 45.4962 46.3512 44.9432Z" />
|
|
17
|
+
<path d="M4.35619 18.0396C7.82677 23.6036 12.601 26.6621 18.6789 27.2151C15.2083 21.6511 10.4341 18.5926 4.35619 18.0396Z" />
|
|
18
|
+
<path d="M48.4919 36.0795C54.0047 38.9725 59.5175 38.9725 65.0303 36.0795C59.5175 33.1864 54.0047 33.1864 48.4919 36.0795Z" />
|
|
19
|
+
<path d="M0 36.0792C5.51282 38.9723 11.0256 38.9723 16.5385 36.0792C11.0256 33.1862 5.51282 33.1862 0 36.0792Z" />
|
|
20
|
+
<path d="M46.3514 27.2154C52.4293 26.6624 57.2035 23.6039 60.6741 18.0399C54.5962 18.5929 49.822 21.6514 46.3514 27.2154Z" />
|
|
21
|
+
<path d="M4.35626 54.1187C10.4341 53.5657 15.2084 50.5072 18.679 44.9432C12.6011 45.4963 7.82684 48.5548 4.35626 54.1187Z" />
|
|
22
|
+
<path d="M40.5037 20.7286C45.5181 16.8777 48.2745 11.5802 48.7729 4.83625C43.7585 8.68715 41.0021 13.9846 40.5037 20.7286Z" />
|
|
23
|
+
<path d="M16.2579 67.3262C21.2722 63.4753 24.0286 58.1779 24.527 51.4339C19.5126 55.2848 16.7562 60.5822 16.2579 67.3262Z" />
|
|
24
|
+
</svg>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The full "phosra" wordmark with the spark integrated as the "o" (viewBox 0 0 404 108). */
|
|
29
|
+
export function PhosraWordmark({ height = 18, fill = 'currentColor', ...rest }: { height?: number; fill?: string } & React.SVGProps<SVGSVGElement>) {
|
|
30
|
+
return (
|
|
31
|
+
<svg height={height} viewBox="0 0 404 108" fill={fill} aria-label="Phosra" role="img" {...rest}>
|
|
32
|
+
<path d="M15.936 107.464C14.88 107.464 14.016 107.176 13.44 106.504C12.864 105.832 12.576 104.872 12.576 103.72V58.408C12.576 53.416 13.632 49 15.744 44.968C17.856 40.936 20.736 37.768 24.288 35.464C27.936 33.16 31.968 32.008 36.48 32.008C41.088 32.008 45.216 33.256 48.864 35.56C52.416 37.864 55.296 41.032 57.408 45.064C59.52 49.096 60.576 53.704 60.576 58.792C60.576 63.88 59.52 68.392 57.504 72.424C55.488 76.456 52.704 79.72 49.152 82.024C45.696 84.328 41.664 85.48 37.248 85.48C33.408 85.48 29.856 84.616 26.784 82.792C23.616 80.968 21.12 78.568 19.2 75.496V103.72C19.2 104.872 18.912 105.832 18.24 106.504C17.664 107.176 16.896 107.464 15.936 107.464ZM36.48 78.76C39.84 78.76 42.816 77.896 45.408 76.168C48.096 74.44 50.208 72.136 51.744 69.064C53.28 65.992 54.048 62.632 54.048 58.792C54.048 54.952 53.28 51.592 51.744 48.52C50.208 45.544 48.096 43.144 45.408 41.416C42.816 39.688 39.84 38.728 36.48 38.728C33.216 38.728 30.24 39.688 27.552 41.416C24.96 43.144 22.848 45.544 21.312 48.52C19.872 51.592 19.104 54.952 19.104 58.792C19.104 62.632 19.872 65.992 21.312 69.064C22.848 72.136 24.96 74.44 27.552 76.168C30.24 77.896 33.216 78.76 36.48 78.76ZM90.0923 63.592C89.0363 63.592 88.1723 63.304 87.5963 62.536C87.0203 61.864 86.7323 61 86.7323 59.848V13.768C86.7323 12.616 87.0203 11.752 87.5963 11.08C88.1723 10.408 89.0363 10.024 90.0923 10.024C91.0523 10.024 91.8203 10.408 92.3963 11.08C93.0683 11.752 93.3563 12.616 93.3563 13.768V59.848C93.3563 61 93.0683 61.864 92.3963 62.536C91.8203 63.304 91.0523 63.592 90.0923 63.592ZM127.244 85C126.188 85 125.42 84.712 124.748 83.944C124.172 83.272 123.884 82.408 123.884 81.352V56.392C123.884 52.456 123.116 49.192 121.772 46.6C120.428 44.104 118.604 42.184 116.3 40.84C113.9 39.592 111.308 38.92 108.332 38.92C105.548 38.92 102.956 39.592 100.748 40.744C98.4443 41.992 96.6203 43.624 95.3723 45.736C94.0283 47.848 93.3563 50.248 93.3563 52.936H88.5563C88.7483 48.904 89.7083 45.352 91.5323 42.184C93.3563 39.016 95.8523 36.52 98.8282 34.696C101.9 32.872 105.356 31.912 109.1 31.912C113.132 31.912 116.78 32.872 120.044 34.792C123.308 36.712 125.804 39.4 127.724 43.048C129.548 46.696 130.508 51.208 130.508 56.392V81.352C130.508 82.408 130.22 83.272 129.548 83.944C128.876 84.712 128.108 85 127.244 85ZM90.0923 85C89.0363 85 88.1723 84.712 87.5963 84.04C87.0203 83.368 86.7323 82.504 86.7323 81.352V36.232C86.7323 35.08 87.0203 34.216 87.5963 33.544C88.1723 32.872 89.0363 32.488 90.0923 32.488C91.0523 32.488 91.8203 32.872 92.3963 33.544C93.0683 34.216 93.3563 35.08 93.3563 36.232V81.352C93.3563 82.504 93.0683 83.368 92.3963 84.04C91.8203 84.712 91.0523 85 90.0923 85ZM248.93 85.48C245.474 85.48 242.114 84.904 238.754 83.56C235.49 82.312 232.706 80.392 230.69 77.8C230.018 77.032 229.826 76.168 229.922 75.208C230.018 74.248 230.402 73.48 231.17 72.712C232.034 72.136 232.802 71.848 233.666 71.944C234.53 72.136 235.298 72.52 235.874 73.192C237.218 75.016 239.042 76.36 241.346 77.32C243.65 78.28 246.146 78.76 248.93 78.76C253.154 78.76 256.226 77.992 258.146 76.36C260.066 74.728 261.026 72.712 261.122 70.408C261.122 68.104 260.066 66.28 258.146 64.744C256.226 63.208 252.962 62.056 248.45 61.192C242.69 60.232 238.37 58.408 235.682 55.816C232.994 53.32 231.65 50.248 231.65 46.696C231.65 43.432 232.418 40.744 233.954 38.536C235.49 36.424 237.602 34.792 240.194 33.64C242.786 32.584 245.666 32.008 248.738 32.008C252.578 32.008 255.938 32.776 258.722 34.216C261.506 35.752 263.81 37.672 265.538 40.168C266.114 41.032 266.306 41.896 266.21 42.76C266.114 43.72 265.634 44.392 264.77 44.968C264.098 45.448 263.33 45.64 262.466 45.448C261.506 45.256 260.738 44.776 260.162 44.008C258.722 42.184 256.994 40.84 255.17 39.976C253.25 39.112 251.042 38.632 248.546 38.632C245.378 38.632 242.882 39.4 240.962 40.84C239.042 42.28 238.082 44.104 238.082 46.216C238.082 47.752 238.466 49 239.234 50.056C239.906 51.208 241.154 52.168 242.882 52.936C244.706 53.8 247.106 54.472 250.274 55.048C254.594 55.816 258.05 56.968 260.546 58.504C263.042 60.04 264.866 61.768 265.922 63.784C266.978 65.8 267.458 68.008 267.458 70.312C267.458 73.384 266.69 75.976 265.154 78.28C263.522 80.584 261.314 82.312 258.53 83.56C255.746 84.904 252.578 85.48 248.93 85.48ZM296.233 52.648C296.425 48.712 297.385 45.16 299.113 41.992C300.745 38.92 302.953 36.424 305.737 34.6C308.425 32.872 311.497 31.912 314.857 31.912C317.449 31.912 319.561 32.392 321.001 33.16C322.441 34.024 322.921 35.272 322.633 36.808C322.345 37.768 321.961 38.44 321.481 38.728C320.905 39.112 320.233 39.208 319.465 39.112C318.697 39.112 317.833 39.016 316.873 38.92C313.609 38.632 310.729 39.016 308.233 40.072C305.737 41.128 303.721 42.76 302.281 44.872C300.841 47.08 300.073 49.672 300.073 52.648H296.233ZM296.809 85C295.753 85 294.985 84.712 294.409 84.04C293.833 83.464 293.545 82.504 293.545 81.352V36.136C293.545 34.984 293.833 34.12 294.409 33.448C294.985 32.872 295.753 32.488 296.809 32.488C297.865 32.488 298.633 32.872 299.209 33.448C299.785 34.12 300.073 34.984 300.073 36.136V81.352C300.073 82.504 299.785 83.464 299.209 84.04C298.633 84.712 297.865 85 296.809 85ZM365.187 85.48C360.675 85.48 356.739 84.328 353.187 82.024C349.731 79.72 346.947 76.456 344.835 72.424C342.819 68.392 341.859 63.88 341.859 58.792C341.859 53.704 342.819 49.096 344.931 45.064C347.043 41.032 349.923 37.864 353.571 35.56C357.219 33.256 361.251 32.008 365.859 32.008C370.371 32.008 374.499 33.256 378.051 35.56C381.699 37.864 384.483 41.032 386.595 45.064C388.707 49.096 389.763 53.704 389.859 58.792L387.171 61.096C387.171 65.704 386.211 69.928 384.291 73.576C382.371 77.224 379.683 80.2 376.419 82.312C373.059 84.424 369.315 85.48 365.187 85.48ZM365.859 78.76C369.123 78.76 372.195 77.896 374.787 76.168C377.379 74.44 379.491 72.136 381.027 69.064C382.563 65.992 383.331 62.632 383.331 58.792C383.331 54.952 382.563 51.592 381.027 48.52C379.491 45.544 377.379 43.144 374.787 41.416C372.195 39.688 369.123 38.728 365.859 38.728C362.499 38.728 359.619 39.688 356.931 41.416C354.243 43.144 352.227 45.544 350.595 48.52C349.059 51.592 348.291 54.952 348.291 58.792C348.291 62.632 349.059 65.992 350.595 69.064C352.227 72.136 354.243 74.44 356.931 76.168C359.619 77.896 362.499 78.76 365.859 78.76ZM386.499 85C385.539 85 384.675 84.712 384.099 84.04C383.427 83.368 383.139 82.504 383.139 81.352V64.936L384.771 57.352L389.859 58.792V81.352C389.859 82.504 389.571 83.368 388.899 84.04C388.227 84.712 387.459 85 386.499 85Z" />
|
|
33
|
+
<path d="M179.515 76.1357C176.908 82.2527 176.908 88.3696 179.515 94.4866C182.123 88.3696 182.123 82.2527 179.515 76.1357Z" />
|
|
34
|
+
<path d="M179.515 22.3289C176.908 28.4458 176.908 34.5627 179.515 40.6797C182.123 34.5627 182.123 28.4458 179.515 22.3289Z" />
|
|
35
|
+
<path d="M187.504 73.7606C188.002 80.5045 190.758 85.802 195.773 89.6529C195.274 82.9089 192.518 77.6115 187.504 73.7606Z" />
|
|
36
|
+
<path d="M163.258 27.1627C163.756 33.9066 166.512 39.2041 171.527 43.055C171.028 36.311 168.272 31.0136 163.258 27.1627Z" />
|
|
37
|
+
<path d="M193.351 67.272C196.822 72.836 201.596 75.8945 207.674 76.4476C204.203 70.8836 199.429 67.8251 193.351 67.272Z" />
|
|
38
|
+
<path d="M151.356 40.3685C154.827 45.9324 159.601 48.9909 165.679 49.5439C162.208 43.98 157.434 40.9215 151.356 40.3685Z" />
|
|
39
|
+
<path d="M195.492 58.4083C201.005 61.3014 206.518 61.3014 212.03 58.4083C206.518 55.5153 201.005 55.5153 195.492 58.4083Z" />
|
|
40
|
+
<path d="M147 58.4081C152.513 61.3011 158.026 61.3011 163.538 58.4081C158.026 55.515 152.513 55.515 147 58.4081Z" />
|
|
41
|
+
<path d="M193.351 49.5442C199.429 48.9912 204.204 45.9327 207.674 40.3688C201.596 40.9218 196.822 43.9803 193.351 49.5442Z" />
|
|
42
|
+
<path d="M151.356 76.4476C157.434 75.8945 162.208 72.836 165.679 67.2721C159.601 67.8251 154.827 70.8836 151.356 76.4476Z" />
|
|
43
|
+
<path d="M187.504 43.0572C192.518 39.2063 195.274 33.9088 195.773 27.1649C190.758 31.0158 188.002 36.3132 187.504 43.0572Z" />
|
|
44
|
+
<path d="M163.258 89.6548C168.272 85.8039 171.029 80.5065 171.527 73.7625C166.513 77.6134 163.756 82.9109 163.258 89.6548Z" />
|
|
45
|
+
</svg>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The OCSS bracket-document glyph [≡] (viewBox 0 0 32 32). */
|
|
50
|
+
export function OcssMark({ size = 16, stroke = 'currentColor', ...rest }: { size?: number; stroke?: string } & React.SVGProps<SVGSVGElement>) {
|
|
51
|
+
return (
|
|
52
|
+
<svg width={size} height={size} viewBox="0 0 32 32" fill="none" aria-hidden="true" {...rest}>
|
|
53
|
+
<path d="M11 7.5H8.5V24.5H11" stroke={stroke} strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round" />
|
|
54
|
+
<path d="M21 7.5H23.5V24.5H21" stroke={stroke} strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round" />
|
|
55
|
+
<line x1="14" y1="13" x2="18" y2="13" stroke={stroke} strokeWidth="2.4" strokeLinecap="round" />
|
|
56
|
+
<line x1="14" y1="16" x2="18" y2="16" stroke={stroke} strokeWidth="2.4" strokeLinecap="round" />
|
|
57
|
+
<line x1="14" y1="19" x2="16.5" y2="19" stroke={stroke} strokeWidth="2.4" strokeLinecap="round" />
|
|
58
|
+
</svg>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Generic platform glyph placeholder (a chat bubble). The PCA passes its own
|
|
64
|
+
* platform logo via the `platformGlyph` prop; this is only the fallback.
|
|
65
|
+
*/
|
|
66
|
+
export function PlatformGlyph({ size = 30, fill = '#00C46A', ...rest }: { size?: number; fill?: string } & React.SVGProps<SVGSVGElement>) {
|
|
67
|
+
return (
|
|
68
|
+
<svg width={size} height={size} viewBox="0 0 32 32" aria-hidden="true" {...rest}>
|
|
69
|
+
<rect x="4" y="6" width="24" height="18" rx="7" fill={fill} />
|
|
70
|
+
<path d="M11 30l3-6h4z" fill={fill} />
|
|
71
|
+
<circle cx="12" cy="15" r="1.7" fill="#fff" />
|
|
72
|
+
<circle cx="16" cy="15" r="1.7" fill="#fff" />
|
|
73
|
+
<circle cx="20" cy="15" r="1.7" fill="#fff" />
|
|
74
|
+
</svg>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function CheckIcon({ size = 13, stroke = '#00A15C', ...rest }: { size?: number; stroke?: string } & React.SVGProps<SVGSVGElement>) {
|
|
79
|
+
return (
|
|
80
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" aria-hidden="true" {...rest}>
|
|
81
|
+
<path d="M5 12l5 5L19 7" stroke={stroke} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
|
|
82
|
+
</svg>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/* Phosra Connect — styled drop-in. Ports the approved mockup (connect-mocks/
|
|
2
|
+
final-a-plus-c.html). All tokens are CSS variables scoped to .phosra-connect so
|
|
3
|
+
a PCA can retheme without forking. Ships as source into the PCA's bundle. */
|
|
4
|
+
|
|
5
|
+
.phosra-connect {
|
|
6
|
+
--pc-ink: #0c110e;
|
|
7
|
+
--pc-muted: #6b7772;
|
|
8
|
+
--pc-faint: #9aa5a0;
|
|
9
|
+
--pc-line: #eaedeb;
|
|
10
|
+
--pc-signal: #00d47e;
|
|
11
|
+
--pc-deep: #00794a;
|
|
12
|
+
--pc-green600: #00b568;
|
|
13
|
+
--pc-paper: #ffffff;
|
|
14
|
+
|
|
15
|
+
box-sizing: border-box;
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
width: 100%;
|
|
19
|
+
min-height: 100%;
|
|
20
|
+
padding: 14px 26px 14px;
|
|
21
|
+
background: var(--pc-paper);
|
|
22
|
+
color: var(--pc-ink);
|
|
23
|
+
font-family: -apple-system, "SF Pro Display", system-ui, sans-serif;
|
|
24
|
+
-webkit-font-smoothing: antialiased;
|
|
25
|
+
}
|
|
26
|
+
.phosra-connect *,
|
|
27
|
+
.phosra-connect *::before,
|
|
28
|
+
.phosra-connect *::after {
|
|
29
|
+
box-sizing: border-box;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* header */
|
|
33
|
+
.phosra-connect__header {
|
|
34
|
+
position: relative;
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
justify-content: space-between;
|
|
38
|
+
height: 46px;
|
|
39
|
+
flex: 0 0 auto;
|
|
40
|
+
}
|
|
41
|
+
.phosra-connect__nav {
|
|
42
|
+
appearance: none;
|
|
43
|
+
border: 0;
|
|
44
|
+
background: none;
|
|
45
|
+
font-size: 22px;
|
|
46
|
+
line-height: 1;
|
|
47
|
+
color: var(--pc-ink);
|
|
48
|
+
width: 26px;
|
|
49
|
+
padding: 0;
|
|
50
|
+
cursor: pointer;
|
|
51
|
+
}
|
|
52
|
+
.phosra-connect__nav--close {
|
|
53
|
+
text-align: right;
|
|
54
|
+
font-weight: 400;
|
|
55
|
+
}
|
|
56
|
+
.phosra-connect__brand {
|
|
57
|
+
position: absolute;
|
|
58
|
+
left: 50%;
|
|
59
|
+
transform: translateX(-50%);
|
|
60
|
+
display: flex;
|
|
61
|
+
align-items: center;
|
|
62
|
+
gap: 9px;
|
|
63
|
+
}
|
|
64
|
+
.phosra-connect__wordmark {
|
|
65
|
+
display: block;
|
|
66
|
+
color: var(--pc-ink);
|
|
67
|
+
}
|
|
68
|
+
.phosra-connect__divider {
|
|
69
|
+
width: 1px;
|
|
70
|
+
height: 15px;
|
|
71
|
+
background: #d7ddd9;
|
|
72
|
+
}
|
|
73
|
+
.phosra-connect__ocss {
|
|
74
|
+
display: flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
gap: 4px;
|
|
77
|
+
opacity: 0.72;
|
|
78
|
+
}
|
|
79
|
+
.phosra-connect__ocss-label {
|
|
80
|
+
font-size: 10.5px;
|
|
81
|
+
font-weight: 800;
|
|
82
|
+
letter-spacing: 1.2px;
|
|
83
|
+
color: #37433d;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* body */
|
|
87
|
+
.phosra-connect__body {
|
|
88
|
+
display: flex;
|
|
89
|
+
flex-direction: column;
|
|
90
|
+
align-items: stretch;
|
|
91
|
+
flex: 1 1 auto;
|
|
92
|
+
}
|
|
93
|
+
.phosra-connect__logos {
|
|
94
|
+
display: flex;
|
|
95
|
+
justify-content: center;
|
|
96
|
+
align-items: center;
|
|
97
|
+
margin-top: 48px;
|
|
98
|
+
}
|
|
99
|
+
.phosra-connect__logo {
|
|
100
|
+
width: 64px;
|
|
101
|
+
height: 64px;
|
|
102
|
+
border-radius: 50%;
|
|
103
|
+
display: flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
}
|
|
107
|
+
.phosra-connect__logo--phosra {
|
|
108
|
+
background: #eafbf3;
|
|
109
|
+
}
|
|
110
|
+
.phosra-connect__logo--platform {
|
|
111
|
+
background: var(--pc-paper);
|
|
112
|
+
border: 1.5px solid var(--pc-line);
|
|
113
|
+
margin-left: -14px;
|
|
114
|
+
box-shadow: 0 3px 12px rgba(16, 24, 32, 0.09);
|
|
115
|
+
}
|
|
116
|
+
.phosra-connect__title {
|
|
117
|
+
text-align: center;
|
|
118
|
+
font-size: 25px;
|
|
119
|
+
font-weight: 800;
|
|
120
|
+
letter-spacing: -0.5px;
|
|
121
|
+
margin: 30px 0 0;
|
|
122
|
+
}
|
|
123
|
+
.phosra-connect__sub {
|
|
124
|
+
text-align: center;
|
|
125
|
+
font-size: 15.5px;
|
|
126
|
+
line-height: 1.5;
|
|
127
|
+
color: var(--pc-muted);
|
|
128
|
+
margin: 12px 0 0;
|
|
129
|
+
padding: 0 6px;
|
|
130
|
+
}
|
|
131
|
+
.phosra-connect__sub b {
|
|
132
|
+
color: var(--pc-ink);
|
|
133
|
+
font-weight: 700;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* accreditation chip */
|
|
137
|
+
.phosra-connect__chip {
|
|
138
|
+
display: flex;
|
|
139
|
+
justify-content: center;
|
|
140
|
+
margin-top: 20px;
|
|
141
|
+
}
|
|
142
|
+
.phosra-connect__chip-pill {
|
|
143
|
+
display: inline-flex;
|
|
144
|
+
align-items: center;
|
|
145
|
+
gap: 7px;
|
|
146
|
+
padding: 7px 14px;
|
|
147
|
+
border: 1px solid #d7ece1;
|
|
148
|
+
background: #f3fbf7;
|
|
149
|
+
border-radius: 22px;
|
|
150
|
+
font-size: 12px;
|
|
151
|
+
font-weight: 700;
|
|
152
|
+
color: var(--pc-deep);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* rules preview */
|
|
156
|
+
.phosra-connect__list {
|
|
157
|
+
border: 1px solid var(--pc-line);
|
|
158
|
+
border-radius: 16px;
|
|
159
|
+
margin-top: 28px;
|
|
160
|
+
overflow: hidden;
|
|
161
|
+
}
|
|
162
|
+
.phosra-connect__eyebrow {
|
|
163
|
+
font-size: 11px;
|
|
164
|
+
font-weight: 800;
|
|
165
|
+
letter-spacing: 1.2px;
|
|
166
|
+
color: var(--pc-muted);
|
|
167
|
+
padding: 15px 18px 11px;
|
|
168
|
+
}
|
|
169
|
+
.phosra-connect__row {
|
|
170
|
+
display: flex;
|
|
171
|
+
align-items: center;
|
|
172
|
+
gap: 13px;
|
|
173
|
+
padding: 14px 18px;
|
|
174
|
+
border-top: 1px solid var(--pc-line);
|
|
175
|
+
}
|
|
176
|
+
.phosra-connect__tick {
|
|
177
|
+
width: 22px;
|
|
178
|
+
height: 22px;
|
|
179
|
+
border-radius: 6px;
|
|
180
|
+
background: #e7f5ee;
|
|
181
|
+
display: flex;
|
|
182
|
+
align-items: center;
|
|
183
|
+
justify-content: center;
|
|
184
|
+
flex: 0 0 auto;
|
|
185
|
+
}
|
|
186
|
+
.phosra-connect__row-label {
|
|
187
|
+
font-size: 15.5px;
|
|
188
|
+
font-weight: 600;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* child picker */
|
|
192
|
+
.phosra-connect__picker {
|
|
193
|
+
margin-top: 26px;
|
|
194
|
+
border: 1px solid var(--pc-line);
|
|
195
|
+
border-radius: 16px;
|
|
196
|
+
overflow: hidden;
|
|
197
|
+
}
|
|
198
|
+
.phosra-connect__picker-row {
|
|
199
|
+
appearance: none;
|
|
200
|
+
width: 100%;
|
|
201
|
+
border: 0;
|
|
202
|
+
border-top: 1px solid var(--pc-line);
|
|
203
|
+
background: none;
|
|
204
|
+
display: flex;
|
|
205
|
+
align-items: center;
|
|
206
|
+
justify-content: space-between;
|
|
207
|
+
gap: 12px;
|
|
208
|
+
padding: 16px 18px;
|
|
209
|
+
font-size: 16px;
|
|
210
|
+
font-weight: 600;
|
|
211
|
+
color: var(--pc-ink);
|
|
212
|
+
cursor: pointer;
|
|
213
|
+
text-align: left;
|
|
214
|
+
}
|
|
215
|
+
.phosra-connect__picker-row:first-child {
|
|
216
|
+
border-top: 0;
|
|
217
|
+
}
|
|
218
|
+
.phosra-connect__picker-row:hover {
|
|
219
|
+
background: #f7faf8;
|
|
220
|
+
}
|
|
221
|
+
.phosra-connect__picker-chevron {
|
|
222
|
+
color: var(--pc-faint);
|
|
223
|
+
font-size: 20px;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* status / spinner */
|
|
227
|
+
.phosra-connect__status {
|
|
228
|
+
display: flex;
|
|
229
|
+
flex-direction: column;
|
|
230
|
+
align-items: center;
|
|
231
|
+
gap: 14px;
|
|
232
|
+
margin-top: 40px;
|
|
233
|
+
}
|
|
234
|
+
.phosra-connect__status-label {
|
|
235
|
+
font-size: 15.5px;
|
|
236
|
+
color: var(--pc-muted);
|
|
237
|
+
font-weight: 600;
|
|
238
|
+
}
|
|
239
|
+
.phosra-connect__spinner {
|
|
240
|
+
width: 26px;
|
|
241
|
+
height: 26px;
|
|
242
|
+
border-radius: 50%;
|
|
243
|
+
border: 3px solid #d7ece1;
|
|
244
|
+
border-top-color: var(--pc-green600);
|
|
245
|
+
animation: phosra-connect-spin 0.7s linear infinite;
|
|
246
|
+
}
|
|
247
|
+
.phosra-connect__spinner--sm {
|
|
248
|
+
width: 16px;
|
|
249
|
+
height: 16px;
|
|
250
|
+
border-width: 2px;
|
|
251
|
+
border-top-color: #04150d;
|
|
252
|
+
}
|
|
253
|
+
@keyframes phosra-connect-spin {
|
|
254
|
+
to {
|
|
255
|
+
transform: rotate(360deg);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/* success */
|
|
260
|
+
.phosra-connect__success {
|
|
261
|
+
display: flex;
|
|
262
|
+
flex-direction: column;
|
|
263
|
+
align-items: center;
|
|
264
|
+
text-align: center;
|
|
265
|
+
gap: 14px;
|
|
266
|
+
margin-top: 34px;
|
|
267
|
+
}
|
|
268
|
+
.phosra-connect__success-badge {
|
|
269
|
+
width: 62px;
|
|
270
|
+
height: 62px;
|
|
271
|
+
border-radius: 50%;
|
|
272
|
+
background: #eafbf3;
|
|
273
|
+
display: flex;
|
|
274
|
+
align-items: center;
|
|
275
|
+
justify-content: center;
|
|
276
|
+
}
|
|
277
|
+
.phosra-connect__success-title {
|
|
278
|
+
font-size: 23px;
|
|
279
|
+
font-weight: 800;
|
|
280
|
+
letter-spacing: -0.4px;
|
|
281
|
+
}
|
|
282
|
+
.phosra-connect__verified {
|
|
283
|
+
display: inline-flex;
|
|
284
|
+
align-items: center;
|
|
285
|
+
gap: 6px;
|
|
286
|
+
font-size: 12.5px;
|
|
287
|
+
font-weight: 700;
|
|
288
|
+
color: var(--pc-deep);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/* error */
|
|
292
|
+
.phosra-connect__error {
|
|
293
|
+
display: flex;
|
|
294
|
+
flex-direction: column;
|
|
295
|
+
align-items: center;
|
|
296
|
+
text-align: center;
|
|
297
|
+
gap: 10px;
|
|
298
|
+
margin-top: 40px;
|
|
299
|
+
}
|
|
300
|
+
.phosra-connect__error-title {
|
|
301
|
+
font-size: 19px;
|
|
302
|
+
font-weight: 800;
|
|
303
|
+
}
|
|
304
|
+
.phosra-connect__error-msg {
|
|
305
|
+
font-size: 14px;
|
|
306
|
+
color: var(--pc-muted);
|
|
307
|
+
line-height: 1.5;
|
|
308
|
+
max-width: 300px;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/* footer + actions */
|
|
312
|
+
.phosra-connect__spacer {
|
|
313
|
+
flex: 1 1 auto;
|
|
314
|
+
min-height: 20px;
|
|
315
|
+
}
|
|
316
|
+
.phosra-connect__footnote {
|
|
317
|
+
text-align: center;
|
|
318
|
+
font-size: 11.5px;
|
|
319
|
+
color: var(--pc-faint);
|
|
320
|
+
line-height: 1.5;
|
|
321
|
+
padding: 0 16px 11px;
|
|
322
|
+
margin: 0;
|
|
323
|
+
}
|
|
324
|
+
.phosra-connect__actions {
|
|
325
|
+
display: flex;
|
|
326
|
+
flex-direction: column;
|
|
327
|
+
gap: 8px;
|
|
328
|
+
flex: 0 0 auto;
|
|
329
|
+
}
|
|
330
|
+
.phosra-connect__btn {
|
|
331
|
+
appearance: none;
|
|
332
|
+
border: 0;
|
|
333
|
+
border-radius: 16px;
|
|
334
|
+
height: 56px;
|
|
335
|
+
display: flex;
|
|
336
|
+
align-items: center;
|
|
337
|
+
justify-content: center;
|
|
338
|
+
gap: 8px;
|
|
339
|
+
font-size: 17px;
|
|
340
|
+
font-weight: 800;
|
|
341
|
+
cursor: pointer;
|
|
342
|
+
font-family: inherit;
|
|
343
|
+
}
|
|
344
|
+
.phosra-connect__btn--primary {
|
|
345
|
+
background: var(--pc-signal);
|
|
346
|
+
color: #04150d;
|
|
347
|
+
}
|
|
348
|
+
.phosra-connect__btn--primary:disabled {
|
|
349
|
+
opacity: 0.6;
|
|
350
|
+
cursor: default;
|
|
351
|
+
}
|
|
352
|
+
.phosra-connect__btn--ghost {
|
|
353
|
+
background: none;
|
|
354
|
+
color: var(--pc-muted);
|
|
355
|
+
height: 44px;
|
|
356
|
+
font-weight: 700;
|
|
357
|
+
font-size: 15px;
|
|
358
|
+
}
|
package/src/web/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { PhosraConnect } from './PhosraConnect.js';
|
|
2
|
+
export type { PhosraConnectProps } from './PhosraConnect.js';
|
|
3
|
+
export { ConnectFlow } from './ConnectFlow.js';
|
|
4
|
+
export type { ConnectFlowProps } from './ConnectFlow.js';
|
|
5
|
+
export { createWebAuthorizeOpener } from './openAuthorizeUrl.js';
|
|
6
|
+
export type { WebAuthorizeOpenerOptions } from './openAuthorizeUrl.js';
|
|
7
|
+
export { Spark, PhosraWordmark, OcssMark, PlatformGlyph, CheckIcon } from './assets.js';
|
|
8
|
+
|
|
9
|
+
// Re-export the core so `@phosra/connect/web` consumers get the hook + types too.
|
|
10
|
+
export * from '../core/index.js';
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { AuthorizeOpener } from '../core/types.js';
|
|
2
|
+
|
|
3
|
+
export interface WebAuthorizeOpenerOptions {
|
|
4
|
+
/** Popup dimensions. */
|
|
5
|
+
width?: number;
|
|
6
|
+
height?: number;
|
|
7
|
+
/**
|
|
8
|
+
* The origin the redirect page will `postMessage` from. Defaults to the
|
|
9
|
+
* current origin (the PCA's own domain hosts the redirect page). The opener
|
|
10
|
+
* ignores messages from any other origin.
|
|
11
|
+
*/
|
|
12
|
+
expectedOrigin?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Web "open secure webview" primitive. Opens `authorizeUrl` in a centered popup.
|
|
17
|
+
* The PCA hosts a tiny redirect page at `redirectUri` (its OWN origin) that reads
|
|
18
|
+
* `code` + `state` off its query string and does:
|
|
19
|
+
*
|
|
20
|
+
* window.opener.postMessage({ type: 'phosra-connect', code, state }, location.origin);
|
|
21
|
+
* window.close();
|
|
22
|
+
*
|
|
23
|
+
* This opener resolves `{code, state}` from that message, or `{canceled: true}`
|
|
24
|
+
* if the parent closes the popup. Everything stays on the PCA's origin — no
|
|
25
|
+
* Phosra-hosted page, no iframe.
|
|
26
|
+
*/
|
|
27
|
+
export function createWebAuthorizeOpener(options: WebAuthorizeOpenerOptions = {}): AuthorizeOpener {
|
|
28
|
+
const { width = 480, height = 720 } = options;
|
|
29
|
+
|
|
30
|
+
return (authorizeUrl) =>
|
|
31
|
+
new Promise((resolve, reject) => {
|
|
32
|
+
const expectedOrigin = options.expectedOrigin ?? window.location.origin;
|
|
33
|
+
const dualScreenLeft = window.screenLeft ?? 0;
|
|
34
|
+
const dualScreenTop = window.screenTop ?? 0;
|
|
35
|
+
const left = dualScreenLeft + (window.outerWidth - width) / 2;
|
|
36
|
+
const top = dualScreenTop + (window.outerHeight - height) / 2;
|
|
37
|
+
|
|
38
|
+
const popup = window.open(
|
|
39
|
+
authorizeUrl,
|
|
40
|
+
'phosra-connect',
|
|
41
|
+
`popup=1,width=${width},height=${height},left=${Math.max(0, left)},top=${Math.max(0, top)}`,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// Popup blocked (no user gesture / blocker / extension). Reject so the
|
|
45
|
+
// controller lands in error{authorizing} instead of a spinner that never
|
|
46
|
+
// resolves — never leave a failure hidden behind a hung state.
|
|
47
|
+
if (!popup) {
|
|
48
|
+
reject(new Error('Couldn’t open the secure window. Please allow popups and try again.'));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let settled = false;
|
|
53
|
+
const finish = (result: { code: string; state: string } | { canceled: true }) => {
|
|
54
|
+
if (settled) return;
|
|
55
|
+
settled = true;
|
|
56
|
+
window.removeEventListener('message', onMessage);
|
|
57
|
+
clearInterval(pollClosed);
|
|
58
|
+
try {
|
|
59
|
+
popup?.close();
|
|
60
|
+
} catch {
|
|
61
|
+
/* cross-origin close may throw; ignore */
|
|
62
|
+
}
|
|
63
|
+
resolve(result);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const onMessage = (ev: MessageEvent) => {
|
|
67
|
+
if (ev.origin !== expectedOrigin) return;
|
|
68
|
+
const data = ev.data as { type?: string; code?: string; state?: string } | null;
|
|
69
|
+
if (!data || data.type !== 'phosra-connect') return;
|
|
70
|
+
if (typeof data.code === 'string' && typeof data.state === 'string') {
|
|
71
|
+
finish({ code: data.code, state: data.state });
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
window.addEventListener('message', onMessage);
|
|
75
|
+
|
|
76
|
+
// Parent closed the popup without completing → treat as cancel.
|
|
77
|
+
const pollClosed = setInterval(() => {
|
|
78
|
+
if (popup?.closed) finish({ canceled: true });
|
|
79
|
+
}, 400);
|
|
80
|
+
});
|
|
81
|
+
}
|