Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
daviidwang committed Jan 23, 2024
2 parents fc20b88 + c8b027c commit 55f5da2
Show file tree
Hide file tree
Showing 27 changed files with 617 additions and 244 deletions.
2 changes: 1 addition & 1 deletion docs/zh-CN/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ GCopy重视您的数据隐私, 不持久化存储您的数据, 它们都在内
目前, 你可以在Windows电脑与MacOS电脑之间共享剪切板, 支持文字, 截图和文件.
它对网络没有太高的要求, 不同的设备可以在同一个局域网, 也可以不在.

最开始我使用Git作为后端存储, 使用powershell、osascript这样的脚本在不同的设备之间同步剪切板. 但是因为它依赖Git, 注定不能让更多的非技术朋友使用. 所以, 我使用Golang替换了Git, 来作为不同设备之间的数据中转服务, 但是它仍然需要您在设备上下载运行GCopy客户端, 给使用带来了门槛. 所以, 我做了现在的`GCopy v1.0`, 它一个Web服务, 您可以直接打开网站[https://gcopy.rutron.net](https://gcopy.rutron.net)使用, 同时不用担心您的数据泄露.
最开始我使用Git作为后端存储, 使用powershell、osascript这样的脚本在不同的设备之间同步剪切板. 但是因为它依赖Git, 注定不能让更多的非技术朋友使用. 所以, 我使用Golang替换了Git, 来作为不同设备之间的数据中转服务, 但是它仍然需要您在设备上下载运行GCopy客户端, 给使用带来了门槛. 所以, 我做了现在的`GCopy v1.0`, 它是一个Web服务, 您可以直接打开网站[https://gcopy.rutron.net](https://gcopy.rutron.net)使用, 同时不用担心您的数据泄露.

## 不足之处

Expand Down
32 changes: 32 additions & 0 deletions frontend/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Inter } from "next/font/google";
import "@/app/globals.css";
import { ReactNode } from "react";
import { getTranslations } from "next-intl/server";

export async function generateMetadata({
params: { locale },
}: {
params: { locale: string };
}) {
const t = await getTranslations({ locale, namespace: "Metadata" });
return {
title: t("title"),
description: t("description"),
};
}

const inter = Inter({ subsets: ["latin"] });

export default function RootLayout({
children,
params: { locale },
}: {
children: ReactNode;
params: { locale: string };
}) {
return (
<html lang={locale}>
<body className={`${inter.className} bg-base-200`}>{children}</body>
</html>
);
}
29 changes: 29 additions & 0 deletions frontend/app/[locale]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Navbar from "@/components/navbar";
import SyncClipboard from "@/components/sync-clipboard";
import Notice from "@/components/notice";
import Footer from "@/components/footer";
import { NextIntlClientProvider, useMessages } from "next-intl";

export default function Home({
params: { locale },
}: {
params: { locale: string };
}) {
const messages = useMessages();
return (
<div className="min-h-screen flex flex-col items-center justify-between mx-auto max-w-5xl">
<header className="p-6 w-full">
<NextIntlClientProvider locale={locale} messages={messages}>
<Navbar />
</NextIntlClientProvider>
</header>
<main className="flex-1 p-6 w-full">
<NextIntlClientProvider locale={locale} messages={messages}>
<SyncClipboard />
</NextIntlClientProvider>
<Notice />
</main>
<Footer />
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
import Image from "next/image";
import { FormEvent, useState } from "react";
import { useRouter } from "next/navigation";
import { useLocale, useTranslations } from "next-intl";

export default function EmailCode({
searchParams,
}: {
searchParams: { email: string };
}) {
const locale = useLocale();
const t = useTranslations("EmailCode");
const [errorMessage, setErrorMessage] = useState("");
const email = searchParams.email;
const router = useRouter();
Expand All @@ -31,43 +34,38 @@ export default function EmailCode({
setErrorMessage(body.message);
}
if (res.status == 200) {
router.push("/user/login?email=" + email);
router.push(`/${locale}/user/login?email=${email}`);
}
});
};

return (
<form
onSubmit={createEmailCode}
className="card w-[32rem] bg-base-100 shadow-xl"
className="card w-full md:w-[32rem] bg-base-100 shadow-xl"
>
<div className="card-body gap-4">
<Image
src="/gcopy.svg"
width={50}
height={50}
alt="Picture of the author"
/>
<Image src="/gcopy.svg" width={50} height={50} alt="gcopy's logo" />
<div className="flex flex-col gap-0">
<h2 className="card-title">Sign in</h2>
<span className="text-xs">to continue to GCopy</span>
<h2 className="card-title">{t("title")}</h2>
<span className="text-xs">{t("smallTitle")}</span>
</div>
<p>Support for text, screenshots & file synchronization.</p>
<p>{t("subTitle")}</p>
<div>
<input
name="email"
type="text"
placeholder="Enter email"
placeholder={t("placeholder")}
className="input input-bordered w-full"
defaultValue={email}
autoFocus
/>
<span className="text-xs">Your privacy is important to GCopy!</span>
<span className="text-xs">{t("tip")}</span>
</div>
{errorMessage && <p className="text-error">{errorMessage}</p>}
<div className="card-actions justify-end">
<button type="submit" className="btn btn-primary">
Next
{t("buttonText")}
</button>
</div>
</div>
Expand Down
18 changes: 18 additions & 0 deletions frontend/app/[locale]/user/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { NextIntlClientProvider, useMessages } from "next-intl";

export default function UserLayout({
params: { locale },
children,
}: {
children: React.ReactNode;
params: { locale: string };
}) {
const messages = useMessages();
return (
<div className="min-h-screen flex flex-col items-center justify-center mx-auto">
<NextIntlClientProvider locale={locale} messages={messages}>
{children}
</NextIntlClientProvider>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
import Image from "next/image";
import { FormEvent, useState } from "react";
import { useRouter } from "next/navigation";
import { useLocale, useTranslations } from "next-intl";

export default function Login({
searchParams,
}: {
searchParams: { email: string };
}) {
const locale = useLocale();
const t = useTranslations("Login");
const email = searchParams.email;
const [errorMessage, setErrorMessage] = useState("");
const router = useRouter();
Expand All @@ -33,27 +36,22 @@ export default function Login({
setErrorMessage(body.message);
}
if (res.status == 200) {
router.push("/");
router.push(`/${locale}/`);
}
});
};

return (
<form
onSubmit={validateEmailCode}
className="card w-[32rem] bg-base-100 shadow-xl"
className="card w-full md:w-[32rem] bg-base-100 shadow-xl"
>
<div className="card-body gap-4">
<Image
src="/gcopy.svg"
width={50}
height={50}
alt="Picture of the author"
/>
<Image src="/gcopy.svg" width={50} height={50} alt="gcopy's logo" />
<div className="flex items-center">
<a
className="btn btn-ghost btn-circle btn-xs"
href={`/user/email-code?email=${email}`}
href={`/${locale}/user/email-code?email=${email}`}
>
<svg
xmlns="http://www.w3.org/2000/svg"
Expand All @@ -72,24 +70,23 @@ export default function Login({
</a>
<span className="ml-1">{email}</span>
</div>
<h2 className="card-title">Enter code</h2>
<p>We emailed a code to {email}. Please enter the code to sign in.</p>

<h2 className="card-title">{t("title")}</h2>
<p>{t("subTitle", { email: email })}</p>
<div>
<input name="email" type="hidden" value={email} />
<input
name="code"
type="text"
placeholder="Enter code"
placeholder={t("placeholder")}
className="input input-bordered w-full"
autoFocus
/>
<span className="text-xs">Your privacy is important to GCopy!</span>
<span className="text-xs">{t("tip")}</span>
</div>
{errorMessage && <p className="text-error">{errorMessage}</p>}
<div className="card-actions justify-end">
<button className="btn btn-primary" type="submit">
Sign in
{t("buttonText")}
</button>
</div>
</div>
Expand Down
26 changes: 0 additions & 26 deletions frontend/app/api/session/route.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,11 @@
// import { NextRequest } from "next/server";
import { cookies } from "next/headers";
import { getIronSession } from "iron-session";
import { SessionData, sessionOptions } from "@/lib/session";
import { UserInfo, defaultUserInfo } from "@/lib/types";

// login
// export async function POST(request: NextRequest) {
// const session = await getIronSession<SessionData>(cookies(), sessionOptions);

// const { username = "No username" } = (await request.json()) as {
// username: string;
// };

// session.isLoggedIn = true;
// session.username = username;
// await session.save();

// // simulate looking up the user in db
// await sleep(250);

// return Response.json(session);
// }

// read session
export async function GET() {
const session = await getIronSession<SessionData>(cookies(), sessionOptions);

// simulate looking up the user in db
// await sleep(250);

if (session.isLoggedIn !== true) {
return Response.json({ defaultUserInfo });
}
Expand All @@ -39,11 +16,8 @@ export async function GET() {
} as UserInfo);
}

// logout
export async function DELETE() {
const session = await getIronSession<SessionData>(cookies(), sessionOptions);

session.destroy();

return Response.json(defaultUserInfo);
}
24 changes: 9 additions & 15 deletions frontend/app/api/user/email-code/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ import { z } from "zod";
import { NextRequest } from "next/server";
import nodemailer from "nodemailer";
import SMTPTransport from "nodemailer/lib/smtp-transport";
import { getAcceptLanguageLocale } from "@/lib/i18n";
import { getTranslations } from "next-intl/server";

export async function POST(request: NextRequest) {
const locale = getAcceptLanguageLocale(request.headers);
const t = await getTranslations({ locale, namespace: "EmailCode" });
const body = (await request.json()) as { email: string };

const validatedFields = z
.object({
email: z.string().email({ message: "Invalid email" }),
email: z.string().email({ message: t("invalidEmail") }),
})
.safeParse({
email: body.email,
Expand Down Expand Up @@ -45,21 +49,11 @@ export async function POST(request: NextRequest) {
address: process.env.SMTP_SENDER || "",
},
to: validatedFields.data.email,
subject: code + " is your verification code",
text:
"Enter the verification code when prompted: " +
code +
". Code will expire in 5 minutes. To protect your account, do not share this code.",
html:
"<p>Enter the following verification code when prompted:<br/>" +
code +
"<br/>Code will expire in 5 minutes.<br/>To protect your account, do not share this code.</p>",
subject: t("sendEmail.subject", { code: code }),
text: t("sendEmail.text", { code: code }),
});
} catch {
return Response.json(
{ message: "Failed to send email. Please check and retry." },
{ status: 500 },
);
return Response.json({ message: t("sendEmail.failed") }, { status: 500 });
}

const session = await getIronSession<SessionData>(cookies(), sessionOptions);
Expand All @@ -68,5 +62,5 @@ export async function POST(request: NextRequest) {
session.createdTime = Date.now();
await session.save();

return Response.json({ message: "success" });
return Response.json({ message: t("sendEmail.success") });
}
20 changes: 9 additions & 11 deletions frontend/app/api/user/login/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@ import { cookies } from "next/headers";
import { getIronSession } from "iron-session";
import { SessionData, sessionOptions } from "@/lib/session";
import { z } from "zod";
import { getAcceptLanguageLocale } from "@/lib/i18n";
import { getTranslations } from "next-intl/server";

export async function POST(request: Request) {
const locale = getAcceptLanguageLocale(request.headers);
const t = await getTranslations({ locale, namespace: "Login" });
const body = (await request.json()) as {
email: string;
code: string;
};
const validatedFields = z
.object({
email: z.string().email({ message: "Invalid email" }),
email: z.string().email({ message: t("invalidEmail") }),
code: z
.string()
.regex(new RegExp("[0-9]{6}"), { message: "Incorrect code" }),
.regex(new RegExp("[0-9]{6}"), { message: t("incorrectCode") }),
})
.safeParse({
email: body.email,
Expand All @@ -37,23 +41,17 @@ export async function POST(request: Request) {

if (validatedFields.data.code == session.emailCode) {
if (Date.now() - session.createdTime > 5 * 60 * 1000) {
return Response.json(
{ email: "The code was expired. Please go back and retry." },
{ status: 422 },
);
return Response.json({ message: t("expiredCode") }, { status: 422 });
}
session.isLoggedIn = true;
await session.save();
return Response.json({
message: "success",
message: t("success"),
data: {
email: validatedFields.data.email,
},
});
}

return Response.json(
{ message: "Incorrect code. Please go back and retry." },
{ status: 401 },
);
return Response.json({ message: t("incorrectCode") }, { status: 401 });
}
Loading

0 comments on commit 55f5da2

Please sign in to comment.