diff --git a/apps/tauri/render-version.ts b/apps/tauri/render-version.ts
index 96f1c10..0bd3fca 100644
--- a/apps/tauri/render-version.ts
+++ b/apps/tauri/render-version.ts
@@ -12,9 +12,7 @@ const tauriConfigPath = path.resolve(__dirname, "./src-tauri/tauri.conf.json");
const isUnrender = process.argv.includes("--unrender");
// Version Source
-const packageJson = JSON.parse(
- fs.readFileSync(rootPackageJsonPath, "utf8")
-);
+const packageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath, "utf8"));
const packageVersion: string = packageJson.version;
@@ -22,9 +20,7 @@ if (!packageVersion && !isUnrender) {
throw new Error("No version found in package.json");
}
-const targetVersion = isUnrender
- ? PLACEHOLDER_VERSION
- : packageVersion;
+const targetVersion = isUnrender ? PLACEHOLDER_VERSION : packageVersion;
// Helpers
function updateCargoToml(content: string): string {
@@ -34,10 +30,7 @@ function updateCargoToml(content: string): string {
throw new Error("Could not find version field in Cargo.toml");
}
- return content.replace(
- regex,
- `version = "${targetVersion}"`
- );
+ return content.replace(regex, `version = "${targetVersion}"`);
}
function updateTauriConfig(content: string): string {
@@ -47,10 +40,7 @@ function updateTauriConfig(content: string): string {
throw new Error("Could not find version field in tauri.conf.json");
}
- return content.replace(
- regex,
- `"version": "${targetVersion}"`
- );
+ return content.replace(regex, `"version": "${targetVersion}"`);
}
// Update Cargo.toml
@@ -72,4 +62,4 @@ if (isUnrender) {
console.log(`Unrendered versions back to ${PLACEHOLDER_VERSION}`);
} else {
console.log(`Rendered version ${targetVersion}`);
-}
\ No newline at end of file
+}
diff --git a/apps/web/src/components/modals/profile.tsx b/apps/web/src/components/modals/profile.tsx
new file mode 100644
index 0000000..6a31e36
--- /dev/null
+++ b/apps/web/src/components/modals/profile.tsx
@@ -0,0 +1,34 @@
+import type { User } from "@tensamin/user/context";
+import { Avatar, AvatarFallback, AvatarImage } from "@tensamin/ui";
+import Text from "@tensamin/markdown/text";
+
+export default function Profile({ user }: { user: User }) {
+ return (
+
+
+
+
+
+ {user.display.slice(0, 2).toUpperCase()}
+
+
+
+
{user.display}
+
{user.username}
+
+
+
+
+
+ iota: {user.iota_id}
+
+
+ user: {user.user_id}
+
+
+ pub key: {user.public_key}
+
+
+
+ );
+}
diff --git a/apps/web/src/components/navbar.tsx b/apps/web/src/components/navbar.tsx
index 9e3ec9d..57f9d24 100644
--- a/apps/web/src/components/navbar.tsx
+++ b/apps/web/src/components/navbar.tsx
@@ -1,7 +1,8 @@
-import { Button } from "@tensamin/ui";
+import { Button, Popover, PopoverContent, PopoverTrigger } from "@tensamin/ui";
import {
ArrowLeft,
ChevronDown,
+ ChevronUp,
House,
Phone,
Settings,
@@ -17,6 +18,7 @@ import { useState } from "react";
import { SidebarTrigger, useSidebar } from "@tensamin/ui";
import { WindowControls as Controls } from "@tensamin/ui";
import { useSession } from "@tensamin/storage/session";
+import Profile from "./modals/profile";
export default function Navbar({ forMobile }: { forMobile: boolean }) {
const navigate = useNavigate();
@@ -33,6 +35,8 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) {
const [selectOpen, setSelectOpen] = useState(false);
+ const [userInfoOpen, setUserInfoOpen] = useState(false);
+
return (
(
-
+ <>
+
+
+ {user?.display}
+ {userInfoOpen ? : }
+
+ }
+ />
+
+
+
+
+ >
)}
loading={
}
/>
diff --git a/bun.lock b/bun.lock
index a15bdbe..1f1b34a 100644
--- a/bun.lock
+++ b/bun.lock
@@ -171,6 +171,7 @@
"name": "@tensamin/notifications",
"version": "0.0.0",
"dependencies": {
+ "@tanstack/react-router": "^1.169.1",
"@tauri-apps/api": "^2.11.0",
"@tensamin/chat": "workspace:*",
"@tensamin/crypto": "workspace:*",
diff --git a/packages/call/src/store.tsx b/packages/call/src/store.tsx
index 5b49603..5398e70 100644
--- a/packages/call/src/store.tsx
+++ b/packages/call/src/store.tsx
@@ -295,13 +295,15 @@ async function updateLocalParticipantAttributes(
screenSharePreviewLength: attributes.screenSharePreview?.length ?? 0,
});
- await getRoom().localParticipant.setAttributes(attributes).catch((error) => {
- log(1, "call", "red", "Failed to update local participant attributes", {
- attributes: Object.keys(attributes),
- error,
+ await getRoom()
+ .localParticipant.setAttributes(attributes)
+ .catch((error) => {
+ log(1, "call", "red", "Failed to update local participant attributes", {
+ attributes: Object.keys(attributes),
+ error,
+ });
+ throw error;
});
- throw error;
- });
log(2, "call", "purple", "Updated local participant attributes", {
attributeKeys: Object.keys(attributes),
@@ -795,11 +797,13 @@ export async function connect(callId: string) {
syncAllRemoteTrackSubscriptions();
- await getRoom().localParticipant.setMicrophoneEnabled(true).catch((error) => {
- log(1, "call", "red", "Failed to enable microphone", error);
- toast("error", "Failed to enable microphone.");
- throw error;
- });
+ await getRoom()
+ .localParticipant.setMicrophoneEnabled(true)
+ .catch((error) => {
+ log(1, "call", "red", "Failed to enable microphone", error);
+ toast("error", "Failed to enable microphone.");
+ throw error;
+ });
syncParticipantState();
}
diff --git a/packages/chat/src/components/input.tsx b/packages/chat/src/components/input.tsx
index b2aef9e..cf74848 100644
--- a/packages/chat/src/components/input.tsx
+++ b/packages/chat/src/components/input.tsx
@@ -97,6 +97,7 @@ export default function InputComponent({
>
(undefined);
-function reduceDisplay(display: string) {
- const words = display.split(" ");
- if (words.length === 1) {
- return display.slice(0, 2).toUpperCase();
- } else {
- return words[0].charAt(0).toUpperCase() + words[1].charAt(0).toUpperCase();
- }
+async function requestNotificationPermission() {
+ if (!("Notification" in window)) return false;
+
+ if (Notification.permission === "granted") return true;
+
+ const permission = await Notification.requestPermission();
+ return permission === "granted";
}
export default function Provider(props: { children: React.ReactNode }) {
@@ -29,6 +30,7 @@ export default function Provider(props: { children: React.ReactNode }) {
const { decryptText, getSharedSecret } = useCrypto();
const { addLiveMessage, userId } = useChat();
const { moveUserIdToTop } = useSession();
+ const navigate = useNavigate();
useEffect(() => {
return subscribePush(async (ttpMessage) => {
@@ -54,7 +56,7 @@ export default function Provider(props: { children: React.ReactNode }) {
(await load("settings.read_confirmations")) &&
userId === sender_id
) {
- await send(
+ void send(
"message_state",
{
message_state: "read",
@@ -64,7 +66,7 @@ export default function Provider(props: { children: React.ReactNode }) {
},
);
} else {
- await send(
+ void send(
"message_state",
{
message_state: "received",
@@ -92,18 +94,41 @@ export default function Provider(props: { children: React.ReactNode }) {
if (isTauri()) {
console.log("weewoo");
} else {
- sonnerToast(user.display, {
- classNames: {
- content: "pl-4",
- },
- description: decryptedContent,
- icon: (
-
-
- {reduceDisplay(user.display)}
-
- ),
- });
+ const hasPermissions = await requestNotificationPermission();
+
+ if (hasPermissions) {
+ const notification = new Notification(user.display, {
+ body: decryptedContent,
+ icon: user.avatar || user.display.slice(0, 2).toUpperCase(),
+ badge: user.avatar || user.display.slice(0, 2).toUpperCase(),
+ tag: `message-${user.user_id}`,
+ silent: true,
+ });
+
+ notification.onclick = () => {
+ window.focus();
+ navigate({
+ to: `/chat?id=${user.user_id}`,
+ });
+
+ notification.close();
+ };
+ } else {
+ sonnerToast(user.display, {
+ classNames: {
+ content: "pl-4",
+ },
+ description: decryptedContent,
+ icon: (
+
+
+
+ {user.display.slice(0, 2).toUpperCase()}
+
+
+ ),
+ });
+ }
}
}
});
@@ -117,6 +142,7 @@ export default function Provider(props: { children: React.ReactNode }) {
userId,
moveUserIdToTop,
send,
+ navigate,
]);
return (
diff --git a/packages/ttp/src/context.tsx b/packages/ttp/src/context.tsx
index 2f8fc40..d33313d 100644
--- a/packages/ttp/src/context.tsx
+++ b/packages/ttp/src/context.tsx
@@ -257,7 +257,7 @@ export function Provider(props: {
setErrorDescription(
"You could try to restart your Iota, check for updates or check your network connection.",
);
- })
+ });
}, [connected, subscribePush]);
useEffect(() => {