wip fix registration

This commit is contained in:
Rizky 2024-03-10 16:43:56 +07:00
parent 4070f54e9b
commit 8d94e2779a
14 changed files with 416 additions and 251 deletions

View File

@ -6,5 +6,7 @@ export const _ = {
async api() { async api() {
const { res } = apiContext(this); const { res } = apiContext(this);
res.setHeader("set-cookie", `${session.cookieKey}=X`); res.setHeader("set-cookie", `${session.cookieKey}=X`);
return res;
}, },
}; };

41
app/srv/api/register.ts Normal file
View File

@ -0,0 +1,41 @@
import { apiContext } from "service-srv";
import argon from "@node-rs/argon2";
export const _ = {
url: "/_register",
async api(p: { username: string; password: string; email: string }) {
const { req, res } = apiContext(this);
const user = await _db.user.findFirst({
where: {
username: p.username,
},
select: { id: true },
});
if (!user) {
const pwd = await argon.hash(p.password);
const user = await _db.user.create({
data: {
username: p.username,
email: p.email,
password: pwd,
phone: "",
},
});
return {
status: "success",
user: {
id: user.id,
},
};
}
return {
status: "failed",
reason: `\
Register failed!
Username already exists, please choose another username.`,
};
},
};

File diff suppressed because one or more lines are too long

View File

