diff --git a/api/db/migrations/20240921181721_more_socials/migration.sql b/api/db/migrations/20240921181721_more_socials/migration.sql new file mode 100644 index 0000000..ea302ab --- /dev/null +++ b/api/db/migrations/20240921181721_more_socials/migration.sql @@ -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'; diff --git a/api/db/migrations/20240921183021_even_more_socials/migration.sql b/api/db/migrations/20240921183021_even_more_socials/migration.sql new file mode 100644 index 0000000..4482ef9 --- /dev/null +++ b/api/db/migrations/20240921183021_even_more_socials/migration.sql @@ -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'; diff --git a/api/db/schema.prisma b/api/db/schema.prisma index be66c69..78caa60 100644 --- a/api/db/schema.prisma +++ b/api/db/schema.prisma @@ -21,9 +21,18 @@ enum Handle { facebook tiktok youtube + steam + discord + twitch linkedin github + gitea + forgejo + gitlab + bitbucket + leetcode email + phone custom } diff --git a/api/src/graphql/socials.sdl.ts b/api/src/graphql/socials.sdl.ts index 3d7c8c5..ab3491e 100644 --- a/api/src/graphql/socials.sdl.ts +++ b/api/src/graphql/socials.sdl.ts @@ -13,9 +13,18 @@ export const schema = gql` facebook tiktok youtube + steam + discord + twitch linkedin github + gitea + forgejo + gitlab + bitbucket + leetcode email + phone custom } diff --git a/api/src/services/socials/socials.ts b/api/src/services/socials/socials.ts index 4fc9162..887b9bb 100644 --- a/api/src/services/socials/socials.ts +++ b/api/src/services/socials/socials.ts @@ -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) diff --git a/web/config/tailwind.config.js b/web/config/tailwind.config.js index 7c23ecd..492d82e 100644 --- a/web/config/tailwind.config.js +++ b/web/config/tailwind.config.js @@ -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, + }, }, }, } diff --git a/web/src/components/Social/SocialForm/SocialForm.tsx b/web/src/components/Social/SocialForm/SocialForm.tsx index b9029e4..6453fd4 100644 --- a/web/src/components/Social/SocialForm/SocialForm.tsx +++ b/web/src/components/Social/SocialForm/SocialForm.tsx @@ -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( props.social?.type ?? 'x' @@ -76,7 +89,7 @@ const SocialForm = (props: SocialFormProps) => { onSubmit={onSubmit} error={props.error} - className="h-96 max-w-80 space-y-2" + className="h-128 max-w-80 space-y-2" > diff --git a/web/src/lib/handle.tsx b/web/src/lib/handle.tsx index 16bae31..cae0188 100644 --- a/web/src/lib/handle.tsx +++ b/web/src/lib/handle.tsx @@ -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 = { 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 = { ), tiktok: , youtube: , + steam: , + discord: , + twitch: , linkedin: ( ), github: , + gitea: , + forgejo: , + gitlab: , + bitbucket: ( + + ), + leetcode: ( + + ), email: , + phone: , custom: , }