refactor ticket layout and positioning logic

This commit is contained in:
Francesco Sansalvadore committed 2024-11-18 13:57:44 -04:00
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: {