Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/app/[lang]/DiscoveryFeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type DiscoveryFeedProps = {
locale: string;
needsOnboarding: boolean;
savedProfileIds: string[];
currentProfileId?: string;
userAvatarUrl?: string;
};

Expand All @@ -17,6 +18,7 @@ export const DiscoveryFeed = async ({
locale,
needsOnboarding,
savedProfileIds,
currentProfileId,
userAvatarUrl,
}: DiscoveryFeedProps) => {
let profiles: DiscoveryProfile[] = [];
Expand All @@ -38,6 +40,7 @@ export const DiscoveryFeed = async ({
needsOnboarding={needsOnboarding}
profiles={profiles}
savedProfileIds={savedProfileIds}
currentProfileId={currentProfileId}
userAvatarUrl={userAvatarUrl}
/>
);
Expand Down
4 changes: 4 additions & 0 deletions src/app/[lang]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ export default async function Home({
const user = await getCurrentUser();
let needsOnboarding = false;
let savedProfileIds: string[] = [];
let currentProfileId: string | undefined;

if (user) {
const persistedUser = await upsertDiscordUser(user);
const profile = await getProfileByUserId(persistedUser.id);
needsOnboarding = !profile;
currentProfileId = profile?.profile.id;
savedProfileIds = await listSavedProfileIds(persistedUser.id);
}

Expand All @@ -39,6 +41,7 @@ export default async function Home({
isLoggedIn={isLoggedIn}
locale={lang}
needsOnboarding={needsOnboarding}
currentProfileId={currentProfileId}
userAvatarUrl={user?.avatarUrl}
/>
}
Expand All @@ -49,6 +52,7 @@ export default async function Home({
locale={lang}
needsOnboarding={needsOnboarding}
savedProfileIds={savedProfileIds}
currentProfileId={currentProfileId}
userAvatarUrl={user?.avatarUrl}
/>
</Suspense>
Expand Down
3 changes: 3 additions & 0 deletions src/app/[lang]/saved/SavedRouteClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ import { Navbar } from '@/features/Navbar';
type SavedRouteClientProps = {
locale: string;
profiles: DiscoveryProfile[];
currentProfileId?: string;
userAvatarUrl?: string;
};

export const SavedRouteClient = ({
locale,
profiles: initialProfiles,
currentProfileId,
userAvatarUrl,
}: SavedRouteClientProps) => {
const router = useRouter();
Expand Down Expand Up @@ -65,6 +67,7 @@ export const SavedRouteClient = ({
profiles={profiles}
isLoggedIn
savedProfileIds={profiles.map((profile) => profile.id)}
currentProfileId={currentProfileId}
onSaveProfile={saveProfileRequest}
onProfileUnsaved={handleProfileUnsaved}
/>
Expand Down
4 changes: 3 additions & 1 deletion src/app/[lang]/saved/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { redirect } from 'next/navigation';
import { listSavedProfiles, upsertDiscordUser } from '@/db';
import { getProfileByUserId, listSavedProfiles, upsertDiscordUser } from '@/db';
import { getCurrentUser } from '@/lib/auth';
import { SavedRouteClient } from './SavedRouteClient';

Expand All @@ -16,12 +16,14 @@ export default async function SavedRoute({
}

const persistedUser = await upsertDiscordUser(user);
const profile = await getProfileByUserId(persistedUser.id);
const savedProfiles = await listSavedProfiles(persistedUser.id);

return (
<SavedRouteClient
locale={lang}
profiles={savedProfiles}
currentProfileId={profile?.profile.id}
userAvatarUrl={user.avatarUrl}
/>
);
Expand Down
8 changes: 8 additions & 0 deletions src/app/api/saved/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ export const POST = async (request: Request) => {
}

const user = await upsertDiscordUser(currentUser);

if (target.profile.userId === user.id) {
return NextResponse.json(
{ error: 'Cannot save your own profile' },
{ status: 400 },
);
}

await saveProfile(user.id, profileId);

return NextResponse.json({ saved: true });
Expand Down
7 changes: 4 additions & 3 deletions src/db/saved.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import 'server-only';

import { and, desc, eq } from 'drizzle-orm';
import { and, desc, eq, ne } from 'drizzle-orm';
import { db } from './client';
import { listPublicProfilesByIds } from './profiles';
import { savedProfiles } from './schema';
import { profiles, savedProfiles } from './schema';

const missingSavedProfilesStorageCodes = new Set(['42P01', '42703']);

Expand Down Expand Up @@ -36,7 +36,8 @@ export const listSavedProfileIds = async (userId: string) => {
const rows = await db
.select({ profileId: savedProfiles.profileId })
.from(savedProfiles)
.where(eq(savedProfiles.userId, userId))
.innerJoin(profiles, eq(savedProfiles.profileId, profiles.id))
.where(and(eq(savedProfiles.userId, userId), ne(profiles.userId, userId)))
.orderBy(desc(savedProfiles.createdAt));

return rows.map((row) => row.profileId);
Expand Down
3 changes: 3 additions & 0 deletions src/features/Discovery/DiscoveryPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type DiscoveryPageProps = {
needsOnboarding?: boolean;
profiles?: DiscoveryProfile[];
savedProfileIds?: string[];
currentProfileId?: string;
userAvatarUrl?: string;
};

Expand All @@ -65,6 +66,7 @@ export const DiscoveryPage = ({
needsOnboarding = false,
profiles = [],
savedProfileIds,
currentProfileId,
userAvatarUrl,
}: DiscoveryPageProps) => {
const router = useRouter();
Expand Down Expand Up @@ -306,6 +308,7 @@ export const DiscoveryPage = ({
profiles={pageItems}
isLoggedIn={isLoggedIn}
savedProfileIds={savedProfileIds}
currentProfileId={currentProfileId}
onSaveProfile={saveProfileRequest}
emptyState={
hasActiveFilters ? undefined : t('emptyFeedDescription')
Expand Down
11 changes: 9 additions & 2 deletions src/features/Discovery/ProfileCard.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,12 @@ export const SaveAction: Story = {
},
play: async ({ args, canvasElement }) => {
const canvas = within(canvasElement);
const body = within(canvasElement.ownerDocument.body);

await userEvent.click(canvas.getByRole('button', { name: 'Save profile' }));
await userEvent.click(canvas.getByRole('button', { name: 'Card menu' }));
await userEvent.click(
await body.findByRole('button', { name: 'Save profile' }),
);
await expect(args.onToggleSave).toHaveBeenCalledWith('1');
},
};
Expand All @@ -269,9 +273,12 @@ export const Saved: Story = {
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const body = within(canvasElement.ownerDocument.body);

await userEvent.click(canvas.getByRole('button', { name: 'Card menu' }));

await expect(
canvas.getByRole('button', { name: 'Remove from saved' }),
await body.findByRole('button', { name: 'Remove from saved' }),
).toBeInTheDocument();
},
};
Expand Down
33 changes: 14 additions & 19 deletions src/features/Discovery/ProfileCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ export const ProfileCard = ({

const handleToggleSave = () => {
onToggleSave?.(profile.id);
setIsMenuOpen(false);
};

const renderTag = (value: string, key?: string) => (
Expand Down Expand Up @@ -393,25 +394,6 @@ export const ProfileCard = ({
{profile.lastBumpRelative}
</span>
)}
{!isPreview && onToggleSave && (
<button
type="button"
onClick={handleToggleSave}
className={`flex h-[26px] w-[26px] items-center justify-center rounded-full bg-black/30 backdrop-blur-sm transition-colors hover:bg-black/50 ${
isSaved
? 'text-[var(--ct-accent,var(--color-primary))]'
: 'text-white/90 hover:text-white'
}`}
aria-label={isSaved ? t('unsaveProfile') : t('saveProfile')}
aria-pressed={isSaved}
>
{isSaved ? (
<MdBookmark size={16} />
) : (
<MdBookmarkBorder size={16} />
)}
</button>
)}
{!isPreview && (
<Popover.Root open={isMenuOpen} onOpenChange={setIsMenuOpen}>
<Popover.Trigger asChild>
Expand Down Expand Up @@ -439,6 +421,19 @@ export const ProfileCard = ({
>
{t('viewProfile')}
</MenuItem>
{onToggleSave ? (
<MenuItem
icon={isSaved ? MdBookmark : MdBookmarkBorder}
onClick={handleToggleSave}
iconClassName={
isSaved
? 'text-[var(--ct-accent,var(--color-primary))]'
: undefined
}
>
{isSaved ? t('unsaveProfile') : t('saveProfile')}
</MenuItem>
) : null}
{(typeof navigator !== 'undefined' &&
typeof navigator.share === 'function') ||
!!onShare ? (
Expand Down
46 changes: 26 additions & 20 deletions src/features/Discovery/ProfileGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type ProfileGridProps = {
emptyState?: ReactNode;
isLoggedIn?: boolean;
savedProfileIds?: string[];
currentProfileId?: string;
matchCriteria?: MatchCriteria | null;
sortByMatchScore?: boolean;
onCopyUsername?: (username: string, profileId: string) => void;
Expand Down Expand Up @@ -81,6 +82,7 @@ export const ProfileGrid = ({
emptyState,
isLoggedIn = false,
savedProfileIds,
currentProfileId,
matchCriteria = null,
sortByMatchScore = false,
onCopyUsername,
Expand Down Expand Up @@ -191,26 +193,30 @@ export const ProfileGrid = ({
}
};

const renderProfileCard = ({ profile, index }: ProfileGridItem) => (
<ProfileCard
key={profile.id}
profile={{
...profile,
cardTheme: profile.cardTheme ?? getFreeCardTheme(index),
}}
isLoggedIn={isLoggedIn}
isSaved={savedIds.has(profile.id)}
onCopyUsername={handleCopyUsername}
onToggleSave={saveEnabled ? handleToggleSave : undefined}
onTagClick={onTagClick}
onLanguageClick={onLanguageClick}
onCountryClick={onCountryClick}
onViewProfile={onViewProfile}
onReport={onReport}
onBlock={onBlock}
onShare={onShare}
/>
);
const renderProfileCard = ({ profile, index }: ProfileGridItem) => {
const canSaveProfile = saveEnabled && profile.id !== currentProfileId;

return (
<ProfileCard
key={profile.id}
profile={{
...profile,
cardTheme: profile.cardTheme ?? getFreeCardTheme(index),
}}
isLoggedIn={isLoggedIn}
isSaved={savedIds.has(profile.id)}
onCopyUsername={handleCopyUsername}
onToggleSave={canSaveProfile ? handleToggleSave : undefined}
onTagClick={onTagClick}
onLanguageClick={onLanguageClick}
onCountryClick={onCountryClick}
onViewProfile={onViewProfile}
onReport={onReport}
onBlock={onBlock}
onShare={onShare}
/>
);
};

const renderProfileColumns = (columnCount: number, className: string) => (
<div className={`mx-auto w-full max-w-[1180px] gap-6 ${className}`}>
Expand Down
2 changes: 1 addition & 1 deletion src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@
"title": "Saved profiles",
"subtitle": "Profiles you saved to revisit. Private or removed profiles drop off automatically.",
"emptyTitle": "No saved profiles yet",
"emptyDescription": "Use the bookmark on any profile card to keep it here for later.",
"emptyDescription": "Use the save action in any profile card menu to keep it here for later.",
"emptyAction": "Browse profiles"
},
"Onboarding": {
Expand Down
2 changes: 1 addition & 1 deletion src/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@
"title": "保存したプロフィール",
"subtitle": "あとで見返すために保存したプロフィール。非公開や削除されたプロフィールは自動的に表示されなくなります。",
"emptyTitle": "保存したプロフィールはまだありません",
"emptyDescription": "プロフィールカードのブックマークを押すと、ここに保存して後で見返せます。",
"emptyDescription": "プロフィールカードメニューの保存アクションを使うと、ここに保存して後で見返せます。",
"emptyAction": "プロフィールを見る"
},
"Onboarding": {
Expand Down
Loading