UI
Made UI changes, mainly fixing bugs and changing some theming
This commit is contained in:
parent
be59453ce4
commit
25b379aadd
26 changed files with 1027 additions and 655 deletions
2
.idea/discord.xml
generated
2
.idea/discord.xml
generated
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="ASK" />
|
||||
<option name="show" value="PROJECT_FILES" />
|
||||
<option name="description" value="" />
|
||||
<option name="applicationTheme" value="default" />
|
||||
<option name="iconsTheme" value="default" />
|
||||
|
|
|
|||
207
package-lock.json
generated
207
package-lock.json
generated
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"name": "whispr",
|
||||
"name": "sipher",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "whispr",
|
||||
"name": "sipher",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-avatar": "^1.1.1",
|
||||
|
|
@ -15,6 +15,7 @@
|
|||
"@radix-ui/react-separator": "^1.1.0",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-toast": "^1.2.2",
|
||||
"@radix-ui/react-tooltip": "^1.1.4",
|
||||
"@supabase/ssr": "^0.5.2",
|
||||
"@supabase/supabase-js": "^2.47.3",
|
||||
"argon2": "^0.41.1",
|
||||
|
|
@ -58,6 +59,44 @@
|
|||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.6.8",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz",
|
||||
"integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/utils": "^0.2.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "1.6.12",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.12.tgz",
|
||||
"integrity": "sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.6.0",
|
||||
"@floating-ui/utils": "^0.2.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/react-dom": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz",
|
||||
"integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/utils": {
|
||||
"version": "0.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz",
|
||||
"integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-arm64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
|
||||
|
|
@ -643,6 +682,29 @@
|
|||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz",
|
||||
"integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA=="
|
||||
},
|
||||
"node_modules/@radix-ui/react-arrow": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz",
|
||||
"integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-primitive": "2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-avatar": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.1.tgz",
|
||||
|
|
@ -783,6 +845,24 @@
|
|||
"react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-id": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
|
||||
"integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-label": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz",
|
||||
|
|
@ -805,6 +885,53 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-popper": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz",
|
||||
"integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/react-dom": "^2.0.0",
|
||||
"@radix-ui/react-arrow": "1.1.0",
|
||||
"@radix-ui/react-compose-refs": "1.1.0",
|
||||
"@radix-ui/react-context": "1.1.0",
|
||||
"@radix-ui/react-primitive": "2.0.0",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0",
|
||||
"@radix-ui/react-use-rect": "1.1.0",
|
||||
"@radix-ui/react-use-size": "1.1.0",
|
||||
"@radix-ui/rect": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-context": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz",
|
||||
"integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-portal": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz",
|
||||
|
|
@ -975,6 +1102,40 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.4.tgz",
|
||||
"integrity": "sha512-QpObUH/ZlpaO4YgHSaYzrLO2VuO+ZBFFgGzjMUPwtiYnAzzNNDPJeEGRrT7qNOrWm/Jr08M1vlp+vTHtnSQ0Uw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.0",
|
||||
"@radix-ui/react-compose-refs": "1.1.0",
|
||||
"@radix-ui/react-context": "1.1.1",
|
||||
"@radix-ui/react-dismissable-layer": "1.1.1",
|
||||
"@radix-ui/react-id": "1.1.0",
|
||||
"@radix-ui/react-popper": "1.2.0",
|
||||
"@radix-ui/react-portal": "1.1.2",
|
||||
"@radix-ui/react-presence": "1.1.1",
|
||||
"@radix-ui/react-primitive": "2.0.0",
|
||||
"@radix-ui/react-slot": "1.1.0",
|
||||
"@radix-ui/react-use-controllable-state": "1.1.0",
|
||||
"@radix-ui/react-visually-hidden": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-callback-ref": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
|
||||
|
|
@ -1037,6 +1198,42 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-rect": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz",
|
||||
"integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/rect": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-size": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz",
|
||||
"integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-visually-hidden": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz",
|
||||
|
|
@ -1059,6 +1256,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/rect": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz",
|
||||
"integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@supabase/auth-js": {
|
||||
"version": "2.66.1",
|
||||
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.66.1.tgz",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "whispr",
|
||||
"name": "sipher",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
"@radix-ui/react-separator": "^1.1.0",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-toast": "^1.2.2",
|
||||
"@radix-ui/react-tooltip": "^1.1.4",
|
||||
"@supabase/ssr": "^0.5.2",
|
||||
"@supabase/supabase-js": "^2.47.3",
|
||||
"argon2": "^0.41.1",
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export async function POST(request: Request) {
|
|||
} catch (error) {
|
||||
if (typeof error === "object") {
|
||||
return NextResponse.json(
|
||||
{error: `Registration failed: ${JSON.stringify(error)}`},
|
||||
{error: JSON.stringify(error)},
|
||||
{status: 400}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use client"
|
||||
|
||||
import React, {useEffect, useState} from 'react'
|
||||
import React, {useCallback, useEffect, useState} from 'react'
|
||||
import Image from 'next/image'
|
||||
import {motion} from 'framer-motion'
|
||||
import {Button} from "@/components/ui/button"
|
||||
|
|
@ -26,20 +26,26 @@ export default function AuthPage() {
|
|||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const check = async () => {
|
||||
const isAuthenticated = await checkAuth();
|
||||
const check = useCallback(async () => {
|
||||
const isAuthenticated = await checkAuth("Called on Login page");
|
||||
if (isAuthenticated) {
|
||||
router.replace('/');
|
||||
} else {
|
||||
setMounted(true);
|
||||
}
|
||||
};
|
||||
}, [checkAuth, router, setMounted])
|
||||
|
||||
check();
|
||||
}, [checkAuth, router]);
|
||||
useEffect(() => {
|
||||
check().then(() => {
|
||||
console.log("Login page check finished")
|
||||
})
|
||||
}, []);
|
||||
|
||||
if (!mounted) return null;
|
||||
if (!mounted) {
|
||||
return <div className="min-h-screen flex items-center justify-center">
|
||||
{/* Optional: Add a loading spinner or skeleton here */}
|
||||
</div>;
|
||||
}
|
||||
|
||||
|
||||
const getTheme = () => {
|
||||
|
|
@ -76,25 +82,37 @@ export default function AuthPage() {
|
|||
}
|
||||
|
||||
if (response.code !== 200) {
|
||||
if (isLogin && response.code === 400) {
|
||||
console.log(response)
|
||||
toast({
|
||||
title: "E-mail not verified",
|
||||
description: response.message,
|
||||
variant: "destructive",
|
||||
duration: 5000, // Increased duration for better visibility
|
||||
action: response.action!
|
||||
});
|
||||
setIsSubmitting(false);
|
||||
return;
|
||||
const msg = response.message
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(msg);
|
||||
let desc = parsed.name;
|
||||
|
||||
switch (desc) {
|
||||
case "AuthWeakPasswordError": {
|
||||
desc = "Password too weak, please try again.";
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
desc = "An unknown error occurred";
|
||||
}
|
||||
}
|
||||
|
||||
toast({
|
||||
title: "Error",
|
||||
description: response.message,
|
||||
description: desc,
|
||||
variant: "destructive",
|
||||
duration: 5000, // Increased duration for better visibility
|
||||
duration: 5000
|
||||
});
|
||||
} catch (e) {
|
||||
// If msg isn't valid JSON, show the raw message
|
||||
toast({
|
||||
title: "Error",
|
||||
description: msg,
|
||||
variant: "destructive",
|
||||
duration: 5000
|
||||
});
|
||||
}
|
||||
} else {
|
||||
toast({
|
||||
title: "Success",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* @param password - The plain-text password of the user. Will be encrypted later by Supabase
|
||||
* @constructor
|
||||
*/
|
||||
export default async function Register(password: string, username: string) {
|
||||
export default async function Register(username: string, password: string) {
|
||||
try {
|
||||
// Sends the request to the API
|
||||
let res = await fetch('/api/auth/register', {
|
||||
|
|
@ -19,8 +19,8 @@ export default async function Register(password: string, username: string) {
|
|||
if (!res.ok) {
|
||||
let data = await res.json();
|
||||
return {
|
||||
code: data.code,
|
||||
message: data.message
|
||||
code: res.status,
|
||||
message: data.error
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ export default async function Register(password: string, username: string) {
|
|||
} catch (e: any) {
|
||||
return {
|
||||
code: 500,
|
||||
message: `An unknown error occurred: ${e.message}`
|
||||
message: e.error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ import {UserProvider} from "@/contexts/user";
|
|||
import Sidebar from "@/components/main/sidebar/sidebar";
|
||||
import {getAuthenticatedUser} from "@/lib/auth";
|
||||
import {SharedStateProvider} from "@/hooks/shared-states";
|
||||
import {ThemeProvider} from "next-themes";
|
||||
import ThemeProvider from "@/components/ui/theme-provider";
|
||||
import {headers} from "next/headers";
|
||||
import {Toaster} from "@/components/ui/toaster";
|
||||
|
||||
const publicSans = Public_Sans({
|
||||
subsets: ['latin'],
|
||||
|
|
@ -15,34 +17,31 @@ const publicSans = Public_Sans({
|
|||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
title: "SiPher - Where Shadows Live",
|
||||
description: "Secrecy? Not here, absolutely.",
|
||||
icons: [{rel: "icon", url: "/logos/logo.png"}],
|
||||
};
|
||||
|
||||
export default async function RootLayout({
|
||||
export default async function RootLayout(
|
||||
{
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode & { props?: { childProp?: { segment?: string } } };
|
||||
}) {
|
||||
}) {
|
||||
const initialUser = await getAuthenticatedUser();
|
||||
const isAuthPage = (children as any)?.props?.childProp?.segment === 'auth';
|
||||
const isAuthPage = (await headers()).get("x-current-pathname")?.includes("auth");
|
||||
|
||||
// Auth layout
|
||||
if (isAuthPage) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body className={`${publicSans.variable} font-sans antialiased`}>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
>
|
||||
<ThemeProvider>
|
||||
<UserProvider initialUser={initialUser}>
|
||||
<main className="min-h-screen flex items-center justify-center">
|
||||
{children}
|
||||
</main>
|
||||
</UserProvider>
|
||||
</ThemeProvider>
|
||||
<Toaster/>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
|
@ -52,11 +51,7 @@ export default async function RootLayout({
|
|||
return (
|
||||
<html lang="en">
|
||||
<body className={`${publicSans.variable} font-sans antialiased`}>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
>
|
||||
<ThemeProvider>
|
||||
<UserProvider initialUser={initialUser}>
|
||||
<SharedStateProvider>
|
||||
<div className={`max-h-[1080px] p-6 bg-secondary`}>
|
||||
|
|
@ -68,6 +63,7 @@ export default async function RootLayout({
|
|||
</div>
|
||||
</SharedStateProvider>
|
||||
</UserProvider>
|
||||
<Toaster/>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
102
src/app/page.tsx
102
src/app/page.tsx
|
|
@ -1,12 +1,106 @@
|
|||
"use client"
|
||||
import {useTheme} from "next-themes";
|
||||
import Image from "next/image";
|
||||
import {Feather, Search} from "lucide-react";
|
||||
import {useEffect, useState} from "react";
|
||||
|
||||
export default function SiPher() {
|
||||
const {theme} = useTheme()
|
||||
const {theme, systemTheme} = useTheme();
|
||||
const [isSearchExpanded, setIsSearchExpanded] = useState(false);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
|
||||
|
||||
}, []);
|
||||
|
||||
const getTheme = () => {
|
||||
if (!mounted) return "light";
|
||||
if (theme === "system") {
|
||||
return systemTheme === "dark" ? "dark" : "light";
|
||||
}
|
||||
return theme === "dark" ? "dark" : "light";
|
||||
};
|
||||
|
||||
const currentTheme = getTheme();
|
||||
|
||||
return (
|
||||
<div className={`flex-1 ${theme === "dark" ? "dark" : ""}`}>
|
||||
abc
|
||||
<div
|
||||
className={`relative flex-1 ${currentTheme === "dark" ? "dark" : ""} w-full max-h-[600px] bg-gradient-to-b from-background to-background/95`}>
|
||||
{/* Animated background elements */}
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||
<div
|
||||
className="absolute inset-0 bg-[radial-gradient(circle_500px_at_50%_50%,rgba(120,120,120,0.05),transparent)]"/>
|
||||
</div>
|
||||
)
|
||||
|
||||
<div className="relative flex flex-col justify-center items-center h-screen px-4 select-none space-y-8">
|
||||
{/* Logo section with subtle hover effect */}
|
||||
<div className="relative group">
|
||||
<div
|
||||
className="absolute inset-0 bg-primary/5 rounded-full blur-xl group-hover:bg-primary/10 transition-all duration-500"/>
|
||||
<Image
|
||||
priority
|
||||
src={`/logos/logo.png`}
|
||||
alt="SiPher"
|
||||
width={128}
|
||||
height={128}
|
||||
draggable={false}
|
||||
className="relative transform transition-transform duration-500 group-hover:scale-105"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Main text content with improved typography and spacing */}
|
||||
<div className="max-w-2xl space-y-6 text-center">
|
||||
<p className="text-lg md:text-xl font-medium leading-relaxed text-primary">
|
||||
Where shadows dance and secrets nest, Silent Whisper serves as the dark sanctuary for those
|
||||
who value discretion above all. Born from ancient corvid traditions, this messenger's haven ensures your
|
||||
whispers remain unheard by all but their intended recipients.
|
||||
</p>
|
||||
|
||||
<p className="text-sm md:text-base font-medium text-muted-foreground leading-relaxed">
|
||||
Like the sacred ravens of old, your messages fly through the darkness, their contents sealed by shadows and
|
||||
protected by forgotten wards. Each member of our dark fellowship is known only by their chosen name, their
|
||||
true identity shrouded in mystery.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Enhanced search component */}
|
||||
<div className="relative mt-8">
|
||||
<div
|
||||
className={`flex items-center rounded-full transition-all duration-300 ${
|
||||
isSearchExpanded
|
||||
? "bg-secondary/30 backdrop-blur-sm border border-primary/20 shadow-lg"
|
||||
: ""
|
||||
}`}
|
||||
style={{
|
||||
width: isSearchExpanded ? "240px" : "40px",
|
||||
}}
|
||||
>
|
||||
<button
|
||||
className={`flex-shrink-0 w-10 h-10 flex items-center justify-center rounded-full
|
||||
${currentTheme === "dark" ? "hover:bg-secondary/60" : "hover:bg-primary/10"}
|
||||
transition-colors duration-200`}
|
||||
onClick={() => setIsSearchExpanded(!isSearchExpanded)}
|
||||
>
|
||||
<Search className="w-5 h-5"/>
|
||||
</button>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Find fellow shadows..."
|
||||
className={`w-full bg-transparent focus:outline-none text-primary placeholder-primary/50
|
||||
transition-all duration-300 ${isSearchExpanded ? "px-4" : "w-0 px-0"}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Decorative feather icon */}
|
||||
<Feather
|
||||
className={`absolute -right-6 top-1/2 -translate-y-1/2 w-4 h-4 text-primary/30 transform rotate-45
|
||||
transition-opacity duration-300 ${isSearchExpanded ? "opacity-100" : "opacity-0"}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,14 +1,15 @@
|
|||
"use client"
|
||||
import React from 'react'
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { HamburgerMenuIcon } from "@radix-ui/react-icons"
|
||||
import { useTheme } from "next-themes"
|
||||
import {Button} from "@/components/ui/button"
|
||||
import {HamburgerMenuIcon} from "@radix-ui/react-icons"
|
||||
import {useTheme} from "next-themes"
|
||||
import Image from "next/image"
|
||||
import { useUIState } from "@/hooks/shared-states"
|
||||
import {useUIState} from "@/hooks/shared-states"
|
||||
import Link from "next/link";
|
||||
|
||||
const MobileHeader: React.FC = () => {
|
||||
const { setIsDrawerOpen } = useUIState()
|
||||
const { theme, systemTheme } = useTheme()
|
||||
const {setIsDrawerOpen} = useUIState()
|
||||
const {theme, systemTheme} = useTheme()
|
||||
|
||||
const getTheme = () => {
|
||||
if (theme === "system") {
|
||||
|
|
@ -33,7 +34,7 @@ const MobileHeader: React.FC = () => {
|
|||
onClick={() => setIsDrawerOpen(true)}
|
||||
className="rounded-full"
|
||||
>
|
||||
<HamburgerMenuIcon className="w-6 h-6" />
|
||||
<HamburgerMenuIcon className="w-6 h-6"/>
|
||||
</Button>
|
||||
|
||||
<div className="flex items-center justify-center flex-1">
|
||||
|
|
@ -49,7 +50,7 @@ const MobileHeader: React.FC = () => {
|
|||
</div>
|
||||
|
||||
{/* Empty div to maintain center alignment */}
|
||||
<div className="w-10 mb-8" />
|
||||
<div className="w-10 mb-8"/>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
"use client"
|
||||
import React, {useCallback, useEffect, useRef, useState} from "react"
|
||||
import React, {useCallback, useEffect, useState} from "react"
|
||||
import {usePathname} from "next/navigation"
|
||||
import Link from "next/link"
|
||||
import {AnimatePresence, motion} from "framer-motion"
|
||||
|
|
@ -8,13 +8,14 @@ import {Button} from "@/components/ui/button"
|
|||
import {Avatar, AvatarFallback} from "@/components/ui/avatar"
|
||||
import {Separator} from "@/components/ui/separator"
|
||||
import {ScrollArea} from "@/components/ui/scroll-area"
|
||||
import {useTheme} from "next-themes"
|
||||
import {GearIcon} from "@radix-ui/react-icons"
|
||||
import Image from "next/image";
|
||||
import MobileHeader from "@/components/main/sidebar/mobile";
|
||||
import {useUser} from "@/contexts/user";
|
||||
import {useUIState} from "@/hooks/shared-states";
|
||||
import {useRefs, useUIState} from "@/hooks/shared-states";
|
||||
import {useToast} from "@/hooks/use-toast";
|
||||
import {useTheme} from "next-themes";
|
||||
import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from "@/components/ui/tooltip";
|
||||
|
||||
type SidebarProps = {
|
||||
children?: React.ReactNode
|
||||
|
|
@ -26,41 +27,51 @@ function Sidebar(
|
|||
}: SidebarProps
|
||||
) {
|
||||
const pathname = usePathname()
|
||||
const drawerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const [selectedThreads, setSelectedThreads] = useState("");
|
||||
const [threads, setThreads] = useState<SiPher.Messages[] | []>([]);
|
||||
const [threadMenu, setThreadMenu] = useState<SiPher.Messages[] | []>([]);
|
||||
const [copied, setCopied] = useState<boolean>(false);
|
||||
const {theme, systemTheme} = useTheme()
|
||||
const {toast} = useToast();
|
||||
|
||||
const {isDrawerOpen, setIsDrawerOpen} = useUIState()
|
||||
const {drawerRef} = useRefs();
|
||||
|
||||
const user = useUser().user!;
|
||||
|
||||
const {
|
||||
username,
|
||||
suuid
|
||||
} = user
|
||||
|
||||
useEffect(() => {
|
||||
const getThreads = async () => {
|
||||
try {
|
||||
const req = await fetch("/api/user/get/threads")
|
||||
|
||||
if (req.ok) {
|
||||
const {threads} = await req.json() as { threads: SiPher.Messages[] | [] }
|
||||
setThreads(threads)
|
||||
return;
|
||||
} else {
|
||||
setThreads([]);
|
||||
setThreads([])
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "An unknown error occurred",
|
||||
variant: "destructive",
|
||||
duration: 5000, // Increased duration for better visibility
|
||||
duration: 5000,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
setThreads([])
|
||||
}
|
||||
}
|
||||
|
||||
getThreads();
|
||||
|
||||
return () => {
|
||||
setThreads([]);
|
||||
}
|
||||
}, [setThreads])
|
||||
getThreads()
|
||||
return () => setThreads([])
|
||||
}, [toast])
|
||||
|
||||
const generateThreads = useCallback(() => {
|
||||
threads.map(async(thread) => {
|
||||
threads.map(async (thread) => {
|
||||
if (thread.participants.length > 2) {
|
||||
return (
|
||||
<li key={thread.id}>
|
||||
|
|
@ -78,46 +89,37 @@ function Sidebar(
|
|||
</li>
|
||||
)
|
||||
} else {
|
||||
const fetchOtherUser = await useUser().getUser(thread.id)
|
||||
const fetchOtherUser = await useUser().getUser("fetchOtherUser - const", thread.id)
|
||||
}
|
||||
})
|
||||
}, [threads])
|
||||
|
||||
const user = useUser().user!;
|
||||
|
||||
const {
|
||||
username,
|
||||
suuid
|
||||
} = user
|
||||
|
||||
const {isDrawerOpen, setIsDrawerOpen} = useUIState()
|
||||
|
||||
const {theme, systemTheme} = useTheme()
|
||||
const getTheme = () => {
|
||||
if (theme === "system") {
|
||||
switch (systemTheme) {
|
||||
case "dark":
|
||||
return "dark"
|
||||
default:
|
||||
return "light"
|
||||
}
|
||||
}
|
||||
|
||||
return theme === "dark" ? "dark" : "light"
|
||||
}
|
||||
const isDarkMode = getTheme() === "dark";
|
||||
const isDarkMode = theme === "system"
|
||||
? systemTheme === "dark"
|
||||
: theme === "dark"
|
||||
|
||||
const RightSidebarContent = () => (
|
||||
<div className={`flex flex-col h-full w-[240px]`}>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={copied} onOpenChange={setCopied}>
|
||||
<TooltipTrigger/>
|
||||
<TooltipContent arrowPadding={10} className={"p-2 shadow-cyan-950 shadow-md"}>
|
||||
Copied SUUID to clipboard!
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<div
|
||||
className={`flex items-center p-3 m-2 ${isDarkMode ? "hover:bg-accent/90" : "hover:bg-secondary/20"} rounded-full transition-colors duration-200`}>
|
||||
onClick={() => {
|
||||
setCopied(true)
|
||||
navigator.clipboard.writeText(suuid)
|
||||
}}
|
||||
className={`flex items-center p-3 m-2 ${isDarkMode ? "hover:bg-accent/90" : "hover:bg-secondary/20"} rounded-full transition-colors duration-200 cursor-pointer select-none`}>
|
||||
<Avatar className="w-12 h-12 mr-3">
|
||||
<AvatarFallback>{username.charAt(0)}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<h3 className={`font-semibold text-[17px] ${isDarkMode ? "text-white" : "text-black"}`}>{username}</h3>
|
||||
<p className="text-sm text-muted-foreground">@{username}</p>
|
||||
<p className="text-xs text-muted">${suuid}</p>
|
||||
<p className="text-xs text-muted-foreground">${suuid}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="my-2"/>
|
||||
|
|
@ -179,15 +181,16 @@ function Sidebar(
|
|||
isDarkMode ? "bg-background" : "white"
|
||||
}`}
|
||||
>
|
||||
<div className="flex justify-items-start w-[240px] mt-1.5">
|
||||
<Link href={"/"} passHref>
|
||||
<div className="flex justify-items-start w-[240px] mt-1.5 hover:scale-105 transition-all duration-300">
|
||||
<Link href={"/"} passHref className={"flex flex-row items-center ml-1.5"}>
|
||||
<Image
|
||||
src={isDarkMode ? "/logos/logo.png" : "/logos/logo-light.png"}
|
||||
alt="Tocka‘s Nest"
|
||||
width={64}
|
||||
height={64}
|
||||
className="w-16 h-16 cursor-pointer rounded-full hover:bg-secondary/20"
|
||||
className="w-16 h-16 cursor-pointer rounded-full antialiased"
|
||||
/>
|
||||
<p className={"text-center text-xl font-bold antialiased"}>SiPher</p>
|
||||
</Link>
|
||||
</div>
|
||||
<RightSidebarContent/>
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
import * as React from "react"
|
||||
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import {cn} from "@/lib/utils"
|
||||
|
||||
const Avatar = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
>(({className, ...props}, ref) => (
|
||||
<AvatarPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
|
|
@ -23,7 +23,7 @@ Avatar.displayName = AvatarPrimitive.Root.displayName
|
|||
const AvatarImage = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Image>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
||||
>(({ className, ...props }, ref) => (
|
||||
>(({className, ...props}, ref) => (
|
||||
<AvatarPrimitive.Image
|
||||
ref={ref}
|
||||
className={cn("aspect-square h-full w-full", className)}
|
||||
|
|
@ -35,7 +35,7 @@ AvatarImage.displayName = AvatarPrimitive.Image.displayName
|
|||
const AvatarFallback = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
||||
>(({ className, ...props }, ref) => (
|
||||
>(({className, ...props}, ref) => (
|
||||
<AvatarPrimitive.Fallback
|
||||
ref={ref}
|
||||
className={cn(
|
||||
|
|
@ -47,4 +47,4 @@ const AvatarFallback = React.forwardRef<
|
|||
))
|
||||
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
|
||||
|
||||
export { Avatar, AvatarImage, AvatarFallback }
|
||||
export {Avatar, AvatarImage, AvatarFallback}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import {Slot} from "@radix-ui/react-slot"
|
||||
import {cva, type VariantProps} from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import {cn} from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
|
|
@ -41,11 +41,11 @@ export interface ButtonProps
|
|||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
({className, variant, size, asChild = false, ...props}, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
className={cn(buttonVariants({variant, size, className}))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
|
|
@ -54,4 +54,4 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|||
)
|
||||
Button.displayName = "Button"
|
||||
|
||||
export { Button, buttonVariants }
|
||||
export {Button, buttonVariants}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import {cn} from "@/lib/utils"
|
||||
|
||||
const Card = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
>(({className, ...props}, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
|
|
@ -20,7 +20,7 @@ Card.displayName = "Card"
|
|||
const CardHeader = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
>(({className, ...props}, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||
|
|
@ -32,7 +32,7 @@ CardHeader.displayName = "CardHeader"
|
|||
const CardTitle = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
>(({className, ...props}, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("font-semibold leading-none tracking-tight", className)}
|
||||
|
|
@ -44,7 +44,7 @@ CardTitle.displayName = "CardTitle"
|
|||
const CardDescription = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
>(({className, ...props}, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
|
|
@ -56,7 +56,7 @@ CardDescription.displayName = "CardDescription"
|
|||
const CardContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
>(({className, ...props}, ref) => (
|
||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||
))
|
||||
CardContent.displayName = "CardContent"
|
||||
|
|
@ -64,7 +64,7 @@ CardContent.displayName = "CardContent"
|
|||
const CardFooter = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
>(({className, ...props}, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex items-center p-6 pt-0", className)}
|
||||
|
|
@ -73,4 +73,4 @@ const CardFooter = React.forwardRef<
|
|||
))
|
||||
CardFooter.displayName = "CardFooter"
|
||||
|
||||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||
export {Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import {cn} from "@/lib/utils"
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
({className, type, ...props}, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
|
|
@ -19,4 +19,4 @@ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
|||
)
|
||||
Input.displayName = "Input"
|
||||
|
||||
export { Input }
|
||||
export {Input}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import {cva, type VariantProps} from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import {cn} from "@/lib/utils"
|
||||
|
||||
const labelVariants = cva(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
|
|
@ -14,7 +14,7 @@ const Label = React.forwardRef<
|
|||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||
VariantProps<typeof labelVariants>
|
||||
>(({ className, ...props }, ref) => (
|
||||
>(({className, ...props}, ref) => (
|
||||
<LabelPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(labelVariants(), className)}
|
||||
|
|
@ -23,4 +23,4 @@ const Label = React.forwardRef<
|
|||
))
|
||||
Label.displayName = LabelPrimitive.Root.displayName
|
||||
|
||||
export { Label }
|
||||
export {Label}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
import * as React from "react"
|
||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import {cn} from "@/lib/utils"
|
||||
|
||||
const ScrollArea = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
>(({className, children, ...props}, ref) => (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("relative overflow-hidden", className)}
|
||||
|
|
@ -17,8 +17,8 @@ const ScrollArea = React.forwardRef<
|
|||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
<ScrollBar/>
|
||||
<ScrollAreaPrimitive.Corner/>
|
||||
</ScrollAreaPrimitive.Root>
|
||||
))
|
||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
||||
|
|
@ -26,7 +26,7 @@ ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
|||
const ScrollBar = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
>(({ className, orientation = "vertical", ...props }, ref) => (
|
||||
>(({className, orientation = "vertical", ...props}, ref) => (
|
||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||
ref={ref}
|
||||
orientation={orientation}
|
||||
|
|
@ -40,9 +40,9 @@ const ScrollBar = React.forwardRef<
|
|||
)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
||||
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border"/>
|
||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
))
|
||||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
|
||||
|
||||
export { ScrollArea, ScrollBar }
|
||||
export {ScrollArea, ScrollBar}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@
|
|||
import * as React from "react"
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import {cn} from "@/lib/utils"
|
||||
|
||||
const Separator = React.forwardRef<
|
||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||
>(
|
||||
(
|
||||
{ className, orientation = "horizontal", decorative = true, ...props },
|
||||
{className, orientation = "horizontal", decorative = true, ...props},
|
||||
ref
|
||||
) => (
|
||||
<SeparatorPrimitive.Root
|
||||
|
|
@ -28,4 +28,4 @@ const Separator = React.forwardRef<
|
|||
)
|
||||
Separator.displayName = SeparatorPrimitive.Root.displayName
|
||||
|
||||
export { Separator }
|
||||
export {Separator}
|
||||
|
|
|
|||
19
src/components/ui/theme-provider.tsx
Normal file
19
src/components/ui/theme-provider.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// components/providers/theme-provider.tsx
|
||||
'use client'
|
||||
|
||||
import {ThemeProvider as NextThemesProvider, type ThemeProviderProps} from "next-themes"
|
||||
import {useEffect, useState} from "react"
|
||||
|
||||
export default function ThemeProvider({children, ...props}: ThemeProviderProps) {
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
if (!mounted) {
|
||||
return <>{children}</>
|
||||
}
|
||||
|
||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
||||
}
|
||||
|
|
@ -2,17 +2,17 @@
|
|||
|
||||
import * as React from "react"
|
||||
import * as ToastPrimitives from "@radix-ui/react-toast"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { X } from "lucide-react"
|
||||
import {cva, type VariantProps} from "class-variance-authority"
|
||||
import {X} from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import {cn} from "@/lib/utils"
|
||||
|
||||
const ToastProvider = ToastPrimitives.Provider
|
||||
|
||||
const ToastViewport = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Viewport>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
|
||||
>(({ className, ...props }, ref) => (
|
||||
>(({className, ...props}, ref) => (
|
||||
<ToastPrimitives.Viewport
|
||||
ref={ref}
|
||||
className={cn(
|
||||
|
|
@ -44,11 +44,11 @@ const Toast = React.forwardRef<
|
|||
React.ElementRef<typeof ToastPrimitives.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
|
||||
VariantProps<typeof toastVariants>
|
||||
>(({ className, variant, ...props }, ref) => {
|
||||
>(({className, variant, ...props}, ref) => {
|
||||
return (
|
||||
<ToastPrimitives.Root
|
||||
ref={ref}
|
||||
className={cn(toastVariants({ variant }), className)}
|
||||
className={cn(toastVariants({variant}), className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
|
@ -58,7 +58,7 @@ Toast.displayName = ToastPrimitives.Root.displayName
|
|||
const ToastAction = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Action>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
|
||||
>(({ className, ...props }, ref) => (
|
||||
>(({className, ...props}, ref) => (
|
||||
<ToastPrimitives.Action
|
||||
ref={ref}
|
||||
className={cn(
|
||||
|
|
@ -73,7 +73,7 @@ ToastAction.displayName = ToastPrimitives.Action.displayName
|
|||
const ToastClose = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Close>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
|
||||
>(({ className, ...props }, ref) => (
|
||||
>(({className, ...props}, ref) => (
|
||||
<ToastPrimitives.Close
|
||||
ref={ref}
|
||||
className={cn(
|
||||
|
|
@ -83,7 +83,7 @@ const ToastClose = React.forwardRef<
|
|||
toast-close=""
|
||||
{...props}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
<X className="h-4 w-4"/>
|
||||
</ToastPrimitives.Close>
|
||||
))
|
||||
ToastClose.displayName = ToastPrimitives.Close.displayName
|
||||
|
|
@ -91,7 +91,7 @@ ToastClose.displayName = ToastPrimitives.Close.displayName
|
|||
const ToastTitle = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
>(({className, ...props}, ref) => (
|
||||
<ToastPrimitives.Title
|
||||
ref={ref}
|
||||
className={cn("text-sm font-semibold [&+div]:text-xs", className)}
|
||||
|
|
@ -103,7 +103,7 @@ ToastTitle.displayName = ToastPrimitives.Title.displayName
|
|||
const ToastDescription = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
>(({className, ...props}, ref) => (
|
||||
<ToastPrimitives.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm opacity-90", className)}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,14 @@
|
|||
"use client"
|
||||
|
||||
import { useToast } from "@/hooks/use-toast"
|
||||
import {
|
||||
Toast,
|
||||
ToastClose,
|
||||
ToastDescription,
|
||||
ToastProvider,
|
||||
ToastTitle,
|
||||
ToastViewport,
|
||||
} from "@/components/ui/toast"
|
||||
import {useToast} from "@/hooks/use-toast"
|
||||
import {Toast, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport,} from "@/components/ui/toast"
|
||||
|
||||
export function Toaster() {
|
||||
const { toasts } = useToast()
|
||||
const {toasts} = useToast()
|
||||
|
||||
return (
|
||||
<ToastProvider>
|
||||
{toasts.map(function ({ id, title, description, action, ...props }) {
|
||||
{toasts.map(function ({id, title, description, action, ...props}) {
|
||||
return (
|
||||
<Toast key={id} {...props}>
|
||||
<div className="grid gap-1">
|
||||
|
|
@ -25,11 +18,11 @@ export function Toaster() {
|
|||
)}
|
||||
</div>
|
||||
{action}
|
||||
<ToastClose />
|
||||
<ToastClose/>
|
||||
</Toast>
|
||||
)
|
||||
})}
|
||||
<ToastViewport />
|
||||
<ToastViewport/>
|
||||
</ToastProvider>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
32
src/components/ui/tooltip.tsx
Normal file
32
src/components/ui/tooltip.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||
|
||||
import {cn} from "@/lib/utils"
|
||||
|
||||
const TooltipProvider = TooltipPrimitive.Provider
|
||||
|
||||
const Tooltip = TooltipPrimitive.Root
|
||||
|
||||
const TooltipTrigger = TooltipPrimitive.Trigger
|
||||
|
||||
const TooltipContent = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||
>(({className, sideOffset = 4, ...props}, ref) => (
|
||||
<TooltipPrimitive.Portal>
|
||||
<TooltipPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</TooltipPrimitive.Portal>
|
||||
))
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
||||
|
||||
export {Tooltip, TooltipTrigger, TooltipContent, TooltipProvider}
|
||||
|
|
@ -6,7 +6,7 @@ import {useRouter} from 'next/navigation';
|
|||
|
||||
interface UserContextType {
|
||||
user: NonNullable<SiPher.User>;
|
||||
getUser: () => Promise<NonNullable<SiPher.User>>;
|
||||
getUser: (context: string) => Promise<NonNullable<SiPher.User>>;
|
||||
}
|
||||
|
||||
const UserContext = createContext<UserContextType | null>(null);
|
||||
|
|
@ -21,7 +21,11 @@ export function useUser() {
|
|||
|
||||
return {
|
||||
user: context.user,
|
||||
getUser: async (userId?: string) => {
|
||||
getUser: async (context: string, userId?: string) => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.log(`useUser().getUser(): Being called by ${context}`)
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/auth/get_user?${
|
||||
userId && `uuid=${
|
||||
|
|
@ -44,9 +48,12 @@ export function useUser() {
|
|||
throw error;
|
||||
}
|
||||
},
|
||||
checkAuth: async () => {
|
||||
checkAuth: async (context: string) => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.log(`useUser().checkAuth(): Being called by ${context}`)
|
||||
}
|
||||
try {
|
||||
const response = await fetch('/api/auth/get/user');
|
||||
const response = await fetch('/api/auth/get_user');
|
||||
return response.ok;
|
||||
} catch {
|
||||
return false;
|
||||
|
|
@ -68,7 +75,7 @@ export function UserProvider(
|
|||
<UserContext.Provider value={{
|
||||
user: initialUser,
|
||||
getUser: async () => {
|
||||
const response = await fetch('/api/auth/get/user');
|
||||
const response = await fetch('/api/auth/get_user');
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to get user');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,7 @@
|
|||
// Inspired by react-hot-toast library
|
||||
import * as React from "react"
|
||||
|
||||
import type {
|
||||
ToastActionElement,
|
||||
ToastProps,
|
||||
} from "@/components/ui/toast"
|
||||
import type {ToastActionElement, ToastProps,} from "@/components/ui/toast"
|
||||
|
||||
const TOAST_LIMIT = 1
|
||||
const TOAST_REMOVE_DELAY = 1000000
|
||||
|
|
@ -38,19 +35,19 @@ type Action =
|
|||
| {
|
||||
type: ActionType["ADD_TOAST"]
|
||||
toast: ToasterToast
|
||||
}
|
||||
}
|
||||
| {
|
||||
type: ActionType["UPDATE_TOAST"]
|
||||
toast: Partial<ToasterToast>
|
||||
}
|
||||
}
|
||||
| {
|
||||
type: ActionType["DISMISS_TOAST"]
|
||||
toastId?: ToasterToast["id"]
|
||||
}
|
||||
}
|
||||
| {
|
||||
type: ActionType["REMOVE_TOAST"]
|
||||
toastId?: ToasterToast["id"]
|
||||
}
|
||||
}
|
||||
|
||||
interface State {
|
||||
toasts: ToasterToast[]
|
||||
|
|
@ -86,12 +83,12 @@ export const reducer = (state: State, action: Action): State => {
|
|||
return {
|
||||
...state,
|
||||
toasts: state.toasts.map((t) =>
|
||||
t.id === action.toast.id ? { ...t, ...action.toast } : t
|
||||
t.id === action.toast.id ? {...t, ...action.toast} : t
|
||||
),
|
||||
}
|
||||
|
||||
case "DISMISS_TOAST": {
|
||||
const { toastId } = action
|
||||
const {toastId} = action
|
||||
|
||||
// ! Side effects ! - This could be extracted into a dismissToast() action,
|
||||
// but I'll keep it here for simplicity
|
||||
|
|
@ -131,7 +128,7 @@ export const reducer = (state: State, action: Action): State => {
|
|||
|
||||
const listeners: Array<(state: State) => void> = []
|
||||
|
||||
let memoryState: State = { toasts: [] }
|
||||
let memoryState: State = {toasts: []}
|
||||
|
||||
function dispatch(action: Action) {
|
||||
memoryState = reducer(memoryState, action)
|
||||
|
|
@ -142,15 +139,15 @@ function dispatch(action: Action) {
|
|||
|
||||
type Toast = Omit<ToasterToast, "id">
|
||||
|
||||
function toast({ ...props }: Toast) {
|
||||
function toast({...props}: Toast) {
|
||||
const id = genId()
|
||||
|
||||
const update = (props: ToasterToast) =>
|
||||
dispatch({
|
||||
type: "UPDATE_TOAST",
|
||||
toast: { ...props, id },
|
||||
toast: {...props, id},
|
||||
})
|
||||
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
|
||||
const dismiss = () => dispatch({type: "DISMISS_TOAST", toastId: id})
|
||||
|
||||
dispatch({
|
||||
type: "ADD_TOAST",
|
||||
|
|
@ -187,8 +184,8 @@ function useToast() {
|
|||
return {
|
||||
...state,
|
||||
toast,
|
||||
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
|
||||
dismiss: (toastId?: string) => dispatch({type: "DISMISS_TOAST", toastId}),
|
||||
}
|
||||
}
|
||||
|
||||
export { useToast, toast }
|
||||
export {useToast, toast}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
"use server"
|
||||
// lib/auth/index.ts
|
||||
import {createClient} from '@/lib/supabase/server';
|
||||
import {headers} from 'next/headers';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import {type ClassValue, clsx} from "clsx"
|
||||
import {twMerge} from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
|
|
|
|||
|
|
@ -16,9 +16,14 @@ const isPublicRoute = (path: string) => {
|
|||
}
|
||||
|
||||
export async function middleware(request: NextRequest) {
|
||||
|
||||
const requestHeaders = new Headers(request.headers);
|
||||
requestHeaders.set('x-current-pathname', request.url)
|
||||
requestHeaders.set('x-next-pathname', request.nextUrl.pathname);
|
||||
|
||||
let response = NextResponse.next({
|
||||
request: {
|
||||
headers: request.headers,
|
||||
headers: requestHeaders,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -33,7 +38,9 @@ export async function middleware(request: NextRequest) {
|
|||
redirectUrl.search = request.nextUrl.search;
|
||||
}
|
||||
redirectUrl.searchParams.set('redirectTo', request.nextUrl.pathname);
|
||||
return NextResponse.redirect(redirectUrl);
|
||||
const redirect = NextResponse.redirect(redirectUrl);
|
||||
redirect.headers.set('x-current-pathname', path);
|
||||
return redirect;
|
||||
}
|
||||
|
||||
if (user && path.startsWith('/auth/') && !path.includes("/auth/complete")) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue