More socials

This commit is contained in:
Ahmed Al-Taiar
2024-09-21 15:29:14 -04:00
parent 43be1abf96
commit 430a2da835
8 changed files with 176 additions and 16 deletions

View File

@ -0,0 +1,10 @@
-- AlterEnum
-- This migration adds more than one value to an enum.
-- With PostgreSQL versions 11 and earlier, this is not possible
-- in a single migration. This can be worked around by creating
-- multiple migrations, each migration adding only one value to
-- the enum.
ALTER TYPE "Handle" ADD VALUE 'gitea';
ALTER TYPE "Handle" ADD VALUE 'leetcode';

View File

@ -0,0 +1,15 @@
-- AlterEnum
-- This migration adds more than one value to an enum.
-- With PostgreSQL versions 11 and earlier, this is not possible
-- in a single migration. This can be worked around by creating
-- multiple migrations, each migration adding only one value to
-- the enum.
ALTER TYPE "Handle" ADD VALUE 'steam';
ALTER TYPE "Handle" ADD VALUE 'discord';
ALTER TYPE "Handle" ADD VALUE 'twitch';
ALTER TYPE "Handle" ADD VALUE 'forgejo';
ALTER TYPE "Handle" ADD VALUE 'gitlab';
ALTER TYPE "Handle" ADD VALUE 'bitbucket';
ALTER TYPE "Handle" ADD VALUE 'phone';

View File

@ -21,9 +21,18 @@ enum Handle {
facebook
tiktok
youtube
steam
discord
twitch
linkedin
github
gitea
forgejo
gitlab
bitbucket
leetcode
email
phone
custom
}

View File

