Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Firebase Configuration
FIREBASE_SERVICE_ACCOUNT={"type":"service_account","project_id":"your-project-id","private_key_id":"your-key-id","private_key":"","client_email":"your-service-account@your-project.iam.gserviceaccount.com","client_id":"your-client-id","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_x509_cert_url":"https://www.googleapis.com/robot/v1/metadata/x509/your-service-account%40your-project.iam.gserviceaccount.com","universe_domain":"googleapis.com"}

FIREBASE_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\nYOUR_PRIVATE_KEY_HERE\n-----END PRIVATE KEY-----

FIREBASE_DBURL=https://your-project-default-rtdb.firebaseio.com

# Google Sheets API (opcional)
GOOGLE_SHEETS_CREDENTIALS={"type":"service_account","project_id":"your-project"}

# Telegram Bot (opcional)
TELEGRAM_BOT_TOKEN=your_bot_token_here
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,26 @@ node scripts/populate-inventario.js
## 🚀 Desenvolvimento

```bash
# Servidor de desenvolvimento
# Servidor de desenvolvimento (Telegram + Web)
npm run dev

# Modo web otimizado (apenas navegador)
npm run dev:web

# Verificar tipos
npm run typecheck

# Linting
npm run lint
```

### 🌐 Modo Web
O sistema agora suporta testes completos em navegador web comum:
- Menu de desenvolvimento automático fora do Telegram
- Simulação de diferentes tipos de usuário
- Todas as funcionalidades disponíveis
- Ver [WEB_MODE.md](WEB_MODE.md) para detalhes

## 📦 Deploy

```bash
Expand Down Expand Up @@ -153,6 +163,7 @@ npm start

## 📚 Documentação Específica

- [Modo Web - Testes fora do Telegram](WEB_MODE.md)
- [Sistema de Biblioteca](BIBLIOTECA_README.md)
- [Recursos Independentes](RECURSOS_INDEPENDENTES.md)
- [Registro de Empréstimos](REGISTRO_EMPRESTIMOS_README.md)
Expand Down
105 changes: 105 additions & 0 deletions WEB_MODE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Modo Web - Testes fora do Telegram

Este projeto agora suporta testes em navegador web comum, fora do ambiente Telegram.

## 🚀 Como usar

### Desenvolvimento Local
```bash
# Modo desenvolvimento normal (com menu dev sempre visível)
npm run dev

# Modo web otimizado (simula produção web)
npm run dev:web
```

### Funcionalidades em Modo Web

✅ **Funcionam normalmente:**
- Todas as funcionalidades do sistema
- Sistema de permissões via menu de desenvolvimento
- Interface responsiva
- Navegação entre páginas
- Formulários e ações

⚠️ **Limitações:**
- Não há integração real com Telegram
- Dados de usuário são simulados
- Notificações do Telegram não funcionam

## 🔧 Sistema de Usuários de Teste

O sistema automaticamente detecta quando não está rodando no Telegram e ativa o **Menu de Desenvolvimento** que permite:

### Usuários Disponíveis:
1. **João Silva** - Usuário Comum (`ANY_USER`)
2. **Maria Santos** - Ameciclista (`AMECICLISTAS`)
3. **Pedro Costa** - Coordenador de Projeto (`PROJECT_COORDINATORS`)
4. **Ana Lima** - Coordenadora Ameciclo (`AMECICLO_COORDINATORS`)

### Como trocar de usuário:
1. Clique no menu vermelho no topo da página
2. Selecione o usuário desejado
3. O sistema automaticamente aplicará as permissões correspondentes

## 🎯 Cenários de Teste

### Teste como Usuário Comum
- Acesso à Biblioteca (visualização)
- Acesso ao Bota pra Rodar (visualização)
- Links úteis

### Teste como Ameciclista
- Empréstimos de livros
- Solicitação de bicicletas
- Registro de empréstimos
- Consumo de recursos

### Teste como Coordenador
- Gestão de empréstimos
- Aprovação de solicitações
- Relatórios e estatísticas
- Gestão de usuários (apenas Coord. Ameciclo)

## 🔄 Persistência

O usuário selecionado é salvo automaticamente em:
- `localStorage` do navegador
- Cookie da sessão

Isso significa que ao recarregar a página, o usuário selecionado será mantido.

## 🌐 Acesso Remoto

Para testar em dispositivos móveis ou compartilhar com outros:

```bash
# Inicia servidor acessível na rede local
npm run dev:web
```

O servidor ficará disponível em:
- Local: `http://localhost:5173`
- Rede: `http://[seu-ip]:5173`

## 🔍 Debug

