Compare commits

...

21 Commits

Author SHA1 Message Date
Carlos 2b74586b00 refactor: arquitetura escalavel em src/ para crescimento de mini apps
- Migra components/lib/styles/legacy para src/ (pages mantidas na raiz)
- Adiciona alias @/* -> src/* no jsconfig.json
- Atualiza imports de _app e dashboard para novo caminho
- Cria src/components/common com 10 componentes compartilhados (placeholders)
- Cria src/modules/ para 6 mini apps (noticias, calculadora, bloco_notas, criptomoedas, conversor, jogos)
- Cria src/services/ (api, auth, ads, news, currency, crypto, storage, analytics) como placeholders
- Prepara auth (providers google/apple/email/anonymous + interfaces) sem SDK
- Prepara ads (Adapter + Banner/Interstitial/Rewarded/Native) sem SDK
- Cria src/config/ (appConfig, featureFlags, environment, theme, ads)
- Remove duplicatas index.js em pages/apps/*
- Nenhuma funcionalidade implementada; apenas arquitetura preparada
- Build Next.js validado (10 rotas, sem erros)
2026-07-11 22:44:52 +02:00
Carlos dce148338f refactor: reorganiza projeto em legacy/ apos remocao de login
- Move paginas de auth (signin, signup, nextauth) para legacy/auth/
- Move Landing Page para legacy/landing/
- Move dashboard.jsx.bak para legacy/
- Adiciona README documentando backup e deps necessarias
- Nenhuma rota ativa depende de NextAuth; app abre direto no dashboard
- Deps (next-auth, prisma-adapter, bcryptjs) mantidas para nao quebrar backup
- Build Next.js validado (10 rotas, sem rotas de auth/landing)
2026-07-11 22:34:26 +02:00
Carlos cf96f84470 feat: remove fluxo de login (NextAuth) e abre direto no dashboard; landing preservada
- pages/_app.jsx: remove SessionProvider/next-auth
- pages/index.jsx: nova rota '/' redireciona para /dashboard (sem auth)
- pages/landing.jsx: index.jsx renomeado e comentado como desativado (preservado)
- pages/dashboard.jsx: remove useSession/signOut, bloco 'Olá/Sair', importa React p/ build
- pages/apps/trendsnews e worldcup2026: remove useSession (não usado no render)
- pages/signin.jsx, signup.jsx, api/auth/[...nextauth].js: preservados com comentário de desativação (sem exclusão)
- next build validado com sucesso
2026-07-11 19:27:38 +02:00
Carlos 07835d2a7b WIP: ajustes Dockerfile, signin e schema.prisma (correção SQLite/Prisma da sessão anterior) 2026-07-11 19:20:41 +02:00
Carlos 3b4117c9b4 fix: use docker service name for DATABASE_URL instead of IP 2026-07-11 19:20:11 +02:00
Carlos 8884e28ed7 feat: update dashboard to use NextAuth session, show user name, fix avatar component usage 2026-07-10 21:59:55 +02:00
Carlos 71da57088f feat: replace custom login/signup with NextAuth credentials provider, add signup page using Prisma, update NextAuth config to JWT strategy 2026-07-10 21:59:06 +02:00
Carlos 583170efd1 fix: move prisma generate to builder stage, add migration and entrypoint, replace custom login with NextAuth credentials provider 2026-07-10 21:56:45 +02:00
Carlos cef5402cfa fix: add trustHost true to NextAuth config for proxy 2026-07-09 11:18:13 +02:00
Carlos cd6a36b6e4 fix: add linux-musl-openssl-3.0.x binaryTarget for Prisma 2026-07-08 06:33:26 +02:00
Carlos 59798f9315 chore: remove Tibia Simulator app 2026-07-07 04:26:25 +02:00
Carlos 8de02817d1 feat: add Tibia Simulator Em Breve 2026-07-04 01:28:20 +02:00
Carlos cb48cfdaa9 test: verify deploy flag 2026-07-03 20:56:19 +02:00
Carlos 4dd8bf7bc3 test: verify webhook v2 2026-07-03 20:37:45 +02:00
Carlos f1cc0528c3 test: verify webhook Gitea -> Coolify auto deploy 2026-07-03 20:24:57 +02:00
Carlos 5b08ece7df fix: restore tictactoe with correct content 2026-07-02 15:32:35 +02:00
Carlos 6e1c5a847e fix: correct tictactoe page content 2026-07-02 15:31:02 +02:00
Carlos faac88483e feat: migrate TikTakToe, ExpedicaoGold, HumanCity, Aurea, TerraVerse into 1000apps 2026-07-02 07:01:06 +02:00
Carlos ca74a0dbd2 chore: trigger rebuild 2026-07-02 05:42:43 +02:00
Carlos 58ec0ada47 feat: migrate TikTakToe, ExpedicaoGold, HumanCity, Aurea, TerraVerse into 1000apps 2026-07-02 04:37:54 +02:00
Hermes Agent f4494f007e feat: update 1000apps with corrected DATABASE_URL and configs 2026-07-02 02:35:15 +00:00
84 changed files with 978 additions and 433 deletions
+8 -2
View File
@@ -6,16 +6,20 @@ WORKDIR /app
FROM base AS deps
RUN apk add --no-cache libc6-compat openssl
COPY package.json package-lock.json* ./
COPY prisma ./prisma/
RUN npm install --frozen-lockfile
# Generate Prisma Client with correct binary targets
RUN npx prisma generate
# Build stage
FROM base AS builder
WORKDIR /app
ENV DATABASE_URL="postgresql://user:***@localhost:5432/1000apps"
# Install openssl for Prisma binary target detection
RUN apk add --no-cache openssl
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npx prisma generate
RUN npm run build
# Production stage
@@ -40,6 +44,8 @@ COPY --from=builder --chown=nextjs:nodejs /app/pages ./pages
COPY --from=builder --chown=nextjs:nodejs /app/lib ./lib
COPY --from=builder --chown=nextjs:nodejs /app/components ./components
COPY --from=builder --chown=nextjs:nodejs /app/styles ./styles
COPY entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/entrypoint.sh
EXPOSE 3000
CMD ["npm", "run", "start"]
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
+6
View File
@@ -0,0 +1,6 @@
# 1000Apps Platform
# Build trigger Thu Jul 2 05:42:43 CEST 2026
# Teste webhook Fri Jul 3 20:24:47 CEST 2026
# Teste webhook Fri Jul 3 20:24:56 CEST 2026
# Teste webhook 2 Fri Jul 3 20:37:45 CEST 2026
# Teste deploy flag Fri Jul 3 20:56:19 CEST 2026
+1 -1
View File
@@ -19,7 +19,7 @@ services:
depends_on:
- db
environment:
DATABASE_URL: postgresql://user:change_me_password@10.0.1.21:5432/1000apps
DATABASE_URL: postgresql://user:change_me_password@db:5432/1000apps
NEXTAUTH_SECRET: test-secret-2x4=
NEXTAUTH_URL: https://1000apps.fluxtechnologies.uk
GOOGLE_CLIENT_ID: ""
Executable
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
set -e
echo "Running Prisma migrations..."
npx prisma migrate deploy
echo "Starting Next.js..."
npm run start
+6 -2
View File
@@ -1,5 +1,9 @@
{
"compilerOptions": {
"jsx": "react-jsx"
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
}
+1 -2
View File
@@ -15,8 +15,7 @@
"next-auth": "^4.24.0",
"@next-auth/prisma-adapter": "^1.0.7",
"@prisma/client": "^5.0.0",
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.0"
"bcryptjs": "^2.4.3"
},
"devDependencies": {
"prisma": "^5.0.0",
+6 -10
View File
@@ -1,13 +1,9 @@
import { SessionProvider } from "next-auth/react"
import "../styles/globals.css"
// _app.jsx — SessionProvider de autenticação REMOVIDO (login desativado temporariamente).
// App agora abre direto no dashboard, sem verificação de sessão.
import "@/styles/globals.css"
export default function App({
Component,
pageProps: { session, ...pageProps }
}) {
export default function App({ Component, pageProps }) {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
<Component {...pageProps} />
)
}
}
-53
View File
@@ -1,53 +0,0 @@
import NextAuth from "next-auth"
import CredentialsProvider from "next-auth/providers/credentials"
import { PrismaAdapter } from "@next-auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"
import bcrypt from "bcryptjs"
const prisma = new PrismaClient()
export default NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
username: { label: "Username", type: "text", placeholder: "seu_usuario" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
if (!credentials?.username || !credentials?.password) return null
const user = await prisma.user.findUnique({
where: { username: credentials.username }
})
if (user && user.password) {
const isValid = await bcrypt.compare(credentials.password, user.password)
if (isValid) {
return {
id: user.id,
email: user.email,
name: user.name || user.username,
username: user.username
}
}
}
return null
}
})
],
secret: process.env.NEXTAUTH_SECRET,
session: { strategy: "jwt" },
pages: {
signIn: "/signin",
},
callbacks: {
async session({ session, token, user }) {
if (session.user) {
session.user.id = token.sub
}
return session
}
}
})
-54
View File
@@ -1,54 +0,0 @@
import { PrismaClient } from "@prisma/client"
import bcrypt from "bcryptjs"
const prisma = new PrismaClient()
export default async function handler(req, res) {
if (req.method !== "POST") {
return res.status(405).json({ error: "Método não permitido" })
}
const { username, password } = req.body
if (!username || !password) {
return res.status(400).json({ error: "Username e senha são obrigatórios" })
}
try {
const user = await prisma.user.findUnique({
where: { username }
})
if (!user) {
return res.status(401).json({ error: "Usuário não encontrado" })
}
if (!user.password) {
return res.status(401).json({ error: "Senha não configurada" })
}
const isValid = await bcrypt.compare(password, user.password)
if (!isValid) {
return res.status(401).json({ error: "Senha incorreta" })
}
// Create a JWT token manually for the session
const token = require("jsonwebtoken").sign(
{ sub: user.id, email: user.email, name: user.name || user.username },
process.env.NEXTAUTH_SECRET || "test-secret-2x4=",
{ expiresIn: "30d" }
)
return res.status(200).json({
ok: true,
user: { id: user.id, email: user.email, name: user.name || user.username },
token
})
} catch (error) {
console.error("Signin error:", error)
return res.status(500).json({ error: "Erro interno do servidor" })
} finally {
await prisma.$disconnect()
}
}
-53
View File
@@ -1,53 +0,0 @@
import { PrismaClient } from "@prisma/client"
import bcrypt from "bcryptjs"
const prisma = new PrismaClient()
export default async function handler(req, res) {
if (req.method !== "POST") {
return res.status(405).json({ error: "Método não permitido" })
}
const { username, password } = req.body
if (!username || !password) {
return res.status(400).json({ error: "Username e senha são obrigatórios" })
}
if (password.length < 6) {
return res.status(400).json({ error: "Senha deve ter pelo menos 6 caracteres" })
}
try {
// Check if username exists
const existingUser = await prisma.user.findUnique({
where: { username }
})
if (existingUser) {
return res.status(400).json({ error: "Nome de usuário já existe" })
}
// Hash password
const hashedPassword = await bcrypt.hash(password, 12)
// Create user
const user = await prisma.user.create({
data: {
username,
password: hashedPassword,
email: `${username}@1000apps.local`
}
})
return res.status(201).json({
message: "Usuário criado com sucesso",
user: { id: user.id, username: user.username }
})
} catch (error) {
console.error("Signup error:", error)
return res.status(500).json({ error: "Erro interno do servidor" })
} finally {
await prisma.$disconnect()
}
}
+11
View File
@@ -0,0 +1,11 @@
export default function Aurea() {
return (
<div style={{minHeight:'100vh',background:'linear-gradient(135deg,#1a0a00,#3d2000)',display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',color:'#FFD700',fontFamily:'serif',textAlign:'center',padding:'2rem'}}>
<div style={{fontSize:'4rem',marginBottom:'1rem'}}>👑</div>
<h1 style={{fontSize:'3rem',marginBottom:'0.5rem'}}>Aurea</h1>
<p style={{fontSize:'1.2rem',color:'#c8a400',marginBottom:'2rem'}}>O Império Começa Aqui</p>
<p style={{color:'#a0826d',maxWidth:'500px',marginBottom:'2rem'}}>Simulador de vida onde cada escolha te leva mais perto da riqueza. Construa impérios, domine o mercado.</p>
<span style={{background:'#FFD700',color:'#1a0a00',padding:'0.5rem 2rem',borderRadius:'20px',fontWeight:'bold'}}>Em Breve</span>
</div>
)
}
+11
View File
@@ -0,0 +1,11 @@
export default function ExpedicaoGold() {
return (
<div style={{minHeight:'100vh',background:'linear-gradient(135deg,#0a1a00,#1a3a00)',display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',color:'#90EE90',fontFamily:'sans-serif',textAlign:'center',padding:'2rem'}}>
<div style={{fontSize:'4rem',marginBottom:'1rem'}}>🗺</div>
<h1 style={{fontSize:'3rem',marginBottom:'0.5rem'}}>Expedição Gold</h1>
<p style={{fontSize:'1.2rem',color:'#50aa50',marginBottom:'2rem'}}>Conquiste o Ouro do Mundo</p>
<p style={{color:'#668866',maxWidth:'500px',marginBottom:'2rem'}}>Expedições terrestres, marítimas, espaciais e espirituais. Cada jornada é única. Cada conquista, sua.</p>
<span style={{background:'#90EE90',color:'#0a1a00',padding:'0.5rem 2rem',borderRadius:'20px',fontWeight:'bold'}}>Em Breve no 1000Apps</span>
</div>
)
}
+11
View File
@@ -0,0 +1,11 @@
export default function HumanCity() {
return (
<div style={{minHeight:'100vh',background:'linear-gradient(135deg,#050520,#0a0a40)',display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',color:'#a0a0ff',fontFamily:'sans-serif',textAlign:'center',padding:'2rem'}}>
<div style={{fontSize:'4rem',marginBottom:'1rem'}}>🏙</div>
<h1 style={{fontSize:'3rem',marginBottom:'0.5rem'}}>Human City</h1>
<p style={{fontSize:'1.2rem',color:'#7070cc',marginBottom:'2rem'}}>Simule. Construa. Viva.</p>
<p style={{color:'#5555aa',maxWidth:'500px',marginBottom:'2rem'}}>Simulação de vida estilo The Sims. Crie personagens, construa cidades, escreva sua história.</p>
<span style={{background:'#a0a0ff',color:'#050520',padding:'0.5rem 2rem',borderRadius:'20px',fontWeight:'bold'}}>Em Desenvolvimento</span>
</div>
)
}
+11
View File
@@ -0,0 +1,11 @@
export default function TerraVerse() {
return (
<div style={{minHeight:'100vh',background:'linear-gradient(135deg,#000510,#001a3a)',display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',color:'#00d4ff',fontFamily:'sans-serif',textAlign:'center',padding:'2rem'}}>
<div style={{fontSize:'4rem',marginBottom:'1rem'}}>🌍</div>
<h1 style={{fontSize:'3rem',marginBottom:'0.5rem'}}>TerraVerse</h1>
<p style={{fontSize:'1.2rem',color:'#0099bb',marginBottom:'2rem'}}>O Novo Mundo te Espera</p>
<p style={{color:'#6699aa',maxWidth:'500px',marginBottom:'2rem'}}>Game de realidade aumentada que vai revolucionar como você explora, conquista e monetiza o mundo real.</p>
<span style={{background:'#00d4ff',color:'#000510',padding:'0.5rem 2rem',borderRadius:'20px',fontWeight:'bold'}}>Em Breve</span>
</div>
)
}
+61
View File
@@ -0,0 +1,61 @@
import { useState } from 'react';
export default function TicTacToe() {
const [board, setBoard] = useState(Array(9).fill(null));
const [xIsNext, setXIsNext] = useState(true);
const winner = calculateWinner(board);
const status = winner
? `Winner: ${winner}`
: `Next player: ${xIsNext ? 'X' : 'O'}`;
function handleClick(i) {
const boardCopy = [...board];
if (winner || boardCopy[i]) return;
boardCopy[i] = xIsNext ? 'X' : 'O';
setBoard(boardCopy);
setXIsNext(!xIsNext);
}
function calculateWinner(squares) {
const lines = [
[0, 1, 2], [3, 4, 5], [6, 7, 8],
[0, 3, 6], [1, 4, 7], [2, 5, 8],
[0, 4, 8], [2, 4, 6],
];
for (let [a, b, c] of lines) {
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
const renderSquare = (i) => (
<button className="square" onClick={() => handleClick(i)}>
{board[i]}
</button>
);
const statusStyle = { marginBottom: '1rem', fontSize: '1.2rem' };
const boardStyle = { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '4px', width: '300px', margin: '0 auto' };
const squareStyle = { background: '#fff', border: '1px solid #999', fontSize: '2rem', width: '100%', height: '80px', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer' };
return (
<div style={{ padding: '2rem', fontFamily: 'sans-serif' }}>
<h1>Tic Tac Toe</h1>
<div style={statusStyle}>{status}</div>
<div style={boardStyle}>
{[0, 1, 2, 3, 4, 5, 6, 7, 8].map(i => (
<div key={i} style={squareStyle}>
{renderSquare(i)}
</div>
))}
</div>
{winner && (
<button onClick={() => { setBoard(Array(9).fill(null)); setXIsNext(true); }}>
Play Again
</button>
)}
</div>
);
}
-2
View File
@@ -1,5 +1,4 @@
import { useState, useEffect } from "react"
import { useSession } from "next-auth/react"
import { useRouter } from "next/router"
// Top 100 Notícias - Trends Brasil e Mundo (Junho 2026)
@@ -122,7 +121,6 @@ const weeklyTrends = [
]
export default function TrendsNews() {
const { data: session } = useSession()
const router = useRouter()
const [selectedCategory, setSelectedCategory] = useState("TODAS")
const [trendingNews, setTrendingNews] = useState(weeklyTrends)
-2
View File
@@ -1,5 +1,4 @@
import { useState, useEffect } from "react"
import { useSession } from "next-auth/react"
import { useRouter } from "next/router"
// Copa do Mundo 2026 - Grupos e Classificação (48 seleções, 12 grupos de 4)
@@ -95,7 +94,6 @@ const worldCupNews = [
]
export default function WorldCup2026() {
const { data: session } = useSession()
const router = useRouter()
const [selectedGroup, setSelectedGroup] = useState("TODOS")
const [selectedCategory, setSelectedCategory] = useState("TODAS")
+112 -83
View File
@@ -1,70 +1,80 @@
import { useSession, signOut } from "next-auth/react"
import React from "react"
import { useState } from "react"
import { useRouter } from "next/router"
import { useState, useEffect } from "react"
import AvatarSelector from "../components/AvatarSelector"
import CategoryMenu from "../components/CategoryMenu"
// Apps organizados por categoria
const ALL_APPS = [
// Produtividade
{ id: 1, name: "Tendências Semanais", desc: "Notícias em alta • Top 100", path: "/apps/trendsnews", icon: "📈", category: "produtividade", favorite: false },
{ id: 2, name: "Conversor", desc: "Conversão de unidades", path: "/apps/converter", icon: "🔄", category: "produtividade", favorite: false },
{ id: 3, name: "Calculadora", desc: "Calculos rápidos", path: "/apps/calculator", icon: "🧮", category: "produtividade", favorite: false },
// Utilidades
{ id: 4, name: "Relógio", desc: "Relógio mundial", path: "/apps/clock", icon: "⏰", category: "utilidades", favorite: false },
{ id: 5, name: "Gerador Senhas", desc: "Senhas seguras", path: "/apps/password-generator", icon: "🔐", category: "utilidades", favorite: false },
{ id: 6, name: "QR Code", desc: "Gerador QR", path: "/apps/qrcode", icon: "📱", category: "utilidades", favorite: false },
{ id: 7, name: "Cronômetro", desc: "Tempo e alarmes", path: "/apps/timer", icon: "⏱️", category: "utilidades", favorite: false },
// Games
{ id: 8, name: "Jogo da Velha", desc: "Jogo clássico", path: "/apps/tictactoe", icon: "❌", category: "games", favorite: false },
// Ferramentas
{ id: 9, name: "Editor Texto", desc: "Texto simples", path: "/apps/text-editor", icon: "📝", category: "ferramentas", favorite: false },
{ id: 10, name: "Conversor Moedas", desc: "Câmbio em tempo real", path: "/apps/currency", icon: "💱", category: "ferramentas", favorite: false },
// Educação
{ id: 11, name: "Dicionário", desc: "Significados", path: "/apps/dictionary", icon: "📖", category: "educacao", favorite: false },
{ id: 12, name: "Tradutor", desc: "Multi-idiomas", path: "/apps/translator", icon: "🌐", category: "educacao", favorite: false },
// Esportes
{ id: 13, name: "Copa do Mundo 2026", desc: "Tabela, grupos, notícias", path: "/apps/worldcup2026", icon: "⚽", category: "esportes", favorite: false }
]
import AvatarSelector from "@/components/dashboard/AvatarSelector"
import CategoryMenu from "@/components/dashboard/CategoryMenu"
export default function Dashboard() {
const { data: session } = useSession()
const router = useRouter()
const [selectedAvatar, setSelectedAvatar] = useState("😀")
const [selectedCategory, setSelectedCategory] = useState("all")
const [favorites, setFavorites] = useState([])
const [apps, setApps] = useState(ALL_APPS)
const [confirmPassword, setConfirmPassword] = useState("")
const [loading, setLoading] = useState(false)
// Apps organized by category
const ALL_APPS = [
// Produtividade
{ id: 1, name: "Tendências Semanais", desc: "Notícias em alta • Top 100", path: "/apps/trendsnews", icon: "📈", category: "produtividade", favorite: false },
{ id: 2, name: "Conversor", desc: "Conversão de unidades", path: "/apps/converter", icon: "🔄", category: "produtividade", favorite: false },
{ id: 3, name: "Calculadora", desc: "Cálculos rápidos", path: "/apps/calculator", icon: "🧮", category: "produtividade", favorite: false },
// Utilidades
{ id: 4, name: "Relógio", desc: "Relógio mundial", path: "/apps/clock", icon: "⏰", category: "utilidades", favorite: false },
{ id: 5, name: "Gerador Senhas", desc: "Senhas seguras", path: "/apps/password-generator", icon: "🔐", category: "utilidades", favorite: false },
{ id: 6, name: "QR Code", desc: "Gerador QR", path: "/apps/qrcode", icon: "📱", category: "utilidades", favorite: false },
{ id: 7, name: "Cronômetro", desc: "Tempo e alarmes", path: "/apps/timer", icon: "⏱️", category: "utilidades", favorite: false },
// Games
{ id: 8, name: "Jogo da Velha", desc: "Jogo da velha clássico 2 jogadores", path: "/apps/tictactoe", icon: "⭕", category: "games", favorite: false },
{ id: 14, name: "Expedição Gold", desc: "Expedições para conquistar ouro", path: "/apps/expedicao", icon: "🗺️", category: "games", favorite: false },
{ id: 15, name: "Human City", desc: "Simulação de cidade estilo Sims", path: "/apps/humancity", icon: "🏙️", category: "games", favorite: false },
{ id: 16, name: "Aurea", desc: "Simulador de vida e riqueza", path: "/apps/aurea", icon: "👑", category: "games", favorite: false },
{ id: 17, name: "TerraVerse", desc: "Game de realidade aumentada", path: "/apps/terraverse", icon: "🌍", category: "games", favorite: false },
// Ferramentas
{ id: 9, name: "Editor Texto", desc: "Texto simples", path: "/apps/text-editor", icon: "📝", category: "ferramentas", favorite: false },
{ id: 10, name: "Conversor Moedas", desc: "Câmbio em tempo real", path: "/apps/currency", icon: "💱", category: "ferramentas", favorite: false },
// Educação
{ id: 11, name: "Dicionário", desc: "Significados", path: "/apps/dictionary", icon: "📖", category: "educacao", favorite: false },
{ id: 12, name: "Tradutor", desc: "Multi-idiomas", path: "/apps/translator", icon: "🌐", category: "educacao", favorite: false },
// Esportes
{ id: 13, name: "Copa do Mundo 2026", desc: "Tabela, grupos, notícias", path: "/apps/worldcup2026", icon: "⚽", category: "esportes", favorite: false }
]
// Carregar favoritos do localStorage
useEffect(() => {
const savedFavorites = localStorage.getItem("favorites")
const savedAvatar = localStorage.getItem("selectedAvatar")
if (savedFavorites) {
setFavorites(JSON.parse(savedFavorites))
}
if (savedAvatar) {
setSelectedAvatar(savedAvatar)
React.useEffect(() => {
const saved = localStorage.getItem("favorites")
if (saved) {
try {
setFavorites(JSON.parse(saved))
} catch (e) {
console.warn("Failed to parse favorites from localStorage", e)
}
}
}, [])
// Salvar favoritos no localStorage
useEffect(() => {
React.useEffect(() => {
localStorage.setItem("favorites", JSON.stringify(favorites))
}, [favorites])
// Salvar avatar no localStorage
useEffect(() => {
React.useEffect(() => {
localStorage.setItem("selectedAvatar", selectedAvatar)
}, [selectedAvatar])
// Carregar avatar do localStorage
React.useEffect(() => {
const saved = localStorage.getItem("selectedAvatar")
if (saved) setSelectedAvatar(saved)
}, [])
const toggleFavorite = (appId) => {
setFavorites(prev =>
prev.includes(appId)
setFavorites(prev =>
prev.includes(appId)
? prev.filter(id => id !== appId)
: [...prev, appId]
)
@@ -72,11 +82,11 @@ export default function Dashboard() {
// Filtrar apps
const filteredApps = selectedCategory === "all"
? apps
: apps.filter(app => app.category === selectedCategory)
? ALL_APPS
: ALL_APPS.filter(app => app.category === selectedCategory)
// Apps favoritos
const favoriteApps = apps.filter(app => favorites.includes(app.id))
const favoriteApps = ALL_APPS.filter(app => favorites.includes(app.id))
return (
<div className="min-h-screen bg-gray-50">
@@ -84,15 +94,18 @@ export default function Dashboard() {
<nav className="bg-white shadow-lg p-4 flex justify-between items-center">
<div className="flex items-center gap-4">
<h1 className="text-2xl font-bold">1000Apps</h1>
<span className="text-xs bg-green-100 text-green-800 px-2 py-1 rounded-full font-semibold">FREE</span>
<span className="text-xs bg-green-100 text-green-800 px-2 py-1 rounded-full font-semibold">
FREE
</span>
</div>
<div className="flex items-center gap-4">
<AvatarSelector selectedAvatar={selectedAvatar} onSelect={setSelectedAvatar} />
<span className="text-gray-600">Olá, {session?.user?.name || session?.user?.username || "usuário"}!</span>
<button onClick={() => signOut()} className="text-red-600 hover:underline font-medium">Sair</button>
<AvatarSelector
selectedAvatar={selectedAvatar}
onSelect={setSelectedAvatar}
/>
</div>
</nav>
<main className="max-w-6xl mx-auto p-6">
{/* Favoritos Section */}
{favorites.length > 0 && (
@@ -101,45 +114,67 @@ export default function Dashboard() {
<span></span> Favoritos
</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{favoriteApps.map(app => (
<button
key={app.id}
onClick={() => router.push(app.path)}
className="bg-white rounded-xl p-6 shadow-md hover:shadow-lg transition-all text-left hover:scale-105 cursor-pointer border-2 border-yellow-200"
>
<div className="text-3xl mb-2">{app.icon}</div>
<h3 className="font-semibold text-gray-800 text-sm">{app.name}</h3>
<p className="text-xs text-gray-500 mt-1">{app.desc}</p>
</button>
))}
{favorites.map((favId) => {
const app = ALL_APPS.find((a) => a.id === favId)
if (!app) return null
return (
<div key={app.id} className="relative">
<button
onClick={() => router.push(app.path)}
className="bg-white rounded-xl p-6 shadow-md hover:shadow-lg transition-all text-left hover:scale-105 cursor-pointer w-full"
>
<div className="text-3xl mb-2">{app.icon}</div>
<h3 className="font-semibold text-gray-800 text-sm">{app.name}</h3>
<p className="text-xs text-gray-500 mt-1">{app.desc}</p>
</button>
<button
onClick={() => toggleFavorite(app.id)}
className="absolute top-2 right-2 text-lg"
title={favorites.includes(favId) ? "Remover dos favoritos" : "Adicionar aos favoritos"}
>
{favorites.includes(favId) ? "⭐" : "☆"}
</button>
</div>
)
})}
</div>
</div>
)}
{/* Category Filter */}
<CategoryMenu selectedCategory={selectedCategory} onSelect={setSelectedCategory} />
<CategoryMenu
selectedCategory={selectedCategory}
onSelect={setSelectedCategory}
/>
{/* Apps Grid */}
<div className="mb-6">
<h2 className="text-3xl font-bold text-gray-800 mb-2">
{selectedCategory === "all" ? "Todos os Apps" :
selectedCategory === "produtividade" ? "Produtividade" :
selectedCategory === "utilidades" ? "Utilidades" :
selectedCategory === "games" ? "Games" :
selectedCategory === "ferramentas" ? "Ferramentas" :
selectedCategory === "educacao" ? "Educação" :
selectedCategory === "entretenimento" ? "Entretenimento" :
selectedCategory === "esportes" ? "Esportes" : "Apps"}
{selectedCategory === "all"
? "Todos os Apps"
: selectedCategory === "produtividade"
? "Produtividade"
: selectedCategory === "utilidades"
? "Utilidades"
: selectedCategory === "games"
? "Games"
: selectedCategory === "ferramentas"
? "Ferramentas"
: selectedCategory === "educacao"
? "Educação"
: selectedCategory === "esportes"
? "Esportes"
: "Apps"}
</h2>
<p className="text-gray-500">
{selectedCategory === "all"
? `${apps.length} apps disponíveis`
{selectedCategory === "all"
? `${ALL_APPS.length} apps disponíveis`
: `${filteredApps.length} apps nesta categoria`}
</p>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{filteredApps.map(app => (
{filteredApps.map((app) => (
<div key={app.id} className="relative">
<button
onClick={() => router.push(app.path)}
@@ -162,10 +197,4 @@ export default function Dashboard() {
</main>
</div>
)
}
export async function getServerSideProps() {
return {
props: {},
}
}
+12 -137
View File
@@ -1,139 +1,14 @@
// Updated: force rebuild for cache invalidation
// index.jsx — rota "/" agora redireciona direto para o dashboard.
// O login foi removido; o app abre sem autenticação.
export default function Home() {
return (
<div className="bg-gray-50 min-h-screen">
{/* Header */}
<header className="bg-white shadow-sm">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 flex justify-between items-center">
<h1 className="text-2xl font-bold text-gray-900">1000Apps</h1>
<div className="space-x-4">
<a href="/signin" className="text-gray-600 hover:text-gray-900 font-medium">Entrar</a>
<a href="/signup" className="bg-blue-600 text-white px-4 py-2 rounded-lg font-medium hover:bg-blue-700 transition">Cadastrar</a>
</div>
</div>
</header>
return null
}
{/* Hero Section */}
<section className="bg-gradient-to-r from-blue-600 to-purple-600 text-white py-20">
<div className="max-w-4xl mx-auto px-4 text-center">
<h2 className="text-5xl font-bold mb-6">Seu Canivete Suíço Digital</h2>
<p className="text-xl mb-8 opacity-90">
Mais de 1000 ferramentas úteis, games e produtividade - tudo online, sem instalar nada!
</p>
<div className="flex justify-center space-x-4">
<a href="/signup" className="bg-white text-blue-600 px-8 py-3 rounded-lg font-semibold text-lg hover:bg-gray-100 transition">Começar Grátis</a>
<a href="#apps" className="border border-white text-white px-8 py-3 rounded-lg font-semibold text-lg hover:bg-white/10 transition">Explorar Apps</a>
</div>
</div>
</section>
{/* Categories */}
<section id="apps" className="py-16 px-4">
<div className="max-w-6xl mx-auto">
<h3 className="text-3xl font-bold text-center mb-12 text-gray-800">Categorias</h3>
<div className="grid md:grid-cols-3 gap-8">
<div className="bg-white rounded-xl p-8 card-hover transition transform hover:-translate-y-1">
<div className="w-14 h-14 bg-blue-100 rounded-lg flex items-center justify-center mb-4">
<svg className="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5l7 7-7 7" />
</svg>
</div>
<h4 className="text-xl font-semibold mb-2">Produtividade</h4>
<p className="text-gray-600 mb-4">Ferramentas para otimizar seu tempo e trabalho</p>
<span className="text-blue-600 font-medium">Ver apps </span>
</div>
<div className="bg-white rounded-xl p-8 card-hover transition transform hover:-translate-y-1">
<div className="w-14 h-14 bg-green-100 rounded-lg flex items-center justify-center mb-4">
<svg className="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M11 4a2 2 0 114 0v5a2 2 0 01-2 2H7a2 2 0 00-2-2V6a2 2 0 012-2h2z" />
</svg>
</div>
<h4 className="text-xl font-semibold mb-2">Utilidades</h4>
<p className="text-gray-600 mb-4">Ferramentas práticas para o dia a dia</p>
<span className="text-green-600 font-medium">Ver apps </span>
</div>
<div className="bg-white rounded-xl p-8 card-hover transition transform hover:-translate-y-1">
<div className="w-14 h-14 bg-purple-100 rounded-lg flex items-center justify-center mb-4">
<svg className="w-8 h-8 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M14.752 12.795a7.95 7.95 0 001.904-5.452 7.95 7.95 0 00-7.95 7.95A7.95 7.95 0 0012.795 14.752" />
</svg>
</div>
<h4 className="text-xl font-semibold mb-2">Games</h4>
<p className="text-gray-600 mb-4">Diversão rápida e envolvente</p>
<span className="text-purple-600 font-medium">Ver apps </span>
</div>
</div>
</div>
</section>
{/* Features */}
<section className="bg-gray-100 py-16 px-4">
<div className="max-w-6xl mx-auto">
<h3 className="text-3xl font-bold text-center mb-12 text-gray-800">Por que 1000Apps?</h3>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
<div className="text-center">
<div className="w-16 h-16 bg-blue-600 rounded-full flex items-center justify-center mx-auto mb-4">
<span className="text-2xl font-bold text-white">01</span>
</div>
<h5 className="font-semibold mb-2">Nada para Instalar</h5>
<p className="text-sm text-gray-600">Tudo rodando direto no navegador</p>
</div>
<div className="text-center">
<div className="w-16 h-16 bg-green-600 rounded-full flex items-center justify-center mx-auto mb-4">
<span className="text-2xl font-bold text-white">02</span>
</div>
<h5 className="font-semibold mb-2">Sempre Atualizado</h5>
<p className="text-sm text-gray-600">Conteúdo fresco todo dia</p>
</div>
<div className="text-center">
<div className="w-16 h-16 bg-purple-600 rounded-full flex items-center justify-center mx-auto mb-4">
<span className="text-2xl font-bold text-white">03</span>
</div>
<h5 className="font-semibold mb-2">Curadoria Inteligente</h5>
<p className="text-sm text-gray-600"> apps úteis e testados</p>
</div>
<div className="text-center">
<div className="w-16 h-16 bg-orange-600 rounded-full flex items-center justify-center mx-auto mb-4">
<span className="text-2xl font-bold text-white">04</span>
</div>
<h5 className="font-semibold mb-2">Grátis para Sempre</h5>
<p className="text-sm text-gray-600">Acesso completo sem pagar nada</p>
</div>
</div>
</div>
</section>
{/* CTA Final */}
<section className="py-20 px-4">
<div className="max-w-3xl mx-auto text-center">
<h3 className="text-4xl font-bold mb-4 text-gray-900">Pronto para começar?</h3>
<p className="text-lg text-gray-600 mb-8">
Junte-se a milhares de usuários que economizam tempo com 1000Apps
</p>
<a href="/signup" className="inline-block bg-blue-600 text-white px-10 py-4 rounded-lg font-semibold text-xl hover:bg-blue-700 transition transform hover:scale-105">
Criar Conta Grátis
</a>
</div>
</section>
{/* Footer */}
<footer className="bg-gray-900 text-white py-8 px-4">
<div className="max-w-6xl mx-auto text-center">
<p className="mb-4">&copy; 2026 1000Apps. Todos os direitos reservados.</p>
<div className="space-x-6 text-sm">
<a href="#" className="hover:text-gray-300">Termos</a>
<a href="#" className="hover:text-gray-300">Privacidade</a>
<a href="#" className="hover:text-gray-300">Contato</a>
</div>
</div>
</footer>
</div>
)
}
export async function getServerSideProps() {
return {
redirect: {
destination: "/dashboard",
permanent: false,
},
}
}
@@ -0,0 +1 @@
{}
@@ -0,0 +1,39 @@
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"username" TEXT,
"name" TEXT,
"image" TEXT,
"password" TEXT,
"role" TEXT NOT NULL DEFAULT 'user',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "App" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"version" TEXT NOT NULL DEFAULT '1.0.0',
"entryUrl" TEXT NOT NULL,
"ownerId" TEXT NOT NULL,
"isPublic" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "App_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
-- AddForeignKey
ALTER TABLE "App" ADD CONSTRAINT "App_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+1
View File
@@ -1,5 +1,6 @@
generator client {
provider = "prisma-client-js"
binaryTargets = ["linux-musl-openssl-3.0.x"]
}
datasource db {
+1
View File
@@ -0,0 +1 @@
# App-specific components (placeholder)
+12
View File
@@ -0,0 +1,12 @@
// CardApp — cartão de mini app no grid do dashboard.
// Placeholder arquitetural: ainda não utilizado ativamente.
export default function CardApp({ title, icon, description, href, onClick }) {
return (
<a href={href || "#"} onClick={onClick}
className="block rounded-xl border border-gray-200 p-4 hover:shadow-md transition">
<div className="text-2xl mb-2">{icon}</div>
<h3 className="font-semibold text-gray-900">{title}</h3>
{description && <p className="text-sm text-gray-600 mt-1">{description}</p>}
</a>
)
}
+11
View File
@@ -0,0 +1,11 @@
// CategoryCard — cartão de categoria na navegação.
// Placeholder arquitetural.
export default function CategoryCard({ label, icon, active, onSelect }) {
return (
<button onClick={() => onSelect && onSelect(label)}
className={"flex flex-col items-center p-3 rounded-lg " + (active ? "bg-blue-50 text-blue-600" : "text-gray-600")}>
<span className="text-xl">{icon}</span>
<span className="text-xs mt-1">{label}</span>
</button>
)
}
+15
View File
@@ -0,0 +1,15 @@
// ConfirmDialog — diálogo de confirmação.
// Placeholder arquitetural.
export default function ConfirmDialog({ open, title, message, onConfirm, onCancel }) {
if (!open) return null
return (
<Modal open={open}>
<h2 className="font-semibold mb-2">{title || "Confirma?"}</h2>
<p className="text-sm text-gray-600 mb-4">{message}</p>
<div className="flex justify-end space-x-2">
<button onClick={onCancel} className="px-3 py-1 text-gray-600">Cancelar</button>
<button onClick={onConfirm} className="px-3 py-1 bg-red-600 text-white rounded">Confirmar</button>
</div>
</Modal>
)
}
+5
View File
@@ -0,0 +1,5 @@
// EmptyState — estado vazio.
// Placeholder arquitetural.
export default function EmptyState({ message }) {
return <div className="p-4 text-center text-gray-400">{message || "Nada por aqui."}</div>
}
+10
View File
@@ -0,0 +1,10 @@
// ErrorState — estado de erro.
// Placeholder arquitetural.
export default function ErrorState({ message, onRetry }) {
return (
<div className="p-4 text-center text-red-600">
<p>{message || "Algo deu errado."}</p>
{onRetry && <button onClick={onRetry} className="mt-2 text-sm underline">Tentar novamente</button>}
</div>
)
}
+9
View File
@@ -0,0 +1,9 @@
// Footer — rodapé global.
// Placeholder arquitetural.
export default function Footer() {
return (
<footer className="bg-gray-900 text-white py-8 text-center text-sm">
&copy; 2026 1000Apps. Todos os direitos reservados.
</footer>
)
}
+12
View File
@@ -0,0 +1,12 @@
// Header — cabeçalho global do app.
// Placeholder arquitetural (não renderiza nada ainda).
export default function Header({ title, children }) {
return (
<header className="bg-white shadow-sm">
<div className="max-w-7xl mx-auto px-4 py-4 flex justify-between items-center">
<h1 className="text-xl font-bold">{title || "1000Apps"}</h1>
{children}
</div>
</header>
)
}
+5
View File
@@ -0,0 +1,5 @@
// Loading — indicador de carregamento.
// Placeholder arquitetural.
export default function Loading({ label }) {
return <div className="p-4 text-center text-gray-500">{label || "Carregando..."}</div>
}
+10
View File
@@ -0,0 +1,10 @@
// Modal — janela modal genérica.
// Placeholder arquitetural.
export default function Modal({ open, onClose, children }) {
if (!open) return null
return (
<div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50" onClick={onClose}>
<div className="bg-white rounded-lg p-6" onClick={(e) => e.stopPropagation()}>{children}</div>
</div>
)
}
+9
View File
@@ -0,0 +1,9 @@
// SearchBar — barra de busca de mini apps.
// Placeholder arquitetural.
export default function SearchBar({ value, onChange, placeholder }) {
return (
<input type="search" value={value || ""} onChange={(e) => onChange && onChange(e.target.value)}
placeholder={placeholder || "Buscar..."}
className="w-full p-2 border border-gray-300 rounded-lg" />
)
}
+11
View File
@@ -0,0 +1,11 @@
// Barrel export dos componentes compartilhados.
export { default as CardApp } from "./CardApp"
export { default as CategoryCard } from "./CategoryCard"
export { default as SearchBar } from "./SearchBar"
export { default as Header } from "./Header"
export { default as Footer } from "./Footer"
export { default as Loading } from "./Loading"
export { default as EmptyState } from "./EmptyState"
export { default as ErrorState } from "./ErrorState"
export { default as Modal } from "./Modal"
export { default as ConfirmDialog } from "./ConfirmDialog"
+1
View File
@@ -0,0 +1 @@
# Layout components (placeholder)
+27
View File
@@ -0,0 +1,27 @@
# Legacy / Backup
Esta pasta contém código **preservado** da fase de autenticação e da Landing Page.
**Nenhum arquivo aqui participa do build do Next.js** (estão fora de `pages/`).
Mantido para reativação futura sem perda de histórico (conforme decisão de 2026-07-11).
## Estrutura
- `legacy/auth/` → telas de login/cadastro e configuração NextAuth
- `legacy/landing/` → Landing Page original (era a rota `/`)
- `legacy/dashboard.jsx.bak` → cópia de `pages/dashboard.jsx` antes da remoção do `useSession`
## Dependências necessárias para reativar o auth
Os arquivos em `legacy/auth/` importam:
- `next-auth` ^4.24.0
- `@next-auth/prisma-adapter` ^1.0.7
- `bcryptjs` ^2.4.3
- `@prisma/client` (já usado pelo app ativo)
> Estas dependências permanecem em `package.json` **exatamente para não quebrar este backup**.
> Nenhuma rota ativa (fora de `legacy/`) as utiliza.
## Como reativar
1. Mover `legacy/auth/*` de volta para `pages/` (ex.: `signin.jsx`, `signup.jsx`, `api/auth/[...nextauth].js`).
2. Mover `legacy/landing/index.jsx` para `pages/index.jsx` (ou `pages/landing.jsx` + ajustar redirect).
3. Restaurar `SessionProvider` em `pages/_app.jsx` e imports de `useSession` onde necessário.
4. `npm install` (garante que as deps acima estão presentes) e `npm run build`.
+9
View File
@@ -0,0 +1,9 @@
# Auth Legacy
Arquivos preservados do fluxo de login (NextAuth):
- `signin.jsx` — tela de login; usa `signIn` de `next-auth/react`
- `signup.jsx` — tela de cadastro; faz POST para `/api/auth/register`
- `nextauth.js` — configuração do NextAuth (CredentialsProvider + Prisma + bcrypt)
Nenhum destes é compilado pelo Next.js enquanto estiver fora de `pages/`.
+62
View File
@@ -0,0 +1,62 @@
// API DE AUTENTICAÇÃO DESATIVADA TEMPORARIAMENTE (2026-07-11)
// Arquivo PRESERVADO para reativação futura. Nenhum componente/chamada
// ativa depende desta rota após a remoção do fluxo de login.
import NextAuth from "next-auth"
import CredentialsProvider from "next-auth/providers/credentials"
import { PrismaClient } from "@prisma/client"
import bcrypt from "bcryptjs"
const prisma = new PrismaClient()
export default NextAuth({
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
username: { label: "Username", type: "text" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
if (!credentials?.username || !credentials?.password) return null
const user = await prisma.user.findUnique({
where: { username: credentials.username }
})
if (!user) return null
const isValid = await bcrypt.compare(credentials.password, user.password)
if (!isValid) return null
return {
id: user.id,
username: user.username,
email: user.email,
name: user.name,
// optional: role, image, etc.
}
}
})
],
secret: process.env.NEXTAUTH_SECRET,
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60 // 30 days
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id
token.username = user.username
token.email = user.email
token.name = user.name
}
return token
},
async session({ session, token }) {
if (token) {
session.user.id = token.id
session.user.username = token.username
session.user.email = token.email
session.user.name = token.name
}
return session
}
}
})
+14 -12
View File
@@ -1,5 +1,9 @@
// LOGIN DESATIVADO TEMPORARIAMENTE (2026-07-11)
// Arquivo PRESERVADO para reativação futura. Nada neste app o referencia
// ativamente (sem links, sem SessionProvider, sem getSession).
import { useState } from "react"
import { useRouter } from "next/router"
import { signIn } from "next-auth/react"
export default function SignIn() {
const [username, setUsername] = useState("")
@@ -12,22 +16,20 @@ export default function SignIn() {
e.preventDefault()
setError("")
setLoading(true)
try {
const res = await fetch("/api/auth/signin", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password })
const result = await signIn("credentials", {
username,
password,
redirect: false,
callbackUrl: "/dashboard"
})
const data = await res.json()
if (res.ok && data.ok) {
// Set session cookie
document.cookie = `next-auth.session-token=${data.token}; path=/; SameSite=Lax`
if (result?.ok) {
router.push("/dashboard")
router.refresh()
} else {
setError(data.error || "Erro ao entrar")
setError(result?.error || "Erro ao entrar")
}
} catch (err) {
setError("Erro de conexão")
+20 -20
View File
@@ -1,3 +1,5 @@
// CADASTRO DESATIVADO TEMPORARIAMENTE (2026-07-11)
// Arquivo PRESERVADO para reativação futura. Não há mais link ativo para cá.
import { useState } from "react"
import { useRouter } from "next/router"
@@ -12,28 +14,26 @@ export default function SignUp() {
const handleSubmit = async (e) => {
e.preventDefault()
setError("")
if (password !== confirmPassword) {
setError("Senhas não coincidem")
return
}
if (password.length < 6) {
setError("Senha deve ter pelo menos 6 caracteres")
return
}
setLoading(true)
try {
const res = await fetch("/api/auth/signup", {
if (password !== confirmPassword) {
setError("Senhas não coincidem")
return
}
if (password.length < 6) {
setError("Senha deve ter pelo menos 6 caracteres")
return
}
const res = await fetch("/api/auth/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password })
})
const data = await res.json()
if (res.ok) {
router.push("/signin")
} else {
@@ -52,7 +52,7 @@ export default function SignUp() {
<h2 className="text-2xl font-bold mb-6">Cadastrar - 1000Apps</h2>
{error && (
<div className="bg-red-100 text-red-700 p-3 rounded mb-4">
<div className="mb-4 bg-red-100 text-red-700 p-3 rounded">
{error}
</div>
)}
@@ -61,30 +61,30 @@ export default function SignUp() {
<input
type="text"
placeholder="Nome de usuário"
className="w-full p-2 border rounded"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="w-full p-2 border rounded"
required
/>
<input
type="password"
placeholder="Senha"
className="w-full p-2 border rounded"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full p-2 border rounded"
required
minLength={6}
/>
<input
type="password"
placeholder="Confirmar senha"
className="w-full p-2 border rounded"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="w-full p-2 border rounded"
required
/>
<button
type="submit"
type="submit"
disabled={loading}
className="w-full bg-green-600 text-white py-2 rounded-lg disabled:opacity-50"
>
+175
View File
@@ -0,0 +1,175 @@
import { useSession, signOut } from "next-auth/react"
import { useRouter } from "next/router"
import { useState, useEffect } from "react"
import AvatarSelector from "../components/AvatarSelector"
import CategoryMenu from "../components/CategoryMenu"
// Apps organizados por categoria
const ALL_APPS = [
// Produtividade
{ id: 1, name: "Tendências Semanais", desc: "Notícias em alta • Top 100", path: "/apps/trendsnews", icon: "📈", category: "produtividade", favorite: false },
{ id: 2, name: "Conversor", desc: "Conversão de unidades", path: "/apps/converter", icon: "🔄", category: "produtividade", favorite: false },
{ id: 3, name: "Calculadora", desc: "Calculos rápidos", path: "/apps/calculator", icon: "🧮", category: "produtividade", favorite: false },
// Utilidades
{ id: 4, name: "Relógio", desc: "Relógio mundial", path: "/apps/clock", icon: "⏰", category: "utilidades", favorite: false },
{ id: 5, name: "Gerador Senhas", desc: "Senhas seguras", path: "/apps/password-generator", icon: "🔐", category: "utilidades", favorite: false },
{ id: 6, name: "QR Code", desc: "Gerador QR", path: "/apps/qrcode", icon: "📱", category: "utilidades", favorite: false },
{ id: 7, name: "Cronômetro", desc: "Tempo e alarmes", path: "/apps/timer", icon: "⏱️", category: "utilidades", favorite: false },
// Games
{ id: 8, name: "Jogo da Velha", desc: "Jogo da velha clássico 2 jogadores", path: "/apps/tictactoe", icon: "⭕", category: "games", favorite: false },
{ id: 14, name: "Expedição Gold", path: "/apps/expedicao", icon: "🗺️", category: "games", desc: "Expedições para conquistar ouro" },
{ id: 15, name: "Human City", path: "/apps/humancity", icon: "🏙️", category: "games", desc: "Simulação de cidade estilo Sims" },
{ id: 16, name: "Aurea", path: "/apps/aurea", icon: "👑", category: "games", desc: "Simulador de vida e riqueza" },
{ id: 17, name: "TerraVerse", path: "/apps/terraverse", icon: "🌍", category: "games", desc: "Game de realidade aumentada" },
// Ferramentas
{ id: 9, name: "Editor Texto", desc: "Texto simples", path: "/apps/text-editor", icon: "📝", category: "ferramentas", favorite: false },
{ id: 10, name: "Conversor Moedas", desc: "Câmbio em tempo real", path: "/apps/currency", icon: "💱", category: "ferramentas", favorite: false },
// Educação
{ id: 11, name: "Dicionário", desc: "Significados", path: "/apps/dictionary", icon: "📖", category: "educacao", favorite: false },
{ id: 12, name: "Tradutor", desc: "Multi-idiomas", path: "/apps/translator", icon: "🌐", category: "educacao", favorite: false },
// Esportes
{ id: 13, name: "Copa do Mundo 2026", desc: "Tabela, grupos, notícias", path: "/apps/worldcup2026", icon: "⚽", category: "esportes", favorite: false }
]
export default function Dashboard() {
const { data: session } = useSession()
const router = useRouter()
const [selectedAvatar, setSelectedAvatar] = useState("😀")
const [selectedCategory, setSelectedCategory] = useState("all")
const [favorites, setFavorites] = useState([])
const [apps, setApps] = useState(ALL_APPS)
// Carregar favoritos do localStorage
useEffect(() => {
const savedFavorites = localStorage.getItem("favorites")
const savedAvatar = localStorage.getItem("selectedAvatar")
if (savedFavorites) {
setFavorites(JSON.parse(savedFavorites))
}
if (savedAvatar) {
setSelectedAvatar(savedAvatar)
}
}, [])
// Salvar favoritos no localStorage
useEffect(() => {
localStorage.setItem("favorites", JSON.stringify(favorites))
}, [favorites])
// Salvar avatar no localStorage
useEffect(() => {
localStorage.setItem("selectedAvatar", selectedAvatar)
}, [selectedAvatar])
const toggleFavorite = (appId) => {
setFavorites(prev =>
prev.includes(appId)
? prev.filter(id => id !== appId)
: [...prev, appId]
)
}
// Filtrar apps
const filteredApps = selectedCategory === "all"
? apps
: apps.filter(app => app.category === selectedCategory)
// Apps favoritos
const favoriteApps = apps.filter(app => favorites.includes(app.id))
return (
<div className="min-h-screen bg-gray-50">
{/* Header */}
<nav className="bg-white shadow-lg p-4 flex justify-between items-center">
<div className="flex items-center gap-4">
<h1 className="text-2xl font-bold">1000Apps</h1>
<span className="text-xs bg-green-100 text-green-800 px-2 py-1 rounded-full font-semibold">FREE</span>
</div>
<div className="flex items-center gap-4">
<AvatarSelector selectedAvatar={selectedAvatar} onSelect={setSelectedAvatar} />
<span className="text-gray-600">Olá, {session?.user?.name || session?.user?.username || "usuário"}!</span>
<button onClick={() => signOut()} className="text-red-600 hover:underline font-medium">Sair</button>
</div>
</nav>
<main className="max-w-6xl mx-auto p-6">
{/* Favoritos Section */}
{favorites.length > 0 && (
<div className="mb-8">
<h2 className="text-2xl font-bold text-gray-800 mb-4 flex items-center gap-2">
<span>⭐</span> Favoritos
</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{favoriteApps.map(app => (
<button
key={app.id}
onClick={() => router.push(app.path)}
className="bg-white rounded-xl p-6 shadow-md hover:shadow-lg transition-all text-left hover:scale-105 cursor-pointer border-2 border-yellow-200"
>
<div className="text-3xl mb-2">{app.icon}</div>
<h3 className="font-semibold text-gray-800 text-sm">{app.name}</h3>
<p className="text-xs text-gray-500 mt-1">{app.desc}</p>
</button>
))}
</div>
</div>
)}
{/* Category Filter */}
<CategoryMenu selectedCategory={selectedCategory} onSelect={setSelectedCategory} />
{/* Apps Grid */}
<div className="mb-6">
<h2 className="text-3xl font-bold text-gray-800 mb-2">
{selectedCategory === "all" ? "Todos os Apps" :
selectedCategory === "produtividade" ? "Produtividade" :
selectedCategory === "utilidades" ? "Utilidades" :
selectedCategory === "games" ? "Games" :
selectedCategory === "ferramentas" ? "Ferramentas" :
selectedCategory === "educacao" ? "Educação" :
selectedCategory === "entretenimento" ? "Entretenimento" :
selectedCategory === "esportes" ? "Esportes" : "Apps"}
</h2>
<p className="text-gray-500">
{selectedCategory === "all"
? `${apps.length} apps disponíveis`
: `${filteredApps.length} apps nesta categoria`}
</p>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{filteredApps.map(app => (
<div key={app.id} className="relative">
<button
onClick={() => router.push(app.path)}
className="bg-white rounded-xl p-6 shadow-md hover:shadow-lg transition-all text-left hover:scale-105 cursor-pointer w-full"
>
<div className="text-3xl mb-2">{app.icon}</div>
<h3 className="font-semibold text-gray-800 text-sm">{app.name}</h3>
<p className="text-xs text-gray-500 mt-1">{app.desc}</p>
</button>
<button
onClick={() => toggleFavorite(app.id)}
className="absolute top-2 right-2 text-lg"
title={favorites.includes(app.id) ? "Remover dos favoritos" : "Adicionar aos favoritos"}
>
{favorites.includes(app.id) ? "⭐" : "☆"}
</button>
</div>
))}
</div>
</main>
</div>
)
}
export async function getServerSideProps() {
return {
props: {},
}
}
+7
View File
@@ -0,0 +1,7 @@
# Landing Legacy
- `index.jsx` — Landing Page original (era a rota `/` antes da remoção do login).
- Estilos inline + classes em `styles/globals.css` (ex.: `card-hover`).
- Não participa do build enquanto estiver fora de `pages/`.
Para reativar: mover para `pages/index.jsx` (e remover o redirect atual em `pages/index.jsx`).
+143
View File
@@ -0,0 +1,143 @@
// LANDING PAGE — DESATIVADA TEMPORARIAMENTE (2026-07-11)
// Motivo: remoção do fluxo de login; app agora abre direto no dashboard.
// Arquivo PRESERVADO para reativação futura. Não foi apagado componente,
// asset ou estilo. Para reativar: renomear para pages/index.jsx.
// Updated: force rebuild for cache invalidation
export default function Home() {
return (
<div className="bg-gray-50 min-h-screen">
{/* Header */}
<header className="bg-white shadow-sm">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 flex justify-between items-center">
<h1 className="text-2xl font-bold text-gray-900">1000Apps</h1>
<div className="space-x-4">
<a href="/signin" className="text-gray-600 hover:text-gray-900 font-medium">Entrar</a>
<a href="/signup" className="bg-blue-600 text-white px-4 py-2 rounded-lg font-medium hover:bg-blue-700 transition">Cadastrar</a>
</div>
</div>
</header>
{/* Hero Section */}
<section className="bg-gradient-to-r from-blue-600 to-purple-600 text-white py-20">
<div className="max-w-4xl mx-auto px-4 text-center">
<h2 className="text-5xl font-bold mb-6">Seu Canivete Suíço Digital</h2>
<p className="text-xl mb-8 opacity-90">
Mais de 1000 ferramentas úteis, games e produtividade - tudo online, sem instalar nada!
</p>
<div className="flex justify-center space-x-4">
<a href="/signup" className="bg-white text-blue-600 px-8 py-3 rounded-lg font-semibold text-lg hover:bg-gray-100 transition">Começar Grátis</a>
<a href="#apps" className="border border-white text-white px-8 py-3 rounded-lg font-semibold text-lg hover:bg-white/10 transition">Explorar Apps</a>
</div>
</div>
</section>
{/* Categories */}
<section id="apps" className="py-16 px-4">
<div className="max-w-6xl mx-auto">
<h3 className="text-3xl font-bold text-center mb-12 text-gray-800">Categorias</h3>
<div className="grid md:grid-cols-3 gap-8">
<div className="bg-white rounded-xl p-8 card-hover transition transform hover:-translate-y-1">
<div className="w-14 h-14 bg-blue-100 rounded-lg flex items-center justify-center mb-4">
<svg className="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5l7 7-7 7" />
</svg>
</div>
<h4 className="text-xl font-semibold mb-2">Produtividade</h4>
<p className="text-gray-600 mb-4">Ferramentas para otimizar seu tempo e trabalho</p>
<span className="text-blue-600 font-medium">Ver apps </span>
</div>
<div className="bg-white rounded-xl p-8 card-hover transition transform hover:-translate-y-1">
<div className="w-14 h-14 bg-green-100 rounded-lg flex items-center justify-center mb-4">
<svg className="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M11 4a2 2 0 114 0v5a2 2 0 01-2 2H7a2 2 0 00-2-2V6a2 2 0 012-2h2z" />
</svg>
</div>
<h4 className="text-xl font-semibold mb-2">Utilidades</h4>
<p className="text-gray-600 mb-4">Ferramentas práticas para o dia a dia</p>
<span className="text-green-600 font-medium">Ver apps </span>
</div>
<div className="bg-white rounded-xl p-8 card-hover transition transform hover:-translate-y-1">
<div className="w-14 h-14 bg-purple-100 rounded-lg flex items-center justify-center mb-4">
<svg className="w-8 h-8 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M14.752 12.795a7.95 7.95 0 001.904-5.452 7.95 7.95 0 00-7.95 7.95A7.95 7.95 0 0012.795 14.752" />
</svg>
</div>
<h4 className="text-xl font-semibold mb-2">Games</h4>
<p className="text-gray-600 mb-4">Diversão rápida e envolvente</p>
<span className="text-purple-600 font-medium">Ver apps </span>
</div>
</div>
</div>
</section>
{/* Features */}
<section className="bg-gray-100 py-16 px-4">
<div className="max-w-6xl mx-auto">
<h3 className="text-3xl font-bold text-center mb-12 text-gray-800">Por que 1000Apps?</h3>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
<div className="text-center">
<div className="w-16 h-16 bg-blue-600 rounded-full flex items-center justify-center mx-auto mb-4">
<span className="text-2xl font-bold text-white">01</span>
</div>
<h5 className="font-semibold mb-2">Nada para Instalar</h5>
<p className="text-sm text-gray-600">Tudo rodando direto no navegador</p>
</div>
<div className="text-center">
<div className="w-16 h-16 bg-green-600 rounded-full flex items-center justify-center mx-auto mb-4">
<span className="text-2xl font-bold text-white">02</span>
</div>
<h5 className="font-semibold mb-2">Sempre Atualizado</h5>
<p className="text-sm text-gray-600">Conteúdo fresco todo dia</p>
</div>
<div className="text-center">
<div className="w-16 h-16 bg-purple-600 rounded-full flex items-center justify-center mx-auto mb-4">
<span className="text-2xl font-bold text-white">03</span>
</div>
<h5 className="font-semibold mb-2">Curadoria Inteligente</h5>
<p className="text-sm text-gray-600"> apps úteis e testados</p>
</div>
<div className="text-center">
<div className="w-16 h-16 bg-orange-600 rounded-full flex items-center justify-center mx-auto mb-4">
<span className="text-2xl font-bold text-white">04</span>
</div>
<h5 className="font-semibold mb-2">Grátis para Sempre</h5>
<p className="text-sm text-gray-600">Acesso completo sem pagar nada</p>
</div>
</div>
</div>
</section>
{/* CTA Final */}
<section className="py-20 px-4">
<div className="max-w-3xl mx-auto text-center">
<h3 className="text-4xl font-bold mb-4 text-gray-900">Pronto para começar?</h3>
<p className="text-lg text-gray-600 mb-8">
Junte-se a milhares de usuários que economizam tempo com 1000Apps
</p>
<a href="/signup" className="inline-block bg-blue-600 text-white px-10 py-4 rounded-lg font-semibold text-xl hover:bg-blue-700 transition transform hover:scale-105">
Criar Conta Grátis
</a>
</div>
</section>
{/* Footer */}
<footer className="bg-gray-900 text-white py-8 px-4">
<div className="max-w-6xl mx-auto text-center">
<p className="mb-4">&copy; 2026 1000Apps. Todos os direitos reservados.</p>
<div className="space-x-6 text-sm">
<a href="#" className="hover:text-gray-300">Termos</a>
<a href="#" className="hover:text-gray-300">Privacidade</a>
<a href="#" className="hover:text-gray-300">Contato</a>
</div>
</div>
</footer>
</div>
)
}
View File
+4
View File
@@ -0,0 +1,4 @@
# Módulo: bloco_notas
Estrutura preparada para o mini app 'bloco_notas'.
Nenhuma funcionalidade implementada ainda.
@@ -0,0 +1 @@
# bloco_notas/components (placeholder)
+1
View File
@@ -0,0 +1 @@
# bloco_notas/hooks (placeholder)
+1
View File
@@ -0,0 +1 @@
# bloco_notas/pages (placeholder)
@@ -0,0 +1 @@
# bloco_notas/services (placeholder)
+4
View File
@@ -0,0 +1,4 @@
# Módulo: calculadora
Estrutura preparada para o mini app 'calculadora'.
Nenhuma funcionalidade implementada ainda.
@@ -0,0 +1 @@
# calculadora/components (placeholder)
+1
View File
@@ -0,0 +1 @@
# calculadora/hooks (placeholder)
+1
View File
@@ -0,0 +1 @@
# calculadora/pages (placeholder)
@@ -0,0 +1 @@
# calculadora/services (placeholder)
+4
View File
@@ -0,0 +1,4 @@
# Módulo: conversor
Estrutura preparada para o mini app 'conversor'.
Nenhuma funcionalidade implementada ainda.
@@ -0,0 +1 @@
# conversor/components (placeholder)
+1
View File
@@ -0,0 +1 @@
# conversor/hooks (placeholder)
+1
View File
@@ -0,0 +1 @@
# conversor/pages (placeholder)
+1
View File
@@ -0,0 +1 @@
# conversor/services (placeholder)
+4
View File
@@ -0,0 +1,4 @@
# Módulo: criptomoedas
Estrutura preparada para o mini app 'criptomoedas'.
Nenhuma funcionalidade implementada ainda.
@@ -0,0 +1 @@
# criptomoedas/components (placeholder)
+1
View File
@@ -0,0 +1 @@
# criptomoedas/hooks (placeholder)
+1
View File
@@ -0,0 +1 @@
# criptomoedas/pages (placeholder)
@@ -0,0 +1 @@
# criptomoedas/services (placeholder)
+4
View File
@@ -0,0 +1,4 @@
# Módulo: jogos
Estrutura preparada para o mini app 'jogos'.
Nenhuma funcionalidade implementada ainda.
+1
View File
@@ -0,0 +1 @@
# jogos/components (placeholder)
+1
View File
@@ -0,0 +1 @@
# jogos/hooks (placeholder)
+1
View File
@@ -0,0 +1 @@
# jogos/pages (placeholder)
+1
View File
@@ -0,0 +1 @@
# jogos/services (placeholder)
+4
View File
@@ -0,0 +1,4 @@
# Módulo: noticias
Estrutura preparada para o mini app 'noticias'.
Nenhuma funcionalidade implementada ainda.
+1
View File
@@ -0,0 +1 @@
# noticias/components (placeholder)
+1
View File
@@ -0,0 +1 @@
# noticias/hooks (placeholder)
+1
View File
@@ -0,0 +1 @@
# noticias/pages (placeholder)
+1
View File
@@ -0,0 +1 @@
# noticias/services (placeholder)
+12
View File
@@ -0,0 +1,12 @@
// Serviço de API central.
// Placeholder arquitetural — ainda não implementado.
const API_BASE = process.env.NEXT_PUBLIC_API_BASE || "/api"
export async function get(path) {
// TODO: implementar wrapper fetch com auth/erros
throw new Error("api.get not implemented")
}
export async function post(path, body) {
throw new Error("api.post not implemented")
}
export default { API_BASE, get, post }
+6
View File
@@ -0,0 +1,6 @@
// Aggregador de provedores de auth (preparação).
export { PROVIDERS, AuthProvider } from "./interfaces"
export { googleProvider } from "./providers/google"
export { appleProvider } from "./providers/apple"
export { emailProvider } from "./providers/email"
export { anonymousProvider } from "./providers/anonymous"
+16
View File
@@ -0,0 +1,16 @@
// Interfaces de provedores de autenticação (preparação, sem SDK).
// Implementação real ocorrerá em fase futura.
export const AuthProvider = {
// Cada provedor deve implementar:
// login(): Promise<{ user, token }>
// logout(): Promise<void>
// getSession(): Promise<Session | null>
}
export const PROVIDERS = {
GOOGLE: "google",
APPLE: "apple",
EMAIL: "email",
ANONYMOUS: "anonymous",
}
+6
View File
@@ -0,0 +1,6 @@
// Login Anônimo — placeholder arquitetural.
export const anonymousProvider = {
id: "anonymous",
async login() { throw new Error("Anonymous login not implemented") },
async logout() { throw new Error("Anonymous logout not implemented") },
}
+6
View File
@@ -0,0 +1,6 @@
// Apple Login — placeholder arquitetural (sem SDK instalado).
export const appleProvider = {
id: "apple",
async login() { throw new Error("Apple login not implemented") },
async logout() { throw new Error("Apple logout not implemented") },
}
+6
View File
@@ -0,0 +1,6 @@
// Email/Senha — placeholder arquitetural (sem backend de auth ativo).
export const emailProvider = {
id: "email",
async login({ username, password }) { throw new Error("Email login not implemented") },
async logout() { throw new Error("Email logout not implemented") },
}
+6
View File
@@ -0,0 +1,6 @@
// Google Login — placeholder arquitetural (sem SDK instalado).
export const googleProvider = {
id: "google",
async login() { throw new Error("Google login not implemented") },
async logout() { throw new Error("Google logout not implemented") },
}