@insforge/react 1.0.4 → 1.0.5-dev.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.
@@ -232,7 +232,7 @@ var require_react_is_development = __commonJS({
232
232
  var ContextProvider = REACT_PROVIDER_TYPE;
233
233
  var Element = REACT_ELEMENT_TYPE;
234
234
  var ForwardRef = REACT_FORWARD_REF_TYPE;
235
- var Fragment9 = REACT_FRAGMENT_TYPE;
235
+ var Fragment10 = REACT_FRAGMENT_TYPE;
236
236
  var Lazy = REACT_LAZY_TYPE;
237
237
  var Memo = REACT_MEMO_TYPE;
238
238
  var Portal = REACT_PORTAL_TYPE;
@@ -291,7 +291,7 @@ var require_react_is_development = __commonJS({
291
291
  exports$1.ContextProvider = ContextProvider;
292
292
  exports$1.Element = Element;
293
293
  exports$1.ForwardRef = ForwardRef;
294
- exports$1.Fragment = Fragment9;
294
+ exports$1.Fragment = Fragment10;
295
295
  exports$1.Lazy = Lazy;
296
296
  exports$1.Memo = Memo;
297
297
  exports$1.Portal = Portal;
@@ -2148,6 +2148,8 @@ var theme = {
2148
2148
  sm: "0.875rem",
2149
2149
  // 14px
2150
2150
  base: "1rem",
2151
+ // 16px
2152
+ lg: "1.125rem",
2151
2153
  // 20px
2152
2154
  "2xl": "1.5rem"
2153
2155
  // 24px
@@ -2179,6 +2181,10 @@ var theme = {
2179
2181
  base: "200ms cubic-bezier(0.4, 0, 0.2, 1)"
2180
2182
  },
2181
2183
  sizes: {
2184
+ input: {
2185
+ height: "2.5rem"
2186
+ // 40px
2187
+ },
2182
2188
  button: {
2183
2189
  height: "2.5rem",
2184
2190
  // 40px for submit
@@ -4694,9 +4700,382 @@ var UserButtonMenuItemIcon = styled.div`
4694
4700
  height: 100%;
4695
4701
  }
4696
4702
  `;
4697
- function UserButton({ afterSignOutUrl = "/", mode = "simple" }) {
4703
+ var ProfileModalOverlay = styled.div`
4704
+ position: fixed;
4705
+ inset: 0;
4706
+ background-color: rgba(0, 0, 0, 0.5);
4707
+ display: flex;
4708
+ align-items: center;
4709
+ justify-content: center;
4710
+ z-index: 100;
4711
+ padding: ${theme.spacing[4]};
4712
+ `;
4713
+ var ProfileModalContainer = styled.div`
4714
+ background-color: ${theme.colors.bgWhite};
4715
+ border-radius: ${theme.radius.xl};
4716
+ box-shadow: ${theme.shadow.lg};
4717
+ width: 100%;
4718
+ max-width: 400px;
4719
+ max-height: 90vh;
4720
+ overflow: hidden;
4721
+ display: flex;
4722
+ flex-direction: column;
4723
+ font-family: ${theme.fontFamily.base};
4724
+ `;
4725
+ var ProfileModalHeader = styled.div`
4726
+ display: flex;
4727
+ align-items: center;
4728
+ justify-content: space-between;
4729
+ padding: ${theme.spacing[4]} ${theme.spacing[6]};
4730
+ border-bottom: 1px solid ${theme.colors.border};
4731
+ `;
4732
+ var ProfileModalTitle = styled.h2`
4733
+ font-size: ${theme.fontSize.lg};
4734
+ font-weight: ${theme.fontWeight.semibold};
4735
+ color: ${theme.colors.text};
4736
+ margin: 0;
4737
+ `;
4738
+ var ProfileModalCloseButton = styled.button`
4739
+ display: flex;
4740
+ align-items: center;
4741
+ justify-content: center;
4742
+ width: 2rem;
4743
+ height: 2rem;
4744
+ border-radius: ${theme.radius.md};
4745
+ background: none;
4746
+ border: none;
4747
+ cursor: pointer;
4748
+ color: ${theme.colors.textSecondary};
4749
+ transition: all ${theme.transition.fast};
4750
+
4751
+ &:hover {
4752
+ background-color: ${theme.colors.bgLight};
4753
+ color: ${theme.colors.text};
4754
+ }
4755
+
4756
+ svg {
4757
+ width: 1.25rem;
4758
+ height: 1.25rem;
4759
+ }
4760
+ `;
4761
+ var ProfileModalBody = styled.div`
4762
+ padding: ${theme.spacing[6]};
4763
+ overflow-y: auto;
4764
+ flex: 1;
4765
+ `;
4766
+ var ProfileAvatarSection = styled.div`
4767
+ display: flex;
4768
+ flex-direction: column;
4769
+ align-items: center;
4770
+ margin-bottom: ${theme.spacing[6]};
4771
+ `;
4772
+ var ProfileAvatar = styled.div`
4773
+ width: 5rem;
4774
+ height: 5rem;
4775
+ border-radius: ${theme.radius.full};
4776
+ background-color: ${theme.colors.primary};
4777
+ color: ${theme.colors.bgWhite};
4778
+ display: flex;
4779
+ align-items: center;
4780
+ justify-content: center;
4781
+ font-weight: ${theme.fontWeight.semibold};
4782
+ font-size: ${theme.fontSize["2xl"]};
4783
+ overflow: hidden;
4784
+ `;
4785
+ var ProfileAvatarImage = styled.img`
4786
+ width: 100%;
4787
+ height: 100%;
4788
+ object-fit: cover;
4789
+ `;
4790
+ var ProfileFieldsContainer = styled.div`
4791
+ display: flex;
4792
+ flex-direction: column;
4793
+ gap: ${theme.spacing[4]};
4794
+ `;
4795
+ var ProfileField = styled.div`
4796
+ display: flex;
4797
+ flex-direction: column;
4798
+ gap: ${theme.spacing[1]};
4799
+ `;
4800
+ var ProfileFieldLabel = styled.label`
4801
+ font-size: ${theme.fontSize.sm};
4802
+ font-weight: ${theme.fontWeight.medium};
4803
+ color: ${theme.colors.textSecondary};
4804
+ text-transform: capitalize;
4805
+ `;
4806
+ var ProfileFieldValue = styled.div`
4807
+ font-size: ${theme.fontSize.base};
4808
+ color: ${theme.colors.text};
4809
+ padding: ${theme.spacing[2]} 0;
4810
+ word-break: break-word;
4811
+ `;
4812
+ var ProfileFieldInput = styled.input`
4813
+ width: 100%;
4814
+ height: ${theme.sizes.input.height};
4815
+ padding: 0 ${theme.spacing[3]};
4816
+ border: 1px solid ${theme.colors.border};
4817
+ border-radius: ${theme.radius.md};
4818
+ font-size: ${theme.fontSize.base};
4819
+ font-family: ${theme.fontFamily.base};
4820
+ color: ${theme.colors.text};
4821
+ background-color: ${theme.colors.bgWhite};
4822
+ transition: border-color ${theme.transition.fast};
4823
+
4824
+ &:focus {
4825
+ outline: none;
4826
+ border-color: ${theme.colors.borderFocus};
4827
+ }
4828
+
4829
+ &:disabled {
4830
+ background-color: ${theme.colors.bgLight};
4831
+ color: ${theme.colors.textSecondary};
4832
+ cursor: not-allowed;
4833
+ }
4834
+ `;
4835
+ var ProfileModalFooter = styled.div`
4836
+ display: flex;
4837
+ align-items: center;
4838
+ justify-content: flex-end;
4839
+ gap: ${theme.spacing[3]};
4840
+ padding: ${theme.spacing[4]} ${theme.spacing[6]};
4841
+ border-top: 1px solid ${theme.colors.border};
4842
+ `;
4843
+ var ProfileButton = styled.button`
4844
+ display: flex;
4845
+ align-items: center;
4846
+ justify-content: center;
4847
+ gap: ${theme.spacing[2]};
4848
+ height: ${theme.sizes.button.height};
4849
+ padding: 0 ${theme.spacing[4]};
4850
+ border-radius: ${theme.radius.md};
4851
+ font-size: ${theme.fontSize.sm};
4852
+ font-weight: ${theme.fontWeight.medium};
4853
+ font-family: ${theme.fontFamily.base};
4854
+ cursor: pointer;
4855
+ transition: all ${theme.transition.fast};
4856
+
4857
+ ${(props) => props.$primary ? `
4858
+ background-color: ${theme.colors.primary};
4859
+ color: ${theme.colors.bgWhite};
4860
+ border: none;
4861
+
4862
+ &:hover:not(:disabled) {
4863
+ background-color: ${theme.colors.primaryHover};
4864
+ }
4865
+
4866
+ &:disabled {
4867
+ opacity: 0.5;
4868
+ cursor: not-allowed;
4869
+ }
4870
+ ` : `
4871
+ background-color: ${theme.colors.bgWhite};
4872
+ color: ${theme.colors.text};
4873
+ border: 1px solid ${theme.colors.border};
4874
+
4875
+ &:hover:not(:disabled) {
4876
+ background-color: ${theme.colors.bgLight};
4877
+ }
4878
+
4879
+ &:disabled {
4880
+ opacity: 0.5;
4881
+ cursor: not-allowed;
4882
+ }
4883
+ `}
4884
+ `;
4885
+ var ProfileSpinner = styled.div`
4886
+ width: 1rem;
4887
+ height: 1rem;
4888
+ border: 2px solid transparent;
4889
+ border-top-color: currentColor;
4890
+ border-radius: ${theme.radius.full};
4891
+ animation: spin 0.6s linear infinite;
4892
+
4893
+ @keyframes spin {
4894
+ to {
4895
+ transform: rotate(360deg);
4896
+ }
4897
+ }
4898
+ `;
4899
+ var READ_ONLY_FIELDS = ["id", "email", "avatar_url", "created_at", "updated_at"];
4900
+ var HIDDEN_FIELDS = ["id"];
4901
+ function formatFieldLabel(key) {
4902
+ return key.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase();
4903
+ }
4904
+ function UserProfileModal({ onClose, onError }) {
4905
+ const { user, updateUser, isLoaded } = useInsforge();
4906
+ const [isEditing, setIsEditing] = React2.useState(false);
4907
+ const [isSaving, setIsSaving] = React2.useState(false);
4908
+ const [imageError, setImageError] = React2.useState(false);
4909
+ const [formData, setFormData] = React2.useState({});
4910
+ React2.useEffect(() => {
4911
+ if (user) {
4912
+ const initialData = {
4913
+ name: user.name || ""
4914
+ };
4915
+ if (user.profile) {
4916
+ Object.entries(user.profile).forEach(([key, value]) => {
4917
+ if (!HIDDEN_FIELDS.includes(key) && typeof value === "string") {
4918
+ initialData[key] = value;
4919
+ }
4920
+ });
4921
+ }
4922
+ setFormData(initialData);
4923
+ }
4924
+ }, [user]);
4925
+ React2.useEffect(() => {
4926
+ setImageError(false);
4927
+ const avatarUrl = user?.avatarUrl;
4928
+ if (!avatarUrl) {
4929
+ return;
4930
+ }
4931
+ const checkImageUrl = async () => {
4932
+ try {
4933
+ const response = await fetch(avatarUrl, {
4934
+ method: "HEAD",
4935
+ cache: "no-cache"
4936
+ });
4937
+ if (!response.ok) {
4938
+ setImageError(true);
4939
+ }
4940
+ } catch {
4941
+ setImageError(true);
4942
+ }
4943
+ };
4944
+ void checkImageUrl();
4945
+ }, [user?.avatarUrl]);
4946
+ const handleFieldChange = React2.useCallback((key, value) => {
4947
+ setFormData((prev2) => ({
4948
+ ...prev2,
4949
+ [key]: value
4950
+ }));
4951
+ }, []);
4952
+ const handleSave = React2.useCallback(async () => {
4953
+ if (!user) return;
4954
+ setIsSaving(true);
4955
+ try {
4956
+ const { name, ...dynamicFields } = formData;
4957
+ const updateData = {
4958
+ name
4959
+ };
4960
+ if (Object.keys(dynamicFields).length > 0) {
4961
+ updateData.profile = dynamicFields;
4962
+ }
4963
+ const result = await updateUser(updateData);
4964
+ if (result?.error) {
4965
+ onError?.(result.error);
4966
+ } else {
4967
+ setIsEditing(false);
4968
+ }
4969
+ } catch (error) {
4970
+ onError?.(error instanceof Error ? error.message : "Failed to update profile");
4971
+ } finally {
4972
+ setIsSaving(false);
4973
+ }
4974
+ }, [user, formData, updateUser, onError]);
4975
+ const handleCancel = React2.useCallback(() => {
4976
+ if (user) {
4977
+ const resetData = {
4978
+ name: user.name || ""
4979
+ };
4980
+ if (user.profile) {
4981
+ Object.entries(user.profile).forEach(([key, value]) => {
4982
+ if (!HIDDEN_FIELDS.includes(key) && typeof value === "string") {
4983
+ resetData[key] = value;
4984
+ }
4985
+ });
4986
+ }
4987
+ setFormData(resetData);
4988
+ }
4989
+ setIsEditing(false);
4990
+ }, [user]);
4991
+ const handleOverlayClick = React2.useCallback(
4992
+ (e) => {
4993
+ if (e.target === e.currentTarget) {
4994
+ onClose();
4995
+ }
4996
+ },
4997
+ [onClose]
4998
+ );
4999
+ React2.useEffect(() => {
5000
+ const handleEscape = (e) => {
5001
+ if (e.key === "Escape") {
5002
+ if (isEditing) {
5003
+ handleCancel();
5004
+ } else {
5005
+ onClose();
5006
+ }
5007
+ }
5008
+ };
5009
+ document.addEventListener("keydown", handleEscape);
5010
+ return () => document.removeEventListener("keydown", handleEscape);
5011
+ }, [isEditing, handleCancel, onClose]);
5012
+ if (!isLoaded || !user) {
5013
+ return null;
5014
+ }
5015
+ const initials = user.name ? user.name.charAt(0).toUpperCase() : user.email.split("@")[0].slice(0, 2).toUpperCase();
5016
+ const fields = [];
5017
+ fields.push({ key: "email", value: user.email, readOnly: true });
5018
+ fields.push({
5019
+ key: "name",
5020
+ value: isEditing ? formData.name || "" : user.name || "",
5021
+ readOnly: false
5022
+ });
5023
+ if (user.profile) {
5024
+ Object.entries(user.profile).forEach(([key, value]) => {
5025
+ if (!HIDDEN_FIELDS.includes(key) && typeof value === "string") {
5026
+ fields.push({
5027
+ key,
5028
+ value: isEditing ? formData[key] ?? value : value,
5029
+ readOnly: READ_ONLY_FIELDS.includes(key)
5030
+ });
5031
+ }
5032
+ });
5033
+ }
5034
+ return /* @__PURE__ */ jsxRuntime.jsx(ProfileModalOverlay, { onClick: handleOverlayClick, children: /* @__PURE__ */ jsxRuntime.jsxs(ProfileModalContainer, { onClick: (e) => e.stopPropagation(), children: [
5035
+ /* @__PURE__ */ jsxRuntime.jsxs(ProfileModalHeader, { children: [
5036
+ /* @__PURE__ */ jsxRuntime.jsx(ProfileModalTitle, { children: "Profile" }),
5037
+ /* @__PURE__ */ jsxRuntime.jsx(ProfileModalCloseButton, { onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, {}) })
5038
+ ] }),
5039
+ /* @__PURE__ */ jsxRuntime.jsxs(ProfileModalBody, { children: [
5040
+ /* @__PURE__ */ jsxRuntime.jsx(ProfileAvatarSection, { children: /* @__PURE__ */ jsxRuntime.jsx(ProfileAvatar, { children: user.avatarUrl && !imageError ? /* @__PURE__ */ jsxRuntime.jsx(
5041
+ ProfileAvatarImage,
5042
+ {
5043
+ src: user.avatarUrl,
5044
+ alt: user.name || user.email,
5045
+ onError: () => setImageError(true)
5046
+ }
5047
+ ) : initials }) }),
5048
+ /* @__PURE__ */ jsxRuntime.jsx(ProfileFieldsContainer, { children: fields.map(({ key, value, readOnly }) => /* @__PURE__ */ jsxRuntime.jsxs(ProfileField, { children: [
5049
+ /* @__PURE__ */ jsxRuntime.jsx(ProfileFieldLabel, { children: formatFieldLabel(key) }),
5050
+ isEditing && !readOnly ? /* @__PURE__ */ jsxRuntime.jsx(
5051
+ ProfileFieldInput,
5052
+ {
5053
+ type: "text",
5054
+ value: formData[key] ?? value,
5055
+ onChange: (e) => handleFieldChange(key, e.target.value),
5056
+ disabled: isSaving
5057
+ }
5058
+ ) : /* @__PURE__ */ jsxRuntime.jsx(ProfileFieldValue, { children: value || "-" })
5059
+ ] }, key)) })
5060
+ ] }),
5061
+ /* @__PURE__ */ jsxRuntime.jsx(ProfileModalFooter, { children: isEditing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5062
+ /* @__PURE__ */ jsxRuntime.jsx(ProfileButton, { onClick: handleCancel, disabled: isSaving, children: "Cancel" }),
5063
+ /* @__PURE__ */ jsxRuntime.jsxs(ProfileButton, { $primary: true, onClick: handleSave, disabled: isSaving, children: [
5064
+ isSaving && /* @__PURE__ */ jsxRuntime.jsx(ProfileSpinner, {}),
5065
+ isSaving ? "Saving..." : "Save"
5066
+ ] })
5067
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(ProfileButton, { $primary: true, onClick: () => setIsEditing(true), children: "Edit Profile" }) })
5068
+ ] }) });
5069
+ }
5070
+ function UserButton({
5071
+ afterSignOutUrl = "/",
5072
+ mode = "simple",
5073
+ showProfile = true,
5074
+ onProfileError
5075
+ }) {
4698
5076
  const { user } = useInsforge();
4699
5077
  const [isOpen, setIsOpen] = React2.useState(false);
5078
+ const [showProfileModal, setShowProfileModal] = React2.useState(false);
4700
5079
  const [imageError, setImageError] = React2.useState(false);
4701
5080
  const [openUpward, setOpenUpward] = React2.useState(false);
4702
5081
  const [horizontalOffset, setHorizontalOffset] = React2.useState(0);
@@ -4794,10 +5173,32 @@ function UserButton({ afterSignOutUrl = "/", mode = "simple" }) {
4794
5173
  ]
4795
5174
  }
4796
5175
  ),
4797
- isOpen && /* @__PURE__ */ jsxRuntime.jsx(UserButtonMenu, { ref: menuRef, $openUpward: openUpward, $horizontalOffset: horizontalOffset, children: /* @__PURE__ */ jsxRuntime.jsx(SignOutButton, { afterSignOutUrl, children: /* @__PURE__ */ jsxRuntime.jsxs(UserButtonMenuItem, { $signout: true, onClick: () => setIsOpen(false), children: [
4798
- /* @__PURE__ */ jsxRuntime.jsx(UserButtonMenuItemIcon, { children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.LogOut, {}) }),
4799
- "Sign out"
4800
- ] }) }) })
5176
+ isOpen && /* @__PURE__ */ jsxRuntime.jsxs(UserButtonMenu, { ref: menuRef, $openUpward: openUpward, $horizontalOffset: horizontalOffset, children: [
5177
+ showProfile && /* @__PURE__ */ jsxRuntime.jsxs(
5178
+ UserButtonMenuItem,
5179
+ {
5180
+ onClick: () => {
5181
+ setShowProfileModal(true);
5182
+ setIsOpen(false);
5183
+ },
5184
+ children: [
5185
+ /* @__PURE__ */ jsxRuntime.jsx(UserButtonMenuItemIcon, { children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.User, {}) }),
5186
+ "Profile"
5187
+ ]
5188
+ }
5189
+ ),
5190
+ /* @__PURE__ */ jsxRuntime.jsx(SignOutButton, { afterSignOutUrl, children: /* @__PURE__ */ jsxRuntime.jsxs(UserButtonMenuItem, { $signout: true, onClick: () => setIsOpen(false), children: [
5191
+ /* @__PURE__ */ jsxRuntime.jsx(UserButtonMenuItemIcon, { children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.LogOut, {}) }),
5192
+ "Sign out"
5193
+ ] }) })
5194
+ ] }),
5195
+ showProfileModal && /* @__PURE__ */ jsxRuntime.jsx(
5196
+ UserProfileModal,
5197
+ {
5198
+ onClose: () => setShowProfileModal(false),
5199
+ onError: onProfileError
5200
+ }
5201
+ )
4801
5202
  ] });
4802
5203
  }
4803
5204
  function Protect({
@@ -4946,6 +5347,7 @@ exports.SignUpForm = SignUpForm;
4946
5347
  exports.SignedIn = SignedIn;
4947
5348
  exports.SignedOut = SignedOut;
4948
5349
  exports.UserButton = UserButton;
5350
+ exports.UserProfileModal = UserProfileModal;
4949
5351
  exports.VerifyEmail = VerifyEmail;
4950
5352
  exports.VerifyEmailStatus = VerifyEmailStatus;
4951
5353
  //# sourceMappingURL=components.cjs.map