first commit
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Api } from "../client/auth_client";
|
||||
import {UnAuthorized} from './Auth.tsx'
|
||||
import PhoneInput from 'react-phone-input-2';
|
||||
import 'react-phone-input-2/lib/style.css';
|
||||
import './AccountSettings.css';
|
||||
import {useNavigate} from "react-router-dom";
|
||||
|
||||
const AccountSettings: React.FC = () => {
|
||||
const [email, setEmail] = useState<string | null>();
|
||||
const [dialCodeAndPhoneNumber, setDialCodeAndPhoneNumber] = useState<string>("");
|
||||
const [dialCode, setDialCode] = useState<string | null>();
|
||||
const [phoneNumber, setPhoneNumber] = useState<string |null>();
|
||||
const [currentPassword, setCurrentPassword] = useState("");
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
const [confirmPassword, setConfirmPassword] = useState("");
|
||||
const [problem, setProblem] = useState<string | null>(null);
|
||||
const [view, setView] = useState<"profile" | "password">("profile");
|
||||
const [isOk, setOk] = useState<boolean>(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
const getUserDetail = async () => {
|
||||
const token = localStorage.getItem("authToken");
|
||||
const client = new Api({
|
||||
baseUrl: "/auth",
|
||||
securityWorker: () => ({ headers: { Authorization: `Bearer ${token}` } }),
|
||||
});
|
||||
|
||||
const result = await client.account.getMyDetailCreate();
|
||||
|
||||
if (result?.data) {
|
||||
const dialCode = result.data.dialCode ?? "";
|
||||
const phoneNumber = result.data.phone ?? "";
|
||||
|
||||
setDialCode(dialCode);
|
||||
setPhoneNumber(phoneNumber);
|
||||
setDialCodeAndPhoneNumber(dialCode + phoneNumber);
|
||||
setEmail(result.data.email);
|
||||
}
|
||||
setOk(true);
|
||||
};
|
||||
|
||||
getUserDetail().then();
|
||||
}, []);
|
||||
|
||||
const handleSaveEmailChanges = async () => {
|
||||
try {
|
||||
setProblem(null);
|
||||
|
||||
const token = localStorage.getItem("authToken");
|
||||
const client = new Api({
|
||||
baseUrl: "/auth",
|
||||
securityWorker: () => ({ headers: { Authorization: `Bearer ${token}` } }),
|
||||
});
|
||||
|
||||
const emailResponse = await client.account.updateUserEmailCreate({ newEmail: email });
|
||||
|
||||
if(emailResponse.status === 401) await UnAuthorized(navigate)
|
||||
|
||||
if (!emailResponse.data.updateSuccess) {
|
||||
setProblem("Failed to update email: " + JSON.stringify(emailResponse.data.problems));
|
||||
return;
|
||||
}
|
||||
|
||||
alert("Email changes saved successfully!");
|
||||
} catch (error) {
|
||||
setProblem("An error occurred while saving email changes. Please try again.");
|
||||
console.error("Error saving email changes", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSavePhoneChanges = async () => {
|
||||
try {
|
||||
setProblem(null);
|
||||
|
||||
const token = localStorage.getItem("authToken");
|
||||
const client = new Api({
|
||||
baseUrl: "/auth",
|
||||
securityWorker: () => ({ headers: { Authorization: `Bearer ${token}` } }),
|
||||
});
|
||||
|
||||
const phoneResponse = await client.account.updatePhoneNumberCreate({
|
||||
prefix: dialCode,
|
||||
number: phoneNumber,
|
||||
});
|
||||
|
||||
if(phoneResponse.status === 401) await UnAuthorized(navigate)
|
||||
|
||||
if (!phoneResponse.data.updateSuccess) {
|
||||
setProblem("Failed to update phone: " + JSON.stringify(phoneResponse.data.problems));
|
||||
return;
|
||||
}
|
||||
|
||||
alert("Phone changes saved successfully!");
|
||||
} catch (error) {
|
||||
setProblem("An error occurred while saving phone changes. Please try again.");
|
||||
console.error("Error saving phone changes", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangePassword = async () => {
|
||||
const token = localStorage.getItem("authToken");
|
||||
const client = new Api({
|
||||
baseUrl: "/auth",
|
||||
securityWorker: () => ({ headers: { Authorization: `Bearer ${token}` } }),
|
||||
});
|
||||
|
||||
try {
|
||||
setProblem(null);
|
||||
|
||||
if (newPassword && newPassword === confirmPassword) {
|
||||
const passwordResponse = await client.account.changePasswordCreate({
|
||||
password: currentPassword,
|
||||
newPassword1: newPassword,
|
||||
newPassword2: confirmPassword,
|
||||
logOutAllSession: false
|
||||
});
|
||||
|
||||
setCurrentPassword("")
|
||||
setConfirmPassword("")
|
||||
setNewPassword("")
|
||||
|
||||
if(passwordResponse.status === 401) await UnAuthorized(navigate)
|
||||
|
||||
if (!passwordResponse.data.isSuccess) {
|
||||
setProblem("Failed to change password: " + passwordResponse.data.mess);
|
||||
return;
|
||||
}
|
||||
|
||||
alert("Password changed successfully!");
|
||||
} else if (newPassword !== confirmPassword) {
|
||||
setProblem("New password and confirm password do not match.");
|
||||
}
|
||||
} catch (error) {
|
||||
setProblem("An error occurred while changing the password. Please try again.");
|
||||
console.error("Error changing password", error);
|
||||
}
|
||||
};
|
||||
|
||||
if (!isOk) {
|
||||
return (
|
||||
<div className="loading-screen">
|
||||
<div className="spinner">
|
||||
<div className="lds-ring">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="settings-container">
|
||||
<div className="settings-sidebar">
|
||||
<h2>Account Settings</h2>
|
||||
|
||||
<div className="toggle-buttons">
|
||||
<button
|
||||
className={`btn ${view === "profile" ? "btn-selected" : ""}`}
|
||||
onClick={() => setView("profile")}
|
||||
>
|
||||
Profile Changes
|
||||
</button>
|
||||
<button
|
||||
className={`btn ${view === "password" ? "btn-selected" : ""}`}
|
||||
onClick={() => setView("password")}
|
||||
>
|
||||
Password Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="settings-content">
|
||||
<div className="toggle-form-container">
|
||||
<div className="divider-line-vertical"></div>
|
||||
|
||||
{view === "profile" && (
|
||||
<div className="settings-section">
|
||||
<h4>Profile Information</h4>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Email</label>
|
||||
<div className="input-button-container">
|
||||
<input
|
||||
type="email"
|
||||
value={email ?? ""}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="form-control compact-input"
|
||||
/>
|
||||
<button className="btn btn-primary" onClick={handleSaveEmailChanges}>
|
||||
Save Email Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="form-group">
|
||||
<label>Phone</label>
|
||||
<PhoneInput
|
||||
country={'pl'}
|
||||
value={dialCodeAndPhoneNumber}
|
||||
onChange={(phone, data) => {
|
||||
let lenOfDialCode : number = 0;
|
||||
if ("dialCode" in data) {
|
||||
lenOfDialCode = data.dialCode.length;
|
||||
setDialCode(data.dialCode);
|
||||
}
|
||||
|
||||
const phoneNumber = phone.slice(lenOfDialCode, phone.length);
|
||||
|
||||
setPhoneNumber(phoneNumber);
|
||||
|
||||
setDialCodeAndPhoneNumber(phone)
|
||||
|
||||
}}
|
||||
inputProps={{
|
||||
name: 'phone',
|
||||
required: true,
|
||||
}}
|
||||
/>
|
||||
<button className="btn btn-primary" onClick={handleSavePhoneChanges}>
|
||||
Save Phone Changes
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)}
|
||||
|
||||
{view === "password" && (
|
||||
<div className="settings-section">
|
||||
<h4>Change Password</h4>
|
||||
<div className="form-group">
|
||||
<label>Current Password</label>
|
||||
<input
|
||||
type="password"
|
||||
value={currentPassword}
|
||||
onChange={(e) => setCurrentPassword(e.target.value)}
|
||||
className="form-control compact-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>New Password</label>
|
||||
<input
|
||||
type="password"
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
className="form-control compact-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Confirm New Password</label>
|
||||
<input
|
||||
type="password"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
className="form-control compact-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button className="btn btn-primary compact-button" onClick={handleChangePassword}>
|
||||
Change Password
|
||||
</button>
|
||||
|
||||
{problem && <div className="problem-field">{problem}</div>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccountSettings;
|
||||
@@ -0,0 +1,129 @@
|
||||
.settings-container {
|
||||
display: flex;
|
||||
max-width: 100%;
|
||||
margin: 20px;
|
||||
padding: 0;
|
||||
color: #edf2f7;
|
||||
align-items: flex-start;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.settings-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
min-width: 250px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.toggle-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background-color: #2d3748;
|
||||
color: #edf2f7;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #4a5568;
|
||||
}
|
||||
|
||||
.btn-selected {
|
||||
background-color: #3182ce;
|
||||
}
|
||||
|
||||
.btn-selected:hover {
|
||||
background-color: #5e97d0;
|
||||
}
|
||||
|
||||
.settings-content {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
max-width: 800px;
|
||||
margin-left: 20px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.toggle-form-container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.divider-line-vertical {
|
||||
width: 2px;
|
||||
height: auto;
|
||||
background-color: #4a5568;
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
width: 100%;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.settings-section h4 {
|
||||
margin-bottom: 15px;
|
||||
color: #63b3ed;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
padding: 10px;
|
||||
border: 1px solid #4a5568;
|
||||
border-radius: 5px;
|
||||
background-color: #2d3748;
|
||||
color: #edf2f7;
|
||||
}
|
||||
|
||||
.compact-input {
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: #63b3ed;
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(99, 179, 237, 0.5);
|
||||
}
|
||||
|
||||
.compact-button {
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #3182ce;
|
||||
border-color: #3182ce;
|
||||
color: #edf2f7;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #63b3ed;
|
||||
border-color: #63b3ed;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import {createRemoteJWKSet, jwtVerify} from 'jose';
|
||||
import {Api as Auth} from '../client/auth_client';
|
||||
import {useNavigate} from "react-router-dom";
|
||||
|
||||
export interface TokenPayload {
|
||||
unique_name: string;
|
||||
exp: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export const validateToken = async (token: string): Promise<TokenPayload | null> => {
|
||||
try {
|
||||
const JWKS = createRemoteJWKSet(
|
||||
new URL('auth/.well-known/jwks', window.location.origin)
|
||||
);
|
||||
const { payload } = await jwtVerify(token, JWKS);
|
||||
const tokenPayload = payload as TokenPayload;
|
||||
const currentTime = Math.floor(Date.now() / 1000);
|
||||
|
||||
if (tokenPayload.exp && tokenPayload.exp > currentTime) {
|
||||
return tokenPayload;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Token validation failed', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const handleLogout = async (navigate: ReturnType<typeof useNavigate>) => {
|
||||
const token = localStorage.getItem('authToken');
|
||||
if (token) {
|
||||
const client = new Auth({
|
||||
baseUrl: 'auth',
|
||||
securityWorker: () => ({ headers: { Authorization: `Bearer ${token}` } }),
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await client.logOut.logOutList();
|
||||
if (result?.data?.logOutSuccess) {
|
||||
localStorage.removeItem('authToken');
|
||||
navigate('/login');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Logout failed', error);
|
||||
}
|
||||
} else {
|
||||
navigate('/login');
|
||||
}
|
||||
};
|
||||
|
||||
export const GetUserPrivileges = async (navigate: ReturnType<typeof useNavigate>) => {
|
||||
const token = localStorage.getItem('authToken');
|
||||
if (token) {
|
||||
const client = new Auth({
|
||||
baseUrl: 'auth',
|
||||
securityWorker: () => ({ headers: { Authorization: `Bearer ${token}` } }),
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await client.privilege.getUserPrivilegeCreate();
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
localStorage.removeItem('authToken');
|
||||
navigate('/login');
|
||||
}
|
||||
} else {
|
||||
navigate('/login');
|
||||
}
|
||||
};
|
||||
|
||||
export const UnAuthorized = async (navigate: ReturnType<typeof useNavigate>) => {
|
||||
localStorage.removeItem('authToken');
|
||||
navigate('/login');
|
||||
};
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: #2d3748; /* Modern dark background */
|
||||
color: #edf2f7; /* Light text color for readability */
|
||||
}
|
||||
|
||||
.login-container {
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
background-color: #1a202c; /* Dark background for login card */
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.25);
|
||||
padding: 20px;
|
||||
color: #edf2f7;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #1a202c;
|
||||
border: none;
|
||||
color: #edf2f7;
|
||||
}
|
||||
|
||||
.card .form-label {
|
||||
color: #a0aec0; /* Subtle color for form labels */
|
||||
}
|
||||
|
||||
.form-control {
|
||||
background-color: #2d3748;
|
||||
color: #edf2f7;
|
||||
border: 1px solid #4a5568;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: #63b3ed;
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(99, 179, 237, 0.5);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #3182ce;
|
||||
border-color: #3182ce;
|
||||
color: #edf2f7;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #63b3ed;
|
||||
border-color: #63b3ed;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #e53e3e; /* Bright red for error messages */
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Api as auth } from '../client/auth_client.ts';
|
||||
import '../LoadingScreen.css';
|
||||
import './Login.css'
|
||||
|
||||
const LoginForm: React.FC = () => {
|
||||
const [login, setLogin] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [problem, setProblem] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const client = new auth({ baseUrl: 'auth' });
|
||||
|
||||
useEffect(() => {
|
||||
document.title = 'Login - My App';
|
||||
}, []);
|
||||
|
||||
const handleEnterKey = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleLogin();
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogin = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await client.login.loginCreate({ login: login, pass: password });
|
||||
|
||||
if (response.status === 200 && response.data && response.data.token) {
|
||||
const token = response.data.token;
|
||||
localStorage.setItem('authToken', token);
|
||||
navigate('/');
|
||||
}
|
||||
} catch (error) {
|
||||
setProblem('Login failed. Please check your credentials.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="loading-screen">
|
||||
<div className="spinner">
|
||||
<div className="lds-ring"><div></div><div></div><div></div><div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="d-flex justify-content-center align-items-center vh-100">
|
||||
<div className="">
|
||||
<div className="card p-4 shadow-lg login-container">
|
||||
<h3 className="text-center mb-4">Login</h3>
|
||||
<div className="text-danger">{problem}</div>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="username" className="form-label">Username</label>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
onKeyDown={handleEnterKey}
|
||||
value={login}
|
||||
onChange={(e) => setLogin(e.target.value)}
|
||||
className="form-control"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="password" className="form-label">Password</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
onKeyDown={handleEnterKey}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="form-control"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<button className="btn btn-primary btn-lg btn-block w-100" onClick={handleLogin}>Login</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginForm;
|
||||
@@ -0,0 +1,330 @@
|
||||
.react-tel-input {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 15px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.react-tel-input :disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.react-tel-input .flag {
|
||||
width: 16px;
|
||||
height: 11px;
|
||||
}
|
||||
|
||||
.react-tel-input .form-control {
|
||||
position: relative;
|
||||
font-size: 14px;
|
||||
letter-spacing: 0.01rem;
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
padding-left: 48px;
|
||||
margin-left: 0;
|
||||
background: #ffffff;
|
||||
border: 1px solid #cacaca;
|
||||
border-radius: 5px;
|
||||
line-height: 25px;
|
||||
height: 35px;
|
||||
width: 300px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.react-tel-input .form-control.invalid-number {
|
||||
border: 1px solid #d79f9f;
|
||||
background-color: #faf0f0;
|
||||
border-left-color: #cacaca;
|
||||
}
|
||||
|
||||
.react-tel-input .form-control.invalid-number:focus {
|
||||
border: 1px solid #d79f9f;
|
||||
border-left-color: #cacaca;
|
||||
background-color: #faf0f0;
|
||||
}
|
||||
|
||||
.react-tel-input .flag-dropdown {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
padding: 0;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #cacaca;
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
|
||||
.react-tel-input .flag-dropdown:hover,
|
||||
.react-tel-input .flag-dropdown:focus {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.react-tel-input .flag-dropdown.invalid-number {
|
||||
border-color: #d79f9f;
|
||||
}
|
||||
|
||||
.react-tel-input .flag-dropdown.open {
|
||||
z-index: 2;
|
||||
background: #ffffff;
|
||||
border-radius: 3px 0 0 0;
|
||||
}
|
||||
|
||||
.react-tel-input .flag-dropdown.open .selected-flag {
|
||||
background: #ffffff;
|
||||
border-radius: 3px 0 0 0;
|
||||
}
|
||||
|
||||
.react-tel-input input[disabled] + .flag-dropdown:hover {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.react-tel-input input[disabled] + .flag-dropdown:hover .selected-flag {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.react-tel-input .selected-flag {
|
||||
outline: none;
|
||||
position: relative;
|
||||
width: 38px;
|
||||
height: 100%;
|
||||
padding: 0 0 0 8px;
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
|
||||
.react-tel-input .selected-flag:hover,
|
||||
.react-tel-input .selected-flag:focus {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.react-tel-input .selected-flag .flag {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
.react-tel-input .selected-flag .arrow {
|
||||
position: relative;
|
||||
top: 50%;
|
||||
margin-top: -2px;
|
||||
left: 20px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 3px solid transparent;
|
||||
border-right: 3px solid transparent;
|
||||
border-top: 4px solid #555;
|
||||
}
|
||||
|
||||
.react-tel-input .selected-flag .arrow.up {
|
||||
border-top: none;
|
||||
border-bottom: 4px solid #555;
|
||||
}
|
||||
|
||||
.react-tel-input .country-list {
|
||||
outline: none;
|
||||
z-index: 1;
|
||||
list-style: none;
|
||||
position: absolute;
|
||||
padding: 0;
|
||||
margin: 10px 0 10px -1px;
|
||||
box-shadow: 1px 2px 10px rgba(0, 0, 0, 0.35);
|
||||
background-color: #ffffff;
|
||||
width: 300px;
|
||||
max-height: 200px;
|
||||
overflow-y: scroll;
|
||||
border-radius: 0 0 3px 3px;
|
||||
}
|
||||
|
||||
.react-tel-input .country-list .flag {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.react-tel-input .country-list .divider {
|
||||
padding-bottom: 5px;
|
||||
margin-bottom: 5px;
|
||||
border-bottom: 1px solid #cccccc;
|
||||
}
|
||||
|
||||
.react-tel-input .country-list .country {
|
||||
padding: 7px 9px;
|
||||
}
|
||||
|
||||
.react-tel-input .country-list .country .dial-code {
|
||||
color: #6b6b6b;
|
||||
}
|
||||
|
||||
.react-tel-input .country-list .country:hover {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.react-tel-input .country-list .country.highlight {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.react-tel-input .country-list .flag {
|
||||
margin-right: 7px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.react-tel-input .country-list .country-name {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.react-tel-input .country-list .search {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: #ffffff;
|
||||
padding: 10px 0 6px 10px;
|
||||
}
|
||||
|
||||
.react-tel-input .country-list .search-emoji {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.react-tel-input .country-list .search-box {
|
||||
border: 1px solid #cacaca;
|
||||
border-radius: 3px;
|
||||
font-size: 15px;
|
||||
line-height: 15px;
|
||||
margin-left: 6px;
|
||||
padding: 3px 8px 5px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.react-tel-input .country-list .no-entries-message {
|
||||
padding: 7px 10px 11px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.react-tel-input .invalid-number-message {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
font-size: 13px;
|
||||
left: 46px;
|
||||
top: -8px;
|
||||
background: #ffffff;
|
||||
padding: 0 2px;
|
||||
color: #de0000;
|
||||
}
|
||||
|
||||
.react-tel-input .special-label {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
font-size: 13px;
|
||||
left: 46px;
|
||||
top: -8px;
|
||||
background: #ffffff;
|
||||
padding: 0 2px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.react-tel-input * {
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
}
|
||||
|
||||
.react-tel-input .hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.react-tel-input .v-hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.react-tel-input {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 15px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.react-tel-input :disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.react-tel-input .form-control {
|
||||
border: 1px solid #4a5568;
|
||||
border-radius: 5px;
|
||||
background-color: #2d3748;
|
||||
color: #edf2f7;
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.react-tel-input .form-control:focus {
|
||||
border-color: #63b3ed;
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(99, 179, 237, 0.5);
|
||||
}
|
||||
|
||||
.react-tel-input .flag-dropdown {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
padding: 0;
|
||||
background-color: #2d3748;
|
||||
border: 1px solid #4a5568;
|
||||
border-radius: 5px 0 0 5px;
|
||||
}
|
||||
|
||||
.react-tel-input .flag-dropdown:hover, .react-tel-input .flag-dropdown:focus {
|
||||
cursor: pointer;
|
||||
background-color: #4a5568;
|
||||
}
|
||||
|
||||
.react-tel-input .selected-flag {
|
||||
outline: none;
|
||||
position: relative;
|
||||
width: 38px;
|
||||
height: 100%;
|
||||
padding: 0 0 0 8px;
|
||||
border-radius: 5px 0 0 5px;
|
||||
background-color: #2d3748;
|
||||
}
|
||||
|
||||
.react-tel-input .selected-flag:hover, .react-tel-input .selected-flag:focus {
|
||||
background-color: #4a5568;
|
||||
}
|
||||
|
||||
.react-tel-input .country-list {
|
||||
outline: none;
|
||||
z-index: 1;
|
||||
list-style: none;
|
||||
position: absolute;
|
||||
padding: 0;
|
||||
margin: 10px 0 10px -1px;
|
||||
box-shadow: 1px 2px 10px rgba(0, 0, 0, 0.35);
|
||||
background-color: #2d3748;
|
||||
width: 300px;
|
||||
max-height: 200px;
|
||||
overflow-y: scroll;
|
||||
border-radius: 0 0 5px 5px;
|
||||
color: #edf2f7;
|
||||
}
|
||||
|
||||
.react-tel-input .country-list .country:hover {
|
||||
background-color: #4a5568;
|
||||
}
|
||||
|
||||
.react-tel-input .country-list .search-box {
|
||||
border: 1px solid #4a5568;
|
||||
border-radius: 5px;
|
||||
font-size: 15px;
|
||||
line-height: 15px;
|
||||
padding: 3px 8px 5px;
|
||||
outline: none;
|
||||
background-color: #2d3748;
|
||||
color: #edf2f7;
|
||||
}
|
||||
|
||||
.react-tel-input .country-list .search-box:focus {
|
||||
border-color: #63b3ed;
|
||||
box-shadow: 0 0 0 2px rgba(99, 179, 237, 0.5);
|
||||
}
|
||||
|
||||
.react-tel-input .country-list .country.highlight {
|
||||
background-color: #343c4b;
|
||||
}
|
||||
|
||||
.react-tel-input .flag-dropdown.open .selected-flag {
|
||||
background: #404752;
|
||||
}
|
||||
Reference in New Issue
Block a user