Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,5 @@ docs/plans
# Ignore key files for decrypting credentials and more.
/config/*.key


mise.toml
21 changes: 18 additions & 3 deletions app/frontend/components/shared/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,35 @@ import { twMerge } from 'tailwind-merge'

type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: 'primary' | 'link'
visuallyDisabled?: boolean
}

export default function Button({ variant = 'primary', disabled, className, children, ...props }: ButtonProps) {
export default function Button({
variant = 'primary',
disabled,
visuallyDisabled,
className,
children,
...props
}: ButtonProps) {
const looks = disabled || visuallyDisabled

const base =
variant === 'link'
? 'text-lg underline cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed disabled:no-underline'
: 'py-1.5 px-4 border-2 font-bold uppercase'

const state = disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'
const state = looks ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'

const colors = 'bg-brown text-light-brown border-dark-brown'

return (
<button className={twMerge(base, variant !== 'link' && colors, state, className)} disabled={disabled} {...props}>
<button
className={twMerge(base, variant !== 'link' && colors, state, className)}
disabled={disabled}
aria-disabled={visuallyDisabled || undefined}
{...props}
>
{children}
</button>
)
Expand Down
103 changes: 40 additions & 63 deletions app/frontend/pages/landing/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@ export default function LandingIndex() {
const style = document.createElement('style')
style.textContent = '*, *::before, *::after { cursor: none !important; }'
document.head.appendChild(style)
return () => {
document.head.removeChild(style)
}
return () => document.head.removeChild(style)
}, [])

useEffect(() => {
Expand Down Expand Up @@ -241,8 +239,7 @@ export default function LandingIndex() {
dTop = clientY,
dBottom = innerHeight - clientY
const min = Math.min(dLeft, dRight, dTop, dBottom)
let tx = clientX - 8,
ty = clientY - 5
let tx = clientX - 8, ty = clientY - 5
if (min === dLeft) tx = -40
else if (min === dRight) tx = innerWidth + 8
else if (min === dTop) ty = -52
Expand Down Expand Up @@ -361,9 +358,7 @@ export default function LandingIndex() {
>
<span className="relative overflow-hidden block leading-none">
<span className="block transition-transform duration-300 group-hover:translate-y-full">LOG IN</span>
<span className="absolute inset-0 block transition-transform duration-300 -translate-y-full group-hover:translate-y-0">
LOG IN
</span>
<span className="absolute inset-0 block transition-transform duration-300 -translate-y-full group-hover:translate-y-0">LOG IN</span>
</span>
</a>
<div
Expand Down Expand Up @@ -407,9 +402,7 @@ export default function LandingIndex() {
>
<span className="relative overflow-hidden block leading-none">
<span className="block transition-transform duration-300 group-hover:translate-y-full">LOG IN</span>
<span className="absolute inset-0 block transition-transform duration-300 -translate-y-full group-hover:translate-y-0">
LOG IN
</span>
<span className="absolute inset-0 block transition-transform duration-300 -translate-y-full group-hover:translate-y-0">LOG IN</span>
</span>
</a>
</nav>
Expand Down Expand Up @@ -448,12 +441,8 @@ export default function LandingIndex() {
disabled={submitting}
>
<span className="relative overflow-hidden block leading-none">
<span className="block transition-transform duration-300 group-hover:translate-y-full">
{submitting ? '...' : 'START NOW'}
</span>
<span className="absolute inset-0 block transition-transform duration-300 -translate-y-full group-hover:translate-y-0">
{submitting ? '...' : 'START NOW'}
</span>
<span className="block transition-transform duration-300 group-hover:translate-y-full">{submitting ? '...' : 'START NOW'}</span>
<span className="absolute inset-0 block transition-transform duration-300 -translate-y-full group-hover:translate-y-0">{submitting ? '...' : 'START NOW'}</span>
</span>
</button>
</form>
Expand Down Expand Up @@ -539,6 +528,31 @@ export default function LandingIndex() {
</a>
</p>
</div>
{/* <div className="fixed bottom-10 right-10 w-[80%] xs:w-100 h-auto rounded-xs overflow-hidden bg-beige z-50 flex flex-col border-2 border-black cursor-pointer">
<div className="w-full flex justify-between items-center px-4 py-2 cursor-pointer" onClick={() => {
setVideoOpen((v) => {
const next = !v
const msg = next ? 'playVideo' : 'pauseVideo'
iframeRef.current?.contentWindow?.postMessage(`{"event":"command","func":"${msg}","args":""}`, '*')
return next
})
}}>
<span className="font-medium text-black text-2xl">{videoOpen ? 'Close Video' : 'Open Video'}</span>
<img src="/arrow.svg" className={`h-5 w-auto transition-transform duration-300 ${videoOpen ? 'rotate-180' : 'rotate-0'}`} />
</div>
<div className={`aspect-16/9 w-full h-auto p-3 pt-0${videoOpen ? '' : ' hidden'}`}>
<iframe
width="100%"
height="100%"
className="rounded-lg border-beige border-2"
ref={iframeRef}
src="https://www.youtube.com/embed/SrP2ZeNHm6s?si=orljJtYrC7EGSNzi&controls=1&modestbranding=1&rel=0&enablejsapi=1"
title="YouTube video player"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin"
allowFullScreen>
</iframe>
</div>
</div> */}
</section>

