Skip to content

Commit

Permalink
feat: implement image viewer
Browse files Browse the repository at this point in the history
  • Loading branch information
solufa committed May 10, 2024
1 parent c9eca8e commit 43fb393
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 39 deletions.
4 changes: 3 additions & 1 deletion client/pages/@components/BasicHeader/BasicHeader.module.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
.container {
position: sticky;
top: 0;
height: 60px;
background: #fafafa;
background: #fff;
border-bottom: 1px solid #eee;
}

Expand Down
75 changes: 63 additions & 12 deletions client/pages/index.module.css
Original file line number Diff line number Diff line change
@@ -1,22 +1,73 @@
.container {
min-height: 100vh;
padding: 40px 16px;
background: #f8f8f8;
}

.main {
display: flex;
flex-direction: column;
gap: 32px;
width: 640px;
max-width: 100%;
margin: 0 auto;
margin-top: 32px;
}

.card {
overflow: hidden;
background: #fff;
border-radius: 8px;
box-shadow: 2px 2px 12px #0004;
}

@keyframes gradient {
0% {
background-position: 0% 50%;
}

50% {
background-position: 100% 50%;
}

100% {
background-position: 0% 50%;
}
}

.title {
padding-top: 80px;
font-size: 80px;
font-weight: bold;
font-weight: 900;
color: transparent;
text-align: center;
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
background-clip: text;
background-size: 200% 200%;
animation: gradient 10s ease infinite;
}

.controls {
display: flex;
gap: 8px;
align-items: center;
padding: 12px 16px;
}

.tasks {
width: 300px;
padding: 0;
margin: 20px auto 0;
text-align: left;
list-style-type: none;
.textInput {
width: 200px;
padding: 4px 8px;
border-radius: 4px;
}

.tasks > li {
margin-top: 10px;
border-bottom: 1px solid #eee;
.btn {
padding: 4px 8px;
margin-left: auto;
cursor: pointer;
border-radius: 4px;
}

.deleteBtn {
float: right;
.taskImage {
width: 100%;
vertical-align: bottom;
}
91 changes: 65 additions & 26 deletions client/pages/index.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,39 @@ import { Loading } from 'components/Loading/Loading';
import { useAtom } from 'jotai';
import { BasicHeader } from 'pages/@components/BasicHeader/BasicHeader';
import type { ChangeEvent, FormEvent } from 'react';
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { apiClient } from 'utils/apiClient';
import { returnNull } from 'utils/returnNull';
import { userAtom } from '../atoms/user';
import styles from './index.module.css';

const Home = () => {
const [user] = useAtom(userAtom);
const fileRef = useRef<HTMLInputElement | null>(null);
const [tasks, setTasks] = useState<TaskEntity[]>();
const [label, setLabel] = useState('');
const [image, setImage] = useState<File>();
const [previewImageUrl, setPreviewImageUrl] = useState<string>();
const inputLabel = (e: ChangeEvent<HTMLInputElement>) => {
setLabel(e.target.value);
};
const inputFile = (e: ChangeEvent<HTMLInputElement>) => {
setImage(e.target.files?.[0]);
};
const fetchTasks = async () => {
const tasks = await apiClient.private.tasks.$get().catch(returnNull);

if (tasks !== null) setTasks(tasks);
};
const createTask = async (e: FormEvent) => {
e.preventDefault();
if (!label) return;
if (!fileRef.current) return;

await apiClient.private.tasks.post({ body: { label } }).catch(returnNull);
await apiClient.private.tasks.post({ body: { label, image } }).catch(returnNull);
setLabel('');
setImage(undefined);
setPreviewImageUrl(undefined);
fileRef.current.value = '';
await fetchTasks();
};
const toggleDone = async (task: TaskEntity) => {
Expand All @@ -47,35 +56,65 @@ const Home = () => {
fetchTasks();
}, [user]);

useEffect(() => {
if (!image) return;

const newUrl = URL.createObjectURL(image);
setPreviewImageUrl(newUrl);
return () => {
URL.revokeObjectURL(newUrl);
};
}, [image]);

if (!tasks || !user) return <Loading visible />;

return (
<>
<BasicHeader user={user} />
<div className={styles.title} style={{ marginTop: '160px' }}>
Welcome to frourio!
</div>
<div className={styles.container}>
<div className={styles.title}>Welcome to frourio!</div>

<form style={{ textAlign: 'center', marginTop: '80px' }} onSubmit={createTask}>
<input value={label} type="text" onChange={inputLabel} />
<input type="submit" value="ADD" />
</form>
<ul className={styles.tasks}>
{tasks.map((task) => (
<li key={task.id}>
<label>
<input type="checkbox" checked={task.done} onChange={() => toggleDone(task)} />
<span>{task.label}</span>
</label>
<input
type="button"
value="DELETE"
className={styles.deleteBtn}
onClick={() => deleteTask(task)}
/>
</li>
))}
</ul>
<div className={styles.main}>
<div className={styles.card}>
<form onSubmit={createTask}>
{previewImageUrl && <img src={previewImageUrl} className={styles.taskImage} />}
<div className={styles.controls}>
<input
value={label}
className={styles.textInput}
type="text"
placeholder="Todo task"
onChange={inputLabel}
/>
<input
type="file"
ref={fileRef}
accept=".png,.jpg,.jpeg,.gif,.webp,.svg"
onChange={inputFile}
/>
<input className={styles.btn} disabled={label === ''} type="submit" value="ADD" />
</div>
</form>
</div>
{tasks.map((task) => (
<div key={task.id} className={styles.card}>
{task.image && (
<img src={task.image.url} alt={task.label} className={styles.taskImage} />
)}
<div className={styles.controls}>
<input type="checkbox" checked={task.done} onChange={() => toggleDone(task)} />
<span>{task.label}</span>
<input
type="button"
value="DELETE"
className={styles.btn}
onClick={() => deleteTask(task)}
/>
</div>
</div>
))}
</div>
</div>
</>
);
};
Expand Down

0 comments on commit 43fb393

Please sign in to comment.