-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from felipeog/prototype
Prototype
- Loading branch information
Showing
12 changed files
with
466 additions
and
141 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,18 @@ | ||
<!DOCTYPE html> | ||
|
||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Vite + React + TS</title> | ||
|
||
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> | ||
|
||
<title>Reddit Client</title> | ||
</head> | ||
|
||
<body> | ||
<div id="root"></div> | ||
|
||
<script type="module" src="/src/main.tsx"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { FormEvent, useRef, useState } from "react"; | ||
import { useBoardContext } from "./BoardContext"; | ||
import { useBoardExists } from "./useBoardExists"; | ||
|
||
export const ADD_BOARD_DIALOG_HEIGHT = "40px"; | ||
|
||
export function AddBoardDialog() { | ||
const [board, setBoard] = useState(""); | ||
const inputRef = useRef<HTMLInputElement>(null); | ||
const dialogRef = useRef<HTMLDialogElement>(null); | ||
const { checkBoardExistance, isLoading } = useBoardExists(); | ||
const { boardContext } = useBoardContext(); | ||
|
||
async function handleFormSubmit(event: FormEvent) { | ||
event.preventDefault(); | ||
|
||
const doesBoardExist = await checkBoardExistance(board); | ||
|
||
if (!doesBoardExist) { | ||
return alert("This board does not exist"); | ||
} | ||
|
||
boardContext.addBoard(board); | ||
closeDialog(); | ||
setBoard(""); | ||
} | ||
|
||
function openDialog() { | ||
dialogRef.current?.showModal(); | ||
inputRef.current?.focus(); | ||
} | ||
|
||
function closeDialog() { | ||
dialogRef.current?.close(); | ||
} | ||
|
||
return ( | ||
<div | ||
className="AddBoardDialog" | ||
style={{ | ||
height: ADD_BOARD_DIALOG_HEIGHT, | ||
display: "flex", | ||
placeContent: "center", | ||
padding: 8, | ||
borderBottom: `1px solid var(--neutral-60)`, | ||
}} | ||
> | ||
<button onClick={openDialog}>Add board</button> | ||
|
||
<dialog | ||
ref={dialogRef} | ||
style={{ | ||
padding: "revert", | ||
margin: "revert", | ||
backgroundColor: "var(--neutral-90)", | ||
color: "var(--neutral-10)", | ||
borderColor: "var(--neutral-60)", | ||
}} | ||
> | ||
<button onClick={closeDialog} disabled={isLoading}> | ||
Close | ||
</button> | ||
|
||
<form onSubmit={handleFormSubmit}> | ||
<p>Enter the name of the board</p> | ||
<input | ||
ref={inputRef} | ||
type="text" | ||
name="board" | ||
required | ||
disabled={isLoading} | ||
value={board} | ||
onChange={(event) => setBoard(event.target.value)} | ||
/> | ||
<button disabled={isLoading}>Add board</button> | ||
</form> | ||
</dialog> | ||
</div> | ||
); | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,11 @@ | ||
import { useState } from "react"; | ||
import reactLogo from "./assets/react.svg"; | ||
import viteLogo from "/vite.svg"; | ||
import "./App.css"; | ||
|
||
function App() { | ||
const [count, setCount] = useState(0); | ||
import { AddBoardDialog } from "./AddBoardDialog"; | ||
import { LanesContainer } from "./LanesContainer"; | ||
|
||
export function App() { | ||
return ( | ||
<> | ||
<div> | ||
<a href="https://vitejs.dev" target="_blank"> | ||
<img src={viteLogo} className="logo" alt="Vite logo" /> | ||
</a> | ||
<a href="https://react.dev" target="_blank"> | ||
<img src={reactLogo} className="logo react" alt="React logo" /> | ||
</a> | ||
</div> | ||
<h1>Vite + React</h1> | ||
<div className="card"> | ||
<button onClick={() => setCount((count) => count + 1)}> | ||
count is {count} | ||
</button> | ||
<p> | ||
Edit <code>src/App.tsx</code> and save to test HMR | ||
</p> | ||
</div> | ||
<p className="read-the-docs"> | ||
Click on the Vite and React logos to learn more | ||
</p> | ||
</> | ||
<div className="App"> | ||
<AddBoardDialog /> | ||
<LanesContainer /> | ||
</div> | ||
); | ||
} | ||
|
||
export default App; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import { | ||
createContext, | ||
ReactNode, | ||
useCallback, | ||
useContext, | ||
useEffect, | ||
useState, | ||
} from "react"; | ||
|
||
const LOCAL_STORAGE_KEY = "@felipeog/rm-reddit-client/boards"; | ||
|
||
type TBoardContext = { | ||
boards: string[]; | ||
addBoard: (board: string) => void; | ||
removeBoard: (board: string) => void; | ||
reorderBoard: (from: string, to: string) => void; | ||
}; | ||
|
||
type TBoardContextProviderProps = { | ||
children: ReactNode; | ||
}; | ||
|
||
const BoardContext = createContext<TBoardContext>(null! as TBoardContext); | ||
|
||
export function BoardContextProvider(props: TBoardContextProviderProps) { | ||
const [boards, setBoards] = useState<string[]>(() => { | ||
const boardsString = localStorage.getItem(LOCAL_STORAGE_KEY) || "[]"; | ||
return JSON.parse(boardsString); | ||
}); | ||
|
||
const addBoard = useCallback( | ||
(board: string) => | ||
setBoards((prevBoards) => { | ||
if (prevBoards.includes(board)) return prevBoards; | ||
return [...prevBoards, board]; | ||
}), | ||
[] | ||
); | ||
|
||
const removeBoard = useCallback( | ||
(board: string) => | ||
setBoards((prevBoards) => | ||
prevBoards.filter((prevBoard) => prevBoard !== board) | ||
), | ||
[] | ||
); | ||
|
||
const reorderBoard = useCallback( | ||
(from: string, to: string) => | ||
setBoards((prevBoards) => { | ||
const boards = [...prevBoards]; | ||
const fromIndex = boards.indexOf(from); | ||
const toIndex = boards.indexOf(to); | ||
|
||
boards.splice(fromIndex, 1); | ||
boards.splice(toIndex, 0, from); | ||
|
||
return boards; | ||
}), | ||
[] | ||
); | ||
|
||
useEffect(() => { | ||
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(boards)); | ||
}, [boards]); | ||
|
||
return ( | ||
<BoardContext.Provider | ||
value={{ | ||
boards, | ||
addBoard, | ||
removeBoard, | ||
reorderBoard, | ||
}} | ||
> | ||
{props.children} | ||
</BoardContext.Provider> | ||
); | ||
} | ||
|
||
export function useBoardContext() { | ||
const boardContext = useContext(BoardContext); | ||
|
||
if (!boardContext) { | ||
throw new Error( | ||
"useBoardContext has to be used within BoardContextProvider." | ||
); | ||
} | ||
|
||
return { boardContext }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import { ADD_BOARD_DIALOG_HEIGHT } from "./AddBoardDialog"; | ||
import { useBoardContent } from "./useBoardContent"; | ||
import { useBoardContext } from "./BoardContext"; | ||
import { formatDate } from "./formatDate"; | ||
import { DragEvent } from "react"; | ||
|
||
export function LanesContainer() { | ||
const { boardContext } = useBoardContext(); | ||
|
||
return ( | ||
<div | ||
className="LanesContainer" | ||
style={{ | ||
display: "flex", | ||
height: `calc(100vh - ${ADD_BOARD_DIALOG_HEIGHT})`, | ||
overflowX: "scroll", | ||
}} | ||
> | ||
{boardContext.boards.map((board) => ( | ||
<Lane key={board} board={board} /> | ||
))} | ||
</div> | ||
); | ||
} | ||
|
||
type TLaneProps = { | ||
board: string; | ||
}; | ||
|
||
function Lane(props: TLaneProps) { | ||
const { boardContext } = useBoardContext(); | ||
const { boardContent, isLoading, loadMore } = useBoardContent(props.board); | ||
|
||
function onDragStart(event: DragEvent<HTMLDivElement>) { | ||
event.dataTransfer.setData("text/plain", props.board); | ||
} | ||
|
||
function onDragEnter(event: DragEvent<HTMLDivElement>) { | ||
event.preventDefault(); | ||
} | ||
|
||
function onDragOver(event: DragEvent<HTMLDivElement>) { | ||
event.preventDefault(); | ||
} | ||
|
||
function onDrop(event: DragEvent<HTMLDivElement>) { | ||
const board = event.dataTransfer.getData("text/plain"); | ||
if (board !== props.board) { | ||
boardContext.reorderBoard(board, props.board); | ||
} | ||
} | ||
|
||
return ( | ||
<div | ||
className="Lane" | ||
style={{ | ||
width: "30vw", | ||
minWidth: "30vw", | ||
height: "100%", | ||
borderRight: "1px solid var(--neutral-60)", | ||
overflowY: "scroll", | ||
}} | ||
draggable | ||
onDragStart={onDragStart} | ||
onDragEnter={onDragEnter} | ||
onDragOver={onDragOver} | ||
onDrop={onDrop} | ||
> | ||
<div | ||
style={{ | ||
position: "sticky", | ||
top: 0, | ||
backgroundColor: "var(--neutral-90)", | ||
paddingBottom: 10, | ||
}} | ||
> | ||
<h1> | ||
<a | ||
href={`https://www.reddit.com/r/${props.board}`} | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
{props.board} | ||
</a> | ||
</h1> | ||
<button onClick={() => boardContext.removeBoard(props.board)}> | ||
Remove board | ||
</button> | ||
</div> | ||
|
||
<div style={{ backgroundColor: "var(--neutral-90)" }}> | ||
{boardContent.length > 0 | ||
? boardContent.map((item) => ( | ||
<div key={item.name}> | ||
<p> | ||
Title:{" "} | ||
<a href={item.url} target="_blank" rel="noopener noreferrer"> | ||
{item.title} | ||
</a> | ||
</p> | ||
<p>Author: {item.author}</p> | ||
<p>Created: {formatDate(item.created * 1000)}</p> | ||
<p> | ||
Ups/Downs: {item.ups}/{item.downs} | ||
</p> | ||
<br /> | ||
</div> | ||
)) | ||
: null} | ||
</div> | ||
|
||
<button onClick={loadMore} disabled={isLoading}> | ||
Load more | ||
</button> | ||
</div> | ||
); | ||
} |
Oops, something went wrong.