frontend client for gemstone. decentralised workplace app

feat: atcute, restructure project

serenity bc28e38c 16cdcb45

-21
app/[slug].tsx
···
-
import ChatComponentProfiled from "@/app/components/ChatComponentProfiled";
-
import { capitalise } from "@/app/lib/utils/strings";
-
import { useLocalSearchParams } from "expo-router";
-
import { Text, View } from "react-native";
-
-
export default function Index() {
-
const { slug } = useLocalSearchParams();
-
const name = typeof slug === "string" ? slug : slug[0];
-
return (
-
<View
-
style={{
-
flex: 1,
-
justifyContent: "center",
-
alignItems: "center",
-
}}
-
>
-
<Text>Hi, {capitalise(name)}!</Text>
-
<ChatComponentProfiled />
-
</View>
-
);
-
}
-5
app/_layout.tsx
···
-
import { Stack } from "expo-router";
-
-
export default function RootLayout() {
-
return <Stack />;
-
}
+1 -1
app/components/ChatComponent.tsx src/components/ChatComponent.tsx
···
-
import { useWebSocket } from "@/app/hooks/useWebSocket";
+
import { useWebSocket } from "@/hooks/useWebSocket";
import { useState } from "react";
import {
View,
+22 -3
app/components/ChatComponentProfiled.tsx src/components/ChatComponentProfiled.tsx
···
-
import { useWebSocket } from "@/app/hooks/useWebSocket";
+
import { Loading } from "@/components/Loading";
+
import { useWebSocket } from "@/hooks/useWebSocket";
+
import type { DidPlc } from "@/lib/types/atproto";
+
import { getBskyProfile } from "@/queries/get-profile";
+
import { useQuery } from "@tanstack/react-query";
import { useState } from "react";
import {
View,
···
ScrollView,
} from "react-native";
-
export default function ChatComponentProfiled() {
+
export default function ChatComponentProfiled({ did }: { did: DidPlc }) {
const [inputText, setInputText] = useState("");
const { messages, isConnected, sendMessage } = useWebSocket(
"ws://localhost:8080",
···
}
};
-
return (
+
const { data, isPending, isError, error } = useQuery({
+
queryKey: ["profile"],
+
queryFn: async () => {
+
return await getBskyProfile(did);
+
},
+
});
+
+
return isPending ? (
+
<Loading />
+
) : isError ? (
+
<View>
+
<Text>Something went wrong :(</Text>
+
<Text>{error.message}</Text>
+
</View>
+
) : (
<View style={styles.container}>
+
{data && <Text>Hi, {data.displayName ?? data.handle}!</Text>}
<View style={styles.header}>
<Text style={styles.status}>
{isConnected ? "🟢 Connected" : "🔴 Disconnected"}
+2 -2
app/hooks/useWebSocket.ts src/hooks/useWebSocket.ts
···
-
import type { ShardMessage } from "@/app/lib/types/messages";
+
import type { ShardMessage } from "@/lib/types/messages";
import {
validateHistoryMessage,
validateNewMessage,
validateWsMessageString,
validateWsMessageType,
-
} from "@/app/lib/validators";
+
} from "@/lib/validators";
import { useEffect, useRef, useState } from "react";
export function useWebSocket(url: string) {
+1 -1
app/index.tsx src/app/index.tsx
···
-
import ChatComponent from "@/app/components/ChatComponent";
+
import ChatComponent from "@/components/ChatComponent";
import { View } from "react-native";
export default function Index() {
app/lib/types/messages.ts src/lib/types/messages.ts
app/lib/utils/strings.ts src/lib/utils/strings.ts
+1 -5
app/lib/validators.ts src/lib/validators.ts
···
-
import {
-
historyMessageSchema,
-
shardMessageSchema,
-
websocketMessageSchema,
-
} from "@/app/lib/types/messages";
+
import { historyMessageSchema, shardMessageSchema, websocketMessageSchema } from "@/lib/types/messages";
import { z } from "zod";
export const validateWsMessageString = (data: unknown) => {
+5
package.json
···
"dev:expo": "expo start"
},
"dependencies": {
+
"@atcute/atproto": "^3.1.7",
+
"@atcute/bluesky": "^3.2.6",
+
"@atcute/client": "^4.0.4",
"@expo/vector-icons": "^15.0.2",
"@react-navigation/bottom-tabs": "^7.4.0",
"@react-navigation/elements": "^2.6.3",
"@react-navigation/native": "^7.1.8",
+
"@tanstack/react-query": "^5.90.3",
"expo": "~54.0.12",
"expo-constants": "~18.0.9",
"expo-font": "~14.0.8",
···
"react-native-reanimated": "~4.1.1",
"react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.16.0",
+
"react-native-svg": "^15.14.0",
"react-native-web": "~0.21.0",
"react-native-worklets": "0.5.1",
"zod": "^4.1.12"
+179
pnpm-lock.yaml
···
.:
dependencies:
+
'@atcute/atproto':
+
specifier: ^3.1.7
+
version: 3.1.7
+
'@atcute/bluesky':
+
specifier: ^3.2.6
+
version: 3.2.6
+
'@atcute/client':
+
specifier: ^4.0.4
+
version: 4.0.4
'@expo/vector-icons':
specifier: ^15.0.2
version: 15.0.2(expo-font@14.0.8(expo@54.0.12)(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
···
'@react-navigation/native':
specifier: ^7.1.8
version: 7.1.18(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
+
'@tanstack/react-query':
+
specifier: ^5.90.3
+
version: 5.90.3(react@19.1.0)
expo:
specifier: ~54.0.12
version: 54.0.12(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.10)(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
···
react-native-screens:
specifier: ~4.16.0
version: 4.16.0(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
+
react-native-svg:
+
specifier: ^15.14.0
+
version: 15.14.0(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
react-native-web:
specifier: ~0.21.0
version: 0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
···
graphql:
optional: true
+
'@atcute/atproto@3.1.7':
+
resolution: {integrity: sha512-3Ym8qaVZg2vf8qw0KO1aue39z/5oik5J+UDoSes1vr8ddw40UVLA5sV4bXSKmLnhzQHiLLgoVZXe4zaKfozPoQ==}
+
+
'@atcute/bluesky@3.2.6':
+
resolution: {integrity: sha512-jUSSTW5Th1vy0bWBazVHuhGQ3Xz4cX648WvLNpYDv7WPzlFzIWm6cnQCbUToQ+uK3K4WyVuuqYtZqqI0f4wWUQ==}
+
+
'@atcute/client@4.0.4':
+
resolution: {integrity: sha512-0vkYe6HcGAef8FS4dlGMqCCPG4I4Lve1R8Amk8UEviUVofiqlv1WGoeez9CJFL8G/7vhcgVV9rPTHLJEjZ4RdQ==}
+
+
'@atcute/identity@1.1.1':
+
resolution: {integrity: sha512-zax42n693VEhnC+5tndvO2KLDTMkHOz8UExwmklvJv7R9VujfEwiSWhcv6Jgwb3ellaG8wjiQ1lMOIjLLvwh0Q==}
+
+
'@atcute/lexicons@1.2.2':
+
resolution: {integrity: sha512-bgEhJq5Z70/0TbK5sx+tAkrR8FsCODNiL2gUEvS5PuJfPxmFmRYNWaMGehxSPaXWpU2+Oa9ckceHiYbrItDTkA==}
+
'@babel/code-frame@7.10.4':
resolution: {integrity: sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==}
···
'@babel/types@7.28.4':
resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==}
engines: {node: '>=6.9.0'}
+
+
'@badrap/valita@0.4.6':
+
resolution: {integrity: sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg==}
+
engines: {node: '>= 18'}
'@egjs/hammerjs@2.0.17':
resolution: {integrity: sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==}
···
'@sinonjs/fake-timers@10.3.0':
resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==}
+
+
'@standard-schema/spec@1.0.0':
+
resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
+
+
'@tanstack/query-core@5.90.3':
+
resolution: {integrity: sha512-HtPOnCwmx4dd35PfXU8jjkhwYrsHfuqgC8RCJIwWglmhIUIlzPP0ZcEkDAc+UtAWCiLm7T8rxeEfHZlz3hYMCA==}
+
+
'@tanstack/react-query@5.90.3':
+
resolution: {integrity: sha512-i/LRL6DtuhG6bjGzavIMIVuKKPWx2AnEBIsBfuMm3YoHne0a20nWmsatOCBcVSaT0/8/5YFjNkebHAPLVUSi0Q==}
+
peerDependencies:
+
react: ^18 || ^19
'@tybys/wasm-util@0.10.1':
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
···
resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==}
engines: {node: '>=0.6'}
+
boolbase@1.0.0:
+
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
+
bplist-creator@0.1.0:
resolution: {integrity: sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==}
···
css-in-js-utils@3.1.0:
resolution: {integrity: sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==}
+
css-select@5.2.2:
+
resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==}
+
+
css-tree@1.1.3:
+
resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==}
+
engines: {node: '>=8.0.0'}
+
+
css-what@6.2.2:
+
resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==}
+
engines: {node: '>= 6'}
+
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
···
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
engines: {node: '>=0.10.0'}
+
dom-serializer@2.0.0:
+
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
+
+
domelementtype@2.3.0:
+
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
+
+
domhandler@5.0.3:
+
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
+
engines: {node: '>= 4'}
+
+
domutils@3.2.2:
+
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
+
dotenv-expand@11.0.7:
resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==}
engines: {node: '>=12'}
···
encodeurl@2.0.0:
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
engines: {node: '>= 0.8'}
+
+
entities@4.5.0:
+
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
+
engines: {node: '>=0.12'}
env-editor@0.4.2:
resolution: {integrity: sha512-ObFo8v4rQJAE59M69QzwloxPZtd33TpYEIjtKD1rrFDcM1Gd7IkDxEBU+HriziN6HSHQnBJi8Dmy+JWkav5HKA==}
···
peerDependenciesMeta:
jiti:
optional: true
+
+
esm-env@1.2.2:
+
resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==}
espree@10.4.0:
resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
···
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
+
mdn-data@2.0.14:
+
resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
+
memoize-one@5.2.1:
resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==}
···
npm-package-arg@11.0.3:
resolution: {integrity: sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==}
engines: {node: ^16.14.0 || >=18.0.0}
+
+
nth-check@2.1.1:
+
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
nullthrows@1.1.1:
resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==}
···
react: '*'
react-native: '*'
+
react-native-svg@15.14.0:
+
resolution: {integrity: sha512-B3gYc7WztcOT4N54AtUutbe0Nuqqh/nkresY0fAXzUHYLsWuIu/yGiCCD3DKfAs6GLv5LFtWTu7N333Q+e3bkg==}
+
peerDependencies:
+
react: '*'
+
react-native: '*'
+
react-native-web@0.21.1:
resolution: {integrity: sha512-BeNsgwwe4AXUFPAoFU+DKjJ+CVQa3h54zYX77p7GVZrXiiNo3vl03WYDYVEy5R2J2HOPInXtQZB5gmj3vuzrKg==}
peerDependencies:
···
'@0no-co/graphql.web@1.2.0': {}
+
'@atcute/atproto@3.1.7':
+
dependencies:
+
'@atcute/lexicons': 1.2.2
+
+
'@atcute/bluesky@3.2.6':
+
dependencies:
+
'@atcute/atproto': 3.1.7
+
'@atcute/lexicons': 1.2.2
+
+
'@atcute/client@4.0.4':
+
dependencies:
+
'@atcute/identity': 1.1.1
+
'@atcute/lexicons': 1.2.2
+
+
'@atcute/identity@1.1.1':
+
dependencies:
+
'@atcute/lexicons': 1.2.2
+
'@badrap/valita': 0.4.6
+
+
'@atcute/lexicons@1.2.2':
+
dependencies:
+
'@standard-schema/spec': 1.0.0
+
esm-env: 1.2.2
+
'@babel/code-frame@7.10.4':
dependencies:
'@babel/highlight': 7.25.9
···
dependencies:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.27.1
+
+
'@badrap/valita@0.4.6': {}
'@egjs/hammerjs@2.0.17':
dependencies:
···
dependencies:
'@sinonjs/commons': 3.0.1
+
'@standard-schema/spec@1.0.0': {}
+
+
'@tanstack/query-core@5.90.3': {}
+
+
'@tanstack/react-query@5.90.3(react@19.1.0)':
+
dependencies:
+
'@tanstack/query-core': 5.90.3
+
react: 19.1.0
+
'@tybys/wasm-util@0.10.1':
dependencies:
tslib: 2.8.1
···
big-integer@1.6.52: {}
+
boolbase@1.0.0: {}
+
bplist-creator@0.1.0:
dependencies:
stream-buffers: 2.2.0
···
dependencies:
hyphenate-style-name: 1.1.0
+
css-select@5.2.2:
+
dependencies:
+
boolbase: 1.0.0
+
css-what: 6.2.2
+
domhandler: 5.0.3
+
domutils: 3.2.2
+
nth-check: 2.1.1
+
+
css-tree@1.1.3:
+
dependencies:
+
mdn-data: 2.0.14
+
source-map: 0.6.1
+
+
css-what@6.2.2: {}
+
csstype@3.1.3: {}
data-view-buffer@1.0.2:
···
dependencies:
esutils: 2.0.3
+
dom-serializer@2.0.0:
+
dependencies:
+
domelementtype: 2.3.0
+
domhandler: 5.0.3
+
entities: 4.5.0
+
+
domelementtype@2.3.0: {}
+
+
domhandler@5.0.3:
+
dependencies:
+
domelementtype: 2.3.0
+
+
domutils@3.2.2:
+
dependencies:
+
dom-serializer: 2.0.0
+
domelementtype: 2.3.0
+
domhandler: 5.0.3
+
dotenv-expand@11.0.7:
dependencies:
dotenv: 16.4.7
···
encodeurl@1.0.2: {}
encodeurl@2.0.0: {}
+
+
entities@4.5.0: {}
env-editor@0.4.2: {}
···
jiti: 2.6.1
transitivePeerDependencies:
- supports-color
+
+
esm-env@1.2.2: {}
espree@10.4.0:
dependencies:
···
math-intrinsics@1.1.0: {}
+
mdn-data@2.0.14: {}
+
memoize-one@5.2.1: {}
memoize-one@6.0.0: {}
···
semver: 7.7.3
validate-npm-package-name: 5.0.1
+
nth-check@2.1.1:
+
dependencies:
+
boolbase: 1.0.0
+
nullthrows@1.1.1: {}
ob1@0.83.1:
···
react-freeze: 1.0.4(react@19.1.0)
react-native: 0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0)
react-native-is-edge-to-edge: 1.2.1(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
+
warn-once: 0.1.1
+
+
react-native-svg@15.14.0(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
+
dependencies:
+
css-select: 5.2.2
+
css-tree: 1.1.3
+
react: 19.1.0
+
react-native: 0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0)
warn-once: 0.1.1
react-native-web@0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+36
src/app/[slug].tsx
···
+
import ChatComponentProfiled from "@/components/ChatComponentProfiled";
+
import { didPlcSchema } from "@/lib/types/atproto";
+
import { useLocalSearchParams } from "expo-router";
+
import { Text, View } from "react-native";
+
+
export default function Index() {
+
const { slug } = useLocalSearchParams();
+
const { success, error, data: did } = didPlcSchema.safeParse(slug);
+
if (!success) {
+
console.error("slug was not a did plc");
+
console.error(error);
+
return (
+
<View
+
style={{
+
flex: 1,
+
justifyContent: "center",
+
alignItems: "center",
+
}}
+
>
+
<Text>Provide a valid did:plc pls (no did web yet)</Text>
+
</View>
+
);
+
}
+
+
return (
+
<View
+
style={{
+
flex: 1,
+
justifyContent: "center",
+
alignItems: "center",
+
}}
+
>
+
<ChatComponentProfiled did={did} />
+
</View>
+
);
+
}
+12
src/app/_layout.tsx
···
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+
import { Stack } from "expo-router";
+
+
const queryClient = new QueryClient();
+
+
export default function RootLayout() {
+
return (
+
<QueryClientProvider client={queryClient}>
+
<Stack />
+
</QueryClientProvider>
+
);
+
}
+11
src/components/Loading.tsx
···
+
import { ActivityIndicator, View } from "react-native";
+
+
export const Loading = () => {
+
return (
+
<View>
+
<ActivityIndicator size="large" />
+
</View>
+
);
+
};
+
+
export default Loading;
+5
src/lib/types/atproto.ts
···
+
import { z } from "zod";
+
+
export const didPlcSchema = z.templateLiteral(["did:plc:", z.string()]);
+
+
export type DidPlc = z.infer<typeof didPlcSchema>;
+5
src/lib/utils/atproto/client.ts
···
+
import { Client, simpleFetchHandler } from "@atcute/client";
+
import type {} from "@atcute/bluesky";
+
+
const handler = simpleFetchHandler({ service: "https://public.api.bsky.app" });
+
export const client = new Client({ handler });
+36
src/queries/get-profile.ts
···
+
import type { DidPlc } from "@/lib/types/atproto";
+
import { client } from "@/lib/utils/atproto/client";
+
+
export const getBskyProfile = async (did: DidPlc) => {
+
const { ok, data } = await client.get("app.bsky.actor.getProfile", {
+
params: {
+
actor: did,
+
},
+
});
+
+
if (!ok) {
+
switch (data.error) {
+
case "InvalidRequest": {
+
console.error("There is no account at", did);
+
return;
+
}
+
case "AccountTakedown": {
+
console.error("Account of", did, "was taken down");
+
return;
+
}
+
case "AccountDeactivated": {
+
console.error("Account of", did, "was deactivated");
+
return;
+
}
+
default: {
+
console.error(
+
"Something went wrong fetching the profile of",
+
did,
+
);
+
return;
+
}
+
}
+
}
+
+
return data;
+
};
+15 -15
tsconfig.json
···
{
-
"extends": "expo/tsconfig.base",
-
"compilerOptions": {
-
"strict": true,
-
"paths": {
-
"@/*": [
-
"./*"
-
]
-
}
-
},
-
"include": [
-
"**/*.ts",
-
"**/*.tsx",
-
".expo/types/**/*.ts",
-
"expo-env.d.ts"
-
]
+
"extends": "expo/tsconfig.base",
+
"compilerOptions": {
+
"strict": true,
+
"paths": {
+
"@/*": [
+
"./src/*"
+
]
+
}
+
},
+
"include": [
+
"**/*.ts",
+
"**/*.tsx",
+
".expo/types/**/*.ts",
+
"expo-env.d.ts"
+
]
}