mirror of
https://github.com/supabase/supabase.git
synced 2026-10-17 07:09:07 +03:00
refactor ticket layout and positioning logic
This commit is contained in:
1 parent
8dc3645ec7
commit
f4fc9232de
3 files changed
+76
-69
No files matched your search
@@ -9,45 +9,50 @@ import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js'
|
||||
import { useParams } from 'common'
|
||||
import { cn } from 'ui'
|
||||
|
||||
const ThreeCanvas: React.FC<{ username: string; className?: string }> = ({
|
||||
username = 'Francesco Sansalvadore',
|
||||
className,
|
||||
}) => {
|
||||
const ThreeCanvas: React.FC<{
|
||||
username: string
|
||||
className?: string
|
||||
ticketPosition?: 'left' | 'right'
|
||||
}> = ({ username = 'Francesco Sansalvadore', className, ticketPosition = 'right' }) => {
|
||||
const params = useParams()
|
||||
const canvasRef = useRef<HTMLDivElement>(null)
|
||||
const ticketRef = useRef<THREE.Mesh | null>(null)
|
||||
const orbitRef = useRef<THREE.Object3D | null>(null)
|
||||
const animationFrameRef = useRef<number>()
|
||||
const targetRotation = useRef<{ x: number; y: number }>({ x: 0, y: 0 })
|
||||
const textLines = params?.username?.split(' ').reverse() || username?.split(' ').reverse() || []
|
||||
const LINE_HEIGHT = 1.4
|
||||
const MIN_CANVAS_HEIGHT = 600
|
||||
const positionRight = ticketPosition === 'right'
|
||||
|
||||
useEffect(() => {
|
||||
if (!canvasRef.current) return
|
||||
|
||||
const isDesktop = () => window.innerWidth > 1024
|
||||
const desktopWidth = () => (window.innerWidth / 2) * 3 - 140
|
||||
const canvasWidth = isDesktop() ? desktopWidth() : window.innerWidth
|
||||
const canvasHeight = window.innerHeight - 65
|
||||
const isDesktop = (width: number) => width > 1024
|
||||
const calculateDesktopWidth = () => window.innerWidth
|
||||
const initialCanvasWidth = window.innerWidth
|
||||
const initialCanvasHeight =
|
||||
window.innerHeight < MIN_CANVAS_HEIGHT ? MIN_CANVAS_HEIGHT - 65 : window.innerHeight - 65
|
||||
const getTicketScale = (width: number) => (isDesktop(width) ? 0.35 : 0.3)
|
||||
const getTicketXPosition = (width: number, isRight: boolean) =>
|
||||
isDesktop(width) ? (isRight ? 5 : -5) : 0
|
||||
const ticketYIdleRotation = isDesktop(window.innerWidth) ? 0 : 0
|
||||
|
||||
// Initialize scene, camera, and renderer
|
||||
const scene = new THREE.Scene()
|
||||
const camera = new THREE.PerspectiveCamera(75, canvasWidth / canvasHeight, 0.1, 1000)
|
||||
const camera = new THREE.PerspectiveCamera(
|
||||
25,
|
||||
initialCanvasWidth / initialCanvasHeight,
|
||||
0.1,
|
||||
1000
|
||||
)
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
|
||||
renderer.setSize(canvasWidth, canvasHeight)
|
||||
renderer.setSize(initialCanvasWidth, initialCanvasHeight)
|
||||
renderer.shadowMap.enabled = true
|
||||
canvasRef.current.appendChild(renderer.domElement)
|
||||
|
||||
// Create orbit object for camera rotation
|
||||
const orbit = new THREE.Object3D()
|
||||
orbit.rotation.order = 'YXZ'
|
||||
scene.add(orbit)
|
||||
orbitRef.current = orbit
|
||||
|
||||
// Camera setup
|
||||
const cameraDistance = 30
|
||||
const cameraDistance = 10
|
||||
camera.position.z = cameraDistance
|
||||
orbit.add(camera)
|
||||
|
||||
// Rest of your existing setup code (loader, materials, etc.)
|
||||
const loader = new GLTFLoader()
|
||||
@@ -70,6 +75,9 @@ const ThreeCanvas: React.FC<{ username: string; className?: string }> = ({
|
||||
|
||||
let ticket3DImport: THREE.Mesh
|
||||
const ticketGroup = new THREE.Group()
|
||||
ticketGroup.position.x = getTicketXPosition(window.innerWidth, positionRight)
|
||||
const ticketScale = getTicketScale(window.innerWidth)
|
||||
ticketGroup.scale.set(ticketScale, ticketScale, ticketScale)
|
||||
|
||||
loader.load('/images/launchweek/13/ticket/3D-ticket.glb', (gltf) => {
|
||||
ticket3DImport = gltf.scene.children[0] as THREE.Mesh
|
||||
@@ -140,11 +148,12 @@ const ThreeCanvas: React.FC<{ username: string; className?: string }> = ({
|
||||
textMesh.castShadow = true
|
||||
ticketGroup.add(textMesh)
|
||||
})
|
||||
|
||||
camera.position.x = 0
|
||||
})
|
||||
})
|
||||
|
||||
camera.position.z = 30
|
||||
ticketGroup.scale.set(1.1, 1.1, 1.1)
|
||||
|
||||
const getTicketScreenPosition = () => {
|
||||
if (!ticketRef.current) return null
|
||||
@@ -153,22 +162,19 @@ const ThreeCanvas: React.FC<{ username: string; className?: string }> = ({
|
||||
ticketRef.current.getWorldPosition(vector)
|
||||
vector.project(camera)
|
||||
|
||||
const x = (vector.x * 0.5 + 0.5) * canvasWidth
|
||||
const y = -(vector.y * 0.5 - 0.5) * canvasHeight
|
||||
const x = (vector.x * 0.5 + 0.5) * initialCanvasWidth
|
||||
const y = -(vector.y * 0.5 - 0.5) * initialCanvasHeight
|
||||
|
||||
return { x, y }
|
||||
}
|
||||
|
||||
// Animation function with smooth transitions
|
||||
const animate = () => {
|
||||
if (orbitRef.current) {
|
||||
// Smooth interpolation
|
||||
orbitRef.current.rotation.x +=
|
||||
(targetRotation.current.x - orbitRef.current.rotation.x) * 0.1
|
||||
orbitRef.current.rotation.y +=
|
||||
(targetRotation.current.y - orbitRef.current.rotation.y) * 0.1
|
||||
orbitRef.current.rotation.z = 0
|
||||
}
|
||||
// Smooth interpolation
|
||||
ticketGroup.rotation.x += (targetRotation.current.x - ticketGroup.rotation.x) * 0.1
|
||||
ticketGroup.rotation.y +=
|
||||
ticketYIdleRotation + (targetRotation.current.y - ticketGroup.rotation.y) * 0.1
|
||||
ticketGroup.rotation.z = 0
|
||||
|
||||
renderer.render(scene, camera)
|
||||
animationFrameRef.current = requestAnimationFrame(animate)
|
||||
@@ -179,7 +185,7 @@ const ThreeCanvas: React.FC<{ username: string; className?: string }> = ({
|
||||
|
||||
// Mouse movement handler with distance-based sensitivity
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (!canvasRef.current || !orbitRef.current) return
|
||||
if (!canvasRef.current) return
|
||||
|
||||
// Get canvas bounds
|
||||
const canvasRect = canvasRef.current.getBoundingClientRect()
|
||||
@@ -215,10 +221,18 @@ const ThreeCanvas: React.FC<{ username: string; className?: string }> = ({
|
||||
|
||||
// Handle window resize
|
||||
const handleResize = () => {
|
||||
const newWidth = isDesktop() ? desktopWidth() : window.innerWidth
|
||||
camera.aspect = newWidth / (window.innerHeight - 65)
|
||||
const newWidth = isDesktop(window.innerWidth) ? calculateDesktopWidth() : window.innerWidth
|
||||
ticketGroup.position.x = getTicketXPosition(window.innerWidth, positionRight)
|
||||
const tickeScale = getTicketScale(window.innerWidth)
|
||||
ticketGroup.scale.set(tickeScale, tickeScale, tickeScale)
|
||||
camera.aspect =
|
||||
newWidth /
|
||||
(window.innerHeight < MIN_CANVAS_HEIGHT ? MIN_CANVAS_HEIGHT - 65 : window.innerHeight - 65)
|
||||
camera.updateProjectionMatrix()
|
||||
renderer.setSize(newWidth, window.innerHeight - 65)
|
||||
renderer.setSize(
|
||||
newWidth,
|
||||
window.innerHeight < MIN_CANVAS_HEIGHT ? MIN_CANVAS_HEIGHT - 65 : window.innerHeight - 65
|
||||
)
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
|
||||
@@ -47,7 +47,7 @@ interface CellState {
|
||||
|
||||
export const INTERACTIVE_GRID_COLORS = (isDark: boolean) => ({
|
||||
GRID_STROKE: isDark ? '#242424' : '#EDEDED',
|
||||
CURRENT_USER_HOVER: isDark ? '#242424' : '#d3d3d3',
|
||||
CURRENT_USER_HOVER: isDark ? '#00311D' : '#72E3AD',
|
||||
})
|
||||
|
||||
// Generate a random user id
|
||||
|
||||
@@ -6,24 +6,19 @@ import dynamic from 'next/dynamic'
|
||||
import Link from 'next/link'
|
||||
import Error from 'next/error'
|
||||
import { createClient, Session } from '@supabase/supabase-js'
|
||||
import { Button, Divider, Separator } from 'ui'
|
||||
import { SITE_URL, LW_URL, LW12_DATE } from '~/lib/constants'
|
||||
import { Button } from 'ui'
|
||||
import { SITE_URL, LW_URL } from '~/lib/constants'
|
||||
import supabase from '~/lib/supabase'
|
||||
import { Database } from '~/lib/database.types'
|
||||
import { AnimatePresence, m, LazyMotion, domAnimation } from 'framer-motion'
|
||||
import { DEFAULT_TRANSITION, INITIAL_BOTTOM, getAnimation } from '~/lib/animations'
|
||||
import MetalTicket from '~/components/LaunchWeek/13/MetalTicket'
|
||||
|
||||
import DefaultLayout from '~/components/Layouts/Default'
|
||||
import SectionContainer from '~/components/Layouts/SectionContainer'
|
||||
import { TicketState, ConfDataContext, UserData } from '~/components/LaunchWeek/hooks/use-conf-data'
|
||||
import LW12Background from '~/components/LaunchWeek/12/LW12Background'
|
||||
import { useTheme } from 'next-themes'
|
||||
import TicketCopy from '~/components/LaunchWeek/12/Ticket/TicketCopy'
|
||||
|
||||
const LW12TicketContainer = dynamic(
|
||||
() => import('~/components/LaunchWeek/12/Ticket/TicketContainer')
|
||||
)
|
||||
const CTABanner = dynamic(() => import('~/components/CTABanner'))
|
||||
import CanvasSingleMode from '~/components/LaunchWeek/13/Multiplayer/CanvasSingleMode'
|
||||
import classNames from 'classnames'
|
||||
|
||||
interface Props {
|
||||
user: UserData
|
||||
@@ -37,12 +32,12 @@ export default function UsernamePage({ user, ogImageUrl }: Props) {
|
||||
const DISPLAY_NAME = name || username
|
||||
const FIRST_NAME = DISPLAY_NAME?.split(' ')[0]
|
||||
const TITLE = `${DISPLAY_NAME ? DISPLAY_NAME.split(' ')[0] + '’s' : 'Get your'} Launch Week Ticket`
|
||||
const DESCRIPTION = `Claim your Supabase Launch Week 12 ticket for a chance to win supa swag.`
|
||||
const DESCRIPTION = `Claim your Supabase Launch Week 13 ticket for a chance to win supa swag.`
|
||||
const PAGE_URL = `${LW_URL}/tickets/${username}`
|
||||
|
||||
const [session] = useState<Session | null>(null)
|
||||
const [ticketState, setTicketState] = useState<TicketState>('ticket')
|
||||
const { resolvedTheme, setTheme } = useTheme()
|
||||
// const { resolvedTheme, setTheme } = useTheme()
|
||||
|
||||
// const isDark = resolvedTheme?.includes('dark')
|
||||
// const isDarkTheme = resolvedTheme === 'dark'
|
||||
@@ -93,15 +88,8 @@ export default function UsernamePage({ user, ogImageUrl }: Props) {
|
||||
setTicketState,
|
||||
}}
|
||||
>
|
||||
<DefaultLayout
|
||||
className="
|
||||
-mt-[60px] pt-[60px]
|
||||
overflow-hidden
|
||||
xl:h-screen !min-h-fit
|
||||
xl:!max-h-[calc(100vh-60px)]
|
||||
"
|
||||
>
|
||||
<SectionContainer className="relative h-full flex-1">
|
||||
<DefaultLayout className="lg:h-[calc(100dvh-65px)] min-h-[calc(100dvh-65px)] overflow-hidden">
|
||||
<SectionContainer className="relative h-full flex-1 pt-4 md:pt-4 pointer-events-none">
|
||||
<div className="relative z-10 flex h-full">
|
||||
<LazyMotion features={domAnimation}>
|
||||
<AnimatePresence mode="wait" key={ticketState}>
|
||||
@@ -110,25 +98,25 @@ export default function UsernamePage({ user, ogImageUrl }: Props) {
|
||||
initial={initial}
|
||||
animate={animate}
|
||||
exit={exit}
|
||||
className="w-full flex-1 min-h-[400px] h-full flex flex-col xl:flex-row items-center xl:justify-center xl:items-center gap-8 md:gap-10 xl:gap-32 text-foreground text-center md:text-left"
|
||||
className="w-full flex-1 h-full flex flex-col lg:flex-row items-center lg:justify-center lg:items-center gap-8 md:gap-10 lg:gap-32 text-foreground text-center md:text-left"
|
||||
>
|
||||
<div className="w-full lg:w-auto h-full mt-3 md:mt-6 xl:mt-0 max-w-lg flex flex-col items-center justify-center gap-3">
|
||||
<LW12TicketContainer />
|
||||
<TicketCopy />
|
||||
</div>
|
||||
<div className="xl:h-full w-full max-w-lg gap-8 flex flex-col items-center justify-center xl:items-start xl:justify-center text-center xl:text-left">
|
||||
<div className="flex flex-col items-center justify-center xl:justify-start xl:items-start gap-2 text-foreground text-center md:text-left max-w-sm">
|
||||
<div className="w-full lg:w-full h-full mt-3 md:mt-6 lg:mt-0 max-w-lg flex flex-col items-center justify-center gap-3"></div>
|
||||
<div className="lg:h-full w-full max-w-lg gap-8 flex flex-col items-center justify-center lg:items-start lg:justify-center text-center lg:text-left">
|
||||
<div className="flex flex-col items-center justify-center lg:justify-start lg:items-start gap-2 text-foreground text-center md:text-left max-w-sm">
|
||||
<h1 className="text-foreground text-2xl">
|
||||
{DISPLAY_NAME?.split(' ')[0]}'s Ticket
|
||||
</h1>
|
||||
<span className="text-foreground-light text-2xl">
|
||||
Join {FIRST_NAME} for Supabase Launch Week 12. Claim your ticket for a
|
||||
Join {FIRST_NAME} for Supabase Launch Week 13. Claim your ticket for a
|
||||
chance to win limited swag.
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<Button type="primary" asChild size="small">
|
||||
<Link href={`${SITE_URL}${username ? '?referral=' + username : ''}`}>
|
||||
<Link
|
||||
href={`${SITE_URL}${username ? '?referral=' + username : ''}`}
|
||||
className="pointer-events-auto"
|
||||
>
|
||||
Claim your ticket
|
||||
</Link>
|
||||
</Button>
|
||||
@@ -138,8 +126,13 @@ export default function UsernamePage({ user, ogImageUrl }: Props) {
|
||||
</AnimatePresence>
|
||||
</LazyMotion>
|
||||
</div>
|
||||
<LW12Background className={'opacity-80 dark:opacity-60'} />
|
||||
</SectionContainer>
|
||||
<CanvasSingleMode />
|
||||
<MetalTicket
|
||||
username={DISPLAY_NAME ?? ''}
|
||||
className="relative -mt-56 -mb-20 lg:my-0 lg:absolute"
|
||||
ticketPosition="left"
|
||||
/>
|
||||
</DefaultLayout>
|
||||
</ConfDataContext.Provider>
|
||||
</>
|
||||
@@ -163,14 +156,14 @@ export const getStaticProps: GetStaticProps = async ({ params }) => {
|
||||
// stores the og images in supabase storage
|
||||
fetch(`${SITE_URL}/api-v2/ticket-og?username=${encodeURIComponent(username ?? '')}`)
|
||||
|
||||
// supabaseAdmin.functions.invoke(`lw12-ticket-og?username=${encodeURIComponent(username ?? '')}`)
|
||||
// supabaseAdmin.functions.invoke(`lw13-ticket-og?username=${encodeURIComponent(username ?? '')}`)
|
||||
|
||||
// fetch a specific user
|
||||
if (username) {
|
||||
const { data } = await supabaseAdmin!
|
||||
.from('tickets_view')
|
||||
.select('name, username, ticket_number, metadata, platinum, secret, role, company, location')
|
||||
.eq('launch_week', 'lw12')
|
||||
.eq('launch_week', 'lw13')
|
||||
.eq('username', username)
|
||||
.single()
|
||||
|
||||
@@ -189,7 +182,7 @@ export const getStaticProps: GetStaticProps = async ({ params }) => {
|
||||
}
|
||||
|
||||
const ticketType = user?.secret ? 'secret' : user?.platinum ? 'platinum' : 'regular'
|
||||
const ogImageUrl = `${process.env.NEXT_PUBLIC_SUPABASE_URL}/storage/v1/object/public/images/launch-week/lw12/og/${ticketType}/${username}.png?t=${dayjs(new Date()).format('DHHmmss')}`
|
||||
const ogImageUrl = `${process.env.NEXT_PUBLIC_SUPABASE_URL}/storage/v1/object/public/images/launch-week/lw13/og/${ticketType}/${username}.png?t=${dayjs(new Date()).format('DHHmmss')}`
|
||||
|
||||
return {
|
||||
props: {
|
||||
|
||||
Reference in new issue
Block a user