style: 优化样式
This commit is contained in:
parent
b35c692998
commit
910bd2d4dc
@ -1,11 +1,24 @@
|
||||
'use client';
|
||||
import { signout } from '@/app/lib/actions';
|
||||
|
||||
export default function SignoutButton() {
|
||||
return <button onClick={async () => {
|
||||
"use client";
|
||||
import { signout } from "@/app/lib/actions/auth";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
export default function SignoutButton({
|
||||
className,
|
||||
}: {
|
||||
className: string | undefined;
|
||||
}) {
|
||||
const pathname = usePathname();
|
||||
if (pathname.match("/signin") || pathname.match("/signup")) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<button
|
||||
className={className}
|
||||
onClick={async () => {
|
||||
await signout();
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
注销
|
||||
</button>
|
||||
};
|
||||
);
|
||||
}
|
||||
|
258
app/layout.tsx
258
app/layout.tsx
@ -1,6 +1,7 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import SignoutButton from "./components/signout-button";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
@ -16,7 +17,262 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html lang="zh_CN" data-theme="light">
|
||||
<body className={inter.className}>{children}</body>
|
||||
<body className={inter.className}>
|
||||
<header className="text-gray-600 body-font">
|
||||
<div className="container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center">
|
||||
<a
|
||||
href="#"
|
||||
className="flex title-font font-medium items-center text-gray-900 mb-4 md:mb-0"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
className="w-10 h-10 text-white p-2 bg-indigo-500 rounded-full"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"></path>
|
||||
</svg>
|
||||
<span className="ml-3 text-xl">音乐弹幕</span>
|
||||
</a>
|
||||
<nav className="md:ml-auto md:mr-auto flex flex-wrap items-center text-base justify-center">
|
||||
<a href="#" className="mr-5 hover:text-gray-900">
|
||||
First Link
|
||||
</a>
|
||||
<a href="#" className="mr-5 hover:text-gray-900">
|
||||
Second Link
|
||||
</a>
|
||||
<a href="#" className="mr-5 hover:text-gray-900">
|
||||
Third Link
|
||||
</a>
|
||||
<a href="#" className="mr-5 hover:text-gray-900">
|
||||
Fourth Link
|
||||
</a>
|
||||
</nav>
|
||||
<SignoutButton className="inline-flex items-center bg-gray-100 border-0 py-1 px-3 focus:outline-none hover:bg-gray-200 rounded text-base mt-4 md:mt-0" />
|
||||
</div>
|
||||
</header>
|
||||
<main className="min-h-[50vh]">{children}</main>
|
||||
<footer className="text-gray-600 body-font">
|
||||
<div className="container px-5 py-24 mx-auto flex md:items-center lg:items-start md:flex-row md:flex-nowrap flex-wrap flex-col">
|
||||
<div className="w-64 flex-shrink-0 md:mx-0 mx-auto text-center md:text-left">
|
||||
<a
|
||||
href="#"
|
||||
className="flex title-font font-medium items-center md:justify-start justify-center text-gray-900"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
className="w-10 h-10 text-white p-2 bg-indigo-500 rounded-full"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"></path>
|
||||
</svg>
|
||||
<span className="ml-3 text-xl">Tailblocks</span>
|
||||
</a>
|
||||
<p className="mt-2 text-sm text-gray-500">
|
||||
Air plant banjo lyft occupy retro adaptogen indego
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex-grow flex flex-wrap md:pl-20 -mb-10 md:mt-0 mt-10 md:text-left text-center">
|
||||
<div className="lg:w-1/4 md:w-1/2 w-full px-4">
|
||||
<h2 className="title-font font-medium text-gray-900 tracking-widest text-sm mb-3">
|
||||
CATEGORIES
|
||||
</h2>
|
||||
<nav className="list-none mb-10">
|
||||
<li>
|
||||
<a href="#" className="text-gray-600 hover:text-gray-800">
|
||||
First Link
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" className="text-gray-600 hover:text-gray-800">
|
||||
Second Link
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" className="text-gray-600 hover:text-gray-800">
|
||||
Third Link
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" className="text-gray-600 hover:text-gray-800">
|
||||
Fourth Link
|
||||
</a>
|
||||
</li>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="lg:w-1/4 md:w-1/2 w-full px-4">
|
||||
<h2 className="title-font font-medium text-gray-900 tracking-widest text-sm mb-3">
|
||||
CATEGORIES
|
||||
</h2>
|
||||
<nav className="list-none mb-10">
|
||||
<li>
|
||||
<a href="#" className="text-gray-600 hover:text-gray-800">
|
||||
First Link
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" className="text-gray-600 hover:text-gray-800">
|
||||
Second Link
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" className="text-gray-600 hover:text-gray-800">
|
||||
Third Link
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" className="text-gray-600 hover:text-gray-800">
|
||||
Fourth Link
|
||||
</a>
|
||||
</li>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="lg:w-1/4 md:w-1/2 w-full px-4">
|
||||
<h2 className="title-font font-medium text-gray-900 tracking-widest text-sm mb-3">
|
||||
CATEGORIES
|
||||
</h2>
|
||||
<nav className="list-none mb-10">
|
||||
<li>
|
||||
<a href="#" className="text-gray-600 hover:text-gray-800">
|
||||
First Link
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" className="text-gray-600 hover:text-gray-800">
|
||||
Second Link
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" className="text-gray-600 hover:text-gray-800">
|
||||
Third Link
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" className="text-gray-600 hover:text-gray-800">
|
||||
Fourth Link
|
||||
</a>
|
||||
</li>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="lg:w-1/4 md:w-1/2 w-full px-4">
|
||||
<h2 className="title-font font-medium text-gray-900 tracking-widest text-sm mb-3">
|
||||
CATEGORIES
|
||||
</h2>
|
||||
<nav className="list-none mb-10">
|
||||
<li>
|
||||
<a href="#" className="text-gray-600 hover:text-gray-800">
|
||||
First Link
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" className="text-gray-600 hover:text-gray-800">
|
||||
Second Link
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" className="text-gray-600 hover:text-gray-800">
|
||||
Third Link
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" className="text-gray-600 hover:text-gray-800">
|
||||
Fourth Link
|
||||
</a>
|
||||
</li>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-100">
|
||||
<div className="container mx-auto py-4 px-5 flex flex-wrap flex-col sm:flex-row">
|
||||
<p className="text-gray-500 text-sm text-center sm:text-left">
|
||||
© 2024 音乐弹幕 —
|
||||
<a
|
||||
href="https://github.com/sealday"
|
||||
rel="noopener noreferrer"
|
||||
className="text-gray-600 ml-1"
|
||||
target="_blank"
|
||||
>
|
||||
@sealday
|
||||
</a>
|
||||
</p>
|
||||
<span className="inline-flex sm:ml-auto sm:mt-0 mt-2 justify-center sm:justify-start">
|
||||
<a href="#" className="text-gray-500">
|
||||
<svg
|
||||
fill="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
className="w-5 h-5"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M18 2h-3a5 5 0 00-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 011-1h3z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="#" className="ml-3 text-gray-500">
|
||||
<svg
|
||||
fill="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
className="w-5 h-5"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M23 3a10.9 10.9 0 01-3.14 1.53 4.48 4.48 0 00-7.86 3v1A10.66 10.66 0 013 4s-4 9 5 13a11.64 11.64 0 01-7 2c9 5 20 0 20-11.5a4.5 4.5 0 00-.08-.83A7.72 7.72 0 0023 3z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="#" className="ml-3 text-gray-500">
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
className="w-5 h-5"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<rect
|
||||
width="20"
|
||||
height="20"
|
||||
x="2"
|
||||
y="2"
|
||||
rx="5"
|
||||
ry="5"
|
||||
></rect>
|
||||
<path d="M16 11.37A4 4 0 1112.63 8 4 4 0 0116 11.37zm1.5-4.87h.01"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="#" className="ml-3 text-gray-500">
|
||||
<svg
|
||||
fill="currentColor"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="0"
|
||||
className="w-5 h-5"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke="none"
|
||||
d="M16 8a6 6 0 016 6v7h-4v-7a2 2 0 00-2-2 2 2 0 00-2 2v7h-4v-7a6 6 0 016-6zM2 9h4v12H2z"
|
||||
></path>
|
||||
<circle cx="4" cy="4" r="2" stroke="none"></circle>
|
||||
</svg>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
@ -1,71 +0,0 @@
|
||||
'use server';
|
||||
import { redirect } from 'next/navigation'
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
const SIGNUP_URL = 'https://tachy.daoyoucloud.com/api/auth:signUp';
|
||||
const SIGNIN_URL = 'https://tachy.daoyoucloud.com/api/auth:signIn';
|
||||
const SIGNOUT_URL = 'https://tachy.daoyoucloud.com/api/auth:signOut'
|
||||
|
||||
|
||||
export async function signout() {
|
||||
const token = cookies().get('token');
|
||||
const result = await fetch(SIGNOUT_URL, {
|
||||
"headers": {
|
||||
"authorization": "Bearer " + token?.value,
|
||||
"cache-control": "no-cache",
|
||||
"pragma": "no-cache",
|
||||
"x-app": "danmu-sim",
|
||||
"x-authenticator": "basic",
|
||||
"x-locale": "zh-CN",
|
||||
"x-timezone": "+08:00",
|
||||
"x-with-acl-meta": "true",
|
||||
},
|
||||
"body": '',
|
||||
"method": "POST"
|
||||
});
|
||||
cookies().delete('token');
|
||||
redirect('/signin');
|
||||
}
|
||||
|
||||
|
||||
export async function signin(_currentState: unknown, formData: FormData) {
|
||||
const result = await fetch(SIGNIN_URL, {
|
||||
"headers": {
|
||||
"cache-control": "no-cache",
|
||||
"content-type": "application/json",
|
||||
"pragma": "no-cache",
|
||||
"x-app": "danmu-sim",
|
||||
"x-authenticator": "basic",
|
||||
"x-hostname": "tachy.daoyoucloud.com",
|
||||
"x-locale": "zh-CN",
|
||||
"x-timezone": "+08:00",
|
||||
"x-with-acl-meta": "true",
|
||||
},
|
||||
"body": JSON.stringify(Object.fromEntries(formData)),
|
||||
"method": "POST"
|
||||
});
|
||||
const res = await result.json();
|
||||
if (res.errors) {
|
||||
return res.errors[0].message;
|
||||
}
|
||||
cookies().set('token', res.data.token);
|
||||
redirect('/');
|
||||
}
|
||||
|
||||
export async function signup(_currentState: unknown, formData: FormData) {
|
||||
const result = await fetch(SIGNUP_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-App': 'danmu-sim',
|
||||
"content-type": "application/json",
|
||||
"x-authenticator": "basic",
|
||||
"x-locale": "zh-CN",
|
||||
"x-timezone": "+08:00",
|
||||
},
|
||||
body: JSON.stringify(Object.fromEntries(formData))
|
||||
})
|
||||
const response = await result.json();
|
||||
if (response.errors) {
|
||||
return response.errors[0].message;
|
||||
}
|
||||
}
|
70
app/lib/actions/auth.ts
Normal file
70
app/lib/actions/auth.ts
Normal file
@ -0,0 +1,70 @@
|
||||
"use server";
|
||||
import { redirect } from "next/navigation";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
const SIGNUP_URL = "https://tachy.daoyoucloud.com/api/auth:signUp";
|
||||
const SIGNIN_URL = "https://tachy.daoyoucloud.com/api/auth:signIn";
|
||||
const SIGNOUT_URL = "https://tachy.daoyoucloud.com/api/auth:signOut";
|
||||
|
||||
export async function signout() {
|
||||
const token = cookies().get("token");
|
||||
const result = await fetch(SIGNOUT_URL, {
|
||||
headers: {
|
||||
authorization: "Bearer " + token?.value,
|
||||
"cache-control": "no-cache",
|
||||
pragma: "no-cache",
|
||||
"x-app": "danmu-sim",
|
||||
"x-authenticator": "basic",
|
||||
"x-locale": "zh-CN",
|
||||
"x-timezone": "+08:00",
|
||||
"x-with-acl-meta": "true",
|
||||
},
|
||||
body: "",
|
||||
method: "POST",
|
||||
});
|
||||
cookies().delete("token");
|
||||
redirect("/signin");
|
||||
}
|
||||
|
||||
export async function signin(_currentState: unknown, formData: FormData) {
|
||||
const result = await fetch(SIGNIN_URL, {
|
||||
headers: {
|
||||
"cache-control": "no-cache",
|
||||
"content-type": "application/json",
|
||||
pragma: "no-cache",
|
||||
"x-app": "danmu-sim",
|
||||
"x-authenticator": "basic",
|
||||
"x-hostname": "tachy.daoyoucloud.com",
|
||||
"x-locale": "zh-CN",
|
||||
"x-timezone": "+08:00",
|
||||
"x-with-acl-meta": "true",
|
||||
},
|
||||
body: JSON.stringify(Object.fromEntries(formData)),
|
||||
method: "POST",
|
||||
});
|
||||
const res = await result.json();
|
||||
if (res.errors) {
|
||||
return res.errors[0].message;
|
||||
}
|
||||
cookies().set("token", res.data.token);
|
||||
redirect("/");
|
||||
}
|
||||
|
||||
export async function signup(_currentState: unknown, formData: FormData) {
|
||||
const result = await fetch(SIGNUP_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-App": "danmu-sim",
|
||||
"content-type": "application/json",
|
||||
"x-authenticator": "basic",
|
||||
"x-locale": "zh-CN",
|
||||
"x-timezone": "+08:00",
|
||||
},
|
||||
body: JSON.stringify(Object.fromEntries(formData)),
|
||||
});
|
||||
const response = await result.json();
|
||||
if (response.errors) {
|
||||
return response.errors[0].message;
|
||||
}
|
||||
redirect("/signin");
|
||||
}
|
1
app/music.svg
Normal file
1
app/music.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 204 KiB |
158
app/page.tsx
158
app/page.tsx
@ -1,124 +1,44 @@
|
||||
import Image from "next/image";
|
||||
import SignoutButton from "@/app/components/signout-button";
|
||||
import Link from "next/link";
|
||||
import musicSvg from "@/app/music.svg";
|
||||
|
||||
export default function Home() {
|
||||
return <main>
|
||||
<SignoutButton />
|
||||
<Link className="btn" href="/music">弹幕测试</Link>
|
||||
</main>
|
||||
return (
|
||||
<section className="text-gray-600 body-font">
|
||||
<div className="container mx-auto flex px-5 py-24 md:flex-row flex-col items-center">
|
||||
<div className="lg:flex-grow md:w-1/2 lg:pr-24 md:pr-16 flex flex-col md:items-start md:text-left mb-16 md:mb-0 items-center text-center">
|
||||
<h1 className="title-font sm:text-4xl text-3xl mb-4 font-medium text-gray-900">
|
||||
Before they sold out
|
||||
<br className="hidden lg:inline-block" />
|
||||
readymade gluten
|
||||
</h1>
|
||||
|
||||
<p className="mb-8 leading-relaxed">
|
||||
Copper mug try-hard pitchfork pour-over freegan heirloom neutra air
|
||||
plant cold-pressed tacos poke beard tote bag. Heirloom echo park
|
||||
mlkshk tote bag selvage hot chicken authentic tumeric truffaut
|
||||
hexagon try-hard chambray.
|
||||
</p>
|
||||
<div className="flex justify-center">
|
||||
<Link
|
||||
href="/music"
|
||||
className="inline-flex text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded text-lg"
|
||||
>
|
||||
弹幕测试
|
||||
</Link>
|
||||
<button className="ml-4 inline-flex text-gray-700 bg-gray-100 border-0 py-2 px-6 focus:outline-none hover:bg-gray-200 rounded text-lg">
|
||||
Button
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:max-w-lg lg:w-full md:w-1/2 w-5/6">
|
||||
<Image
|
||||
className="object-cover object-center rounded"
|
||||
alt="hero"
|
||||
src={musicSvg}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// export default function Home() {
|
||||
// return (
|
||||
// <main className="flex min-h-screen flex-col items-center justify-between p-24">
|
||||
// <SignoutButton />
|
||||
// <Link className="btn" href="/music">弹幕测试</Link>
|
||||
// <div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
|
||||
// <p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
|
||||
// Get started by editing
|
||||
// <code className="font-mono font-bold">app/page.tsx</code>
|
||||
// </p>
|
||||
// <div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none">
|
||||
// <a
|
||||
// className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
|
||||
// href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
// target="_blank"
|
||||
// rel="noopener noreferrer"
|
||||
// >
|
||||
// By{" "}
|
||||
// <Image
|
||||
// src="/vercel.svg"
|
||||
// alt="Vercel Logo"
|
||||
// className="dark:invert"
|
||||
// width={100}
|
||||
// height={24}
|
||||
// priority
|
||||
// />
|
||||
// </a>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// <div className="relative flex place-items-center before:absolute before:h-[300px] before:w-full sm:before:w-[480px] before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-full sm:after:w-[240px] after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700 before:dark:opacity-10 after:dark:from-sky-900 after:dark:via-[#0141ff] after:dark:opacity-40 before:lg:h-[360px] z-[-1]">
|
||||
// <Image
|
||||
// className="relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert"
|
||||
// src="/next.svg"
|
||||
// alt="Next.js Logo"
|
||||
// width={180}
|
||||
// height={37}
|
||||
// priority
|
||||
// />
|
||||
// </div>
|
||||
|
||||
// <div className="mb-32 grid text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:grid-cols-4 lg:text-left">
|
||||
// <a
|
||||
// href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
// className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
// target="_blank"
|
||||
// rel="noopener noreferrer"
|
||||
// >
|
||||
// <h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
// Docs{" "}
|
||||
// <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
// ->
|
||||
// </span>
|
||||
// </h2>
|
||||
// <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
||||
// Find in-depth information about Next.js features and API.
|
||||
// </p>
|
||||
// </a>
|
||||
|
||||
// <a
|
||||
// href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
// className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
// target="_blank"
|
||||
// rel="noopener noreferrer"
|
||||
// >
|
||||
// <h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
// Learn{" "}
|
||||
// <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
// ->
|
||||
// </span>
|
||||
// </h2>
|
||||
// <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
||||
// Learn about Next.js in an interactive course with quizzes!
|
||||
// </p>
|
||||
// </a>
|
||||
|
||||
// <a
|
||||
// href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
// className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
// target="_blank"
|
||||
// rel="noopener noreferrer"
|
||||
// >
|
||||
// <h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
// Templates{" "}
|
||||
// <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
// ->
|
||||
// </span>
|
||||
// </h2>
|
||||
// <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
||||
// Explore starter templates for Next.js.
|
||||
// </p>
|
||||
// </a>
|
||||
|
||||
// <a
|
||||
// href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
// className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
// target="_blank"
|
||||
// rel="noopener noreferrer"
|
||||
// >
|
||||
// <h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
// Deploy{" "}
|
||||
// <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
// ->
|
||||
// </span>
|
||||
// </h2>
|
||||
// <p className={`m-0 max-w-[30ch] text-sm opacity-50 text-balance`}>
|
||||
// Instantly deploy your Next.js site to a shareable URL with Vercel.
|
||||
// </p>
|
||||
// </a>
|
||||
// </div>
|
||||
// </main>
|
||||
// );
|
||||
// }
|
||||
|
@ -1,33 +1,89 @@
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
import { signin } from '@/app/lib/actions'
|
||||
import { useFormState, useFormStatus } from 'react-dom'
|
||||
import { signin } from "@/app/lib/actions/auth";
|
||||
import Link from "next/link";
|
||||
import { useFormState, useFormStatus } from "react-dom";
|
||||
|
||||
export default function Page() {
|
||||
const [errorMessage, dispatch] = useFormState(signin, undefined)
|
||||
const [errorMessage, dispatch] = useFormState(signin, undefined);
|
||||
|
||||
return (
|
||||
<form action={dispatch}>
|
||||
<input name="account" placeholder="Account" required />
|
||||
<input type="password" name="password" placeholder="Password" required />
|
||||
<div>{errorMessage && <p>{errorMessage}</p>}</div>
|
||||
<LoginButton />
|
||||
<section className="text-gray-600 body-font">
|
||||
<div className="container px-5 py-24 mx-auto flex flex-wrap items-center">
|
||||
<div className="lg:w-3/5 md:w-1/2 md:pr-16 lg:pr-0 pr-0">
|
||||
<h1 className="title-font font-medium text-3xl text-gray-900">
|
||||
Slow-carb next level shoindcgoitch ethical authentic, poko scenester
|
||||
</h1>
|
||||
<p className="leading-relaxed mt-4">
|
||||
Poke slow-carb mixtape knausgaard, typewriter street art gentrify
|
||||
hammock starladder roathse. Craies vegan tousled etsy austin.
|
||||
</p>
|
||||
</div>
|
||||
<form
|
||||
action={dispatch}
|
||||
className="lg:w-2/6 md:w-1/2 bg-gray-100 rounded-lg p-8 flex flex-col md:ml-auto w-full mt-10 md:mt-0"
|
||||
>
|
||||
<h2 className="text-gray-900 text-lg font-medium title-font mb-5">
|
||||
登录
|
||||
</h2>
|
||||
<div className="relative mb-4">
|
||||
<label
|
||||
htmlFor="account"
|
||||
className="leading-7 text-sm text-gray-600"
|
||||
>
|
||||
账号
|
||||
</label>
|
||||
<input
|
||||
id="account"
|
||||
className="w-full bg-white rounded border border-gray-300 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
|
||||
type="text"
|
||||
required
|
||||
name="account"
|
||||
/>
|
||||
</div>
|
||||
<div className="relative mb-4">
|
||||
<label
|
||||
htmlFor="password"
|
||||
className="leading-7 text-sm text-gray-600"
|
||||
>
|
||||
密码
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
className="w-full bg-white rounded border border-gray-300 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
|
||||
required
|
||||
type="password"
|
||||
name="password"
|
||||
/>
|
||||
</div>
|
||||
<LoginButton className="text-white bg-indigo-500 border-0 py-2 px-8 focus:outline-none hover:bg-indigo-600 rounded text-lg" />
|
||||
<Link href="/signup" className="btn btn-link">还没有账号?可以注册一个。</Link>
|
||||
{errorMessage ? (
|
||||
<p className="text-xs text-red-400 mt-3">{errorMessage}</p>
|
||||
) : null}
|
||||
</form>
|
||||
)
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function LoginButton() {
|
||||
const { pending } = useFormStatus()
|
||||
function LoginButton({ className }: { className: string | undefined }) {
|
||||
const { pending } = useFormStatus();
|
||||
|
||||
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
if (pending) {
|
||||
event.preventDefault()
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<button aria-disabled={pending} type="submit" onClick={handleClick}>
|
||||
Login
|
||||
<button
|
||||
className={className}
|
||||
aria-disabled={pending}
|
||||
type="submit"
|
||||
onClick={handleClick}
|
||||
>
|
||||
登录
|
||||
</button>
|
||||
)
|
||||
);
|
||||
}
|
@ -1,35 +1,125 @@
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
import { signup } from '@/app/lib/actions'
|
||||
import { useFormState, useFormStatus } from 'react-dom'
|
||||
import { signup } from "@/app/lib/actions/auth";
|
||||
import Link from "next/link";
|
||||
import { useFormState, useFormStatus } from "react-dom";
|
||||
|
||||
export default function Page() {
|
||||
const [errorMessage, dispatch] = useFormState(signup, undefined)
|
||||
const [errorMessage, dispatch] = useFormState(signup, undefined);
|
||||
|
||||
console.log(errorMessage, '=== in client');
|
||||
return (
|
||||
<form action={dispatch}>
|
||||
<input name="username" required />
|
||||
<input type="password" name="password" placeholder='密码' required />
|
||||
<input type="password" name="confirm_password" placeholder='确认密码' required />
|
||||
<div>{errorMessage && <p>{errorMessage}</p>}</div>
|
||||
<LoginButton />
|
||||
<section className="text-gray-600 body-font">
|
||||
<div className="container px-5 py-24 mx-auto flex flex-wrap items-center">
|
||||
<div className="lg:w-3/5 md:w-1/2 md:pr-16 lg:pr-0 pr-0">
|
||||
<h1 className="title-font font-medium text-3xl text-gray-900">
|
||||
Slow-carb next level shoindcgoitch ethical authentic, poko scenester
|
||||
</h1>
|
||||
<p className="leading-relaxed mt-4">
|
||||
Poke slow-carb mixtape knausgaard, typewriter street art gentrify
|
||||
hammock starladder roathse. Craies vegan tousled etsy austin.
|
||||
</p>
|
||||
</div>
|
||||
<form
|
||||
action={dispatch}
|
||||
className="lg:w-2/6 md:w-1/2 bg-gray-100 rounded-lg p-8 flex flex-col md:ml-auto w-full mt-10 md:mt-0"
|
||||
>
|
||||
<h2 className="text-gray-900 text-lg font-medium title-font mb-5">
|
||||
注册
|
||||
</h2>
|
||||
<div className="relative mb-4">
|
||||
<label
|
||||
htmlFor="username"
|
||||
className="leading-7 text-sm text-gray-600"
|
||||
>
|
||||
账号
|
||||
</label>
|
||||
<input
|
||||
id="username"
|
||||
className="w-full bg-white rounded border border-gray-300 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
|
||||
type="text"
|
||||
required
|
||||
name="username"
|
||||
/>
|
||||
</div>
|
||||
<div className="relative mb-4">
|
||||
<label
|
||||
htmlFor="password"
|
||||
className="leading-7 text-sm text-gray-600"
|
||||
>
|
||||
密码
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
className="w-full bg-white rounded border border-gray-300 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
|
||||
required
|
||||
type="password"
|
||||
name="password"
|
||||
/>
|
||||
</div>
|
||||
<div className="relative mb-4">
|
||||
<label
|
||||
htmlFor="confirm_password"
|
||||
className="leading-7 text-sm text-gray-600"
|
||||
>
|
||||
确认密码
|
||||
</label>
|
||||
<input
|
||||
id="confirm_password"
|
||||
className="w-full bg-white rounded border border-gray-300 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
|
||||
required
|
||||
type="password"
|
||||
name="confirm_password"
|
||||
/>
|
||||
</div>
|
||||
<SignupButton className="text-white bg-indigo-500 border-0 py-2 px-8 focus:outline-none hover:bg-indigo-600 rounded text-lg" />
|
||||
<Link href="/signin" className="btn btn-link">已经有账号?点此直接登录。</Link>
|
||||
{errorMessage ? (
|
||||
<p className="text-xs text-red-400 mt-3">{errorMessage}</p>
|
||||
) : null}
|
||||
</form>
|
||||
)
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function LoginButton() {
|
||||
const { pending } = useFormStatus()
|
||||
|
||||
// export default function Page() {
|
||||
// const [errorMessage, dispatch] = useFormState(signup, undefined);
|
||||
|
||||
// return (
|
||||
// <form action={dispatch}>
|
||||
// <input name="username" required />
|
||||
// <input type="password" name="password" placeholder="密码" required />
|
||||
// <input
|
||||
// type="password"
|
||||
// name="confirm_password"
|
||||
// placeholder="确认密码"
|
||||
// required
|
||||
// />
|
||||
// <div>{errorMessage && <p>{errorMessage}</p>}</div>
|
||||
// <LoginButton />
|
||||
// </form>
|
||||
// );
|
||||
// }
|
||||
|
||||
function SignupButton({ className }: { className: string | undefined }) {
|
||||
const { pending } = useFormStatus();
|
||||
|
||||
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
if (pending) {
|
||||
event.preventDefault()
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<button aria-disabled={pending} type="submit" onClick={handleClick}>
|
||||
Login {pending}
|
||||
<button
|
||||
className={className}
|
||||
aria-disabled={pending}
|
||||
type="submit"
|
||||
onClick={handleClick}
|
||||
>
|
||||
注册
|
||||
</button>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
20
package.json
20
package.json
@ -11,21 +11,21 @@
|
||||
"dependencies": {
|
||||
"axios": "^1.6.8",
|
||||
"next": "14.1.4",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-use-websocket": "^4.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"autoprefixer": "^10.0.1",
|
||||
"@types/node": "^20.12.4",
|
||||
"@types/react": "^18.2.74",
|
||||
"@types/react-dom": "^18.2.24",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"daisyui": "^4.10.1",
|
||||
"eslint": "^8",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-next": "14.1.4",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.3.0",
|
||||
"typescript": "^5"
|
||||
"postcss": "^8.4.38",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "^5.4.4"
|
||||
},
|
||||
"packageManager": "pnpm@8.15.5+sha256.4b4efa12490e5055d59b9b9fc9438b7d581a6b7af3b5675eb5c5f447cee1a589"
|
||||
}
|
@ -12,10 +12,10 @@ dependencies:
|
||||
specifier: 14.1.4
|
||||
version: 14.1.4(react-dom@18.2.0)(react@18.2.0)
|
||||
react:
|
||||
specifier: ^18
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0
|
||||
react-dom:
|
||||
specifier: ^18
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0(react@18.2.0)
|
||||
react-use-websocket:
|
||||
specifier: ^4.8.1
|
||||
@ -23,34 +23,34 @@ dependencies:
|
||||
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^20
|
||||
specifier: ^20.12.4
|
||||
version: 20.12.4
|
||||
'@types/react':
|
||||
specifier: ^18
|
||||
specifier: ^18.2.74
|
||||
version: 18.2.74
|
||||
'@types/react-dom':
|
||||
specifier: ^18
|
||||
specifier: ^18.2.24
|
||||
version: 18.2.24
|
||||
autoprefixer:
|
||||
specifier: ^10.0.1
|
||||
specifier: ^10.4.19
|
||||
version: 10.4.19(postcss@8.4.38)
|
||||
daisyui:
|
||||
specifier: ^4.10.1
|
||||
version: 4.10.1(postcss@8.4.38)
|
||||
eslint:
|
||||
specifier: ^8
|
||||
specifier: ^8.57.0
|
||||
version: 8.57.0
|
||||
eslint-config-next:
|
||||
specifier: 14.1.4
|
||||
version: 14.1.4(eslint@8.57.0)(typescript@5.4.4)
|
||||
postcss:
|
||||
specifier: ^8
|
||||
specifier: ^8.4.38
|
||||
version: 8.4.38
|
||||
tailwindcss:
|
||||
specifier: ^3.3.0
|
||||
specifier: ^3.4.3
|
||||
version: 3.4.3
|
||||
typescript:
|
||||
specifier: ^5
|
||||
specifier: ^5.4.4
|
||||
version: 5.4.4
|
||||
|
||||
packages:
|
||||
@ -588,7 +588,7 @@ packages:
|
||||
postcss: ^8.1.0
|
||||
dependencies:
|
||||
browserslist: 4.23.0
|
||||
caniuse-lite: 1.0.30001605
|
||||
caniuse-lite: 1.0.30001606
|
||||
fraction.js: 4.3.7
|
||||
normalize-range: 0.1.2
|
||||
picocolors: 1.0.0
|
||||
@ -658,7 +658,7 @@ packages:
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001605
|
||||
caniuse-lite: 1.0.30001606
|
||||
electron-to-chromium: 1.4.728
|
||||
node-releases: 2.0.14
|
||||
update-browserslist-db: 1.0.13(browserslist@4.23.0)
|
||||
@ -692,8 +692,8 @@ packages:
|
||||
engines: {node: '>= 6'}
|
||||
dev: true
|
||||
|
||||
/caniuse-lite@1.0.30001605:
|
||||
resolution: {integrity: sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ==}
|
||||
/caniuse-lite@1.0.30001606:
|
||||
resolution: {integrity: sha512-LPbwnW4vfpJId225pwjZJOgX1m9sGfbw/RKJvw/t0QhYOOaTXHvkjVGFGPpvwEzufrjvTlsULnVTxdy4/6cqkg==}
|
||||
|
||||
/chalk@4.1.2:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
@ -2073,7 +2073,7 @@ packages:
|
||||
'@next/env': 14.1.4
|
||||
'@swc/helpers': 0.5.2
|
||||
busboy: 1.6.0
|
||||
caniuse-lite: 1.0.30001605
|
||||
caniuse-lite: 1.0.30001606
|
||||
graceful-fs: 4.2.11
|
||||
postcss: 8.4.31
|
||||
react: 18.2.0
|
||||
|
Loading…
Reference in New Issue
Block a user