@ -13,9 +13,18 @@ export const schema = gql`
facebook
tiktok
youtube
steam
discord
twitch
linkedin
github
gitea
forgejo
gitlab
bitbucket
leetcode
email
phone
custom
}

View File

@ -3,6 +3,7 @@ import type {
MutationResolvers,
CreateSocialInput,
UpdateSocialInput,
Handle,
} from 'types/graphql'
import { ValidationError } from '@redwoodjs/graphql-server'
@ -12,6 +13,8 @@ import { db } from 'src/lib/db'
const urlRegex =
/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i
const phoneRegex = /^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$/
const emailRegex =
/^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-]+)(\.[a-zA-Z]{2,5}){1,2}$/
@ -50,8 +53,12 @@ const validateInput = (input: CreateSocialInput | UpdateSocialInput) => {
throw new ValidationError('Name is required')
if (!input.type) throw new ValidationError('Type is required')
if (input.type === 'custom' && !urlRegex.test(input.username))
const urlHandles: Handle[] = ['custom', 'gitea', 'forgejo']
if (urlHandles.includes(input.type) && !urlRegex.test(input.username))
throw new ValidationError('Invalid URL')
else if (input.type === 'phone' && !phoneRegex.test(input.username))
throw new ValidationError('Invalid Phone Number')
else if (input.type === 'email' && !emailRegex.test(input.username))
throw new ValidationError('Invalid Email')
else if (input.username.trim().length === 0)

View File

@ -7,6 +7,14 @@ import {
SiYoutubeHex,
SiLinkedinHex,
SiGithubHex,
SiGiteaHex,
SiLeetcodeHex,
SiSteamHex,
SiDiscordHex,
SiTwitchHex,
SiForgejoHex,
SiGitlabHex,
SiBitbucketHex,
} from '@icons-pack/react-simple-icons'
const invertColor = (hex) => {
@ -36,6 +44,9 @@ export const theme = {
maxWidth: {
68: '17rem',
},
height: {
128: '32rem',
},
aspectRatio: {
portrait: '4 / 5',
},
@ -84,6 +95,19 @@ export const theme = {
light: SiYoutubeHex,
dark: SiYoutubeHex,
},
steam: {
light: SiSteamHex,
dark: invertColor(SiSteamHex),
},
discord: {
light: SiDiscordHex,
dark: SiDiscordHex,
},
twitch: {
light: SiTwitchHex,
dark: SiTwitchHex,
},
linkedin: {
light: SiLinkedinHex,
dark: SiLinkedinHex,
@ -92,6 +116,26 @@ export const theme = {
light: SiGithubHex,
dark: invertColor(SiGithubHex),
},
gitea: {
light: SiGiteaHex,
dark: SiGiteaHex,
},
forgejo: {
light: SiForgejoHex,
dark: SiForgejoHex,
},
gitlab: {
light: SiGitlabHex,
dark: SiGitlabHex,
},
bitbucket: {
light: SiBitbucketHex,
dark: SiBitbucketHex,
},
leetcode: {
light: SiLeetcodeHex,
dark: SiLeetcodeHex,
},
},
},
}

View File

@ -7,6 +7,8 @@ import {
mdiRename,
mdiAt,
mdiLinkVariant,
mdiPound,
mdiAccountPlus,
} from '@mdi/js'
import Icon from '@mdi/react'
import type { EditSocialById, UpdateSocialInput } from 'types/graphql'
@ -32,12 +34,23 @@ const types: FormSocial['type'][] = [
'facebook',
'tiktok',
'youtube',
'steam',
'discord',
'twitch',
'linkedin',
'github',
'gitea',
'forgejo',
'gitlab',
'bitbucket',
'leetcode',
'email',
'phone',
'custom',
]
const urlHandles: FormSocial['type'][] = ['custom', 'gitea', 'forgejo']
const SocialForm = (props: SocialFormProps) => {
const [type, setType] = useState<FormSocial['type']>(
props.social?.type ?? 'x'
@ -76,7 +89,7 @@ const SocialForm = (props: SocialFormProps) => {
<Form<FormSocial>
onSubmit={onSubmit}
error={props.error}
className="h-96 max-w-80 space-y-2"
className="h-128 max-w-80 space-y-2"
>
<Label name="name" className="form-control w-full">
<Label
@ -128,16 +141,28 @@ const SocialForm = (props: SocialFormProps) => {
path={
type == 'email'
? mdiAt
: type == 'custom'
? mdiLinkVariant
: mdiAccount
: type == 'phone'
? mdiPound
: urlHandles.includes(type)
? mdiLinkVariant
: type == 'discord'
? mdiAccountPlus
: mdiAccount
}
/>
</Label>
<TextField
name="username"
placeholder={
type == 'custom' ? 'URL' : type == 'email' ? 'Email' : 'Username'
urlHandles.includes(type)
? 'URL'
: type == 'phone'
? 'Phone'
: type == 'email'
? 'Email'
: type == 'discord'
? 'Invite Code'
: 'Username'
}
className="w-full"
defaultValue={username}
@ -153,10 +178,16 @@ const SocialForm = (props: SocialFormProps) => {
value:
type == 'email'
? /^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-]+)(\.[a-zA-Z]{2,5}){1,2}$/
: type == 'custom' &&
/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i,
: type == 'phone'
? /^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$/
: urlHandles.includes(type) &&
/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i,
message: `Invalid ${
type == 'custom' ? 'URL' : type == 'email' && 'Email'
urlHandles.includes(type)
? 'URL'
: type == 'phone'
? 'Phone Number'
: type == 'email' && 'Email'
}`,
},
}}
@ -167,9 +198,14 @@ const SocialForm = (props: SocialFormProps) => {
name="username"
className="text-xs font-semibold text-error"
/>
{type !== 'custom' && type !== 'email' && (
<span className="label-text-alt">{`${baseUrls[type]}${username}`}</span>
{type == 'phone' && (
<span className="label-text-alt">Format: +1 555-555-5555</span>
)}
{!urlHandles.includes(type) &&
type !== 'phone' &&
type !== 'email' && (
<span className="label-text-alt">{`${baseUrls[type]}${username}`}</span>
)}
</div>
</Label>

View File

@ -9,21 +9,38 @@ import {
SiYoutube,
SiLinkedin,
SiGithub,
SiGitea,
SiLeetcode,
SiBitbucket,
SiDiscord,
SiForgejo,
SiGitlab,
SiSteam,
SiTwitch,
} from '@icons-pack/react-simple-icons'
import { mdiEmail, mdiLink } from '@mdi/js'
import { mdiEmail, mdiLink, mdiPhone } from '@mdi/js'
import Icon from '@mdi/react'
import type { Handle } from 'types/graphql'
export const baseUrls: Record<Handle, string> = {
x: 'https://x.com/',
facebook: 'https://www.facebook.com/',
github: 'https://github.com/',
instagram: 'https://www.instagram.com/',
linkedin: 'https://www.linkedin.com/in/',
threads: 'https://www.threads.net/@',
instagram: 'https://www.instagram.com/',
facebook: 'https://www.facebook.com/',
tiktok: 'https://www.tiktok.com/@',
youtube: 'https://www.youtube.com/@',
steam: 'https://steamcommunity.com/id/',
discord: 'https://discord.gg/',
twitch: 'https://www.twitch.tv/',
linkedin: 'https://www.linkedin.com/in/',
github: 'https://github.com/',
gitea: '',
forgejo: '',
gitlab: 'https://gitlab.com/',
bitbucket: 'https://bitbucket.org/',
leetcode: 'https://leetcode.com/u/',
email: 'mailto:',
phone: 'tel:',
custom: '',
}
@ -38,11 +55,24 @@ const logoComponents: Record<Handle, ReactElement> = {
),
tiktok: <SiTiktok className="text-tiktok-light dark:text-tiktok-dark" />,
youtube: <SiYoutube className="text-youtube-light dark:text-youtube-dark" />,
steam: <SiSteam className="text-steam-light dark:text-steam-dark" />,
discord: <SiDiscord className="text-discord-light dark:text-discord-dark" />,
twitch: <SiTwitch className="text-twitch-light dark:text-twitch-dark" />,
linkedin: (
<SiLinkedin className="text-linkedin-light dark:text-linkedin-dark" />
),
github: <SiGithub className="text-github-light dark:text-github-dark" />,
gitea: <SiGitea className="text-gitea-light dark:text-gitea-dark" />,
forgejo: <SiForgejo className="text-forgejo-light dark:text-forgejo-dark" />,
gitlab: <SiGitlab className="text-gitlab-light dark:text-gitlab-dark" />,
bitbucket: (
<SiBitbucket className="text-bitbucket-light dark:text-bitbucket-dark" />
),
leetcode: (
<SiLeetcode className="text-leetcode-light dark:text-leetcode-dark" />
),
email: <Icon path={mdiEmail} className="size-7" />,
phone: <Icon path={mdiPhone} className="size-7" />,
custom: <Icon path={mdiLink} className="size-7" />,
}