207 lines
7.3 KiB
TypeScript
207 lines
7.3 KiB
TypeScript
import { JobApplication } from '../types';
|
|
|
|
let cachedAccessToken: string | null = localStorage.getItem('ai_job_portal_google_token');
|
|
|
|
export const getCachedAccessToken = () => cachedAccessToken;
|
|
|
|
const getEnvVar = (key: string) => {
|
|
try {
|
|
return (import.meta as any).env?.[key];
|
|
} catch (e) {
|
|
return undefined;
|
|
}
|
|
};
|
|
|
|
const OAUTH_CLIENT_ID = getEnvVar('VITE_GOOGLE_OAUTH_CLIENT_ID') || '488548028456-tal52pta8fm2uc9t4o2u82itqvi91rp7.apps.googleusercontent.com';
|
|
|
|
/**
|
|
* Direct Google OAuth 2.0 Flow via Google Identity Services
|
|
* Obtains real access token directly from accounts.google.com for Google Drive/Sheets API calls.
|
|
*/
|
|
export const googleWorkspaceSignIn = async (): Promise<{ email: string; name: string; accessToken: string }> => {
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
const scope = encodeURIComponent('https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/documents');
|
|
const redirectUri = encodeURIComponent(`${window.location.origin}`);
|
|
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${OAUTH_CLIENT_ID}&redirect_uri=${redirectUri}&response_type=token&scope=${scope}&prompt=select_account`;
|
|
|
|
const width = 500;
|
|
const height = 650;
|
|
const left = window.screenX + (window.outerWidth - width) / 2;
|
|
const top = window.screenY + (window.outerHeight - height) / 2;
|
|
|
|
const popup = window.open(
|
|
authUrl,
|
|
'Google Workspace OAuth Sign-In',
|
|
`width=${width},height=${height},left=${left},top=${top},scrollbars=yes`
|
|
);
|
|
|
|
if (!popup) {
|
|
throw new Error('Popup window was blocked by your browser. Please allow popups for http://localhost:3000.');
|
|
}
|
|
|
|
const interval = setInterval(async () => {
|
|
try {
|
|
if (popup.closed) {
|
|
clearInterval(interval);
|
|
if (!cachedAccessToken) {
|
|
reject(new Error('Sign-in window closed before completing authentication.'));
|
|
}
|
|
return;
|
|
}
|
|
|
|
const currentUrl = popup.location.href;
|
|
if (currentUrl && currentUrl.includes(window.location.origin)) {
|
|
const hash = popup.location.hash;
|
|
if (hash && hash.includes('access_token=')) {
|
|
clearInterval(interval);
|
|
popup.close();
|
|
|
|
const params = new URLSearchParams(hash.substring(1));
|
|
const token = params.get('access_token');
|
|
|
|
if (token) {
|
|
cachedAccessToken = token;
|
|
localStorage.setItem('ai_job_portal_google_token', token);
|
|
localStorage.setItem('ai_job_portal_google_connected', 'true');
|
|
|
|
try {
|
|
const userRes = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
|
|
headers: { Authorization: `Bearer ${token}` }
|
|
});
|
|
if (userRes.ok) {
|
|
const userData = await userRes.json();
|
|
resolve({
|
|
email: userData.email || 'dkifer19@gmail.com',
|
|
name: userData.name || 'David Kifer',
|
|
accessToken: token
|
|
});
|
|
return;
|
|
}
|
|
} catch (e) {}
|
|
|
|
resolve({
|
|
email: 'dkifer19@gmail.com',
|
|
name: 'David Kifer',
|
|
accessToken: token
|
|
});
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {}
|
|
}, 400);
|
|
|
|
} catch (err: any) {
|
|
reject(err);
|
|
}
|
|
});
|
|
};
|
|
|
|
export const googleSignOut = async () => {
|
|
cachedAccessToken = null;
|
|
localStorage.removeItem('ai_job_portal_google_token');
|
|
localStorage.removeItem('ai_job_portal_google_connected');
|
|
localStorage.removeItem('ai_job_portal_google_user');
|
|
};
|
|
|
|
|
|
|
|
// Workspace Action Handlers
|
|
|
|
export async function exportApplicationsToGoogleSheets(
|
|
applications: JobApplication[],
|
|
accessToken?: string
|
|
): Promise<{ spreadsheetId: string; spreadsheetUrl: string; isLocalCsv?: boolean; message?: string }> {
|
|
const token = accessToken || cachedAccessToken;
|
|
const res = await fetch('/api/workspace/sheets/export', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...(token ? { Authorization: `Bearer ${token}` } : {})
|
|
},
|
|
body: JSON.stringify({ applications, accessToken: token })
|
|
});
|
|
const data = await res.json();
|
|
if (!data.success) {
|
|
throw new Error(data.error || 'Failed to export applications to Google Sheets');
|
|
}
|
|
return { spreadsheetId: data.spreadsheetId, spreadsheetUrl: data.spreadsheetUrl, isLocalCsv: data.isLocalCsv, message: data.message };
|
|
}
|
|
|
|
export async function createTailoredGoogleDoc(
|
|
title: string,
|
|
content: string,
|
|
docType: 'resume' | 'coverLetter' | 'note',
|
|
accessToken?: string
|
|
): Promise<{ documentId: string; documentUrl: string; isLocalDoc?: boolean; message?: string }> {
|
|
const token = accessToken || cachedAccessToken;
|
|
const res = await fetch('/api/workspace/docs/create', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...(token ? { Authorization: `Bearer ${token}` } : {})
|
|
},
|
|
body: JSON.stringify({ title, content, docType, accessToken: token })
|
|
});
|
|
const data = await res.json();
|
|
if (!data.success) {
|
|
throw new Error(data.error || 'Failed to create Google Doc');
|
|
}
|
|
return { documentId: data.documentId, documentUrl: data.documentUrl, isLocalDoc: data.isLocalDoc, message: data.message };
|
|
}
|
|
|
|
export async function uploadFileToGoogleDrive(
|
|
fileName: string,
|
|
fileContent: string,
|
|
mimeType: string = 'text/plain',
|
|
accessToken?: string
|
|
): Promise<{ fileId: string; driveUrl: string; isLocalFile?: boolean; message?: string }> {
|
|
const token = accessToken || cachedAccessToken;
|
|
const res = await fetch('/api/workspace/drive/upload', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...(token ? { Authorization: `Bearer ${token}` } : {})
|
|
},
|
|
body: JSON.stringify({ fileName, fileContent, mimeType, accessToken: token })
|
|
});
|
|
const data = await res.json();
|
|
if (!data.success) {
|
|
throw new Error(data.error || 'Failed to upload file to Google Drive');
|
|
}
|
|
return { fileId: data.fileId, driveUrl: data.driveUrl, isLocalFile: data.isLocalFile, message: data.message };
|
|
}
|
|
|
|
export async function listGoogleDriveWorkspaceFiles(accessToken?: string): Promise<any[]> {
|
|
const token = accessToken || cachedAccessToken;
|
|
const res = await fetch('/api/workspace/drive/files', {
|
|
headers: {
|
|
...(token ? { Authorization: `Bearer ${token}` } : {})
|
|
}
|
|
});
|
|
const data = await res.json();
|
|
return data.files || [];
|
|
}
|
|
|
|
export async function saveGoogleKeepNote(
|
|
title: string,
|
|
content: string,
|
|
color: string = 'DEFAULT',
|
|
accessToken?: string
|
|
): Promise<{ noteId: string; title: string; content: string; color: string; timestamp: string }> {
|
|
const token = accessToken || cachedAccessToken;
|
|
const res = await fetch('/api/workspace/keep/create', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...(token ? { Authorization: `Bearer ${token}` } : {})
|
|
},
|
|
body: JSON.stringify({ title, content, color, accessToken: token })
|
|
});
|
|
const data = await res.json();
|
|
if (!data.success) {
|
|
throw new Error(data.error || 'Failed to create Keep note');
|
|
}
|
|
return data.note;
|
|
}
|