Para verificar se está em modo web:
1. Abra o console do navegador (F12)
2. Procure por: `"Executando em modo web (fora do Telegram)"`
3. O menu de desenvolvimento deve estar visível no topo

## 📱 Simulação Mobile

Para uma experiência mais próxima do Telegram:
1. Abra as ferramentas de desenvolvedor (F12)
2. Ative o modo responsivo/mobile
3. Escolha um dispositivo móvel
4. Teste a interface touch

## ⚡ Dicas

- Use o **modo web** para desenvolvimento rápido
- Teste diferentes permissões facilmente
- Ideal para demonstrações e apresentações
- Perfeito para debug de funcionalidades específicas
48 changes: 40 additions & 8 deletions app/api/firebaseAdmin.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,48 @@ import admin from "firebase-admin";
import dotenv from "dotenv";

dotenv.config();
const serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT);
serviceAccount.private_key = (process.env.FIREBASE_PRIVATE_KEY).replace(/\\n/g, "\n");

if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: process.env.FIREBASE_DBURL,
});
let serviceAccount;
try {
if (!process.env.FIREBASE_SERVICE_ACCOUNT) {
throw new Error('FIREBASE_SERVICE_ACCOUNT não está definido');
}
serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT);
if (process.env.FIREBASE_PRIVATE_KEY) {
serviceAccount.private_key = process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, "\n");
}
} catch (error) {
console.error('Erro ao configurar Firebase:', error.message);
// Configuração mínima para evitar crash
serviceAccount = {
type: "service_account",
project_id: "demo-project",
private_key: "",
client_email: "demo@demo.com"
};
}

const db = admin.database();
let db;
try {
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: process.env.FIREBASE_DBURL || 'https://demo.firebaseio.com',
});
}
db = admin.database();
} catch (error) {
console.error('Erro ao inicializar Firebase Admin:', error.message);
// Mock para desenvolvimento
db = {
ref: () => ({
once: () => Promise.resolve({ val: () => null }),
set: () => Promise.resolve(),
push: () => Promise.resolve({ key: 'mock-key' }),
update: () => Promise.resolve(),
remove: () => Promise.resolve()
})
};
}

export default db;
25 changes: 22 additions & 3 deletions app/components/BotaPraRodarGestao.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useState } from "react";
import { Form, useSubmit } from "@remix-run/react";
import type { EmprestimoBicicleta, SolicitacaoEmprestimoBicicleta, Bicicleta } from "~/utils/types";
import { useAuth } from "~/utils/useAuth";
import { TelegramUserInput } from "~/components/TelegramUserInput";

