Skip to content

Commit

Permalink
feat(profile/socials): update styles (#158)
Browse files Browse the repository at this point in the history
* style(profile/verified-badge): update styles

* feat(profile/socials): update styles
  • Loading branch information
chimpdev authored Jan 8, 2025
1 parent 258bfe5 commit 359831b
Show file tree
Hide file tree
Showing 18 changed files with 153 additions and 217 deletions.
92 changes: 33 additions & 59 deletions client/app/(profiles)/profile/[slug]/components/sections/Social.jsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,14 @@
'use client';

import useThemeStore from '@/stores/theme';
import Image from 'next/image';
import { IoEarth } from 'react-icons/io5';
import { MdArrowOutward } from 'react-icons/md';
import { motion } from 'framer-motion';
import getIconPath from '@/lib/utils/profiles/getIconPath';
import getDisplayableURL from '@/lib/utils/profiles/getDisplayableURL';
import { t } from '@/stores/language';
import MotionLink from '@/app/components/Motion/Link';
import getIcon from '@/lib/utils/profiles/getIcon';
import colors from '@/lib/utils/profiles/colors';

export default function Social({ data }) {
const colors = {
instagram: '225 48 108',
x: '0 0 0',
twitter: '29 161 242',
tiktok: '255 0 80',
facebook: '66 103 178',
steam: '0 0 0',
github: '110 84 148',
twitch: '145 70 255',
youtube: '255 0 0',
telegram: '36 161 222',
custom: '150 150 150',
unknown: '0 0 0'
};

const theme = useThemeStore(state => state.theme);

return (
<div className='flex w-full flex-col'>
<motion.h2
Expand All @@ -51,47 +32,40 @@ export default function Social({ data }) {
</p>
)}

{data.map((social, index) => (
<MotionLink
className='group flex h-10 w-full items-center justify-between gap-x-2 rounded-lg border-2 border-[rgb(var(--brand-color)/0.5)] bg-gradient-to-r from-[rgb(var(--brand-color)/0.2)] px-2 text-sm font-semibold text-secondary hover:border-[rgb(var(--brand-color)/0.8)] hover:bg-secondary hover:from-[rgb(var(--brand-color)/0.3)]'
key={social.link}
style={{
'--brand-color': colors[social.type]
}}
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, type: 'spring', stiffness: 100, damping: 10, delay: 0.40 + (.20 * index) }}
href={social.link}
target='_blank'
>
<div className='flex max-w-[80%] flex-auto gap-x-2 sm:max-w-[90%]'>
{social.type === 'custom' ? (
<>
<IoEarth className='flex-auto text-primary' size={20} />
{data.map((social, index) => {
const SocialIcon = getIcon(social.type);

<span className='w-full truncate'>
{getDisplayableURL(social.link)}
</span>
</>
) : (
<>
<Image
src={getIconPath(social.type, theme)}
width={20}
height={20}
alt={`${social.type} Icon`}
/>
return (
<MotionLink
className='group flex items-center justify-between gap-x-1 rounded-2xl border border-primary bg-secondary px-2 py-3 transition-colors hover:bg-tertiary'
key={social.link}
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, type: 'spring', stiffness: 100, damping: 10, delay: 0.20 + (.20 * index) }}
href={social.link}
target='_blank'
style={{
'--brand-color': colors[social.type]
}}
>
<div className='flex items-center gap-x-2 text-sm text-tertiary transition-colors group-hover:text-primary'>
<SocialIcon
className='text-tertiary transition-colors group-hover:text-[rgb(var(--brand-color))]'
size={20}
/>

<span className='w-full truncate'>
{social.handle}
</span>
</>
)}
</div>
<span className='font-semibold'>
{social.type === 'custom' ? getDisplayableURL(social.link) : social.handle}
</span>
</div>

<MdArrowOutward className='text-[rgba(var(--brand-color))]' size={18} />
</MotionLink>
))}
<MdArrowOutward
className='text-tertiary opacity-0 transition-opacity group-hover:opacity-100'
size={18}
/>
</MotionLink>
);
})}
</motion.div>
</div>
);
Expand Down
162 changes: 71 additions & 91 deletions client/app/(profiles)/profile/[slug]/edit/components/Socials.jsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,24 @@
'use client';

import { IoEarth } from 'react-icons/io5';
import { FaQuestion } from 'react-icons/fa6';
import { nanoid } from 'nanoid';
import Image from 'next/image';
import Link from 'next/link';
import { MdArrowOutward } from 'react-icons/md';
import { useState, useEffect } from 'react';
import useThemeStore from '@/stores/theme';
import addSocial from '@/lib/request/profiles/addSocial';
import deleteSocial from '@/lib/request/profiles/deleteSocial';
import { toast } from 'sonner';
import { FiX } from 'react-icons/fi';
import cn from '@/lib/cn';
import config from '@/config';
import getDisplayableURL from '@/lib/utils/profiles/getDisplayableURL';
import getIconPath from '@/lib/utils/profiles/getIconPath';
import revalidateProfile from '@/lib/revalidate/profile';
import { t } from '@/stores/language';
import colors from '@/lib/utils/profiles/colors';
import getIcon from '@/lib/utils/profiles/getIcon';
import { TbLoader } from 'react-icons/tb';

