diff --git a/apps/nextjs/next.config.mjs b/apps/nextjs/next.config.mjs index 017c62ac..866e07af 100644 --- a/apps/nextjs/next.config.mjs +++ b/apps/nextjs/next.config.mjs @@ -12,7 +12,7 @@ const config = { eslint: { ignoreDuringBuilds: !!process.env.CI }, typescript: { ignoreBuildErrors: !!process.env.CI }, images: { - domains: ["play.google.com", "tools.applemediaservices.com", "tools.applemediaservices.com"], + domains: ["play.google.com", "tools.applemediaservices.com", 'i.scdn.co', "misc.scdn.co", "image-cdn-ak.spotifycdn.com", "mosaic.scdn.co"], }, }; diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index 970ec482..38223dcd 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -18,16 +18,21 @@ "@fissa/db": "*", "@fissa/tailwind-config": "*", "@fissa/utils": "*", + "@headlessui/react": "^2.1.8", "@tanstack/react-query": "4.36.1", "@trpc/client": "10.21.1", "@trpc/next": "10.21.1", "@trpc/react-query": "10.21.1", "@trpc/server": "10.21.1", "@vercel/analytics": "1.2.2", + "clsx": "^2.1.1", + "framer-motion": "^11.5.6", + "lucide-react": "^0.445.0", "next": "14.2.3", "react": "18.2.0", "react-dom": "18.2.0", "react-toastify": "10.0.5", + "use-debounce": "^10.0.3", "zod": "3.23.8" }, "devDependencies": { diff --git a/apps/nextjs/public/icon.svg b/apps/nextjs/public/icon.svg new file mode 100644 index 00000000..3f49abab --- /dev/null +++ b/apps/nextjs/public/icon.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/nextjs/src/components/AppDemo.tsx b/apps/nextjs/src/components/AppDemo.tsx new file mode 100644 index 00000000..8f04ada3 --- /dev/null +++ b/apps/nextjs/src/components/AppDemo.tsx @@ -0,0 +1,13 @@ +import { AppScreen } from './AppScreen' +import { TrackList } from './TrackList' + + +export function AppDemo() { + return ( + + + + + + ) +} diff --git a/apps/nextjs/src/components/AppScreen.tsx b/apps/nextjs/src/components/AppScreen.tsx new file mode 100644 index 00000000..348b7593 --- /dev/null +++ b/apps/nextjs/src/components/AppScreen.tsx @@ -0,0 +1,72 @@ +import clsx from 'clsx' +import { Settings } from 'lucide-react' +import { forwardRef } from 'react' +import { useTheme } from '~/providers/ThemeProvider' + +export function AppScreen({ + children, + className, + ...props +}: React.ComponentPropsWithoutRef<'div'>) { + return ( +
+
+

Fissa 1994

+ +
+ {children} +
+ ) +} + +AppScreen.Header = forwardRef< + React.ElementRef<'div'>, + { children: React.ReactNode } +>(function AppScreenHeader({ children }, ref) { + return ( +
+ {children} +
+ ) +}) + +AppScreen.Title = forwardRef< + React.ElementRef<'div'>, + { children: React.ReactNode } +>(function AppScreenTitle({ children }, ref) { + return ( +
+ {children} +
+ ) +}) + +AppScreen.Subtitle = forwardRef< + React.ElementRef<'div'>, + { children: React.ReactNode } +>(function AppScreenSubtitle({ children }, ref) { + return ( +
+ {children} +
+ ) +}) + +AppScreen.Body = forwardRef< + React.ElementRef<'div'>, + { className?: string; children: React.ReactNode } +>(function AppScreenBody({ children, className }, ref) { + const { theme } = useTheme() + + return ( +
+ {children} +
+ ) +}) diff --git a/apps/nextjs/src/components/AppStoreLink.tsx b/apps/nextjs/src/components/AppStoreLink.tsx new file mode 100644 index 00000000..8865a159 --- /dev/null +++ b/apps/nextjs/src/components/AppStoreLink.tsx @@ -0,0 +1,19 @@ +import Image from "next/image"; +import Link from 'next/link'; + +export function AppStoreLink() { + return ( + + Download on the App Store + + ) +} diff --git a/apps/nextjs/src/components/Button.tsx b/apps/nextjs/src/components/Button.tsx new file mode 100644 index 00000000..11f854a9 --- /dev/null +++ b/apps/nextjs/src/components/Button.tsx @@ -0,0 +1,59 @@ +import clsx from 'clsx' +import Link from 'next/link' + +const baseStyles = { + solid: + 'inline-flex justify-center rounded-lg py-2 px-3 text-sm font-semibold outline-2 outline-offset-2 transition-colors', + outline: + 'inline-flex justify-center rounded-lg border py-[calc(theme(spacing.2)-1px)] px-[calc(theme(spacing.3)-1px)] text-sm outline-2 outline-offset-2 transition-colors', +} + +const variantStyles = { + solid: { + cyan: 'relative overflow-hidden bg-cyan-500 text-white before:absolute before:inset-0 active:before:bg-transparent hover:before:bg-white/10 active:bg-cyan-600 active:text-white/80 before:transition-colors', + white: + 'bg-white text-cyan-900 hover:bg-white/90 active:bg-white/90 active:text-cyan-900/70', + gray: 'bg-gray-800 text-white hover:bg-gray-900 active:bg-gray-800 active:text-white/80', + }, + outline: { + gray: 'border-gray-300 text-gray-700 hover:border-gray-400 active:bg-gray-100 active:text-gray-700/80', + }, +} + +type ButtonProps = ( + | { + variant?: 'solid' + color?: keyof typeof variantStyles.solid + } + | { + variant: 'outline' + color?: keyof typeof variantStyles.outline + } +) & + ( + | Omit, 'color'> + | (Omit, 'color'> & { + href?: undefined + }) + ) + +export function Button({ className, ...props }: ButtonProps) { + props.variant ??= 'solid' + props.color ??= 'gray' + + className = clsx( + baseStyles[props.variant], + props.variant === 'outline' + ? variantStyles.outline[props.color] + : props.variant === 'solid' + ? variantStyles.solid[props.color] + : undefined, + className, + ) + + return typeof props.href === 'undefined' ? ( + + + ) +} diff --git a/apps/nextjs/src/components/Footer.tsx b/apps/nextjs/src/components/Footer.tsx new file mode 100644 index 00000000..bd376d5c --- /dev/null +++ b/apps/nextjs/src/components/Footer.tsx @@ -0,0 +1,42 @@ + +import { useTheme } from '~/providers/ThemeProvider' +import { AppStoreLink } from './AppStoreLink' +import { Container } from './Container' +import { Logomark } from './Logo' +import { NavLinks } from './NavLinks' +import { PlayStoreLink } from './PlayStoreLink' + +export function Footer() { + const { theme } = useTheme() + + return ( +
+ +
+
+
+ +
+

Fissa

+

Everyone can be a DJ.

+
+
+ +
+
+ + +
+
+
+ Made with ♥️ by Xiduzo +

+ © Copyleft {new Date().getFullYear()}. No rights reserved. +

+
+
+
+ ) +} diff --git a/apps/nextjs/src/components/Header.tsx b/apps/nextjs/src/components/Header.tsx new file mode 100644 index 00000000..ccabc4dc --- /dev/null +++ b/apps/nextjs/src/components/Header.tsx @@ -0,0 +1,111 @@ +import { + Popover, + PopoverBackdrop, + PopoverButton, + PopoverPanel, +} from '@headlessui/react' +import { AnimatePresence, motion } from 'framer-motion' +import Link from 'next/link' + +import { ChevronUp, MenuIcon } from 'lucide-react' +import { useTheme } from '~/providers/ThemeProvider' +import { Container } from './Container' +import { Logo } from './Logo' +import { NavLinks } from './NavLinks' + +function MobileNavLink( + props: Omit< + React.ComponentPropsWithoutRef>, + 'as' | 'className' + >, +) { + const { theme } = useTheme() + + return ( + + ) +} + +export function Header() { + const { theme } = useTheme() + return ( +
+ +
+ ) +} diff --git a/apps/nextjs/src/components/Hero.tsx b/apps/nextjs/src/components/Hero.tsx new file mode 100644 index 00000000..9898d6c5 --- /dev/null +++ b/apps/nextjs/src/components/Hero.tsx @@ -0,0 +1,93 @@ +import { useId } from 'react' + +// import logoBbc from '@/images/logos/bbc.svg' +// import logoCbs from '@/images/logos/cbs.svg' +// import logoCnn from '@/images/logos/cnn.svg' +// import logoFastCompany from '@/images/logos/fast-company.svg' +// import logoForbes from '@/images/logos/forbes.svg' +// import logoHuffpost from '@/images/logos/huffpost.svg' +// import logoTechcrunch from '@/images/logos/techcrunch.svg' +// import logoWired from '@/images/logos/wired.svg' +import { useTheme } from '~/providers/ThemeProvider' +import { AppDemo } from './AppDemo' +import { AppStoreLink } from './AppStoreLink' +import { Container } from './Container' +import { PhoneFrame } from './PhoneFrame' +import { PlayStoreLink } from './PlayStoreLink' + +function BackgroundIllustration(props: React.ComponentPropsWithoutRef<'div'>) { + const id = useId() + const {theme} = useTheme() + + return ( +
+ + +
+ ) +} + +export function Hero() { + const { theme } = useTheme() + + return ( +
+ +
+
+

+ Not only one person should decide what is playing on a party +

+

+ Having friends at a party with a bad taste in music stinks. Use Fissa to create a collaborative and democratic playlist for everyone to enjoy. +

+
+ + +
+
+
+ +
+ + + +
+
+
+
+
+ ) +} diff --git a/apps/nextjs/src/components/JoinAFissa.tsx b/apps/nextjs/src/components/JoinAFissa.tsx new file mode 100644 index 00000000..1a8f7b61 --- /dev/null +++ b/apps/nextjs/src/components/JoinAFissa.tsx @@ -0,0 +1,57 @@ +import { useMemo } from "react"; +import { useTheme } from "~/providers/ThemeProvider"; +import { api } from "~/utils/api"; +import { Container } from "./Container"; +import { FissaCode } from "./FissaCode"; + +export function JoinAFissa() { + const { theme } = useTheme(); + const { data } = api.fissa.activeFissaCount.useQuery(undefined, { + refetchInterval: 1000 * 60 * 5 + }) + + const amount = useMemo(() => { + if(data && data > 0) { + return data + } + + return Math.ceil(Math.random() * 3) + }, [data]) + + const prefix = useMemo(() => { + if(amount === 1) { + return 'is' + } + + return 'are' + }, [amount]) + + const suffix = useMemo(() => { + if(amount === 1) { + return 'Fissa' + } + + return 'Fissa\'s' + }, [amount]) + + return ( +
+ +
+

+ Join a Fissa +

+

+ There {prefix} {amount} active {suffix} +

+
+ +
+
+ ) +} diff --git a/apps/nextjs/src/components/Layout.tsx b/apps/nextjs/src/components/Layout.tsx index 93440baa..692096a3 100644 --- a/apps/nextjs/src/components/Layout.tsx +++ b/apps/nextjs/src/components/Layout.tsx @@ -1,16 +1,13 @@ import { type FC, type PropsWithChildren } from "react"; -import { theme } from "@fissa/tailwind-config"; +import { Footer } from "./Footer"; +import { Header } from "./Header"; export const Layout: FC = ({ children }) => { return ( -
- {children} -
+ <> +
+
{children}
+