From 20add6b617cc9d205299f4ca02758b0ac55639ad Mon Sep 17 00:00:00 2001 From: liuweiqing Date: Thu, 8 Feb 2024 10:28:00 +0800 Subject: [PATCH 01/23] =?UTF-8?q?feat:=20supa=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/lemon/callback/route.ts | 92 +++++++++++++++++++++++++++++ app/api/supa/data/route.ts | 49 +++++++++++++++ app/api/supa/paper-numbers/route.ts | 61 +++++++++++++++++++ app/api/supa/user-papers/route.ts | 43 ++++++++++++++ app/api/supa/vip/route.ts | 0 utils/supabase/server.ts | 5 ++ 6 files changed, 250 insertions(+) create mode 100644 app/api/lemon/callback/route.ts create mode 100644 app/api/supa/data/route.ts create mode 100644 app/api/supa/paper-numbers/route.ts create mode 100644 app/api/supa/user-papers/route.ts create mode 100644 app/api/supa/vip/route.ts diff --git a/app/api/lemon/callback/route.ts b/app/api/lemon/callback/route.ts new file mode 100644 index 0000000..8b70958 --- /dev/null +++ b/app/api/lemon/callback/route.ts @@ -0,0 +1,92 @@ +// app/api/payment/webhooks/route.ts +import { headers } from "next/headers"; +import { Buffer } from "buffer"; +import crypto from "crypto"; +import rawBody from "raw-body"; +import { Readable } from "stream"; +import { NextResponse } from "next/server"; +import { createClient } from "@/utils/supabase/server"; +import { cookies } from "next/headers"; +import { SupabaseClient } from "@supabase/supabase-js"; + +export async function POST(request: Request) { + const cookieStore = cookies(); + const supabaseAdmin = createClient(cookieStore); + console.log("webhook"); + const body = await rawBody(Readable.from(Buffer.from(await request.text()))); + const headersList = headers(); + const payload = JSON.parse(body.toString()); + + const sigString = headersList.get("x-signature"); + if (!sigString) { + console.error(`Signature header not found`); + return NextResponse.json( + { message: "Signature header not found" }, + { status: 401 } + ); + } + const secret = process.env.LEMONS_SQUEEZY_SIGNATURE_SECRET as string; + const hmac = crypto.createHmac("sha256", secret); + const digest = Buffer.from(hmac.update(body).digest("hex"), "utf8"); + const signature = Buffer.from( + Array.isArray(sigString) ? sigString.join("") : sigString || "", + "utf8" + ); + // 校验签名 + if (!crypto.timingSafeEqual(digest, signature)) { + return NextResponse.json({ message: "Invalid signature" }, { status: 403 }); + } + + const userEmail = (payload.attributes && payload.attributes.user_email) || ""; + // 检查custom里的参数 + if (!userEmail) + return NextResponse.json( + { message: "No userEmail provided" }, + { status: 403 } + ); + return await setVip(supabaseAdmin, userEmail); +} + +async function getUserId(supabaseAdmin: SupabaseClient, email: string) { + const { data, error } = await supabaseAdmin + .from("auth.users") + .select("id") + .eq("email", email) + .single(); + + if (error) { + console.error("查询用户 ID 失败:", error); + return null; + } + + return data.id; +} + +async function setVip( + supabaseAdmin: SupabaseClient, + email: string, + isVip = true, + startDate = new Date(), + endDate = new Date() +) { + const userId = await getUserId(supabaseAdmin, email); + if (!userId) + return NextResponse.json({ message: "No user found" }, { status: 403 }); + const { data, error } = await supabaseAdmin.from("vip_statuses").upsert( + { + user_id: userId, + is_vip: isVip, + start_date: startDate, + end_date: endDate, + }, + { onConflict: "user_id" } + ); + if (error) { + console.error("设置 VIP 失败:", error); + return NextResponse.json( + { message: "Failed to set VIP 设置 VIP 状态失败" }, + { status: 403 } + ); + } + return NextResponse.json({ message: "Success VIP 状态已更新:" }); +} diff --git a/app/api/supa/data/route.ts b/app/api/supa/data/route.ts new file mode 100644 index 0000000..79a4d44 --- /dev/null +++ b/app/api/supa/data/route.ts @@ -0,0 +1,49 @@ +import { createClient } from "@/utils/supabase/server"; +import { cookies } from "next/headers"; + +export async function POST(req: Request) { + const cookieStore = cookies(); + const supabaseAdmin = createClient(cookieStore); + + // 从请求体中提取数据 + const { userId, paperContent, paperReference, paperNumber } = + await req.json(); + + // 使用Supabase客户端进行数据上传 + const { data, error } = await supabaseAdmin.from("user_paper").upsert( + [ + { + user_id: userId, + paper_content: paperContent, + paper_reference: paperReference, + paper_number: paperNumber, + }, + ], + { onConflict: "user_id, paper_number" } + ); + // console.log("测试supabaseAdmin", supabaseAdmin); + // 返回JSON格式的响应 + if (error) { + // 如果有错误,返回错误信息 + return new Response( + JSON.stringify({ message: "Error saving paper", error: error.message }), + { + status: 400, // 或其他适当的错误状态码 + headers: { + "Content-Type": "application/json", + }, + } + ); + } else { + // 成功保存,返回成功信息 + return new Response( + JSON.stringify({ message: "Success in user_paper save" }), + { + status: 200, + headers: { + "Content-Type": "application/json", + }, + } + ); + } +} diff --git a/app/api/supa/paper-numbers/route.ts b/app/api/supa/paper-numbers/route.ts new file mode 100644 index 0000000..12e7656 --- /dev/null +++ b/app/api/supa/paper-numbers/route.ts @@ -0,0 +1,61 @@ +import { createClient } from "@/utils/supabase/server"; +import { cookies } from "next/headers"; + +export async function POST(req: Request) { + try { + const cookieStore = cookies(); + const supabaseAdmin = createClient(cookieStore); + const { + data: { user }, + } = await supabaseAdmin.auth.getUser(); + // 从请求体中提取数据 + if (!user) throw new Error("No user found"); + // const { userId } = await req.json(); + const userId = await user.id; + console.log("userId", userId); + const { data, error } = await supabaseAdmin + .from("user_paper") // 指定表名 + .select("paper_number") // 仅选择paper_number列 + .eq("user_id", userId); // 筛选特定user_id的记录 + + // 返回JSON格式的响应 + if (error) { + // 如果有错误,返回错误信息 + return new Response( + JSON.stringify({ + message: "Error get paper numbers", + error: error.message, + }), + { + status: 400, // 或其他适当的错误状态码 + headers: { + "Content-Type": "application/json", + }, + } + ); + } else { + console.log("获取到的用户论文数量:", data); + // 成功保存,返回成功信息 + return new Response(JSON.stringify(data), { + status: 200, + headers: { + "Content-Type": "application/json", + }, + }); + } + } catch (e) { + console.error("Error get paper numbers", e); + return new Response( + JSON.stringify({ + message: "Error get paper numbers", + error: e.message, + }), + { + status: 400, // 或其他适当的错误状态码 + headers: { + "Content-Type": "application/json", + }, + } + ); + } +} diff --git a/app/api/supa/user-papers/route.ts b/app/api/supa/user-papers/route.ts new file mode 100644 index 0000000..9a6631b --- /dev/null +++ b/app/api/supa/user-papers/route.ts @@ -0,0 +1,43 @@ +import { createClient } from "@/utils/supabase/server"; +import { cookies } from "next/headers"; + +export async function POST(req: Request) { + const cookieStore = cookies(); + const supabaseAdmin = createClient(cookieStore); + + // 从请求体中提取数据 + const { userId, paperNumber } = await req.json(); + + // 使用Supabase客户端进行数据上传 + const { data, error } = await supabaseAdmin + .from("user_paper") // 指定表名 + .select("paper_content,paper_reference") // 仅选择paper_content列 + .eq("user_id", userId) // 筛选特定user_id的记录 + .eq("paper_number", paperNumber) + .single(); // 筛选特定paper_number的记录 + + // 返回JSON格式的响应 + if (error) { + // 如果有错误,返回错误信息 + return new Response( + JSON.stringify({ + message: "Error get specific paper", + error: error.message, + }), + { + status: 400, // 或其他适当的错误状态码 + headers: { + "Content-Type": "application/json", + }, + } + ); + } else { + // 成功保存,返回成功信息 + return new Response(JSON.stringify(data), { + status: 200, + headers: { + "Content-Type": "application/json", + }, + }); + } +} diff --git a/app/api/supa/vip/route.ts b/app/api/supa/vip/route.ts new file mode 100644 index 0000000..e69de29 diff --git a/utils/supabase/server.ts b/utils/supabase/server.ts index 8c7bfb3..57e8b8f 100644 --- a/utils/supabase/server.ts +++ b/utils/supabase/server.ts @@ -2,6 +2,11 @@ import { createServerClient, type CookieOptions } from "@supabase/ssr"; import { cookies } from "next/headers"; export const createClient = (cookieStore: ReturnType) => { + // console.log( + // "process.env.SUPABASE_SECRET_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!", + // process.env.SUPABASE_SECRET_KEY || + // process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! + // ); return createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SECRET_KEY || From fe31198124f9459c579260018ba673a1353b077f Mon Sep 17 00:00:00 2001 From: liuweiqing Date: Thu, 8 Feb 2024 10:28:52 +0800 Subject: [PATCH 02/23] =?UTF-8?q?feat:=20=E7=AE=A1=E7=90=86=E4=BA=91?= =?UTF-8?q?=E7=AB=AF=E5=A4=9A=E7=AF=87=E8=AE=BA=E6=96=87=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/PaperManagement.tsx | 155 ++++++++++++++++++++++++ components/ParagraphDeleteInterface.tsx | 18 ++- 2 files changed, 168 insertions(+), 5 deletions(-) create mode 100644 components/PaperManagement.tsx diff --git a/components/PaperManagement.tsx b/components/PaperManagement.tsx new file mode 100644 index 0000000..eef5a71 --- /dev/null +++ b/components/PaperManagement.tsx @@ -0,0 +1,155 @@ +"use client"; + +import { useCallback, useState, useEffect } from "react"; +//redux +import { useAppDispatch, useAppSelector } from "@/app/store"; +import { + setEditorContent, + setReferencesRedux, +} from "@/app/store/slices/authSlice"; +import { + setPaperNumberRedux, + setContentUpdatedFromNetwork, +} from "@/app/store/slices/stateSlice"; +//supabase +import { createClient } from "@/utils/supabase/client"; +import { + getUser, + getUserPaperNumbers, + getUserPaper, + submitPaper, + deletePaper, +} from "@/utils/supabase/supabaseutils"; +//动画 +import { CSSTransition } from "react-transition-group"; +//删除远程论文按钮 +import ParagraphDeleteButton from "@/components/ParagraphDeleteInterface"; + +const PaperManagement = () => { + //supabase + const supabase = createClient(); + //redux + const dispatch = useAppDispatch(); + const paperNumberRedux = useAppSelector( + (state) => state.state.paperNumberRedux + ); + const showPaperManagement = useAppSelector( + (state) => state.state.showPaperManagement + ); + //状态 + const [paperNumbers, setPaperNumbers] = useState([]); + //user id的状态设置 + const [userId, setUserId] = useState(""); + + //获取用户存储在云端的论文 + // 使用useCallback定义一个记忆化的函数来获取用户论文 + const fetchPapers = useCallback(async () => { + const user = await getUser(supabase); + if (user && user.id) { + const numbers = await getUserPaperNumbers(user.id); + setPaperNumbers(numbers || []); // 直接在这里更新状态 + setUserId(user.id); + } + }, [supabase]); // 依赖项数组中包含supabase,因为它可能会影响到fetchPapers函数的结果 + + // 使用useEffect在组件挂载后立即获取数据 + useEffect(() => { + fetchPapers(); + }, [fetchPapers]); + + const handlePaperClick = async (paperNumber: string) => { + const data = await getUserPaper(userId, paperNumber); // 假设这个函数异步获取论文内容 + if (!data) { + throw new Error("查询出错"); + } + console.log("paperNumber", paperNumber); + // 更新状态以反映选中的论文内容 + dispatch(setEditorContent(data.paper_content)); // 更新 Redux store + dispatch(setReferencesRedux(JSON.parse(data.paper_reference))); // 清空引用列表 + dispatch(setPaperNumberRedux(paperNumber)); // 更新当前论文编号 + //从网络请求中更新editorContent时,同时设置contentUpdatedFromNetwork为true + dispatch(setContentUpdatedFromNetwork(true)); // 更新 Redux store + }; + + const handleAddPaperClick = async () => { + // 添加一个新的空白论文 + await submitPaper( + supabase, + "This is a blank page", + [], + String(Math.max(...paperNumbers.map(Number)) + 1) + ); + // 重新获取论文列表 + await fetchPapers(); + }; + + const noop = (...args: any) => {}; + + return ( + + <> +
+
+

