Skip to content
This repository has been archived by the owner on Jun 27, 2024. It is now read-only.

Commit

Permalink
Landing page (#3)
Browse files Browse the repository at this point in the history
* styling setup

* button component

* newsletter form

* feat: setup react query for subscription mutation
  • Loading branch information
front-bender authored Mar 20, 2024
1 parent 88136ad commit 1a9a8eb
Show file tree
Hide file tree
Showing 36 changed files with 1,723 additions and 746 deletions.
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20.11.1
8 changes: 8 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"plugins": [
"prettier-plugin-organize-imports",
"prettier-plugin-tailwindcss"
],
"tailwindConfig": "./tailwind.config.js",
"tailwindFunctions": ["tv"]
}
29 changes: 6 additions & 23 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,19 @@
import 'tailwindcss/tailwind.css'

import { IBM_Plex_Mono, Inter, PT_Serif } from 'next/font/google'
import { karelia } from '@/fonts/fonts'

const serif = PT_Serif({
variable: '--font-serif',
style: ['normal', 'italic'],
subsets: ['latin'],
weight: ['400', '700'],
})
const sans = Inter({
variable: '--font-sans',
subsets: ['latin'],
// @todo: understand why extrabold (800) isn't being respected when explicitly specified in this weight array
// weight: ['500', '700', '800'],
})
const mono = IBM_Plex_Mono({
variable: '--font-mono',
subsets: ['latin'],
weight: ['500', '700'],
})
import { Providers } from './providers'

export default async function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html
lang="en"
className={`${mono.variable} ${sans.variable} ${serif.variable}`}
>
<body>{children}</body>
<html lang="en" className={`${karelia.variable}`}>
<body className="overscroll-none">
<Providers>{children}</Providers>
</body>
</html>
)
}
11 changes: 11 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Metadata } from 'next/types'

import { HomePage } from '@/components/pages/home/HomePage'

export const metadata: Metadata = {
title: 'Datum',
}

export default async function Homepage() {
return <HomePage />
}
15 changes: 15 additions & 0 deletions app/providers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use client'

import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'

const queryClient = new QueryClient()

