Polishing touches and tweaks

This commit is contained in:
Ahmed Al-Taiar
2024-10-06 00:31:59 -04:00
parent e5f9bbd462
commit fb542bb5b5
13 changed files with 96 additions and 61 deletions

View File

@ -142,4 +142,16 @@ export const theme = {
export const darkMode = ['class', '[data-theme="dark"]'] export const darkMode = ['class', '[data-theme="dark"]']
export const plugins = [require('daisyui')] export const plugins = [require('daisyui')]
export const daisyui = { themes: ['light', 'dark'] } export const daisyui = {
themes: [
'light',
{
dark: {
...require('daisyui/src/theming/themes')['dark'],
'base-100': '#212121',
'base-200': '#1d1d1d',
'base-300': '#191919',
},
},
],
}

View File

@ -22,6 +22,7 @@ const ColorPicker = ({ color, setColor }: ColorPickerProps) => {
<HexColorInput color={color} className="w-16" /> <HexColorInput color={color} className="w-16" />
</label> </label>
<button <button
type="button"
className="btn btn-square btn-sm" className="btn btn-square btn-sm"
onClick={async () => { onClick={async () => {
try { try {
@ -35,6 +36,7 @@ const ColorPicker = ({ color, setColor }: ColorPickerProps) => {
<Icon path={mdiContentCopy} className="size-4" /> <Icon path={mdiContentCopy} className="size-4" />
</button> </button>
<button <button
type="button"
className="btn btn-square btn-sm " className="btn btn-square btn-sm "
onClick={async () => { onClick={async () => {
try { try {

View File

@ -1,4 +1,4 @@
import { useState, useRef, useEffect } from 'react' import { useState, useRef, useLayoutEffect } from 'react'
import SocialLinksCell from 'src/components/Social/SocialLinksCell' import SocialLinksCell from 'src/components/Social/SocialLinksCell'
@ -12,7 +12,7 @@ const ContactCard = ({ portraitUrl }: ContactCardProps) => {
const observedDiv = useRef(null) const observedDiv = useRef(null)
useEffect(() => { useLayoutEffect(() => {
if (!observedDiv.current) return if (!observedDiv.current) return
const resizeObserver = new ResizeObserver(() => { const resizeObserver = new ResizeObserver(() => {

View File

@ -4,6 +4,7 @@ import Icon from '@mdi/react'
interface FormTextListProps { interface FormTextListProps {
name: string name: string
hint?: string
itemPlaceholder: string itemPlaceholder: string
icon?: string icon?: string
list: string[] list: string[]
@ -13,6 +14,7 @@ interface FormTextListProps {
const FormTextList = ({ const FormTextList = ({
name, name,
hint,
itemPlaceholder, itemPlaceholder,
icon, icon,
list, list,
@ -23,8 +25,12 @@ const FormTextList = ({
<div className="flex flex-col space-y-2 bg-base-100 rounded-xl"> <div className="flex flex-col space-y-2 bg-base-100 rounded-xl">
<div className="flex space-x-2 justify-between"> <div className="flex space-x-2 justify-between">
<div className="flex items-center"> <div className="flex items-center">
<p className="font-semibold">{name}</p> <p className="font-semibold text-center">{name}</p>
</div> </div>
<div className="flex gap-2 items-center">
{hint && (
<p className="opacity-70 text-xs font-light text-center">{hint}</p>
)}
<button <button
className="btn btn-square btn-sm" className="btn btn-square btn-sm"
type="button" type="button"
@ -33,6 +39,7 @@ const FormTextList = ({
<Icon path={mdiPlus} className="size-4" /> <Icon path={mdiPlus} className="size-4" />
</button> </button>
</div> </div>
</div>
{list.map((item, i) => ( {list.map((item, i) => (
<label <label
className={`input input-bordered pr-2 flex items-center space-x-2 w-full ${errors[i] && 'input-error'}`} className={`input input-bordered pr-2 flex items-center space-x-2 w-full ${errors[i] && 'input-error'}`}
@ -53,11 +60,11 @@ const FormTextList = ({
} }
/> />
<button <button
className="btn btn-square btn-sm flex-none" className="btn btn-square btn-error btn-sm flex-none"
type="button" type="button"
onClick={() => setList(list.filter((_, j) => j !== i))} onClick={() => setList(list.filter((_, j) => j !== i))}
> >
<Icon path={mdiDelete} className="size-4 text-error" /> <Icon path={mdiDelete} className="size-4" />
</button> </button>
</label> </label>
))} ))}

View File

@ -1,4 +1,4 @@
import { useRef, useState } from 'react' import { useState } from 'react'
import { Meta, UploadResult } from '@uppy/core' import { Meta, UploadResult } from '@uppy/core'
import type { import type {
@ -56,13 +56,7 @@ const CREATE_PORTRAIT_MUTATION: TypedDocumentNode<
` `
const PortraitForm = ({ portrait }: PortraitFormProps) => { const PortraitForm = ({ portrait }: PortraitFormProps) => {
const [fileId, _setFileId] = useState<string>(portrait?.fileId) const [fileId, setFileId] = useState<string>(portrait?.fileId)
const fileIdRef = useRef<string>(fileId)
const setFileId = (fileId: string) => {
_setFileId(fileId)
fileIdRef.current = fileId
}
const unloadAbortController = new AbortController() const unloadAbortController = new AbortController()
@ -96,7 +90,7 @@ const PortraitForm = ({ portrait }: PortraitFormProps) => {
setFileId(result.successful[0]?.uploadURL) setFileId(result.successful[0]?.uploadURL)
window.addEventListener( window.addEventListener(
'beforeunload', 'beforeunload',
(e) => handleBeforeUnload(e, [fileIdRef.current]), (e) => handleBeforeUnload(e, [fileId]),
{ {
once: true, once: true,
signal: unloadAbortController.signal, signal: unloadAbortController.signal,

View File

@ -46,18 +46,23 @@ const Project = ({ project }: Props) => {
<> <>
<h2 className="font-bold text-3xl w-fit">Links</h2> <h2 className="font-bold text-3xl w-fit">Links</h2>
<div className="flex flex-col gap-2 w-fit"> <div className="flex flex-col gap-2 w-fit">
<ul className="list-none">
{project.links.map((link, i) => ( {project.links.map((link, i) => (
<li key={i}>
<div className="flex gap-2 items-center justify-start">
<Icon path={mdiLinkVariant} className="size-4" />
<a <a
key={i}
href={link} href={link}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
className="btn btn-wide" className="list-item link link-hover"
> >
<Icon path={mdiLinkVariant} className="size-5" />
{link} {link}
</a> </a>
</div>
</li>
))} ))}
</ul>
</div> </div>
</> </>
)} )}

View File

@ -198,6 +198,7 @@ const ProjectForm = (props: ProjectFormProps) => {
<div className={`${!pickerVisible && 'pt-2'}`}> <div className={`${!pickerVisible && 'pt-2'}`}>
<FormTextList <FormTextList
name="Links" name="Links"
hint="Short links are recommended"
itemPlaceholder="URL" itemPlaceholder="URL"
icon={mdiLinkVariant} icon={mdiLinkVariant}
list={links} list={links}
@ -246,16 +247,13 @@ const ProjectForm = (props: ProjectFormProps) => {
<div className="card-actions rounded-md justify-end"> <div className="card-actions rounded-md justify-end">
<button <button
type="button" type="button"
className="btn btn-square btn-sm shadow-xl" className="btn btn-square btn-sm btn-error shadow-xl"
onClick={() => { onClick={() => {
setToDelete([...toDelete, fileId]) setToDelete([...toDelete, fileId])
setFileIds(fileIds.filter((id) => id !== fileId)) setFileIds(fileIds.filter((id) => id !== fileId))
}} }}
> >
<Icon <Icon path={mdiDelete} className="size-4" />
path={mdiDelete}
className="size-4 text-error"
/>
</button> </button>
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
import { useRef, useState } from 'react' import { useState } from 'react'
import { Meta, UploadResult } from '@uppy/core' import { Meta, UploadResult } from '@uppy/core'
import type { import type {
@ -55,13 +55,7 @@ const CREATE_RESUME_MUTATION: TypedDocumentNode<
` `
const ResumeForm = ({ resume }: ResumeFormProps) => { const ResumeForm = ({ resume }: ResumeFormProps) => {
const [fileId, _setFileId] = useState<string>(resume?.fileId) const [fileId, setFileId] = useState<string>(resume?.fileId)
const fileIdRef = useRef<string>(fileId)
const setFileId = (fileId: string) => {
_setFileId(fileId)
fileIdRef.current = fileId
}
const unloadAbortController = new AbortController() const unloadAbortController = new AbortController()
@ -95,7 +89,7 @@ const ResumeForm = ({ resume }: ResumeFormProps) => {
setFileId(result.successful[0]?.uploadURL) setFileId(result.successful[0]?.uploadURL)
window.addEventListener( window.addEventListener(
'beforeunload', 'beforeunload',
(e) => handleBeforeUnload(e, [fileIdRef.current]), (e) => handleBeforeUnload(e, [fileId]),
{ {
once: true, once: true,
signal: unloadAbortController.signal, signal: unloadAbortController.signal,

View File

@ -8,7 +8,7 @@ const DARK_THEME = 'dark'
const ThemeToggle = () => { const ThemeToggle = () => {
const [theme, setTheme] = useState( const [theme, setTheme] = useState(
localStorage.getItem('theme') ? localStorage.getItem('theme') : LIGHT_THEME localStorage.getItem('theme') ?? LIGHT_THEME
) )
const handleToggle = (e: React.ChangeEvent<HTMLInputElement>) => { const handleToggle = (e: React.ChangeEvent<HTMLInputElement>) => {
@ -37,13 +37,8 @@ const ThemeToggle = () => {
checked={theme === DARK_THEME} checked={theme === DARK_THEME}
onChange={handleToggle} onChange={handleToggle}
/> />
<Icon path={mdiWeatherSunny} className="swap-off size-8 text-warning" />
<Icon <Icon path={mdiWeatherNight} className="swap-on size-8 text-primary" />
path={mdiWeatherSunny}
className="swap-off size-8 text-yellow-500"
/>
<Icon path={mdiWeatherNight} className="swap-on size-8 text-blue-500" />
</label> </label>
) )
} }

View File

@ -23,6 +23,12 @@ export const Titles = ({ titles, className }: TitlesProps) => {
backDelay={1000} backDelay={1000}
startWhenVisible startWhenVisible
loop loop
onStringTyped={(pos, self) => {
if (pos === 0) {
self.stop()
setTimeout(() => self.start(), 2500)
}
}}
/> />
</> </>
)} )}

View File

@ -56,6 +56,9 @@ const TitlesForm = ({ titles }: TitlesFormProps) => {
return ( return (
<Form onSubmit={onSubmit} className="max-w-80 space-y-2"> <Form onSubmit={onSubmit} className="max-w-80 space-y-2">
<p className="text-center opacity-70">
The first one gets displayed for longer
</p>
{Array.from({ length: MAX_TITLES }).map((_, i) => ( {Array.from({ length: MAX_TITLES }).map((_, i) => (
<Label key={i} name={`title${i}`} className="form-control w-full"> <Label key={i} name={`title${i}`} className="form-control w-full">
<Label <Label

View File

@ -8,9 +8,8 @@ export const deleteFile = async (fileId: string) => {
}) })
} }
export const handleBeforeUnload = (_e: BeforeUnloadEvent, files: string[]) => { export const handleBeforeUnload = (_e: BeforeUnloadEvent, files: string[]) =>
batchDelete(files) batchDelete(files)
}
export const batchDelete = (files: string[]) => { export const batchDelete = (files: string[]) => {
for (const file of files) deleteFile(file) for (const file of files) deleteFile(file)

View File

@ -1,3 +1,7 @@
import { mdiCompass, mdiContacts } from '@mdi/js'
import Icon from '@mdi/react'
import { Link, routes } from '@redwoodjs/router'
import { Metadata } from '@redwoodjs/web' import { Metadata } from '@redwoodjs/web'
import TitlesCell from 'src/components/Title/TitlesCell' import TitlesCell from 'src/components/Title/TitlesCell'
@ -7,11 +11,27 @@ const HomePage = () => {
<> <>
<Metadata title="Home" /> <Metadata title="Home" />
<div className="hero min-h-64"> <div className="hero min-h-[calc(100vh-6rem)]">
<div className="hero-content"> <div className="hero-content flex flex-col gap-8">
<div className="max-w-xl text-center"> <div className="text-center">
<TitlesCell className="text-primary" /> <TitlesCell className="text-primary" />
</div> </div>
<div className="flex gap-2">
<Link
to={routes.projects()}
className="btn btn-primary btn-outline sm:btn-wide btn-lg"
>
<Icon path={mdiCompass} className="size-6" />
Explore
</Link>
<Link
to={routes.contact()}
className="btn btn-primary btn-outline sm:btn-wide btn-lg"
>
<Icon path={mdiContacts} className="size-6" />
Contact
</Link>
</div>
</div> </div>
</div> </div>
</> </>