diff --git a/backend/src/db/models/ai_game_projects.js b/backend/src/db/models/ai_game_projects.js index aafd80e..8ca8bff 100644 --- a/backend/src/db/models/ai_game_projects.js +++ b/backend/src/db/models/ai_game_projects.js @@ -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; -}; - - +}; \ No newline at end of file diff --git a/backend/src/routes/auth.js b/backend/src/routes/auth.js index 8777aa8..603d5f2 100644 --- a/backend/src/routes/auth.js +++ b/backend/src/routes/auth.js @@ -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; \ No newline at end of file +module.exports = router; diff --git a/backend/src/services/ai_game_projects.js b/backend/src/services/ai_game_projects.js index 778a70c..0ce062e 100644 --- a/backend/src/services/ai_game_projects.js +++ b/backend/src/services/ai_game_projects.js @@ -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; } } - - -}; \ No newline at end of file +}; diff --git a/backend/src/services/auth.js b/backend/src/services/auth.js index 0984d19..a8d0140 100644 --- a/backend/src/services/auth.js +++ b/backend/src/services/auth.js @@ -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; \ No newline at end of file +module.exports = Auth; diff --git a/frontend/src/components/AsideMenu.tsx b/frontend/src/components/AsideMenu.tsx index 442dfac..e9e387e 100644 --- a/frontend/src/components/AsideMenu.tsx +++ b/frontend/src/components/AsideMenu.tsx @@ -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 ( <> } ) -} +} \ No newline at end of file diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx index eb155e3..a191d43 100644 --- a/frontend/src/components/NavBarItem.tsx +++ b/frontend/src/components/NavBarItem.tsx @@ -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 = ( <>
- {item.icon && } - - {itemLabel} - {item.isCurrentUser && } + {item.icon && } + + {getLabel()} + {item.menu && ( - setIsDropdownActive(false)} excludedElements={[excludedRef]}> - - +
)} ) - if (item.isDivider) { - return - } - if (item.href) { return ( - + {NavBarItemComponentContents} ) } - return
{NavBarItemComponentContents}
-} + return
{NavBarItemComponentContents}
+} \ No newline at end of file diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx index 1b9907d..26c3572 100644 --- a/frontend/src/layouts/Authenticated.tsx +++ b/frontend/src/layouts/Authenticated.tsx @@ -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({ ) -} +} \ No newline at end of file diff --git a/frontend/src/pages/ai-developer.tsx b/frontend/src/pages/ai-developer.tsx index d2e0979..5bc4236 100644 --- a/frontend/src/pages/ai-developer.tsx +++ b/frontend/src/pages/ai-developer.tsx @@ -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 ( + + + +

Access Restricted

+

This portal is reserved for the platform developer with the primary private key.

+
+
+ ) + } + return ( <> @@ -78,12 +91,12 @@ const AiDeveloperPortal = () => {
-

Initialize New Game Project

-

Describe your vision. The Intelligent Developer AI will architect the design document, mechanics, and asset requirements.

+

Initialize Autonomous Game Build

+

The engine will generate the logic, assets, and compiled executables for all platforms.

- + setProjectName(e.target.value)} @@ -92,30 +105,29 @@ const AiDeveloperPortal = () => { /> - + - +