enhnace login styles on desktop and tablet screens

This commit is contained in:
2025-11-29 13:18:17 +03:00
parent fd4f68d2ac
commit 48bbd9ae85
6 changed files with 428 additions and 164 deletions

View File

@@ -47,6 +47,7 @@ const ProHeader: FunctionComponent<ProHeaderProps> = ({
backgroundColor: themeName === "light" ? "white" : ProBlack2, backgroundColor: themeName === "light" ? "white" : ProBlack2,
height: isMobile ? "8vh" : "6vh", // Set a fixed height for the container height: isMobile ? "8vh" : "6vh", // Set a fixed height for the container
padding: "4px 16px 0px 16px", // Add some horizontal padding padding: "4px 16px 0px 16px", // Add some horizontal padding
width: "100%",
}} }}
> >
<Button <Button

View File

@@ -31,6 +31,7 @@ import TableNumberCard from "pages/cart/components/TableNumberCard.tsx";
import TimeEstimateCard from "pages/cart/components/timeEstimate/TimeEstimateCard.tsx"; import TimeEstimateCard from "pages/cart/components/timeEstimate/TimeEstimateCard.tsx";
import { OrderType } from "pages/checkout/hooks/types"; import { OrderType } from "pages/checkout/hooks/types";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Variant } from "utils/types/appTypes";
interface CartMobileTabletLayoutProps { interface CartMobileTabletLayoutProps {
form: FormInstance; form: FormInstance;
@@ -154,8 +155,8 @@ export default function CartMobileTabletLayout({
}} }}
> >
{isRTL {isRTL
? item.variant?.optionsAR?.[0]?.value ? (item.variant as Variant)?.optionsAR?.[0]?.value
: item.variant?.options?.[0]?.value} : (item.variant as Variant)?.options?.[0]?.value}
</span> </span>
</ProText> </ProText>
<br /> <br />

View File

