diff --git a/.env.sample b/.env.sample index bb66312..c752934 100644 --- a/.env.sample +++ b/.env.sample @@ -5,4 +5,10 @@ VITE_SLACK_REDIRECT_URI=http://localhost:5173/punch # Mainteiner.tsx VITE_USAGE_URL= +VITE_MAINTAINER_URL= VITE_INQUIRY_CHANNEL_URL= + +# Development Mode (set to 'true' to bypass Slack authentication) +# When enabled, the app will use mock authentication data and simulate API calls +# This allows developers to test the UI and functionality without needing Slack credentials +VITE_DEV_MODE=false diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx index ebba895..5702815 100644 --- a/src/context/AuthContext.tsx +++ b/src/context/AuthContext.tsx @@ -64,6 +64,12 @@ export const AuthProvider: FC = ( * 初回アクセス時の処理 */ useEffect(() => { + // 開発モードの場合は処理をスキップ + if (env.DEV_MODE) { + setAuthIsLoading(false) + return + } + // ログイン情報がない場合は何もしない if ( oauthAuthorizationCode === null && @@ -87,6 +93,64 @@ export const AuthProvider: FC = ( setAuthIsLoading(false) }, [oauthAuthorizationCode, localStorageSlackOauthToken]) + // 開発モードの場合は認証をバイパス + if (env.DEV_MODE) { + const mockUserProfile: UsersProfileGetResponse = { + ok: true, + profile: { + title: 'Developer', + phone: '', + skype: '', + real_name: '開発者', + real_name_normalized: '開発者', + display_name: '開発者', + display_name_normalized: '開発者', + fields: undefined, + status_text: '', + status_emoji: '', + status_expiration: 0, + avatar_hash: '', + image_original: '', + is_custom_image: false, + first_name: '開発', + last_name: '者', + image_24: '', + image_32: '', + image_48: '', + image_72: '', + image_192: '', + image_512: '', + image_1024: '', + status_text_canonical: '', + }, + } + + const mockSlackOauthToken: SlackOauthToken = { + accessToken: 'dev-mode-token', + refreshToken: 'dev-mode-refresh-token', + expiresAt: Date.now() / 1000 + 86400, // 24時間後 + } + + const value: AuthContextProps = { + authIsLoading: false, + authErrorMessage: undefined, + slackOauthToken: mockSlackOauthToken, + userProfile: mockUserProfile, + handleLogout: () => { + console.log('開発モード: ログアウト処理は実行されません') + }, + handleRemoveLocalStorageSlackOauthToken: () => { + console.log('開発モード: LocalStorage の削除は実行されません') + }, + } + + return ( + + {props.children} + + ) + } + const handleSetError = ( message: string, error: string | undefined | unknown, diff --git a/src/infra/repository/slack.ts b/src/infra/repository/slack.ts index abd20ed..6baa539 100644 --- a/src/infra/repository/slack.ts +++ b/src/infra/repository/slack.ts @@ -3,6 +3,7 @@ import { ConversationsHistoryResponse, ConversationsInfoResponse, } from '@slack/web-api' +import { env } from '../../utils' import { Conversations } from '../../types' import { RawSlackConversations, SlackConversations } from '../../types' import { @@ -16,6 +17,31 @@ export const getConversations = async (args: { conversations: Conversations accessToken?: string }): Promise => { + // 開発モードの場合はモックデータを返す + if (env.DEV_MODE) { + return args.conversations.map((conversation) => ({ + id: conversation.id, + conversationsInfo: { + ok: true, + channel: { + id: conversation.channelId, + name: `dev-channel-${conversation.id}`, + context_team_id: 'dev-team-id', + }, + }, + conversationsHistory: { + ok: true, + messages: [ + { + type: 'message', + text: conversation.searchMessage || 'デモメッセージ', + ts: '1234567890.123456', + }, + ], + }, + })) + } + const results = await Promise.all( args.conversations.map(async (conversation) => { const conversationsInfo = await getConversationsInfo({ @@ -84,6 +110,28 @@ export const postMessages = async (args: { message: string accessToken?: string }) => { + // 開発モードの場合はメッセージをコンソールに出力 + if (env.DEV_MODE) { + console.log('開発モード: メッセージ送信をシミュレート') + args.conversations.forEach((conversation) => { + console.log( + `チャンネル: ${conversation.channelName || conversation.channelId}`, + ) + console.log(`メッセージ: ${args.message}`) + if (conversation.threadTs) { + console.log(`スレッド: ${conversation.threadTs}`) + } + + notifications.show({ + title: `${conversation.channelName || 'Dev Channel'} メッセージ送信完了 (開発モード)`, + message: args.message, + color: 'blue', + position: 'top-right', + }) + }) + return + } + if (args.accessToken) { args.conversations.forEach((conversation) => { if (!conversation.channelId) { @@ -154,6 +202,24 @@ export const updateEmoji = async (args: { statusExpiration: number accessToken?: string }) => { + // 開発モードの場合はステータス更新をシミュレート + if (env.DEV_MODE) { + console.log('開発モード: ステータス絵文字更新をシミュレート') + console.log(`絵文字: ${args.statusEmoji}`) + console.log(`テキスト: ${args.statusText}`) + console.log( + `有効期限: ${new Date(args.statusExpiration * 1000).toLocaleString()}`, + ) + + notifications.show({ + title: 'ステータス更新完了 (開発モード)', + message: `${args.statusEmoji} ${args.statusText}`, + color: 'blue', + position: 'top-right', + }) + return + } + if (args.accessToken) { try { await updateStatusEmoji({ diff --git a/src/utils/env.ts b/src/utils/env.ts index 643f28d..58eaa21 100644 --- a/src/utils/env.ts +++ b/src/utils/env.ts @@ -6,14 +6,34 @@ const getEnvValue = (key: string): string => { return value } +const getOptionalEnvValue = (key: string, defaultValue = ''): string => { + return import.meta.env[key] || defaultValue +} + const getNullableEnvValue = (key: string): string | undefined => { return import.meta.env[key] } +const getBooleanEnvValue = (key: string, defaultValue = false): boolean => { + const value = import.meta.env[key] + if (!value) return defaultValue + return value.toLowerCase() === 'true' +} + +const isDevMode = getBooleanEnvValue('VITE_DEV_MODE') + export const env = { - SLACK_CLIENT_ID: getEnvValue('VITE_SLACK_CLIENT_ID'), - SLACK_CLIENT_SECRET: getEnvValue('VITE_SLACK_CLIENT_SECRET'), - SLACK_REDIRECT_URI: getEnvValue('VITE_SLACK_REDIRECT_URI'), + SLACK_CLIENT_ID: isDevMode + ? getOptionalEnvValue('VITE_SLACK_CLIENT_ID') + : getEnvValue('VITE_SLACK_CLIENT_ID'), + SLACK_CLIENT_SECRET: isDevMode + ? getOptionalEnvValue('VITE_SLACK_CLIENT_SECRET') + : getEnvValue('VITE_SLACK_CLIENT_SECRET'), + SLACK_REDIRECT_URI: isDevMode + ? getOptionalEnvValue('VITE_SLACK_REDIRECT_URI') + : getEnvValue('VITE_SLACK_REDIRECT_URI'), USAGE_URL: getNullableEnvValue('VITE_USAGE_URL'), + MAINTAINER_URL: getNullableEnvValue('VITE_MAINTAINER_URL'), INQUIRY_CHANNEL_URL: getNullableEnvValue('VITE_INQUIRY_CHANNEL_URL'), + DEV_MODE: isDevMode, } as const