@manyrows/appkit-react 0.1.4 → 0.1.6

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/index.cjs CHANGED
@@ -114,58 +114,6 @@ function AppKitAuthed(props) {
114
114
  function mkErr(code, message, details) {
115
115
  return { code, message, details };
116
116
  }
117
- function DefaultSkeleton() {
118
- return /* @__PURE__ */ jsxRuntime.jsxs(
119
- "div",
120
- {
121
- "aria-busy": "true",
122
- "aria-live": "polite",
123
- style: {
124
- width: "100%",
125
- borderRadius: 10,
126
- border: "1px solid rgba(0,0,0,0.08)",
127
- padding: 12
128
- },
129
- children: [
130
- /* @__PURE__ */ jsxRuntime.jsx(
131
- "div",
132
- {
133
- style: {
134
- height: 12,
135
- width: 140,
136
- borderRadius: 6,
137
- background: "rgba(0,0,0,0.06)",
138
- marginBottom: 10
139
- }
140
- }
141
- ),
142
- /* @__PURE__ */ jsxRuntime.jsx(
143
- "div",
144
- {
145
- style: {
146
- height: 10,
147
- width: "65%",
148
- borderRadius: 6,
149
- background: "rgba(0,0,0,0.06)",
150
- marginBottom: 8
151
- }
152
- }
153
- ),
154
- /* @__PURE__ */ jsxRuntime.jsx(
155
- "div",
156
- {
157
- style: {
158
- height: 10,
159
- width: "40%",
160
- borderRadius: 6,
161
- background: "rgba(0,0,0,0.06)"
162
- }
163
- }
164
- )
165
- ]
166
- }
167
- );
168
- }
169
117
  function DefaultError({ err }) {
170
118
  return /* @__PURE__ */ jsxRuntime.jsxs(
171
119
  "div",
@@ -335,6 +283,7 @@ function AppKit(props) {
335
283
  workspace: props.workspace.trim(),
336
284
  appId: props.appId.trim(),
337
285
  baseURL: props.baseURL,
286
+ theme: props.theme,
338
287
  silent: props.silent,
339
288
  throwOnError: props.throwOnError,
340
289
  // ✅ If host provides children, hide runtime default authed UI.
@@ -425,15 +374,448 @@ function AppKit(props) {
425
374
  };
426
375
  }, [status, lastError, readyInfo, snapshot]);
427
376
  return /* @__PURE__ */ jsxRuntime.jsx(Ctx.Provider, { value: ctx, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: props.className, style: props.style, children: [
428
- showLoading ? props.loading ?? /* @__PURE__ */ jsxRuntime.jsx(DefaultSkeleton, {}) : null,
377
+ showLoading && props.loading ? props.loading : null,
429
378
  showError && lastError ? props.errorUI ? props.errorUI(lastError) : /* @__PURE__ */ jsxRuntime.jsx(DefaultError, { err: lastError }) : null,
430
379
  props.children,
431
380
  /* @__PURE__ */ jsxRuntime.jsx("div", { id: containerId })
432
381
  ] }) });
433
382
  }