@@ -1,7 +1,163 @@
.loginPage { .loginPage {
background-color: var(--secondary-background); background-color: var(--secondary-background);
padding: 24px;
display: flex;
flex-direction: column;
justify-content: center;
height: 100vh;
} }
:global(.darkApp) .loginPage { :global(.darkApp) .loginPage {
background-color: var(--background); background-color: var(--background);
} }
.loginWrapper {
width: 100%;
display: flex;
flex-direction: column;
}
.restaurantCoverContainer {
display: none;
}
.restaurantCover {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 0;
}
.loginContainer {
width: 100%;
max-width: 100%;
}
.loginTitle {
text-align: center;
margin: 20px 0 10% 0;
font-size: 18px;
}
.loginIconContainer {
position: relative;
margin-bottom: 20px;
width: fit-content;
}
.loginFormContainer {
width: 100%;
}
/* Tablet devices (min-width: 769px and max-width: 1024px) */
@media (min-width: 769px) and (max-width: 1024px) {
.loginPage {
padding: 24px;
align-items: center;
justify-content: center;
}
.loginWrapper {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.loginContainer {
max-width: 500px;
width: 100%;
background-color: var(--background);
padding: 48px 40px;
border-radius: 24px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
margin: 0 auto;
}
.loginTitle {
font-size: 22px;
margin: 0 0 32px 0;
}
.loginIconContainer {
margin-bottom: 32px;
}
.loginFormContainer {
width: 100%;
}
}
/* Desktop devices (min-width: 1025px) */
@media (min-width: 1025px) {
.loginPage {
padding: 0;
align-items: stretch;
max-width: 100%;
margin: 0;
height: 100vh;
overflow: hidden;
}
.loginWrapper {
display: grid;
grid-template-columns: 1fr 1fr;
height: 100vh;
width: 100%;
max-width: 1920px;
margin: 0 auto;
}
.restaurantCoverContainer {
display: block;
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
}
.restaurantCover {
width: 100%;
height: 100%;
object-fit: cover;
}
.loginContainer {
background-color: var(--background);
padding: 60px 10vw;
border-radius: 0;
box-shadow: none;
display: flex;
flex-direction: column;
justify-content: center;
overflow-y: auto;
margin: 0 auto;
width: 100%;
}
.loginTitle {
font-size: 24px;
margin: 0 0 40px 0;
}
.loginIconContainer {
margin-bottom: 40px;
}
.loginFormContainer {
width: 100%;
}
}
:global(.ant-app-rtl) .loginWrapper {
direction: rtl;
}
:global(.ant-app-rtl) .loginWrapper {
grid-template-columns: 1fr 1fr;
}
:global(.darkApp) .loginContainer {
background-color: var(--secondary-background);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}

View File

@@ -2,6 +2,7 @@
import { Button, Form, Input, message } from "antd"; import { Button, Form, Input, message } from "antd";
import DatePickerBottomSheet from "components/CustomBottomSheet/DatePickerBottomSheet"; import DatePickerBottomSheet from "components/CustomBottomSheet/DatePickerBottomSheet";
import ImageWithFallback from "components/ImageWithFallback";
import LoginManIcon from "components/Icons/LoginManIcon"; import LoginManIcon from "components/Icons/LoginManIcon";
import ProPhoneInput from "components/ProPhoneInput"; import ProPhoneInput from "components/ProPhoneInput";
import ProText from "components/ProText"; import ProText from "components/ProText";
@@ -11,9 +12,14 @@ import { useTranslation } from "react-i18next";
import "react-phone-input-2/lib/style.css"; import "react-phone-input-2/lib/style.css";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { useSendOtpMutation } from "redux/api/auth"; import { useSendOtpMutation } from "redux/api/auth";
import { useGetRestaurantDetailsQuery } from "redux/api/others";
import { useAppSelector } from "redux/hooks"; import { useAppSelector } from "redux/hooks";
import { colors, DisabledColor, ProGray1 } from "ThemeConstants"; import { colors, DisabledColor, ProGray1 } from "ThemeConstants";
import { default_image } from "utils/constants";
import styles from "./login.module.css"; import styles from "./login.module.css";
import { Layout } from "antd";
import ProHeader from "components/ProHeader/ProHeader";
import useBreakPoint from "hooks/useBreakPoint";
export default function LoginPage() { export default function LoginPage() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -22,6 +28,10 @@ export default function LoginPage() {
const [form] = Form.useForm(); const [form] = Form.useForm();
const [sendOtp, { isLoading }] = useSendOtpMutation(); const [sendOtp, { isLoading }] = useSendOtpMutation();
const { subdomain } = useParams(); const { subdomain } = useParams();
const { data: restaurant } = useGetRestaurantDetailsQuery(subdomain, {
skip: !subdomain,
});
const { isTablet } = useBreakPoint();
// const [phone, setPhone] = useState<string>(""); // const [phone, setPhone] = useState<string>("");
const [selectedDate, setSelectedDate] = useState<string>(""); const [selectedDate, setSelectedDate] = useState<string>("");
@@ -42,109 +52,120 @@ export default function LoginPage() {
}; };
return ( return (
<div <Layout className={styles.loginPage}>
style={{ {isTablet ? <ProHeader>{t("login.singup/Login")}</ProHeader> : null}
padding: 24,
display: "flex",
flexDirection: "column",
justifyContent: "center",
height: "100vh",
}}
className={styles.loginPage}
>
<ProTitle
level={5}
style={{ textAlign: "center", margin: "20px 0 10% 0", fontSize: 18 }}
>
{t("login.singup/Login")}
</ProTitle>
<div <Layout.Content className={styles.loginWrapper}>
style={{ {/* Restaurant Cover Image - Desktop Only */}
position: "relative", <div className={styles.restaurantCoverContainer}>
marginBottom: 20, <ImageWithFallback
[isRTL ? "right" : "left"]: "50%", src={restaurant?.restaurant_cover}
width: "fit-content", fallbackSrc={default_image}
transform: isRTL ? "translateX(50%)" : "translateX(-50%)", alt={t("login.restaurantCover")}
}} className={styles.restaurantCover}
> width="100%"
<LoginManIcon /> height="100%"
</div> preview={false}
<ProTitle level={5}>{t("login.EnterYourNumber")} 👋</ProTitle>
<ProText style={{ fontSize: 12, color: ProGray1 }}>
{t("login.WeWillSendYouAWhatsAppMessageWithAOneTimeVerificationCode")}
</ProText>
<Form
layout="vertical"
style={{ marginTop: 20 }}
name="loginForm"
form={form}
>
<Form.Item
label={t("login.name")}
name="name"
rules={[{ required: true, message: "" }]}
>
<Input
placeholder={t("login.EnterYourName")}
size="large"
style={{
height: 50,
fontSize: 14,
}}
/> />
</Form.Item> </div>
<Form.Item {/* Login Form Container */}
label={t("login.date")} <div className={styles.loginContainer}>
name="date" {!isTablet ? (
rules={[{ required: true, message: "" }]} <ProTitle level={5} className={styles.loginTitle}>
> {t("login.singup/Login")}
<Input </ProTitle>
placeholder={t("login.DateOfBirth")} ) : null}
size="large"
onClick={() => setIsOpen(true)} <div
readOnly className={styles.loginIconContainer}
value={selectedDate}
style={{ style={{
cursor: "pointer", [isRTL ? "right" : "left"]: "50%",
height: 50, transform: isRTL ? "translateX(50%)" : "translateX(-50%)",
fontSize: 14,
}} }}
/>
</Form.Item>
<ProPhoneInput label={t("login.phone")} propName="phone" />
<Form.Item label={null}>
<Button
onClick={handleLogin}
type="primary"
size="large"
loading={isLoading}
style={{
width: "100%",
borderRadius: 1000,
backgroundColor: isLoading
? themeName === "light"
? "rgba(233, 233, 233, 1)"
: DisabledColor
: colors.primary,
color: "#FFF",
height: 50,
border: "none",
marginTop: "2rem",
boxShadow: "none",
}}
disabled={isLoading}
htmlType="submit"
> >
{t("login.sendCode")} <LoginManIcon />
</Button> </div>
</Form.Item>
</Form> <ProTitle level={5}>{t("login.EnterYourNumber")} 👋</ProTitle>
<ProText style={{ fontSize: 12, color: ProGray1 }}>
{t(
"login.WeWillSendYouAWhatsAppMessageWithAOneTimeVerificationCode",
)}
</ProText>
<Form
layout="vertical"
style={{ marginTop: 20 }}
name="loginForm"
form={form}
className={styles.loginFormContainer}
>
<Form.Item
label={t("login.name")}
name="name"
rules={[{ required: true, message: "" }]}
>
<Input
placeholder={t("login.EnterYourName")}
size="large"
style={{
height: 50,
fontSize: 14,
}}
/>
</Form.Item>
<Form.Item
label={t("login.date")}
name="date"
rules={[{ required: true, message: "" }]}
>
<Input
placeholder={t("login.DateOfBirth")}
size="large"
onClick={() => setIsOpen(true)}
readOnly
value={selectedDate}
style={{
cursor: "pointer",
height: 50,
fontSize: 14,
}}
/>
</Form.Item>
<ProPhoneInput label={t("login.phone")} propName="phone" />
<Form.Item label={null}>
<Button
onClick={handleLogin}
type="primary"
size="large"
loading={isLoading}
style={{
width: "100%",
borderRadius: 1000,
backgroundColor: isLoading
? themeName === "light"
? "rgba(233, 233, 233, 1)"
: DisabledColor
: colors.primary,
color: "#FFF",
height: 50,
border: "none",
marginTop: "2rem",
boxShadow: "none",
}}
disabled={isLoading}
htmlType="submit"
>
{t("login.sendCode")}
</Button>
</Form.Item>
</Form>
</div>
</Layout.Content>
<DatePickerBottomSheet <DatePickerBottomSheet
isOpen={isOpen} isOpen={isOpen}
@@ -156,6 +177,6 @@ export default function LoginPage() {
}} }}
initialDate={new Date(1990, 0, 1)} initialDate={new Date(1990, 0, 1)}
/> />
</div> </Layout>
); );
} }

