diff --git a/src/webapp/features/cards/components/urlCardContent/SembleProfileCardContent.tsx b/src/webapp/features/cards/components/urlCardContent/SembleProfileCardContent.tsx
new file mode 100644
index 00000000..4e5152e3
--- /dev/null
+++ b/src/webapp/features/cards/components/urlCardContent/SembleProfileCardContent.tsx
@@ -0,0 +1,46 @@
+'use client';
+
+import useProfile from '@/features/profile/lib/queries/useProfile';
+import { Avatar, Badge, Card, Group, Stack, Text } from '@mantine/core';
+import { sanitizeText } from '@/lib/utils/text';
+import RichTextRenderer from '@/components/contentDisplay/richTextRenderer/RichTextRenderer';
+
+interface Props {
+ handle: string;
+}
+
+export default function SembleProfileCardContent(props: Props) {
+ const { data: profile } = useProfile({ didOrHandle: props.handle });
+
+ return (
+
+
+
+
+
+
+
+ {sanitizeText(profile.name) || profile.handle}
+
+
+ @{profile.handle}
+
+
+
+
+
+ {profile.followsYou && (
+
+ Follows you
+
+ )}
+
+
+ {profile.description && }
+
+
+ );
+}
diff --git a/src/webapp/features/cards/components/urlCardContent/UrlCardContent.tsx b/src/webapp/features/cards/components/urlCardContent/UrlCardContent.tsx
index db5feab2..021653d9 100644
--- a/src/webapp/features/cards/components/urlCardContent/UrlCardContent.tsx
+++ b/src/webapp/features/cards/components/urlCardContent/UrlCardContent.tsx
@@ -3,6 +3,7 @@
import { detectUrlPlatform, SupportedPlatform } from '@/lib/utils/link';
import { UrlCard } from '@semble/types';
import SembleCollectionCardContent from './SembleCollectionCardContent';
+import SembleProfileCardContent from './SembleProfileCardContent';
import LinkCardContent from './LinkCardContent';
import BlueskyPost from '@/features/platforms/bluesky/components/blueskyPost/BlueskyPost';
import { Suspense } from 'react';
@@ -53,6 +54,33 @@ export default function UrlCardContent(props: Props) {
);
}
+ if (platform.type === SupportedPlatform.SEMBLE_PROFILE) {
+ if (!platform.handle) {
+ return (
+
+ );
+ }
+ return (
+
+ }
+ >
+ }>
+
+
+
+ );
+ }
+
if (
platform.type === SupportedPlatform.BLUESKY_POST ||
platform.type === SupportedPlatform.BLACKSKY_POST
diff --git a/src/webapp/features/profile/components/profileCard/ProfileCard.tsx b/src/webapp/features/profile/components/profileCard/ProfileCard.tsx
index 72621fe1..073626d7 100644
--- a/src/webapp/features/profile/components/profileCard/ProfileCard.tsx
+++ b/src/webapp/features/profile/components/profileCard/ProfileCard.tsx
@@ -18,7 +18,7 @@ export default function ProfileCard(props: Props) {
href={`/profile/${props.profile.handle}`}
h={'100%'}
>
-
+
-
+
diff --git a/src/webapp/lib/utils/link.ts b/src/webapp/lib/utils/link.ts
index 54db07d6..e1c90866 100644
--- a/src/webapp/lib/utils/link.ts
+++ b/src/webapp/lib/utils/link.ts
@@ -61,8 +61,7 @@ export const isProfilePage = (url: string) => {
if (!isSembleUrl(parsedUrl.href)) return false;
// matches:
// /profile/:handle
- // /profile/:handle/:subroute
- const pattern = /^\/profile\/[^/]+(?:\/[^/]+)?\/?$/;
+ const pattern = /^\/profile/;
return pattern.test(parsedUrl.pathname);
} catch {
// invalid URL
@@ -74,6 +73,7 @@ export enum SupportedPlatform {
BLUESKY_POST = 'bluesky post',
BLACKSKY_POST = 'blacksky post',
SEMBLE_COLLECTION = 'semble collection',
+ SEMBLE_PROFILE = 'semble profile',
SPOTIFY = 'spotify',
PLYRFM_TRACK = 'plyr.fm',
YOUTUBE_VIDEO = 'youtube video',
@@ -90,7 +90,15 @@ type PlatformData =
rkey: string;
}
| {
- type: Exclude;
+ type: SupportedPlatform.SEMBLE_PROFILE;
+ url: string;
+ handle: string;
+ }
+ | {
+ type: Exclude<
+ SupportedPlatform,
+ SupportedPlatform.SEMBLE_COLLECTION | SupportedPlatform.SEMBLE_PROFILE
+ >;
url: string;
};
@@ -113,6 +121,18 @@ export const detectUrlPlatform = (url: string): PlatformData => {
}
}
+ if (isProfilePage(url)) {
+ try {
+ const parsedUrl = new URL(url, window.location.origin);
+ const pathParts = parsedUrl.pathname.split('/').filter(Boolean);
+ // expected format: profile/:handle or profile/:handle/:subroute
+ const handle = pathParts[1];
+ return { type: SupportedPlatform.SEMBLE_PROFILE, url, handle };
+ } catch {
+ return { type: SupportedPlatform.DEFAULT, url };
+ }
+ }
+
try {
const parsedUrl = new URL(url);