From 0d844eed3f0014dd40b4b06ce20257c6c8db4c43 Mon Sep 17 00:00:00 2001 From: louiscklaw Date: Sun, 8 Jun 2025 19:07:38 +0800 Subject: [PATCH] update demo-react-login, --- .../DemoReactLogin/components/Action.tsx | 21 ++++ .../components/CustomField.module.scss | 29 +++++ .../DemoReactLogin/components/CustomField.tsx | 38 ++++++ .../pages/DemoReactLogin/components/Wave.tsx | 13 ++ .../src/pages/DemoReactLogin/data/fields.tsx | 82 +++++++++++++ .../src/pages/DemoReactLogin/data/utils.tsx | 46 +++++++ .../mobile/src/pages/DemoReactLogin/index.tsx | 30 +++-- .../src/pages/DemoReactLogin/module.d.ts | 9 ++ .../DemoReactLogin/pages/Home.module.scss | 38 ++++++ .../src/pages/DemoReactLogin/pages/Home.tsx | 59 +++++++++ .../DemoReactLogin/pages/Login.module.scss | 17 +++ .../src/pages/DemoReactLogin/pages/Login.tsx | 112 ++++++++++++++++++ .../DemoReactLogin/pages/Signup.module.scss | 17 +++ .../src/pages/DemoReactLogin/pages/Signup.tsx | 111 +++++++++++++++++ .../DemoReactLogin/store/AccountStore.tsx | 19 +++ .../pages/DemoReactLogin/store/Selectors.tsx | 13 ++ .../src/pages/DemoReactLogin/store/index.tsx | 1 + .../src/pages/DemoReactLogin/style.scss | 103 ---------------- .../pages/DemoReactLogin/theme/variables.scss | 0 19 files changed, 643 insertions(+), 115 deletions(-) create mode 100644 03_source/mobile/src/pages/DemoReactLogin/components/Action.tsx create mode 100644 03_source/mobile/src/pages/DemoReactLogin/components/CustomField.module.scss create mode 100644 03_source/mobile/src/pages/DemoReactLogin/components/CustomField.tsx create mode 100644 03_source/mobile/src/pages/DemoReactLogin/components/Wave.tsx create mode 100644 03_source/mobile/src/pages/DemoReactLogin/data/fields.tsx create mode 100644 03_source/mobile/src/pages/DemoReactLogin/data/utils.tsx create mode 100644 03_source/mobile/src/pages/DemoReactLogin/module.d.ts create mode 100644 03_source/mobile/src/pages/DemoReactLogin/pages/Home.module.scss create mode 100644 03_source/mobile/src/pages/DemoReactLogin/pages/Home.tsx create mode 100644 03_source/mobile/src/pages/DemoReactLogin/pages/Login.module.scss create mode 100644 03_source/mobile/src/pages/DemoReactLogin/pages/Login.tsx create mode 100644 03_source/mobile/src/pages/DemoReactLogin/pages/Signup.module.scss create mode 100644 03_source/mobile/src/pages/DemoReactLogin/pages/Signup.tsx create mode 100644 03_source/mobile/src/pages/DemoReactLogin/store/AccountStore.tsx create mode 100644 03_source/mobile/src/pages/DemoReactLogin/store/Selectors.tsx create mode 100644 03_source/mobile/src/pages/DemoReactLogin/store/index.tsx create mode 100644 03_source/mobile/src/pages/DemoReactLogin/theme/variables.scss diff --git a/03_source/mobile/src/pages/DemoReactLogin/components/Action.tsx b/03_source/mobile/src/pages/DemoReactLogin/components/Action.tsx new file mode 100644 index 0000000..2ef32b7 --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactLogin/components/Action.tsx @@ -0,0 +1,21 @@ +import { IonCol, IonRouterLink, IonRow } from '@ionic/react'; + +interface ActionProps { + message: string; + text: string; + link: string; +} + +export const Action = (props: ActionProps): React.JSX.Element => ( + + +

+ {props.message} + + {' '} + {props.text} → + +

+
+
+); diff --git a/03_source/mobile/src/pages/DemoReactLogin/components/CustomField.module.scss b/03_source/mobile/src/pages/DemoReactLogin/components/CustomField.module.scss new file mode 100644 index 0000000..ec37f17 --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactLogin/components/CustomField.module.scss @@ -0,0 +1,29 @@ +.field:not(:last-child) { + margin-bottom: 1rem !important; +} + +.field { + ion-label { + padding-left: 0.2rem; + padding-right: 0.5rem; + color: #d3a6c7; + display: flex; + justify-content: space-between; + align-content: center; + align-items: center; + + p { + color: rgb(236, 149, 35); + } + } +} +.customInput { + --background: #834e76; + --padding-bottom: 1rem; + --padding-top: 1rem; + --padding-start: 1rem; + --padding-end: 1rem; + border-radius: 10px; + margin-top: 0.25rem; + transition: all 0.2s linear; +} diff --git a/03_source/mobile/src/pages/DemoReactLogin/components/CustomField.tsx b/03_source/mobile/src/pages/DemoReactLogin/components/CustomField.tsx new file mode 100644 index 0000000..de5a628 --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactLogin/components/CustomField.tsx @@ -0,0 +1,38 @@ +import { IonInput, IonLabel } from '@ionic/react'; +import styles from './CustomField.module.scss'; + +interface FieldType { + id: string; + label: string; + input: { + props: any; + state: any; + }; +} + +interface ErrorType { + id: string; + message: string; +} + +interface CustomFieldProps { + field: FieldType; + errors?: ErrorType[]; +} + +const CustomField = ({ field, errors }: CustomFieldProps): React.JSX.Element => { + const error = errors && errors.filter((e) => e.id === field.id)[0]; + const errorMessage = error && errors.filter((e) => e.id === field.id)[0].message; + + return ( +
+ + {field.label} + {error &&

{errorMessage}

} +
+ +
+ ); +}; + +export default CustomField; diff --git a/03_source/mobile/src/pages/DemoReactLogin/components/Wave.tsx b/03_source/mobile/src/pages/DemoReactLogin/components/Wave.tsx new file mode 100644 index 0000000..6e7c821 --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactLogin/components/Wave.tsx @@ -0,0 +1,13 @@ +export const Wave = (): React.JSX.Element => ( + + + +); diff --git a/03_source/mobile/src/pages/DemoReactLogin/data/fields.tsx b/03_source/mobile/src/pages/DemoReactLogin/data/fields.tsx new file mode 100644 index 0000000..62f7eac --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactLogin/data/fields.tsx @@ -0,0 +1,82 @@ +import { useFormInput } from "./utils"; + +export const useSignupFields = () => { + + return [ + { + id: "name", + label: "Name", + required: true, + input: { + + props: { + + type: "text", + placeholder: "Joe Bloggs" + }, + state: useFormInput("") + } + }, + { + id: "email", + label: "Email", + required: true, + input: { + + props: { + + type: "email", + placeholder: "joe@bloggs.com" + }, + state: useFormInput("") + } + }, + { + id: "password", + label: "Password", + required: true, + input: { + + props: { + + type: "password", + placeholder: "*********" + }, + state: useFormInput("") + } + } + ]; +} + +export const useLoginFields = () => { + + return [ + + { + id: "email", + label: "Email", + required: true, + input: { + + props: { + type: "email", + placeholder: "joe@bloggs.com" + }, + state: useFormInput("") + } + }, + { + id: "password", + label: "Password", + required: true, + input: { + + props: { + type: "password", + placeholder: "*******" + }, + state: useFormInput("") + } + } + ]; +} \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoReactLogin/data/utils.tsx b/03_source/mobile/src/pages/DemoReactLogin/data/utils.tsx new file mode 100644 index 0000000..10a39dd --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactLogin/data/utils.tsx @@ -0,0 +1,46 @@ +import { useState } from "react"; + +export const useFormInput = (initialValue = "") => { + + const [ value, setValue ] = useState(initialValue); + + const handleChange = async e => { + + const tempValue = await e.currentTarget.value; + setValue(tempValue); + } + + return { + + value, + reset: (newValue) => setValue(newValue), + onIonChange: handleChange, + onKeyUp: handleChange + }; +} + +export const validateForm = fields => { + + let errors = []; + + fields.forEach(field => { + + if (field.required) { + + const fieldValue = field.input.state.value; + + if (fieldValue === "") { + + const error = { + + id: field.id, + message: `Please check your ${ field.id }`, + }; + + errors.push(error); + } + } + }); + + return errors; +} \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoReactLogin/index.tsx b/03_source/mobile/src/pages/DemoReactLogin/index.tsx index fbd5d16..3c4c4dc 100644 --- a/03_source/mobile/src/pages/DemoReactLogin/index.tsx +++ b/03_source/mobile/src/pages/DemoReactLogin/index.tsx @@ -1,28 +1,33 @@ -import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react'; +import { IonRouterOutlet, IonTabs } from '@ionic/react'; -import { cloudOutline, searchOutline } from 'ionicons/icons'; import { Route, Redirect } from 'react-router'; -import Tab1 from './AppPages/Tab1'; -import Tab2 from './AppPages/Tab2'; +import './theme/variables.scss'; -import './style.scss'; +import Home from './pages/Home'; +import Login from './pages/Login'; +import Signup from './pages/Signup'; function DemoReactLogin() { return ( - - - - - + + - + + + + + + + + + - {/* */} + {/* @@ -33,6 +38,7 @@ function DemoReactLogin() { Search + */} ); } diff --git a/03_source/mobile/src/pages/DemoReactLogin/module.d.ts b/03_source/mobile/src/pages/DemoReactLogin/module.d.ts new file mode 100644 index 0000000..4af7be7 --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactLogin/module.d.ts @@ -0,0 +1,9 @@ +declare module '*.module.css' { + const classes: { readonly [key: string]: string }; + export default classes; +} + +declare module '*.module.scss' { + const classes: { readonly [key: string]: string }; + export default classes; +} diff --git a/03_source/mobile/src/pages/DemoReactLogin/pages/Home.module.scss b/03_source/mobile/src/pages/DemoReactLogin/pages/Home.module.scss new file mode 100644 index 0000000..e600dab --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactLogin/pages/Home.module.scss @@ -0,0 +1,38 @@ +.homePage { + ion-header { + ion-img { + border-bottom: 3px solid rgb(236, 149, 35); + } + } + + ion-footer { + background-color: #7c3b6a; + color: white; + } +} + +.getStarted { + height: 100%; + background-color: #ffffff; + background-image: + radial-gradient(#b8b8b8 1px, transparent 1px), radial-gradient(#b8b8b8 1px, #ffffff 1px); + background-size: 40px 40px; + background-position: + 0 0, + 20px 20px; + + ion-card-title { + color: black !important; + letter-spacing: -0.08rem; + font-weight: 900 !important; + } +} + +.heading { + margin-top: 7rem; +} + +.getStartedButton { + font-size: 1.2rem; + margin-top: 1rem; +} diff --git a/03_source/mobile/src/pages/DemoReactLogin/pages/Home.tsx b/03_source/mobile/src/pages/DemoReactLogin/pages/Home.tsx new file mode 100644 index 0000000..e1329e8 --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactLogin/pages/Home.tsx @@ -0,0 +1,59 @@ +import { + IonButton, + IonCardTitle, + IonCol, + IonContent, + IonFooter, + IonGrid, + IonHeader, + IonImg, + IonPage, + IonRouterLink, + IonRow, + IonToolbar, +} from '@ionic/react'; +import { Action } from '../components/Action'; +import styles from './Home.module.scss'; + +const Home = (): React.JSX.Element => { + return ( + + + {/* */} + + {/* */} + + +
+ + + + + Join millions of other people discovering their creative side + + + + + + + + + Get started → + + + + + +
+
+ + + + + + +
+ ); +}; + +export default Home; diff --git a/03_source/mobile/src/pages/DemoReactLogin/pages/Login.module.scss b/03_source/mobile/src/pages/DemoReactLogin/pages/Login.module.scss new file mode 100644 index 0000000..344e511 --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactLogin/pages/Login.module.scss @@ -0,0 +1,17 @@ +.loginPage { + ion-toolbar { + --border-style: none; + --border-color: transparent; + --padding-top: 1rem; + --padding-bottom: 1rem; + --padding-start: 1rem; + --padding-end: 1rem; + } +} + +.headingText { + h5 { + margin-top: 0.2rem; + color: #d3a6c7; + } +} diff --git a/03_source/mobile/src/pages/DemoReactLogin/pages/Login.tsx b/03_source/mobile/src/pages/DemoReactLogin/pages/Login.tsx new file mode 100644 index 0000000..bcde778 --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactLogin/pages/Login.tsx @@ -0,0 +1,112 @@ +import { + IonBackButton, + IonButton, + IonButtons, + IonCardTitle, + IonCol, + IonContent, + IonFooter, + IonGrid, + IonHeader, + IonIcon, + IonPage, + IonRouterLink, + IonRow, + IonToolbar, +} from '@ionic/react'; +import styles from './Login.module.scss'; + +import { arrowBack, shapesOutline } from 'ionicons/icons'; +import CustomField from '../components/CustomField'; +import { useLoginFields } from '../data/fields'; +import { Action } from '../components/Action'; +import { Wave } from '../components/Wave'; +import { useEffect, useState } from 'react'; +import { validateForm } from '../data/utils'; +import { useParams } from 'react-router'; + +interface FieldType { + id: string; + label: string; + input: { + props: any; + state: any; + }; +} + +interface ErrorType { + id: string; + message: string; +} + +const Login = (): React.JSX.Element => { + const params = useParams(); + + const fields: FieldType[] = useLoginFields(); + const [errors, setErrors] = useState(false); + + const login = (): void => { + const errors = validateForm(fields); + setErrors(errors); + + if (!errors.length) { + // Submit your form here + } + }; + + useEffect(() => { + return () => { + fields.forEach((field) => field.input.state.reset('')); + setErrors(false); + }; + }, [params]); + + return ( + + + + + + + + + + + + + + + + + + + Log in +
Welcome back, hope you're doing well
+
+
+ + + + {fields.map((field) => { + return ; + })} + + + Login + + + +
+
+ + + + + + + +
+ ); +}; + +export default Login; diff --git a/03_source/mobile/src/pages/DemoReactLogin/pages/Signup.module.scss b/03_source/mobile/src/pages/DemoReactLogin/pages/Signup.module.scss new file mode 100644 index 0000000..0b1f4a7 --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactLogin/pages/Signup.module.scss @@ -0,0 +1,17 @@ +.signupPage { + ion-toolbar { + --border-style: none; + --border-color: transparent; + --padding-top: 1rem; + --padding-bottom: 1rem; + --padding-start: 1rem; + --padding-end: 1rem; + } +} + +.headingText { + h5 { + margin-top: 0.2rem; + color: #d3a6c7; + } +} diff --git a/03_source/mobile/src/pages/DemoReactLogin/pages/Signup.tsx b/03_source/mobile/src/pages/DemoReactLogin/pages/Signup.tsx new file mode 100644 index 0000000..f607d69 --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactLogin/pages/Signup.tsx @@ -0,0 +1,111 @@ +import { + IonBackButton, + IonButton, + IonButtons, + IonCardTitle, + IonCol, + IonContent, + IonFooter, + IonGrid, + IonHeader, + IonIcon, + IonPage, + IonRouterLink, + IonRow, + IonToolbar, +} from '@ionic/react'; +import styles from './Signup.module.scss'; + +import { arrowBack, shapesOutline } from 'ionicons/icons'; +import CustomField from '../components/CustomField'; +import { useSignupFields } from '../data/fields'; +import { Action } from '../components/Action'; +import { Wave } from '../components/Wave'; +import { useEffect, useState } from 'react'; +import { validateForm } from '../data/utils'; +import { useParams } from 'react-router'; + +interface FieldType { + id: string; + label: string; + input: { + props: any; + state: any; + }; +} + +interface ErrorType { + id: string; + message: string; +} + +const Signup = (): React.JSX.Element => { + const params = useParams(); + const fields: FieldType[] = useSignupFields(); + const [errors, setErrors] = useState(false); + + const createAccount = (): void => { + const errors = validateForm(fields); + setErrors(errors); + + if (!errors.length) { + // Submit your form here + } + }; + + useEffect(() => { + return () => { + fields.forEach((field) => field.input.state.reset('')); + setErrors(false); + }; + }, [params]); + + return ( + + + + + + + + + + + + + + + + + + + Sign up +
Lets get to know each other
+
+
+ + + + {fields.map((field) => { + return ; + })} + + + Create account + + + +
+
+ + + + + + + +
+ ); +}; + +export default Signup; diff --git a/03_source/mobile/src/pages/DemoReactLogin/store/AccountStore.tsx b/03_source/mobile/src/pages/DemoReactLogin/store/AccountStore.tsx new file mode 100644 index 0000000..acfc099 --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactLogin/store/AccountStore.tsx @@ -0,0 +1,19 @@ +import { Store } from "pullstate"; + +const AccountStore = new Store({ + + logged_in: false, + coffee_ids: [] +}); + +export default AccountStore; + +// export const addToCart = (coffeeID) => { + +// CartStore.update(s => { s.coffee_ids = [ ...s.coffee_ids, `${ parseInt(coffeeID) }` ]; }); +// } + +// export const removeFromCart = coffeeIndex => { + +// CartStore.update(s => { s.coffee_ids.splice(coffeeIndex, 1) }); +// } \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoReactLogin/store/Selectors.tsx b/03_source/mobile/src/pages/DemoReactLogin/store/Selectors.tsx new file mode 100644 index 0000000..f6cd2f9 --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactLogin/store/Selectors.tsx @@ -0,0 +1,13 @@ +import { createSelector } from 'reselect'; + +const getState = state => state; + +// General getters +// export const getCoffees = createSelector(getState, state => state.coffees); +// export const getOffers = createSelector(getState, state => state.offers); +// export const getCoffeeSizes = createSelector(getState, state => state.sizes); +// export const getCartCoffees = createSelector(getState, state => state.coffee_ids); +// export const getFavouriteCoffees = createSelector(getState, state => state.coffee_ids); + +// // More specific getters +// export const getCoffee = id => createSelector(getState, state => state.coffees.filter(c => parseInt(c.id) === parseInt(id))[0]); \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoReactLogin/store/index.tsx b/03_source/mobile/src/pages/DemoReactLogin/store/index.tsx new file mode 100644 index 0000000..d8e5a9b --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactLogin/store/index.tsx @@ -0,0 +1 @@ +export { default as AccountStore } from "./AccountStore"; \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoReactLogin/style.scss b/03_source/mobile/src/pages/DemoReactLogin/style.scss index 37c1e1a..e69de29 100644 --- a/03_source/mobile/src/pages/DemoReactLogin/style.scss +++ b/03_source/mobile/src/pages/DemoReactLogin/style.scss @@ -1,103 +0,0 @@ -#about-page { - ion-toolbar { - position: absolute; - - top: 0; - left: 0; - right: 0; - - --background: transparent; - --color: white; - } - - ion-toolbar ion-back-button, - ion-toolbar ion-button, - ion-toolbar ion-menu-button { - --color: white; - } - - .about-header { - position: relative; - - width: 100%; - height: 30%; - } - - .about-header .about-image { - position: absolute; - - top: 0; - left: 0; - bottom: 0; - right: 0; - - background-position: center; - background-size: cover; - background-repeat: no-repeat; - - opacity: 0; - - transition: opacity 500ms ease-in-out; - } - - .about-header .madison { - background-image: url('/assets/WeatherDemo/img/about/madison.jpg'); - } - - .about-header .austin { - background-image: url('/assets/WeatherDemo/img/about/austin.jpg'); - } - - .about-header .chicago { - background-image: url('/assets/WeatherDemo/img/about/chicago.jpg'); - } - - .about-header .seattle { - background-image: url('/assets/WeatherDemo/img/about/seattle.jpg'); - } - - .about-info { - position: relative; - margin-top: -10px; - border-radius: 10px; - background: var(--ion-background-color, #fff); - z-index: 2; // display rounded border above header image - } - - .about-info h3 { - margin-top: 0; - } - - .about-info ion-list { - padding-top: 0; - } - - .about-info p { - line-height: 130%; - - color: var(--ion-color-dark); - } - - .about-info ion-icon { - margin-inline-end: 32px; - } - - /* - * iOS Only - */ - - .ios .about-info { - --ion-padding: 19px; - } - - .ios .about-info h3 { - font-weight: 700; - } -} - -#date-input-popover { - --offset-y: -var(--ion-safe-area-bottom); - - --max-width: 90%; - --width: 336px; -} diff --git a/03_source/mobile/src/pages/DemoReactLogin/theme/variables.scss b/03_source/mobile/src/pages/DemoReactLogin/theme/variables.scss new file mode 100644 index 0000000..e69de29