Files
Postiz-android/artifacts/postiz-mobile/context/PostizContext.tsx
T
billisdead 8b7a2eb644
Release APK / build (push) Has been cancelled
feat: multi-workspace support + channels grouped by workspace and network
- PostizContext: new PostizWorkspace type, multi-workspace storage
  (postiz_workspaces_v2), auto-migration from legacy single config,
  addWorkspace / updateWorkspace / removeWorkspace, clients map
- Settings: full rewrite with workspace card list (add / edit / delete)
- Compose: channels displayed in two levels — workspace section then
  network type (X/Twitter, Instagram, LinkedIn...) within each workspace;
  submit routes posts and image uploads per workspace
- MediaLibraryModal: workspace tabs when multiple workspaces configured,
  returned items carry their workspaceId

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 14:50:20 +02:00

189 lines
5.1 KiB
TypeScript

import axios, { AxiosInstance } from "axios";
import * as SecureStore from "expo-secure-store";
import React, {
createContext,
useCallback,
useContext,
useEffect,
useState,
} from "react";
const WORKSPACES_KEY = "postiz_workspaces_v2";
const LEGACY_API_KEY = "postiz_api_key";
const LEGACY_BASE_URL = "postiz_base_url";
export const DEFAULT_BASE_URL = "https://postiz.gyozamancave.fr/api/public/v1";
export interface PostizWorkspace {
id: string;
name: string;
apiKey: string;
baseUrl: string;
}
export interface PostizIntegration {
id: string;
name: string;
type: string;
picture?: string;
identifier?: string;
internalType?: string;
}
export interface PostizMediaItem {
id: string;
path: string;
}
export interface PostizPost {
id: string;
content: string;
state: "QUEUE" | "PUBLISHED" | "ERROR" | "DRAFT";
publishDate: string;
integration?: PostizIntegration;
integrations?: PostizIntegration[];
image?: PostizMediaItem[];
group?: string;
errorMessage?: string;
}
export interface PostizUploadResult {
id: string;
path: string;
}
interface PostizContextValue {
workspaces: PostizWorkspace[];
isConfigured: boolean;
isLoading: boolean;
clients: Record<string, AxiosInstance>;
addWorkspace: (ws: Omit<PostizWorkspace, "id">) => Promise<void>;
updateWorkspace: (ws: PostizWorkspace) => Promise<void>;
removeWorkspace: (id: string) => Promise<void>;
// backward compat for posts.tsx (first workspace)
client: AxiosInstance | null;
apiKey: string;
baseUrl: string;
}
const PostizContext = createContext<PostizContextValue>({
workspaces: [],
isConfigured: false,
isLoading: true,
clients: {},
addWorkspace: async () => {},
updateWorkspace: async () => {},
removeWorkspace: async () => {},
client: null,
apiKey: "",
baseUrl: DEFAULT_BASE_URL,
});
function makeClient(ws: PostizWorkspace): AxiosInstance {
const baseURL = ws.baseUrl.endsWith("/") ? ws.baseUrl : ws.baseUrl + "/";
const instance = axios.create({
baseURL,
headers: { Authorization: ws.apiKey, "Content-Type": "application/json" },
timeout: 15000,
});
instance.interceptors.request.use((config) => {
console.log(">>> REQUEST:", config.method?.toUpperCase(), (config.baseURL ?? "") + (config.url ?? ""));
return config;
});
return instance;
}
function buildClients(list: PostizWorkspace[]): Record<string, AxiosInstance> {
return Object.fromEntries(list.map((ws) => [ws.id, makeClient(ws)]));
}
export function PostizProvider({ children }: { children: React.ReactNode }) {
const [workspaces, setWorkspaces] = useState<PostizWorkspace[]>([]);
const [clients, setClients] = useState<Record<string, AxiosInstance>>({});
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
(async () => {
try {
const stored = await SecureStore.getItemAsync(WORKSPACES_KEY);
if (stored) {
const list: PostizWorkspace[] = JSON.parse(stored);
setWorkspaces(list);
setClients(buildClients(list));
} else {
// Migrate legacy single-workspace config
const legacyKey = await SecureStore.getItemAsync(LEGACY_API_KEY);
const legacyUrl = await SecureStore.getItemAsync(LEGACY_BASE_URL);
if (legacyKey) {
const migrated: PostizWorkspace = {
id: Date.now().toString(),
name: "Default",
apiKey: legacyKey,
baseUrl: (legacyUrl || DEFAULT_BASE_URL).replace(/\/$/, ""),
};
const list = [migrated];
await SecureStore.setItemAsync(WORKSPACES_KEY, JSON.stringify(list));
setWorkspaces(list);
setClients(buildClients(list));
}
}
} catch {
} finally {
setIsLoading(false);
}
})();
}, []);
const persist = useCallback(async (list: PostizWorkspace[]) => {
await SecureStore.setItemAsync(WORKSPACES_KEY, JSON.stringify(list));
setWorkspaces(list);
setClients(buildClients(list));
}, []);
const addWorkspace = useCallback(
async (ws: Omit<PostizWorkspace, "id">) => {
await persist([...workspaces, { ...ws, id: Date.now().toString() }]);
},
[workspaces, persist]
);
const updateWorkspace = useCallback(
async (ws: PostizWorkspace) => {
await persist(workspaces.map((w) => (w.id === ws.id ? ws : w)));
},
[workspaces, persist]
);
const removeWorkspace = useCallback(
async (id: string) => {
await persist(workspaces.filter((w) => w.id !== id));
},
[workspaces, persist]
);
const primaryWorkspace = workspaces[0] ?? null;
return (
<PostizContext.Provider
value={{
workspaces,
isConfigured: workspaces.length > 0,
isLoading,
clients,
addWorkspace,
updateWorkspace,
removeWorkspace,
client: primaryWorkspace ? (clients[primaryWorkspace.id] ?? null) : null,
apiKey: primaryWorkspace?.apiKey ?? "",
baseUrl: primaryWorkspace?.baseUrl ?? DEFAULT_BASE_URL,
}}
>
{children}
</PostizContext.Provider>
);
}
export function usePostiz() {
return useContext(PostizContext);
}