interface BotaPraRodarGestaoProps {
emprestimos: EmprestimoBicicleta[];
Expand All @@ -10,6 +12,7 @@ interface BotaPraRodarGestaoProps {
}

export function BotaPraRodarGestao({ emprestimos, solicitacoes, bicicletas, users }: BotaPraRodarGestaoProps) {
const { isDevMode, devUser } = useAuth();
const getUserName = (userId: string) => {
const user = users[userId];

Expand Down Expand Up @@ -43,29 +46,44 @@ export function BotaPraRodarGestao({ emprestimos, solicitacoes, bicicletas, user
const formData = new FormData();
formData.append("action", "registrar_devolucao");
formData.append("emprestimo_id", emprestimoId);
if (typeof window !== 'undefined' && window.Telegram?.WebApp?.initDataUnsafe?.user?.id) {

// Adicionar user_id baseado no contexto
if (isDevMode && devUser) {
formData.append("user_id", devUser.id.toString());
} else if (typeof window !== 'undefined' && window.Telegram?.WebApp?.initDataUnsafe?.user?.id) {
formData.append("user_id", window.Telegram.WebApp.initDataUnsafe.user.id.toString());
}

submit(formData, { method: "post" });
};

const handleAprovarSolicitacao = (solicitacaoId: string) => {
const formData = new FormData();
formData.append("action", "aprovar_solicitacao");
formData.append("solicitacao_id", solicitacaoId);
if (typeof window !== 'undefined' && window.Telegram?.WebApp?.initDataUnsafe?.user?.id) {

// Adicionar user_id baseado no contexto
if (isDevMode && devUser) {
formData.append("user_id", devUser.id.toString());
} else if (typeof window !== 'undefined' && window.Telegram?.WebApp?.initDataUnsafe?.user?.id) {
formData.append("user_id", window.Telegram.WebApp.initDataUnsafe.user.id.toString());
}

submit(formData, { method: "post" });
};

const handleRejeitarSolicitacao = (solicitacaoId: string) => {
const formData = new FormData();
formData.append("action", "rejeitar_solicitacao");
formData.append("solicitacao_id", solicitacaoId);
if (typeof window !== 'undefined' && window.Telegram?.WebApp?.initDataUnsafe?.user?.id) {

// Adicionar user_id baseado no contexto
if (isDevMode && devUser) {
formData.append("user_id", devUser.id.toString());
} else if (typeof window !== 'undefined' && window.Telegram?.WebApp?.initDataUnsafe?.user?.id) {
formData.append("user_id", window.Telegram.WebApp.initDataUnsafe.user.id.toString());
}

submit(formData, { method: "post" });
};

Expand Down Expand Up @@ -194,6 +212,7 @@ export function BotaPraRodarGestao({ emprestimos, solicitacoes, bicicletas, user
<div className="max-w-md mx-auto">
<Form method="post" className="space-y-4">
<input type="hidden" name="action" value="cadastrar_bicicleta" />
<TelegramUserInput />

<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Expand Down
19 changes: 10 additions & 9 deletions app/components/DevMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,21 @@ interface DevMenuProps {

export function DevMenu({ currentUser, onUserChange }: DevMenuProps) {
const [isOpen, setIsOpen] = useState(false);
const [isClient, setIsClient] = useState(false);

useEffect(() => {
setIsClient(true);
mockTelegramWebApp(currentUser);
// Salvar usuário atual no cookie para o middleware
if (currentUser && typeof document !== 'undefined') {
document.cookie = `devUser=${encodeURIComponent(JSON.stringify(currentUser))}; path=/`;
}
}, [currentUser]);

if (process.env.NODE_ENV === 'production') {
// Não renderizar no servidor
if (!isClient) {
return null;
}

// Mostrar menu se estiver em desenvolvimento OU se não estiver no Telegram
const isInTelegram = window.Telegram?.WebApp?.platform !== 'unknown';
if (process.env.NODE_ENV === 'production' && isInTelegram) {
return null;
}

Expand All @@ -73,10 +78,6 @@ export function DevMenu({ currentUser, onUserChange }: DevMenuProps) {
onClick={() => {
onUserChange(user);
setIsOpen(false);
// Forçar atualização do cookie
if (typeof document !== 'undefined') {
document.cookie = `devUser=${encodeURIComponent(JSON.stringify(user))}; path=/`;
}
}}
className={`block w-full text-left px-4 py-2 hover:bg-gray-100 text-sm ${
currentUser?.id === user.id ? 'bg-blue-50 font-semibold' : ''
Expand Down
6 changes: 5 additions & 1 deletion app/components/ProtectedComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ export function ProtectedComponent({
}: ProtectedComponentProps) {
const { userPermissions, isDevMode } = useAuth();

if (!isDevMode && !isAuth(userPermissions, requiredPermission)) {
// Em modo dev, sempre permitir acesso se o usuário tem a permissão
// Em produção, verificar normalmente
const hasPermission = isAuth(userPermissions, requiredPermission);

if (!hasPermission) {
return fallback ? <>{fallback}</> : null;
}

Expand Down
19 changes: 19 additions & 0 deletions app/components/TelegramUserInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useAuth } from "~/utils/useAuth";

export function TelegramUserInput() {
const { isDevMode, devUser } = useAuth();

const getUserId = () => {
if (isDevMode && devUser) {
return devUser.id.toString();
}

if (typeof window !== 'undefined' && window.Telegram?.WebApp?.initDataUnsafe?.user?.id) {
return window.Telegram.WebApp.initDataUnsafe.user.id.toString();
}

return "";
};

return <input type="hidden" name="user_id" value={getUserId()} />;
}
36 changes: 36 additions & 0 deletions app/components/WebModeNotice.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useState, useEffect } from "react";

export function WebModeNotice() {
const [isClient, setIsClient] = useState(false);

useEffect(() => {
setIsClient(true);
}, []);

if (!isClient) {
return null;
}

// Detectar se está no Telegram
const isInTelegram = window.Telegram?.WebApp?.platform !== 'unknown';

if (isInTelegram || process.env.NODE_ENV === 'development') {
return null;
}

return (
<div className="bg-blue-50 border-l-4 border-blue-400 p-4 mb-4">
<div className="flex">
<div className="flex-shrink-0">
<span className="text-blue-400 text-xl">ℹ️</span>
</div>
<div className="ml-3">
<p className="text-sm text-blue-700">
<strong>Modo Web:</strong> Você está acessando fora do Telegram.
Para a experiência completa, acesse através do bot oficial da Ameciclo no Telegram.
</p>
</div>
</div>
</div>
);
}
Loading