@planningcenter/chat-react-native 3.17.0 → 3.17.1
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/build/contexts/api_provider.d.ts.map +1 -1
- package/build/contexts/api_provider.js +4 -1
- package/build/contexts/api_provider.js.map +1 -1
- package/build/hooks/use_message_draft.d.ts.map +1 -1
- package/build/hooks/use_message_draft.js +12 -5
- package/build/hooks/use_message_draft.js.map +1 -1
- package/package.json +7 -4
- package/src/contexts/api_provider.tsx +5 -1
- package/src/hooks/use_message_draft.ts +15 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api_provider.d.ts","sourceRoot":"","sources":["../../src/contexts/api_provider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,WAAW,EAIZ,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAwC,MAAM,OAAO,CAAA;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAkBxC,eAAO,MAAM,eAAe,aAO1B,CAAA;AAEF,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"api_provider.d.ts","sourceRoot":"","sources":["../../src/contexts/api_provider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,WAAW,EAIZ,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAwC,MAAM,OAAO,CAAA;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAkBxC,eAAO,MAAM,eAAe,aAO1B,CAAA;AAEF,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,EAAE,SAAS,qBAsBlD;AAcD,wBAAgB,eAAe,WAQ9B"}
|
|
@@ -23,12 +23,15 @@ export const chatQueryClient = new QueryClient({
|
|
|
23
23
|
export function ApiProvider({ children }) {
|
|
24
24
|
const { token, env } = useContext(ChatContext);
|
|
25
25
|
const sessionChanged = useSessionChanged({ token, env });
|
|
26
|
-
|
|
26
|
+
const client = useApiClient();
|
|
27
27
|
useEffect(() => {
|
|
28
28
|
if (!sessionChanged)
|
|
29
29
|
return;
|
|
30
30
|
chatQueryClient.clear();
|
|
31
31
|
}, [sessionChanged]);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
apiClient = client;
|
|
34
|
+
}, [client]);
|
|
32
35
|
return (<QueryClientProvider client={chatQueryClient}>
|
|
33
36
|
<PrefetchQueries />
|
|
34
37
|
{children}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api_provider.js","sourceRoot":"","sources":["../../src/contexts/api_provider.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,WAAW,EACX,mBAAmB,EAEnB,gBAAgB,GACjB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAK,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAE5D,OAAO,EAAE,WAAW,EAAoB,MAAM,gBAAgB,CAAA;AAC9D,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAmB,MAAM,UAAU,CAAA;AACpF,OAAO,EAAa,YAAY,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAEpD,IAAI,SAAgC,CAAA;AAEpC,MAAM,cAAc,GAAG,CAAC,EAAE,QAAQ,EAA0B,EAAE,EAAE;IAC9D,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAA;IACrC,CAAC;IAED,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,GAAG,MAAM,CAAC,GAAG,QAA2B,CAAA;IAEtE,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAA;AACnD,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,WAAW,CAAC;IAC7C,cAAc,EAAE;QACd,OAAO,EAAE;YACP,OAAO,EAAE,cAAc;YACvB,KAAK,EAAE,CAAC;SACT;KACF;CACF,CAAC,CAAA;AAEF,MAAM,UAAU,WAAW,CAAC,EAAE,QAAQ,EAAa;IACjD,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,UAAU,CAAC,WAAW,CAAC,CAAA;IAC9C,MAAM,cAAc,GAAG,iBAAiB,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;IAExD,
|
|
1
|
+
{"version":3,"file":"api_provider.js","sourceRoot":"","sources":["../../src/contexts/api_provider.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n focusManager,\n QueryClient,\n QueryClientProvider,\n QueryKey,\n usePrefetchQuery,\n} from '@tanstack/react-query'\nimport React, { useContext, useEffect, useRef } from 'react'\nimport { ViewProps } from 'react-native'\nimport { ChatContext, ChatContextValue } from './chat_context'\nimport { appGrantsRequestArgs, getRequestQueryKey, RequestQueryKey } from '../hooks'\nimport { ApiClient, useApiClient } from '../hooks/use_api_client'\nimport { useAppState } from '../hooks/use_app_state'\n\nlet apiClient: ApiClient | undefined\n\nconst defaultQueryFn = ({ queryKey }: { queryKey: QueryKey }) => {\n if (!apiClient) {\n throw new Error('No token present')\n }\n\n const [url, data, headers, app = 'chat'] = queryKey as RequestQueryKey\n\n return apiClient[app].get({ url, data, headers })\n}\n\nexport const chatQueryClient = new QueryClient({\n defaultOptions: {\n queries: {\n queryFn: defaultQueryFn,\n retry: 3,\n },\n },\n})\n\nexport function ApiProvider({ children }: ViewProps) {\n const { token, env } = useContext(ChatContext)\n const sessionChanged = useSessionChanged({ token, env })\n\n const client = useApiClient()\n\n useEffect(() => {\n if (!sessionChanged) return\n\n chatQueryClient.clear()\n }, [sessionChanged])\n\n useEffect(() => {\n apiClient = client\n }, [client])\n\n return (\n <QueryClientProvider client={chatQueryClient}>\n <PrefetchQueries />\n {children}\n </QueryClientProvider>\n )\n}\n\n// Component to prefetch queries when the app is focused\n// This needs to live in the provider so that it can access the api client\n// and the chat query client\nconst PrefetchQueries = () => {\n usePrefetchQuery({\n queryKey: getRequestQueryKey(appGrantsRequestArgs),\n queryFn: defaultQueryFn,\n })\n\n return null\n}\n\nexport function useFocusManager() {\n const appState = useAppState()\n\n useEffect(() => {\n focusManager.setFocused(appState === 'active')\n }, [appState])\n\n return appState\n}\n\nfunction useSessionChanged(value: Pick<ChatContextValue, 'token' | 'env'>): boolean {\n const { token: newToken, env: newEnv } = value\n const { token: prevToken, env: prevEnv } = usePrevious<typeof value>(value)\n\n return Boolean(prevToken && newToken !== prevToken) || Boolean(prevEnv && newEnv !== prevEnv)\n}\n\nfunction usePrevious<T>(value: T): T {\n const ref = useRef<T>(value)\n\n useEffect(() => {\n ref.current = value\n }, [value])\n\n return ref.current\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use_message_draft.d.ts","sourceRoot":"","sources":["../../src/hooks/use_message_draft.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gEAAgE,CAAA;AAEpG,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,cAAc,EAAE,CAAA;IAC7B,WAAW,EAAE,MAAM,CAAA;CACpB;AAQD,wBAAgB,eAAe,CAAC,cAAc,EAAE,MAAM;;
|
|
1
|
+
{"version":3,"file":"use_message_draft.d.ts","sourceRoot":"","sources":["../../src/hooks/use_message_draft.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gEAAgE,CAAA;AAEpG,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,cAAc,EAAE,CAAA;IAC7B,WAAW,EAAE,MAAM,CAAA;CACpB;AAQD,wBAAgB,eAAe,CAAC,cAAc,EAAE,MAAM;;sBAmD3C,MAAM,eAAe,cAAc,EAAE;;EAkD/C"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { useAsyncStorage } from './use_async_storage';
|
|
3
3
|
const DRAFT_STORAGE_KEY = 'chat_message_drafts';
|
|
4
4
|
const DRAFT_EXPIRY_HOURS = 720; // 30 days
|
|
@@ -13,16 +13,23 @@ export function useMessageDraft(conversationId) {
|
|
|
13
13
|
// Filter expired attachments
|
|
14
14
|
if (storedDraft?.attachments) {
|
|
15
15
|
const now = Date.now();
|
|
16
|
-
|
|
16
|
+
const validAttachments = storedDraft.attachments.filter(attachment => {
|
|
17
17
|
if (!attachment.uploadedAt)
|
|
18
18
|
return false;
|
|
19
19
|
return now - attachment.uploadedAt < ATTACHMENT_EXPIRY_MS;
|
|
20
20
|
});
|
|
21
|
+
return {
|
|
22
|
+
...storedDraft,
|
|
23
|
+
attachments: validAttachments,
|
|
24
|
+
};
|
|
21
25
|
}
|
|
22
26
|
return storedDraft;
|
|
23
27
|
});
|
|
24
|
-
// Clean up expired drafts on mount
|
|
28
|
+
// Clean up expired drafts only once on mount - React 18 recommended pattern
|
|
29
|
+
const hasCleanedUp = useRef(false);
|
|
25
30
|
useEffect(() => {
|
|
31
|
+
if (hasCleanedUp.current)
|
|
32
|
+
return;
|
|
26
33
|
const now = Date.now();
|
|
27
34
|
const validDrafts = {};
|
|
28
35
|
Object.entries(allDrafts).forEach(([id, draftData]) => {
|
|
@@ -34,8 +41,8 @@ export function useMessageDraft(conversationId) {
|
|
|
34
41
|
if (Object.keys(validDrafts).length !== Object.keys(allDrafts).length) {
|
|
35
42
|
setAllDrafts(validDrafts);
|
|
36
43
|
}
|
|
37
|
-
|
|
38
|
-
}, []);
|
|
44
|
+
hasCleanedUp.current = true;
|
|
45
|
+
}, [allDrafts, setAllDrafts]);
|
|
39
46
|
const saveDraft = useCallback((text, attachments) => {
|
|
40
47
|
const hasContent = text.trim().length > 0 || attachments.length > 0;
|
|
41
48
|
const updatedDrafts = { ...allDrafts };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use_message_draft.js","sourceRoot":"","sources":["../../src/hooks/use_message_draft.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"use_message_draft.js","sourceRoot":"","sources":["../../src/hooks/use_message_draft.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react'\nimport { useAsyncStorage } from './use_async_storage'\nimport type { FileAttachment } from '../types/resources/denormalized_attachment_resource_for_create'\n\ninterface MessageDraft {\n text: string\n attachments: FileAttachment[]\n lastUpdated: number\n}\n\nconst DRAFT_STORAGE_KEY = 'chat_message_drafts'\nconst DRAFT_EXPIRY_HOURS = 720 // 30 days\nconst DRAFT_EXPIRY_MS = DRAFT_EXPIRY_HOURS * 60 * 60 * 1000\nconst ATTACHMENT_EXPIRY_HOURS = 48 // 2 days\nconst ATTACHMENT_EXPIRY_MS = ATTACHMENT_EXPIRY_HOURS * 60 * 60 * 1000\n\nexport function useMessageDraft(conversationId: number) {\n const conversationKey = conversationId.toString()\n const [allDrafts, setAllDrafts] = useAsyncStorage<Record<string, MessageDraft>>(\n DRAFT_STORAGE_KEY,\n {}\n )\n\n const [draft, setDraft] = useState<MessageDraft | null>(() => {\n const storedDraft = allDrafts[conversationKey] || null\n\n // Filter expired attachments\n if (storedDraft?.attachments) {\n const now = Date.now()\n const validAttachments = storedDraft.attachments.filter(attachment => {\n if (!attachment.uploadedAt) return false\n return now - attachment.uploadedAt < ATTACHMENT_EXPIRY_MS\n })\n\n return {\n ...storedDraft,\n attachments: validAttachments,\n }\n }\n\n return storedDraft\n })\n\n // Clean up expired drafts only once on mount - React 18 recommended pattern\n const hasCleanedUp = useRef(false)\n\n useEffect(() => {\n if (hasCleanedUp.current) return\n\n const now = Date.now()\n const validDrafts: Record<string, MessageDraft> = {}\n\n Object.entries(allDrafts).forEach(([id, draftData]) => {\n if (draftData?.lastUpdated && now - draftData.lastUpdated < DRAFT_EXPIRY_MS) {\n validDrafts[id] = draftData\n }\n })\n\n // If we filtered out any drafts, update storage\n if (Object.keys(validDrafts).length !== Object.keys(allDrafts).length) {\n setAllDrafts(validDrafts)\n }\n\n hasCleanedUp.current = true\n }, [allDrafts, setAllDrafts])\n\n const saveDraft = useCallback(\n (text: string, attachments: FileAttachment[]) => {\n const hasContent = text.trim().length > 0 || attachments.length > 0\n const updatedDrafts = { ...allDrafts }\n\n if (hasContent) {\n // Convert attachments to serializable format (similar to web implementation)\n const normalizedAttachments = attachments\n .filter(att => att.id && att.status === 'success')\n .map(att => ({\n ...att,\n file: {\n uri: att.file.uri,\n name: att.file.name,\n type: att.file.type,\n size: att.file.size,\n width: att.file.width,\n height: att.file.height,\n },\n }))\n\n const newDraft: MessageDraft = {\n text,\n attachments: normalizedAttachments,\n lastUpdated: Date.now(),\n }\n\n updatedDrafts[conversationKey] = newDraft\n setDraft(newDraft)\n } else {\n delete updatedDrafts[conversationKey]\n setDraft(null)\n }\n\n setAllDrafts(updatedDrafts)\n },\n [conversationKey, allDrafts, setAllDrafts]\n )\n\n const clearDraft = useCallback(() => {\n const updatedDrafts = { ...allDrafts }\n delete updatedDrafts[conversationKey]\n setAllDrafts(updatedDrafts)\n setDraft(null)\n }, [conversationKey, allDrafts, setAllDrafts])\n\n return {\n draft,\n saveDraft,\n clearDraft,\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/chat-react-native",
|
|
3
|
-
"version": "3.17.
|
|
3
|
+
"version": "3.17.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
"test": "expo-module test",
|
|
17
17
|
"prepublishOnly": "expo-module prepublishOnly"
|
|
18
18
|
},
|
|
19
|
+
"target": "18",
|
|
19
20
|
"dependencies": {
|
|
20
|
-
"lodash-inflection": "^1.5.0"
|
|
21
|
+
"lodash-inflection": "^1.5.0",
|
|
22
|
+
"react-compiler-runtime": "^1.0.0"
|
|
21
23
|
},
|
|
22
24
|
"peerDependencies": {
|
|
23
25
|
"@planningcenter/datetime-fmt": ">=1.0.0",
|
|
@@ -33,7 +35,7 @@
|
|
|
33
35
|
"lodash": "*",
|
|
34
36
|
"moment": ">=2.0.0",
|
|
35
37
|
"moment-timezone": "*",
|
|
36
|
-
"react": "
|
|
38
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
37
39
|
"react-native": "*",
|
|
38
40
|
"react-native-device-info": "*",
|
|
39
41
|
"react-native-gesture-handler": "*",
|
|
@@ -47,6 +49,7 @@
|
|
|
47
49
|
"@testing-library/react-hooks": "^8.0.1",
|
|
48
50
|
"@types/jest": "^29.5.14",
|
|
49
51
|
"@typescript-eslint/parser": "^8.32.0",
|
|
52
|
+
"eslint-plugin-react-compiler": "^19.1.0-rc.2",
|
|
50
53
|
"expo-module-scripts": "^5.0.7",
|
|
51
54
|
"fast-text-encoding": "^1.0.6",
|
|
52
55
|
"jest": "^29.7.0",
|
|
@@ -56,5 +59,5 @@
|
|
|
56
59
|
"react-native-url-polyfill": "^2.0.0",
|
|
57
60
|
"typescript": "<5.6.0"
|
|
58
61
|
},
|
|
59
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "7c2622a94c9803e4085cf9c58929f76b8614895e"
|
|
60
63
|
}
|
|
@@ -37,7 +37,7 @@ export function ApiProvider({ children }: ViewProps) {
|
|
|
37
37
|
const { token, env } = useContext(ChatContext)
|
|
38
38
|
const sessionChanged = useSessionChanged({ token, env })
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
const client = useApiClient()
|
|
41
41
|
|
|
42
42
|
useEffect(() => {
|
|
43
43
|
if (!sessionChanged) return
|
|
@@ -45,6 +45,10 @@ export function ApiProvider({ children }: ViewProps) {
|
|
|
45
45
|
chatQueryClient.clear()
|
|
46
46
|
}, [sessionChanged])
|
|
47
47
|
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
apiClient = client
|
|
50
|
+
}, [client])
|
|
51
|
+
|
|
48
52
|
return (
|
|
49
53
|
<QueryClientProvider client={chatQueryClient}>
|
|
50
54
|
<PrefetchQueries />
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from 'react'
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
2
2
|
import { useAsyncStorage } from './use_async_storage'
|
|
3
3
|
import type { FileAttachment } from '../types/resources/denormalized_attachment_resource_for_create'
|
|
4
4
|
|
|
@@ -27,17 +27,26 @@ export function useMessageDraft(conversationId: number) {
|
|
|
27
27
|
// Filter expired attachments
|
|
28
28
|
if (storedDraft?.attachments) {
|
|
29
29
|
const now = Date.now()
|
|
30
|
-
|
|
30
|
+
const validAttachments = storedDraft.attachments.filter(attachment => {
|
|
31
31
|
if (!attachment.uploadedAt) return false
|
|
32
32
|
return now - attachment.uploadedAt < ATTACHMENT_EXPIRY_MS
|
|
33
33
|
})
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
...storedDraft,
|
|
37
|
+
attachments: validAttachments,
|
|
38
|
+
}
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
return storedDraft
|
|
37
42
|
})
|
|
38
43
|
|
|
39
|
-
// Clean up expired drafts on mount
|
|
44
|
+
// Clean up expired drafts only once on mount - React 18 recommended pattern
|
|
45
|
+
const hasCleanedUp = useRef(false)
|
|
46
|
+
|
|
40
47
|
useEffect(() => {
|
|
48
|
+
if (hasCleanedUp.current) return
|
|
49
|
+
|
|
41
50
|
const now = Date.now()
|
|
42
51
|
const validDrafts: Record<string, MessageDraft> = {}
|
|
43
52
|
|
|
@@ -51,8 +60,9 @@ export function useMessageDraft(conversationId: number) {
|
|
|
51
60
|
if (Object.keys(validDrafts).length !== Object.keys(allDrafts).length) {
|
|
52
61
|
setAllDrafts(validDrafts)
|
|
53
62
|
}
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
|
|
64
|
+
hasCleanedUp.current = true
|
|
65
|
+
}, [allDrafts, setAllDrafts])
|
|
56
66
|
|
|
57
67
|
const saveDraft = useCallback(
|
|
58
68
|
(text: string, attachments: FileAttachment[]) => {
|