update,
@@ -0,0 +1,6 @@
|
||||
Chrome >=79
|
||||
ChromeAndroid >=79
|
||||
Firefox >=70
|
||||
Edge >=79
|
||||
Safari >=14
|
||||
iOS >=14
|
31
quotation3_e-shop/_lab/001_ionic-instagram-clone/.gitignore
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/dist
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
/.nx
|
||||
/.nx/cache
|
||||
/.vscode/*
|
||||
!/.vscode/extensions.json
|
||||
.idea
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from "cypress";
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
baseUrl: "http://localhost:5173",
|
||||
setupNodeEvents(on, config) {
|
||||
// implement node event listeners here
|
||||
},
|
||||
},
|
||||
});
|
@@ -0,0 +1,6 @@
|
||||
describe('My First Test', () => {
|
||||
it('Visits the app root url', () => {
|
||||
cy.visit('/')
|
||||
cy.contains('ion-content', 'Tab 1 page')
|
||||
})
|
||||
})
|
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
/// <reference types="cypress" />
|
||||
// ***********************************************
|
||||
// This example commands.ts shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
//
|
||||
// declare global {
|
||||
// namespace Cypress {
|
||||
// interface Chainable {
|
||||
// login(email: string, password: string): Chainable<void>
|
||||
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
||||
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
||||
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
|
||||
// }
|
||||
// }
|
||||
// }
|
@@ -0,0 +1,20 @@
|
||||
// ***********************************************************
|
||||
// This example support/e2e.ts is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
@@ -0,0 +1,30 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ['dist', 'cypress.config.ts'] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
},
|
||||
},
|
||||
)
|
30
quotation3_e-shop/_lab/001_ionic-instagram-clone/index.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Ionic App</title>
|
||||
|
||||
<base href="/" />
|
||||
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta name="msapplication-tap-highlight" content="no" />
|
||||
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
|
||||
<link rel="shortcut icon" type="image/png" href="/favicon.png" />
|
||||
|
||||
<!-- add to homescreen for ios -->
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-title" content="Ionic App" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "ionic-instagram-clone",
|
||||
"integrations": {},
|
||||
"type": "react-vite"
|
||||
}
|
11954
quotation3_e-shop/_lab/001_ionic-instagram-clone/package-lock.json
generated
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"name": "ionic-instagram-clone",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 0.0.0.0 --cors --force --clearScreen",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"test.e2e": "cypress run",
|
||||
"test.unit": "vitest",
|
||||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@capacitor/app": "7.0.0",
|
||||
"@capacitor/core": "7.0.1",
|
||||
"@capacitor/haptics": "7.0.0",
|
||||
"@capacitor/keyboard": "7.0.0",
|
||||
"@capacitor/status-bar": "7.0.0",
|
||||
"@ionic/react": "^8.0.0",
|
||||
"@ionic/react-router": "^8.0.0",
|
||||
"@types/react-router": "^5.1.20",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"ionicons": "^7.0.0",
|
||||
"pullstate": "^1.25.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router": "^5.3.4",
|
||||
"react-router-dom": "^5.3.4",
|
||||
"sass": "^1.85.1",
|
||||
"web-vitals": "^4.2.4",
|
||||
"workbox-background-sync": "^7.3.0",
|
||||
"workbox-broadcast-update": "^7.3.0",
|
||||
"workbox-cacheable-response": "^7.3.0",
|
||||
"workbox-core": "^7.3.0",
|
||||
"workbox-expiration": "^7.3.0",
|
||||
"workbox-google-analytics": "^7.3.0",
|
||||
"workbox-navigation-preload": "^7.3.0",
|
||||
"workbox-precaching": "^7.3.0",
|
||||
"workbox-range-requests": "^7.3.0",
|
||||
"workbox-routing": "^7.3.0",
|
||||
"workbox-strategies": "^7.3.0",
|
||||
"workbox-streams": "^7.3.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/cli": "7.0.1",
|
||||
"@testing-library/dom": ">=7.21.4",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@types/react": "^18.0.27",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"@vitejs/plugin-legacy": "^5.0.0",
|
||||
"@vitejs/plugin-react": "^4.0.1",
|
||||
"cypress": "^13.5.0",
|
||||
"eslint": "^9.20.1",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^5.1.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.19",
|
||||
"globals": "^15.15.0",
|
||||
"jsdom": "^22.1.0",
|
||||
"terser": "^5.4.0",
|
||||
"typescript": "^5.1.6",
|
||||
"typescript-eslint": "^8.24.0",
|
||||
"vite": "~5.2.0",
|
||||
"vitest": "^0.34.6"
|
||||
},
|
||||
"description": "An Ionic project"
|
||||
}
|
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 930 B |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 30 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="350" height="140" xmlns="http://www.w3.org/2000/svg" style="background:#f6f7f9"><g fill="none" fill-rule="evenodd"><path fill="#F04141" style="mix-blend-mode:multiply" d="M61.905-34.23l96.194 54.51-66.982 54.512L22 34.887z"/><circle fill="#10DC60" style="mix-blend-mode:multiply" cx="155.5" cy="135.5" r="57.5"/><path fill="#3880FF" style="mix-blend-mode:multiply" d="M208.538 9.513l84.417 15.392L223.93 93.93z"/><path fill="#FFCE00" style="mix-blend-mode:multiply" d="M268.625 106.557l46.332-26.75 46.332 26.75v53.5l-46.332 26.75-46.332-26.75z"/><circle fill="#7044FF" style="mix-blend-mode:multiply" cx="299.5" cy="9.5" r="38.5"/><rect fill="#11D3EA" style="mix-blend-mode:multiply" transform="rotate(-60 148.47 37.886)" x="143.372" y="-7.056" width="10.196" height="89.884" rx="5.098"/><path d="M-25.389 74.253l84.86 8.107c5.498.525 9.53 5.407 9.004 10.905a10 10 0 0 1-.057.477l-12.36 85.671a10.002 10.002 0 0 1-11.634 8.42l-86.351-15.226c-5.44-.959-9.07-6.145-8.112-11.584l13.851-78.551a10 10 0 0 1 10.799-8.219z" fill="#7044FF" style="mix-blend-mode:multiply"/><circle fill="#0CD1E8" style="mix-blend-mode:multiply" cx="273.5" cy="106.5" r="20.5"/></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 930 B |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"short_name": "Ionic App",
|
||||
"name": "My Ionic App",
|
||||
"icons": [
|
||||
{
|
||||
"src": "assets/icon/favicon.png",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "assets/icon/icon.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff"
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders without crashing', () => {
|
||||
const { baseElement } = render(<App />);
|
||||
expect(baseElement).toBeDefined();
|
||||
});
|
169
quotation3_e-shop/_lab/001_ionic-instagram-clone/src/App.tsx
Normal file
@@ -0,0 +1,169 @@
|
||||
import { Redirect, Route } from "react-router-dom";
|
||||
import {
|
||||
IonApp,
|
||||
IonIcon,
|
||||
IonLabel,
|
||||
IonRouterOutlet,
|
||||
IonTabBar,
|
||||
IonTabButton,
|
||||
IonTabs,
|
||||
setupIonicReact,
|
||||
} from "@ionic/react";
|
||||
import { IonReactRouter } from "@ionic/react-router";
|
||||
import {
|
||||
bagOutline,
|
||||
ellipse,
|
||||
home,
|
||||
playCircleOutline,
|
||||
searchOutline,
|
||||
square,
|
||||
triangle,
|
||||
} from "ionicons/icons";
|
||||
import Tab1 from "./pages/Tab1";
|
||||
import Tab2 from "./pages/Tab2";
|
||||
import Tab3 from "./pages/Tab3";
|
||||
|
||||
/* Core CSS required for Ionic components to work properly */
|
||||
import "@ionic/react/css/core.css";
|
||||
|
||||
/* Basic CSS for apps built with Ionic */
|
||||
import "@ionic/react/css/normalize.css";
|
||||
import "@ionic/react/css/structure.css";
|
||||
import "@ionic/react/css/typography.css";
|
||||
|
||||
/* Optional CSS utils that can be commented out */
|
||||
import "@ionic/react/css/padding.css";
|
||||
import "@ionic/react/css/float-elements.css";
|
||||
import "@ionic/react/css/text-alignment.css";
|
||||
import "@ionic/react/css/text-transformation.css";
|
||||
import "@ionic/react/css/flex-utils.css";
|
||||
import "@ionic/react/css/display.css";
|
||||
|
||||
/**
|
||||
* Ionic Dark Mode
|
||||
* -----------------------------------------------------
|
||||
* For more info, please see:
|
||||
* https://ionicframework.com/docs/theming/dark-mode
|
||||
*/
|
||||
|
||||
/* import '@ionic/react/css/palettes/dark.always.css'; */
|
||||
/* import '@ionic/react/css/palettes/dark.class.css'; */
|
||||
import "@ionic/react/css/palettes/dark.system.css";
|
||||
|
||||
/* Theme variables */
|
||||
import "./theme/variables.css";
|
||||
import Home from "./pages/Home";
|
||||
import { ProfileStore } from "./pages/ProfileStore";
|
||||
import MyProfile from "./pages/MyProfile";
|
||||
import Profile from "./pages/Profile";
|
||||
|
||||
setupIonicReact();
|
||||
|
||||
function App() {
|
||||
const profile = ProfileStore.useState((s) => s.profile);
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonApp>
|
||||
<IonReactRouter>
|
||||
<IonTabs>
|
||||
<IonRouterOutlet>
|
||||
<Route exact path="/home">
|
||||
<Home />
|
||||
</Route>
|
||||
|
||||
<Route exact path="/myprofile">
|
||||
<MyProfile />
|
||||
</Route>
|
||||
|
||||
<Route exact path="/profile/:id">
|
||||
<Profile />
|
||||
</Route>
|
||||
|
||||
<Route exact path="/tab1">
|
||||
<Tab1 />
|
||||
</Route>
|
||||
<Route exact path="/tab2">
|
||||
<Tab2 />
|
||||
</Route>
|
||||
<Route path="/tab3">
|
||||
<Tab3 />
|
||||
</Route>
|
||||
<Route exact path="/">
|
||||
<Redirect to="/home" />
|
||||
</Route>
|
||||
</IonRouterOutlet>
|
||||
{/* */}
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="home" href="/home">
|
||||
<IonIcon icon={home} />
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab2" href="/tab2">
|
||||
<IonIcon icon={searchOutline} />
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab3" href="/tab3">
|
||||
<IonIcon icon={playCircleOutline} />
|
||||
</IonTabButton>
|
||||
|
||||
<IonTabButton tab="tab4" href="/tab3">
|
||||
<IonIcon icon={bagOutline} />
|
||||
</IonTabButton>
|
||||
|
||||
<IonTabButton tab="tab5" href="/myprofile">
|
||||
<img alt="tab avatar" src={profile.avatar} />
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
</IonTabs>
|
||||
</IonReactRouter>
|
||||
</IonApp>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const App2: React.FC = () => (
|
||||
<IonApp>
|
||||
<IonReactRouter>
|
||||
<IonTabs>
|
||||
<IonRouterOutlet>
|
||||
<Route exact path="/home">
|
||||
<Home />
|
||||
</Route>
|
||||
<Route exact path="/tab1">
|
||||
<Tab1 />
|
||||
</Route>
|
||||
<Route exact path="/tab2">
|
||||
<Tab2 />
|
||||
</Route>
|
||||
<Route path="/tab3">
|
||||
<Tab3 />
|
||||
</Route>
|
||||
<Route exact path="/">
|
||||
<Redirect to="/tab1" />
|
||||
</Route>
|
||||
</IonRouterOutlet>
|
||||
{/* */}
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="home" href="/home">
|
||||
<IonIcon icon={home} />
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab2" href="/tab2">
|
||||
<IonIcon icon={searchOutline} />
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab3" href="/tab3">
|
||||
<IonIcon icon={playCircleOutline} />
|
||||
</IonTabButton>
|
||||
|
||||
<IonTabButton tab="tab4" href="/tab3">
|
||||
<IonIcon icon={bagOutline} />
|
||||
</IonTabButton>
|
||||
|
||||
<IonTabButton tab="tab5" href="/myprofile">
|
||||
<img alt="tab avatar" src={"profile.avatar"} />
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
</IonTabs>
|
||||
</IonReactRouter>
|
||||
</IonApp>
|
||||
);
|
||||
|
||||
export default App;
|
@@ -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;
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
import './ExploreContainer.css';
|
||||
|
||||
interface ContainerProps {
|
||||
name: string;
|
||||
}
|
||||
|
||||
const ExploreContainer: React.FC<ContainerProps> = ({ name }) => {
|
||||
return (
|
||||
<div className="container">
|
||||
<strong>{name}</strong>
|
||||
<p>Explore <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExploreContainer;
|
@@ -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;
|
||||
}
|
@@ -0,0 +1,130 @@
|
||||
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: any) => {
|
||||
const { posts } = props;
|
||||
const profile = ProfileStore.useState((s) => s.profile);
|
||||
const profiles = ProfilesStore.useState((s) => s.profiles);
|
||||
|
||||
const addLike = (event: any, postID: any, liked: any) => {
|
||||
likePost(event, postID, liked);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.postsContainer}>
|
||||
{posts.map((post: any, index: any) => {
|
||||
const postProfile = profiles.filter((p) => p.id === post.profile_id)[0];
|
||||
|
||||
return (
|
||||
<div key={index} className={styles.postContainer}>
|
||||
<div className={styles.postProfile}>
|
||||
<div className={styles.postProfileInfo}>
|
||||
<IonRouterLink routerLink={`/profile/${postProfile.id}`}>
|
||||
<IonAvatar>
|
||||
<img alt="post avatar" src={postProfile.avatar} />
|
||||
</IonAvatar>
|
||||
</IonRouterLink>
|
||||
|
||||
<IonRouterLink routerLink={`/profile/${postProfile.id}`}>
|
||||
<p>{postProfile.username}</p>
|
||||
</IonRouterLink>
|
||||
</div>
|
||||
|
||||
<div className={styles.postProfileMore}>
|
||||
<IonIcon icon={ellipsisVertical} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={styles.postImage}
|
||||
style={{
|
||||
backgroundImage: `url(${post.image})`,
|
||||
backgroundPosition: "center, center",
|
||||
backgroundSize: "cover",
|
||||
}}
|
||||
>
|
||||
<IonIcon
|
||||
id={`postLike_${post.id}`}
|
||||
className={`animated__animated animate__heartBeat ${styles.postImageLike}`}
|
||||
icon={heart}
|
||||
color="light"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.postActionsContainer}>
|
||||
<div className={styles.postActions}>
|
||||
<IonIcon
|
||||
className="animate__animated"
|
||||
color={post.liked ? "danger" : "dark"}
|
||||
icon={post.liked ? heart : heartOutline}
|
||||
onClick={(e) => addLike(e, post.id, post.liked)}
|
||||
/>
|
||||
<IonIcon icon={chatbubbleOutline} />
|
||||
<IonIcon icon={paperPlaneOutline} />
|
||||
</div>
|
||||
|
||||
<div className={styles.postBookmark}>
|
||||
<IonIcon icon={bookmarkOutline} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.postLikesContainer}>
|
||||
<p>
|
||||
Liked by{" "}
|
||||
<span className={styles.postLikedName}>alanmontgomery</span> and{" "}
|
||||
<span className={styles.postLikedName}>2 others</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.postCaption}>
|
||||
<p>
|
||||
<span className={styles.postName}>
|
||||
<IonRouterLink routerLink={`/profile/${postProfile.id}`}>
|
||||
{postProfile.username}
|
||||
</IonRouterLink>
|
||||
</span>{" "}
|
||||
{post.caption}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.postComments}>
|
||||
<p>View all {post.comments.length} comments</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.postAddComment}>
|
||||
<div className={styles.postAddCommentProfile}>
|
||||
<IonAvatar>
|
||||
<img alt="add comment avatar" src={profile.avatar} />
|
||||
</IonAvatar>
|
||||
<p className="ion-margin-left">Add a comment...</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.postAddCommentActions}>
|
||||
<IonIcon icon={heart} color="danger" />
|
||||
<IonIcon icon={addCircleOutline} color="medium" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.postTime}>
|
||||
<p>{post.time}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Feed;
|
@@ -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;
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
import { IonCol, IonRouterLink, IonRow } from "@ionic/react";
|
||||
import styles from "./Stories.module.scss";
|
||||
|
||||
const Stories = (props: any) => {
|
||||
const { profiles } = props;
|
||||
|
||||
return (
|
||||
<IonRow className={styles.stories}>
|
||||
<div className={styles.storiesContainer}>
|
||||
{profiles.map((story: any, index: any) => {
|
||||
return (
|
||||
<IonCol
|
||||
key={index}
|
||||
className={index === 0 ? styles.yourStory : styles.story}
|
||||
>
|
||||
<img alt="story avatar" src={story.avatar} />
|
||||
{index === 0 && <div className={styles.storyAdd}>+</div>}
|
||||
|
||||
<IonRouterLink routerLink={`/profile/${story.id}`}>
|
||||
<p>{index === 0 ? "Your story" : story.username}</p>
|
||||
</IonRouterLink>
|
||||
</IonCol>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</IonRow>
|
||||
);
|
||||
};
|
||||
|
||||
export default Stories;
|
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import App from './App';
|
||||
|
||||
const container = document.getElementById('root');
|
||||
const root = createRoot(container!);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
@@ -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;
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonPage,
|
||||
IonToolbar,
|
||||
} from "@ionic/react";
|
||||
import {
|
||||
addCircleOutline,
|
||||
heartOutline,
|
||||
paperPlaneOutline,
|
||||
} from "ionicons/icons";
|
||||
|
||||
import { PostStore } from "./PostStore";
|
||||
import { ProfilesStore } from "./ProfilesStore";
|
||||
import Stories from "../components/Stories";
|
||||
import Feed from "../components/Feed";
|
||||
|
||||
const Home: React.FC = () => {
|
||||
const profiles = ProfilesStore.useState((s) => s.profiles);
|
||||
const posts = PostStore.useState((s) => s.posts);
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<img
|
||||
alt="main logo"
|
||||
src="/assets/logo.png"
|
||||
style={{ width: "7rem" }}
|
||||
/>
|
||||
</IonButtons>
|
||||
|
||||
<IonButtons slot="end">
|
||||
<IonButton color="dark">
|
||||
<IonIcon icon={addCircleOutline} />
|
||||
</IonButton>
|
||||
<IonButton color="dark">
|
||||
<IonIcon icon={heartOutline} />
|
||||
</IonButton>
|
||||
|
||||
<IonButton color="dark">
|
||||
<IonIcon icon={paperPlaneOutline} />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen>
|
||||
<Stories profiles={profiles} />
|
||||
<Feed posts={posts} />
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
@@ -0,0 +1,193 @@
|
||||
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 = () => {
|
||||
const currentProfile = ProfileStore.useState((s) => s.profile);
|
||||
const profiles = ProfilesStore.useState((s) => s.profiles);
|
||||
const [profile, setProfile] = useState(false);
|
||||
|
||||
useIonViewWillEnter(() => {
|
||||
const profileID = "123321";
|
||||
const tempProfile = profiles.filter(
|
||||
(p: any) => parseInt(p.id) === parseInt(profileID)
|
||||
)[0];
|
||||
setProfile(true);
|
||||
});
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<p className={styles.username}>
|
||||
{"profile.username"}
|
||||
<IonIcon icon={chevronDown} />
|
||||
</p>
|
||||
</IonButtons>
|
||||
|
||||
<IonButtons slot="end">
|
||||
<IonButton color="dark">
|
||||
<IonIcon icon={addCircleOutline} />
|
||||
</IonButton>
|
||||
<IonButton color="dark">
|
||||
<IonIcon icon={menuOutline} />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen>
|
||||
<IonGrid>
|
||||
<IonRow className="ion-text-center ion-justify-content-between ion-align-self-center ion-align-items-center">
|
||||
<IonCol size="4">
|
||||
<img
|
||||
src={
|
||||
"https://random-image-pepebigotes.vercel.app/api/random-image"
|
||||
}
|
||||
alt="profile avatar"
|
||||
className={styles.profileAvatar}
|
||||
/>
|
||||
</IonCol>
|
||||
|
||||
<IonCol>
|
||||
<IonRow className="ion-text-center ion-justify-content-between ion-align-items-center ion-align-self-center ion-align">
|
||||
<IonCol size="4" className="ion-text-center">
|
||||
<IonCardTitle className={styles.value}>
|
||||
{'"profile.posts" && "profile.posts.length"'}
|
||||
</IonCardTitle>
|
||||
<IonCardSubtitle className={styles.label}>
|
||||
Posts
|
||||
</IonCardSubtitle>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="4" className="ion-text-center">
|
||||
<IonCardTitle className={styles.value}>
|
||||
{"profile.followers"}
|
||||
</IonCardTitle>
|
||||
<IonCardSubtitle className={styles.label}>
|
||||
Followers
|
||||
</IonCardSubtitle>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="4" className="ion-text-center">
|
||||
<IonCardTitle className={styles.value}>
|
||||
{"profile.following"}
|
||||
</IonCardTitle>
|
||||
<IonCardSubtitle className={styles.label}>
|
||||
Following
|
||||
</IonCardSubtitle>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow>
|
||||
<IonCol size="12" className={styles.profileInfo}>
|
||||
<p className={styles.profileUsername}>
|
||||
{"profile.firstname"} {"profile.surname"}
|
||||
</p>
|
||||
<p className={styles.profileTitle}>{"profile.title"}</p>
|
||||
<p className={styles.profileBio}>{"profile.bio"}</p>
|
||||
<a className={styles.profileLink} href={"profile.link"}>
|
||||
{"profile.link"}
|
||||
</a>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow className={styles.profileActions}>
|
||||
<IonCol size="4">
|
||||
<IonButton
|
||||
className={styles.lightButton}
|
||||
expand="block"
|
||||
fill="outline"
|
||||
>
|
||||
Edit Profile
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="4">
|
||||
<IonButton
|
||||
className={styles.lightButton}
|
||||
fill="outline"
|
||||
expand="block"
|
||||
>
|
||||
Promotions
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="4">
|
||||
<IonButton
|
||||
className={styles.lightButton}
|
||||
fill="outline"
|
||||
expand="block"
|
||||
>
|
||||
Insights
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
|
||||
<IonRow className="ion-text-center ion-justify-content-center ion-align-items-center ion-align-self-center">
|
||||
<IonCol
|
||||
size="6"
|
||||
className="ion-justify-content-center ion-align-items-center ion-align-self-center"
|
||||
style={{ borderBottom: "2px solid black", marginBottom: "2px" }}
|
||||
>
|
||||
<IonIcon style={{ fontSize: "1.5rem" }} icon={gridOutline} />
|
||||
</IonCol>
|
||||
|
||||
<IonCol
|
||||
size="6"
|
||||
className="ion-justify-content-center ion-align-items-center ion-align-self-center"
|
||||
>
|
||||
<IonIcon style={{ fontSize: "1.5rem" }} icon={bookmarksOutline} />
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow className="ion-no-padding ion-no-margin">
|
||||
{/* FIXME */}
|
||||
{
|
||||
// profile.posts
|
||||
[0, 1, 2, 3, 4].map((post: any, index: any) => {
|
||||
return (
|
||||
<IonCol className={styles.postCol} key={index} size="4">
|
||||
<img
|
||||
alt="post"
|
||||
src="https://random-image-pepebigotes.vercel.app/api/random-image"
|
||||
/>
|
||||
</IonCol>
|
||||
);
|
||||
})
|
||||
}
|
||||
</IonRow>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default MyProfile;
|
@@ -0,0 +1,141 @@
|
||||
import { Store } from "pullstate";
|
||||
|
||||
export const PostStore = new Store({
|
||||
posts: [
|
||||
{
|
||||
id: 1,
|
||||
image: "https://random-image-pepebigotes.vercel.app/api/random-image",
|
||||
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
|
||||
);
|
||||
});
|
||||
};
|
@@ -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;
|
||||
}
|
@@ -0,0 +1,177 @@
|
||||
import {
|
||||
IonBackButton,
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonCardSubtitle,
|
||||
IonCardTitle,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonGrid,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonViewWillEnter,
|
||||
} from "@ionic/react";
|
||||
import {
|
||||
addCircleOutline,
|
||||
arrowBackOutline,
|
||||
bookmarksOutline,
|
||||
chevronDown,
|
||||
ellipsisVertical,
|
||||
gridOutline,
|
||||
menuOutline,
|
||||
personOutline,
|
||||
} from "ionicons/icons";
|
||||
import ExploreContainer from "../components/ExploreContainer";
|
||||
import "./Tab1.css";
|
||||
import { useParams } from "react-router";
|
||||
import { ProfilesStore } from "./ProfilesStore";
|
||||
import { ProfileStore } from "./ProfileStore";
|
||||
import { useState } from "react";
|
||||
import styles from "./Profile.module.scss";
|
||||
|
||||
const Profile: React.FC = () => {
|
||||
const params = useParams();
|
||||
const profiles = ProfilesStore.useState((s) => s.profiles);
|
||||
const currentProfile = ProfileStore.useState((s) => s.profile);
|
||||
const [profile, setProfile] = useState<any>(false);
|
||||
|
||||
useIonViewWillEnter(() => {
|
||||
// FIXME
|
||||
// const profileID = params.id;
|
||||
const profileID = "123321";
|
||||
const tempProfile = profiles.filter(
|
||||
(p: any) => parseInt(p.id) === parseInt(profileID)
|
||||
)[0];
|
||||
setProfile(tempProfile);
|
||||
});
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Tab 1</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen>
|
||||
<IonGrid>
|
||||
<IonRow className="ion-text-center ion-justify-content-between ion-align-self-center ion-align-items-center">
|
||||
<IonCol size="4">
|
||||
<img
|
||||
src={profile.avatar}
|
||||
alt="profile avatar"
|
||||
className={styles.profileAvatar}
|
||||
/>
|
||||
</IonCol>
|
||||
|
||||
<IonCol>
|
||||
<IonRow className="ion-text-center ion-justify-content-between ion-align-items-center ion-align-self-center ion-align">
|
||||
<IonCol size="4" className="ion-text-center">
|
||||
<IonCardTitle className={styles.value}>
|
||||
{profile.posts && profile.posts.length}
|
||||
</IonCardTitle>
|
||||
<IonCardSubtitle className={styles.label}>
|
||||
Posts
|
||||
</IonCardSubtitle>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="4" className="ion-text-center">
|
||||
<IonCardTitle className={styles.value}>
|
||||
{profile.followers}
|
||||
</IonCardTitle>
|
||||
<IonCardSubtitle className={styles.label}>
|
||||
Followers
|
||||
</IonCardSubtitle>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="4" className="ion-text-center">
|
||||
<IonCardTitle className={styles.value}>
|
||||
{profile.following}
|
||||
</IonCardTitle>
|
||||
<IonCardSubtitle className={styles.label}>
|
||||
Following
|
||||
</IonCardSubtitle>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow>
|
||||
<IonCol size="12" className={styles.profileInfo}>
|
||||
<p className={styles.profileUsername}>
|
||||
{profile.firstname} {profile.surname}
|
||||
</p>
|
||||
<p className={styles.profileTitle}>{profile.title}</p>
|
||||
<p className={styles.profileBio}>{profile.bio}</p>
|
||||
<a className={styles.profileLink} href={profile.link}>
|
||||
{profile.link}
|
||||
</a>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow className={styles.profileActions}>
|
||||
<IonCol size="5">
|
||||
<IonButton expand="block" color="primary">
|
||||
Follow
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="5">
|
||||
<IonButton
|
||||
className={styles.lightButton}
|
||||
fill="outline"
|
||||
expand="block"
|
||||
>
|
||||
Message
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="2">
|
||||
<IonButton className={styles.lightButton} fill="outline">
|
||||
<IonIcon icon={chevronDown} />
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
|
||||
<IonRow className="ion-text-center ion-justify-content-center ion-align-items-center ion-align-self-center">
|
||||
<IonCol
|
||||
size="6"
|
||||
className="ion-justify-content-center ion-align-items-center ion-align-self-center"
|
||||
style={{ borderBottom: "2px solid black", marginBottom: "2px" }}
|
||||
>
|
||||
<IonIcon style={{ fontSize: "1.5rem" }} icon={gridOutline} />
|
||||
</IonCol>
|
||||
|
||||
<IonCol
|
||||
size="6"
|
||||
className="ion-justify-content-center ion-align-items-center ion-align-self-center"
|
||||
>
|
||||
<IonIcon style={{ fontSize: "1.5rem" }} icon={bookmarksOutline} />
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow className="ion-no-padding ion-no-margin">
|
||||
FIXME
|
||||
{/* {profile.posts &&
|
||||
profile.posts.map((post, index) => {
|
||||
return (
|
||||
<IonCol className={styles.postCol} key={index} size="4">
|
||||
<img
|
||||
alt="post"
|
||||
src="https://images.pexels.com/photos/699122/pexels-photo-699122.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260"
|
||||
/>
|
||||
</IonCol>
|
||||
);
|
||||
})} */}
|
||||
</IonRow>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Profile;
|
@@ -0,0 +1,21 @@
|
||||
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: any): void => {
|
||||
// FIXME:
|
||||
// ProfileStore.update((s) => {
|
||||
// s.posts = [...s.posts, newPost];
|
||||
// });
|
||||
};
|
@@ -0,0 +1,210 @@
|
||||
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://random-image-pepebigotes.vercel.app/api/random-image",
|
||||
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://random-image-pepebigotes.vercel.app/api/random-image",
|
||||
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://random-image-pepebigotes.vercel.app/api/random-image",
|
||||
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://random-image-pepebigotes.vercel.app/api/random-image",
|
||||
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://random-image-pepebigotes.vercel.app/api/random-image",
|
||||
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://random-image-pepebigotes.vercel.app/api/random-image",
|
||||
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://random-image-pepebigotes.vercel.app/api/random-image",
|
||||
followers: "677",
|
||||
following: "219",
|
||||
posts: [{}, {}, {}, {}, {}, {}, {}],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const addProfilePost = (newPost) => {
|
||||
ProfilesStore.update((s) => {
|
||||
s.posts = [...s.posts, newPost];
|
||||
});
|
||||
};
|
@@ -0,0 +1,25 @@
|
||||
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react';
|
||||
import ExploreContainer from '../components/ExploreContainer';
|
||||
import './Tab1.css';
|
||||
|
||||
const Tab1: React.FC = () => {
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Tab 1</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Tab 1</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<ExploreContainer name="Tab 1 page" />
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tab1;
|
@@ -0,0 +1,25 @@
|
||||
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react';
|
||||
import ExploreContainer from '../components/ExploreContainer';
|
||||
import './Tab2.css';
|
||||
|
||||
const Tab2: React.FC = () => {
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Tab 2</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Tab 2</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<ExploreContainer name="Tab 2 page" />
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tab2;
|
@@ -0,0 +1,31 @@
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from "@ionic/react";
|
||||
import ExploreContainer from "../components/ExploreContainer";
|
||||
import "./Tab3.css";
|
||||
|
||||
const Tab3: React.FC = () => {
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Tab 3</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Tab 3</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<ExploreContainer name="Tab 3 page" />
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tab3;
|
@@ -0,0 +1,14 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
|
||||
// Mock matchmedia
|
||||
window.matchMedia = window.matchMedia || function() {
|
||||
return {
|
||||
matches: false,
|
||||
addListener: function() {},
|
||||
removeListener: function() {}
|
||||
};
|
||||
};
|
@@ -0,0 +1,124 @@
|
||||
/* 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;
|
||||
}
|
1
quotation3_e-shop/_lab/001_ionic-instagram-clone/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
/// <reference types="vitest" />
|
||||
|
||||
import legacy from '@vitejs/plugin-legacy'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
legacy()
|
||||
],
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
setupFiles: './src/setupTests.ts',
|
||||
}
|
||||
})
|
9
quotation3_e-shop/_lab/NOTES.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# NOTES
|
||||
|
||||
其實班靚媽幫襯你有可能因為你夠直接,一搵就搵到你,
|
||||
|
||||
## ionic-instgram-clone
|
||||
|
||||
i stopped at the middle of `ionic-instgram-clone`,
|
||||
i found that the `ionic-instagram-clone` should be simple enough to re-structure/refactor to use ionic7
|
||||
i like the layout and want to clone the layout from this source
|
BIN
quotation3_e-shop/_lab/kiosko.1.0.0.zip
Normal file
29
quotation3_e-shop/_lab/new_project.sh
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
# ionic start --help
|
||||
ionic start "ionic-instagram-clone" tabs --capacitor --type react --no-git
|
||||
|
||||
cd ionic-instagram-clone
|
||||
|
||||
npm i --save pullstate
|
||||
npm i --save sass
|
||||
npm i --save "web-vitals"
|
||||
npm i --save "workbox-background-sync"
|
||||
npm i --save "workbox-broadcast-update"
|
||||
npm i --save "workbox-cacheable-response"
|
||||
npm i --save "workbox-core"
|
||||
npm i --save "workbox-expiration"
|
||||
npm i --save "workbox-google-analytics"
|
||||
npm i --save "workbox-navigation-preload"
|
||||
npm i --save "workbox-precaching"
|
||||
npm i --save "workbox-range-requests"
|
||||
npm i --save "workbox-routing"
|
||||
npm i --save "workbox-strategies"
|
||||
npm i --save "workbox-streams"
|
||||
|
||||
|
||||
cd ..
|
||||
|
||||
echo "done"
|
After Width: | Height: | Size: 204 KiB |
After Width: | Height: | Size: 1.9 MiB |
16
quotation3_e-shop/_lab/product_samples/product1/NOTES.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# PRODUCT1
|
||||
|
||||

|
||||
|
||||
📣📣預訂📣📣 台灣製造 台灣直送
|
||||
‼️2-3 星期到‼️
|
||||
|
||||
$68@1盒/ $130@2 盒
|
||||
1 盒 6 包
|
||||
4m+ bb 可以食‼️‼️‼️
|
||||
|
||||
★100%無農藥殘留、無添加香精、無添加鹽、無添加糖、無添加油、無麩質,全素可食。
|
||||
|
||||
★質地綿密、入口即化,從4個月以上寶寶至牙口不便銀髮族皆可食用,老少咸宜。
|
||||
★創意心型與條狀米菓,小手抓握一次一顆剛剛好。
|
||||
★內含6份貼心獨立小包裝,次次都吃的到最新鮮的產品。
|
BIN
quotation3_e-shop/_lab/product_samples/product2/shot1.jpeg
Normal file
After Width: | Height: | Size: 172 KiB |
BIN
quotation3_e-shop/_lab/product_samples/product2/shot2.jpeg
Normal file
After Width: | Height: | Size: 173 KiB |
After Width: | Height: | Size: 287 KiB |
After Width: | Height: | Size: 3.3 MiB |
24
quotation3_e-shop/_lab/product_samples/product3/NOTES.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# PRODUCT3
|
||||
|
||||
<!--  -->
|
||||
|
||||
<!--  -->
|
||||
📣📣台灣好物分享📣📣
|
||||
|
||||
依個真係真係勁大推比易敏感既bb試試
|
||||
|
||||
妹妹係去台灣之前下巴突然紅曬
|
||||
好似生濕疹甘 又r損曬
|
||||
|
||||
因為真係查極唔同既cream 都依然冇改善 我又唔想佢查藥膏
|
||||
所以去左台灣藥房搵‼️‼️‼️
|
||||
~~~~~
|
||||
|
||||
佢地推介左依款膏(英國製造)
|
||||
|
||||
❤️成份天然
|
||||
冇任何藥性成份
|
||||
|
||||
👉🏻👉🏻👉🏻蜂膠萃取物、甜杏仁油、蜂蠟、維他命E
|
||||
|
||||
取自蜜蜂為保護蜂巢所分泌的蜂膠,含有多重來自花粉產生的微量元素與維生素,可適度緩解與修護肌膚不適問題。*不含刺激成分,眼睛與臉部也可塗抹。
|
@@ -0,0 +1 @@
|
||||
代碼,類型,貨號,"GTIN, UPC, EAN, or ISBN",名稱,已發佈,是特色商品?,目錄的可見度,簡短內容說明,描述,折扣價開始日期,折扣價結束日期,稅金狀態,稅率類別,有庫存?,庫存,低庫存量,允許無庫存下單嗎?,單獨銷售?,"重量 (公斤)","長 (公分)","寬 (公分)","高 (公分)",允許客戶評論嗎?,購買備註,折扣價,原價,分類,標籤,運送類別,圖片,下載限制,下載點過期天數,上層,組合商品,追加銷售,交叉銷售,外部網址,按鈕文字,位置,Brands
|
|
1
quotation3_e-shop/_lab/wordpress-docker-compose/.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
github: nezhar
|
1
quotation3_e-shop/_lab/wordpress-docker-compose/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.env
|
21
quotation3_e-shop/_lab/wordpress-docker-compose/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 nezhar
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
159
quotation3_e-shop/_lab/wordpress-docker-compose/README.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# WPDC - WordPress Docker Compose
|
||||
|
||||
Easy WordPress development with Docker and Docker Compose.
|
||||
|
||||
With this project you can quickly run the following:
|
||||
|
||||
- [WordPress and WP CLI](https://hub.docker.com/_/wordpress/)
|
||||
- [phpMyAdmin](https://hub.docker.com/r/phpmyadmin/phpmyadmin/)
|
||||
- [MySQL](https://hub.docker.com/_/mysql/)
|
||||
|
||||
Contents:
|
||||
|
||||
- [Requirements](#requirements)
|
||||
- [Configuration](#configuration)
|
||||
- [Installation](#installation)
|
||||
- [Usage](#usage)
|
||||
|
||||
## Requirements
|
||||
|
||||
Make sure you have the latest versions of **Docker** and **Docker Compose** installed on your machine.
|
||||
|
||||
Clone this repository or copy the files from this repository into a new folder. In the **docker-compose.yml** file you may change the IP address (in case you run multiple containers) or the database from MySQL to MariaDB.
|
||||
|
||||
Make sure to [add your user to the `docker` group](https://docs.docker.com/install/linux/linux-postinstall/#manage-docker-as-a-non-root-user) when using Linux.
|
||||
|
||||
## Configuration
|
||||
|
||||
Copy the example environment into `.env`
|
||||
|
||||
```
|
||||
cp env.example .env
|
||||
```
|
||||
|
||||
Edit the `.env` file to change the default IP address, MySQL root password and WordPress database name.
|
||||
|
||||
## Installation
|
||||
|
||||
Open a terminal and `cd` to the folder in which `docker-compose.yml` is saved and run:
|
||||
|
||||
```
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
This creates two new folders next to your `docker-compose.yml` file.
|
||||
|
||||
* `wp-data` – used to store and restore database dumps
|
||||
* `wp-app` – the location of your WordPress application
|
||||
|
||||
The containers are now built and running. You should be able to access the WordPress installation with the configured IP in the browser address. By default it is `http://127.0.0.1`.
|
||||
|
||||
For convenience you may add a new entry into your hosts file.
|
||||
|
||||
## Usage
|
||||
|
||||
### Starting containers
|
||||
|
||||
You can start the containers with the `up` command in daemon mode (by adding `-d` as an argument) or by using the `start` command:
|
||||
|
||||
```
|
||||
docker-compose start
|
||||
```
|
||||
|
||||
### Stopping containers
|
||||
|
||||
```
|
||||
docker-compose stop
|
||||
```
|
||||
|
||||
### Removing containers
|
||||
|
||||
To stop and remove all the containers use the`down` command:
|
||||
|
||||
```
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
Use `-v` if you need to remove the database volume which is used to persist the database:
|
||||
|
||||
```
|
||||
docker-compose down -v
|
||||
```
|
||||
|
||||
### Project from existing source
|
||||
|
||||
Copy the `docker-compose.yml` file into a new directory. In the directory you create two folders:
|
||||
|
||||
* `wp-data` – here you add the database dump
|
||||
* `wp-app` – here you copy your existing WordPress code
|
||||
|
||||
You can now use the `up` command:
|
||||
|
||||
```
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
This will create the containers and populate the database with the given dump. You may set your host entry and change it in the database, or you simply overwrite it in `wp-config.php` by adding:
|
||||
|
||||
```
|
||||
define('WP_HOME','http://wp-app.local');
|
||||
define('WP_SITEURL','http://wp-app.local');
|
||||
```
|
||||
|
||||
### Creating database dumps
|
||||
|
||||
```
|
||||
./export.sh
|
||||
```
|
||||
|
||||
### Developing a Theme
|
||||
|
||||
Configure the volume to load the theme in the container in the `docker-compose.yml`:
|
||||
|
||||
```
|
||||
volumes:
|
||||
- ./theme-name/trunk/:/var/www/html/wp-content/themes/theme-name
|
||||
```
|
||||
|
||||
### Developing a Plugin
|
||||
|
||||
Configure the volume to load the plugin in the container in the `docker-compose.yml`:
|
||||
|
||||
```
|
||||
volumes:
|
||||
- ./plugin-name/trunk/:/var/www/html/wp-content/plugins/plugin-name
|
||||
```
|
||||
|
||||
### WP CLI
|
||||
|
||||
The docker compose configuration also provides a service for using the [WordPress CLI](https://developer.wordpress.org/cli/commands/).
|
||||
|
||||
Sample command to install WordPress:
|
||||
|
||||
```
|
||||
docker-compose run --rm wpcli core install --url=http://localhost --title=test --admin_user=admin --admin_email=test@example.com
|
||||
```
|
||||
|
||||
Or to list installed plugins:
|
||||
|
||||
```
|
||||
docker-compose run --rm wpcli plugin list
|
||||
```
|
||||
|
||||
For an easier usage you may consider adding an alias for the CLI:
|
||||
|
||||
```
|
||||
alias wp="docker-compose run --rm wpcli"
|
||||
```
|
||||
|
||||
This way you can use the CLI command above as follows:
|
||||
|
||||
```
|
||||
wp plugin list
|
||||
```
|
||||
|
||||
### phpMyAdmin
|
||||
|
||||
You can also visit `http://127.0.0.1:8080` to access phpMyAdmin after starting the containers.
|
||||
|
||||
The default username is `root`, and the password is the same as supplied in the `.env` file.
|
@@ -0,0 +1,69 @@
|
||||
|
||||
services:
|
||||
wp:
|
||||
image: wordpress:latest # https://hub.docker.com/_/wordpress/
|
||||
ports:
|
||||
- ${IP}:${PORT}:80 # change ip if required
|
||||
volumes:
|
||||
- ./volumes/config/wp_php.ini:/usr/local/etc/php/conf.d/conf.ini
|
||||
- ./volumes/wp-app:/var/www/html # Full wordpress project
|
||||
- ./src/plugin-helloworld/trunk/:/var/www/html/wp-content/plugins/plugin-helloworld # Plugin development
|
||||
- ./src/theme-helloworld/trunk/:/var/www/html/wp-content/themes/theme-helloworld # Theme development
|
||||
environment:
|
||||
WORDPRESS_DB_HOST: db
|
||||
WORDPRESS_DB_NAME: "${DB_NAME}"
|
||||
WORDPRESS_DB_USER: root
|
||||
WORDPRESS_DB_PASSWORD: "${DB_ROOT_PASSWORD}"
|
||||
depends_on:
|
||||
- db
|
||||
links:
|
||||
- db
|
||||
|
||||
wpcli:
|
||||
image: wordpress:cli
|
||||
volumes:
|
||||
- ./volumes/config/wp_php.ini:/usr/local/etc/php/conf.d/conf.ini
|
||||
- ./volumes/wp-app:/var/www/html
|
||||
environment:
|
||||
WORDPRESS_DB_HOST: db
|
||||
WORDPRESS_DB_NAME: "${DB_NAME}"
|
||||
WORDPRESS_DB_USER: root
|
||||
WORDPRESS_DB_PASSWORD: "${DB_ROOT_PASSWORD}"
|
||||
depends_on:
|
||||
- db
|
||||
- wp
|
||||
|
||||
pma:
|
||||
image: phpmyadmin:latest # https://hub.docker.com/_/phpmyadmin
|
||||
environment:
|
||||
# https://docs.phpmyadmin.net/en/latest/setup.html#docker-environment-variables
|
||||
PMA_HOST: db
|
||||
PMA_PORT: 3306
|
||||
MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASSWORD}"
|
||||
UPLOAD_LIMIT: 50M
|
||||
ports:
|
||||
- ${IP}:8080:80
|
||||
links:
|
||||
- db:db
|
||||
volumes:
|
||||
- ./volumes/config/pma_php.ini:/usr/local/etc/php/conf.d/conf.ini
|
||||
- ./volumes/config/pma_config.php:/etc/phpmyadmin/config.user.inc.php
|
||||
|
||||
db:
|
||||
image: mysql:latest # https://hub.docker.com/_/mysql/ - or mariadb https://hub.docker.com/_/mariadb
|
||||
# platform: linux/x86_64 # Uncomment if your machine is running on arm (ex: Apple Silicon processor)
|
||||
ports:
|
||||
- ${IP}:3306:3306 # change ip if required
|
||||
command: [
|
||||
'--character-set-server=utf8mb4',
|
||||
'--collation-server=utf8mb4_unicode_ci'
|
||||
]
|
||||
volumes:
|
||||
- ./volumes/wp-data:/docker-entrypoint-initdb.d
|
||||
- db_data:/var/lib/mysql
|
||||
environment:
|
||||
MYSQL_DATABASE: "${DB_NAME}"
|
||||
MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASSWORD}"
|
||||
|
||||
volumes:
|
||||
db_data:
|
@@ -0,0 +1,4 @@
|
||||
IP=127.0.0.1
|
||||
PORT=80
|
||||
DB_ROOT_PASSWORD=password
|
||||
DB_NAME=wordpress
|
14
quotation3_e-shop/_lab/wordpress-docker-compose/export.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
_os="`uname`"
|
||||
_now=$(date +"%m_%d_%Y")
|
||||
_file="wp-data/data_$_now.sql"
|
||||
|
||||
# Export dump
|
||||
EXPORT_COMMAND='exec mysqldump "$MYSQL_DATABASE" -uroot -p"$MYSQL_ROOT_PASSWORD"'
|
||||
docker-compose exec db sh -c "$EXPORT_COMMAND" > $_file
|
||||
|
||||
if [[ $_os == "Darwin"* ]] ; then
|
||||
sed -i '.bak' 1,1d $_file
|
||||
else
|
||||
sed -i 1,1d $_file # Removes the password warning from the file
|
||||
fi
|
14
quotation3_e-shop/_lab/wordpress-docker-compose/scripts/dc_up.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
docker compose kill
|
||||
docker compose down
|
||||
|
||||
sleep 1
|
||||
|
||||
docker compose up -d
|
||||
|
||||
docker compose logs -f
|
||||
|
||||
echo "done"
|
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* Kiosko functions and definitions
|
||||
*
|
||||
* @link https://developer.wordpress.org/themes/basics/theme-functions/
|
||||
*
|
||||
* @package Kiosko
|
||||
* @since Kiosko 1.0
|
||||
*/
|
||||
|
||||
|
||||
if ( ! function_exists( 'kiosko_support' ) ) :
|
||||
|
||||
/**
|
||||
* Sets up theme defaults and registers support for various WordPress features.
|
||||
*
|
||||
* @since Kiosko 1.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function kiosko_support() {
|
||||
|
||||
// Enqueue editor styles.
|
||||
add_editor_style( 'style.css' );
|
||||
|
||||
// Make theme available for translation.
|
||||
load_theme_textdomain( 'kiosko' );
|
||||
}
|
||||
|
||||
endif;
|
||||
|
||||
add_action( 'after_setup_theme', 'kiosko_support' );
|
||||
|
||||
if ( ! function_exists( 'kiosko_styles' ) ) :
|
||||
|
||||
/**
|
||||
* Enqueue styles.
|
||||
*
|
||||
* @since Kiosko 1.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function kiosko_styles() {
|
||||
// Register theme stylesheet.
|
||||
$theme_version = wp_get_theme()->get( 'Version' );
|
||||
|
||||
$version_string = is_string( $theme_version ) ? $theme_version : false;
|
||||
wp_register_style(
|
||||
'kiosko-style',
|
||||
get_template_directory_uri() . '/style.css',
|
||||
array(),
|
||||
$version_string
|
||||
);
|
||||
|
||||
// Enqueue theme stylesheet.
|
||||
wp_enqueue_style( 'kiosko-style' );
|
||||
|
||||
// Enqueue the additional stylesheet for Twenty Twenty Three.
|
||||
if ( class_exists( 'WooCommerce' ) && function_exists( 'WC' ) && defined( 'WC_ABSPATH' ) && file_exists( WC_ABSPATH . 'assets/css/twenty-twenty-three.css' ) ) {
|
||||
|
||||
wp_enqueue_style( 'woocommerce-twenty-twenty-three', WC()->plugin_url() . '/assets/css/twenty-twenty-three.css' );
|
||||
|
||||
wp_dequeue_style( 'woocommerce-general' );
|
||||
}
|
||||
}
|
||||
|
||||
endif;
|
||||
|
||||
add_action( 'wp_enqueue_scripts', 'kiosko_styles' );
|
||||
|
||||
if ( ! function_exists( 'kiosko_woocommerce_init' ) ) :
|
||||
/**
|
||||
* Initialize WooCommerce compatibility.
|
||||
*
|
||||
* @since Kiosko 1.0
|
||||
* @return void
|
||||
*/
|
||||
function kiosko_woocommerce_init() {
|
||||
if ( ! class_exists( 'WooCommerce' ) || ! defined( 'WC_ABSPATH' ) || ! file_exists( WC_ABSPATH . 'includes/theme-support/class-wc-twenty-twenty-three.php' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load WooCommerce compatibility file if WooCommerce is loaded and the compatibility file exists.
|
||||
include_once WC_ABSPATH . 'includes/theme-support/class-wc-twenty-twenty-three.php';
|
||||
}
|
||||
endif;
|
||||
|
||||
add_action( 'after_setup_theme', 'kiosko_woocommerce_init' );
|
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
/**
|
||||
* Title: A 404 page
|
||||
* Slug: kiosko/404
|
||||
* Categories: text
|
||||
*/
|
||||
|
||||
declare( strict_types = 1 );
|
||||
?>
|
||||
|
||||
<!-- wp:heading {"level":1,"style":{"typography":{"lineHeight":1.1}},"textColor":"contrast","className":"wp-block-heading","fontSize":"large","anchor":"oops-that-page-can-t-be-found"} -->
|
||||
<h1 class="wp-block-heading has-contrast-color has-text-color has-large-font-size" id="oops-that-page-can-t-be-found" style="line-height:1.1"><?php echo esc_html__( 'Oops! That page can’t be found.', 'kiosko' ); ?></h1>
|
||||
<!-- /wp:heading -->
|
||||
|
||||
<!-- wp:paragraph {"textColor":"contrast","className":"has-contrast-color has-text-color"} -->
|
||||
<p class="has-contrast-color has-text-color has-contrast-color"><?php echo esc_html__( 'It looks like nothing was found at this location. Maybe try a search?', 'kiosko' ); ?></p>
|
||||
<!-- /wp:paragraph -->
|
||||
|
||||
<!-- wp:search {"label":"","showLabel":false,"placeholder":"<?php echo esc_html_x( 'Search...', 'This is a placeholder text in a search field', 'kiosko' ); ?>","buttonText":"Search"} /-->
|
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* Title: Comments
|
||||
* Slug: kiosko/comments
|
||||
* Categories: text
|
||||
*/
|
||||
|
||||
declare( strict_types = 1 );
|
||||
?>
|
||||
|
||||
<!-- wp:comments {"className":"wp-block-comments-query-loop"} -->
|
||||
<div class="wp-block-comments wp-block-comments-query-loop">
|
||||
<!-- wp:comments-title {"level":3} /-->
|
||||
|
||||
<!-- wp:comment-template -->
|
||||
<!-- wp:group {"style":{"spacing":{"margin":{"top":"0","bottom":"var:preset|spacing|50"}}}} -->
|
||||
<div class="wp-block-group" style="margin-top:0;margin-bottom:var(--wp--preset--spacing--50)">
|
||||
<!-- wp:group {"layout":{"type":"flex","flexWrap":"nowrap"},"style":{"spacing":{"blockGap":"0.5em"}}} -->
|
||||
<div class="wp-block-group">
|
||||
<!-- wp:avatar {"size":40} /-->
|
||||
|
||||
<!-- wp:group -->
|
||||
<div class="wp-block-group">
|
||||
<!-- wp:comment-author-name /-->
|
||||
|
||||
<!-- wp:group {"layout":{"type":"flex"},"style":{"spacing":{"margin":{"top":"0px","bottom":"0px"},"blockGap":"0.875rem"}}} -->
|
||||
<div class="wp-block-group" style="margin-top:0px;margin-bottom:0px">
|
||||
<!-- wp:comment-date {"format":"F j, Y \\a\\t g:i a"} /-->
|
||||
|
||||
<!-- wp:comment-edit-link /-->
|
||||
</div>
|
||||
<!-- /wp:group -->
|
||||
</div>
|
||||
<!-- /wp:group -->
|
||||
</div>
|
||||
<!-- /wp:group -->
|
||||
|
||||
<!-- wp:comment-content /-->
|
||||
|
||||
<!-- wp:comment-reply-link /-->
|
||||
</div>
|
||||
<!-- /wp:group -->
|
||||
<!-- /wp:comment-template -->
|
||||
|
||||
<!-- wp:comments-pagination {"paginationArrow":"arrow","layout":{"type":"flex","justifyContent":"space-between"}} -->
|
||||
<!-- wp:comments-pagination-previous /-->
|
||||
|
||||
<!-- wp:comments-pagination-next /-->
|
||||
<!-- /wp:comments-pagination -->
|
||||
|
||||
<!-- wp:post-comments-form {"style":{"spacing":{"margin":{"top":"var:preset|spacing|70"}}}} /-->
|
||||
</div>
|
||||
<!-- /wp:comments -->
|
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
/**
|
||||
* Title: Default footer
|
||||
* Slug: kiosko/footer
|
||||
* Categories: footer
|
||||
* Block Types: core/template-part/footer
|
||||
*/
|
||||
|
||||
declare( strict_types = 1 );
|
||||
?>
|
||||
<!-- wp:group {"style":{"elements":{"link":{"color":{"text":"var:preset|color|base"}}},"spacing":{"padding":{"top":"var:preset|spacing|70","bottom":"var:preset|spacing|70"}}},"backgroundColor":"primary","textColor":"base","layout":{"type":"constrained"}} -->
|
||||
<div class="wp-block-group has-base-color has-primary-background-color has-text-color has-background has-link-color" style="padding-top:var(--wp--preset--spacing--70);padding-bottom:var(--wp--preset--spacing--70)">
|
||||
<!-- wp:columns {"align":"wide","style":{"spacing":{"padding":{"top":"var:preset|spacing|70","bottom":"var:preset|spacing|70"}}}} -->
|
||||
<div class="wp-block-columns alignwide" style="padding-top:var(--wp--preset--spacing--70);padding-bottom:var(--wp--preset--spacing--70)">
|
||||
<!-- wp:column {"width":"40%","fontSize":"small"} -->
|
||||
<div class="wp-block-column has-small-font-size" style="flex-basis:40%">
|
||||
<!-- wp:site-title {"level":2,"isLink":false,"style":{"typography":{"textTransform":"uppercase"},"elements":{"link":{"color":{"text":"var:preset|color|base"}}}},"textColor":"base","fontSize":"large"} /-->
|
||||
|
||||
<!-- wp:paragraph -->
|
||||
<p><?php echo esc_html__( 'We are a poster shop bringing you carefully created and unique art prints. We are a curious and creative team based in Tokyo, Japan, and we believe in diversity, value and quality.', 'kiosko' ); ?></p>
|
||||
<!-- /wp:paragraph -->
|
||||
|
||||
<!-- wp:social-links {"iconColor":"base","iconColorValue":"#ffffff","size":"has-small-icon-size","style":{"spacing":{"blockGap":{"top":"var:preset|spacing|40","left":"var:preset|spacing|40"}}},"className":"is-style-logos-only"} -->
|
||||
<ul class="wp-block-social-links has-small-icon-size has-icon-color is-style-logos-only">
|
||||
<!-- wp:social-link {"url":"#","service":"wordpress"} /-->
|
||||
|
||||
<!-- wp:social-link {"url":"#","service":"tumblr"} /-->
|
||||
|
||||
<!-- wp:social-link {"url":"#","service":"instagram"} /-->
|
||||
|
||||
<!-- wp:social-link {"url":"#","service":"facebook"} /-->
|
||||
</ul>
|
||||
<!-- /wp:social-links -->
|
||||
</div>
|
||||
<!-- /wp:column -->
|
||||
|
||||
<!-- wp:column {"width":"20%"} -->
|
||||
<div class="wp-block-column" style="flex-basis:20%">
|
||||
<!-- wp:heading {"style":{"typography":{"textTransform":"uppercase"}},"fontSize":"large"} -->
|
||||
<h2 class="wp-block-heading has-link-color has-large-font-size" style="text-transform:uppercase"><?php echo esc_html__( 'Location', 'kiosko' ); ?></h2>
|
||||
<!-- /wp:heading -->
|
||||
|
||||
<!-- wp:group {"style":{"spacing":{"blockGap":"0rem"}},"layout":{"type":"constrained"},"fontSize":"small"} -->
|
||||
<div class="wp-block-group has-small-font-size">
|
||||
<!-- wp:paragraph -->
|
||||
<p><?php echo esc_html__( '12-3 Udagawacho, Shibuya City, Tokyo 123-4567', 'kiosko' ); ?></p>
|
||||
<!-- /wp:paragraph -->
|
||||
|
||||
<!-- wp:paragraph -->
|
||||
<p><?php echo esc_html__( 'Mon - Fri 10 am - 7 pm', 'kiosko' ); ?></p>
|
||||
<!-- /wp:paragraph -->
|
||||
|
||||
<!-- wp:paragraph -->
|
||||
<p><?php echo esc_html__( 'Sat - Sun 12 am - 5 pm', 'kiosko' ); ?></p>
|
||||
<!-- /wp:paragraph -->
|
||||
|
||||
<!-- wp:paragraph -->
|
||||
<p><?php echo esc_html__( '0123 456 7890', 'kiosko' ); ?></p>
|
||||
<!-- /wp:paragraph -->
|
||||
</div>
|
||||
<!-- /wp:group -->
|
||||
</div>
|
||||
<!-- /wp:column -->
|
||||
|
||||
<!-- wp:column {"width":"20%"} -->
|
||||
<div class="wp-block-column" style="flex-basis:20%">
|
||||
<!-- wp:heading {"style":{"typography":{"textTransform":"uppercase"}},"fontSize":"large"} -->
|
||||
<h2 class="wp-block-heading has-large-font-size" style="text-transform:uppercase"><?php echo esc_html__( 'Shop', 'kiosko' ); ?></h2>
|
||||
<!-- /wp:heading -->
|
||||
|
||||
<!-- wp:navigation {"layout":{"type":"flex","orientation":"vertical"},"style":{"spacing":{"blockGap":"0px"}},"fontSize":"small"} /-->
|
||||
</div>
|
||||
<!-- /wp:column -->
|
||||
|
||||
<!-- wp:column {"width":"20%"} -->
|
||||
<div class="wp-block-column" style="flex-basis:20%">
|
||||
<!-- wp:heading {"style":{"typography":{"textTransform":"uppercase","fontStyle":"normal","fontWeight":"400"}},"fontSize":"large"} -->
|
||||
<h2 class="wp-block-heading has-large-font-size" style="font-style:normal;font-weight:400;text-transform:uppercase"><?php echo esc_html__( 'Info', 'kiosko' ); ?></h2>
|
||||
<!-- /wp:heading -->
|
||||
|
||||
<!-- wp:navigation {"layout":{"type":"flex","orientation":"vertical"},"style":{"spacing":{"blockGap":"0px"}},"fontSize":"small"} /-->
|
||||
</div>
|
||||
<!-- /wp:column -->
|
||||
</div>
|
||||
<!-- /wp:columns -->
|
||||
|
||||
<!-- wp:group {"align":"wide","style":{"spacing":{"blockGap":"var:preset|spacing|30","margin":{"top":"0px","bottom":"0px"},"padding":{"bottom":"var:preset|spacing|70"}}},"layout":{"type":"flex","justifyContent":"left"}} -->
|
||||
<div class="wp-block-group alignwide" style="margin-top:0px;margin-bottom:0px;padding-bottom:var(--wp--preset--spacing--70)">
|
||||
<!-- wp:paragraph {"align":"right","fontSize":"small"} -->
|
||||
<p class="has-text-align-right has-small-font-size">© <?php echo date("Y"); ?></p>
|
||||
<!-- /wp:paragraph -->
|
||||
|
||||
<!-- wp:site-title {"level":0,"isLink":false,"style":{"typography":{"textTransform":"none"},"elements":{"link":{"color":{"text":"var:preset|color|base"}}}},"textColor":"base","fontSize":"small"} /-->
|
||||
|
||||
<!-- wp:paragraph {"align":"right","fontSize":"small"} -->
|
||||
<p class="has-text-align-right has-small-font-size"><?php
|
||||
/* Translators: WordPress link. */
|
||||
$wordpress_link = '<a href="' . esc_url( __( 'https://wordpress.org', 'kiosko' ) ) . '" rel="nofollow">WordPress</a>';
|
||||
echo sprintf(
|
||||
esc_html__( 'Designed with %1$s', 'kiosko' ),
|
||||
$wordpress_link
|
||||
);
|
||||
?></p>
|
||||
<!-- /wp:paragraph -->
|
||||
</div>
|
||||
<!-- /wp:group -->
|
||||
</div>
|
||||
<!-- /wp:group -->
|