From 52e66c7c0e3e12f261e85066343da51e8a717f26 Mon Sep 17 00:00:00 2001 From: Carlos Andres Perez Ubeda Date: Mon, 20 Jan 2025 18:48:25 -0600 Subject: [PATCH 1/8] refactor(avatar): improve avatar source handling logic - Updated `AvatarProfile` to use a local variable `imageSrc` for clearer and safer source URL handling. - Ensured generated avatar fallback works correctly for invalid or missing URLs. --- src/components/avatar/avatar.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/avatar/avatar.tsx b/src/components/avatar/avatar.tsx index e00be614..c170d54c 100644 --- a/src/components/avatar/avatar.tsx +++ b/src/components/avatar/avatar.tsx @@ -11,12 +11,11 @@ interface AvatarProfileProps { } const AvatarProfile: FC = ({ src, alt, sx, ...other }) => { - //Check if src is a valid URL starting with http or https; if not, use the dicebear API to generate a random avatar - if (!src.startsWith('http') && !src.startsWith('https')) { src = dicebear(src) } + const imageSrc = src.startsWith('http') || src.startsWith('https') ? src : dicebear(src); return ( - + ) } From 4f6191a9adef8a3be93edb179ce9c7a581605b64 Mon Sep 17 00:00:00 2001 From: jadapema Date: Mon, 20 Jan 2025 18:55:12 -0600 Subject: [PATCH 2/8] refactor: supabase actions --- src/hooks/use-referrals.ts | 45 +++++++++--------- src/types/invitation.ts | 10 ++++ src/utils/supabase-actions.ts | 87 +++++++++++++++++++++++++++++------ 3 files changed, 105 insertions(+), 37 deletions(-) create mode 100644 src/types/invitation.ts diff --git a/src/hooks/use-referrals.ts b/src/hooks/use-referrals.ts index ff1dd70a..5ef98de5 100644 --- a/src/hooks/use-referrals.ts +++ b/src/hooks/use-referrals.ts @@ -1,7 +1,9 @@ import { useState } from 'react'; +import { useSelector } from 'react-redux'; import emailjs from '@emailjs/browser'; + import { GLOBAL_CONSTANTS } from '@src/config-global'; -import { useSelector } from 'react-redux'; +import { Invitation } from '@src/types/invitation'; // <-- Importing from separate types file import { fetchInvitations as fetchInvitationsAction, checkIfMyEmailHasPendingInvite as checkIfMyEmailHasPendingInviteAction, @@ -9,20 +11,12 @@ import { checkIfInvitationSent as checkIfInvitationSentAction, checkIfEmailAlreadyAccepted as checkIfEmailAlreadyAcceptedAction, sendInvitation as sendInvitationAction, - acceptOrCreateInvitationForUser as acceptOrCreateInvitationForUserAction + acceptOrCreateInvitationForUser as acceptOrCreateInvitationForUserAction, } from '@src/utils/supabase-actions'; -export interface Invitation { - id: string; - status: 'pending' | 'accepted' | 'rejected'; - sender_email: string; - destination: string; - sender_id: string; - receiver_id: string | null; - payload: any; - created_at: string; -} - +/** + * The type for sending emails through EmailJS. + */ export type EmailParams = { to_email: string; from_name: string; @@ -94,7 +88,10 @@ const useReferrals = () => { setLoading(true); setError(null); - const { data, error } = await acceptInvitationAction(invitationId, sessionData?.profile?.id); + const { data, error } = await acceptInvitationAction( + invitationId, + sessionData?.profile?.id + ); if (error) { setError(error); @@ -102,6 +99,7 @@ const useReferrals = () => { return null; } + // Update local state to reflect the accepted status setInvitations((prev) => prev.map((inv) => inv.id === invitationId @@ -168,6 +166,7 @@ const useReferrals = () => { * @returns {Promise} - Throws an error if something goes wrong. */ const sendInvitation = async (destination: string, payload: any): Promise => { + // Insert a new invitation into Supabase const { error } = await sendInvitationAction(destination, payload, userEmail, sessionData); if (error) { @@ -176,7 +175,7 @@ const useReferrals = () => { } else { console.log('Invitation stored successfully in Supabase.'); - // Send the email using EmailJS + // Send the email via EmailJS await sendEmail({ to_email: destination, from_name: payload?.data?.from?.displayName ?? 'Watchit Web3xAI', @@ -201,13 +200,17 @@ const useReferrals = () => { } }; + /** + * Send an email with EmailJS. + */ const sendEmail = async (data: EmailParams) => { const { from_name, to_email } = data; + // Set the template parameters for EmailJS const templateParams = { to_email, from_name, - from_email: GLOBAL_CONSTANTS.SENDER_EMAIL, + from_email: GLOBAL_CONSTANTS.SENDER_EMAIL, // <-- Enforcing the global from_email }; try { @@ -233,17 +236,15 @@ const useReferrals = () => { loading, error, - // Fetch/CRUD Methods + // CRUD/Fetch Methods fetchInvitations, - sendInvitation, - sendEmail, - - // Additional Validation/Check Methods checkIfMyEmailHasPendingInvite, acceptInvitation, checkIfInvitationSent, checkIfEmailAlreadyAccepted, - acceptOrCreateInvitationForUser + sendInvitation, + acceptOrCreateInvitationForUser, + sendEmail, }; }; diff --git a/src/types/invitation.ts b/src/types/invitation.ts new file mode 100644 index 00000000..8bcaaa10 --- /dev/null +++ b/src/types/invitation.ts @@ -0,0 +1,10 @@ +export interface Invitation { + id: string; + status: 'pending' | 'accepted' | 'rejected'; + sender_email: string; + destination: string; + sender_id: string; + receiver_id: string | null; + payload: any; + created_at: string; +} diff --git a/src/utils/supabase-actions.ts b/src/utils/supabase-actions.ts index 9ee39692..ef413350 100644 --- a/src/utils/supabase-actions.ts +++ b/src/utils/supabase-actions.ts @@ -1,7 +1,12 @@ import { supabase } from '@src/utils/supabase'; -import { Invitation } from '@src/hooks/use-referrals'; - -export const fetchInvitations = async (senderId: string): Promise<{ data: Invitation[] | null, error: string | null }> => { +import { Invitation } from '@src/types/invitation'; + +/** + * Fetches all invitations from Supabase filtered by senderId. + */ +export const fetchInvitations = async ( + senderId: string +): Promise<{ data: Invitation[] | null; error: string | null }> => { try { const { data, error } = await supabase .from('invitations') @@ -14,7 +19,12 @@ export const fetchInvitations = async (senderId: string): Promise<{ data: Invita } }; -export const checkIfMyEmailHasPendingInvite = async (userEmail: string): Promise<{ hasPending: boolean, error: string | null }> => { +/** + * Checks whether an email has a pending invitation. + */ +export const checkIfMyEmailHasPendingInvite = async ( + userEmail: string +): Promise<{ hasPending: boolean; error: string | null }> => { try { const { data, error } = await supabase .from('invitations') @@ -22,13 +32,23 @@ export const checkIfMyEmailHasPendingInvite = async (userEmail: string): Promise .eq('destination', userEmail) .eq('status', 'pending'); - return { hasPending: data && data.length > 0, error: error ? error.message : null }; + return { + hasPending: !!data && data.length > 0, + error: error ? error.message : null, + }; } catch (err: any) { return { hasPending: false, error: err.message }; } }; -export const acceptInvitation = async (invitationId: string, receiverId: string | null): Promise<{ data: Invitation | null, error: string | null }> => { +/** + * Accepts an existing invitation by setting its status to 'accepted' + * and assigning receiver_id to the current user's profile ID. + */ +export const acceptInvitation = async ( + invitationId: string, + receiverId: string | null +): Promise<{ data: Invitation | null; error: string | null }> => { try { const { data, error } = await supabase .from('invitations') @@ -45,7 +65,14 @@ export const acceptInvitation = async (invitationId: string, receiverId: string } }; -export const checkIfInvitationSent = async (userEmail: string, destinationEmail: string): Promise<{ exists: boolean, error: string | null }> => { +/** + * Checks whether the current user (userEmail) already sent an invitation + * to destinationEmail. + */ +export const checkIfInvitationSent = async ( + userEmail: string, + destinationEmail: string +): Promise<{ exists: boolean; error: string | null }> => { try { const { data, error } = await supabase .from('invitations') @@ -53,13 +80,21 @@ export const checkIfInvitationSent = async (userEmail: string, destinationEmail: .eq('sender_email', userEmail) .eq('destination', destinationEmail); - return { exists: data && data.length > 0, error: error ? error.message : null }; + return { + exists: !!data && data.length > 0, + error: error ? error.message : null, + }; } catch (err: any) { return { exists: false, error: err.message }; } }; -export const checkIfEmailAlreadyAccepted = async (destinationEmail: string): Promise<{ accepted: boolean, error: string | null }> => { +/** + * Checks if the specified email already has an accepted invitation. + */ +export const checkIfEmailAlreadyAccepted = async ( + destinationEmail: string +): Promise<{ accepted: boolean; error: string | null }> => { try { const { data, error } = await supabase .from('invitations') @@ -67,13 +102,24 @@ export const checkIfEmailAlreadyAccepted = async (destinationEmail: string): Pro .eq('destination', destinationEmail) .eq('status', 'accepted'); - return { accepted: data && data.length > 0, error: error ? error.message : null }; + return { + accepted: !!data && data.length > 0, + error: error ? error.message : null, + }; } catch (err: any) { return { accepted: false, error: err.message }; } }; -export const sendInvitation = async (destination: string, payload: any, userEmail: string, sessionData: any): Promise<{ error: string | null }> => { +/** + * Sends (inserts) a new invitation in Supabase. + */ +export const sendInvitation = async ( + destination: string, + payload: any, + userEmail: string, + sessionData: any +): Promise<{ error: string | null }> => { const { error } = await supabase .from('invitations') .insert([ @@ -83,13 +129,20 @@ export const sendInvitation = async (destination: string, payload: any, userEmai payload, sender_email: userEmail, sender_address: sessionData?.address, - } + }, ]); return { error: error ? error.message : null }; }; -export const acceptOrCreateInvitationForUser = async (userEmail: string, sessionData: any): Promise<{ error: string | null }> => { +/** + * Accepts the first invitation found for userEmail, + * or creates a new invitation with status = 'accepted' if none exist. + */ +export const acceptOrCreateInvitationForUser = async ( + userEmail: string, + sessionData: any +): Promise<{ error: string | null }> => { try { const { data: invites, error: pendingError } = await supabase .from('invitations') @@ -101,6 +154,7 @@ export const acceptOrCreateInvitationForUser = async (userEmail: string, session throw new Error(`Error fetching pending invites: ${pendingError.message}`); } + // If we already have at least one invitation, accept the first. if (invites && invites.length > 0) { const invitationId = invites[0].id; const { error } = await acceptInvitation(invitationId, sessionData?.profile?.id); @@ -108,6 +162,7 @@ export const acceptOrCreateInvitationForUser = async (userEmail: string, session throw new Error(error); } } else { + // Otherwise, create a new invitation with status='accepted' const { error: createError } = await supabase .from('invitations') .insert([ @@ -118,14 +173,16 @@ export const acceptOrCreateInvitationForUser = async (userEmail: string, session receiver_id: sessionData?.profile?.id ?? null, sender_email: userEmail, payload: { - self_register: true + self_register: true, }, status: 'accepted', }, ]); if (createError) { - throw new Error(`Error creating 'accepted' invitation: ${createError.message}`); + throw new Error( + `Error creating 'accepted' invitation: ${createError.message}` + ); } } From 01a82deef08e5df8d8d27c5d4909e1257585dafd Mon Sep 17 00:00:00 2001 From: jadapema Date: Mon, 20 Jan 2025 19:15:56 -0600 Subject: [PATCH 3/8] refactor: supabase actions --- src/utils/supabase-actions.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/utils/supabase-actions.ts b/src/utils/supabase-actions.ts index ef413350..71d95a07 100644 --- a/src/utils/supabase-actions.ts +++ b/src/utils/supabase-actions.ts @@ -154,6 +154,9 @@ export const acceptOrCreateInvitationForUser = async ( throw new Error(`Error fetching pending invites: ${pendingError.message}`); } + console.log('acceptOrCreateInvitationForUser invites') + console.log(invites) + // If we already have at least one invitation, accept the first. if (invites && invites.length > 0) { const invitationId = invites[0].id; From bc3f9cea2bf7ff6ab8cc2beb714299ccb70fee36 Mon Sep 17 00:00:00 2001 From: Carlos Andres Perez Ubeda Date: Mon, 20 Jan 2025 20:23:45 -0600 Subject: [PATCH 4/8] feat: add validation for email invitations and improve UX - `finance-invite-friends.tsx`: - Added validation for inviting users: prevent inviting self, malformed emails, or already invited users. - Integrated `checkIfEmailAlreadyInvited` from Supabase actions. - Disabled invite button when email is invalid or loading. - `supabase-actions.ts`: - Created `checkIfEmailAlreadyInvited` to validate if an email has been previously invited. - `errors.ts`: - Introduced new error messages for duplicate invitations and self-invitations. --- .../components/finance-invite-friends.tsx | 33 +++++++++++++++++-- src/utils/notifications/errors.ts | 4 +++ src/utils/supabase-actions.ts | 24 ++++++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/sections/finance/components/finance-invite-friends.tsx b/src/sections/finance/components/finance-invite-friends.tsx index 6473b0f7..e835cc5d 100644 --- a/src/sections/finance/components/finance-invite-friends.tsx +++ b/src/sections/finance/components/finance-invite-friends.tsx @@ -19,6 +19,7 @@ import { ERRORS } from '@notifications/errors.ts'; import useReferrals from "@src/hooks/use-referrals"; import LoadingButton from '@mui/lab/LoadingButton'; +import {checkIfEmailAlreadyInvited} from "@src/utils/supabase-actions.ts"; interface Props extends BoxProps { img?: string; @@ -42,6 +43,7 @@ export default function FinanceInviteFriends({ } = useReferrals(); const theme = useTheme(); const sessionData = useSelector((state: any) => state.auth.session); + const userLoggedEmail = useSelector((state: any) => state.auth.email); const [email, setEmail] = useState(''); const [loading, setLoading] = useState(false); @@ -49,10 +51,17 @@ export default function FinanceInviteFriends({ setEmail(event.target.value); }; - const handleInviteClick = async () => { - // Basic email format check + /* + * Return true if the email is valid, false otherwise. + * */ + const handleValidEmail = () => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!emailRegex.test(email)) { + return emailRegex.test(email); + } + + const handleInviteClick = async () => { + + if (!handleValidEmail()) { notifyError(ERRORS.INVITATION_EMAIL_ERROR); return; } @@ -61,6 +70,23 @@ export default function FinanceInviteFriends({ // Check if there's an existing invitation from the current user to this email const alreadySent = await checkIfInvitationSent(email); + + // Check if the user has already been invited but someone else + const { invited } = await checkIfEmailAlreadyInvited(email); + + if (invited) { + notifyError(ERRORS.INVITATION_USER_ALREADY_INVITED); + setLoading(false); + return; + } + + // Check if the email entered is the same as the logged user's email + if (email === userLoggedEmail) { + notifyError(ERRORS.INVITATION_USER_CANT_INVITE_SELF); + setLoading(false); + return; + } + if (alreadySent) { // You can adapt the notification message to match your requirements notifyError(ERRORS.ALREADY_SENT_INVITATION); @@ -165,6 +191,7 @@ export default function FinanceInviteFriends({ onChange={handleInputChange} endAdornment={ = { [ERRORS.ALREADY_SENT_INVITATION]: 'You have already sent an invitation to this email address!', [ERRORS.ALREADY_ENROLLED]: 'This user is already enrolled!', [ERRORS.INVITATION_SEND_ERROR]: 'An error occurred while sending the invitation.', + [ERRORS.INVITATION_USER_ALREADY_INVITED]: 'This user has already been invited!', + [ERRORS.INVITATION_USER_CANT_INVITE_SELF]: 'You cannot invite yourself!', }; diff --git a/src/utils/supabase-actions.ts b/src/utils/supabase-actions.ts index 71d95a07..44028d54 100644 --- a/src/utils/supabase-actions.ts +++ b/src/utils/supabase-actions.ts @@ -194,3 +194,27 @@ export const acceptOrCreateInvitationForUser = async ( return { error: err.message }; } }; + + +/* +* Verify if the email has already been invited. +* Find in supabase if the email has already been invited, taking the destination email as a parameter. +* */ + +export const checkIfEmailAlreadyInvited = async ( + destinationEmail: string +): Promise<{ invited: boolean; error: string | null }> => { + try { + const { data, error } = await supabase + .from('invitations') + .select('id') + .eq('destination', destinationEmail); + + return { + invited: !!data && data.length > 0, + error: error ? error.message : null, + }; + } catch (err: any) { + return { invited: false, error: err.message }; + } +}; From 428ae78c6fe12b24f83d1d1467bb13bbb77368ab Mon Sep 17 00:00:00 2001 From: Carlos Andres Perez Ubeda Date: Mon, 20 Jan 2025 20:38:20 -0600 Subject: [PATCH 5/8] feat: update verified badge color to gold - Updated the badge color in `BadgeVerified.tsx` from white (`#FFF`) to gold (`#cca421`) for better visibility and distinction. --- src/components/user-item/BadgeVerified.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/user-item/BadgeVerified.tsx b/src/components/user-item/BadgeVerified.tsx index e4e5d16b..226a0b58 100644 --- a/src/components/user-item/BadgeVerified.tsx +++ b/src/components/user-item/BadgeVerified.tsx @@ -17,7 +17,7 @@ const BadgeVerified: FC = ({ address }) => { // If the user is not verified, do not render the badge if (!isVerified) return null; - return ; + return ; }; export default BadgeVerified; From 2a7ce4c918d666a19494ab2fb5bdf4acb34be848 Mon Sep 17 00:00:00 2001 From: Carlos Andres Perez Ubeda Date: Mon, 20 Jan 2025 20:43:11 -0600 Subject: [PATCH 6/8] feat: enhance avatar styling with font and background changes - **src/sections/user/profile-header.tsx**: Added `fontSize: '3em'` to style for profile header avatar. - **src/components/avatar/avatar.tsx**: - Integrated `COLORS.GRAY_DARK` as the default background color for avatars. - Modified `alt` to render as uppercase. - Merged additional styling properties for consistency. - **src/layouts/_common/account-popover.tsx**: Added `fontSize: '1.25rem'` to avatar style for popover. --- src/components/avatar/avatar.tsx | 11 ++++++++++- src/layouts/_common/account-popover.tsx | 1 + src/sections/user/profile-header.tsx | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/avatar/avatar.tsx b/src/components/avatar/avatar.tsx index c170d54c..6009011b 100644 --- a/src/components/avatar/avatar.tsx +++ b/src/components/avatar/avatar.tsx @@ -2,6 +2,7 @@ import Avatar from '@mui/material/Avatar'; import {FC} from "react"; import {dicebear} from "@src/utils/dicebear.ts"; +import {COLORS} from "@src/layouts/config-layout.ts"; interface AvatarProfileProps { src: string; @@ -14,8 +15,16 @@ const AvatarProfile: FC = ({ src, alt, sx, ...other }) => { //Check if src is a valid URL starting with http or https; if not, use the dicebear API to generate a random avatar const imageSrc = src.startsWith('http') || src.startsWith('https') ? src : dicebear(src); + // Default styles for the Avatar component + sx = { + backgroundColor: COLORS.GRAY_DARK, + fontWeight: 'bold', + ...sx, + }; + + return ( - + ) } diff --git a/src/layouts/_common/account-popover.tsx b/src/layouts/_common/account-popover.tsx index 1568b721..2bee9b61 100644 --- a/src/layouts/_common/account-popover.tsx +++ b/src/layouts/_common/account-popover.tsx @@ -198,6 +198,7 @@ export default function AccountPopover() { } alt="avatar" sx={{ + fontSize: '1.25rem', width: 36, height: 36, border: (theme: any) => `solid 2px ${theme.palette.background.default}`, diff --git a/src/sections/user/profile-header.tsx b/src/sections/user/profile-header.tsx index b5815ef4..49b53575 100644 --- a/src/sections/user/profile-header.tsx +++ b/src/sections/user/profile-header.tsx @@ -332,6 +332,7 @@ const ProfileHeader = ({ alt={profile?.handle?.localName ?? ''} variant="rounded" sx={{ + fontSize: '3em', width: { xs: 96, md: 128 }, height: { xs: 96, md: 128 }, border: `solid 2px ${theme.palette.common.white}`, From 9c8331977a036e3a87ac587dc47972fda8486d56 Mon Sep 17 00:00:00 2001 From: Carlos Andres Perez Ubeda Date: Mon, 20 Jan 2025 21:12:08 -0600 Subject: [PATCH 7/8] fix: update typo in Sentry DSN and adjust minor details - **src/config-global.ts**: Correct typo from `SENTRY_DNS` to `SENTRY_DSN`. - **src/index.tsx**: Update Sentry configuration to reflect the corrected `dsn` key and add `tracePropagationTargets`. - **src/sections/user/profile-header.tsx**: Fix typo in share text, changing "in Watchit" to "on Watchit". These changes ensure proper functionality and correct phrasing across the application. --- src/config-global.ts | 2 +- src/index.tsx | 3 ++- src/sections/user/profile-header.tsx | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/config-global.ts b/src/config-global.ts index 90e46998..4595bf4e 100644 --- a/src/config-global.ts +++ b/src/config-global.ts @@ -31,7 +31,7 @@ export const GLOBAL_CONSTANTS = { process.env.VITE_ACCESS_MANAGER_ADDRESS || import.meta.env.VITE_ACCESS_MANAGER_ADDRESS || '', SENTRY_AUTH_TOKEN: process.env.VITE_SENTRY_AUTH_TOKEN || import.meta.env.VITE_SENTRY_AUTH_TOKEN || '', - SENTRY_DNS: process.env.VITE_SENTRY_DNS || import.meta.env.VITE_SENTRY_DNS || '', + SENTRY_DSN: process.env.VITE_SENTRY_DSN || import.meta.env.VITE_SENTRY_DSN || '', PINATA_API_KEY: process.env.VITE_PINATA_API_KEY || import.meta.env.VITE_PINATA_API_KEY || '', PINATA_SECRET_API_KEY: process.env.VITE_PINATA_SECRET_API_KEY || import.meta.env.VITE_PINATA_SECRET_API_KEY || '', diff --git a/src/index.tsx b/src/index.tsx index 1f35240d..ac5d1f00 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -19,10 +19,11 @@ const isDevelopment = GLOBAL_CONSTANTS.ENVIRONMENT === 'development'; Sentry.init({ environment: GLOBAL_CONSTANTS.ENVIRONMENT, enabled: !isDevelopment, - dsn: GLOBAL_CONSTANTS.SENTRY_DNS, + dsn: GLOBAL_CONSTANTS.SENTRY_DSN, integrations: [Sentry.browserTracingIntegration(), Sentry.replayIntegration()], // Tracing tracesSampleRate: 1.0, // Capture 100% of the transactions + tracePropagationTargets: ["localhost", /^https:\/\/app.watchit\.movie/], // Session Replay replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production. replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur. diff --git a/src/sections/user/profile-header.tsx b/src/sections/user/profile-header.tsx index 49b53575..5ef9340a 100644 --- a/src/sections/user/profile-header.tsx +++ b/src/sections/user/profile-header.tsx @@ -62,7 +62,7 @@ const shareLinks = [ { icon: 'mingcute:social-x-line', label: 'X', - url: `https://x.com/share/?url=${encodeURIComponent(urlToShare)}&text=Visit%20my%20profile%20in%20Watchit&hashtags=Watchit`, + url: `https://x.com/share/?url=${encodeURIComponent(urlToShare)}&text=Visit%20my%20profile%20on%20Watchit&hashtags=Watchit`, }, { icon: 'mdi:facebook', From f6e2c36b2bed772d8eff5fb39df8c8c066052693 Mon Sep 17 00:00:00 2001 From: Carlos Andres Perez Ubeda Date: Mon, 20 Jan 2025 21:14:55 -0600 Subject: [PATCH 8/8] feat(profile-header): update social share hashtags on X - Updated `profile-header.tsx` to include additional hashtags, "Crypto" and "Blockchain," in the share URL for X (formerly Twitter). - Helps improve post visibility and align with platform focus. --- src/sections/user/profile-header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sections/user/profile-header.tsx b/src/sections/user/profile-header.tsx index 5ef9340a..d877a4d2 100644 --- a/src/sections/user/profile-header.tsx +++ b/src/sections/user/profile-header.tsx @@ -62,7 +62,7 @@ const shareLinks = [ { icon: 'mingcute:social-x-line', label: 'X', - url: `https://x.com/share/?url=${encodeURIComponent(urlToShare)}&text=Visit%20my%20profile%20on%20Watchit&hashtags=Watchit`, + url: `https://x.com/share/?url=${encodeURIComponent(urlToShare)}&text=Visit%20my%20profile%20on%20Watchit&hashtags=Watchit,Crypto,Blockchain`, }, { icon: 'mdi:facebook',