Merge pull request #62 from expsa/expert_theme
expert Odoo 18 theme module
This commit is contained in:
commit
093a0a47af
|
|
@ -0,0 +1,30 @@
|
|||
# Expert Theme
|
||||
|
||||
A simple and clean theme for Odoo 18 backend that provides a custom home page displaying only installed modules.
|
||||
|
||||
## Features
|
||||
|
||||
- **Custom Home Page**: Clean interface showing only your installed modules
|
||||
- **Simple Navigation**: Easy access to all your applications
|
||||
- **Responsive Design**: Works on all devices
|
||||
- **Minimal Interface**: No clutter, just functionality
|
||||
|
||||
## Installation
|
||||
|
||||
1. Copy this module to your Odoo addons directory
|
||||
2. Update the apps list in Odoo
|
||||
3. Install the "Expert Theme" module
|
||||
4. The custom home page will be available after installation
|
||||
|
||||
## Usage
|
||||
|
||||
After installation, when you log into Odoo, you'll see the new home page with all your installed modules displayed in a clean grid layout. Simply click on any module to access it.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Odoo 18.0
|
||||
- No additional dependencies
|
||||
|
||||
## Support
|
||||
|
||||
This is a simple theme module designed for basic customization of the Odoo backend home page.
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import models
|
||||
from . import controllers
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': 'Expert Theme',
|
||||
'version': '18.0.1.0.0',
|
||||
'category': 'Theme/Backend',
|
||||
'summary': 'Custom backend theme with installed modules home page',
|
||||
'description': """
|
||||
Expert Theme
|
||||
============
|
||||
|
||||
A simple custom theme for Odoo 18 backend that provides:
|
||||
- Custom home page displaying only installed modules
|
||||
- Clean and minimal interface
|
||||
- Easy navigation to installed applications
|
||||
""",
|
||||
'author': 'Expert',
|
||||
'website': '',
|
||||
'depends': ['base', 'web'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'data/expert_login_template_data.xml',
|
||||
'views/login_templates.xml',
|
||||
'views/expert_login_template_views.xml',
|
||||
'views/expert_theme_config_views.xml',
|
||||
'views/expert_home_views.xml',
|
||||
'views/expert_menu_views.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'expert_theme/static/src/css/expert_theme_config.css',
|
||||
'expert_theme/static/src/css/expert_theme.css',
|
||||
'expert_theme/static/src/js/expert_theme_dynamic.js',
|
||||
'expert_theme/static/src/js/expert_theme_config.js',
|
||||
'expert_theme/static/src/js/expert_home.js',
|
||||
'expert_theme/static/src/js/expert_login_template_list.js',
|
||||
'expert_theme/static/src/xml/expert_home.xml',
|
||||
],
|
||||
'web.assets_frontend': [
|
||||
'expert_theme/static/src/css/expert_login.css',
|
||||
'expert_theme/static/src/css/login_modern.css',
|
||||
'expert_theme/static/src/css/login_minimal.css',
|
||||
'expert_theme/static/src/js/expert_login_template.js',
|
||||
],
|
||||
},
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'application': False,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import expert_controller
|
||||
|
|
@ -0,0 +1,286 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import http, fields
|
||||
from odoo.http import request
|
||||
import json
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ExpertController(http.Controller):
|
||||
|
||||
@http.route('/expert_theme/test', type='http', auth='user')
|
||||
def test_controller(self):
|
||||
"""Simple test endpoint to verify controller is working"""
|
||||
return request.make_json_response({
|
||||
'success': True,
|
||||
'message': 'Controller is working!',
|
||||
'timestamp': fields.Datetime.now()
|
||||
})
|
||||
|
||||
@http.route('/expert_theme/get_css_variables', type='http', auth='user')
|
||||
def get_css_variables(self):
|
||||
"""Get CSS variables for the active theme configuration"""
|
||||
try:
|
||||
config = request.env['expert.theme.config'].get_active_config()
|
||||
css_variables = config.get_css_variables()
|
||||
return request.make_json_response({
|
||||
'success': True,
|
||||
'css_variables': css_variables
|
||||
})
|
||||
except Exception as e:
|
||||
return request.make_json_response({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
})
|
||||
|
||||
@http.route('/expert_theme/apply_theme', type='http', auth='user', methods=['POST'])
|
||||
def apply_theme(self):
|
||||
"""Apply theme changes immediately"""
|
||||
try:
|
||||
# Get the active configuration
|
||||
config = request.env['expert.theme.config'].get_active_config()
|
||||
|
||||
# Apply the theme (this could trigger cache invalidation, etc.)
|
||||
config.apply_theme()
|
||||
|
||||
return request.make_json_response({
|
||||
'success': True,
|
||||
'message': 'Theme applied successfully!'
|
||||
})
|
||||
except Exception as e:
|
||||
return request.make_json_response({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
})
|
||||
|
||||
@http.route('/web/login', type='http', auth='none', methods=['GET', 'POST'], csrf=False)
|
||||
def web_login(self, redirect=None, **kw):
|
||||
"""Override Odoo login to use expert login templates when configured.
|
||||
|
||||
- POST: delegate to standard Odoo login logic (authentication, redirects, etc.).
|
||||
- GET: render the active expert login template if not 'default', otherwise use standard login.
|
||||
"""
|
||||
# Import Home from its new location in Odoo 18
|
||||
from odoo.addons.web.controllers.home import Home
|
||||
|
||||
# Handle login submission via standard controller
|
||||
if request.httprequest.method == 'POST':
|
||||
home = Home()
|
||||
return home.web_login(redirect=redirect, **kw)
|
||||
|
||||
# GET: decide which template to render
|
||||
try:
|
||||
login_template = request.env['expert.login.template'].sudo().get_active_template()
|
||||
template_name = login_template.get_template_name()
|
||||
except Exception as e:
|
||||
_logger.error("Error getting active login template: %s", e)
|
||||
template_name = 'web.login'
|
||||
|
||||
# For default template, just call the standard login
|
||||
if template_name == 'web.login':
|
||||
home = Home()
|
||||
return home.web_login(redirect=redirect, **kw)
|
||||
|
||||
# Try to render the selected expert template; fall back to default on error
|
||||
try:
|
||||
return request.render(template_name, {
|
||||
'redirect': redirect or '',
|
||||
'login': kw.get('login', ''),
|
||||
'login_template': login_template,
|
||||
})
|
||||
except Exception as e:
|
||||
_logger.error("Error rendering login template '%s': %s", template_name, e)
|
||||
home = Home()
|
||||
return home.web_login(redirect=redirect, **kw)
|
||||
|
||||
@http.route('/expert_theme/get_login_template_styles', type='http', auth='public', methods=['GET'])
|
||||
def get_login_template_styles(self):
|
||||
"""Get CSS styles for the active login template (public access for login page)"""
|
||||
try:
|
||||
template = request.env['expert.login.template'].sudo().get_active_template()
|
||||
styles = template.get_template_styles()
|
||||
return request.make_json_response({
|
||||
'success': True,
|
||||
'styles': styles
|
||||
})
|
||||
except Exception as e:
|
||||
# Return default styles if error
|
||||
return request.make_json_response({
|
||||
'success': False,
|
||||
'error': str(e),
|
||||
'styles': {'background_color': '#FFFFFF'}
|
||||
})
|
||||
|
||||
@http.route('/expert_theme/get_installed_modules_http', type='http', auth='user')
|
||||
def get_installed_modules_http(self):
|
||||
"""Return list of installed modules for the home page via HTTP"""
|
||||
try:
|
||||
# Get all installed modules
|
||||
installed_modules = request.env['ir.module.module'].search([
|
||||
('state', '=', 'installed'),
|
||||
('name', '!=', 'expert_theme') # Exclude our own module
|
||||
])
|
||||
|
||||
# Get all top-level menu items that belong to installed modules
|
||||
menu_items = request.env['ir.ui.menu'].search([
|
||||
('parent_id', '=', False), # Top level menus only
|
||||
('active', '=', True),
|
||||
('name', 'not in', ['Expert Home', 'Dashboard']) # Exclude our own menus
|
||||
])
|
||||
|
||||
result = []
|
||||
installed_module_names = installed_modules.mapped('name')
|
||||
|
||||
debug_info = {
|
||||
'total_installed_modules': len(installed_modules),
|
||||
'total_menu_items': len(menu_items)
|
||||
}
|
||||
|
||||
for menu in menu_items:
|
||||
# Check if this menu belongs to an installed module
|
||||
module_name = None
|
||||
if menu.web_icon and ',' in menu.web_icon:
|
||||
module_name = menu.web_icon.split(',')[0]
|
||||
|
||||
# Include menu if it belongs to an installed module OR if it has no web_icon (base modules)
|
||||
if (module_name and module_name in installed_module_names) or not menu.web_icon:
|
||||
|
||||
# Create URL based on action type - use proper Odoo navigation
|
||||
if menu.action and menu.action.type == 'ir.actions.act_window':
|
||||
# For act_window, use the action ID
|
||||
url = f'/web#action={menu.action.id}'
|
||||
elif menu.action and menu.action.type == 'ir.actions.client':
|
||||
# For client actions, use the action ID
|
||||
url = f'/web#action={menu.action.id}'
|
||||
elif menu.action and menu.action.type == 'ir.actions.server':
|
||||
# For server actions, use the action ID
|
||||
url = f'/web#action={menu.action.id}'
|
||||
else:
|
||||
# For menus without actions, use menu_id
|
||||
url = f'/web#menu_id={menu.id}'
|
||||
|
||||
result.append({
|
||||
'id': menu.id,
|
||||
'name': menu.name,
|
||||
'web_icon': menu.web_icon or 'base,static/description/icon.png',
|
||||
'action': menu.action.id if menu.action else False,
|
||||
'url': url,
|
||||
'module_name': module_name
|
||||
})
|
||||
|
||||
return request.make_json_response({
|
||||
'success': True,
|
||||
'modules': result,
|
||||
'debug_info': debug_info
|
||||
})
|
||||
except Exception as e:
|
||||
return request.make_json_response({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
})
|
||||
|
||||
@http.route('/web/signup', type='http', auth='public', methods=['GET', 'POST'], website=True, csrf=False)
|
||||
def web_auth_signup(self, redirect=None, **kw):
|
||||
"""Override Odoo signup to use expert signup templates when modern template is active.
|
||||
|
||||
- POST: delegate to standard Odoo signup logic (authentication, redirects, etc.).
|
||||
- GET: render the active expert signup template if modern template is active, otherwise use standard signup.
|
||||
"""
|
||||
# Import AuthSignupHome from auth_signup module
|
||||
try:
|
||||
from odoo.addons.auth_signup.controllers.main import AuthSignupHome
|
||||
except ImportError:
|
||||
# If auth_signup is not installed, return 404
|
||||
from werkzeug.exceptions import NotFound
|
||||
raise NotFound()
|
||||
|
||||
# Handle signup submission via standard controller
|
||||
if request.httprequest.method == 'POST':
|
||||
auth_signup_home = AuthSignupHome()
|
||||
return auth_signup_home.web_auth_signup(redirect=redirect, **kw)
|
||||
|
||||
# GET: decide which template to render
|
||||
try:
|
||||
login_template = request.env['expert.login.template'].sudo().get_active_template()
|
||||
template_name = login_template.get_signup_template_name()
|
||||
except Exception as e:
|
||||
_logger.error("Error getting active signup template: %s", e)
|
||||
template_name = 'auth_signup.signup'
|
||||
|
||||
# For default template, just call the standard signup
|
||||
if template_name == 'auth_signup.signup':
|
||||
auth_signup_home = AuthSignupHome()
|
||||
return auth_signup_home.web_auth_signup(redirect=redirect, **kw)
|
||||
|
||||
# Try to render the selected expert template; fall back to default on error
|
||||
try:
|
||||
# Get signup context from auth_signup
|
||||
auth_signup_home = AuthSignupHome()
|
||||
qcontext = auth_signup_home.get_auth_signup_qcontext()
|
||||
qcontext.update({
|
||||
'redirect': redirect or '',
|
||||
'login': kw.get('login', ''),
|
||||
'login_template': login_template,
|
||||
})
|
||||
response = request.render(template_name, qcontext)
|
||||
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
|
||||
response.headers['Content-Security-Policy'] = "frame-ancestors 'self'"
|
||||
return response
|
||||
except Exception as e:
|
||||
_logger.error("Error rendering signup template '%s': %s", template_name, e)
|
||||
auth_signup_home = AuthSignupHome()
|
||||
return auth_signup_home.web_auth_signup(redirect=redirect, **kw)
|
||||
|
||||
@http.route('/web/reset_password', type='http', auth='public', methods=['GET', 'POST'], website=True, csrf=False)
|
||||
def web_auth_reset_password(self, redirect=None, **kw):
|
||||
"""Override Odoo reset password to use expert reset password templates when modern template is active.
|
||||
|
||||
- POST: delegate to standard Odoo reset password logic (authentication, redirects, etc.).
|
||||
- GET: render the active expert reset password template if modern template is active, otherwise use standard reset password.
|
||||
"""
|
||||
# Import AuthSignupHome from auth_signup module
|
||||
try:
|
||||
from odoo.addons.auth_signup.controllers.main import AuthSignupHome
|
||||
except ImportError:
|
||||
# If auth_signup is not installed, return 404
|
||||
from werkzeug.exceptions import NotFound
|
||||
raise NotFound()
|
||||
|
||||
# Handle reset password submission via standard controller
|
||||
if request.httprequest.method == 'POST':
|
||||
auth_signup_home = AuthSignupHome()
|
||||
return auth_signup_home.web_auth_reset_password(redirect=redirect, **kw)
|
||||
|
||||
# GET: decide which template to render
|
||||
try:
|
||||
login_template = request.env['expert.login.template'].sudo().get_active_template()
|
||||
template_name = login_template.get_reset_password_template_name()
|
||||
except Exception as e:
|
||||
_logger.error("Error getting active reset password template: %s", e)
|
||||
template_name = 'auth_signup.reset_password'
|
||||
|
||||
# For default template, just call the standard reset password
|
||||
if template_name == 'auth_signup.reset_password':
|
||||
auth_signup_home = AuthSignupHome()
|
||||
return auth_signup_home.web_auth_reset_password(redirect=redirect, **kw)
|
||||
|
||||
# Try to render the selected expert template; fall back to default on error
|
||||
try:
|
||||
# Get reset password context from auth_signup
|
||||
auth_signup_home = AuthSignupHome()
|
||||
qcontext = auth_signup_home.get_auth_signup_qcontext()
|
||||
qcontext.update({
|
||||
'redirect': redirect or '',
|
||||
'login': kw.get('login', ''),
|
||||
'login_template': login_template,
|
||||
})
|
||||
response = request.render(template_name, qcontext)
|
||||
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
|
||||
response.headers['Content-Security-Policy'] = "frame-ancestors 'self'"
|
||||
return response
|
||||
except Exception as e:
|
||||
_logger.error("Error rendering reset password template '%s': %s", template_name, e)
|
||||
auth_signup_home = AuthSignupHome()
|
||||
return auth_signup_home.web_auth_reset_password(redirect=redirect, **kw)
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!-- Default Login Templates -->
|
||||
|
||||
<!-- Template 1: Default Odoo Login -->
|
||||
<record id="expert_login_template_default" model="expert.login.template">
|
||||
<field name="name">Default Template</field>
|
||||
<field name="active">True</field>
|
||||
<field name="sequence">1</field>
|
||||
<field name="template_type">default</field>
|
||||
<field name="background_color">#FFFFFF</field>
|
||||
<field name="is_default">True</field>
|
||||
<field name="description">Standard Odoo login page template</field>
|
||||
</record>
|
||||
|
||||
<!-- Template 2: Modern Card Design -->
|
||||
<record id="expert_login_template_modern" model="expert.login.template">
|
||||
<field name="name">Modern Template</field>
|
||||
<field name="active">False</field>
|
||||
<field name="sequence">2</field>
|
||||
<field name="template_type">modern</field>
|
||||
<field name="background_color">#667eea</field>
|
||||
<field name="is_default">False</field>
|
||||
<field name="description">Modern card design with gradient background</field>
|
||||
</record>
|
||||
|
||||
<!-- Template 3: Minimal Design -->
|
||||
<record id="expert_login_template_minimal" model="expert.login.template">
|
||||
<field name="name">Minimal Template</field>
|
||||
<field name="active">False</field>
|
||||
<field name="sequence">3</field>
|
||||
<field name="template_type">minimal</field>
|
||||
<field name="background_color">#F8F9FA</field>
|
||||
<field name="is_default">False</field>
|
||||
<field name="description">Clean minimal design template</field>
|
||||
</record>
|
||||
|
||||
<!-- Template 4: Corporate Design -->
|
||||
<record id="expert_login_template_corporate" model="expert.login.template">
|
||||
<field name="name">Corporate Template</field>
|
||||
<field name="active">False</field>
|
||||
<field name="sequence">4</field>
|
||||
<field name="template_type">corporate</field>
|
||||
<field name="background_color">#1a1a1a</field>
|
||||
<field name="is_default">False</field>
|
||||
<field name="description">Professional corporate dark theme</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import expert_theme_config
|
||||
from . import expert_login_template
|
||||
|
|
@ -0,0 +1,289 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class ExpertLoginTemplate(models.Model):
|
||||
_name = 'expert.login.template'
|
||||
_description = 'Expert Login Page Template'
|
||||
_rec_name = 'name'
|
||||
_order = 'sequence, name'
|
||||
|
||||
name = fields.Char(string='Template Name', required=True, help='Name of the login page template')
|
||||
active = fields.Boolean(string='Active', default=False, help='Only one template can be active at a time. Activate this template to use it on the login page.')
|
||||
sequence = fields.Integer(string='Sequence', default=10, help='Order of display')
|
||||
|
||||
# Template Design Selection
|
||||
template_type = fields.Selection([
|
||||
('default', 'Default Odoo Login'),
|
||||
('modern', 'Modern Card Design'),
|
||||
('minimal', 'Minimal Design'),
|
||||
('corporate', 'Corporate Design'),
|
||||
], string='Template Design', default='default', required=True,
|
||||
help='Select the login page template design. Each design has a completely different HTML structure and layout.')
|
||||
|
||||
# Template Settings (for backward compatibility and CSS customization)
|
||||
background_color = fields.Char(string='Background Color', default='#FFFFFF', help='Background color for the login page')
|
||||
is_default = fields.Boolean(string='Is Default Template', default=False, help='Default template to use')
|
||||
|
||||
# Template Images
|
||||
modern_template_image = fields.Binary(string='Modern Template Image', help='Image to display on the right side of Modern login, signup, and reset password pages')
|
||||
minimal_template_image = fields.Binary(string='Minimal Template Image', help='Image to display on the right side of Minimal login, signup, and reset password pages')
|
||||
corporate_template_image = fields.Binary(string='Corporate Template Image', help='Image to display on the right side of Corporate login, signup, and reset password pages')
|
||||
|
||||
# Template Logos
|
||||
modern_template_logo = fields.Binary(string='Modern Template Logo', help='Company logo to display on Modern login, signup, and reset password pages')
|
||||
minimal_template_logo = fields.Binary(string='Minimal Template Logo', help='Company logo to display on Minimal login, signup, and reset password pages')
|
||||
corporate_template_logo = fields.Binary(string='Corporate Template Logo', help='Company logo to display on Corporate login, signup, and reset password pages')
|
||||
|
||||
# Modern Template Text Fields
|
||||
modern_login_title = fields.Char(string='Login Title', default='Welcome to Expert 👋', help='Title text for Modern template login page')
|
||||
modern_login_subtitle = fields.Char(string='Login Subtitle', default='Kindly fill in your details below to sign in to your account', help='Subtitle text for Modern template login page')
|
||||
modern_login_button_text = fields.Char(string='Login Button Text', default='Sign In', help='Button text for Modern template login page')
|
||||
modern_login_button_bg_color = fields.Char(string='Login Button Background', default='#007bff', help='Background color for login button')
|
||||
modern_login_button_text_color = fields.Char(string='Login Button Text Color', default='#FFFFFF', help='Text color for login button')
|
||||
modern_login_button_bg_hover = fields.Char(string='Login Button BG Hover', default='#0056b3', help='Background color for login button on hover')
|
||||
modern_login_button_text_hover = fields.Char(string='Login Button Text Hover', default='#FFFFFF', help='Text color for login button on hover')
|
||||
|
||||
modern_signup_title = fields.Char(string='Signup Title', default='Create an account', help='Title text for Modern template signup page')
|
||||
modern_signup_subtitle = fields.Char(string='Signup Subtitle', default='Join us today and get started', help='Subtitle text for Modern template signup page')
|
||||
modern_signup_button_text = fields.Char(string='Signup Button Text', default='Create an account', help='Button text for Modern template signup page')
|
||||
modern_signup_button_bg_color = fields.Char(string='Signup Button Background', default='#28a745', help='Background color for signup button')
|
||||
modern_signup_button_text_color = fields.Char(string='Signup Button Text Color', default='#FFFFFF', help='Text color for signup button')
|
||||
modern_signup_button_bg_hover = fields.Char(string='Signup Button BG Hover', default='#218838', help='Background color for signup button on hover')
|
||||
modern_signup_button_text_hover = fields.Char(string='Signup Button Text Hover', default='#FFFFFF', help='Text color for signup button on hover')
|
||||
|
||||
modern_reset_title = fields.Char(string='Reset Password Title', default='Reset your password', help='Title text for Modern template reset password page')
|
||||
modern_reset_subtitle = fields.Char(string='Reset Password Subtitle', default='Enter your email to receive reset instructions', help='Subtitle text for Modern template reset password page')
|
||||
|
||||
# Minimal Template Text Fields
|
||||
minimal_login_title = fields.Char(string='Login Title', default='Welcome to Expert 👋', help='Title text for Minimal template login page')
|
||||
minimal_login_subtitle = fields.Char(string='Login Subtitle', default='Kindly fill in your details below to sign in to your account', help='Subtitle text for Minimal template login page')
|
||||
minimal_login_button_text = fields.Char(string='Login Button Text', default='Sign In', help='Button text for Minimal template login page')
|
||||
minimal_login_button_bg_color = fields.Char(string='Login Button Background', default='#E5E5E5', help='Background color for login button')
|
||||
minimal_login_button_text_color = fields.Char(string='Login Button Text Color', default='#000000', help='Text color for login button')
|
||||
minimal_login_button_bg_hover = fields.Char(string='Login Button BG Hover', default='#D0D0D0', help='Background color for login button on hover')
|
||||
minimal_login_button_text_hover = fields.Char(string='Login Button Text Hover', default='#000000', help='Text color for login button on hover')
|
||||
|
||||
minimal_signup_title = fields.Char(string='Signup Title', default='Create an account', help='Title text for Minimal template signup page')
|
||||
minimal_signup_subtitle = fields.Char(string='Signup Subtitle', default='Join us today and get started', help='Subtitle text for Minimal template signup page')
|
||||
minimal_signup_button_text = fields.Char(string='Signup Button Text', default='Create an account', help='Button text for Minimal template signup page')
|
||||
minimal_signup_button_bg_color = fields.Char(string='Signup Button Background', default='#000000', help='Background color for signup button')
|
||||
minimal_signup_button_text_color = fields.Char(string='Signup Button Text Color', default='#FFFFFF', help='Text color for signup button')
|
||||
minimal_signup_button_bg_hover = fields.Char(string='Signup Button BG Hover', default='#333333', help='Background color for signup button on hover')
|
||||
minimal_signup_button_text_hover = fields.Char(string='Signup Button Text Hover', default='#FFFFFF', help='Text color for signup button on hover')
|
||||
|
||||
minimal_reset_title = fields.Char(string='Reset Password Title', default='Reset your password', help='Title text for Minimal template reset password page')
|
||||
minimal_reset_subtitle = fields.Char(string='Reset Password Subtitle', default='Enter your email to receive reset instructions', help='Subtitle text for Minimal template reset password page')
|
||||
|
||||
# Description
|
||||
description = fields.Text(string='Description', help='Description of this template')
|
||||
|
||||
@api.model
|
||||
def get_active_template(self):
|
||||
"""Get the active login template or return default"""
|
||||
try:
|
||||
template = self.search([('active', '=', True)], limit=1, order='sequence')
|
||||
if not template:
|
||||
# Create a default template if none exists
|
||||
template = self.create({
|
||||
'name': 'Default Template',
|
||||
'active': True,
|
||||
'template_type': 'default',
|
||||
'background_color': '#FFFFFF',
|
||||
'is_default': True,
|
||||
'sequence': 1
|
||||
})
|
||||
return template
|
||||
except Exception as e:
|
||||
# If there's an error, try to return any template or create a minimal one
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
_logger.error(f"Error in get_active_template: {str(e)}")
|
||||
# Try to get any template
|
||||
template = self.search([], limit=1)
|
||||
if template:
|
||||
return template
|
||||
# Last resort - return a recordset that won't cause errors
|
||||
return self.browse([])
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
"""Ensure only one template is active at a time"""
|
||||
# If no active value is set, default to False (don't auto-activate new templates)
|
||||
if 'active' not in vals:
|
||||
vals['active'] = False
|
||||
# Only deactivate others if this one is being set to active
|
||||
if vals.get('active'):
|
||||
# Deactivate all other templates (excluding the one being created)
|
||||
self.search([('active', '=', True)]).write({'active': False})
|
||||
return super(ExpertLoginTemplate, self).create(vals)
|
||||
|
||||
def write(self, vals):
|
||||
"""Ensure only one template is active at a time"""
|
||||
# Track if we need to reload
|
||||
need_reload = False
|
||||
|
||||
# Check if we're activating a template
|
||||
if 'active' in vals and vals.get('active'):
|
||||
# Deactivate all other templates (excluding the current recordset)
|
||||
other_templates = self.search([('active', '=', True), ('id', 'not in', self.ids)])
|
||||
if other_templates:
|
||||
# Use super().write to avoid recursion
|
||||
super(ExpertLoginTemplate, other_templates).write({'active': False})
|
||||
need_reload = True
|
||||
|
||||
result = super(ExpertLoginTemplate, self).write(vals)
|
||||
|
||||
# Update template views if active state changed
|
||||
if 'active' in vals or 'template_type' in vals:
|
||||
self._update_template_views()
|
||||
|
||||
# If we're in a list view context and need reload, return reload action
|
||||
if need_reload and self.env.context.get('from_list_view'):
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'reload',
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
@api.onchange('active')
|
||||
def _onchange_active(self):
|
||||
"""When activating a template in the form view, show a warning about deactivating others"""
|
||||
if self.active and not self._origin.active:
|
||||
other_active = self.search([('active', '=', True), ('id', '!=', self.id)])
|
||||
if other_active:
|
||||
return {
|
||||
'warning': {
|
||||
'title': 'Other Templates Will Be Deactivated',
|
||||
'message': f'Activating this template will automatically deactivate: {", ".join(other_active.mapped("name"))}'
|
||||
}
|
||||
}
|
||||
|
||||
def get_template_styles(self):
|
||||
"""Get CSS styles for this template"""
|
||||
return {
|
||||
'background_color': self.background_color or '#FFFFFF',
|
||||
}
|
||||
|
||||
def get_template_name(self):
|
||||
"""Get the QWeb template name to render"""
|
||||
template_map = {
|
||||
'default': 'web.login', # Use standard Odoo login
|
||||
'modern': 'expert_theme.login_template_modern_page',
|
||||
'minimal': 'expert_theme.login_template_minimal_page',
|
||||
'corporate': 'expert_theme.login_template_corporate_page',
|
||||
}
|
||||
return template_map.get(self.template_type, 'web.login')
|
||||
|
||||
def get_signup_template_name(self):
|
||||
"""Get the QWeb template name for signup page"""
|
||||
template_map = {
|
||||
'default': 'auth_signup.signup', # Use standard Odoo signup
|
||||
'modern': 'expert_theme.signup_template_modern_page',
|
||||
'minimal': 'expert_theme.signup_template_minimal_page',
|
||||
'corporate': 'auth_signup.signup', # Use default for now
|
||||
}
|
||||
return template_map.get(self.template_type, 'auth_signup.signup')
|
||||
|
||||
def get_reset_password_template_name(self):
|
||||
"""Get the QWeb template name for reset password page"""
|
||||
template_map = {
|
||||
'default': 'auth_signup.reset_password', # Use standard Odoo reset password
|
||||
'modern': 'expert_theme.reset_password_template_modern_page',
|
||||
'minimal': 'expert_theme.reset_password_template_minimal_page',
|
||||
'corporate': 'auth_signup.reset_password', # Use default for now
|
||||
}
|
||||
return template_map.get(self.template_type, 'auth_signup.reset_password')
|
||||
|
||||
def toggle_active(self):
|
||||
"""Toggle active state - ensures only one template is active"""
|
||||
# Ensure we're working with exactly one record
|
||||
self.ensure_one()
|
||||
|
||||
# Get the specific record ID to ensure we activate the correct one
|
||||
template_id = self.id
|
||||
|
||||
# Deactivate all other templates first (including any that might be active)
|
||||
self.env['expert.login.template'].search([
|
||||
('id', '!=', template_id),
|
||||
('active', '=', True)
|
||||
]).write({'active': False})
|
||||
|
||||
# Activate this specific template by ID using browse to ensure we're working with the correct record
|
||||
self.browse(template_id).write({'active': True})
|
||||
|
||||
# Update template view active states
|
||||
self._update_template_views()
|
||||
|
||||
# Return True instead of reload action to avoid potential issues
|
||||
return True
|
||||
|
||||
def _update_template_views(self):
|
||||
"""Update the active state of template views based on active template"""
|
||||
try:
|
||||
# Map template types to their view XML IDs
|
||||
template_view_map = {
|
||||
'modern': 'expert_theme.login_template_modern_page',
|
||||
'minimal': 'expert_theme.login_template_minimal_page',
|
||||
'corporate': 'expert_theme.login_template_corporate_page',
|
||||
}
|
||||
|
||||
# Get the active template
|
||||
active_template = self.search([('active', '=', True)], limit=1)
|
||||
if not active_template:
|
||||
# If no active template, deactivate all custom template views
|
||||
for view_xmlid in template_view_map.values():
|
||||
try:
|
||||
view = self.env.ref(view_xmlid, raise_if_not_found=False)
|
||||
if view:
|
||||
view.write({'active': False})
|
||||
except Exception:
|
||||
pass # View doesn't exist, skip
|
||||
return
|
||||
|
||||
# Deactivate all template views first
|
||||
for view_xmlid in template_view_map.values():
|
||||
try:
|
||||
view = self.env.ref(view_xmlid, raise_if_not_found=False)
|
||||
if view:
|
||||
view.write({'active': False})
|
||||
except Exception:
|
||||
pass # View doesn't exist, skip
|
||||
|
||||
# Activate the view for the active template type (if not default)
|
||||
if active_template.template_type != 'default':
|
||||
view_xmlid = template_view_map.get(active_template.template_type)
|
||||
if view_xmlid:
|
||||
try:
|
||||
view = self.env.ref(view_xmlid, raise_if_not_found=False)
|
||||
if view:
|
||||
view.write({'active': True})
|
||||
except Exception:
|
||||
pass # View doesn't exist, skip
|
||||
except Exception:
|
||||
pass # Silently fail if views don't exist
|
||||
|
||||
def action_duplicate(self):
|
||||
"""Duplicate the current template"""
|
||||
self.ensure_one()
|
||||
copy_vals = {
|
||||
'name': self.name + ' (Copy)',
|
||||
'active': False, # Don't activate the copy
|
||||
'sequence': self.sequence + 1,
|
||||
'template_type': self.template_type,
|
||||
'background_color': self.background_color,
|
||||
'description': self.description,
|
||||
'is_default': False,
|
||||
}
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Login Page Template',
|
||||
'res_model': 'expert.login.template',
|
||||
'view_mode': 'form',
|
||||
'target': 'current',
|
||||
'res_id': self.create(copy_vals).id,
|
||||
'context': {'default_name': copy_vals['name']},
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class ExpertThemeConfig(models.Model):
|
||||
_name = 'expert.theme.config'
|
||||
_description = 'Expert Theme Color Configuration'
|
||||
_rec_name = 'name'
|
||||
|
||||
name = fields.Char(string='Configuration Name', required=True, default='Default Theme')
|
||||
active = fields.Boolean(string='Active', default=True)
|
||||
|
||||
# Primary Colors
|
||||
primary_color = fields.Char(string='Primary Color', default='#875A7B', help='Main brand color - affects navbar, primary buttons, and accents')
|
||||
primary_hover = fields.Char(string='Primary Hover', default='#6B4C6B', help='Hover state for primary elements')
|
||||
primary_light = fields.Char(string='Primary Light', default='#A67B9B', help='Light version of primary color')
|
||||
|
||||
# Secondary Colors
|
||||
secondary_color = fields.Char(string='Secondary Color', default='#00A09D', help='Secondary brand color - affects upgrade buttons and secondary elements')
|
||||
secondary_hover = fields.Char(string='Secondary Hover', default='#008B8A', help='Hover state for secondary elements')
|
||||
|
||||
# Button Colors
|
||||
btn_primary = fields.Char(string='Primary Button', default='#875A7B', help='Primary buttons (like "Activate" buttons)')
|
||||
btn_primary_hover = fields.Char(string='Primary Button Hover', default='#6B4C6B', help='Primary button hover state')
|
||||
btn_secondary = fields.Char(string='Secondary Button', default='#00A09D', help='Secondary buttons (like "Upgrade" buttons)')
|
||||
btn_secondary_hover = fields.Char(string='Secondary Button Hover', default='#008B8A', help='Secondary button hover state')
|
||||
btn_light = fields.Char(string='Light Button', default='#F8F9FA', help='Light buttons (like "Learn More" buttons)')
|
||||
btn_light_hover = fields.Char(string='Light Button Hover', default='#E9ECEF', help='Light button hover state')
|
||||
btn_light_text = fields.Char(string='Light Button Text', default='#6C757D', help='Light button text color')
|
||||
|
||||
# Background Colors
|
||||
bg_primary = fields.Char(string='Primary Background', default='#875A7B', help='Navbar background')
|
||||
bg_secondary = fields.Char(string='Secondary Background', default='#F8F9FA', help='Sidebar background')
|
||||
bg_content = fields.Char(string='Content Background', default='#FFFFFF', help='Main content background')
|
||||
bg_gradient_start = fields.Char(string='Gradient Start', default='#f5f7fa', help='Page gradient start color')
|
||||
bg_gradient_end = fields.Char(string='Gradient End', default='#c3cfe2', help='Page gradient end color')
|
||||
|
||||
# Text Colors
|
||||
text_primary = fields.Char(string='Primary Text', default='#212529', help='Main text color (dark)')
|
||||
text_secondary = fields.Char(string='Secondary Text', default='#6C757D', help='Secondary text color (gray)')
|
||||
text_light = fields.Char(string='Light Text', default='#FFFFFF', help='Light text (for dark backgrounds)')
|
||||
|
||||
# Border Colors
|
||||
border_color = fields.Char(string='Border Color', default='#DEE2E6', help='Default border color')
|
||||
border_light = fields.Char(string='Light Border', default='#E9ECEF', help='Light border color')
|
||||
|
||||
# Status Colors
|
||||
success_color = fields.Char(string='Success Color', default='#28A745', help='Success/Active state (green)')
|
||||
warning_color = fields.Char(string='Warning Color', default='#FFC107', help='Warning state (yellow)')
|
||||
danger_color = fields.Char(string='Danger Color', default='#DC3545', help='Danger/Error state (red)')
|
||||
info_color = fields.Char(string='Info Color', default='#17A2B8', help='Info state (blue)')
|
||||
|
||||
@api.model
|
||||
def get_active_config(self):
|
||||
"""Get the active configuration or create a default one"""
|
||||
config = self.search([('active', '=', True)], limit=1)
|
||||
if not config:
|
||||
config = self.create({
|
||||
'name': 'Default Theme',
|
||||
'active': True
|
||||
})
|
||||
return config
|
||||
|
||||
@api.model
|
||||
def get_css_variables(self):
|
||||
"""Get CSS variables for the active configuration"""
|
||||
config = self.get_active_config()
|
||||
return {
|
||||
'--expert-primary-color': config.primary_color,
|
||||
'--expert-primary-hover': config.primary_hover,
|
||||
'--expert-primary-light': config.primary_light,
|
||||
'--expert-secondary-color': config.secondary_color,
|
||||
'--expert-secondary-hover': config.secondary_hover,
|
||||
'--expert-btn-primary': config.btn_primary,
|
||||
'--expert-btn-primary-hover': config.btn_primary_hover,
|
||||
'--expert-btn-secondary': config.btn_secondary,
|
||||
'--expert-btn-secondary-hover': config.btn_secondary_hover,
|
||||
'--expert-btn-light': config.btn_light,
|
||||
'--expert-btn-light-hover': config.btn_light_hover,
|
||||
'--expert-btn-light-text': config.btn_light_text,
|
||||
'--expert-bg-primary': config.bg_primary,
|
||||
'--expert-bg-secondary': config.bg_secondary,
|
||||
'--expert-bg-content': config.bg_content,
|
||||
'--expert-bg-gradient-start': config.bg_gradient_start,
|
||||
'--expert-bg-gradient-end': config.bg_gradient_end,
|
||||
'--expert-text-primary': config.text_primary,
|
||||
'--expert-text-secondary': config.text_secondary,
|
||||
'--expert-text-light': config.text_light,
|
||||
'--expert-border-color': config.border_color,
|
||||
'--expert-border-light': config.border_light,
|
||||
'--expert-success': config.success_color,
|
||||
'--expert-warning': config.warning_color,
|
||||
'--expert-danger': config.danger_color,
|
||||
'--expert-info': config.info_color,
|
||||
}
|
||||
|
||||
def apply_theme(self):
|
||||
"""Apply the current theme configuration"""
|
||||
# This method can be called to trigger theme updates
|
||||
return True
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_expert_theme_config_user,expert.theme.config.user,model_expert_theme_config,base.group_user,1,1,1,1
|
||||
access_expert_theme_config_admin,expert.theme.config.admin,model_expert_theme_config,base.group_system,1,1,1,1
|
||||
access_expert_login_template_user,expert.login.template.user,model_expert_login_template,base.group_user,1,1,1,1
|
||||
access_expert_login_template_admin,expert.login.template.admin,model_expert_login_template,base.group_system,1,1,1,1
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Expert Theme</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #667eea;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.feature {
|
||||
margin: 20px 0;
|
||||
padding: 15px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 5px;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
.icon {
|
||||
text-align: center;
|
||||
font-size: 48px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="icon">🎨</div>
|
||||
<h1>Expert Theme</h1>
|
||||
|
||||
<div class="feature">
|
||||
<h3>Custom Home Page</h3>
|
||||
<p>A clean and modern home page that displays only your installed modules for easy navigation.</p>
|
||||
</div>
|
||||
|
||||
<div class="feature">
|
||||
<h3>Simple Interface</h3>
|
||||
<p>Minimal design focused on functionality - no clutter, just your modules.</p>
|
||||
</div>
|
||||
|
||||
<div class="feature">
|
||||
<h3>Responsive Design</h3>
|
||||
<p>Works perfectly on desktop, tablet, and mobile devices.</p>
|
||||
</div>
|
||||
|
||||
<div class="feature">
|
||||
<h3>Easy Installation</h3>
|
||||
<p>Simply install the module and your new home page will be ready to use.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/* Expert Theme - Login Page Styles */
|
||||
|
||||
:root {
|
||||
--expert-login-bg-color: #FFFFFF;
|
||||
}
|
||||
|
||||
/* Apply background color to login page */
|
||||
.expert_login_page {
|
||||
background-color: var(--expert-login-bg-color) !important;
|
||||
}
|
||||
|
||||
/* Ensure the background applies to the entire login area */
|
||||
body.oe_login_layout,
|
||||
body {
|
||||
background-color: var(--expert-login-bg-color) !important;
|
||||
}
|
||||
|
||||
/* Apply to main login container */
|
||||
.oe_login_form {
|
||||
background-color: var(--expert-login-bg-color) !important;
|
||||
}
|
||||
|
||||
/* Apply to html element as well */
|
||||
html {
|
||||
background-color: var(--expert-login-bg-color) !important;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,314 @@
|
|||
/* Expert Theme Styles */
|
||||
|
||||
/* CSS Variables for Customizable Colors */
|
||||
:root {
|
||||
/* Primary Colors */
|
||||
--expert-primary-color: #875A7B; /* Main brand color (Odoo purple) */
|
||||
--expert-primary-hover: #6B4C6B; /* Primary hover state */
|
||||
--expert-primary-light: #A67B9B; /* Light primary */
|
||||
|
||||
/* Secondary Colors */
|
||||
--expert-secondary-color: #00A09D; /* Teal for upgrades */
|
||||
--expert-secondary-hover: #008B8A; /* Teal hover */
|
||||
|
||||
/* Button Colors */
|
||||
--expert-btn-primary: #875A7B; /* Primary button (Activate) */
|
||||
--expert-btn-primary-hover: #6B4C6B; /* Primary button hover */
|
||||
--expert-btn-secondary: #00A09D; /* Secondary button (Upgrade) */
|
||||
--expert-btn-secondary-hover: #008B8A; /* Secondary button hover */
|
||||
--expert-btn-light: #F8F9FA; /* Light button (Learn More) */
|
||||
--expert-btn-light-hover: #E9ECEF; /* Light button hover */
|
||||
--expert-btn-light-text: #6C757D; /* Light button text */
|
||||
|
||||
/* Background Colors */
|
||||
--expert-bg-primary: #875A7B; /* Navbar background */
|
||||
--expert-bg-secondary: #F8F9FA; /* Sidebar background */
|
||||
--expert-bg-content: #FFFFFF; /* Main content background */
|
||||
--expert-bg-gradient-start: #f5f7fa; /* Gradient start */
|
||||
--expert-bg-gradient-end: #c3cfe2; /* Gradient end */
|
||||
|
||||
/* Text Colors */
|
||||
--expert-text-primary: #212529; /* Primary text */
|
||||
--expert-text-secondary: #6C757D; /* Secondary text */
|
||||
--expert-text-light: #FFFFFF; /* Light text (on dark backgrounds) */
|
||||
|
||||
/* Border Colors */
|
||||
--expert-border-color: #DEE2E6; /* Default border */
|
||||
--expert-border-light: #E9ECEF; /* Light border */
|
||||
|
||||
/* Status Colors */
|
||||
--expert-success: #28A745; /* Success/Active state */
|
||||
--expert-warning: #FFC107; /* Warning state */
|
||||
--expert-danger: #DC3545; /* Danger/Error state */
|
||||
--expert-info: #17A2B8; /* Info state */
|
||||
}
|
||||
|
||||
/* Apply gradient to the entire page background */
|
||||
body {
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%) !important;
|
||||
background: linear-gradient(135deg, var(--expert-bg-gradient-start, #f5f7fa) 0%, var(--expert-bg-gradient-end, #c3cfe2) 100%) !important;
|
||||
min-height: 100vh !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* Ensure the gradient shows on the main content area */
|
||||
.o_action_manager,
|
||||
.o_action_manager .o_view_controller,
|
||||
.o_action_manager .o_view_controller .o_content {
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%) !important;
|
||||
background: linear-gradient(135deg, var(--expert-bg-gradient-start, #f5f7fa) 0%, var(--expert-bg-gradient-end, #c3cfe2) 100%) !important;
|
||||
min-height: 100vh !important;
|
||||
}
|
||||
|
||||
/* Override any Odoo background colors that might interfere */
|
||||
.o_web_client {
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%) !important;
|
||||
background: linear-gradient(135deg, var(--expert-bg-gradient-start, #f5f7fa) 0%, var(--expert-bg-gradient-end, #c3cfe2) 100%) !important;
|
||||
}
|
||||
|
||||
.expert-home-container {
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.expert-theme-controls {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.expert-theme-btn {
|
||||
background: linear-gradient(135deg, var(--expert-primary-color) 0%, var(--expert-primary-hover) 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 12px 24px;
|
||||
border-radius: 25px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.expert-theme-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(0,0,0,0.15);
|
||||
background: linear-gradient(135deg, var(--expert-primary-hover) 0%, var(--expert-primary-color) 100%);
|
||||
}
|
||||
|
||||
.expert-home-header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, var(--expert-primary-color) 0%, var(--expert-primary-hover) 100%);
|
||||
color: var(--expert-text-light);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.expert-home-header h1 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 2.5rem;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.expert-home-header p {
|
||||
margin: 0;
|
||||
font-size: 1.1rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.expert-modules-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.expert-module-card {
|
||||
background: var(--expert-bg-content);
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--expert-border-light);
|
||||
}
|
||||
|
||||
.expert-module-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 25px rgba(0,0,0,0.15);
|
||||
border-color: var(--expert-primary-color);
|
||||
}
|
||||
|
||||
.expert-module-icon {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.module-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.expert-module-info h3 {
|
||||
margin: 0 0 10px 0;
|
||||
color: var(--expert-text-primary);
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.expert-module-info p {
|
||||
margin: 0;
|
||||
color: var(--expert-text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.expert-loading {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.spinner-border {
|
||||
display: inline-block;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
vertical-align: text-bottom;
|
||||
border: 0.25em solid currentColor;
|
||||
border-right-color: transparent;
|
||||
border-radius: 50%;
|
||||
animation: spinner-border 0.75s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spinner-border {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.expert-error,
|
||||
.expert-no-modules {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
color: #721c24;
|
||||
background-color: #f8d7da;
|
||||
border-color: #f5c6cb;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
color: #0c5460;
|
||||
background-color: #d1ecf1;
|
||||
border-color: #bee5eb;
|
||||
}
|
||||
|
||||
/* Override Odoo's default colors */
|
||||
.o_main_navbar {
|
||||
background-color: var(--expert-bg-primary) !important;
|
||||
border-bottom: 1px solid var(--expert-border-color) !important;
|
||||
}
|
||||
|
||||
.o_main_navbar .o_menu_item > a {
|
||||
color: var(--expert-text-light) !important;
|
||||
}
|
||||
|
||||
.o_main_navbar .o_menu_item > a:hover {
|
||||
background-color: var(--expert-primary-hover) !important;
|
||||
color: var(--expert-text-light) !important;
|
||||
}
|
||||
|
||||
/* Button overrides */
|
||||
.btn-primary {
|
||||
background-color: var(--expert-btn-primary) !important;
|
||||
border-color: var(--expert-btn-primary) !important;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--expert-btn-primary-hover) !important;
|
||||
border-color: var(--expert-btn-primary-hover) !important;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: var(--expert-btn-secondary) !important;
|
||||
border-color: var(--expert-btn-secondary) !important;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--expert-btn-secondary-hover) !important;
|
||||
border-color: var(--expert-btn-secondary-hover) !important;
|
||||
}
|
||||
|
||||
.btn-light {
|
||||
background-color: var(--expert-btn-light) !important;
|
||||
border-color: var(--expert-btn-light) !important;
|
||||
color: var(--expert-btn-light-text) !important;
|
||||
}
|
||||
|
||||
.btn-light:hover {
|
||||
background-color: var(--expert-btn-light-hover) !important;
|
||||
border-color: var(--expert-btn-light-hover) !important;
|
||||
color: var(--expert-btn-light-text) !important;
|
||||
}
|
||||
|
||||
/* Sidebar overrides */
|
||||
.o_main_navbar .o_menu_sections {
|
||||
background-color: var(--expert-bg-secondary) !important;
|
||||
}
|
||||
|
||||
.o_main_navbar .o_menu_sections .o_menu_item > a {
|
||||
color: var(--expert-text-primary) !important;
|
||||
}
|
||||
|
||||
.o_main_navbar .o_menu_sections .o_menu_item > a:hover {
|
||||
background-color: var(--expert-primary-light) !important;
|
||||
color: var(--expert-text-light) !important;
|
||||
}
|
||||
|
||||
/* Content area overrides */
|
||||
.o_action_manager {
|
||||
background-color: var(--expert-bg-content) !important;
|
||||
}
|
||||
|
||||
/* Status colors */
|
||||
.text-success {
|
||||
color: var(--expert-success) !important;
|
||||
}
|
||||
|
||||
.text-warning {
|
||||
color: var(--expert-warning) !important;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: var(--expert-danger) !important;
|
||||
}
|
||||
|
||||
.text-info {
|
||||
color: var(--expert-info) !important;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.expert-modules-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.expert-home-header h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.expert-home-container {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
/* Expert Theme Color Configuration */
|
||||
/*
|
||||
* This file contains the color variables for the Expert Theme.
|
||||
* You can easily customize the colors by changing the values below.
|
||||
*
|
||||
* Color Format: Use hex codes (e.g., #FF0000) or CSS color names (e.g., red, blue)
|
||||
*
|
||||
* To apply changes:
|
||||
* 1. Modify the colors below
|
||||
* 2. Save the file
|
||||
* 3. Refresh your Odoo page
|
||||
*/
|
||||
|
||||
:root {
|
||||
/* ===== PRIMARY COLORS ===== */
|
||||
/* Main brand color - affects navbar, primary buttons, and accents */
|
||||
--expert-primary-color: #875A7B; /* Default: Odoo Purple */
|
||||
--expert-primary-hover: #6B4C6B; /* Hover state for primary elements */
|
||||
--expert-primary-light: #A67B9B; /* Light version of primary */
|
||||
|
||||
/* ===== SECONDARY COLORS ===== */
|
||||
/* Secondary brand color - affects upgrade buttons and secondary elements */
|
||||
--expert-secondary-color: #00A09D; /* Default: Teal */
|
||||
--expert-secondary-hover: #008B8A; /* Hover state for secondary elements */
|
||||
|
||||
/* ===== BUTTON COLORS ===== */
|
||||
/* Primary buttons (like "Activate" buttons) */
|
||||
--expert-btn-primary: #875A7B; /* Same as primary color */
|
||||
--expert-btn-primary-hover: #6B4C6B; /* Hover state */
|
||||
|
||||
/* Secondary buttons (like "Upgrade" buttons) */
|
||||
--expert-btn-secondary: #00A09D; /* Same as secondary color */
|
||||
--expert-btn-secondary-hover: #008B8A; /* Hover state */
|
||||
|
||||
/* Light buttons (like "Learn More" buttons) */
|
||||
--expert-btn-light: #F8F9FA; /* Light gray background */
|
||||
--expert-btn-light-hover: #E9ECEF; /* Slightly darker on hover */
|
||||
--expert-btn-light-text: #6C757D; /* Dark gray text */
|
||||
|
||||
/* ===== BACKGROUND COLORS ===== */
|
||||
/* Main backgrounds */
|
||||
--expert-bg-primary: #875A7B; /* Navbar background */
|
||||
--expert-bg-secondary: #F8F9FA; /* Sidebar background */
|
||||
--expert-bg-content: #FFFFFF; /* Main content background */
|
||||
|
||||
/* Gradient background for the page */
|
||||
--expert-bg-gradient-start: #f5f7fa; /* Gradient start color */
|
||||
--expert-bg-gradient-end: #c3cfe2; /* Gradient end color */
|
||||
|
||||
/* ===== TEXT COLORS ===== */
|
||||
--expert-text-primary: #212529; /* Main text color (dark) */
|
||||
--expert-text-secondary: #6C757D; /* Secondary text color (gray) */
|
||||
--expert-text-light: #FFFFFF; /* Light text (for dark backgrounds) */
|
||||
|
||||
/* ===== BORDER COLORS ===== */
|
||||
--expert-border-color: #DEE2E6; /* Default border color */
|
||||
--expert-border-light: #E9ECEF; /* Light border color */
|
||||
|
||||
/* ===== STATUS COLORS ===== */
|
||||
--expert-success: #28A745; /* Success/Active state (green) */
|
||||
--expert-warning: #FFC107; /* Warning state (yellow) */
|
||||
--expert-danger: #DC3545; /* Danger/Error state (red) */
|
||||
--expert-info: #17A2B8; /* Info state (blue) */
|
||||
}
|
||||
|
||||
/* ===== EXAMPLE COLOR SCHEMES ===== */
|
||||
|
||||
/*
|
||||
* BLUE THEME EXAMPLE:
|
||||
* Uncomment the section below to use a blue color scheme
|
||||
*/
|
||||
/*
|
||||
:root {
|
||||
--expert-primary-color: #007BFF;
|
||||
--expert-primary-hover: #0056B3;
|
||||
--expert-primary-light: #66B3FF;
|
||||
--expert-secondary-color: #6F42C1;
|
||||
--expert-secondary-hover: #5A32A3;
|
||||
--expert-btn-primary: #007BFF;
|
||||
--expert-btn-primary-hover: #0056B3;
|
||||
--expert-btn-secondary: #6F42C1;
|
||||
--expert-btn-secondary-hover: #5A32A3;
|
||||
--expert-bg-primary: #007BFF;
|
||||
--expert-bg-gradient-start: #E3F2FD;
|
||||
--expert-bg-gradient-end: #BBDEFB;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
* GREEN THEME EXAMPLE:
|
||||
* Uncomment the section below to use a green color scheme
|
||||
*/
|
||||
/*
|
||||
:root {
|
||||
--expert-primary-color: #28A745;
|
||||
--expert-primary-hover: #1E7E34;
|
||||
--expert-primary-light: #5CB85C;
|
||||
--expert-secondary-color: #20C997;
|
||||
--expert-secondary-hover: #17A2B8;
|
||||
--expert-btn-primary: #28A745;
|
||||
--expert-btn-primary-hover: #1E7E34;
|
||||
--expert-btn-secondary: #20C997;
|
||||
--expert-btn-secondary-hover: #17A2B8;
|
||||
--expert-bg-primary: #28A745;
|
||||
--expert-bg-gradient-start: #E8F5E8;
|
||||
--expert-bg-gradient-end: #C3E6C3;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
* DARK THEME EXAMPLE:
|
||||
* Uncomment the section below to use a dark color scheme
|
||||
*/
|
||||
/*
|
||||
:root {
|
||||
--expert-primary-color: #343A40;
|
||||
--expert-primary-hover: #23272B;
|
||||
--expert-primary-light: #495057;
|
||||
--expert-secondary-color: #6C757D;
|
||||
--expert-secondary-hover: #545B62;
|
||||
--expert-btn-primary: #343A40;
|
||||
--expert-btn-primary-hover: #23272B;
|
||||
--expert-btn-secondary: #6C757D;
|
||||
--expert-btn-secondary-hover: #545B62;
|
||||
--expert-bg-primary: #343A40;
|
||||
--expert-bg-secondary: #495057;
|
||||
--expert-bg-content: #F8F9FA;
|
||||
--expert-bg-gradient-start: #2C3E50;
|
||||
--expert-bg-gradient-end: #34495E;
|
||||
--expert-text-primary: #FFFFFF;
|
||||
--expert-text-secondary: #E9ECEF;
|
||||
--expert-text-light: #FFFFFF;
|
||||
}
|
||||
*/
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
/* Expert Theme - Minimal Login Template Styles */
|
||||
|
||||
.expert-login-minimal {
|
||||
background: #FFFFFF;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.expert-login-minimal .container-fluid {
|
||||
height: calc(100vh - 40px);
|
||||
}
|
||||
|
||||
.expert-login-minimal .row {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Left Column: Login Form */
|
||||
.expert-login-minimal .expert-login-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Logo Section */
|
||||
.expert-login-minimal .expert-login-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 40px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-logo .expert-logo-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: #000000;
|
||||
border-radius: 8px;
|
||||
margin-right: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-logo .expert-logo-icon span {
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-logo .expert-logo-text {
|
||||
color: #000000;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Welcome Section */
|
||||
.expert-login-minimal .expert-login-welcome {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-welcome h1 {
|
||||
color: #000000;
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-welcome h1 .welcome-emoji {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-welcome p {
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
font-size: 16px;
|
||||
margin-bottom: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Form Section */
|
||||
.expert-login-minimal .expert-login-form {
|
||||
max-width: 450px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-form .form-group {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-form .form-group label {
|
||||
font-weight: 500;
|
||||
color: #000000;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-form .form-group input[type="text"],
|
||||
.expert-login-minimal .expert-login-form .form-group input[type="password"] {
|
||||
background: #FFFFFF !important;
|
||||
border: 1px solid #000000 !important;
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
color: #000000 !important;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-form .form-group input[type="text"]::placeholder,
|
||||
.expert-login-minimal .expert-login-form .form-group input[type="password"]::placeholder {
|
||||
color: rgba(0, 0, 0, 0.5) !important;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-form .form-group input[type="text"]:focus,
|
||||
.expert-login-minimal .expert-login-form .form-group input[type="password"]:focus {
|
||||
background: #FFFFFF !important;
|
||||
border-color: #000000 !important;
|
||||
outline: none;
|
||||
color: #000000 !important;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-form .form-check {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-form .form-check label {
|
||||
color: #000000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-form .form-check label input[type="checkbox"] {
|
||||
margin-right: 8px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
accent-color: #000000;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-form button[type="submit"] {
|
||||
width: 100%;
|
||||
background: #E5E5E5;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
font-weight: 600;
|
||||
color: #000000;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-form button[type="submit"]:hover {
|
||||
background: #D5D5D5;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-form button[type="submit"]:active {
|
||||
background: #CCCCCC;
|
||||
}
|
||||
|
||||
/* Login Link Section */
|
||||
.expert-login-minimal .expert-login-link {
|
||||
margin-top: 24px;
|
||||
max-width: 450px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-link p {
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-link p a {
|
||||
color: #000000;
|
||||
text-decoration: underline;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-link p a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Right Column: Image Section */
|
||||
.expert-login-minimal .expert-login-right {
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-right .expert-login-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #FFFFFF;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-right .expert-login-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-minimal-illustration {
|
||||
width: 600px !important;
|
||||
height: 600px !important;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* Alert Messages */
|
||||
.expert-login-minimal .alert {
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 20px;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.expert-login-minimal .alert-danger {
|
||||
background-color: #F8D7DA;
|
||||
border: 1px solid #F5C6CB;
|
||||
color: #721C24;
|
||||
}
|
||||
|
||||
.expert-login-minimal .alert-success {
|
||||
background-color: #D4EDDA;
|
||||
border: 1px solid #C3E6CB;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 991.98px) {
|
||||
.expert-login-minimal .expert-login-left {
|
||||
padding: 30px 20px;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-welcome h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-welcome h1 .welcome-emoji {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.expert-login-minimal .expert-login-welcome p {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
/* Expert Theme - Modern Login Template Styles */
|
||||
|
||||
.expert-login-modern {
|
||||
background: #19181F !important;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.expert-login-modern .container-fluid {
|
||||
height: calc(100vh - 40px);
|
||||
}
|
||||
|
||||
.expert-login-modern .row {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Left Column: Login Form */
|
||||
.expert-login-modern .expert-login-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Logo Section */
|
||||
.expert-login-modern .expert-login-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 40px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-logo .expert-logo-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 8px;
|
||||
margin-right: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-logo .expert-logo-icon span {
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-logo .expert-logo-text {
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Welcome Section */
|
||||
.expert-login-modern .expert-login-welcome {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-welcome h1 {
|
||||
color: white;
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-welcome h1 .welcome-emoji {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-welcome p {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 16px;
|
||||
margin-bottom: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Form Section */
|
||||
.expert-login-modern .expert-login-form {
|
||||
max-width: 450px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-form .form-group {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-form .form-group label {
|
||||
font-weight: 500;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-form .form-group input[type="text"],
|
||||
.expert-login-modern .expert-login-form .form-group input[type="password"] {
|
||||
background: rgba(255, 255, 255, 0.1) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2) !important;
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
color: white !important;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-form .form-group input[type="text"]::placeholder,
|
||||
.expert-login-modern .expert-login-form .form-group input[type="password"]::placeholder {
|
||||
color: rgba(255, 255, 255, 0.5) !important;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-form .form-group input[type="text"]:focus,
|
||||
.expert-login-modern .expert-login-form .form-group input[type="password"]:focus {
|
||||
background: rgba(255, 255, 255, 0.15) !important;
|
||||
border-color: rgba(255, 255, 255, 0.4) !important;
|
||||
outline: none;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-form .form-check {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-form .form-check label {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-form .form-check label input[type="checkbox"] {
|
||||
margin-right: 8px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
accent-color: #667eea;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-form button[type="submit"] {
|
||||
width: 100%;
|
||||
background: #764ba2;
|
||||
border: none !important;
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-form button[type="submit"]:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* Login Link Section */
|
||||
.expert-login-modern .expert-login-link {
|
||||
margin-top: 24px;
|
||||
max-width: 450px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-link p {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-link p a {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-link p a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Right Column: Image Section */
|
||||
.expert-login-modern .expert-login-right {
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-right .expert-login-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-right .expert-login-image img {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
object-fit: cover !important;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 991.98px) {
|
||||
.expert-login-modern .expert-login-left {
|
||||
padding: 30px 20px;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-welcome h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-welcome h1 .welcome-emoji {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.expert-login-modern .expert-login-welcome p {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 526 KiB |
|
|
@ -0,0 +1,134 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { Component, onMounted, useState } from "@odoo/owl";
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
|
||||
export class ExpertHome extends Component {
|
||||
setup() {
|
||||
this.state = useState({
|
||||
modules: [],
|
||||
loading: true,
|
||||
error: null
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
this.loadModules();
|
||||
});
|
||||
}
|
||||
|
||||
async loadModules() {
|
||||
try {
|
||||
this.state.loading = true;
|
||||
|
||||
// Try to get modules dynamically from the controller
|
||||
try {
|
||||
const response = await fetch('/expert_theme/get_installed_modules_http');
|
||||
|
||||
// Check if response is HTML (error page) instead of JSON
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (!contentType || !contentType.includes('application/json')) {
|
||||
throw new Error(`Server returned HTML (status: ${response.status}). Check if controller is working.`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success && result.modules && result.modules.length > 0) {
|
||||
this.state.modules = result.modules;
|
||||
return;
|
||||
}
|
||||
} catch (httpError) {
|
||||
// Don't set error state, try fallback instead
|
||||
}
|
||||
|
||||
// Fallback: Try to get modules from DOM
|
||||
try {
|
||||
// Try multiple selectors to find the sidebar menus
|
||||
let sidebarMenus = document.querySelectorAll('.o_main_navbar .o_menu_item a');
|
||||
if (sidebarMenus.length === 0) {
|
||||
sidebarMenus = document.querySelectorAll('.o_menu_item a');
|
||||
}
|
||||
if (sidebarMenus.length === 0) {
|
||||
sidebarMenus = document.querySelectorAll('nav a');
|
||||
}
|
||||
if (sidebarMenus.length === 0) {
|
||||
sidebarMenus = document.querySelectorAll('a[href*="/web#"]');
|
||||
}
|
||||
|
||||
const modules = [];
|
||||
|
||||
sidebarMenus.forEach((link, index) => {
|
||||
const name = link.textContent.trim();
|
||||
const href = link.href;
|
||||
|
||||
if (name && href &&
|
||||
!name.includes('Expert Home') &&
|
||||
!name.includes('Dashboard') &&
|
||||
name.length > 0 &&
|
||||
href.includes('/web#')) {
|
||||
modules.push({
|
||||
id: index + 1,
|
||||
name: name,
|
||||
web_icon: 'base,static/description/icon.png',
|
||||
action: true,
|
||||
url: href
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (modules.length > 0) {
|
||||
this.state.modules = modules;
|
||||
return;
|
||||
}
|
||||
} catch (domError) {
|
||||
// DOM fallback failed
|
||||
}
|
||||
|
||||
// Fallback: Show empty state with message
|
||||
this.state.modules = [];
|
||||
|
||||
} catch (error) {
|
||||
this.state.error = error.message;
|
||||
} finally {
|
||||
this.state.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
getModuleIcon(webIcon) {
|
||||
// Custom icon mapping for modules
|
||||
const customIcons = {
|
||||
'Discuss': '/web/static/src/img/discuss-icon.png',
|
||||
'To-do': '/web/static/src/img/todo-icon.png',
|
||||
'Calendar': '/web/static/src/img/calendar-icon.png',
|
||||
'Contacts': '/web/static/src/img/contacts-icon.png',
|
||||
'CRM': '/web/static/src/img/crm-icon.png',
|
||||
'Project': '/web/static/src/img/project-icon.png',
|
||||
'Website': '/web/static/src/img/website-icon.png',
|
||||
'Email Marketing': '/web/static/src/img/email-icon.png',
|
||||
'Surveys': '/web/static/src/img/survey-icon.png',
|
||||
'Employees': '/web/static/src/img/employees-icon.png',
|
||||
'Link Tracker': '/web/static/src/img/link-icon.png',
|
||||
'Apps': '/web/static/src/img/apps-icon.png',
|
||||
'Settings': '/web/static/src/img/settings-icon.png'
|
||||
};
|
||||
|
||||
// Try to get custom icon first, fallback to default
|
||||
return customIcons[webIcon] || '/web/static/img/placeholder.png';
|
||||
}
|
||||
|
||||
openModule(module) {
|
||||
if (module.url) {
|
||||
window.location.href = module.url;
|
||||
}
|
||||
}
|
||||
|
||||
openThemeColors() {
|
||||
// Navigate to the theme colors configuration
|
||||
window.location.href = '/web#action=expert_theme.action_expert_theme_config';
|
||||
}
|
||||
}
|
||||
|
||||
ExpertHome.template = "expert_theme.ExpertHomeTemplate";
|
||||
|
||||
// Register the component as a client action
|
||||
registry.category("actions").add("expert_home", ExpertHome);
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
// Load login template styles dynamically
|
||||
// Temporarily disabled to fix login page access
|
||||
/*
|
||||
(function() {
|
||||
function applyLoginStyles() {
|
||||
fetch('/expert_theme/get_login_template_styles')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success && data.styles) {
|
||||
const root = document.documentElement;
|
||||
if (data.styles.background_color) {
|
||||
// Set CSS variable
|
||||
root.style.setProperty('--expert-login-bg-color', data.styles.background_color);
|
||||
|
||||
// Apply background color to body (main background)
|
||||
if (document.body) {
|
||||
document.body.style.backgroundColor = data.styles.background_color;
|
||||
}
|
||||
|
||||
// Apply to login container if exists
|
||||
const loginContainer = document.querySelector('.oe_login_form') ||
|
||||
document.querySelector('.o_database_list') ||
|
||||
document.querySelector('main') ||
|
||||
document.body;
|
||||
if (loginContainer) {
|
||||
loginContainer.style.backgroundColor = data.styles.background_color;
|
||||
}
|
||||
|
||||
// Also apply to html element
|
||||
if (document.documentElement) {
|
||||
document.documentElement.style.backgroundColor = data.styles.background_color;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.warn('Could not load login template styles:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Apply styles when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', applyLoginStyles);
|
||||
} else {
|
||||
applyLoginStyles();
|
||||
}
|
||||
|
||||
// Also apply after a short delay to ensure everything is loaded
|
||||
setTimeout(applyLoginStyles, 100);
|
||||
})();
|
||||
*/
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
console.log('expert_login_template_list.js loaded');
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { ListBooleanToggleField } from "@web/views/fields/boolean_toggle/list_boolean_toggle_field";
|
||||
|
||||
// Patch ListBooleanToggleField to reload after toggle
|
||||
patch(ListBooleanToggleField.prototype, {
|
||||
async onClick() {
|
||||
const isActiveField = this.props.name === 'active' && this.props.record.resModel === 'expert.login.template';
|
||||
console.log('ListBooleanToggleField onClick - isActiveField:', isActiveField, 'name:', this.props.name, 'resModel:', this.props.record.resModel);
|
||||
|
||||
// Call parent method first
|
||||
await super.onClick(...arguments);
|
||||
|
||||
// If this is the active field, reload the page
|
||||
if (isActiveField) {
|
||||
console.log('Active field toggled! Reloading page in 500ms...');
|
||||
setTimeout(() => {
|
||||
console.log('Reloading now!');
|
||||
window.location.reload();
|
||||
}, 500);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { FormController } from "@web/views/form/form_controller";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
export class ExpertThemeConfigController extends FormController {
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
// Listen for field changes to apply colors in real-time
|
||||
this.env.bus.addEventListener('FIELD_CHANGED', this.onFieldChanged.bind(this));
|
||||
}
|
||||
|
||||
onFieldChanged(event) {
|
||||
// Check if the changed field is a color field
|
||||
if (event.detail.fieldName && event.detail.fieldName.includes('color')) {
|
||||
this.applyThemePreview();
|
||||
}
|
||||
}
|
||||
|
||||
async applyThemePreview() {
|
||||
try {
|
||||
// Get current form values
|
||||
const formData = this.model.root;
|
||||
const record = formData.data;
|
||||
|
||||
// Create CSS variables object from form data
|
||||
const cssVariables = {
|
||||
'--expert-primary-color': record.primary_color || '#875A7B',
|
||||
'--expert-primary-hover': record.primary_hover || '#6B4C6B',
|
||||
'--expert-primary-light': record.primary_light || '#A67B9B',
|
||||
'--expert-secondary-color': record.secondary_color || '#00A09D',
|
||||
'--expert-secondary-hover': record.secondary_hover || '#008B8A',
|
||||
'--expert-btn-primary': record.btn_primary || '#875A7B',
|
||||
'--expert-btn-primary-hover': record.btn_primary_hover || '#6B4C6B',
|
||||
'--expert-btn-secondary': record.btn_secondary || '#00A09D',
|
||||
'--expert-btn-secondary-hover': record.btn_secondary_hover || '#008B8A',
|
||||
'--expert-btn-light': record.btn_light || '#F8F9FA',
|
||||
'--expert-btn-light-hover': record.btn_light_hover || '#E9ECEF',
|
||||
'--expert-btn-light-text': record.btn_light_text || '#6C757D',
|
||||
'--expert-bg-primary': record.bg_primary || '#875A7B',
|
||||
'--expert-bg-secondary': record.bg_secondary || '#F8F9FA',
|
||||
'--expert-bg-content': record.bg_content || '#FFFFFF',
|
||||
'--expert-bg-gradient-start': record.bg_gradient_start || '#f5f7fa',
|
||||
'--expert-bg-gradient-end': record.bg_gradient_end || '#c3cfe2',
|
||||
'--expert-text-primary': record.text_primary || '#212529',
|
||||
'--expert-text-secondary': record.text_secondary || '#6C757D',
|
||||
'--expert-text-light': record.text_light || '#FFFFFF',
|
||||
'--expert-border-color': record.border_color || '#DEE2E6',
|
||||
'--expert-border-light': record.border_light || '#E9ECEF',
|
||||
'--expert-success': record.success_color || '#28A745',
|
||||
'--expert-warning': record.warning_color || '#FFC107',
|
||||
'--expert-danger': record.danger_color || '#DC3545',
|
||||
'--expert-info': record.info_color || '#17A2B8',
|
||||
};
|
||||
|
||||
// Apply the CSS variables
|
||||
this.applyCSSVariables(cssVariables);
|
||||
|
||||
} catch (error) {
|
||||
console.warn('Error applying theme preview:', error);
|
||||
}
|
||||
}
|
||||
|
||||
applyCSSVariables(cssVariables) {
|
||||
const root = document.documentElement;
|
||||
|
||||
// Apply each CSS variable to the root element
|
||||
Object.entries(cssVariables).forEach(([property, value]) => {
|
||||
if (value) {
|
||||
root.style.setProperty(property, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async save() {
|
||||
const result = await super.save();
|
||||
|
||||
// After saving, apply the theme
|
||||
if (result) {
|
||||
await this.applyTheme();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async applyTheme() {
|
||||
try {
|
||||
const response = await fetch('/expert_theme/apply_theme', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
// Show success message
|
||||
this.env.services.notification.add('Theme applied successfully!', {
|
||||
type: 'success',
|
||||
});
|
||||
} else {
|
||||
this.env.services.notification.add('Error applying theme: ' + result.error, {
|
||||
type: 'danger',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.env.services.notification.add('Error applying theme: ' + error.message, {
|
||||
type: 'danger',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register the controller for the expert.theme.config model
|
||||
registry.category("views").add("expert_theme_config_form", {
|
||||
...registry.category("views").get("form"),
|
||||
Controller: ExpertThemeConfigController,
|
||||
});
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { Component, onMounted, onWillStart } from "@odoo/owl";
|
||||
|
||||
export class ExpertThemeDynamic extends Component {
|
||||
setup() {
|
||||
onWillStart(() => {
|
||||
this.loadThemeColors();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
this.loadThemeColors();
|
||||
});
|
||||
}
|
||||
|
||||
async loadThemeColors() {
|
||||
try {
|
||||
const response = await fetch('/expert_theme/get_css_variables');
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success && result.css_variables) {
|
||||
this.applyCSSVariables(result.css_variables);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Could not load dynamic theme colors:', error);
|
||||
}
|
||||
}
|
||||
|
||||
applyCSSVariables(cssVariables) {
|
||||
const root = document.documentElement;
|
||||
|
||||
// Apply each CSS variable to the root element
|
||||
Object.entries(cssVariables).forEach(([property, value]) => {
|
||||
if (value) {
|
||||
root.style.setProperty(property, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Register the component to run on every page load
|
||||
registry.category("services").add("expert_theme_dynamic", {
|
||||
start() {
|
||||
const component = new ExpertThemeDynamic();
|
||||
component.loadThemeColors();
|
||||
|
||||
// Also load colors when the page is refreshed or navigated
|
||||
window.addEventListener('load', () => {
|
||||
component.loadThemeColors();
|
||||
});
|
||||
|
||||
return component;
|
||||
}
|
||||
});
|
||||
|
||||
// Also create a standalone function that can be called manually
|
||||
window.expertThemeApplyColors = async function() {
|
||||
try {
|
||||
const response = await fetch('/expert_theme/get_css_variables');
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success && result.css_variables) {
|
||||
const root = document.documentElement;
|
||||
Object.entries(result.css_variables).forEach(([property, value]) => {
|
||||
if (value) {
|
||||
root.style.setProperty(property, value);
|
||||
}
|
||||
});
|
||||
console.log('Expert Theme colors applied successfully!');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error applying Expert Theme colors:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Auto-apply colors when the script loads
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
window.expertThemeApplyColors();
|
||||
});
|
||||
|
|
@ -0,0 +1,258 @@
|
|||
/* Expert Theme - Modern Login Template Styles */
|
||||
|
||||
.expert-login-modern {
|
||||
background: #19181F;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
|
||||
.container-fluid {
|
||||
height: calc(100vh - 40px);
|
||||
}
|
||||
|
||||
.row {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// Left Column: Login Form
|
||||
.expert-login-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
// Logo Section
|
||||
.expert-login-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 40px;
|
||||
|
||||
.expert-logo-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 8px;
|
||||
margin-right: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
span {
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.expert-logo-text {
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
// Welcome Section
|
||||
.expert-login-welcome {
|
||||
h1 {
|
||||
color: white;
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.2;
|
||||
|
||||
.welcome-emoji {
|
||||
font-size: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 16px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
// Form Section
|
||||
.expert-login-form {
|
||||
max-width: 450px;
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 24px;
|
||||
|
||||
label {
|
||||
font-weight: 500;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="password"] {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background: rgba(255, 255, 255, 0.15) !important;
|
||||
border-color: rgba(255, 255, 255, 0.4) !important;
|
||||
outline: none;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-check {
|
||||
margin-bottom: 32px;
|
||||
|
||||
label {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-right: 8px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
accent-color: #667eea;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
width: 100%;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Login Link Section
|
||||
.expert-login-link {
|
||||
margin-top: 24px;
|
||||
max-width: 450px;
|
||||
|
||||
p {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
|
||||
a {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Right Column: Image Section
|
||||
.expert-login-right {
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: 20px 0 0 20px;
|
||||
|
||||
.expert-login-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 25%, #667eea 75%, #764ba2 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
|
||||
.expert-spiral-decoration {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
position: relative;
|
||||
opacity: 0.9;
|
||||
|
||||
.spiral-circle {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
|
||||
|
||||
&.circle-1 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
&.circle-2 {
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
top: 10%;
|
||||
left: 10%;
|
||||
background: radial-gradient(circle, rgba(255, 255, 255, 0.08) 0%, transparent 70%);
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
&.circle-3 {
|
||||
width: 60%;
|
||||
height: 60%;
|
||||
top: 20%;
|
||||
left: 20%;
|
||||
background: radial-gradient(circle, rgba(255, 255, 255, 0.06) 0%, transparent 70%);
|
||||
transform: rotate(30deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Responsive adjustments
|
||||
@media (max-width: 991.98px) {
|
||||
.expert-login-modern {
|
||||
.expert-login-left {
|
||||
padding: 30px 20px;
|
||||
}
|
||||
|
||||
.expert-login-welcome {
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
|
||||
.welcome-emoji {
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates>
|
||||
<t t-name="expert_theme.ExpertHomeTemplate" owl="1">
|
||||
<div class="expert-home-container">
|
||||
|
||||
<!-- Theme Colors Button -->
|
||||
<div class="expert-theme-controls">
|
||||
<button class="btn btn-primary expert-theme-btn"
|
||||
t-on-click="() => this.openThemeColors()">
|
||||
🎨 Customize Theme Colors
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div t-if="state.loading" class="expert-loading">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
<p>Loading your modules...</p>
|
||||
</div>
|
||||
|
||||
<div t-if="state.error" class="expert-error">
|
||||
<div class="alert alert-danger">
|
||||
<strong>Error:</strong> <t t-esc="state.error"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div t-if="!state.loading and !state.error" class="expert-modules-grid">
|
||||
<div t-foreach="state.modules" t-as="module" t-key="module.id"
|
||||
class="expert-module-card"
|
||||
t-on-click="() => this.openModule(module)">
|
||||
<div class="expert-module-icon">
|
||||
<img t-att-src="this.getModuleIcon(module.web_icon)"
|
||||
t-att-alt="module.name"
|
||||
class="module-icon"/>
|
||||
</div>
|
||||
<div class="expert-module-info">
|
||||
<h3 t-esc="module.name"/>
|
||||
<p>Click to open</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div t-if="!state.loading and !state.error and state.modules.length === 0"
|
||||
class="expert-no-modules">
|
||||
<div class="alert alert-info">
|
||||
<strong>No installed modules found.</strong>
|
||||
<p>This could mean:</p>
|
||||
<ul>
|
||||
<li>No modules are currently installed</li>
|
||||
<li>The module detection is not working properly</li>
|
||||
<li>Check the browser console for debugging information</li>
|
||||
</ul>
|
||||
<p>Try refreshing the page or check if modules are properly installed in Odoo.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Custom Home Page View -->
|
||||
<record id="view_expert_home" model="ir.ui.view">
|
||||
<field name="name">expert.theme.home</field>
|
||||
<field name="model">ir.ui.menu</field>
|
||||
<field name="type">qweb</field>
|
||||
<field name="arch" type="xml">
|
||||
<t t-name="expert_theme.home">
|
||||
<div class="expert-home-container">
|
||||
<div class="expert-home-header">
|
||||
<h1>Welcome to Expert Theme</h1>
|
||||
<p>Your installed modules are listed below</p>
|
||||
</div>
|
||||
<div class="expert-modules-grid" id="expert-modules-container">
|
||||
<!-- Modules will be loaded here via JavaScript -->
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action for Expert Home -->
|
||||
<record id="action_expert_home" model="ir.actions.client">
|
||||
<field name="name">Expert Home</field>
|
||||
<field name="tag">expert_home</field>
|
||||
<field name="target">current</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Expert Login Template Form View -->
|
||||
<record id="view_expert_login_template_form" model="ir.ui.view">
|
||||
<field name="name">expert.login.template.form</field>
|
||||
<field name="model">expert.login.template</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Login Page Template">
|
||||
<header>
|
||||
<button string="Save" special="save"
|
||||
class="btn-primary"
|
||||
icon="fa-save"/>
|
||||
<button name="toggle_active" string="Activate" type="object"
|
||||
class="btn-primary"
|
||||
invisible="active"/>
|
||||
<button name="action_duplicate" string="Duplicate" type="object"
|
||||
class="btn-secondary"/>
|
||||
<field name="active" widget="boolean_toggle"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="name" placeholder="Template Name"/>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning" role="alert" invisible="not id">
|
||||
<strong>Editing Existing Template:</strong> You are editing an existing template.
|
||||
To create a NEW template, go back to the list and click "New" button.
|
||||
</div>
|
||||
|
||||
<group>
|
||||
<group string="Template Design">
|
||||
<field name="template_type"
|
||||
widget="radio"
|
||||
options="{'horizontal': true}"
|
||||
required="1"/>
|
||||
<div class="alert alert-info" role="alert" col="2">
|
||||
<strong>Template Types:</strong><br/>
|
||||
• <strong>Default:</strong> Standard Odoo login page<br/>
|
||||
• <strong>Modern:</strong> Card design with gradient background<br/>
|
||||
• <strong>Minimal:</strong> Clean minimal design<br/>
|
||||
• <strong>Corporate:</strong> Professional dark theme
|
||||
</div>
|
||||
</group>
|
||||
<group string="Template Settings">
|
||||
<field name="sequence"/>
|
||||
<field name="is_default" readonly="1"/>
|
||||
<field name="background_color" widget="color"
|
||||
string="Background Color"
|
||||
help="Background color for the login page (used for CSS customization)"/>
|
||||
<div class="alert alert-info" role="alert" col="2">
|
||||
<strong>Note:</strong> Only one template can be active at a time.
|
||||
Activating this template will deactivate all others.
|
||||
Each template has a completely different HTML structure and design.
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group string="Description">
|
||||
<field name="description" placeholder="Describe this template..." nolabel="1"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<notebook>
|
||||
<page string="Template Images & Logo" name="template_images">
|
||||
<group>
|
||||
<group string="Modern Template" invisible="template_type != 'modern'">
|
||||
<field name="modern_template_logo" widget="image" class="oe_avatar"
|
||||
options="{'size': [100, 100]}"
|
||||
help="Upload your company logo (recommended size: 40x40px). This will replace the default 'E' icon."/>
|
||||
<field name="modern_template_image" widget="image" class="oe_avatar"
|
||||
options="{'size': [600, 600]}"
|
||||
help="Upload an image to display on the right side of Modern login, signup, and reset password pages"/>
|
||||
<div class="alert alert-info" role="alert" col="2">
|
||||
<strong>Note:</strong> Logo and image will be used across all three pages (Login, Signup, Reset Password) when Modern template is active.
|
||||
</div>
|
||||
</group>
|
||||
<group string="Minimal Template" invisible="template_type != 'minimal'">
|
||||
<field name="minimal_template_logo" widget="image" class="oe_avatar"
|
||||
options="{'size': [100, 100]}"
|
||||
help="Upload your company logo (recommended size: 40x40px). This will replace the default 'E' icon."/>
|
||||
<field name="minimal_template_image" widget="image" class="oe_avatar"
|
||||
options="{'size': [600, 600]}"
|
||||
help="Upload an image to display on the right side of Minimal login, signup, and reset password pages"/>
|
||||
<div class="alert alert-info" role="alert" col="2">
|
||||
<strong>Note:</strong> Logo and image will be used across all three pages (Login, Signup, Reset Password) when Minimal template is active.
|
||||
</div>
|
||||
</group>
|
||||
<group string="Corporate Template" invisible="template_type != 'corporate'">
|
||||
<field name="corporate_template_logo" widget="image" class="oe_avatar"
|
||||
options="{'size': [100, 100]}"
|
||||
help="Upload your company logo (recommended size: 40x40px). This will replace the default 'E' icon."/>
|
||||
<field name="corporate_template_image" widget="image" class="oe_avatar"
|
||||
options="{'size': [600, 600]}"
|
||||
help="Upload an image to display on the right side of Corporate login, signup, and reset password pages"/>
|
||||
<div class="alert alert-info" role="alert" col="2">
|
||||
<strong>Note:</strong> Logo and image will be used across all three pages (Login, Signup, Reset Password) when Corporate template is active.
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<page string="Modern Template Text" name="modern_template_text" invisible="template_type != 'modern'">
|
||||
<group>
|
||||
<group string="Login Page">
|
||||
<field name="modern_login_title" placeholder="e.g., Welcome to Expert 👋"/>
|
||||
<field name="modern_login_subtitle" placeholder="e.g., Kindly fill in your details below to sign in to your account"/>
|
||||
<field name="modern_login_button_text" placeholder="e.g., Sign In"/>
|
||||
<field name="modern_login_button_bg_color" widget="color"/>
|
||||
<field name="modern_login_button_text_color" widget="color"/>
|
||||
<field name="modern_login_button_bg_hover" widget="color"/>
|
||||
<field name="modern_login_button_text_hover" widget="color"/>
|
||||
</group>
|
||||
<group string="Signup Page">
|
||||
<field name="modern_signup_title" placeholder="e.g., Create an account"/>
|
||||
<field name="modern_signup_subtitle" placeholder="e.g., Join us today and get started"/>
|
||||
<field name="modern_signup_button_text" placeholder="e.g., Create an account"/>
|
||||
<field name="modern_signup_button_bg_color" widget="color"/>
|
||||
<field name="modern_signup_button_text_color" widget="color"/>
|
||||
<field name="modern_signup_button_bg_hover" widget="color"/>
|
||||
<field name="modern_signup_button_text_hover" widget="color"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group string="Reset Password Page">
|
||||
<field name="modern_reset_title" placeholder="e.g., Reset your password"/>
|
||||
<field name="modern_reset_subtitle" placeholder="e.g., Enter your email to receive reset instructions"/>
|
||||
</group>
|
||||
<group></group>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<page string="Minimal Template Text" name="minimal_template_text" invisible="template_type != 'minimal'">
|
||||
<group>
|
||||
<group string="Login Page">
|
||||
<field name="minimal_login_title" placeholder="e.g., Welcome to Expert 👋"/>
|
||||
<field name="minimal_login_subtitle" placeholder="e.g., Kindly fill in your details below to sign in to your account"/>
|
||||
<field name="minimal_login_button_text" placeholder="e.g., Sign In"/>
|
||||
<field name="minimal_login_button_bg_color" widget="color"/>
|
||||
<field name="minimal_login_button_text_color" widget="color"/>
|
||||
<field name="minimal_login_button_bg_hover" widget="color"/>
|
||||
<field name="minimal_login_button_text_hover" widget="color"/>
|
||||
</group>
|
||||
<group string="Signup Page">
|
||||
<field name="minimal_signup_title" placeholder="e.g., Create an account"/>
|
||||
<field name="minimal_signup_subtitle" placeholder="e.g., Join us today and get started"/>
|
||||
<field name="minimal_signup_button_text" placeholder="e.g., Create an account"/>
|
||||
<field name="minimal_signup_button_bg_color" widget="color"/>
|
||||
<field name="minimal_signup_button_text_color" widget="color"/>
|
||||
<field name="minimal_signup_button_bg_hover" widget="color"/>
|
||||
<field name="minimal_signup_button_text_hover" widget="color"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group string="Reset Password Page">
|
||||
<field name="minimal_reset_title" placeholder="e.g., Reset your password"/>
|
||||
<field name="minimal_reset_subtitle" placeholder="e.g., Enter your email to receive reset instructions"/>
|
||||
</group>
|
||||
<group></group>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Expert Login Template List View -->
|
||||
<record id="view_expert_login_template_tree" model="ir.ui.view">
|
||||
<field name="name">expert.login.template.tree</field>
|
||||
<field name="model">expert.login.template</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Login Page Templates" default_order="sequence">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="template_type"/>
|
||||
<field name="background_color" widget="color"/>
|
||||
<field name="active" widget="boolean_toggle" context="{'from_list_view': True}"/>
|
||||
<field name="is_default"/>
|
||||
<field name="description" invisible="1"/>
|
||||
<button name="toggle_active" string="Activate" type="object"
|
||||
class="btn-primary"
|
||||
invisible="active"
|
||||
icon="fa-check"
|
||||
confirm="Are you sure you want to activate this template? It will deactivate all other templates."/>
|
||||
<button name="action_duplicate" string="Duplicate" type="object"
|
||||
class="btn-secondary"
|
||||
icon="fa-copy"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Expert Login Template Search View -->
|
||||
<record id="view_expert_login_template_search" model="ir.ui.view">
|
||||
<field name="name">expert.login.template.search</field>
|
||||
<field name="model">expert.login.template</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Login Templates">
|
||||
<field name="name"/>
|
||||
<filter string="Active" name="active" domain="[('active', '=', True)]"/>
|
||||
<filter string="Inactive" name="inactive" domain="[('active', '=', False)]"/>
|
||||
<filter string="Default" name="default" domain="[('is_default', '=', True)]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Status" name="group_active" context="{'group_by': 'active'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Expert Login Template Action -->
|
||||
<record id="action_expert_login_template" model="ir.actions.act_window">
|
||||
<field name="name">Login Page Templates</field>
|
||||
<field name="res_model">expert.login.template</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="domain">[]</field>
|
||||
<field name="context">{'active_test': False}</field>
|
||||
<field name="search_view_id" ref="view_expert_login_template_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create your first login page template!
|
||||
</p>
|
||||
<p>
|
||||
Configure different login page templates and switch between them.
|
||||
Each template can have different colors, backgrounds, and styles.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Create a new top-level menu for Expert Home -->
|
||||
<menuitem id="menu_expert_root"
|
||||
name="Expert Home"
|
||||
sequence="1"/>
|
||||
|
||||
<menuitem id="menu_expert_home"
|
||||
name="Dashboard"
|
||||
parent="menu_expert_root"
|
||||
action="action_expert_home"
|
||||
sequence="1"/>
|
||||
|
||||
<menuitem id="menu_expert_login_templates"
|
||||
name="Login Page Templates"
|
||||
parent="menu_expert_root"
|
||||
action="action_expert_login_template"
|
||||
sequence="2"/>
|
||||
|
||||
<menuitem id="menu_expert_theme_colors"
|
||||
name="Theme Colors"
|
||||
parent="menu_expert_root"
|
||||
action="action_expert_theme_config"
|
||||
sequence="3"/>
|
||||
|
||||
<!-- Also add Theme Colors to the main menu for easier access -->
|
||||
<menuitem id="menu_theme_colors_main"
|
||||
name="Theme Colors"
|
||||
action="action_expert_theme_config"
|
||||
sequence="100"/>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Expert Theme Configuration Form View -->
|
||||
<record id="view_expert_theme_config_form" model="ir.ui.view">
|
||||
<field name="name">expert.theme.config.form</field>
|
||||
<field name="model">expert.theme.config</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Expert Theme Configuration">
|
||||
<header>
|
||||
<button name="apply_theme" string="Apply Theme" type="object" class="btn-primary"/>
|
||||
<field name="active" widget="boolean_toggle"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="name" placeholder="Theme Name"/>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<group>
|
||||
<group string="Primary Colors">
|
||||
<field name="primary_color" widget="color"/>
|
||||
<field name="primary_hover" widget="color"/>
|
||||
<field name="primary_light" widget="color"/>
|
||||
</group>
|
||||
<group string="Secondary Colors">
|
||||
<field name="secondary_color" widget="color"/>
|
||||
<field name="secondary_hover" widget="color"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<group>
|
||||
<group string="Button Colors">
|
||||
<field name="btn_primary" widget="color"/>
|
||||
<field name="btn_primary_hover" widget="color"/>
|
||||
<field name="btn_secondary" widget="color"/>
|
||||
<field name="btn_secondary_hover" widget="color"/>
|
||||
<field name="btn_light" widget="color"/>
|
||||
<field name="btn_light_hover" widget="color"/>
|
||||
<field name="btn_light_text" widget="color"/>
|
||||
</group>
|
||||
<group string="Background Colors">
|
||||
<field name="bg_primary" widget="color"/>
|
||||
<field name="bg_secondary" widget="color"/>
|
||||
<field name="bg_content" widget="color"/>
|
||||
<field name="bg_gradient_start" widget="color"/>
|
||||
<field name="bg_gradient_end" widget="color"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<group>
|
||||
<group string="Text Colors">
|
||||
<field name="text_primary" widget="color"/>
|
||||
<field name="text_secondary" widget="color"/>
|
||||
<field name="text_light" widget="color"/>
|
||||
</group>
|
||||
<group string="Border Colors">
|
||||
<field name="border_color" widget="color"/>
|
||||
<field name="border_light" widget="color"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<group>
|
||||
<group string="Status Colors">
|
||||
<field name="success_color" widget="color"/>
|
||||
<field name="warning_color" widget="color"/>
|
||||
<field name="danger_color" widget="color"/>
|
||||
<field name="info_color" widget="color"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Expert Theme Configuration List View -->
|
||||
<record id="view_expert_theme_config_tree" model="ir.ui.view">
|
||||
<field name="name">expert.theme.config.list</field>
|
||||
<field name="model">expert.theme.config</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Expert Theme Configurations">
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
<field name="primary_color" widget="color"/>
|
||||
<field name="secondary_color" widget="color"/>
|
||||
<field name="bg_gradient_start" widget="color"/>
|
||||
<field name="bg_gradient_end" widget="color"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Expert Theme Configuration Search View -->
|
||||
<record id="view_expert_theme_config_search" model="ir.ui.view">
|
||||
<field name="name">expert.theme.config.search</field>
|
||||
<field name="model">expert.theme.config</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Theme Configurations">
|
||||
<field name="name"/>
|
||||
<filter string="Active" name="active" domain="[('active', '=', True)]"/>
|
||||
<filter string="Inactive" name="inactive" domain="[('active', '=', False)]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Status" name="group_active" context="{'group_by': 'active'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Expert Theme Configuration Action -->
|
||||
<record id="action_expert_theme_config" model="ir.actions.act_window">
|
||||
<field name="name">Theme Colors</field>
|
||||
<field name="res_model">expert.theme.config</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="search_view_id" ref="view_expert_theme_config_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create your first theme configuration!
|
||||
</p>
|
||||
<p>
|
||||
Customize the colors of your Expert Theme by creating a new configuration.
|
||||
You can set primary colors, button colors, backgrounds, and more.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,888 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!--
|
||||
Multiple Login Page Templates with Different HTML Structures
|
||||
Standalone templates rendered by the /web/login override.
|
||||
Each template calls web.layout and defines its own login form.
|
||||
-->
|
||||
|
||||
<!-- Template 1: Modern Card Design -->
|
||||
<template id="login_template_modern_page" name="Modern Login Template">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="head">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<t t-call-assets="web.assets_frontend" t-js="false"/>
|
||||
<style>
|
||||
body.bg-100 {
|
||||
background-color: <t t-if="login_template and login_template.background_color"><t t-esc="login_template.background_color"/></t><t t-else="">#19181F</t> !important;
|
||||
}
|
||||
</style>
|
||||
</t>
|
||||
<t t-set="body_classname" t-value="'bg-100'"/>
|
||||
<div class="oe_login_form expert-login-modern">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row">
|
||||
<!-- Left Column: Login Form (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 expert-login-left">
|
||||
<!-- Logo and Text -->
|
||||
<div class="expert-login-logo">
|
||||
<t t-if="login_template and login_template.modern_template_logo">
|
||||
<img t-att-src="'data:image/png;base64,%s' % login_template.modern_template_logo.decode('utf-8')"
|
||||
alt="Company Logo"
|
||||
style="width: 40px; height: 40px; object-fit: contain; margin-right: 12px;"/>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div class="expert-logo-icon">
|
||||
<span>E</span>
|
||||
</div>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<!-- Welcome Text -->
|
||||
<div class="expert-login-welcome">
|
||||
<h1>
|
||||
<t t-if="login_template and login_template.modern_login_title">
|
||||
<t t-esc="login_template.modern_login_title"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Welcome to Expert <span class="welcome-emoji">👋</span>
|
||||
</t>
|
||||
</h1>
|
||||
<p>
|
||||
<t t-if="login_template and login_template.modern_login_subtitle">
|
||||
<t t-esc="login_template.modern_login_subtitle"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Kindly fill in your details below to sign in to your account
|
||||
</t>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Login Form -->
|
||||
<form class="oe_login_form expert-login-form" role="form" method="post" t-att-action="request.httprequest.path">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="redirect" t-att-value="redirect or ''"/>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Email or Username</label>
|
||||
<input type="text" name="login" class="form-control"
|
||||
placeholder="Enter your email or username"
|
||||
t-att-value="login"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Password</label>
|
||||
<input type="password" name="password" class="form-control"
|
||||
placeholder="Enter your password"/>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<label>
|
||||
<input type="checkbox" name="remember"/>
|
||||
Remember me
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary expert-modern-login-btn">
|
||||
<t t-if="login_template and login_template.modern_login_button_text">
|
||||
<t t-esc="login_template.modern_login_button_text"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Sign In
|
||||
</t>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Button Styles -->
|
||||
<style>
|
||||
.expert-modern-login-btn {
|
||||
background-color: <t t-if="login_template and login_template.modern_login_button_bg_color"><t t-esc="login_template.modern_login_button_bg_color"/></t><t t-else="">#007bff</t> !important;
|
||||
color: <t t-if="login_template and login_template.modern_login_button_text_color"><t t-esc="login_template.modern_login_button_text_color"/></t><t t-else="">#FFFFFF</t> !important;
|
||||
border: none !important;
|
||||
}
|
||||
.expert-modern-login-btn:hover {
|
||||
background-color: <t t-if="login_template and login_template.modern_login_button_bg_hover"><t t-esc="login_template.modern_login_button_bg_hover"/></t><t t-else="">#0056b3</t> !important;
|
||||
color: <t t-if="login_template and login_template.modern_login_button_text_hover"><t t-esc="login_template.modern_login_button_text_hover"/></t><t t-else="">#FFFFFF</t> !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Signup Link -->
|
||||
<div class="expert-login-link">
|
||||
<p>
|
||||
Don't have an account?
|
||||
<a t-attf-href="/web/signup?{{ keep_query() }}">Sign Up</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Image (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 d-none d-lg-block expert-login-right">
|
||||
<div class="expert-login-image">
|
||||
<img t-if="login_template and login_template.modern_template_image"
|
||||
t-att-src="'data:image/png;base64,%s' % login_template.modern_template_image.decode('utf-8')"
|
||||
alt="Login Image" class="img-fluid"/>
|
||||
<img t-else=""
|
||||
src="/expert_theme/static/src/img/modern-template-bg.png"
|
||||
alt="Login Image" class="img-fluid"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Modern Signup Template -->
|
||||
<template id="signup_template_modern_page" name="Modern Signup Template">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="head">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<t t-call-assets="web.assets_frontend" t-js="false"/>
|
||||
<style>
|
||||
body.bg-100 {
|
||||
background-color: <t t-if="login_template and login_template.background_color"><t t-esc="login_template.background_color"/></t><t t-else="">#19181F</t> !important;
|
||||
}
|
||||
</style>
|
||||
</t>
|
||||
<t t-set="body_classname" t-value="'bg-100'"/>
|
||||
<div class="oe_login_form expert-login-modern">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row">
|
||||
<!-- Left Column: Signup Form (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 expert-login-left">
|
||||
<!-- Logo and Text -->
|
||||
<div class="expert-login-logo">
|
||||
<t t-if="login_template and login_template.modern_template_logo">
|
||||
<img t-att-src="'data:image/png;base64,%s' % login_template.modern_template_logo.decode('utf-8')"
|
||||
alt="Company Logo"
|
||||
style="width: 40px; height: 40px; object-fit: contain; margin-right: 12px;"/>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div class="expert-logo-icon">
|
||||
<span>E</span>
|
||||
</div>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<!-- Welcome Text -->
|
||||
<div class="expert-login-welcome">
|
||||
<h1>
|
||||
<t t-if="login_template and login_template.modern_signup_title">
|
||||
<t t-esc="login_template.modern_signup_title"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Create an account
|
||||
</t>
|
||||
</h1>
|
||||
<p>
|
||||
<t t-if="login_template and login_template.modern_signup_subtitle">
|
||||
<t t-esc="login_template.modern_signup_subtitle"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Join us today and get started
|
||||
</t>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Signup Form -->
|
||||
<form class="oe_signup_form expert-login-form" role="form" method="post" t-if="not message">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="redirect" t-att-value="redirect or ''"/>
|
||||
<input type="hidden" name="token" t-att-value="token or ''"/>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Your Email</label>
|
||||
<input type="text" name="login" class="form-control"
|
||||
placeholder="Enter your email"
|
||||
t-att-value="login"
|
||||
t-att-readonly="'readonly' if (token and not invalid_token) else None"
|
||||
autofocus="autofocus"
|
||||
autocapitalize="off"
|
||||
required="required"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group" t-if="not (token and not invalid_token)">
|
||||
<label>Your Name</label>
|
||||
<input type="text" name="name" class="form-control"
|
||||
placeholder="e.g. John Doe"
|
||||
t-att-value="name"
|
||||
required="required"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Password</label>
|
||||
<input type="password" name="password" id="password" class="form-control"
|
||||
placeholder="Enter your password"
|
||||
required="required"
|
||||
t-att-autofocus="'autofocus' if (token and not invalid_token) else None"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Confirm Password</label>
|
||||
<input type="password" name="confirm_password" id="confirm_password" class="form-control"
|
||||
placeholder="Confirm your password"
|
||||
required="required"/>
|
||||
</div>
|
||||
|
||||
<p class="alert alert-danger" t-if="error" role="alert">
|
||||
<t t-esc="error"/>
|
||||
</p>
|
||||
|
||||
<button type="submit" class="btn btn-primary expert-modern-signup-btn">
|
||||
<t t-if="login_template and login_template.modern_signup_button_text">
|
||||
<t t-esc="login_template.modern_signup_button_text"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Sign Up
|
||||
</t>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Button Styles -->
|
||||
<style>
|
||||
.expert-modern-signup-btn {
|
||||
background-color: <t t-if="login_template and login_template.modern_signup_button_bg_color"><t t-esc="login_template.modern_signup_button_bg_color"/></t><t t-else="">#28a745</t> !important;
|
||||
color: <t t-if="login_template and login_template.modern_signup_button_text_color"><t t-esc="login_template.modern_signup_button_text_color"/></t><t t-else="">#FFFFFF</t> !important;
|
||||
border: none !important;
|
||||
}
|
||||
.expert-modern-signup-btn:hover {
|
||||
background-color: <t t-if="login_template and login_template.modern_signup_button_bg_hover"><t t-esc="login_template.modern_signup_button_bg_hover"/></t><t t-else="">#218838</t> !important;
|
||||
color: <t t-if="login_template and login_template.modern_signup_button_text_hover"><t t-esc="login_template.modern_signup_button_text_hover"/></t><t t-else="">#FFFFFF</t> !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Signup Link -->
|
||||
<div class="expert-login-link">
|
||||
<p>
|
||||
Already have an account?
|
||||
<a t-attf-href="/web/login?{{ keep_query() }}">Log In</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Image (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 d-none d-lg-block expert-login-right">
|
||||
<div class="expert-login-image">
|
||||
<img t-if="login_template and login_template.modern_template_image"
|
||||
t-att-src="'data:image/png;base64,%s' % login_template.modern_template_image.decode('utf-8')"
|
||||
alt="Signup Image" class="img-fluid"/>
|
||||
<img t-else=""
|
||||
src="/expert_theme/static/src/img/modern-template-bg.png"
|
||||
alt="Signup Image" class="img-fluid"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Modern Reset Password Template -->
|
||||
<template id="reset_password_template_modern_page" name="Modern Reset Password Template">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="head">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<t t-call-assets="web.assets_frontend" t-js="false"/>
|
||||
<style>
|
||||
body.bg-100 {
|
||||
background-color: <t t-if="login_template and login_template.background_color"><t t-esc="login_template.background_color"/></t><t t-else="">#19181F</t> !important;
|
||||
}
|
||||
</style>
|
||||
</t>
|
||||
<t t-set="body_classname" t-value="'bg-100'"/>
|
||||
<div class="oe_login_form expert-login-modern">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row">
|
||||
<!-- Left Column: Reset Password Form (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 expert-login-left">
|
||||
<!-- Logo and Text -->
|
||||
<div class="expert-login-logo">
|
||||
<t t-if="login_template and login_template.modern_template_logo">
|
||||
<img t-att-src="'data:image/png;base64,%s' % login_template.modern_template_logo.decode('utf-8')"
|
||||
alt="Company Logo"
|
||||
style="width: 40px; height: 40px; object-fit: contain; margin-right: 12px;"/>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div class="expert-logo-icon">
|
||||
<span>E</span>
|
||||
</div>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<!-- Welcome Text -->
|
||||
<div class="expert-login-welcome">
|
||||
<h1>
|
||||
<t t-if="login_template and login_template.modern_reset_title">
|
||||
<t t-esc="login_template.modern_reset_title"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Reset Password <span class="welcome-emoji">🔐</span>
|
||||
</t>
|
||||
</h1>
|
||||
<p t-if="not token">
|
||||
<t t-if="login_template and login_template.modern_reset_subtitle">
|
||||
<t t-esc="login_template.modern_reset_subtitle"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Enter your email address and we'll send you instructions to reset your password
|
||||
</t>
|
||||
</p>
|
||||
<p t-if="token and not invalid_token">
|
||||
Enter your new password below
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Success Message -->
|
||||
<div t-if="message" class="expert-login-form">
|
||||
<p class="alert alert-success" role="status">
|
||||
<t t-esc="message"/>
|
||||
</p>
|
||||
<a href="/web/login" class="btn btn-link" style="color: #667eea;">Back to Login</a>
|
||||
</div>
|
||||
|
||||
<!-- Reset Password Form -->
|
||||
<form class="oe_reset_password_form expert-login-form" role="form" method="post" t-if="not message">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="redirect" t-att-value="redirect or ''"/>
|
||||
<input type="hidden" name="token" t-att-value="token or ''"/>
|
||||
|
||||
<!-- Token-based reset (set new password) -->
|
||||
<t t-if="token and not invalid_token">
|
||||
<div class="form-group">
|
||||
<label>New Password</label>
|
||||
<input type="password" name="password" id="password" class="form-control"
|
||||
placeholder="Enter your new password"
|
||||
required="required"
|
||||
autofocus="autofocus"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Confirm Password</label>
|
||||
<input type="password" name="confirm_password" id="confirm_password" class="form-control"
|
||||
placeholder="Confirm your new password"
|
||||
required="required"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<!-- Email-based reset (request reset) -->
|
||||
<t t-if="not token">
|
||||
<div class="form-group">
|
||||
<label>Your Email</label>
|
||||
<input type="text" name="login" class="form-control"
|
||||
placeholder="Enter your email"
|
||||
t-att-value="login"
|
||||
autofocus="autofocus"
|
||||
required="required"
|
||||
autocapitalize="off"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<p class="alert alert-danger" t-if="error" role="alert">
|
||||
<t t-esc="error"/>
|
||||
</p>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<t t-if="token and not invalid_token">Reset Password</t>
|
||||
<t t-if="not token">Send Reset Instructions</t>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Reset Password Link -->
|
||||
<div class="expert-login-link" t-if="not message">
|
||||
<p>
|
||||
<a t-if="not token" t-attf-href="/web/login?{{ keep_query() }}" style="color: #667eea;">Back to Login</a>
|
||||
<a t-if="invalid_token" href="/web/login" style="color: #667eea;">Back to Login</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Image (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 d-none d-lg-block expert-login-right">
|
||||
<div class="expert-login-image">
|
||||
<img t-if="login_template and login_template.modern_template_image"
|
||||
t-att-src="'data:image/png;base64,%s' % login_template.modern_template_image.decode('utf-8')"
|
||||
alt="Reset Password Image" class="img-fluid"/>
|
||||
<img t-else=""
|
||||
src="/expert_theme/static/src/img/modern-template-bg.png"
|
||||
alt="Reset Password Image" class="img-fluid"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Template 2: Minimal Design -->
|
||||
<template id="login_template_minimal_page" name="Minimal Login Template">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="head">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<t t-call-assets="web.assets_frontend" t-js="false"/>
|
||||
<style>
|
||||
body.bg-100 {
|
||||
background-color: <t t-if="login_template and login_template.background_color"><t t-esc="login_template.background_color"/></t><t t-else="">#FFFFFF</t> !important;
|
||||
}
|
||||
</style>
|
||||
</t>
|
||||
<t t-set="body_classname" t-value="'bg-100'"/>
|
||||
<div class="oe_login_form expert-login-minimal">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row">
|
||||
<!-- Left Column: Login Form (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 expert-login-left">
|
||||
<!-- Logo and Text -->
|
||||
<div class="expert-login-logo">
|
||||
<t t-if="login_template and login_template.minimal_template_logo">
|
||||
<img t-att-src="'data:image/png;base64,%s' % login_template.minimal_template_logo.decode('utf-8')"
|
||||
alt="Company Logo"
|
||||
style="width: 40px; height: 40px; object-fit: contain; margin-right: 12px;"/>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div class="expert-logo-icon">
|
||||
<span>E</span>
|
||||
</div>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<!-- Welcome Text -->
|
||||
<div class="expert-login-welcome">
|
||||
<h1>
|
||||
<t t-if="login_template and login_template.minimal_login_title">
|
||||
<t t-esc="login_template.minimal_login_title"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Welcome to Expert <span class="welcome-emoji">👋</span>
|
||||
</t>
|
||||
</h1>
|
||||
<p>
|
||||
<t t-if="login_template and login_template.minimal_login_subtitle">
|
||||
<t t-esc="login_template.minimal_login_subtitle"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Kindly fill in your details below to sign in to your account
|
||||
</t>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Login Form -->
|
||||
<form class="oe_login_form expert-login-form" role="form" method="post" t-att-action="request.httprequest.path">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="redirect" t-att-value="redirect or ''"/>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Email or Username</label>
|
||||
<input type="text" name="login" class="form-control"
|
||||
placeholder="Enter your email or username"
|
||||
t-att-value="login"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Password</label>
|
||||
<input type="password" name="password" class="form-control"
|
||||
placeholder="Enter your password"/>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<label>
|
||||
<input type="checkbox" name="remember"/>
|
||||
Remember me
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary expert-minimal-login-btn">
|
||||
<t t-if="login_template and login_template.minimal_login_button_text">
|
||||
<t t-esc="login_template.minimal_login_button_text"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Sign In
|
||||
</t>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Button Styles -->
|
||||
<style>
|
||||
.expert-minimal-login-btn {
|
||||
background-color: <t t-if="login_template and login_template.minimal_login_button_bg_color"><t t-esc="login_template.minimal_login_button_bg_color"/></t><t t-else="">#E5E5E5</t> !important;
|
||||
color: <t t-if="login_template and login_template.minimal_login_button_text_color"><t t-esc="login_template.minimal_login_button_text_color"/></t><t t-else="">#000000</t> !important;
|
||||
border: none !important;
|
||||
}
|
||||
.expert-minimal-login-btn:hover {
|
||||
background-color: <t t-if="login_template and login_template.minimal_login_button_bg_hover"><t t-esc="login_template.minimal_login_button_bg_hover"/></t><t t-else="">#D0D0D0</t> !important;
|
||||
color: <t t-if="login_template and login_template.minimal_login_button_text_hover"><t t-esc="login_template.minimal_login_button_text_hover"/></t><t t-else="">#000000</t> !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Signup Link -->
|
||||
<div class="expert-login-link">
|
||||
<p>
|
||||
Don't have an account?
|
||||
<a t-attf-href="/web/signup?{{ keep_query() }}">Sign Up</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Image (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 d-none d-lg-block expert-login-right">
|
||||
<div class="expert-login-image">
|
||||
<img t-if="login_template and login_template.minimal_template_image"
|
||||
t-att-src="'data:image/png;base64,%s' % login_template.minimal_template_image.decode('utf-8')"
|
||||
alt="Login Illustration" class="img-fluid expert-minimal-illustration"/>
|
||||
<img t-else=""
|
||||
src="/expert_theme/static/src/img/minimal-login-img.png"
|
||||
alt="Login Illustration" class="img-fluid expert-minimal-illustration"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Minimal Signup Template -->
|
||||
<template id="signup_template_minimal_page" name="Minimal Signup Template">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="head">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<t t-call-assets="web.assets_frontend" t-js="false"/>
|
||||
<style>
|
||||
body.bg-100 {
|
||||
background-color: <t t-if="login_template and login_template.background_color"><t t-esc="login_template.background_color"/></t><t t-else="">#FFFFFF</t> !important;
|
||||
}
|
||||
</style>
|
||||
</t>
|
||||
<t t-set="body_classname" t-value="'bg-100'"/>
|
||||
<div class="oe_login_form expert-login-minimal">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row">
|
||||
<!-- Left Column: Signup Form (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 expert-login-left">
|
||||
<!-- Logo and Text -->
|
||||
<div class="expert-login-logo">
|
||||
<t t-if="login_template and login_template.minimal_template_logo">
|
||||
<img t-att-src="'data:image/png;base64,%s' % login_template.minimal_template_logo.decode('utf-8')"
|
||||
alt="Company Logo"
|
||||
style="width: 40px; height: 40px; object-fit: contain; margin-right: 12px;"/>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div class="expert-logo-icon">
|
||||
<span>E</span>
|
||||
</div>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<!-- Welcome Text -->
|
||||
<div class="expert-login-welcome">
|
||||
<h1>
|
||||
<t t-if="login_template and login_template.minimal_signup_title">
|
||||
<t t-esc="login_template.minimal_signup_title"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Create an account
|
||||
</t>
|
||||
</h1>
|
||||
<p>
|
||||
<t t-if="login_template and login_template.minimal_signup_subtitle">
|
||||
<t t-esc="login_template.minimal_signup_subtitle"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<a t-attf-href="/web/login?{{ keep_query() }}" style="color: #000; text-decoration: underline;">log in instead</a>
|
||||
</t>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Signup Form -->
|
||||
<form class="oe_signup_form expert-login-form" role="form" method="post" t-if="not message">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="redirect" t-att-value="redirect or ''"/>
|
||||
<input type="hidden" name="token" t-att-value="token or ''"/>
|
||||
|
||||
<div class="form-group" t-if="not (token and not invalid_token)">
|
||||
<label>Your Name</label>
|
||||
<input type="text" name="name" class="form-control"
|
||||
placeholder="e.g. John Doe"
|
||||
t-att-value="name"
|
||||
required="required"
|
||||
t-att-autofocus="'autofocus' if login and not (token and not invalid_token) else None"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Your Email</label>
|
||||
<input type="text" name="login" class="form-control"
|
||||
placeholder="Enter your email"
|
||||
t-att-value="login"
|
||||
t-att-readonly="'readonly' if (token and not invalid_token) else None"
|
||||
autofocus="autofocus"
|
||||
autocapitalize="off"
|
||||
required="required"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Password</label>
|
||||
<input type="password" name="password" id="password" class="form-control"
|
||||
placeholder="Enter your password"
|
||||
required="required"
|
||||
t-att-autofocus="'autofocus' if (token and not invalid_token) else None"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Confirm Password</label>
|
||||
<input type="password" name="confirm_password" id="confirm_password" class="form-control"
|
||||
placeholder="Confirm your password"
|
||||
required="required"/>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<label>
|
||||
<input type="checkbox" name="terms" required="required"/>
|
||||
By creating an account, I agree to our <a href="#" style="color: #000; text-decoration: underline;">Terms of use</a> and <a href="#" style="color: #000; text-decoration: underline;">Privacy Policy</a>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<p class="alert alert-danger" t-if="error" role="alert">
|
||||
<t t-esc="error"/>
|
||||
</p>
|
||||
|
||||
<button type="submit" class="btn btn-primary expert-minimal-signup-btn">
|
||||
<t t-if="login_template and login_template.minimal_signup_button_text">
|
||||
<t t-esc="login_template.minimal_signup_button_text"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Create an account
|
||||
</t>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Button Styles -->
|
||||
<style>
|
||||
.expert-minimal-signup-btn {
|
||||
background-color: <t t-if="login_template and login_template.minimal_signup_button_bg_color"><t t-esc="login_template.minimal_signup_button_bg_color"/></t><t t-else="">#000000</t> !important;
|
||||
color: <t t-if="login_template and login_template.minimal_signup_button_text_color"><t t-esc="login_template.minimal_signup_button_text_color"/></t><t t-else="">#FFFFFF</t> !important;
|
||||
border: none !important;
|
||||
}
|
||||
.expert-minimal-signup-btn:hover {
|
||||
background-color: <t t-if="login_template and login_template.minimal_signup_button_bg_hover"><t t-esc="login_template.minimal_signup_button_bg_hover"/></t><t t-else="">#333333</t> !important;
|
||||
color: <t t-if="login_template and login_template.minimal_signup_button_text_hover"><t t-esc="login_template.minimal_signup_button_text_hover"/></t><t t-else="">#FFFFFF</t> !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Login Link -->
|
||||
<div class="expert-login-link">
|
||||
<p>
|
||||
Already have an account?
|
||||
<a t-attf-href="/web/login?{{ keep_query() }}" style="color: #000; text-decoration: underline;">Log In</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Image (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 d-none d-lg-block expert-login-right">
|
||||
<div class="expert-login-image">
|
||||
<img t-if="login_template and login_template.minimal_template_image"
|
||||
t-att-src="'data:image/png;base64,%s' % login_template.minimal_template_image.decode('utf-8')"
|
||||
alt="Signup Illustration" class="img-fluid expert-minimal-illustration"/>
|
||||
<img t-else=""
|
||||
src="/expert_theme/static/src/img/minimal-login-img.png"
|
||||
alt="Signup Illustration" class="img-fluid expert-minimal-illustration"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Minimal Reset Password Template -->
|
||||
<template id="reset_password_template_minimal_page" name="Minimal Reset Password Template">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="head">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<t t-call-assets="web.assets_frontend" t-js="false"/>
|
||||
<style>
|
||||
body.bg-100 {
|
||||
background-color: <t t-if="login_template and login_template.background_color"><t t-esc="login_template.background_color"/></t><t t-else="">#FFFFFF</t> !important;
|
||||
}
|
||||
</style>
|
||||
</t>
|
||||
<t t-set="body_classname" t-value="'bg-100'"/>
|
||||
<div class="oe_login_form expert-login-minimal">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row">
|
||||
<!-- Left Column: Reset Password Form (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 expert-login-left">
|
||||
<!-- Logo and Text -->
|
||||
<div class="expert-login-logo">
|
||||
<t t-if="login_template and login_template.minimal_template_logo">
|
||||
<img t-att-src="'data:image/png;base64,%s' % login_template.minimal_template_logo.decode('utf-8')"
|
||||
alt="Company Logo"
|
||||
style="width: 40px; height: 40px; object-fit: contain; margin-right: 12px;"/>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div class="expert-logo-icon">
|
||||
<span>E</span>
|
||||
</div>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<!-- Welcome Text -->
|
||||
<div class="expert-login-welcome">
|
||||
<h1>
|
||||
<t t-if="login_template and login_template.minimal_reset_title">
|
||||
<t t-esc="login_template.minimal_reset_title"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Reset Password <span class="welcome-emoji">🔐</span>
|
||||
</t>
|
||||
</h1>
|
||||
<p t-if="not token">
|
||||
<t t-if="login_template and login_template.minimal_reset_subtitle">
|
||||
<t t-esc="login_template.minimal_reset_subtitle"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Enter your email address and we'll send you instructions to reset your password
|
||||
</t>
|
||||
</p>
|
||||
<p t-if="token and not invalid_token">
|
||||
Enter your new password below
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Success Message -->
|
||||
<div t-if="message" class="expert-login-form">
|
||||
<p class="alert alert-success" role="status">
|
||||
<t t-esc="message"/>
|
||||
</p>
|
||||
<a href="/web/login" class="btn btn-link" style="color: #000; text-decoration: underline;">Back to Login</a>
|
||||
</div>
|
||||
|
||||
<!-- Reset Password Form -->
|
||||
<form class="oe_reset_password_form expert-login-form" role="form" method="post" t-if="not message">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="redirect" t-att-value="redirect or ''"/>
|
||||
<input type="hidden" name="token" t-att-value="token or ''"/>
|
||||
|
||||
<!-- Token-based reset (set new password) -->
|
||||
<t t-if="token and not invalid_token">
|
||||
<div class="form-group">
|
||||
<label>New Password</label>
|
||||
<input type="password" name="password" id="password" class="form-control"
|
||||
placeholder="Enter your new password"
|
||||
required="required"
|
||||
autofocus="autofocus"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Confirm Password</label>
|
||||
<input type="password" name="confirm_password" id="confirm_password" class="form-control"
|
||||
placeholder="Confirm your new password"
|
||||
required="required"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<!-- Email-based reset (request reset) -->
|
||||
<t t-if="not token">
|
||||
<div class="form-group">
|
||||
<label>Email</label>
|
||||
<input type="text" name="login" class="form-control"
|
||||
placeholder="Enter your email"
|
||||
t-att-value="login"
|
||||
autofocus="autofocus"
|
||||
required="required"
|
||||
autocapitalize="off"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<p class="alert alert-danger" t-if="error" role="alert">
|
||||
<t t-esc="error"/>
|
||||
</p>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<t t-if="token and not invalid_token">Reset Password</t>
|
||||
<t t-if="not token">Send Reset Instructions</t>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Reset Password Link -->
|
||||
<div class="expert-login-link" t-if="not message">
|
||||
<p>
|
||||
<a t-if="not token" t-attf-href="/web/login?{{ keep_query() }}" style="color: #000; text-decoration: underline;">Back to Login</a>
|
||||
<a t-if="invalid_token" href="/web/login" style="color: #000; text-decoration: underline;">Back to Login</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Image (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 d-none d-lg-block expert-login-right">
|
||||
<div class="expert-login-image">
|
||||
<img t-if="login_template and login_template.minimal_template_image"
|
||||
t-att-src="'data:image/png;base64,%s' % login_template.minimal_template_image.decode('utf-8')"
|
||||
alt="Reset Password Illustration" class="img-fluid expert-minimal-illustration"/>
|
||||
<img t-else=""
|
||||
src="/expert_theme/static/src/img/minimal-login-img.png"
|
||||
alt="Reset Password Illustration" class="img-fluid expert-minimal-illustration"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Template 3: Corporate Design -->
|
||||
<template id="login_template_corporate_page" name="Corporate Login Template">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="head">
|
||||
<style>
|
||||
body.bg-100 {
|
||||
background-color: <t t-if="login_template and login_template.background_color"><t t-esc="login_template.background_color"/></t><t t-else="">#1a1a1a</t> !important;
|
||||
}
|
||||
</style>
|
||||
</t>
|
||||
<div class="oe_login_form" t-attf-style="background: {{login_template.background_color if login_template and login_template.background_color else '#1a1a1a'}}; min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px;">
|
||||
<div style="width: 100%; max-width: 500px; background: #2d2d2d; padding: 60px 50px; border-radius: 8px; border: 1px solid #404040;">
|
||||
<div class="text-center mb-5">
|
||||
<h2 style="color: #fff; font-size: 32px; font-weight: 600; margin-bottom: 10px;">Company Portal</h2>
|
||||
<p style="color: #999; font-size: 14px; margin: 0;">Secure Login Access</p>
|
||||
</div>
|
||||
<form class="oe_login_form" role="form" method="post" t-att-action="request.httprequest.path">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="redirect" t-att-value="redirect or ''"/>
|
||||
|
||||
<div style="margin-bottom: 25px;">
|
||||
<label style="color: #ccc; font-size: 13px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px; display: block;">Username</label>
|
||||
<input type="text" name="login" class="form-control"
|
||||
placeholder="Enter your username"
|
||||
t-att-value="login"
|
||||
style="background: #1a1a1a; border: 1px solid #404040; color: #fff; padding: 12px 15px; border-radius: 4px; width: 100%; box-sizing: border-box;"/>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 35px;">
|
||||
<label style="color: #ccc; font-size: 13px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px; display: block;">Password</label>
|
||||
<input type="password" name="password" class="form-control"
|
||||
placeholder="Enter your password"
|
||||
style="background: #1a1a1a; border: 1px solid #404040; color: #fff; padding: 12px 15px; border-radius: 4px; width: 100%; box-sizing: border-box;"/>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-block"
|
||||
style="width: 100%; background: #007bff; color: white; border: none; padding: 14px; font-size: 15px; font-weight: 600; border-radius: 4px; text-transform: uppercase; letter-spacing: 0.5px;">
|
||||
Access Portal
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,785 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!--
|
||||
Multiple Login Page Templates with Different HTML Structures
|
||||
Standalone templates rendered by the /web/login override.
|
||||
Each template calls web.layout and defines its own login form.
|
||||
-->
|
||||
|
||||
<!-- Template 1: Modern Card Design -->
|
||||
<template id="login_template_modern_page" name="Modern Login Template">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="head">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<t t-call-assets="web.assets_frontend" t-js="false"/>
|
||||
</t>
|
||||
<t t-set="body_classname" t-value="'bg-100'"/>
|
||||
<div class="oe_login_form expert-login-modern">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row">
|
||||
<!-- Left Column: Login Form (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 expert-login-left">
|
||||
<!-- Logo and Text -->
|
||||
<div class="expert-login-logo">
|
||||
<t t-if="login_template and login_template.modern_template_logo">
|
||||
<img t-att-src="'data:image/png;base64,%s' % login_template.modern_template_logo.decode('utf-8')"
|
||||
alt="Company Logo"
|
||||
style="width: 40px; height: 40px; object-fit: contain; margin-right: 12px;"/>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div class="expert-logo-icon">
|
||||
<span>E</span>
|
||||
</div>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<!-- Welcome Text -->
|
||||
<div class="expert-login-welcome">
|
||||
<h1>
|
||||
<t t-if="login_template and login_template.modern_login_title">
|
||||
<t t-esc="login_template.modern_login_title"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Welcome to Expert <span class="welcome-emoji">👋</span>
|
||||
</t>
|
||||
</h1>
|
||||
<p>
|
||||
<t t-if="login_template and login_template.modern_login_subtitle">
|
||||
<t t-esc="login_template.modern_login_subtitle"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Kindly fill in your details below to sign in to your account
|
||||
</t>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Login Form -->
|
||||
<form class="oe_login_form expert-login-form" role="form" method="post" t-att-action="request.httprequest.path">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="redirect" t-att-value="redirect or ''"/>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Email or Username</label>
|
||||
<input type="text" name="login" class="form-control"
|
||||
placeholder="Enter your email or username"
|
||||
t-att-value="login"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Password</label>
|
||||
<input type="password" name="password" class="form-control"
|
||||
placeholder="Enter your password"/>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<label>
|
||||
<input type="checkbox" name="remember"/>
|
||||
Remember me
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<t t-if="login_template and login_template.modern_login_button_text"><t t-esc="login_template.modern_login_button_text"/></t><t t-else="">Sign In</t>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Signup Link -->
|
||||
<div class="expert-login-link">
|
||||
<p>
|
||||
Don't have an account?
|
||||
<a t-attf-href="/web/signup?{{ keep_query() }}">Sign Up</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Image (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 d-none d-lg-block expert-login-right">
|
||||
<div class="expert-login-image">
|
||||
<img t-if="login_template and login_template.modern_template_image"
|
||||
t-att-src="'data:image/png;base64,%s' % login_template.modern_template_image.decode('utf-8')"
|
||||
alt="Login Image" class="img-fluid"/>
|
||||
<img t-else=""
|
||||
src="/expert_theme/static/src/img/modern-template-bg.png"
|
||||
alt="Login Image" class="img-fluid"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Modern Signup Template -->
|
||||
<template id="signup_template_modern_page" name="Modern Signup Template">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="head">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<t t-call-assets="web.assets_frontend" t-js="false"/>
|
||||
</t>
|
||||
<t t-set="body_classname" t-value="'bg-100'"/>
|
||||
<div class="oe_login_form expert-login-modern">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row">
|
||||
<!-- Left Column: Signup Form (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 expert-login-left">
|
||||
<!-- Logo and Text -->
|
||||
<div class="expert-login-logo">
|
||||
<t t-if="login_template and login_template.modern_template_logo">
|
||||
<img t-att-src="'data:image/png;base64,%s' % login_template.modern_template_logo.decode('utf-8')"
|
||||
alt="Company Logo"
|
||||
style="width: 40px; height: 40px; object-fit: contain; margin-right: 12px;"/>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div class="expert-logo-icon">
|
||||
<span>E</span>
|
||||
</div>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<!-- Welcome Text -->
|
||||
<div class="expert-login-welcome">
|
||||
<h1>
|
||||
<t t-if="login_template and login_template.modern_signup_title">
|
||||
<t t-esc="login_template.modern_signup_title"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Create an account
|
||||
</t>
|
||||
</h1>
|
||||
<p>
|
||||
<t t-if="login_template and login_template.modern_signup_subtitle">
|
||||
<t t-esc="login_template.modern_signup_subtitle"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Join us today and get started
|
||||
</t>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Signup Form -->
|
||||
<form class="oe_signup_form expert-login-form" role="form" method="post" t-if="not message">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="redirect" t-att-value="redirect or ''"/>
|
||||
<input type="hidden" name="token" t-att-value="token or ''"/>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Your Email</label>
|
||||
<input type="text" name="login" class="form-control"
|
||||
placeholder="Enter your email"
|
||||
t-att-value="login"
|
||||
t-att-readonly="'readonly' if (token and not invalid_token) else None"
|
||||
autofocus="autofocus"
|
||||
autocapitalize="off"
|
||||
required="required"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group" t-if="not (token and not invalid_token)">
|
||||
<label>Your Name</label>
|
||||
<input type="text" name="name" class="form-control"
|
||||
placeholder="e.g. John Doe"
|
||||
t-att-value="name"
|
||||
required="required"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Password</label>
|
||||
<input type="password" name="password" id="password" class="form-control"
|
||||
placeholder="Enter your password"
|
||||
required="required"
|
||||
t-att-autofocus="'autofocus' if (token and not invalid_token) else None"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Confirm Password</label>
|
||||
<input type="password" name="confirm_password" id="confirm_password" class="form-control"
|
||||
placeholder="Confirm your password"
|
||||
required="required"/>
|
||||
</div>
|
||||
|
||||
<p class="alert alert-danger" t-if="error" role="alert">
|
||||
<t t-esc="error"/>
|
||||
</p>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<t t-if="login_template and login_template.modern_signup_button_text">
|
||||
<t t-esc="login_template.modern_signup_button_text"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Sign Up
|
||||
</t>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Signup Link -->
|
||||
<div class="expert-login-link">
|
||||
<p>
|
||||
Already have an account?
|
||||
<a t-attf-href="/web/login?{{ keep_query() }}">Log In</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Image (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 d-none d-lg-block expert-login-right">
|
||||
<div class="expert-login-image">
|
||||
<img t-if="login_template and login_template.modern_template_image"
|
||||
t-att-src="'data:image/png;base64,%s' % login_template.modern_template_image.decode('utf-8')"
|
||||
alt="Signup Image" class="img-fluid"/>
|
||||
<img t-else=""
|
||||
src="/expert_theme/static/src/img/modern-template-bg.png"
|
||||
alt="Signup Image" class="img-fluid"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Modern Reset Password Template -->
|
||||
<template id="reset_password_template_modern_page" name="Modern Reset Password Template">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="head">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<t t-call-assets="web.assets_frontend" t-js="false"/>
|
||||
</t>
|
||||
<t t-set="body_classname" t-value="'bg-100'"/>
|
||||
<div class="oe_login_form expert-login-modern">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row">
|
||||
<!-- Left Column: Reset Password Form (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 expert-login-left">
|
||||
<!-- Logo and Text -->
|
||||
<div class="expert-login-logo">
|
||||
<t t-if="login_template and login_template.modern_template_logo">
|
||||
<img t-att-src="'data:image/png;base64,%s' % login_template.modern_template_logo.decode('utf-8')"
|
||||
alt="Company Logo"
|
||||
style="width: 40px; height: 40px; object-fit: contain; margin-right: 12px;"/>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div class="expert-logo-icon">
|
||||
<span>E</span>
|
||||
</div>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<!-- Welcome Text -->
|
||||
<div class="expert-login-welcome">
|
||||
<h1>
|
||||
<t t-if="login_template and login_template.modern_reset_title">
|
||||
<t t-esc="login_template.modern_reset_title"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Reset Password <span class="welcome-emoji">🔐</span>
|
||||
</t>
|
||||
</h1>
|
||||
<p t-if="not token">
|
||||
<t t-if="login_template and login_template.modern_reset_subtitle">
|
||||
<t t-esc="login_template.modern_reset_subtitle"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Enter your email address and we'll send you instructions to reset your password
|
||||
</t>
|
||||
</p>
|
||||
<p t-if="token and not invalid_token">
|
||||
Enter your new password below
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Success Message -->
|
||||
<div t-if="message" class="expert-login-form">
|
||||
<p class="alert alert-success" role="status">
|
||||
<t t-esc="message"/>
|
||||
</p>
|
||||
<a href="/web/login" class="btn btn-link" style="color: #667eea;">Back to Login</a>
|
||||
</div>
|
||||
|
||||
<!-- Reset Password Form -->
|
||||
<form class="oe_reset_password_form expert-login-form" role="form" method="post" t-if="not message">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="redirect" t-att-value="redirect or ''"/>
|
||||
<input type="hidden" name="token" t-att-value="token or ''"/>
|
||||
|
||||
<!-- Token-based reset (set new password) -->
|
||||
<t t-if="token and not invalid_token">
|
||||
<div class="form-group">
|
||||
<label>New Password</label>
|
||||
<input type="password" name="password" id="password" class="form-control"
|
||||
placeholder="Enter your new password"
|
||||
required="required"
|
||||
autofocus="autofocus"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Confirm Password</label>
|
||||
<input type="password" name="confirm_password" id="confirm_password" class="form-control"
|
||||
placeholder="Confirm your new password"
|
||||
required="required"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<!-- Email-based reset (request reset) -->
|
||||
<t t-if="not token">
|
||||
<div class="form-group">
|
||||
<label>Your Email</label>
|
||||
<input type="text" name="login" class="form-control"
|
||||
placeholder="Enter your email"
|
||||
t-att-value="login"
|
||||
autofocus="autofocus"
|
||||
required="required"
|
||||
autocapitalize="off"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<p class="alert alert-danger" t-if="error" role="alert">
|
||||
<t t-esc="error"/>
|
||||
</p>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<t t-if="token and not invalid_token">Reset Password</t>
|
||||
<t t-if="not token">Send Reset Instructions</t>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Reset Password Link -->
|
||||
<div class="expert-login-link" t-if="not message">
|
||||
<p>
|
||||
<a t-if="not token" t-attf-href="/web/login?{{ keep_query() }}" style="color: #667eea;">Back to Login</a>
|
||||
<a t-if="invalid_token" href="/web/login" style="color: #667eea;">Back to Login</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Image (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 d-none d-lg-block expert-login-right">
|
||||
<div class="expert-login-image">
|
||||
<img t-if="login_template and login_template.modern_template_image"
|
||||
t-att-src="'data:image/png;base64,%s' % login_template.modern_template_image.decode('utf-8')"
|
||||
alt="Reset Password Image" class="img-fluid"/>
|
||||
<img t-else=""
|
||||
src="/expert_theme/static/src/img/modern-template-bg.png"
|
||||
alt="Reset Password Image" class="img-fluid"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Template 2: Minimal Design -->
|
||||
<template id="login_template_minimal_page" name="Minimal Login Template">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="head">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<t t-call-assets="web.assets_frontend" t-js="false"/>
|
||||
</t>
|
||||
<t t-set="body_classname" t-value="'bg-100'"/>
|
||||
<div class="oe_login_form expert-login-minimal">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row">
|
||||
<!-- Left Column: Login Form (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 expert-login-left">
|
||||
<!-- Logo and Text -->
|
||||
<div class="expert-login-logo">
|
||||
<t t-if="login_template and login_template.minimal_template_logo">
|
||||
<img t-att-src="'data:image/png;base64,%s' % login_template.minimal_template_logo.decode('utf-8')"
|
||||
alt="Company Logo"
|
||||
style="width: 40px; height: 40px; object-fit: contain; margin-right: 12px;"/>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div class="expert-logo-icon">
|
||||
<span>E</span>
|
||||
</div>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<!-- Welcome Text -->
|
||||
<div class="expert-login-welcome">
|
||||
<h1>
|
||||
<t t-if="login_template and login_template.minimal_login_title">
|
||||
<t t-esc="login_template.minimal_login_title"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Welcome to Expert <span class="welcome-emoji">👋</span>
|
||||
</t>
|
||||
</h1>
|
||||
<p>
|
||||
<t t-if="login_template and login_template.minimal_login_subtitle">
|
||||
<t t-esc="login_template.minimal_login_subtitle"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Kindly fill in your details below to sign in to your account
|
||||
</t>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Login Form -->
|
||||
<form class="oe_login_form expert-login-form" role="form" method="post" t-att-action="request.httprequest.path">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="redirect" t-att-value="redirect or ''"/>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Email or Username</label>
|
||||
<input type="text" name="login" class="form-control"
|
||||
placeholder="Enter your email or username"
|
||||
t-att-value="login"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Password</label>
|
||||
<input type="password" name="password" class="form-control"
|
||||
placeholder="Enter your password"/>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<label>
|
||||
<input type="checkbox" name="remember"/>
|
||||
Remember me
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<t t-if="login_template and login_template.modern_login_button_text"><t t-esc="login_template.modern_login_button_text"/></t><t t-else="">Sign In</t>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Signup Link -->
|
||||
<div class="expert-login-link">
|
||||
<p>
|
||||
Don't have an account?
|
||||
<a t-attf-href="/web/signup?{{ keep_query() }}">Sign Up</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Image (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 d-none d-lg-block expert-login-right">
|
||||
<div class="expert-login-image">
|
||||
<img t-if="login_template and login_template.minimal_template_image"
|
||||
t-att-src="'data:image/png;base64,%s' % login_template.minimal_template_image.decode('utf-8')"
|
||||
alt="Login Illustration" class="img-fluid expert-minimal-illustration"/>
|
||||
<img t-else=""
|
||||
src="/expert_theme/static/src/img/minimal-login-img.png"
|
||||
alt="Login Illustration" class="img-fluid expert-minimal-illustration"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Minimal Signup Template -->
|
||||
<template id="signup_template_minimal_page" name="Minimal Signup Template">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="head">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<t t-call-assets="web.assets_frontend" t-js="false"/>
|
||||
</t>
|
||||
<t t-set="body_classname" t-value="'bg-100'"/>
|
||||
<div class="oe_login_form expert-login-minimal">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row">
|
||||
<!-- Left Column: Signup Form (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 expert-login-left">
|
||||
<!-- Logo and Text -->
|
||||
<div class="expert-login-logo">
|
||||
<t t-if="login_template and login_template.minimal_template_logo">
|
||||
<img t-att-src="'data:image/png;base64,%s' % login_template.minimal_template_logo.decode('utf-8')"
|
||||
alt="Company Logo"
|
||||
style="width: 40px; height: 40px; object-fit: contain; margin-right: 12px;"/>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div class="expert-logo-icon">
|
||||
<span>E</span>
|
||||
</div>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<!-- Welcome Text -->
|
||||
<div class="expert-login-welcome">
|
||||
<h1>
|
||||
<t t-if="login_template and login_template.minimal_signup_title">
|
||||
<t t-esc="login_template.minimal_signup_title"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Create an account
|
||||
</t>
|
||||
</h1>
|
||||
<p>
|
||||
<t t-if="login_template and login_template.minimal_signup_subtitle">
|
||||
<t t-esc="login_template.minimal_signup_subtitle"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<a t-attf-href="/web/login?{{ keep_query() }}" style="color: #000; text-decoration: underline;">log in instead</a>
|
||||
</t>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Signup Form -->
|
||||
<form class="oe_signup_form expert-login-form" role="form" method="post" t-if="not message">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="redirect" t-att-value="redirect or ''"/>
|
||||
<input type="hidden" name="token" t-att-value="token or ''"/>
|
||||
|
||||
<div class="form-group" t-if="not (token and not invalid_token)">
|
||||
<label>Your Name</label>
|
||||
<input type="text" name="name" class="form-control"
|
||||
placeholder="e.g. John Doe"
|
||||
t-att-value="name"
|
||||
required="required"
|
||||
t-att-autofocus="'autofocus' if login and not (token and not invalid_token) else None"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Your Email</label>
|
||||
<input type="text" name="login" class="form-control"
|
||||
placeholder="Enter your email"
|
||||
t-att-value="login"
|
||||
t-att-readonly="'readonly' if (token and not invalid_token) else None"
|
||||
autofocus="autofocus"
|
||||
autocapitalize="off"
|
||||
required="required"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Password</label>
|
||||
<input type="password" name="password" id="password" class="form-control"
|
||||
placeholder="Enter your password"
|
||||
required="required"
|
||||
t-att-autofocus="'autofocus' if (token and not invalid_token) else None"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Confirm Password</label>
|
||||
<input type="password" name="confirm_password" id="confirm_password" class="form-control"
|
||||
placeholder="Confirm your password"
|
||||
required="required"/>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<label>
|
||||
<input type="checkbox" name="terms" required="required"/>
|
||||
By creating an account, I agree to our <a href="#" style="color: #000; text-decoration: underline;">Terms of use</a> and <a href="#" style="color: #000; text-decoration: underline;">Privacy Policy</a>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<p class="alert alert-danger" t-if="error" role="alert">
|
||||
<t t-esc="error"/>
|
||||
</p>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Create an account
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Login Link -->
|
||||
<div class="expert-login-link">
|
||||
<p>
|
||||
Already have an account?
|
||||
<a t-attf-href="/web/login?{{ keep_query() }}" style="color: #000; text-decoration: underline;">Log In</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Image (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 d-none d-lg-block expert-login-right">
|
||||
<div class="expert-login-image">
|
||||
<img t-if="login_template and login_template.minimal_template_image"
|
||||
t-att-src="'data:image/png;base64,%s' % login_template.minimal_template_image.decode('utf-8')"
|
||||
alt="Signup Illustration" class="img-fluid expert-minimal-illustration"/>
|
||||
<img t-else=""
|
||||
src="/expert_theme/static/src/img/minimal-login-img.png"
|
||||
alt="Signup Illustration" class="img-fluid expert-minimal-illustration"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Minimal Reset Password Template -->
|
||||
<template id="reset_password_template_minimal_page" name="Minimal Reset Password Template">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="head">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<t t-call-assets="web.assets_frontend" t-js="false"/>
|
||||
</t>
|
||||
<t t-set="body_classname" t-value="'bg-100'"/>
|
||||
<div class="oe_login_form expert-login-minimal">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row">
|
||||
<!-- Left Column: Reset Password Form (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 expert-login-left">
|
||||
<!-- Logo and Text -->
|
||||
<div class="expert-login-logo">
|
||||
<t t-if="login_template and login_template.minimal_template_logo">
|
||||
<img t-att-src="'data:image/png;base64,%s' % login_template.minimal_template_logo.decode('utf-8')"
|
||||
alt="Company Logo"
|
||||
style="width: 40px; height: 40px; object-fit: contain; margin-right: 12px;"/>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div class="expert-logo-icon">
|
||||
<span>E</span>
|
||||
</div>
|
||||
<span class="expert-logo-text">Expert</span>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<!-- Welcome Text -->
|
||||
<div class="expert-login-welcome">
|
||||
<h1>
|
||||
<t t-if="login_template and login_template.minimal_reset_title">
|
||||
<t t-esc="login_template.minimal_reset_title"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Reset Password <span class="welcome-emoji">🔐</span>
|
||||
</t>
|
||||
</h1>
|
||||
<p t-if="not token">
|
||||
<t t-if="login_template and login_template.minimal_reset_subtitle">
|
||||
<t t-esc="login_template.minimal_reset_subtitle"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Enter your email address and we'll send you instructions to reset your password
|
||||
</t>
|
||||
</p>
|
||||
<p t-if="token and not invalid_token">
|
||||
Enter your new password below
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Success Message -->
|
||||
<div t-if="message" class="expert-login-form">
|
||||
<p class="alert alert-success" role="status">
|
||||
<t t-esc="message"/>
|
||||
</p>
|
||||
<a href="/web/login" class="btn btn-link" style="color: #000; text-decoration: underline;">Back to Login</a>
|
||||
</div>
|
||||
|
||||
<!-- Reset Password Form -->
|
||||
<form class="oe_reset_password_form expert-login-form" role="form" method="post" t-if="not message">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="redirect" t-att-value="redirect or ''"/>
|
||||
<input type="hidden" name="token" t-att-value="token or ''"/>
|
||||
|
||||
<!-- Token-based reset (set new password) -->
|
||||
<t t-if="token and not invalid_token">
|
||||
<div class="form-group">
|
||||
<label>New Password</label>
|
||||
<input type="password" name="password" id="password" class="form-control"
|
||||
placeholder="Enter your new password"
|
||||
required="required"
|
||||
autofocus="autofocus"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Confirm Password</label>
|
||||
<input type="password" name="confirm_password" id="confirm_password" class="form-control"
|
||||
placeholder="Confirm your new password"
|
||||
required="required"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<!-- Email-based reset (request reset) -->
|
||||
<t t-if="not token">
|
||||
<div class="form-group">
|
||||
<label>Email</label>
|
||||
<input type="text" name="login" class="form-control"
|
||||
placeholder="Enter your email"
|
||||
t-att-value="login"
|
||||
autofocus="autofocus"
|
||||
required="required"
|
||||
autocapitalize="off"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<p class="alert alert-danger" t-if="error" role="alert">
|
||||
<t t-esc="error"/>
|
||||
</p>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<t t-if="token and not invalid_token">Reset Password</t>
|
||||
<t t-if="not token">Send Reset Instructions</t>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Reset Password Link -->
|
||||
<div class="expert-login-link" t-if="not message">
|
||||
<p>
|
||||
<a t-if="not token" t-attf-href="/web/login?{{ keep_query() }}" style="color: #000; text-decoration: underline;">Back to Login</a>
|
||||
<a t-if="invalid_token" href="/web/login" style="color: #000; text-decoration: underline;">Back to Login</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Image (6 columns) -->
|
||||
<div class="col-lg-6 col-md-12 d-none d-lg-block expert-login-right">
|
||||
<div class="expert-login-image">
|
||||
<img t-if="login_template and login_template.minimal_template_image"
|
||||
t-att-src="'data:image/png;base64,%s' % login_template.minimal_template_image.decode('utf-8')"
|
||||
alt="Reset Password Illustration" class="img-fluid expert-minimal-illustration"/>
|
||||
<img t-else=""
|
||||
src="/expert_theme/static/src/img/minimal-login-img.png"
|
||||
alt="Reset Password Illustration" class="img-fluid expert-minimal-illustration"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Template 3: Corporate Design -->
|
||||
<template id="login_template_corporate_page" name="Corporate Login Template">
|
||||
<t t-call="web.layout">
|
||||
<div class="oe_login_form" style="background: #1a1a1a; min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px;">
|
||||
<div style="width: 100%; max-width: 500px; background: #2d2d2d; padding: 60px 50px; border-radius: 8px; border: 1px solid #404040;">
|
||||
<div class="text-center mb-5">
|
||||
<h2 style="color: #fff; font-size: 32px; font-weight: 600; margin-bottom: 10px;">Company Portal</h2>
|
||||
<p style="color: #999; font-size: 14px; margin: 0;">Secure Login Access</p>
|
||||
</div>
|
||||
<form class="oe_login_form" role="form" method="post" t-att-action="request.httprequest.path">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="redirect" t-att-value="redirect or ''"/>
|
||||
|
||||
<div style="margin-bottom: 25px;">
|
||||
<label style="color: #ccc; font-size: 13px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px; display: block;">Username</label>
|
||||
<input type="text" name="login" class="form-control"
|
||||
placeholder="Enter your username"
|
||||
t-att-value="login"
|
||||
style="background: #1a1a1a; border: 1px solid #404040; color: #fff; padding: 12px 15px; border-radius: 4px; width: 100%; box-sizing: border-box;"/>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 35px;">
|
||||
<label style="color: #ccc; font-size: 13px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px; display: block;">Password</label>
|
||||
<input type="password" name="password" class="form-control"
|
||||
placeholder="Enter your password"
|
||||
style="background: #1a1a1a; border: 1px solid #404040; color: #fff; padding: 12px 15px; border-radius: 4px; width: 100%; box-sizing: border-box;"/>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-block"
|
||||
style="width: 100%; background: #007bff; color: white; border: none; padding: 14px; font-size: 15px; font-weight: 600; border-radius: 4px; text-transform: uppercase; letter-spacing: 0.5px;">
|
||||
Access Portal
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
|
||||
Loading…
Reference in New Issue