From 0a41ebbcda9908b1a6228f89678b4f96a0f3e48a Mon Sep 17 00:00:00 2001 From: MartinBraquet Date: Mon, 27 Oct 2025 00:37:54 +0100 Subject: [PATCH] Add an option to disable your profile --- backend/api/src/get-profiles.ts | 2 ++ backend/email/emails/functions/mock.ts | 2 ++ backend/supabase/profiles.sql | 2 +- common/src/api/zod-types.ts | 7 +++--- common/src/supabase/schema.ts | 3 +++ web/components/profile/profile-header.tsx | 30 +++++++++++++++++++++++ web/lib/util/disable.ts | 7 ++++++ web/pages/[username]/index.tsx | 19 +++++++++++--- 8 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 web/lib/util/disable.ts diff --git a/backend/api/src/get-profiles.ts b/backend/api/src/get-profiles.ts index 823172e6..ae919e78 100644 --- a/backend/api/src/get-profiles.ts +++ b/backend/api/src/get-profiles.ts @@ -118,6 +118,7 @@ export const loadProfiles = async (props: profileQueryType) => { (has_kids == 0 && !l.has_kids) || (l.has_kids && l.has_kids > 0)) && (is_smoker === undefined || l.is_smoker === is_smoker) && + (!l.disabled) && (l.id.toString() != skipId) && (!geodbCityIds || (l.geodb_city_id && geodbCityIds.includes(l.geodb_city_id))) && @@ -149,6 +150,7 @@ export const loadProfiles = async (props: profileQueryType) => { join('users on users.id = profiles.user_id'), leftJoin(userActivityJoin), where('looking_for_matches = true'), + where(`profiles.disabled != true`), // where(`pinned_url is not null and pinned_url != ''`), where( `(data->>'isBannedFromPosting' != 'true' or data->>'isBannedFromPosting' is null)` diff --git a/backend/email/emails/functions/mock.ts b/backend/email/emails/functions/mock.ts index e04352b5..d08ca1e9 100644 --- a/backend/email/emails/functions/mock.ts +++ b/backend/email/emails/functions/mock.ts @@ -39,6 +39,7 @@ export const sinclairProfile: ProfileRow = { religion: [], pref_relation_styles: ['friendship'], pref_romantic_styles: ['poly', 'open', 'mono'], + disabled: false, wants_kids_strength: 3, looking_for_matches: true, visibility: 'public', @@ -137,6 +138,7 @@ export const jamesProfile: ProfileRow = { city: 'San Francisco', gender: 'male', pref_gender: ['female'], + disabled: false, pref_age_min: 22, pref_age_max: 32, religion: [], diff --git a/backend/supabase/profiles.sql b/backend/supabase/profiles.sql index 1c52f57b..e9d4a915 100644 --- a/backend/supabase/profiles.sql +++ b/backend/supabase/profiles.sql @@ -1,4 +1,3 @@ - DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'profile_visibility') THEN @@ -53,6 +52,7 @@ CREATE TABLE IF NOT EXISTS profiles ( visibility profile_visibility DEFAULT 'member'::profile_visibility NOT NULL, wants_kids_strength INTEGER DEFAULT 0 NOT NULL, website TEXT, + disabled BOOLEAN DEFAULT FALSE NOT NULL, CONSTRAINT profiles_pkey PRIMARY KEY (id) ); diff --git a/common/src/api/zod-types.ts b/common/src/api/zod-types.ts index fac9751e..92254878 100644 --- a/common/src/api/zod-types.ts +++ b/common/src/api/zod-types.ts @@ -82,9 +82,10 @@ const optionalProfilesSchema = z.object({ ethnicity: z.array(z.string()).optional(), born_in_location: z.string().optional(), height_in_inches: z.number().optional(), - has_pets: zBoolean.optional().optional(), + has_pets: zBoolean.optional(), education_level: z.string().optional(), - is_smoker: zBoolean.optional().optional(), + is_smoker: zBoolean.optional(), + disabled: zBoolean.optional(), drinks_per_month: z.number().min(0).optional(), diet: z.array(z.string()).optional(), has_kids: z.number().min(0).optional(), @@ -92,7 +93,7 @@ const optionalProfilesSchema = z.object({ occupation_title: z.string().optional(), occupation: z.string().optional(), company: z.string().optional(), - comments_enabled: zBoolean.optional().optional(), + comments_enabled: zBoolean.optional(), website: z.string().optional(), bio: contentSchema.optional().nullable(), twitter: z.string().optional(), diff --git a/common/src/supabase/schema.ts b/common/src/supabase/schema.ts index bb51d04e..8c1c50c3 100644 --- a/common/src/supabase/schema.ts +++ b/common/src/supabase/schema.ts @@ -541,6 +541,7 @@ export type Database = { country: string | null created_time: string diet: string[] | null + disabled: boolean drinks_per_month: number | null education_level: string | null ethnicity: string[] | null @@ -591,6 +592,7 @@ export type Database = { country?: string | null created_time?: string diet?: string[] | null + disabled?: boolean drinks_per_month?: number | null education_level?: string | null ethnicity?: string[] | null @@ -641,6 +643,7 @@ export type Database = { country?: string | null created_time?: string diet?: string[] | null + disabled?: boolean drinks_per_month?: number | null education_level?: string | null ethnicity?: string[] | null diff --git a/web/components/profile/profile-header.tsx b/web/components/profile/profile-header.tsx index a26f2ffd..72da7b10 100644 --- a/web/components/profile/profile-header.tsx +++ b/web/components/profile/profile-header.tsx @@ -23,6 +23,7 @@ import {VisibilityConfirmationModal} from './visibility-confirmation-modal' import {deleteAccount} from "web/lib/util/delete"; import toast from "react-hot-toast"; import {StarButton} from "web/components/widgets/star-button"; +import {disableProfile} from "web/lib/util/disable"; export default function ProfileHeader(props: { user: User @@ -47,12 +48,14 @@ export default function ProfileHeader(props: { const currentUser = useUser() const isCurrentUser = currentUser?.id === user.id const [showVisibilityModal, setShowVisibilityModal] = useState(false) + const disabled = profile.disabled console.debug('ProfileProfileHeader', {user, profile, userActivity, currentUser}) return ( + {currentUser && isCurrentUser && disabled &&
You disabled your profile, so no one else can access it.
} @@ -135,6 +138,33 @@ export default function ProfileHeader(props: { } }, }, + { + name: disabled ? 'Enable profile' : 'Disable profile', + icon: null, + onClick: async () => { + const confirmed = true // confirm( + // 'Are you sure you want to disable your profile? This will hide your profile from searches and listings..' + // ) + if (confirmed) { + toast + .promise(disableProfile(!disabled), { + loading: disabled ? 'Enabling profile...' : 'Disabling profile...', + success: () => { + return `Profile ${disabled ? 'enabled' : 'disabled'}` + }, + error: () => { + return `Failed to ${disabled ? 'enable' : 'disable'} profile` + }, + }) + .then(() => { + refreshProfile() + }) + .catch(() => { + // return false + }) + } + }, + }, ]} /> diff --git a/web/lib/util/disable.ts b/web/lib/util/disable.ts new file mode 100644 index 00000000..5fe98a24 --- /dev/null +++ b/web/lib/util/disable.ts @@ -0,0 +1,7 @@ +import {track} from "web/lib/service/analytics"; +import {updateProfile} from "web/lib/api"; + +export async function disableProfile(disabled: boolean) { + track(`disable profile ${disabled ? 'on' : 'off'}`) + await updateProfile({disabled: disabled}) +} \ No newline at end of file diff --git a/web/pages/[username]/index.tsx b/web/pages/[username]/index.tsx index c4e43a6a..253377eb 100644 --- a/web/pages/[username]/index.tsx +++ b/web/pages/[username]/index.tsx @@ -158,19 +158,32 @@ function UserPageInner(props: ActiveUserPageProps) { const fromSignup = query.fromSignup === 'true' const currentUser = useUser() - // const isCurrentUser = currentUser?.id === user?.id + const isCurrentUser = currentUser?.id === user?.id useSaveReferral(currentUser, {defaultReferrerUsername: username}) - useTracking('viewprofile', {username: user?.username}) + useTracking('view profile', {username: user?.username}) const [staticProfile] = useState( props.profile && user ? {...props.profile, user: user} : null ) const {profile: clientProfile, refreshProfile} = useProfileByUser(user) - // Show previous profile while loading another one + // Show the previous profile while loading another one const profile = clientProfile ?? staticProfile // console.debug('profile:', user?.username, profile, clientProfile, staticProfile) + if (!isCurrentUser && profile?.disabled) { + return + +
+ The user disabled their profile. +
+ +
+ } + return (