@@ -71,34 +71,33 @@ export async function POST(req: NextRequest) {
|
|||||||
*/
|
*/
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
const url = new URL(req.url);
|
const url = new URL(req.url);
|
||||||
const query_id = url.searchParams.get("id");
|
const id = url.searchParams.get("id");
|
||||||
|
const username = url.searchParams.get("username");
|
||||||
const token = req.cookies.get("token")?.value;
|
const token = req.cookies.get("token")?.value;
|
||||||
|
|
||||||
if (!query_id && !token)
|
if (!token && !id && !username)
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ message: "Query arg should be token or id" },
|
{ message: "Query arg should be token or id or username" },
|
||||||
{ status: StatusCodes.BAD_REQUEST }
|
{ status: StatusCodes.BAD_REQUEST }
|
||||||
);
|
);
|
||||||
|
|
||||||
let user_id = query_id;
|
const { data: profile, error } = id
|
||||||
if (!user_id && token) {
|
? await supabase
|
||||||
const {
|
.from("profiles")
|
||||||
data: { user },
|
.select("id, username, display_name, avatar_url, node_address")
|
||||||
} = await supabase.auth.getUser(token);
|
.eq("id", id)
|
||||||
user_id = user?.id ?? null;
|
.maybeSingle()
|
||||||
}
|
: username
|
||||||
|
? await supabase
|
||||||
if (!user_id)
|
.from("profiles")
|
||||||
return NextResponse.json(
|
.select("id, username, display_name, avatar_url, node_address")
|
||||||
{ message: "Invalid token" },
|
.eq("username", username)
|
||||||
{ status: StatusCodes.BAD_REQUEST }
|
.maybeSingle()
|
||||||
);
|
: await supabase
|
||||||
|
.from("profiles")
|
||||||
const { data: profile, error } = await supabase
|
.select("id, username, display_name, avatar_url, node_address")
|
||||||
.from("profiles")
|
.eq("id", (await supabase.auth.getUser(token)).data.user?.id)
|
||||||
.select("id, username, display_name, avatar_url, node_address")
|
.maybeSingle();
|
||||||
.eq("id", user_id)
|
|
||||||
.maybeSingle();
|
|
||||||
|
|
||||||
if (error)
|
if (error)
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
|
|||||||
@@ -4,10 +4,30 @@ import MessageBox from "@/components/MessageBox";
|
|||||||
import AppSidebar from "@/components/Sidebar";
|
import AppSidebar from "@/components/Sidebar";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import useApp from "@/hooks/use-app";
|
import useApp from "@/hooks/use-app";
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
import auth from "@/lib/auth";
|
||||||
|
|
||||||
export default function DMs() {
|
export default function DMs() {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const app = useApp();
|
const app = useApp();
|
||||||
|
const targetNode = useRef<WebSocket | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function connectTargetNode() {
|
||||||
|
const target = await app.getUserById(id);
|
||||||
|
if (target) {
|
||||||
|
auth(
|
||||||
|
target.node_address,
|
||||||
|
targetNode,
|
||||||
|
() => {},
|
||||||
|
app.addMessage,
|
||||||
|
() => {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connectTargetNode().then(() => {});
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppSidebar app={app}>
|
<AppSidebar app={app}>
|
||||||
@@ -20,7 +40,7 @@ export default function DMs() {
|
|||||||
(m.from === id && m.channel_id === app.profile?.id)
|
(m.from === id && m.channel_id === app.profile?.id)
|
||||||
)}
|
)}
|
||||||
sendMessage={(message) => {
|
sendMessage={(message) => {
|
||||||
app.node.current?.send(
|
targetNode.current?.send(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
type: "send_message",
|
type: "send_message",
|
||||||
params: {
|
params: {
|
||||||
|
|||||||
+51
-43
@@ -37,6 +37,7 @@ import { get, Response } from "@/lib/request";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import App from "@/types/app";
|
import App from "@/types/app";
|
||||||
import { ProfileSettings } from "@/types/settings";
|
import { ProfileSettings } from "@/types/settings";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
export default function AppSidebar({
|
export default function AppSidebar({
|
||||||
children,
|
children,
|
||||||
@@ -53,7 +54,9 @@ export default function AppSidebar({
|
|||||||
}>) {
|
}>) {
|
||||||
const [servers, setServers] = useState<[string, string][]>([]);
|
const [servers, setServers] = useState<[string, string][]>([]);
|
||||||
const [newServer, setNewServer] = useState({ name: "", ip: "" });
|
const [newServer, setNewServer] = useState({ name: "", ip: "" });
|
||||||
|
const [query, setQuery] = useState("");
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [filteredDms, setFilteredDms] = useState<UserProfile[]>([]);
|
||||||
|
|
||||||
const dms = Array.from(
|
const dms = Array.from(
|
||||||
new Set(
|
new Set(
|
||||||
@@ -63,6 +66,21 @@ export default function AppSidebar({
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const load = async () => {
|
||||||
|
const users = await Promise.all(dms.map((id) => app.getUserById(id)));
|
||||||
|
setFilteredDms(
|
||||||
|
users.filter(
|
||||||
|
(user) =>
|
||||||
|
user !== null &&
|
||||||
|
(user?.username.toLowerCase().includes(query.toLowerCase()) ||
|
||||||
|
user?.id.toLowerCase().includes(query.toLowerCase()))
|
||||||
|
) as UserProfile[]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
load();
|
||||||
|
}, [dms]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const s = Cookies.get("servers")?.split(",");
|
const s = Cookies.get("servers")?.split(",");
|
||||||
if (s) setServers(s.map((x) => x.trim().split("@") as [string, string]));
|
if (s) setServers(s.map((x) => x.trim().split("@") as [string, string]));
|
||||||
@@ -213,12 +231,16 @@ export default function AppSidebar({
|
|||||||
<h2>{server.name}</h2>
|
<h2>{server.name}</h2>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="p-3 border-b flex items-center gap-2">
|
<div className="p-3 border-b flex items-center">
|
||||||
<Search className="h-4 w-4 text-muted-foreground" />
|
<div className="relative w-full">
|
||||||
<Input
|
<Input
|
||||||
placeholder="Find or start a conversation"
|
placeholder="Find or start a conversation"
|
||||||
className="h-8"
|
className="pr-8 h-8"
|
||||||
/>
|
value={query}
|
||||||
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Search className="absolute right-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<ScrollArea className="flex-1">
|
<ScrollArea className="flex-1">
|
||||||
@@ -231,22 +253,34 @@ export default function AppSidebar({
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{dms.map((id) => (
|
{filteredDms.map((user) => (
|
||||||
<DMItem
|
<DMItem
|
||||||
name={
|
name={user.display_name}
|
||||||
getUser(id, app.profiles, app.setProfiles)
|
id={user.id}
|
||||||
?.display_name || id
|
|
||||||
}
|
|
||||||
id={id}
|
|
||||||
status="online"
|
status="online"
|
||||||
avatar={
|
avatar={user.avatar_url}
|
||||||
getUser(id, app.profiles, app.setProfiles)
|
key={user.id}
|
||||||
?.avatar_url || ""
|
|
||||||
}
|
|
||||||
key={id}
|
|
||||||
app={app}
|
app={app}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
{filteredDms.length === 0 && query.trim() !== "" ? (
|
||||||
|
<div className="px-3 py-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="w-full justify-start gap-2 text-sm"
|
||||||
|
onClick={async () => {
|
||||||
|
const response = await axios.get("/api/profile", {
|
||||||
|
params: { username: query },
|
||||||
|
});
|
||||||
|
const user = response.data as UserProfile;
|
||||||
|
router.push(`/chat/${user.id}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Plus className="h-4 w-4" />
|
||||||
|
Start new DM with “{query}”
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -335,29 +369,3 @@ function ChannelIcon({ kind }: { kind: "text" | "voice" }) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUser(
|
|
||||||
id: string,
|
|
||||||
userList: StringMap<UserProfile | null>,
|
|
||||||
setUserList: React.Dispatch<
|
|
||||||
React.SetStateAction<StringMap<UserProfile | null>>
|
|
||||||
>
|
|
||||||
) {
|
|
||||||
if (userList[id] !== undefined) return userList[id];
|
|
||||||
|
|
||||||
const onResponse = (res: Response<UserProfile>) => {
|
|
||||||
setUserList((prev) => {
|
|
||||||
prev[id] = res.data as UserProfile;
|
|
||||||
return prev;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
get(`/api/profile/?id=${id}`, onResponse)
|
|
||||||
?.then(onResponse)
|
|
||||||
.catch((e) => {
|
|
||||||
setUserList((prev) => {
|
|
||||||
prev[id] = null;
|
|
||||||
return prev;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ export default function CreateAccount() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const signup = async () => {
|
const signup = async () => {
|
||||||
|
localStorage.removeItem("chat-messages");
|
||||||
|
localStorage.removeItem("server-messages");
|
||||||
setFeedback(undefined);
|
setFeedback(undefined);
|
||||||
const validationError = validateInput();
|
const validationError = validateInput();
|
||||||
if (validationError) {
|
if (validationError) {
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ export default function Login() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const login = async () => {
|
const login = async () => {
|
||||||
|
localStorage.removeItem("chat-messages");
|
||||||
|
localStorage.removeItem("server-messages");
|
||||||
setFeedback(undefined);
|
setFeedback(undefined);
|
||||||
try {
|
try {
|
||||||
const res = await axios.get("/api/user", {
|
const res = await axios.get("/api/user", {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useMessages } from "./use-messages";
|
|||||||
import { StringMap } from "@/types/typeUtils";
|
import { StringMap } from "@/types/typeUtils";
|
||||||
import useUser, { UserProfile } from "./get-user";
|
import useUser, { UserProfile } from "./get-user";
|
||||||
import { useClientSettings } from "./use-settings";
|
import { useClientSettings } from "./use-settings";
|
||||||
|
import { get, Response } from "@/lib/request";
|
||||||
|
|
||||||
export default function useApp(): App {
|
export default function useApp(): App {
|
||||||
const node = useRef<WebSocket | null>(null);
|
const node = useRef<WebSocket | null>(null);
|
||||||
@@ -24,5 +25,23 @@ export default function useApp(): App {
|
|||||||
addMessage,
|
addMessage,
|
||||||
sidebarOpen,
|
sidebarOpen,
|
||||||
setSidebarOpen,
|
setSidebarOpen,
|
||||||
|
getUserById: async (id: string) => {
|
||||||
|
if (profiles[id] !== undefined) return profiles[id];
|
||||||
|
|
||||||
|
const onResponse = (res: Response<UserProfile>) => {
|
||||||
|
setProfiles((prev) => {
|
||||||
|
prev[id] = res.data as UserProfile;
|
||||||
|
return prev;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await get(`/api/profile/?id=${id}`, onResponse);
|
||||||
|
onResponse(user);
|
||||||
|
return user.data;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-11
@@ -1,19 +1,21 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
|
|
||||||
const MOBILE_BREAKPOINT = 768
|
const MOBILE_BREAKPOINT = 768;
|
||||||
|
|
||||||
export function useIsMobile() {
|
export function useIsMobile() {
|
||||||
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
||||||
const onChange = () => {
|
const onChange = () => {
|
||||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
||||||
}
|
};
|
||||||
mql.addEventListener("change", onChange)
|
mql.addEventListener("change", onChange);
|
||||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
||||||
return () => mql.removeEventListener("change", onChange)
|
return () => mql.removeEventListener("change", onChange);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
return !!isMobile
|
return !!isMobile;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,4 +21,6 @@ export default interface App {
|
|||||||
|
|
||||||
sidebarOpen: boolean;
|
sidebarOpen: boolean;
|
||||||
setSidebarOpen: SetState<boolean>;
|
setSidebarOpen: SetState<boolean>;
|
||||||
|
|
||||||
|
getUserById: (userId: string) => Promise<UserProfile | null>;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user