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