update
This commit is contained in:
parent
60e6fa9a85
commit
086e539155
11 changed files with 289 additions and 55 deletions
9
.env.sample
Executable file
9
.env.sample
Executable file
|
@ -0,0 +1,9 @@
|
|||
BASE_URL="https://zv.example.org"
|
||||
AUTH_SECRET="Ah6Lm3h8Qmnhm8oDfw09AOUYEOMKpzfqIFPjBA2waLPm9SD65BA1eSXde9OS80nE"
|
||||
DATABASE_URL="Server=aptabase_db;Port=5432;User Id=aptabase;Password=0000000000000000000000000000000000000000000000000000000000000000;Database=aptabase"
|
||||
CLICKHOUSE_URL="Host=aptabase_events_db;Port=8123;Username=aptabase;Password=0000000000000000000000000000000000000000000000000000000000000000"
|
||||
SMTP_HOST="smtp.resend.com"
|
||||
SMTP_PORT="465"
|
||||
SMTP_USERNAME="resend"
|
||||
SMTP_PASSWORD=""
|
||||
SMTP_FROM_ADDRESS="no-reply@example.org"
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
.env
|
||||
src/node_modules
|
||||
src/wwwroot/
|
||||
src/wwwroot/
|
||||
tmp/
|
18
README.md
18
README.md
|
@ -4,6 +4,7 @@ A fork of Aptabase Server software, modified to only work with projects using Za
|
|||
|
||||
## Changes
|
||||
- Updated app key generation to use `ZV` instead of `SH`
|
||||
- Updated the name "Aptabase" to "Zalvena" in some sections
|
||||
- Updated browserslist
|
||||
- Updated `node:18` to `over/bun:1` in `Dockerfile`
|
||||
- Updated the pacakge `@types/node` from `20.8.7` to `22.13.4`
|
||||
|
@ -41,6 +42,8 @@ A fork of Aptabase Server software, modified to only work with projects using Za
|
|||
- Updated the pacakge `rehype-highlight` from `7.0.0` to `7.0.2`
|
||||
- Updated the pacakge `remark-gfm` from `4.0.0` to `4.0.1`
|
||||
- Updated the pacakge `sonner` from `1.0.3` to `2.0.0`
|
||||
- Added `.env.sample`
|
||||
- Redesigned login screen
|
||||
- Remote sources removed to make this fork comply with SudoVanilla Umbrella Policy
|
||||
- Default avatar points to https://md.sudovanilla.org/images/icons/Aptabase.jpg
|
||||
- Flags point to https://md.sudovanilla.org/images/flags/
|
||||
|
@ -51,6 +54,21 @@ A fork of Aptabase Server software, modified to only work with projects using Za
|
|||
- Removed `/tests/`
|
||||
- Removed `/tools/`
|
||||
|
||||
## Planned Changes
|
||||
- Revamp emails
|
||||
- Use SudoVanilla Gateway email template
|
||||
- Revamp design
|
||||
- Use Mona Font
|
||||
- Replace sidebar layout with header
|
||||
- Replace Tabler icons with Iconoir
|
||||
- Add SudoVanilla Gateway OpenID Login
|
||||
- Add new instruction screens
|
||||
- Remove all traces of Aptabase trademarks, branding, and name to differentiate Zalvena from Aptabase
|
||||
- Remove all Aptabase Cloud functions including Billing, US/EU Regions, and more
|
||||
|
||||
**Maybe**
|
||||
- Replace usage of React with Astro? 🤔
|
||||
|
||||
## License
|
||||
|
||||
Aptabase and Zalvena Service is open-source under the [AGPLv3 license](./LICENSE). You can use it for free, but you must share any changes you make to the code.
|
|
@ -1,5 +1,5 @@
|
|||
<p>Hi ##NAME##,</p>
|
||||
<p>You asked for a magic link to Aptabase and here it is.</p>
|
||||
<p>You asked for a magic link to Zalvena and here it is.</p>
|
||||
<p>
|
||||
<a target="_blank" rel="noopener noreferrer nofollow" href="##URL##">Confirm your registration</a>
|
||||
</p>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react",
|
||||
"composite": true,
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "Node",
|
||||
|
|
|
@ -12,7 +12,7 @@ export function Page(props: Props) {
|
|||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
document.title = `${props.title} · Aptabase`;
|
||||
document.title = `${props.title} · Zalvena`;
|
||||
}, [props.title]);
|
||||
|
||||
return (
|
||||
|
|
|
@ -80,7 +80,7 @@ export function AppSharing(props: Props) {
|
|||
</ol>
|
||||
|
||||
<p>
|
||||
<span className="font-bold">Note:</span> Aptabase won't send an email as part of the sharing process. We
|
||||
<span className="font-bold">Note:</span> Zalvena won't send an email as part of the sharing process. We
|
||||
recommend you getting in contact with them directly and asking them to sign up.
|
||||
</p>
|
||||
</AlertDescription>
|
||||
|
|
130
src/webapp/features/auth/LoginPage.backup.tsx
Executable file
130
src/webapp/features/auth/LoginPage.backup.tsx
Executable file
|
@ -0,0 +1,130 @@
|
|||
import { requestSignInLink } from "@features/auth";
|
||||
import { Page } from "@components/Page";
|
||||
import { useState } from "react";
|
||||
import { Link, useSearchParams } from "react-router-dom";
|
||||
import { DataResidency } from "./DataResidency";
|
||||
import { LegalNotice } from "./LegalNotice";
|
||||
import { RegionSwitch } from "./RegionSwitch";
|
||||
import { SignInWithGitHub } from "./SignInWithGitHub";
|
||||
import { SignInWithGoogle } from "./SignInWithGoogle";
|
||||
import { isOAuthEnabled } from "@features/env";
|
||||
import { Logo } from "./Logo";
|
||||
import { Button } from "@components/Button";
|
||||
import { TextInput } from "@components/TextInput";
|
||||
|
||||
type FormStatus = "idle" | "loading" | "success" | "notfound";
|
||||
|
||||
type StatusMessageProps = {
|
||||
status: FormStatus;
|
||||
};
|
||||
|
||||
const SignUpMessage = () => (
|
||||
<span className="block">
|
||||
Don't have an account?{" "}
|
||||
<Link className="font-semibold text-foreground" to="/auth/register">
|
||||
Sign up
|
||||
</Link>{" "}
|
||||
for free.
|
||||
</span>
|
||||
);
|
||||
|
||||
const StatusMessage = (props: StatusMessageProps) => {
|
||||
if (props.status === "success") {
|
||||
return <span className="text-success">Woo-hoo! Email sent, go check your inbox!</span>;
|
||||
}
|
||||
|
||||
if (props.status === "notfound") {
|
||||
return (
|
||||
<>
|
||||
<span className="text-destructive">Could not find an account with that email.</span>
|
||||
<SignUpMessage />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return <SignUpMessage />;
|
||||
};
|
||||
|
||||
const RedirectErrorMessage = () => {
|
||||
const [params] = useSearchParams();
|
||||
|
||||
const error = params.get("error");
|
||||
if (!error) {
|
||||
return null;
|
||||
}
|
||||
const message = error === "expired" ? "This link has expired." : "This link is invalid.";
|
||||
|
||||
return (
|
||||
<p className="mx-auto text-center mb-10 text-destructive text-sm">
|
||||
{message} Please request a new one.
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
Component.displayName = "LoginPage";
|
||||
export function Component() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [status, setStatus] = useState<FormStatus>("idle");
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
setStatus("loading");
|
||||
|
||||
const found = await requestSignInLink(email);
|
||||
setStatus(found ? "success" : "notfound");
|
||||
};
|
||||
|
||||
return (
|
||||
<Page title="Login">
|
||||
<div className="mx-auto text-center mb-10">
|
||||
<RedirectErrorMessage />
|
||||
</div>
|
||||
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md text-center">
|
||||
<Logo className="mx-auto h-12 w-auto text-primary" />
|
||||
<h2 className="text-center text-3xl text-foreground font-bold">Sign in to your account</h2>
|
||||
<DataResidency />
|
||||
</div>
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="py-8 px-4 sm:rounded-lg sm:px-10">
|
||||
{isOAuthEnabled && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<SignInWithGitHub />
|
||||
<SignInWithGoogle />
|
||||
</div>
|
||||
|
||||
<div className="relative my-4">
|
||||
<div className="absolute inset-0 flex items-center" aria-hidden="true">
|
||||
<div className="w-full border-t" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 bg-muted">OR</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="flex flex-col space-y-4">
|
||||
<TextInput
|
||||
label="Enter your email address"
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="peter.parker@corp.com"
|
||||
autoComplete="email"
|
||||
value={email}
|
||||
required={true}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
<Button loading={status === "loading"}>Send magic link</Button>
|
||||
<p className="text-center text-sm h-10 text-muted-foreground">
|
||||
<StatusMessage status={status} />
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
<LegalNotice operation="signin" />
|
||||
<RegionSwitch />
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
|
@ -1,14 +1,7 @@
|
|||
import { requestSignInLink } from "@features/auth";
|
||||
import { Page } from "@components/Page";
|
||||
import { useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { Link, useSearchParams } from "react-router-dom";
|
||||
import { DataResidency } from "./DataResidency";
|
||||
import { LegalNotice } from "./LegalNotice";
|
||||
import { RegionSwitch } from "./RegionSwitch";
|
||||
import { SignInWithGitHub } from "./SignInWithGitHub";
|
||||
import { SignInWithGoogle } from "./SignInWithGoogle";
|
||||
import { isOAuthEnabled } from "@features/env";
|
||||
import { Logo } from "./Logo";
|
||||
import { Button } from "@components/Button";
|
||||
import { TextInput } from "@components/TextInput";
|
||||
|
||||
|
@ -30,13 +23,13 @@ const SignUpMessage = () => (
|
|||
|
||||
const StatusMessage = (props: StatusMessageProps) => {
|
||||
if (props.status === "success") {
|
||||
return <span className="text-success">Woo-hoo! Email sent, go check your inbox!</span>;
|
||||
return <span className="text-success">The link has been sent to your inbox, check it now.</span>;
|
||||
}
|
||||
|
||||
if (props.status === "notfound") {
|
||||
return (
|
||||
<>
|
||||
<span className="text-destructive">Could not find an account with that email.</span>
|
||||
<span className="text-destructive">An account with that email was not found, try again.</span>
|
||||
<SignUpMessage />
|
||||
</>
|
||||
);
|
||||
|
@ -56,7 +49,7 @@ const RedirectErrorMessage = () => {
|
|||
|
||||
return (
|
||||
<p className="mx-auto text-center mb-10 text-destructive text-sm">
|
||||
{message} Please request a new one.
|
||||
{message} Link invalid, try again. Links only last 15 minutes.
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
@ -75,56 +68,44 @@ export function Component() {
|
|||
};
|
||||
|
||||
return (
|
||||
<Page title="Login">
|
||||
<div className="mx-auto text-center mb-10">
|
||||
<RedirectErrorMessage />
|
||||
</div>
|
||||
<div className="login">
|
||||
<div className="login-box">
|
||||
<div className="lb-start">
|
||||
<img src="https://ocean.sudovanilla.org/media/images/wallpapers/Trenta%20OS%20%28Discontinued%29/mountains.jpg"/>
|
||||
<p>Zalvena</p>
|
||||
</div>
|
||||
<div className="lb-end">
|
||||
<p className="redir-error"><RedirectErrorMessage/></p>
|
||||
<h2>Login to your account</h2>
|
||||
<button id="sv-gw-disabled" class="generic-oauth">Login with SudoVanilla Gateway</button>
|
||||
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md text-center">
|
||||
<Logo className="mx-auto h-12 w-auto text-primary" />
|
||||
<h2 className="text-center text-3xl text-foreground font-bold">Sign in to your account</h2>
|
||||
<DataResidency />
|
||||
</div>
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="py-8 px-4 sm:rounded-lg sm:px-10">
|
||||
{isOAuthEnabled && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<SignInWithGitHub />
|
||||
<SignInWithGoogle />
|
||||
</div>
|
||||
<div className="relative my-4">
|
||||
<div className="absolute inset-0 flex items-center" aria-hidden="true">
|
||||
<div className="w-full border-t" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 bg-muted">OR</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative my-4">
|
||||
<div className="absolute inset-0 flex items-center" aria-hidden="true">
|
||||
<div className="w-full border-t" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 bg-muted">OR</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="flex flex-col space-y-4">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<TextInput
|
||||
label="Enter your email address"
|
||||
label="Email Address"
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="peter.parker@corp.com"
|
||||
placeholder="nemo@example.org"
|
||||
autoComplete="email"
|
||||
value={email}
|
||||
required={true}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
<Button loading={status === "loading"}>Send magic link</Button>
|
||||
<p className="text-center text-sm h-10 text-muted-foreground">
|
||||
<StatusMessage status={status} />
|
||||
</p>
|
||||
<Button loading={status === "loading"}>Send Login Link</Button>
|
||||
<p><StatusMessage status={status}/></p>
|
||||
</form>
|
||||
<hr/>
|
||||
<p>By signing in, you agree to the Privacy Policy.</p>
|
||||
</div>
|
||||
<LegalNotice operation="signin" />
|
||||
<RegionSwitch />
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ export function ToolsList() {
|
|||
icon="excel"
|
||||
name="Microsoft Excel"
|
||||
href="https://www.microsoft.com/en-ie/microsoft-365"
|
||||
description="The most popular spreadsheet works great with Aptabase CSV files. Open it on Excel and start exploring your data with Pivot Tables and Charts."
|
||||
description="The most popular spreadsheet works great with Zalvena CSV files. Open it on Excel and start exploring your data with Pivot Tables and Charts."
|
||||
/>
|
||||
|
||||
<Tool
|
||||
|
|
|
@ -84,4 +84,98 @@
|
|||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
@apply bg-muted-foreground;
|
||||
}
|
||||
|
||||
/* Login Page */
|
||||
.login {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(0deg, #1a1a1a,black);
|
||||
}
|
||||
.login-box {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
background: black;
|
||||
border-radius: 12px;
|
||||
border: 2px #242424 solid;
|
||||
padding: 6px;
|
||||
}
|
||||
.lb-start {
|
||||
width: 100%;
|
||||
max-width: 524px;
|
||||
position: relative;
|
||||
}
|
||||
.lb-start img {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: bottom;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.lb-start p {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
align-content: center;
|
||||
backdrop-filter: blur(6px) brightness(0.5);
|
||||
border-radius: 6px;
|
||||
font-size: 40px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
font-family: 'Cal Sans';
|
||||
}
|
||||
.lb-end {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 48px 24px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.lb-end * {
|
||||
max-width: 472px;
|
||||
width: inherit;
|
||||
}
|
||||
.lb-end .relative.my-4 {
|
||||
text-align: center;
|
||||
max-width: max-content;
|
||||
}
|
||||
.generic-oauth {
|
||||
border: 1px #1a1a1a solid;
|
||||
border-radius: 6px;
|
||||
padding: 6px 12px;
|
||||
font-size: 14px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.lb-end form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.lb-end hr {
|
||||
margin: 12px 0px;
|
||||
}
|
||||
#sv-gw-disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.lb-end h2 {
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
margin-bottom: 14px;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue