(feat): version bump needed for new ttp version #4
14 changed files with 203 additions and 44 deletions
(feat): improved conversation adding flow
(feat): visual tweaks for user modal (feat): now using full width for profile settings page on mobile (fix): profile not saving with empty avatar (chore): bump version due to prod release not getting created (chore): update licenses
commit
ba59a49f98
|
|
@ -31,12 +31,12 @@ export function Basic({
|
|||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<div className="absolute -bottom-0.75 -right-0.75 z-10 flex h-4 w-4 items-center justify-center rounded-full bg-card">
|
||||
<div className="absolute -bottom-0.5 -right-0.5 z-10 flex h-3.5 w-3.5 items-center justify-center rounded-full bg-card">
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: getStatusColor(user.online_status),
|
||||
}}
|
||||
className="h-2.5 w-2.5 rounded-full"
|
||||
className="h-2.25 w-2.25 rounded-full"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -52,7 +52,7 @@ export function Basic({
|
|||
<div className="flex flex-col gap-1 w-full items-start justify-center text-[15px]">
|
||||
<p>{user.display}</p>
|
||||
</div>
|
||||
<div className="pr-2">{extra}</div>
|
||||
<div className="pr-1">{extra}</div>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) {
|
|||
const currentCalls = calls.filter((call) =>
|
||||
call.call_members.some((member) => member === id),
|
||||
);
|
||||
//const currentCalls = ["leck", "schleck", "und", "eck"];
|
||||
|
||||
const [selectOpen, setSelectOpen] = useState(false);
|
||||
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ export default function Sidebar() {
|
|||
<button
|
||||
type="button"
|
||||
aria-label="Open profile menu"
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
|
||||
className="cursor-pointer inline-flex h-8 w-8 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
|
||||
>
|
||||
<Ellipsis />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -76,6 +76,16 @@ export default function List() {
|
|||
</div>
|
||||
);
|
||||
})}
|
||||
<p
|
||||
className="text-center text-sm px-2 text-muted-foreground transition-all duration-200 ease-in-out"
|
||||
hidden={contacts.length !== 0 || category !== "conversations"}
|
||||
style={{
|
||||
opacity:
|
||||
contacts.length === 0 && category === "conversations" ? 1 : 0,
|
||||
}}
|
||||
>
|
||||
Get started by adding a conversation
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@ import {
|
|||
useIsMobile,
|
||||
} from "@tensamin/ui";
|
||||
import z from "zod";
|
||||
import { toast } from "@tensamin/shared/log";
|
||||
import { useTTP } from "@tensamin/ttp";
|
||||
import { useState } from "react";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { isTauri } from "@tauri-apps/api/core";
|
||||
import { useStorage } from "@tensamin/storage/context";
|
||||
import { useSession } from "@tensamin/storage/session";
|
||||
|
||||
// The page
|
||||
export default function Page() {
|
||||
|
|
@ -43,11 +43,16 @@ export default function Page() {
|
|||
// Add Conversation Button Component
|
||||
function AddConversationButton() {
|
||||
const { send } = useTTP();
|
||||
const { contacts, insertContact } = useSession();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
function submit(username: string | null) {
|
||||
async function submit(username: string | null) {
|
||||
if (loading) return;
|
||||
setError(null);
|
||||
|
||||
// username check
|
||||
const schema = z
|
||||
.string()
|
||||
.min(1, "Username is too short")
|
||||
|
|
@ -56,26 +61,50 @@ function AddConversationButton() {
|
|||
const result = schema.safeParse(username?.toLowerCase().trim());
|
||||
|
||||
if (!result.success) {
|
||||
toast("error", result.error.issues[0].message);
|
||||
setError(result.error.issues[0].message);
|
||||
return;
|
||||
}
|
||||
|
||||
// user existence check
|
||||
const user = await send("get_user_data", {
|
||||
username: result.data,
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.data.user_id === 0) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
return data;
|
||||
})
|
||||
.catch(() => {
|
||||
setError("User not found");
|
||||
return;
|
||||
});
|
||||
if (!user) return;
|
||||
|
||||
// alrady added check
|
||||
if (contacts.some((contact) => contact.user_id === user.data.user_id)) {
|
||||
setError("Conversation already exists");
|
||||
return;
|
||||
}
|
||||
|
||||
// add the conv
|
||||
const timeout = setTimeout(() => setLoading(true), 500);
|
||||
|
||||
send("add_conversation", {
|
||||
chat_partner_name: result.data,
|
||||
})
|
||||
.then(() => {
|
||||
toast("success", "Conversation added");
|
||||
insertContact(user.data.user_id);
|
||||
setOpen(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (String(error).includes("error_not_found")) {
|
||||
toast("error", "User not found");
|
||||
setError("User not found");
|
||||
return;
|
||||
}
|
||||
|
||||
toast("error", "Failed to add conversation, check console for details");
|
||||
console.error("Failed to add conversation", error);
|
||||
setError(String(error));
|
||||
})
|
||||
.finally(() => {
|
||||
clearTimeout(timeout);
|
||||
|
|
@ -84,7 +113,15 @@ function AddConversationButton() {
|
|||
}
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(value) => {
|
||||
if (value) {
|
||||
setError(null);
|
||||
}
|
||||
setOpen(value);
|
||||
}}
|
||||
>
|
||||
<DialogTrigger render={<Button>Add Conversation</Button>} />
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
|
|
@ -110,7 +147,10 @@ function AddConversationButton() {
|
|||
name="username"
|
||||
placeholder="Enter username..."
|
||||
/>
|
||||
<div className="flex w-full justify-end gap-1">
|
||||
<div className="flex w-full justify-end items-center gap-1">
|
||||
{error && (
|
||||
<p className="text-sm text-destructive w-full">{error}</p>
|
||||
)}
|
||||
<DialogClose render={<Button variant="outline">Cancel</Button>} />
|
||||
<Button disabled={loading} type="submit">
|
||||
{loading && <Loader2 className="animate-spin" />} Continue
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import {
|
|||
AvatarFallback,
|
||||
AvatarImage,
|
||||
Button,
|
||||
cn,
|
||||
Input,
|
||||
useIsMobile,
|
||||
} from "@tensamin/ui";
|
||||
import { useUser, type User } from "@tensamin/user/context";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
|
@ -83,6 +85,8 @@ export default function Page() {
|
|||
}
|
||||
};
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return currentUser ? (
|
||||
<>
|
||||
<input
|
||||
|
|
@ -93,7 +97,7 @@ export default function Page() {
|
|||
}
|
||||
type="file"
|
||||
/>
|
||||
<div className="flex flex-col gap-5 w-80">
|
||||
<div className={cn("flex flex-col gap-5", isMobile ? "w-full" : "w-80")}>
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar className="size-14">
|
||||
<AvatarImage src={effectiveAvatar} />
|
||||
|
|
@ -121,7 +125,7 @@ export default function Page() {
|
|||
</Button>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
GIFs are supported in decentralised mode or with Tensamin Premium
|
||||
GIFs are supported in decentralised mode or with Tensamin Premium.
|
||||
<br />
|
||||
Maximum file size is 16mb.
|
||||
</p>
|
||||
|
|
@ -162,13 +166,16 @@ export default function Page() {
|
|||
/>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const { avatar, ...draftUsersWithoutAvatar } = draftUser;
|
||||
const payload = {
|
||||
...draftUser,
|
||||
avatar:
|
||||
typeof draftUser.avatar === "string" &&
|
||||
draftUser.avatar.startsWith("data:")
|
||||
? (draftUser.avatar.split(",", 2)[1] ?? "")
|
||||
: draftUser.avatar,
|
||||
...draftUsersWithoutAvatar,
|
||||
...(typeof avatar === "string"
|
||||
? {
|
||||
avatar: avatar.startsWith("data:")
|
||||
? (avatar.split(",", 2)[1] ?? "")
|
||||
: avatar,
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
|
||||
const validation = ttp.change_user_data.request.safeParse(payload);
|
||||
|
|
|
|||
|
|
@ -163,18 +163,18 @@ Generated from bun.lock and installed packages in workspace node_modules folders
|
|||
- Folder: `licenses/@tauri-apps_plugin-opener@2.5.4`
|
||||
- Source package dir: `apps/tauri/node_modules/@tauri-apps/plugin-opener`
|
||||
|
||||
## @tensamin/ttp-core@0.0.15
|
||||
## @tensamin/ttp-core@0.0.19
|
||||
|
||||
- License: UNKNOWN
|
||||
- Included files: LICENSE
|
||||
- Folder: `licenses/@tensamin_ttp-core@0.0.15`
|
||||
- Folder: `licenses/@tensamin_ttp-core@0.0.19`
|
||||
- Source package dir: `packages/ttp/node_modules/@tensamin/ttp-core`
|
||||
|
||||
## @tensamin/ui@0.0.30
|
||||
## @tensamin/ui@0.0.34
|
||||
|
||||
- License: UNKNOWN
|
||||
- Included files: none found
|
||||
- Folder: `licenses/@tensamin_ui@0.0.30`
|
||||
- Folder: `licenses/@tensamin_ui@0.0.34`
|
||||
- Source package dir: `apps/tauri/node_modules/@tensamin/ui`
|
||||
|
||||
## @types/node@25.6.0
|
||||
|
|
@ -449,6 +449,16 @@ Generated from bun.lock and installed packages in workspace node_modules folders
|
|||
- Folder: `licenses/tailwindcss@4.2.4`
|
||||
- Source package dir: `apps/web/node_modules/tailwindcss`
|
||||
|
||||
## tauri-plugin-app-events-api@0.2.0
|
||||
|
||||
- License: MIT
|
||||
- Homepage: https://github.com/wtto00/tauri-plugin-app-events#readme
|
||||
- Repository: git+https://github.com/wtto00/tauri-plugin-app-events.git
|
||||
- Description: A plugin for tauri@v2 to listen some events on iOS and Android.
|
||||
- Included files: LICENSE
|
||||
- Folder: `licenses/tauri-plugin-app-events-api@0.2.0`
|
||||
- Source package dir: `apps/web/node_modules/tauri-plugin-app-events-api`
|
||||
|
||||
## tw-animate-css@1.4.0
|
||||
|
||||
- License: MIT
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"specVersion": "1.5",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "2026-05-04T13:05:17.472Z",
|
||||
"timestamp": "2026-05-21T10:15:15.541Z",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "OpenAI",
|
||||
|
|
@ -580,15 +580,15 @@
|
|||
},
|
||||
{
|
||||
"type": "library",
|
||||
"bomRef": "pkg:npm/%40tensamin/ttp-core@0.0.15",
|
||||
"bomRef": "pkg:npm/%40tensamin/ttp-core@0.0.19",
|
||||
"name": "@tensamin/ttp-core",
|
||||
"version": "0.0.15",
|
||||
"purl": "pkg:npm/%40tensamin/ttp-core@0.0.15",
|
||||
"version": "0.0.19",
|
||||
"purl": "pkg:npm/%40tensamin/ttp-core@0.0.19",
|
||||
"externalReferences": [],
|
||||
"properties": [
|
||||
{
|
||||
"name": "local:licenseFolder",
|
||||
"value": "licenses/@tensamin_ttp-core@0.0.15"
|
||||
"value": "licenses/@tensamin_ttp-core@0.0.19"
|
||||
},
|
||||
{
|
||||
"name": "local:sourcePackageDir",
|
||||
|
|
@ -598,15 +598,15 @@
|
|||
},
|
||||
{
|
||||
"type": "library",
|
||||
"bomRef": "pkg:npm/%40tensamin/ui@0.0.30",
|
||||
"bomRef": "pkg:npm/%40tensamin/ui@0.0.34",
|
||||
"name": "@tensamin/ui",
|
||||
"version": "0.0.30",
|
||||
"purl": "pkg:npm/%40tensamin/ui@0.0.30",
|
||||
"version": "0.0.34",
|
||||
"purl": "pkg:npm/%40tensamin/ui@0.0.34",
|
||||
"externalReferences": [],
|
||||
"properties": [
|
||||
{
|
||||
"name": "local:licenseFolder",
|
||||
"value": "licenses/@tensamin_ui@0.0.30"
|
||||
"value": "licenses/@tensamin_ui@0.0.34"
|
||||
},
|
||||
{
|
||||
"name": "local:sourcePackageDir",
|
||||
|
|
@ -1562,6 +1562,41 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "library",
|
||||
"bomRef": "pkg:npm/tauri-plugin-app-events-api@0.2.0",
|
||||
"name": "tauri-plugin-app-events-api",
|
||||
"version": "0.2.0",
|
||||
"purl": "pkg:npm/tauri-plugin-app-events-api@0.2.0",
|
||||
"description": "A plugin for tauri@v2 to listen some events on iOS and Android.",
|
||||
"licenses": [
|
||||
{
|
||||
"license": {
|
||||
"id": "MIT"
|
||||
}
|
||||
}
|
||||
],
|
||||
"externalReferences": [
|
||||
{
|
||||
"type": "website",
|
||||
"url": "https://github.com/wtto00/tauri-plugin-app-events#readme"
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "git+https://github.com/wtto00/tauri-plugin-app-events.git"
|
||||
}
|
||||
],
|
||||
"properties": [
|
||||
{
|
||||
"name": "local:licenseFolder",
|
||||
"value": "licenses/tauri-plugin-app-events-api@0.2.0"
|
||||
},
|
||||
{
|
||||
"name": "local:sourcePackageDir",
|
||||
"value": "apps/web/node_modules/tauri-plugin-app-events-api"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "library",
|
||||
"bomRef": "pkg:npm/tw-animate-css@1.4.0",
|
||||
|
|
|
|||
21
licenses/tauri-plugin-app-events-api@0.2.0/LICENSE
Normal file
21
licenses/tauri-plugin-app-events-api@0.2.0/LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 简静凡
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"generatedAt": "2026-05-04T13:05:17.471Z",
|
||||
"packageCount": 53,
|
||||
"generatedAt": "2026-05-21T10:15:15.540Z",
|
||||
"packageCount": 54,
|
||||
"packages": [
|
||||
{
|
||||
"name": "@codemirror/commands",
|
||||
|
|
@ -227,7 +227,7 @@
|
|||
},
|
||||
{
|
||||
"name": "@tensamin/ttp-core",
|
||||
"version": "0.0.15",
|
||||
"version": "0.0.19",
|
||||
"license": "UNKNOWN",
|
||||
"homepage": null,
|
||||
"repository": null,
|
||||
|
|
@ -235,18 +235,18 @@
|
|||
"files": [
|
||||
"LICENSE"
|
||||
],
|
||||
"licenseFolder": "licenses/@tensamin_ttp-core@0.0.15",
|
||||
"licenseFolder": "licenses/@tensamin_ttp-core@0.0.19",
|
||||
"sourcePackageDir": "packages/ttp/node_modules/@tensamin/ttp-core"
|
||||
},
|
||||
{
|
||||
"name": "@tensamin/ui",
|
||||
"version": "0.0.30",
|
||||
"version": "0.0.34",
|
||||
"license": "UNKNOWN",
|
||||
"homepage": null,
|
||||
"repository": null,
|
||||
"description": null,
|
||||
"files": [],
|
||||
"licenseFolder": "licenses/@tensamin_ui@0.0.30",
|
||||
"licenseFolder": "licenses/@tensamin_ui@0.0.34",
|
||||
"sourcePackageDir": "apps/tauri/node_modules/@tensamin/ui"
|
||||
},
|
||||
{
|
||||
|
|
@ -614,6 +614,19 @@
|
|||
"licenseFolder": "licenses/tailwindcss@4.2.4",
|
||||
"sourcePackageDir": "apps/web/node_modules/tailwindcss"
|
||||
},
|
||||
{
|
||||
"name": "tauri-plugin-app-events-api",
|
||||
"version": "0.2.0",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/wtto00/tauri-plugin-app-events#readme",
|
||||
"repository": "git+https://github.com/wtto00/tauri-plugin-app-events.git",
|
||||
"description": "A plugin for tauri@v2 to listen some events on iOS and Android.",
|
||||
"files": [
|
||||
"LICENSE"
|
||||
],
|
||||
"licenseFolder": "licenses/tauri-plugin-app-events-api@0.2.0",
|
||||
"sourcePackageDir": "apps/web/node_modules/tauri-plugin-app-events-api"
|
||||
},
|
||||
{
|
||||
"name": "tw-animate-css",
|
||||
"version": "1.4.0",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "tensamin",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.2",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
|
|
|
|||
|
|
@ -115,7 +115,8 @@ export const ttp = {
|
|||
},
|
||||
get_user_data: {
|
||||
request: z.object({
|
||||
user_id: z.number(),
|
||||
user_id: z.number().optional(),
|
||||
username: z.string().optional(),
|
||||
}),
|
||||
response: user,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ interface SessionContextType {
|
|||
communities: Communities;
|
||||
calls: Calls;
|
||||
moveUserIdToTop: (userId: number) => void;
|
||||
insertContact: (userId: number) => void;
|
||||
}
|
||||
|
||||
const SessionContext = createContext<SessionContextType | undefined>(undefined);
|
||||
|
|
@ -69,9 +70,31 @@ export default function SessionProvider({ children }: { children: ReactNode }) {
|
|||
});
|
||||
};
|
||||
|
||||
const insertContact = (userId: number) => {
|
||||
setContacts((prevContacts) => {
|
||||
if (prevContacts.some((contact) => contact.user_id === userId)) {
|
||||
return prevContacts;
|
||||
}
|
||||
|
||||
const newUser = {
|
||||
user_id: userId,
|
||||
last_message_at: new Date().getTime(),
|
||||
messages: [],
|
||||
} satisfies Contacts[0];
|
||||
|
||||
return [newUser, ...prevContacts];
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<SessionContext.Provider
|
||||
value={{ contacts, communities, calls: freshCalls, moveUserIdToTop }}
|
||||
value={{
|
||||
contacts,
|
||||
communities,
|
||||
calls: freshCalls,
|
||||
moveUserIdToTop,
|
||||
insertContact,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SessionContext.Provider>
|
||||
|
|
|
|||
Loading…
Reference in a new issue