frontend client for gemstone. decentralised workplace app

Compare changes

Choose any two refs to compare.

Changed files
+109 -42
src
components
lib
hooks
utils
providers
+7 -5
src/components/Chat/index.tsx
···
export const Chat = ({ channelAtUri }: { channelAtUri: AtUri }) => {
const [inputText, setInputText] = useState("");
-
const { messages, sendMessageToChannel, isConnected } =
-
useChannel(channelAtUri);
const record = useChannelRecordByAtUriObject(channelAtUri);
const { semantic } = useCurrentPalette();
const { typography, atoms } = useFacet();
+
const channel = useChannel(channelAtUri);
+
const { isLoading } = useProfile();
+
+
if (!channel) return <></>;
+
+
const { messages, sendMessageToChannel, isConnected } = channel;
const handleSend = () => {
if (inputText.trim()) {
···
}
};
-
const { isLoading } = useProfile();
-
if (!record)
return (
<View>
<Text>
-
Something has gone wrong. Could not resolve channel record
+
Something has gone wrong.Could not resolve channel record
from given at:// URI.
</Text>
</View>
+17 -9
src/lib/hooks/useChannel.ts
···
const { sessionInfo, socket } = findChannelSession(channel);
useEffect(() => {
-
if (!sessionInfo)
-
throw new Error(
+
if (!sessionInfo) {
+
console.warn(
"Channel did not resolve to a valid sessionInfo object.",
);
-
if (!socket)
-
throw new Error(
+
return;
+
}
+
if (!socket) {
+
console.warn(
"Session info did not resolve to a valid websocket connection. This should not happen and is likely a bug. Check the sessions map object.",
);
+
return;
+
}
// attach handlers here
···
};
}, [socket, sessionInfo, channel]);
-
if (!oAuthSession) throw new Error("No OAuth session");
-
if (!sessionInfo)
-
throw new Error(
+
if (!oAuthSession) {console.warn("No OAuth session"); return }
+
if (!sessionInfo) {
+
console.warn(
"Channel did not resolve to a valid sessionInfo object.",
);
-
if (!socket)
-
throw new Error(
+
return;
+
}
+
if (!socket) {
+
console.warn(
"Session info did not resolve to a valid websocket connection. This should not happen and is likely a bug. Check the sessions map object.",
);
+
return;
+
}
const channelStringified = atUriToString(channel);
+1 -3
src/providers/authed/SessionsProvider.tsx
···
console.log("tried to find", channel);
if (!sessionInfo)
-
throw new Error(
-
"Provided channel at:// URI (object) could not be found in any existing lattice sessions",
-
);
+
return { sessionInfo: undefined, socket: undefined };
return { sessionInfo, socket: sessionsMap.get(sessionInfo) };
},
+1 -1
src/lib/utils/gmstn.ts
···
agent: Agent;
}): Promise<Result<undefined, string>> => {
const now = new Date();
-
const rkey = TID.create(now.getTime(), Math.random());
+
const rkey = TID.create(now.getTime() * 1_000, Math.random());
const record: Omit<SystemsGmstnDevelopmentChannelInvite, "$type"> = {
// @ts-expect-error we want to explicitly use the ISO string variant
+1 -1
src/lib/utils/atproto/oauth.web.ts
···
} from "@atproto/oauth-client-browser";
import type { ExpoOAuthClientOptions } from "@atproto/oauth-client-expo";
import { ExpoOAuthClient as PbcWebExpoOAuthClient } from "@atproto/oauth-client-expo";
-
import oAuthMetadata from "../../../../assets/oauth-client-metadata.json";
+
import oAuthMetadata from "../../../../public/oauth-client-metadata.json";
import { __DEV__loopbackOAuthMetadata } from "@/lib/consts";
// suuuuuch a hack holy shit
+82 -23
src/components/Auth/Login.web.tsx
···
+
import { GmstnLogoColor } from "@/components/icons/gmstn/GmstnLogoColor";
+
import { Text } from "@/components/primitives/Text";
+
import { useFacet } from "@/lib/facet";
+
import { lighten } from "@/lib/facet/src/lib/colors";
import { useOAuthSetter, useOAuthValue } from "@/providers/OAuthProvider";
+
import { useCurrentPalette } from "@/providers/ThemeProvider";
import { Agent } from "@atproto/api";
+
import { ArrowRight } from "lucide-react-native";
import { useState } from "react";
-
import { Button, StyleSheet, TextInput, View } from "react-native";
+
import { Pressable, TextInput, View } from "react-native";
export const Login = () => {
+
const { semantic } = useCurrentPalette();
+
const { atoms, typography } = useFacet();
const [atprotoHandle, setAtprotoHandle] = useState("");
const oAuth = useOAuthValue();
const setOAuth = useOAuthSetter();
···
};
return (
-
<View>
-
<TextInput
-
style={styles.input}
-
value={atprotoHandle}
-
onChangeText={setAtprotoHandle}
-
placeholder="alice.bsky.social"
-
onSubmitEditing={handleSubmit}
-
/>
-
<Button title="Log in with your PDS ->" onPress={handleSubmit} />
+
<View
+
style={{
+
flex: 1,
+
flexDirection: "column",
+
alignItems: "center",
+
justifyContent: "center",
+
gap: 16,
+
}}
+
>
+
<View style={{ alignItems: "center" }}>
+
<View style={{ padding: 8, paddingLeft: 12, paddingTop: 12 }}>
+
<GmstnLogoColor height={36} width={36} />
+
</View>
+
<Text
+
style={[
+
typography.sizes.xl,
+
typography.weights.byName.medium,
+
]}
+
>
+
Gemstone
+
</Text>
+
</View>
+
<View style={{ gap: 10 }}>
+
<TextInput
+
style={[{
+
flex: 1,
+
borderWidth: 1,
+
borderColor: semantic.border,
+
borderRadius: atoms.radii.lg,
+
paddingHorizontal: 14,
+
paddingVertical: 12,
+
marginRight: 8,
+
fontSize: 16,
+
color: semantic.text
+
}, typography.weights.byName.light]}
+
value={atprotoHandle}
+
onChangeText={setAtprotoHandle}
+
placeholder="alice.bsky.social"
+
onSubmitEditing={handleSubmit}
+
placeholderTextColor={semantic.textPlaceholder}
+
/>
+
<Pressable onPress={handleSubmit}>
+
{({ hovered }) => (
+
<View
+
style={{
+
backgroundColor: hovered
+
? lighten(semantic.primary, 7)
+
: semantic.primary,
+
flexDirection: "row",
+
gap: 4,
+
alignItems: "center",
+
justifyContent: "center",
+
paddingVertical: 10,
+
borderRadius: atoms.radii.lg,
+
}}
+
>
+
<Text
+
style={[
+
{ color: semantic.textInverse },
+
typography.weights.byName.normal,
+
]}
+
>
+
Log in with ATProto
+
</Text>
+
<ArrowRight
+
height={16}
+
width={16}
+
color={semantic.textInverse}
+
/>
+
</View>
+
)}
+
</Pressable>
+
</View>
</View>
);
};
-
-
const styles = StyleSheet.create({
-
input: {
-
flex: 1,
-
borderWidth: 1,
-
borderColor: "#ccc",
-
borderRadius: 8,
-
paddingHorizontal: 12,
-
paddingVertical: 8,
-
marginRight: 8,
-
fontSize: 16,
-
},
-
});