676 lines
24 KiB
HTML
676 lines
24 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<html
|
||
lang="en-US"
|
||
class="light-style"
|
||
>
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta
|
||
name="viewport"
|
||
content="width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"
|
||
/>
|
||
|
||
<title>User info - SisAi world</title>
|
||
|
||
<meta name="description" id="site_desc" content="" />
|
||
|
||
<!-- Favicon -->
|
||
<link rel="icon" type="image/x-icon" href="files/favicon.ico" />
|
||
|
||
<!-- Fonts -->
|
||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||
<link
|
||
href="https://fonts.googleapis.com/css2?family=Public+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap"
|
||
rel="stylesheet"
|
||
/>
|
||
|
||
<!-- Icons. Uncomment required icon fonts -->
|
||
<link rel="stylesheet" href="files/boxicons.css?v=20230405" />
|
||
|
||
<!-- Core CSS -->
|
||
<link rel="stylesheet" href="files/core.css" class="template-customizer-core-css" />
|
||
<link rel="stylesheet" href="files/theme-default.css" class="template-customizer-theme-css" />
|
||
<link rel="stylesheet" href="files/demo.css" />
|
||
|
||
<!-- Vendors CSS -->
|
||
<link rel="stylesheet" href="files/perfect-scrollbar.css" />
|
||
|
||
<!-- Page CSS -->
|
||
|
||
<!-- Helpers -->
|
||
<script src="files/helpers.js"></script>
|
||
|
||
<!--! Template customizer & Theme config files MUST be included after core stylesheets and helpers.js in the <head> section -->
|
||
<!--? Config: Mandatory theme config file contain global vars & default theme options, Set your preferred theme option in this file. -->
|
||
<script src="files/config.js"></script>
|
||
|
||
<script src="files/jquery.js"></script>
|
||
<script src="files/popper.js"></script>
|
||
<script src="files/bootstrap.js"></script>
|
||
<script src="files/perfect-scrollbar.js"></script>
|
||
<script src="files/menu.js"></script>
|
||
<script src="files/main.js"></script>
|
||
|
||
<script src="files/ua-parser.js"></script>
|
||
|
||
<link rel="stylesheet" href="files/spinner.css" />
|
||
<script src="files/amipro_utils.js?v=20230401402"></script>
|
||
|
||
<script>
|
||
|
||
var user_id;
|
||
|
||
const i18n_messages = new Map();
|
||
|
||
var lang_map = new Map();
|
||
lang_map.set("en-US", "Add device");
|
||
lang_map.set("zh-CN", "添加设备");
|
||
lang_map.set("ja", "デバイスを追加");
|
||
i18n_messages.set("btn_add", lang_map);
|
||
|
||
lang_map = new Map();
|
||
lang_map.set("en-US", "Go to top page");
|
||
lang_map.set("zh-CN", "去首页");
|
||
lang_map.set("ja", "トップページへ");
|
||
i18n_messages.set("btn_top", lang_map);
|
||
|
||
lang_map = new Map();
|
||
lang_map.set("en-US", "My devices");
|
||
lang_map.set("zh-CN", "我的设备");
|
||
lang_map.set("ja", "マイデバイス");
|
||
i18n_messages.set("my_devices", lang_map);
|
||
|
||
lang_map = new Map();
|
||
lang_map.set("en-US", "Device registered");
|
||
lang_map.set("zh-CN", "添加设备成功");
|
||
lang_map.set("ja", "デバイス登録完了。");
|
||
i18n_messages.set("msg_register_ok", lang_map);
|
||
|
||
lang_map = new Map();
|
||
lang_map.set("en-US", "Device");
|
||
lang_map.set("zh-CN", "设备");
|
||
lang_map.set("ja", "デバイス");
|
||
i18n_messages.set("title_device", lang_map);
|
||
|
||
lang_map = new Map();
|
||
lang_map.set("en-US", "Registered time");
|
||
lang_map.set("zh-CN", "添加时间");
|
||
lang_map.set("ja", "登録時間");
|
||
i18n_messages.set("title_time", lang_map);
|
||
|
||
lang_map = new Map();
|
||
lang_map.set("en-US", "Actions");
|
||
lang_map.set("zh-CN", "操作");
|
||
lang_map.set("ja", "操作");
|
||
i18n_messages.set("title_act", lang_map);
|
||
|
||
lang_map = new Map();
|
||
lang_map.set("en-US", "Delete");
|
||
lang_map.set("zh-CN", "删除");
|
||
lang_map.set("ja", "削除");
|
||
i18n_messages.set("title_del", lang_map);
|
||
|
||
lang_map = new Map();
|
||
lang_map.set("en-US", "Contact us");
|
||
lang_map.set("zh-CN", "联系我们");
|
||
lang_map.set("ja", "お問い合わせ");
|
||
i18n_messages.set("title_contact", lang_map);
|
||
|
||
lang_map = new Map();
|
||
lang_map.set("en-US", "Log out");
|
||
lang_map.set("zh-CN", "登出");
|
||
lang_map.set("ja", "ログアウト");
|
||
i18n_messages.set("title_logout", lang_map);
|
||
|
||
lang_map = new Map();
|
||
lang_map.set("en-US", "Deleted device.");
|
||
lang_map.set("zh-CN", "设备删除成功。");
|
||
lang_map.set("ja", "デバイスを削除しました。");
|
||
i18n_messages.set("msg_deldev_ok", lang_map);
|
||
|
||
lang_map = new Map();
|
||
lang_map.set("en-US", "Do you want to delete this device?");
|
||
lang_map.set("zh-CN", "确认删除此设备吗?");
|
||
lang_map.set("ja", "デバイスを削除しますか?");
|
||
i18n_messages.set("msg_confirm_deldev", lang_map);
|
||
|
||
lang_map = new Map();
|
||
lang_map.set("en-US", "Account information");
|
||
lang_map.set("zh-CN", "账号信息");
|
||
lang_map.set("ja", "アカウント情報");
|
||
i18n_messages.set("user_info", lang_map);
|
||
|
||
lang_map = new Map();
|
||
lang_map.set("en-US", "Save");
|
||
lang_map.set("zh-CN", "保 存");
|
||
lang_map.set("ja", "保 存");
|
||
i18n_messages.set("btn_update_info", lang_map);
|
||
|
||
lang_map = new Map();
|
||
lang_map.set("en-US", "Nick name");
|
||
lang_map.set("zh-CN", "昵 称");
|
||
lang_map.set("ja", "ニックネーム");
|
||
i18n_messages.set("label_nick_name", lang_map);
|
||
|
||
lang_map = new Map();
|
||
lang_map.set("en-US", "Password");
|
||
lang_map.set("zh-CN", "密 码");
|
||
lang_map.set("ja", "パスワード");
|
||
i18n_messages.set("label_password", lang_map);
|
||
|
||
lang_map = new Map();
|
||
lang_map.set("en-US", "Password again");
|
||
lang_map.set("zh-CN", "确认密码");
|
||
lang_map.set("ja", "パスワード(確認)");
|
||
i18n_messages.set("label_pw_confirm", lang_map);
|
||
|
||
lang_map = new Map();
|
||
lang_map.set("en-US", "Password and confirmation password are different. Please re-enter.");
|
||
lang_map.set("zh-CN", "密码与确认密码不同,请重新输入。");
|
||
lang_map.set("ja", "パスワードが一致しません。もう一度入力してください。");
|
||
i18n_messages.set("msg_pw_confirm_err", lang_map);
|
||
|
||
lang_map = new Map();
|
||
lang_map.set("en-US", "Please enter a nickname. Enter between 5 and 30 characters.");
|
||
lang_map.set("zh-CN", "请输入昵称,长度在5-30字之间。");
|
||
lang_map.set("ja", "ニックネームを入力してください。5-30文字の間で入力してください。");
|
||
i18n_messages.set("msg_input_nickname", lang_map);
|
||
|
||
lang_map = new Map();
|
||
lang_map.set("en-US", "This nickname is already in use. Please enter another nickname");
|
||
lang_map.set("zh-CN", "此昵称已经被使用,请重新输入。");
|
||
lang_map.set("ja", "このニックネームは既に使用されています。別のニックネームを入力してください。");
|
||
i18n_messages.set("msg_using_nickname", lang_map);
|
||
|
||
lang_map = new Map();
|
||
lang_map.set("en-US", "Password must be at least 8 characters long.");
|
||
lang_map.set("zh-CN", "请输入密码,长度大于8个文字。");
|
||
lang_map.set("ja", "パスワードは8文字以上で入力してください。");
|
||
i18n_messages.set("msg_pw_short_err", lang_map);
|
||
|
||
lang_map = new Map();
|
||
lang_map.set("en-US", "System error. Please try later.");
|
||
lang_map.set("zh-CN", "系统错误,请稍后再试。");
|
||
lang_map.set("ja", "システムエラー。後でもう一度お試しください。");
|
||
i18n_messages.set("msg_sys_fail", lang_map);
|
||
|
||
var reg_session_id = null;
|
||
|
||
var invite_session_id = null;
|
||
|
||
window.onload = async function() {
|
||
setI18NText(i18n_messages);
|
||
|
||
let url = new URL(window.location.href);
|
||
let params = url.searchParams;
|
||
reg_session_id = params.get("rid");
|
||
invite_session_id = params.get("nid");
|
||
|
||
//showSpinner();
|
||
|
||
if(reg_session_id){
|
||
const reg_username = await getRegistrationUser(reg_session_id);
|
||
user_id = reg_username;
|
||
if(reg_username && 0 < reg_username.length){
|
||
$('#user_email').html(user_id);
|
||
}else{
|
||
window.location.href = "login.html";
|
||
}
|
||
}else if(invite_session_id){
|
||
const response = await fetch("/invtinguser", {
|
||
method: "POST",
|
||
cache: "no-cache",
|
||
headers: {
|
||
"Content-Type": "application/json"
|
||
},
|
||
body: JSON.stringify({session: invite_session_id})
|
||
});
|
||
if(response.status == 200){
|
||
const resp = await response.json();
|
||
if (resp.status=='OK') {
|
||
if(resp.exist){
|
||
processInviteSession(invite_session_id)
|
||
window.location.href = "top.html";
|
||
}else{
|
||
//Register user first
|
||
$('#user_email').html(resp.user_email);
|
||
}
|
||
}else{//No session
|
||
alert(getI18NText(i18n_messages, 'msg_sys_fail'));
|
||
window.location.href = "login.html";
|
||
}
|
||
}
|
||
}
|
||
|
||
hideSpinner();
|
||
}
|
||
|
||
async function getRegistrationUser(reg_session_id){
|
||
showSpinner();
|
||
var lstJson = {};
|
||
lstJson["session"] = reg_session_id;
|
||
try{
|
||
const response = await fetch("/getreguser", {
|
||
method: "POST",
|
||
cache: "no-cache",
|
||
headers: {
|
||
"Content-Type": "application/json"
|
||
},
|
||
body: JSON.stringify(lstJson)
|
||
})
|
||
|
||
hideSpinner();
|
||
if(response.status == 200){
|
||
const resp = await response.json();
|
||
if (resp.status=='OK') {
|
||
return resp.user
|
||
}
|
||
}
|
||
}catch(err){
|
||
alert(getI18NText(i18n_messages, 'msg_sys_fail'));
|
||
}finally{
|
||
hideSpinner();
|
||
}
|
||
return null;
|
||
}
|
||
|
||
async function updateUser(){
|
||
if($('#password').val().length < 8){
|
||
alert(getI18NText(i18n_messages, 'msg_pw_short_err'));
|
||
//alert("Password must be at least 8 characters long.");
|
||
return;
|
||
}
|
||
if($('#password').val() != $('#pw_confirm').val()){
|
||
alert(getI18NText(i18n_messages, 'msg_pw_confirm_err'));
|
||
return;
|
||
}
|
||
var user_name = $('#user_name').val().trim();
|
||
if(user_name.length < 5 || user_name.length > 30){
|
||
alert(getI18NText(i18n_messages, 'msg_input_nickname'));
|
||
return;
|
||
}
|
||
|
||
showSpinner();
|
||
|
||
//add try-catch for await rejection
|
||
try{
|
||
const result = await fetch("/chksamenm", {
|
||
method: "POST",
|
||
cache: "no-cache",
|
||
headers: {
|
||
"Content-Type": "application/json"
|
||
},
|
||
body: JSON.stringify({type:'nick', nickname: user_name})
|
||
});
|
||
if(result.status == 200){
|
||
const resp = await result.json();
|
||
if (resp.status=='OK') {
|
||
if(resp.same){
|
||
hideSpinner();
|
||
alert(getI18NText(i18n_messages, 'msg_using_nickname'));
|
||
return;
|
||
}
|
||
}
|
||
}else{
|
||
hideSpinner();
|
||
return;
|
||
}
|
||
|
||
var lstJson = {};
|
||
if(reg_session_id && 0<reg_session_id.length) lstJson["reg_session_id"] = reg_session_id;
|
||
else if(invite_session_id && 0<invite_session_id.length) lstJson["invt_session_id"] = invite_session_id;
|
||
lstJson["user_name"] = user_name;
|
||
lstJson["password"] = $('#password').val();
|
||
lstJson["language"] = window.navigator.language;
|
||
|
||
const response = await fetch("/setuser", {
|
||
method: "POST",
|
||
cache: "no-cache",
|
||
headers: {
|
||
"Content-Type": "application/json"
|
||
},
|
||
body: JSON.stringify(lstJson)
|
||
})
|
||
|
||
if(response.status == 200){
|
||
const email = $('#user_email').html();
|
||
const resp = await response.json();
|
||
if (resp.status=='OK') {
|
||
if(reg_session_id){
|
||
login(email, lstJson["password"],"market.html");
|
||
}else if(invite_session_id && 0 < invite_session_id.length){
|
||
processInviteSession(invite_session_id)
|
||
login(email, lstJson["password"], "top.html");
|
||
}else{
|
||
window.location.href = "top.html";
|
||
}
|
||
}else{
|
||
alert(resp.errorMessage);
|
||
}
|
||
}
|
||
}catch(err){
|
||
alert(getI18NText(i18n_messages, 'msg_sys_fail'));
|
||
}finally{
|
||
hideSpinner();
|
||
}
|
||
}
|
||
|
||
async function login(email, pwd, goto){
|
||
const response = await fetch("/login", {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/json"
|
||
},
|
||
body: JSON.stringify({email: email, pwd: pwd}),
|
||
cache: "no-cache"
|
||
})
|
||
if(response.status == 200){
|
||
const resp = await response.json();
|
||
if (resp.status!='OK') {
|
||
window.location.href = "top.html";
|
||
}else{
|
||
const nm = getI18NJsonText(resp.nickname);
|
||
sessionStorage.setItem('nickname', nm);
|
||
window.location.href = goto;
|
||
}
|
||
}
|
||
}
|
||
|
||
async function processInviteSession(invite_session_id){
|
||
showSpinner();
|
||
var lstJson = {};
|
||
lstJson["session"] = invite_session_id;
|
||
try{
|
||
const response = await fetch("/invt", {
|
||
method: "POST",
|
||
cache: "no-cache",
|
||
headers: {
|
||
"Content-Type": "application/json"
|
||
},
|
||
body: JSON.stringify(lstJson)
|
||
})
|
||
|
||
hideSpinner();
|
||
if(response.status == 200){
|
||
const resp = await response.json();
|
||
if (resp.status=='OK') {
|
||
return resp.user_email
|
||
}
|
||
}
|
||
}catch(err){
|
||
alert(getI18NText(i18n_messages, 'msg_sys_fail'));
|
||
}finally{
|
||
hideSpinner();
|
||
}
|
||
return null;
|
||
}
|
||
|
||
async function registerUser(user_email) {
|
||
try {
|
||
let req = {user_email: user_email, session_id: sessionStorage.getItem("session_id")}
|
||
const response = await fetch("/reguser", {
|
||
method: "POST",
|
||
cache: "no-cache",
|
||
headers: {
|
||
"Content-Type": "application/json"
|
||
},
|
||
body: JSON.stringify(req)
|
||
})
|
||
} catch (err) {
|
||
console.log(err)
|
||
return null;
|
||
}finally{
|
||
hideSpinner();
|
||
}
|
||
}
|
||
|
||
async function addDevice(){
|
||
const the_user_id = user_id?user_id:sessionStorage.getItem("uid")
|
||
const result = await registerFido2(the_user_id, 'user_'+the_user_id);
|
||
|
||
var rtn = false
|
||
if(result.status === 'ok'){
|
||
if(reg_session_id){
|
||
await registerUser(the_user_id);
|
||
}
|
||
alert($('#msg_register_ok').html());
|
||
rtn = true;
|
||
}else{
|
||
errProcessFido2(result)
|
||
rtn = false;
|
||
}
|
||
|
||
listDevices();
|
||
|
||
return rtn
|
||
}
|
||
|
||
async function delDevice(device_id){
|
||
if(window.confirm(getI18NText(i18n_messages, 'msg_confirm_deldev')) == true){
|
||
const result = await delUserDeviceFido2(device_id)
|
||
if("ok" === result.status){
|
||
alert(getI18NText(i18n_messages, 'msg_deldev_ok'));
|
||
listDevices();
|
||
}else{
|
||
const msg = getI18NErrorMessage(result.errorMessage);
|
||
alert(msg?msg:result.errorMessage);
|
||
}
|
||
}
|
||
}
|
||
|
||
async function listDevices(){
|
||
const result = await listUserDevicesFido2()
|
||
$("#devices_list").html("");
|
||
if("ok" === result.status){
|
||
let lst_html = ""
|
||
|
||
for(let dev of result.devices){
|
||
var dev_desc = dev.desc
|
||
if(!dev_desc || 0 === dev_desc.length){
|
||
let parser = new UAParser(dev.userAgent);
|
||
if(parser.getOS().name){
|
||
dev_desc = parser.getDevice().model + ',' + parser.getOS().name + ',' + parser.getBrowser().name;
|
||
}else{
|
||
dev_desc = dvc.userAgent;
|
||
}
|
||
}
|
||
|
||
var date = new Date(dev.registered_time);
|
||
|
||
lst_html += '<tr><td><strong>'+ dev_desc +'</strong></td><td>'+
|
||
date.toLocaleString()+'</td><td>'+
|
||
"<a style=\"padding-left: 0px;\" class=\"dropdown-item\" href=\"javascript:delDevice('"+dev.device_id+"');\">"+
|
||
'<i class="bx bx-trash me-1"></i><span id="title_del"> '+ getI18NText(i18n_messages, 'title_del') +'</span></a></div></td></tr>'
|
||
}
|
||
$("#devices_list").html(lst_html);
|
||
}else{
|
||
const msg = getI18NErrorMessage(result.errorMessage); //fido2LibErrMsgLanguages.japanese
|
||
alert(msg?msg:result.errorMessage);
|
||
}
|
||
}
|
||
|
||
function showSpinner() {
|
||
$('#btn_add').prop('disabled', true);
|
||
$('#btn_top').prop('disabled', true);
|
||
document.getElementById("spinner").style.display = "block";
|
||
}
|
||
|
||
function hideSpinner() {
|
||
$('#btn_add').prop('disabled', false);
|
||
$('#btn_top').prop('disabled', false);
|
||
document.getElementById("spinner").style.display = "none";
|
||
}
|
||
|
||
</script>
|
||
</head>
|
||
|
||
<body>
|
||
<div id="msg_register_ok" style="display:none;">Device registered</div>
|
||
|
||
<!-- Layout wrapper -->
|
||
<div class="layout-wrapper layout-content-navbar">
|
||
<div class="layout-container">
|
||
<!-- Layout container -->
|
||
<div class="layout-page">
|
||
<!-- Navbar -->
|
||
|
||
<nav
|
||
class="layout-navbar container-xxl navbar navbar-expand-xl navbar-detached align-items-center bg-navbar-theme"
|
||
id="layout-navbar"
|
||
>
|
||
<div class="layout-menu-toggle navbar-nav align-items-xl-center me-3 me-xl-0 d-xl-none">
|
||
<a class="nav-item nav-link px-0 me-xl-4" href="javascript:void(0)">
|
||
<i class="bx bx-menu bx-sm"></i>
|
||
</a>
|
||
</div>
|
||
|
||
<div class="navbar-nav-right d-flex align-items-center" id="navbar-collapse">
|
||
<div class="navbar-nav align-items-center">
|
||
<div class="nav-item d-flex align-items-center">
|
||
<i class="bx bx-user fs-4 lh-0"></i>
|
||
<span id="user_email"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<ul class="navbar-nav flex-row align-items-center ms-auto">
|
||
<li>
|
||
<button style="display:none;" class="btn btn-info" onclick="javascript:window.location='top.html'"
|
||
id="btn_top">Go to top</button>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- / Navbar -->
|
||
|
||
<!-- Content wrapper -->
|
||
<div class="content-wrapper">
|
||
<!-- Content -->
|
||
<div>
|
||
<div class="container-xxl flex-grow-1 container-p-y">
|
||
<div class="card">
|
||
<h5 class="card-header" id="user_info">My email</h5>
|
||
<div class="card-body">
|
||
<div class="row justify-content-center">
|
||
<div class="col-md-6">
|
||
<!-- form class="mb-3" action="javascript:authenticate();" method="POST"></!-->
|
||
<div class="mb-3">
|
||
<label for="uid" class="form-label" id="label_nick_name">Nick name</label>
|
||
<input type="text" class="form-control" id="user_name" name="user_name" placeholder="Enter your nick name" autofocus="">
|
||
</div>
|
||
<div class="mb-3 form-password-toggle">
|
||
<div class="d-flex justify-content-between">
|
||
<label class="form-label" for="password" id="label_password">Password</label>
|
||
</div>
|
||
<div class="input-group input-group-merge">
|
||
<input type="password" id="password" class="form-control" name="password" placeholder="············" >
|
||
<span class="input-group-text cursor-pointer"></span>
|
||
</div>
|
||
</div>
|
||
<div class="mb-3 form-password-toggle">
|
||
<div class="d-flex justify-content-between">
|
||
<label class="form-label" for="pw_confirm" id="label_pw_confirm">Password again</label>
|
||
</div>
|
||
<div class="input-group input-group-merge">
|
||
<input type="password" id="pw_confirm" class="form-control" name="pw_confirm" placeholder="············" >
|
||
<span class="input-group-text cursor-pointer"></i></span>
|
||
</div>
|
||
</div>
|
||
<div class="mb-3">
|
||
<button class="btn btn-primary d-grid w-100" id="btn_update_info" onclick="javascript:updateUser();">Update</button>
|
||
</div>
|
||
<!-- /form -->
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- div class="container-xxl flex-grow-1 container-p-y">
|
||
|
||
<div class="card">
|
||
<h5 class="card-header" id="my_devices">My devices</h5>
|
||
<div style="margin-right: 10px;">
|
||
<button class="btn btn-primary" style="width:30%;float: right;margin-left: 66%;" onclick="addDevice();"
|
||
id="btn_add">Add device</button>
|
||
</div>
|
||
<div class="table-responsive text-nowrap">
|
||
<table class="table">
|
||
<thead>
|
||
<tr>
|
||
<th id="title_device">Device</th>
|
||
<th id="title_time">Registered time</th>
|
||
<th id="title_act">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="table-border-bottom-0" id="devices_list">
|
||
<tr>
|
||
<td colspan="3" style="text-align: center;" id="title_empty_list"></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
</ -->
|
||
|
||
<div id="spinner" class="spinner">
|
||
<div class="spinner-icon"></div>
|
||
</div>
|
||
<!-- / Content -->
|
||
|
||
<!-- Footer -->
|
||
<footer class="content-footer footer bg-footer-theme">
|
||
<div class="container-xxl d-flex flex-wrap justify-content-between py-2 flex-md-row flex-column">
|
||
<div class="mb-2 mb-md-0">
|
||
©
|
||
<script>
|
||
document.write(new Date().getFullYear());
|
||
</script>
|
||
<a href="#" target="_blank" class="footer-link fw-bolder">amiPro(Powered by OpenAI-4o)</a>
|
||
</div>
|
||
<div>
|
||
|
||
<a
|
||
href="mailto:support@amipro.me?subject=contact"
|
||
target="_blank"
|
||
class="footer-link me-4"
|
||
id="title_contact"
|
||
>Contact</a
|
||
>
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
<!-- / Footer -->
|
||
|
||
<div class="content-backdrop fade"></div>
|
||
</div>
|
||
<!-- Content wrapper -->
|
||
</div>
|
||
<!-- / Layout page -->
|
||
</div>
|
||
|
||
<!-- Overlay -->
|
||
<div class="layout-overlay layout-menu-toggle"></div>
|
||
</div>
|
||
<!-- / Layout wrapper -->
|
||
|
||
<!-- div class="buy-now">
|
||
<a
|
||
href="https://themeselection.com/products/sneat-bootstrap-html-admin-template/"
|
||
target="_blank"
|
||
class="btn btn-danger btn-buy-now"
|
||
>Upgrade to Pro</a
|
||
>
|
||
</div -->
|
||
|
||
<!-- Page JS -->
|
||
|
||
<!-- Place this tag in your head or just before your close body tag. -->
|
||
<script async defer src="https://buttons.github.io/buttons.js"></script>
|
||
</body>
|
||
</html>
|