@planningcenter/chat-react-native 1.3.0-rc.2 → 1.3.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,29 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.3.0-rc.4](https://github.com/planningcenter/chat-js/compare/v1.2.0...v1.3.0-rc.4) (2025-02-07)
7
+
8
+
9
+ ### Features
10
+
11
+ * add auth to the example app ([#50](https://github.com/planningcenter/chat-js/issues/50)) ([5e0ae00](https://github.com/planningcenter/chat-js/commit/5e0ae008345743f6bba388e982a3ad768265971a))
12
+ * create higher fidelity components ([#54](https://github.com/planningcenter/chat-js/issues/54)) ([367b707](https://github.com/planningcenter/chat-js/commit/367b7076b6d3a81ede7852248e98a199adea9978))
13
+ * drop stream and set global dependencies ([#49](https://github.com/planningcenter/chat-js/issues/49)) ([1214b9f](https://github.com/planningcenter/chat-js/commit/1214b9fbc17f4ab67a1de1d39954604a28268f35))
14
+ * **StreamChat:** export core package ([#41](https://github.com/planningcenter/chat-js/issues/41)) ([e9afde0](https://github.com/planningcenter/chat-js/commit/e9afde0e2a11d9c7e13b84f8eee8a6cfa7aff795)), closes [#45](https://github.com/planningcenter/chat-js/issues/45)
15
+
16
+
17
+
18
+ ## [1.3.0-rc.3](https://github.com/planningcenter/chat-js/compare/v1.2.0...v1.3.0-rc.3) (2025-02-06)
19
+
20
+
21
+ ### Features
22
+
23
+ * add auth to the example app ([#50](https://github.com/planningcenter/chat-js/issues/50)) ([5e0ae00](https://github.com/planningcenter/chat-js/commit/5e0ae008345743f6bba388e982a3ad768265971a))
24
+ * drop stream and set global dependencies ([#49](https://github.com/planningcenter/chat-js/issues/49)) ([1214b9f](https://github.com/planningcenter/chat-js/commit/1214b9fbc17f4ab67a1de1d39954604a28268f35))
25
+ * **StreamChat:** export core package ([#41](https://github.com/planningcenter/chat-js/issues/41)) ([e9afde0](https://github.com/planningcenter/chat-js/commit/e9afde0e2a11d9c7e13b84f8eee8a6cfa7aff795)), closes [#45](https://github.com/planningcenter/chat-js/issues/45)
26
+
27
+
28
+
6
29
  ## [1.3.0-rc.2](https://github.com/planningcenter/chat-js/compare/v1.2.0...v1.3.0-rc.2) (2025-02-06)
7
30
 
8
31
 
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ export declare function Conversations(): React.JSX.Element;
3
+ //# sourceMappingURL=conversations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversations.d.ts","sourceRoot":"","sources":["../../src/components/conversations.tsx"],"names":[],"mappings":"AACA,OAAO,KAA+B,MAAM,OAAO,CAAA;AAYnD,wBAAgB,aAAa,sBAQ5B"}
@@ -0,0 +1,49 @@
1
+ import { useSuspenseQuery } from '@tanstack/react-query';
2
+ import React, { Suspense, useContext } from 'react';
3
+ import { FlatList, StyleSheet, Text } from 'react-native';
4
+ import { ChatContext } from '../context/chat_context';
5
+ import ErrorBoundary from './error_boundary';
6
+ export function Conversations() {
7
+ return (<ErrorBoundary>
8
+ <Suspense fallback={<></>}>
9
+ <Loaded />
10
+ </Suspense>
11
+ </ErrorBoundary>);
12
+ }
13
+ const Loaded = () => {
14
+ const { token, onTokenExpired } = useContext(ChatContext);
15
+ const styles = useStyles();
16
+ const { data: conversations } = useSuspenseQuery({
17
+ queryKey: ['conversations', token],
18
+ queryFn: () =>
19
+ // TODO: replace with an api client
20
+ fetch('https://api.planningcenteronline.com/chat/v2/me/conversations', {
21
+ headers: {
22
+ Authorization: `Bearer ${token?.access_token}`,
23
+ },
24
+ })
25
+ .then(validateResponse)
26
+ .then(response => response.json())
27
+ .catch(error => {
28
+ if (error.message === 'Token expired') {
29
+ onTokenExpired();
30
+ }
31
+ return null;
32
+ }),
33
+ });
34
+ return (<FlatList data={conversations?.data} ListEmptyComponent={<Text>No conversations</Text>} contentContainerStyle={styles.container} ListHeaderComponent={<Text style={styles.foo}>Conversations</Text>} renderItem={({ item }) => <Text>{item.attributes.title}</Text>}/>);
35
+ };
36
+ const useStyles = () => {
37
+ return StyleSheet.create({
38
+ container: { columnGap: 16 },
39
+ foo: { fontSize: 24 },
40
+ });
41
+ };
42
+ const validateResponse = (response) => {
43
+ const isExpired = response.status === 401;
44
+ if (isExpired) {
45
+ throw new Error('Token expired');
46
+ }
47
+ return response;
48
+ };
49
+ //# sourceMappingURL=conversations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversations.js","sourceRoot":"","sources":["../../src/components/conversations.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,KAAK,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AACnD,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAErD,OAAO,aAAa,MAAM,kBAAkB,CAAA;AAQ5C,MAAM,UAAU,aAAa;IAC3B,OAAO,CACL,CAAC,aAAa,CACZ;MAAA,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CACxB;QAAA,CAAC,MAAM,CAAC,AAAD,EACT;MAAA,EAAE,QAAQ,CACZ;IAAA,EAAE,aAAa,CAAC,CACjB,CAAA;AACH,CAAC;AAED,MAAM,MAAM,GAAG,GAAG,EAAE;IAClB,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,GAAG,UAAU,CAAC,WAAW,CAAC,CAAA;IACzD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,gBAAgB,CAAwB;QACtE,QAAQ,EAAE,CAAC,eAAe,EAAE,KAAK,CAAC;QAClC,OAAO,EAAE,GAAG,EAAE;QACZ,mCAAmC;QACnC,KAAK,CAAC,+DAA+D,EAAE;YACrE,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,KAAK,EAAE,YAAY,EAAE;aAC/C;SACF,CAAC;aACC,IAAI,CAAC,gBAAgB,CAAC;aACtB,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;aACjC,KAAK,CAAC,KAAK,CAAC,EAAE;YACb,IAAI,KAAK,CAAC,OAAO,KAAK,eAAe,EAAE,CAAC;gBACtC,cAAc,EAAE,CAAA;YAClB,CAAC;YACD,OAAO,IAAI,CAAA;QACb,CAAC,CAAC;KACP,CAAC,CAAA;IAEF,OAAO,CACL,CAAC,QAAQ,CACP,IAAI,CAAC,CAAC,aAAa,EAAE,IAAI,CAAC,CAC1B,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC,CAClD,qBAAqB,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CACxC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CACnE,UAAU,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC,EAC/D,CACH,CAAA;AACH,CAAC,CAAA;AAED,MAAM,SAAS,GAAG,GAAG,EAAE;IACrB,OAAO,UAAU,CAAC,MAAM,CAAC;QACvB,SAAS,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE;QAC5B,GAAG,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE;KACtB,CAAC,CAAA;AACJ,CAAC,CAAA;AAED,MAAM,gBAAgB,GAAG,CAAC,QAAkB,EAAE,EAAE;IAC9C,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,KAAK,GAAG,CAAA;IACzC,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAA;IAClC,CAAC;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC,CAAA","sourcesContent":["import { useSuspenseQuery } from '@tanstack/react-query'\nimport React, { Suspense, useContext } from 'react'\nimport { FlatList, StyleSheet, Text } from 'react-native'\nimport { ChatContext } from '../context/chat_context'\nimport { ConversationRecord } from '../types'\nimport ErrorBoundary from './error_boundary'\n\ntype ConversationsResponse = {\n data: ConversationRecord[]\n links: Record<string, string>\n meta: Record<string, string>\n}\n\nexport function Conversations() {\n return (\n <ErrorBoundary>\n <Suspense fallback={<></>}>\n <Loaded />\n </Suspense>\n </ErrorBoundary>\n )\n}\n\nconst Loaded = () => {\n const { token, onTokenExpired } = useContext(ChatContext)\n const styles = useStyles()\n const { data: conversations } = useSuspenseQuery<ConversationsResponse>({\n queryKey: ['conversations', token],\n queryFn: () =>\n // TODO: replace with an api client\n fetch('https://api.planningcenteronline.com/chat/v2/me/conversations', {\n headers: {\n Authorization: `Bearer ${token?.access_token}`,\n },\n })\n .then(validateResponse)\n .then(response => response.json())\n .catch(error => {\n if (error.message === 'Token expired') {\n onTokenExpired()\n }\n return null\n }),\n })\n\n return (\n <FlatList\n data={conversations?.data}\n ListEmptyComponent={<Text>No conversations</Text>}\n contentContainerStyle={styles.container}\n ListHeaderComponent={<Text style={styles.foo}>Conversations</Text>}\n renderItem={({ item }) => <Text>{item.attributes.title}</Text>}\n />\n )\n}\n\nconst useStyles = () => {\n return StyleSheet.create({\n container: { columnGap: 16 },\n foo: { fontSize: 24 },\n })\n}\n\nconst validateResponse = (response: Response) => {\n const isExpired = response.status === 401\n if (isExpired) {\n throw new Error('Token expired')\n }\n\n return response\n}\n"]}
@@ -0,0 +1,12 @@
1
+ import React, { PropsWithChildren } from 'react';
2
+ declare class ErrorBoundary extends React.Component<PropsWithChildren<{}>> {
3
+ state: {
4
+ error: null;
5
+ unsubscriber: () => void;
6
+ };
7
+ componentDidCatch(error: any): void;
8
+ handleError(error: any): void;
9
+ render(): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | React.JSX.Element | null | undefined;
10
+ }
11
+ export default ErrorBoundary;
12
+ //# sourceMappingURL=error_boundary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error_boundary.d.ts","sourceRoot":"","sources":["../../src/components/error_boundary.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAA;AAGhD,cAAM,aAAc,SAAQ,KAAK,CAAC,SAAS,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC;IAChE,KAAK;;;MAGJ;IAED,iBAAiB,CAAC,KAAK,EAAE,GAAG;IAI5B,WAAW,CAAC,KAAK,EAAE,GAAG;IAItB,MAAM;CAOP;AAED,eAAe,aAAa,CAAA"}
@@ -0,0 +1,24 @@
1
+ import React from 'react';
2
+ import { Text } from 'react-native';
3
+ class ErrorBoundary extends React.Component {
4
+ state = {
5
+ error: null,
6
+ unsubscriber: () => { },
7
+ };
8
+ componentDidCatch(error) {
9
+ this.handleError(error);
10
+ }
11
+ handleError(error) {
12
+ this.setState({ error });
13
+ }
14
+ render() {
15
+ if (this.state.error) {
16
+ return <Text>{JSON.stringify(this.state.error, null, 2)}</Text>;
17
+ }
18
+ else {
19
+ return this.props.children;
20
+ }
21
+ }
22
+ }
23
+ export default ErrorBoundary;
24
+ //# sourceMappingURL=error_boundary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error_boundary.js","sourceRoot":"","sources":["../../src/components/error_boundary.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAA;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AAEnC,MAAM,aAAc,SAAQ,KAAK,CAAC,SAAgC;IAChE,KAAK,GAAG;QACN,KAAK,EAAE,IAAI;QACX,YAAY,EAAE,GAAG,EAAE,GAAE,CAAC;KACvB,CAAA;IAED,iBAAiB,CAAC,KAAU;QAC1B,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;IACzB,CAAC;IAED,WAAW,CAAC,KAAU;QACpB,IAAI,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;IAC1B,CAAC;IAED,MAAM;QACJ,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YACrB,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAA;QACjE,CAAC;aAAM,CAAC;YACN,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAA;QAC5B,CAAC;IACH,CAAC;CACF;AAED,eAAe,aAAa,CAAA","sourcesContent":["import React, { PropsWithChildren } from 'react'\nimport { Text } from 'react-native'\n\nclass ErrorBoundary extends React.Component<PropsWithChildren<{}>> {\n state = {\n error: null,\n unsubscriber: () => {},\n }\n\n componentDidCatch(error: any) {\n this.handleError(error)\n }\n\n handleError(error: any) {\n this.setState({ error })\n }\n\n render() {\n if (this.state.error) {\n return <Text>{JSON.stringify(this.state.error, null, 2)}</Text>\n } else {\n return this.props.children\n }\n }\n}\n\nexport default ErrorBoundary\n"]}
@@ -0,0 +1,9 @@
1
+ import { OauthToken } from '../types';
2
+ type ContextValue = {
3
+ token: OauthToken | null;
4
+ onTokenExpired: () => void;
5
+ theme: any;
6
+ };
7
+ export declare const ChatContext: import("react").Context<ContextValue>;
8
+ export {};
9
+ //# sourceMappingURL=chat_context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat_context.d.ts","sourceRoot":"","sources":["../../src/context/chat_context.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAErC,KAAK,YAAY,GAAG;IAClB,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;IACxB,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B,KAAK,EAAE,GAAG,CAAA;CACX,CAAA;AAED,eAAO,MAAM,WAAW,uCAItB,CAAA"}
@@ -0,0 +1,7 @@
1
+ import { createContext } from 'react';
2
+ export const ChatContext = createContext({
3
+ theme: null,
4
+ token: null,
5
+ onTokenExpired: () => { },
6
+ });
7
+ //# sourceMappingURL=chat_context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat_context.js","sourceRoot":"","sources":["../../src/context/chat_context.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AASrC,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAe;IACrD,KAAK,EAAE,IAAI;IACX,KAAK,EAAE,IAAI;IACX,cAAc,EAAE,GAAG,EAAE,GAAE,CAAC;CACzB,CAAC,CAAA","sourcesContent":["import { createContext } from 'react'\nimport { OauthToken } from '../types'\n\ntype ContextValue = {\n token: OauthToken | null\n onTokenExpired: () => void\n theme: any\n}\n\nexport const ChatContext = createContext<ContextValue>({\n theme: null,\n token: null,\n onTokenExpired: () => {},\n})\n"]}
package/build/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
- import React from 'react';
2
- export declare function PCOChat({ token, onTokenExpired, }: {
3
- token?: OathToken;
4
- onTokenExpired: () => void;
5
- }): React.JSX.Element;
1
+ import { Conversations } from './components/conversations';
2
+ import { ChatContext } from './context/chat_context';
3
+ import { OauthToken } from './types';
4
+ export { ChatContext, Conversations, OauthToken as OathToken };
6
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAGlD,wBAAgB,OAAO,CAAC,EACtB,KAAK,EACL,cAAc,GACf,EAAE;IACD,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,cAAc,EAAE,MAAM,IAAI,CAAA;CAC3B,qBAiCA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAEpC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,UAAU,IAAI,SAAS,EAAE,CAAA"}
package/build/index.js CHANGED
@@ -1,39 +1,4 @@
1
- import React, { useEffect, useState } from 'react';
2
- import { FlatList, StyleSheet, Text } from 'react-native';
3
- export function PCOChat({ token, onTokenExpired, }) {
4
- const [conversations, setConversations] = useState(null);
5
- const styles = useStyles();
6
- useEffect(() => {
7
- if (!token) {
8
- return;
9
- }
10
- fetch('https://api.planningcenteronline.com/chat/v2/me/conversations', {
11
- headers: {
12
- Authorization: `Bearer ${token.access_token}`,
13
- },
14
- })
15
- .then(validateResponse)
16
- .then(response => response.json())
17
- .then(setConversations)
18
- .catch(error => {
19
- if (error.message === 'Token expired') {
20
- onTokenExpired();
21
- }
22
- });
23
- }, [onTokenExpired, token]);
24
- return (<FlatList data={conversations?.data} ListEmptyComponent={<Text>No conversations</Text>} contentContainerStyle={styles.container} ListHeaderComponent={<Text style={styles.foo}>Conversations</Text>} renderItem={({ item }) => <Text>{item.attributes.title}</Text>}/>);
25
- }
26
- const useStyles = () => {
27
- return StyleSheet.create({
28
- container: { columnGap: 16 },
29
- foo: { fontSize: 24 },
30
- });
31
- };
32
- const validateResponse = (response) => {
33
- const isExpired = response.status === 401;
34
- if (isExpired) {
35
- throw new Error('Token expired');
36
- }
37
- return response;
38
- };
1
+ import { Conversations } from './components/conversations';
2
+ import { ChatContext } from './context/chat_context';
3
+ export { ChatContext, Conversations };
39
4
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAClD,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AAEzD,MAAM,UAAU,OAAO,CAAC,EACtB,KAAK,EACL,cAAc,GAIf;IACC,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAM,IAAI,CAAC,CAAA;IAC7D,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAE1B,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAM;QACR,CAAC;QAED,KAAK,CAAC,+DAA+D,EAAE;YACrE,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,KAAK,CAAC,YAAY,EAAE;aAC9C;SACF,CAAC;aACC,IAAI,CAAC,gBAAgB,CAAC;aACtB,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;aACjC,IAAI,CAAC,gBAAgB,CAAC;aACtB,KAAK,CAAC,KAAK,CAAC,EAAE;YACb,IAAI,KAAK,CAAC,OAAO,KAAK,eAAe,EAAE,CAAC;gBACtC,cAAc,EAAE,CAAA;YAClB,CAAC;QACH,CAAC,CAAC,CAAA;IACN,CAAC,EAAE,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC,CAAA;IAE3B,OAAO,CACL,CAAC,QAAQ,CACP,IAAI,CAAC,CAAC,aAAa,EAAE,IAAI,CAAC,CAC1B,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC,CAClD,qBAAqB,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CACxC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CACnE,UAAU,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC,EAC/D,CACH,CAAA;AACH,CAAC;AAED,MAAM,SAAS,GAAG,GAAG,EAAE;IACrB,OAAO,UAAU,CAAC,MAAM,CAAC;QACvB,SAAS,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE;QAC5B,GAAG,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE;KACtB,CAAC,CAAA;AACJ,CAAC,CAAA;AAED,MAAM,gBAAgB,GAAG,CAAC,QAAkB,EAAE,EAAE;IAC9C,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,KAAK,GAAG,CAAA;IACzC,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAA;IAClC,CAAC;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC,CAAA","sourcesContent":["import React, { useEffect, useState } from 'react'\nimport { FlatList, StyleSheet, Text } from 'react-native'\n\nexport function PCOChat({\n token,\n onTokenExpired,\n}: {\n token?: OathToken\n onTokenExpired: () => void\n}) {\n const [conversations, setConversations] = useState<any>(null)\n const styles = useStyles()\n\n useEffect(() => {\n if (!token) {\n return\n }\n\n fetch('https://api.planningcenteronline.com/chat/v2/me/conversations', {\n headers: {\n Authorization: `Bearer ${token.access_token}`,\n },\n })\n .then(validateResponse)\n .then(response => response.json())\n .then(setConversations)\n .catch(error => {\n if (error.message === 'Token expired') {\n onTokenExpired()\n }\n })\n }, [onTokenExpired, token])\n\n return (\n <FlatList\n data={conversations?.data}\n ListEmptyComponent={<Text>No conversations</Text>}\n contentContainerStyle={styles.container}\n ListHeaderComponent={<Text style={styles.foo}>Conversations</Text>}\n renderItem={({ item }) => <Text>{item.attributes.title}</Text>}\n />\n )\n}\n\nconst useStyles = () => {\n return StyleSheet.create({\n container: { columnGap: 16 },\n foo: { fontSize: 24 },\n })\n}\n\nconst validateResponse = (response: Response) => {\n const isExpired = response.status === 401\n if (isExpired) {\n throw new Error('Token expired')\n }\n\n return response\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAGpD,OAAO,EAAE,WAAW,EAAE,aAAa,EAA2B,CAAA","sourcesContent":["import { Conversations } from './components/conversations'\nimport { ChatContext } from './context/chat_context'\nimport { OauthToken } from './types'\n\nexport { ChatContext, Conversations, OauthToken as OathToken }\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/chat-react-native",
3
- "version": "1.3.0-rc.2",
3
+ "version": "1.3.0-rc.4",
4
4
  "description": "",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -13,15 +13,17 @@
13
13
  "prepublishOnly": "expo-module prepublishOnly"
14
14
  },
15
15
  "peerDependencies": {
16
+ "@tanstack/react-query": "^5.0.0",
16
17
  "react": "*",
17
18
  "react-native": "*"
18
19
  },
19
20
  "devDependencies": {
20
21
  "@react-native/eslint-config": "^0.77.0",
22
+ "@tanstack/react-query": "^5.66.0",
21
23
  "@typescript-eslint/parser": "^8.23.0",
22
24
  "expo-module-scripts": "^3.4.0",
23
25
  "prettier": "^3.4.2",
24
26
  "react-native": "0.74.5"
25
27
  },
26
- "gitHead": "01657a32952a72a2a82786e1de30edfc0f52b4f3"
28
+ "gitHead": "75d05da3bdb2dc2509914389d319df9b2868042f"
27
29
  }
@@ -0,0 +1,5 @@
1
+ describe('Chat', () => {
2
+ it('should do maths', () => {
3
+ expect(1 + 1).toBe(2)
4
+ })
5
+ })
@@ -0,0 +1,71 @@
1
+ import { useSuspenseQuery } from '@tanstack/react-query'
2
+ import React, { Suspense, useContext } from 'react'
3
+ import { FlatList, StyleSheet, Text } from 'react-native'
4
+ import { ChatContext } from '../context/chat_context'
5
+ import { ConversationRecord } from '../types'
6
+ import ErrorBoundary from './error_boundary'
7
+
8
+ type ConversationsResponse = {
9
+ data: ConversationRecord[]
10
+ links: Record<string, string>
11
+ meta: Record<string, string>
12
+ }
13
+
14
+ export function Conversations() {
15
+ return (
16
+ <ErrorBoundary>
17
+ <Suspense fallback={<></>}>
18
+ <Loaded />
19
+ </Suspense>
20
+ </ErrorBoundary>
21
+ )
22
+ }
23
+
24
+ const Loaded = () => {
25
+ const { token, onTokenExpired } = useContext(ChatContext)
26
+ const styles = useStyles()
27
+ const { data: conversations } = useSuspenseQuery<ConversationsResponse>({
28
+ queryKey: ['conversations', token],
29
+ queryFn: () =>
30
+ // TODO: replace with an api client
31
+ fetch('https://api.planningcenteronline.com/chat/v2/me/conversations', {
32
+ headers: {
33
+ Authorization: `Bearer ${token?.access_token}`,
34
+ },
35
+ })
36
+ .then(validateResponse)
37
+ .then(response => response.json())
38
+ .catch(error => {
39
+ if (error.message === 'Token expired') {
40
+ onTokenExpired()
41
+ }
42
+ return null
43
+ }),
44
+ })
45
+
46
+ return (
47
+ <FlatList
48
+ data={conversations?.data}
49
+ ListEmptyComponent={<Text>No conversations</Text>}
50
+ contentContainerStyle={styles.container}
51
+ ListHeaderComponent={<Text style={styles.foo}>Conversations</Text>}
52
+ renderItem={({ item }) => <Text>{item.attributes.title}</Text>}
53
+ />
54
+ )
55
+ }
56
+
57
+ const useStyles = () => {
58
+ return StyleSheet.create({
59
+ container: { columnGap: 16 },
60
+ foo: { fontSize: 24 },
61
+ })
62
+ }
63
+
64
+ const validateResponse = (response: Response) => {
65
+ const isExpired = response.status === 401
66
+ if (isExpired) {
67
+ throw new Error('Token expired')
68
+ }
69
+
70
+ return response
71
+ }
@@ -0,0 +1,27 @@
1
+ import React, { PropsWithChildren } from 'react'
2
+ import { Text } from 'react-native'
3
+
4
+ class ErrorBoundary extends React.Component<PropsWithChildren<{}>> {
5
+ state = {
6
+ error: null,
7
+ unsubscriber: () => {},
8
+ }
9
+
10
+ componentDidCatch(error: any) {
11
+ this.handleError(error)
12
+ }
13
+
14
+ handleError(error: any) {
15
+ this.setState({ error })
16
+ }
17
+
18
+ render() {
19
+ if (this.state.error) {
20
+ return <Text>{JSON.stringify(this.state.error, null, 2)}</Text>
21
+ } else {
22
+ return this.props.children
23
+ }
24
+ }
25
+ }
26
+
27
+ export default ErrorBoundary
@@ -0,0 +1,14 @@
1
+ import { createContext } from 'react'
2
+ import { OauthToken } from '../types'
3
+
4
+ type ContextValue = {
5
+ token: OauthToken | null
6
+ onTokenExpired: () => void
7
+ theme: any
8
+ }
9
+
10
+ export const ChatContext = createContext<ContextValue>({
11
+ theme: null,
12
+ token: null,
13
+ onTokenExpired: () => {},
14
+ })
package/src/index.tsx CHANGED
@@ -1,59 +1,5 @@
1
- import React, { useEffect, useState } from 'react'
2
- import { FlatList, StyleSheet, Text } from 'react-native'
1
+ import { Conversations } from './components/conversations'
2
+ import { ChatContext } from './context/chat_context'
3
+ import { OauthToken } from './types'
3
4
 
4
- export function PCOChat({
5
- token,
6
- onTokenExpired,
7
- }: {
8
- token?: OathToken
9
- onTokenExpired: () => void
10
- }) {
11
- const [conversations, setConversations] = useState<any>(null)
12
- const styles = useStyles()
13
-
14
- useEffect(() => {
15
- if (!token) {
16
- return
17
- }
18
-
19
- fetch('https://api.planningcenteronline.com/chat/v2/me/conversations', {
20
- headers: {
21
- Authorization: `Bearer ${token.access_token}`,
22
- },
23
- })
24
- .then(validateResponse)
25
- .then(response => response.json())
26
- .then(setConversations)
27
- .catch(error => {
28
- if (error.message === 'Token expired') {
29
- onTokenExpired()
30
- }
31
- })
32
- }, [onTokenExpired, token])
33
-
34
- return (
35
- <FlatList
36
- data={conversations?.data}
37
- ListEmptyComponent={<Text>No conversations</Text>}
38
- contentContainerStyle={styles.container}
39
- ListHeaderComponent={<Text style={styles.foo}>Conversations</Text>}
40
- renderItem={({ item }) => <Text>{item.attributes.title}</Text>}
41
- />
42
- )
43
- }
44
-
45
- const useStyles = () => {
46
- return StyleSheet.create({
47
- container: { columnGap: 16 },
48
- foo: { fontSize: 24 },
49
- })
50
- }
51
-
52
- const validateResponse = (response: Response) => {
53
- const isExpired = response.status === 401
54
- if (isExpired) {
55
- throw new Error('Token expired')
56
- }
57
-
58
- return response
59
- }
5
+ export { ChatContext, Conversations, OauthToken as OathToken }
package/src/types.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ export type ConversationRecord = {
2
+ id: string
3
+ type: 'Conversation'
4
+ attributes: {
5
+ title: string
6
+ created_at: string
7
+ updated_at: string
8
+ }
9
+ }
10
+
11
+ export type OauthToken = {
12
+ subdomain: any
13
+ type: any
14
+ access_token: any
15
+ created_at: number
16
+ expires_in: any
17
+ person: any
18
+ refresh_token: any
19
+ }