') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Custom Pages for `<UserProfile />` and `<OrganizationProfile />` components by anagstef · Pull Request #1822 · clerk/javascript · GitHub
Skip to content

Custom Pages for <UserProfile /> and <OrganizationProfile /> components - #1822

Merged
anagstef merged 16 commits into
mainfrom
js-621-rfc-poc
Oct 24, 2023
Merged

Custom Pages for <UserProfile /> and <OrganizationProfile /> components#1822
anagstef merged 16 commits into
mainfrom
js-621-rfc-poc

Conversation

@anagstef

@anagstefanagstef commented Oct 3, 2023

Copy link
Copy Markdown
Contributor

Description

This PR introduces custom pages and links inside the UserProfile and OrganizationProfile components.

UserProfile

Custom Pages

The UserProfile component supports the addition of custom pages. These custom pages can be rendered inside the UserProfile component and provide a way to incorporate app-specific settings or additional functionality.

To add a custom page, use the <UserProfile.Page> component. It accepts the following props:

  • label: The name that will be displayed in the navigation sidebar for the custom page.
  • labelIcon: An icon displayed next to the label in the navigation sidebar.
  • url: The path segment that will be used to navigate to the custom page. (e.g. if the UserProfile component is rendered at /user, then the custom page will be accessed at /user/{url} when using path routing)
  • children: The components to be rendered as content inside the custom page.

Types:

typeUserProfilePageProps={label: string;labelIcon: React.ReactElement;url: string;children: React.ReactElement;};

Example usage:

<UserProfile><UserProfile.Pagelabel="Custom Page"url="custom"labelIcon={<CustomIcon/>}><MyCustomPageContent/></UserProfile.Page><UserProfile.Pagelabel="Custom2"url="custom2"labelIcon={<CustomIcon/>}><MyCustomPageContent2/></UserProfile.Page></UserProfile>

Custom Links

In addition to custom pages, you can add external links to the UserProfile navigation sidebar using the <UserProfile.Link> component. It accepts the following props:

  • label: The name that will be displayed in the navigation sidebar for the link.
  • labelIcon: An icon displayed next to the label in the navigation sidebar.
  • url: The absolute or relative url to navigate to.

Types:

typeUserProfileLinkProps={label: string;labelIcon: React.ReactElement;url: string;};

Example usage:

<UserProfile><UserProfile.Linklabel="External"url="/home"labelIcon={<Icon/>}/></UserProfile>

Advanced

Reordering Default Routes

If you want to reorder the default routes (Account and Security) in the UserProfile navigation sidebar, you can use the <UserProfile.Page> component with the label prop set to 'account' or 'security'. This will target the existing default page and allow you to rearrange it.

Example usage:

<UserProfile><UserProfile.Pagelabel="Custom Page"url="custom"labelIcon={<CustomIcon/>}><MyCustomPageContent/></UserProfile.Page><UserProfile.Linklabel="External"url="/home"labelIcon={<Icon/>}/><UserProfile.Pagelabel="account"/><UserProfile.Pagelabel="security"/></UserProfile>

The above will result in the following order:

  1. Custom Page
  2. External
  3. Account
  4. Security

Notes

  • The first page in the list will be rendered under the root path / (its url will be ignored) and the Clerk pages will be rendered under the path /account.
  • The first item in the list cannot be a <UserProfile.Link> component.

Using custom pages with the UserButton component

If you are using the UserButton component with the default props (where the UserProfile opens as a modal), then you should also be providing these custom pages as children to the component (using the <UserButton.UserProfilePage> and <UserButton.UserProfileLink> components respectively).

Example usage:

<UserButtonafterSignOutUrl='/'><UserButton.UserProfilePagelabel="Custom"url="custom"labelIcon={<CustomIcon/>}><MyCustomPageContent/></UserButton.UserProfilePage><UserButton.UserProfileLinklabel="Link"url="/home"labelIcon={<Icon/>}/></UserButton>