export function Providers({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
}
17 changes: 17 additions & 0 deletions components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "styles/index.css",
"baseColor": "slate",
"cssVariables": false,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
25 changes: 25 additions & 0 deletions components/global/Logo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
type LogoProps = {
w?: number
h?: number
}

export const Logo = ({ w = 145, h = 39 }: LogoProps) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={`${w}px`}
height={`${h}px`}
fill="none"
viewBox="0 0 145 39"
>
<path
fill="#F27A67"
d="M14.254 0a.996.996 0 0 0-.995.995v9.725c0 .548.445.995.995.995h4.778c1.951 0 3.81.762 5.233 2.144 1.433 1.392 2.233 3.229 2.255 5.176a7.35 7.35 0 0 1-2.14 5.29 7.352 7.352 0 0 1-5.264 2.197h-.084c-1.947-.023-3.784-.823-5.176-2.255a7.485 7.485 0 0 1-2.144-5.234v-4.778a.996.996 0 0 0-.995-.995H.995a.996.996 0 0 0-.995.995v9.725c0 .548.446.995.995.995H8.67c1.032 0 1.697.014 2.245.089.728.098 1.216.306 1.584.673.368.367.575.856.674 1.584.074.55.088 1.213.088 2.247v7.672c0 .548.446.995.995.995h4.863a19.08 19.08 0 0 0 6.372-1.09c7.623-2.694 12.745-9.939 12.745-18.027 0-8.089-5.122-15.334-12.745-18.029A19.08 19.08 0 0 0 19.118 0H14.254Z"
/>
<path
fill="#fff"
d="M139.479 13.68c1.607 0 2.831.503 3.671 1.51.851.995 1.276 2.359 1.276 4.091v10.968h-3.858V20.183c0-2.105-.82-3.158-2.459-3.158-.943 0-1.726.436-2.349 1.307-.622.871-.933 1.996-.933 3.376v8.541h-3.843V20.183c0-1.026-.208-1.81-.622-2.349-.415-.54-1.027-.809-1.836-.809-.944 0-1.727.436-2.35 1.307-.622.871-.933 1.986-.933 3.345v8.572h-3.889V14.007h3.827v2.956h.093c.291-.902.835-1.675 1.634-2.318.809-.643 1.815-.965 3.018-.965 1.224 0 2.22.296 2.987.887.778.591 1.312 1.431 1.603 2.52h.093a5.092 5.092 0 0 1 1.742-2.427c.83-.653 1.873-.98 3.128-.98ZM105.001 25.022V14.007h3.858v10.129c0 2.074.845 3.111 2.536 3.111.996 0 1.82-.446 2.474-1.338.653-.892.98-2.038.98-3.438v-8.464h3.874V30.25h-3.812v-3.034h-.093c-.374.975-.939 1.779-1.696 2.412-.757.622-1.711.933-2.863.933-3.505 0-5.258-1.846-5.258-5.539ZM94.828 25.567V17.18h-2.132v-3.018h.794c.643 0 1.068-.14 1.276-.42.218-.28.326-.85.326-1.712V8.904h3.594v5.103h4.045v3.174h-4.045v7.717c0 .902.156 1.535.467 1.898.311.352.825.529 1.54.529.695 0 1.375-.161 2.038-.482v3.189c-.912.384-1.918.576-3.018.576-1.732 0-2.982-.39-3.75-1.167-.756-.789-1.135-2.08-1.135-3.874ZM75.112 22.253c0-2.635.643-4.72 1.93-6.255 1.286-1.545 2.992-2.318 5.118-2.318 1.234 0 2.23.275 2.987.825.757.539 1.265 1.156 1.525 1.851h.109v-2.35h3.78V30.25h-3.78v-2.925h-.11c-.217.872-.746 1.629-1.586 2.272-.84.643-1.934.965-3.283.965-.944 0-1.82-.182-2.63-.545a6.292 6.292 0 0 1-2.13-1.571c-.602-.685-1.074-1.556-1.416-2.614-.343-1.068-.514-2.261-.514-3.578Zm11.59.093v-.794c0-1.472-.352-2.634-1.057-3.485-.695-.86-1.571-1.29-2.63-1.29-1.213 0-2.183.481-2.909 1.446-.715.965-1.073 2.266-1.073 3.905 0 1.68.352 2.987 1.058 3.92.705.934 1.628 1.4 2.77 1.4 1.12 0 2.037-.45 2.753-1.353.726-.902 1.089-2.152 1.089-3.75ZM55.258 8.904h7.872c1.307 0 2.51.177 3.61.53 1.11.352 2.063.845 2.862 1.477a9.063 9.063 0 0 1 2.054 2.256c.57.871.995 1.83 1.275 2.878.28 1.048.42 2.163.42 3.345 0 1.535-.233 2.961-.7 4.279a10.355 10.355 0 0 1-1.975 3.438c-.861.975-1.956 1.742-3.283 2.303-1.328.56-2.816.84-4.465.84h-7.67V8.904ZM62.71 26.78c1.1 0 2.069-.181 2.91-.544.85-.363 1.54-.872 2.068-1.525a6.717 6.717 0 0 0 1.183-2.318c.27-.882.404-1.862.404-2.94 0-1.017-.13-1.945-.389-2.785a6.588 6.588 0 0 0-1.166-2.24c-.52-.654-1.204-1.157-2.054-1.51-.85-.363-1.836-.544-2.956-.544h-3.47V26.78h3.47Z"
/>
</svg>
)
}
161 changes: 161 additions & 0 deletions components/pages/home/HomePage.animation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
'use client'
import Matter from 'matter-js'
import React, { useEffect, useRef } from 'react'

