commit
ba35f1899c
|
|
@ -1,8 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': 'Expert Theme',
|
||||
'version': '18.0.1.0.0',
|
||||
'category': 'Theme/Backend',
|
||||
'version': '1.0.0',
|
||||
'category': 'Themes',
|
||||
'summary': 'Custom backend theme with installed modules home page',
|
||||
'description': """
|
||||
Expert Theme
|
||||
|
|
@ -14,8 +14,8 @@
|
|||
- Easy navigation to installed applications
|
||||
""",
|
||||
'author': 'Expert',
|
||||
'website': '',
|
||||
'depends': ['base', 'web'],
|
||||
'website': 'https://www.exp-sa.com',
|
||||
'depends': ['base', 'web','website'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'data/expert_login_template_data.xml',
|
||||
|
|
@ -25,23 +25,23 @@
|
|||
'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',
|
||||
],
|
||||
},
|
||||
'assets': {
|
||||
'web.assets_frontend': [
|
||||
'expert_theme/static/src/scss/login_modern.scss',
|
||||
'expert_theme/static/src/scss/expert_login.scss',
|
||||
'expert_theme/static/src/scss/login_minimal.scss',
|
||||
'expert_theme/static/src/js/expert_login_template.js',
|
||||
],
|
||||
'web.assets_backend': [
|
||||
'expert_theme/static/src/scss/expert_theme_config.scss',
|
||||
'expert_theme/static/src/scss/expert_theme.scss',
|
||||
'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',
|
||||
]
|
||||
},
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'application': False,
|
||||
|
|
|
|||
|
|
@ -55,46 +55,6 @@ class ExpertController(http.Controller):
|
|||
'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)"""
|
||||
|
|
@ -181,106 +141,4 @@ class ExpertController(http.Controller):
|
|||
'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)
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ class ExpertLoginTemplate(models.Model):
|
|||
_rec_name = 'name'
|
||||
_order = 'sequence, name'
|
||||
|
||||
name = fields.Char(string='Template Name', required=True, help='Name of the login page template')
|
||||
name = fields.Char(string='Template Name', required=True, translate=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')
|
||||
|
||||
|
|
@ -37,47 +37,47 @@ class ExpertLoginTemplate(models.Model):
|
|||
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_title = fields.Char(string='Login Title', default='Welcome to Expert 👋', translate=True, 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', translate=True, help='Subtitle text for Modern template login page')
|
||||
modern_login_button_text = fields.Char(string='Login Button Text', default='Sign In', translate=True, 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_title = fields.Char(string='Signup Title', default='Create an account', translate=True, help='Title text for Modern template signup page')
|
||||
modern_signup_subtitle = fields.Char(string='Signup Subtitle', default='Join us today and get started', translate=True, help='Subtitle text for Modern template signup page')
|
||||
modern_signup_button_text = fields.Char(string='Signup Button Text', default='Create an account', translate=True, 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')
|
||||
modern_reset_title = fields.Char(string='Reset Password Title', default='Reset your password', translate=True, 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', translate=True, 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_title = fields.Char(string='Login Title', default='Welcome to Expert 👋', translate=True, 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', translate=True, help='Subtitle text for Minimal template login page')
|
||||
minimal_login_button_text = fields.Char(string='Login Button Text', default='Sign In', translate=True, 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_title = fields.Char(string='Signup Title', default='Create an account', translate=True, help='Title text for Minimal template signup page')
|
||||
minimal_signup_subtitle = fields.Char(string='Signup Subtitle', default='Join us today and get started', translate=True, help='Subtitle text for Minimal template signup page')
|
||||
minimal_signup_button_text = fields.Char(string='Signup Button Text', default='Create an account', translate=True, 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')
|
||||
minimal_reset_title = fields.Char(string='Reset Password Title', default='Reset your password', translate=True, 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', translate=True, help='Subtitle text for Minimal template reset password page')
|
||||
|
||||
# Description
|
||||
description = fields.Text(string='Description', help='Description of this template')
|
||||
description = fields.Text(string='Description', translate=True, help='Description of this template')
|
||||
|
||||
@api.model
|
||||
def get_active_template(self):
|
||||
|
|
@ -111,13 +111,14 @@ class ExpertLoginTemplate(models.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
|
||||
if 'active' not in vals[0]:
|
||||
vals[0]['active'] = False
|
||||
# Only deactivate others if this one is being set to active
|
||||
if vals.get('active'):
|
||||
print(vals[0])
|
||||
if vals[0].get('active'):
|
||||
# Deactivate all other templates (excluding the one being created)
|
||||
self.search([('active', '=', True)]).write({'active': False})
|
||||
return super(ExpertLoginTemplate, self).create(vals)
|
||||
return super(ExpertLoginTemplate, self).create(vals[0])
|
||||
|
||||
def write(self, vals):
|
||||
"""Ensure only one template is active at a time"""
|
||||
|
|
@ -133,6 +134,7 @@ class ExpertLoginTemplate(models.Model):
|
|||
super(ExpertLoginTemplate, other_templates).write({'active': False})
|
||||
need_reload = True
|
||||
|
||||
view = self.env.ref('custom_auth_theme.view_custom_login_inherit', raise_if_not_found=False)
|
||||
result = super(ExpertLoginTemplate, self).write(vals)
|
||||
|
||||
# Update template views if active state changed
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
|
|
@ -1,269 +0,0 @@
|
|||
/* 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,234 +0,0 @@
|
|||
/* 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -47,9 +47,9 @@
|
|||
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;
|
||||
// min-height: 100vh !important;
|
||||
// margin: 0 !important;
|
||||
// padding: 0 !important;
|
||||
}
|
||||
|
||||
/* Ensure the gradient shows on the main content area */
|
||||
|
|
@ -58,7 +58,7 @@ body {
|
|||
.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;
|
||||
// min-height: 100vh !important;
|
||||
}
|
||||
|
||||
/* Override any Odoo background colors that might interfere */
|
||||
|
|
@ -0,0 +1,280 @@
|
|||
/* Expert Theme - Minimal Login Template Styles */
|
||||
.expert-login-minimal {
|
||||
background: #FFFFFF;
|
||||
min-height: 100vh;
|
||||
|
||||
.expert-login-left {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
min-height: 100vh;
|
||||
|
||||
.expert-login-form-container {
|
||||
max-width: 450px;
|
||||
|
||||
|
||||
.expert-login-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 40px;
|
||||
width: 100%;
|
||||
|
||||
.expert-logo-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: #000000;
|
||||
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: #000000;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.expert-login-welcome {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
|
||||
h1 {
|
||||
color: #000000;
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
|
||||
.welcome-emoji {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
font-size: 16px;
|
||||
margin-bottom: 0;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.expert-login-form {
|
||||
max-width: 450px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
background-color: transparent !important;
|
||||
|
||||
.oe_login_form,
|
||||
.oe_signup_form,
|
||||
.oe_reset_password_form {
|
||||
max-width: 450px;
|
||||
background-color: transparent !important;
|
||||
|
||||
label {
|
||||
font-weight: 500;
|
||||
color: #000000;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
input {
|
||||
|
||||
&[type="text"],
|
||||
&[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;
|
||||
}
|
||||
|
||||
&[type="text"]::placeholder,
|
||||
&[type="password"]::placeholder {
|
||||
color: rgba(0, 0, 0, 0.5) !important;
|
||||
}
|
||||
|
||||
&[type="text"]:focus,
|
||||
&[type="password"]:focus {
|
||||
background: #FFFFFF !important;
|
||||
border-color: #000000 !important;
|
||||
outline: none;
|
||||
color: #000000 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-check {
|
||||
margin-bottom: 32px;
|
||||
|
||||
label {
|
||||
color: #000000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-right: 8px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
accent-color: #000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.btn-link {
|
||||
color: #5a5a5a;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
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;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&:hover {
|
||||
background: #D5D5D5;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #CCCCCC;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.expert-login-link {
|
||||
margin-top: 24px;
|
||||
max-width: 450px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
|
||||
p {
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
|
||||
a {
|
||||
color: #000000;
|
||||
text-decoration: underline;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.expert-login-right {
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
min-height: 100vh;
|
||||
|
||||
.expert-login-image {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background: #FFFFFF;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.expert-minimal-illustration {
|
||||
width: 600px !important;
|
||||
height: 600px !important;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.alert {
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 20px;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
background-color: #F8D7DA;
|
||||
border: 1px solid #F5C6CB;
|
||||
color: #721C24;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: #D4EDDA;
|
||||
border: 1px solid #C3E6CB;
|
||||
color: #155724;
|
||||
}
|
||||
}
|
||||
|
||||
/* Left Column: Login Form */
|
||||
|
||||
/* Logo Section */
|
||||
|
||||
/* Welcome Section */
|
||||
|
||||
/* Form Section */
|
||||
|
||||
/* Login Link Section */
|
||||
|
||||
/* Right Column: Image Section */
|
||||
|
||||
/* Alert Messages */
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 991.98px) {
|
||||
.expert-login-minimal {
|
||||
.expert-login-left {
|
||||
padding: 30px 20px;
|
||||
}
|
||||
|
||||
.expert-login-welcome {
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
|
||||
.welcome-emoji {
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,177 +1,180 @@
|
|||
/* 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;
|
||||
min-height: 100vh;
|
||||
|
||||
.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-form-container {
|
||||
max-width: 450px;
|
||||
|
||||
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);
|
||||
// Logo Section
|
||||
.expert-login-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
margin-bottom: 40px;
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-right: 8px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
accent-color: #667eea;
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
// Welcome Section
|
||||
.expert-login-welcome {
|
||||
h1 {
|
||||
color: white;
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.2;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
|
||||
.welcome-emoji {
|
||||
font-size: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
p {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 16px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Login Link Section
|
||||
.expert-login-link {
|
||||
margin-top: 24px;
|
||||
max-width: 450px;
|
||||
// Form Section
|
||||
.expert-login-form {
|
||||
|
||||
p {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
.oe_login_form,
|
||||
.oe_signup_form,
|
||||
.oe_reset_password_form {
|
||||
max-width: 450px;
|
||||
background-color: transparent !important;
|
||||
|
||||
a {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
.form-label,
|
||||
label {
|
||||
font-weight: 500;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
.form-control {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-link {
|
||||
color: #939393;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
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;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&: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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -183,15 +186,27 @@
|
|||
height: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: 20px 0 0 20px;
|
||||
|
||||
min-height: 100vh;
|
||||
|
||||
.expert-login-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 25%, #667eea 75%, #764ba2 100%);
|
||||
// background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 25%, #667eea 75%, #764ba2 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
padding: 20px;
|
||||
|
||||
.expert-login-image-cover {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: #667eea;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.expert-spiral-decoration {
|
||||
width: 400px;
|
||||
|
|
@ -254,5 +269,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -3,31 +3,33 @@
|
|||
<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"/>
|
||||
name="Odex Theme"
|
||||
web_icon="expert_theme,static/description/icon.png"
|
||||
sequence="1000" />
|
||||
|
||||
<!-- <menuitem id="menu_expert_home"
|
||||
name="Dashboard"
|
||||
parent="menu_expert_root"
|
||||
action="action_expert_home"
|
||||
sequence="1" /> -->
|
||||
|
||||
<menuitem id="menu_expert_theme_colors"
|
||||
name="Theme Colors"
|
||||
parent="menu_expert_root"
|
||||
action="action_expert_theme_config"
|
||||
sequence="1" />
|
||||
|
||||
<menuitem id="menu_expert_login_templates"
|
||||
name="Login Page Templates"
|
||||
parent="menu_expert_root"
|
||||
action="action_expert_login_template"
|
||||
sequence="2" />
|
||||
|
||||
|
||||
<!-- 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>
|
||||
</odoo>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,785 +0,0 @@
|
|||
<?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>
|
||||
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from . import version
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
{
|
||||
'name': 'Odex30 Web',
|
||||
'category': 'Odex30-base',
|
||||
'author': 'Expert Co. Ltd.',
|
||||
'name': 'Odex Web Client',
|
||||
'category': 'Hidden',
|
||||
'version': '1.0',
|
||||
'description': """
|
||||
Odex Web Client.
|
||||
===========================
|
||||
|
||||
This module modifies the web addon to provide Odex design and responsiveness.
|
||||
Web Client customizations for Odex
|
||||
""",
|
||||
'summary': 'Web Client customizations for Odex',
|
||||
'author': 'Exp SA',
|
||||
'website': 'https://exp-sa.com',
|
||||
'company': 'Expert Ltd.',
|
||||
'depends': ['web', 'base_setup'],
|
||||
'auto_install': ['web'],
|
||||
'data': [
|
||||
|
|
@ -27,7 +28,7 @@ This module modifies the web addon to provide Odex design and responsiveness.
|
|||
('before', 'web/static/src/scss/bootstrap_overridden.scss', 'odex30_web/static/src/scss/bootstrap_overridden.scss'),
|
||||
],
|
||||
'web.assets_frontend': [
|
||||
'odex30_web/static/src/webclient/home_menu/home_menu_background.scss',
|
||||
'odex30_web/static/src/webclient/home_menu/home_menu_background.scss', # used by login page
|
||||
'odex30_web/static/src/webclient/navbar/navbar.scss',
|
||||
],
|
||||
'web.assets_backend': [
|
||||
|
|
@ -42,6 +43,7 @@ This module modifies the web addon to provide Odex design and responsiveness.
|
|||
'odex30_web/static/src/views/**/*.xml',
|
||||
('remove', 'odex30_web/static/src/views/pivot/**'),
|
||||
|
||||
# Don't include dark mode files in light mode
|
||||
('remove', 'odex30_web/static/src/**/*.dark.scss'),
|
||||
],
|
||||
'web.assets_backend_lazy': [
|
||||
|
|
@ -49,37 +51,29 @@ This module modifies the web addon to provide Odex design and responsiveness.
|
|||
],
|
||||
'web.assets_backend_lazy_dark': [
|
||||
('include', 'web.dark_mode_variables'),
|
||||
# web._assets_backend_helpers
|
||||
('before', 'odex30_web/static/src/scss/bootstrap_overridden.scss', 'odex30_web/static/src/scss/bootstrap_overridden.dark.scss'),
|
||||
('after', 'web/static/lib/bootstrap/scss/_functions.scss', 'odex30_web/static/src/scss/bs_functions_overridden.dark.scss'),
|
||||
],
|
||||
'web.assets_web': [
|
||||
('replace', 'web/static/src/main.js', 'odex30_web/static/src/main.js'),
|
||||
],
|
||||
# ========= Dark Mode =========
|
||||
"web.dark_mode_variables": [
|
||||
# web._assets_primary_variables
|
||||
('before', 'odex30_web/static/src/scss/primary_variables.scss', 'odex30_web/static/src/scss/primary_variables.dark.scss'),
|
||||
('before', 'odex30_web/static/src/**/*.variables.scss', 'odex30_web/static/src/**/*.variables.dark.scss'),
|
||||
# web._assets_secondary_variables
|
||||
('before', 'odex30_web/static/src/scss/secondary_variables.scss', 'odex30_web/static/src/scss/secondary_variables.dark.scss'),
|
||||
],
|
||||
"web.assets_web_dark": [
|
||||
('include', 'web.dark_mode_variables'),
|
||||
# web._assets_backend_helpers
|
||||
('before', 'odex30_web/static/src/scss/bootstrap_overridden.scss', 'odex30_web/static/src/scss/bootstrap_overridden.dark.scss'),
|
||||
('after', 'web/static/lib/bootstrap/scss/_functions.scss', 'odex30_web/static/src/scss/bs_functions_overridden.dark.scss'),
|
||||
# assets_backend
|
||||
'odex30_web/static/src/**/*.dark.scss',
|
||||
],
|
||||
'web.tests_assets': [
|
||||
'odex30_web/static/tests/*.js',
|
||||
],
|
||||
"web.assets_tests": [
|
||||
"odex30_web/static/tests/tours/**/*.js",
|
||||
],
|
||||
'web.assets_unit_tests': [
|
||||
'odex30_web/static/tests/**/*.test.js',
|
||||
],
|
||||
'web.qunit_suite_tests': [
|
||||
'odex30_web/static/tests/views/**/*.js',
|
||||
'odex30_web/static/tests/webclient/**/*.js',
|
||||
('remove', 'odex30_web/static/tests/**/*.test.js'),
|
||||
],
|
||||
},
|
||||
'license': 'OEEL-1',
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import ir_http
|
||||
from . import res_users_settings
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
|
||||
|
|
@ -10,10 +11,10 @@ class Http(models.AbstractModel):
|
|||
|
||||
@classmethod
|
||||
def _post_logout(cls):
|
||||
super()._post_logout()
|
||||
request.future_response.set_cookie('color_scheme', max_age=0)
|
||||
|
||||
def webclient_rendering_context(self):
|
||||
""" Extend the rendering context with session info."""
|
||||
return {
|
||||
'session_info': self.session_info(),
|
||||
}
|
||||
|
|
@ -21,17 +22,6 @@ class Http(models.AbstractModel):
|
|||
def session_info(self):
|
||||
ICP = self.env['ir.config_parameter'].sudo()
|
||||
|
||||
if self.env.user.has_group('base.group_system'):
|
||||
warn_enterprise = 'admin'
|
||||
elif self.env.user._is_internal():
|
||||
warn_enterprise = 'user'
|
||||
else:
|
||||
warn_enterprise = False
|
||||
|
||||
result = super(Http, self).session_info()
|
||||
result['support_url'] = "https://www.odoo.com/help"
|
||||
if warn_enterprise:
|
||||
result['warning'] = warn_enterprise
|
||||
result['expiration_date'] = ICP.get_param('database.expiration_date')
|
||||
result['expiration_reason'] = ICP.get_param('database.expiration_reason')
|
||||
result['support_url'] = "https://exp-sa.com/support_center"
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
// Custom SCSS for enterprise version of notebook tabs
|
||||
|
||||
.o_notebook {
|
||||
--notebook-link-border-color: #{$border-color};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { startWebClient } from "@web/start";
|
||||
import { WebClientOdex } from "./webclient/webclient";
|
||||
import { WebClientEnterprise } from "./webclient/webclient";
|
||||
|
||||
/**
|
||||
* This file starts the webclient. In the manifest, it replaces
|
||||
* This file starts the enterprise webclient. In the manifest, it replaces
|
||||
* the community main.js to load a different webclient class
|
||||
* (WebClientOdex instead of WebClient)
|
||||
* (WebClientEnterprise instead of WebClient)
|
||||
*/
|
||||
startWebClient(WebClientOdex);
|
||||
startWebClient(WebClientEnterprise);
|
||||
|
|
|
|||
|
|
@ -61,7 +61,6 @@ $component-active-bg: $o-gray-300 !default;
|
|||
|
||||
// == Typography
|
||||
$mark-bg: #ffdebc !default;
|
||||
$mark-color: shift-color($mark-bg, -75%) !default;
|
||||
|
||||
// == Tables
|
||||
$table-bg: $o-view-background-color !default;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
$o-colors-original: lighten(#000, 46.7%), #e74e4e, #f4b660, #F7CD1F, #6cedeb, #8d5482,
|
||||
#f07b50, #2C8397, #475577, #dc0457, #30C381, #9365B8 !default;
|
||||
$o-colors-original: lighten(#000, 46.7%), #f07b50, #f4b660, #F7CD1F, #6cedeb, #8d5482,
|
||||
#e74e4e, #2C8397, #475577, #dc0457, #30C381, #9365B8 !default;
|
||||
|
||||
$o-colors-secondary-original: #aa4b6b, #30C381, #97743a, #F7CD1F, #4285F4, #8E24AA,
|
||||
#D6145F, #173e43, #348F50, #AA3A38, #795548, #5e0231,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
.o_field_property_definition_type, .o_field_property_definition_type_menu {
|
||||
img {
|
||||
.o_field_property_dropdown > img {
|
||||
-webkit-filter: invert(100%);
|
||||
filter: invert(100%);
|
||||
}
|
||||
|
|
@ -11,3 +11,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.o_field_property_definition_type_popover.popover {
|
||||
.dropdown-item img {
|
||||
-webkit-filter: invert(100%);
|
||||
filter: invert(100%);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,9 +60,6 @@ export const patchListRendererDesktop = () => ({
|
|||
// we set them to not editable too.
|
||||
return false;
|
||||
}
|
||||
if (action.res_model === "account.bank.statement.line") {
|
||||
return false; // bank reconciliation isn't editable
|
||||
}
|
||||
return Boolean(action.res_model);
|
||||
};
|
||||
const onUiUpdated = () => {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { BurgerMenu } from "@web/webclient/burger_menu/burger_menu";
|
|||
import { useService } from "@web/core/utils/hooks";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
export class EnterpriseBurgerMenu extends BurgerMenu {
|
||||
export class OdexBurgerMenu extends BurgerMenu {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.hm = useService("home_menu");
|
||||
|
|
@ -15,7 +15,7 @@ export class EnterpriseBurgerMenu extends BurgerMenu {
|
|||
}
|
||||
|
||||
const systrayItem = {
|
||||
Component: EnterpriseBurgerMenu,
|
||||
Component: OdexBurgerMenu,
|
||||
};
|
||||
|
||||
registry.category("systray").add("burger_menu", systrayItem, { sequence: 0, force: true });
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { hasTouch, isIosApp, isMacOS } from "@web/core/browser/feature_detection
|
|||
import { useHotkey } from "@web/core/hotkeys/hotkey_hook";
|
||||
import { user } from "@web/core/user";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { ExpirationPanel } from "./expiration_panel";
|
||||
import { useSortable } from "@web/core/utils/sortable_owl";
|
||||
|
||||
import {
|
||||
|
|
@ -37,7 +36,7 @@ class FooterComponent extends Component {
|
|||
*/
|
||||
export class HomeMenu extends Component {
|
||||
static template = "odex30_web.HomeMenu";
|
||||
static components = { ExpirationPanel };
|
||||
static components = { };
|
||||
static props = {
|
||||
apps: {
|
||||
type: Array,
|
||||
|
|
@ -93,7 +92,6 @@ export class HomeMenu extends Component {
|
|||
this.command = useService("command");
|
||||
this.menus = useService("menu");
|
||||
this.homeMenuService = useService("home_menu");
|
||||
this.subscription = useState(useService("enterprise_subscription"));
|
||||
this.ui = useService("ui");
|
||||
this.state = useState({
|
||||
focusedIndex: null,
|
||||
|
|
@ -351,9 +349,7 @@ export class HomeMenu extends Component {
|
|||
_onInputSearch() {
|
||||
const onClose = () => {
|
||||
this._focusInput();
|
||||
if (this.inputRef.el) {
|
||||
this.inputRef.el.value = "";
|
||||
}
|
||||
this.inputRef.el.value = "";
|
||||
};
|
||||
const searchValue = this.compositionStart ? "/" : `/${this.inputRef.el.value.trim()}`;
|
||||
this.compositionStart = false;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
.o_home_menu_background {
|
||||
// 'Home menu background' design is shared with enterprise login
|
||||
// screens and it's located in './home_menu_background.scss'
|
||||
|
||||
// When applied on webclient (note: we do not specify the webclient class
|
||||
// here to avoid breaking studio custom style)
|
||||
&:not(.o_home_menu_background_custom):not(.o_in_studio) .o_main_navbar {
|
||||
background: transparent;
|
||||
border-bottom-color: transparent;
|
||||
|
|
@ -36,8 +40,8 @@
|
|||
.o_app_icon {
|
||||
width: $o-home-menu-app-icon-max-width;
|
||||
aspect-ratio: 1;
|
||||
padding: $o-home-menu-app-icon-padding;
|
||||
background-color: var(--AppSwitcherIcon-background, #{$o-home-menu-app-icon-background-color});
|
||||
padding: 10px;
|
||||
background-color: var(--AppSwitcherIcon-background, rgba(#fff, 1));
|
||||
object-fit: cover;
|
||||
transform-origin: center bottom;
|
||||
transition: box-shadow ease-in 0.1s, transform ease-in 0.1s;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
$o-home-menu-font-size-base: 1rem;
|
||||
$o-home-menu-container-size: 850px;
|
||||
$o-home-menu-app-icon-max-width: 70px;
|
||||
$o-home-menu-app-icon-padding: 10px;
|
||||
$o-home-menu-app-icon-background-color: rgba(#fff, 1);
|
||||
|
||||
$o-home-menu-caption-color: $o-gray-700 !default;
|
||||
$o-home-menu-caption-shadow: none !default;
|
||||
|
|
|
|||
|
|
@ -10,10 +10,7 @@
|
|||
t-att-aria-expanded="displayedApps.length ? 'true' : 'false'"
|
||||
aria-autocomplete="list"
|
||||
aria-haspopup="listbox"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<!-- When the subscription has expired, the expiration panel is show over the whole UI instead of here -->
|
||||
<ExpirationPanel t-if="subscription.warningType and !subscription.isWarningHidden and subscription.daysLeft <= 30 and subscription.daysLeft > 0"/>
|
||||
<div t-if="displayedApps.length" role="listbox" class="o_apps row user-select-none mt-5 mx-0">
|
||||
<div t-foreach="displayedApps" t-as="app" t-key="app.id" class="col-3 col-md-2 o_draggable mb-3 px-0">
|
||||
<a t-att-id="'result_app_' + app_index"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@
|
|||
size: cover;
|
||||
attachment: fixed;
|
||||
color: var(--homeMenu-bg-color, #{$o-gray-200});
|
||||
image: var(--homeMenu-bg-image, url("/odex30_web/static/img/background-light.svg"));
|
||||
image: var(--homeMenu-bg-image, url("/odex30_web/static/img/background-light.svg"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import { useService, useBus } from "@web/core/utils/hooks";
|
|||
import { _t } from "@web/core/l10n/translation";
|
||||
import { useState, useEffect, useRef } from "@odoo/owl";
|
||||
|
||||
export class OdexNavBar extends NavBar {
|
||||
static template = "odex30_web.OdexNavBar";
|
||||
export class EnterpriseNavBar extends NavBar {
|
||||
static template = "odex30_web.EnterpriseNavBar";
|
||||
setup() {
|
||||
super.setup();
|
||||
this.hm = useState(useService("home_menu"));
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// Ensuring SuperUser Design menu is not compressed in Enterprise
|
||||
// ============================================================================
|
||||
body.o_is_superuser .o_menu_systray {
|
||||
border-image-outset: map-get($border-widths, 5);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
|
||||
// = Enterprise Main Navbar Variables
|
||||
// ============================================================================
|
||||
$o-navbar-background: $o-white !default;
|
||||
$o-navbar-padding-v: 10px !default;
|
||||
$o-navbar-border-bottom: 0 !default;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="odex30_web.OdexNavBar" t-inherit="web.NavBar" t-inherit-mode="primary">
|
||||
<t t-name="odex30_web.EnterpriseNavBar" t-inherit="web.NavBar" t-inherit-mode="primary">
|
||||
<xpath expr="//nav" position="attributes">
|
||||
<attribute name="t-ref">nav</attribute>
|
||||
</xpath>
|
||||
|
|
@ -45,7 +45,7 @@
|
|||
<xpath expr="//DropdownItem[@t-esc='currentApp.name']" position="replace"/>
|
||||
</t>
|
||||
|
||||
<t t-name="odex30_web.OdexNavBar.SectionsMenu" t-inherit="web.NavBar.SectionsMenu" t-inherit-mode="extension">
|
||||
<t t-name="odex30_web.EnterpriseNavBar.SectionsMenu" t-inherit="web.NavBar.SectionsMenu" t-inherit-mode="extension">
|
||||
<xpath expr="//Dropdown/button" position="attributes">
|
||||
<attribute name="class" add="fw-normal" separator=" "/>
|
||||
</xpath>
|
||||
|
|
|
|||
|
|
@ -5,20 +5,10 @@
|
|||
|
||||
<xpath expr="//h3" position="replace">
|
||||
<h3 class="px-0">
|
||||
Odoo <t t-esc="serverVersion"/> (Enterprise Edition)
|
||||
Odex 30
|
||||
</h3>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//*[@id='license']" position="replace">
|
||||
<a id="license" target="_blank" href="https://www.odoo.com/documentation/master/legal/licenses.html" style="text-decoration: underline;">Odoo Enterprise Edition License V1.0</a>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//h3" position="after">
|
||||
<t t-if="expirationDate">
|
||||
<h5>Database expiration: <t t-esc="expirationDate"/></h5>
|
||||
</t>
|
||||
</xpath>
|
||||
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
import { WebClient } from "@web/webclient/webclient";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { OdexNavBar } from "./navbar/navbar";
|
||||
import { EnterpriseNavBar } from "./navbar/navbar";
|
||||
|
||||
export class WebClientOdex extends WebClient {
|
||||
export class WebClientEnterprise extends WebClient {
|
||||
static components = {
|
||||
...WebClient.components,
|
||||
NavBar: OdexNavBar,
|
||||
NavBar: EnterpriseNavBar,
|
||||
};
|
||||
setup() {
|
||||
super.setup();
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
========================
|
||||
Odex Sidebar Backend Theme v2
|
||||
========================
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
This module provides a modern, collapsible sidebar menu system that replaces the default Odex app menu bar with an enhanced navigation experience. It delivers an improved user interface with better space management and responsive design.
|
||||
|
||||
Features
|
||||
========
|
||||
|
||||
* **Collapsible/Expandable Sidebar Menu**: Toggle the sidebar to maximize screen space for your work
|
||||
* **Smooth Navigation**: Seamlessly navigate across all Odex applications and menus
|
||||
* **Responsive Design**: Adapts beautifully to different screen sizes (desktop, tablet, mobile)
|
||||
* **Persistent State**: Sidebar collapse/expand state is saved and persists across user sessions
|
||||
* **Enable/Disable Toggle**: Control the sidebar feature through the Settings panel
|
||||
* **Optimized Performance**: Minimal performance impact with efficient menu rendering
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
1. Download or clone this module into your Odex addons directory
|
||||
2. Restart the Odex server
|
||||
3. Navigate to **Apps** and search for "Odex Sidebar Backend Theme 2"
|
||||
4. Click **Install**
|
||||
|
||||
Dependencies
|
||||
============
|
||||
|
||||
This module requires:
|
||||
|
||||
* Odex 18.0 (or compatible version)
|
||||
* web module
|
||||
* base module
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
After installation, you can configure the sidebar menu feature:
|
||||
|
||||
1. Navigate to **Settings > General Settings** (or **Settings > Sidebar Menu** if available)
|
||||
2. Find the **Sidebar Menu** section
|
||||
3. Enable or disable the sidebar menu feature as needed
|
||||
4. Click **Save**
|
||||
|
||||
The setting is automatically saved and persists across all user sessions.
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
Once enabled:
|
||||
|
||||
* Click the **menu icon** (≡) at the top of the sidebar to collapse/expand it
|
||||
* The sidebar state is automatically saved for your next session
|
||||
* All applications and menu items are accessible through the sidebar
|
||||
* The responsive design automatically adjusts on smaller screens
|
||||
|
||||
Module Structure
|
||||
================
|
||||
|
||||
::
|
||||
|
||||
odex_sidebar_backend_theme2/
|
||||
├── __init__.py # Python initialization
|
||||
├── __manifest__.py # Module metadata
|
||||
├── views/
|
||||
│ └── res_config_settings.xml # Settings configuration
|
||||
├── static/
|
||||
│ └── src/
|
||||
│ ├── scss/
|
||||
│ │ └── sidebar_menu.scss # Sidebar styling
|
||||
│ ├── js/
|
||||
│ │ ├── sidebar_menu.js # Main sidebar logic
|
||||
│ │ ├── sidebar_css_loader.js # CSS loading functionality
|
||||
│ │ ├── menu_item.js # Menu item handling
|
||||
│ │ └── navbar_patch.js # Navigation bar customizations
|
||||
│ └── xml/
|
||||
│ ├── sidebar_menu_template.xml # Sidebar template
|
||||
│ ├── menu_item_template.xml # Menu item template
|
||||
│ └── navbar_patch.xml # Navbar patches
|
||||
└── README.rst # This file
|
||||
|
||||
Technologies
|
||||
=============
|
||||
|
||||
* JavaScript (ES6+)
|
||||
* SCSS/CSS
|
||||
* XML (Odex QWeb templates)
|
||||
* Python
|
||||
|
||||
Browser Support
|
||||
===============
|
||||
|
||||
* Chrome/Edge 90+
|
||||
* Firefox 88+
|
||||
* Safari 14+
|
||||
* Mobile browsers (iOS Safari, Chrome Mobile)
|
||||
|
||||
Troubleshooting
|
||||
===============
|
||||
|
||||
**Sidebar not appearing:**
|
||||
- Clear your browser cache
|
||||
- Log out and log back in
|
||||
- Check that the module is enabled in Settings
|
||||
|
||||
**Sidebar not saving state:**
|
||||
- Ensure cookies are enabled in your browser
|
||||
- Check browser console for JavaScript errors (F12)
|
||||
|
||||
**Performance issues:**
|
||||
- Clear browser cache and local storage
|
||||
- Try disabling and re-enabling the module
|
||||
|
||||
Support
|
||||
=======
|
||||
|
||||
For issues, questions, or feature requests, please contact Epxert Ltd.
|
||||
|
||||
* Website: https://www.exp-sa.com
|
||||
* Email: support@exp-sa.com
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
This module is licensed under the LGPL-3 License. See the LICENSE file for details.
|
||||
|
||||
Authors
|
||||
=======
|
||||
|
||||
* Epxert Ltd. (https://www.exp-sa.com)
|
||||
|
||||
Changelog
|
||||
=========
|
||||
|
||||
**Version 1.0.0** (Initial Release)
|
||||
- Initial release of Odex Sidebar Backend Theme 2
|
||||
- Collapsible sidebar menu functionality
|
||||
- Persistent state management
|
||||
- Responsive design support
|
||||
- Enable/disable toggle in settings
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import models
|
||||
|
|
@ -1,26 +1,46 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': 'Odex Sidebar Backend Theme2',
|
||||
'version': '18.0.1.0.0',
|
||||
'category': 'Web',
|
||||
'summary': 'Custom menu for navigating all Odoo apps and menus smoothly',
|
||||
'name': 'Odex Sidebar Backend Theme 2',
|
||||
'version': '1.0.0',
|
||||
'category': 'Web/Themes',
|
||||
'summary': 'Enhanced collapsible sidebar menu for Odex backend navigation',
|
||||
'description': """
|
||||
Replace the default Odoo app menu bar with a collapsible sidebar menu.
|
||||
Odex Sidebar Backend Theme 2
|
||||
=============================
|
||||
|
||||
This module provides a modern, collapsible sidebar menu system that replaces
|
||||
the default Odex app menu bar with an enhanced navigation experience.
|
||||
|
||||
Key Features:
|
||||
- Collapsible/expandable sidebar menu for better space management
|
||||
- Smooth navigation across all Odex applications and menus
|
||||
- Responsive design that adapts to different screen sizes
|
||||
- Persistent sidebar state (collapse/expand) across sessions
|
||||
- Enable/disable sidebar via Settings panel
|
||||
- Minimal performance impact with optimized menu rendering
|
||||
|
||||
Configuration:
|
||||
Navigate to Settings > Sidebar Menu to enable or disable the sidebar menu feature.
|
||||
The setting is automatically saved and persists across all user sessions.
|
||||
""",
|
||||
'author': 'Your Company',
|
||||
'website': 'https://www.yourcompany.com',
|
||||
'depends': ['web'],
|
||||
'data': [],
|
||||
'author': 'Epxert Ltd.',
|
||||
'website': 'https://www.exp-sa.com',
|
||||
'license': 'LGPL-3',
|
||||
'depends': [
|
||||
'web',
|
||||
'base',
|
||||
],
|
||||
'data': [
|
||||
'views/res_config_settings.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'odex_sidebar_backend_theme2/static/src/scss/sidebar_menu.scss',
|
||||
|
||||
'odex_sidebar_backend_theme2/static/src/xml/sidebar_menu_template.xml',
|
||||
'odex_sidebar_backend_theme2/static/src/xml/menu_item_template.xml',
|
||||
|
||||
'odex_sidebar_backend_theme2/static/src/js/sidebar_menu.js',
|
||||
'odex_sidebar_backend_theme2/static/src/js/sidebar_css_loader.js',
|
||||
'odex_sidebar_backend_theme2/static/src/js/menu_item.js',
|
||||
|
||||
'odex_sidebar_backend_theme2/static/src/xml/navbar_patch.xml',
|
||||
'odex_sidebar_backend_theme2/static/src/js/navbar_patch.js',
|
||||
],
|
||||
|
|
@ -28,5 +48,4 @@
|
|||
'installable': True,
|
||||
'application': False,
|
||||
'auto_install': False,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import res_config_settings
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import fields,api, models
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
sidebar_menu_enable = fields.Boolean(
|
||||
config_parameter='odex_sidebar_backend_theme2.sidebar_menu_enable',
|
||||
string='Enable Sidebar Menu',
|
||||
help='Enable or disable the sidebar menu in the backend'
|
||||
)
|
||||
|
||||
disable_nav_menu_section = fields.Boolean(
|
||||
config_parameter='odex_sidebar_backend_theme2.disable_nav_menu_section',
|
||||
string='Disable Navigation Menu Section',
|
||||
help='Enable or disable the top navigation bar menu section in the backend interface'
|
||||
)
|
||||
|
||||
sidebar_menu_icon = fields.Binary(
|
||||
string="Sidebar Icon",
|
||||
help="Upload an icon for the sidebar menu.",
|
||||
)
|
||||
|
||||
# set default value for the setting
|
||||
def get_values(self):
|
||||
res = super(ResConfigSettings, self).get_values()
|
||||
IrConfigParam = self.env['ir.config_parameter'].sudo()
|
||||
sidebar_menu_enable = IrConfigParam.get_param('odex_sidebar_backend_theme2.sidebar_menu_enable')
|
||||
disable_nav_menu_section = IrConfigParam.get_param('odex_sidebar_backend_theme2.disable_nav_menu_section')
|
||||
res.update(
|
||||
sidebar_menu_enable=sidebar_menu_enable == 'True',
|
||||
disable_nav_menu_section=disable_nav_menu_section == 'True',
|
||||
sidebar_menu_icon=IrConfigParam.get_param('odex_sidebar_backend_theme2.sidebar_menu_icon')
|
||||
)
|
||||
return res
|
||||
|
||||
def _generate_sidebar_css(self):
|
||||
"""Generate CSS rules for sidebar menu state"""
|
||||
if self.disable_nav_menu_section:
|
||||
return """
|
||||
/* Sidebar Menu Disabled - Hide Top Menu Sections */
|
||||
.o_main_navbar .o_menu_sections {
|
||||
{
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
}
|
||||
"""
|
||||
return ""
|
||||
|
||||
# save the setting value
|
||||
def set_values(self):
|
||||
super(ResConfigSettings, self).set_values()
|
||||
IrConfigParam = self.env['ir.config_parameter'].sudo()
|
||||
IrConfigParam.set_param(
|
||||
'odex_sidebar_backend_theme2.sidebar_menu_enable',
|
||||
str(self.sidebar_menu_enable)
|
||||
)
|
||||
IrConfigParam.set_param(
|
||||
'odex_sidebar_backend_theme2.disable_nav_menu_section',
|
||||
str(self.disable_nav_menu_section)
|
||||
)
|
||||
IrConfigParam.set_param(
|
||||
'odex_sidebar_backend_theme2.sidebar_menu_icon',
|
||||
self.sidebar_menu_icon or ''
|
||||
)
|
||||
|
||||
if self.sidebar_menu_icon:
|
||||
# Store the image URL in config parameter
|
||||
image_url = f"/web/image/res.config.settings/{self.id}/sidebar_menu_icon"
|
||||
self.env['ir.config_parameter'].sudo().set_param('odex_sidebar_backend_theme2.sidebar_menu_icon_url', image_url)
|
||||
else:
|
||||
self.env['ir.config_parameter'].sudo().set_param('odex_sidebar_backend_theme2.sidebar_menu_icon_url', '')
|
||||
|
||||
# Generate and store CSS for sidebar state
|
||||
css_code = self._generate_sidebar_css()
|
||||
IrConfigParam.set_param(
|
||||
'odex_sidebar_backend_theme2.sidebar_css',
|
||||
css_code
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def get_sidebar_setting(self):
|
||||
IrConfigParam = self.env['ir.config_parameter'].sudo()
|
||||
sidebar_enabled = IrConfigParam.get_param('odex_sidebar_backend_theme2.sidebar_menu_enable') == 'True'
|
||||
sidebar_icon_url = IrConfigParam.get_param('odex_sidebar_backend_theme2.sidebar_menu_icon_url')
|
||||
return {'sidebar_enabled': sidebar_enabled, 'sidebar_icon_url': sidebar_icon_url}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
/**
|
||||
* Load and inject sidebar CSS rules based on configuration
|
||||
*/
|
||||
export function loadSidebarCSS() {
|
||||
// Get the RPC service
|
||||
const rpc = useService("rpc");
|
||||
|
||||
const loadCSS = async () => {
|
||||
try {
|
||||
// Fetch the stored CSS from config parameter
|
||||
const css = await rpc(
|
||||
'/web/dataset/call_kw/ir.config_parameter/get_param',
|
||||
{
|
||||
model: 'ir.config_parameter',
|
||||
method: 'get_param',
|
||||
args: ['odex_sidebar_backend_theme2.sidebar_css'],
|
||||
kwargs: {},
|
||||
}
|
||||
);
|
||||
|
||||
if (css && css.trim()) {
|
||||
// Create a style element and inject the CSS
|
||||
const style = document.createElement('style');
|
||||
style.type = 'text/css';
|
||||
style.id = 'sidebar-dynamic-css';
|
||||
style.innerHTML = css;
|
||||
document.head.appendChild(style);
|
||||
console.log('Sidebar CSS injected successfully');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading sidebar CSS:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Load CSS when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', loadCSS);
|
||||
} else {
|
||||
loadCSS();
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize on module load
|
||||
registry.category("web_tour.tours").add("sidebar_css_loader", {
|
||||
steps: [],
|
||||
});
|
||||
|
||||
// Auto-load CSS on page load
|
||||
// loadSidebarCSS();
|
||||
|
|
@ -4,6 +4,7 @@ import { Component, onMounted, useEffect, useState } from "@odoo/owl";
|
|||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { MenuItem } from "./menu_item";
|
||||
import { rpc } from "@web/core/network/rpc"
|
||||
|
||||
export class SidebarMenu extends Component {
|
||||
static template = "odex_sidebar_backend_theme2.SidebarMenu"
|
||||
|
|
@ -14,13 +15,18 @@ export class SidebarMenu extends Component {
|
|||
this.menuService = useService("menu");
|
||||
this.busService = useService("bus_service");
|
||||
this.actionService = useService("action");
|
||||
this.rpc = rpc;
|
||||
|
||||
this.state = useState({
|
||||
menus: [],
|
||||
isOpen: true,
|
||||
isCollapsed: false
|
||||
isCollapsed: false,
|
||||
sidebarEnabled: false,
|
||||
sidebarMenuIconUrl: null,
|
||||
});
|
||||
|
||||
this.loadSidebarSetting()
|
||||
|
||||
// =================== JavaScript Control Starts Here ===================
|
||||
|
||||
const applyLayoutChanges = (isOpen) => {
|
||||
|
|
@ -92,6 +98,33 @@ export class SidebarMenu extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
async loadSidebarSetting() {
|
||||
try {
|
||||
const result = await this.rpc('/web/dataset/call_kw', {
|
||||
model: 'res.config.settings',
|
||||
method: 'get_sidebar_setting',
|
||||
args: [],
|
||||
kwargs: {},
|
||||
});
|
||||
|
||||
// Convert string result to boolean
|
||||
this.state.sidebarEnabled = result.sidebar_enabled !== 'False' && result.sidebar_enabled !== false && result.sidebar_enabled !== '';
|
||||
|
||||
// If sidebar is disabled, close it
|
||||
if (!this.state.sidebarEnabled) {
|
||||
this.state.isOpen = false;
|
||||
}
|
||||
|
||||
// Load sidebar menu icon URL
|
||||
this.state.sidebarMenuIconUrl = result.sidebar_icon_url || '/odex_sidebar_backend_theme2/static/src/img/logo1.png';
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading sidebar setting:', error);
|
||||
// Default to enabled if setting cannot be loaded
|
||||
this.state.sidebarEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
loadMenus() {
|
||||
const allMenus = this.menuService.getAll();
|
||||
const clonedMenus = structuredClone(allMenus);
|
||||
|
|
|
|||
|
|
@ -461,11 +461,6 @@
|
|||
display: none !important;
|
||||
}
|
||||
|
||||
/* Hide submenus inside the top bar */
|
||||
.o_main_navbar .o_menu_sections {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.o_web_client {
|
||||
margin-left: 0 !important;
|
||||
transition: all 0.3s ease-in-out;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="odex_sidebar_backend_theme2.SidebarMenu" owl="1">
|
||||
<div class="custom_sidebar" t-att-class="{ 'is-open': state.isOpen, 'is-collapsed': state.isCollapsed }">
|
||||
<div class="custom_sidebar" t-att-class="{ 'is-open': state.isOpen, 'is-collapsed': state.isCollapsed, 'is-disabled': !state.sidebarEnabled }" t-if="state.sidebarEnabled">
|
||||
<!-- Sidebar Header -->
|
||||
<header class="sidebar-header">
|
||||
<a href="/" class="header-logo" t-att-title="state.isCollapsed ? 'Home' : ''">
|
||||
<img src="/odex_sidebar_backend_theme2/static/src/img/logo1.png" alt="CodingNepal" />
|
||||
<img t-att-src="state.sidebarMenuIconUrl" alt="CodingNepal" />
|
||||
</a>
|
||||
<button class="sidebar-toggler" t-on-click="() => this.toggleCollapse()">
|
||||
<span class="material-symbols-rounded">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="res_config_settings_view_form_inherit_sidebar" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.form.inherit.sidebar</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="base.res_config_settings_view_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//form" position="inside">
|
||||
<app data-string="Odex Sidebar Menu" string="Odex Sidebar Menu"
|
||||
name="odex_sidebar_backend_theme2">
|
||||
<block title="Odex Sidebar Menu" name="odex_sidebar_settings_block">
|
||||
<setting>
|
||||
<div class="content-group">
|
||||
<div class="row mt16">
|
||||
<label for="sidebar_menu_enable" />
|
||||
<div class="text-muted">
|
||||
Enable or disable the sidebar menu in the backend
|
||||
interface
|
||||
</div>
|
||||
<field name="sidebar_menu_enable" />
|
||||
</div>
|
||||
<div class="row mt16">
|
||||
<label for="disable_nav_menu_section" />
|
||||
<div class="text-muted">
|
||||
Enable or disable the top navigation bar menu section
|
||||
in the backend interface
|
||||
</div>
|
||||
<field name="disable_nav_menu_section" />
|
||||
</div>
|
||||
|
||||
<div class="row mt16">
|
||||
<label for="sidebar_menu_icon" />
|
||||
<field name="sidebar_menu_icon" widget="image"
|
||||
options="{'size': [128, 128]}"/>
|
||||
</div>
|
||||
</div>
|
||||
</setting>
|
||||
</block>
|
||||
</app>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Loading…
Reference in New Issue