-
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.
- Loading branch information
1 parent
42a4eda
commit 16ba1e7
Showing
11 changed files
with
2,200 additions
and
384 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
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
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
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,39 @@ | ||
/* eslint-disable @next/next/no-img-element */ | ||
import { signIn, signOut, useSession } from "next-auth/react"; | ||
|
||
export const Header = () => { | ||
const { data: sessionData } = useSession(); | ||
|
||
return ( | ||
<div className="navbar bg-primary text-primary-content"> | ||
<div className="flex-1 pl-5 text-3xl font-bold"> | ||
{sessionData?.user?.name ? `Notes for ${sessionData.user.name}` : ""} | ||
</div> | ||
<div className="flex-none gap-2"> | ||
<div className="dropdown-end dropdown"> | ||
{sessionData?.user ? ( | ||
<label | ||
tabIndex={0} | ||
className="btn-ghost btn-circle avatar btn" | ||
onClick={() => void signOut()} | ||
> | ||
<div className="w-10 rounded-full"> | ||
<img | ||
src={sessionData?.user?.image ?? ""} | ||
alt={sessionData?.user?.name ?? ""} | ||
/> | ||
</div> | ||
</label> | ||
) : ( | ||
<button | ||
className="btn-ghost rounded-btn btn" | ||
onClick={() => void signIn()} | ||
> | ||
Sign in | ||
</button> | ||
)} | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
}; |
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,41 @@ | ||
import { useState } from "react"; | ||
|
||
import ReactMarkdown from "react-markdown"; | ||
|
||
import { type RouterOutputs } from "../utils/api"; | ||
|
||
type Note = RouterOutputs["note"]["getAll"][0]; | ||
|
||
export const NoteCard = ({ | ||
note, | ||
onDelete, | ||
}: { | ||
note: Note; | ||
onDelete: () => void; | ||
}) => { | ||
const [isExpanded, setIsExpanded] = useState<boolean>(true); | ||
|
||
return ( | ||
<div className="card mt-5 border border-gray-200 bg-base-100 shadow-xl"> | ||
<div className="card-body m-0 p-3"> | ||
<div | ||
className={`collapse-arrow ${isExpanded ? "collapse-open" : "" | ||
} collapse`} | ||
onClick={() => setIsExpanded(!isExpanded)} | ||
> | ||
<div className="collapse-title text-xl font-bold">{note.title}</div> | ||
<div className="collapse-content"> | ||
<article className="prose lg:prose-xl"> | ||
<ReactMarkdown>{note.content}</ReactMarkdown> | ||
</article> | ||
</div> | ||
</div> | ||
<div className="card-actions mx-2 flex justify-end"> | ||
<button className="btn-warning btn-xs btn px-5" onClick={onDelete}> | ||
Delete | ||
</button> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
}; |
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,58 @@ | ||
import { useState } from "react"; | ||
|
||
import CodeMirror from "@uiw/react-codemirror"; | ||
import { markdown, markdownLanguage } from "@codemirror/lang-markdown"; | ||
import { languages } from "@codemirror/language-data"; | ||
|
||
export const NoteEditor = ({ | ||
onSave, | ||
}: { | ||
onSave: (note: { title: string; content: string }) => void; | ||
}) => { | ||
const [code, setCode] = useState<string>(""); | ||
const [title, setTitle] = useState<string>(""); | ||
|
||
return ( | ||
<div className="card mt-5 border border-gray-200 bg-base-100 shadow-xl"> | ||
<div className="card-body"> | ||
<h2 className="card-title"> | ||
<input | ||
type="text" | ||
placeholder="Note title" | ||
className="input-primary input input-lg w-full font-bold" | ||
value={title} | ||
onChange={(e) => setTitle(e.currentTarget.value)} | ||
/> | ||
</h2> | ||
<CodeMirror | ||
value={code} | ||
width="500px" | ||
height="30vh" | ||
minWidth="100%" | ||
minHeight="30vh" | ||
extensions={[ | ||
markdown({ base: markdownLanguage, codeLanguages: languages }), | ||
]} | ||
onChange={(value) => setCode(value)} | ||
className="border border-gray-300" | ||
/> | ||
</div> | ||
<div className="card-actions justify-end"> | ||
<button | ||
onClick={() => { | ||
onSave({ | ||
title, | ||
content: code, | ||
}); | ||
setCode(""); | ||
setTitle(""); | ||
}} | ||
className="btn-primary btn" | ||
disabled={title.trim().length === 0 || code.trim().length === 0} | ||
> | ||
Save | ||
</button> | ||
</div> | ||
</div> | ||
); | ||
}; |
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,83 +1,130 @@ | ||
import { useState } from "react"; | ||
import { type NextPage } from "next"; | ||
import Head from "next/head"; | ||
import Link from "next/link"; | ||
import { signIn, signOut, useSession } from "next-auth/react"; | ||
import { useSession } from "next-auth/react"; | ||
|
||
import { api } from "~/utils/api"; | ||
import { api, type RouterOutputs } from "../utils/api"; | ||
import { Header } from "../components/Header"; | ||
import { NoteEditor } from "../components/NoteEditor"; | ||
import { NoteCard } from "../components/NoteCard"; | ||
|
||
const Home: NextPage = () => { | ||
const hello = api.example.hello.useQuery({ text: "from tRPC" }); | ||
|
||
return ( | ||
<> | ||
<Head> | ||
<title>Create T3 App</title> | ||
<title>Notetaker</title> | ||
<meta name="description" content="Generated by create-t3-app" /> | ||
<link rel="icon" href="/favicon.ico" /> | ||
</Head> | ||
<main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]"> | ||
<div className="container flex flex-col items-center justify-center gap-12 px-4 py-16 "> | ||
<h1 className="text-5xl font-extrabold tracking-tight text-white sm:text-[5rem]"> | ||
Create <span className="text-[hsl(280,100%,70%)]">T3</span> App | ||
</h1> | ||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:gap-8"> | ||
<Link | ||
className="flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 text-white hover:bg-white/20" | ||
href="https://create.t3.gg/en/usage/first-steps" | ||
target="_blank" | ||
> | ||
<h3 className="text-2xl font-bold">First Steps →</h3> | ||
<div className="text-lg"> | ||
Just the basics - Everything you need to know to set up your | ||
database and authentication. | ||
</div> | ||
</Link> | ||
<Link | ||
className="flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 text-white hover:bg-white/20" | ||
href="https://create.t3.gg/en/introduction" | ||
target="_blank" | ||
> | ||
<h3 className="text-2xl font-bold">Documentation →</h3> | ||
<div className="text-lg"> | ||
Learn more about Create T3 App, the libraries it uses, and how | ||
to deploy it. | ||
</div> | ||
</Link> | ||
</div> | ||
<div className="flex flex-col items-center gap-2"> | ||
<p className="text-2xl text-white"> | ||
{hello.data ? hello.data.greeting : "Loading tRPC query..."} | ||
</p> | ||
<AuthShowcase /> | ||
</div> | ||
</div> | ||
<main> | ||
<Header /> | ||
<Content /> | ||
</main> | ||
</> | ||
); | ||
}; | ||
|
||
export default Home; | ||
|
||
const AuthShowcase: React.FC = () => { | ||
type Topic = RouterOutputs["topic"]["getAll"][0]; | ||
|
||
const Content: React.FC = () => { | ||
const { data: sessionData } = useSession(); | ||
|
||
const { data: secretMessage } = api.example.getSecretMessage.useQuery( | ||
const [selectedTopic, setSelectedTopic] = useState<Topic | null>(null); | ||
|
||
const { data: topics, refetch: refetchTopics } = api.topic.getAll.useQuery( | ||
undefined, // no input | ||
{ enabled: sessionData?.user !== undefined }, | ||
{ | ||
enabled: sessionData?.user !== undefined, | ||
onSuccess: (data) => { | ||
setSelectedTopic(selectedTopic ?? data[0] ?? null); | ||
}, | ||
} | ||
); | ||
|
||
const createTopic = api.topic.create.useMutation({ | ||
onSuccess: () => { | ||
void refetchTopics(); | ||
}, | ||
}); | ||
|
||
const { data: notes, refetch: refetchNotes } = api.note.getAll.useQuery( | ||
{ | ||
topicId: selectedTopic?.id ?? "", | ||
}, | ||
{ | ||
enabled: sessionData?.user !== undefined && selectedTopic !== null, | ||
} | ||
); | ||
|
||
const createNote = api.note.create.useMutation({ | ||
onSuccess: () => { | ||
void refetchNotes(); | ||
}, | ||
}); | ||
|
||
const deleteNote = api.note.delete.useMutation({ | ||
onSuccess: () => { | ||
void refetchNotes(); | ||
}, | ||
}); | ||
|
||
return ( | ||
<div className="flex flex-col items-center justify-center gap-4"> | ||
<p className="text-center text-2xl text-white"> | ||
{sessionData && <span>Logged in as {sessionData.user?.name}</span>} | ||
{secretMessage && <span> - {secretMessage}</span>} | ||
</p> | ||
<button | ||
className="rounded-full bg-white/10 px-10 py-3 font-semibold text-white no-underline transition hover:bg-white/20" | ||
onClick={sessionData ? () => void signOut() : () => void signIn()} | ||
> | ||
{sessionData ? "Sign out" : "Sign in"} | ||
</button> | ||
<div className="mx-5 mt-5 grid grid-cols-4 gap-2"> | ||
<div className="px-2"> | ||
<ul className="menu rounded-box w-56 bg-base-100 p-2"> | ||
{topics?.map((topic) => ( | ||
<li key={topic.id}> | ||
<a | ||
href="#" | ||
onClick={(evt) => { | ||
evt.preventDefault(); | ||
setSelectedTopic(topic); | ||
}} | ||
> | ||
{topic.title} | ||
</a> | ||
</li> | ||
))} | ||
</ul> | ||
<div className="divider"></div> | ||
<input | ||
type="text" | ||
placeholder="New Topic" | ||
className="input-bordered input input-sm w-full" | ||
onKeyDown={(e) => { | ||
if (e.key === "Enter") { | ||
createTopic.mutate({ | ||
title: e.currentTarget.value, | ||
}); | ||
e.currentTarget.value = ""; | ||
} | ||
}} | ||
/> | ||
</div> | ||
<div className="col-span-3"> | ||
<div> | ||
{notes?.map((note) => ( | ||
<div key={note.id} className="mt-5"> | ||
<NoteCard | ||
note={note} | ||
onDelete={() => void deleteNote.mutate({ id: note.id })} | ||
/> | ||
</div> | ||
))} | ||
</div> | ||
|
||
<NoteEditor | ||
onSave={({ title, content }) => { | ||
void createNote.mutate({ | ||
title, | ||
content, | ||
topicId: selectedTopic?.id ?? "", | ||
}); | ||
}} | ||
/> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
}; |
Oops, something went wrong.