<div className="md:pt-40 md:qualify-outer">
Expand Down Expand Up @@ -609,37 +623,6 @@ export default function LandingIndex() {
{/* <h4>China</h4> */}
</div>
</section>
<section className="px-6 md:px-8 lg:px-18 xl:px-36 2xl:px-54 py-20 bg-beige text-dark-brown text-2xl">
<div className="w-full bg-white border-2 border-black p-6 my-2 rounded-sm">
<span className="font-semibold text-2xl">Hack Club is recognized by:</span>
<ul className="flex gap-2 md:gap-4 w-full items-center justify-center py-6 px-4 flex-wrap lg:flex-nowrap">
<li>
<img src="/landing/logos/gwc.webp" className="h-10 md:h-18 object-contain" alt="Girls Who Code" />
</li>
<li>
<img src="/landing/logos/github.webp" className="h-12 md:h-22 object-contain" alt="GitHub" />
</li>
<li>
<img
src="/landing/logos/mit.webp"
className="h-14 md:h-24 object-contain"
alt="MIT School of Engineering"
/>
</li>
<li>
<img
src="/landing/logos/cac.webp"
className="h-10 md:h-16 object-contain"
alt="Congressional App Challenge"
/>
</li>
<li>
<img src="/landing/logos/amd.webp" className="h-10 md:h-16 object-contain" alt="AMD" />
</li>
</ul>
</div>
<span className="text-2xl">Upon the completion of 60h, we will issue you a certificate.</span>
</section>
<section className="px-6 md:px-8 lg:px-18 xl:px-36 2xl:px-54 py-20 bg-beige text-dark-brown">
<h2 className="text-6xl font-semibold mb-10 font-outfit">FAQ</h2>
<div className="divide-y-2 divide-dark-brown border-y-2 border-dark-brown font-outfit">
Expand Down Expand Up @@ -807,22 +790,16 @@ export default function LandingIndex() {
</div>
</div>
<div className="fixed bottom-10 right-10 w-[80%] xs:w-100 h-auto rounded-sm overflow-hidden bg-white z-50 flex flex-col border-2 border-dark-brown [transform:translateZ(0)]">
<div
className="w-full flex justify-between items-center px-4 py-2 cursor-pointer"
onClick={() => {
setVideoOpen((v) => {
const next = !v
const msg = next ? 'playVideo' : 'pauseVideo'
iframeRef.current?.contentWindow?.postMessage(`{"event":"command","func":"${msg}","args":""}`, '*')
return next
})
}}
>
<div className="w-full flex justify-between items-center px-4 py-2 cursor-pointer" onClick={() => {
setVideoOpen((v) => {
const next = !v
const msg = next ? 'playVideo' : 'pauseVideo'
iframeRef.current?.contentWindow?.postMessage(`{"event":"command","func":"${msg}","args":""}`, '*')
return next
})
}}>
<span className="font-medium text-dark-brown text-2xl">{videoOpen ? 'Close Video' : 'Open Video'}</span>
<img
src="/arrow.svg"
className={`h-5 w-auto transition-transform duration-300 ${videoOpen ? 'rotate-180' : 'rotate-0'}`}
/>
<img src="/arrow.svg" className={`h-5 w-auto transition-transform duration-300 ${videoOpen ? 'rotate-180' : 'rotate-0'}`} />
</div>
<div className={`aspect-16/9 w-full h-auto p-3 pt-0 ${videoOpen ? '' : ' hidden'}`}>
<iframe
Expand Down
62 changes: 46 additions & 16 deletions app/frontend/pages/projects/onboarding/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@ import Button from '@/components/shared/Button'
import Input from '@/components/shared/Input'
import TextArea from '@/components/shared/TextArea'
import { Pagination, PaginationPage } from '@/components/shared/Pagination'
import { Label } from '@headlessui/react'

function ValidationError({ message, show }: { message: string; show: boolean }) {
if (!show) return null
return <p className="text-red-700 text-sm mt-1 font-medium">{message}</p>
}

function ProjectsOnboarding({ is_modal }: { is_modal: boolean }) {
const videoRef = useRef<HTMLVideoElement>(null)
const [name, setName] = useState('')
const [description, setDescription] = useState('')
const [processing, setProcessing] = useState(false)
const [descriptionError, setDescriptionError] = useState(false)
const [nameError, setNameError] = useState(false)
const modalRef = useRef<{ close: () => void }>(null)

function submit(e: React.FormEvent<HTMLFormElement>) {
Expand Down Expand Up @@ -41,14 +46,12 @@ function ProjectsOnboarding({ is_modal }: { is_modal: boolean }) {
We've made a really cool video showing how Fallout works. Give it a watch!
</p>
</div>
<div className="grow min-h-0 flex items-center justify-center">
<video
ref={videoRef}
src="/intro.mp4"
className="max-w-full max-h-full rounded-lg"
autoPlay
playsInline
controls
<div className="grow min-h-0 flex items-center justify-center overflow-hidden">
<iframe
src="https://www.youtube-nocookie.com/embed/SrP2ZeNHm6s?autoplay=1"
className="w-full h-full rounded-lg"
allow="autoplay; encrypted-media"
allowFullScreen
/>
</div>
<Button type="button" onClick={next} className="ml-auto">
Expand Down Expand Up @@ -90,15 +93,23 @@ function ProjectsOnboarding({ is_modal }: { is_modal: boolean }) {
</label>
<TextArea
value={description}
onChange={(e) => setDescription(e.target.value)}
onChange={(e) => {
setDescription(e.target.value)
setDescriptionError(false)
}}
className="w-full h-full resize-y px-3 py-2 text-base"
/>
<ValidationError message="Please describe your project before continuing" show={descriptionError} />
</div>
<div className="flex justify-between">
<Button variant="link" type="button" onClick={prev}>
Back
</Button>
<Button type="button" onClick={next} disabled={!description.trim()}>
<Button
type="button"
visuallyDisabled={!description.trim()}
onClick={() => (description.trim() ? next() : setDescriptionError(true))}
>
Continue
</Button>
</div>
Expand Down Expand Up @@ -143,16 +154,35 @@ function ProjectsOnboarding({ is_modal }: { is_modal: boolean }) {
<Input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
className="text-base px-3 py-2"
placeholder=""
onChange={(e) => {
setName(e.target.value)
setNameError(false)
}}
placeholder="My awesome project"
autoFocus
className={nameError ? 'border-red-700' : ''}
/>
<ValidationError message="Give your project a name" show={nameError} />
</div>
<div className="flex justify-between">
<Button variant="link" type="button" onClick={prev}>
Back
</Button>
<Button type="submit" disabled={!name.trim() || processing}>
<Button
type="button"
visuallyDisabled={!name.trim()}
disabled={processing}
onClick={() => {
if (!name.trim()) {
setNameError(true)
return
}
if (!processing) {
const form = document.querySelector('form') as HTMLFormElement | null
form?.requestSubmit()
}
}}
>
{processing ? 'Creating...' : "Let's start!"}
</Button>
</div>
Expand Down
Loading