This repetition of the same property can be avoided when the user is using the userProfileMode='navigation' and userProfileUrl='<some url>' props on the UserButton component and has implemented a dedicated page for the UserProfile component.

OrganizationProfile

Custom Pages

The OrganizationProfile component supports the addition of custom pages. These custom pages can be rendered inside the OrganizationProfile component and provide a way to incorporate organization-specific settings or additional functionality.

To add a custom page, use the <OrganizationProfile.Page> component. It accepts the following props:

  • label: The name that will be displayed in the navigation sidebar for the custom page.
  • labelIcon: An icon displayed next to the label in the navigation sidebar.
  • url: The path segment that will be used to navigate to the custom page. (e.g. if the OrganizationProfile component is rendered at /organization, then the custom page will be accessed at /organization/{url} when using path routing)
  • children: The components to be rendered as content inside the custom page.

Types:

typeOrganizationProfilePageProps={label: string;labelIcon: React.ReactElement;url: string;children: React.ReactElement;};

Example usage:

<OrganizationProfile><OrganizationProfile.Pagelabel="Custom Page"url="custom"labelIcon={<CustomIcon/>}><MyCustomPageContent/></OrganizationProfile.Page><OrganizationProfile.Pagelabel="Custom2"url="custom2"labelIcon={<CustomIcon/>}><MyCustomPageContent2/></OrganizationProfile.Page></OrganizationProfile>

Custom Links

In addition to custom pages, you can add external links to the OrganizationProfile navigation sidebar using the <OrganizationProfile.Link> component. It accepts the following props:

  • label: The name that will be displayed in the navigation sidebar for the link.
  • labelIcon: An icon displayed next to the label in the navigation sidebar.
  • url: The absolute or relative url to navigate to.

Types:

typeOrganizationProfileLinkProps={label: string;labelIcon: React.ReactElement;url: string;};

Example usage:

<OrganizationProfile><OrganizationProfile.Linklabel="External"url="/home"labelIcon={<Icon/>}/></OrganizationProfile>

Advanced

Reordering Default Routes

If you want to reorder the default routes (Members and Settings) in the OrganizationProfile navigation sidebar, you can use the <OrganizationProfile.Page> component with the label prop set to 'members' or 'settings'. This will target the existing default page and allow you to rearrange it.

Example usage:

<OrganizationProfile><OrganizationProfile.Pagelabel="Custom Page"url="custom"labelIcon={<CustomIcon/>}><MyCustomPageContent/></OrganizationProfile.Page><OrganizationProfile.Linklabel="External"url="/home"labelIcon={<Icon/>}/><OrganizationProfile.Pagelabel="members"/><OrganizationProfile.Pagelabel="settings"/></OrganizationProfile>

The above will result in the following order:

  1. Custom Page
  2. External
  3. Members
  4. Settings

Notes

  • The first page in the list will be rendered under the root path / (its url will be ignored) and the Clerk pages will be rendered under the path /organitzation-members and /organization-settings.
  • The first item in the list cannot be an <OrganizationProfile.Link> component.

Using custom pages with the OrganizationSwitcher component

If you are using the OrganizationSwitcher component with the default props (where the OrganizationProfile opens as a modal), then you should also be providing these custom pages as children to the component (using the <OrganizationSwitcher.OrganizationProfilePage> and <OrganizationSwitcher.OrganizationProfileLink> components respectively).

Example usage:

<OrganizationSwitcher><OrganizationSwitcher.OrganizationProfilePagelabel="Custom"url="custom"labelIcon={<CustomIcon/>}><MyCustomPageContent/></OrganizationSwitcher.OrganizationProfilePage><OrganizationSwitcher.OrganizationProfileLinklabel="Link"url="/home"labelIcon={<Icon/>}/></OrganizationSwitcher>

This repetition of the same property can be avoided when the user is using the organizationProfileMode='navigation' and organizationProfileUrl='<some url>' props on the OrganizationSwitcher component and has implemented a dedicated page for the OrganizationProfile component.

