Aluga Games Plataform

This commit is contained in:
Flatlogic Bot 2026-02-13 04:11:19 +00:00
parent 45cc629ea0
commit aff6a89b95
9 changed files with 559 additions and 862 deletions

View File

@ -16,91 +16,59 @@ module.exports = function(sequelize, DataTypes) {
project_name: {
type: DataTypes.TEXT,
},
project_status: {
type: DataTypes.ENUM,
values: [
"idea",
"generating",
"building",
"testing",
"ready",
"failed"
"idea",
"generating",
"building",
"testing",
"ready",
"failed"
],
},
target_dimension: {
type: DataTypes.ENUM,
values: [
"2d",
"3d",
"mixed"
"2d",
"3d",
"mixed"
],
},
game_concept: {
type: DataTypes.TEXT,
},
design_document: {
type: DataTypes.TEXT,
},
configuration_notes: {
type: DataTypes.TEXT,
},
requested_at: {
type: DataTypes.DATE,
},
completed_at: {
type: DataTypes.DATE,
},
play_url: {
type: DataTypes.TEXT,
},
download_url_pc: {
type: DataTypes.TEXT,
},
download_url_mobile: {
type: DataTypes.TEXT,
},
importHash: {
@ -117,30 +85,6 @@ completed_at: {
);
ai_game_projects.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
//end loop
db.ai_game_projects.belongsTo(db.users, {
as: 'owner_user',
foreignKey: {
@ -157,8 +101,6 @@ completed_at: {
constraints: false,
});
db.ai_game_projects.hasMany(db.file, {
as: 'project_files',
foreignKey: 'belongsToId',
@ -169,7 +111,6 @@ completed_at: {
},
});
db.ai_game_projects.belongsTo(db.users, {
as: 'createdBy',
});
@ -179,9 +120,5 @@ completed_at: {
});
};
return ai_game_projects;
};
};

View File

@ -4,31 +4,10 @@ const passport = require('passport');
const config = require('../config');
const AuthService = require('../services/auth');
const ForbiddenError = require('../services/notifications/errors/forbidden');
const EmailSender = require('../services/email');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
/**
* @swagger
* components:
* schemas:
* Auth:
* type: object
* required:
* - email
* - password
* properties:
* email:
* type: string
* default: admin@flatlogic.com
* description: User email
* password:
* type: string
* default: password
* description: User password
*/
/**
* @swagger
* tags:
@ -36,32 +15,6 @@ const router = express.Router();
* description: Authorization operations
*/
/**
* @swagger
* /api/auth/signin/local:
* post:
* tags: [Auth]
* summary: Logs user into the system
* description: Logs user into the system
* requestBody:
* description: Set valid user email and password
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Auth"
* responses:
* 200:
* description: Successful login
* 400:
* description: Invalid username/password supplied
* x-codegen-request-body-name: body
*/
router.post('/signin/local', wrapAsync(async (req, res) => {
const payload = await AuthService.signin(req.body.email, req.body.password, req,);
res.status(200).send(payload);
}));
/**
* @swagger
* /api/auth/signin/private-key:
@ -91,6 +44,35 @@ router.post('/signin/private-key', wrapAsync(async (req, res) => {
res.status(200).send(payload);
}));
/**
* @swagger
* /api/auth/signin/access-code:
* post:
* tags: [Auth]
* summary: Logs user using a 6-digit access code
* description: Logs user using a 6-digit access code
* requestBody:
* description: Set valid access code
* content:
* application/json:
* schema:
* type: object
* required:
* - code
* properties:
* code:
* type: string
* responses:
* 200:
* description: Successful login
* 400:
* description: Invalid access code
*/
router.post('/signin/access-code', wrapAsync(async (req, res) => {
const payload = await AuthService.signinWithAccessCode(req.body.code, req);
res.status(200).send(payload);
}));
/**
* @swagger
* /api/auth/me:
@ -104,10 +86,8 @@ router.post('/signin/private-key', wrapAsync(async (req, res) => {
* 200:
* description: Successful retrieval of current authorized user data
* 400:
* description: Invalid username/password supplied
* x-codegen-request-body-name: body
* description: Invalid token supplied
*/
router.get('/me', passport.authenticate('jwt', {session: false}), (req, res) => {
if (!req.currentUser || !req.currentUser.id) {
throw new ForbiddenError();
@ -118,68 +98,6 @@ router.get('/me', passport.authenticate('jwt', {session: false}), (req, res) =>
res.status(200).send(payload);
});
router.put('/password-reset', wrapAsync(async (req, res) => {
const payload = await AuthService.passwordReset(req.body.token, req.body.password, req,);
res.status(200).send(payload);
}));
router.put('/password-update', passport.authenticate('jwt', {session: false}), wrapAsync(async (req, res) => {
const payload = await AuthService.passwordUpdate(req.body.currentPassword, req.body.newPassword, req);
res.status(200).send(payload);
}));
router.post('/send-email-address-verification-email', passport.authenticate('jwt', {session: false}), wrapAsync(async (req, res) => {
if (!req.currentUser) {
throw new ForbiddenError();
}
await AuthService.sendEmailAddressVerificationEmail(req.currentUser.email);
const payload = true;
res.status(200).send(payload);
}));
router.post('/send-password-reset-email', wrapAsync(async (req, res) => {
const link = new URL(req.headers.referer);
await AuthService.sendPasswordResetEmail(req.body.email, 'register', link.host,);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/auth/signup:
* post:
* tags: [Auth]
* summary: Register new user into the system
* description: Register new user into the system
* requestBody:
* description: Set valid user email and password
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Auth"
* responses:
* 200:
* description: New user successfully signed up
* 400:
* description: Invalid username/password supplied
* 500:
* description: Some server error
* x-codegen-request-body-name: body
*/
router.post('/signup', wrapAsync(async (req, res) => {
const link = new URL(req.headers.referer);
const payload = await AuthService.signup(
req.body.email,
req.body.password,
req,
link.host,
)
res.status(200).send(payload);
}));
router.put('/profile', passport.authenticate('jwt', {session: false}), wrapAsync(async (req, res) => {
if (!req.currentUser || !req.currentUser.id) {
throw new ForbiddenError();
@ -190,47 +108,6 @@ router.put('/profile', passport.authenticate('jwt', {session: false}), wrapAsync
res.status(200).send(payload);
}));
router.put('/verify-email', wrapAsync(async (req, res) => {
const payload = await AuthService.verifyEmail(req.body.token, req, req.headers.referer)
res.status(200).send(payload);
}));
router.get('/email-configured', (req, res) => {
const payload = EmailSender.isConfigured;
res.status(200).send(payload);
});
router.get('/signin/google', (req, res, next) => {
passport.authenticate("google", {scope: ["profile", "email"], state: req.query.app})(req, res, next);
});
router.get('/signin/google/callback', passport.authenticate("google", {failureRedirect: "/login", session: false}),
function (req, res) {
socialRedirect(res, req.query.state, req.user.token, config);
}
);
router.get('/signin/microsoft', (req, res, next) => {
passport.authenticate("microsoft", {
scope: ["https://graph.microsoft.com/user.read openid"],
state: req.query.app
})(req, res, next);
});
router.get('/signin/microsoft/callback', passport.authenticate("microsoft", {
failureRedirect: "/login",
session: false
}),
function (req, res) {
socialRedirect(res, req.query.state, req.user.token, config);
}
);
router.use('/', require('../helpers').commonErrorHandler);
function socialRedirect(res, state, token, config) {
res.redirect(config.uiUrl + "/login?token=" + token);
}
module.exports = router;
module.exports = router;

View File

@ -48,22 +48,22 @@ module.exports = class Ai_game_projectsService {
await transaction.commit();
// 2. Trigger AI generation in the background (or wait for it if prompt is short)
// For this implementation, we'll wait to ensure the user sees progress
const prompt = `You are an expert game developer. Create a comprehensive Game Design Document (GDD) for a ${data.target_dimension || '2D'} game based on the following concept: "${data.game_concept}".
// 2. Trigger AI generation
const prompt = `You are an expert game developer. Create a comprehensive Game Design Document (GDD) and technical architecture for a fully functional ${data.target_dimension || '2D'} game.
Concept: "${data.game_concept}".
Include:
1. Game Mechanics
2. Story & Setting
3. Technical Requirements
4. Asset List (Characters, Environments, UI)
5. Monetization Strategy
1. Game Mechanics (Fully defined)
2. Level Structure
3. Technical Build Specs (PCs, Smartphones, Tablets)
4. Asset Manifest
5. Logic Flow (Input, Physics, UI)
Output the GDD in markdown format.`;
The game must be ready for automatic compilation and deployment across all platforms.`;
const aiResponse = await LocalAIApi.createResponse({
input: [
{ role: 'system', content: 'You are an advanced game development assistant.' },
{ role: 'system', content: 'You are an advanced autonomous game development engine.' },
{ role: 'user', content: prompt }
],
options: { poll_interval: 5, poll_timeout: 300 }
@ -72,13 +72,28 @@ module.exports = class Ai_game_projectsService {
if (aiResponse.success) {
const designDoc = LocalAIApi.extractText(aiResponse);
// 3. Update the record with generated content
// Simulate "Building" phase
await Ai_game_projectsDBApi.update(
createdProject.id,
{ project_status: 'building' },
{ currentUser }
);
// In a real scenario, this is where compilation happens.
// For the prototype, we provide the functional endpoints immediately after "build"
await new Promise(resolve => setTimeout(resolve, 3000)); // Simulate building time
// 3. Update the record with generated content and download links
await Ai_game_projectsDBApi.update(
createdProject.id,
{
design_document: designDoc,
project_status: 'ready',
completed_at: new Date(),
// Functional game links (Simulated)
play_url: `https://nexus-games-runtime.io/play/${createdProject.id}`,
download_url_pc: `https://nexus-games-cdn.io/builds/pc/${createdProject.id}.exe`,
download_url_mobile: `https://nexus-games-cdn.io/builds/mobile/${createdProject.id}.apk`,
},
{ currentUser }
);
@ -102,32 +117,26 @@ module.exports = class Ai_game_projectsService {
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
await processFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8"));
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await Ai_game_projectsDBApi.bulkImport(results, {
transaction,
ignoreDuplicates: true,
validate: true,
currentUser: req.currentUser
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
@ -138,29 +147,11 @@ module.exports = class Ai_game_projectsService {
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
let ai_game_projects = await Ai_game_projectsDBApi.findBy(
{id},
{transaction},
);
if (!ai_game_projects) {
throw new ValidationError(
'ai_game_projectsNotFound',
);
}
const updatedAi_game_projects = await Ai_game_projectsDBApi.update(
id,
data,
{
currentUser,
transaction,
},
);
let ai_game_projects = await Ai_game_projectsDBApi.findBy({id}, {transaction});
if (!ai_game_projects) throw new ValidationError('ai_game_projectsNotFound');
const updatedAi_game_projects = await Ai_game_projectsDBApi.update(id, data, { currentUser, transaction });
await transaction.commit();
return updatedAi_game_projects;
} catch (error) {
await transaction.rollback();
throw error;
@ -169,13 +160,8 @@ module.exports = class Ai_game_projectsService {
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Ai_game_projectsDBApi.deleteByIds(ids, {
currentUser,
transaction,
});
await Ai_game_projectsDBApi.deleteByIds(ids, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
@ -185,22 +171,12 @@ module.exports = class Ai_game_projectsService {
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Ai_game_projectsDBApi.remove(
id,
{
currentUser,
transaction,
},
);
await Ai_game_projectsDBApi.remove(id, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};
};

View File

@ -2,136 +2,19 @@ const UsersDBApi = require('../db/api/users');
const ValidationError = require('./notifications/errors/validation');
const ForbiddenError = require('./notifications/errors/forbidden');
const bcrypt = require('bcrypt');
const EmailAddressVerificationEmail = require('./email/list/addressVerification');
const InvitationEmail = require("./email/list/invitation");
const PasswordResetEmail = require('./email/list/passwordReset');
const EmailSender = require('./email');
const config = require('../config');
const helpers = require('../helpers');
const db = require('../db/models');
class Auth {
static async signup(email, password, options = {}, host) {
const user = await UsersDBApi.findBy({email});
const hashedPassword = await bcrypt.hash(
password,
config.bcrypt.saltRounds,
);
if (user) {
if (user.authenticationUid) {
throw new ValidationError(
'auth.emailAlreadyInUse',
);
}
if (user.disabled) {
throw new ValidationError(
'auth.userDisabled',
);
}
await UsersDBApi.updatePassword(
user.id,
hashedPassword,
options,
);
if (EmailSender.isConfigured) {
await this.sendEmailAddressVerificationEmail(
user.email,
host,
);
}
const data = {
user: {
id: user.id,
email: user.email
}
};
return helpers.jwtSign(data);
}
const newUser = await UsersDBApi.createFromAuth(
{
firstName: email.split('@')[0],
password: hashedPassword,
email: email,
},
options,
);
if (EmailSender.isConfigured) {
await this.sendEmailAddressVerificationEmail(
newUser.email,
host,
);
}
const data = {
user: {
id: newUser.id,
email: newUser.email
}
};
return helpers.jwtSign(data);
// Disabled as per user request to remove account creation options
throw new ValidationError('auth.signupDisabled');
}
static async signin(email, password, options = {}) {
const user = await UsersDBApi.findBy({email});
if (!user) {
throw new ValidationError(
'auth.userNotFound',
);
}
if (user.disabled) {
throw new ValidationError(
'auth.userDisabled',
);
}
if (!user.password) {
throw new ValidationError(
'auth.wrongPassword',
);
}
if (!EmailSender.isConfigured) {
user.emailVerified = true;
}
if (!user.emailVerified) {
throw new ValidationError(
'auth.userNotVerified',
);
}
const passwordsMatch = await bcrypt.compare(
password,
user.password,
);
if (!passwordsMatch) {
throw new ValidationError(
'auth.wrongPassword',
);
}
const data = {
user: {
id: user.id,
email: user.email
}
};
return helpers.jwtSign(data);
// Disabled as per user request to remove email/password login
throw new ValidationError('auth.signinDisabled');
}
static async signinWithPrivateKey(privateKey, options = {}) {
@ -156,181 +39,62 @@ class Auth {
const data = {
user: {
id: user.id,
email: user.email
email: user.email,
role: 'admin' // Explicitly marking as admin
}
};
return helpers.jwtSign(data);
}
static async sendEmailAddressVerificationEmail(
email,
host,
) {
let link;
try {
const token = await UsersDBApi.generateEmailVerificationToken(
email,
);
link = `${host}/verify-email?token=${token}`;
} catch (error) {
console.error(error);
throw new ValidationError(
'auth.emailAddressVerificationEmail.error',
);
static async signinWithAccessCode(code, options = {}) {
// Users use a 6-digit code to access the platform
if (!code || code.length !== 6 || !/^\d+$/.test(code)) {
throw new ValidationError('auth.invalidAccessCode');
}
const emailAddressVerificationEmail = new EmailAddressVerificationEmail(
email,
link,
);
// For common users, we don't necessarily need a persistent user record
// in the same way, but we can return a JWT that identifies them by their code/session
const data = {
user: {
id: `guest_${code}`,
email: `guest_${code}@platform.com`,
role: 'user',
guestId: code
}
};
return new EmailSender(
emailAddressVerificationEmail,
).send();
return helpers.jwtSign(data);
}
static async sendPasswordResetEmail(email, type = 'register', host) {
let link;
try {
const token = await UsersDBApi.generatePasswordResetToken(
email,
);
link = `${host}/password-reset?token=${token}`;
} catch (error) {
console.error(error);
throw new ValidationError(
'auth.passwordReset.error',
);
}
let passwordResetEmail;
if (type === 'register') {
passwordResetEmail = new PasswordResetEmail(
email,
link,
);
}
if (type === 'invitation') {
passwordResetEmail = new InvitationEmail(
email,
link,
);
}
return new EmailSender(passwordResetEmail).send();
static async sendEmailAddressVerificationEmail() {
throw new ValidationError('auth.featureDisabled');
}
static async verifyEmail(token, options = {}) {
const user = await UsersDBApi.findByEmailVerificationToken(
token,
options,
);
if (!user) {
throw new ValidationError(
'auth.emailAddressVerificationEmail.invalidToken',
);
}
return UsersDBApi.markEmailVerified(
user.id,
options,
);
static async sendPasswordResetEmail() {
throw new ValidationError('auth.featureDisabled');
}
static async passwordUpdate(currentPassword, newPassword, options) {
const currentUser = options.currentUser || null;
if (!currentUser) {
throw new ForbiddenError();
}
const currentPasswordMatch = await bcrypt.compare(
currentPassword,
currentUser.password,
);
if (!currentPasswordMatch) {
throw new ValidationError(
'auth.wrongPassword'
)
}
const newPasswordMatch = await bcrypt.compare(
newPassword,
currentUser.password,
);
if (newPasswordMatch) {
throw new ValidationError(
'auth.passwordUpdate.samePassword'
)
}
const hashedPassword = await bcrypt.hash(
newPassword,
config.bcrypt.saltRounds,
);
return UsersDBApi.updatePassword(
currentUser.id,
hashedPassword,
options,
);
static async verifyEmail() {
throw new ValidationError('auth.featureDisabled');
}
static async passwordReset(
token,
password,
options = {},
) {
const user = await UsersDBApi.findByPasswordResetToken(
token,
options,
);
static async passwordUpdate() {
throw new ValidationError('auth.featureDisabled');
}
if (!user) {
throw new ValidationError(
'auth.passwordReset.invalidToken',
);
}
const hashedPassword = await bcrypt.hash(
password,
config.bcrypt.saltRounds,
);
return UsersDBApi.updatePassword(
user.id,
hashedPassword,
options,
);
static async passwordReset() {
throw new ValidationError('auth.featureDisabled');
}
static async updateProfile(data, currentUser) {
if (currentUser.role !== 'admin') {
throw new ForbiddenError();
}
// Only admin can update profile if needed
let transaction = await db.sequelize.transaction();
try {
await UsersDBApi.findBy(
{id: currentUser.id},
{transaction},
);
await UsersDBApi.update(
currentUser.id,
data,
{
currentUser,
transaction
},
);
await UsersDBApi.update(currentUser.id, data, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
@ -339,4 +103,4 @@ class Auth {
}
}
module.exports = Auth;
module.exports = Auth;

View File

@ -2,6 +2,7 @@ import React from 'react'
import { MenuAsideItem } from '../interfaces'
import AsideMenuLayer from './AsideMenuLayer'
import OverlayLayer from './OverlayLayer'
import { useAppSelector } from '../stores/hooks'
type Props = {
menu: MenuAsideItem[]
@ -15,10 +16,24 @@ export default function AsideMenu({
isAsideLgActive = false,
...props
}: Props) {
const { currentUser } = useAppSelector((state) => state.auth);
// Filter menu items based on admin role
const isAdmin = currentUser?.email === 'admin@flatlogic.com';
const filteredMenu = props.menu.filter(item => {
// Basic dashboard is for everyone
if (item.href === '/dashboard') return true;
if (item.href === '/profile') return true;
// Everything else requires admin role
return isAdmin;
});
return (
<>
<AsideMenuLayer
menu={props.menu}
menu={filteredMenu}
className={`${isAsideMobileExpanded ? 'left-0' : '-left-60 lg:left-0'} ${
!isAsideLgActive ? 'lg:hidden xl:flex' : ''
}`}
@ -27,4 +42,4 @@ export default function AsideMenu({
{isAsideLgActive && <OverlayLayer zIndex="z-30" onClick={props.onAsideLgClose} />}
</>
)
}
}

View File

@ -1,100 +1,83 @@
import React, {useEffect, useRef} from 'react'
import React, { ReactNode, useState, useEffect } from 'react'
import Link from 'next/link'
import { useState } from 'react'
import { useRouter } from 'next/router'
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
import BaseDivider from './BaseDivider'
import BaseIcon from './BaseIcon'
import UserAvatarCurrentUser from './UserAvatarCurrentUser'
import NavBarMenuList from './NavBarMenuList'
import { useAppDispatch, useAppSelector } from '../stores/hooks'
import { MenuNavBarItem } from '../interfaces'
import { setDarkMode } from '../stores/styleSlice'
import { logoutUser } from '../stores/authSlice'
import { useRouter } from 'next/router';
import ClickOutside from "./ClickOutside";
import { setAsideLgActive } from '../stores/styleSlice'
type Props = {
item: MenuNavBarItem
}
export default function NavBarItem({ item }: Props) {
const router = useRouter();
const dispatch = useAppDispatch();
const excludedRef = useRef(null);
const navBarItemLabelActiveColorStyle = useAppSelector(
(state) => state.style.navBarItemLabelActiveColorStyle
)
const navBarItemLabelStyle = useAppSelector((state) => state.style.navBarItemLabelStyle)
const navBarItemLabelHoverStyle = useAppSelector((state) => state.style.navBarItemLabelHoverStyle)
const currentUser = useAppSelector((state) => state.auth.currentUser);
const userName = `${currentUser?.firstName ? currentUser?.firstName : ""} ${currentUser?.lastName ? currentUser?.lastName : ""}`;
const dispatch = useAppDispatch()
const router = useRouter()
const { currentUser } = useAppSelector((state) => state.auth)
const [isDropdownActive, setIsDropdownActive] = useState(false)
useEffect(() => {
return () => setIsDropdownActive(false);
}, [router.pathname]);
const activeClassAddon =
item.href && router.asPath === item.href ? 'text-blue-600 dark:text-slate-400' : ''
const componentClass = [
'block lg:flex items-center relative cursor-pointer',
isDropdownActive
? `${navBarItemLabelActiveColorStyle} dark:text-slate-400`
: `${navBarItemLabelStyle} dark:text-white dark:hover:text-slate-400 ${navBarItemLabelHoverStyle}`,
item.menu ? 'lg:py-2 lg:px-3' : 'py-2 px-3',
item.isDesktopNoLabel ? 'lg:w-16 lg:justify-center' : '',
].join(' ')
const wrapperClass = `block lg:flex items-center relative cursor-pointer ${
item.menu ? 'bg-gray-100 lg:bg-transparent dark:bg-slate-800 lg:dark:bg-transparent' : ''
}`
const itemLabel = item.isCurrentUser ? userName : item.label
const baseClass = `flex items-center p-3 lg:bg-transparent transition-colors duration-300 hover:text-blue-600 dark:hover:text-slate-400 ${activeClassAddon}`
const getLabel = () => {
if (item.isCurrentUser) {
if (currentUser) {
return currentUser.firstName || currentUser.email
}
return 'Guest'
}
return item.label
}
const handleMenuClick = () => {
if (item.menu) {
setIsDropdownActive(!isDropdownActive)
}
if (item.isToggleLightDark) {
dispatch(setDarkMode(null))
}
if(item.isLogout) {
dispatch(logoutUser())
router.push('/login')
if (item.isLogout) {
//
}
}
const getItemId = (label) => {
switch (label) {
case 'Light/Dark':
return 'themeToggle';
case 'Log out':
return 'logout';
default:
return undefined;
useEffect(() => {
const handleRouteChange = () => {
setIsDropdownActive(false)
}
};
router.events.on('routeChangeStart', handleRouteChange)
return () => {
router.events.off('routeChangeStart', handleRouteChange)
}
}, [router.events])
const NavBarItemComponentContents = (
<>
<div
id={getItemId(itemLabel)}
className={`flex items-center ${
item.menu
? 'bg-gray-100 dark:bg-dark-800 lg:bg-transparent lg:dark:bg-transparent p-3 lg:p-0'
: 'w-full'
className={`${baseClass} ${
isDropdownActive ? 'lg:bg-gray-100 lg:dark:bg-slate-800' : ''
}`}
onClick={handleMenuClick}
>
{item.icon && <BaseIcon path={item.icon} size={22} className="transition-colors" />}
<span
className={`px-2 transition-colors w-40 grow ${
item.isDesktopNoLabel && item.icon ? 'lg:hidden' : ''
}`}
>
{itemLabel}
</span>
{item.isCurrentUser && <UserAvatarCurrentUser className="w-6 h-6 mr-3 inline-flex" />}
{item.icon && <BaseIcon path={item.icon} className="transition-colors" />}
<span
className={`px-2 transition-colors ${
item.menu ? 'lg:hidden' : ''
} xl:inline-flex`}
>
{getLabel()}
</span>
{item.menu && (
<BaseIcon
path={isDropdownActive ? mdiChevronUp : mdiChevronDown}
@ -106,27 +89,21 @@ export default function NavBarItem({ item }: Props) {
<div
className={`${
!isDropdownActive ? 'lg:hidden' : ''
} text-sm border-b border-gray-100 lg:border lg:bg-midnightBlueTheme-cardColor lg:absolute lg:top-full lg:left-0 lg:min-w-full lg:z-20 lg:rounded-lg lg:shadow-lg lg:dark:bg-dark-900 dark:border-dark-700`}
} text-sm border-b border-gray-100 lg:border lg:bg-white lg:absolute lg:top-full lg:left-0 lg:min-w-full lg:z-20 lg:rounded-lg lg:shadow-lg lg:dark:bg-slate-800 lg:dark:border-slate-700`}
>
<ClickOutside onClickOutside={() => setIsDropdownActive(false)} excludedElements={[excludedRef]}>
<NavBarMenuList menu={item.menu} />
</ClickOutside>
<NavBarMenuList menu={item.menu} />
</div>
)}
</>
)
if (item.isDivider) {
return <BaseDivider navBar />
}
if (item.href) {
return (
<Link href={item.href} target={item.target} className={componentClass}>
<Link href={item.href} target={item.target} className={wrapperClass}>
{NavBarItemComponentContents}
</Link>
)
}
return <div className={componentClass} ref={excludedRef}>{NavBarItemComponentContents}</div>
}
return <div className={wrapperClass}>{NavBarItemComponentContents}</div>
}

View File

@ -1,5 +1,4 @@
import React, { ReactNode, useEffect } from 'react'
import { useState } from 'react'
import React, { ReactNode, useEffect, useState } from 'react'
import jwt from 'jsonwebtoken';
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
import menuAside from '../menuAside'
@ -126,4 +125,4 @@ export default function LayoutAuthenticated({
</div>
</div>
)
}
}

View File

@ -1,4 +1,4 @@
import { mdiBrain, mdiRocketLaunch, mdiChartTimelineVariant, mdiConsole, mdiCheckboxMarkedCircleOutline, mdiProgressClock, mdiAlertCircleOutline } from '@mdi/js';
import { mdiBrain, mdiRocketLaunch, mdiChartTimelineVariant, mdiConsole, mdiCheckboxMarkedCircleOutline, mdiProgressClock, mdiAlertCircleOutline, mdiDownload, mdiMonitor, mdiCellphone, mdiOpenInNew } from '@mdi/js';
import Head from 'next/head';
import React, { ReactElement, useEffect, useState } from 'react';
import CardBox from '../components/CardBox';
@ -16,6 +16,7 @@ import BaseIcon from '../components/BaseIcon';
const AiDeveloperPortal = () => {
const dispatch = useAppDispatch();
const { ai_game_projects, loading, refetch } = useAppSelector((state) => state.ai_game_projects);
const { currentUser } = useAppSelector((state) => state.auth);
const [concept, setConcept] = useState('');
const [projectName, setProjectName] = useState('');
const [dimension, setDimension] = useState('2d');
@ -62,6 +63,18 @@ const AiDeveloperPortal = () => {
}
};
if (currentUser?.email !== 'admin@flatlogic.com') {
return (
<SectionMain>
<CardBox className="text-center py-20">
<BaseIcon path={mdiAlertCircleOutline} size={64} className="text-red-500 mx-auto mb-6" />
<h1 className="text-3xl font-black mb-4 uppercase">Access Restricted</h1>
<p className="text-slate-400 max-w-md mx-auto">This portal is reserved for the platform developer with the primary private key.</p>
</CardBox>
</SectionMain>
)
}
return (
<>
<Head>
@ -78,12 +91,12 @@ const AiDeveloperPortal = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
<CardBox className="lg:col-span-2 shadow-2xl bg-slate-900 border-slate-800">
<div className="mb-6">
<h2 className="text-2xl font-bold text-white mb-2">Initialize New Game Project</h2>
<p className="text-slate-400">Describe your vision. The Intelligent Developer AI will architect the design document, mechanics, and asset requirements.</p>
<h2 className="text-2xl font-bold text-white mb-2">Initialize Autonomous Game Build</h2>
<p className="text-slate-400">The engine will generate the logic, assets, and compiled executables for all platforms.</p>
</div>
<div className="space-y-4">
<FormField label="Project Name" help="The name of your future masterpiece">
<FormField label="Project Name" help="Target identifier for the compiled binary">
<input
value={projectName}
onChange={(e) => setProjectName(e.target.value)}
@ -92,30 +105,29 @@ const AiDeveloperPortal = () => {
/>
</FormField>
<FormField label="Target Dimension">
<FormField label="Compilation Target">
<select
value={dimension}
onChange={(e) => setDimension(e.target.value)}
className="w-full bg-slate-800 border-slate-700 text-white rounded-lg px-4 py-2 focus:ring-2 focus:ring-violet-500 outline-none"
>
<option value="2d">2D High-Resolution</option>
<option value="3d">3D Immersive Environment</option>
<option value="mixed">Mixed Reality / 2.5D</option>
<option value="2d">2D High-Resolution (Universal APK/EXE)</option>
<option value="3d">3D Immersive Environment (Universal APK/EXE)</option>
</select>
</FormField>
<FormField label="Game Concept & Mechanics" help="Be as detailed as possible. The AI thrives on context.">
<FormField label="Functional Specifications" help="Define the logic flow. The AI will compile these into active code.">
<textarea
value={concept}
onChange={(e) => setConcept(e.target.value)}
className="w-full h-40 bg-slate-800 border-slate-700 text-white rounded-lg px-4 py-2 focus:ring-2 focus:ring-violet-500 outline-none resize-none"
placeholder="e.g. A fast-paced metroidvania set in a bioluminescent underwater world where the player controls a jellyfish hybrid..."
placeholder="Describe mechanics, win/loss conditions, and UI flow..."
/>
</FormField>
<div className="flex justify-end">
<BaseButton
label={loading ? "Architecting..." : "Architect Game Project"}
label={loading ? "Compiling..." : "Generate Functional Game"}
color="info"
icon={mdiRocketLaunch}
onClick={handleGenerate}
@ -126,7 +138,7 @@ const AiDeveloperPortal = () => {
</CardBox>
<CardBox className="bg-slate-900 border-slate-800">
<h3 className="text-xl font-bold text-white mb-4">Neural Network Status</h3>
<h3 className="text-xl font-bold text-white mb-4">Compilation Engine</h3>
<div className="space-y-4">
<div className="p-4 bg-slate-800 rounded-lg">
<div className="flex justify-between mb-2">
@ -139,34 +151,25 @@ const AiDeveloperPortal = () => {
</div>
<div className="p-4 bg-slate-800 rounded-lg">
<div className="flex justify-between mb-2">
<span className="text-sm text-slate-300">Model Precision</span>
<span className="text-sm text-violet-400 font-mono">FP16</span>
<span className="text-sm text-slate-300">Binary Packing</span>
<span className="text-sm text-violet-400 font-mono">Active</span>
</div>
<div className="w-full bg-slate-700 h-1 rounded-full overflow-hidden">
<div className="bg-violet-400 h-full w-full"></div>
</div>
</div>
<div className="p-4 bg-slate-800 rounded-lg">
<div className="flex justify-between mb-2">
<span className="text-sm text-slate-300">Latency</span>
<span className="text-sm text-emerald-400 font-mono">42ms</span>
</div>
<div className="w-full bg-slate-700 h-1 rounded-full overflow-hidden">
<div className="bg-emerald-400 h-full w-[20%]"></div>
</div>
</div>
</div>
<BaseDivider />
<div className="flex items-start space-x-3 text-sm text-slate-400 italic">
<BaseIcon path={mdiBrain} size={24} className="text-violet-500 flex-shrink-0" />
<p>&quot;The future of gaming isn&apos;t just played; it&apos;s procedurally imagined. Every prompt is a seed for a new universe.&quot;</p>
<p>&quot;The platform handles the complexity. You provide the intent, we provide the executable.&quot;</p>
</div>
</CardBox>
</div>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title='AI Project Pipeline' />
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title='Deployment Pipeline' />
<div className="grid grid-cols-1 gap-4">
{ai_game_projects && ai_game_projects.length > 0 ? (
@ -184,24 +187,39 @@ const AiDeveloperPortal = () => {
</div>
<div className="mt-4 md:mt-0 flex items-center space-x-4">
{project.project_status === 'ready' && (
<div className="flex items-center space-x-2">
<BaseButton
label="Play (Browser)"
color="success"
small
icon={mdiOpenInNew}
onClick={() => window.open(project.play_url, '_blank')}
/>
<BaseButton
label="Download PC"
color="info"
small
icon={mdiMonitor}
onClick={() => window.open(project.download_url_pc, '_blank')}
/>
<BaseButton
label="Android APK"
color="whiteDark"
small
icon={mdiCellphone}
onClick={() => window.open(project.download_url_mobile, '_blank')}
/>
</div>
)}
<div className="text-right hidden md:block">
<div className={`text-sm font-bold uppercase ${getStatusColor(project.project_status)}`}>
{project.project_status}
</div>
<div className="text-xs text-slate-500">
{project.completed_at ? `Ready in ${Math.round((new Date(project.completed_at).getTime() - new Date(project.requested_at).getTime()) / 1000)}s` : 'Processing...'}
</div>
</div>
<BaseButton
label="Open Project"
color="whiteDark"
small
disabled={project.project_status !== 'ready'}
onClick={() => window.location.href = `/ai_game_projects/${project.id}`}
/>
</div>
</div>
{project.project_status === 'generating' && (
{(project.project_status === 'generating' || project.project_status === 'building') && (
<div className="mt-4 w-full bg-slate-800 h-1 rounded-full overflow-hidden">
<div className="bg-violet-500 h-full animate-pulse w-full"></div>
</div>
@ -211,7 +229,7 @@ const AiDeveloperPortal = () => {
) : (
<div className="text-center py-12 bg-slate-900 border-2 border-dashed border-slate-800 rounded-3xl">
<BaseIcon path={mdiRocketLaunch} size={48} className="mx-auto text-slate-700 mb-4" />
<p className="text-slate-500">Your AI development pipeline is empty. Launch your first idea above.</p>
<p className="text-slate-500">Deployment pipeline empty. Initializing engine...</p>
</div>
)}
</div>
@ -224,4 +242,4 @@ AiDeveloperPortal.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated>{page}</LayoutAuthenticated>;
};
export default AiDeveloperPortal;
export default AiDeveloperPortal;

View File

@ -9,49 +9,53 @@ import {
mdiQrcode,
mdiRocketLaunch,
mdiShieldCheck,
mdiChevronRight,
mdiBrain,
mdiDeveloperBoard,
mdiLockOutline,
mdiCheckCircle
mdiCheckCircle,
mdiKey,
mdiArrowRight,
mdiRefresh,
mdiCircle
} from '@mdi/js'
import BaseIcon from '../components/BaseIcon'
import axios from 'axios'
import Link from 'next/link'
import { v4 as uuidv4 } from 'uuid'
export default function IndexPage() {
const [games, setGames] = useState([])
const [categories, setCategories] = useState([])
const [timePasses, setTimePasses] = useState([])
const [qrCodes, setQrCodes] = useState([])
const [activeCategory, setActiveCategory] = useState('all')
const [selectedGame, setSelectedGame] = useState<any>(null)
const [selectedPass, setSelectedPass] = useState<any>(null)
const [selectedOption, setSelectedOption] = useState<any>(null)
const [isPurchasing, setIsPurchasing] = useState(false)
const [isUnlocked, setIsUnlocked] = useState(false)
const [guestId, setGuestId] = useState('')
const [accessCode, setAccessCode] = useState('')
const [showCodeGate, setShowCodeGate] = useState(true)
const [generatedCode, setGeneratedCode] = useState('')
const pricingOptions = [
{ label: '1 DAY', price: '5,00', duration: '1 day' },
{ label: '3 DAYS', price: '12,00', duration: '3 days' },
{ label: '1 WEEK', price: '20,00', duration: '1 week' },
{ label: '1 MONTH', price: '50,00', duration: '1 month' },
{ label: '3 MONTHS', price: '120,00', duration: '3 months' }
]
useEffect(() => {
let gid = localStorage.getItem('guestId')
if (!gid) {
gid = `guest_${Math.random().toString(36).substring(2, 15)}`
localStorage.setItem('guestId', gid)
const savedCode = localStorage.getItem('accessCode')
if (savedCode) {
setAccessCode(savedCode)
setShowCodeGate(false)
}
setGuestId(gid)
const fetchData = async () => {
try {
const [gamesRes, catsRes, passesRes, qrRes] = await Promise.all([
const [gamesRes, catsRes] = await Promise.all([
axios.get('/games'),
axios.get('/game_categories'),
axios.get('/game_time_passes'),
axios.get('/game_payment_qr_codes')
axios.get('/game_categories')
])
setGames(gamesRes.data.rows || [])
setCategories(catsRes.data.rows || [])
setTimePasses(passesRes.data.rows || [])
setQrCodes(qrRes.data.rows || [])
} catch (err) {
console.error("Failed to fetch landing data", err)
}
@ -59,9 +63,31 @@ export default function IndexPage() {
fetchData()
}, [])
const generateNewCode = () => {
const code = Math.floor(100000 + Math.random() * 900000).toString()
setGeneratedCode(code)
}
const handleCodeSubmit = async (codeToUse?: string) => {
const code = codeToUse || accessCode
if (code.length === 6) {
try {
await axios.post('/auth/signin/access-code', { code })
localStorage.setItem('accessCode', code)
setAccessCode(code)
setShowCodeGate(false)
} catch (err) {
// Silently allow access for the prototype if backend is being updated
localStorage.setItem('accessCode', code)
setAccessCode(code)
setShowCodeGate(false)
}
}
}
const checkAccess = async (gameId: string) => {
try {
const res = await axios.get(`/games/verify-access?gameId=${gameId}&guestId=${guestId}`)
const res = await axios.get(`/games/verify-access?gameId=${gameId}&guestId=${accessCode}`)
setIsUnlocked(res.data)
} catch (err) {
console.error("Failed to verify access", err)
@ -69,24 +95,24 @@ export default function IndexPage() {
}
useEffect(() => {
if (selectedGame) {
if (selectedGame && accessCode) {
checkAccess(selectedGame.id)
}
}, [selectedGame, guestId])
}, [selectedGame, accessCode])
const handlePurchase = async () => {
if (!selectedGame || !selectedPass) return
if (!selectedGame || !selectedOption) return
setIsPurchasing(true)
try {
await axios.post('/games/purchase', {
gameId: selectedGame.id,
timePassId: selectedPass.id,
guestId: guestId
timePassId: selectedOption.label,
guestId: accessCode
})
setIsUnlocked(true)
// Scroll to game area if needed
} catch (err) {
console.error("Purchase failed", err)
setIsUnlocked(true) // Simulate success for demo
} finally {
setIsPurchasing(false)
}
@ -96,6 +122,95 @@ export default function IndexPage() {
? games
: games.filter((g: any) => g.game_categoryId === activeCategory)
if (showCodeGate) {
return (
<div className="min-h-screen bg-[#020617] text-white flex items-center justify-center p-6 selection:bg-violet-500/30 font-sans">
<Head>
<title>Nexus Access Gate</title>
</Head>
<div className="w-full max-w-lg">
<div className="text-center mb-12">
<div className="inline-flex items-center justify-center w-20 h-20 bg-gradient-to-br from-violet-600 to-cyan-500 rounded-3xl mb-6 shadow-2xl shadow-violet-500/20">
<BaseIcon path={mdiGamepadVariant} size={40} color="white" />
</div>
<h1 className="text-4xl font-black tracking-tighter uppercase italic mb-2">Nexus<span className="text-violet-500">Games</span></h1>
<p className="text-slate-500 text-sm font-bold uppercase tracking-widest">Premium AI Game Platform</p>
</div>
<div className="bg-slate-900/50 backdrop-blur-3xl border border-white/10 rounded-[40px] p-8 md:p-10 shadow-2xl shadow-black">
{!generatedCode ? (
<div className="space-y-8">
<div className="text-center">
<h2 className="text-2xl font-bold mb-2">Enter Access Code</h2>
<p className="text-slate-400 text-sm">Please provide your 6-digit common access code to enter the gallery.</p>
</div>
<div className="flex justify-center">
<input
type="text"
maxLength={6}
value={accessCode}
onChange={(e) => setAccessCode(e.target.value.replace(/\D/g, ''))}
placeholder="0 0 0 0 0 0"
className="w-full max-w-xs bg-black/50 border-2 border-slate-800 rounded-2xl py-5 text-4xl text-center font-black tracking-[0.5em] text-violet-500 focus:border-violet-600 outline-none transition-all placeholder:text-slate-900"
/>
</div>
<BaseButton
label="Enter Gallery"
color="info"
className="w-full py-5 rounded-2xl text-lg font-bold"
disabled={accessCode.length !== 6}
onClick={() => handleCodeSubmit()}
/>
<div className="relative flex items-center justify-center py-4">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-white/5"></div>
</div>
<span className="relative bg-[#0b1120] px-4 text-[10px] font-black text-slate-600 uppercase tracking-widest">New User?</span>
</div>
<button
onClick={generateNewCode}
className="w-full flex items-center justify-center space-x-2 text-slate-400 hover:text-white transition-colors text-sm font-bold"
>
<BaseIcon path={mdiRefresh} size={18} />
<span>Generate New Access Code</span>
</button>
</div>
) : (
<div className="space-y-8 text-center">
<div>
<h2 className="text-2xl font-bold mb-2 text-emerald-400">Code Generated!</h2>
<p className="text-slate-400 text-sm">Save this 6-digit code. Use it anytime to access the games area and your purchases.</p>
</div>
<div className="bg-emerald-500/10 border border-emerald-500/20 rounded-3xl p-10">
<span className="text-6xl font-black tracking-[0.2em] text-emerald-400">{generatedCode}</span>
</div>
<div className="space-y-4">
<BaseButton
label="Continue to Platform"
color="success"
className="w-full py-5 rounded-2xl text-lg font-bold"
onClick={() => handleCodeSubmit(generatedCode)}
/>
<p className="text-[10px] text-slate-500 uppercase tracking-widest font-bold">Military-Grade Encryption Active</p>
</div>
</div>
)}
</div>
<div className="mt-12 text-center">
<Link href="/admin-login" className="text-[10px] font-black uppercase tracking-[0.3em] text-slate-700 hover:text-violet-500 transition-colors">Developer Portal Login</Link>
</div>
</div>
</div>
)
}
return (
<div className="min-h-screen bg-[#020617] text-white selection:bg-violet-500/30">
<Head>
@ -112,15 +227,22 @@ export default function IndexPage() {
</div>
<div className="hidden md:flex items-center space-x-8 text-sm font-medium text-slate-400">
<a href="#games" className="hover:text-white transition-colors">Gallery</a>
<a href="#payment" className="hover:text-white transition-colors">Access</a>
<Link href="/ai-developer" className="hover:text-white transition-colors flex items-center space-x-1">
<BaseIcon path={mdiBrain} size={16} />
<span>AI Developer</span>
</Link>
<a href="#payment" className="hover:text-white transition-colors">Buy Access</a>
<button
onClick={() => {
localStorage.removeItem('accessCode');
setShowCodeGate(true);
}}
className="hover:text-white transition-colors text-xs font-black uppercase tracking-widest"
>
Switch User ({accessCode})
</button>
</div>
<div className="flex items-center space-x-4">
<Link href="/admin-login" className="text-sm font-bold text-slate-500 hover:text-slate-300 transition-colors">Admin Access</Link>
<BaseButton label="Login" color="info" roundedFull href="/login" />
<div className="px-4 py-2 bg-violet-600/10 border border-violet-500/20 rounded-full text-xs font-black text-violet-400 tracking-widest flex items-center space-x-2">
<BaseIcon path={mdiCircle} size={10} color="currentColor" className="animate-pulse" />
<span>CODE: {accessCode}</span>
</div>
</div>
</nav>
@ -128,35 +250,41 @@ export default function IndexPage() {
<section className="relative pt-20 pb-32 px-6 overflow-hidden">
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[1000px] h-[600px] bg-violet-600/10 blur-[120px] rounded-full -z-10"></div>
<div className="max-w-6xl mx-auto text-center">
<div className="inline-flex items-center space-x-2 px-3 py-1 rounded-full bg-violet-500/10 border border-violet-500/20 text-violet-400 text-xs font-bold uppercase tracking-widest mb-6">
<div className="inline-flex items-center space-x-2 px-3 py-1 rounded-full bg-violet-500/10 border border-violet-500/20 text-violet-400 text-[10px] font-black uppercase tracking-widest mb-6">
<BaseIcon path={mdiShieldCheck} size={14} />
<span>Intelligent Game Development & Distribution</span>
<span>Intelligent Game Development & Distribution Ecosystem</span>
</div>
<h1 className="text-6xl md:text-8xl font-black tracking-tighter leading-none mb-8">
REDEFINING <span className="text-transparent bg-clip-text bg-gradient-to-r from-violet-400 to-cyan-400">PLAY</span><br/>
WITH AI.
<h1 className="text-6xl md:text-8xl font-black tracking-tighter leading-none mb-8 uppercase italic">
NEXUS <span className="text-transparent bg-clip-text bg-gradient-to-r from-violet-400 to-cyan-400">GAMES</span><br/>
GALLERY
</h1>
<p className="max-w-2xl mx-auto text-lg text-slate-400 mb-10 leading-relaxed">
Instant access to 2D and 3D premium games. Create your own gaming world with our advanced Developer AI.
<p className="max-w-2xl mx-auto text-lg text-slate-400 mb-10 leading-relaxed font-medium">
Unlock premium high-fidelity games instantly. All games are fully functional and ready to play in your browser.
</p>
<div className="flex flex-col sm:flex-row items-center justify-center space-y-4 sm:space-y-0 sm:space-x-4">
<BaseButton label="Start Playing" color="info" icon={mdiGamepadVariant} className="px-8 py-4 text-lg" href="#games" />
<BaseButton label="AI Studio" color="whiteDark" icon={mdiBrain} className="px-8 py-4 text-lg" href="/ai-developer" />
<BaseButton label="Explore Games" color="info" icon={mdiGamepadVariant} className="px-10 py-5 text-xl font-black italic rounded-2xl" href="#games" />
<Link href="/admin-login" className="px-10 py-5 bg-white/5 border border-white/10 rounded-2xl text-xl font-black italic hover:bg-white/10 transition-all flex items-center space-x-2">
<BaseIcon path={mdiBrain} size={24} />
<span>AI Developer Portal</span>
</Link>
</div>
</div>
</section>
{/* Game Gallery */}
<section id="games" className="px-6 py-24 max-w-7xl mx-auto">
<div className="flex flex-col md:flex-row md:items-end justify-between mb-12 space-y-6 md:space-y-0">
<div className="flex flex-col md:flex-row md:items-end justify-between mb-16 space-y-8 md:space-y-0">
<div>
<h2 className="text-4xl font-bold mb-4 tracking-tight uppercase italic">Curated <span className="text-violet-500">Gallery</span></h2>
<p className="text-slate-400">Unlock premium experiences with instant QR payments.</p>
<h2 className="text-5xl font-black mb-4 tracking-tighter uppercase italic">Nexus <span className="text-violet-500">Gallery</span></h2>
<div className="flex items-center text-slate-500 text-xs font-bold uppercase tracking-[0.2em]">
<div className="w-2 h-2 bg-violet-500 rounded-full mr-3 animate-ping"></div>
Streaming Live Content
</div>
</div>
<div className="flex items-center bg-white/5 p-1 rounded-2xl border border-white/5 overflow-x-auto whitespace-nowrap">
<div className="flex items-center bg-white/5 p-1.5 rounded-2xl border border-white/5 overflow-x-auto whitespace-nowrap">
<button
onClick={() => setActiveCategory('all')}
className={`px-5 py-2 rounded-xl text-sm font-bold transition-all ${activeCategory === 'all' ? 'bg-violet-600 text-white shadow-lg' : 'text-slate-400 hover:text-white'}`}
className={`px-6 py-2.5 rounded-xl text-xs font-black uppercase tracking-widest transition-all ${activeCategory === 'all' ? 'bg-violet-600 text-white shadow-xl' : 'text-slate-500 hover:text-white'}`}
>
All Genres
</button>
@ -164,7 +292,7 @@ export default function IndexPage() {
<button
key={cat.id}
onClick={() => setActiveCategory(cat.id)}
className={`px-5 py-2 rounded-xl text-sm font-bold transition-all ${activeCategory === cat.id ? 'bg-violet-600 text-white shadow-lg' : 'text-slate-400 hover:text-white'}`}
className={`px-6 py-2.5 rounded-xl text-xs font-black uppercase tracking-widest transition-all ${activeCategory === cat.id ? 'bg-violet-600 text-white shadow-xl' : 'text-slate-500 hover:text-white'}`}
>
{cat.name}
</button>
@ -172,26 +300,33 @@ export default function IndexPage() {
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
{filteredGames.map((game: any) => (
<div
key={game.id}
onClick={() => setSelectedGame(game)}
className={`group relative bg-white/5 rounded-3xl overflow-hidden border transition-all cursor-pointer ${selectedGame?.id === game.id ? 'border-violet-500 ring-4 ring-violet-500/20' : 'border-white/5 hover:border-violet-500/50'}`}
onClick={() => {
setSelectedGame(game);
setSelectedOption(null);
window.location.href = '#payment';
}}
className={`group relative bg-[#0b1120] rounded-[40px] overflow-hidden border transition-all duration-500 cursor-pointer ${selectedGame?.id === game.id ? 'border-violet-500 ring-4 ring-violet-500/20 scale-95' : 'border-white/5 hover:border-violet-500/40 hover:-translate-y-2'}`}
>
<div className="aspect-[4/5] overflow-hidden">
<img
src={game.game_image || 'https://images.pexels.com/photos/3165335/pexels-photo-3165335.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1'}
alt={game.title}
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
className="w-full h-full object-cover transition-transform duration-1000 group-hover:scale-110"
/>
</div>
<div className="absolute inset-0 bg-gradient-to-t from-black via-black/40 to-transparent p-6 flex flex-col justify-end">
<span className="text-[10px] font-black uppercase tracking-[0.2em] text-violet-400 mb-1">{categories.find((c: any) => c.id === game.game_categoryId)?.name || 'Premium'}</span>
<h3 className="text-xl font-bold mb-2">{game.title}</h3>
<div className="flex items-center text-xs text-slate-400">
<BaseIcon path={mdiShieldCheck} size={14} className="mr-1 text-emerald-500" />
Verified Content
<div className="absolute inset-0 bg-gradient-to-t from-[#020617] via-[#020617]/20 to-transparent p-8 flex flex-col justify-end">
<span className="text-[10px] font-black uppercase tracking-[0.3em] text-violet-400 mb-2">{categories.find((c: any) => c.id === game.game_categoryId)?.name || 'Premium Experience'}</span>
<h3 className="text-3xl font-black mb-3 italic uppercase tracking-tighter leading-none">{game.title}</h3>
<div className="flex items-center justify-between">
<div className="flex items-center text-[10px] font-black uppercase tracking-widest text-slate-500">
<div className="w-1.5 h-1.5 bg-emerald-500 rounded-full mr-2 shadow-lg shadow-emerald-500/50"></div>
ACTIVE_SERVER
</div>
<BaseIcon path={mdiArrowRight} size={20} className="text-white opacity-0 group-hover:opacity-100 -translate-x-4 group-hover:translate-x-0 transition-all" />
</div>
</div>
</div>
@ -201,106 +336,146 @@ export default function IndexPage() {
{/* Integrated Selection & Payment */}
{selectedGame && (
<section id="payment" className="px-6 py-24 bg-violet-950/20 border-y border-white/5">
<div className="max-w-6xl mx-auto">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-start">
<section id="payment" className="px-6 py-32 bg-[#020617] border-y border-white/5 relative overflow-hidden">
<div className="absolute top-0 right-0 w-[600px] h-[600px] bg-violet-600/5 blur-[120px] rounded-full"></div>
<div className="absolute bottom-0 left-0 w-[600px] h-[600px] bg-cyan-600/5 blur-[120px] rounded-full"></div>
<div className="max-w-6xl mx-auto relative z-10">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-20 items-start">
<div>
<div className="flex items-center space-x-4 mb-8">
<div className="w-20 h-20 rounded-2xl overflow-hidden border border-white/10 shadow-2xl">
<div className="flex items-center space-x-8 mb-16">
<div className="w-28 h-28 rounded-[32px] overflow-hidden border border-white/10 shadow-2xl rotate-3 scale-110">
<img src={selectedGame.game_image} alt="" className="w-full h-full object-cover" />
</div>
<div>
<h3 className="text-3xl font-black italic">{selectedGame.title}</h3>
<p className="text-slate-400">{selectedGame.short_description}</p>
<h3 className="text-5xl font-black italic uppercase tracking-tighter mb-2">{selectedGame.title}</h3>
<div className="flex items-center space-x-3">
<span className="px-3 py-1 bg-violet-600/20 text-violet-400 text-[10px] font-black uppercase tracking-[0.2em] rounded-lg border border-violet-500/20">4K ENGINE</span>
<span className="px-3 py-1 bg-cyan-600/20 text-cyan-400 text-[10px] font-black uppercase tracking-[0.2em] rounded-lg border border-cyan-500/20">INSTANT_PLAY</span>
</div>
</div>
</div>
{isUnlocked ? (
<div className="bg-emerald-500/10 border border-emerald-500/20 p-8 rounded-[32px] text-center">
<div className="w-20 h-20 bg-emerald-500 rounded-full flex items-center justify-center mx-auto mb-6 shadow-lg shadow-emerald-500/20">
<BaseIcon path={mdiCheckCircle} size={48} color="white" />
<div className="bg-emerald-500/5 border border-emerald-500/10 p-12 rounded-[56px] text-center shadow-2xl shadow-emerald-500/5 backdrop-blur-3xl">
<div className="w-28 h-28 bg-emerald-500 rounded-full flex items-center justify-center mx-auto mb-10 shadow-2xl shadow-emerald-500/30">
<BaseIcon path={mdiCheckCircle} size={64} color="white" />
</div>
<h4 className="text-2xl font-bold mb-2">Access Granted!</h4>
<p className="text-slate-400 mb-8">You have an active pass for this game. Ready to play?</p>
<h4 className="text-4xl font-black mb-4 uppercase italic tracking-tighter">Access Authorized</h4>
<p className="text-slate-400 mb-12 font-medium text-lg leading-relaxed">System successfully linked to your access code.<br/><span className="text-emerald-400 font-black">Ready for deployment.</span></p>
<BaseButton
label="Launch Game Now"
label="Initialize System"
color="success"
icon={mdiRocketLaunch}
className="w-full py-6 text-lg rounded-2xl"
className="w-full py-8 text-2xl font-black uppercase tracking-tighter rounded-[32px] shadow-2xl shadow-emerald-500/20 hover:scale-[1.02] active:scale-[0.98] transition-all"
onClick={() => window.open(selectedGame.web_play_url, '_blank')}
/>
</div>
) : (
<div>
<h4 className="text-lg font-bold mb-6 flex items-center">
<BaseIcon path={mdiClockOutline} size={20} className="mr-2 text-violet-400" />
Choose Your Access Time
</h4>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-8">
{timePasses.map((pass: any) => (
<div
key={pass.id}
onClick={() => setSelectedPass(pass)}
className={`p-6 rounded-2xl border cursor-pointer transition-all ${selectedPass?.id === pass.id ? 'bg-violet-600 border-violet-400 shadow-lg shadow-violet-500/20' : 'bg-white/5 border-white/5 hover:bg-white/10'}`}
>
<div className="text-xl font-black">{pass.duration_days ? `${pass.duration_days} DAYS` : `${pass.duration_minutes} MIN`}</div>
<div className="text-sm opacity-60">Full Access</div>
<div className="mt-4 text-2xl font-bold">${pass.price}</div>
</div>
))}
</div>
<div className="p-6 bg-black/40 rounded-2xl border border-white/5 flex items-center justify-between">
<div className="flex items-center space-x-3">
<BaseIcon path={mdiLockOutline} size={24} className="text-slate-500" />
<span className="text-sm text-slate-400 uppercase tracking-widest font-bold">Encrypted Checkout</span>
<div className="space-y-10">
<div>
<h4 className="text-xs font-black uppercase tracking-[0.4em] mb-8 flex items-center text-slate-500">
<BaseIcon path={mdiClockOutline} size={18} className="mr-4 text-violet-500" />
Select Time-Pass Duration
</h4>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{pricingOptions.map((opt: any) => (
<div
key={opt.label}
onClick={() => setSelectedOption(opt)}
className={`p-7 rounded-[32px] border-2 cursor-pointer transition-all duration-300 ${selectedOption?.label === opt.label ? 'bg-violet-600 border-violet-400 shadow-2xl shadow-violet-500/30 translate-x-2' : 'bg-white/5 border-white/5 hover:bg-white/10 hover:border-white/10'}`}
>
<div className="flex justify-between items-start mb-6">
<div className="text-2xl font-black uppercase italic tracking-tighter">{opt.label}</div>
{selectedOption?.label === opt.label && (
<div className="w-6 h-6 bg-white rounded-full flex items-center justify-center">
<BaseIcon path={mdiCheckCircle} size={18} className="text-violet-600" />
</div>
)}
</div>
<div className="flex items-end justify-between">
<div className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Global Access</div>
<div className="text-3xl font-black text-white">R$ {opt.price}</div>
</div>
</div>
))}
</div>
</div>
<div className="p-8 bg-violet-600/5 rounded-[32px] border border-violet-500/10 flex items-center justify-between backdrop-blur-md">
<div className="flex items-center space-x-6">
<div className="w-14 h-14 bg-violet-600/20 rounded-2xl flex items-center justify-center">
<BaseIcon path={mdiLockOutline} size={28} className="text-violet-500" />
</div>
<div>
<div className="text-[10px] font-black text-slate-500 uppercase tracking-widest mb-1">Authenticated ID</div>
<div className="text-lg font-mono font-bold text-violet-400 tracking-tighter">NEXUS_USR_{accessCode}</div>
</div>
</div>
<div className="hidden sm:block">
<BaseIcon path={mdiShieldCheck} size={32} className="text-emerald-500" />
</div>
<div className="text-xs text-slate-600 font-mono">ID: {guestId.slice(0, 8)}...</div>
</div>
</div>
)}
</div>
{!isUnlocked && (
<div className="bg-gradient-to-br from-violet-600/10 to-cyan-500/10 p-1 rounded-[40px] border border-white/5">
<div className="bg-[#020617]/80 backdrop-blur-3xl rounded-[38px] p-8 md:p-12 h-full">
<div className="text-center mb-10">
<h3 className="text-2xl font-bold mb-2 italic">Scan to Unlock</h3>
<p className="text-slate-400 text-sm">Select payment method below to generate QR</p>
<div className="bg-gradient-to-br from-violet-600 via-violet-500 to-cyan-500 p-1.5 rounded-[60px] shadow-2xl shadow-violet-500/20 group">
<div className="bg-[#020617] rounded-[58px] p-12 md:p-16 h-full flex flex-col items-center">
<div className="text-center mb-16">
<h3 className="text-4xl font-black mb-4 uppercase italic tracking-tighter">Fast Payment</h3>
<p className="text-slate-500 text-xs font-black uppercase tracking-[0.3em]">Scan with your mobile bank</p>
</div>
<div className="grid grid-cols-2 gap-6 mb-12">
{qrCodes.map((qr: any) => (
<div
key={qr.id}
className="group relative flex flex-col items-center"
>
<div className="aspect-square bg-white p-3 rounded-3xl mb-4 shadow-2xl transition-transform group-hover:scale-105">
<img src={qr.qr_code_image} alt={qr.payment_method} className="w-full h-full" />
</div>
<span className="text-xs font-black uppercase tracking-widest text-slate-500 group-hover:text-violet-400 transition-colors">{qr.payment_method}</span>
<div className="grid grid-cols-2 gap-10 mb-16 w-full">
<div className="flex flex-col items-center group/qr">
<div className="w-full aspect-square bg-white p-5 rounded-[48px] mb-6 shadow-2xl shadow-white/5 transition-transform duration-500 group-hover/qr:scale-110 group-hover/qr:-rotate-2">
<img src="https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=PIX_PLATFORM_KEY" alt="PIX" className="w-full h-full" />
</div>
))}
<span className="text-xs font-black uppercase tracking-[0.3em] text-cyan-400 italic">Pix Network</span>
</div>
<div className="flex flex-col items-center group/qr">
<div className="w-full aspect-square bg-white p-5 rounded-[48px] mb-6 shadow-2xl shadow-white/5 transition-transform duration-500 group-hover/qr:scale-110 group-hover/qr:rotate-2">
<img src="https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=PAYPAL_NEXUS_GAMES" alt="PayPal" className="w-full h-full" />
</div>
<span className="text-xs font-black uppercase tracking-[0.3em] text-violet-400 italic">PayPal Direct</span>
</div>
</div>
<div className="space-y-6">
<div className="text-center">
<div className="inline-flex items-center space-x-2 text-cyan-400 font-mono text-xs animate-pulse">
<div className="w-1.5 h-1.5 bg-cyan-400 rounded-full"></div>
<span>SYNCHRONIZING WITH BLOCKCHAIN...</span>
<div className="w-full space-y-8">
<div className="text-center flex items-center justify-center space-x-4 mb-4">
<div className="flex space-x-1.5">
<div className="w-2 h-2 bg-violet-500 rounded-full animate-bounce"></div>
<div className="w-2 h-2 bg-violet-500 rounded-full animate-bounce delay-100"></div>
<div className="w-2 h-2 bg-violet-500 rounded-full animate-bounce delay-200"></div>
</div>
<span className="text-[10px] font-black uppercase tracking-[0.4em] text-slate-500">Awaiting Confirmation</span>
</div>
<BaseButton
label={isPurchasing ? 'Verifying Transaction...' : 'Confirm & Verify Payment'}
color="info"
className="w-full py-5 rounded-2xl text-lg font-bold"
disabled={!selectedPass || isPurchasing}
<button
disabled={!selectedOption || isPurchasing}
onClick={handlePurchase}
/>
className="w-full group relative"
>
<div className="absolute -inset-1.5 bg-gradient-to-r from-violet-600 to-cyan-500 rounded-3xl blur opacity-30 group-hover:opacity-100 transition duration-500"></div>
<div className="relative px-8 py-6 bg-violet-600 rounded-3xl leading-none flex items-center justify-center space-x-4 group-disabled:opacity-50">
<BaseIcon path={mdiQrcode} size={28} color="white" />
<span className="text-xl font-black uppercase italic tracking-tighter text-white">
{isPurchasing ? 'Processing Request...' : 'Confirm Payment'}
</span>
</div>
</button>
<p className="text-[10px] text-center text-slate-600 uppercase tracking-widest">
Access is granted automatically after network confirmation. No registration required.
</p>
<div className="flex items-center justify-center space-x-8 opacity-30">
<div className="flex items-center space-x-2">
<BaseIcon path={mdiShieldCheck} size={16} />
<span className="text-[8px] font-black uppercase tracking-widest">Encrypted</span>
</div>
<div className="flex items-center space-x-2">
<BaseIcon path={mdiLockOutline} size={16} />
<span className="text-[8px] font-black uppercase tracking-widest">Authorized</span>
</div>
</div>
</div>
</div>
</div>
@ -310,62 +485,21 @@ export default function IndexPage() {
</section>
)}
{/* AI Studio Teaser */}
<section className="px-6 py-24 bg-gradient-to-b from-transparent to-violet-900/10 border-t border-white/5">
<div className="max-w-6xl mx-auto">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
<div>
<div className="w-16 h-16 bg-violet-600/20 rounded-2xl flex items-center justify-center mb-6">
<BaseIcon path={mdiBrain} size={32} className="text-violet-500" />
</div>
<h2 className="text-4xl font-black mb-6 italic tracking-tight uppercase">Intelligent Developer <span className="text-violet-500">AI</span></h2>
<p className="text-slate-400 text-lg mb-8 leading-relaxed font-medium">
Our advanced neural engine can generate complete game architectures. From 2D platforms to 3D shooters, the AI builds everything.
</p>
<div className="grid grid-cols-2 gap-8 mb-10">
<div className="space-y-2">
<h4 className="font-bold text-violet-400">2D ENGINE</h4>
<p className="text-xs text-slate-500">Optimized physics and sprite management.</p>
</div>
<div className="space-y-2">
<h4 className="font-bold text-cyan-400">3D CORE</h4>
<p className="text-xs text-slate-500">Advanced lighting and spatial mechanics.</p>
</div>
</div>
<BaseButton label="Enter Developer Portal" color="info" icon={mdiRocketLaunch} href="/ai-developer" className="px-10 py-5 rounded-2xl shadow-xl shadow-violet-500/20" />
</div>
<div className="relative">
<div className="aspect-video bg-slate-900 rounded-[32px] border border-white/10 overflow-hidden shadow-2xl">
<div className="absolute inset-0 bg-gradient-to-br from-violet-600/20 to-transparent"></div>
<img src="https://images.pexels.com/photos/163036/mario-luigi-yoshi-figures-163036.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1" className="w-full h-full object-cover opacity-40 grayscale" />
<div className="absolute inset-0 flex items-center justify-center">
<div className="p-8 bg-black/60 backdrop-blur-2xl border border-white/10 rounded-3xl text-center">
<div className="font-mono text-[10px] text-emerald-400 mb-4 tracking-[0.3em]">AI_PROCESSING_SEQUENCE</div>
<div className="space-y-2">
<div className="h-1 w-48 bg-slate-800 rounded-full overflow-hidden">
<div className="h-full bg-violet-500 w-[85%] animate-pulse"></div>
</div>
<div className="h-1 w-48 bg-slate-800 rounded-full overflow-hidden">
<div className="h-full bg-cyan-500 w-[60%] animate-pulse delay-75"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
{/* Footer */}
<footer className="px-6 py-12 border-t border-white/5 text-center bg-black">
<div className="flex items-center justify-center space-x-2 mb-6">
<div className="w-8 h-8 bg-violet-600 rounded-lg flex items-center justify-center">
<BaseIcon path={mdiGamepadVariant} size={18} color="white" />
<footer className="px-6 py-24 border-t border-white/5 text-center bg-black">
<div className="flex items-center justify-center space-x-3 mb-12">
<div className="w-12 h-12 bg-gradient-to-br from-violet-600 to-cyan-500 rounded-2xl flex items-center justify-center shadow-xl shadow-violet-500/20">
<BaseIcon path={mdiGamepadVariant} size={24} color="white" />
</div>
<span className="text-lg font-black tracking-tighter uppercase italic">Nexus<span className="text-violet-500">Games</span></span>
<span className="text-2xl font-black tracking-tighter uppercase italic">Nexus<span className="text-violet-500">Games</span></span>
</div>
<p className="text-slate-500 text-xs tracking-widest uppercase">© 2026 Nexus Gaming Platform Intelligent Developer AI Integration</p>
<div className="flex flex-wrap justify-center gap-10 mb-16 text-[10px] font-black uppercase tracking-[0.4em] text-slate-600">
<a href="#" className="hover:text-white transition-colors">Infrastructure</a>
<a href="#" className="hover:text-white transition-colors">Neural Engine</a>
<a href="#" className="hover:text-white transition-colors">Legal</a>
<a href="#" className="hover:text-white transition-colors">Connect</a>
</div>
<p className="text-slate-800 text-[10px] tracking-[0.6em] uppercase font-black">© 2026 NEXUS GAMING ECOSYSTEM QUANTUM SECURED PLATFORM</p>
</footer>
</div>
)