mirror of
https://github.com/ramvignesh-b/pi-ku.git
synced 2026-05-04 08:56:52 +00:00
refactor: extract welcome modal for consistency
This commit is contained in:
@@ -0,0 +1,85 @@
|
|||||||
|
import {
|
||||||
|
HandPalmIcon,
|
||||||
|
ShieldCheckIcon,
|
||||||
|
WarningIcon,
|
||||||
|
} from "@phosphor-icons/react";
|
||||||
|
import Logo from "../Logo.tsx";
|
||||||
|
import { Modal } from "../ui/Modal";
|
||||||
|
import Saajan from "../ui/Saajan.tsx";
|
||||||
|
|
||||||
|
export default function WelcomeModal({
|
||||||
|
setShowWelcome,
|
||||||
|
}: {
|
||||||
|
setShowWelcome: (show: boolean) => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal isOpen={true}>
|
||||||
|
<div className="flex flex-col items-center text-center gap-4">
|
||||||
|
<div className="bg-primary/10 p-4 rounded-full animate-pulse">
|
||||||
|
<ShieldCheckIcon
|
||||||
|
size={48}
|
||||||
|
weight="duotone"
|
||||||
|
className="text-primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h3 className="font-display text-2xl font-bold text-primary">
|
||||||
|
Welcome to
|
||||||
|
<Logo /> !
|
||||||
|
</h3>
|
||||||
|
<p className="text-base-content/80 leading-relaxed">
|
||||||
|
Before we begin, let me make a small promise.
|
||||||
|
<HandPalmIcon
|
||||||
|
size={18}
|
||||||
|
className="inline text-primary"
|
||||||
|
weight="fill"
|
||||||
|
/>
|
||||||
|
<div className="divider my-0"></div>
|
||||||
|
<br />
|
||||||
|
Everything you write here is sealed with your password,{" "}
|
||||||
|
<span className="font-display text-success">cryptographically</span>
|
||||||
|
, before it leaves your hands.
|
||||||
|
<br />A fancy way of saying, I couldn't if I tried.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="alert alert-warning bg-paper/20 border-paper/20 flex items-start gap-3 text-left py-3">
|
||||||
|
<WarningIcon size={24} weight="fill" className="shrink-0 mt-0.5" />
|
||||||
|
<p className="text-sm font-medium text-primary-content">
|
||||||
|
If you ever happen to forget your password, your letters are lost
|
||||||
|
to time, forever.
|
||||||
|
<br />
|
||||||
|
<span className="font-bold mt-2">
|
||||||
|
I highly, highly recommend storing this password in your{" "}
|
||||||
|
<a
|
||||||
|
href="https://www.privacyguides.org/en/passwords/"
|
||||||
|
target="_blank"
|
||||||
|
className="link link-primary-content"
|
||||||
|
rel="noopener"
|
||||||
|
>
|
||||||
|
password manager
|
||||||
|
</a>{" "}
|
||||||
|
or somewhere safe to remember it.
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="modal-action w-full">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowWelcome(false)}
|
||||||
|
className="btn btn-primary w-full shadow-lg"
|
||||||
|
>
|
||||||
|
I'll remember
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
<div className="absolute bottom-0 z-1000 font-sans w-full">
|
||||||
|
<Saajan
|
||||||
|
position="top"
|
||||||
|
message={"I've lost words before.\nI know what it feels like."}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -25,14 +25,18 @@ export const LogModal = ({
|
|||||||
<WarningIcon className="text-warning" size={16} weight="duotone" />
|
<WarningIcon className="text-warning" size={16} weight="duotone" />
|
||||||
)}
|
)}
|
||||||
{message}
|
{message}
|
||||||
<div className="divider text-primary-content text-xs uppercase tracking-widest">
|
{log && (
|
||||||
Error Stack
|
<>
|
||||||
</div>
|
<div className="divider text-primary-content text-xs uppercase tracking-widest">
|
||||||
<div className="mockup-code bg-base-100 text-error w-full">
|
Error Stack
|
||||||
<pre>
|
</div>
|
||||||
<code>{String(log)}</code>
|
<div className="mockup-code bg-base-100 text-error w-full">
|
||||||
</pre>
|
<pre>
|
||||||
</div>
|
<code>{String(log)}</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import {
|
|
||||||
HandPalmIcon,
|
|
||||||
ShieldCheckIcon,
|
|
||||||
WarningIcon,
|
|
||||||
} from "@phosphor-icons/react";
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
@@ -11,8 +7,8 @@ import { useLocation, useNavigate } from "react-router-dom";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { api, publicApi } from "../api/apiClient";
|
import { api, publicApi } from "../api/apiClient";
|
||||||
import Logo from "../components/Logo";
|
import Logo from "../components/Logo";
|
||||||
|
import WelcomeModal from "../components/login/WelcomeModal.tsx";
|
||||||
import FormField from "../components/ui/FormField";
|
import FormField from "../components/ui/FormField";
|
||||||
import { Modal } from "../components/ui/Modal";
|
|
||||||
import Saajan from "../components/ui/Saajan";
|
import Saajan from "../components/ui/Saajan";
|
||||||
import { endpoints } from "../config/endpoints";
|
import { endpoints } from "../config/endpoints";
|
||||||
import { ROUTES } from "../config/routes";
|
import { ROUTES } from "../config/routes";
|
||||||
@@ -26,83 +22,6 @@ const loginSchema = z.object({
|
|||||||
|
|
||||||
type LoginInputs = z.infer<typeof loginSchema>;
|
type LoginInputs = z.infer<typeof loginSchema>;
|
||||||
|
|
||||||
function WelcomeModal({
|
|
||||||
setShowWelcome,
|
|
||||||
}: {
|
|
||||||
setShowWelcome: (show: boolean) => void;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Modal isOpen={true}>
|
|
||||||
<div className="flex flex-col items-center text-center gap-4">
|
|
||||||
<div className="bg-primary/10 p-4 rounded-full animate-pulse">
|
|
||||||
<ShieldCheckIcon
|
|
||||||
size={48}
|
|
||||||
weight="duotone"
|
|
||||||
className="text-primary"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<h3 className="font-display text-2xl font-bold text-primary">
|
|
||||||
Welcome to
|
|
||||||
<Logo /> !
|
|
||||||
</h3>
|
|
||||||
<p className="text-base-content/80 leading-relaxed">
|
|
||||||
Before we begin, let me make a small promise.
|
|
||||||
<HandPalmIcon
|
|
||||||
size={18}
|
|
||||||
className="inline text-primary"
|
|
||||||
weight="fill"
|
|
||||||
/>
|
|
||||||
<div className="divider my-0"></div>
|
|
||||||
<br />
|
|
||||||
Everything you write here is sealed with your password,{" "}
|
|
||||||
<span className="font-display text-success">cryptographically</span>
|
|
||||||
, before it leaves your hands.
|
|
||||||
<br />A fancy way of saying, I couldn't if I tried.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="alert alert-warning bg-paper/20 border-paper/20 flex items-start gap-3 text-left py-3">
|
|
||||||
<WarningIcon size={24} weight="fill" className="shrink-0 mt-0.5" />
|
|
||||||
<p className="text-sm font-medium text-primary-content">
|
|
||||||
If you ever happen to forget your password, your letters are lost
|
|
||||||
to time, forever.
|
|
||||||
<br />
|
|
||||||
<span className="font-bold mt-2">
|
|
||||||
I highly, highly recommend storing this password in your{" "}
|
|
||||||
<a
|
|
||||||
href="https://www.privacyguides.org/en/passwords/"
|
|
||||||
target="_blank"
|
|
||||||
className="link link-primary-content"
|
|
||||||
rel="noopener"
|
|
||||||
>
|
|
||||||
password manager
|
|
||||||
</a>{" "}
|
|
||||||
or somewhere safe to remember it.
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="modal-action w-full">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowWelcome(false)}
|
|
||||||
className="btn btn-primary w-full shadow-lg"
|
|
||||||
>
|
|
||||||
I'll remember
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
<div className="absolute bottom-0 z-1000 font-sans w-full">
|
|
||||||
<Saajan
|
|
||||||
position="top"
|
|
||||||
message={"I've lost words before.\nI know what it feels like."}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -127,7 +46,7 @@ export default function Login() {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setApiError(null);
|
setApiError(null);
|
||||||
try {
|
try {
|
||||||
// client side key derivation for 0 knowledge
|
// client side key derivation for e2e encryption
|
||||||
const { masterKey, authHash } = await CryptoUtils.deriveKeyBundle(
|
const { masterKey, authHash } = await CryptoUtils.deriveKeyBundle(
|
||||||
data.password,
|
data.password,
|
||||||
data.email,
|
data.email,
|
||||||
@@ -143,7 +62,6 @@ export default function Login() {
|
|||||||
headers: { Authorization: `Bearer ${authData.access}` },
|
headers: { Authorization: `Bearer ${authData.access}` },
|
||||||
});
|
});
|
||||||
|
|
||||||
// store the auth related data
|
|
||||||
await setAuthStore(authData.access, userData, masterKey);
|
await setAuthStore(authData.access, userData, masterKey);
|
||||||
|
|
||||||
navigate(nextRoute, { replace: true });
|
navigate(nextRoute, { replace: true });
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { endpoints } from "../config/endpoints";
|
|||||||
import { ROUTES } from "../config/routes";
|
import { ROUTES } from "../config/routes";
|
||||||
import { CryptoUtils } from "../utils/crypto";
|
import { CryptoUtils } from "../utils/crypto";
|
||||||
|
|
||||||
// validation logic
|
|
||||||
const registerSchema = z
|
const registerSchema = z
|
||||||
.object({
|
.object({
|
||||||
full_name: z.string().min(2, "Name must be at least 2 characters"),
|
full_name: z.string().min(2, "Name must be at least 2 characters"),
|
||||||
@@ -49,7 +48,7 @@ export default function Register() {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setApiError(null);
|
setApiError(null);
|
||||||
try {
|
try {
|
||||||
// We generate the key bundle here to get the authHash (password) for the server.
|
// we generate the key bundle here to get the authHash (password) to be haSHed and stored in the db.
|
||||||
const { authHash } = await CryptoUtils.deriveKeyBundle(
|
const { authHash } = await CryptoUtils.deriveKeyBundle(
|
||||||
data.password,
|
data.password,
|
||||||
data.email,
|
data.email,
|
||||||
@@ -136,7 +135,6 @@ export default function Register() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Warning */}
|
|
||||||
<div className="alert alert-warning items-start text-left p-3 gap-2 rounded-md border-warning/20">
|
<div className="alert alert-warning items-start text-left p-3 gap-2 rounded-md border-warning/20">
|
||||||
<InfoIcon size={20} weight="duotone" className="mt-0.5 shrink-0" />
|
<InfoIcon size={20} weight="duotone" className="mt-0.5 shrink-0" />
|
||||||
<p className="text-sm font-semibold">
|
<p className="text-sm font-semibold">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { openDB } from "idb";
|
import { openDB } from "idb";
|
||||||
|
|
||||||
// we use this to store master key in browser - secure and good UX
|
// we use indexedDB to securely store master key for easier access across tabs (better UX than having to store in session)
|
||||||
const db = openDB("piku-keys", 1, {
|
const db = openDB("piku-keys", 1, {
|
||||||
upgrade(db) {
|
upgrade(db) {
|
||||||
db.createObjectStore("master-key");
|
db.createObjectStore("master-key");
|
||||||
|
|||||||
Reference in New Issue
Block a user