Caveats

  • These custom components can be rendered only on the client, and in Next.js projects, they need to be under the "use client"; flag.

Checklist

  • npm test runs as expected.
  • npm run build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Packages affected

  • @clerk/clerk-js
  • @clerk/clerk-react
  • @clerk/nextjs
  • @clerk/remix
  • @clerk/types
  • @clerk/themes
  • @clerk/localizations
  • @clerk/clerk-expo
  • @clerk/backend
  • @clerk/clerk-sdk-node
  • @clerk/shared
  • @clerk/fastify
  • @clerk/chrome-extension
  • gatsby-plugin-clerk
  • build/tooling/chore

@anagstef
anagstef requested a review from a team as a code ownerOctober 3, 2023 14:45
@changeset-bot

changeset-botBot commented Oct 3, 2023

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 3d8b86a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
NameType
@clerk/clerk-jsMinor
@clerk/clerk-reactMinor
@clerk/typesMinor
@clerk/chrome-extensionPatch
@clerk/clerk-expoPatch
gatsby-plugin-clerkPatch
@clerk/nextjsPatch
@clerk/remixPatch
@clerk/backendPatch
@clerk/fastifyPatch
@clerk/localizationsPatch
@clerk/clerk-sdk-nodePatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@anagstefanagstef changed the title Custom Pages for UserProfileCustom Pages for UserProfile componentOct 3, 2023
@anagstefanagstef self-assigned this Oct 3, 2023
@anagstef

Copy link
Copy Markdown
ContributorAuthor

!snapshot

@clerk-cookie

Copy link
Copy Markdown
Collaborator

Hey @anagstef - the snapshot version command generated the following package versions:

PackageVersion
@clerk/backend0.30.2-snapshot.875725a
@clerk/chrome-extension0.4.5-snapshot.875725a
@clerk/clerk-js4.60.0-snapshot.875725a
@clerk/clerk-expo0.19.7-snapshot.875725a
@clerk/fastify0.6.12-snapshot.875725a
gatsby-plugin-clerk4.4.13-snapshot.875725a
@clerk/localizations1.26.3-snapshot.875725a
@clerk/nextjs4.25.2-snapshot.875725a
@clerk/clerk-react4.27.0-snapshot.875725a
@clerk/remix3.0.4-snapshot.875725a
@clerk/clerk-sdk-node4.12.11-snapshot.875725a
@clerk/shared0.24.2-snapshot.875725a
@clerk/themes1.7.7-snapshot.875725a
@clerk/types3.54.0-snapshot.875725a

Tip: use the snippet copy button below to quickly install the required packages.

# @clerk/backend
npm i @clerk/backend@0.30.2-snapshot.875725a
# @clerk/chrome-extension
npm i @clerk/chrome-extension@0.4.5-snapshot.875725a
# @clerk/clerk-js
npm i @clerk/clerk-js@4.60.0-snapshot.875725a
# @clerk/clerk-expo
npm i @clerk/clerk-expo@0.19.7-snapshot.875725a
# @clerk/fastify
npm i @clerk/fastify@0.6.12-snapshot.875725a
# gatsby-plugin-clerk
npm i gatsby-plugin-clerk@4.4.13-snapshot.875725a
# @clerk/localizations
npm i @clerk/localizations@1.26.3-snapshot.875725a
# @clerk/nextjs
npm i @clerk/nextjs@4.25.2-snapshot.875725a
# @clerk/clerk-react
npm i @clerk/clerk-react@4.27.0-snapshot.875725a
# @clerk/remix
npm i @clerk/remix@3.0.4-snapshot.875725a
# @clerk/clerk-sdk-node
npm i @clerk/clerk-sdk-node@4.12.11-snapshot.875725a
# @clerk/shared
npm i @clerk/shared@0.24.2-snapshot.875725a
# @clerk/themes
npm i @clerk/themes@1.7.7-snapshot.875725a
# @clerk/types
npm i @clerk/types@3.54.0-snapshot.875725a