export default function Socials({ profile }) {
const [socials, setSocials] = useState(profile.socials);

const colors = {
instagram: '225 48 108',
x: '0 0 0',
twitter: '29 161 242',
tiktok: '255 0 80',
facebook: '66 103 178',
steam: '0 0 0',
github: '110 84 148',
twitch: '145 70 255',
youtube: '255 0 0',
telegram: '36 161 222',
custom: '150 150 150',
unknown: '0 0 0'
};

const typeRegexps = {
instagram: /(?:http(?:s)?:\/\/)?(?:www\.)?instagram\.com\/([\w](?!.*?\.{2})[\w.]{1,28}[\w])/,
x: /(?:http(?:s)?:\/\/)?(?:www\.)?x\.com\/([a-zA-Z0-9_]+)/,
Expand All @@ -51,8 +33,6 @@ export default function Socials({ profile }) {
custom: /\b(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[^\s]*)?\b/
};

const theme = useThemeStore(state => state.theme);

const [currentlyAddingNewSocial, setCurrentlyAddingNewSocial] = useState(false);
const [newSocialType, setNewSocialType] = useState('unknown');
const [newSocialValue, setNewSocialValue] = useState('');
Expand Down Expand Up @@ -130,8 +110,11 @@ export default function Socials({ profile }) {
}
}

const [deletingSocialId, setDeletingSocialId] = useState(null);

function deleteSelectedSocial(id) {
setLoading(true);
setDeletingSocialId(id);

toast.promise(deleteSocial(profile.slug, id),
{
Expand All @@ -141,13 +124,15 @@ export default function Socials({ profile }) {
setNewSocialType('unknown');
setNewSocialValue('');
setLoading(false);
setDeletingSocialId(null);
setSocials(newSocials);
revalidateProfile(profile.slug);

return t('editProfilePage.toast.socialDeleted');
},
error: message => {
setLoading(false);
setDeletingSocialId(null);

return message;
}
Expand All @@ -166,78 +151,73 @@ export default function Socials({ profile }) {
</p>

<div className='mt-4 flex flex-wrap gap-4'>
{socials.map(social => (
<div
className='flex h-10 w-full max-w-[calc(50%_-_1rem)] items-center justify-between gap-x-2 rounded-lg border-2 border-[rgb(var(--brand-color)/0.5)] bg-gradient-to-r from-[rgb(var(--brand-color)/0.2)] px-2 text-sm font-semibold text-secondary'
key={nanoid()}
style={{
'--brand-color': colors[social.type]
}}
>
<div className='flex max-w-[82%] flex-auto gap-x-2'>
{social.type === 'custom' ? (
<>
<IoEarth className='flex-auto' size={20} />
<span className='w-full truncate'>
{getDisplayableURL(social.link)}
</span>
</>
) : (
<>
<Image
src={getIconPath(social.type, theme)}
width={20}
height={20}
alt={`${social.type} Icon`}
/>

<span className='w-full truncate'>
{social.handle}
</span>
</>
)}
{socials.map(social => {
const SocialIcon = getIcon(social.type);

return (
<div
className='group flex w-full max-w-[calc(50%_-_1rem)] items-center justify-between gap-x-1 rounded-2xl border border-primary bg-secondary px-2 py-3 transition-colors hover:bg-tertiary'
key={social.link}
>
<div className='flex items-center gap-x-2 text-sm text-tertiary transition-colors group-hover:text-primary'>
<SocialIcon
className='text-tertiary group-hover:text-[rgb(var(--brand-color))]'
size={20}
/>

<span className='select-none font-semibold'>
{social.type === 'custom' ? getDisplayableURL(social.link) : social.handle}
</span>
</div>

<div className='flex items-center gap-x-1'>
<Link
className='text-tertiary opacity-0 transition-all hover:text-primary group-hover:opacity-100'
href={social.link}
target='_blank'
>
<MdArrowOutward size={18} />
</Link>

<button
className='text-tertiary transition-all hover:text-primary disabled:pointer-events-none disabled:opacity-70'
onClick={() => deleteSelectedSocial(social._id)}
disabled={loading}
>
{(loading && deletingSocialId === social._id) ? (
<TbLoader
className='animate-spin'
size={18}
/>
) : (
<FiX size={18} />
)}
</button>
</div>
</div>

<div className='flex gap-x-1'>
<button className='text-tertiary hover:text-primary disabled:pointer-events-none disabled:opacity-70' onClick={() => deleteSelectedSocial(social._id)} disabled={loading}>
<FiX size={18} />
</button>

<Link className='text-tertiary hover:text-primary' href={social.link} target='_blank'>
<MdArrowOutward size={18} />
</Link>
</div>
</div>
))}
);
})}

<div
className={cn(
'transition-all w-full max-w-[calc(50%_-_1rem)] h-10 rounded-lg px-2 text-sm font-semibold bg-[rgb(var(--brand-color))]/10 items-center justify-between gap-x-2 text-secondary',
'transition-all [&:has(input:focus)]:bg-tertiary border border-primary w-full max-w-[calc(50%_-_1rem)] rounded-2xl px-2 py-3 text-sm font-semibold bg-secondary items-center justify-between gap-x-2',
currentlyAddingNewSocial ? 'flex' : 'hidden'
)}
style={{
'--brand-color': colors[newSocialType]
}}
>
<div className='flex w-full items-center gap-x-2'>
{newSocialType === 'unknown' ? (
<FaQuestion
className='flex-auto'
size={20}
/>
) : newSocialType === 'custom' ? (
<IoEarth
className='flex-auto'
size={20}
/>
) : (
<Image
src={getIconPath(newSocialType, theme)}
width={20}
height={20}
alt={`${newSocialType} Icon`}
/>
)}
{(() => {
const Icon = getIcon(newSocialType);

return (
<Icon
style={{
color: `rgba(${colors[newSocialType]})`
}}
className='text-tertiary transition-colors'
size={20}
/>
);
})()}

<input
type='text'
Expand All @@ -248,7 +228,7 @@ export default function Socials({ profile }) {
autoComplete='off'
spellCheck='false'
disabled={loading}
className='w-full bg-transparent font-medium text-secondary outline-none placeholder:text-placeholder disabled:pointer-events-none disabled:opacity-70'
className='w-full bg-transparent font-medium text-secondary outline-none disabled:pointer-events-none disabled:opacity-70'
/>
</div>
</div>
Expand All @@ -260,7 +240,7 @@ export default function Socials({ profile }) {
)}
>
<button
className='flex h-10 w-full max-w-[calc(50%_-_1rem)] items-center justify-center gap-x-2 rounded-lg bg-tertiary text-sm font-semibold text-secondary hover:bg-quaternary hover:text-primary disabled:pointer-events-none disabled:opacity-70' onClick={() => {
className='flex w-full max-w-[calc(50%_-_1rem)] items-center justify-center gap-x-2 rounded-2xl bg-tertiary py-3 text-sm font-semibold text-secondary hover:bg-quaternary hover:text-primary disabled:pointer-events-none disabled:opacity-70' onClick={() => {
setCurrentlyAddingNewSocial(false);
setNewSocialType('unknown');
setNewSocialValue('');
Expand All @@ -270,15 +250,15 @@ export default function Socials({ profile }) {
{t('buttons.cancel')}
</button>

<button className='flex h-10 w-full max-w-[calc(50%_-_1rem)] items-center justify-center gap-x-2 rounded-lg bg-tertiary text-sm font-semibold text-secondary hover:bg-quaternary hover:text-primary disabled:pointer-events-none disabled:opacity-70' onClick={saveNewSocial} disabled={loading}>
<button className='flex w-full max-w-[calc(50%_-_1rem)] items-center justify-center gap-x-2 rounded-2xl bg-tertiary py-3 text-sm font-semibold text-secondary hover:bg-quaternary hover:text-primary disabled:pointer-events-none disabled:opacity-70' onClick={saveNewSocial} disabled={loading}>
{t('buttons.add')}
</button>
</div>

{socials.length < config.profilesMaxSocialsLength && (
<button
className={cn(
'flex w-full max-w-[calc(50%_-_1rem)] h-10 rounded-lg justify-center text-sm font-semibold border-primary border hover:bg-tertiary hover:border-[rgb(var(--bg-tertiary))] items-center gap-x-2 text-secondary hover:text-primary disabled:pointer-events-none disabled:opacity-70',
'flex w-full py-3 max-w-[calc(50%_-_1rem)] rounded-2xl justify-center text-sm font-semibold border-primary border hover:bg-tertiary hover:border-[rgb(var(--bg-tertiary))] items-center gap-x-2 text-secondary hover:text-primary disabled:pointer-events-none disabled:opacity-70',
currentlyAddingNewSocial && 'hidden'
)}
onClick={() => setCurrentlyAddingNewSocial(true)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default function VerifiedBadge() {
const theme = useThemeStore(state => state.theme);

return (
<div className='flex select-none items-center gap-x-1 rounded-full border-black text-sm font-semibold dark:border-white sm:border-2 sm:bg-black/10 sm:px-3 sm:py-0.5 sm:dark:bg-white/20'>
<div className='flex select-none items-center gap-x-1 rounded-full text-sm font-semibold sm:bg-black/10 sm:px-3 sm:py-0.5 sm:dark:bg-white/10'>
<Image
src={`/profile-badges/${theme === 'dark' ? 'white' : 'black'}_verified.svg`}
width={16}
Expand Down
16 changes: 16 additions & 0 deletions client/lib/utils/profiles/colors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const colors = {
instagram: '225 48 108',
x: 'var(--text-tertiary)',
twitter: '29 161 242',
tiktok: '255 0 80',
facebook: '66 103 178',
steam: 'var(--text-tertiary)',
github: '110 84 148',
twitch: '145 70 255',
youtube: '255 0 0',
telegram: '36 161 222',
custom: '150 150 150',
unknown: 'var(--text-tertiary)'
};

export default colors;
Loading

0 comments on commit 359831b

Please sign in to comment.