8b7a2eb644
Release APK / build (push) Has been cancelled
- 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>
189 lines
5.1 KiB
TypeScript
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);
|
|
}
|