Comment threadpackages/react/src/types.ts Outdated
export type UserProfilePageProps = {
url?: string;
label: string;
labelIcon?: React.ReactElement;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Couldn't this be ReactNode ?

@brkalowbrkalow left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good! Nice test cases.

<Route
index={!pages.isAccountPageRoot && index === 0}
path={!pages.isAccountPageRoot && index === 0 ? undefined : customPage.url}
key={`custom-page-${index}`}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 I don't expect the custom pages list to change really, but using url in the key might be better here.

throw new Error('Clerk: useUserProfileContext called outside of the mounted UserProfile component.');
}

const pages = useMemo(() => createCustomPages(customPages || []), [customPages]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Is customPages memoized upstream from this? Otherwise, this useMemo() call might be getting recomputed on every call to this hook.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I can tell, there isn't anything that guarantees referential equality of customPages, so this might run more than we intend.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right! We cannot hook on the customPages changes, because we can't really be 100% sure when custom pages contents change. So, I may just remove the useMemo here.

export const NavBar = (props: NavBarProps) => {
const { contentRef, routes, header } = props;
const [activeId, setActiveId] = React.useState<RouteId>(routes[0]['id']);
const [activeId, setActiveId] = React.useState<RouteId>('');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Why are we removing this default value?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was causing a flicker when accessing directly a subpath in the UserProfile. In the first render, it always highlighted the first item in the navbar and then changed to the actual active. Now, it highlights nothing for half a second and then the correct item, which looks smoother in the eye.

Comment on lines +21 to +23
<>
<div ref={nodeRef}></div>
</>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<>
<divref={nodeRef}></div>
</>
<divref={nodeRef}/>

Comment on lines +37 to +78
if (isDevelopmentEnvironment()) {
checkForDuplicateUsageOfReorderingItems(customPages);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

love the helpful error checks here 👏

};

const checkForDuplicateUsageOfReorderingItems = (customPages: CustomPage[]) => {
const reorderItems = customPages.filter(cp => isAccountReorderItem(cp) || isSecurityReorderItem(cp));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just thinking for the future, if we ever add another internal page that can be reordered, we'll have a few places that need updating. Is there a way we can consolidate that logic?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have refactored this part. Thanks for pointing out.

Comment on lines +179 to +238
const sanitizeCustomPageURL = (url: string): string => {
if (!url) {
throw new Error('URL is required for custom pages');
}
if (isValidUrl(url)) {
throw new Error('Absolute URLs are not supported for custom pages');
}
return (url as string).charAt(0) === '/' && (url as string).length > 1 ? (url as string).substring(1) : url;
};

const sanitizeCustomLinkURL = (url: string): string => {
if (!url) {
throw new Error('URL is required for custom links');
}
if (isValidUrl(url)) {
return url;
}
return (url as string).charAt(0) === '/' ? url : `/${url}`;
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Why are we removing the leading / in one of these methods and prepending it in another?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want the Custom Link items (external links) to have a leading / so that navigate works correctly, and on the other hand Custom Page items need a relative segment, to navigate inside the UserProfile/OrganizationProfile routing mechanism.

Comment on lines +104 to +152
export function UserProfilePage({ children }: PropsWithChildren<UserProfilePageProps>) {
if (isDevelopmentEnvironment()) {
console.error(userProfilePageRenderedError);
}
return <div>{children}</div>;
}

export function UserProfileLink({ children }: PropsWithChildren<UserProfileLinkProps>) {
if (isDevelopmentEnvironment()) {
console.error(userProfileLinkRenderedError);
}
return <div>{children}</div>;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we not export these instead of logging an error when they are used directly?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't export them from our package. We do this so we can import it in the useCustomPages hook, and check if this is the component used as a child inside the UserProfile

ref: https://github.com/clerkinc/javascript/blob/3bf4d1a78916f24b47d094cbf97065e510d3f337/packages/react/src/utils/useCustomPages.tsx#L69

Comment on lines +132 to +133
Page: ({ children }: PropsWithChildren<UserProfilePageProps>) => React.JSX.Element;
Link: ({ children }: PropsWithChildren<UserProfileLinkProps>) => React.JSX.Element;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work?

Suggested change
Page: ({ children }: PropsWithChildren<UserProfilePageProps>)=>React.JSX.Element;
Link: ({ children }: PropsWithChildren<UserProfileLinkProps>)=>React.JSX.Element;
Page: typeofUserProfilePage;
Link: typeofUserProfileLink;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might also be helpful to place these types at the top of the file, instead of interleaved with the source.

Comment on lines +105 to +107
if (isDevelopmentEnvironment()) {
console.error(userProfilePageRenderedError);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your errorInDevMode helper could be used here.

@anagstefanagstef changed the title Custom Pages for UserProfile componentCustom Pages for UserProfile and OrganizationProfile componentsOct 5, 2023
@anagstef

Copy link
Copy Markdown
ContributorAuthor

!snapshot

@clerk-cookie

Copy link
Copy Markdown
Collaborator

Hey @anagstef - the snapshot version command generated the following package versions:

PackageVersion
@clerk/backend0.30.2-snapshot.3bf4d1a
@clerk/chrome-extension0.4.5-snapshot.3bf4d1a
@clerk/clerk-js4.60.0-snapshot.3bf4d1a
@clerk/clerk-expo0.19.7-snapshot.3bf4d1a
@clerk/fastify0.6.12-snapshot.3bf4d1a
gatsby-plugin-clerk4.4.13-snapshot.3bf4d1a
@clerk/localizations1.26.3-snapshot.3bf4d1a
@clerk/nextjs4.25.2-snapshot.3bf4d1a
@clerk/clerk-react4.27.0-snapshot.3bf4d1a
@clerk/remix3.0.4-snapshot.3bf4d1a
@clerk/clerk-sdk-node4.12.11-snapshot.3bf4d1a
@clerk/shared0.24.2-snapshot.3bf4d1a
@clerk/themes1.7.7-snapshot.3bf4d1a
@clerk/types3.54.0-snapshot.3bf4d1a

Tip: use the snippet copy button below to quickly install the required packages.

# @clerk/backend
npm i @clerk/backend@0.30.2-snapshot.3bf4d1a
# @clerk/chrome-extension
npm i @clerk/chrome-extension@0.4.5-snapshot.3bf4d1a
# @clerk/clerk-js
npm i @clerk/clerk-js@4.60.0-snapshot.3bf4d1a
# @clerk/clerk-expo
npm i @clerk/clerk-expo@0.19.7-snapshot.3bf4d1a
# @clerk/fastify
npm i @clerk/fastify@0.6.12-snapshot.3bf4d1a
# gatsby-plugin-clerk
npm i gatsby-plugin-clerk@4.4.13-snapshot.3bf4d1a
# @clerk/localizations
npm i @clerk/localizations@1.26.3-snapshot.3bf4d1a
# @clerk/nextjs
npm i @clerk/nextjs@4.25.2-snapshot.3bf4d1a
# @clerk/clerk-react
npm i @clerk/clerk-react@4.27.0-snapshot.3bf4d1a
# @clerk/remix
npm i @clerk/remix@3.0.4-snapshot.3bf4d1a
# @clerk/clerk-sdk-node
npm i @clerk/clerk-sdk-node@4.12.11-snapshot.3bf4d1a
# @clerk/shared
npm i @clerk/shared@0.24.2-snapshot.3bf4d1a
# @clerk/themes
npm i @clerk/themes@1.7.7-snapshot.3bf4d1a
# @clerk/types
npm i @clerk/types@3.54.0-snapshot.3bf4d1a

@anagstef

Copy link
Copy Markdown
ContributorAuthor

!snapshot

@clerk-cookie

Copy link
Copy Markdown
Collaborator

Hey @anagstef - the snapshot version command generated the following package versions:

PackageVersion
@clerk/backend0.30.3-snapshot.09b5917
@clerk/chrome-extension0.4.6-snapshot.09b5917
@clerk/clerk-js4.61.0-snapshot.09b5917
@clerk/clerk-expo0.19.8-snapshot.09b5917
@clerk/fastify0.6.13-snapshot.09b5917
gatsby-plugin-clerk4.4.14-snapshot.09b5917
@clerk/localizations1.26.4-snapshot.09b5917
@clerk/nextjs4.25.3-snapshot.09b5917
@clerk/clerk-react4.27.0-snapshot.09b5917
@clerk/remix3.0.5-snapshot.09b5917
@clerk/clerk-sdk-node4.12.12-snapshot.09b5917
@clerk/shared0.24.3-snapshot.09b5917
@clerk/types3.55.0-snapshot.09b5917

Tip: use the snippet copy button below to quickly install the required packages.

# @clerk/backend
npm i @clerk/backend@0.30.3-snapshot.09b5917
# @clerk/chrome-extension
npm i @clerk/chrome-extension@0.4.6-snapshot.09b5917
# @clerk/clerk-js
npm i @clerk/clerk-js@4.61.0-snapshot.09b5917
# @clerk/clerk-expo
npm i @clerk/clerk-expo@0.19.8-snapshot.09b5917
# @clerk/fastify
npm i @clerk/fastify@0.6.13-snapshot.09b5917
# gatsby-plugin-clerk
npm i gatsby-plugin-clerk@4.4.14-snapshot.09b5917
# @clerk/localizations
npm i @clerk/localizations@1.26.4-snapshot.09b5917
# @clerk/nextjs
npm i @clerk/nextjs@4.25.3-snapshot.09b5917
# @clerk/clerk-react
npm i @clerk/clerk-react@4.27.0-snapshot.09b5917
# @clerk/remix
npm i @clerk/remix@3.0.5-snapshot.09b5917
# @clerk/clerk-sdk-node
npm i @clerk/clerk-sdk-node@4.12.12-snapshot.09b5917
# @clerk/shared
npm i @clerk/shared@0.24.3-snapshot.09b5917
# @clerk/types
npm i @clerk/types@3.55.0-snapshot.09b5917


export const OrganizationProfileRoutes = (props: PropsOfComponent<typeof ProfileCardContent>) => {
const { pages } = useOrganizationProfileContext();
const isMembersPageRoot = pages.routes[0].id === 'members';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any chance this array is empty?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No chance; it always appends Clerk default routes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ is there a way to use a constant or enum instead of hard-coding members?

path='profile'
flowStart
index={!isPredefinedPageRoot && index === 0}
path={!isPredefinedPageRoot && index === 0 ? undefined : customPage.url}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we extract this condition and give it some context with a good variable name?

Comment threadpackages/types/src/clerk.ts
Comment on lines -97 to -98
export type NavbarItemId = 'account' | 'security' | 'members' | 'settings';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are interested in keeping this we could do

exporttypeNavbarItemId='account'|'security'|'members'|'settings'|(string&{});

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to keep this?

Comment on lines +31 to +32

const portal = () => <>{nodes[index] ? createPortal(el.component, nodes[index] as Element) : null}</>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 Is it a common pattern to have a react hook returning JSX ?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's not that common, but we need this to be updated on each render. Do you have any suggestions on different approaches?

Comment threadpackages/react/src/utils/useCustomElementPortal.tsx Outdated
Comment threadpackages/react/src/utils/errorInDevMode.ts Outdated
Comment threadpackages/react/src/components/uiComponents.tsx Outdated
Comment threadpackages/react/src/components/uiComponents.tsx Outdated
Comment on lines +20 to +28
type UserProfileCustomPage = {
label: string;
url: string;
mountIcon: (el: HTMLDivElement) => void;
unmountIcon: (el?: HTMLDivElement) => void;
mount: (el: HTMLDivElement) => void;
unmount: (el?: HTMLDivElement) => void;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙃

Suggested change
typeUserProfileCustomPage={
label: string;
url: string;
mountIcon: (el: HTMLDivElement)=>void;
unmountIcon: (el?: HTMLDivElement)=>void;
mount: (el: HTMLDivElement)=>void;
unmount: (el?: HTMLDivElement)=>void;
};
typeUserProfileCustomPage=CustomPage

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a different type. CustomPage is more generic for all three types of custom items (pages, links, reordering), which is why all of its properties except label are optional.

@anagstefanagstef changed the title Custom Pages for UserProfile and OrganizationProfile componentsCustom Pages for <UserProfile /> and <OrganizationProfile /> componentsOct 12, 2023
@anagstef

Copy link
Copy Markdown
ContributorAuthor

!snapshot

@clerk-cookie

Copy link
Copy Markdown
Collaborator

Hey @anagstef - the snapshot version command generated the following package versions:

PackageVersion
@clerk/backend0.30.3-snapshot.93fdf20
@clerk/chrome-extension0.4.6-snapshot.93fdf20
@clerk/clerk-js4.61.0-snapshot.93fdf20
@clerk/clerk-expo0.19.8-snapshot.93fdf20
@clerk/fastify0.6.13-snapshot.93fdf20
gatsby-plugin-clerk4.4.14-snapshot.93fdf20
@clerk/localizations1.26.4-snapshot.93fdf20
@clerk/nextjs4.25.3-snapshot.93fdf20
@clerk/clerk-react4.27.0-snapshot.93fdf20
@clerk/remix3.0.5-snapshot.93fdf20
@clerk/clerk-sdk-node4.12.12-snapshot.93fdf20
@clerk/shared0.24.3-snapshot.93fdf20
@clerk/types3.55.0-snapshot.93fdf20

Tip: use the snippet copy button below to quickly install the required packages.

# @clerk/backend
npm i @clerk/backend@0.30.3-snapshot.93fdf20
# @clerk/chrome-extension
npm i @clerk/chrome-extension@0.4.6-snapshot.93fdf20
# @clerk/clerk-js
npm i @clerk/clerk-js@4.61.0-snapshot.93fdf20
# @clerk/clerk-expo
npm i @clerk/clerk-expo@0.19.8-snapshot.93fdf20
# @clerk/fastify
npm i @clerk/fastify@0.6.13-snapshot.93fdf20
# gatsby-plugin-clerk
npm i gatsby-plugin-clerk@4.4.14-snapshot.93fdf20
# @clerk/localizations
npm i @clerk/localizations@1.26.4-snapshot.93fdf20
# @clerk/nextjs
npm i @clerk/nextjs@4.25.3-snapshot.93fdf20
# @clerk/clerk-react
npm i @clerk/clerk-react@4.27.0-snapshot.93fdf20
# @clerk/remix
npm i @clerk/remix@3.0.5-snapshot.93fdf20
# @clerk/clerk-sdk-node
npm i @clerk/clerk-sdk-node@4.12.12-snapshot.93fdf20
# @clerk/shared
npm i @clerk/shared@0.24.3-snapshot.93fdf20
# @clerk/types
npm i @clerk/types@3.55.0-snapshot.93fdf20

@anagstef
anagstef added this pull request to the merge queue Oct 24, 2023
Merged via the queue into main with commit 51861adOct 24, 2023
@anagstef
anagstef deleted the js-621-rfc-poc branch October 24, 2023 01:47
@clerk-cookieclerk-cookie mentioned this pull request Oct 24, 2023
@clerk-cookie

Copy link
Copy Markdown
Collaborator

This PR has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@clerkclerk locked as resolved and limited conversation to collaborators Oct 24, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@anagstef@clerk-cookie@brkalow@dimkl@panteliselef@desiprisg