383
+ function Toast({ toast, onClose }) {
384
+ React.useEffect(() => {
385
+ if (toast) {
386
+ const timer = setTimeout(onClose, 4e3);
387
+ return () => clearTimeout(timer);
388
+ }
389
+ }, [toast, onClose]);
390
+ if (!toast) return null;
391
+ return /* @__PURE__ */ jsxRuntime.jsxs(
392
+ "div",
393
+ {
394
+ style: {
395
+ position: "fixed",
396
+ bottom: 24,
397
+ left: "50%",
398
+ transform: "translateX(-50%)",
399
+ padding: "12px 20px",
400
+ borderRadius: 8,
401
+ backgroundColor: toast.type === "success" ? "#2e7d32" : "#d32f2f",
402
+ color: "#fff",
403
+ fontSize: 14,
404
+ fontWeight: 500,
405
+ boxShadow: "0 4px 12px rgba(0,0,0,0.2)",
406
+ zIndex: 1e4,
407
+ display: "flex",
408
+ alignItems: "center",
409
+ gap: 12
410
+ },
411
+ children: [
412
+ toast.message,
413
+ /* @__PURE__ */ jsxRuntime.jsx(
414
+ "button",
415
+ {
416
+ onClick: onClose,
417
+ style: {
418
+ background: "none",
419
+ border: "none",
420
+ color: "#fff",
421
+ cursor: "pointer",
422
+ fontSize: 18,
423
+ lineHeight: 1,
424
+ padding: 0,
425
+ opacity: 0.8
426
+ },
427
+ children: "\xD7"
428
+ }
429
+ )
430
+ ]
431
+ }
432
+ );
433
+ }
434
+ function getInitials(name, email) {
435
+ if (name) {
436
+ const parts = name.trim().split(/\s+/);
437
+ if (parts.length >= 2) {
438
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
439
+ }
440
+ return name.slice(0, 2).toUpperCase();
441
+ }
442
+ if (email) {
443
+ return email.slice(0, 2).toUpperCase();
444
+ }
445
+ return "?";
446
+ }
447
+ function stringToColor(str) {
448
+ let hash = 0;
449
+ for (let i = 0; i < str.length; i++) {
450
+ hash = str.charCodeAt(i) + ((hash << 5) - hash);
451
+ }
452
+ const colors = [
453
+ "#1976d2",
454
+ "#388e3c",
455
+ "#d32f2f",
456
+ "#7b1fa2",
457
+ "#c2185b",
458
+ "#0288d1",
459
+ "#00796b",
460
+ "#f57c00"
461
+ ];
462
+ return colors[Math.abs(hash) % colors.length];
463
+ }
464
+ function UserButton({ size = "medium" }) {
465
+ const { snapshot, logout, refresh, readyInfo } = useAppKit();
466
+ const [open, setOpen] = React.useState(false);
467
+ const [loggingOut, setLoggingOut] = React.useState(false);
468
+ const account = snapshot?.appData?.account;
469
+ const initials = getInitials(account?.name, account?.email);
470
+ const avatarColor = stringToColor(account?.email || "user");
471
+ const sizeMap = { small: 32, medium: 40, large: 48 };
472
+ const fontSizeMap = { small: 14, medium: 16, large: 20 };
473
+ const avatarSize = sizeMap[size];
474
+ const handleLogout = async () => {
475
+ setLoggingOut(true);
476
+ try {
477
+ await logout();
478
+ } finally {
479
+ setLoggingOut(false);
480
+ setOpen(false);
481
+ }
482
+ };
483
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
484
+ /* @__PURE__ */ jsxRuntime.jsx(
485
+ "button",
486
+ {
487
+ onClick: () => setOpen(true),
488
+ "aria-label": "Open profile",
489
+ style: {
490
+ display: "flex",
491
+ alignItems: "center",
492
+ justifyContent: "center",
493
+ width: avatarSize,
494
+ height: avatarSize,
495
+ borderRadius: "50%",
496
+ backgroundColor: avatarColor,
497
+ color: "#fff",
498
+ border: "none",
499
+ cursor: "pointer",
500
+ fontSize: fontSizeMap[size],
501
+ fontWeight: 600,
502
+ fontFamily: "inherit",
503
+ padding: 0
504
+ },
505
+ children: initials
506
+ }
507
+ ),
508
+ open && /* @__PURE__ */ jsxRuntime.jsx(
509
+ ProfileModal,
510
+ {
511
+ account,
512
+ onClose: () => setOpen(false),
513
+ onLogout: handleLogout,
514
+ loggingOut,
515
+ avatarColor,
516
+ initials,
517
+ baseURL: readyInfo?.baseURL,
518
+ jwtToken: snapshot?.jwtToken,
519
+ onNameUpdated: refresh
520
+ }
521
+ )
522
+ ] });
523
+ }
524
+ function ProfileModal({
525
+ account,
526
+ onClose,
527
+ onLogout,
528
+ loggingOut,
529
+ avatarColor,
530
+ initials,
531
+ baseURL,
532
+ jwtToken,
533
+ onNameUpdated
534
+ }) {
535
+ const [editing, setEditing] = React.useState(false);
536
+ const [editName, setEditName] = React.useState(account?.name || "");
537
+ const [saving, setSaving] = React.useState(false);
538
+ const [error, setError] = React.useState(null);
539
+ const [toast, setToast] = React.useState(null);
540
+ const clearToast = React.useCallback(() => setToast(null), []);
541
+ React.useEffect(() => {
542
+ setEditName(account?.name || "");
543
+ setEditing(false);
544
+ setError(null);
545
+ }, [account?.name]);
546
+ const handleSave = async () => {
547
+ const trimmed = editName.trim();
548
+ if (!trimmed) {
549
+ setError("Display name is required");
550
+ return;
551
+ }
552
+ if (trimmed.length > 200) {
553
+ setError("Display name is too long");
554
+ return;
555
+ }
556
+ if (trimmed === account?.name) {
557
+ setEditing(false);
558
+ return;
559
+ }
560
+ setSaving(true);
561
+ setError(null);
562
+ try {
563
+ const res = await fetch(`${baseURL}/a/profile/display-name`, {
564
+ method: "POST",
565
+ headers: {
566
+ Authorization: `Bearer ${jwtToken}`,
567
+ "Content-Type": "application/json"
568
+ },
569
+ body: JSON.stringify({ displayName: trimmed })
570
+ });
571
+ if (!res.ok) {
572
+ const text = await res.text();
573
+ throw new Error(text || "Failed to update name");
574
+ }
575
+ setEditing(false);
576
+ onNameUpdated();
577
+ setToast({ message: "Display name updated", type: "success" });
578
+ } catch (err) {
579
+ const msg = err.message || "Failed to update name";
580
+ setError(msg);
581
+ setToast({ message: msg, type: "error" });
582
+ } finally {
583
+ setSaving(false);
584
+ }
585
+ };
586
+ const handleKeyDown = (e) => {
587
+ if (e.key === "Enter") {
588
+ e.preventDefault();
589
+ handleSave();
590
+ } else if (e.key === "Escape") {
591
+ setEditing(false);
592
+ setEditName(account?.name || "");
593
+ setError(null);
594
+ }
595
+ };
596
+ return /* @__PURE__ */ jsxRuntime.jsxs(
597
+ "div",
598
+ {
599
+ style: {
600
+ position: "fixed",
601
+ inset: 0,
602
+ zIndex: 9999,
603
+ display: "flex",
604
+ alignItems: "center",
605
+ justifyContent: "center"
606
+ },
607
+ children: [
608
+ /* @__PURE__ */ jsxRuntime.jsx(
609
+ "div",
610
+ {
611
+ onClick: onClose,
612
+ style: {
613
+ position: "absolute",
614
+ inset: 0,
615
+ backgroundColor: "rgba(0, 0, 0, 0.5)"
616
+ }
617
+ }
618
+ ),
619
+ /* @__PURE__ */ jsxRuntime.jsxs(
620
+ "div",
621
+ {
622
+ style: {
623
+ position: "relative",
624
+ backgroundColor: "#fff",
625
+ borderRadius: 12,
626
+ boxShadow: "0 8px 32px rgba(0,0,0,0.2)",
627
+ width: "100%",
628
+ maxWidth: 360,
629
+ margin: 16,
630
+ overflow: "hidden"
631
+ },
632
+ children: [
633
+ /* @__PURE__ */ jsxRuntime.jsxs(
634
+ "div",
635
+ {
636
+ style: {
637
+ display: "flex",
638
+ alignItems: "center",
639
+ justifyContent: "space-between",
640
+ padding: "16px 20px",
641
+ borderBottom: "1px solid #eee"
642
+ },
643
+ children: [
644
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: { margin: 0, fontSize: 18, fontWeight: 600 }, children: "Profile" }),
645
+ /* @__PURE__ */ jsxRuntime.jsx(
646
+ "button",
647
+ {
648
+ onClick: onClose,
649
+ style: {
650
+ background: "none",
651
+ border: "none",
652
+ cursor: "pointer",
653
+ padding: 4,
654
+ fontSize: 20,
655
+ lineHeight: 1,
656
+ color: "#666"
657
+ },
658
+ children: "\xD7"
659
+ }
660
+ )
661
+ ]
662
+ }
663
+ ),
664
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: 20 }, children: [
665
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 16, marginBottom: 20 }, children: [
666
+ /* @__PURE__ */ jsxRuntime.jsx(
667
+ "div",
668
+ {
669
+ style: {
670
+ width: 64,
671
+ height: 64,
672
+ borderRadius: "50%",
673
+ backgroundColor: avatarColor,
674
+ color: "#fff",
675
+ display: "flex",
676
+ alignItems: "center",
677
+ justifyContent: "center",
678
+ fontSize: 24,
679
+ fontWeight: 600,
680
+ flexShrink: 0
681
+ },
682
+ children: initials
683
+ }
684
+ ),
685
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { minWidth: 0, flex: 1 }, children: [
686
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 18, fontWeight: 600, marginBottom: 2 }, children: account?.name || "User" }),
687
+ /* @__PURE__ */ jsxRuntime.jsx(
688
+ "div",
689
+ {
690
+ style: {
691
+ fontSize: 14,
692
+ color: "#666",
693
+ overflow: "hidden",
694
+ textOverflow: "ellipsis",
695
+ whiteSpace: "nowrap"
696
+ },
697
+ children: account?.email
698
+ }
699
+ )
700
+ ] })
701
+ ] }),
702
+ /* @__PURE__ */ jsxRuntime.jsx("hr", { style: { border: "none", borderTop: "1px solid #eee", margin: "16px 0" } }),
703
+ error && /* @__PURE__ */ jsxRuntime.jsx(
704
+ "div",
705
+ {
706
+ style: {
707
+ padding: "10px 14px",
708
+ marginBottom: 16,
709
+ backgroundColor: "#ffebee",
710
+ color: "#c62828",
711
+ borderRadius: 6,
712
+ fontSize: 14
713
+ },
714
+ children: error
715
+ }
716
+ ),
717
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 16 }, children: [
718
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 4 }, children: [
719
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12, color: "#888", fontWeight: 600 }, children: "Display Name" }),
720
+ !editing && /* @__PURE__ */ jsxRuntime.jsx(
721
+ "button",
722
+ {
723
+ onClick: () => setEditing(true),
724
+ style: {
725
+ background: "none",
726
+ border: "none",
727
+ cursor: "pointer",
728
+ padding: 2,
729
+ fontSize: 12,
730
+ color: "#1976d2"
731
+ },
732
+ children: "Edit"
733
+ }
734
+ )
735
+ ] }),
736
+ editing ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 8 }, children: [
737
+ /* @__PURE__ */ jsxRuntime.jsx(
738
+ "input",
739
+ {
740
+ type: "text",
741
+ value: editName,
742
+ onChange: (e) => setEditName(e.target.value),
743
+ onKeyDown: handleKeyDown,
744
+ disabled: saving,
745
+ autoFocus: true,
746
+ placeholder: "Enter display name",
747
+ style: {
748
+ flex: 1,
749
+ padding: "8px 12px",
750
+ fontSize: 14,
751
+ border: `1px solid ${error ? "#d32f2f" : "#ddd"}`,
752
+ borderRadius: 6,
753
+ outline: "none"
754
+ }
755
+ }
756
+ ),
757
+ /* @__PURE__ */ jsxRuntime.jsx(
758
+ "button",
759
+ {
760
+ onClick: handleSave,
761
+ disabled: saving,
762
+ style: {
763
+ padding: "8px 14px",
764
+ fontSize: 14,
765
+ backgroundColor: "#1976d2",
766
+ color: "#fff",
767
+ border: "none",
768
+ borderRadius: 6,
769
+ cursor: saving ? "not-allowed" : "pointer",
770
+ opacity: saving ? 0.6 : 1
771
+ },
772
+ children: saving ? "\u2026" : "Save"
773
+ }
774
+ )
775
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 15 }, children: account?.name || "\u2014" })
776
+ ] }),
777
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 20 }, children: [
778
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12, color: "#888", fontWeight: 600, marginBottom: 4 }, children: "Email" }),
779
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 15 }, children: account?.email || "\u2014" })
780
+ ] }),
781
+ /* @__PURE__ */ jsxRuntime.jsx("hr", { style: { border: "none", borderTop: "1px solid #eee", margin: "16px 0" } }),
782
+ /* @__PURE__ */ jsxRuntime.jsx(
783
+ "button",
784
+ {
785
+ onClick: onLogout,
786
+ disabled: loggingOut,
787
+ style: {
788
+ width: "100%",
789
+ padding: "10px 16px",
790
+ fontSize: 15,
791
+ fontWeight: 500,
792
+ color: "#d32f2f",
793
+ backgroundColor: "#fff",
794
+ border: "1px solid #d32f2f",
795
+ borderRadius: 8,
796
+ cursor: loggingOut ? "not-allowed" : "pointer",
797
+ opacity: loggingOut ? 0.6 : 1,
798
+ display: "flex",
799
+ alignItems: "center",
800
+ justifyContent: "center",
801
+ gap: 8
802
+ },
803
+ children: loggingOut ? "Logging out\u2026" : "Log out"
804
+ }
805
+ )
806
+ ] })
807
+ ]
808
+ }
809
+ ),
810
+ /* @__PURE__ */ jsxRuntime.jsx(Toast, { toast, onClose: clearToast })
811
+ ]
812
+ }
813
+ );
814
+ }
434
815
 
435
816
  exports.AppKit = AppKit;
436
817
  exports.AppKitAuthed = AppKitAuthed;
818
+ exports.UserButton = UserButton;
437
819
  exports.useAppKit = useAppKit;
438
820
  //# sourceMappingURL=index.cjs.map
439
821
  //# sourceMappingURL=index.cjs.map