View File

@@ -1,3 +1,93 @@
/* OTP Page Styles */
.otpPage {
background-color: var(--secondary-background);
padding: 16px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
height: 100vh;
}
:global(.darkApp) .otpPage {
background-color: var(--background);
}
.otpWrapper {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.otpContainer {
width: 100%;
max-width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 24px;
}
.otpContent {
display: flex;
flex-direction: column;
gap: 8px;
}
.otpTitle {
margin: 0;
}
.otpInputContainer {
width: 100%;
display: flex;
justify-content: center;
}
.otpButton {
width: 100%;
height: 48px;
margin-bottom: 16px;
color: white;
border: none;
box-shadow: none;
}
/* Tablet devices (min-width: 769px and max-width: 1024px) */
@media (min-width: 769px) and (max-width: 1024px) {
.otpPage {
padding: 24px;
align-items: center;
justify-content: center;
}
.otpWrapper {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.otpContainer {
max-width: 500px;
width: 100%;
background-color: var(--background);
padding: 48px 40px;
border-radius: 24px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
margin: 0 auto;
}
}
:global(.darkApp) .otpContainer {
background-color: var(--secondary-background);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
.productContainer :global(.ant-radio-wrapper:last-child) { .productContainer :global(.ant-radio-wrapper:last-child) {
width: 100% !important; width: 100% !important;
} }

View File

@@ -1,4 +1,4 @@
import { Button, Space, message } from "antd"; import { Button, Layout, Space, message } from "antd";
import OtpIcon from "components/Icons/otpIcon.tsx"; import OtpIcon from "components/Icons/otpIcon.tsx";
import OtpInput from "components/OtpInput/OtpInput"; import OtpInput from "components/OtpInput/OtpInput";
import ProText from "components/ProText"; import ProText from "components/ProText";
@@ -10,6 +10,9 @@ import { useNavigate, useParams } from "react-router-dom";
import { useConfirmOtpMutation, useSendOtpMutation } from "redux/api/auth"; import { useConfirmOtpMutation, useSendOtpMutation } from "redux/api/auth";
import { useAppDispatch } from "redux/hooks"; import { useAppDispatch } from "redux/hooks";
import { ProGray1 } from "ThemeConstants"; import { ProGray1 } from "ThemeConstants";
import styles from "./otp.module.css";
import ProHeader from "components/ProHeader/ProHeader";
import useBreakPoint from "hooks/useBreakPoint";
export default function OtpPage() { export default function OtpPage() {
const { subdomain } = useParams(); const { subdomain } = useParams();
@@ -18,6 +21,7 @@ export default function OtpPage() {
const [sendOtp, { isLoading }] = useSendOtpMutation(); const [sendOtp, { isLoading }] = useSendOtpMutation();
const [confirmOtp, { isLoading: isConfirmLoading }] = useConfirmOtpMutation(); const [confirmOtp, { isLoading: isConfirmLoading }] = useConfirmOtpMutation();
const [otp, setOtp] = useState<string>(""); const [otp, setOtp] = useState<string>("");
const { isTablet } = useBreakPoint();
const handleOtpSave = (otp: string) => { const handleOtpSave = (otp: string) => {
try { try {
setOtp(otp); setOtp(otp);
@@ -28,70 +32,61 @@ export default function OtpPage() {
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
<> <Layout className={styles.otpPage}>
<div {isTablet ? <ProHeader>{t("otp.verification")}</ProHeader> : null}
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
textAlign: "center",
padding: 16,
gap: 24,
height: "92vh",
}}
>
<OtpIcon />
<div> <Layout.Content className={styles.otpWrapper}>
<ProTitle level={4}>{t("otp.verification")}</ProTitle> <div className={styles.otpContainer}>
<ProText style={{ color: ProGray1 }}> <OtpIcon />
{t("otp.enterThe4DigitCodeThatSentToYourPhoneNumber")}
</ProText> <div className={styles.otpContent}>
<br /> <ProTitle level={4} className={styles.otpTitle}>
<ProText style={{ color: ProGray1 }}> {t("otp.verification")}
{localStorage.getItem("otp")} </ProTitle>
</ProText> <ProText style={{ color: ProGray1 }}>
{t("otp.enterThe4DigitCodeThatSentToYourPhoneNumber")}
</ProText>
<br />
<ProText style={{ color: ProGray1 }}>
{localStorage.getItem("otp")}
</ProText>
</div>
<div className={styles.otpInputContainer}>
<Space size="small">
<OtpInput
length={5}
onComplete={handleOtpSave}
resendOtp={() =>
sendOtp({
phone: localStorage.getItem("userPhone") || "",
})
}
isLoading={isLoading || isConfirmLoading}
/>
</Space>
</div>
<Button
type="primary"
shape="round"
className={styles.otpButton}
disabled={!otp || otp.length < 5 || isConfirmLoading}
onClick={() => {
confirmOtp({
otp: otp,
phone: localStorage.getItem("userPhone"),
})
.unwrap()
.then((response) => {
dispatch(loginSuccess(response));
message.info(t("otp.confirmOTPSuccess"));
navigate(`/${subdomain}/menu`);
});
}}
>
{t("otp.continue")}
</Button>
</div> </div>
<div> </Layout.Content>
<Space size="small"> </Layout>
<OtpInput
length={5}
onComplete={handleOtpSave}
resendOtp={() =>
sendOtp({
phone: localStorage.getItem("userPhone") || "",
})
}
isLoading={isLoading || isConfirmLoading}
/>
</Space>
</div>
<Button
type="primary"
shape="round"
style={{
width: "100%",
height: 48,
marginBottom: 16,
color: "white",
border: "none",
boxShadow: "none",
}}
disabled={!otp || otp.length < 5 || isConfirmLoading}
onClick={() => {
confirmOtp({ otp: otp, phone: localStorage.getItem("userPhone") })
.unwrap()
.then((response) => {
dispatch(loginSuccess(response));
message.info(t("otp.confirmOTPSuccess"));
navigate(`/${subdomain}/menu`);
});
}}
>
{t("otp.continue")}
</Button>
</div>
</>
); );
} }