@ -1,14 +1,12 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { page, useGlobal } from "web-utils"; import { page, useGlobal } from "web-utils";
import { EDGlobal } from "../../nova/ed/logic/ed-global"; import { EDGlobal } from "../../nova/ed/logic/ed-global";
import { edInitSync } from "../../nova/ed/logic/ed-sync";
import { Loading } from "../../utils/ui/loading";
import { isLocalhost } from "../../utils/ui/is-localhost"; import { isLocalhost } from "../../utils/ui/is-localhost";
import { Loading } from "../../utils/ui/loading";
export default page({ export default page({
url: "**", url: "**",
component: ({}) => { component: ({}) => {
const p = useGlobal(EDGlobal, "EDITOR"); const p = useGlobal(EDGlobal, "EDITOR");
useEffect(() => { useEffect(() => {
if (localStorage.getItem("prasi-session")) { if (localStorage.getItem("prasi-session")) {
@ -16,7 +14,7 @@ export default page({
location.pathname === "/ed" || location.pathname === "/ed" ||
location.pathname.startsWith("/ed/") location.pathname.startsWith("/ed/")
) { ) {
edInitSync(p); location.href = "/ed/_/_";
} else if (location.pathname.startsWith("/editor")) { } else if (location.pathname.startsWith("/editor")) {
const arr = location.pathname.split("/"); const arr = location.pathname.split("/");
if (arr.length <= 2) { if (arr.length <= 2) {

View File

@ -4,6 +4,7 @@ import { Loading } from "../../../utils/ui/loading";
export default page({ export default page({
url: "/logout", url: "/logout",
component: ({}) => { component: ({}) => {
localStorage.clear();
_api.logout().then(() => { _api.logout().then(() => {
location.href = "/login"; location.href = "/login";
}); });

View File

@ -47,6 +47,8 @@ export default page({
form.render(); form.render();
alert(s.reason); alert(s.reason);
} else { } else {
await _api.login(form.username, form.password);
alert("Registration success!");
navigate("/ed"); navigate("/ed");
} }
}} }}

View File

@ -1,14 +1,19 @@
import { page, useGlobal } from "web-utils"; import { validate } from "uuid";
import { EdBase } from "../../nova/ed/ed-base";
import { EDGlobal } from "../../nova/ed/logic/ed-global";
import { edInitSync } from "../../nova/ed/logic/ed-sync";
import { Loading } from "../../utils/ui/loading";
import init from "wasm-gzip"; import init from "wasm-gzip";
import { page, useGlobal, useLocal } from "web-utils";
import { EdBase } from "../../nova/ed/ed-base";
import { EDGlobal, PG } from "../../nova/ed/logic/ed-global";
import { edInitSync, loadSession } from "../../nova/ed/logic/ed-sync";
import { EdFormSite } from "../../nova/ed/panel/popup/site/site-form";
import { Loading } from "../../utils/ui/loading";
export default page({ export default page({
url: "/ed/:site_id/:page_id", url: "/ed/:site_id/:page_id",
component: ({}) => { component: ({}) => {
const p = useGlobal(EDGlobal, "EDITOR"); const p = useGlobal(EDGlobal, "EDITOR");
const local = useLocal({
new_site: false,
});
const w = window as any; const w = window as any;
if (!w.Y) { if (!w.Y) {
@ -23,10 +28,174 @@ export default page({
w.isEditor = true; w.isEditor = true;
if (!edInitSync(p) && !p.sync) { if (p.status === "no-site") {
return <Loading note="connecting-ws" />; return (
<div className="flex-1 flex flex-col items-center justify-center">
{local.new_site ? (
<EdFormSite
group_id=""
site={{}}
onSave={(data) => {
if (data) {
location.href = `/ed/${data.id}/_`;
}
}}
onClose={() => {}}
header={
<div className="border-b border-blue-500 text-xl">
Create New Site
</div>
}
/>
) : (
<div className="flex flex-col p-10 rounded-lg border shadow-2xl">
<div className="text-3xl">Welcome to Prasi</div>
<div className="">
You are logged in!
<br />
<br /> Now ask someone to invite to their site.
<br /> Or you can{" "}
<span
className="underline text-blue-500 cursor-pointer"
onClick={() => {
local.new_site = true;
local.render();
}}
>
create your own site
</span>
<br />
<br />
Change account?{" "}
<a
href="/logout"
className="underline text-blue-500 cursor-pointer"
>
Logout here
</a>
.
</div>
</div>
)}
</div>
);
}
if (validate(params.page_id) && validate(params.site_id)) {
if (!edInitSync(p) && !p.sync) {
return <Loading note="connecting-ws" />;
}
} else {
navSitePage(p);
return <Loading note="finding-page" />;
} }
return <EdBase />; return <EdBase />;
}, },
}); });
const navSitePage = async (p: PG) => {
loadSession(p);
const e = await _db.page.findFirst({
where: {
is_deleted: false,
is_default_layout: false,
site: validate(params.site_id)
? { id: params.site_id }
: {
org: {
org_user: {
some: {
id_user: p.user.id,
},
},
},
},
name: {
contains: "root",
mode: "insensitive",
},
},
select: { id: true, id_site: true },
orderBy: {
site: {
name: "asc",
},
},
});
if (e && e.id && e.id_site) location.href = `/ed/${e.id_site}/${e.id}`;
else {
const e = await _db.page.findFirst({
where: {
is_deleted: false,
is_default_layout: false,
site: validate(params.site_id)
? { id: params.site_id }
: {
org: {
org_user: {
some: {
id_user: p.user.id,
},
},
},
},
name: {
contains: "home",
mode: "insensitive",
},
},
select: { id: true, id_site: true },
});
if (e && e.id && e.id_site) location.href = `/ed/${e.id_site}/${e.id}`;
else {
const e = await _db.page.findFirst({
where: {
is_deleted: false,
is_default_layout: false,
site: validate(params.site_id)
? { id: params.site_id }
: {
org: {
org_user: {
some: {
id_user: p.user.id,
},
},
},
},
},
select: { id: true, id_site: true },
});
if (e) {
if (e.id && e.id_site) location.href = `/ed/${e.id_site}/${e.id}`;
else {
p.status = "no-site";
p.render();
}
} else {
if (validate(params.site_id)) {
const page = await _db.page.create({
data: {
content_tree: {
childs: [],
id: "root",
type: "root",
},
name: "home",
url: "/",
id_site: params.site_id,
},
});
location.href = `/ed/${params.site_id}/${page.id}`;
return;
} else {
p.status = "no-site";
p.render();
}
}
}
}
};

View File

@ -39,7 +39,20 @@ export const EdLeft = () => {
"h-[35px] border-b flex p-1 items-stretch text-[12px] justify-between" "h-[35px] border-b flex p-1 items-stretch text-[12px] justify-between"
)} )}
> >
<EdSitePicker /> <div className="flex items-stretch">
<EdSitePicker />
<div
className="flex items-center ml-2 text-[12px] cursor-pointer"
onClick={() => {
if (confirm("Logout ?")) {
location.href = "/logout";
}
}}
>
<div>Logout</div>
</div>
</div>
<div className="flex items-stretch space-x-1 pl-2"> <div className="flex items-stretch space-x-1 pl-2">
<EdSiteJS /> <EdSiteJS />
<EdApi /> <EdApi />

View File

@ -146,7 +146,8 @@ export const EDGlobal = {
| "reload" | "reload"
| "site-not-found" | "site-not-found"
| "page-not-found" | "page-not-found"
| "ready", | "ready"
| "no-site",
preview: { preview: {
url_cache: new Set<string>(), url_cache: new Set<string>(),
route_cache: createRouter<{ url: string; id: string }>(), route_cache: createRouter<{ url: string; id: string }>(),

View File

@ -18,7 +18,7 @@ const page = {
route: null as null | RadixRouter<{ id: string; url: string }>, route: null as null | RadixRouter<{ id: string; url: string }>,
}; };
export const edInitSync = (p: PG) => { export const loadSession = (p: PG) => {
const session = JSON.parse( const session = JSON.parse(
localStorage.getItem("prasi-session") || "null" localStorage.getItem("prasi-session") || "null"
) as { data: { user: { id: string; username: string } } }; ) as { data: { user: { id: string; username: string } } };
@ -35,6 +35,10 @@ export const edInitSync = (p: PG) => {
p.user.username = "anonymous"; p.user.username = "anonymous";
} }
};
export const edInitSync = (p: PG) => {
loadSession(p);
if (location.pathname.startsWith("/vi/")) { if (location.pathname.startsWith("/vi/")) {
if (page.list.length === 0) { if (page.list.length === 0) {
_db.page _db.page
@ -81,89 +85,6 @@ export const edInitSync = (p: PG) => {
} }
} }
if (!params.page_id) {
if (location.pathname.startsWith("/ed")) {
(async () => {
const e = await _db.page.findFirst({
where: {
is_deleted: false,
is_default_layout: false,
site: params.site_id
? { id: params.site_id }
: {
org: {
org_user: {
some: {
id_user: p.user.id,
},
},
},
},
name: {
contains: "root",
mode: "insensitive",
},
},
select: { id: true, id_site: true },
orderBy: {
site: {
name: "asc",
},
},
});
if (e) location.href = `/ed/${e.id_site}/${e.id}`;
else {
const e = await _db.page.findFirst({
where: {
is_deleted: false,
is_default_layout: false,
site: params.site_id
? { id: params.site_id }
: {
org: {
org_user: {
some: {
id_user: p.user.id,
},
},
},
},
name: {
contains: "home",
mode: "insensitive",
},
},
select: { id: true, id_site: true },
});
if (e) location.href = `/ed/${e.id_site}/${e.id}`;
else {
const e = await _db.page.findFirst({
where: {
is_deleted: false,
is_default_layout: false,
site: params.site_id
? { id: params.site_id }
: {
org: {
org_user: {
some: {
id_user: p.user.id,
},
},
},
},
},
select: { id: true, id_site: true },
});
if (e) location.href = `/ed/${e.id_site}/${e.id}`;
}
}
})();
return false;
}
}
if (p.sync) { if (p.sync) {
if (p.site.id === "--loading--") return false; if (p.site.id === "--loading--") return false;
if (params.site_id !== p.site.id) { if (params.site_id !== p.site.id) {
@ -199,6 +120,10 @@ export const edInitSync = (p: PG) => {
select: { id: true }, select: { id: true },
}) })
.then((e) => { .then((e) => {
if (params.site_id === "_") {
alert("asdsa");
return;
}
if (e) location.href = `/ed/${params.site_id}/${e.id}`; if (e) location.href = `/ed/${params.site_id}/${e.id}`;
}); });
return false; return false;

View File

@ -10,7 +10,7 @@ import {
iconNewTab, iconNewTab,
iconScrollOff, iconScrollOff,
iconScrollOn, iconScrollOn,
iconUpload iconUpload,
} from "./icons"; } from "./icons";
export const code = { export const code = {
@ -290,46 +290,21 @@ const CodeBody = () => {
</div> </div>
)} )}
{code_mode === "vsc" ? ( <div className="flex flex-1 relative">
<div className="flex flex-1 relative"> {!p.ui.popup.code.open ? (
{!p.ui.popup.code.open ? ( <Loading backdrop={false} />
<Loading backdrop={false} /> ) : (
) : ( <>
<> <iframe
<iframe className="flex flex-1 absolute inset-0 w-full h-full z-10"
className="flex flex-1 absolute inset-0 w-full h-full z-10" src={`${vscode_url}folder=/site/${p.site.id}/site/src`}
src={`${vscode_url}folder=/site/${p.site.id}/site/src`} ></iframe>
></iframe> <div className="flex flex-1 absolute inset-0 z-0 items-center justify-center">
<div className="flex flex-1 absolute inset-0 z-0 items-center justify-center"> Loading VSCode...
Loading VSCode... </div>
</div> </>
</> )}
)} </div>
</div>
) : (
<div className="flex flex-col flex-1 relative items-center justify-center space-y-2">
<div className="text-xs">VSCode is turned off</div>
<div
className="flex items-center p-2 cursor-pointer text-xs font-mono space-x-1 bg-green-700 text-white hover:opacity-40 transition-all"
onClick={async () => {
if (
confirm(
"Are you sure want to turn on VSCode?\nThis will disable old npm module (you can enable it again later)."
)
) {
localStorage.vsc_opened = "yes";
await _db.site.update({
where: { id: p.site.id },
data: { code_mode: "vsc" },
});
location.reload();
}
}}
>
<div>Turn on VSCode</div>
</div>
</div>
)}
{(local.namePicker || local.codeAssign) && ( {(local.namePicker || local.codeAssign) && (
<div <div

View File

@ -1,5 +1,5 @@
import { site } from "dbgen"; import { site } from "dbgen";
import { FC } from "react"; import { FC, ReactNode } from "react";
import { useGlobal, useLocal } from "web-utils"; import { useGlobal, useLocal } from "web-utils";
import { EDGlobal } from "../../../logic/ed-global"; import { EDGlobal } from "../../../logic/ed-global";
import { formStyle } from "../../../../../utils/ui/form.style"; import { formStyle } from "../../../../../utils/ui/form.style";
@ -8,14 +8,16 @@ import { Input } from "../../../../../utils/ui/form/input";
export const EdFormSite: FC<{ export const EdFormSite: FC<{
site: Partial<site>; site: Partial<site>;
onClose: () => void; onClose: () => void;
onSave: () => void; onSave: (data: null | site) => void;
group_id: string; group_id: string;
}> = ({ site, onClose, onSave, group_id }) => { header?: ReactNode;
}> = ({ site, onClose, onSave, group_id, header }) => {
const p = useGlobal(EDGlobal, "EDITOR"); const p = useGlobal(EDGlobal, "EDITOR");
const local = useLocal({ const local = useLocal({
init: false, init: false,
saving: false, saving: false,
preventClose: false, preventClose: false,
domain_follow_name: false,
}); });
const form = useLocal({} as Partial<site>); const form = useLocal({} as Partial<site>);
@ -57,15 +59,43 @@ export const EdFormSite: FC<{
local.saving = true; local.saving = true;
local.render(); local.render();
try { try {
let gid = group_id;
if (!gid) {
let org = await _db.org.findFirst({
where: {
org_user: {
some: {
id_user: p.user.id,
},
},
},
});
if (!org) {
org = await _db.org.create({
data: {
name: `${p.user.username}'s sites`,
org_user: {
create: { id_user: p.user.id },
},
},
});
}
if (org) {
gid = org.id;
}
}
let data = null as null | site;
if (!form.id) { if (!form.id) {
try { try {
await _db.site.create({ data = await _db.site.create({
data: { data: {
name: form.name, name: form.name,
favicon: "", favicon: "",
domain: form.domain || "", domain: form.domain || "",
id_user: p.user.id, id_user: p.user.id,
id_org: group_id, id_org: gid,
responsive: form.responsive, responsive: form.responsive,
}, },
}); });
@ -73,7 +103,7 @@ export const EdFormSite: FC<{
alert(e); alert(e);
} }
} else { } else {
await _db.site.update({ data = await _db.site.update({
data: { data: {
name: form.name, name: form.name,
domain: form.domain, domain: form.domain,
@ -82,7 +112,7 @@ export const EdFormSite: FC<{
where: { id: form.id }, where: { id: form.id },
}); });
} }
onSave(); onSave(data);
} catch (e) { } catch (e) {
alert(e); alert(e);
} }
@ -94,101 +124,109 @@ export const EdFormSite: FC<{
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
}} }}
className={cx(formStyle, "bg-white shadow-2xl border")}
> >
<label> {header}
<span>Name</span> <div className={cx(formStyle, "bg-white shadow-2xl border")}>
<Input
form={form}
autoFocus
name={"name"}
onBlur={() => {
if (!form.domain) {
form.domain = (form.name || "")
.toLowerCase()
.replace(/[^a-z0-9\-_\.]/g, "");
form.render();
}
}}
/>
</label>
<label>
<span>Domain</span>
<Input
form={form}
name={"domain"}
onChange={(text) => {
return text.replace(/[^a-z0-9\-_\.]/g, "");
}}
/>
</label>
<label>
<span>Responsive</span>
<select
value={form.responsive}
onChange={(e) => {
form.responsive = e.currentTarget.value as any;
local.render();
}}
>
<option value="all">All</option>
<option value="mobile-only">Mobile Only</option>
<option value="desktop-only">Desktop Only</option>
</select>
</label>
{form.id && (
<label> <label>
<span>Site ID:</span> <span>Name</span>
<Input form={form} name="id" disabled /> <Input
</label> form={form}
)} autoFocus
name={"name"}
onChange={(e) => {
if (!form.domain) {
local.domain_follow_name = true;
}
<div className="flex"> if (local.domain_follow_name) {
<button type="submit" disabled={local.saving} className="flex-1"> form.domain = (form.name || "")
{local.saving ? "Saving..." : "Save"} .toLowerCase()
</button> .replace(/[^a-z0-9\-_\.]/g, "");
{form.id && (
<button form.render();
className="bg-red-600 w-[40px] flex justify-center items-center"
onClick={async () => {
if (confirm("Delete site cannot be undone. Are you sure ?")) {
if (
prompt(
"Please type 'yes' (without quote) to confirm deletion: "
)?.toLowerCase() === "yes"
) {
await _db.site.update({
where: {
id: site.id,
},
data: {
is_deleted: true,
},
});
onSave();
}
} }
}} }}
/>
</label>
<label>
<span>Domain</span>
<Input
form={form}
name={"domain"}
onChange={(text) => {
return text.replace(/[^a-z0-9\-_\.]/g, "");
}}
/>
</label>
<label>
<span>Responsive</span>
<select
value={form.responsive}
onChange={(e) => {
form.responsive = e.currentTarget.value as any;
local.render();
}}
> >
<svg <option value="all">All</option>
xmlns="http://www.w3.org/2000/svg" <option value="mobile-only">Mobile Only</option>
width="15" <option value="desktop-only">Desktop Only</option>
height="15" </select>
fill="none" </label>
viewBox="0 0 15 15"
> {form.id && (
<path <label>
fill="currentColor" <span>Site ID:</span>
fillRule="evenodd" <Input form={form} name="id" disabled />
d="M5.5 1a.5.5 0 000 1h4a.5.5 0 000-1h-4zM3 3.5a.5.5 0 01.5-.5h8a.5.5 0 010 1H11v8a1 1 0 01-1 1H5a1 1 0 01-1-1V4h-.5a.5.5 0 01-.5-.5zM5 4h5v8H5V4z" </label>
clipRule="evenodd"
></path>
</svg>
</button>
)} )}
<div className="flex">
<button type="submit" disabled={local.saving} className="flex-1">
{local.saving ? "Saving..." : "Save"}
</button>
{form.id && (
<button
className="bg-red-600 w-[40px] flex justify-center items-center"
onClick={async () => {
if (
confirm("Delete site cannot be undone. Are you sure ?")
) {
if (
prompt(
"Please type 'yes' (without quote) to confirm deletion: "
)?.toLowerCase() === "yes"
) {
let data = await _db.site.update({
where: {
id: site.id,
},
data: {
is_deleted: true,
},
});
onSave(data);
}
}
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="15"
height="15"
fill="none"
viewBox="0 0 15 15"
>
<path
fill="currentColor"
fillRule="evenodd"
d="M5.5 1a.5.5 0 000 1h4a.5.5 0 000-1h-4zM3 3.5a.5.5 0 01.5-.5h8a.5.5 0 010 1H11v8a1 1 0 01-1 1H5a1 1 0 01-1-1V4h-.5a.5.5 0 01-.5-.5zM5 4h5v8H5V4z"
clipRule="evenodd"
></path>
</svg>
</button>
)}
</div>
</div> </div>
</form> </form>
</div> </div>

View File

@ -34,7 +34,7 @@ export const base = {
domain: string; domain: string;
api_url: string; api_url: string;
code: { code: {
mode: "new"; mode: "new" | "vsc";
}; };
api: any; api: any;
db: any; db: any;

View File

@ -51,7 +51,7 @@ export const initBaseRoute = async (isPreviewProd: boolean) => {
} }
base.site = res.site; base.site = res.site;
base.site.code = { mode: "new" }; base.site.code = { mode: "vsc" };
await injectSiteScript(); await injectSiteScript();
base.site.api = apiProxy(base.site.api_url); base.site.api = apiProxy(base.site.api_url);