Paper Management

+
+ +
+

Your Papers

+ {paperNumbers.length > 0 ? ( +
    + {[...paperNumbers] + .sort((a, b) => parseInt(a, 10) - parseInt(b, 10)) + .map((number, index) => ( +
  • handlePaperClick(number)} + > + Paper {number} + { + await deletePaper(supabase, userId, number); + const numbers = await getUserPaperNumbers( + userId, + supabase + ); + setPaperNumbers(numbers || []); // 直接在这里更新状态 + }} + isRemovePaper={true} + title="Do you want to delete this paper?" + text="This action cannot be undone" + > + {/* handleTitleChange(index, e.target.value)} + placeholder="Enter paper title" + className="mt-2 p-2 border rounded" + /> */} +
  • + ))} +
+ ) : ( +

No papers found.

+ )} +
+
+ +
+ ); +}; + +export default PaperManagement; diff --git a/components/ParagraphDeleteInterface.tsx b/components/ParagraphDeleteInterface.tsx index 012dcc0..3a2c00e 100644 --- a/components/ParagraphDeleteInterface.tsx +++ b/components/ParagraphDeleteInterface.tsx @@ -8,14 +8,18 @@ interface SweetAlertComponentProps { removeReferenceUpdateIndex: (index: number, rmPg: boolean) => void; } -const ParagraphDeleteButton: React.FC = ({ +const ParagraphDeleteButton: React.FC = ({ index, removeReferenceUpdateIndex, + isRemovePaper = false, + title = "需要同时删除与文献相关的整个段落吗", + text = "根据周围的换行符来判断是否是同一个段落", }) => { + //这里传递函数的时候应该把参数先提前弄好 2.7 const showAlert = async () => { const result = await Swal.fire({ - title: "需要同时删除与文献相关的整个段落吗?", - text: "根据周围的换行符来判断是否是同一个段落", + title: title, + text: text, icon: "warning", showCancelButton: true, confirmButtonColor: "#3085d6", @@ -23,10 +27,14 @@ const ParagraphDeleteButton: React.FC = ({ confirmButtonText: "Yes, delete it!", }); if (result.isConfirmed) { - removeReferenceUpdateIndex(index, true); + if (isRemovePaper) { + removeReferenceUpdateIndex(index, true); + } else { + removeReferenceUpdateIndex(); + } // Swal.fire("Deleted!", "Your file has been deleted.", "success"); } else { - removeReferenceUpdateIndex(index, false); + if (!isRemovePaper) removeReferenceUpdateIndex(index, false); // Swal.fire("Cancelled", "Your imaginary file is safe :)", "error"); } }; From 2be00c56d311a0d522bee693a3a0c672e9d4e13c Mon Sep 17 00:00:00 2001 From: liuweiqing Date: Thu, 8 Feb 2024 10:29:21 +0800 Subject: [PATCH 03/23] chore: other --- .gitignore | 3 ++- app/globals.css | 33 +++++++++++++++++++++++++++++++++ components/AuthButton.tsx | 8 ++++++-- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index fd9e3ac..a9190de 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,5 @@ yarn-error.log* *.tsbuildinfo next-env.d.ts -pass \ No newline at end of file +pass +.env diff --git a/app/globals.css b/app/globals.css index 8caced0..1e565dd 100644 --- a/app/globals.css +++ b/app/globals.css @@ -76,3 +76,36 @@ .vip-icon { animation: flash 1s linear infinite; } + +/* 动画的基本样式 */ +.slide-enter { + opacity: 0; + transform: translateY(100%); /* 从底部滑入 */ +} +.slide-enter-active { + opacity: 1; + transform: translateY(0); + transition: opacity 300ms ease-out, transform 300ms ease-out; +} + +.slide-exit { + opacity: 1; +} +.slide-exit-active { + opacity: 0; + transform: translateY(100%); /* 向底部滑出 */ + transition: opacity 300ms ease-in, transform 300ms ease-in; +} + +.paper-management-container { + position: fixed; /* 或者使用 `absolute` 根据需要 */ + top: 50%; /* 调整到视口的垂直中心 */ + left: 50%; /* 调整到视口的水平中心 */ + background-color: rgba(255, 255, 255, 0.5); + transform: translate( + -50%, + -50% + ); /* 从中心点向上和向左偏移自身的50%,确保组件居中 */ + z-index: 1000; /* 确保悬浮层在其他内容之上 */ + /* 可以添加其他样式来美化组件,如背景色、阴影等 */ +} diff --git a/components/AuthButton.tsx b/components/AuthButton.tsx index 631a838..0148b70 100644 --- a/components/AuthButton.tsx +++ b/components/AuthButton.tsx @@ -1,7 +1,10 @@ +// "use server"; + import { createClient } from "@/utils/supabase/server"; import Link from "next/link"; import { cookies } from "next/headers"; import { redirect } from "next/navigation"; +// import { signOut } from "@/utils/supabase/serversignout"; export default async function AuthButton() { const cookieStore = cookies(); @@ -17,15 +20,16 @@ export default async function AuthButton() { const cookieStore = cookies(); const supabase = createClient(cookieStore); await supabase.auth.signOut(); + return redirect("/login"); }; return user ? (
Hey, {user.email}! -
+ {/*
VIP -
+
*/}
- {/* */} + + ); +} + +export default BuyVipButton; diff --git a/components/PaperListButton.tsx b/components/PaperListButton.tsx index 0ac61ea..3569633 100644 --- a/components/PaperListButton.tsx +++ b/components/PaperListButton.tsx @@ -1,7 +1,7 @@ "use client"; import React from "react"; -import { useAppDispatch } from "@/app/store"; // 确保路径正确 -import { setShowPaperManagement } from "@/app/store/slices/stateSlice"; // 确保路径正确 +import { useAppDispatch } from "@/app/store"; +import { setShowPaperManagement } from "@/app/store/slices/stateSlice"; export default function PaperListButton() { const dispatch = useAppDispatch(); diff --git a/components/PaperManagement.tsx b/components/PaperManagement.tsx index eef5a71..c1c9cfa 100644 --- a/components/PaperManagement.tsx +++ b/components/PaperManagement.tsx @@ -19,11 +19,14 @@ import { getUserPaper, submitPaper, deletePaper, + fetchUserVipStatus, } from "@/utils/supabase/supabaseutils"; //动画 import { CSSTransition } from "react-transition-group"; //删除远程论文按钮 import ParagraphDeleteButton from "@/components/ParagraphDeleteInterface"; +//vip充值按钮 +import BuyVipButton from "@/components/BuyVipButton"; // 假设这是购买VIP的按钮组件 const PaperManagement = () => { //supabase @@ -36,29 +39,43 @@ const PaperManagement = () => { const showPaperManagement = useAppSelector( (state) => state.state.showPaperManagement ); - //状态 + //获取的论文数量列表状态 const [paperNumbers, setPaperNumbers] = useState([]); //user id的状态设置 const [userId, setUserId] = useState(""); + //vip状态 + const [isVip, setIsVip] = useState(false); - //获取用户存储在云端的论文 - // 使用useCallback定义一个记忆化的函数来获取用户论文 + //获取用户存储在云端的论文,使用useCallback定义一个记忆化的函数来获取用户论文 const fetchPapers = useCallback(async () => { const user = await getUser(supabase); if (user && user.id) { - const numbers = await getUserPaperNumbers(user.id); + const numbers = await getUserPaperNumbers(user.id, supabase); setPaperNumbers(numbers || []); // 直接在这里更新状态 setUserId(user.id); } }, [supabase]); // 依赖项数组中包含supabase,因为它可能会影响到fetchPapers函数的结果 + //获取用户VIP状态 + const initFetch = async () => { + const user = await getUser(); + if (user && user.id) { + const isVip = await fetchUserVipStatus(user.id); + console.log("isVip", isVip); + setIsVip(isVip); + } + }; + useEffect(() => { + initFetch(); + }, []); + // 使用useEffect在组件挂载后立即获取数据 useEffect(() => { fetchPapers(); }, [fetchPapers]); const handlePaperClick = async (paperNumber: string) => { - const data = await getUserPaper(userId, paperNumber); // 假设这个函数异步获取论文内容 + const data = await getUserPaper(userId, paperNumber, supabase); // 假设这个函数异步获取论文内容 if (!data) { throw new Error("查询出错"); } @@ -71,20 +88,26 @@ const PaperManagement = () => { dispatch(setContentUpdatedFromNetwork(true)); // 更新 Redux store }; + function getNextPaperNumber(paperNumbers: string[]) { + if (paperNumbers.length === 0) { + return "1"; + } else { + return String(Math.max(...paperNumbers.map(Number)) + 1); + } + } + const handleAddPaperClick = async () => { // 添加一个新的空白论文 await submitPaper( supabase, "This is a blank page", [], - String(Math.max(...paperNumbers.map(Number)) + 1) + getNextPaperNumber(paperNumbers) ); // 重新获取论文列表 await fetchPapers(); }; - const noop = (...args: any) => {}; - return ( {

Paper Management

- -
-

Your Papers

- {paperNumbers.length > 0 ? ( -
    - {[...paperNumbers] - .sort((a, b) => parseInt(a, 10) - parseInt(b, 10)) - .map((number, index) => ( -
  • handlePaperClick(number)} - > - Paper {number} - { - await deletePaper(supabase, userId, number); - const numbers = await getUserPaperNumbers( - userId, - supabase - ); - setPaperNumbers(numbers || []); // 直接在这里更新状态 - }} - isRemovePaper={true} - title="Do you want to delete this paper?" - text="This action cannot be undone" - > - {/* + +
    +

    Your Papers

    + {paperNumbers.length > 0 ? ( +
      + {[...paperNumbers] + .sort((a, b) => parseInt(a, 10) - parseInt(b, 10)) + .map((number, index) => ( +
    • handlePaperClick(number)} + > + Paper {number} + { + await deletePaper(supabase, userId, number); + const numbers = await getUserPaperNumbers( + userId, + supabase + ); + setPaperNumbers(numbers || []); // 直接在这里更新状态 + }} + isRemovePaper={true} + title="Do you want to delete this paper?" + text="This action cannot be undone" + > + {/* handleTitleChange(index, e.target.value)} placeholder="Enter paper title" className="mt-2 p-2 border rounded" /> */} -
    • - ))} -
    - ) : ( -

    No papers found.

    - )} -
    +
  • + ))} +
+ ) : ( +

No papers found.

+ )} +
+
+ ) : ( + + )} diff --git a/utils/supabase/supabaseutils.ts b/utils/supabase/supabaseutils.ts index 94ffaa5..867ede6 100644 --- a/utils/supabase/supabaseutils.ts +++ b/utils/supabase/supabaseutils.ts @@ -147,11 +147,14 @@ export async function fetchUserVipStatus(userId: string) { .select("is_vip") .eq("user_id", userId) .single(); - if (error) { console.error("Error fetching VIP status:", error); - return null; + return false; + } + if ("is_vip" in data) { + console.log("VIP status:", data.is_vip); + return data.is_vip; + } else { + return false; } - - return data?.is_vip; } From 6b9dce875d554d2511c049e9033d90c39cf05730 Mon Sep 17 00:00:00 2001 From: liuweiqing Date: Thu, 8 Feb 2024 15:47:37 +0800 Subject: [PATCH 12/23] =?UTF-8?q?chore:=20sql=E5=A4=87=E4=BB=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/supabase/supabaseaql.sql | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 utils/supabase/supabaseaql.sql diff --git a/utils/supabase/supabaseaql.sql b/utils/supabase/supabaseaql.sql new file mode 100644 index 0000000..4b33757 --- /dev/null +++ b/utils/supabase/supabaseaql.sql @@ -0,0 +1,11 @@ +CREATE POLICY "userpaper" ON public.user_paper FOR +INSERT WITH CHECK (auth.uid() = user_id); +-- Super base的表 +create table public."user_paper" ( + id bigint generated by default as identity, + created_at timestamp with time zone not null default now(), + user_id UUID REFERENCES auth.users NOT NULL, + paper_content character varying [] null, + paper_reference character varying [] null, + constraint userPaper_pkey primary key (id) +) tablespace pg_default; \ No newline at end of file From 2061f6c36aa50c349d91e722e7fcfbb55052cc52 Mon Sep 17 00:00:00 2001 From: liuweiqing Date: Thu, 8 Feb 2024 15:47:49 +0800 Subject: [PATCH 13/23] style: gold color --- tailwind.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tailwind.config.js b/tailwind.config.js index 97b4613..d687c88 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -16,6 +16,7 @@ module.exports = { "blue-gray": { 100: "#F0F4F8", // 这里使用你选择的颜色值 }, + gold: "#FFD700", }, }, }, From 481ffd0697bac6daa8e56de1d67b91f1b2cbe2c0 Mon Sep 17 00:00:00 2001 From: liuweiqing Date: Thu, 8 Feb 2024 21:57:21 +0800 Subject: [PATCH 14/23] chore: others --- app/store/slices/stateSlice.ts | 2 +- components/BuyVipButton.tsx | 2 +- components/PaperManagement.tsx | 1 + components/QuillEditor.tsx | 6 +++--- utils/supabase/supabaseaql.sql | 23 ++++++++++++++++++++++- utils/supabase/supabaseutils.ts | 2 +- 6 files changed, 29 insertions(+), 7 deletions(-) diff --git a/app/store/slices/stateSlice.ts b/app/store/slices/stateSlice.ts index 826270f..c8d44ec 100644 --- a/app/store/slices/stateSlice.ts +++ b/app/store/slices/stateSlice.ts @@ -7,7 +7,7 @@ export interface APIState { const initialState: APIState = { showPaperManagement: false, - paperNumberRedux: "", + paperNumberRedux: "1", //默认得给个值 contentUpdatedFromNetwork: false, }; diff --git a/components/BuyVipButton.tsx b/components/BuyVipButton.tsx index 489b140..4daed8b 100644 --- a/components/BuyVipButton.tsx +++ b/components/BuyVipButton.tsx @@ -3,7 +3,7 @@ import React from "react"; // BuyVipButton 组件 function BuyVipButton() { // 这是购买VIP的目标URL - const targetUrl = "https://store.paperai.life"; + const targetUrl = "https://store.paperai.life/checkout"; return (