Skip to content

Commit

Permalink
feat: fix based on usability testing feedback v2
Browse files Browse the repository at this point in the history
  • Loading branch information
mrevanzak committed Jun 27, 2024
1 parent 0a783b5 commit 6f5934d
Show file tree
Hide file tree
Showing 9 changed files with 298 additions and 229 deletions.
12 changes: 10 additions & 2 deletions apps/web/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,23 @@
"close": "Close",
"confirmation": "Are you sure you want to delete",
"changeTitleHistorySuccess": "Title '{name}' has been changed",
"deleteHistorySuccess": "History has been deleted"
"deleteHistorySuccess": "History has been deleted",
"theme": {
"toggle": "Toggle theme",
"light": "Light",
"dark": "Dark",
"system": "Sistem"
}
},
"Home": {
"title": "Tanya.in",
"chooseTopic": "To choose the topic",
"hint": "Ask a question with english",
"history": {
"show": "Show history",
"title": "History",
"empty": "No history"
"empty": "No history",
"new": "New chat"
},
"unsolvable": {
"title": "Unsolvable",
Expand Down
12 changes: 10 additions & 2 deletions apps/web/public/locales/id.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,23 @@
"close": "Tutup",
"confirmation": "Apakah Anda yakin untuk menghapus",
"changeTitleHistorySuccess": "Judul '{name}' berhasil diubah",
"deleteHistorySuccess": "Riwayat pertanyaan berhasil dihapus"
"deleteHistorySuccess": "Riwayat pertanyaan berhasil dihapus",
"theme": {
"toggle": "Ubah tema",
"light": "Terang",
"dark": "Gelap",
"system": "Sistem"
}
},
"Home": {
"title": "Tanya.in saja!",
"chooseTopic": "Untuk memilih topik",
"hint": "Tulis pertanyaan dengan bahasa Indonesia",
"history": {
"show": "Tampilkan riwayat",
"title": "Riwayat",
"empty": "Tidak ada riwayat pertanyaan"
"empty": "Tidak ada riwayat pertanyaan",
"new": "Chat baru"
},
"unsolvable": {
"title": "Tak terjawab",
Expand Down
237 changes: 60 additions & 177 deletions apps/web/src/app/(app)/@user/history.tsx
Original file line number Diff line number Diff line change
@@ -1,194 +1,37 @@
"use client";

import { useRef, useState } from "react";
import Link from "next/link";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { Drawer, DrawerContent, DrawerTrigger } from "@/components/drawer";
import { HistoryList } from "@/components/history-list";
import { ThemeToggle } from "@/components/theme-toggle";
import { api } from "@/trpc/react";
import {
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
Tab,
Tabs,
Tooltip,
useDisclosure,
} from "@nextui-org/react";
import { Tab, Tabs, Tooltip } from "@nextui-org/react";
import { useTranslations } from "next-intl";
import { FaChevronRight } from "react-icons/fa6";
import { FaChevronRight, FaPlus } from "react-icons/fa6";
import { IoMenu } from "react-icons/io5";
import { RiCheckFill, RiDeleteBin2Line, RiPencilLine } from "react-icons/ri";
import { useMediaQuery } from "usehooks-ts";

import { Button } from "@tanya.in/ui/button";
import { Card, CardContent } from "@tanya.in/ui/card";
import { Input } from "@tanya.in/ui/form";
import { toast } from "@tanya.in/ui/toast";
import { Card, CardContent, CardFooter } from "@tanya.in/ui/card";

function Content() {
const chatHistory = api.chat.get.useQuery({});
const unsolvable = api.chat.get.useQuery({ unsolvable: true });

function List(props: { chat: NonNullable<typeof chatHistory.data>[0] }) {
const t = useTranslations("Common");

const { isOpen, onOpenChange, onOpen } = useDisclosure();
const [edit, setEdit] = useState(false);
const utils = api.useUtils();

const deleteChat = api.chat.delete.useMutation();
const changeChatTitle = api.chat.changeTitle.useMutation();

const title = props.chat.title ?? props.chat.messages.at(0)?.content;
const titleRef = useRef<HTMLInputElement>(null);

function preventDefault(e: React.MouseEvent) {
e.preventDefault();
e.nativeEvent.stopImmediatePropagation();
}

return (
<>
<Tooltip
content={title}
placement="top-start"
delay={500}
closeDelay={0}
>
<Link
href={`?id=${props.chat.id}`}
className="flex items-center justify-between gap-2 rounded-r-md border-s-3 border-primary-its p-2 hover:bg-primary-its/20"
>
{edit ? (
<Input
ref={titleRef}
defaultValue={title}
onClick={(e) => {
preventDefault(e);
}}
/>
) : (
<p className="overflow-hidden text-ellipsis">{title}</p>
)}
<div className="">
{edit ? (
<Button
variant="light"
color="success"
isIconOnly
size="sm"
isLoading={changeChatTitle.isPending}
onClick={(e) => {
preventDefault(e);
if (!titleRef.current?.value) {
return;
}
changeChatTitle.mutate(
{
id: props.chat.id,
title: titleRef.current.value,
},
{
onSuccess: () => {
void utils.chat.get.invalidate();
toast.success(
t("changeTitleHistorySuccess", {
name: titleRef.current?.value,
}),
);
setEdit(false);
},
},
);
}}
>
<RiCheckFill />
</Button>
) : (
<>
<Button
variant="light"
color="default"
isIconOnly
size="sm"
onClick={(e) => {
preventDefault(e);
setEdit(!edit);
}}
>
<RiPencilLine />
</Button>
<Button
variant="light"
isIconOnly
color="danger"
size="sm"
onClick={(e) => {
preventDefault(e);
onOpen();
}}
>
<RiDeleteBin2Line />
</Button>
</>
)}
</div>
</Link>
</Tooltip>

<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent>
{(onClose) => (
<>
<ModalHeader>{t("delete")}</ModalHeader>
<ModalBody>
<p>
{t("confirmation")} "<b>{title}</b>"?
</p>
</ModalBody>
<ModalFooter>
<Button variant="light" onClick={onClose}>
{t("close")}
</Button>
<Button
color="danger"
onPress={() => {
deleteChat.mutate(
{
id: props.chat.id,
},
{
onSuccess: () => {
void utils.chat.invalidate();
toast.success(t("deleteHistorySuccess"));
onClose();
},
},
);
}}
isLoading={deleteChat.isPending}
>
{t("delete")}
</Button>
</ModalFooter>
</>
)}
</ModalContent>
</Modal>
</>
);
}

const t = useTranslations("Home");

return (
<Tabs color="warning" variant="bordered" fullWidth>
<Tabs color="warning" variant="solid" fullWidth>
<Tab title={t("history.title")} className="px-0">
{chatHistory.data?.length ? (
<ul className="h-full space-y-4">
{chatHistory.data.map((chat) => (
<List key={chat.id} chat={chat} />
<HistoryList
key={chat.id}
id={chat.id}
title={chat.title ?? chat.messages.at(0)?.content ?? ""}
/>
))}
</ul>
) : (
Expand All @@ -199,7 +42,11 @@ function Content() {
{unsolvable.data?.length ? (
<ul className="h-full space-y-4">
{unsolvable.data.map((chat) => (
<List key={chat.id} chat={chat} />
<HistoryList
key={chat.id}
id={chat.id}
title={chat.title ?? chat.messages.at(0)?.content ?? ""}
/>
))}
</ul>
) : (
Expand All @@ -212,8 +59,11 @@ function Content() {

export function ChatHistory() {
const [open, setOpen] = useState(false);
const router = useRouter();
const isMobile = useMediaQuery("(max-width: 768px)");

const t = useTranslations("Home.history");

if (isMobile) {
return (
<Drawer open={open} onOpenChange={setOpen}>
Expand All @@ -229,8 +79,21 @@ export function ChatHistory() {
<IoMenu className="size-5" />
</Button>
</DrawerTrigger>
<DrawerContent className="h-[40vh] max-h-dvh bg-content1">
<div className="overflow-auto p-6">
<DrawerContent className="min-h-[60vh] bg-content1">
<div className="space-y-4 overflow-auto p-6">
<Button
variant="bordered"
color="default"
fullWidth
onClick={() => {
setOpen(false);
router.push("/");
}}
>
<FaPlus />
{t("new")}
</Button>

<Content />
</div>
</DrawerContent>
Expand All @@ -240,11 +103,27 @@ export function ChatHistory() {

return (
<div
className="group relative hidden h-dvh w-80 self-center duration-500 transition-size data-[open=false]:w-0 sm:block"
className="group relative hidden h-dvh w-80 self-center duration-500 transition-size data-[open=false]:w-12 sm:block"
data-open={open}
>
<Card className="h-full overflow-x-hidden whitespace-nowrap rounded-lg rounded-s-none rounded-t-none">
<Tooltip content="Show Chat History" placement="bottom-end">
<Card className="flex h-full flex-col overflow-x-hidden whitespace-nowrap rounded-lg rounded-s-none rounded-t-none">
<div className="w-full self-center px-4 pt-3">
<Button
variant="ghost"
color="default"
fullWidth
className="relative min-w-10 justify-start overflow-clip px-0 transition-all group-data-[open=true]:min-w-20 group-data-[open=false]:-translate-x-3.5"
onClick={() => {
setOpen(false);
router.push("/");
}}
>
<FaPlus className="translate-x-3/4 transition-all" />
<span className="absolute left-9">{t("new")}</span>
</Button>
</div>

<Tooltip content={t("show")} placement="bottom-end">
<Button
className="absolute -right-7 top-1/4 z-10"
isIconOnly
Expand All @@ -257,9 +136,13 @@ export function ChatHistory() {
</Button>
</Tooltip>

<CardContent className="h-full p-4 pt-2">
<CardContent className="h-full flex-1 overflow-y-scroll p-4 pt-2 opacity-100 transition-opacity group-data-[open=false]:pointer-events-none group-data-[open=false]:opacity-0">
<Content />
</CardContent>

<CardFooter className="h-1/6 p-1 transition-transform group-data-[open=true]:translate-x-4">
<ThemeToggle />
</CardFooter>
</Card>
</div>
);
Expand Down
5 changes: 0 additions & 5 deletions apps/web/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { getLocale, getMessages } from "next-intl/server";
import NextTopLoader from "nextjs-toploader";

import { cn } from "@tanya.in/ui";
import { ThemeToggle } from "@tanya.in/ui/theme";
import { Toaster } from "@tanya.in/ui/toast";

import "@/styles/globals.css";
Expand Down Expand Up @@ -97,10 +96,6 @@ export default async function RootLayout(props: { children: React.ReactNode }) {
<Providers user={session?.user}>
{props.children}

<div className="fixed bottom-4 right-4">
<ThemeToggle />
</div>

<Toaster />
<Analytics />
<SpeedInsights />
Expand Down
Loading

0 comments on commit 6f5934d

Please sign in to comment.