update toaster LO; add forms where in necessary

This commit is contained in:
yzned
2025-07-20 12:38:04 +03:00
committed by yzned
parent ee83aefabc
commit 39b3a837df
10 changed files with 151 additions and 88 deletions

View File

@@ -3,7 +3,12 @@ import { useRef, useState } from "react";
import Arrow from "@/icons/arrow.svg?react";
import Paperclip from "@/icons/paperclip.svg?react";
import Clock from "@/icons/clock.svg?react";
import { cn } from "@/lib/utils";
import { useNavigate } from "@tanstack/react-router";
import { useAccountStore } from "@/contexts/AccountContext";
import { Button } from "../ui/button";
import { useToast } from "@/lib/hooks/use-toast";
const Chat = observer(() => {
return (
@@ -14,6 +19,10 @@ const Chat = observer(() => {
});
const WelcomePanel = () => {
const navigate = useNavigate();
const { userId } = useAccountStore();
const { toast } = useToast();
return (
<div className="max-w-[741px] flex flex-col gap-8">
<div className="flex flex-col gap-2 font-medium">
@@ -42,6 +51,14 @@ const WelcomePanel = () => {
<button
type="button"
key={emoji}
onClick={() => {
if (userId === undefined) {
navigate({
to: "/auth/mail",
viewTransition: { types: ["warp"] },
});
}
}}
className="text-left cursor-pointer hover:bg-fill-100 transition-all w-[173px] h-[123px] p-4 rounded-[16px] border border-[#E8E8E8] flex flex-col justify-between"
>
<p className="text-[14px] font-medium leading-[120%]">{text}</p>
@@ -57,6 +74,9 @@ const WelcomePanel = () => {
};
const ChatInput = () => {
const navigate = useNavigate();
const { userId } = useAccountStore();
const [message, setMessage] = useState("");
const [showShadow, setShowShadow] = useState(false);
const textareaRef = useRef<HTMLTextAreaElement>(null);
@@ -116,14 +136,20 @@ const ChatInput = () => {
</div>
<div className="flex items-center gap-4">
<button
type="button"
<Button
className="w-10 h-10 rotate-180"
disabled={message.trim() === ""}
className="w-10 h-10 rounded-[8px] flex items-center justify-center rotate-180 disabled:opacity-50 disabled:cursor-not-allowed"
style={{ backgroundColor: "#E8E8E8" }}
onClick={() => {
if (userId === undefined) {
navigate({
to: "/auth/mail",
viewTransition: { types: ["warp"] },
});
}
}}
>
<Arrow color="white" />
</button>
</Button>
</div>
</div>
</div>

View File

@@ -9,7 +9,7 @@ const buttonVariants = cva(
variants: {
variant: {
primary:
"bg-fill-800 text-white hover:bg-fill-700 disabled:bg-fill-400",
"bg-fill-800 text-white hover:bg-fill-700 disabled:bg-fill-200",
secondary:
"bg-fill-100 text-text-light-900 hover:bg-fill-150 disabled:bg-fill-100 disabled:text-text-light-200",
tertiary:

View File

@@ -17,7 +17,7 @@ export const TextLink: FC<TextLinkProps> = ({
<a
data-disabled={disabled}
className={cn(
"text-feedback-info-900 data-[disabled=true]:opacity-50 font-[600] data-[disabled=false]:cursor-pointer data-[disabled=false]:hover:border-b-[2px] w-fit transition-all duration-75",
"text-feedback-info-900 data-[disabled=true]:opacity-50 font-[500] data-[disabled=false]:cursor-pointer data-[disabled=false]:hover:border-b-[2px] w-fit transition-all duration-75",
className,
)}
{...props}

View File

@@ -13,6 +13,10 @@ import { cn } from "@/lib/utils";
import clsx from "clsx";
import { useToast } from "@/lib/hooks/use-toast";
import Discord from "@/icons/discord.svg?react";
import Copy from "@/icons/copy.svg?react";
import { TextLink } from "./text-link";
const ToastProvider = ToastPrimitives.Provider;
const ToastViewport = React.forwardRef<
@@ -35,10 +39,10 @@ const toastVariants = cva(
{
variants: {
variant: {
success: "bg-feedback-positive-100",
warning: "bg-feedback-attention-100",
error: "bg-feedback-negative-100",
info: "bg-feedback-info-100",
success: "bg-fill-100",
warning: "bg-fill-100",
error: "bg-fill-100",
info: "bg-fill-100",
},
},
defaultVariants: {
@@ -51,7 +55,7 @@ const ToastIcon = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
VariantProps<typeof toastVariants>
>(({ className, variant }) => {
>(({ className, variant = "success" }) => {
if (variant === "success") {
return (
<SuccessIcon className={clsx(className, "text-feedback-positive-900")} />
@@ -213,7 +217,7 @@ type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
type ToastActionElement = React.ReactElement<typeof ToastAction>;
export function Toaster() {
function Toaster() {
const { toasts, isPaused, setIsPaused } = useToast();
React.useEffect(() => {
@@ -258,28 +262,12 @@ export function Toaster() {
>
<ToastIcon className="size-12 mr-4" variant={props.variant} />
<div className="grid gap-2 w-full">
{title && <ToastTitle>{title}</ToastTitle>}
<div className="flex items-center w-full justify-between">
{title && <ToastTitle>{title}</ToastTitle>} <ToastClose />
</div>
{description && (
<ToastDescription>
{/* {props.variant === "errorWithCopy" && (
<p className="text-[16px] mb-2">
To solve problems, contact us <br />
via{" "}
<a
href={"https://discord.gg/cytonic"}
target="_blank"
rel="noreferrer"
className="text-brand relative"
>
Discord
<div className="h-[1px] w-[56px] bg-brand absolute left-0" />
</a>{" "}
and send the error text
</p>
)} */}
{description}
</ToastDescription>
<ToastDescription>{description}</ToastDescription>
)}
{copy && (
@@ -287,7 +275,6 @@ export function Toaster() {
)}
</div>
{action}
<ToastClose />
{withTimeline && (
<ToastTimeline
@@ -304,6 +291,35 @@ export function Toaster() {
);
}
const TOASTER_DESCRIPTION_PATTERNS = {
errorPattern: (
<div>
<span>
<p>Please try again later</p>
<p className="mt-2">
If the problem persists, copy the error message below and contact our
support team
</p>
</span>
<div className="space-x-4 flex">
<TextLink className="flex items-center gap-1">
<Copy className="text-feedback-info-900" width={15} height={12} />[
Error code ]
</TextLink>
<TextLink
className="flex items-center gap-1"
href="https://discord.gg/cytonic"
>
<Discord className="text-feedback-info-900" width={15} height={12} />[
Support ]
</TextLink>
</div>
</div>
),
};
export {
type ToastProps,
type ToastActionElement,
@@ -317,4 +333,6 @@ export {
ToastIcon,
ToastCopy,
ToastTimeline,
Toaster,
TOASTER_DESCRIPTION_PATTERNS,
};

3
src/icons/discord.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="24" height="18" viewBox="0 0 24 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.9831 1.50753C18.4748 0.794022 16.8791 0.287044 15.2381 0C15.0337 0.37359 14.7949 0.87607 14.6303 1.27582C12.8611 1.00684 11.1081 1.00684 9.37143 1.27582C9.20684 0.876161 8.96264 0.37359 8.75636 0C7.11383 0.287168 5.51674 0.795438 4.00767 1.51126C1.00474 6.09907 0.190662 10.5728 0.597654 14.9832C2.5894 16.4869 4.5196 17.4004 6.41728 17.9982C6.88893 17.3425 7.30584 16.6483 7.66368 15.9228C6.98233 15.6605 6.32548 15.3373 5.70096 14.9571C5.86529 14.8339 6.02577 14.7055 6.18216 14.5722C9.96656 16.3618 14.0785 16.3618 17.8178 14.5722C17.9749 14.7047 18.1353 14.833 18.2989 14.9571C17.6734 15.3384 17.0153 15.6622 16.3326 15.9247C16.6925 16.6531 17.1087 17.348 17.579 18C19.4785 17.4023 21.4105 16.4888 23.4022 14.9832C23.8798 9.87048 22.5865 5.43781 19.9831 1.50753ZM8.1793 12.2709C7.04322 12.2709 6.11154 11.1986 6.11154 9.89292C6.11154 8.58722 7.02337 7.51313 8.1793 7.51313C9.33523 7.51313 10.2669 8.58531 10.247 9.89292C10.2488 11.1986 9.33531 12.2709 8.1793 12.2709ZM15.8206 12.2709C14.6845 12.2709 13.7529 11.1986 13.7529 9.89292C13.7529 8.58722 14.6647 7.51313 15.8206 7.51313C16.9766 7.51313 17.9082 8.58531 17.8883 9.89292C17.8883 11.1986 16.9766 12.2709 15.8206 12.2709Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -12,7 +12,7 @@ import { Route as rootRouteImport } from './routes/__root'
import { Route as InteractionPanelRouteImport } from './routes/interaction-panel'
import { Route as AuthRouteImport } from './routes/auth'
import { Route as IndexRouteImport } from './routes/index'
import { Route as MailCodeMailCodeRouteImport } from './routes/mail-code/$mail-code'
import { Route as MagicLinkMagicLinkRouteImport } from './routes/magic-link/$magic-link'
import { Route as AuthUsernameRouteImport } from './routes/auth/username'
import { Route as AuthMailRouteImport } from './routes/auth/mail'
import { Route as AuthCodeRouteImport } from './routes/auth/code'
@@ -32,9 +32,9 @@ const IndexRoute = IndexRouteImport.update({
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
const MailCodeMailCodeRoute = MailCodeMailCodeRouteImport.update({
id: '/mail-code/$mail-code',
path: '/mail-code/$mail-code',
const MagicLinkMagicLinkRoute = MagicLinkMagicLinkRouteImport.update({
id: '/magic-link/$magic-link',
path: '/magic-link/$magic-link',
getParentRoute: () => rootRouteImport,
} as any)
const AuthUsernameRoute = AuthUsernameRouteImport.update({
@@ -60,7 +60,7 @@ export interface FileRoutesByFullPath {
'/auth/code': typeof AuthCodeRoute
'/auth/mail': typeof AuthMailRoute
'/auth/username': typeof AuthUsernameRoute
'/mail-code/$mail-code': typeof MailCodeMailCodeRoute
'/magic-link/$magic-link': typeof MagicLinkMagicLinkRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
@@ -69,7 +69,7 @@ export interface FileRoutesByTo {
'/auth/code': typeof AuthCodeRoute
'/auth/mail': typeof AuthMailRoute
'/auth/username': typeof AuthUsernameRoute
'/mail-code/$mail-code': typeof MailCodeMailCodeRoute
'/magic-link/$magic-link': typeof MagicLinkMagicLinkRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
@@ -79,7 +79,7 @@ export interface FileRoutesById {
'/auth/code': typeof AuthCodeRoute
'/auth/mail': typeof AuthMailRoute
'/auth/username': typeof AuthUsernameRoute
'/mail-code/$mail-code': typeof MailCodeMailCodeRoute
'/magic-link/$magic-link': typeof MagicLinkMagicLinkRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
@@ -90,7 +90,7 @@ export interface FileRouteTypes {
| '/auth/code'
| '/auth/mail'
| '/auth/username'
| '/mail-code/$mail-code'
| '/magic-link/$magic-link'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
@@ -99,7 +99,7 @@ export interface FileRouteTypes {
| '/auth/code'
| '/auth/mail'
| '/auth/username'
| '/mail-code/$mail-code'
| '/magic-link/$magic-link'
id:
| '__root__'
| '/'
@@ -108,14 +108,14 @@ export interface FileRouteTypes {
| '/auth/code'
| '/auth/mail'
| '/auth/username'
| '/mail-code/$mail-code'
| '/magic-link/$magic-link'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
AuthRoute: typeof AuthRouteWithChildren
InteractionPanelRoute: typeof InteractionPanelRoute
MailCodeMailCodeRoute: typeof MailCodeMailCodeRoute
MagicLinkMagicLinkRoute: typeof MagicLinkMagicLinkRoute
}
declare module '@tanstack/react-router' {
@@ -141,11 +141,11 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
'/mail-code/$mail-code': {
id: '/mail-code/$mail-code'
path: '/mail-code/$mail-code'
fullPath: '/mail-code/$mail-code'
preLoaderRoute: typeof MailCodeMailCodeRouteImport
'/magic-link/$magic-link': {
id: '/magic-link/$magic-link'
path: '/magic-link/$magic-link'
fullPath: '/magic-link/$magic-link'
preLoaderRoute: typeof MagicLinkMagicLinkRouteImport
parentRoute: typeof rootRouteImport
}
'/auth/username': {
@@ -190,7 +190,7 @@ const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
AuthRoute: AuthRouteWithChildren,
InteractionPanelRoute: InteractionPanelRoute,
MailCodeMailCodeRoute: MailCodeMailCodeRoute,
MagicLinkMagicLinkRoute: MagicLinkMagicLinkRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)

View File

@@ -101,6 +101,15 @@ function RouteComponent() {
setResendTimeout(60);
};
const handleSubmit = () => {
const fullCode = code.join("");
console.log("Confirm code:", fullCode);
navigate({
to: "/auth/username",
viewTransition: { types: ["warp"] },
});
};
return (
<div className="flex items-center justify-center h-full gap-10 tracking-[-2%] flex-col">
<div className="text-brand-gray text-center">
@@ -114,7 +123,15 @@ function RouteComponent() {
</p>
</div>
<div className="space-y-10">
<form
onSubmit={(e) => {
e.preventDefault();
if (code.every((c) => c !== "")) {
handleSubmit();
}
}}
className="space-y-10"
>
<div className="flex space-x-2">
{code.map((digit, i) => (
<Input
@@ -136,16 +153,9 @@ function RouteComponent() {
<div className="space-y-4">
<Button
type="submit"
className="text-[18px] w-full"
disabled={code.some((c) => c === "")}
onClick={() => {
const fullCode = code.join("");
console.log("Confirm code:", fullCode);
navigate({
to: "/auth/username",
viewTransition: { types: ["warp"] },
});
}}
>
Confirm
</Button>
@@ -165,7 +175,7 @@ function RouteComponent() {
)}
</div>
</div>
</div>
</form>
</div>
);
}

View File

@@ -29,17 +29,19 @@ function RouteComponent() {
</div>
<div className="space-y-6 w-[300px]">
<form
onSubmit={(e) => {
e.preventDefault();
handleSubmit();
}}
>
<Input
label="Email"
placeholder="Email@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
handleSubmit();
}
}}
/>
</form>
<Button
className="text-[18px] w-full"

View File

@@ -32,17 +32,19 @@ function RouteComponent() {
</div>
<div className="space-y-6 w-[300px]">
<form
onSubmit={(e) => {
e.preventDefault();
handleSubmit();
}}
>
<Input
label="Username"
placeholder="Pink Axolotl"
value={username}
onChange={(e) => setUsername(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
handleSubmit();
}
}}
/>
</form>
<div className="space-y-2">
<Button
@@ -55,7 +57,9 @@ function RouteComponent() {
<Button
variant={"secondary"}
className="text-[18px] w-full"
// onClick={handleSubmit}
onClick={() => {
navigate({ to: "/", viewTransition: { types: ["warp"] } });
}}
>
Skip for now
</Button>

View File

@@ -5,13 +5,13 @@ import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
import { TextLink } from "@/components/ui/text-link";
import LogoWithText from "@/icons/logo-with-text.svg?react";
export const Route = createFileRoute("/mail-code/$mail-code")({
export const Route = createFileRoute("/magic-link/$magic-link")({
component: RouteComponent,
});
function RouteComponent() {
const navigate = useNavigate();
const { "mail-code": codeFromUrl } = Route.useParams();
const { "magic-link": magicLink } = Route.useParams();
const [code, setCode] = useState<string[]>(Array(6).fill(""));
const [isAutoSubmitted, setIsAutoSubmitted] = useState(false);
@@ -31,8 +31,8 @@ function RouteComponent() {
});
useEffect(() => {
if (codeFromUrl && /^\d{6}$/.test(codeFromUrl)) {
const codeArray = codeFromUrl.split("").slice(0, 6);
if (magicLink && /^\d{6}$/.test(magicLink)) {
const codeArray = magicLink.split("").slice(0, 6);
setCode(codeArray);
const timer = setTimeout(() => {
@@ -41,7 +41,7 @@ function RouteComponent() {
return () => clearTimeout(timer);
}
}, [codeFromUrl]);
}, [magicLink]);
useEffect(() => {
if (resendTimeout <= 0) {
@@ -143,7 +143,7 @@ function RouteComponent() {
{code.map((digit, i) => (
<Input
key={i}
id={`code-input-${i}`}
id={"code-input-${i}"}
className="w-10 h-10 p-0 text-center text-lg"
type="text"
inputMode="numeric"