basic frontend added

This commit is contained in:
2026-02-06 00:49:38 +05:00
parent c35b466de6
commit fbbe35c9c0
19 changed files with 2374 additions and 7 deletions
+213
View File
@@ -0,0 +1,213 @@
<template>
<div class="app-container">
<!-- Header -->
<header class="header">
<div class="header-left">
<h1 class="header-title">Textpipe</h1>
<div class="header-status">
<span class="status-dot"></span>
<span>Connected</span>
</div>
</div>
<button class="btn-logout" @click="$emit('logout')">Logout</button>
</header>
<!-- Main Content -->
<div style="flex: 1; display: flex; flex-direction: column; overflow: hidden;">
<!-- Conversation View -->
<template v-if="selectedConversation">
<MessageThread
:conversation="selectedConversation"
:messages="selectedMessages"
:apiKey="apiKey"
@back="selectedConversation = null"
@messageSent="loadMessages"
/>
</template>
<!-- Conversation List -->
<template v-else>
<div class="conversation-list">
<div v-if="loading" class="loading">
<div class="spinner"></div>
</div>
<div v-else-if="conversations.length === 0" class="empty-state">
<div class="empty-state-icon">💬</div>
<h2 class="empty-state-title">No messages yet</h2>
<p class="empty-state-text">Start a new conversation</p>
</div>
<div
v-else
v-for="conv in conversations"
:key="conv.address"
class="conversation-item"
@click="selectConversation(conv)"
>
<div class="conversation-avatar">
{{ getInitial(conv.address) }}
</div>
<div class="conversation-info">
<div class="conversation-name">{{ conv.address }}</div>
<div class="conversation-preview">{{ conv.lastMessage }}</div>
</div>
<div class="conversation-meta">
<div class="conversation-time">{{ formatTime(conv.timestamp) }}</div>
<span v-if="conv.unread" class="conversation-badge">{{ conv.unread }}</span>
</div>
</div>
</div>
<!-- FAB -->
<button class="fab" @click="showNewMessage = true">+</button>
</template>
</div>
<!-- New Message Modal -->
<div v-if="showNewMessage" class="modal-overlay" @click.self="showNewMessage = false">
<div class="modal">
<div class="modal-header">
<h2 class="modal-title">New Message</h2>
<button class="btn-close" @click="showNewMessage = false">&times;</button>
</div>
<div class="modal-body">
<div class="form-group">
<label>To</label>
<input
v-model="newMessageTo"
type="tel"
placeholder="Phone number"
/>
</div>
<div class="form-group">
<label>Message</label>
<textarea
v-model="newMessageText"
placeholder="Type your message..."
rows="4"
></textarea>
</div>
</div>
<div class="modal-footer">
<button
class="btn btn-primary"
:disabled="!newMessageTo || !newMessageText || sending"
@click="sendNewMessage"
>
{{ sending ? 'Sending...' : 'Send' }}
</button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { api } from '../api'
import MessageThread from './MessageThread.vue'
const props = defineProps(['apiKey'])
const emit = defineEmits(['logout'])
const messages = ref([])
const loading = ref(true)
const selectedConversation = ref(null)
const showNewMessage = ref(false)
const newMessageTo = ref('')
const newMessageText = ref('')
const sending = ref(false)
let pollInterval = null
const conversations = computed(() => {
const grouped = {}
messages.value.forEach(msg => {
const address = msg.address
if (!grouped[address]) {
grouped[address] = {
address,
messages: [],
lastMessage: '',
timestamp: 0,
unread: 0
}
}
grouped[address].messages.push(msg)
if (msg.timestamp > grouped[address].timestamp) {
grouped[address].timestamp = msg.timestamp
grouped[address].lastMessage = msg.text.substring(0, 50) + (msg.text.length > 50 ? '...' : '')
}
})
return Object.values(grouped).sort((a, b) => b.timestamp - a.timestamp)
})
const selectedMessages = computed(() => {
if (!selectedConversation.value) return []
return messages.value
.filter(m => m.address === selectedConversation.value.address)
.sort((a, b) => a.timestamp - b.timestamp)
})
onMounted(() => {
loadMessages()
pollInterval = setInterval(loadMessages, 5000)
})
onUnmounted(() => {
if (pollInterval) clearInterval(pollInterval)
})
const loadMessages = async () => {
try {
const data = await api.getMessages(props.apiKey)
messages.value = data.messages || []
} catch (e) {
console.error('Failed to load messages:', e)
} finally {
loading.value = false
}
}
const selectConversation = (conv) => {
selectedConversation.value = conv
}
const getInitial = (address) => {
return address.charAt(0).toUpperCase()
}
const formatTime = (timestamp) => {
const date = new Date(timestamp)
const now = new Date()
const diff = now - date
if (diff < 86400000) {
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
} else if (diff < 604800000) {
return date.toLocaleDateString([], { weekday: 'short' })
} else {
return date.toLocaleDateString([], { month: 'short', day: 'numeric' })
}
}
const sendNewMessage = async () => {
if (!newMessageTo.value || !newMessageText.value) return
sending.value = true
try {
await api.sendMessage(props.apiKey, newMessageTo.value, newMessageText.value)
showNewMessage.value = false
newMessageTo.value = ''
newMessageText.value = ''
await loadMessages()
} catch (e) {
alert('Failed to send message: ' + e.message)
} finally {
sending.value = false
}
}
</script>