export const HomePageAnimation = () => {
const sceneRef = useRef(null)

useEffect(() => {
const {
Engine,
Render,
Runner,
Bodies,
Composite,
Composites,
Constraint,
Mouse,
MouseConstraint,
Common,
Events,
Body,
} = Matter

const currentRef = sceneRef.current
if (!currentRef) return

const engine = Engine.create()
const world = engine.world
const render = Render.create({
element: currentRef,
engine: engine,
options: {
width: 1000,
height: 1100,
wireframes: true,
background: 'white',
},
})

Render.run(render)
const runner = Runner.create()
Runner.run(runner, engine)

// create obstacles
const obstacles = Composites.stack(10, 0, 15, 3, 10, 10, function (x, y) {
let sides = Math.round(Common.random(1, 8)),
options = {
render: {
fillStyle: Common.choose([
'#48f164',
'#f5d259',
'#f55a3c',
'#063e7b',
'#ececd1',
]),
},
}

switch (Math.round(Common.random(0, 1))) {
case 0:
if (Common.random() < 0.8) {
return Bodies.rectangle(
x,
y,
Common.random(25, 50),
Common.random(25, 50),
options,
)
} else {
return Bodies.rectangle(
x,
y,
Common.random(80, 120),
Common.random(25, 30),
options,
)
}
case 1:
return Bodies.polygon(x, y, sides, Common.random(25, 50), options)
}
})

Composite.add(world, [obstacles])

let timeScaleTarget = 1,
lastTime = Common.now()

Events.on(engine, 'afterUpdate', function (event) {
var timeScale = (event.delta || 1000 / 60) / 1000

// tween the timescale for slow-mo
if (mouse.button === -1) {
engine.timing.timeScale +=
(timeScaleTarget - engine.timing.timeScale) * 3 * timeScale
} else {
engine.timing.timeScale = 1
}

// every 2 sec (real time)
if (Common.now() - lastTime >= 2000) {
// flip the timescale
if (timeScaleTarget < 1) {
timeScaleTarget = 1
} else {
timeScaleTarget = 0.05
}

// update last time
lastTime = Common.now()
}

for (let i = 0; i < obstacles.bodies.length; i += 1) {
var body = obstacles.bodies[i],
bounds = body.bounds

// move obstacles back to the top of the screen
if (bounds.min.y > render.bounds.max.y + 100) {
Body.translate(body, {
x: -bounds.min.x,
y: -render.bounds.max.y - 300,
})
}
}
})

// add mouse control and make the mouse revolute
var mouse = Mouse.create(render.canvas),
mouseConstraint = MouseConstraint.create(engine, {
mouse: mouse,
constraint: {
stiffness: 0.6,
length: 0,
angularStiffness: 0,
render: {
visible: false,
},
},
})

Composite.add(world, mouseConstraint)

// keep the mouse in sync with rendering
render.mouse = mouse

// fit the render viewport to the scene
Render.lookAt(render, {
min: { x: 0, y: 0 },
max: { x: 800, y: 600 },
})

return () => {
Render.stop(render)
Runner.stop(runner)
while (currentRef.firstChild) {
currentRef.removeChild(currentRef.firstChild)
}
}
}, [])

return <div ref={sceneRef} className="w-full h-full overflow-hidden"></div>
}
86 changes: 86 additions & 0 deletions components/pages/home/HomePage.newsletter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
'use client'

import { zodResolver } from '@hookform/resolvers/zod'
import { LoaderCircle, MailCheck } from 'lucide-react'
import { useForm } from 'react-hook-form'
import { z } from 'zod'

import {
Button,
Form,
FormControl,
FormField,
FormMessage,
Input,
} from '@/components/ui'
import { useSubscribeMutation } from '@/hooks/mutations/useSubscribeMutation'

import { newsletterStyles } from './HomePage.styles'

const formSchema = z.object({
email: z.string().email(),
})

export const HomePageNewsletter = () => {
const {
wrapper,
input,
button,
errorMessage,
success,
successMessage,
successIcon,
} = newsletterStyles()
const { mutate, status, isError, error, data } = useSubscribeMutation()
const isLoading = status === 'pending'

const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
email: '',
},
})

const onSubmit = ({ email }: z.infer<typeof formSchema>) => {
mutate(email)
}

return (
<>
{status === 'success' ? (
<div className={success()}>
<MailCheck size={24} className={successIcon()} />
<span className={successMessage()}>{data.message}</span>
</div>
) : (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className={wrapper()}>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<>
<FormControl>
<Input
type="email"
placeholder="Your email"
variant="outline"
className={input()}
{...field}
/>
</FormControl>
<FormMessage />
</>
)}
/>
<Button type="submit" variant="white" className={button()}>
{isLoading && <LoaderCircle className="animate-spin" size={20} />}
{isLoading ? 'Loading' : 'Stay in the loop'}
</Button>
</form>
{isError && <div className={errorMessage()}>{error.message}</div>}
</Form>
)}
</>
)
}
Loading

0 comments on commit 1a9a8eb

Please sign in to comment.