Compare commits

..

1 Commits

Author SHA1 Message Date
Carlos 8f7d33c929 feat: migrate TikTakToe, ExpedicaoGold, HumanCity, Aurea, TerraVerse into 1000apps 2026-07-02 04:32:25 +02:00
84 changed files with 476 additions and 890 deletions
+2 -8
View File
@@ -6,20 +6,16 @@ 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
@@ -44,8 +40,6 @@ 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
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["npm", "run", "start"]
-6
View File
@@ -1,6 +0,0 @@
# 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@db:5432/1000apps
DATABASE_URL: postgresql://user:change_me_password@10.0.1.21:5432/1000apps
NEXTAUTH_SECRET: test-secret-2x4=
NEXTAUTH_URL: https://1000apps.fluxtechnologies.uk
GOOGLE_CLIENT_ID: ""
-6
View File
@@ -1,6 +0,0 @@
#!/bin/sh
set -e
echo "Running Prisma migrations..."
npx prisma migrate deploy
echo "Starting Next.js..."
npm run start
+1 -5
View File
@@ -1,9 +1,5 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
"jsx": "react-jsx"
}
}
View File
+2 -1
View File
@@ -15,7 +15,8 @@
"next-auth": "^4.24.0",
"@next-auth/prisma-adapter": "^1.0.7",
"@prisma/client": "^5.0.0",
"bcryptjs": "^2.4.3"
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.0"
},
"devDependencies": {
"prisma": "^5.0.0",
+8 -4
View File
@@ -1,9 +1,13 @@
// _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"
import { SessionProvider } from "next-auth/react"
import "../styles/globals.css"
export default function App({ Component, pageProps }) {
export default function App({
Component,
pageProps: { session, ...pageProps }
}) {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
)
}
+53
View File
@@ -0,0 +1,53 @@
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
@@ -0,0 +1,54 @@
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
@@ -0,0 +1,53 @@
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()
}
}
@@ -18,9 +18,14 @@ export default function TicTacToe() {
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],
[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]) {
@@ -31,14 +36,36 @@ export default function TicTacToe() {
}
const renderSquare = (i) => (
<button className="square" onClick={() => handleClick(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' };
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' }}>
@@ -51,11 +78,6 @@ export default function TicTacToe() {
</div>
))}
</div>
{winner && (
<button onClick={() => { setBoard(Array(9).fill(null)); setXIsNext(true); }}>
Play Again
</button>
)}
</div>
);
}
+2
View File
@@ -1,4 +1,5 @@
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)
@@ -121,6 +122,7 @@ 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,4 +1,5 @@
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)
@@ -94,6 +95,7 @@ const worldCupNews = [
]
export default function WorldCup2026() {
const { data: session } = useSession()
const router = useRouter()
const [selectedGroup, setSelectedGroup] = useState("TODOS")
const [selectedCategory, setSelectedCategory] = useState("TODAS")
+55 -80
View File
@@ -1,23 +1,15 @@
import React from "react"
import { useState } from "react"
import { useSession, signOut } from "next-auth/react"
import { useRouter } from "next/router"
import AvatarSelector from "@/components/dashboard/AvatarSelector"
import CategoryMenu from "@/components/dashboard/CategoryMenu"
import { useState, useEffect } from "react"
import AvatarSelector from "../components/AvatarSelector"
import CategoryMenu from "../components/CategoryMenu"
export default function Dashboard() {
const router = useRouter()
const [selectedAvatar, setSelectedAvatar] = useState("😀")
const [selectedCategory, setSelectedCategory] = useState("all")
const [favorites, setFavorites] = useState([])
const [confirmPassword, setConfirmPassword] = useState("")
const [loading, setLoading] = useState(false)
// Apps organized by category
// 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: "Cálculos rápidos", path: "/apps/calculator", 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 },
@@ -27,10 +19,10 @@ export default function Dashboard() {
// 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 },
{ 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 },
@@ -44,34 +36,36 @@ export default function Dashboard() {
{ 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
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)
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
React.useEffect(() => {
useEffect(() => {
localStorage.setItem("favorites", JSON.stringify(favorites))
}, [favorites])
// Salvar avatar no localStorage
React.useEffect(() => {
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)
@@ -82,11 +76,11 @@ export default function Dashboard() {
// Filtrar apps
const filteredApps = selectedCategory === "all"
? ALL_APPS
: ALL_APPS.filter(app => app.category === selectedCategory)
? apps
: apps.filter(app => app.category === selectedCategory)
// Apps favoritos
const favoriteApps = ALL_APPS.filter(app => favorites.includes(app.id))
const favoriteApps = apps.filter(app => favorites.includes(app.id))
return (
<div className="min-h-screen bg-gray-50">
@@ -94,15 +88,12 @@ 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}
/>
<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>
@@ -114,67 +105,45 @@ export default function Dashboard() {
<span></span> Favoritos
</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{favorites.map((favId) => {
const app = ALL_APPS.find((a) => a.id === favId)
if (!app) return null
return (
<div key={app.id} className="relative">
{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 w-full"
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>
<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 === "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 === "entretenimento" ? "Entretenimento" :
selectedCategory === "esportes" ? "Esportes" : "Apps"}
</h2>
<p className="text-gray-500">
{selectedCategory === "all"
? `${ALL_APPS.length} apps disponíveis`
? `${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)}
@@ -198,3 +167,9 @@ export default function Dashboard() {
</div>
)
}
export async function getServerSideProps() {
return {
props: {},
}
}
+136 -11
View File
@@ -1,14 +1,139 @@
// index.jsx rota "/" agora redireciona direto para o dashboard.
// O login foi removido; o app abre sem autenticação.
// Updated: force rebuild for cache invalidation
export default function Home() {
return null
}
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>
export async function getServerSideProps() {
return {
redirect: {
destination: "/dashboard",
permanent: false,
},
}
{/* 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>
)
}
+10 -12
View File
@@ -1,9 +1,5 @@
// 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("")
@@ -18,18 +14,20 @@ export default function SignIn() {
setLoading(true)
try {
const result = await signIn("credentials", {
username,
password,
redirect: false,
callbackUrl: "/dashboard"
const res = await fetch("/api/auth/signin", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password })
})
if (result?.ok) {
const data = await res.json()
if (res.ok && data.ok) {
// Set session cookie
document.cookie = `next-auth.session-token=${data.token}; path=/; SameSite=Lax`
router.push("/dashboard")
router.refresh()
} else {
setError(result?.error || "Erro ao entrar")
setError(data.error || "Erro ao entrar")
}
} catch (err) {
setError("Erro de conexão")
@@ -1,5 +1,3 @@
// 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"
@@ -14,8 +12,7 @@ export default function SignUp() {
const handleSubmit = async (e) => {
e.preventDefault()
setError("")
setLoading(true)
try {
if (password !== confirmPassword) {
setError("Senhas não coincidem")
return
@@ -26,7 +23,10 @@ export default function SignUp() {
return
}
const res = await fetch("/api/auth/register", {
setLoading(true)
try {
const res = await fetch("/api/auth/signup", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password })
@@ -52,7 +52,7 @@ export default function SignUp() {
<h2 className="text-2xl font-bold mb-6">Cadastrar - 1000Apps</h2>
{error && (
<div className="mb-4 bg-red-100 text-red-700 p-3 rounded">
<div className="bg-red-100 text-red-700 p-3 rounded mb-4">
{error}
</div>
)}
@@ -61,26 +61,26 @@ 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
@@ -1 +0,0 @@
{}
@@ -1,39 +0,0 @@
-- 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,6 +1,5 @@
generator client {
provider = "prisma-client-js"
binaryTargets = ["linux-musl-openssl-3.0.x"]
}
datasource db {
-1
View File
@@ -1 +0,0 @@
# App-specific components (placeholder)
-12
View File
@@ -1,12 +0,0 @@
// 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
@@ -1,11 +0,0 @@
// 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
@@ -1,15 +0,0 @@
// 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
@@ -1,5 +0,0 @@
// 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
@@ -1,10 +0,0 @@
// 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
@@ -1,9 +0,0 @@
// 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
@@ -1,12 +0,0 @@
// 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
@@ -1,5 +0,0 @@
// 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
@@ -1,10 +0,0 @@
// 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
@@ -1,9 +0,0 @@
// 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
@@ -1,11 +0,0 @@
// 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
@@ -1 +0,0 @@
# Layout components (placeholder)
-27
View File
@@ -1,27 +0,0 @@
# 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
@@ -1,9 +0,0 @@
# 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
@@ -1,62 +0,0 @@
// 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
}
}
})
-175
View File
@@ -1,175 +0,0 @@
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
@@ -1,7 +0,0 @@
# 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
@@ -1,143 +0,0 @@
// 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>
)
}
-4
View File
@@ -1,4 +0,0 @@
# Módulo: bloco_notas
Estrutura preparada para o mini app 'bloco_notas'.
Nenhuma funcionalidade implementada ainda.
@@ -1 +0,0 @@
# bloco_notas/components (placeholder)
-1
View File
@@ -1 +0,0 @@
# bloco_notas/hooks (placeholder)
-1
View File
@@ -1 +0,0 @@
# bloco_notas/pages (placeholder)
@@ -1 +0,0 @@
# bloco_notas/services (placeholder)
-4
View File
@@ -1,4 +0,0 @@
# Módulo: calculadora
Estrutura preparada para o mini app 'calculadora'.
Nenhuma funcionalidade implementada ainda.
@@ -1 +0,0 @@
# calculadora/components (placeholder)
-1
View File
@@ -1 +0,0 @@
# calculadora/hooks (placeholder)
-1
View File
@@ -1 +0,0 @@
# calculadora/pages (placeholder)
@@ -1 +0,0 @@
# calculadora/services (placeholder)
-4
View File
@@ -1,4 +0,0 @@
# Módulo: conversor
Estrutura preparada para o mini app 'conversor'.
Nenhuma funcionalidade implementada ainda.
@@ -1 +0,0 @@
# conversor/components (placeholder)
-1
View File
@@ -1 +0,0 @@
# conversor/hooks (placeholder)
-1
View File
@@ -1 +0,0 @@
# conversor/pages (placeholder)
-1
View File
@@ -1 +0,0 @@
# conversor/services (placeholder)
-4
View File
@@ -1,4 +0,0 @@
# Módulo: criptomoedas
Estrutura preparada para o mini app 'criptomoedas'.
Nenhuma funcionalidade implementada ainda.
@@ -1 +0,0 @@
# criptomoedas/components (placeholder)
-1
View File
@@ -1 +0,0 @@
# criptomoedas/hooks (placeholder)
-1
View File
@@ -1 +0,0 @@
# criptomoedas/pages (placeholder)
@@ -1 +0,0 @@
# criptomoedas/services (placeholder)
-4
View File
@@ -1,4 +0,0 @@
# Módulo: jogos
Estrutura preparada para o mini app 'jogos'.
Nenhuma funcionalidade implementada ainda.
-1
View File
@@ -1 +0,0 @@
# jogos/components (placeholder)
-1
View File
@@ -1 +0,0 @@
# jogos/hooks (placeholder)
-1
View File
@@ -1 +0,0 @@
# jogos/pages (placeholder)
-1
View File
@@ -1 +0,0 @@
# jogos/services (placeholder)
-4
View File
@@ -1,4 +0,0 @@
# Módulo: noticias
Estrutura preparada para o mini app 'noticias'.
Nenhuma funcionalidade implementada ainda.
-1
View File
@@ -1 +0,0 @@
# noticias/components (placeholder)
-1
View File
@@ -1 +0,0 @@
# noticias/hooks (placeholder)
-1
View File
@@ -1 +0,0 @@
# noticias/pages (placeholder)
-1
View File
@@ -1 +0,0 @@
# noticias/services (placeholder)
-12
View File
@@ -1,12 +0,0 @@
// 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
@@ -1,6 +0,0 @@
// 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
@@ -1,16 +0,0 @@
// 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
@@ -1,6 +0,0 @@
// 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
@@ -1,6 +0,0 @@
// 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
@@ -1,6 +0,0 @@
// 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
@@ -1,6 +0,0 @@
// 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") },
}