mirror of
https://github.com/ershisan99/www.git
synced 2025-12-17 12:34:17 +00:00
add social links support
This commit is contained in:
@@ -1,11 +1,5 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from '@/components/ui/dropdown-menu'
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -32,6 +26,7 @@ import {
|
|||||||
} from '@/components/ui/select'
|
} from '@/components/ui/select'
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { auth } from '@/server/auth'
|
||||||
import { RANKED_CHANNEL, VANILLA_CHANNEL } from '@/shared/constants'
|
import { RANKED_CHANNEL, VANILLA_CHANNEL } from '@/shared/constants'
|
||||||
import { api } from '@/trpc/react'
|
import { api } from '@/trpc/react'
|
||||||
import {
|
import {
|
||||||
@@ -40,15 +35,16 @@ import {
|
|||||||
BarChart3,
|
BarChart3,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
ChevronUp,
|
ChevronUp,
|
||||||
EllipsisVertical,
|
|
||||||
Filter,
|
Filter,
|
||||||
IceCreamCone,
|
IceCreamCone,
|
||||||
ShieldHalf,
|
ShieldHalf,
|
||||||
Star,
|
Star,
|
||||||
Trophy,
|
Trophy,
|
||||||
|
Twitch,
|
||||||
UserIcon,
|
UserIcon,
|
||||||
|
Youtube,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { ExternalIcon } from 'next/dist/client/components/react-dev-overlay/ui/icons/external'
|
import { useSession } from 'next-auth/react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { useParams } from 'next/navigation'
|
import { useParams } from 'next/navigation'
|
||||||
import { isNonNullish } from 'remeda'
|
import { isNonNullish } from 'remeda'
|
||||||
@@ -206,20 +202,6 @@ export function UserInfo() {
|
|||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
{/*<DropdownMenu>*/}
|
|
||||||
{/* <DropdownMenuTrigger asChild>*/}
|
|
||||||
{/* <Button variant={'ghost'} size={'iconSm'}>*/}
|
|
||||||
{/* <EllipsisVertical className={'size-4'} />*/}
|
|
||||||
{/* </Button>*/}
|
|
||||||
{/* </DropdownMenuTrigger>*/}
|
|
||||||
{/* <DropdownMenuContent>*/}
|
|
||||||
{/* <DropdownMenuItem asChild>*/}
|
|
||||||
{/* <Link href={`/stream-card/${id}`} target={'_blank'}>*/}
|
|
||||||
{/* Stream widget <ExternalIcon />*/}
|
|
||||||
{/* </Link>*/}
|
|
||||||
{/* </DropdownMenuItem>*/}
|
|
||||||
{/* </DropdownMenuContent>*/}
|
|
||||||
{/*</DropdownMenu>*/}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className='pt-2 text-gray-500 text-sm dark:text-zinc-400'>
|
<p className='pt-2 text-gray-500 text-sm dark:text-zinc-400'>
|
||||||
@@ -258,6 +240,38 @@ export function UserInfo() {
|
|||||||
</span>
|
</span>
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
|
{discord_user.twitch_url && (
|
||||||
|
<Badge
|
||||||
|
variant='outline'
|
||||||
|
className='border-gray-200 bg-gray-50 dark:border-zinc-700 dark:bg-zinc-800'
|
||||||
|
>
|
||||||
|
<Twitch className='mr-1 h-3 w-3 text-purple-500' />
|
||||||
|
<a
|
||||||
|
href={discord_user.twitch_url}
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
className='text-gray-700 hover:underline dark:text-zinc-300'
|
||||||
|
>
|
||||||
|
Twitch
|
||||||
|
</a>
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{discord_user.youtube_url && (
|
||||||
|
<Badge
|
||||||
|
variant='outline'
|
||||||
|
className='border-gray-200 bg-gray-50 dark:border-zinc-700 dark:bg-zinc-800'
|
||||||
|
>
|
||||||
|
<Youtube className='mr-1 h-3 w-3 text-red-500' />
|
||||||
|
<a
|
||||||
|
href={discord_user.youtube_url}
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
className='text-gray-700 hover:underline dark:text-zinc-300'
|
||||||
|
>
|
||||||
|
YouTube
|
||||||
|
</a>
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
116
src/app/(home)/profile/settings/page-client.tsx
Normal file
116
src/app/(home)/profile/settings/page-client.tsx
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
import { api } from '@/trpc/react'
|
||||||
|
import { SiTwitch, SiYoutube } from '@icons-pack/react-simple-icons'
|
||||||
|
import { ExternalLink } from 'lucide-react'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { toast } from 'sonner'
|
||||||
|
|
||||||
|
export function ProfileSettingsPageClient({
|
||||||
|
userId,
|
||||||
|
}: { userId: string | null }) {
|
||||||
|
const [socialLinks] = api.profile.getSocialLinks.useSuspenseQuery()
|
||||||
|
const [twitchUrl, setTwitchUrl] = useState<string | null>(
|
||||||
|
socialLinks.twitch_url
|
||||||
|
)
|
||||||
|
const [youtubeUrl, setYoutubeUrl] = useState<string | null>(
|
||||||
|
socialLinks.youtube_url
|
||||||
|
)
|
||||||
|
|
||||||
|
const { mutate: updateSocialLinks, isPending } =
|
||||||
|
api.profile.updateSocialLinks.useMutation({
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success('Social links updated successfully')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
updateSocialLinks({
|
||||||
|
twitch_url: twitchUrl,
|
||||||
|
youtube_url: youtubeUrl,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='mx-auto flex w-[calc(100%-1rem)] max-w-fd-container flex-1 flex-col py-8'>
|
||||||
|
<div className='mb-6'>
|
||||||
|
<h1 className='font-bold text-3xl text-gray-900 dark:text-white'>
|
||||||
|
Profile Settings
|
||||||
|
</h1>
|
||||||
|
<p className='mt-2 text-gray-500 dark:text-zinc-400'>
|
||||||
|
Manage your profile settings and social links
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='space-y-6'>
|
||||||
|
<div className='rounded-lg border bg-white p-6 dark:bg-zinc-800/20'>
|
||||||
|
<h2 className='mb-4 font-semibold text-xl'>Stream Widget</h2>
|
||||||
|
<p className='mb-4 text-gray-500 dark:text-zinc-400'>
|
||||||
|
Use this widget to display your stats on your stream
|
||||||
|
</p>
|
||||||
|
{userId && (
|
||||||
|
<Link
|
||||||
|
href={`/stream-card/${userId}`}
|
||||||
|
target='_blank'
|
||||||
|
className='inline-flex items-center gap-2 text-violet-600 hover:text-violet-700 dark:text-violet-400 dark:hover:text-violet-300'
|
||||||
|
>
|
||||||
|
Open Stream Widget <ExternalLink className='h-4 w-4' />
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
handleSave()
|
||||||
|
}}
|
||||||
|
className='rounded-lg border bg-white p-6 dark:bg-zinc-800/20'
|
||||||
|
>
|
||||||
|
<h2 className='mb-4 font-semibold text-xl'>Social Links</h2>
|
||||||
|
<div className='space-y-4'>
|
||||||
|
<div>
|
||||||
|
<Label className='mb-2' htmlFor={'ttv-url'}>
|
||||||
|
Twitch URL
|
||||||
|
</Label>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<SiTwitch className='h-5 w-5 text-purple-500' />
|
||||||
|
<Input
|
||||||
|
id={'ttv-url'}
|
||||||
|
type='url'
|
||||||
|
value={twitchUrl || ''}
|
||||||
|
onChange={(e) => setTwitchUrl(e.target.value || null)}
|
||||||
|
placeholder='https://twitch.tv/yourusername'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label className='mb-2' htmlFor={'yt-url'}>
|
||||||
|
YouTube URL
|
||||||
|
</Label>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<SiYoutube className='h-5 w-5 text-red-500' />
|
||||||
|
<Input
|
||||||
|
id={'yt-url'}
|
||||||
|
type='url'
|
||||||
|
value={youtubeUrl || ''}
|
||||||
|
onChange={(e) => setYoutubeUrl(e.target.value || null)}
|
||||||
|
placeholder='https://youtube.com/c/yourchannel'
|
||||||
|
className='w-full rounded-md border border-gray-300 p-2 dark:border-zinc-700 dark:bg-zinc-900'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='mt-6 flex justify-end gap-2'>
|
||||||
|
<Button disabled={isPending} type={'submit'}>
|
||||||
|
{isPending ? 'Saving...' : 'Save Changes'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ import { ThemeToggle } from 'fumadocs-ui/components/layout/theme-toggle'
|
|||||||
import type { HomeLayoutProps } from 'fumadocs-ui/layouts/home'
|
import type { HomeLayoutProps } from 'fumadocs-ui/layouts/home'
|
||||||
import type { LinkItemType } from 'fumadocs-ui/layouts/links'
|
import type { LinkItemType } from 'fumadocs-ui/layouts/links'
|
||||||
import { replaceOrDefault } from 'fumadocs-ui/layouts/shared'
|
import { replaceOrDefault } from 'fumadocs-ui/layouts/shared'
|
||||||
import { LogIn, LogOut, Tv, User } from 'lucide-react'
|
import { LogIn, LogOut, Settings, Tv, User } from 'lucide-react'
|
||||||
import { signIn, signOut, useSession } from 'next-auth/react'
|
import { signIn, signOut, useSession } from 'next-auth/react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { Fragment } from 'react'
|
import { Fragment } from 'react'
|
||||||
@@ -106,6 +106,15 @@ export function Header({
|
|||||||
<span>Profile</span>
|
<span>Profile</span>
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem asChild>
|
||||||
|
<Link
|
||||||
|
href='/profile/settings'
|
||||||
|
className='flex w-full items-center'
|
||||||
|
>
|
||||||
|
<Settings className='mr-2 h-4 w-4' />
|
||||||
|
<span>Settings</span>
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem asChild>
|
<DropdownMenuItem asChild>
|
||||||
<Link
|
<Link
|
||||||
href={`/stream-card/${session.user.discord_id}`}
|
href={`/stream-card/${session.user.discord_id}`}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { discord_router } from '@/server/api/routers/discord'
|
|||||||
import { history_router } from '@/server/api/routers/history'
|
import { history_router } from '@/server/api/routers/history'
|
||||||
import { leaderboard_router } from '@/server/api/routers/leaderboard'
|
import { leaderboard_router } from '@/server/api/routers/leaderboard'
|
||||||
import { playerStateRouter } from '@/server/api/routers/player-state'
|
import { playerStateRouter } from '@/server/api/routers/player-state'
|
||||||
|
import { profileRouter } from '@/server/api/routers/profile'
|
||||||
import { releasesRouter } from '@/server/api/routers/releases'
|
import { releasesRouter } from '@/server/api/routers/releases'
|
||||||
import { createCallerFactory, createTRPCRouter } from '@/server/api/trpc'
|
import { createCallerFactory, createTRPCRouter } from '@/server/api/trpc'
|
||||||
|
|
||||||
@@ -17,6 +18,7 @@ export const appRouter = createTRPCRouter({
|
|||||||
discord: discord_router,
|
discord: discord_router,
|
||||||
leaderboard: leaderboard_router,
|
leaderboard: leaderboard_router,
|
||||||
playerState: playerStateRouter,
|
playerState: playerStateRouter,
|
||||||
|
profile: profileRouter,
|
||||||
releases: releasesRouter,
|
releases: releasesRouter,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { createTRPCRouter, publicProcedure } from '@/server/api/trpc'
|
import { createTRPCRouter, publicProcedure } from '@/server/api/trpc'
|
||||||
|
import { db } from '@/server/db'
|
||||||
|
import { users } from '@/server/db/schema'
|
||||||
import { discord_service } from '@/server/services/discord.service'
|
import { discord_service } from '@/server/services/discord.service'
|
||||||
|
import { eq } from 'drizzle-orm'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
export const discord_router = createTRPCRouter({
|
export const discord_router = createTRPCRouter({
|
||||||
@@ -10,6 +13,21 @@ export const discord_router = createTRPCRouter({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
return await discord_service.get_user_by_id(input.user_id)
|
const discordUser = await discord_service.get_user_by_id(input.user_id)
|
||||||
|
|
||||||
|
// Get social media links from the database
|
||||||
|
const userData = await db.query.users.findFirst({
|
||||||
|
where: eq(users.discord_id, input.user_id),
|
||||||
|
columns: {
|
||||||
|
twitch_url: true,
|
||||||
|
youtube_url: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
...discordUser,
|
||||||
|
twitch_url: userData?.twitch_url || null,
|
||||||
|
youtube_url: userData?.youtube_url || null,
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ export const users = pgTable('user', (d) => ({
|
|||||||
.default(sql`CURRENT_TIMESTAMP`),
|
.default(sql`CURRENT_TIMESTAMP`),
|
||||||
image: d.varchar({ length: 255 }),
|
image: d.varchar({ length: 255 }),
|
||||||
discord_id: d.varchar({ length: 255 }),
|
discord_id: d.varchar({ length: 255 }),
|
||||||
|
twitch_url: d.varchar({ length: 255 }),
|
||||||
|
youtube_url: d.varchar({ length: 255 }),
|
||||||
role: d.varchar({ length: 255 }).notNull().default('user'),
|
role: d.varchar({ length: 255 }).notNull().default('user'),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user