From 6ceac4039460734250084b1476db785f8921db52 Mon Sep 17 00:00:00 2001 From: reya <123083837+reyamir@users.noreply.github.com> Date: Mon, 29 Jul 2024 14:49:13 +0700 Subject: [PATCH] feat: update ui and add compose dialog --- package.json | 1 + pnpm-lock.yaml | 291 +++++++++++++++++++++++++ src-tauri/src/commands/account.rs | 14 ++ src-tauri/src/commands/chat.rs | 2 +- src-tauri/src/main.rs | 1 + src/commands.ts | 8 + src/commons.ts | 7 + src/components/user/avatar.tsx | 18 +- src/routes.gen.ts | 42 ++-- src/routes/$account.chats.$id.lazy.tsx | 288 ++++++++++++++++++++++++ src/routes/$account.chats.$id.tsx | 284 ------------------------ src/routes/$account.chats.lazy.tsx | 203 ++++++++++++++--- src/routes/$account.contacts.lazy.tsx | 63 ++++++ src/routes/$account.contacts.tsx | 14 ++ src/routes/contacts.lazy.tsx | 5 - tailwind.config.js | 17 +- 16 files changed, 913 insertions(+), 345 deletions(-) create mode 100644 src/routes/$account.chats.$id.lazy.tsx create mode 100644 src/routes/$account.contacts.lazy.tsx create mode 100644 src/routes/$account.contacts.tsx delete mode 100644 src/routes/contacts.lazy.tsx diff --git a/package.json b/package.json index 54a5219..4c4f6fc 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "@phosphor-icons/react": "^2.1.7", "@radix-ui/react-avatar": "^1.1.0", + "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-scroll-area": "^1.1.0", "@tanstack/query-sync-storage-persister": "^5.51.15", "@tanstack/react-query": "^5.51.15", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2ef7a25..8f7b4ab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@radix-ui/react-avatar': specifier: ^1.1.0 version: 1.1.0(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1) + '@radix-ui/react-dialog': + specifier: ^1.1.1 + version: 1.1.1(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1) '@radix-ui/react-scroll-area': specifier: ^1.1.0 version: 1.1.0(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1) @@ -521,6 +524,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-dialog@1.1.1': + resolution: {integrity: sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==} + 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 + '@radix-ui/react-direction@1.1.0': resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==} peerDependencies: @@ -530,6 +546,63 @@ packages: '@types/react': optional: true + '@radix-ui/react-dismissable-layer@1.1.0': + resolution: {integrity: sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==} + 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 + + '@radix-ui/react-focus-guards@1.1.0': + resolution: {integrity: sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.0': + resolution: {integrity: sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==} + 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 + + '@radix-ui/react-id@1.1.0': + resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-portal@1.1.1': + resolution: {integrity: sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==} + 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 + '@radix-ui/react-presence@1.1.0': resolution: {integrity: sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==} peerDependencies: @@ -587,6 +660,24 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-controllable-state@1.1.0': + resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.0': + resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-layout-effect@1.1.0': resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==} peerDependencies: @@ -906,6 +997,10 @@ packages: arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + aria-hidden@1.2.4: + resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} + engines: {node: '>=10'} + autoprefixer@10.4.19: resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==} engines: {node: ^10 || ^12 || >=14} @@ -1005,6 +1100,9 @@ packages: supports-color: optional: true + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -1066,6 +1164,10 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1330,6 +1432,36 @@ packages: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} + react-remove-scroll-bar@2.3.6: + resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.5.7: + resolution: {integrity: sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.1: + resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + react@19.0.0-rc-d025ddd3-20240722: resolution: {integrity: sha512-pZZ3zHponPdVhwgOoQoI7qoHh+Hn3GAVc+/g4LICRv/XZ0IEJPsPOehV/Iu8Ssl/DoruEO4EdeK6kTSmcaAo8A==} engines: {node: '>=0.10.0'} @@ -1463,6 +1595,9 @@ packages: typescript: optional: true + tslib@2.6.3: + resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + types-react-dom@19.0.0-rc.1: resolution: {integrity: sha512-VSLZJl8VXCD0fAWp7DUTFUDCcZ8DVXOQmjhJMD03odgeFmu14ZQJHCXeETm3BEAhJqfgJaFkLnGkQv88sRx0fQ==} @@ -1488,6 +1623,26 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + use-callback-ref@1.3.2: + resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.2: + resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + use-sync-external-store@1.2.2: resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} peerDependencies: @@ -1938,12 +2093,81 @@ snapshots: optionalDependencies: '@types/react': types-react@19.0.0-rc.1 + '@radix-ui/react-dialog@1.1.1(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1) + '@radix-ui/react-context': 1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1) + '@radix-ui/react-dismissable-layer': 1.1.0(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1) + '@radix-ui/react-focus-guards': 1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1) + '@radix-ui/react-focus-scope': 1.1.0(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1) + '@radix-ui/react-id': 1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1) + '@radix-ui/react-portal': 1.1.1(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1) + '@radix-ui/react-presence': 1.1.0(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1) + '@radix-ui/react-primitive': 2.0.0(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1) + '@radix-ui/react-slot': 1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1) + '@radix-ui/react-use-controllable-state': 1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1) + aria-hidden: 1.2.4 + react: 19.0.0-rc-d025ddd3-20240722 + react-dom: 19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722) + react-remove-scroll: 2.5.7(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1) + optionalDependencies: + '@types/react': types-react@19.0.0-rc.1 + '@types/react-dom': types-react-dom@19.0.0-rc.1 + '@radix-ui/react-direction@1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1)': dependencies: react: 19.0.0-rc-d025ddd3-20240722 optionalDependencies: '@types/react': types-react@19.0.0-rc.1 + '@radix-ui/react-dismissable-layer@1.1.0(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1) + '@radix-ui/react-primitive': 2.0.0(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1) + '@radix-ui/react-use-callback-ref': 1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1) + '@radix-ui/react-use-escape-keydown': 1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1) + react: 19.0.0-rc-d025ddd3-20240722 + react-dom: 19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722) + optionalDependencies: + '@types/react': types-react@19.0.0-rc.1 + '@types/react-dom': types-react-dom@19.0.0-rc.1 + + '@radix-ui/react-focus-guards@1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1)': + dependencies: + react: 19.0.0-rc-d025ddd3-20240722 + optionalDependencies: + '@types/react': types-react@19.0.0-rc.1 + + '@radix-ui/react-focus-scope@1.1.0(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1) + '@radix-ui/react-primitive': 2.0.0(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1) + '@radix-ui/react-use-callback-ref': 1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1) + react: 19.0.0-rc-d025ddd3-20240722 + react-dom: 19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722) + optionalDependencies: + '@types/react': types-react@19.0.0-rc.1 + '@types/react-dom': types-react-dom@19.0.0-rc.1 + + '@radix-ui/react-id@1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1) + react: 19.0.0-rc-d025ddd3-20240722 + optionalDependencies: + '@types/react': types-react@19.0.0-rc.1 + + '@radix-ui/react-portal@1.1.1(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1)': + dependencies: + '@radix-ui/react-primitive': 2.0.0(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1) + '@radix-ui/react-use-layout-effect': 1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1) + react: 19.0.0-rc-d025ddd3-20240722 + react-dom: 19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722) + optionalDependencies: + '@types/react': types-react@19.0.0-rc.1 + '@types/react-dom': types-react-dom@19.0.0-rc.1 + '@radix-ui/react-presence@1.1.0(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1) @@ -1993,6 +2217,20 @@ snapshots: optionalDependencies: '@types/react': types-react@19.0.0-rc.1 + '@radix-ui/react-use-controllable-state@1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1) + react: 19.0.0-rc-d025ddd3-20240722 + optionalDependencies: + '@types/react': types-react@19.0.0-rc.1 + + '@radix-ui/react-use-escape-keydown@1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1) + react: 19.0.0-rc-d025ddd3-20240722 + optionalDependencies: + '@types/react': types-react@19.0.0-rc.1 + '@radix-ui/react-use-layout-effect@1.1.0(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1)': dependencies: react: 19.0.0-rc-d025ddd3-20240722 @@ -2277,6 +2515,10 @@ snapshots: arg@5.0.2: {} + aria-hidden@1.2.4: + dependencies: + tslib: 2.6.3 + autoprefixer@10.4.19(postcss@8.4.40): dependencies: browserslist: 4.23.2 @@ -2386,6 +2628,8 @@ snapshots: dependencies: ms: 2.1.2 + detect-node-es@1.1.0: {} + didyoumean@1.2.2: {} dlv@1.1.3: {} @@ -2458,6 +2702,8 @@ snapshots: gensync@1.0.0-beta.2: {} + get-nonce@1.0.1: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -2671,6 +2917,34 @@ snapshots: react-refresh@0.14.2: {} + react-remove-scroll-bar@2.3.6(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1): + dependencies: + react: 19.0.0-rc-d025ddd3-20240722 + react-style-singleton: 2.2.1(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1) + tslib: 2.6.3 + optionalDependencies: + '@types/react': types-react@19.0.0-rc.1 + + react-remove-scroll@2.5.7(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1): + dependencies: + react: 19.0.0-rc-d025ddd3-20240722 + react-remove-scroll-bar: 2.3.6(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1) + react-style-singleton: 2.2.1(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1) + tslib: 2.6.3 + use-callback-ref: 1.3.2(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1) + use-sidecar: 1.1.2(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1) + optionalDependencies: + '@types/react': types-react@19.0.0-rc.1 + + react-style-singleton@2.2.1(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1): + dependencies: + get-nonce: 1.0.1 + invariant: 2.2.4 + react: 19.0.0-rc-d025ddd3-20240722 + tslib: 2.6.3 + optionalDependencies: + '@types/react': types-react@19.0.0-rc.1 + react@19.0.0-rc-d025ddd3-20240722: {} read-cache@1.0.0: @@ -2826,6 +3100,8 @@ snapshots: optionalDependencies: typescript: 5.5.4 + tslib@2.6.3: {} + types-react-dom@19.0.0-rc.1: dependencies: '@types/react': 18.3.3 @@ -2851,6 +3127,21 @@ snapshots: escalade: 3.1.2 picocolors: 1.0.1 + use-callback-ref@1.3.2(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1): + dependencies: + react: 19.0.0-rc-d025ddd3-20240722 + tslib: 2.6.3 + optionalDependencies: + '@types/react': types-react@19.0.0-rc.1 + + use-sidecar@1.1.2(react@19.0.0-rc-d025ddd3-20240722)(types-react@19.0.0-rc.1): + dependencies: + detect-node-es: 1.1.0 + react: 19.0.0-rc-d025ddd3-20240722 + tslib: 2.6.3 + optionalDependencies: + '@types/react': types-react@19.0.0-rc.1 + use-sync-external-store@1.2.2(react@19.0.0-rc-d025ddd3-20240722): dependencies: react: 19.0.0-rc-d025ddd3-20240722 diff --git a/src-tauri/src/commands/account.rs b/src-tauri/src/commands/account.rs index e2dbbad..68a391f 100644 --- a/src-tauri/src/commands/account.rs +++ b/src-tauri/src/commands/account.rs @@ -141,6 +141,15 @@ pub async fn connect_account(uri: &str, state: State<'_, Nostr>) -> Result) -> Result, ()> { + let contact_list = state.contact_list.lock().await; + let list = contact_list.clone().into_iter().map(|c| c.public_key.to_hex()).collect::>(); + + Ok(list) +} + #[tauri::command] #[specta::specta] pub async fn login( @@ -185,6 +194,11 @@ pub async fn login( } } + if let Ok(contacts) = client.get_contact_list(Some(Duration::from_secs(10))).await { + let mut contact_list = state.contact_list.lock().await; + *contact_list = contacts; + }; + let inbox = Filter::new().kind(Kind::Custom(10050)).author(public_key).limit(1); if let Ok(events) = client.get_events_of(vec![inbox], None).await { diff --git a/src-tauri/src/commands/chat.rs b/src-tauri/src/commands/chat.rs index 990ebe7..49766b9 100644 --- a/src-tauri/src/commands/chat.rs +++ b/src-tauri/src/commands/chat.rs @@ -54,7 +54,7 @@ pub async fn get_chat_messages(id: String, state: State<'_, Nostr>) -> Result { stream::iter(events) .filter_map(|ev| async move { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index ec0a5a8..128522b 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -27,6 +27,7 @@ fn main() { connect_account, get_accounts, get_metadata, + get_contact_list, get_chats, get_chat_messages, connect_inbox, diff --git a/src/commands.ts b/src/commands.ts index 1510885..c9c031c 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -47,6 +47,14 @@ try { else return { status: "error", error: e as any }; } }, +async getContactList() : Promise> { +try { + return { status: "ok", data: await TAURI_INVOKE("get_contact_list") }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, async getChats() : Promise> { try { return { status: "ok", data: await TAURI_INVOKE("get_chats") }; diff --git a/src/commons.ts b/src/commons.ts index e9de03c..ffb52d5 100644 --- a/src/commons.ts +++ b/src/commons.ts @@ -87,3 +87,10 @@ export function groupEventByDate(events: NostrEvent[]) { return groups; } + +export function isEmojiOnly(str: string) { + const stringToTest = str.replace(/ /g, ""); + const emojiRegex = + /^(?:(?:\p{RI}\p{RI}|\p{Emoji}(?:\p{Emoji_Modifier}|\u{FE0F}\u{20E3}?|[\u{E0020}-\u{E007E}]+\u{E007F})?(?:\u{200D}\p{Emoji}(?:\p{Emoji_Modifier}|\u{FE0F}\u{20E3}?|[\u{E0020}-\u{E007E}]+\u{E007F})?)*)|[\u{1f900}-\u{1f9ff}\u{2600}-\u{26ff}\u{2700}-\u{27bf}])+$/u; + return emojiRegex.test(stringToTest) && Number.isNaN(Number(stringToTest)); +} diff --git a/src/components/user/avatar.tsx b/src/components/user/avatar.tsx index 868d0c5..6d7eb16 100644 --- a/src/components/user/avatar.tsx +++ b/src/components/user/avatar.tsx @@ -24,18 +24,20 @@ export function UserAvatar({ className }: { className?: string }) { > {!user.isLoading ? ( <> - + {user.profile?.picture ? ( + + ) : null} {user.pubkey} diff --git a/src/routes.gen.ts b/src/routes.gen.ts index 679cb28..03679bd 100644 --- a/src/routes.gen.ts +++ b/src/routes.gen.ts @@ -14,6 +14,7 @@ import { createFileRoute } from '@tanstack/react-router' import { Route as rootRoute } from './routes/__root' import { Route as IndexImport } from './routes/index' +import { Route as AccountContactsImport } from './routes/$account.contacts' import { Route as AccountChatsIdImport } from './routes/$account.chats.$id' // Create Virtual Routes @@ -22,7 +23,6 @@ const NostrConnectLazyImport = createFileRoute('/nostr-connect')() const NewLazyImport = createFileRoute('/new')() const ImportKeyLazyImport = createFileRoute('/import-key')() const CreateAccountLazyImport = createFileRoute('/create-account')() -const ContactsLazyImport = createFileRoute('/contacts')() const AccountChatsLazyImport = createFileRoute('/$account/chats')() const AccountChatsNewLazyImport = createFileRoute('/$account/chats/new')() @@ -50,11 +50,6 @@ const CreateAccountLazyRoute = CreateAccountLazyImport.update({ import('./routes/create-account.lazy').then((d) => d.Route), ) -const ContactsLazyRoute = ContactsLazyImport.update({ - path: '/contacts', - getParentRoute: () => rootRoute, -} as any).lazy(() => import('./routes/contacts.lazy').then((d) => d.Route)) - const IndexRoute = IndexImport.update({ path: '/', getParentRoute: () => rootRoute, @@ -67,6 +62,13 @@ const AccountChatsLazyRoute = AccountChatsLazyImport.update({ import('./routes/$account.chats.lazy').then((d) => d.Route), ) +const AccountContactsRoute = AccountContactsImport.update({ + path: '/$account/contacts', + getParentRoute: () => rootRoute, +} as any).lazy(() => + import('./routes/$account.contacts.lazy').then((d) => d.Route), +) + const AccountChatsNewLazyRoute = AccountChatsNewLazyImport.update({ path: '/new', getParentRoute: () => AccountChatsLazyRoute, @@ -77,7 +79,9 @@ const AccountChatsNewLazyRoute = AccountChatsNewLazyImport.update({ const AccountChatsIdRoute = AccountChatsIdImport.update({ path: '/$id', getParentRoute: () => AccountChatsLazyRoute, -} as any) +} as any).lazy(() => + import('./routes/$account.chats.$id.lazy').then((d) => d.Route), +) // Populate the FileRoutesByPath interface @@ -90,13 +94,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexImport parentRoute: typeof rootRoute } - '/contacts': { - id: '/contacts' - path: '/contacts' - fullPath: '/contacts' - preLoaderRoute: typeof ContactsLazyImport - parentRoute: typeof rootRoute - } '/create-account': { id: '/create-account' path: '/create-account' @@ -125,6 +122,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof NostrConnectLazyImport parentRoute: typeof rootRoute } + '/$account/contacts': { + id: '/$account/contacts' + path: '/$account/contacts' + fullPath: '/$account/contacts' + preLoaderRoute: typeof AccountContactsImport + parentRoute: typeof rootRoute + } '/$account/chats': { id: '/$account/chats' path: '/$account/chats' @@ -153,11 +157,11 @@ declare module '@tanstack/react-router' { export const routeTree = rootRoute.addChildren({ IndexRoute, - ContactsLazyRoute, CreateAccountLazyRoute, ImportKeyLazyRoute, NewLazyRoute, NostrConnectLazyRoute, + AccountContactsRoute, AccountChatsLazyRoute: AccountChatsLazyRoute.addChildren({ AccountChatsIdRoute, AccountChatsNewLazyRoute, @@ -173,20 +177,17 @@ export const routeTree = rootRoute.addChildren({ "filePath": "__root.tsx", "children": [ "/", - "/contacts", "/create-account", "/import-key", "/new", "/nostr-connect", + "/$account/contacts", "/$account/chats" ] }, "/": { "filePath": "index.tsx" }, - "/contacts": { - "filePath": "contacts.lazy.tsx" - }, "/create-account": { "filePath": "create-account.lazy.tsx" }, @@ -199,6 +200,9 @@ export const routeTree = rootRoute.addChildren({ "/nostr-connect": { "filePath": "nostr-connect.lazy.tsx" }, + "/$account/contacts": { + "filePath": "$account.contacts.tsx" + }, "/$account/chats": { "filePath": "$account.chats.lazy.tsx", "children": [ diff --git a/src/routes/$account.chats.$id.lazy.tsx b/src/routes/$account.chats.$id.lazy.tsx new file mode 100644 index 0000000..9b4474c --- /dev/null +++ b/src/routes/$account.chats.$id.lazy.tsx @@ -0,0 +1,288 @@ +import { commands } from "@/commands"; +import { cn, getReceivers, groupEventByDate, time } from "@/commons"; +import { Spinner } from "@/components/spinner"; +import { User } from "@/components/user"; +import { ArrowUp, Paperclip } from "@phosphor-icons/react"; +import * as ScrollArea from "@radix-ui/react-scroll-area"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { createLazyFileRoute } from "@tanstack/react-router"; +import { listen } from "@tauri-apps/api/event"; +import { message } from "@tauri-apps/plugin-dialog"; +import type { NostrEvent } from "nostr-tools"; +import { useCallback, useRef, useState, useTransition } from "react"; +import { useEffect } from "react"; +import { Virtualizer } from "virtua"; + +type Payload = { + event: string; + sender: string; +}; + +export const Route = createLazyFileRoute("/$account/chats/$id")({ + component: Screen, + pendingComponent: Pending, +}); + +function Pending() { + return ( +
+ +
+ ); +} + +function Screen() { + return ( +
+
+ +
+
+ ); +} + +function Header() { + const { account, id } = Route.useParams(); + + return ( +
+
+
+ + + + + + + + + + +
+
+
+
+ + + + +
Connected
+
+
+
+ ); +} + +function List() { + const { account, id } = Route.useParams(); + const { isLoading, isError, data } = useQuery({ + queryKey: ["chats", id], + queryFn: async () => { + const res = await commands.getChatMessages(id); + + if (res.status === "ok") { + const raw = res.data; + const events: NostrEvent[] = raw.map((item) => JSON.parse(item)); + + return events; + } else { + throw new Error(res.error); + } + }, + select: (data) => { + const groups = groupEventByDate(data); + return Object.entries(groups).reverse(); + }, + refetchOnWindowFocus: false, + }); + + const queryClient = useQueryClient(); + const ref = useRef(null); + + const renderItem = useCallback( + (item: NostrEvent, idx: number) => { + const self = account === item.pubkey; + + return ( +
+
+
+ {item.content} +
+
+
+ + {time(item.created_at)} + +
+
+ ); + }, + [data], + ); + + useEffect(() => { + const unlisten = listen("event", async (data) => { + const event: NostrEvent = JSON.parse(data.payload.event); + const sender = data.payload.sender; + const receivers = getReceivers(event.tags); + const group = [account, id]; + + if (!group.includes(sender)) return; + if (!group.some((item) => receivers.includes(item))) return; + + await queryClient.setQueryData( + ["chats", id], + (prevEvents: NostrEvent[]) => { + if (!prevEvents) return prevEvents; + return [...prevEvents, event]; + }, + ); + }); + + return () => { + unlisten.then((f) => f()); + }; + }, []); + + return ( + + + + {isLoading ? ( + <> +
+
+
+
+
+
+
+
+
+
+
+
+ + ) : isError ? ( +
+
+ Cannot load message. Please try again later. +
+
+ ) : ( + data.map((item) => ( +
+
+ {item[0]} +
+
+ {item[1] + .sort((a, b) => a.created_at - b.created_at) + .map((item, idx) => renderItem(item, idx))} +
+
+ )) + )} + + + + + + + + ); +} + +function Form() { + const { id } = Route.useParams(); + const { inbox } = Route.useRouteContext(); + + const [newMessage, setNewMessage] = useState(""); + const [isPending, startTransition] = useTransition(); + + const submit = async () => { + startTransition(async () => { + if (!newMessage.length) return; + + const res = await commands.sendMessage(id, newMessage); + + if (res.status === "error") { + await message(res.error, { title: "Coop", kind: "error" }); + return; + } + + setNewMessage(""); + }); + }; + + return ( +
+ {!inbox.length ? ( +
+ This user doesn't have inbox relays. You cannot send messages to them. +
+ ) : ( +
+
+
+ +
+
+ setNewMessage(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") submit(); + }} + className="flex-1 h-9 rounded-full px-3.5 bg-transparent border border-neutral-200 dark:border-neutral-800 focus:outline-none focus:border-blue-500 placeholder:text-neutral-400 dark:placeholder:text-neutral-600" + /> + +
+ )} +
+ ); +} diff --git a/src/routes/$account.chats.$id.tsx b/src/routes/$account.chats.$id.tsx index fabb157..4ab3f62 100644 --- a/src/routes/$account.chats.$id.tsx +++ b/src/routes/$account.chats.$id.tsx @@ -1,293 +1,9 @@ -import { commands } from "@/commands"; -import { cn, getReceivers, groupEventByDate, time } from "@/commons"; -import { Spinner } from "@/components/spinner"; -import { User } from "@/components/user"; -import { ArrowUp, Paperclip } from "@phosphor-icons/react"; -import * as ScrollArea from "@radix-ui/react-scroll-area"; -import { useQuery, useQueryClient } from "@tanstack/react-query"; import { createFileRoute } from "@tanstack/react-router"; import { invoke } from "@tauri-apps/api/core"; -import { listen } from "@tauri-apps/api/event"; -import { message } from "@tauri-apps/plugin-dialog"; -import type { NostrEvent } from "nostr-tools"; -import { useCallback, useRef, useState, useTransition } from "react"; -import { useEffect } from "react"; -import { Virtualizer } from "virtua"; - -type Payload = { - event: string; - sender: string; -}; export const Route = createFileRoute("/$account/chats/$id")({ beforeLoad: async ({ params }) => { const inbox: string[] = await invoke("connect_inbox", { id: params.id }); return { inbox }; }, - component: Screen, - pendingComponent: Pending, }); - -function Pending() { - return ( -
- -
- ); -} - -function Screen() { - return ( -
-
- - -
- ); -} - -function Header() { - const { account, id } = Route.useParams(); - - return ( -
-
-
- - - - - - - - - - -
-
-
-
- - - - -
Connected
-
-
-
- ); -} - -function List() { - const { account, id } = Route.useParams(); - const { isLoading, isError, data } = useQuery({ - queryKey: ["chats", id], - queryFn: async () => { - const res = await commands.getChatMessages(id); - - if (res.status === "ok") { - const raw = res.data; - const events: NostrEvent[] = raw.map((item) => JSON.parse(item)); - - return events; - } else { - throw new Error(res.error); - } - }, - select: (data) => { - const groups = groupEventByDate(data); - return Object.entries(groups).reverse(); - }, - refetchOnWindowFocus: false, - }); - - const queryClient = useQueryClient(); - const ref = useRef(null); - - const renderItem = useCallback( - (item: NostrEvent, idx: number) => { - const self = account === item.pubkey; - - return ( -
-
-
- {item.content} -
-
-
- - {time(item.created_at)} - -
-
- ); - }, - [data], - ); - - useEffect(() => { - const unlisten = listen("event", async (data) => { - const event: NostrEvent = JSON.parse(data.payload.event); - const sender = data.payload.sender; - const receivers = getReceivers(event.tags); - const group = [account, id]; - - if (!group.includes(sender)) return; - if (!group.some((item) => receivers.includes(item))) return; - - await queryClient.setQueryData( - ["chats", id], - (prevEvents: NostrEvent[]) => { - if (!prevEvents) return prevEvents; - return [...prevEvents, event]; - }, - ); - }); - - return () => { - unlisten.then((f) => f()); - }; - }, []); - - return ( - - - - {isLoading ? ( - <> -
-
-
-
-
-
-
-
-
-
-
-
- - ) : isError ? ( -
-
- Cannot load message. Please try again later. -
-
- ) : ( - data.map((item) => ( -
-
- {item[0]} -
-
- {item[1] - .sort((a, b) => a.created_at - b.created_at) - .map((item, idx) => renderItem(item, idx))} -
-
- )) - )} - - - - - - - - ); -} - -function Form() { - const { id } = Route.useParams(); - const { inbox } = Route.useRouteContext(); - - const [newMessage, setNewMessage] = useState(""); - const [isPending, startTransition] = useTransition(); - - const submit = async () => { - startTransition(async () => { - if (!newMessage.length) return; - - const res = await commands.sendMessage(id, newMessage); - - if (res.status === "error") { - await message(res.error, { title: "Coop", kind: "error" }); - return; - } - - setNewMessage(""); - }); - }; - - return ( -
- {!inbox.length ? ( -
- This user doesn't have inbox relays. You cannot send messages to them. -
- ) : ( -
-
-
- -
-
- setNewMessage(e.target.value)} - onKeyDown={(e) => { - if (e.key === "Enter") submit(); - }} - className="flex-1 h-9 rounded-full px-3.5 bg-transparent border border-neutral-200 dark:border-neutral-800 focus:outline-none focus:border-blue-500" - /> - -
- )} -
- ); -} diff --git a/src/routes/$account.chats.lazy.tsx b/src/routes/$account.chats.lazy.tsx index b67154c..cf4d07a 100644 --- a/src/routes/$account.chats.lazy.tsx +++ b/src/routes/$account.chats.lazy.tsx @@ -1,14 +1,17 @@ import { commands } from "@/commands"; import { ago, cn } from "@/commons"; +import { Spinner } from "@/components/spinner"; import { User } from "@/components/user"; -import { DotsThree, Plus, UsersThree } from "@phosphor-icons/react"; +import { ArrowRight, CirclesFour, Plus, X } from "@phosphor-icons/react"; +import * as Dialog from "@radix-ui/react-dialog"; import * as ScrollArea from "@radix-ui/react-scroll-area"; import { useQuery } from "@tanstack/react-query"; import { Link, Outlet, createLazyFileRoute } from "@tanstack/react-router"; import { listen } from "@tauri-apps/api/event"; import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu"; +import { message } from "@tauri-apps/plugin-dialog"; import type { NostrEvent } from "nostr-tools"; -import { useCallback, useEffect } from "react"; +import { useCallback, useEffect, useState, useTransition } from "react"; type Payload = { event: string; @@ -28,7 +31,6 @@ function Screen() { >
-
@@ -38,24 +40,27 @@ function Screen() { } function Header() { + const { platform } = Route.useRouteContext(); + const { account } = Route.useParams(); + return (
-
+ +
- - - - + +
); @@ -83,7 +88,7 @@ function ChatList() { useEffect(() => { const unlisten = listen("synchronized", async () => { - await queryClient.refetchQueries({ queryKey: ["chats"] }) + await queryClient.refetchQueries({ queryKey: ["chats"] }); }); return () => { @@ -193,6 +198,146 @@ function ChatList() { ); } +function Compose() { + const [isOpen, setIsOpen] = useState(false); + const [target, setTarget] = useState(""); + const [newMessage, setNewMessage] = useState(""); + const [isPending, startTransition] = useTransition(); + + const { account } = Route.useParams(); + const { isLoading, data: contacts } = useQuery({ + queryKey: ["contacts", account], + queryFn: async () => { + const res = await commands.getContactList(); + + if (res.status === "ok") { + return res.data; + } else { + return []; + } + }, + refetchOnWindowFocus: false, + enabled: isOpen, + }); + + const navigate = Route.useNavigate(); + + const sendMessage = async () => { + startTransition(async () => { + if (!newMessage.length) return; + if (!target.length) return; + + const res = await commands.sendMessage(target, newMessage); + + if (res.status === "ok") { + navigate({ + to: "/$account/chats/$id", + params: { account, id: target }, + }); + } else { + await message(res.error, { title: "Coop", kind: "error" }); + return; + } + }); + }; + + return ( + + + + + + + +
+
+ Send to + + + +
+
+ To: + setTarget(e.target.value)} + disabled={isPending || isLoading} + className="flex-1 h-9 bg-transparent focus:outline-none placeholder:text-neutral-400 dark:placeholder:text-neutral-600" + /> +
+
+ Message: + setNewMessage(e.target.value)} + disabled={isPending || isLoading} + className="flex-1 h-9 bg-transparent focus:outline-none placeholder:text-neutral-400 dark:placeholder:text-neutral-600" + /> + +
+
+ + + {isLoading ? ( +
+ +
+ ) : !contacts?.length ? ( +
+

Contact is empty.

+
+ ) : ( + contacts?.map((contact) => ( + + )) + )} +
+ + + + +
+
+
+
+ ); +} + function CurrentUser() { const params = Route.useParams(); const navigate = Route.useNavigate(); @@ -201,6 +346,14 @@ function CurrentUser() { e.preventDefault(); const menuItems = await Promise.all([ + MenuItem.new({ + text: "Contacts", + action: () => + navigate({ + to: "/$account/contacts", + params: { account: params.account }, + }), + }), MenuItem.new({ text: "Settings", action: () => navigate({ to: "/" }), @@ -224,20 +377,16 @@ function CurrentUser() { }, []); return ( -
+ -
+ ); } diff --git a/src/routes/$account.contacts.lazy.tsx b/src/routes/$account.contacts.lazy.tsx new file mode 100644 index 0000000..2f8084c --- /dev/null +++ b/src/routes/$account.contacts.lazy.tsx @@ -0,0 +1,63 @@ +import { User } from "@/components/user"; +import { X } from "@phosphor-icons/react"; +import * as ScrollArea from "@radix-ui/react-scroll-area"; +import { Link, createLazyFileRoute } from "@tanstack/react-router"; + +export const Route = createLazyFileRoute("/$account/contacts")({ + component: Screen, +}); + +function Screen() { + const params = Route.useParams(); + const contacts = Route.useLoaderData(); + + return ( + +
+
+
Contact List
+
+ + + +
+
+ +
+ {contacts.map((contact) => ( + + + + + + + + + ))} +
+
+ + + + + + ); +} diff --git a/src/routes/$account.contacts.tsx b/src/routes/$account.contacts.tsx new file mode 100644 index 0000000..f9ac9fd --- /dev/null +++ b/src/routes/$account.contacts.tsx @@ -0,0 +1,14 @@ +import { commands } from "@/commands"; +import { createFileRoute } from "@tanstack/react-router"; + +export const Route = createFileRoute("/$account/contacts")({ + loader: async () => { + const res = await commands.getContactList(); + + if (res.status === "ok") { + return res.data; + } else { + return []; + } + }, +}); diff --git a/src/routes/contacts.lazy.tsx b/src/routes/contacts.lazy.tsx deleted file mode 100644 index 5204e88..0000000 --- a/src/routes/contacts.lazy.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { createLazyFileRoute } from '@tanstack/react-router' - -export const Route = createLazyFileRoute('/contacts')({ - component: () =>
Hello /contacts!
-}) \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js index f7de5e9..4501259 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -2,7 +2,22 @@ export default { content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], theme: { - extend: {}, + extend: { + keyframes: { + overlay: { + from: { opacity: '0' }, + to: { opacity: '1' }, + }, + content: { + from: { opacity: '0', transform: 'translate(-50%, -48%) scale(0.96)' }, + to: { opacity: '1', transform: 'translate(-50%, -50%) scale(1)' }, + }, + }, + animation: { + overlay: 'overlay 150ms cubic-bezier(0.16, 1, 0.3, 1)', + content: 'content 150ms cubic-bezier(0.16, 1, 0.3, 1)', + }, + }, }, plugins: [], };