diff --git a/03_source/mobile/src/pages/DemoInstagramClone/components/ExploreContainer.css b/03_source/mobile/src/pages/DemoInstagramClone/components/ExploreContainer.css
new file mode 100644
index 0000000..e99f514
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoInstagramClone/components/ExploreContainer.css
@@ -0,0 +1,24 @@
+.container {
+ text-align: center;
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 50%;
+ transform: translateY(-50%);
+}
+
+.container strong {
+ font-size: 20px;
+ line-height: 26px;
+}
+
+.container p {
+ font-size: 16px;
+ line-height: 22px;
+ color: #8c8c8c;
+ margin: 0;
+}
+
+.container a {
+ text-decoration: none;
+}
\ No newline at end of file
diff --git a/03_source/mobile/src/pages/DemoInstagramClone/components/ExploreContainer.tsx b/03_source/mobile/src/pages/DemoInstagramClone/components/ExploreContainer.tsx
new file mode 100644
index 0000000..396477f
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoInstagramClone/components/ExploreContainer.tsx
@@ -0,0 +1,21 @@
+import './ExploreContainer.css';
+
+const ExploreContainer = ({ name }): React.JSX.Element => {
+ return (
+
+ );
+};
+
+export default ExploreContainer;
diff --git a/03_source/mobile/src/pages/DemoInstagramClone/components/Feed.module.scss b/03_source/mobile/src/pages/DemoInstagramClone/components/Feed.module.scss
new file mode 100644
index 0000000..4854d16
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoInstagramClone/components/Feed.module.scss
@@ -0,0 +1,221 @@
+.postsContainer {
+
+ margin-top: 1.5rem;
+ margin-bottom: 1.5rem;
+}
+
+.postContainer {
+
+ display: flex;
+ flex-direction: column;
+ margin-top: 1rem;
+}
+
+.postProfile {
+
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ align-content: center;
+ padding-right: 0.75rem;
+ padding-left: 0.75rem;
+}
+
+.postProfile ion-router-link {
+
+ display: flex !important;
+ flex-direction: row !important;
+}
+
+.postProfileInfo ion-avatar {
+
+ height: 2.2rem;
+ width: 2.2rem;
+}
+
+.postProfileInfo p {
+
+ margin: 0;
+ padding: 0;
+ margin-left: 0.5rem;
+ font-weight: 500;
+ font-size: 0.9rem;
+ color: black;
+}
+
+.postProfileInfo {
+
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ align-content: center;
+}
+
+.postImage {
+
+ border-top: 1px solid rgb(216, 216, 216);
+ margin-top: 0.5rem;
+ height: 20rem;
+ width: 100%;
+}
+
+.postImageLike {
+
+ font-size: 10rem;
+ color: rgb(231, 231, 231);
+ position: absolute;
+ left: 32vmin;
+ margin-top: 20vmin;
+ display: none;
+}
+
+.postActionsContainer {
+
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ align-content: center;
+ padding-right: 0.75rem;
+ padding-left: 0.75rem;
+ margin-top: 0.5rem;
+}
+
+.postActions {
+
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+}
+
+.postActions ion-icon,
+.postBookmark ion-icon {
+
+ font-size: 1.5rem;
+}
+
+.postActions ion-icon:not(:first-child) {
+
+ padding-left: 0.7rem;
+}
+
+.postLikesContainer {
+
+ padding-left: 0.75rem;
+ margin-top: 0.5rem;
+}
+
+.postLikesContainer p {
+
+ margin: 0;
+ padding: 0;
+ font-weight: 200 !important;
+ font-size: 0.8rem;
+}
+
+.postLikedName {
+
+ font-weight: 600;
+}
+
+.postCaption {
+
+ padding-left: 0.75rem;
+ padding-right: 0.75rem;
+ margin-top: 0.3rem;
+}
+
+.postCaption p {
+
+ margin: 0;
+ padding: 0;
+ font-weight: 200 !important;
+ font-size: 0.8rem;
+}
+
+.postName {
+
+ color: black !important;
+ font-weight: 600 !important;
+}
+
+.postName ion-router-link {
+
+ color: black;
+}
+
+.postComments {
+
+ padding-left: 0.75rem;
+ padding-right: 0.75rem;
+ margin-top: 0.5rem;
+}
+
+.postComments p {
+
+ margin: 0;
+ padding: 0;
+ color:rgb(175, 175, 175);
+ font-size: 0.8rem;
+}
+
+.postAddComment {
+
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ align-content: center;
+ padding-left: 0.75rem;
+ padding-right: 0.75rem;
+}
+
+.postAddCommentProfile {
+
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-content: center;
+ align-items: center;
+ margin-top: 0.5rem;
+}
+
+.postAddCommentProfile ion-avatar {
+
+ height: 1.9rem;
+ width: 1.9rem;
+}
+
+.postAddCommentProfile p {
+
+ padding-left: 0.75rem;
+ font-size: 0.8rem;
+ color: rgb(175, 175, 175);
+}
+
+.postAddCommentActions {
+
+
+}
+
+.postAddCommentActions ion-icon {
+
+ padding-left: 0.5rem;
+}
+
+.postTime {
+
+ padding-left: 0.75rem;
+ padding-right: 0.75rem;
+ margin-top: 0rem;
+}
+
+.postTime p {
+
+ margin: 0;
+ padding: 0;
+ color: rgb(175, 175, 175);
+ font-size: 0.6rem;
+}
\ No newline at end of file
diff --git a/03_source/mobile/src/pages/DemoInstagramClone/components/Feed.tsx b/03_source/mobile/src/pages/DemoInstagramClone/components/Feed.tsx
new file mode 100644
index 0000000..dbcfeff
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoInstagramClone/components/Feed.tsx
@@ -0,0 +1,129 @@
+import { IonAvatar, IonIcon, IonRouterLink } from '@ionic/react';
+import {
+ addCircleOutline,
+ bookmarkOutline,
+ chatbubbleOutline,
+ ellipsisVertical,
+ heart,
+ heartOutline,
+ paperPlaneOutline,
+} from 'ionicons/icons';
+import { likePost } from '../pages/PostStore';
+import { ProfilesStore } from '../pages/ProfilesStore';
+import { ProfileStore } from '../pages/ProfileStore';
+import styles from './Feed.module.scss';
+
+const Feed = (props): React.JSX.Element => {
+ const { posts } = props;
+ const profile = ProfileStore.useState((s) => s.profile);
+ const profiles = ProfilesStore.useState((s) => s.profiles);
+
+ const addLike = (event, postID, liked) => {
+ likePost(event, postID, liked);
+ };
+
+ return (
+
+ {posts.map((post, index) => {
+ const postProfile = profiles.filter((p) => p.id === post.profile_id)[0];
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {postProfile.username}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ addLike(e, post.id, post.liked)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ Liked by alanmontgomery and{' '}
+ 2 others
+
+
+
+
+
+
+
+ {postProfile.username}
+
+ {' '}
+ {post.caption}
+
+
+
+
+
View all {post.comments.length} comments
+
+
+
+
+
+
+
+
Add a comment...
+
+
+
+
+
+
+
+
+
+
+ );
+ })}
+
+ );
+};
+
+export default Feed;
diff --git a/03_source/mobile/src/pages/DemoInstagramClone/components/Stories.module.scss b/03_source/mobile/src/pages/DemoInstagramClone/components/Stories.module.scss
new file mode 100644
index 0000000..aeb99cb
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoInstagramClone/components/Stories.module.scss
@@ -0,0 +1,98 @@
+$border: linear-gradient(to bottom, #d82b7e, #f57939);
+
+.stories {
+
+ height: fit-content;
+ margin-top: -0.7rem;
+}
+
+.storiesContainer {
+
+ overflow-x: scroll;
+ overflow-y: hidden;
+ -webkit-overflow-scrolling: touch;
+ display: flex;
+ flex-direction: row;
+ width: 100%;
+}
+
+.storiesContainer::-webkit-scrollbar {
+
+ display: none;
+}
+
+.story,
+.yourStory {
+
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ align-content: center;
+ margin: 0 auto;
+ width: 4rem !important;
+ margin-left: 1rem;
+}
+
+.story:first-child,
+.yourStory:first-child {
+
+ margin-left: 0.75rem;
+}
+
+.story p,
+.yourStory p {
+
+ text-align: center;
+ margin: 0;
+ padding: 0;
+ margin-top: 0.2rem;
+ color: rgb(95, 95, 95);
+ font-size: 0.7rem;
+ font-weight: 400;
+ width: 120%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ position: relative;
+ margin-top: 5rem;
+}
+
+.story img,
+.yourStory img {
+
+ height: 3.5rem !important;
+ width: 3.5rem !important;
+ position: absolute;
+ border-radius: 500px;
+ background: $border;
+ padding: 0.1rem;
+}
+
+.yourStory img {
+
+ background:rgb(214, 214, 214);
+}
+
+.storyAdd {
+
+ position: absolute;
+ color: white;
+ background-color: var(--ion-color-primary);
+ width: 1rem;
+ height: 1rem;
+ text-align: center;
+ margin: 0 auto;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ align-content: center;
+ justify-content: center;
+ border-radius: 500px;
+ border: 2px solid white;
+
+ bottom: 20px;
+ right: 0;
+ padding: 0.5rem;
+ font-size: 0.9rem;
+}
\ No newline at end of file
diff --git a/03_source/mobile/src/pages/DemoInstagramClone/components/Stories.tsx b/03_source/mobile/src/pages/DemoInstagramClone/components/Stories.tsx
new file mode 100644
index 0000000..c902b30
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoInstagramClone/components/Stories.tsx
@@ -0,0 +1,27 @@
+import { IonCol, IonRouterLink, IonRow } from '@ionic/react';
+import styles from './Stories.module.scss';
+
+const Stories = (props): React.JSX.Element => {
+ const { profiles } = props;
+
+ return (
+
+
+ {profiles.map((story, index) => {
+ return (
+
+
+ {index === 0 && +
}
+
+
+ {index === 0 ? 'Your story' : story.username}
+
+
+ );
+ })}
+
+
+ );
+};
+
+export default Stories;
diff --git a/03_source/mobile/src/pages/DemoInstagramClone/index.tsx b/03_source/mobile/src/pages/DemoInstagramClone/index.tsx
index c438e5b..ab4b29b 100644
--- a/03_source/mobile/src/pages/DemoInstagramClone/index.tsx
+++ b/03_source/mobile/src/pages/DemoInstagramClone/index.tsx
@@ -1,29 +1,61 @@
import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
-import { cloudOutline, searchOutline } from 'ionicons/icons';
+import { bagOutline, cloudOutline, home, playCircleOutline, searchOutline } from 'ionicons/icons';
import { Route, Redirect } from 'react-router';
-import Tab1 from './AppPages/Tab1';
-import Tab2 from './AppPages/Tab2';
+// import Tab1 from './AppPages/Tab1';
+// import Tab2 from './AppPages/Tab2';
import './style.scss';
+import Home from './pages/Home';
+import Tab2 from './pages/Tab2';
+import Tab3 from './pages/Tab3';
+
+import './theme/variables.scss';
+import { ProfileStore } from './pages/ProfileStore';
+import Profile from './pages/Profile';
+import MyProfile from './pages/MyProfile';
function DemoInstagramClone() {
+ const profile = ProfileStore.useState((s) => s.profile);
+
return (
-
+
+ {/*
+
+
+
+ */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- {/* */}
+ {/*
Dashboard
@@ -32,6 +64,25 @@ function DemoInstagramClone() {
Search
+ */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
diff --git a/03_source/mobile/src/pages/DemoInstagramClone/pages/Home.module.scss b/03_source/mobile/src/pages/DemoInstagramClone/pages/Home.module.scss
new file mode 100644
index 0000000..48a1bdb
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoInstagramClone/pages/Home.module.scss
@@ -0,0 +1,198 @@
+.postsContainer {
+
+ margin-top: 1.5rem;
+ margin-bottom: 1.5rem;
+}
+
+.postContainer {
+
+ display: flex;
+ flex-direction: column;
+ margin-top: 1rem;
+}
+
+.postProfile {
+
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ align-content: center;
+ padding-right: 0.75rem;
+ padding-left: 0.75rem;
+}
+
+.postProfileInfo ion-avatar {
+
+ height: 2.2rem;
+ width: 2.2rem;
+}
+
+.postProfileInfo p {
+
+ margin: 0;
+ padding: 0;
+ margin-left: 0.5rem;
+ font-weight: 500;
+ font-size: 0.9rem;
+}
+
+.postProfileInfo {
+
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ align-content: center;
+}
+
+.postImage {
+
+ border-top: 1px solid rgb(216, 216, 216);
+ margin-top: 0.5rem;
+ height: 20rem;
+ width: 100%;
+}
+
+.postActionsContainer {
+
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ align-content: center;
+ padding-right: 0.75rem;
+ padding-left: 0.75rem;
+ margin-top: 0.5rem;
+}
+
+.postActions {
+
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+}
+
+.postActions ion-icon,
+.postBookmark ion-icon {
+
+ font-size: 1.5rem;
+}
+
+.postActions ion-icon:not(:first-child) {
+
+ padding-left: 0.7rem;
+}
+
+.postLikesContainer {
+
+ padding-left: 0.75rem;
+ margin-top: 0.5rem;
+}
+
+.postLikesContainer p {
+
+ margin: 0;
+ padding: 0;
+ font-weight: 200 !important;
+ font-size: 0.8rem;
+}
+
+.postLikedName {
+
+ font-weight: 600;
+}
+
+.postCaption {
+
+ padding-left: 0.75rem;
+ padding-right: 0.75rem;
+ margin-top: 0.3rem;
+}
+
+.postCaption p {
+
+ margin: 0;
+ padding: 0;
+ font-weight: 200 !important;
+ font-size: 0.8rem;
+}
+
+.postName {
+
+ font-weight: 600 !important;
+}
+
+.postComments {
+
+ padding-left: 0.75rem;
+ padding-right: 0.75rem;
+ margin-top: 0.5rem;
+}
+
+.postComments p {
+
+ margin: 0;
+ padding: 0;
+ color:rgb(175, 175, 175);
+ font-size: 0.8rem;
+}
+
+.postAddComment {
+
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ align-content: center;
+ padding-left: 0.75rem;
+ padding-right: 0.75rem;
+}
+
+.postAddCommentProfile {
+
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-content: center;
+ align-items: center;
+ margin-top: 0.5rem;
+}
+
+.postAddCommentProfile ion-avatar {
+
+ height: 1.9rem;
+ width: 1.9rem;
+}
+
+.postAddCommentProfile p {
+
+ padding-left: 0.75rem;
+ font-size: 0.8rem;
+ color: rgb(175, 175, 175);
+}
+
+.postAddCommentActions {
+
+
+}
+
+.postAddCommentActions ion-icon {
+
+ padding-left: 0.5rem;
+}
+
+.postTime {
+
+ padding-left: 0.75rem;
+ padding-right: 0.75rem;
+ margin-top: 0rem;
+}
+
+.postTime p {
+
+ margin: 0;
+ padding: 0;
+ color: rgb(175, 175, 175);
+ font-size: 0.6rem;
+}
\ No newline at end of file
diff --git a/03_source/mobile/src/pages/DemoInstagramClone/pages/Home.tsx b/03_source/mobile/src/pages/DemoInstagramClone/pages/Home.tsx
new file mode 100644
index 0000000..64f15f8
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoInstagramClone/pages/Home.tsx
@@ -0,0 +1,66 @@
+import {
+ IonButton,
+ IonButtons,
+ IonContent,
+ IonHeader,
+ IonIcon,
+ IonPage,
+ IonToolbar,
+ useIonRouter,
+} from '@ionic/react';
+import {
+ addCircleOutline,
+ chevronBackOutline,
+ heartOutline,
+ paperPlaneOutline,
+} from 'ionicons/icons';
+import Feed from '../components/Feed';
+import Stories from '../components/Stories';
+import { PostStore } from './PostStore';
+import { ProfilesStore } from './ProfilesStore';
+
+const Home = (): React.JSX.Element => {
+ const profiles = ProfilesStore.useState((s) => s.profiles);
+ const posts = PostStore.useState((s) => s.posts);
+
+ const router = useIonRouter();
+ function handleBackClick() {
+ router.goBack();
+ }
+
+ return (
+
+
+
+
+ handleBackClick()}>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Home;
diff --git a/03_source/mobile/src/pages/DemoInstagramClone/pages/MyProfile.tsx b/03_source/mobile/src/pages/DemoInstagramClone/pages/MyProfile.tsx
new file mode 100644
index 0000000..f643088
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoInstagramClone/pages/MyProfile.tsx
@@ -0,0 +1,160 @@
+import {
+ IonButton,
+ IonButtons,
+ IonCardSubtitle,
+ IonCardTitle,
+ IonCol,
+ IonContent,
+ IonGrid,
+ IonHeader,
+ IonIcon,
+ IonPage,
+ IonRow,
+ IonToolbar,
+ useIonViewWillEnter,
+} from '@ionic/react';
+import {
+ addCircleOutline,
+ bookmarksOutline,
+ chevronDown,
+ gridOutline,
+ menuOutline,
+} from 'ionicons/icons';
+import { useState } from 'react';
+import styles from './Profile.module.scss';
+
+import { ProfilesStore } from './ProfilesStore';
+import { ProfileStore } from './ProfileStore';
+
+const MyProfile = (): React.JSX.Element => {
+ const currentProfile = ProfileStore.useState((s) => s.profile);
+ const profiles = ProfilesStore.useState((s) => s.profiles);
+ const [profile, setProfile] = useState(false);
+
+ useIonViewWillEnter(() => {
+ const profileID = currentProfile.id;
+ const tempProfile = profiles.filter((p) => parseInt(p.id) === parseInt(profileID))[0];
+ setProfile(tempProfile);
+ });
+
+ return (
+
+
+
+
+
+ {profile.username}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {profile.posts && profile.posts.length}
+
+ Posts
+
+
+
+ {profile.followers}
+ Followers
+
+
+
+ {profile.following}
+ Following
+
+
+
+
+
+
+
+
+ {profile.firstname} {profile.surname}
+
+ {profile.title}
+ {profile.bio}
+
+ {profile.link}
+
+
+
+
+
+
+
+ Edit Profile
+
+
+
+
+
+ Promotions
+
+
+
+
+
+ Insights
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {profile.posts &&
+ profile.posts.map((post, index) => {
+ return (
+
+
+
+ );
+ })}
+
+
+
+ );
+};
+
+export default MyProfile;
diff --git a/03_source/mobile/src/pages/DemoInstagramClone/pages/PostStore.tsx b/03_source/mobile/src/pages/DemoInstagramClone/pages/PostStore.tsx
new file mode 100644
index 0000000..441b84b
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoInstagramClone/pages/PostStore.tsx
@@ -0,0 +1,139 @@
+import { Store } from 'pullstate';
+
+export const PostStore = new Store({
+ posts: [
+ {
+ id: 1,
+ image: 'https://ionic.io/img/ioniconf/ioniconf-open-graph.png',
+ caption: 'Ioniconf 2021! Register Now!',
+ likes: 73,
+ liked: false,
+ profile_id: 6,
+ time: '1 hour ago',
+ comments: [
+ {
+ profile_id: 3,
+ comment: 'Test',
+ },
+ ],
+ },
+
+ {
+ id: 2,
+ image:
+ 'https://creativetacos.com/wp-content/uploads/2019/08/Free-Chipper-Personal-Finance-App-Kit.jpg',
+ caption: 'Ionic React Hub! UI Components, Templates, Clones and more!',
+ likes: 73,
+ liked: true,
+ profile_id: 1,
+ time: '1 hour ago',
+ comments: [
+ {
+ profile_id: 3,
+ comment: 'Test',
+ },
+ {
+ profile_id: 3,
+ comment: 'Test',
+ },
+ {
+ profile_id: 3,
+ comment: 'Test',
+ },
+ ],
+ },
+
+ {
+ id: 3,
+ image: 'https://cdn.buttercms.com/AIcP6e8FRx6fgsKa7bvy',
+ caption: 'Join the first ever Ionic Event!',
+ likes: 73,
+ liked: false,
+ profile_id: 4,
+ time: '2 hours ago',
+ comments: [
+ {
+ profile_id: 1,
+ comment: 'Test',
+ },
+ {
+ profile_id: 2,
+ comment: 'Test',
+ },
+ ],
+ },
+
+ {
+ id: 4,
+ image: 'https://ionicframework.com/img/meta/ionic-framework-og.png',
+ caption: 'Build cross platform mobile apps with the Ionic Framework!',
+ likes: 73,
+ liked: false,
+ profile_id: 2,
+ time: '3 hours ago',
+ comments: [
+ {
+ profile_id: 1,
+ comment: 'Test',
+ },
+ {
+ profile_id: 2,
+ comment: 'Test',
+ },
+ ],
+ },
+ ],
+});
+
+export const likePost = (event, postID, liked) => {
+ event.target.classList.add('animate__heartBeat');
+
+ if (!liked) {
+ document.getElementById(`postLike_${postID}`).style.display = 'inline';
+ }
+
+ setTimeout(() => {
+ event.target.classList.remove('animate__heartBeat');
+ document.getElementById(`postLike_${postID}`).style.display = 'none';
+ }, 850);
+
+ PostStore.update((s) => {
+ s.posts.find((p, index) =>
+ parseInt(p.id) === parseInt(postID) ? (s.posts[index].liked = liked ? false : true) : false
+ );
+ });
+
+ if (liked) {
+ PostStore.update((s) => {
+ s.posts.find((p, index) =>
+ parseInt(p.id) === parseInt(postID)
+ ? (s.posts[index].likes = s.posts[index].likes++)
+ : false
+ );
+ });
+ } else {
+ PostStore.update((s) => {
+ s.posts.find((p, index) =>
+ parseInt(p.id) === parseInt(postID)
+ ? (s.posts[index].likes = s.posts[index].likes--)
+ : false
+ );
+ });
+ }
+};
+
+export const addPost = (newPost) => {
+ PostStore.update((s) => {
+ s.posts = [...s.posts, newPost];
+ });
+};
+
+export const addCommentToPost = (newComment, postID) => {
+ PostStore.update((s) => {
+ s.posts.find((p, index) =>
+ parseInt(p.id) === parseInt(postID)
+ ? (s.posts[index].comments = [...s.posts[index].comments, newComment])
+ : false
+ );
+ });
+};
diff --git a/03_source/mobile/src/pages/DemoInstagramClone/pages/Profile.module.scss b/03_source/mobile/src/pages/DemoInstagramClone/pages/Profile.module.scss
new file mode 100644
index 0000000..c749657
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoInstagramClone/pages/Profile.module.scss
@@ -0,0 +1,100 @@
+.username {
+
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-content: center;
+ align-items: center;
+ font-weight: 600;
+}
+
+.username ion-icon {
+
+ font-size: 0.8rem;
+ margin-left: 0.3rem;
+}
+
+.label {
+
+ color: black;
+ font-size: 0.7rem;
+ font-weight: 500;
+ font-family: Arial, Helvetica, sans-serif !important;
+ text-transform: lowercase;
+}
+
+.label::first-letter {
+
+ text-transform: uppercase;
+}
+
+.value {
+
+ font-size: 1rem;
+}
+
+.profileAvatar {
+
+ border-radius: 500px;
+ width: 5.5rem;
+ height: auto;
+}
+
+.profileInfo {
+
+ display: flex;
+ flex-direction: column;
+ padding-left: 0.75rem;
+ padding-right: 0.75rem;
+}
+
+.profileInfo p,
+.profileInfo a {
+
+ // padding: 0.1rem 0 0.1rem 0;
+ margin: 0;
+ font-size: 0.9rem;
+}
+
+.profileUsername {
+
+ font-weight: 600;
+}
+
+.profileTitle {
+
+ color: rgb(136, 136, 136);
+}
+
+.profileLink {
+
+ color: rgb(22, 60, 131);
+ text-decoration: none;
+}
+
+.profileActions ion-button {
+
+ height: 2.3rem;
+ --border-radius: 5px;
+ font-weight: 600;
+}
+
+.lightButton {
+
+ --color: rgb(65, 65, 65);
+ --color-activated: rgb(65, 65, 65);
+ --background-hover: white;
+ --background-focused: white;
+ --background-activated: white;
+ --border-color: rgb(231, 231, 231);
+ --border-width: 2px;
+ font-size: 0.8rem;
+}
+
+.postCol {
+
+ --ion-grid-column-padding: 0rem;
+ padding: 0.1rem;
+ padding-bottom: 0.01rem !important;
+ padding-top: 0.01rem !important;
+}
\ No newline at end of file
diff --git a/03_source/mobile/src/pages/DemoInstagramClone/pages/Profile.tsx b/03_source/mobile/src/pages/DemoInstagramClone/pages/Profile.tsx
new file mode 100644
index 0000000..aa07b55
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoInstagramClone/pages/Profile.tsx
@@ -0,0 +1,183 @@
+import {
+ IonBackButton,
+ IonButton,
+ IonButtons,
+ IonCardSubtitle,
+ IonCardTitle,
+ IonCol,
+ IonContent,
+ IonGrid,
+ IonHeader,
+ IonIcon,
+ IonPage,
+ IonRow,
+ IonToolbar,
+ useIonViewWillEnter,
+} from '@ionic/react';
+import {
+ addCircleOutline,
+ arrowBackOutline,
+ bookmarksOutline,
+ chevronDown,
+ ellipsisVertical,
+ gridOutline,
+ menuOutline,
+ personOutline,
+} from 'ionicons/icons';
+import { useState } from 'react';
+import { useParams } from 'react-router';
+import styles from './Profile.module.scss';
+
+import { ProfilesStore } from './ProfilesStore';
+import { ProfileStore } from './ProfileStore';
+
+const Profile = (): React.JSX.Element => {
+ const params = useParams();
+ const profiles = ProfilesStore.useState((s) => s.profiles);
+ const currentProfile = ProfileStore.useState((s) => s.profile);
+ const [profile, setProfile] = useState(false);
+
+ useIonViewWillEnter(() => {
+ const profileID = params.id;
+ const tempProfile = profiles.filter((p) => parseInt(p.id) === parseInt(profileID))[0];
+ setProfile(tempProfile);
+ });
+
+ return (
+
+
+
+
+ {profile.id === currentProfile.id ? (
+
+ {profile.username}
+
+
+ ) : (
+ <>
+
+
+ {profile.username}
+
+ >
+ )}
+
+
+
+ {profile.id === currentProfile.id ? (
+ <>
+
+
+
+
+
+
+ >
+ ) : (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {profile.posts && profile.posts.length}
+
+ Posts
+
+
+
+ {profile.followers}
+ Followers
+
+
+
+ {profile.following}
+ Following
+
+
+
+
+
+
+
+
+ {profile.firstname} {profile.surname}
+
+ {profile.title}
+ {profile.bio}
+
+ {profile.link}
+
+
+
+
+
+
+
+ Follow
+
+
+
+
+
+ Message
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {profile.posts &&
+ profile.posts.map((post, index) => {
+ return (
+
+
+
+ );
+ })}
+
+
+
+ );
+};
+
+export default Profile;
diff --git a/03_source/mobile/src/pages/DemoInstagramClone/pages/ProfileStore.tsx b/03_source/mobile/src/pages/DemoInstagramClone/pages/ProfileStore.tsx
new file mode 100644
index 0000000..42f4d58
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoInstagramClone/pages/ProfileStore.tsx
@@ -0,0 +1,20 @@
+import { Store } from 'pullstate';
+
+export const ProfileStore = new Store({
+ profile: {
+ id: 1,
+ firstname: 'Alan',
+ surname: 'Montgomery',
+ avatar: '/assets/alan.jpg',
+ followers: 0,
+ following: 0,
+ },
+ posts: [],
+ feed: [],
+});
+
+export const addProfilePost = (newPost) => {
+ ProfileStore.update((s) => {
+ s.posts = [...s.posts, newPost];
+ });
+};
diff --git a/03_source/mobile/src/pages/DemoInstagramClone/pages/ProfilesStore.tsx b/03_source/mobile/src/pages/DemoInstagramClone/pages/ProfilesStore.tsx
new file mode 100644
index 0000000..63813c5
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoInstagramClone/pages/ProfilesStore.tsx
@@ -0,0 +1,172 @@
+import { Store } from 'pullstate';
+
+export const ProfilesStore = new Store({
+ profiles: [
+ {
+ id: 1,
+ firstname: 'Alan',
+ surname: 'Montgomery',
+ username: 'alanmontgomery',
+ title: 'Mobile Team Lead',
+ bio: 'Full Stack 🤓 Mobile Team Lead/Senior React Dev',
+ link: 'alanmontgomery.co.uk',
+ avatar: '/assets/alan.jpg',
+ followers: '1,470',
+ following: '230',
+ posts: [
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ ],
+ },
+ {
+ id: 2,
+ firstname: 'Max',
+ surname: 'Lynch',
+ username: 'maxlynch',
+ title: 'CEO Ionic',
+ bio: 'Co-founder/CEO @ionicframework. Created @capacitorjs. Gamer. @ManUtd fan.',
+ link: 'maxlynch.com',
+ avatar: 'https://pbs.twimg.com/profile_images/1318970727173885953/bln98FNj_400x400.jpg',
+ followers: '21.1K',
+ following: '1,200',
+ posts: [{}, {}, {}, {}, {}, {}, {}, {}, {}],
+ },
+ {
+ id: 3,
+ firstname: 'Ben',
+ surname: 'Sperry',
+ username: 'bensperry',
+ title: 'CDO Ionic',
+ bio: 'Co-founder / CDO @ionicframework. Creator of @ionicons. Product designer. Pixel junkie. Forest explorer.',
+ link: 'bensperry.com',
+ avatar: 'https://pbs.twimg.com/profile_images/1328390491126308864/jHHgl5Dm_400x400.jpg',
+ followers: '800',
+ following: '700',
+ posts: [{}, {}, {}, {}, {}, {}, {}, {}, {}],
+ },
+ {
+ id: 4,
+ firstname: 'Matt',
+ surname: 'Netkow',
+ username: 'mattnetkow',
+ title: 'Head of Product Marketing',
+ bio: 'I help web developers build cross-platform Web Native apps. @IonicFramework: Head of Product Marketing',
+ link: 'webnative.tech',
+ avatar: 'https://pbs.twimg.com/profile_images/1323383930150621187/GKc0nVzi_400x400.jpg',
+ followers: '1,200',
+ following: '900',
+ posts: [{}, {}, {}, {}, {}, {}, {}, {}, {}],
+ },
+ {
+ id: 5,
+ firstname: 'Liam',
+ surname: 'DeBeasi',
+ username: 'liamdebeasi',
+ title: 'Software Engineer',
+ bio: 'Software Engineer at @ionicframework',
+ link: 'liamdebeasi.com',
+ avatar: 'https://pbs.twimg.com/profile_images/1105953692669366273/ZNK4lRAJ_400x400.jpg',
+ followers: '871',
+ following: '510',
+ posts: [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}],
+ },
+ {
+ id: 6,
+ firstname: 'Mike',
+ surname: 'Hartington',
+ username: 'mikehartington',
+ title: 'Senior Dev Rel',
+ bio: 'Google Developer Expert. Mediocre at best. he/him. npx mhartington',
+ link: 'mhartington.io',
+ avatar: 'https://pbs.twimg.com/profile_images/1084993841898446849/DJ8XtR6L_400x400.jpg',
+ followers: '12.3K',
+ following: '2,200',
+ posts: [
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ ],
+ },
+ {
+ id: 7,
+ firstname: 'Adam',
+ surname: 'Bradley',
+ username: 'adambradley',
+ title: 'Director of Technology',
+ bio: 'Proud dad, husband, veteran & dogs best friend. Typos are my own',
+ link: 'ionicframework.com',
+ avatar: 'https://pbs.twimg.com/profile_images/909075942320025600/hfYqicUk_400x400.jpg',
+ followers: '613',
+ following: '571',
+ posts: [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}],
+ },
+ {
+ id: 8,
+ firstname: 'Brody',
+ surname: 'Kidd',
+ username: 'brodykidd',
+ title: 'Enterprise Account Manager',
+ bio: 'Enterprise Account Manager | @ionicframework | @getcapacitor | @stenciljs',
+ link: 'ionicframework.com',
+ avatar: 'https://pbs.twimg.com/profile_images/477539679567228928/JObyaUW__400x400.jpeg',
+ followers: '677',
+ following: '219',
+ posts: [{}, {}, {}, {}, {}, {}, {}],
+ },
+ ],
+});
+
+export const addProfilePost = (newPost) => {
+ ProfilesStore.update((s) => {
+ s.posts = [...s.posts, newPost];
+ });
+};
diff --git a/03_source/mobile/src/pages/DemoInstagramClone/pages/Tab1.css b/03_source/mobile/src/pages/DemoInstagramClone/pages/Tab1.css
new file mode 100644
index 0000000..e69de29
diff --git a/03_source/mobile/src/pages/DemoInstagramClone/pages/Tab2.css b/03_source/mobile/src/pages/DemoInstagramClone/pages/Tab2.css
new file mode 100644
index 0000000..e69de29
diff --git a/03_source/mobile/src/pages/DemoInstagramClone/pages/Tab2.tsx b/03_source/mobile/src/pages/DemoInstagramClone/pages/Tab2.tsx
new file mode 100644
index 0000000..a46ce0c
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoInstagramClone/pages/Tab2.tsx
@@ -0,0 +1,25 @@
+import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react';
+import ExploreContainer from '../components/ExploreContainer';
+import './Tab2.css';
+
+const Tab2 = (): React.JSX.Element => {
+ return (
+
+
+
+ Tab 2
+
+
+
+
+
+ Tab 2
+
+
+
+
+
+ );
+};
+
+export default Tab2;
diff --git a/03_source/mobile/src/pages/DemoInstagramClone/pages/Tab3.css b/03_source/mobile/src/pages/DemoInstagramClone/pages/Tab3.css
new file mode 100644
index 0000000..e69de29
diff --git a/03_source/mobile/src/pages/DemoInstagramClone/pages/Tab3.tsx b/03_source/mobile/src/pages/DemoInstagramClone/pages/Tab3.tsx
new file mode 100644
index 0000000..094a323
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoInstagramClone/pages/Tab3.tsx
@@ -0,0 +1,25 @@
+import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react';
+import ExploreContainer from '../components/ExploreContainer';
+import './Tab3.css';
+
+const Tab3 = (): React.JSX.Element => {
+ return (
+
+
+
+ Tab 3
+
+
+
+
+
+ Tab 3
+
+
+
+
+
+ );
+};
+
+export default Tab3;
diff --git a/03_source/mobile/src/pages/DemoInstagramClone/theme/variables.scss b/03_source/mobile/src/pages/DemoInstagramClone/theme/variables.scss
new file mode 100644
index 0000000..64b2b41
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoInstagramClone/theme/variables.scss
@@ -0,0 +1,126 @@
+.demo-instagram-clone {
+ /* Ionic Variables and Theming. For more info, please see:
+http://ionicframework.com/docs/theming/ */
+
+ * {
+ font-family: Arial, Helvetica, sans-serif !important;
+ scroll-behavior: smooth;
+ }
+
+ ::-webkit-scrollbar,
+ ::-webkit-scrollbar-thumb {
+ width: 0px;
+ }
+
+ /** Ionic CSS Variables **/
+ :root {
+ /** primary **/
+ --ion-color-primary: #3880ff;
+ --ion-color-primary-rgb: 56, 128, 255;
+ --ion-color-primary-contrast: #ffffff;
+ --ion-color-primary-contrast-rgb: 255, 255, 255;
+ --ion-color-primary-shade: #3171e0;
+ --ion-color-primary-tint: #4c8dff;
+
+ /** secondary **/
+ --ion-color-secondary: #3dc2ff;
+ --ion-color-secondary-rgb: 61, 194, 255;
+ --ion-color-secondary-contrast: #ffffff;
+ --ion-color-secondary-contrast-rgb: 255, 255, 255;
+ --ion-color-secondary-shade: #36abe0;
+ --ion-color-secondary-tint: #50c8ff;
+
+ /** tertiary **/
+ --ion-color-tertiary: #5260ff;
+ --ion-color-tertiary-rgb: 82, 96, 255;
+ --ion-color-tertiary-contrast: #ffffff;
+ --ion-color-tertiary-contrast-rgb: 255, 255, 255;
+ --ion-color-tertiary-shade: #4854e0;
+ --ion-color-tertiary-tint: #6370ff;
+
+ /** success **/
+ --ion-color-success: #2dd36f;
+ --ion-color-success-rgb: 45, 211, 111;
+ --ion-color-success-contrast: #ffffff;
+ --ion-color-success-contrast-rgb: 255, 255, 255;
+ --ion-color-success-shade: #28ba62;
+ --ion-color-success-tint: #42d77d;
+
+ /** warning **/
+ --ion-color-warning: #ffc409;
+ --ion-color-warning-rgb: 255, 196, 9;
+ --ion-color-warning-contrast: #000000;
+ --ion-color-warning-contrast-rgb: 0, 0, 0;
+ --ion-color-warning-shade: #e0ac08;
+ --ion-color-warning-tint: #ffca22;
+
+ /** danger **/
+ --ion-color-danger: #eb445a;
+ --ion-color-danger-rgb: 235, 68, 90;
+ --ion-color-danger-contrast: #ffffff;
+ --ion-color-danger-contrast-rgb: 255, 255, 255;
+ --ion-color-danger-shade: #cf3c4f;
+ --ion-color-danger-tint: #ed576b;
+
+ /** dark **/
+ --ion-color-dark: #222428;
+ --ion-color-dark-rgb: 34, 36, 40;
+ --ion-color-dark-contrast: #ffffff;
+ --ion-color-dark-contrast-rgb: 255, 255, 255;
+ --ion-color-dark-shade: #1e2023;
+ --ion-color-dark-tint: #383a3e;
+
+ /** medium **/
+ --ion-color-medium: #92949c;
+ --ion-color-medium-rgb: 146, 148, 156;
+ --ion-color-medium-contrast: #ffffff;
+ --ion-color-medium-contrast-rgb: 255, 255, 255;
+ --ion-color-medium-shade: #808289;
+ --ion-color-medium-tint: #9d9fa6;
+
+ /** light **/
+ --ion-color-light: #f4f5f8;
+ --ion-color-light-rgb: 244, 245, 248;
+ --ion-color-light-contrast: #000000;
+ --ion-color-light-contrast-rgb: 0, 0, 0;
+ --ion-color-light-shade: #d7d8da;
+ --ion-color-light-tint: #f5f6f9;
+ }
+
+ :root {
+ --ion-toolbar-background: white;
+ --ion-tab-bar-color: black;
+ --ion-tab-bar-color-selected: black;
+ --ion-tab-bar-border-color: rgb(235, 235, 235);
+ }
+
+ ion-tab-button ion-icon {
+ font-size: 1.6rem;
+ }
+
+ ion-tab-button img {
+ border-radius: 500px;
+ height: 1.8rem;
+ border: 1px solid black;
+ }
+
+ ion-tab-bar {
+ height: 3rem;
+ }
+
+ ion-toolbar {
+ --border-style: none;
+ --padding-start: 1rem;
+ --padding-end: 1rem;
+ --padding-top: 0.5rem;
+ }
+
+ ion-toolbar ion-icon {
+ font-weight: 900 !important;
+ font-size: 1.6rem;
+ }
+
+ ion-toolbar ion-button:not(:last-child) {
+ padding-right: 0.3rem;
+ }
+}