Skip to content
Open
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
36 changes: 36 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
38 changes: 38 additions & 0 deletions app/components/FeedPost.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Image from "next/image";
import Post from "../model/Post";

interface PostProps {
post: Post;
postDeletedHandler: (post: Post) => void;
}

export default function FeedPost({post, postDeletedHandler}: PostProps) {

return (
<div className="w-full h-fit border-[1px] border-gray-1 bg-gray-3">

<div className='p-3 flex justify-end'>
<Image src="/close-circle-svgrepo-com.svg" alt="Image placeholder" className="cursor-pointer" onClick={_ => postDeletedHandler(post)} width={24} height={24} priority />
</div>

<div className="mx-auto h-full md:flex flex-col md:flex-row md:ml-0 md:gap-x-8 md:px-8 md:pb-8 md:pt-4 gap-x-4 px-4 pb-4 pt-2">
<div className="w-full md:w-1/5 flex justify-center md:justify-start md:ml-0">
<div className="w-24 h-24 flex rounded-full border-[1px] border-neutral-500">
{ post.image == ""
? <Image src="/user-5-svgrepo-com.svg" alt="Image placeholder" className="mx-auto" width={64} height={64} priority/>
: <Image src={post.image} alt="User avatar" className="rounded-full object-cover" width={128} height={128} />
}
</div>
</div>
<div className='h-auto flex flex-col justify-between md:w-4/5 w-full md:mt-0 mt-4'>
<p className="md:text-left text-center text-neutral-400">{post.text}</p>
<p className='md:text-left text-center text-xs font-semibold text-gray-0 mt-4'>
Enviado por <br/>
<span className='text-sm font-light text-neutral-400'>{post.author}</span>
</p>
</div>
</div>
</div>
)

}
100 changes: 100 additions & 0 deletions app/components/PostForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { v4 as uuidv4 } from 'uuid';
import Image from "next/image";
import { useRef, useState } from "react";
import Post from "../model/Post";

interface PostFormProps {
postSubmitedHandler: (post: Post) => void;
}

export default function PostForm({
postSubmitedHandler
}: PostFormProps) {

const imageUploadRef = useRef(null);

const [username, setUsername] = useState<string>("");
const [text, setText] = useState<string>("");
const [imagePreview, setImagePreview] = useState<any>("");

const handleImageClick = () => {
imageUploadRef.current.click();
};

const submit = () => {
if(username === "" || text === "") {
return;
}
let id = uuidv4();
postSubmitedHandler({ id: id, author: username, text: text, image: imagePreview });
setImagePreview("");
setUsername("");
setText("");
}

const clearImage = () => {
setImagePreview("");
}

const discardPost = () => {
setImagePreview("");
setUsername("");
setText("");
}

const handleUsernameChange = (e: any) => setUsername(e.target.value);

const handleTextChange = (e: any) => setText(e.target.value);

const handleImageChange = (e: any) => {
e.preventDefault();
const reader = new FileReader();
const file = e.target.files[0];
if (reader !== undefined && file !== undefined) {
reader.onloadend = () => {
setImagePreview(reader.result);
}
reader.readAsDataURL(file);
}
}

const canBeSubmited = (): boolean => {
return username != "" && text != ""
}

return (
<div>
<input title="File input" ref={imageUploadRef} className="hidden invisible" type="file" name="avatar" accept=".jpef, .png, .jpg" onChange={handleImageChange} />

<div id="formContainer" className="mx-auto border-[1px] border-gray-1 bg-gray-3 w-full md:w-2/5 h-fit p-8 flex flex-col gap-y-4">

<div className="mx-auto gap-x-4 flex bg-opacity-0">
<div onClick={handleImageClick} className="w-32 h-32 flex rounded-full border-[1px] border-neutral-500 cursor-pointer">
{imagePreview == ""
? <Image src="/landscape-focus-svgrepo-com.svg" alt="Image placeholder" className="mx-auto" width={64} height={64} priority />
: <Image src={imagePreview} alt="User avatar" className="rounded-full object-cover" width={128} height={128} />
}
</div>
{imagePreview != "" && <Image src="/trash-1-svgrepo-com.svg" alt="Image placeholder" className="cursor-pointer" onClick={clearImage} width={32} height={32} priority />}
</div>


<input type="text" placeholder="Digite seu nome"
className="text-white text-sm outline-none bg-gray-1 p-2 rounded-md"
value={username} onChange={handleUsernameChange} />

<textarea placeholder="Mensagem"
className="text-white text-sm outline-none bg-gray-1 p-2 rounded-md"
value={text} onChange={handleTextChange} />

<div className="mt-4 ml-auto flex gap-x-4">
<a onClick={discardPost} className="my-auto text-sm underline text-gray-1 cursor-pointer">Descartar</a>
<button onClick={submit} className={`text-sm px-4 py-2 rounded-lg ${canBeSubmited() ? 'bg-lime-500 text-white' : 'bg-gray-1 text-gray-0'}`}>Publicar</button>
</div>

</div>

</div>
)

}
Binary file added app/favicon.ico
Binary file not shown.
33 changes: 33 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}

@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}

body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}

@layer utilities {
.text-balance {
text-wrap: balance;
}
}
22 changes: 22 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
);
}
6 changes: 6 additions & 0 deletions app/model/Post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default interface Post {
id: string;
author: string;
text: string;
image: any;
}
42 changes: 42 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"use client";

import Image from "next/image";
import { useState } from "react";
import Post from './model/Post';
import FeedPost from './components/FeedPost';
import PostForm from "./components/PostForm";

export default function Home() {

const [posts, setPosts] = useState<Post[]>([]);

const postSubmitedHandler = (post: Post) => {
setPosts([...posts, post])
}

const postDeletedHandler = (post: Post) => {
setPosts(posts.filter(x => x.id != post.id))
}

return (
<main className="flex relative min-h-screen flex-col gap-y-16 items-center p-8 md:p-24 bg-gray-2">

<div className="fixed py-6 top-0 w-full bg-gray-4">
<p className="my-auto font-extrabold text-center text-2xl text-lime-500">buildbox</p>
<p className="font-light text-neutral-500 text-center text-xs">WEB CHALLENGE</p>
</div>

<div className="w-full md:mt-8 mt-24 flex flex-col gap-y-16">

<PostForm postSubmitedHandler={postSubmitedHandler}/>

<div id="feed" className="mx-auto flex flex-col w-full md:w-2/5">
<p className="text-gray-0 text-md font-bold mb-2">Feed</p>
<div className="flex flex-col gap-y-4">
{ posts.map(post => <FeedPost post={post} postDeletedHandler={postDeletedHandler} key={post.id}/>) }
</div>
</div>
</div>
</main>
);
}
4 changes: 4 additions & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};

export default nextConfig;
Loading