style(biome): using biome for format, lint, check
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
||||
bun run check-types && bun run check && bun run lint:check
|
||||
bun run typecheck && bun run lint
|
||||
|
||||
Vendored
+8
-10
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"editor.defaultFormatter": "biomejs.biome",
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
@@ -10,21 +9,20 @@
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"editor.codeActionsOnSave": {
|
||||
"quickfix.biome": "explicit",
|
||||
"source.organizeImports.biome": "explicit",
|
||||
"source.fixAll": "explicit",
|
||||
"source.organizeImports": "explicit",
|
||||
"source.sortMembers": "explicit"
|
||||
"source.fixAll.biome": "explicit"
|
||||
},
|
||||
"editor.defaultFormatter": "biomejs.biome",
|
||||
"editor.formatOnSave": true,
|
||||
"files.autoSave": "onFocusChange",
|
||||
"search.exclude": {
|
||||
"**/node_modules": true
|
||||
},
|
||||
"terminal.integrated.localEchoStyle": "dim",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"typescript.preferences.autoImportFileExcludePatterns": [
|
||||
"next/router.d.ts",
|
||||
"next/dist/client/router.d.ts"
|
||||
],
|
||||
"terminal.integrated.localEchoStyle": "dim",
|
||||
"search.exclude": {
|
||||
"**/node_modules": true
|
||||
}
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
{
|
||||
"version": "1",
|
||||
"ignore": ["node_modules", ".git"],
|
||||
"name": "basango",
|
||||
"type": "collection",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
".git"
|
||||
]
|
||||
"version": "1"
|
||||
}
|
||||
+132
-138
@@ -1,141 +1,135 @@
|
||||
{
|
||||
"type": "project",
|
||||
"license": "proprietary",
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true,
|
||||
"require": {
|
||||
"php": ">=8.4",
|
||||
"ext-ctype": "*",
|
||||
"ext-dom": "*",
|
||||
"ext-iconv": "*",
|
||||
"cweagans/composer-patches": "^1.7.3",
|
||||
"doctrine/dbal": "^3.9.4",
|
||||
"doctrine/doctrine-bundle": "^2.13.2",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.4.1",
|
||||
"doctrine/orm": "^3.3.1",
|
||||
"geoip2/geoip2": "^3.1",
|
||||
"gesdinet/jwt-refresh-token-bundle": "^1.4",
|
||||
"knplabs/knp-paginator-bundle": "^6.7",
|
||||
"league/csv": "^9.21",
|
||||
"lexik/jwt-authentication-bundle": "^3.1",
|
||||
"martin-georgiev/postgresql-for-doctrine": "^3.5",
|
||||
"matomo/device-detector": "^6.4",
|
||||
"phpdocumentor/reflection-docblock": "^5.6",
|
||||
"phpstan/phpdoc-parser": "^2.1",
|
||||
"sentry/sentry-symfony": "^5.2",
|
||||
"symfony/console": "7.2.*",
|
||||
"symfony/css-selector": "7.2.*",
|
||||
"symfony/dom-crawler": "7.2.*",
|
||||
"symfony/dotenv": "7.2.*",
|
||||
"symfony/flex": "^2.4.7",
|
||||
"symfony/framework-bundle": "7.2.*",
|
||||
"symfony/http-client": "7.2.*",
|
||||
"symfony/mailer": "7.2.*",
|
||||
"symfony/messenger": "7.2.*",
|
||||
"symfony/monolog-bundle": "^3.10",
|
||||
"symfony/property-access": "7.2.*",
|
||||
"symfony/property-info": "7.2.*",
|
||||
"symfony/runtime": "7.2.*",
|
||||
"symfony/security-bundle": "7.2.*",
|
||||
"symfony/serializer": "7.2.*",
|
||||
"symfony/stopwatch": "7.2.*",
|
||||
"symfony/twig-bundle": "7.2.*",
|
||||
"symfony/uid": "7.2.*",
|
||||
"symfony/validator": "7.2.*",
|
||||
"symfony/yaml": "7.2.*",
|
||||
"twig/extra-bundle": "^2.12|^3.19",
|
||||
"twig/twig": "^2.12|^3.19",
|
||||
"webmozart/assert": "^1.11"
|
||||
},
|
||||
"require-dev": {
|
||||
"behat/behat": "^3.22",
|
||||
"deptrac/deptrac": "^2.0.7",
|
||||
"doctrine/doctrine-fixtures-bundle": "^4.1",
|
||||
"friends-of-behat/symfony-extension": "^2.6",
|
||||
"phpstan/phpstan": "^2.1.12",
|
||||
"phpstan/phpstan-doctrine": "^2.0",
|
||||
"phpstan/phpstan-symfony": "^2.0",
|
||||
"phpunit/phpunit": "^12.1.1",
|
||||
"rector/rector": "^2.0.12",
|
||||
"shipmonk/composer-dependency-analyser": "^1.8.2",
|
||||
"symfony/maker-bundle": "^1.62.1",
|
||||
"symfony/web-profiler-bundle": "7.2.*",
|
||||
"symplify/easy-coding-standard": "^12.1.13",
|
||||
"tomasvotruba/class-leak": "^1.2.7"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"php-http/discovery": true,
|
||||
"symfony/flex": true,
|
||||
"symfony/runtime": true,
|
||||
"cweagans/composer-patches": true
|
||||
},
|
||||
"sort-packages": true
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Basango\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"replace": {
|
||||
"symfony/polyfill-ctype": "*",
|
||||
"symfony/polyfill-iconv": "*",
|
||||
"symfony/polyfill-php72": "*",
|
||||
"symfony/polyfill-php73": "*",
|
||||
"symfony/polyfill-php74": "*",
|
||||
"symfony/polyfill-php80": "*",
|
||||
"symfony/polyfill-php81": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"auto-scripts": {
|
||||
"cache:clear": "symfony-cmd",
|
||||
"assets:install %PUBLIC_DIR%": "symfony-cmd"
|
||||
},
|
||||
"post-install-cmd": [
|
||||
"@auto-scripts"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@auto-scripts"
|
||||
],
|
||||
"app:cs": [
|
||||
"./vendor/bin/ecs check",
|
||||
"bin/console lint:yaml config --parse-tags",
|
||||
"bin/console lint:twig templates",
|
||||
"bin/console lint:container",
|
||||
"./vendor/bin/phpstan analyse --memory-limit=-1 --configuration=phpstan.dist.neon",
|
||||
"./vendor/bin/rector --dry-run"
|
||||
],
|
||||
"app:tests": [
|
||||
"APP_ENV=test ./vendor/bin/phpunit"
|
||||
],
|
||||
"app:behat": [
|
||||
"APP_ENV=test bin/console doctrine:database:create",
|
||||
"APP_ENV=test bin/console doctrine:migration:migrate --no-interaction --allow-no-migration --all-or-nothing",
|
||||
"APP_ENV=test ./vendor/bin/behat --format=progress --no-interaction"
|
||||
],
|
||||
"app:env": [
|
||||
"APP_RUNTIME_ENV=prod bin/console secrets:decrypt-to-local --force",
|
||||
"bin/console dotenv:dump prod"
|
||||
]
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/symfony": "*"
|
||||
},
|
||||
"extra": {
|
||||
"symfony": {
|
||||
"allow-contrib": false,
|
||||
"require": "7.2.*"
|
||||
},
|
||||
"patches": {
|
||||
"symfony/monolog-bundle": {
|
||||
"support telegram topic in configuration": "./patches/monolog-telegram-configuration.patch",
|
||||
"support telegram topic in extension": "./patches/monolog-telegram-extension.patch"
|
||||
}
|
||||
}
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Basango\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"cweagans/composer-patches": true,
|
||||
"php-http/discovery": true,
|
||||
"symfony/flex": true,
|
||||
"symfony/runtime": true
|
||||
},
|
||||
"sort-packages": true
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/symfony": "*"
|
||||
},
|
||||
"extra": {
|
||||
"patches": {
|
||||
"symfony/monolog-bundle": {
|
||||
"support telegram topic in configuration": "./patches/monolog-telegram-configuration.patch",
|
||||
"support telegram topic in extension": "./patches/monolog-telegram-extension.patch"
|
||||
}
|
||||
},
|
||||
"symfony": {
|
||||
"allow-contrib": false,
|
||||
"require": "7.2.*"
|
||||
}
|
||||
},
|
||||
"license": "proprietary",
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true,
|
||||
"replace": {
|
||||
"symfony/polyfill-ctype": "*",
|
||||
"symfony/polyfill-iconv": "*",
|
||||
"symfony/polyfill-php72": "*",
|
||||
"symfony/polyfill-php73": "*",
|
||||
"symfony/polyfill-php74": "*",
|
||||
"symfony/polyfill-php80": "*",
|
||||
"symfony/polyfill-php81": "*"
|
||||
},
|
||||
"require": {
|
||||
"cweagans/composer-patches": "^1.7.3",
|
||||
"doctrine/dbal": "^3.9.4",
|
||||
"doctrine/doctrine-bundle": "^2.13.2",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.4.1",
|
||||
"doctrine/orm": "^3.3.1",
|
||||
"ext-ctype": "*",
|
||||
"ext-dom": "*",
|
||||
"ext-iconv": "*",
|
||||
"geoip2/geoip2": "^3.1",
|
||||
"gesdinet/jwt-refresh-token-bundle": "^1.4",
|
||||
"knplabs/knp-paginator-bundle": "^6.7",
|
||||
"league/csv": "^9.21",
|
||||
"lexik/jwt-authentication-bundle": "^3.1",
|
||||
"martin-georgiev/postgresql-for-doctrine": "^3.5",
|
||||
"matomo/device-detector": "^6.4",
|
||||
"php": ">=8.4",
|
||||
"phpdocumentor/reflection-docblock": "^5.6",
|
||||
"phpstan/phpdoc-parser": "^2.1",
|
||||
"sentry/sentry-symfony": "^5.2",
|
||||
"symfony/console": "7.2.*",
|
||||
"symfony/css-selector": "7.2.*",
|
||||
"symfony/dom-crawler": "7.2.*",
|
||||
"symfony/dotenv": "7.2.*",
|
||||
"symfony/flex": "^2.4.7",
|
||||
"symfony/framework-bundle": "7.2.*",
|
||||
"symfony/http-client": "7.2.*",
|
||||
"symfony/mailer": "7.2.*",
|
||||
"symfony/messenger": "7.2.*",
|
||||
"symfony/monolog-bundle": "^3.10",
|
||||
"symfony/property-access": "7.2.*",
|
||||
"symfony/property-info": "7.2.*",
|
||||
"symfony/runtime": "7.2.*",
|
||||
"symfony/security-bundle": "7.2.*",
|
||||
"symfony/serializer": "7.2.*",
|
||||
"symfony/stopwatch": "7.2.*",
|
||||
"symfony/twig-bundle": "7.2.*",
|
||||
"symfony/uid": "7.2.*",
|
||||
"symfony/validator": "7.2.*",
|
||||
"symfony/yaml": "7.2.*",
|
||||
"twig/extra-bundle": "^2.12|^3.19",
|
||||
"twig/twig": "^2.12|^3.19",
|
||||
"webmozart/assert": "^1.11"
|
||||
},
|
||||
"require-dev": {
|
||||
"behat/behat": "^3.22",
|
||||
"deptrac/deptrac": "^2.0.7",
|
||||
"doctrine/doctrine-fixtures-bundle": "^4.1",
|
||||
"friends-of-behat/symfony-extension": "^2.6",
|
||||
"phpstan/phpstan": "^2.1.12",
|
||||
"phpstan/phpstan-doctrine": "^2.0",
|
||||
"phpstan/phpstan-symfony": "^2.0",
|
||||
"phpunit/phpunit": "^12.1.1",
|
||||
"rector/rector": "^2.0.12",
|
||||
"shipmonk/composer-dependency-analyser": "^1.8.2",
|
||||
"symfony/maker-bundle": "^1.62.1",
|
||||
"symfony/web-profiler-bundle": "7.2.*",
|
||||
"symplify/easy-coding-standard": "^12.1.13",
|
||||
"tomasvotruba/class-leak": "^1.2.7"
|
||||
},
|
||||
"scripts": {
|
||||
"app:behat": [
|
||||
"APP_ENV=test bin/console doctrine:database:create",
|
||||
"APP_ENV=test bin/console doctrine:migration:migrate --no-interaction --allow-no-migration --all-or-nothing",
|
||||
"APP_ENV=test ./vendor/bin/behat --format=progress --no-interaction"
|
||||
],
|
||||
"app:cs": [
|
||||
"./vendor/bin/ecs check",
|
||||
"bin/console lint:yaml config --parse-tags",
|
||||
"bin/console lint:twig templates",
|
||||
"bin/console lint:container",
|
||||
"./vendor/bin/phpstan analyse --memory-limit=-1 --configuration=phpstan.dist.neon",
|
||||
"./vendor/bin/rector --dry-run"
|
||||
],
|
||||
"app:env": [
|
||||
"APP_RUNTIME_ENV=prod bin/console secrets:decrypt-to-local --force",
|
||||
"bin/console dotenv:dump prod"
|
||||
],
|
||||
"app:tests": ["APP_ENV=test ./vendor/bin/phpunit"],
|
||||
"auto-scripts": {
|
||||
"assets:install %PUBLIC_DIR%": "symfony-cmd",
|
||||
"cache:clear": "symfony-cmd"
|
||||
},
|
||||
"post-install-cmd": ["@auto-scripts"],
|
||||
"post-update-cmd": ["@auto-scripts"]
|
||||
},
|
||||
"type": "project"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
{
|
||||
"name": "@basango/crawler",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@basango/logger": "workspace:*",
|
||||
"@devscast/config": "^1.0.3",
|
||||
@@ -16,16 +14,18 @@
|
||||
"@types/turndown": "^5.0.6",
|
||||
"vitest": "^4.0.7"
|
||||
},
|
||||
"name": "@basango/crawler",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"crawler:async": "bun run src/scripts/queue.ts",
|
||||
"crawler:sync": "bun run src/scripts/crawl.ts",
|
||||
"crawler:worker": "bun run src/scripts/worker.ts",
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"format": "biome format --write .",
|
||||
"lint": "biome check .",
|
||||
"lint:fix": "biome check --write .",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "vitest --run"
|
||||
"test": "vitest --run",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"extends": "@basango/typescript-config/base.json",
|
||||
"extends": "@basango/tsconfig/base.json",
|
||||
"include": ["src"],
|
||||
"references": []
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
@@ -3,18 +3,18 @@ import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
variable: "--font-geist-sans",
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
variable: "--font-geist-mono",
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
title: "Create Next App",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
@@ -24,11 +24,7 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
+10
-10
@@ -5,12 +5,12 @@ export default function Home() {
|
||||
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={100}
|
||||
className="dark:invert"
|
||||
height={20}
|
||||
priority
|
||||
src="/next.svg"
|
||||
width={100}
|
||||
/>
|
||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
||||
@@ -19,15 +19,15 @@ export default function Home() {
|
||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
||||
Looking for a starting point or more instructions? Head over to{" "}
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
>
|
||||
Templates
|
||||
</a>{" "}
|
||||
or the{" "}
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
>
|
||||
Learning
|
||||
</a>{" "}
|
||||
@@ -38,23 +38,23 @@ export default function Home() {
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={16}
|
||||
className="dark:invert"
|
||||
height={16}
|
||||
src="/vercel.svg"
|
||||
width={16}
|
||||
/>
|
||||
Deploy Now
|
||||
</a>
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "../../packages/ui/src/styles/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"hooks": "@/hooks",
|
||||
"lib": "@/lib",
|
||||
"utils": "@basango/ui/lib/utils",
|
||||
"ui": "@basango/ui/components"
|
||||
}
|
||||
"ui": "@basango/ui/components",
|
||||
"utils": "@basango/ui/lib/utils"
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"rsc": true,
|
||||
"style": "new-york",
|
||||
"tailwind": {
|
||||
"baseColor": "neutral",
|
||||
"config": "",
|
||||
"css": "../../packages/ui/src/styles/globals.css",
|
||||
"cssVariables": true
|
||||
},
|
||||
"tsx": true
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
transpilePackages: ["@basango/ui"],
|
||||
}
|
||||
};
|
||||
|
||||
export default nextConfig
|
||||
export default nextConfig;
|
||||
|
||||
+13
-13
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"name": "@basango/dashboard",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"next": "catalog:"
|
||||
"react-dom": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "catalog:",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/bun": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"tailwindcss": "^4"
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "catalog:"
|
||||
},
|
||||
"name": "@basango/dashboard",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "next build",
|
||||
"dev": "next dev",
|
||||
"lint": "eslint",
|
||||
"start": "next start"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"extends": "@basango/typescript-config/nextjs.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
@@ -12,12 +11,7 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"next.config.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
"exclude": ["node_modules"],
|
||||
"extends": "@basango/tsconfig/nextjs.json",
|
||||
"include": ["next-env.d.ts", "next.config.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"]
|
||||
}
|
||||
|
||||
+34
-34
@@ -1,40 +1,43 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "basango",
|
||||
"slug": "basango",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./src/assets/images/logo.png",
|
||||
"scheme": "basango",
|
||||
"userInterfaceStyle": "automatic",
|
||||
"newArchEnabled": true,
|
||||
"githubUrl": "https://github.com/bernard-ng/basango",
|
||||
"ios": {
|
||||
"supportsTablet": true,
|
||||
"bundleIdentifier": "dev.ngandu.basango"
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./src/assets/images/logo.png",
|
||||
"backgroundColor": "#ffffff",
|
||||
"foregroundImage": "./src/assets/images/logo.png",
|
||||
"package": "dev.ngandu.basango"
|
||||
},
|
||||
"package": "dev.ngandu.basango"
|
||||
},
|
||||
"web": {
|
||||
"bundler": "metro",
|
||||
"output": "static",
|
||||
"favicon": "./src/assets/images/logo.png"
|
||||
"experiments": {
|
||||
"typedRoutes": true
|
||||
},
|
||||
"extra": {
|
||||
"eas": {
|
||||
"projectId": "57281e7a-46e3-4aac-8715-5165fa0bf560"
|
||||
},
|
||||
"router": {
|
||||
"origin": false
|
||||
}
|
||||
},
|
||||
"githubUrl": "https://github.com/bernard-ng/basango",
|
||||
"icon": "./src/assets/images/logo.png",
|
||||
"ios": {
|
||||
"bundleIdentifier": "dev.ngandu.basango",
|
||||
"supportsTablet": true
|
||||
},
|
||||
"name": "basango",
|
||||
"newArchEnabled": true,
|
||||
"orientation": "portrait",
|
||||
"owner": "bernard-ng",
|
||||
"plugins": [
|
||||
"expo-router",
|
||||
[
|
||||
"expo-splash-screen",
|
||||
{
|
||||
"backgroundColor": "#ffffff",
|
||||
"image": "./src/assets/images/logo.png",
|
||||
"imageWidth": 200,
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
"resizeMode": "contain"
|
||||
}
|
||||
],
|
||||
"expo-build-properties",
|
||||
@@ -42,23 +45,20 @@
|
||||
[
|
||||
"@sentry/react-native/expo",
|
||||
{
|
||||
"url": "https://glitchtip.devscast.tech/",
|
||||
"organization": "devscast-software",
|
||||
"project": "basango",
|
||||
"organization": "devscast-software"
|
||||
"url": "https://glitchtip.devscast.tech/"
|
||||
}
|
||||
]
|
||||
],
|
||||
"experiments": {
|
||||
"typedRoutes": true
|
||||
},
|
||||
"extra": {
|
||||
"router": {
|
||||
"origin": false
|
||||
},
|
||||
"eas": {
|
||||
"projectId": "57281e7a-46e3-4aac-8715-5165fa0bf560"
|
||||
}
|
||||
},
|
||||
"owner": "bernard-ng"
|
||||
"scheme": "basango",
|
||||
"slug": "basango",
|
||||
"userInterfaceStyle": "automatic",
|
||||
"version": "1.0.0",
|
||||
"web": {
|
||||
"bundler": "metro",
|
||||
"favicon": "./src/assets/images/logo.png",
|
||||
"output": "static"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: [["babel-preset-expo", { jsxRuntime: "automatic" }]],
|
||||
plugins: ["react-native-reanimated/plugin"],
|
||||
};
|
||||
module.exports = (api) => {
|
||||
api.cache(true);
|
||||
return {
|
||||
plugins: ["react-native-reanimated/plugin"],
|
||||
presets: [["babel-preset-expo", { jsxRuntime: "automatic" }]],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
module.exports = {
|
||||
extends: ["@commitlint/config-conventional"],
|
||||
rules: {
|
||||
"type-enum": [
|
||||
2,
|
||||
"always",
|
||||
[
|
||||
"feat", // New feature
|
||||
"fix", // Bug fix
|
||||
"docs", // Documentation only changes
|
||||
"style", // Changes that do not affect the meaning of the code (white-space, formatting, etc)
|
||||
"refactor", // Code changes that neither fixes a bug nor adds a feature
|
||||
"perf", // Performance improvements
|
||||
"test", // Adding missing tests or correcting existing tests
|
||||
"build", // Changes that affect the build system or external dependencies
|
||||
"ci", // Changes to CI configuration files and scripts
|
||||
"chore", // Other changes that don't modify src or test files
|
||||
"revert", // Reverts a previous commit
|
||||
],
|
||||
],
|
||||
"subject-case": [2, "never", ["sentence-case", "start-case", "pascal-case", "upper-case"]],
|
||||
},
|
||||
extends: ["@commitlint/config-conventional"],
|
||||
rules: {
|
||||
"subject-case": [2, "never", ["sentence-case", "start-case", "pascal-case", "upper-case"]],
|
||||
"type-enum": [
|
||||
2,
|
||||
"always",
|
||||
[
|
||||
"feat", // New feature
|
||||
"fix", // Bug fix
|
||||
"docs", // Documentation only changes
|
||||
"style", // Changes that do not affect the meaning of the code (white-space, formatting, etc)
|
||||
"refactor", // Code changes that neither fixes a bug nor adds a feature
|
||||
"perf", // Performance improvements
|
||||
"test", // Adding missing tests or correcting existing tests
|
||||
"build", // Changes that affect the build system or external dependencies
|
||||
"ci", // Changes to CI configuration files and scripts
|
||||
"chore", // Other changes that don't modify src or test files
|
||||
"revert", // Reverts a previous commit
|
||||
],
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
{
|
||||
"cli": {
|
||||
"version": ">= 16.3.1",
|
||||
"appVersionSource": "remote"
|
||||
},
|
||||
"build": {
|
||||
"development": {
|
||||
"developmentClient": true,
|
||||
@@ -15,6 +11,10 @@
|
||||
"autoIncrement": true
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
"appVersionSource": "remote",
|
||||
"version": ">= 16.3.1"
|
||||
},
|
||||
"submit": {
|
||||
"production": {}
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
const { defineConfig } = require("eslint/config");
|
||||
const expoConfig = require("eslint-config-expo/flat");
|
||||
const prettierPlugin = require("eslint-plugin-prettier");
|
||||
const unusedImportsPlugin = require("eslint-plugin-unused-imports");
|
||||
|
||||
module.exports = defineConfig([
|
||||
expoConfig,
|
||||
{
|
||||
plugins: {
|
||||
prettier: prettierPlugin,
|
||||
"unused-imports": unusedImportsPlugin,
|
||||
},
|
||||
rules: {
|
||||
"import/default": "off",
|
||||
"react/prop-types": "off",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"import/named": "off",
|
||||
"import/namespace": "error",
|
||||
"import/export": "error",
|
||||
"no-unused-vars": "off",
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
"unused-imports/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
vars: "all",
|
||||
varsIgnorePattern: "^_",
|
||||
args: "after-used",
|
||||
argsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
groups: ["builtin", "external", "internal"],
|
||||
pathGroups: [
|
||||
{
|
||||
pattern: "react",
|
||||
group: "external",
|
||||
position: "before",
|
||||
},
|
||||
],
|
||||
pathGroupsExcludedImportTypes: ["react"],
|
||||
"newlines-between": "always",
|
||||
alphabetize: {
|
||||
order: "asc",
|
||||
caseInsensitive: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
"prettier/prettier": "error",
|
||||
},
|
||||
},
|
||||
{
|
||||
ignores: ["dist/*"],
|
||||
},
|
||||
]);
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"expo.jsEngine": "hermes",
|
||||
"EX_DEV_CLIENT_NETWORK_INSPECTOR": "true",
|
||||
"newArchEnabled": "true",
|
||||
"apple.extraPods": "[]",
|
||||
"apple.ccacheEnabled": "false",
|
||||
"apple.privacyManifestAggregationEnabled": "true"
|
||||
"apple.extraPods": "[]",
|
||||
"apple.privacyManifestAggregationEnabled": "true",
|
||||
"EX_DEV_CLIENT_NETWORK_INSPECTOR": "true",
|
||||
"expo.jsEngine": "hermes",
|
||||
"newArchEnabled": "true"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"version": 1,
|
||||
"author": "expo"
|
||||
"author": "expo",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "expo"
|
||||
"info": {
|
||||
"author": "expo",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -2,19 +2,19 @@
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "1.00000000000000",
|
||||
"green": "1.00000000000000",
|
||||
"red": "1.00000000000000"
|
||||
},
|
||||
"color-space": "srgb"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"version": 1,
|
||||
"author": "expo"
|
||||
"author": "expo",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"idiom": "universal",
|
||||
"filename": "image.png",
|
||||
"idiom": "universal",
|
||||
"scale": "1x"
|
||||
},
|
||||
{
|
||||
"idiom": "universal",
|
||||
"filename": "image@2x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"idiom": "universal",
|
||||
"filename": "image@3x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "3x"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"version": 1,
|
||||
"author": "expo"
|
||||
"author": "expo",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,4 @@
|
||||
{
|
||||
"name": "drc-news",
|
||||
"main": "expo-router/entry",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios",
|
||||
"web": "expo start --web",
|
||||
"test": "jest --watchAll",
|
||||
"============= EAS BUILD =============": "",
|
||||
"build:ios:dev": "eas build --profile development --platform ios",
|
||||
"build:ios:sim": "eas build --profile dev-sim --platform ios",
|
||||
"build:ios:prev": "eas build --profile preview --platform ios",
|
||||
"build:ios:e2e": "eas build --profile ios-e2e --platform ios",
|
||||
"build:android:dev": "eas build --profile development --platform android",
|
||||
"build:android:sim": "eas build --profile dev-sim --platform android",
|
||||
"build:android:prev": "eas build --profile preview --platform android",
|
||||
"build:android:e2e": "eas build --profile android-e2e --platform android",
|
||||
"build:android:prod": "eas build --profile production --platform android",
|
||||
"build:ios:prod": "eas build --profile production --platform ios",
|
||||
"===================== EAS SUBMIT =====================": "",
|
||||
"eas:android:submit": "eas submit -p android --profile production",
|
||||
"eas:ios:submit": "eas submit -p ios --profile production",
|
||||
"=========== CODE STYLE ============": "",
|
||||
"check-types": "tsc --noEmit",
|
||||
"check": "prettier src --check",
|
||||
"format": "prettier src --write",
|
||||
"lint:check": "eslint src --debug",
|
||||
"lint:fix": "eslint src --fix",
|
||||
"============= HUSKY =============": "",
|
||||
"prepare": "husky",
|
||||
"commit": "cz",
|
||||
"============= MISCELLANEOUS =============": "",
|
||||
"delete:dstore": "find -name '.DS_Store' -type f -delete"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.ts": [
|
||||
"prettier --write",
|
||||
"eslint --fix"
|
||||
],
|
||||
"*.tsx": [
|
||||
"prettier --write",
|
||||
"eslint --fix"
|
||||
]
|
||||
},
|
||||
"commitlint": {
|
||||
"extends": [
|
||||
"@commitlint/config-conventional"
|
||||
@@ -54,12 +9,6 @@
|
||||
"path": "cz-conventional-changelog"
|
||||
}
|
||||
},
|
||||
"jest": {
|
||||
"preset": "jest-expo"
|
||||
},
|
||||
"overrides": {
|
||||
"globals": "14.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo-google-fonts/inter": "^0.3.0",
|
||||
"@expo/vector-icons": "^14.0.2",
|
||||
@@ -132,5 +81,56 @@
|
||||
"react-test-renderer": "18.3.1",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"private": true
|
||||
"jest": {
|
||||
"preset": "jest-expo"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.ts": [
|
||||
"prettier --write",
|
||||
"eslint --fix"
|
||||
],
|
||||
"*.tsx": [
|
||||
"prettier --write",
|
||||
"eslint --fix"
|
||||
]
|
||||
},
|
||||
"main": "expo-router/entry",
|
||||
"name": "drc-news",
|
||||
"overrides": {
|
||||
"globals": "14.0.0"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"=========== CODE STYLE ============": "",
|
||||
"============= EAS BUILD =============": "",
|
||||
"============= HUSKY =============": "",
|
||||
"============= MISCELLANEOUS =============": "",
|
||||
"===================== EAS SUBMIT =====================": "",
|
||||
"android": "expo run:android",
|
||||
"build:android:dev": "eas build --profile development --platform android",
|
||||
"build:android:e2e": "eas build --profile android-e2e --platform android",
|
||||
"build:android:prev": "eas build --profile preview --platform android",
|
||||
"build:android:prod": "eas build --profile production --platform android",
|
||||
"build:android:sim": "eas build --profile dev-sim --platform android",
|
||||
"build:ios:dev": "eas build --profile development --platform ios",
|
||||
"build:ios:e2e": "eas build --profile ios-e2e --platform ios",
|
||||
"build:ios:prev": "eas build --profile preview --platform ios",
|
||||
"build:ios:prod": "eas build --profile production --platform ios",
|
||||
"build:ios:sim": "eas build --profile dev-sim --platform ios",
|
||||
"check": "prettier src --check",
|
||||
"check-types": "tsc --noEmit",
|
||||
"commit": "cz",
|
||||
"delete:dstore": "find -name '.DS_Store' -type f -delete",
|
||||
"eas:android:submit": "eas submit -p android --profile production",
|
||||
"eas:ios:submit": "eas submit -p ios --profile production",
|
||||
"format": "prettier src --write",
|
||||
"ios": "expo run:ios",
|
||||
"lint:check": "eslint src --debug",
|
||||
"lint:fix": "eslint src --fix",
|
||||
"prepare": "husky",
|
||||
"start": "expo start",
|
||||
"test": "jest --watchAll",
|
||||
"web": "expo start --web"
|
||||
},
|
||||
"version": "1.0.0"
|
||||
}
|
||||
|
||||
@@ -5,113 +5,113 @@ import { clearTokens, getAccessToken, getRefreshToken, setTokens } from "@/store
|
||||
|
||||
const endpoint = process.env.EXPO_PUBLIC_API_URL!;
|
||||
const client: AxiosInstance = axios.create({
|
||||
baseURL: endpoint,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
baseURL: endpoint,
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
let isAuthTokenRefreshing = false;
|
||||
let failedRequestsQueue: ((token: string) => void)[] = [];
|
||||
|
||||
const processFailedRequestsQueue = (token: string) => {
|
||||
failedRequestsQueue.forEach(callback => callback(token));
|
||||
failedRequestsQueue = [];
|
||||
failedRequestsQueue.forEach((callback) => callback(token));
|
||||
failedRequestsQueue = [];
|
||||
};
|
||||
|
||||
// Wait for 120 seconds before timing out
|
||||
axios.interceptors.request.use(config => {
|
||||
config.timeout = 120_000;
|
||||
return config;
|
||||
axios.interceptors.request.use((config) => {
|
||||
config.timeout = 120_000;
|
||||
return config;
|
||||
});
|
||||
|
||||
// Add the Authorization header to all requests
|
||||
client.interceptors.request.use(async config => {
|
||||
const token = await getAccessToken();
|
||||
if (token) {
|
||||
config.headers["Authorization"] = `Bearer ${token}`;
|
||||
}
|
||||
client.interceptors.request.use(async (config) => {
|
||||
const token = await getAccessToken();
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
return config;
|
||||
return config;
|
||||
});
|
||||
|
||||
// Handle 401 errors and refresh the token
|
||||
client.interceptors.response.use(
|
||||
response => response,
|
||||
async error => {
|
||||
const originalRequest = error.config;
|
||||
const status = error.response?.status;
|
||||
(response) => response,
|
||||
async (error) => {
|
||||
const originalRequest = error.config;
|
||||
const status = error.response?.status;
|
||||
|
||||
if (status === 401 && !originalRequest._retry) {
|
||||
originalRequest._retry = true;
|
||||
if (status === 401 && !originalRequest._retry) {
|
||||
originalRequest._retry = true;
|
||||
|
||||
if (isAuthTokenRefreshing) {
|
||||
return new Promise(resolve => {
|
||||
failedRequestsQueue.push((token: string) => {
|
||||
originalRequest.headers["Authorization"] = `Bearer ${token}`;
|
||||
resolve(client(originalRequest));
|
||||
});
|
||||
});
|
||||
}
|
||||
if (isAuthTokenRefreshing) {
|
||||
return new Promise((resolve) => {
|
||||
failedRequestsQueue.push((token: string) => {
|
||||
originalRequest.headers.Authorization = `Bearer ${token}`;
|
||||
resolve(client(originalRequest));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
isAuthTokenRefreshing = true;
|
||||
isAuthTokenRefreshing = true;
|
||||
|
||||
try {
|
||||
const refreshToken = await getRefreshToken();
|
||||
if (!refreshToken) {
|
||||
await clearTokens();
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
const response = await axios.post<RefreshTokenResponse>(`${endpoint}/token/refresh`, {
|
||||
refresh_token: refreshToken,
|
||||
} as RefreshTokenPayload);
|
||||
|
||||
const updatedToken = response.data.token;
|
||||
await setTokens(updatedToken, refreshToken);
|
||||
processFailedRequestsQueue(updatedToken);
|
||||
|
||||
originalRequest.headers["Authorization"] = `Bearer ${updatedToken}`;
|
||||
return client(originalRequest);
|
||||
} catch (error) {
|
||||
await clearTokens();
|
||||
return Promise.reject(error);
|
||||
} finally {
|
||||
isAuthTokenRefreshing = false;
|
||||
}
|
||||
try {
|
||||
const refreshToken = await getRefreshToken();
|
||||
if (!refreshToken) {
|
||||
await clearTokens();
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
const response = await axios.post<RefreshTokenResponse>(`${endpoint}/token/refresh`, {
|
||||
refresh_token: refreshToken,
|
||||
} as RefreshTokenPayload);
|
||||
|
||||
const updatedToken = response.data.token;
|
||||
await setTokens(updatedToken, refreshToken);
|
||||
processFailedRequestsQueue(updatedToken);
|
||||
|
||||
originalRequest.headers.Authorization = `Bearer ${updatedToken}`;
|
||||
return client(originalRequest);
|
||||
} catch (error) {
|
||||
await clearTokens();
|
||||
return Promise.reject(error);
|
||||
} finally {
|
||||
isAuthTokenRefreshing = false;
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
|
||||
if (__DEV__) {
|
||||
// Log HTTP requests and responses
|
||||
client.interceptors.request.use(
|
||||
async config => {
|
||||
console.log("HTTP REQUEST", {
|
||||
baseURL: config.baseURL,
|
||||
url: config.url,
|
||||
data: config.data,
|
||||
});
|
||||
// Log HTTP requests and responses
|
||||
client.interceptors.request.use(
|
||||
async (config) => {
|
||||
console.log("HTTP REQUEST", {
|
||||
baseURL: config.baseURL,
|
||||
data: config.data,
|
||||
url: config.url,
|
||||
});
|
||||
|
||||
return config;
|
||||
},
|
||||
error => console.log(JSON.stringify(error))
|
||||
);
|
||||
return config;
|
||||
},
|
||||
(error) => console.log(JSON.stringify(error)),
|
||||
);
|
||||
|
||||
client.interceptors.response.use(
|
||||
response => {
|
||||
console.log("HTTP RESPONSE", {
|
||||
stats: response.status,
|
||||
data: response.data,
|
||||
});
|
||||
client.interceptors.response.use(
|
||||
(response) => {
|
||||
console.log("HTTP RESPONSE", {
|
||||
data: response.data,
|
||||
stats: response.status,
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
error => console.log(JSON.stringify(error))
|
||||
);
|
||||
return response;
|
||||
},
|
||||
(error) => console.log(JSON.stringify(error)),
|
||||
);
|
||||
}
|
||||
|
||||
export default client;
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
export const endpoint = {
|
||||
feedManagement: {
|
||||
addArticleToBookmark: (bookmarkId: string, articleId: string) =>
|
||||
`feed/bookmarks/${bookmarkId}/articles/${articleId}`,
|
||||
addCommentToArticle: (articleId: string) => `/feed/articles/${articleId}/comments`,
|
||||
createBookmark: `/feed/bookmarks`,
|
||||
deleteBookmark: (bookmarkId: string) => `/feed/bookmarks/${bookmarkId}`,
|
||||
followSource: (sourceId: string) => `/feed/sources/${sourceId}/follow`,
|
||||
getArticleCommentList: (articleId: string) => `/feed/articles/${articleId}/comments`,
|
||||
getArticleDetails: (articleId: string) => `/feed/articles/${articleId}`,
|
||||
getArticleOverviewList: `/feed/articles`,
|
||||
getBookmarkList: `/feed/bookmarks`,
|
||||
getBookmarkedArticlesList: (bookmarkId: string) => `/feed/bookmarks/${bookmarkId}/articles`,
|
||||
getSourceArticleOverviewList: (sourceId: string) => `/feed/sources/${sourceId}/articles`,
|
||||
getSourceDetails: (sourceId: string) => `/feed/sources/${sourceId}`,
|
||||
getSourceOverviewList: `/feed/sources`,
|
||||
removeArticleFromBookmark: (bookmarkId: string, articleId: string) =>
|
||||
`/feed/bookmarks/${bookmarkId}/articles/${articleId}`,
|
||||
unfollowSource: (sourceId: string) => `/feed/sources/${sourceId}/unfollow`,
|
||||
updateBookmark: (bookmarkId: string) => `/feed/bookmarks/${bookmarkId}`,
|
||||
},
|
||||
identityAndAccess: {
|
||||
login: "/login_check",
|
||||
logout: "/token/invalidate",
|
||||
register: "/register",
|
||||
getUserProfile: "/me",
|
||||
requestPassword: "/password/request",
|
||||
confirmAccount: (token: string) => `/account/confirm/${token}`,
|
||||
resetPassword: (token: string) => `/password/reset/${token}`,
|
||||
unlockAccount: (token: string) => `/account/unlock/${token}`,
|
||||
updatePassword: "/password/update",
|
||||
},
|
||||
feedManagement: {
|
||||
addArticleToBookmark: (bookmarkId: string, articleId: string) =>
|
||||
`feed/bookmarks/${bookmarkId}/articles/${articleId}`,
|
||||
addCommentToArticle: (articleId: string) => `/feed/articles/${articleId}/comments`,
|
||||
createBookmark: `/feed/bookmarks`,
|
||||
deleteBookmark: (bookmarkId: string) => `/feed/bookmarks/${bookmarkId}`,
|
||||
followSource: (sourceId: string) => `/feed/sources/${sourceId}/follow`,
|
||||
getArticleCommentList: (articleId: string) => `/feed/articles/${articleId}/comments`,
|
||||
getArticleDetails: (articleId: string) => `/feed/articles/${articleId}`,
|
||||
getArticleOverviewList: `/feed/articles`,
|
||||
getBookmarkedArticlesList: (bookmarkId: string) => `/feed/bookmarks/${bookmarkId}/articles`,
|
||||
getBookmarkList: `/feed/bookmarks`,
|
||||
getSourceArticleOverviewList: (sourceId: string) => `/feed/sources/${sourceId}/articles`,
|
||||
getSourceDetails: (sourceId: string) => `/feed/sources/${sourceId}`,
|
||||
getSourceOverviewList: `/feed/sources`,
|
||||
removeArticleFromBookmark: (bookmarkId: string, articleId: string) =>
|
||||
`/feed/bookmarks/${bookmarkId}/articles/${articleId}`,
|
||||
unfollowSource: (sourceId: string) => `/feed/sources/${sourceId}/unfollow`,
|
||||
updateBookmark: (bookmarkId: string) => `/feed/bookmarks/${bookmarkId}`,
|
||||
},
|
||||
identityAndAccess: {
|
||||
confirmAccount: (token: string) => `/account/confirm/${token}`,
|
||||
getUserProfile: "/me",
|
||||
login: "/login_check",
|
||||
logout: "/token/invalidate",
|
||||
register: "/register",
|
||||
requestPassword: "/password/request",
|
||||
resetPassword: (token: string) => `/password/reset/${token}`,
|
||||
unlockAccount: (token: string) => `/account/unlock/${token}`,
|
||||
updatePassword: "/password/update",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,19 +1,30 @@
|
||||
import { endpoint } from "@/api/endpoint";
|
||||
import { Article, ArticleOverview, TrendingArticle } from "@/api/schema/feed-management/article";
|
||||
import { ArticleFilters, useGetQuery, usePaginatedInfiniteQuery, usePaginatedQuery } from "@/api/shared";
|
||||
import {
|
||||
ArticleFilters,
|
||||
useGetQuery,
|
||||
usePaginatedInfiniteQuery,
|
||||
usePaginatedQuery,
|
||||
} from "@/api/shared";
|
||||
|
||||
export const useArticleTrendingList = (filters: ArticleFilters = {}) => {
|
||||
return usePaginatedQuery<TrendingArticle>("/feed/trending", filters);
|
||||
return usePaginatedQuery<TrendingArticle>("/feed/trending", filters);
|
||||
};
|
||||
|
||||
export const useArticleDetails = (articleId: string) => {
|
||||
return useGetQuery<Article>(endpoint.feedManagement.getArticleDetails(articleId));
|
||||
return useGetQuery<Article>(endpoint.feedManagement.getArticleDetails(articleId));
|
||||
};
|
||||
|
||||
export const useArticleOverviewList = (filters: ArticleFilters = {}) => {
|
||||
return usePaginatedQuery<ArticleOverview>(endpoint.feedManagement.getArticleOverviewList, filters);
|
||||
return usePaginatedQuery<ArticleOverview>(
|
||||
endpoint.feedManagement.getArticleOverviewList,
|
||||
filters,
|
||||
);
|
||||
};
|
||||
|
||||
export const useInfiniteArticleOverviewList = (filters: ArticleFilters = {}) => {
|
||||
return usePaginatedInfiniteQuery<ArticleOverview>(endpoint.feedManagement.getArticleOverviewList, filters);
|
||||
return usePaginatedInfiniteQuery<ArticleOverview>(
|
||||
endpoint.feedManagement.getArticleOverviewList,
|
||||
filters,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,34 +1,44 @@
|
||||
import { endpoint } from "@/api/endpoint";
|
||||
import { Bookmark, BookmarkedArticle, BookmarkPayload } from "@/api/schema/feed-management/bookmark";
|
||||
import { ArticleFilters, useDeleteQuery, usePaginatedInfiniteQuery, usePostQuery, usePutQuery } from "@/api/shared";
|
||||
import {
|
||||
Bookmark,
|
||||
BookmarkedArticle,
|
||||
BookmarkPayload,
|
||||
} from "@/api/schema/feed-management/bookmark";
|
||||
import {
|
||||
ArticleFilters,
|
||||
useDeleteQuery,
|
||||
usePaginatedInfiniteQuery,
|
||||
usePostQuery,
|
||||
usePutQuery,
|
||||
} from "@/api/shared";
|
||||
|
||||
export const useCreateBookmark = () => {
|
||||
return usePostQuery<BookmarkPayload>(endpoint.feedManagement.createBookmark);
|
||||
return usePostQuery<BookmarkPayload>(endpoint.feedManagement.createBookmark);
|
||||
};
|
||||
|
||||
export const useUpdateBookmark = (bookmarkId: string) => {
|
||||
return usePutQuery<BookmarkPayload>(endpoint.feedManagement.updateBookmark(bookmarkId));
|
||||
return usePutQuery<BookmarkPayload>(endpoint.feedManagement.updateBookmark(bookmarkId));
|
||||
};
|
||||
|
||||
export const useDeleteBookmark = (bookmarkId: string) => {
|
||||
return useDeleteQuery(endpoint.feedManagement.deleteBookmark(bookmarkId));
|
||||
return useDeleteQuery(endpoint.feedManagement.deleteBookmark(bookmarkId));
|
||||
};
|
||||
|
||||
export const useAddArticleToBookmark = (bookmarkId: string, articleId: string) => {
|
||||
return usePostQuery(endpoint.feedManagement.addArticleToBookmark(bookmarkId, articleId));
|
||||
return usePostQuery(endpoint.feedManagement.addArticleToBookmark(bookmarkId, articleId));
|
||||
};
|
||||
|
||||
export const useRemoveArticleFromBookmark = (bookmarkId: string, articleId: string) => {
|
||||
return useDeleteQuery(endpoint.feedManagement.removeArticleFromBookmark(bookmarkId, articleId));
|
||||
return useDeleteQuery(endpoint.feedManagement.removeArticleFromBookmark(bookmarkId, articleId));
|
||||
};
|
||||
|
||||
export const useBookmarkList = (filters: ArticleFilters = {}) => {
|
||||
return usePaginatedInfiniteQuery<Bookmark>(endpoint.feedManagement.getBookmarkList, filters);
|
||||
return usePaginatedInfiniteQuery<Bookmark>(endpoint.feedManagement.getBookmarkList, filters);
|
||||
};
|
||||
|
||||
export const useBookmarkedArticlesList = (bookmarkId: string, filters: ArticleFilters = {}) => {
|
||||
return usePaginatedInfiniteQuery<BookmarkedArticle>(
|
||||
endpoint.feedManagement.getBookmarkedArticlesList(bookmarkId),
|
||||
filters
|
||||
);
|
||||
return usePaginatedInfiniteQuery<BookmarkedArticle>(
|
||||
endpoint.feedManagement.getBookmarkedArticlesList(bookmarkId),
|
||||
filters,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,13 +3,15 @@ import { Comment, CommentPayload } from "@/api/schema/feed-management/comment";
|
||||
import { useDeleteQuery, usePaginatedInfiniteQuery, usePostQuery } from "@/api/shared";
|
||||
|
||||
export const useArticleCommentList = (articleId: string) => {
|
||||
return usePaginatedInfiniteQuery<Comment>(endpoint.feedManagement.getArticleCommentList(articleId));
|
||||
return usePaginatedInfiniteQuery<Comment>(
|
||||
endpoint.feedManagement.getArticleCommentList(articleId),
|
||||
);
|
||||
};
|
||||
|
||||
export const useAddCommentToArticle = (articleId: string) => {
|
||||
return usePostQuery<CommentPayload>(endpoint.feedManagement.addCommentToArticle(articleId));
|
||||
return usePostQuery<CommentPayload>(endpoint.feedManagement.addCommentToArticle(articleId));
|
||||
};
|
||||
|
||||
export const useRemoveCommentFromArticle = (articleId: string, commentId: string) => {
|
||||
return useDeleteQuery(endpoint.feedManagement.removeArticleFromBookmark(articleId, commentId));
|
||||
return useDeleteQuery(endpoint.feedManagement.removeArticleFromBookmark(articleId, commentId));
|
||||
};
|
||||
|
||||
@@ -1,27 +1,36 @@
|
||||
import { endpoint } from "@/api/endpoint";
|
||||
import { ArticleOverview } from "@/api/schema/feed-management/article";
|
||||
import { SourceDetails, SourceOverview } from "@/api/schema/feed-management/source";
|
||||
import { ArticleFilters, useDeleteQuery, useGetQuery, usePaginatedInfiniteQuery, usePostQuery } from "@/api/shared";
|
||||
import {
|
||||
ArticleFilters,
|
||||
useDeleteQuery,
|
||||
useGetQuery,
|
||||
usePaginatedInfiniteQuery,
|
||||
usePostQuery,
|
||||
} from "@/api/shared";
|
||||
|
||||
export const useSourceDetails = (sourceId: string) => {
|
||||
return useGetQuery<SourceDetails>(endpoint.feedManagement.getSourceDetails(sourceId));
|
||||
return useGetQuery<SourceDetails>(endpoint.feedManagement.getSourceDetails(sourceId));
|
||||
};
|
||||
|
||||
export const useSourceOverviewList = (filters: ArticleFilters = {}) => {
|
||||
return usePaginatedInfiniteQuery<SourceOverview>(endpoint.feedManagement.getSourceOverviewList, filters);
|
||||
return usePaginatedInfiniteQuery<SourceOverview>(
|
||||
endpoint.feedManagement.getSourceOverviewList,
|
||||
filters,
|
||||
);
|
||||
};
|
||||
|
||||
export const useSourceArticleOverviewList = (sourceId: string, filters: ArticleFilters = {}) => {
|
||||
return usePaginatedInfiniteQuery<ArticleOverview>(
|
||||
endpoint.feedManagement.getSourceArticleOverviewList(sourceId),
|
||||
filters
|
||||
);
|
||||
return usePaginatedInfiniteQuery<ArticleOverview>(
|
||||
endpoint.feedManagement.getSourceArticleOverviewList(sourceId),
|
||||
filters,
|
||||
);
|
||||
};
|
||||
|
||||
export const useFollowSource = (sourceId: string) => {
|
||||
return usePostQuery(endpoint.feedManagement.followSource(sourceId));
|
||||
return usePostQuery(endpoint.feedManagement.followSource(sourceId));
|
||||
};
|
||||
|
||||
export const useUnfollowSource = (sourceId: string) => {
|
||||
return useDeleteQuery(endpoint.feedManagement.unfollowSource(sourceId));
|
||||
return useDeleteQuery(endpoint.feedManagement.unfollowSource(sourceId));
|
||||
};
|
||||
|
||||
@@ -3,13 +3,13 @@ import { LoginPayload, LoginResponse } from "@/api/schema/identity-and-access/lo
|
||||
import { useGetQuery, usePostQuery } from "@/api/shared";
|
||||
|
||||
export const useLogin = () => {
|
||||
return usePostQuery<LoginPayload, LoginResponse>(endpoint.identityAndAccess.login);
|
||||
return usePostQuery<LoginPayload, LoginResponse>(endpoint.identityAndAccess.login);
|
||||
};
|
||||
|
||||
export const useLogout = () => {
|
||||
return usePostQuery(endpoint.identityAndAccess.logout);
|
||||
return usePostQuery(endpoint.identityAndAccess.logout);
|
||||
};
|
||||
|
||||
export const useUnlockAccount = (token: string) => {
|
||||
return useGetQuery(endpoint.identityAndAccess.unlockAccount(token));
|
||||
return useGetQuery(endpoint.identityAndAccess.unlockAccount(token));
|
||||
};
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { endpoint } from "@/api/endpoint";
|
||||
import {
|
||||
RequestPasswordPayload,
|
||||
ResetPasswordPayload,
|
||||
UpdatePasswordPayload,
|
||||
RequestPasswordPayload,
|
||||
ResetPasswordPayload,
|
||||
UpdatePasswordPayload,
|
||||
} from "@/api/schema/identity-and-access/password";
|
||||
import { usePostQuery, usePutQuery } from "@/api/shared";
|
||||
|
||||
export const usePasswordForgotten = () => {
|
||||
return usePostQuery<RequestPasswordPayload>(endpoint.identityAndAccess.requestPassword);
|
||||
return usePostQuery<RequestPasswordPayload>(endpoint.identityAndAccess.requestPassword);
|
||||
};
|
||||
|
||||
export const usePasswordReset = (token: string) => {
|
||||
return usePostQuery<ResetPasswordPayload>(endpoint.identityAndAccess.resetPassword(token));
|
||||
return usePostQuery<ResetPasswordPayload>(endpoint.identityAndAccess.resetPassword(token));
|
||||
};
|
||||
|
||||
export const usePasswordUpdate = () => {
|
||||
return usePutQuery<UpdatePasswordPayload>(endpoint.identityAndAccess.updatePassword);
|
||||
return usePutQuery<UpdatePasswordPayload>(endpoint.identityAndAccess.updatePassword);
|
||||
};
|
||||
|
||||
@@ -3,9 +3,9 @@ import { RegisterPayload } from "@/api/schema/identity-and-access/register";
|
||||
import { useGetQuery, usePostQuery } from "@/api/shared";
|
||||
|
||||
export const useRegister = () => {
|
||||
return usePostQuery<RegisterPayload>(endpoint.identityAndAccess.register);
|
||||
return usePostQuery<RegisterPayload>(endpoint.identityAndAccess.register);
|
||||
};
|
||||
|
||||
export const useConfirmAccount = (token: string) => {
|
||||
return useGetQuery(endpoint.identityAndAccess.confirmAccount(token));
|
||||
return useGetQuery(endpoint.identityAndAccess.confirmAccount(token));
|
||||
};
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
import { SourceReference } from "@/api/schema/feed-management/source";
|
||||
|
||||
export type ArticleOverview = {
|
||||
id: string;
|
||||
title: string;
|
||||
link: string;
|
||||
categories: string[];
|
||||
excerpt: string;
|
||||
source: SourceReference;
|
||||
publishedAt: string;
|
||||
image?: string;
|
||||
readingTime: number;
|
||||
bookmarked: boolean;
|
||||
id: string;
|
||||
title: string;
|
||||
link: string;
|
||||
categories: string[];
|
||||
excerpt: string;
|
||||
source: SourceReference;
|
||||
publishedAt: string;
|
||||
image?: string;
|
||||
readingTime: number;
|
||||
bookmarked: boolean;
|
||||
};
|
||||
|
||||
export type Article = {
|
||||
id: string;
|
||||
title: string;
|
||||
link: string;
|
||||
categories: string[];
|
||||
body: string;
|
||||
source: SourceReference;
|
||||
hash: string;
|
||||
credibility: {
|
||||
bias: "neutral" | "slightly" | "partisan" | "extreme";
|
||||
reliability: "trusted" | "reliable" | "average" | "unreliable" | "low_trust";
|
||||
transparency: "low" | "medium" | "high";
|
||||
};
|
||||
sentiment: "negative" | "positive" | "neutral";
|
||||
metadata?: {
|
||||
title?: string;
|
||||
description?: string;
|
||||
image?: string;
|
||||
video?: string;
|
||||
audio?: string;
|
||||
locale?: string;
|
||||
};
|
||||
readingTime: number;
|
||||
publishedAt: string;
|
||||
crawledAt: string;
|
||||
updatedAt: string;
|
||||
bookmarked: boolean;
|
||||
id: string;
|
||||
title: string;
|
||||
link: string;
|
||||
categories: string[];
|
||||
body: string;
|
||||
source: SourceReference;
|
||||
hash: string;
|
||||
credibility: {
|
||||
bias: "neutral" | "slightly" | "partisan" | "extreme";
|
||||
reliability: "trusted" | "reliable" | "average" | "unreliable" | "low_trust";
|
||||
transparency: "low" | "medium" | "high";
|
||||
};
|
||||
sentiment: "negative" | "positive" | "neutral";
|
||||
metadata?: {
|
||||
title?: string;
|
||||
description?: string;
|
||||
image?: string;
|
||||
video?: string;
|
||||
audio?: string;
|
||||
locale?: string;
|
||||
};
|
||||
readingTime: number;
|
||||
publishedAt: string;
|
||||
crawledAt: string;
|
||||
updatedAt: string;
|
||||
bookmarked: boolean;
|
||||
};
|
||||
|
||||
export type TrendingArticle = ArticleOverview;
|
||||
|
||||
@@ -3,28 +3,28 @@ import Joi from "joi";
|
||||
import { ArticleOverview } from "@/api/schema/feed-management/article";
|
||||
|
||||
export type BookmarkPayload = {
|
||||
name: string;
|
||||
description?: string;
|
||||
isPublic: boolean;
|
||||
name: string;
|
||||
description?: string;
|
||||
isPublic: boolean;
|
||||
};
|
||||
|
||||
export type Bookmark = {
|
||||
id: string;
|
||||
name: string;
|
||||
createdAt: string;
|
||||
description?: string;
|
||||
articlesCount: number;
|
||||
isPublic: boolean;
|
||||
updatedAt?: string;
|
||||
id: string;
|
||||
name: string;
|
||||
createdAt: string;
|
||||
description?: string;
|
||||
articlesCount: number;
|
||||
isPublic: boolean;
|
||||
updatedAt?: string;
|
||||
};
|
||||
|
||||
export type BookmarkedArticle = ArticleOverview;
|
||||
|
||||
export const BookmarkPayloadSchema = Joi.object({
|
||||
name: Joi.string().required().messages({
|
||||
"string.empty": "Le nom est requis",
|
||||
"any.required": "Le nom est requis",
|
||||
}),
|
||||
description: Joi.string().optional(),
|
||||
isPublic: Joi.boolean().optional(),
|
||||
description: Joi.string().optional(),
|
||||
isPublic: Joi.boolean().optional(),
|
||||
name: Joi.string().required().messages({
|
||||
"any.required": "Le nom est requis",
|
||||
"string.empty": "Le nom est requis",
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
export type Comment = {
|
||||
id: string;
|
||||
content: string;
|
||||
user: {
|
||||
id: string;
|
||||
content: string;
|
||||
user: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
sentiment: "positive" | "neutral" | "negative";
|
||||
createdAt: string;
|
||||
name: string;
|
||||
};
|
||||
sentiment: "positive" | "neutral" | "negative";
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export type CommentPayload = {
|
||||
content: string;
|
||||
content: string;
|
||||
};
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
export type SourceReference = {
|
||||
id: string;
|
||||
name: string;
|
||||
displayName?: string;
|
||||
image: string;
|
||||
url: string;
|
||||
id: string;
|
||||
name: string;
|
||||
displayName?: string;
|
||||
image: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type SourceOverview = {
|
||||
id: string;
|
||||
name: string;
|
||||
displayName?: string;
|
||||
image: string;
|
||||
url: string;
|
||||
followed: boolean;
|
||||
id: string;
|
||||
name: string;
|
||||
displayName?: string;
|
||||
image: string;
|
||||
url: string;
|
||||
followed: boolean;
|
||||
};
|
||||
|
||||
export type CategoryShare = {
|
||||
name: string;
|
||||
count: number;
|
||||
percentage: number;
|
||||
name: string;
|
||||
count: number;
|
||||
percentage: number;
|
||||
};
|
||||
|
||||
export type PublicationEntry = {
|
||||
date: string;
|
||||
count: number;
|
||||
date: string;
|
||||
count: number;
|
||||
};
|
||||
|
||||
export type SourceDetails = {
|
||||
id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
credibility: {
|
||||
bias: "neutral" | "slightly" | "partisan" | "extreme";
|
||||
reliability: "trusted" | "reliable" | "average" | "unreliable" | "low_trust";
|
||||
transparency: "low" | "medium" | "high";
|
||||
};
|
||||
publicationGraph: {
|
||||
items: PublicationEntry[];
|
||||
total: number;
|
||||
};
|
||||
categoryShares: {
|
||||
items: CategoryShare[];
|
||||
total: number;
|
||||
};
|
||||
articlesCount: number;
|
||||
crawledAt: string;
|
||||
displayName?: string;
|
||||
description?: string;
|
||||
updatedAt?: string;
|
||||
metadataAvailable: number;
|
||||
followed: boolean;
|
||||
image: string;
|
||||
id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
credibility: {
|
||||
bias: "neutral" | "slightly" | "partisan" | "extreme";
|
||||
reliability: "trusted" | "reliable" | "average" | "unreliable" | "low_trust";
|
||||
transparency: "low" | "medium" | "high";
|
||||
};
|
||||
publicationGraph: {
|
||||
items: PublicationEntry[];
|
||||
total: number;
|
||||
};
|
||||
categoryShares: {
|
||||
items: CategoryShare[];
|
||||
total: number;
|
||||
};
|
||||
articlesCount: number;
|
||||
crawledAt: string;
|
||||
displayName?: string;
|
||||
description?: string;
|
||||
updatedAt?: string;
|
||||
metadataAvailable: number;
|
||||
followed: boolean;
|
||||
image: string;
|
||||
};
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import Joi from "joi";
|
||||
|
||||
export type LoginPayload = {
|
||||
username: string;
|
||||
password: string;
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
export type LoginResponse = {
|
||||
token: string;
|
||||
refresh_token: string;
|
||||
token: string;
|
||||
refresh_token: string;
|
||||
};
|
||||
|
||||
export type RefreshTokenPayload = {
|
||||
refresh_token: string;
|
||||
refresh_token: string;
|
||||
};
|
||||
|
||||
export type RefreshTokenResponse = {
|
||||
token: string;
|
||||
token: string;
|
||||
};
|
||||
|
||||
export const LoginPayloadSchema = Joi.object<LoginPayload>({
|
||||
username: Joi.string().required().messages({
|
||||
"string.empty": "L'email est requis",
|
||||
"any.required": "L'email est requis",
|
||||
}),
|
||||
password: Joi.string().min(4).required().messages({
|
||||
"string.empty": "Le mot de passe est requis",
|
||||
"string.min": "Le mot de passe doit comporter au moins 4 caractères",
|
||||
"any.required": "Le mot de passe est requis",
|
||||
}),
|
||||
password: Joi.string().min(4).required().messages({
|
||||
"any.required": "Le mot de passe est requis",
|
||||
"string.empty": "Le mot de passe est requis",
|
||||
"string.min": "Le mot de passe doit comporter au moins 4 caractères",
|
||||
}),
|
||||
username: Joi.string().required().messages({
|
||||
"any.required": "L'email est requis",
|
||||
"string.empty": "L'email est requis",
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,53 +1,53 @@
|
||||
import Joi from "joi";
|
||||
|
||||
export type RequestPasswordPayload = {
|
||||
email: string;
|
||||
email: string;
|
||||
};
|
||||
|
||||
export type ResetPasswordPayload = {
|
||||
password: string;
|
||||
confirm: string;
|
||||
password: string;
|
||||
confirm: string;
|
||||
};
|
||||
|
||||
export type UpdatePasswordPayload = {
|
||||
current: string;
|
||||
password: string;
|
||||
confirm: string;
|
||||
current: string;
|
||||
password: string;
|
||||
confirm: string;
|
||||
};
|
||||
|
||||
export const RequestPasswordPayloadSchema = Joi.object<RequestPasswordPayload>({
|
||||
email: Joi.string().required().messages({
|
||||
"string.empty": "L'email est requis",
|
||||
"any.required": "L'email est requis",
|
||||
}),
|
||||
email: Joi.string().required().messages({
|
||||
"any.required": "L'email est requis",
|
||||
"string.empty": "L'email est requis",
|
||||
}),
|
||||
});
|
||||
|
||||
export const ResetPasswordPayloadSchema = Joi.object<ResetPasswordPayload>({
|
||||
password: Joi.string().min(6).required().messages({
|
||||
"string.empty": "Le mot de passe est requis",
|
||||
"string.min": "Le mot de passe doit comporter au moins 6 caractères",
|
||||
"any.required": "Le mot de passe est requis",
|
||||
}),
|
||||
confirm: Joi.string().valid(Joi.ref("password")).required().messages({
|
||||
"any.only": "Les mots de passe ne correspondent pas",
|
||||
"string.empty": "La confirmation du mot de passe est requise",
|
||||
"any.required": "La confirmation du mot de passe est requise",
|
||||
}),
|
||||
confirm: Joi.string().valid(Joi.ref("password")).required().messages({
|
||||
"any.only": "Les mots de passe ne correspondent pas",
|
||||
"any.required": "La confirmation du mot de passe est requise",
|
||||
"string.empty": "La confirmation du mot de passe est requise",
|
||||
}),
|
||||
password: Joi.string().min(6).required().messages({
|
||||
"any.required": "Le mot de passe est requis",
|
||||
"string.empty": "Le mot de passe est requis",
|
||||
"string.min": "Le mot de passe doit comporter au moins 6 caractères",
|
||||
}),
|
||||
});
|
||||
|
||||
export const UpdatePasswordPayloadSchema = Joi.object<UpdatePasswordPayload>({
|
||||
current: Joi.string().required().messages({
|
||||
"string.empty": "Le mot de passe actuel est requis",
|
||||
"any.required": "Le mot de passe actuel est requis",
|
||||
}),
|
||||
password: Joi.string().min(6).required().messages({
|
||||
"string.empty": "Le nouveau mot de passe est requis",
|
||||
"string.min": "Le nouveau mot de passe doit comporter au moins 6 caractères",
|
||||
"any.required": "Le nouveau mot de passe est requis",
|
||||
}),
|
||||
confirm: Joi.string().valid(Joi.ref("password")).required().messages({
|
||||
"any.only": "Les mots de passe ne correspondent pas",
|
||||
"string.empty": "La confirmation du nouveau mot de passe est requise",
|
||||
"any.required": "La confirmation du nouveau mot de passe est requise",
|
||||
}),
|
||||
confirm: Joi.string().valid(Joi.ref("password")).required().messages({
|
||||
"any.only": "Les mots de passe ne correspondent pas",
|
||||
"any.required": "La confirmation du nouveau mot de passe est requise",
|
||||
"string.empty": "La confirmation du nouveau mot de passe est requise",
|
||||
}),
|
||||
current: Joi.string().required().messages({
|
||||
"any.required": "Le mot de passe actuel est requis",
|
||||
"string.empty": "Le mot de passe actuel est requis",
|
||||
}),
|
||||
password: Joi.string().min(6).required().messages({
|
||||
"any.required": "Le nouveau mot de passe est requis",
|
||||
"string.empty": "Le nouveau mot de passe est requis",
|
||||
"string.min": "Le nouveau mot de passe doit comporter au moins 6 caractères",
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import Joi from "joi";
|
||||
|
||||
export type RegisterPayload = {
|
||||
name: string;
|
||||
email: string;
|
||||
password: string;
|
||||
name: string;
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
export const RegisterPayloadSchema = Joi.object<RegisterPayload>({
|
||||
name: Joi.string().required().messages({
|
||||
"string.empty": "Le nom est requis",
|
||||
"any.required": "Le nom est requis",
|
||||
}),
|
||||
email: Joi.string().required().messages({
|
||||
"string.empty": "L'email est requis",
|
||||
"any.required": "L'email est requis",
|
||||
}),
|
||||
password: Joi.string().min(6).required().messages({
|
||||
"string.empty": "Le mot de passe est requis",
|
||||
"string.min": "Le mot de passe doit comporter au moins 4 caractères",
|
||||
"any.required": "Le mot de passe est requis",
|
||||
}),
|
||||
email: Joi.string().required().messages({
|
||||
"any.required": "L'email est requis",
|
||||
"string.empty": "L'email est requis",
|
||||
}),
|
||||
name: Joi.string().required().messages({
|
||||
"any.required": "Le nom est requis",
|
||||
"string.empty": "Le nom est requis",
|
||||
}),
|
||||
password: Joi.string().min(6).required().messages({
|
||||
"any.required": "Le mot de passe est requis",
|
||||
"string.empty": "Le mot de passe est requis",
|
||||
"string.min": "Le mot de passe doit comporter au moins 4 caractères",
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,150 +1,167 @@
|
||||
import { skipToken, useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
skipToken,
|
||||
useInfiniteQuery,
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
import qs from "qs";
|
||||
|
||||
import client from "@/api/client";
|
||||
|
||||
export type PaginationFilters = {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
lastId?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
lastId?: string;
|
||||
};
|
||||
|
||||
export type ArticleFilters = PaginationFilters & {
|
||||
dateRange?: {
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
search?: string;
|
||||
sortDirection?: "asc" | "desc";
|
||||
dateRange?: {
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
search?: string;
|
||||
sortDirection?: "asc" | "desc";
|
||||
};
|
||||
|
||||
export type PaginationInfo = {
|
||||
current: number;
|
||||
limit: number;
|
||||
lastId?: string;
|
||||
offset: number;
|
||||
current: number;
|
||||
limit: number;
|
||||
lastId?: string;
|
||||
offset: number;
|
||||
};
|
||||
|
||||
export type ClientDetailErrorResponse = {
|
||||
type: string;
|
||||
title: string;
|
||||
detail: string;
|
||||
status: number;
|
||||
type: string;
|
||||
title: string;
|
||||
detail: string;
|
||||
status: number;
|
||||
};
|
||||
|
||||
export type ClientErrorResponse = {
|
||||
code: string;
|
||||
message: string;
|
||||
code: string;
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type ErrorResponse = AxiosError<ClientErrorResponse | ClientDetailErrorResponse>;
|
||||
|
||||
export type PaginatedResponse<TItem> = {
|
||||
items: TItem[];
|
||||
pagination: PaginationInfo;
|
||||
items: TItem[];
|
||||
pagination: PaginationInfo;
|
||||
};
|
||||
|
||||
export const safeMessage = (error: AxiosError<ClientErrorResponse | ClientDetailErrorResponse> | Error): string => {
|
||||
if (error instanceof AxiosError && error.response) {
|
||||
const response = error.response.data;
|
||||
export const safeMessage = (
|
||||
error: AxiosError<ClientErrorResponse | ClientDetailErrorResponse> | Error,
|
||||
): string => {
|
||||
if (error instanceof AxiosError && error.response) {
|
||||
const response = error.response.data;
|
||||
|
||||
if ("message" in response) {
|
||||
return response.message;
|
||||
} else if ("detail" in response) {
|
||||
return response.detail;
|
||||
}
|
||||
if ("message" in response) {
|
||||
return response.message;
|
||||
} else if ("detail" in response) {
|
||||
return response.detail;
|
||||
}
|
||||
}
|
||||
|
||||
return "Une erreur est survenue";
|
||||
return "Une erreur est survenue";
|
||||
};
|
||||
|
||||
export const usePaginatedInfiniteQuery = <TItem>(endpoint: string, filters: PaginationFilters = {}) => {
|
||||
return useInfiniteQuery<PaginatedResponse<TItem>, ErrorResponse>({
|
||||
initialData: undefined,
|
||||
initialPageParam: null,
|
||||
queryKey: [endpoint, filters],
|
||||
queryFn: async ({ pageParam = null }) => {
|
||||
const query = qs.stringify({ ...filters, lastId: pageParam }, { skipNulls: true });
|
||||
const url = `${endpoint}?${query}`;
|
||||
const response = await client.get<PaginatedResponse<TItem>>(url);
|
||||
return response.data;
|
||||
},
|
||||
getNextPageParam: (lastPage: PaginatedResponse<TItem>) => {
|
||||
const { lastId } = lastPage.pagination;
|
||||
return lastId ? lastId : null;
|
||||
},
|
||||
staleTime: 1_000 * 60 * 10,
|
||||
});
|
||||
export const usePaginatedInfiniteQuery = <TItem>(
|
||||
endpoint: string,
|
||||
filters: PaginationFilters = {},
|
||||
) => {
|
||||
return useInfiniteQuery<PaginatedResponse<TItem>, ErrorResponse>({
|
||||
getNextPageParam: (lastPage: PaginatedResponse<TItem>) => {
|
||||
const { lastId } = lastPage.pagination;
|
||||
return lastId ? lastId : null;
|
||||
},
|
||||
initialData: undefined,
|
||||
initialPageParam: null,
|
||||
queryFn: async ({ pageParam = null }) => {
|
||||
const query = qs.stringify({ ...filters, lastId: pageParam }, { skipNulls: true });
|
||||
const url = `${endpoint}?${query}`;
|
||||
const response = await client.get<PaginatedResponse<TItem>>(url);
|
||||
return response.data;
|
||||
},
|
||||
queryKey: [endpoint, filters],
|
||||
staleTime: 1_000 * 60 * 10,
|
||||
});
|
||||
};
|
||||
|
||||
export const usePaginatedQuery = <TItem>(endpoint: string, filters: PaginationFilters = {}) => {
|
||||
return useQuery<PaginatedResponse<TItem>, ErrorResponse>({
|
||||
queryKey: [endpoint, filters],
|
||||
queryFn: async (): Promise<PaginatedResponse<TItem>> => {
|
||||
const query = qs.stringify({ ...filters, lastId: null }, { skipNulls: true });
|
||||
const url = `${endpoint}?${query}`;
|
||||
const response = await client.get<PaginatedResponse<TItem>>(url);
|
||||
return response.data;
|
||||
},
|
||||
staleTime: 1_000 * 60 * 10,
|
||||
});
|
||||
return useQuery<PaginatedResponse<TItem>, ErrorResponse>({
|
||||
queryFn: async (): Promise<PaginatedResponse<TItem>> => {
|
||||
const query = qs.stringify({ ...filters, lastId: null }, { skipNulls: true });
|
||||
const url = `${endpoint}?${query}`;
|
||||
const response = await client.get<PaginatedResponse<TItem>>(url);
|
||||
return response.data;
|
||||
},
|
||||
queryKey: [endpoint, filters],
|
||||
staleTime: 1_000 * 60 * 10,
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetQuery = <TItem>(endpoint: string, enabled: boolean = true) => {
|
||||
return useQuery<TItem, ErrorResponse>({
|
||||
queryKey: [endpoint],
|
||||
queryFn: enabled
|
||||
? async (): Promise<TItem> => {
|
||||
const response = await client.get<TItem>(endpoint);
|
||||
return response.data;
|
||||
}
|
||||
: skipToken,
|
||||
staleTime: 1_000 * 60 * 10,
|
||||
});
|
||||
return useQuery<TItem, ErrorResponse>({
|
||||
queryFn: enabled
|
||||
? async (): Promise<TItem> => {
|
||||
const response = await client.get<TItem>(endpoint);
|
||||
return response.data;
|
||||
}
|
||||
: skipToken,
|
||||
queryKey: [endpoint],
|
||||
staleTime: 1_000 * 60 * 10,
|
||||
});
|
||||
};
|
||||
|
||||
export const usePostQuery = <TPayload = void, TResponse = void>(endpoint: string, keys: string[] = []) => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<TResponse, ErrorResponse, TPayload>({
|
||||
mutationFn: async (data: TPayload): Promise<TResponse> => {
|
||||
const response = await client.post<TResponse>(endpoint, data);
|
||||
return response.data;
|
||||
},
|
||||
onSuccess: async () => {
|
||||
for (const key of keys) {
|
||||
await queryClient.invalidateQueries({ queryKey: [key] });
|
||||
}
|
||||
},
|
||||
});
|
||||
export const usePostQuery = <TPayload = void, TResponse = void>(
|
||||
endpoint: string,
|
||||
keys: string[] = [],
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<TResponse, ErrorResponse, TPayload>({
|
||||
mutationFn: async (data: TPayload): Promise<TResponse> => {
|
||||
const response = await client.post<TResponse>(endpoint, data);
|
||||
return response.data;
|
||||
},
|
||||
onSuccess: async () => {
|
||||
for (const key of keys) {
|
||||
await queryClient.invalidateQueries({ queryKey: [key] });
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const usePutQuery = <TPayload = void, TResponse = void>(endpoint: string, keys: string[] = []) => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<TResponse, ErrorResponse, TPayload>({
|
||||
mutationFn: async (data: TPayload): Promise<TResponse> => {
|
||||
const response = await client.put<TResponse>(endpoint, data);
|
||||
return response.data;
|
||||
},
|
||||
onSuccess: async () => {
|
||||
for (const key of keys) {
|
||||
await queryClient.invalidateQueries({ queryKey: [key] });
|
||||
}
|
||||
},
|
||||
});
|
||||
export const usePutQuery = <TPayload = void, TResponse = void>(
|
||||
endpoint: string,
|
||||
keys: string[] = [],
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<TResponse, ErrorResponse, TPayload>({
|
||||
mutationFn: async (data: TPayload): Promise<TResponse> => {
|
||||
const response = await client.put<TResponse>(endpoint, data);
|
||||
return response.data;
|
||||
},
|
||||
onSuccess: async () => {
|
||||
for (const key of keys) {
|
||||
await queryClient.invalidateQueries({ queryKey: [key] });
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteQuery = <TResponse = void>(endpoint: string, keys: string[] = []) => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<TResponse, ErrorResponse>({
|
||||
mutationFn: async (): Promise<TResponse> => {
|
||||
const response = await client.delete<TResponse>(endpoint);
|
||||
return response.data;
|
||||
},
|
||||
onSuccess: async () => {
|
||||
for (const key of keys) {
|
||||
await queryClient.invalidateQueries({ queryKey: [key] });
|
||||
}
|
||||
},
|
||||
});
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<TResponse, ErrorResponse>({
|
||||
mutationFn: async (): Promise<TResponse> => {
|
||||
const response = await client.delete<TResponse>(endpoint);
|
||||
return response.data;
|
||||
},
|
||||
onSuccess: async () => {
|
||||
for (const key of keys) {
|
||||
await queryClient.invalidateQueries({ queryKey: [key] });
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,82 +1,80 @@
|
||||
import React from "react";
|
||||
|
||||
import { BookMarked, Globe, Home, User } from "@tamagui/lucide-icons";
|
||||
import { Tabs } from "expo-router";
|
||||
import { useColorScheme } from "react-native";
|
||||
import { Paragraph } from "tamagui";
|
||||
|
||||
export default function TabLayout() {
|
||||
const colorScheme = useColorScheme();
|
||||
const colorScheme = useColorScheme();
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
initialRouteName="articles"
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
tabBarShowLabel: true,
|
||||
tabBarActiveTintColor: "$accent5",
|
||||
tabBarHideOnKeyboard: true,
|
||||
tabBarStyle: {
|
||||
backgroundColor: colorScheme === "dark" ? "black" : "white",
|
||||
borderTopWidth: 0,
|
||||
paddingBottom: 5,
|
||||
paddingTop: 5,
|
||||
},
|
||||
tabBarLabelStyle: {
|
||||
fontSize: 12,
|
||||
fontWeight: "600",
|
||||
textTransform: "none",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Tabs.Screen
|
||||
name="articles"
|
||||
options={{
|
||||
href: "/(authed)/(tabs)/articles",
|
||||
tabBarLabel: ({ color }) => (
|
||||
<Paragraph size="$2" color={color}>
|
||||
Actualités
|
||||
</Paragraph>
|
||||
),
|
||||
tabBarIcon: ({ color, size }) => <Home size={size} color={color} />,
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="sources"
|
||||
options={{
|
||||
href: "/(authed)/(tabs)/sources",
|
||||
tabBarLabel: ({ color }) => (
|
||||
<Paragraph size="$2" color={color}>
|
||||
Sources
|
||||
</Paragraph>
|
||||
),
|
||||
tabBarIcon: ({ color, size }) => <Globe size={size} color={color} />,
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="bookmarks"
|
||||
options={{
|
||||
href: "/(authed)/(tabs)/bookmarks",
|
||||
tabBarLabel: ({ color }) => (
|
||||
<Paragraph size="$2" color={color}>
|
||||
Signets
|
||||
</Paragraph>
|
||||
),
|
||||
tabBarIcon: ({ color, size }) => <BookMarked size={size} color={color} />,
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="account"
|
||||
options={{
|
||||
href: "/(authed)/(tabs)/account",
|
||||
tabBarLabel: ({ color }) => (
|
||||
<Paragraph size="$2" color={color}>
|
||||
Profil
|
||||
</Paragraph>
|
||||
),
|
||||
tabBarIcon: ({ color, size }) => <User size={size} color={color} />,
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
);
|
||||
return (
|
||||
<Tabs
|
||||
initialRouteName="articles"
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
tabBarActiveTintColor: "$accent5",
|
||||
tabBarHideOnKeyboard: true,
|
||||
tabBarLabelStyle: {
|
||||
fontSize: 12,
|
||||
fontWeight: "600",
|
||||
textTransform: "none",
|
||||
},
|
||||
tabBarShowLabel: true,
|
||||
tabBarStyle: {
|
||||
backgroundColor: colorScheme === "dark" ? "black" : "white",
|
||||
borderTopWidth: 0,
|
||||
paddingBottom: 5,
|
||||
paddingTop: 5,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Tabs.Screen
|
||||
name="articles"
|
||||
options={{
|
||||
href: "/(authed)/(tabs)/articles",
|
||||
tabBarIcon: ({ color, size }) => <Home color={color} size={size} />,
|
||||
tabBarLabel: ({ color }) => (
|
||||
<Paragraph color={color} size="$2">
|
||||
Actualités
|
||||
</Paragraph>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="sources"
|
||||
options={{
|
||||
href: "/(authed)/(tabs)/sources",
|
||||
tabBarIcon: ({ color, size }) => <Globe color={color} size={size} />,
|
||||
tabBarLabel: ({ color }) => (
|
||||
<Paragraph color={color} size="$2">
|
||||
Sources
|
||||
</Paragraph>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="bookmarks"
|
||||
options={{
|
||||
href: "/(authed)/(tabs)/bookmarks",
|
||||
tabBarIcon: ({ color, size }) => <BookMarked color={color} size={size} />,
|
||||
tabBarLabel: ({ color }) => (
|
||||
<Paragraph color={color} size="$2">
|
||||
Signets
|
||||
</Paragraph>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="account"
|
||||
options={{
|
||||
href: "/(authed)/(tabs)/account",
|
||||
tabBarIcon: ({ color, size }) => <User color={color} size={size} />,
|
||||
tabBarLabel: ({ color }) => (
|
||||
<Paragraph color={color} size="$2">
|
||||
Profil
|
||||
</Paragraph>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Stack } from "expo-router";
|
||||
|
||||
export default function Layout() {
|
||||
return <Stack screenOptions={{ headerShown: false }} />;
|
||||
return <Stack screenOptions={{ headerShown: false }} />;
|
||||
}
|
||||
|
||||
@@ -5,26 +5,26 @@ import { Label, ListItem, ScrollView, Separator, YGroup } from "tamagui";
|
||||
import { ScreenView } from "@/ui/components/layout";
|
||||
|
||||
export default function Index() {
|
||||
const router = useRouter();
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<ScreenView>
|
||||
<ScreenView.Heading title="Profile" />
|
||||
return (
|
||||
<ScreenView>
|
||||
<ScreenView.Heading title="Profile" />
|
||||
|
||||
<ScrollView width="100%">
|
||||
<Label>Settings</Label>
|
||||
<YGroup alignSelf="center" bordered size="$4">
|
||||
<YGroup.Item>
|
||||
<ListItem
|
||||
onPress={() => router.push("/account/settings")}
|
||||
icon={Settings}
|
||||
iconAfter={ChevronRight}
|
||||
title="Settings"
|
||||
/>
|
||||
</YGroup.Item>
|
||||
<Separator />
|
||||
</YGroup>
|
||||
</ScrollView>
|
||||
</ScreenView>
|
||||
);
|
||||
<ScrollView width="100%">
|
||||
<Label>Settings</Label>
|
||||
<YGroup alignSelf="center" bordered size="$4">
|
||||
<YGroup.Item>
|
||||
<ListItem
|
||||
icon={Settings}
|
||||
iconAfter={ChevronRight}
|
||||
onPress={() => router.push("/account/settings")}
|
||||
title="Settings"
|
||||
/>
|
||||
</YGroup.Item>
|
||||
<Separator />
|
||||
</YGroup>
|
||||
</ScrollView>
|
||||
</ScreenView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,30 +6,30 @@ import { useAuth } from "@/providers/auth-provider";
|
||||
import { ScreenView } from "@/ui/components/layout";
|
||||
|
||||
export default function Index() {
|
||||
const authState = useAuth();
|
||||
const { mutate, isPending } = useLogout();
|
||||
const authState = useAuth();
|
||||
const { mutate, isPending } = useLogout();
|
||||
|
||||
const handleLogout = async () => {
|
||||
mutate(undefined, {
|
||||
onSuccess: () => authState.logout(),
|
||||
onError: () => authState.logout(),
|
||||
});
|
||||
};
|
||||
const handleLogout = async () => {
|
||||
mutate(undefined, {
|
||||
onError: () => authState.logout(),
|
||||
onSuccess: () => authState.logout(),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ScreenView>
|
||||
<ScreenView.Heading title="Paramètres" />
|
||||
return (
|
||||
<ScreenView>
|
||||
<ScreenView.Heading title="Paramètres" />
|
||||
|
||||
<YStack width="100%">
|
||||
<Button
|
||||
disabled={isPending}
|
||||
onPress={handleLogout}
|
||||
theme={isPending ? "disabled" : "accent"}
|
||||
fontWeight="bold"
|
||||
>
|
||||
{isPending ? <ActivityIndicator /> : "Déconnexion"}
|
||||
</Button>
|
||||
</YStack>
|
||||
</ScreenView>
|
||||
);
|
||||
<YStack width="100%">
|
||||
<Button
|
||||
disabled={isPending}
|
||||
fontWeight="bold"
|
||||
onPress={handleLogout}
|
||||
theme={isPending ? "disabled" : "accent"}
|
||||
>
|
||||
{isPending ? <ActivityIndicator /> : "Déconnexion"}
|
||||
</Button>
|
||||
</YStack>
|
||||
</ScreenView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,79 +12,84 @@ import { ArticleCategoryPill, ArticleCoverImage } from "@/ui/components/content/
|
||||
import { SourceReferencePill } from "@/ui/components/content/source";
|
||||
import { BackButton } from "@/ui/components/controls/BackButton";
|
||||
import { IconButton } from "@/ui/components/controls/IconButton";
|
||||
import { ScreenView } from "@/ui/components/layout";
|
||||
import { LoadingView } from "@/ui/components/LoadingView";
|
||||
import { ScreenView } from "@/ui/components/layout";
|
||||
import { Caption, Text } from "@/ui/components/typography";
|
||||
|
||||
export default function ArticleDetails() {
|
||||
const router = useRouter();
|
||||
const { id } = useLocalSearchParams();
|
||||
const { data, isLoading, error } = useArticleDetails(id as string);
|
||||
const article: Article | undefined = data ?? undefined;
|
||||
const relativeTime = useRelativeTime(article?.publishedAt);
|
||||
const router = useRouter();
|
||||
const { id } = useLocalSearchParams();
|
||||
const { data, isLoading, error } = useArticleDetails(id as string);
|
||||
const article: Article | undefined = data ?? undefined;
|
||||
const relativeTime = useRelativeTime(article?.publishedAt);
|
||||
|
||||
const handleReadIntegrality = async () => {
|
||||
await WebBrowser.openBrowserAsync(article!.link);
|
||||
};
|
||||
const handleReadIntegrality = async () => {
|
||||
await WebBrowser.openBrowserAsync(article!.link);
|
||||
};
|
||||
|
||||
if (error) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Erreur",
|
||||
text2: safeMessage(error),
|
||||
});
|
||||
router.replace("/(authed)/(tabs)/articles");
|
||||
}
|
||||
if (error) {
|
||||
Toast.show({
|
||||
text1: "Erreur",
|
||||
text2: safeMessage(error),
|
||||
type: "error",
|
||||
});
|
||||
router.replace("/(authed)/(tabs)/articles");
|
||||
}
|
||||
|
||||
if (isLoading || article === undefined) {
|
||||
return <LoadingView />;
|
||||
}
|
||||
if (isLoading || article === undefined) {
|
||||
return <LoadingView />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ScreenView>
|
||||
<ScreenView.Heading
|
||||
leadingAction={<BackButton onPress={() => router.dismissTo("/(authed)/(tabs)/articles")} />}
|
||||
trailingActions={
|
||||
<>
|
||||
<IconButton onPress={() => {}} icon={<Bookmark size="$1" />} />
|
||||
<IconButton onPress={() => {}} icon={<Share size="$1" />} />
|
||||
<IconButton onPress={() => {}} icon={<MoreVertical size="$1" />} />
|
||||
</>
|
||||
}
|
||||
return (
|
||||
<ScreenView>
|
||||
<ScreenView.Heading
|
||||
leadingAction={<BackButton onPress={() => router.dismissTo("/(authed)/(tabs)/articles")} />}
|
||||
trailingActions={
|
||||
<>
|
||||
<IconButton icon={<Bookmark size="$1" />} onPress={() => {}} />
|
||||
<IconButton icon={<Share size="$1" />} onPress={() => {}} />
|
||||
<IconButton icon={<MoreVertical size="$1" />} onPress={() => {}} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<ScrollView>
|
||||
<YStack>
|
||||
{article.metadata?.image && (
|
||||
<ArticleCoverImage
|
||||
height={225}
|
||||
marginBottom="$4"
|
||||
uri={article.metadata.image}
|
||||
width="100%"
|
||||
/>
|
||||
<ScrollView>
|
||||
<YStack>
|
||||
{article.metadata?.image && (
|
||||
<ArticleCoverImage uri={article.metadata.image} width="100%" height={225} marginBottom="$4" />
|
||||
)}
|
||||
</YStack>
|
||||
<YStack gap="$4" backgroundColor="$background">
|
||||
<XStack gap="$2" flexWrap="wrap">
|
||||
{article.categories.map((category, index) => (
|
||||
<ArticleCategoryPill key={index} category={category.toLowerCase()} />
|
||||
))}
|
||||
</XStack>
|
||||
<H5 fontWeight="bold" marginBottom="$1">
|
||||
{article.title}
|
||||
</H5>
|
||||
)}
|
||||
</YStack>
|
||||
<YStack backgroundColor="$background" gap="$4">
|
||||
<XStack flexWrap="wrap" gap="$2">
|
||||
{article.categories.map((category, index) => (
|
||||
<ArticleCategoryPill category={category.toLowerCase()} key={index} />
|
||||
))}
|
||||
</XStack>
|
||||
<H5 fontWeight="bold" marginBottom="$1">
|
||||
{article.title}
|
||||
</H5>
|
||||
|
||||
<YStack gap="$2">
|
||||
<SourceReferencePill data={article.source} />
|
||||
<XStack height={20} alignItems="center">
|
||||
<Caption>{relativeTime}</Caption>
|
||||
<Separator alignSelf="stretch" vertical marginHorizontal={16} />
|
||||
<Caption>{article.readingTime} minutes de lecture</Caption>
|
||||
</XStack>
|
||||
</YStack>
|
||||
<YStack gap="$2">
|
||||
<SourceReferencePill data={article.source} />
|
||||
<XStack alignItems="center" height={20}>
|
||||
<Caption>{relativeTime}</Caption>
|
||||
<Separator alignSelf="stretch" marginHorizontal={16} vertical />
|
||||
<Caption>{article.readingTime} minutes de lecture</Caption>
|
||||
</XStack>
|
||||
</YStack>
|
||||
|
||||
<Text size="$3" marginTop="$2">
|
||||
{article.body.trim()}
|
||||
</Text>
|
||||
</YStack>
|
||||
<Button width="100%" onPress={handleReadIntegrality} theme="accent" fontWeight="bold">
|
||||
Consulter l'article
|
||||
</Button>
|
||||
</ScrollView>
|
||||
</ScreenView>
|
||||
);
|
||||
<Text marginTop="$2" size="$3">
|
||||
{article.body.trim()}
|
||||
</Text>
|
||||
</YStack>
|
||||
<Button fontWeight="bold" onPress={handleReadIntegrality} theme="accent" width="100%">
|
||||
Consulter l'article
|
||||
</Button>
|
||||
</ScrollView>
|
||||
</ScreenView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Stack } from "expo-router";
|
||||
|
||||
export default function Layout() {
|
||||
return <Stack screenOptions={{ headerShown: false }} />;
|
||||
return <Stack screenOptions={{ headerShown: false }} />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from "react";
|
||||
|
||||
import { ScrollView, YStack } from "tamagui";
|
||||
|
||||
import { useArticleOverviewList } from "@/api/request/feed-management/article";
|
||||
@@ -13,39 +11,42 @@ import { ScreenView } from "@/ui/components/layout";
|
||||
import { Heading } from "@/ui/components/typography";
|
||||
|
||||
export default function Index() {
|
||||
const { data: articles, isLoading: articlesLoading } = useArticleOverviewList({ limit: 10 });
|
||||
const { data: sources, isLoading: sourcesLoading } = useSourceOverviewList();
|
||||
const articleOverviews: ArticleOverview[] = useFlattenedItems(articles);
|
||||
const sourcesOverviews: SourceOverview[] = useFlattenedItems(sources);
|
||||
const { data: articles, isLoading: articlesLoading } = useArticleOverviewList({ limit: 10 });
|
||||
const { data: sources, isLoading: sourcesLoading } = useSourceOverviewList();
|
||||
const articleOverviews: ArticleOverview[] = useFlattenedItems(articles);
|
||||
const sourcesOverviews: SourceOverview[] = useFlattenedItems(sources);
|
||||
|
||||
return (
|
||||
<ScreenView paddingBottom={0}>
|
||||
<Heading>Actualités</Heading>
|
||||
<ScrollView contentContainerStyle={{ paddingBottom: 0 }}>
|
||||
<YStack gap="$4">
|
||||
<YStack gap="$2">
|
||||
<ScreenView.Section title="Tendances" forwardLink="/(authed)/(tabs)/articles/trending" />
|
||||
return (
|
||||
<ScreenView paddingBottom={0}>
|
||||
<Heading>Actualités</Heading>
|
||||
<ScrollView contentContainerStyle={{ paddingBottom: 0 }}>
|
||||
<YStack gap="$4">
|
||||
<YStack gap="$2">
|
||||
<ScreenView.Section
|
||||
forwardLink="/(authed)/(tabs)/articles/trending"
|
||||
title="Tendances"
|
||||
/>
|
||||
|
||||
{articlesLoading && <ArticleSkeletonList displayMode="card" horizontal={true} />}
|
||||
{!articlesLoading && (
|
||||
<ArticleList
|
||||
data={articleOverviews}
|
||||
refreshing={articlesLoading}
|
||||
displayMode="card"
|
||||
horizontal={true}
|
||||
/>
|
||||
)}
|
||||
</YStack>
|
||||
<YStack gap="$2">
|
||||
<ScreenView.Section title="Nos sources" forwardLink="/(authed)/(tabs)/sources" />
|
||||
{articlesLoading && <ArticleSkeletonList displayMode="card" horizontal={true} />}
|
||||
{!articlesLoading && (
|
||||
<ArticleList
|
||||
data={articleOverviews}
|
||||
displayMode="card"
|
||||
horizontal={true}
|
||||
refreshing={articlesLoading}
|
||||
/>
|
||||
)}
|
||||
</YStack>
|
||||
<YStack gap="$2">
|
||||
<ScreenView.Section forwardLink="/(authed)/(tabs)/sources" title="Nos sources" />
|
||||
|
||||
{sourcesLoading && <SourceSkeletonList horizontal={true} />}
|
||||
{!sourcesLoading && (
|
||||
<SourceList data={sourcesOverviews} refreshing={sourcesLoading} horizontal={true} />
|
||||
)}
|
||||
</YStack>
|
||||
</YStack>
|
||||
</ScrollView>
|
||||
</ScreenView>
|
||||
);
|
||||
{sourcesLoading && <SourceSkeletonList horizontal={true} />}
|
||||
{!sourcesLoading && (
|
||||
<SourceList data={sourcesOverviews} horizontal={true} refreshing={sourcesLoading} />
|
||||
)}
|
||||
</YStack>
|
||||
</YStack>
|
||||
</ScrollView>
|
||||
</ScreenView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from "react";
|
||||
|
||||
import { useRouter } from "expo-router";
|
||||
|
||||
import { useInfiniteArticleOverviewList } from "@/api/request/feed-management/article";
|
||||
@@ -10,31 +8,30 @@ import { BackButton } from "@/ui/components/controls/BackButton";
|
||||
import { ScreenView } from "@/ui/components/layout";
|
||||
|
||||
export default function Trending() {
|
||||
const router = useRouter();
|
||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, refetch } = useInfiniteArticleOverviewList(
|
||||
{ limit: 20 }
|
||||
);
|
||||
const articles: TrendingArticle[] = useFlattenedItems(data);
|
||||
const router = useRouter();
|
||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, refetch } =
|
||||
useInfiniteArticleOverviewList({ limit: 20 });
|
||||
const articles: TrendingArticle[] = useFlattenedItems(data);
|
||||
|
||||
return (
|
||||
<ScreenView paddingBottom={0}>
|
||||
<ScreenView.Heading
|
||||
leadingAction={<BackButton onPress={() => router.dismissTo("/(authed)/(tabs)/articles")} />}
|
||||
title="Actualités"
|
||||
/>
|
||||
return (
|
||||
<ScreenView paddingBottom={0}>
|
||||
<ScreenView.Heading
|
||||
leadingAction={<BackButton onPress={() => router.dismissTo("/(authed)/(tabs)/articles")} />}
|
||||
title="Actualités"
|
||||
/>
|
||||
|
||||
{isLoading && <ArticleSkeletonList displayMode="magazine" />}
|
||||
{!isLoading && (
|
||||
<ArticleList
|
||||
data={articles}
|
||||
fetchNextPage={fetchNextPage}
|
||||
hasNextPage={hasNextPage}
|
||||
isFetchingNextPage={isFetchingNextPage}
|
||||
refreshing={isLoading}
|
||||
onRefresh={refetch}
|
||||
infiniteScroll={true}
|
||||
/>
|
||||
)}
|
||||
</ScreenView>
|
||||
);
|
||||
{isLoading && <ArticleSkeletonList displayMode="magazine" />}
|
||||
{!isLoading && (
|
||||
<ArticleList
|
||||
data={articles}
|
||||
fetchNextPage={fetchNextPage}
|
||||
hasNextPage={hasNextPage}
|
||||
infiniteScroll={true}
|
||||
isFetchingNextPage={isFetchingNextPage}
|
||||
onRefresh={refetch}
|
||||
refreshing={isLoading}
|
||||
/>
|
||||
)}
|
||||
</ScreenView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ import { ScreenView } from "@/ui/components/layout";
|
||||
import { Heading } from "@/ui/components/typography";
|
||||
|
||||
export default function Details() {
|
||||
const { id } = useLocalSearchParams();
|
||||
// const { data, isLoading } = useBookmarkedArticlesList(id as string);
|
||||
// const articles: BookmarkedArticle[] = useFlattenedItems(data);
|
||||
const { id } = useLocalSearchParams();
|
||||
// const { data, isLoading } = useBookmarkedArticlesList(id as string);
|
||||
// const articles: BookmarkedArticle[] = useFlattenedItems(data);
|
||||
|
||||
return (
|
||||
<ScreenView>
|
||||
<Heading>Bookmark Infos</Heading>
|
||||
<Paragraph>{id}</Paragraph>
|
||||
</ScreenView>
|
||||
);
|
||||
return (
|
||||
<ScreenView>
|
||||
<Heading>Bookmark Infos</Heading>
|
||||
<Paragraph>{id}</Paragraph>
|
||||
</ScreenView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Stack } from "expo-router";
|
||||
|
||||
export default function Layout() {
|
||||
return <Stack screenOptions={{ headerShown: false }} />;
|
||||
return <Stack screenOptions={{ headerShown: false }} />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from "react";
|
||||
|
||||
import { Plus, Search } from "@tamagui/lucide-icons";
|
||||
import { YStack } from "tamagui";
|
||||
|
||||
@@ -8,35 +6,36 @@ import { Bookmark } from "@/api/schema/feed-management/bookmark";
|
||||
import { useFlattenedItems } from "@/hooks/use-flattened-items";
|
||||
import { BookmarkList } from "@/ui/components/content/bookmark";
|
||||
import { IconButton } from "@/ui/components/controls/IconButton";
|
||||
import { ScreenView } from "@/ui/components/layout";
|
||||
import { LoadingView } from "@/ui/components/LoadingView";
|
||||
import { ScreenView } from "@/ui/components/layout";
|
||||
|
||||
export default function Index() {
|
||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, refetch } = useBookmarkList();
|
||||
const bookmarks: Bookmark[] = useFlattenedItems(data);
|
||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, refetch } =
|
||||
useBookmarkList();
|
||||
const bookmarks: Bookmark[] = useFlattenedItems(data);
|
||||
|
||||
return (
|
||||
<ScreenView>
|
||||
<ScreenView.Heading
|
||||
title="Bookmarks"
|
||||
leadingAction={<IconButton onPress={() => {}} icon={<Plus size="$1" />} />}
|
||||
trailingActions={<IconButton onPress={() => {}} icon={<Search size="$1" />} />}
|
||||
/>
|
||||
return (
|
||||
<ScreenView>
|
||||
<ScreenView.Heading
|
||||
leadingAction={<IconButton icon={<Plus size="$1" />} onPress={() => {}} />}
|
||||
title="Bookmarks"
|
||||
trailingActions={<IconButton icon={<Search size="$1" />} onPress={() => {}} />}
|
||||
/>
|
||||
|
||||
<YStack width="100%">
|
||||
{isLoading && <LoadingView />}
|
||||
{!isLoading && (
|
||||
<BookmarkList
|
||||
data={bookmarks}
|
||||
refreshing={isLoading}
|
||||
onRefresh={refetch}
|
||||
infiniteScroll={true}
|
||||
hasNextPage={hasNextPage}
|
||||
isFetchingNextPage={isFetchingNextPage}
|
||||
fetchNextPage={fetchNextPage}
|
||||
/>
|
||||
)}
|
||||
</YStack>
|
||||
</ScreenView>
|
||||
);
|
||||
<YStack width="100%">
|
||||
{isLoading && <LoadingView />}
|
||||
{!isLoading && (
|
||||
<BookmarkList
|
||||
data={bookmarks}
|
||||
fetchNextPage={fetchNextPage}
|
||||
hasNextPage={hasNextPage}
|
||||
infiniteScroll={true}
|
||||
isFetchingNextPage={isFetchingNextPage}
|
||||
onRefresh={refetch}
|
||||
refreshing={isLoading}
|
||||
/>
|
||||
)}
|
||||
</YStack>
|
||||
</ScreenView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@ import { ScreenView } from "@/ui/components/layout";
|
||||
import { Heading, Text } from "@/ui/components/typography";
|
||||
|
||||
export default function SourceDetails() {
|
||||
const { name } = useLocalSearchParams();
|
||||
const { name } = useLocalSearchParams();
|
||||
|
||||
return (
|
||||
<ScreenView>
|
||||
<Heading>Source Details</Heading>
|
||||
<Text>{name}</Text>
|
||||
</ScreenView>
|
||||
);
|
||||
return (
|
||||
<ScreenView>
|
||||
<Heading>Source Details</Heading>
|
||||
<Text>{name}</Text>
|
||||
</ScreenView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Stack } from "expo-router";
|
||||
|
||||
export default function Layout() {
|
||||
return <Stack screenOptions={{ headerShown: false }} />;
|
||||
return <Stack screenOptions={{ headerShown: false }} />;
|
||||
}
|
||||
|
||||
@@ -5,15 +5,15 @@ import { SourceList, SourceSkeletonList } from "@/ui/components/content/source";
|
||||
import { ScreenView } from "@/ui/components/layout";
|
||||
|
||||
export default function Sources() {
|
||||
const { data, isLoading } = useSourceOverviewList();
|
||||
const sources: SourceOverview[] = useFlattenedItems(data);
|
||||
const { data, isLoading } = useSourceOverviewList();
|
||||
const sources: SourceOverview[] = useFlattenedItems(data);
|
||||
|
||||
return (
|
||||
<ScreenView>
|
||||
<ScreenView.Heading title="Sources" />
|
||||
return (
|
||||
<ScreenView>
|
||||
<ScreenView.Heading title="Sources" />
|
||||
|
||||
{isLoading && <SourceSkeletonList horizontal={false} />}
|
||||
{!isLoading && <SourceList data={sources} horizontal={false} />}
|
||||
</ScreenView>
|
||||
);
|
||||
{isLoading && <SourceSkeletonList horizontal={false} />}
|
||||
{!isLoading && <SourceList data={sources} horizontal={false} />}
|
||||
</ScreenView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,15 +3,15 @@ import { Redirect, Stack } from "expo-router";
|
||||
import { useAuth } from "@/providers/auth-provider";
|
||||
|
||||
export default function AuthedLayout() {
|
||||
const auth = useAuth();
|
||||
const auth = useAuth();
|
||||
|
||||
if (!auth.isReady) {
|
||||
return null;
|
||||
}
|
||||
if (!auth.isReady) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!auth.isLoggedIn) {
|
||||
return <Redirect href="/signin" />;
|
||||
}
|
||||
if (!auth.isLoggedIn) {
|
||||
return <Redirect href="/signin" />;
|
||||
}
|
||||
|
||||
return <Stack screenOptions={{ headerShown: false }} />;
|
||||
return <Stack screenOptions={{ headerShown: false }} />;
|
||||
}
|
||||
|
||||
@@ -3,15 +3,15 @@ import { Redirect, Stack } from "expo-router";
|
||||
import { useAuth } from "@/providers/auth-provider";
|
||||
|
||||
export default function AuthedLayout() {
|
||||
const auth = useAuth();
|
||||
const auth = useAuth();
|
||||
|
||||
if (!auth.isReady) {
|
||||
return null;
|
||||
}
|
||||
if (!auth.isReady) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (auth.isLoggedIn) {
|
||||
return <Redirect href="/(authed)/(tabs)/articles" />;
|
||||
}
|
||||
if (auth.isLoggedIn) {
|
||||
return <Redirect href="/(authed)/(tabs)/articles" />;
|
||||
}
|
||||
|
||||
return <Stack screenOptions={{ headerShown: false }} />;
|
||||
return <Stack screenOptions={{ headerShown: false }} />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from "react";
|
||||
|
||||
import { joiResolver } from "@hookform/resolvers/joi";
|
||||
import { Link, useRouter } from "expo-router";
|
||||
import { useForm } from "react-hook-form";
|
||||
@@ -7,7 +5,10 @@ import Toast from "react-native-toast-message";
|
||||
import { YStack } from "tamagui";
|
||||
|
||||
import { usePasswordForgotten } from "@/api/request/identity-and-access/password";
|
||||
import { RequestPasswordPayload, RequestPasswordPayloadSchema } from "@/api/schema/identity-and-access/password";
|
||||
import {
|
||||
RequestPasswordPayload,
|
||||
RequestPasswordPayloadSchema,
|
||||
} from "@/api/schema/identity-and-access/password";
|
||||
import { ErrorResponse, safeMessage } from "@/api/shared";
|
||||
import { FormEmailInput } from "@/ui/components/controls/forms";
|
||||
import { SubmitButton } from "@/ui/components/controls/SubmitButton";
|
||||
@@ -15,55 +16,56 @@ import { ScreenView } from "@/ui/components/layout";
|
||||
import { Heading, Text } from "@/ui/components/typography";
|
||||
|
||||
export default function PasswordRequest() {
|
||||
const { mutate, isPending } = usePasswordForgotten();
|
||||
const router = useRouter();
|
||||
const { mutate, isPending } = usePasswordForgotten();
|
||||
const router = useRouter();
|
||||
|
||||
const { control, handleSubmit, formState } = useForm<RequestPasswordPayload>({
|
||||
resolver: joiResolver(RequestPasswordPayloadSchema),
|
||||
});
|
||||
const { control, handleSubmit, formState } = useForm<RequestPasswordPayload>({
|
||||
resolver: joiResolver(RequestPasswordPayloadSchema),
|
||||
});
|
||||
|
||||
const onSubmit = (data: RequestPasswordPayload) => {
|
||||
mutate(data, {
|
||||
onSuccess: () => {
|
||||
Toast.show({
|
||||
text1: "Succès",
|
||||
text2: "Un mail avec les instructions vous a été envoyé",
|
||||
type: "success",
|
||||
});
|
||||
router.push("/(unauthed)/signin");
|
||||
},
|
||||
onError: (error: ErrorResponse) => {
|
||||
Toast.show({
|
||||
text1: "Erreur de connexion",
|
||||
text2: safeMessage(error),
|
||||
type: "error",
|
||||
});
|
||||
},
|
||||
const onSubmit = (data: RequestPasswordPayload) => {
|
||||
mutate(data, {
|
||||
onError: (error: ErrorResponse) => {
|
||||
Toast.show({
|
||||
text1: "Erreur de connexion",
|
||||
text2: safeMessage(error),
|
||||
type: "error",
|
||||
});
|
||||
};
|
||||
},
|
||||
onSuccess: () => {
|
||||
Toast.show({
|
||||
text1: "Succès",
|
||||
text2: "Un mail avec les instructions vous a été envoyé",
|
||||
type: "success",
|
||||
});
|
||||
router.push("/(unauthed)/signin");
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ScreenView>
|
||||
<YStack flex={1} gap="$4" width="100%" justifyContent="flex-start">
|
||||
<YStack gap="$4">
|
||||
<Heading>Mot de passe oublié ?</Heading>
|
||||
<Text>
|
||||
Veuillez entrer votre adresse e-mail pour recevoir un lien de réinitialisation de mot de passe.
|
||||
</Text>
|
||||
</YStack>
|
||||
return (
|
||||
<ScreenView>
|
||||
<YStack flex={1} gap="$4" justifyContent="flex-start" width="100%">
|
||||
<YStack gap="$4">
|
||||
<Heading>Mot de passe oublié ?</Heading>
|
||||
<Text>
|
||||
Veuillez entrer votre adresse e-mail pour recevoir un lien de réinitialisation de mot de
|
||||
passe.
|
||||
</Text>
|
||||
</YStack>
|
||||
|
||||
<FormEmailInput control={control} name="email" />
|
||||
<FormEmailInput control={control} name="email" />
|
||||
|
||||
<Link href="/signin" asChild>
|
||||
<Text>Vous avez pas de compte ? Se connecter</Text>
|
||||
</Link>
|
||||
</YStack>
|
||||
<SubmitButton
|
||||
label="Réinitialiser le mot de passe"
|
||||
onPress={handleSubmit(onSubmit)}
|
||||
isPending={isPending}
|
||||
isValid={formState.isValid}
|
||||
/>
|
||||
</ScreenView>
|
||||
);
|
||||
<Link asChild href="/signin">
|
||||
<Text>Vous avez pas de compte ? Se connecter</Text>
|
||||
</Link>
|
||||
</YStack>
|
||||
<SubmitButton
|
||||
isPending={isPending}
|
||||
isValid={formState.isValid}
|
||||
label="Réinitialiser le mot de passe"
|
||||
onPress={handleSubmit(onSubmit)}
|
||||
/>
|
||||
</ScreenView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from "react";
|
||||
|
||||
import { joiResolver } from "@hookform/resolvers/joi";
|
||||
import { Link, useRouter } from "expo-router";
|
||||
import { useForm } from "react-hook-form";
|
||||
@@ -7,7 +5,11 @@ import Toast from "react-native-toast-message";
|
||||
import { YStack } from "tamagui";
|
||||
|
||||
import { useLogin } from "@/api/request/identity-and-access/login";
|
||||
import { LoginPayload, LoginPayloadSchema, LoginResponse } from "@/api/schema/identity-and-access/login";
|
||||
import {
|
||||
LoginPayload,
|
||||
LoginPayloadSchema,
|
||||
LoginResponse,
|
||||
} from "@/api/schema/identity-and-access/login";
|
||||
import { ErrorResponse, safeMessage } from "@/api/shared";
|
||||
import { useAuth } from "@/providers/auth-provider";
|
||||
import { FormEmailInput, FormPasswordInput } from "@/ui/components/controls/forms";
|
||||
@@ -16,66 +18,66 @@ import { ScreenView } from "@/ui/components/layout";
|
||||
import { Caption, Heading, Text } from "@/ui/components/typography";
|
||||
|
||||
export default function SignIn() {
|
||||
const { mutate, isPending } = useLogin();
|
||||
const auth = useAuth();
|
||||
const router = useRouter();
|
||||
const { mutate, isPending } = useLogin();
|
||||
const auth = useAuth();
|
||||
const router = useRouter();
|
||||
|
||||
if (auth.isLoggedIn) {
|
||||
router.replace("/(authed)/(tabs)/articles");
|
||||
}
|
||||
if (auth.isLoggedIn) {
|
||||
router.replace("/(authed)/(tabs)/articles");
|
||||
}
|
||||
|
||||
const { control, handleSubmit, formState } = useForm<LoginPayload>({
|
||||
resolver: joiResolver(LoginPayloadSchema),
|
||||
});
|
||||
const { control, handleSubmit, formState } = useForm<LoginPayload>({
|
||||
resolver: joiResolver(LoginPayloadSchema),
|
||||
});
|
||||
|
||||
const onSubmit = (data: LoginPayload) => {
|
||||
mutate(data, {
|
||||
onSuccess: async (data: LoginResponse) => {
|
||||
auth.login(data.token, data.refresh_token);
|
||||
Toast.show({ text1: "Connexion réussie", type: "success" });
|
||||
},
|
||||
onError: (error: ErrorResponse) => {
|
||||
Toast.show({
|
||||
text1: "Erreur de connexion",
|
||||
text2: safeMessage(error),
|
||||
type: "error",
|
||||
});
|
||||
},
|
||||
const onSubmit = (data: LoginPayload) => {
|
||||
mutate(data, {
|
||||
onError: (error: ErrorResponse) => {
|
||||
Toast.show({
|
||||
text1: "Erreur de connexion",
|
||||
text2: safeMessage(error),
|
||||
type: "error",
|
||||
});
|
||||
};
|
||||
},
|
||||
onSuccess: async (data: LoginResponse) => {
|
||||
auth.login(data.token, data.refresh_token);
|
||||
Toast.show({ text1: "Connexion réussie", type: "success" });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ScreenView>
|
||||
<YStack flex={1} gap="$4" width="100%" justifyContent="flex-start">
|
||||
<YStack gap="$4">
|
||||
<Heading>Connexion</Heading>
|
||||
<Text>Bienvenue sur Basango, la plateforme d'actualités intelligente</Text>
|
||||
</YStack>
|
||||
return (
|
||||
<ScreenView>
|
||||
<YStack flex={1} gap="$4" justifyContent="flex-start" width="100%">
|
||||
<YStack gap="$4">
|
||||
<Heading>Connexion</Heading>
|
||||
<Text>Bienvenue sur Basango, la plateforme d'actualités intelligente</Text>
|
||||
</YStack>
|
||||
|
||||
<YStack gap="$2">
|
||||
<FormEmailInput control={control} name="username" />
|
||||
<YStack gap="$2">
|
||||
<FormPasswordInput control={control} name="password" />
|
||||
<Link href="/password-request" asChild>
|
||||
<Text color="$accent6"> Mot de passe oublié ?</Text>
|
||||
</Link>
|
||||
</YStack>
|
||||
</YStack>
|
||||
<YStack gap="$2">
|
||||
<FormEmailInput control={control} name="username" />
|
||||
<YStack gap="$2">
|
||||
<FormPasswordInput control={control} name="password" />
|
||||
<Link asChild href="/password-request">
|
||||
<Text color="$accent6"> Mot de passe oublié ?</Text>
|
||||
</Link>
|
||||
</YStack>
|
||||
</YStack>
|
||||
|
||||
<Caption>
|
||||
En continuant, vous acceptez les conditions d'utilisation de Basango et reconnaissez avoir lu
|
||||
notre politique de confidentialité.
|
||||
</Caption>
|
||||
<Link href="/signup" asChild>
|
||||
<Text>Vous n'avez pas de compte ? Créer un compte</Text>
|
||||
</Link>
|
||||
</YStack>
|
||||
<SubmitButton
|
||||
label="Se connecter"
|
||||
isPending={isPending}
|
||||
isValid={formState.isValid}
|
||||
onPress={handleSubmit(onSubmit)}
|
||||
/>
|
||||
</ScreenView>
|
||||
);
|
||||
<Caption>
|
||||
En continuant, vous acceptez les conditions d'utilisation de Basango et reconnaissez
|
||||
avoir lu notre politique de confidentialité.
|
||||
</Caption>
|
||||
<Link asChild href="/signup">
|
||||
<Text>Vous n'avez pas de compte ? Créer un compte</Text>
|
||||
</Link>
|
||||
</YStack>
|
||||
<SubmitButton
|
||||
isPending={isPending}
|
||||
isValid={formState.isValid}
|
||||
label="Se connecter"
|
||||
onPress={handleSubmit(onSubmit)}
|
||||
/>
|
||||
</ScreenView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from "react";
|
||||
|
||||
import { joiResolver } from "@hookform/resolvers/joi";
|
||||
import { User } from "@tamagui/lucide-icons";
|
||||
import { Link, useRouter } from "expo-router";
|
||||
@@ -16,66 +14,66 @@ import { ScreenView } from "@/ui/components/layout";
|
||||
import { Caption, Heading, Text } from "@/ui/components/typography";
|
||||
|
||||
export default function SingUp() {
|
||||
const router = useRouter();
|
||||
const { mutate, isPending } = useRegister();
|
||||
const router = useRouter();
|
||||
const { mutate, isPending } = useRegister();
|
||||
|
||||
const { control, handleSubmit, formState } = useForm<RegisterPayload>({
|
||||
resolver: joiResolver(RegisterPayloadSchema),
|
||||
});
|
||||
const { control, handleSubmit, formState } = useForm<RegisterPayload>({
|
||||
resolver: joiResolver(RegisterPayloadSchema),
|
||||
});
|
||||
|
||||
const onSubmit = (data: RegisterPayload) => {
|
||||
mutate(data, {
|
||||
onSuccess: () => {
|
||||
Toast.show({
|
||||
text1: "Félicitations !",
|
||||
text2: "les détails de votre compte vous ont été envoyés par e-mail.",
|
||||
type: "success",
|
||||
});
|
||||
router.replace("/(unauthed)/signin");
|
||||
},
|
||||
onError: (error: ErrorResponse) => {
|
||||
Toast.show({
|
||||
text1: "Erreur",
|
||||
text2: safeMessage(error),
|
||||
type: "error",
|
||||
});
|
||||
},
|
||||
const onSubmit = (data: RegisterPayload) => {
|
||||
mutate(data, {
|
||||
onError: (error: ErrorResponse) => {
|
||||
Toast.show({
|
||||
text1: "Erreur",
|
||||
text2: safeMessage(error),
|
||||
type: "error",
|
||||
});
|
||||
};
|
||||
},
|
||||
onSuccess: () => {
|
||||
Toast.show({
|
||||
text1: "Félicitations !",
|
||||
text2: "les détails de votre compte vous ont été envoyés par e-mail.",
|
||||
type: "success",
|
||||
});
|
||||
router.replace("/(unauthed)/signin");
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ScreenView>
|
||||
<YStack flex={1} gap="$4" width="100%" justifyContent="flex-start">
|
||||
<YStack gap="$4">
|
||||
<Heading>Inscription</Heading>
|
||||
<Text>Rejoignez la communauté Basango et restez informé des dernières actualités</Text>
|
||||
</YStack>
|
||||
return (
|
||||
<ScreenView>
|
||||
<YStack flex={1} gap="$4" justifyContent="flex-start" width="100%">
|
||||
<YStack gap="$4">
|
||||
<Heading>Inscription</Heading>
|
||||
<Text>Rejoignez la communauté Basango et restez informé des dernières actualités</Text>
|
||||
</YStack>
|
||||
|
||||
<YStack gap="$2">
|
||||
<FormTextInput
|
||||
control={control}
|
||||
name="name"
|
||||
leadingAdornment={User}
|
||||
label="Nom complet"
|
||||
placeholder="John Doe"
|
||||
/>
|
||||
<FormEmailInput control={control} name="email" />
|
||||
<FormPasswordInput control={control} name="password" />
|
||||
</YStack>
|
||||
<Caption>
|
||||
En continuant, vous acceptez les conditions d'utilisation de Basango et reconnaissez avoir lu
|
||||
notre politique de confidentialité.
|
||||
</Caption>
|
||||
<Link href="/signin">
|
||||
<Text>Vous avez un compte ? Connectez-vous</Text>
|
||||
</Link>
|
||||
</YStack>
|
||||
<SubmitButton
|
||||
label="Créer un compte"
|
||||
onPress={handleSubmit(onSubmit)}
|
||||
isPending={isPending}
|
||||
isValid={formState.isValid}
|
||||
/>
|
||||
</ScreenView>
|
||||
);
|
||||
<YStack gap="$2">
|
||||
<FormTextInput
|
||||
control={control}
|
||||
label="Nom complet"
|
||||
leadingAdornment={User}
|
||||
name="name"
|
||||
placeholder="John Doe"
|
||||
/>
|
||||
<FormEmailInput control={control} name="email" />
|
||||
<FormPasswordInput control={control} name="password" />
|
||||
</YStack>
|
||||
<Caption>
|
||||
En continuant, vous acceptez les conditions d'utilisation de Basango et reconnaissez
|
||||
avoir lu notre politique de confidentialité.
|
||||
</Caption>
|
||||
<Link href="/signin">
|
||||
<Text>Vous avez un compte ? Connectez-vous</Text>
|
||||
</Link>
|
||||
</YStack>
|
||||
<SubmitButton
|
||||
isPending={isPending}
|
||||
isValid={formState.isValid}
|
||||
label="Créer un compte"
|
||||
onPress={handleSubmit(onSubmit)}
|
||||
/>
|
||||
</ScreenView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,34 +6,34 @@ import { ScreenView } from "@/ui/components/layout";
|
||||
import { Caption, Display, Text } from "@/ui/components/typography";
|
||||
|
||||
export default function Welcome() {
|
||||
const router = useRouter();
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<ScreenView justifyContent="center">
|
||||
<AppIcon width={120} height={120} />
|
||||
<YStack width="100%" gap="$6">
|
||||
<YStack gap="$3">
|
||||
<Display textAlign="center">Bienvenue sur Basango</Display>
|
||||
<Text textAlign="center" lineHeight="$1" marginTop="auto">
|
||||
La première plateforme d'actualités intelligente qui vous aide à rester informé sur
|
||||
congolaise et internationale.
|
||||
</Text>
|
||||
</YStack>
|
||||
return (
|
||||
<ScreenView justifyContent="center">
|
||||
<AppIcon height={120} width={120} />
|
||||
<YStack gap="$6" width="100%">
|
||||
<YStack gap="$3">
|
||||
<Display textAlign="center">Bienvenue sur Basango</Display>
|
||||
<Text lineHeight="$1" marginTop="auto" textAlign="center">
|
||||
La première plateforme d'actualités intelligente qui vous aide à rester informé sur
|
||||
congolaise et internationale.
|
||||
</Text>
|
||||
</YStack>
|
||||
|
||||
<YStack gap="$4">
|
||||
<Button onPress={() => router.push("/signin")} theme="accent" fontWeight="bold">
|
||||
Se connecter
|
||||
</Button>
|
||||
<Link href="/signup" asChild>
|
||||
<Text textAlign="center">Ouvrir un compte</Text>
|
||||
</Link>
|
||||
</YStack>
|
||||
<YStack gap="$4">
|
||||
<Button fontWeight="bold" onPress={() => router.push("/signin")} theme="accent">
|
||||
Se connecter
|
||||
</Button>
|
||||
<Link asChild href="/signup">
|
||||
<Text textAlign="center">Ouvrir un compte</Text>
|
||||
</Link>
|
||||
</YStack>
|
||||
|
||||
<Caption textAlign="center">
|
||||
En continuant, vous acceptez les conditions d'utilisation de Basango et reconnaissez avoir lu
|
||||
notre politique de confidentialité.
|
||||
</Caption>
|
||||
</YStack>
|
||||
</ScreenView>
|
||||
);
|
||||
<Caption textAlign="center">
|
||||
En continuant, vous acceptez les conditions d'utilisation de Basango et reconnaissez
|
||||
avoir lu notre politique de confidentialité.
|
||||
</Caption>
|
||||
</YStack>
|
||||
</ScreenView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,28 +6,28 @@ import { ScreenView } from "@/ui/components/layout";
|
||||
import { Heading, Text } from "@/ui/components/typography";
|
||||
|
||||
export default function NotFoundScreen() {
|
||||
return (
|
||||
<ScreenView>
|
||||
<Stack.Screen options={{ title: "Oops !" }} />
|
||||
<View flex={1} backgroundColor="$background" padding="$4">
|
||||
<YStack alignItems="center" justifyContent="center" flex={1} gap="$4">
|
||||
<AppIcon width={100} height={100} />
|
||||
<YStack width="100%" gap="$6" alignItems="center" paddingHorizontal="$4">
|
||||
<YStack>
|
||||
<Heading fontWeight="bold" lineHeight="$8" textAlign="center">
|
||||
Une erreur s'est produite
|
||||
</Heading>
|
||||
<Text textAlign="center" lineHeight="$1" marginTop="auto">
|
||||
Nous avons une difficulté à charger la page que vous recherchez.
|
||||
</Text>
|
||||
</YStack>
|
||||
return (
|
||||
<ScreenView>
|
||||
<Stack.Screen options={{ title: "Oops !" }} />
|
||||
<View backgroundColor="$background" flex={1} padding="$4">
|
||||
<YStack alignItems="center" flex={1} gap="$4" justifyContent="center">
|
||||
<AppIcon height={100} width={100} />
|
||||
<YStack alignItems="center" gap="$6" paddingHorizontal="$4" width="100%">
|
||||
<YStack>
|
||||
<Heading fontWeight="bold" lineHeight="$8" textAlign="center">
|
||||
Une erreur s'est produite
|
||||
</Heading>
|
||||
<Text lineHeight="$1" marginTop="auto" textAlign="center">
|
||||
Nous avons une difficulté à charger la page que vous recherchez.
|
||||
</Text>
|
||||
</YStack>
|
||||
|
||||
<Link href="/(unauthed)/welcome">
|
||||
<Text>Recommencer</Text>
|
||||
</Link>
|
||||
</YStack>
|
||||
</YStack>
|
||||
</View>
|
||||
</ScreenView>
|
||||
);
|
||||
<Link href="/(unauthed)/welcome">
|
||||
<Text>Recommencer</Text>
|
||||
</Link>
|
||||
</YStack>
|
||||
</YStack>
|
||||
</View>
|
||||
</ScreenView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from "react";
|
||||
|
||||
import * as Sentry from "@sentry/react-native";
|
||||
import { Stack } from "expo-router";
|
||||
import React from "react";
|
||||
import { useColorScheme } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import Toast from "react-native-toast-message";
|
||||
@@ -12,28 +11,28 @@ import { RootProviders } from "@/providers/root-providers";
|
||||
export { ErrorBoundary } from "expo-router";
|
||||
|
||||
Sentry.init({
|
||||
dsn: process.env.EXPO_PUBLIC_SENTRY_DSN,
|
||||
sendDefaultPii: true,
|
||||
debug: __DEV__,
|
||||
tracesSampleRate: 1.0,
|
||||
tracePropagationTargets: [/.*?/],
|
||||
spotlight: __DEV__,
|
||||
debug: __DEV__,
|
||||
dsn: process.env.EXPO_PUBLIC_SENTRY_DSN,
|
||||
sendDefaultPii: true,
|
||||
spotlight: __DEV__,
|
||||
tracePropagationTargets: [/.*?/],
|
||||
tracesSampleRate: 1.0,
|
||||
});
|
||||
|
||||
function RootLayout() {
|
||||
const colorScheme = useColorScheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
const colorScheme = useColorScheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
return (
|
||||
<React.StrictMode>
|
||||
<RootProviders>
|
||||
<Theme name={colorScheme || "dark"}>
|
||||
<Stack screenOptions={{ headerShown: false }} />
|
||||
<Toast topOffset={insets.top + 10} position="top" visibilityTime={6_000} />
|
||||
</Theme>
|
||||
</RootProviders>
|
||||
</React.StrictMode>
|
||||
);
|
||||
return (
|
||||
<React.StrictMode>
|
||||
<RootProviders>
|
||||
<Theme name={colorScheme || "dark"}>
|
||||
<Stack screenOptions={{ headerShown: false }} />
|
||||
<Toast position="top" topOffset={insets.top + 10} visibilityTime={6_000} />
|
||||
</Theme>
|
||||
</RootProviders>
|
||||
</React.StrictMode>
|
||||
);
|
||||
}
|
||||
|
||||
export default Sentry.wrap(RootLayout);
|
||||
|
||||
@@ -3,11 +3,15 @@ import { Redirect } from "expo-router";
|
||||
import { useAuth } from "@/providers/auth-provider";
|
||||
|
||||
export default function Index() {
|
||||
const auth = useAuth();
|
||||
const auth = useAuth();
|
||||
|
||||
if (!auth.isReady) {
|
||||
return null;
|
||||
}
|
||||
if (!auth.isReady) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return auth.isLoggedIn ? <Redirect href="/(authed)/(tabs)/articles" /> : <Redirect href="/(unauthed)/welcome" />;
|
||||
return auth.isLoggedIn ? (
|
||||
<Redirect href="/(authed)/(tabs)/articles" />
|
||||
) : (
|
||||
<Redirect href="/(unauthed)/welcome" />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import * as React from "react";
|
||||
|
||||
import Svg, { Circle, G, Path, Rect, SvgProps } from "react-native-svg";
|
||||
|
||||
/**
|
||||
@@ -8,302 +6,314 @@ import Svg, { Circle, G, Path, Rect, SvgProps } from "react-native-svg";
|
||||
* @constructor
|
||||
*/
|
||||
export default function BookmarkIllustration(props: SvgProps) {
|
||||
return (
|
||||
<Svg viewBox="0 0 500 500" {...props}>
|
||||
<Path
|
||||
d="M414.54 101.87l-2.29-3.77C393 66.34 358.12 47.18 321.37 49.51a96.75 96.75 0 00-14.07 1.93c-33.41 7.15-62.49 32.42-74.54 64.78-7.08 19-9.06 40.94-22.51 56.05-17.51 19.66-47.22 20.28-73.35 19.46s-55.9-.12-73.28 19.62c-10.91 12.36-14.38 29.94-13.5 46.49 2.33 43.83 32.87 83 71.71 102.5S206.4 381.56 248.9 373c59.55-12 116.18-45 150.22-95.88s42.65-120.29 15.42-175.25z"
|
||||
fill="#ebebeb"
|
||||
/>
|
||||
<Path d="M379.87 196.94l8.35-14.72a39.47 39.47 0 119.45 10.51z" fill="#fff" />
|
||||
<Path
|
||||
d="M379.87 196.94l17.78-4.32-.07.21a.12.12 0 010-.18.12.12 0 01.16 0 40.49 40.49 0 008.07 4.8 39.16 39.16 0 0010.72 3 35 35 0 006.18.29c1.07-.06 2.15-.09 3.24-.19l3.29-.5a39.35 39.35 0 0024.31-15.68 38.86 38.86 0 006.56-15.05 39.73 39.73 0 00-.47-17.3 38.4 38.4 0 00-8.34-16l-1.59-1.75c-.52-.59-1.14-1.07-1.71-1.61a23.36 23.36 0 00-1.76-1.54l-1.87-1.4a16.47 16.47 0 00-1.93-1.27l-2-1.2-2.07-1a16.66 16.66 0 00-2.08-.92 39.71 39.71 0 00-17.44-2.7 38.5 38.5 0 00-16 4.71 39.64 39.64 0 00-18.55 22.72 42.6 42.6 0 00-1.74 12.69 39.22 39.22 0 005.74 19.42.11.11 0 010 .12l-8.46 14.66 8.25-14.78v.13a39.19 39.19 0 01-5.89-19.54 42.76 42.76 0 011.71-12.82 40 40 0 0118.7-23 38.89 38.89 0 0116.22-4.8 40.13 40.13 0 0117.66 2.72 16.55 16.55 0 012.11.94l2.1 1 2 1.22a16.87 16.87 0 011.95 1.29l1.89 1.42c.65.45 1.19 1 1.79 1.55s1.2 1 1.73 1.64l1.61 1.77a38.81 38.81 0 018.45 16.2 40.15 40.15 0 01.46 17.53 39 39 0 01-6.58 15.21 39.66 39.66 0 01-24.62 15.81l-3.33.5c-1.1.09-2.19.12-3.27.18a34.66 34.66 0 01-6.24-.32 39.65 39.65 0 01-10.8-3.08 40.6 40.6 0 01-8.1-4.89l.18-.18a.13.13 0 010 .18h-.05z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M414 143h-4.2a3.63 3.63 0 00-3.63 3.64v23.18l25.26-.37V146.6A3.63 3.63 0 01435 143z"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path d="M414 143h-4.2a3.63 3.63 0 00-3.63 3.64v23.18l25.26-.37V146.6A3.63 3.63 0 01435 143z" />
|
||||
<Path
|
||||
d="M438 146.6a3.61 3.61 0 00-3.62-3.63h-25.26a3.62 3.62 0 013.63 3.63v33.65a1 1 0 001.53.89l11.11-6.24 11.13 5.7a1 1 0 001.49-.91c-.01-5.79-.07-25.01-.01-33.09z"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path
|
||||
d="M425.37 155.93l.92 1.87a.54.54 0 00.4.29l2.07.3a.53.53 0 01.29.9l-1.49 1.46a.52.52 0 00-.16.47l.36 2.06a.53.53 0 01-.77.56l-1.85-1a.53.53 0 00-.49 0l-1.85 1a.53.53 0 01-.77-.56l.35-2.06a.52.52 0 00-.15-.47l-1.5-1.46a.53.53 0 01.3-.9l2.07-.3a.51.51 0 00.39-.29l.93-1.87a.53.53 0 01.95 0z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<G>
|
||||
<Path d="M207 29.4h33.29v53.07H203v-49a4.07 4.07 0 014-4.07z" fill="#23a99c" />
|
||||
<Path d="M207 29.4h33.33v53.07H203v-49a4 4 0 014-4.07z" />
|
||||
<Path
|
||||
d="M254.43 36l-25.8-.12-75.36-.35h-9.07l-22.58-.1a20.27 20.27 0 00-20.4 20.16l-1.38 324.68a20.28 20.28 0 0020.21 20.33l132.82.62a20.28 20.28 0 0020.39-20.16l1.38-324.74A20.28 20.28 0 00254.43 36z"
|
||||
fill="#fafafa"
|
||||
/>
|
||||
<Path
|
||||
d="M254.43 36h1.52a7.62 7.62 0 011.16.12l1.48.23 1.77.46c.62.2 1.29.46 2 .73s1.45.64 2.19 1.11a20.29 20.29 0 014.56 3.59 19.41 19.41 0 013.89 5.89 20.31 20.31 0 011.71 8v9.33c-.09 26.38-.23 64.55-.39 111.71s-.41 103.32-.65 165.67q0 11.69-.09 23.67 0 6-.05 12v3l-.09 1.54-.23 1.52a19.79 19.79 0 01-2 5.82 21.27 21.27 0 01-3.65 5 22.15 22.15 0 01-5 3.68 20.86 20.86 0 01-5.91 2l-1.56.23-1.57.08c-1 .05-2.09 0-3.13 0l-25.21-.12-105-.48a20.68 20.68 0 01-12.64-4.4 20.42 20.42 0 01-7.23-11.15 24.2 24.2 0 01-.62-6.66v-6.64q0-6.61.06-13.19.1-26.28.22-51.76c.15-33.93.29-66.66.43-97.85s.27-60.81.4-88.54q.09-20.79.18-40.13 0-9.65.09-18.91v-4.6-2.29l.07-1.14c0-.38.11-.76.16-1.13A20.32 20.32 0 01119 35.37a32.82 32.82 0 014.19-.16h12.21l30 .16 48.05.27 30.37.19 7.9.06h2.69-10.57l-30.37-.09-48.05-.18-30-.12h-12.2a30.26 30.26 0 00-4.15.16 20 20 0 00-17.39 16.8c0 .37-.13.74-.16 1.11l-.06 1.13V61.57q0 9.25-.07 18.92-.07 19.31-.16 40.12c-.11 27.73-.23 57.36-.35 88.55s-.26 63.91-.4 97.85q-.1 25.44-.22 51.76v19.82a23.4 23.4 0 00.61 6.53 19.83 19.83 0 007 10.87 20.16 20.16 0 0012.32 4.29l105 .49 25.21.12h3.11l1.54-.08 1.52-.22a20.61 20.61 0 005.77-2 21.63 21.63 0 004.89-3.59 21 21 0 003.57-4.87 19.22 19.22 0 001.95-5.68l.23-1.48.08-1.5c.06-1 0-2 0-3l.06-12.05q0-12 .1-23.66c.29-62.36.54-118.51.76-165.67s.42-85.33.56-111.71v-9.33a20.26 20.26 0 00-1.67-7.93 19.59 19.59 0 00-3.87-5.74 20.24 20.24 0 00-4.53-3.59c-.73-.47-1.49-.76-2.17-1.12s-1.37-.53-2-.74l-1.75-.46-1.47-.25a7.89 7.89 0 00-1.16-.13l-.84-.05z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path d="M120.84 256.95H252.26V265.74H120.84z" fill="#23a99c" />
|
||||
<Path
|
||||
d="M247.57 291.56c0 .32-28 .58-62.65.58s-62.65-.26-62.65-.58 28.05-.57 62.65-.57 62.65.25 62.65.57zM247.57 83c0 .32-28 .58-62.65.58s-62.65-.26-62.65-.58 28.05-.57 62.65-.57 62.65.29 62.65.57zM247.57 307.56c0 .32-28 .58-62.65.58s-62.65-.26-62.65-.58 28.05-.57 62.65-.57 62.65.26 62.65.57zM247.57 323.57c0 .31-28 .57-62.65.57s-62.65-.26-62.65-.57 28.05-.58 62.65-.58 62.65.26 62.65.58zM247.57 339.57c0 .32-28 .57-62.65.57s-62.65-.25-62.65-.57 28.05-.58 62.65-.58 62.65.26 62.65.58zM247.57 355.57c0 .32-28 .58-62.65.58s-62.65-.26-62.65-.58 28.05-.57 62.65-.57 62.65.25 62.65.57zM249.36 373.72c0 .31-28.05.57-62.64.57s-62.65-.26-62.65-.57 28-.58 62.65-.58 62.64.26 62.64.58z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path d="M122.27 101.29H254.01999999999998V228.75H122.27z" fill="#455a64" />
|
||||
<Path d="M211.44 139.49A9.48 9.48 0 11202 130a9.48 9.48 0 019.44 9.49z" />
|
||||
<Path d="M243.84 111.27l-111.31-.09c.13-.14-1.2 1.2-.64.65v56.08c0 17.85.05 34.73.06 50.26v.55h.55l80.84-.27 22.58-.15 5.91-.06h1.5a2.69 2.69 0 00.49 0l-27-46.26-18.12 22.27-29.81-41.93-35.86 65.34c0-15.37 0-32 .06-49.68v-27.46-28.08l110.15-.09c.12 31.59.21 57.91.28 76.43.06 9.28.1 16.59.14 21.64 0 2.47 0 4.38.06 5.74v1.48a4 4 0 000 .53 2.56 2.56 0 000-.47v-1.42c0-1.34 0-3.21.06-5.64 0-5 .08-12.29.14-21.52.07-18.7.16-45.33.28-77.31v-.54z" />
|
||||
<Path
|
||||
d="M252.89 34.88a5.46 5.46 0 00-5.45-5.48h-37.93a5.45 5.45 0 015.49 5.46V88.1l19-10.68 19 9.73s-.16-37.86-.11-52.27z"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path
|
||||
d="M235.17 49.32l1.32 2.68a.75.75 0 00.57.42l3 .43a.76.76 0 01.42 1.29l-2.14 2.09a.75.75 0 00-.22.67l.5 2.94a.75.75 0 01-1.09.8l-2.65-1.39a.75.75 0 00-.71 0l-2.64 1.39a.76.76 0 01-1.1-.8l.51-2.94a.75.75 0 00-.22-.67l-2.14-2.09a.76.76 0 01.42-1.29l3-.43a.77.77 0 00.57-.42l1.32-2.68a.76.76 0 011.28 0z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</G>
|
||||
<G>
|
||||
<Path
|
||||
d="M323.81 479.11L193.26 479a28.44 28.44 0 01-28.42-28.44l.07-327.3a28.43 28.43 0 0128.45-28.43l130.55.08a28.43 28.43 0 0128.42 28.44l-.06 327.3a28.44 28.44 0 01-28.46 28.46z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M323.77 106.51h-25.31a6.28 6.28 0 00-6.14 6.42v4.59a6.28 6.28 0 01-6.14 6.42h-49.34a6.31 6.31 0 01-6.15-6.43v-4.59a6.28 6.28 0 00-6.14-6.43h-31.07a19.91 19.91 0 00-19.92 19.91l-.07 319.46a19.91 19.91 0 0019.91 19.92l130.29.08a19.92 19.92 0 0019.93-19.91l.06-319.47a19.93 19.93 0 00-19.91-19.97z"
|
||||
fill="#fafafa"
|
||||
/>
|
||||
<Path
|
||||
d="M249 439a2.51 2.51 0 11-2.52-2.48A2.5 2.5 0 01249 439zM263 438.89a2.5 2.5 0 11-2.52-2.48 2.52 2.52 0 012.52 2.48z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Circle cx={274.51} cy={438.8} r={2.5} transform="rotate(-45.69 274.488 438.779)" fill="#263238" />
|
||||
<Path d="M197.74 318.91H318.79V326.82000000000005H197.74z" fill="#23a99c" />
|
||||
<Path
|
||||
d="M317.06 344.92c0 .28-27.18.52-60.69.52s-60.7-.24-60.7-.52 27.17-.52 60.7-.52 60.69.23 60.69.52zM317.06 359.32c0 .28-27.18.51-60.69.51s-60.7-.23-60.7-.51 27.17-.52 60.7-.52 60.69.2 60.69.52zM317.06 373.71c0 .29-27.18.52-60.69.52s-60.7-.23-60.7-.52 27.17-.51 60.7-.51 60.69.23 60.69.51zM317.06 388.11c0 .28-27.18.52-60.69.52s-60.7-.24-60.7-.52 27.17-.52 60.7-.52 60.69.23 60.69.52zM317.06 402.5c0 .29-27.18.52-60.69.52s-60.7-.23-60.7-.52 27.17-.51 60.7-.51 60.69.23 60.69.51zM318.79 418.83c0 .28-27.17.51-60.68.51s-60.7-.23-60.7-.51 27.17-.52 60.7-.52 60.68.23 60.68.52z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path d="M196.52 181.39H251.78V234.85H196.52z" fill="#455a64" />
|
||||
<Path d="M233.92 197.42a4 4 0 11-4-4 4 4 0 014 4z" />
|
||||
<Path d="M247.51 185.58h-46.69l-.27.28V230.69h.23l33.9-.12 9.47-.06H247.47L236.19 211l-7.6 9.33-12.5-17.58-15 27.41v-20.84-23.25h46.2l.12 32.06c0 3.89 0 7 .06 9.08V230.46a1.39 1.39 0 000-.2v-.59-2.37c0-2.11 0-5.15.06-9 0-7.84.07-19 .12-32.42v-.23z" />
|
||||
<Path d="M265.06 181.39H320.32V234.85H265.06z" fill="#455a64" />
|
||||
<Path d="M302.46 197.42a4 4 0 11-4-4 4 4 0 014 4z" />
|
||||
<Path d="M316.05 185.58h-46.69c.06 0-.5.51-.26.28v44.82h.23l33.91-.12 9.47-.06h3.31L304.73 211l-7.59 9.33-12.51-17.58-15 27.41v-20.84-23.25h46.21c0 13.25.08 24.3.11 32.06 0 3.89.05 7 .06 9.08V227.3c0-2.11 0-5.15.06-9 0-7.84.07-19 .12-32.42v-.23z" />
|
||||
<Path d="M196.52 244.77H251.78V298.23H196.52z" fill="#455a64" />
|
||||
<Path d="M233.92 260.8a4 4 0 11-4-4 4 4 0 014 4z" />
|
||||
<Path d="M247.51 249h-46.69l-.27.27V294h.23l33.9-.11 9.47-.06H247.47l-11.32-19.41-7.6 9.34-12.5-17.58-15 27.4v-20.83V249.45h46.2l.12 32.06c0 3.89 0 7 .06 9.08v3.25a1.57 1.57 0 000-.2v-.6-2.36c0-2.11 0-5.16.06-9 0-7.84.07-19 .12-32.42V249z" />
|
||||
<Path d="M265.03 244.77H320.28999999999996V298.23H265.03z" fill="#455a64" />
|
||||
<Path d="M302.43 260.8a4 4 0 11-4-4 4 4 0 014 4z" />
|
||||
<Path d="M316 249h-46.69l-.27.27V294h.23l33.9-.11 9.47-.06H316l-11.3-19.44-7.6 9.34-12.5-17.58-15 27.4v-20.83-23.27h46.2c0 13.25.09 24.29.12 32.06 0 3.89 0 7 .06 9.08v3.03a.82.82 0 000 .22 1.57 1.57 0 000-.2v-.6-2.36c0-2.11 0-5.16.06-9 0-7.84.07-19 .11-32.42V249z" />
|
||||
<Path
|
||||
d="M320.93 149.12a4 4 0 113.86-4 4.09 4.09 0 01-.63 2.21l.62 1.47a.2.2 0 010 .21.18.18 0 01-.2 0l-1.38-.64a3.71 3.71 0 01-2.27.75zm0-7.69a3.66 3.66 0 000 7.32A3.37 3.37 0 00323 148a.17.17 0 01.18 0l1.06.49-.47-1.14a.19.19 0 010-.18 3.72 3.72 0 00.63-2.08 3.59 3.59 0 00-3.47-3.66z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M334.79 149.26a.13.13 0 01-.1 0l-2.82-2-2.81 2a.19.19 0 01-.19 0 .2.2 0 01-.09-.16v-7.47a.18.18 0 01.18-.18h5.83a.18.18 0 01.18.18v7.47a.19.19 0 01-.1.16zm-2.92-2.44a.2.2 0 01.11 0l2.63 1.88v-6.94h-5.48v6.94l2.64-1.88a.19.19 0 01.1 0z"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path
|
||||
d="M182.15 149.76H183.08l2.52-.07-.09.09v-1.83-1.06a1 1 0 01.87-.79h1.21a1 1 0 01.85 1v2.67l-.14-.15h3.18l-.15.15v-4-1a1 1 0 00-.44-.73l-1.39-1.38-1.34-1.32-.65-.64c-.23-.22-.39-.44-.64-.49a.85.85 0 00-.71.14c-.19.15-.4.39-.59.57l-1.19 1.08c-.72.74-1.48 1.43-2 2.06a2.15 2.15 0 00-.16 1.18v4.5a1.65 1.65 0 010-.38v-1.13c0-.44 0-1.14-.05-1.83v-1.14a2.36 2.36 0 01.17-1.31c.59-.73 1.29-1.36 2-2.14l1.13-1.16c.21-.2.37-.4.62-.61a1.14 1.14 0 01.95-.19 1.75 1.75 0 01.78.56l.65.64 1.35 1.32c.46.44.92.9 1.39 1.36a1.29 1.29 0 01.53.95v5.15h-3.48v-.15-2.67a.76.76 0 00-.62-.75h-1.15a.77.77 0 00-.67.6v2.92H182.49c-.28-.01-.35-.01-.34-.02z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Rect x={197.63} y={138.21} width={116.21} height={13.34} rx={5.12} fill="#e0e0e0" />
|
||||
<Path
|
||||
d="M211.42 144.68V147h-1.17v-2.13c0-.66-.3-1-.82-1s-1 .35-1 1.09v2h-1.17v-5.57h1.17v2a1.79 1.79 0 011.28-.48 1.59 1.59 0 011.71 1.77zM215.08 146.79a1.61 1.61 0 01-1 .26 1.33 1.33 0 01-1.51-1.45v-1.66H212V143h.62v-1h1.17v1h1v.9h-1v1.65a.47.47 0 00.5.53.78.78 0 00.48-.15zM218.26 146.79a1.62 1.62 0 01-1 .26 1.33 1.33 0 01-1.5-1.45v-1.66h-.63V143h.63v-1H217v1h1v.9h-1v1.65a.47.47 0 00.5.53.75.75 0 00.47-.15zM223.32 145a2 2 0 01-2 2.08 1.57 1.57 0 01-1.22-.49v1.89h-1.17V143H220v.47a1.55 1.55 0 011.27-.53 2 2 0 012.05 2.06zm-1.19 0a1 1 0 10-1 1.12 1 1 0 001-1.12zM223.81 143.62a.73.73 0 011.46 0 .73.73 0 11-1.46 0zm0 2.69a.73.73 0 011.46 0 .73.73 0 11-1.46 0zM228.15 140.67h1l-2.48 7.07h-1zM230.55 140.67h1l-2.47 7.07h-1.05z"
|
||||
fill="#263238"
|
||||
/>
|
||||
</G>
|
||||
<G>
|
||||
<Path
|
||||
d="M181.92 385.23a22.53 22.53 0 00-36.9 8.86l2.67 10.33c1-3.52 3.19-6.92 6.55-8.37s7.55-.65 11.13.65 7.09 3.05 10.89 3.28 8.1-1.57 9.23-5.2c1.07-3.42-.99-7.06-3.57-9.55z"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path d="M181.92 385.23a22.53 22.53 0 00-36.9 8.86l2.67 10.33c1-3.52 3.19-6.92 6.55-8.37s7.55-.65 11.13.65 7.09 3.05 10.89 3.28 8.1-1.57 9.23-5.2c1.07-3.42-.99-7.06-3.57-9.55z" />
|
||||
<Path
|
||||
d="M117.57 419.69c-5.57-5.3-5.3-14.52-2.51-21.69s8.85-12.68 15.63-16.31a49.54 49.54 0 0143.16-1.4 20.48 20.48 0 00-16.56 9.71c-3.06 5.07-3.74 11.23-6.26 16.59a26.14 26.14 0 01-33.46 13.08M114.89 387.86c-6.17-7.29-7-19-6.09-28.5s3.12-19.08 1.62-28.51c-.89-5.59-3.06-10.92-3.79-16.54s.3-11.95 4.65-15.58c5-4.16 12.87-3.18 18.07.73s8.21 10.05 10.41 16.17c4.84 13.45 6.61 28.36 2.64 42.09s-13.3 26-27 30.14"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path
|
||||
d="M111.93 447.66a4.53 4.53 0 010-.84v-2.41c.08-2.09.28-5.11.75-8.82a116.62 116.62 0 012.37-12.93 59.85 59.85 0 015.9-15 62.55 62.55 0 019.64-12.88 61.07 61.07 0 0110.11-8.41c1.54-1.06 3-1.87 4.31-2.62s2.47-1.26 3.41-1.72l2.22-.93a4.13 4.13 0 01.79-.29 7 7 0 01-.75.38c-.53.26-1.26.59-2.17 1s-2.06 1.07-3.36 1.79-2.73 1.59-4.25 2.67a63.43 63.43 0 00-19.5 21.24c-5.68 9.93-7.24 20.38-8.36 27.69-.52 3.7-.77 6.71-.91 8.79-.06 1-.11 1.81-.14 2.4a5.62 5.62 0 01-.06.89z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M58.67 359.11c-11.42 6.89-11.39 13.16-9.48 19.09s8.92 10 14.73 7.81c3.29-1.25 5.67-4.1 7.7-7s3.94-6 6.79-8a9.93 9.93 0 016.92-1.9zM85.33 369.13l2.93 1.1a6.39 6.39 0 00-2.93-1.1z"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path d="M58.67 359.11c-11.42 6.89-11.39 13.16-9.48 19.09s8.92 10 14.73 7.81c3.29-1.25 5.67-4.1 7.7-7s3.94-6 6.79-8a9.93 9.93 0 016.92-1.9z" />
|
||||
<Path d="M91.65 372l-10.59-4 4.34.58a6.89 6.89 0 013.15 1.19z" />
|
||||
<Path
|
||||
d="M50.12 366.67c3-4.13 10.36-5.33 15-3.27s8.08 6.15 11.13 10.22 6 8.39 10.26 11.22 10.15 3.87 14.36 1a12.34 12.34 0 004.88-8.45 27.91 27.91 0 00-.67-10c-1-4.79-2.65-9.79-6.49-12.84-4.27-3.39-10.2-3.55-15.65-3.37-6.26.2-12.61.67-18.48 2.84s-11.87 6.85-14.36 12.6"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path
|
||||
d="M102.43 438.51a.79.79 0 010-.25v-.74c0-.68 0-1.63-.09-2.83-.08-2.51-.18-6.06-.3-10.43s-.32-9.64-.64-15.45-.76-12.2-1.64-18.85a61.07 61.07 0 00-1.89-9.58A50.15 50.15 0 0094.3 372a48.59 48.59 0 00-4.42-6.94 45.1 45.1 0 00-4.82-5.29 35.57 35.57 0 00-8.55-5.77c-.56-.26-1.05-.51-1.48-.68l-1.12-.44-.68-.27-.23-.11.24.06.71.23 1.13.39c.45.16.94.4 1.51.64a34.33 34.33 0 018.71 5.74 44.83 44.83 0 014.92 5.3 47.87 47.87 0 014.49 7 49.31 49.31 0 013.57 8.45 60.16 60.16 0 011.92 9.65c.88 6.68 1.3 13.07 1.6 18.89s.43 11.07.53 15.47.12 8 .13 10.44V438.33a.67.67 0 01-.03.18zM106.54 443.68a32.74 32.74 0 01-1.25-4.91 51.7 51.7 0 01-.52-13.78 92 92 0 014.35-20c2.35-7.35 5.53-15.21 8.25-23.66a109.27 109.27 0 003.28-12.52 110.19 110.19 0 001.62-11.94 173.75 173.75 0 00.36-20.42c-.19-5.82-.5-10.53-.7-13.78-.09-1.6-.16-2.85-.22-3.74v-1a1.37 1.37 0 010-.33 1.23 1.23 0 010 .33c0 .24.06.57.1 1 .07.89.18 2.14.31 3.74.26 3.25.62 7.95.86 13.77a166.42 166.42 0 01-.25 20.48 111.7 111.7 0 01-1.6 12 107.63 107.63 0 01-3.28 12.58c-2.73 8.47-5.93 16.32-8.29 23.64a93.11 93.11 0 00-4.43 19.86 53.33 53.33 0 00.36 13.71c.28 1.6.54 2.84.76 3.67.1.39.17.71.23 1a1.27 1.27 0 01.06.3z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path d="M134.87 448.84L81.23 448.84 80 438.51 134.87 438.51 134.87 448.84z" fill="#455a64" />
|
||||
<Path d="M86.98 447.66L93.69 478.63 122.84 478.63 129.11 447.66 86.98 447.66z" fill="#455a64" />
|
||||
<Path
|
||||
d="M134.87 448.84c0 .14-11.8.22-26.34.18s-26.34-.19-26.34-.33 11.79-.22 26.34-.18 26.34.19 26.34.33z"
|
||||
fill="#455a64"
|
||||
/>
|
||||
<Path
|
||||
d="M125.66 464.13a21.87 21.87 0 00-4.72-2.71 16.69 16.69 0 00-5.75-1.16 20.18 20.18 0 00-7.06 1.3c-2.45.83-4.73 1.79-6.95 2.32a16.49 16.49 0 01-6 .18 12.51 12.51 0 01-3.86-1.22 7.09 7.09 0 01-.93-.59c-.21-.16-.31-.24-.29-.26s.45.27 1.31.67a13.24 13.24 0 003.82 1 16.74 16.74 0 005.85-.28c2.15-.54 4.43-1.51 6.9-2.34a20 20 0 017.23-1.29 16.58 16.58 0 015.87 1.29 15.44 15.44 0 013.5 2 9.72 9.72 0 01.83.72c.17.24.26.35.25.37z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M125.25 466.19a23.32 23.32 0 00-4.53-2.67 15.36 15.36 0 00-5.54-1.14 20 20 0 00-6.84 1.23c-2.36.79-4.58 1.71-6.69 2.28a17.25 17.25 0 01-5.78.64 9.61 9.61 0 01-3.78-1 6.54 6.54 0 01-.91-.57c-.19-.15-.29-.23-.28-.24s.45.25 1.27.63a10.31 10.31 0 003.72.83 17.49 17.49 0 005.63-.74c2.07-.58 4.27-1.51 6.66-2.31a19.8 19.8 0 017-1.21 15.19 15.19 0 015.67 1.28 14.78 14.78 0 013.35 2 7.11 7.11 0 01.79.7c.19.19.27.28.26.29z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<Path
|
||||
d="M126.19 462.07a25.67 25.67 0 00-4.85-2.86 17.88 17.88 0 00-5.89-1.41 19.11 19.11 0 00-7.29 1.28 63.77 63.77 0 01-7.19 2.35 18.43 18.43 0 01-6.21 0 13.8 13.8 0 01-4-1.26 6.35 6.35 0 01-1-.6c-.21-.15-.31-.24-.3-.26s.47.28 1.35.68a15.59 15.59 0 004 1.09 18.9 18.9 0 006.06-.12 75 75 0 007.13-2.36 19 19 0 017.48-1.27 17.48 17.48 0 016 1.55 17.19 17.19 0 013.57 2.16 7.83 7.83 0 01.86.74c.2.22.29.28.28.29z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<Path
|
||||
d="M135.34 448.88c0 .14-12.07.26-26.95.26s-26.95-.12-26.95-.26 12.07-.25 27-.25 26.9.11 26.9.25z"
|
||||
fill="#263238"
|
||||
/>
|
||||
</G>
|
||||
<G>
|
||||
<Path
|
||||
d="M374.57 216.23l-2.31-43.64a41.29 41.29 0 00-1.48-10.52 13.85 13.85 0 00-6.35-8.22c-3.16-1.64-7.42-1.35-9.85 1.24l1.72 2.36q-2.12 40.07-1.8 80.22c0 2 .08 4.15 1.25 5.77 2 2.73 6 2.7 9.32 2.39 2.87-.26 6.13-.74 7.73-3.13a8.91 8.91 0 001.16-3.35c1.42-7.6 1.04-15.35.61-23.12z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M360.72 160a8.1 8.1 0 00-4.56-4.5c-2.09-.68-4.72.13-5.63 2.13l4.93 5.56a5 5 0 002.05 1.91 2.82 2.82 0 002.72-.15 2.89 2.89 0 001.15-2.34 6 6 0 00-.66-2.61z"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path
|
||||
d="M369 183.21c-.85-9.42-5.83-18.59-13.81-23.67s-18.87-5.54-26.85-.54a28 28 0 00-10.83 14c-2.1 5.64-1.19 10.31-1.14 16.32 0 3.12.42 11.49 3.11 13.06l42 7.53c5.34-7.73 8.38-17.28 7.52-26.7z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M367.54 176c-1-5.6-6.42-11-10.68-14.79-4.86-4.28-5.55-4.09-12-4.25-6.83-.18-14.29-.15-19.62 4.13-3 2.37-4.87 5.79-6.41 9.24-3.56 8-3 17.13-1.93 25.81s2.93 16.88 10 22l29.87-4.62c6.59-2.45 8.79-10 10.65-16.75s1.28-13.86.12-20.77z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M317.9 181.55c.42-8.21 9.16-16.51 17.35-15.75l26.59 4.73v4.42l.3 55.16a229.59 229.59 0 01-30.67 4.89l-.23-11.06s-10.51-1.62-12.85-15.27c-1.16-6.83-.96-17.99-.49-27.12z"
|
||||
fill="#ffbe9d"
|
||||
/>
|
||||
<Path
|
||||
d="M322.88 192.43a1.76 1.76 0 001.71 1.75 1.68 1.68 0 001.79-1.61 1.75 1.75 0 00-1.71-1.75 1.68 1.68 0 00-1.79 1.61zM320.86 187.33c.22.23 1.54-.77 3.43-.79s3.28.91 3.49.68-.13-.52-.74-.95a4.82 4.82 0 00-2.79-.82 4.65 4.65 0 00-2.73.91c-.52.46-.77.87-.66.97zM339.68 192.43a1.76 1.76 0 001.72 1.75 1.68 1.68 0 001.79-1.61 1.76 1.76 0 00-1.71-1.75 1.69 1.69 0 00-1.8 1.61zM339.43 187.46c.22.23 1.54-.77 3.43-.79s3.28.91 3.48.68-.12-.52-.73-.95a4.83 4.83 0 00-2.8-.82 4.62 4.62 0 00-2.72.91c-.58.45-.77.87-.66.97zM332.74 201.55a12.06 12.06 0 00-3.07-.52c-.48 0-.94-.13-1-.46a2.39 2.39 0 01.3-1.43l1.38-3.69c1.92-5.25 3.3-9.57 3.09-9.65s-1.94 4.12-3.86 9.38l-1.32 3.71a2.84 2.84 0 00-.23 1.89 1.24 1.24 0 00.81.7 3.2 3.2 0 00.82.1 12.15 12.15 0 003.08-.03z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M331.24 223.94A36.26 36.26 0 00350 218.8s-4.51 9.64-18.69 8.55zM333.6 206.09a3.4 3.4 0 013-1.3 3.1 3.1 0 012.16 1.12 2 2 0 01.18 2.25 2.28 2.28 0 01-2.47.66 7.26 7.26 0 01-2.43-1.41 2.06 2.06 0 01-.55-.55.63.63 0 010-.71"
|
||||
fill="#eb996e"
|
||||
/>
|
||||
<Path
|
||||
d="M338.32 202.79c-.31 0-.29 2-2 3.51s-3.92 1.28-3.93 1.56c0 .13.49.39 1.41.41a5.12 5.12 0 003.29-1.19 4.44 4.44 0 001.56-2.94c.04-.86-.19-1.37-.33-1.35zM338.94 182.13c.19.51 2.08.25 4.29.49s4 .82 4.31.36c.13-.22-.19-.7-.92-1.19a7.12 7.12 0 00-3.2-1.08 7.3 7.3 0 00-3.35.43c-.81.34-1.21.75-1.13.99zM320.84 180.86c.34.42 1.63 0 3.2-.08s2.89.25 3.19-.2c.13-.22-.08-.65-.67-1.05a4.49 4.49 0 00-2.6-.67 4.4 4.4 0 00-2.54.9c-.55.44-.73.89-.58 1.1zM327.09 162c-5 3.41-8.52 9-10.22 15.19l.84 5.07c1.17-2.45 1.32-5.08 3.74-7.59a17.35 17.35 0 017.81-4.09c9.12-2.32 12.19-5.63 16.22-11.59-5.91-1.49-13.35-.41-18.39 3.01z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M366.45 175.36a25.07 25.07 0 00-20.23-17.69l-.74 1.31-6.93 8.1a4.78 4.78 0 00-1.3 2.27 3.49 3.49 0 001.47 3.06c2.2 1.8 5.27 1.91 8.11 2s5.94.31 8 2.25c2.57 2.42 2.51 6.53 1.65 10a42.37 42.37 0 00-.85 12.46c.32 3.15 4.48-4.5 7.22-2.91.76.44-1.3 12.8-.5 12.42s.71-4.82 1-5.65a98.68 98.68 0 003.56-13.61 32.69 32.69 0 00-.46-14.01z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M358.68 199.13a4.08 4.08 0 016.05-3.34c1.4.84 2.45 2.52 2.25 5.68-.53 8.42-8.88 6.15-8.87 5.91s.32-4.76.57-8.25z"
|
||||
fill="#ffbe9d"
|
||||
/>
|
||||
<Path
|
||||
d="M360.87 204.34s.14.12.38.25a1.47 1.47 0 001.09.09c.91-.27 1.76-1.63 1.9-3.14a4.91 4.91 0 00-.27-2.13 1.7 1.7 0 00-1-1.2.74.74 0 00-.89.34c-.13.23-.09.4-.13.41s-.18-.15-.08-.49a.88.88 0 01.36-.51 1.14 1.14 0 01.83-.16 2.06 2.06 0 011.44 1.41 5 5 0 01.36 2.38c-.17 1.69-1.15 3.22-2.39 3.49a1.53 1.53 0 01-1.32-.31c-.28-.22-.31-.41-.28-.43z"
|
||||
fill="#eb996e"
|
||||
/>
|
||||
<Path
|
||||
d="M360.81 164.3a27.6 27.6 0 013 4.25 34.66 34.66 0 012.47 5.09 35.77 35.77 0 011.88 6.66 32.68 32.68 0 01-.13 12.52 10.82 10.82 0 01-.27 1.12c-.1.34-.19.67-.27 1a11.8 11.8 0 01-.52 1.56l-.36 1-.14.33a1.61 1.61 0 01.09-.35c.08-.25.19-.57.32-1a16 16 0 00.47-1.56l.25-1a10.93 10.93 0 00.25-1.12 33.44 33.44 0 00.06-12.43 37.54 37.54 0 00-1.85-6.63 38.15 38.15 0 00-2.4-5.08l-.57-1-.56-.85-.48-.75c-.15-.23-.3-.42-.43-.6l-.61-.84z"
|
||||
fill="#455a64"
|
||||
/>
|
||||
<Path
|
||||
d="M361.63 166.87a2.12 2.12 0 01-.05.25 2 2 0 01-.34.68 4.58 4.58 0 01-2.3 1.49c-1.12.37-2.46.7-4 1a35.22 35.22 0 01-5 .72 24.17 24.17 0 01-5-.14 19.15 19.15 0 01-4-1 15.21 15.21 0 01-2.49-1.18 5.83 5.83 0 01-.63-.41c-.14-.09-.22-.15-.21-.16s.31.18.89.48a16.81 16.81 0 002.5 1.1 20.11 20.11 0 003.94.92 25.17 25.17 0 005 .13 44.61 44.61 0 008.87-1.67 4.69 4.69 0 002.28-1.38 3.64 3.64 0 00.54-.83z"
|
||||
fill="#455a64"
|
||||
/>
|
||||
<Path
|
||||
d="M360.11 164s0 .06-.14.17-.24.27-.44.45a10.45 10.45 0 01-1.87 1.36 15 15 0 01-7.35 1.85 30.63 30.63 0 01-7.55-1c-.94-.24-1.7-.45-2.23-.6a5.87 5.87 0 01-.8-.26 4.91 4.91 0 01.83.16l2.24.52a33.61 33.61 0 007.5 1 15.67 15.67 0 007.27-1.75 15.44 15.44 0 002.54-1.9zM358.38 163a2.91 2.91 0 01-.59.2c-.39.12-1 .27-1.67.42a27 27 0 01-11.28-.07c-.71-.15-1.28-.31-1.67-.43a3.24 3.24 0 01-.59-.21 2.62 2.62 0 01.62.11c.39.1 1 .22 1.67.35a30.38 30.38 0 005.61.55 32.07 32.07 0 005.6-.48c.72-.13 1.29-.24 1.68-.33a2.62 2.62 0 01.62-.11z"
|
||||
fill="#455a64"
|
||||
/>
|
||||
<Path
|
||||
d="M347.14 155.85a3.53 3.53 0 01-.2.6c-.15.38-.39.92-.71 1.57a23.52 23.52 0 01-7.41 8.56c-.6.42-1.1.73-1.45.93a3.71 3.71 0 01-.57.29 2.75 2.75 0 01.51-.37c.34-.23.83-.56 1.41-1a26.23 26.23 0 004.13-3.85 26 26 0 003.21-4.64c.35-.64.61-1.17.79-1.53a2.46 2.46 0 01.29-.56z"
|
||||
fill="#455a64"
|
||||
/>
|
||||
<Path
|
||||
d="M317.21 173.94s0-.09.14-.22.25-.34.46-.59a27 27 0 011.84-2.06 22.13 22.13 0 013.13-2.64 19.7 19.7 0 012.08-1.24 18.68 18.68 0 012.41-1 22.55 22.55 0 019-1 20 20 0 012.72.44c.32.07.56.15.72.19a.9.9 0 01.25.09s-.35-.06-1-.18a25.49 25.49 0 00-2.72-.36 23.37 23.37 0 00-8.91 1 19.45 19.45 0 00-7.57 4.75c-.83.81-1.46 1.5-1.89 2l-.5.56a1.83 1.83 0 01-.16.26zM323.53 164.88a5.55 5.55 0 01.64-.42 8.53 8.53 0 01.83-.46c.32-.16.67-.36 1.08-.53s.85-.38 1.35-.55a16.5 16.5 0 011.59-.52 21.76 21.76 0 017.59-.75c.58.06 1.14.11 1.66.21s1 .18 1.43.28a11.27 11.27 0 011.16.31 8.76 8.76 0 01.86.28 5.08 5.08 0 01.71.29 3.88 3.88 0 01-.75-.19 6.74 6.74 0 00-.86-.24c-.34-.09-.73-.2-1.16-.28s-.91-.18-1.42-.24-1.07-.13-1.65-.18a25.53 25.53 0 00-3.79 0 24.4 24.4 0 00-3.72.69c-.56.16-1.1.3-1.58.49s-.94.35-1.35.52-.76.34-1.08.49-.59.28-.81.4a3.21 3.21 0 01-.73.4z"
|
||||
fill="#455a64"
|
||||
/>
|
||||
<Path
|
||||
d="M365.35 206.46c.16-.08.41 0 .69.31a3.18 3.18 0 01.76 1.58 3.85 3.85 0 010 1.32 3.57 3.57 0 01-.63 1.51 3.74 3.74 0 01-1.45 1.19 3.21 3.21 0 01-2.18.2 3.31 3.31 0 01-1.78-1.21 3.8 3.8 0 01-.73-1.7 3.73 3.73 0 01.71-2.79 3.33 3.33 0 011.35-1.11c.38-.15.64-.13.75 0s-.23 1-.5 1.95a2.52 2.52 0 000 1.44 1.46 1.46 0 00.86.93 1 1 0 00.53 0 1.81 1.81 0 00.6-.37 2.26 2.26 0 00.63-1.26c.21-1 .04-1.83.39-1.99z"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path
|
||||
d="M311.21 272.89L317.53 301.14 314.54 320.1 383.95 320.1 379.42 231.59 362.11 230.12 319.83 237.25 311.21 272.89z"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path
|
||||
d="M328.87 236.74s-18.08-1.66-26.69 10.65c-5.27 7.52-12.67 24.81-12.67 24.81l24.07 4.15z"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path d="M353.07 263.48c5.08 6.27 7.47 14.23 10.79 21.59 4.28 9.48 10.21 18.11 16.1 26.68.44.64 1 1.36 1.82 1.27s1.21-1 1.42-1.79a25.13 25.13 0 00-1-14.74 56.1 56.1 0 00-7.2-13.12 152.18 152.18 0 00-15-18.29 8.65 8.65 0 00-3.37-2.5 3 3 0 00-3.67 1.27M317.29 296.23q1.75-6.93 3.52-13.86a10.07 10.07 0 01.95-2.68 10.43 10.43 0 012.45-2.6 11.11 11.11 0 013.73-2.35 21.58 21.58 0 003.5-.87c1.48-.74 2.27-2.35 2.88-3.89.46-1.18.85-2.58.18-3.65a3.8 3.8 0 00-3.2-1.34 14.69 14.69 0 00-13.39 8.23c-1.8 3.86-1.8 8.29-1.77 12.55l.1 11.39" />
|
||||
<Path
|
||||
d="M339.49 296.77c0-.06-.94.1-2.46.22a29.84 29.84 0 01-5.93-.15 29.22 29.22 0 01-5.76-1.38c-1.44-.5-2.3-.9-2.33-.84s.19.16.57.36a15.51 15.51 0 001.65.76 24.4 24.4 0 005.82 1.52 24.06 24.06 0 006 0 13.59 13.59 0 001.79-.31c.43-.08.66-.15.65-.18zM369.78 298c0-.12-9.86 2.68-21.94 6.25S326 310.81 326 310.92s9.86-2.68 21.94-6.25 21.87-6.54 21.84-6.67z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path d="M324 295.1a8.41 8.41 0 007 5.39 8.7 8.7 0 008.14-3.79 30.41 30.41 0 01-15.14-1.6M328.67 310.06l.25.1c6.2 1.11 12 0 17.94-2.11s11.48-5.16 17-8.25z" />
|
||||
<Path
|
||||
d="M365.57 309.37a4.07 4.07 0 010 1.8 10.55 10.55 0 01-4.68 7.09 4.08 4.08 0 01-1.64.73 13.35 13.35 0 011.43-1 12.6 12.6 0 002.86-3 12.4 12.4 0 001.67-3.83 12.94 12.94 0 01.36-1.79zM369.16 313.65a3.48 3.48 0 01-.77 1.42 9.73 9.73 0 01-6.65 3.76 3.45 3.45 0 01-1.61-.08 16 16 0 005.06-1.57 16.16 16.16 0 003.97-3.53z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M405.28 393.92c-1.28-5.24-10.54-39.13-12.32-44.23l-8.34-23.88-.56-7.89-70.73 2.18s-10.07 12.4-13.8 26.46c-3.21 12.05-4.83 30.34 2.76 77.9 5 26.39 10.78 54.47 10.78 54.47h42.37l-5.67-97.15 15.72 54.4 14.33 42.73 45.32-.24z"
|
||||
fill="#455a64"
|
||||
/>
|
||||
<Path
|
||||
d="M309.56 331.62a3.3 3.3 0 01-.12.37c-.09.26-.22.61-.38 1.05s-.39 1-.65 1.72-.55 1.45-.82 2.35a91.78 91.78 0 00-3.69 15.45 133.27 133.27 0 00-1.32 23.52c.21 8.87 1.09 18.61 2.2 28.81 2.22 20.39 6.22 38.57 9.22 51.68 1.5 6.54 2.72 11.83 3.56 15.53.42 1.81.74 3.22 1 4.22l.24 1.1a1.83 1.83 0 01.06.38 1.91 1.91 0 01-.12-.37l-.28-1.09c-.25-1-.61-2.4-1.06-4.2-.91-3.65-2.2-8.93-3.73-15.48-3-13.1-7.08-31.28-9.34-51.72-1.11-10.2-2-19.95-2.17-28.85a131.33 131.33 0 011.43-23.59 87.49 87.49 0 013.87-15.45c.28-.9.59-1.68.87-2.35l.67-1.7.43-1a1.33 1.33 0 01.13-.38zM322.07 475.26c.14 0 .38.49.55 1.16s.17 1.24 0 1.27-.38-.48-.54-1.15-.15-1.24-.01-1.28zM319.77 465.62a12.05 12.05 0 011.14 4.83 12.16 12.16 0 01-1.14-4.83z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M317.52 456a6.17 6.17 0 01.81 2.35 6.07 6.07 0 01.3 2.48 6.18 6.18 0 01-.81-2.36 6 6 0 01-.3-2.47zM315.37 446.29a6.13 6.13 0 01.78 2.36 6 6 0 01.28 2.48 6.25 6.25 0 01-.79-2.37 6.09 6.09 0 01-.27-2.47zM313.36 436.58a6.11 6.11 0 01.75 2.38 5.9 5.9 0 01.23 2.48 12.18 12.18 0 01-1-4.86zM311.54 426.84a5.94 5.94 0 01.7 2.39 6.32 6.32 0 01.19 2.49 12 12 0 01-.89-4.88zM310 417.06a11.82 11.82 0 01.76 4.9 11.82 11.82 0 01-.76-4.9zM308.65 407.25a11.8 11.8 0 01.62 4.91 6.06 6.06 0 01-.57-2.42 6.17 6.17 0 01-.05-2.49zM307.61 397.4a11.81 11.81 0 01.5 4.92 6.12 6.12 0 01-.51-2.43 6.34 6.34 0 01.01-2.49zM306.76 387.53a11.68 11.68 0 01.39 4.94 6.32 6.32 0 01-.45-2.45 6 6 0 01.06-2.49zM306.23 377.64a11.69 11.69 0 01.21 4.95 6.35 6.35 0 01-.36-2.47 6.14 6.14 0 01.15-2.48zM306.2 367.73a6 6 0 01.23 2.48 5.84 5.84 0 01-.29 2.47 6.22 6.22 0 01-.23-2.47 6.32 6.32 0 01.29-2.48zM306.84 357.85a6 6 0 01.06 2.49 6.25 6.25 0 01-.47 2.44 5.88 5.88 0 01-.05-2.49 6.08 6.08 0 01.46-2.44zM308.34 348.06a6.15 6.15 0 01-.18 2.48 6 6 0 01-.69 2.39 6.15 6.15 0 01.18-2.48 6.07 6.07 0 01.69-2.39zM310.83 338.47a11.83 11.83 0 01-1.38 4.76 6.13 6.13 0 01.44-2.45 6 6 0 01.94-2.31zM313.41 331.52c.13.05 0 .61-.23 1.25s-.57 1.11-.71 1.06 0-.61.23-1.25.58-1.12.71-1.06zM354.37 387.05c-.13.07-.84-.8-1.59-1.95a6.28 6.28 0 01-1.14-2.24c.11-.09.82.79 1.58 1.95a6.33 6.33 0 011.15 2.24zM358.66 396.07a6.47 6.47 0 01-1.23-2.19 6.23 6.23 0 01-.74-2.4 6.3 6.3 0 011.22 2.19 6.08 6.08 0 01.75 2.4zM361.89 405.53a6 6 0 01-1-2.3 6.1 6.1 0 01-.51-2.46 6.23 6.23 0 011 2.3 6.15 6.15 0 01.51 2.46zM364.71 415.13a12.17 12.17 0 01-1.4-4.8 6.06 6.06 0 01.95 2.33 6.55 6.55 0 01.45 2.47zM367.49 424.74a6.34 6.34 0 01-.95-2.33 6.24 6.24 0 01-.44-2.48 6.23 6.23 0 01.94 2.33 6.15 6.15 0 01.45 2.48zM370.28 434.34a6.06 6.06 0 01-.95-2.33 6.53 6.53 0 01-.45-2.47 6.06 6.06 0 01.95 2.33 6.53 6.53 0 01.45 2.47zM373.06 444a6.34 6.34 0 01-.95-2.33 6.24 6.24 0 01-.44-2.48 6.34 6.34 0 01.95 2.33 6.24 6.24 0 01.44 2.48zM375.85 453.55a6.06 6.06 0 01-1-2.33 6.53 6.53 0 01-.45-2.47 6.06 6.06 0 01.95 2.33 6.53 6.53 0 01.5 2.47zM378.63 463.15a12.25 12.25 0 01-1.39-4.8 11.94 11.94 0 011.39 4.8zM381.42 472.76a6.06 6.06 0 01-.95-2.33A6.55 6.55 0 01380 468a6.06 6.06 0 011 2.32 6.36 6.36 0 01.42 2.44zM383.34 479.38c-.14 0-.37-.34-.52-.84s-.15-.94 0-1 .37.33.51.84.14.96.01 1zM356.9 319.25a2.45 2.45 0 01.07.58c0 .44 0 1 .09 1.67.07 1.45.14 3.54.16 6.14.07 5.18-.06 12.35-.61 20.24-.61 9.12-1.75 17.22-2.61 22.31v.13h-.13l-4.57.1h-1.2a1.57 1.57 0 01-.42 0 1.54 1.54 0 01.42-.06l1.21-.09 4.56-.25-.16.14c.71-5.1 1.74-13.17 2.38-22.29.55-7.88.74-15 .76-20.21v-7.81a2.39 2.39 0 01.05-.6zM348.53 322.33s-.14.18-.34.49a2.78 2.78 0 00-.43 1.49 2.69 2.69 0 00.86 2 2.5 2.5 0 003.93-2 2.79 2.79 0 00-2.57-2.44h-.6c-.01 0 .19-.14.6-.19a2.7 2.7 0 011.66.45 3 3 0 011.39 2.16 2.95 2.95 0 01-4.72 2.38 3 3 0 01-.92-2.4 2.75 2.75 0 01.63-1.6c.28-.28.5-.36.51-.34zM334.38 321.9a4.19 4.19 0 01-.12 1.16 18 18 0 01-.86 3.07A20.23 20.23 0 01317.91 339a16.78 16.78 0 01-3.17.29 4.27 4.27 0 01-1.16-.08c0-.1 1.67 0 4.26-.57A20.92 20.92 0 00333.06 326c1-2.46 1.22-4.12 1.32-4.1zM389.67 340.78a4.21 4.21 0 01-1.06-.42 20.48 20.48 0 01-2.64-1.63 41 41 0 01-7.21-7.18 73.89 73.89 0 01-6.16-8.11 26 26 0 01-1.47-2.72 4.34 4.34 0 01-.4-1.06c.08 0 .74 1.43 2.18 3.58a96 96 0 006.25 8 47.31 47.31 0 007 7.22c2.07 1.54 3.55 2.24 3.51 2.32zM347.42 451.59a4.59 4.59 0 01-1 .06 26.44 26.44 0 01-2.72-.15 36 36 0 01-8.77-2 36.43 36.43 0 01-8-4.1 26 26 0 01-2.18-1.65c-.49-.42-.74-.66-.72-.69s1.17.81 3.1 2a41.83 41.83 0 0016.62 6.06c2.25.33 3.67.38 3.67.47zM404.67 444.84a16.91 16.91 0 01-1.5 2.81c-1 1.69-2.47 4-4.23 6.4s-3.48 4.53-4.78 6a16.17 16.17 0 01-2.2 2.28 21.74 21.74 0 011.92-2.52c1.22-1.53 2.89-3.65 4.64-6.07s3.26-4.65 4.34-6.29a21.82 21.82 0 011.81-2.61zM368.1 251.72c-.3-3.68 3.91-8 5.11-11.5 2.42-7.05 2.17-10.11 1.36-24l-12.3-2.62c-.38 4.63.29 2.82-.15 7.83a31.91 31.91 0 01-4.39 14.28c-1.92 3.05-4.61 5.87-5 9.45-.53 5.17 4 9.63 4.31 14.82.4 7.21-7.4 13.16-6.45 20.31a5.06 5.06 0 002.36 3.95 7 7 0 003.38.51l7.58-.1a3.25 3.25 0 002.3-.62c.94-.92.44-2.47.25-3.78-.85-5.62 5.17-10.24 5.32-15.93.14-4.4-3.31-8.2-3.68-12.6z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M360.18 269.22s21.2 47.2 33.52 48.65c6.21.73 10-2.67 11.33-9.11s-3.11-48.65-3.11-48.65L380.59 266l1.62 8.18-12.77-12.68z"
|
||||
fill="#ffbe9d"
|
||||
/>
|
||||
<Path
|
||||
d="M379.42 231.59s11.93 1.14 16.37 10.92 10.6 20.8 10.6 20.8l-26.24 5.91s-3.67-19.74-3.95-26.71c-.31-7.63-1.1-11.14 3.22-10.92z"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path
|
||||
d="M400.39 292.17a2.43 2.43 0 00-.3-.33l-.9-.89-3.35-3.26c-2.83-2.75-6.72-6.56-11-10.8S376.79 268.82 374 266l-3.29-3.31-.9-.89c-.21-.2-.33-.31-.34-.29a1.5 1.5 0 00.27.34l.85 1c.74.82 1.83 2 3.19 3.41 2.72 2.86 6.53 6.76 10.81 11s8.19 8 11.07 10.72c1.44 1.35 2.62 2.43 3.44 3.16l1 .83a1.83 1.83 0 00.29.2z"
|
||||
fill="#eb996e"
|
||||
/>
|
||||
<Path
|
||||
d="M341.44 232.56l8.29 3.23s4.56 0 5.15 1.51l1.18 2.94s2.79.71 3.38 2.27 5.44 12.15 5.44 12.15l4.56 6.84-9.26 7.72a21.54 21.54 0 00-3.83-3.83c-1.91-1.32-9.85-8-10-9.8s-4.44-12-4.49-13.08-.16-3.44.92-2.85 2.54 3.53 2.54 3.53l-1-4a6.74 6.74 0 01-2.76-4.4z"
|
||||
fill="#ffbe9d"
|
||||
/>
|
||||
<Path
|
||||
d="M358.86 256.51a8.22 8.22 0 00-.85-1.22l-2.44-3.17-.05-.06H352.16l-.06.11a3.31 3.31 0 01-1.49 1.56 3.65 3.65 0 01-1.15.15 10.21 10.21 0 01-1.24 0l.16.3.56-1.13 2-4.06-.18.11 5.59-.05h-.07l3.09 1.44a8.2 8.2 0 001.17.48 7.15 7.15 0 00-1.09-.63l-3-1.57h-5.71l-.06.11-2 4-.56 1.13-.14.27h.3a8.3 8.3 0 001.31 0h.64a1.92 1.92 0 00.66-.15 3.66 3.66 0 001.68-1.78l-.17.11h3.16l-.13-.06 2.55 3.09a9.32 9.32 0 00.88 1.02zM356.06 243.84a8.4 8.4 0 00-1.37-.09l-3.71-.07h-.64l.57.31a11.37 11.37 0 012.42 1.62c.18.18.31.39.24.52a1.28 1.28 0 01-.54.47 7.13 7.13 0 01-1.6.64h.06a7.62 7.62 0 01-3.13-1 3.07 3.07 0 01-1.19-2.5 11.34 11.34 0 01.11-2.81l-.18.16 6.46-.51 1.84-.18a2.27 2.27 0 00.66-.11 3.17 3.17 0 00-.67 0l-1.84.07-6.47.35h-.16v.15a11.6 11.6 0 00-.15 2.91 5.69 5.69 0 00.34 1.54 2.46 2.46 0 001.06 1.3 7.91 7.91 0 003.34 1.08h.06a7.54 7.54 0 001.68-.7 1.48 1.48 0 00.69-.67.61.61 0 000-.56 1.72 1.72 0 00-.28-.36 11 11 0 00-2.55-1.64l-.07.3 3.71-.07a8.4 8.4 0 001.31-.15z"
|
||||
fill="#eb996e"
|
||||
/>
|
||||
<Path
|
||||
d="M369.44 261.5l-.06-.13-.22-.35-.86-1.35-3.3-5.08c-.89-2-1.94-4.38-3.12-7.07q-.9-2-1.89-4.26c-.18-.38-.32-.72-.53-1.15a3.24 3.24 0 00-.92-1 8 8 0 00-2.43-1.11l.13.11-.77-1.93-.39-1a1.84 1.84 0 00-.86-.84 7.57 7.57 0 00-2.18-.63 19.11 19.11 0 00-2.29-.2h.07l-8.3-3.24-.3-.12v.33c0 .77.06 1.54.12 2.38a6.43 6.43 0 00.88 2.37 7.5 7.5 0 002 2.08l-.1-.13c.35 1.35.69 2.68 1 4l.39-.15a20.89 20.89 0 00-1.1-1.9 6.08 6.08 0 00-1.46-1.67.78.78 0 00-.68-.12.82.82 0 00-.44.51 4 4 0 00-.19 1.1v1.58a3.74 3.74 0 00.13.53c.85 2.62 1.86 5 2.71 7.36.43 1.17.85 2.3 1.22 3.41a12 12 0 01.44 1.63 3.53 3.53 0 00.8 1.55 38.16 38.16 0 004.3 4.32c1.39 1.23 2.68 2.3 3.83 3.2.58.45 1.15.84 1.64 1.21a13.45 13.45 0 011.28 1.11c.73.71 1.26 1.3 1.61 1.7l.4.46.15.15a1.13 1.13 0 00-.12-.18c-.09-.13-.22-.28-.37-.48a20.88 20.88 0 00-1.56-1.76 15.26 15.26 0 00-1.28-1.15c-.5-.39-1-.77-1.61-1.23-1.14-.92-2.41-2-3.78-3.23a40.52 40.52 0 01-4.23-4.32 3.34 3.34 0 01-.72-1.38 12.28 12.28 0 00-.45-1.71c-.36-1.12-.77-2.26-1.2-3.43-.83-2.29-1.83-4.71-2.64-7.29a2.18 2.18 0 01-.11-.46 4.07 4.07 0 010-.5v-1c0-.62.17-1.58.68-1.16a5.64 5.64 0 011.33 1.55 17.94 17.94 0 011.07 1.86L346 245l-.48-1.85c-.33-1.29-.67-2.62-1-4v-.07l-.08-.06a7 7 0 01-1.82-1.94 5.94 5.94 0 01-.82-2.2c-.05-.77-.08-1.58-.11-2.34l-.29.2 8.3 3.23h.07a11.38 11.38 0 014.3.77 1.45 1.45 0 01.68.63c.13.34.27.67.4 1l.77 1.92v.09h.09a7.55 7.55 0 012.31 1 3 3 0 01.83.85c.16.32.34.74.5 1.1l1.93 4.25c1.21 2.68 2.29 5.05 3.19 7.05l3.41 5c.39.56.7 1 .92 1.32l.24.33z"
|
||||
fill="#eb996e"
|
||||
/>
|
||||
<Path
|
||||
d="M382 274a53.11 53.11 0 00-.85-6.49 52.33 52.33 0 00-1.26-6.43 49.7 49.7 0 00.84 6.5A50.74 50.74 0 00382 274zM322.54 231.87l2.59 32.65a2.13 2.13 0 002.26 2l18.2-1.18a2.14 2.14 0 002-2.32l-3-32.62a2.13 2.13 0 00-2.26-1.94l-17.81 1.16a2.14 2.14 0 00-1.98 2.25z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M296.5 273.44s-5.43 36.73-5.22 41.66 5.79 15.88 13.3 16.95 14.59-5.79 14.81-14.8 1.33-37.52 1.33-37.52l-5.61-3.09z"
|
||||
fill="#ffbe9d"
|
||||
/>
|
||||
<Path
|
||||
d="M309.88 275.6a1.73 1.73 0 00-.2.38c-.13.3-.28.66-.48 1.1-.4 1-1 2.35-1.79 4.05-1.54 3.41-3.86 8-6.25 13.22A94.27 94.27 0 00296 308c-.27.9-.44 1.73-.62 2.45s-.3 1.36-.39 1.87-.15.87-.2 1.19-.06.42 0 .42a2.19 2.19 0 00.12-.4c.08-.32.17-.7.28-1.17s.25-1.14.46-1.85.39-1.54.68-2.43a106.79 106.79 0 015.3-13.58c2.39-5.18 4.66-9.83 6.14-13.27.74-1.72 1.31-3.13 1.67-4.11l.4-1.14a1.85 1.85 0 00.04-.38z"
|
||||
fill="#eb996e"
|
||||
/>
|
||||
<Path
|
||||
d="M327.45 243.09s-5.63 3.46-7.29 4.36-1.45 7.61-1.45 7.61l-9.46 22 11.41 4.09s4.06-7.7 5.23-8.67a7.85 7.85 0 012.73-1.36s.58 2.14 2.34 1.17a17.28 17.28 0 003.7-3.31s1.55 1 2.14-.2 1.56-3.7 1.56-3.7-3.32-4.28-4.68-4.28-8.37.39-8.37.39a5.38 5.38 0 012.34-1.37c1.94-.78 13.54-4.19 14.08-4.4.82-.31 1.74-2.44-.79-2.64s-16.8 2.35-16.8 2.35-.78-3.68-.19-4.46 5.64-4.08 3.5-7.58z"
|
||||
fill="#ffbe9d"
|
||||
/>
|
||||
<Path
|
||||
d="M334.67 268.81a3.59 3.59 0 00-.05-1.6 2.85 2.85 0 00-.85-1.58 3.29 3.29 0 00-2.1-.63 18.74 18.74 0 00-3.83.14 4.58 4.58 0 00-1.55.36c0 .13 2.41-.27 5.36-.08a3.12 3.12 0 011.86.51 2.71 2.71 0 01.82 1.35 14.33 14.33 0 00.34 1.53zM332.41 269.51c-.07-.09-1 .51-2.19 1a20.86 20.86 0 00-2.24.9 4.4 4.4 0 002.41-.52 4.31 4.31 0 002.02-1.38zM324.79 255.06a3.69 3.69 0 00-3.07 3c.11 0 .5-.86 1.36-1.67s1.75-1.21 1.71-1.33z"
|
||||
fill="#eb996e"
|
||||
/>
|
||||
</G>
|
||||
</Svg>
|
||||
);
|
||||
return (
|
||||
<Svg viewBox="0 0 500 500" {...props}>
|
||||
<Path
|
||||
d="M414.54 101.87l-2.29-3.77C393 66.34 358.12 47.18 321.37 49.51a96.75 96.75 0 00-14.07 1.93c-33.41 7.15-62.49 32.42-74.54 64.78-7.08 19-9.06 40.94-22.51 56.05-17.51 19.66-47.22 20.28-73.35 19.46s-55.9-.12-73.28 19.62c-10.91 12.36-14.38 29.94-13.5 46.49 2.33 43.83 32.87 83 71.71 102.5S206.4 381.56 248.9 373c59.55-12 116.18-45 150.22-95.88s42.65-120.29 15.42-175.25z"
|
||||
fill="#ebebeb"
|
||||
/>
|
||||
<Path d="M379.87 196.94l8.35-14.72a39.47 39.47 0 119.45 10.51z" fill="#fff" />
|
||||
<Path
|
||||
d="M379.87 196.94l17.78-4.32-.07.21a.12.12 0 010-.18.12.12 0 01.16 0 40.49 40.49 0 008.07 4.8 39.16 39.16 0 0010.72 3 35 35 0 006.18.29c1.07-.06 2.15-.09 3.24-.19l3.29-.5a39.35 39.35 0 0024.31-15.68 38.86 38.86 0 006.56-15.05 39.73 39.73 0 00-.47-17.3 38.4 38.4 0 00-8.34-16l-1.59-1.75c-.52-.59-1.14-1.07-1.71-1.61a23.36 23.36 0 00-1.76-1.54l-1.87-1.4a16.47 16.47 0 00-1.93-1.27l-2-1.2-2.07-1a16.66 16.66 0 00-2.08-.92 39.71 39.71 0 00-17.44-2.7 38.5 38.5 0 00-16 4.71 39.64 39.64 0 00-18.55 22.72 42.6 42.6 0 00-1.74 12.69 39.22 39.22 0 005.74 19.42.11.11 0 010 .12l-8.46 14.66 8.25-14.78v.13a39.19 39.19 0 01-5.89-19.54 42.76 42.76 0 011.71-12.82 40 40 0 0118.7-23 38.89 38.89 0 0116.22-4.8 40.13 40.13 0 0117.66 2.72 16.55 16.55 0 012.11.94l2.1 1 2 1.22a16.87 16.87 0 011.95 1.29l1.89 1.42c.65.45 1.19 1 1.79 1.55s1.2 1 1.73 1.64l1.61 1.77a38.81 38.81 0 018.45 16.2 40.15 40.15 0 01.46 17.53 39 39 0 01-6.58 15.21 39.66 39.66 0 01-24.62 15.81l-3.33.5c-1.1.09-2.19.12-3.27.18a34.66 34.66 0 01-6.24-.32 39.65 39.65 0 01-10.8-3.08 40.6 40.6 0 01-8.1-4.89l.18-.18a.13.13 0 010 .18h-.05z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M414 143h-4.2a3.63 3.63 0 00-3.63 3.64v23.18l25.26-.37V146.6A3.63 3.63 0 01435 143z"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path d="M414 143h-4.2a3.63 3.63 0 00-3.63 3.64v23.18l25.26-.37V146.6A3.63 3.63 0 01435 143z" />
|
||||
<Path
|
||||
d="M438 146.6a3.61 3.61 0 00-3.62-3.63h-25.26a3.62 3.62 0 013.63 3.63v33.65a1 1 0 001.53.89l11.11-6.24 11.13 5.7a1 1 0 001.49-.91c-.01-5.79-.07-25.01-.01-33.09z"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path
|
||||
d="M425.37 155.93l.92 1.87a.54.54 0 00.4.29l2.07.3a.53.53 0 01.29.9l-1.49 1.46a.52.52 0 00-.16.47l.36 2.06a.53.53 0 01-.77.56l-1.85-1a.53.53 0 00-.49 0l-1.85 1a.53.53 0 01-.77-.56l.35-2.06a.52.52 0 00-.15-.47l-1.5-1.46a.53.53 0 01.3-.9l2.07-.3a.51.51 0 00.39-.29l.93-1.87a.53.53 0 01.95 0z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<G>
|
||||
<Path d="M207 29.4h33.29v53.07H203v-49a4.07 4.07 0 014-4.07z" fill="#23a99c" />
|
||||
<Path d="M207 29.4h33.33v53.07H203v-49a4 4 0 014-4.07z" />
|
||||
<Path
|
||||
d="M254.43 36l-25.8-.12-75.36-.35h-9.07l-22.58-.1a20.27 20.27 0 00-20.4 20.16l-1.38 324.68a20.28 20.28 0 0020.21 20.33l132.82.62a20.28 20.28 0 0020.39-20.16l1.38-324.74A20.28 20.28 0 00254.43 36z"
|
||||
fill="#fafafa"
|
||||
/>
|
||||
<Path
|
||||
d="M254.43 36h1.52a7.62 7.62 0 011.16.12l1.48.23 1.77.46c.62.2 1.29.46 2 .73s1.45.64 2.19 1.11a20.29 20.29 0 014.56 3.59 19.41 19.41 0 013.89 5.89 20.31 20.31 0 011.71 8v9.33c-.09 26.38-.23 64.55-.39 111.71s-.41 103.32-.65 165.67q0 11.69-.09 23.67 0 6-.05 12v3l-.09 1.54-.23 1.52a19.79 19.79 0 01-2 5.82 21.27 21.27 0 01-3.65 5 22.15 22.15 0 01-5 3.68 20.86 20.86 0 01-5.91 2l-1.56.23-1.57.08c-1 .05-2.09 0-3.13 0l-25.21-.12-105-.48a20.68 20.68 0 01-12.64-4.4 20.42 20.42 0 01-7.23-11.15 24.2 24.2 0 01-.62-6.66v-6.64q0-6.61.06-13.19.1-26.28.22-51.76c.15-33.93.29-66.66.43-97.85s.27-60.81.4-88.54q.09-20.79.18-40.13 0-9.65.09-18.91v-4.6-2.29l.07-1.14c0-.38.11-.76.16-1.13A20.32 20.32 0 01119 35.37a32.82 32.82 0 014.19-.16h12.21l30 .16 48.05.27 30.37.19 7.9.06h2.69-10.57l-30.37-.09-48.05-.18-30-.12h-12.2a30.26 30.26 0 00-4.15.16 20 20 0 00-17.39 16.8c0 .37-.13.74-.16 1.11l-.06 1.13V61.57q0 9.25-.07 18.92-.07 19.31-.16 40.12c-.11 27.73-.23 57.36-.35 88.55s-.26 63.91-.4 97.85q-.1 25.44-.22 51.76v19.82a23.4 23.4 0 00.61 6.53 19.83 19.83 0 007 10.87 20.16 20.16 0 0012.32 4.29l105 .49 25.21.12h3.11l1.54-.08 1.52-.22a20.61 20.61 0 005.77-2 21.63 21.63 0 004.89-3.59 21 21 0 003.57-4.87 19.22 19.22 0 001.95-5.68l.23-1.48.08-1.5c.06-1 0-2 0-3l.06-12.05q0-12 .1-23.66c.29-62.36.54-118.51.76-165.67s.42-85.33.56-111.71v-9.33a20.26 20.26 0 00-1.67-7.93 19.59 19.59 0 00-3.87-5.74 20.24 20.24 0 00-4.53-3.59c-.73-.47-1.49-.76-2.17-1.12s-1.37-.53-2-.74l-1.75-.46-1.47-.25a7.89 7.89 0 00-1.16-.13l-.84-.05z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path d="M120.84 256.95H252.26V265.74H120.84z" fill="#23a99c" />
|
||||
<Path
|
||||
d="M247.57 291.56c0 .32-28 .58-62.65.58s-62.65-.26-62.65-.58 28.05-.57 62.65-.57 62.65.25 62.65.57zM247.57 83c0 .32-28 .58-62.65.58s-62.65-.26-62.65-.58 28.05-.57 62.65-.57 62.65.29 62.65.57zM247.57 307.56c0 .32-28 .58-62.65.58s-62.65-.26-62.65-.58 28.05-.57 62.65-.57 62.65.26 62.65.57zM247.57 323.57c0 .31-28 .57-62.65.57s-62.65-.26-62.65-.57 28.05-.58 62.65-.58 62.65.26 62.65.58zM247.57 339.57c0 .32-28 .57-62.65.57s-62.65-.25-62.65-.57 28.05-.58 62.65-.58 62.65.26 62.65.58zM247.57 355.57c0 .32-28 .58-62.65.58s-62.65-.26-62.65-.58 28.05-.57 62.65-.57 62.65.25 62.65.57zM249.36 373.72c0 .31-28.05.57-62.64.57s-62.65-.26-62.65-.57 28-.58 62.65-.58 62.64.26 62.64.58z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path d="M122.27 101.29H254.01999999999998V228.75H122.27z" fill="#455a64" />
|
||||
<Path d="M211.44 139.49A9.48 9.48 0 11202 130a9.48 9.48 0 019.44 9.49z" />
|
||||
<Path d="M243.84 111.27l-111.31-.09c.13-.14-1.2 1.2-.64.65v56.08c0 17.85.05 34.73.06 50.26v.55h.55l80.84-.27 22.58-.15 5.91-.06h1.5a2.69 2.69 0 00.49 0l-27-46.26-18.12 22.27-29.81-41.93-35.86 65.34c0-15.37 0-32 .06-49.68v-27.46-28.08l110.15-.09c.12 31.59.21 57.91.28 76.43.06 9.28.1 16.59.14 21.64 0 2.47 0 4.38.06 5.74v1.48a4 4 0 000 .53 2.56 2.56 0 000-.47v-1.42c0-1.34 0-3.21.06-5.64 0-5 .08-12.29.14-21.52.07-18.7.16-45.33.28-77.31v-.54z" />
|
||||
<Path
|
||||
d="M252.89 34.88a5.46 5.46 0 00-5.45-5.48h-37.93a5.45 5.45 0 015.49 5.46V88.1l19-10.68 19 9.73s-.16-37.86-.11-52.27z"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path
|
||||
d="M235.17 49.32l1.32 2.68a.75.75 0 00.57.42l3 .43a.76.76 0 01.42 1.29l-2.14 2.09a.75.75 0 00-.22.67l.5 2.94a.75.75 0 01-1.09.8l-2.65-1.39a.75.75 0 00-.71 0l-2.64 1.39a.76.76 0 01-1.1-.8l.51-2.94a.75.75 0 00-.22-.67l-2.14-2.09a.76.76 0 01.42-1.29l3-.43a.77.77 0 00.57-.42l1.32-2.68a.76.76 0 011.28 0z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</G>
|
||||
<G>
|
||||
<Path
|
||||
d="M323.81 479.11L193.26 479a28.44 28.44 0 01-28.42-28.44l.07-327.3a28.43 28.43 0 0128.45-28.43l130.55.08a28.43 28.43 0 0128.42 28.44l-.06 327.3a28.44 28.44 0 01-28.46 28.46z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M323.77 106.51h-25.31a6.28 6.28 0 00-6.14 6.42v4.59a6.28 6.28 0 01-6.14 6.42h-49.34a6.31 6.31 0 01-6.15-6.43v-4.59a6.28 6.28 0 00-6.14-6.43h-31.07a19.91 19.91 0 00-19.92 19.91l-.07 319.46a19.91 19.91 0 0019.91 19.92l130.29.08a19.92 19.92 0 0019.93-19.91l.06-319.47a19.93 19.93 0 00-19.91-19.97z"
|
||||
fill="#fafafa"
|
||||
/>
|
||||
<Path
|
||||
d="M249 439a2.51 2.51 0 11-2.52-2.48A2.5 2.5 0 01249 439zM263 438.89a2.5 2.5 0 11-2.52-2.48 2.52 2.52 0 012.52 2.48z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Circle
|
||||
cx={274.51}
|
||||
cy={438.8}
|
||||
fill="#263238"
|
||||
r={2.5}
|
||||
transform="rotate(-45.69 274.488 438.779)"
|
||||
/>
|
||||
<Path d="M197.74 318.91H318.79V326.82000000000005H197.74z" fill="#23a99c" />
|
||||
<Path
|
||||
d="M317.06 344.92c0 .28-27.18.52-60.69.52s-60.7-.24-60.7-.52 27.17-.52 60.7-.52 60.69.23 60.69.52zM317.06 359.32c0 .28-27.18.51-60.69.51s-60.7-.23-60.7-.51 27.17-.52 60.7-.52 60.69.2 60.69.52zM317.06 373.71c0 .29-27.18.52-60.69.52s-60.7-.23-60.7-.52 27.17-.51 60.7-.51 60.69.23 60.69.51zM317.06 388.11c0 .28-27.18.52-60.69.52s-60.7-.24-60.7-.52 27.17-.52 60.7-.52 60.69.23 60.69.52zM317.06 402.5c0 .29-27.18.52-60.69.52s-60.7-.23-60.7-.52 27.17-.51 60.7-.51 60.69.23 60.69.51zM318.79 418.83c0 .28-27.17.51-60.68.51s-60.7-.23-60.7-.51 27.17-.52 60.7-.52 60.68.23 60.68.52z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path d="M196.52 181.39H251.78V234.85H196.52z" fill="#455a64" />
|
||||
<Path d="M233.92 197.42a4 4 0 11-4-4 4 4 0 014 4z" />
|
||||
<Path d="M247.51 185.58h-46.69l-.27.28V230.69h.23l33.9-.12 9.47-.06H247.47L236.19 211l-7.6 9.33-12.5-17.58-15 27.41v-20.84-23.25h46.2l.12 32.06c0 3.89 0 7 .06 9.08V230.46a1.39 1.39 0 000-.2v-.59-2.37c0-2.11 0-5.15.06-9 0-7.84.07-19 .12-32.42v-.23z" />
|
||||
<Path d="M265.06 181.39H320.32V234.85H265.06z" fill="#455a64" />
|
||||
<Path d="M302.46 197.42a4 4 0 11-4-4 4 4 0 014 4z" />
|
||||
<Path d="M316.05 185.58h-46.69c.06 0-.5.51-.26.28v44.82h.23l33.91-.12 9.47-.06h3.31L304.73 211l-7.59 9.33-12.51-17.58-15 27.41v-20.84-23.25h46.21c0 13.25.08 24.3.11 32.06 0 3.89.05 7 .06 9.08V227.3c0-2.11 0-5.15.06-9 0-7.84.07-19 .12-32.42v-.23z" />
|
||||
<Path d="M196.52 244.77H251.78V298.23H196.52z" fill="#455a64" />
|
||||
<Path d="M233.92 260.8a4 4 0 11-4-4 4 4 0 014 4z" />
|
||||
<Path d="M247.51 249h-46.69l-.27.27V294h.23l33.9-.11 9.47-.06H247.47l-11.32-19.41-7.6 9.34-12.5-17.58-15 27.4v-20.83V249.45h46.2l.12 32.06c0 3.89 0 7 .06 9.08v3.25a1.57 1.57 0 000-.2v-.6-2.36c0-2.11 0-5.16.06-9 0-7.84.07-19 .12-32.42V249z" />
|
||||
<Path d="M265.03 244.77H320.28999999999996V298.23H265.03z" fill="#455a64" />
|
||||
<Path d="M302.43 260.8a4 4 0 11-4-4 4 4 0 014 4z" />
|
||||
<Path d="M316 249h-46.69l-.27.27V294h.23l33.9-.11 9.47-.06H316l-11.3-19.44-7.6 9.34-12.5-17.58-15 27.4v-20.83-23.27h46.2c0 13.25.09 24.29.12 32.06 0 3.89 0 7 .06 9.08v3.03a.82.82 0 000 .22 1.57 1.57 0 000-.2v-.6-2.36c0-2.11 0-5.16.06-9 0-7.84.07-19 .11-32.42V249z" />
|
||||
<Path
|
||||
d="M320.93 149.12a4 4 0 113.86-4 4.09 4.09 0 01-.63 2.21l.62 1.47a.2.2 0 010 .21.18.18 0 01-.2 0l-1.38-.64a3.71 3.71 0 01-2.27.75zm0-7.69a3.66 3.66 0 000 7.32A3.37 3.37 0 00323 148a.17.17 0 01.18 0l1.06.49-.47-1.14a.19.19 0 010-.18 3.72 3.72 0 00.63-2.08 3.59 3.59 0 00-3.47-3.66z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M334.79 149.26a.13.13 0 01-.1 0l-2.82-2-2.81 2a.19.19 0 01-.19 0 .2.2 0 01-.09-.16v-7.47a.18.18 0 01.18-.18h5.83a.18.18 0 01.18.18v7.47a.19.19 0 01-.1.16zm-2.92-2.44a.2.2 0 01.11 0l2.63 1.88v-6.94h-5.48v6.94l2.64-1.88a.19.19 0 01.1 0z"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path
|
||||
d="M182.15 149.76H183.08l2.52-.07-.09.09v-1.83-1.06a1 1 0 01.87-.79h1.21a1 1 0 01.85 1v2.67l-.14-.15h3.18l-.15.15v-4-1a1 1 0 00-.44-.73l-1.39-1.38-1.34-1.32-.65-.64c-.23-.22-.39-.44-.64-.49a.85.85 0 00-.71.14c-.19.15-.4.39-.59.57l-1.19 1.08c-.72.74-1.48 1.43-2 2.06a2.15 2.15 0 00-.16 1.18v4.5a1.65 1.65 0 010-.38v-1.13c0-.44 0-1.14-.05-1.83v-1.14a2.36 2.36 0 01.17-1.31c.59-.73 1.29-1.36 2-2.14l1.13-1.16c.21-.2.37-.4.62-.61a1.14 1.14 0 01.95-.19 1.75 1.75 0 01.78.56l.65.64 1.35 1.32c.46.44.92.9 1.39 1.36a1.29 1.29 0 01.53.95v5.15h-3.48v-.15-2.67a.76.76 0 00-.62-.75h-1.15a.77.77 0 00-.67.6v2.92H182.49c-.28-.01-.35-.01-.34-.02z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Rect fill="#e0e0e0" height={13.34} rx={5.12} width={116.21} x={197.63} y={138.21} />
|
||||
<Path
|
||||
d="M211.42 144.68V147h-1.17v-2.13c0-.66-.3-1-.82-1s-1 .35-1 1.09v2h-1.17v-5.57h1.17v2a1.79 1.79 0 011.28-.48 1.59 1.59 0 011.71 1.77zM215.08 146.79a1.61 1.61 0 01-1 .26 1.33 1.33 0 01-1.51-1.45v-1.66H212V143h.62v-1h1.17v1h1v.9h-1v1.65a.47.47 0 00.5.53.78.78 0 00.48-.15zM218.26 146.79a1.62 1.62 0 01-1 .26 1.33 1.33 0 01-1.5-1.45v-1.66h-.63V143h.63v-1H217v1h1v.9h-1v1.65a.47.47 0 00.5.53.75.75 0 00.47-.15zM223.32 145a2 2 0 01-2 2.08 1.57 1.57 0 01-1.22-.49v1.89h-1.17V143H220v.47a1.55 1.55 0 011.27-.53 2 2 0 012.05 2.06zm-1.19 0a1 1 0 10-1 1.12 1 1 0 001-1.12zM223.81 143.62a.73.73 0 011.46 0 .73.73 0 11-1.46 0zm0 2.69a.73.73 0 011.46 0 .73.73 0 11-1.46 0zM228.15 140.67h1l-2.48 7.07h-1zM230.55 140.67h1l-2.47 7.07h-1.05z"
|
||||
fill="#263238"
|
||||
/>
|
||||
</G>
|
||||
<G>
|
||||
<Path
|
||||
d="M181.92 385.23a22.53 22.53 0 00-36.9 8.86l2.67 10.33c1-3.52 3.19-6.92 6.55-8.37s7.55-.65 11.13.65 7.09 3.05 10.89 3.28 8.1-1.57 9.23-5.2c1.07-3.42-.99-7.06-3.57-9.55z"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path d="M181.92 385.23a22.53 22.53 0 00-36.9 8.86l2.67 10.33c1-3.52 3.19-6.92 6.55-8.37s7.55-.65 11.13.65 7.09 3.05 10.89 3.28 8.1-1.57 9.23-5.2c1.07-3.42-.99-7.06-3.57-9.55z" />
|
||||
<Path
|
||||
d="M117.57 419.69c-5.57-5.3-5.3-14.52-2.51-21.69s8.85-12.68 15.63-16.31a49.54 49.54 0 0143.16-1.4 20.48 20.48 0 00-16.56 9.71c-3.06 5.07-3.74 11.23-6.26 16.59a26.14 26.14 0 01-33.46 13.08M114.89 387.86c-6.17-7.29-7-19-6.09-28.5s3.12-19.08 1.62-28.51c-.89-5.59-3.06-10.92-3.79-16.54s.3-11.95 4.65-15.58c5-4.16 12.87-3.18 18.07.73s8.21 10.05 10.41 16.17c4.84 13.45 6.61 28.36 2.64 42.09s-13.3 26-27 30.14"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path
|
||||
d="M111.93 447.66a4.53 4.53 0 010-.84v-2.41c.08-2.09.28-5.11.75-8.82a116.62 116.62 0 012.37-12.93 59.85 59.85 0 015.9-15 62.55 62.55 0 019.64-12.88 61.07 61.07 0 0110.11-8.41c1.54-1.06 3-1.87 4.31-2.62s2.47-1.26 3.41-1.72l2.22-.93a4.13 4.13 0 01.79-.29 7 7 0 01-.75.38c-.53.26-1.26.59-2.17 1s-2.06 1.07-3.36 1.79-2.73 1.59-4.25 2.67a63.43 63.43 0 00-19.5 21.24c-5.68 9.93-7.24 20.38-8.36 27.69-.52 3.7-.77 6.71-.91 8.79-.06 1-.11 1.81-.14 2.4a5.62 5.62 0 01-.06.89z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M58.67 359.11c-11.42 6.89-11.39 13.16-9.48 19.09s8.92 10 14.73 7.81c3.29-1.25 5.67-4.1 7.7-7s3.94-6 6.79-8a9.93 9.93 0 016.92-1.9zM85.33 369.13l2.93 1.1a6.39 6.39 0 00-2.93-1.1z"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path d="M58.67 359.11c-11.42 6.89-11.39 13.16-9.48 19.09s8.92 10 14.73 7.81c3.29-1.25 5.67-4.1 7.7-7s3.94-6 6.79-8a9.93 9.93 0 016.92-1.9z" />
|
||||
<Path d="M91.65 372l-10.59-4 4.34.58a6.89 6.89 0 013.15 1.19z" />
|
||||
<Path
|
||||
d="M50.12 366.67c3-4.13 10.36-5.33 15-3.27s8.08 6.15 11.13 10.22 6 8.39 10.26 11.22 10.15 3.87 14.36 1a12.34 12.34 0 004.88-8.45 27.91 27.91 0 00-.67-10c-1-4.79-2.65-9.79-6.49-12.84-4.27-3.39-10.2-3.55-15.65-3.37-6.26.2-12.61.67-18.48 2.84s-11.87 6.85-14.36 12.6"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path
|
||||
d="M102.43 438.51a.79.79 0 010-.25v-.74c0-.68 0-1.63-.09-2.83-.08-2.51-.18-6.06-.3-10.43s-.32-9.64-.64-15.45-.76-12.2-1.64-18.85a61.07 61.07 0 00-1.89-9.58A50.15 50.15 0 0094.3 372a48.59 48.59 0 00-4.42-6.94 45.1 45.1 0 00-4.82-5.29 35.57 35.57 0 00-8.55-5.77c-.56-.26-1.05-.51-1.48-.68l-1.12-.44-.68-.27-.23-.11.24.06.71.23 1.13.39c.45.16.94.4 1.51.64a34.33 34.33 0 018.71 5.74 44.83 44.83 0 014.92 5.3 47.87 47.87 0 014.49 7 49.31 49.31 0 013.57 8.45 60.16 60.16 0 011.92 9.65c.88 6.68 1.3 13.07 1.6 18.89s.43 11.07.53 15.47.12 8 .13 10.44V438.33a.67.67 0 01-.03.18zM106.54 443.68a32.74 32.74 0 01-1.25-4.91 51.7 51.7 0 01-.52-13.78 92 92 0 014.35-20c2.35-7.35 5.53-15.21 8.25-23.66a109.27 109.27 0 003.28-12.52 110.19 110.19 0 001.62-11.94 173.75 173.75 0 00.36-20.42c-.19-5.82-.5-10.53-.7-13.78-.09-1.6-.16-2.85-.22-3.74v-1a1.37 1.37 0 010-.33 1.23 1.23 0 010 .33c0 .24.06.57.1 1 .07.89.18 2.14.31 3.74.26 3.25.62 7.95.86 13.77a166.42 166.42 0 01-.25 20.48 111.7 111.7 0 01-1.6 12 107.63 107.63 0 01-3.28 12.58c-2.73 8.47-5.93 16.32-8.29 23.64a93.11 93.11 0 00-4.43 19.86 53.33 53.33 0 00.36 13.71c.28 1.6.54 2.84.76 3.67.1.39.17.71.23 1a1.27 1.27 0 01.06.3z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M134.87 448.84L81.23 448.84 80 438.51 134.87 438.51 134.87 448.84z"
|
||||
fill="#455a64"
|
||||
/>
|
||||
<Path
|
||||
d="M86.98 447.66L93.69 478.63 122.84 478.63 129.11 447.66 86.98 447.66z"
|
||||
fill="#455a64"
|
||||
/>
|
||||
<Path
|
||||
d="M134.87 448.84c0 .14-11.8.22-26.34.18s-26.34-.19-26.34-.33 11.79-.22 26.34-.18 26.34.19 26.34.33z"
|
||||
fill="#455a64"
|
||||
/>
|
||||
<Path
|
||||
d="M125.66 464.13a21.87 21.87 0 00-4.72-2.71 16.69 16.69 0 00-5.75-1.16 20.18 20.18 0 00-7.06 1.3c-2.45.83-4.73 1.79-6.95 2.32a16.49 16.49 0 01-6 .18 12.51 12.51 0 01-3.86-1.22 7.09 7.09 0 01-.93-.59c-.21-.16-.31-.24-.29-.26s.45.27 1.31.67a13.24 13.24 0 003.82 1 16.74 16.74 0 005.85-.28c2.15-.54 4.43-1.51 6.9-2.34a20 20 0 017.23-1.29 16.58 16.58 0 015.87 1.29 15.44 15.44 0 013.5 2 9.72 9.72 0 01.83.72c.17.24.26.35.25.37z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M125.25 466.19a23.32 23.32 0 00-4.53-2.67 15.36 15.36 0 00-5.54-1.14 20 20 0 00-6.84 1.23c-2.36.79-4.58 1.71-6.69 2.28a17.25 17.25 0 01-5.78.64 9.61 9.61 0 01-3.78-1 6.54 6.54 0 01-.91-.57c-.19-.15-.29-.23-.28-.24s.45.25 1.27.63a10.31 10.31 0 003.72.83 17.49 17.49 0 005.63-.74c2.07-.58 4.27-1.51 6.66-2.31a19.8 19.8 0 017-1.21 15.19 15.19 0 015.67 1.28 14.78 14.78 0 013.35 2 7.11 7.11 0 01.79.7c.19.19.27.28.26.29z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<Path
|
||||
d="M126.19 462.07a25.67 25.67 0 00-4.85-2.86 17.88 17.88 0 00-5.89-1.41 19.11 19.11 0 00-7.29 1.28 63.77 63.77 0 01-7.19 2.35 18.43 18.43 0 01-6.21 0 13.8 13.8 0 01-4-1.26 6.35 6.35 0 01-1-.6c-.21-.15-.31-.24-.3-.26s.47.28 1.35.68a15.59 15.59 0 004 1.09 18.9 18.9 0 006.06-.12 75 75 0 007.13-2.36 19 19 0 017.48-1.27 17.48 17.48 0 016 1.55 17.19 17.19 0 013.57 2.16 7.83 7.83 0 01.86.74c.2.22.29.28.28.29z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<Path
|
||||
d="M135.34 448.88c0 .14-12.07.26-26.95.26s-26.95-.12-26.95-.26 12.07-.25 27-.25 26.9.11 26.9.25z"
|
||||
fill="#263238"
|
||||
/>
|
||||
</G>
|
||||
<G>
|
||||
<Path
|
||||
d="M374.57 216.23l-2.31-43.64a41.29 41.29 0 00-1.48-10.52 13.85 13.85 0 00-6.35-8.22c-3.16-1.64-7.42-1.35-9.85 1.24l1.72 2.36q-2.12 40.07-1.8 80.22c0 2 .08 4.15 1.25 5.77 2 2.73 6 2.7 9.32 2.39 2.87-.26 6.13-.74 7.73-3.13a8.91 8.91 0 001.16-3.35c1.42-7.6 1.04-15.35.61-23.12z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M360.72 160a8.1 8.1 0 00-4.56-4.5c-2.09-.68-4.72.13-5.63 2.13l4.93 5.56a5 5 0 002.05 1.91 2.82 2.82 0 002.72-.15 2.89 2.89 0 001.15-2.34 6 6 0 00-.66-2.61z"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path
|
||||
d="M369 183.21c-.85-9.42-5.83-18.59-13.81-23.67s-18.87-5.54-26.85-.54a28 28 0 00-10.83 14c-2.1 5.64-1.19 10.31-1.14 16.32 0 3.12.42 11.49 3.11 13.06l42 7.53c5.34-7.73 8.38-17.28 7.52-26.7z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M367.54 176c-1-5.6-6.42-11-10.68-14.79-4.86-4.28-5.55-4.09-12-4.25-6.83-.18-14.29-.15-19.62 4.13-3 2.37-4.87 5.79-6.41 9.24-3.56 8-3 17.13-1.93 25.81s2.93 16.88 10 22l29.87-4.62c6.59-2.45 8.79-10 10.65-16.75s1.28-13.86.12-20.77z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M317.9 181.55c.42-8.21 9.16-16.51 17.35-15.75l26.59 4.73v4.42l.3 55.16a229.59 229.59 0 01-30.67 4.89l-.23-11.06s-10.51-1.62-12.85-15.27c-1.16-6.83-.96-17.99-.49-27.12z"
|
||||
fill="#ffbe9d"
|
||||
/>
|
||||
<Path
|
||||
d="M322.88 192.43a1.76 1.76 0 001.71 1.75 1.68 1.68 0 001.79-1.61 1.75 1.75 0 00-1.71-1.75 1.68 1.68 0 00-1.79 1.61zM320.86 187.33c.22.23 1.54-.77 3.43-.79s3.28.91 3.49.68-.13-.52-.74-.95a4.82 4.82 0 00-2.79-.82 4.65 4.65 0 00-2.73.91c-.52.46-.77.87-.66.97zM339.68 192.43a1.76 1.76 0 001.72 1.75 1.68 1.68 0 001.79-1.61 1.76 1.76 0 00-1.71-1.75 1.69 1.69 0 00-1.8 1.61zM339.43 187.46c.22.23 1.54-.77 3.43-.79s3.28.91 3.48.68-.12-.52-.73-.95a4.83 4.83 0 00-2.8-.82 4.62 4.62 0 00-2.72.91c-.58.45-.77.87-.66.97zM332.74 201.55a12.06 12.06 0 00-3.07-.52c-.48 0-.94-.13-1-.46a2.39 2.39 0 01.3-1.43l1.38-3.69c1.92-5.25 3.3-9.57 3.09-9.65s-1.94 4.12-3.86 9.38l-1.32 3.71a2.84 2.84 0 00-.23 1.89 1.24 1.24 0 00.81.7 3.2 3.2 0 00.82.1 12.15 12.15 0 003.08-.03z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M331.24 223.94A36.26 36.26 0 00350 218.8s-4.51 9.64-18.69 8.55zM333.6 206.09a3.4 3.4 0 013-1.3 3.1 3.1 0 012.16 1.12 2 2 0 01.18 2.25 2.28 2.28 0 01-2.47.66 7.26 7.26 0 01-2.43-1.41 2.06 2.06 0 01-.55-.55.63.63 0 010-.71"
|
||||
fill="#eb996e"
|
||||
/>
|
||||
<Path
|
||||
d="M338.32 202.79c-.31 0-.29 2-2 3.51s-3.92 1.28-3.93 1.56c0 .13.49.39 1.41.41a5.12 5.12 0 003.29-1.19 4.44 4.44 0 001.56-2.94c.04-.86-.19-1.37-.33-1.35zM338.94 182.13c.19.51 2.08.25 4.29.49s4 .82 4.31.36c.13-.22-.19-.7-.92-1.19a7.12 7.12 0 00-3.2-1.08 7.3 7.3 0 00-3.35.43c-.81.34-1.21.75-1.13.99zM320.84 180.86c.34.42 1.63 0 3.2-.08s2.89.25 3.19-.2c.13-.22-.08-.65-.67-1.05a4.49 4.49 0 00-2.6-.67 4.4 4.4 0 00-2.54.9c-.55.44-.73.89-.58 1.1zM327.09 162c-5 3.41-8.52 9-10.22 15.19l.84 5.07c1.17-2.45 1.32-5.08 3.74-7.59a17.35 17.35 0 017.81-4.09c9.12-2.32 12.19-5.63 16.22-11.59-5.91-1.49-13.35-.41-18.39 3.01z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M366.45 175.36a25.07 25.07 0 00-20.23-17.69l-.74 1.31-6.93 8.1a4.78 4.78 0 00-1.3 2.27 3.49 3.49 0 001.47 3.06c2.2 1.8 5.27 1.91 8.11 2s5.94.31 8 2.25c2.57 2.42 2.51 6.53 1.65 10a42.37 42.37 0 00-.85 12.46c.32 3.15 4.48-4.5 7.22-2.91.76.44-1.3 12.8-.5 12.42s.71-4.82 1-5.65a98.68 98.68 0 003.56-13.61 32.69 32.69 0 00-.46-14.01z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M358.68 199.13a4.08 4.08 0 016.05-3.34c1.4.84 2.45 2.52 2.25 5.68-.53 8.42-8.88 6.15-8.87 5.91s.32-4.76.57-8.25z"
|
||||
fill="#ffbe9d"
|
||||
/>
|
||||
<Path
|
||||
d="M360.87 204.34s.14.12.38.25a1.47 1.47 0 001.09.09c.91-.27 1.76-1.63 1.9-3.14a4.91 4.91 0 00-.27-2.13 1.7 1.7 0 00-1-1.2.74.74 0 00-.89.34c-.13.23-.09.4-.13.41s-.18-.15-.08-.49a.88.88 0 01.36-.51 1.14 1.14 0 01.83-.16 2.06 2.06 0 011.44 1.41 5 5 0 01.36 2.38c-.17 1.69-1.15 3.22-2.39 3.49a1.53 1.53 0 01-1.32-.31c-.28-.22-.31-.41-.28-.43z"
|
||||
fill="#eb996e"
|
||||
/>
|
||||
<Path
|
||||
d="M360.81 164.3a27.6 27.6 0 013 4.25 34.66 34.66 0 012.47 5.09 35.77 35.77 0 011.88 6.66 32.68 32.68 0 01-.13 12.52 10.82 10.82 0 01-.27 1.12c-.1.34-.19.67-.27 1a11.8 11.8 0 01-.52 1.56l-.36 1-.14.33a1.61 1.61 0 01.09-.35c.08-.25.19-.57.32-1a16 16 0 00.47-1.56l.25-1a10.93 10.93 0 00.25-1.12 33.44 33.44 0 00.06-12.43 37.54 37.54 0 00-1.85-6.63 38.15 38.15 0 00-2.4-5.08l-.57-1-.56-.85-.48-.75c-.15-.23-.3-.42-.43-.6l-.61-.84z"
|
||||
fill="#455a64"
|
||||
/>
|
||||
<Path
|
||||
d="M361.63 166.87a2.12 2.12 0 01-.05.25 2 2 0 01-.34.68 4.58 4.58 0 01-2.3 1.49c-1.12.37-2.46.7-4 1a35.22 35.22 0 01-5 .72 24.17 24.17 0 01-5-.14 19.15 19.15 0 01-4-1 15.21 15.21 0 01-2.49-1.18 5.83 5.83 0 01-.63-.41c-.14-.09-.22-.15-.21-.16s.31.18.89.48a16.81 16.81 0 002.5 1.1 20.11 20.11 0 003.94.92 25.17 25.17 0 005 .13 44.61 44.61 0 008.87-1.67 4.69 4.69 0 002.28-1.38 3.64 3.64 0 00.54-.83z"
|
||||
fill="#455a64"
|
||||
/>
|
||||
<Path
|
||||
d="M360.11 164s0 .06-.14.17-.24.27-.44.45a10.45 10.45 0 01-1.87 1.36 15 15 0 01-7.35 1.85 30.63 30.63 0 01-7.55-1c-.94-.24-1.7-.45-2.23-.6a5.87 5.87 0 01-.8-.26 4.91 4.91 0 01.83.16l2.24.52a33.61 33.61 0 007.5 1 15.67 15.67 0 007.27-1.75 15.44 15.44 0 002.54-1.9zM358.38 163a2.91 2.91 0 01-.59.2c-.39.12-1 .27-1.67.42a27 27 0 01-11.28-.07c-.71-.15-1.28-.31-1.67-.43a3.24 3.24 0 01-.59-.21 2.62 2.62 0 01.62.11c.39.1 1 .22 1.67.35a30.38 30.38 0 005.61.55 32.07 32.07 0 005.6-.48c.72-.13 1.29-.24 1.68-.33a2.62 2.62 0 01.62-.11z"
|
||||
fill="#455a64"
|
||||
/>
|
||||
<Path
|
||||
d="M347.14 155.85a3.53 3.53 0 01-.2.6c-.15.38-.39.92-.71 1.57a23.52 23.52 0 01-7.41 8.56c-.6.42-1.1.73-1.45.93a3.71 3.71 0 01-.57.29 2.75 2.75 0 01.51-.37c.34-.23.83-.56 1.41-1a26.23 26.23 0 004.13-3.85 26 26 0 003.21-4.64c.35-.64.61-1.17.79-1.53a2.46 2.46 0 01.29-.56z"
|
||||
fill="#455a64"
|
||||
/>
|
||||
<Path
|
||||
d="M317.21 173.94s0-.09.14-.22.25-.34.46-.59a27 27 0 011.84-2.06 22.13 22.13 0 013.13-2.64 19.7 19.7 0 012.08-1.24 18.68 18.68 0 012.41-1 22.55 22.55 0 019-1 20 20 0 012.72.44c.32.07.56.15.72.19a.9.9 0 01.25.09s-.35-.06-1-.18a25.49 25.49 0 00-2.72-.36 23.37 23.37 0 00-8.91 1 19.45 19.45 0 00-7.57 4.75c-.83.81-1.46 1.5-1.89 2l-.5.56a1.83 1.83 0 01-.16.26zM323.53 164.88a5.55 5.55 0 01.64-.42 8.53 8.53 0 01.83-.46c.32-.16.67-.36 1.08-.53s.85-.38 1.35-.55a16.5 16.5 0 011.59-.52 21.76 21.76 0 017.59-.75c.58.06 1.14.11 1.66.21s1 .18 1.43.28a11.27 11.27 0 011.16.31 8.76 8.76 0 01.86.28 5.08 5.08 0 01.71.29 3.88 3.88 0 01-.75-.19 6.74 6.74 0 00-.86-.24c-.34-.09-.73-.2-1.16-.28s-.91-.18-1.42-.24-1.07-.13-1.65-.18a25.53 25.53 0 00-3.79 0 24.4 24.4 0 00-3.72.69c-.56.16-1.1.3-1.58.49s-.94.35-1.35.52-.76.34-1.08.49-.59.28-.81.4a3.21 3.21 0 01-.73.4z"
|
||||
fill="#455a64"
|
||||
/>
|
||||
<Path
|
||||
d="M365.35 206.46c.16-.08.41 0 .69.31a3.18 3.18 0 01.76 1.58 3.85 3.85 0 010 1.32 3.57 3.57 0 01-.63 1.51 3.74 3.74 0 01-1.45 1.19 3.21 3.21 0 01-2.18.2 3.31 3.31 0 01-1.78-1.21 3.8 3.8 0 01-.73-1.7 3.73 3.73 0 01.71-2.79 3.33 3.33 0 011.35-1.11c.38-.15.64-.13.75 0s-.23 1-.5 1.95a2.52 2.52 0 000 1.44 1.46 1.46 0 00.86.93 1 1 0 00.53 0 1.81 1.81 0 00.6-.37 2.26 2.26 0 00.63-1.26c.21-1 .04-1.83.39-1.99z"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path
|
||||
d="M311.21 272.89L317.53 301.14 314.54 320.1 383.95 320.1 379.42 231.59 362.11 230.12 319.83 237.25 311.21 272.89z"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path
|
||||
d="M328.87 236.74s-18.08-1.66-26.69 10.65c-5.27 7.52-12.67 24.81-12.67 24.81l24.07 4.15z"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path d="M353.07 263.48c5.08 6.27 7.47 14.23 10.79 21.59 4.28 9.48 10.21 18.11 16.1 26.68.44.64 1 1.36 1.82 1.27s1.21-1 1.42-1.79a25.13 25.13 0 00-1-14.74 56.1 56.1 0 00-7.2-13.12 152.18 152.18 0 00-15-18.29 8.65 8.65 0 00-3.37-2.5 3 3 0 00-3.67 1.27M317.29 296.23q1.75-6.93 3.52-13.86a10.07 10.07 0 01.95-2.68 10.43 10.43 0 012.45-2.6 11.11 11.11 0 013.73-2.35 21.58 21.58 0 003.5-.87c1.48-.74 2.27-2.35 2.88-3.89.46-1.18.85-2.58.18-3.65a3.8 3.8 0 00-3.2-1.34 14.69 14.69 0 00-13.39 8.23c-1.8 3.86-1.8 8.29-1.77 12.55l.1 11.39" />
|
||||
<Path
|
||||
d="M339.49 296.77c0-.06-.94.1-2.46.22a29.84 29.84 0 01-5.93-.15 29.22 29.22 0 01-5.76-1.38c-1.44-.5-2.3-.9-2.33-.84s.19.16.57.36a15.51 15.51 0 001.65.76 24.4 24.4 0 005.82 1.52 24.06 24.06 0 006 0 13.59 13.59 0 001.79-.31c.43-.08.66-.15.65-.18zM369.78 298c0-.12-9.86 2.68-21.94 6.25S326 310.81 326 310.92s9.86-2.68 21.94-6.25 21.87-6.54 21.84-6.67z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path d="M324 295.1a8.41 8.41 0 007 5.39 8.7 8.7 0 008.14-3.79 30.41 30.41 0 01-15.14-1.6M328.67 310.06l.25.1c6.2 1.11 12 0 17.94-2.11s11.48-5.16 17-8.25z" />
|
||||
<Path
|
||||
d="M365.57 309.37a4.07 4.07 0 010 1.8 10.55 10.55 0 01-4.68 7.09 4.08 4.08 0 01-1.64.73 13.35 13.35 0 011.43-1 12.6 12.6 0 002.86-3 12.4 12.4 0 001.67-3.83 12.94 12.94 0 01.36-1.79zM369.16 313.65a3.48 3.48 0 01-.77 1.42 9.73 9.73 0 01-6.65 3.76 3.45 3.45 0 01-1.61-.08 16 16 0 005.06-1.57 16.16 16.16 0 003.97-3.53z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M405.28 393.92c-1.28-5.24-10.54-39.13-12.32-44.23l-8.34-23.88-.56-7.89-70.73 2.18s-10.07 12.4-13.8 26.46c-3.21 12.05-4.83 30.34 2.76 77.9 5 26.39 10.78 54.47 10.78 54.47h42.37l-5.67-97.15 15.72 54.4 14.33 42.73 45.32-.24z"
|
||||
fill="#455a64"
|
||||
/>
|
||||
<Path
|
||||
d="M309.56 331.62a3.3 3.3 0 01-.12.37c-.09.26-.22.61-.38 1.05s-.39 1-.65 1.72-.55 1.45-.82 2.35a91.78 91.78 0 00-3.69 15.45 133.27 133.27 0 00-1.32 23.52c.21 8.87 1.09 18.61 2.2 28.81 2.22 20.39 6.22 38.57 9.22 51.68 1.5 6.54 2.72 11.83 3.56 15.53.42 1.81.74 3.22 1 4.22l.24 1.1a1.83 1.83 0 01.06.38 1.91 1.91 0 01-.12-.37l-.28-1.09c-.25-1-.61-2.4-1.06-4.2-.91-3.65-2.2-8.93-3.73-15.48-3-13.1-7.08-31.28-9.34-51.72-1.11-10.2-2-19.95-2.17-28.85a131.33 131.33 0 011.43-23.59 87.49 87.49 0 013.87-15.45c.28-.9.59-1.68.87-2.35l.67-1.7.43-1a1.33 1.33 0 01.13-.38zM322.07 475.26c.14 0 .38.49.55 1.16s.17 1.24 0 1.27-.38-.48-.54-1.15-.15-1.24-.01-1.28zM319.77 465.62a12.05 12.05 0 011.14 4.83 12.16 12.16 0 01-1.14-4.83z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M317.52 456a6.17 6.17 0 01.81 2.35 6.07 6.07 0 01.3 2.48 6.18 6.18 0 01-.81-2.36 6 6 0 01-.3-2.47zM315.37 446.29a6.13 6.13 0 01.78 2.36 6 6 0 01.28 2.48 6.25 6.25 0 01-.79-2.37 6.09 6.09 0 01-.27-2.47zM313.36 436.58a6.11 6.11 0 01.75 2.38 5.9 5.9 0 01.23 2.48 12.18 12.18 0 01-1-4.86zM311.54 426.84a5.94 5.94 0 01.7 2.39 6.32 6.32 0 01.19 2.49 12 12 0 01-.89-4.88zM310 417.06a11.82 11.82 0 01.76 4.9 11.82 11.82 0 01-.76-4.9zM308.65 407.25a11.8 11.8 0 01.62 4.91 6.06 6.06 0 01-.57-2.42 6.17 6.17 0 01-.05-2.49zM307.61 397.4a11.81 11.81 0 01.5 4.92 6.12 6.12 0 01-.51-2.43 6.34 6.34 0 01.01-2.49zM306.76 387.53a11.68 11.68 0 01.39 4.94 6.32 6.32 0 01-.45-2.45 6 6 0 01.06-2.49zM306.23 377.64a11.69 11.69 0 01.21 4.95 6.35 6.35 0 01-.36-2.47 6.14 6.14 0 01.15-2.48zM306.2 367.73a6 6 0 01.23 2.48 5.84 5.84 0 01-.29 2.47 6.22 6.22 0 01-.23-2.47 6.32 6.32 0 01.29-2.48zM306.84 357.85a6 6 0 01.06 2.49 6.25 6.25 0 01-.47 2.44 5.88 5.88 0 01-.05-2.49 6.08 6.08 0 01.46-2.44zM308.34 348.06a6.15 6.15 0 01-.18 2.48 6 6 0 01-.69 2.39 6.15 6.15 0 01.18-2.48 6.07 6.07 0 01.69-2.39zM310.83 338.47a11.83 11.83 0 01-1.38 4.76 6.13 6.13 0 01.44-2.45 6 6 0 01.94-2.31zM313.41 331.52c.13.05 0 .61-.23 1.25s-.57 1.11-.71 1.06 0-.61.23-1.25.58-1.12.71-1.06zM354.37 387.05c-.13.07-.84-.8-1.59-1.95a6.28 6.28 0 01-1.14-2.24c.11-.09.82.79 1.58 1.95a6.33 6.33 0 011.15 2.24zM358.66 396.07a6.47 6.47 0 01-1.23-2.19 6.23 6.23 0 01-.74-2.4 6.3 6.3 0 011.22 2.19 6.08 6.08 0 01.75 2.4zM361.89 405.53a6 6 0 01-1-2.3 6.1 6.1 0 01-.51-2.46 6.23 6.23 0 011 2.3 6.15 6.15 0 01.51 2.46zM364.71 415.13a12.17 12.17 0 01-1.4-4.8 6.06 6.06 0 01.95 2.33 6.55 6.55 0 01.45 2.47zM367.49 424.74a6.34 6.34 0 01-.95-2.33 6.24 6.24 0 01-.44-2.48 6.23 6.23 0 01.94 2.33 6.15 6.15 0 01.45 2.48zM370.28 434.34a6.06 6.06 0 01-.95-2.33 6.53 6.53 0 01-.45-2.47 6.06 6.06 0 01.95 2.33 6.53 6.53 0 01.45 2.47zM373.06 444a6.34 6.34 0 01-.95-2.33 6.24 6.24 0 01-.44-2.48 6.34 6.34 0 01.95 2.33 6.24 6.24 0 01.44 2.48zM375.85 453.55a6.06 6.06 0 01-1-2.33 6.53 6.53 0 01-.45-2.47 6.06 6.06 0 01.95 2.33 6.53 6.53 0 01.5 2.47zM378.63 463.15a12.25 12.25 0 01-1.39-4.8 11.94 11.94 0 011.39 4.8zM381.42 472.76a6.06 6.06 0 01-.95-2.33A6.55 6.55 0 01380 468a6.06 6.06 0 011 2.32 6.36 6.36 0 01.42 2.44zM383.34 479.38c-.14 0-.37-.34-.52-.84s-.15-.94 0-1 .37.33.51.84.14.96.01 1zM356.9 319.25a2.45 2.45 0 01.07.58c0 .44 0 1 .09 1.67.07 1.45.14 3.54.16 6.14.07 5.18-.06 12.35-.61 20.24-.61 9.12-1.75 17.22-2.61 22.31v.13h-.13l-4.57.1h-1.2a1.57 1.57 0 01-.42 0 1.54 1.54 0 01.42-.06l1.21-.09 4.56-.25-.16.14c.71-5.1 1.74-13.17 2.38-22.29.55-7.88.74-15 .76-20.21v-7.81a2.39 2.39 0 01.05-.6zM348.53 322.33s-.14.18-.34.49a2.78 2.78 0 00-.43 1.49 2.69 2.69 0 00.86 2 2.5 2.5 0 003.93-2 2.79 2.79 0 00-2.57-2.44h-.6c-.01 0 .19-.14.6-.19a2.7 2.7 0 011.66.45 3 3 0 011.39 2.16 2.95 2.95 0 01-4.72 2.38 3 3 0 01-.92-2.4 2.75 2.75 0 01.63-1.6c.28-.28.5-.36.51-.34zM334.38 321.9a4.19 4.19 0 01-.12 1.16 18 18 0 01-.86 3.07A20.23 20.23 0 01317.91 339a16.78 16.78 0 01-3.17.29 4.27 4.27 0 01-1.16-.08c0-.1 1.67 0 4.26-.57A20.92 20.92 0 00333.06 326c1-2.46 1.22-4.12 1.32-4.1zM389.67 340.78a4.21 4.21 0 01-1.06-.42 20.48 20.48 0 01-2.64-1.63 41 41 0 01-7.21-7.18 73.89 73.89 0 01-6.16-8.11 26 26 0 01-1.47-2.72 4.34 4.34 0 01-.4-1.06c.08 0 .74 1.43 2.18 3.58a96 96 0 006.25 8 47.31 47.31 0 007 7.22c2.07 1.54 3.55 2.24 3.51 2.32zM347.42 451.59a4.59 4.59 0 01-1 .06 26.44 26.44 0 01-2.72-.15 36 36 0 01-8.77-2 36.43 36.43 0 01-8-4.1 26 26 0 01-2.18-1.65c-.49-.42-.74-.66-.72-.69s1.17.81 3.1 2a41.83 41.83 0 0016.62 6.06c2.25.33 3.67.38 3.67.47zM404.67 444.84a16.91 16.91 0 01-1.5 2.81c-1 1.69-2.47 4-4.23 6.4s-3.48 4.53-4.78 6a16.17 16.17 0 01-2.2 2.28 21.74 21.74 0 011.92-2.52c1.22-1.53 2.89-3.65 4.64-6.07s3.26-4.65 4.34-6.29a21.82 21.82 0 011.81-2.61zM368.1 251.72c-.3-3.68 3.91-8 5.11-11.5 2.42-7.05 2.17-10.11 1.36-24l-12.3-2.62c-.38 4.63.29 2.82-.15 7.83a31.91 31.91 0 01-4.39 14.28c-1.92 3.05-4.61 5.87-5 9.45-.53 5.17 4 9.63 4.31 14.82.4 7.21-7.4 13.16-6.45 20.31a5.06 5.06 0 002.36 3.95 7 7 0 003.38.51l7.58-.1a3.25 3.25 0 002.3-.62c.94-.92.44-2.47.25-3.78-.85-5.62 5.17-10.24 5.32-15.93.14-4.4-3.31-8.2-3.68-12.6z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M360.18 269.22s21.2 47.2 33.52 48.65c6.21.73 10-2.67 11.33-9.11s-3.11-48.65-3.11-48.65L380.59 266l1.62 8.18-12.77-12.68z"
|
||||
fill="#ffbe9d"
|
||||
/>
|
||||
<Path
|
||||
d="M379.42 231.59s11.93 1.14 16.37 10.92 10.6 20.8 10.6 20.8l-26.24 5.91s-3.67-19.74-3.95-26.71c-.31-7.63-1.1-11.14 3.22-10.92z"
|
||||
fill="#23a99c"
|
||||
/>
|
||||
<Path
|
||||
d="M400.39 292.17a2.43 2.43 0 00-.3-.33l-.9-.89-3.35-3.26c-2.83-2.75-6.72-6.56-11-10.8S376.79 268.82 374 266l-3.29-3.31-.9-.89c-.21-.2-.33-.31-.34-.29a1.5 1.5 0 00.27.34l.85 1c.74.82 1.83 2 3.19 3.41 2.72 2.86 6.53 6.76 10.81 11s8.19 8 11.07 10.72c1.44 1.35 2.62 2.43 3.44 3.16l1 .83a1.83 1.83 0 00.29.2z"
|
||||
fill="#eb996e"
|
||||
/>
|
||||
<Path
|
||||
d="M341.44 232.56l8.29 3.23s4.56 0 5.15 1.51l1.18 2.94s2.79.71 3.38 2.27 5.44 12.15 5.44 12.15l4.56 6.84-9.26 7.72a21.54 21.54 0 00-3.83-3.83c-1.91-1.32-9.85-8-10-9.8s-4.44-12-4.49-13.08-.16-3.44.92-2.85 2.54 3.53 2.54 3.53l-1-4a6.74 6.74 0 01-2.76-4.4z"
|
||||
fill="#ffbe9d"
|
||||
/>
|
||||
<Path
|
||||
d="M358.86 256.51a8.22 8.22 0 00-.85-1.22l-2.44-3.17-.05-.06H352.16l-.06.11a3.31 3.31 0 01-1.49 1.56 3.65 3.65 0 01-1.15.15 10.21 10.21 0 01-1.24 0l.16.3.56-1.13 2-4.06-.18.11 5.59-.05h-.07l3.09 1.44a8.2 8.2 0 001.17.48 7.15 7.15 0 00-1.09-.63l-3-1.57h-5.71l-.06.11-2 4-.56 1.13-.14.27h.3a8.3 8.3 0 001.31 0h.64a1.92 1.92 0 00.66-.15 3.66 3.66 0 001.68-1.78l-.17.11h3.16l-.13-.06 2.55 3.09a9.32 9.32 0 00.88 1.02zM356.06 243.84a8.4 8.4 0 00-1.37-.09l-3.71-.07h-.64l.57.31a11.37 11.37 0 012.42 1.62c.18.18.31.39.24.52a1.28 1.28 0 01-.54.47 7.13 7.13 0 01-1.6.64h.06a7.62 7.62 0 01-3.13-1 3.07 3.07 0 01-1.19-2.5 11.34 11.34 0 01.11-2.81l-.18.16 6.46-.51 1.84-.18a2.27 2.27 0 00.66-.11 3.17 3.17 0 00-.67 0l-1.84.07-6.47.35h-.16v.15a11.6 11.6 0 00-.15 2.91 5.69 5.69 0 00.34 1.54 2.46 2.46 0 001.06 1.3 7.91 7.91 0 003.34 1.08h.06a7.54 7.54 0 001.68-.7 1.48 1.48 0 00.69-.67.61.61 0 000-.56 1.72 1.72 0 00-.28-.36 11 11 0 00-2.55-1.64l-.07.3 3.71-.07a8.4 8.4 0 001.31-.15z"
|
||||
fill="#eb996e"
|
||||
/>
|
||||
<Path
|
||||
d="M369.44 261.5l-.06-.13-.22-.35-.86-1.35-3.3-5.08c-.89-2-1.94-4.38-3.12-7.07q-.9-2-1.89-4.26c-.18-.38-.32-.72-.53-1.15a3.24 3.24 0 00-.92-1 8 8 0 00-2.43-1.11l.13.11-.77-1.93-.39-1a1.84 1.84 0 00-.86-.84 7.57 7.57 0 00-2.18-.63 19.11 19.11 0 00-2.29-.2h.07l-8.3-3.24-.3-.12v.33c0 .77.06 1.54.12 2.38a6.43 6.43 0 00.88 2.37 7.5 7.5 0 002 2.08l-.1-.13c.35 1.35.69 2.68 1 4l.39-.15a20.89 20.89 0 00-1.1-1.9 6.08 6.08 0 00-1.46-1.67.78.78 0 00-.68-.12.82.82 0 00-.44.51 4 4 0 00-.19 1.1v1.58a3.74 3.74 0 00.13.53c.85 2.62 1.86 5 2.71 7.36.43 1.17.85 2.3 1.22 3.41a12 12 0 01.44 1.63 3.53 3.53 0 00.8 1.55 38.16 38.16 0 004.3 4.32c1.39 1.23 2.68 2.3 3.83 3.2.58.45 1.15.84 1.64 1.21a13.45 13.45 0 011.28 1.11c.73.71 1.26 1.3 1.61 1.7l.4.46.15.15a1.13 1.13 0 00-.12-.18c-.09-.13-.22-.28-.37-.48a20.88 20.88 0 00-1.56-1.76 15.26 15.26 0 00-1.28-1.15c-.5-.39-1-.77-1.61-1.23-1.14-.92-2.41-2-3.78-3.23a40.52 40.52 0 01-4.23-4.32 3.34 3.34 0 01-.72-1.38 12.28 12.28 0 00-.45-1.71c-.36-1.12-.77-2.26-1.2-3.43-.83-2.29-1.83-4.71-2.64-7.29a2.18 2.18 0 01-.11-.46 4.07 4.07 0 010-.5v-1c0-.62.17-1.58.68-1.16a5.64 5.64 0 011.33 1.55 17.94 17.94 0 011.07 1.86L346 245l-.48-1.85c-.33-1.29-.67-2.62-1-4v-.07l-.08-.06a7 7 0 01-1.82-1.94 5.94 5.94 0 01-.82-2.2c-.05-.77-.08-1.58-.11-2.34l-.29.2 8.3 3.23h.07a11.38 11.38 0 014.3.77 1.45 1.45 0 01.68.63c.13.34.27.67.4 1l.77 1.92v.09h.09a7.55 7.55 0 012.31 1 3 3 0 01.83.85c.16.32.34.74.5 1.1l1.93 4.25c1.21 2.68 2.29 5.05 3.19 7.05l3.41 5c.39.56.7 1 .92 1.32l.24.33z"
|
||||
fill="#eb996e"
|
||||
/>
|
||||
<Path
|
||||
d="M382 274a53.11 53.11 0 00-.85-6.49 52.33 52.33 0 00-1.26-6.43 49.7 49.7 0 00.84 6.5A50.74 50.74 0 00382 274zM322.54 231.87l2.59 32.65a2.13 2.13 0 002.26 2l18.2-1.18a2.14 2.14 0 002-2.32l-3-32.62a2.13 2.13 0 00-2.26-1.94l-17.81 1.16a2.14 2.14 0 00-1.98 2.25z"
|
||||
fill="#263238"
|
||||
/>
|
||||
<Path
|
||||
d="M296.5 273.44s-5.43 36.73-5.22 41.66 5.79 15.88 13.3 16.95 14.59-5.79 14.81-14.8 1.33-37.52 1.33-37.52l-5.61-3.09z"
|
||||
fill="#ffbe9d"
|
||||
/>
|
||||
<Path
|
||||
d="M309.88 275.6a1.73 1.73 0 00-.2.38c-.13.3-.28.66-.48 1.1-.4 1-1 2.35-1.79 4.05-1.54 3.41-3.86 8-6.25 13.22A94.27 94.27 0 00296 308c-.27.9-.44 1.73-.62 2.45s-.3 1.36-.39 1.87-.15.87-.2 1.19-.06.42 0 .42a2.19 2.19 0 00.12-.4c.08-.32.17-.7.28-1.17s.25-1.14.46-1.85.39-1.54.68-2.43a106.79 106.79 0 015.3-13.58c2.39-5.18 4.66-9.83 6.14-13.27.74-1.72 1.31-3.13 1.67-4.11l.4-1.14a1.85 1.85 0 00.04-.38z"
|
||||
fill="#eb996e"
|
||||
/>
|
||||
<Path
|
||||
d="M327.45 243.09s-5.63 3.46-7.29 4.36-1.45 7.61-1.45 7.61l-9.46 22 11.41 4.09s4.06-7.7 5.23-8.67a7.85 7.85 0 012.73-1.36s.58 2.14 2.34 1.17a17.28 17.28 0 003.7-3.31s1.55 1 2.14-.2 1.56-3.7 1.56-3.7-3.32-4.28-4.68-4.28-8.37.39-8.37.39a5.38 5.38 0 012.34-1.37c1.94-.78 13.54-4.19 14.08-4.4.82-.31 1.74-2.44-.79-2.64s-16.8 2.35-16.8 2.35-.78-3.68-.19-4.46 5.64-4.08 3.5-7.58z"
|
||||
fill="#ffbe9d"
|
||||
/>
|
||||
<Path
|
||||
d="M334.67 268.81a3.59 3.59 0 00-.05-1.6 2.85 2.85 0 00-.85-1.58 3.29 3.29 0 00-2.1-.63 18.74 18.74 0 00-3.83.14 4.58 4.58 0 00-1.55.36c0 .13 2.41-.27 5.36-.08a3.12 3.12 0 011.86.51 2.71 2.71 0 01.82 1.35 14.33 14.33 0 00.34 1.53zM332.41 269.51c-.07-.09-1 .51-2.19 1a20.86 20.86 0 00-2.24.9 4.4 4.4 0 002.41-.52 4.31 4.31 0 002.02-1.38zM324.79 255.06a3.69 3.69 0 00-3.07 3c.11 0 .5-.86 1.36-1.67s1.75-1.21 1.71-1.33z"
|
||||
fill="#eb996e"
|
||||
/>
|
||||
</G>
|
||||
</Svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import { useMemo } from "react";
|
||||
|
||||
interface Page<T> {
|
||||
items: T[];
|
||||
items: T[];
|
||||
}
|
||||
|
||||
interface PaginatedResult<T> {
|
||||
pages?: Page<T>[];
|
||||
items?: T[];
|
||||
pages?: Page<T>[];
|
||||
items?: T[];
|
||||
}
|
||||
|
||||
export const useFlattenedItems = <T>(data: PaginatedResult<T> | undefined | null): T[] => {
|
||||
return useMemo((): T[] => {
|
||||
if (!data) {
|
||||
return [];
|
||||
}
|
||||
return useMemo((): T[] => {
|
||||
if (!data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (data.pages && Array.isArray(data.pages) && data.pages.length > 0) {
|
||||
return data.pages.flatMap(page => page.items || []);
|
||||
} else if (data.items && Array.isArray(data.items)) {
|
||||
return data.items;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}, [data]);
|
||||
if (data.pages && Array.isArray(data.pages) && data.pages.length > 0) {
|
||||
return data.pages.flatMap((page) => page.items || []);
|
||||
} else if (data.items && Array.isArray(data.items)) {
|
||||
return data.items;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}, [data]);
|
||||
};
|
||||
|
||||
@@ -1,61 +1,60 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { formatDistanceToNowStrict, Locale } from "date-fns";
|
||||
import { fr } from "date-fns/locale";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export const useRelativeTime = (
|
||||
dateInput: string | Date | number | null | undefined,
|
||||
options?: {
|
||||
addSuffix?: boolean;
|
||||
unit?: "second" | "minute" | "hour" | "day" | "month" | "year";
|
||||
locale?: Locale;
|
||||
roundingMethod?: "floor" | "ceil" | "round";
|
||||
includeSeconds?: boolean;
|
||||
},
|
||||
updateInterval: number = 60000
|
||||
dateInput: string | Date | number | null | undefined,
|
||||
options?: {
|
||||
addSuffix?: boolean;
|
||||
unit?: "second" | "minute" | "hour" | "day" | "month" | "year";
|
||||
locale?: Locale;
|
||||
roundingMethod?: "floor" | "ceil" | "round";
|
||||
includeSeconds?: boolean;
|
||||
},
|
||||
updateInterval: number = 60000,
|
||||
): string => {
|
||||
const [relativeTime, setRelativeTime] = useState("");
|
||||
const [relativeTime, setRelativeTime] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (dateInput === null || dateInput === undefined) {
|
||||
setRelativeTime("");
|
||||
return;
|
||||
}
|
||||
useEffect(() => {
|
||||
if (dateInput === null || dateInput === undefined) {
|
||||
setRelativeTime("");
|
||||
return;
|
||||
}
|
||||
|
||||
const date = new Date(dateInput);
|
||||
const date = new Date(dateInput);
|
||||
|
||||
// Check if the date is valid
|
||||
if (isNaN(date.getTime())) {
|
||||
setRelativeTime("Invalid Date");
|
||||
return;
|
||||
}
|
||||
// Check if the date is valid
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
setRelativeTime("Invalid Date");
|
||||
return;
|
||||
}
|
||||
|
||||
const updateTime = () => {
|
||||
// Default options if none provided, ensures suffix is added
|
||||
const effectiveOptions = {
|
||||
locale: fr,
|
||||
addSuffix: true,
|
||||
...options,
|
||||
};
|
||||
const updateTime = () => {
|
||||
// Default options if none provided, ensures suffix is added
|
||||
const effectiveOptions = {
|
||||
addSuffix: true,
|
||||
locale: fr,
|
||||
...options,
|
||||
};
|
||||
|
||||
try {
|
||||
const formattedTime = formatDistanceToNowStrict(date, effectiveOptions);
|
||||
setRelativeTime(formattedTime);
|
||||
} catch (error) {
|
||||
console.error("Error formatting relative time:", error);
|
||||
setRelativeTime(dateInput.toString()); // Handle potential errors during formatting
|
||||
}
|
||||
};
|
||||
try {
|
||||
const formattedTime = formatDistanceToNowStrict(date, effectiveOptions);
|
||||
setRelativeTime(formattedTime);
|
||||
} catch (error) {
|
||||
console.error("Error formatting relative time:", error);
|
||||
setRelativeTime(dateInput.toString()); // Handle potential errors during formatting
|
||||
}
|
||||
};
|
||||
|
||||
// Initial update
|
||||
updateTime();
|
||||
// Initial update
|
||||
updateTime();
|
||||
|
||||
// Set up interval for periodic updates
|
||||
const intervalId = setInterval(updateTime, updateInterval);
|
||||
// Set up interval for periodic updates
|
||||
const intervalId = setInterval(updateTime, updateInterval);
|
||||
|
||||
// Clean up the interval when the component unmounts or dateInput changes
|
||||
return () => clearInterval(intervalId);
|
||||
}, [dateInput, options, updateInterval]);
|
||||
// Clean up the interval when the component unmounts or dateInput changes
|
||||
return () => clearInterval(intervalId);
|
||||
}, [dateInput, options, updateInterval]);
|
||||
|
||||
return relativeTime;
|
||||
return relativeTime;
|
||||
};
|
||||
|
||||
@@ -1,86 +1,88 @@
|
||||
import { SplashScreen, useRouter } from "expo-router";
|
||||
import React, { createContext, useContext, useEffect, useState } from "react";
|
||||
|
||||
import { useRouter, SplashScreen } from "expo-router";
|
||||
|
||||
import { clearTokens, setTokens, getAccessToken, getRefreshToken } from "@/store/auth";
|
||||
import { clearTokens, getAccessToken, getRefreshToken, setTokens } from "@/store/auth";
|
||||
|
||||
SplashScreen.preventAutoHideAsync();
|
||||
|
||||
type AuthState = {
|
||||
isReady: boolean;
|
||||
isLoggedIn: boolean;
|
||||
login: (accessToken: string, refreshToken: string) => void;
|
||||
logout: () => void;
|
||||
accessToken: string | null;
|
||||
refreshToken: string | null;
|
||||
isReady: boolean;
|
||||
isLoggedIn: boolean;
|
||||
login: (accessToken: string, refreshToken: string) => void;
|
||||
logout: () => void;
|
||||
accessToken: string | null;
|
||||
refreshToken: string | null;
|
||||
};
|
||||
|
||||
const AuthContext = createContext<AuthState>({
|
||||
isReady: false,
|
||||
isLoggedIn: false,
|
||||
login: () => {},
|
||||
logout: () => {},
|
||||
accessToken: null,
|
||||
refreshToken: null,
|
||||
accessToken: null,
|
||||
isLoggedIn: false,
|
||||
isReady: false,
|
||||
login: () => {},
|
||||
logout: () => {},
|
||||
refreshToken: null,
|
||||
});
|
||||
|
||||
export function useAuth() {
|
||||
return useContext(AuthContext);
|
||||
return useContext(AuthContext);
|
||||
}
|
||||
|
||||
export function AuthProvider({ children }: React.PropsWithChildren) {
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
const [accessToken, setAccessToken] = useState<string | null>(null);
|
||||
const [refreshToken, setRefreshToken] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
const [accessToken, setAccessToken] = useState<string | null>(null);
|
||||
const [refreshToken, setRefreshToken] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
|
||||
const isLoggedIn = !!(accessToken && refreshToken);
|
||||
const isLoggedIn = !!(accessToken && refreshToken);
|
||||
|
||||
const login = (access: string, refresh: string) => {
|
||||
setAccessToken(access);
|
||||
setRefreshToken(refresh);
|
||||
setTokens(access, refresh);
|
||||
router.replace("/(authed)/(tabs)/articles");
|
||||
const login = (access: string, refresh: string) => {
|
||||
setAccessToken(access);
|
||||
setRefreshToken(refresh);
|
||||
setTokens(access, refresh);
|
||||
router.replace("/(authed)/(tabs)/articles");
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
setAccessToken(null);
|
||||
setRefreshToken(null);
|
||||
clearTokens();
|
||||
router.replace("/signin");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const loadTokens = async () => {
|
||||
try {
|
||||
const [storedAccess, storedRefresh] = await Promise.all([
|
||||
getAccessToken(),
|
||||
getRefreshToken(),
|
||||
]);
|
||||
|
||||
if (storedAccess && storedRefresh) {
|
||||
setAccessToken(storedAccess);
|
||||
setRefreshToken(storedRefresh);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Unable to retrieve auth tokens", error);
|
||||
} finally {
|
||||
setIsReady(true);
|
||||
await SplashScreen.hideAsync();
|
||||
}
|
||||
};
|
||||
loadTokens();
|
||||
}, []);
|
||||
|
||||
const logout = () => {
|
||||
setAccessToken(null);
|
||||
setRefreshToken(null);
|
||||
clearTokens();
|
||||
router.replace("/signin");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const loadTokens = async () => {
|
||||
try {
|
||||
const [storedAccess, storedRefresh] = await Promise.all([getAccessToken(), getRefreshToken()]);
|
||||
|
||||
if (storedAccess && storedRefresh) {
|
||||
setAccessToken(storedAccess);
|
||||
setRefreshToken(storedRefresh);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Unable to retrieve auth tokens", error);
|
||||
} finally {
|
||||
setIsReady(true);
|
||||
await SplashScreen.hideAsync();
|
||||
}
|
||||
};
|
||||
loadTokens();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
isReady,
|
||||
isLoggedIn,
|
||||
login,
|
||||
logout,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
accessToken,
|
||||
isLoggedIn,
|
||||
isReady,
|
||||
login,
|
||||
logout,
|
||||
refreshToken,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
import {
|
||||
Inter_100Thin,
|
||||
Inter_200ExtraLight,
|
||||
Inter_300Light,
|
||||
Inter_400Regular,
|
||||
Inter_500Medium,
|
||||
Inter_600SemiBold,
|
||||
Inter_700Bold,
|
||||
Inter_800ExtraBold,
|
||||
Inter_900Black,
|
||||
useFonts,
|
||||
} from "@expo-google-fonts/inter";
|
||||
import { SplashScreen } from "expo-router";
|
||||
import type React from "react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import {
|
||||
SplashScreen.preventAutoHideAsync();
|
||||
|
||||
export const FontsLoaderProvider = ({ children }: React.PropsWithChildren) => {
|
||||
const [fontsLoaded, fontError] = useFonts({
|
||||
Inter_100Thin,
|
||||
Inter_200ExtraLight,
|
||||
Inter_300Light,
|
||||
@@ -11,34 +27,17 @@ import {
|
||||
Inter_700Bold,
|
||||
Inter_800ExtraBold,
|
||||
Inter_900Black,
|
||||
useFonts,
|
||||
} from "@expo-google-fonts/inter";
|
||||
import { SplashScreen } from "expo-router";
|
||||
});
|
||||
|
||||
SplashScreen.preventAutoHideAsync();
|
||||
|
||||
export const FontsLoaderProvider = ({ children }: React.PropsWithChildren) => {
|
||||
const [fontsLoaded, fontError] = useFonts({
|
||||
Inter_100Thin,
|
||||
Inter_200ExtraLight,
|
||||
Inter_300Light,
|
||||
Inter_400Regular,
|
||||
Inter_500Medium,
|
||||
Inter_600SemiBold,
|
||||
Inter_700Bold,
|
||||
Inter_800ExtraBold,
|
||||
Inter_900Black,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (fontsLoaded || fontError) {
|
||||
SplashScreen.hideAsync();
|
||||
}
|
||||
}, [fontsLoaded, fontError]);
|
||||
|
||||
if (!fontsLoaded && !fontError) {
|
||||
return null;
|
||||
useEffect(() => {
|
||||
if (fontsLoaded || fontError) {
|
||||
SplashScreen.hideAsync();
|
||||
}
|
||||
}, [fontsLoaded, fontError]);
|
||||
|
||||
return <>{children}</>;
|
||||
if (!fontsLoaded && !fontError) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
@@ -1,42 +1,41 @@
|
||||
import React, { createContext, useContext, useEffect, useState } from "react";
|
||||
|
||||
import * as Network from "expo-network";
|
||||
import { NetworkStateEvent } from "expo-network";
|
||||
import React, { createContext, useContext, useEffect, useState } from "react";
|
||||
|
||||
type NetworkState = {
|
||||
isConnected: boolean;
|
||||
isConnected: boolean;
|
||||
};
|
||||
|
||||
const NetworkContext = createContext<NetworkState>({
|
||||
isConnected: true,
|
||||
isConnected: true,
|
||||
});
|
||||
|
||||
export const useNetwork = () => useContext(NetworkContext);
|
||||
|
||||
export const NetworkProvider = ({ children }: React.PropsWithChildren) => {
|
||||
const [isConnected, setIsConnected] = useState(true);
|
||||
const [isConnected, setIsConnected] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
let subscription: { remove: () => any };
|
||||
useEffect(() => {
|
||||
let subscription: { remove: () => any };
|
||||
|
||||
const subscribeToNetworkChanges = async () => {
|
||||
const state = await Network.getNetworkStateAsync();
|
||||
updateConnection(state);
|
||||
const subscribeToNetworkChanges = async () => {
|
||||
const state = await Network.getNetworkStateAsync();
|
||||
updateConnection(state);
|
||||
|
||||
subscription = Network.addNetworkStateListener(updateConnection);
|
||||
};
|
||||
subscription = Network.addNetworkStateListener(updateConnection);
|
||||
};
|
||||
|
||||
const updateConnection = (state: NetworkStateEvent) => {
|
||||
const connected = state.isConnected && state.isInternetReachable === true;
|
||||
setIsConnected(connected === undefined ? false : connected);
|
||||
};
|
||||
const updateConnection = (state: NetworkStateEvent) => {
|
||||
const connected = state.isConnected && state.isInternetReachable === true;
|
||||
setIsConnected(connected === undefined ? false : connected);
|
||||
};
|
||||
|
||||
subscribeToNetworkChanges();
|
||||
subscribeToNetworkChanges();
|
||||
|
||||
return () => {
|
||||
subscription && subscription.remove();
|
||||
};
|
||||
}, []);
|
||||
return () => {
|
||||
subscription?.remove();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <NetworkContext.Provider value={{ isConnected }}>{children}</NetworkContext.Provider>;
|
||||
return <NetworkContext.Provider value={{ isConnected }}>{children}</NetworkContext.Provider>;
|
||||
};
|
||||
|
||||
@@ -10,17 +10,17 @@ import { TamaguiConfigProvider } from "@/providers/tamagui-config-provider";
|
||||
import { TanstackQueryProvider } from "@/providers/tanstack-query-provider";
|
||||
|
||||
export const RootProviders = ({ children }: React.PropsWithChildren) => (
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<TanstackQueryProvider>
|
||||
<SafeAreaProvider>
|
||||
<FontsLoaderProvider>
|
||||
<TamaguiConfigProvider>
|
||||
<NetworkProvider>
|
||||
<AuthProvider>{children}</AuthProvider>
|
||||
</NetworkProvider>
|
||||
</TamaguiConfigProvider>
|
||||
</FontsLoaderProvider>
|
||||
</SafeAreaProvider>
|
||||
</TanstackQueryProvider>
|
||||
</GestureHandlerRootView>
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<TanstackQueryProvider>
|
||||
<SafeAreaProvider>
|
||||
<FontsLoaderProvider>
|
||||
<TamaguiConfigProvider>
|
||||
<NetworkProvider>
|
||||
<AuthProvider>{children}</AuthProvider>
|
||||
</NetworkProvider>
|
||||
</TamaguiConfigProvider>
|
||||
</FontsLoaderProvider>
|
||||
</SafeAreaProvider>
|
||||
</TanstackQueryProvider>
|
||||
</GestureHandlerRootView>
|
||||
);
|
||||
|
||||
@@ -5,5 +5,5 @@ import { TamaguiProvider } from "tamagui";
|
||||
import { config } from "~/tamagui.config";
|
||||
|
||||
export const TamaguiConfigProvider = ({ children }: React.PropsWithChildren) => (
|
||||
<TamaguiProvider config={config}>{children}</TamaguiProvider>
|
||||
<TamaguiProvider config={config}>{children}</TamaguiProvider>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type React from "react";
|
||||
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import type React from "react";
|
||||
|
||||
export const queryClient = new QueryClient();
|
||||
|
||||
export const TanstackQueryProvider = ({ children }: React.PropsWithChildren) => (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
|
||||
@@ -4,24 +4,24 @@ export const getAccessToken = () => SecureStore.getItemAsync("user_access_token"
|
||||
export const getRefreshToken = () => SecureStore.getItemAsync("user_refresh_token");
|
||||
|
||||
export const setTokens = async (access: string, refresh: string) => {
|
||||
try {
|
||||
await Promise.all([
|
||||
SecureStore.setItemAsync("user_access_token", access),
|
||||
SecureStore.setItemAsync("user_refresh_token", refresh),
|
||||
]);
|
||||
} catch (error) {
|
||||
console.log(access, refresh);
|
||||
console.error("Unable to save auth tokens", error);
|
||||
}
|
||||
try {
|
||||
await Promise.all([
|
||||
SecureStore.setItemAsync("user_access_token", access),
|
||||
SecureStore.setItemAsync("user_refresh_token", refresh),
|
||||
]);
|
||||
} catch (error) {
|
||||
console.log(access, refresh);
|
||||
console.error("Unable to save auth tokens", error);
|
||||
}
|
||||
};
|
||||
|
||||
export const clearTokens = async () => {
|
||||
try {
|
||||
await Promise.all([
|
||||
SecureStore.deleteItemAsync("user_access_token"),
|
||||
SecureStore.deleteItemAsync("user_refresh_token"),
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error("Unable to clear auth tokens", error);
|
||||
}
|
||||
try {
|
||||
await Promise.all([
|
||||
SecureStore.deleteItemAsync("user_access_token"),
|
||||
SecureStore.deleteItemAsync("user_refresh_token"),
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error("Unable to clear auth tokens", error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { Image } from "tamagui";
|
||||
|
||||
type AppLogoProps = {
|
||||
width?: number;
|
||||
height?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
};
|
||||
|
||||
export const AppIcon = (props: AppLogoProps) => {
|
||||
const { width = 80, height = 80 } = props;
|
||||
const { width = 80, height = 80 } = props;
|
||||
|
||||
return (
|
||||
<Image
|
||||
source={require("@/assets/images/logo.png")}
|
||||
width={height}
|
||||
height={width}
|
||||
objectFit="contain"
|
||||
marginBottom="$2"
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Image
|
||||
height={width}
|
||||
marginBottom="$2"
|
||||
objectFit="contain"
|
||||
source={require("@/assets/images/logo.png")}
|
||||
width={height}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,8 +4,15 @@ import { View } from "tamagui";
|
||||
import { Caption } from "@/ui/components/typography";
|
||||
|
||||
export const LoadingView = () => (
|
||||
<View flex={1} padding="$4" backgroundColor="$background" alignItems="center" justifyContent="center" gap="$4">
|
||||
<ActivityIndicator />
|
||||
<Caption>Chargement...</Caption>
|
||||
</View>
|
||||
<View
|
||||
alignItems="center"
|
||||
backgroundColor="$background"
|
||||
flex={1}
|
||||
gap="$4"
|
||||
justifyContent="center"
|
||||
padding="$4"
|
||||
>
|
||||
<ActivityIndicator />
|
||||
<Caption>Chargement...</Caption>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import React from "react";
|
||||
|
||||
import { Caption } from "@/ui/components/typography";
|
||||
|
||||
type ArticleCategoryPillProps = {
|
||||
category: string;
|
||||
category: string;
|
||||
};
|
||||
|
||||
export const ArticleCategoryPill = (props: ArticleCategoryPillProps) => {
|
||||
const { category } = props;
|
||||
const { category } = props;
|
||||
|
||||
return <Caption>{category}</Caption>;
|
||||
return <Caption>{category}</Caption>;
|
||||
};
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import { GetProps, Image, styled } from "tamagui";
|
||||
|
||||
const StyledImage = styled(Image, {
|
||||
borderRadius: "$4",
|
||||
backgroundColor: "$gray3",
|
||||
objectFit: "cover",
|
||||
backgroundColor: "$gray3",
|
||||
borderRadius: "$4",
|
||||
objectFit: "cover",
|
||||
});
|
||||
|
||||
type ArticleCoverImageProps = GetProps<typeof StyledImage> & {
|
||||
uri: string;
|
||||
width: string | number;
|
||||
height: number;
|
||||
uri: string;
|
||||
width: string | number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
export const ArticleCoverImage = (props: ArticleCoverImageProps) => {
|
||||
const { width, height, uri, ...rest } = props;
|
||||
const { width, height, uri, ...rest } = props;
|
||||
|
||||
return <StyledImage source={{ uri, cache: "force-cache" }} width={width} height={height} {...rest} />;
|
||||
return (
|
||||
<StyledImage height={height} source={{ cache: "force-cache", uri }} width={width} {...rest} />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -15,97 +15,97 @@ const HorizontalSeparator = () => <XStack width="$1" />;
|
||||
const VerticalSeparator = () => <YStack height="$1" />;
|
||||
|
||||
const LoadingIndicator = () => (
|
||||
<>
|
||||
<YStack height="$1" />
|
||||
<ActivityIndicator />
|
||||
<YStack height="$1" />
|
||||
</>
|
||||
<>
|
||||
<YStack height="$1" />
|
||||
<ActivityIndicator />
|
||||
<YStack height="$1" />
|
||||
</>
|
||||
);
|
||||
|
||||
export type ArticleListDisplayMode = "card" | "magazine" | "text-only";
|
||||
|
||||
type ArticleListProps = Omit<FlatListProps<ArticleOverview>, "renderItem"> & {
|
||||
data: ArticleOverview[];
|
||||
horizontal?: boolean;
|
||||
infiniteScroll?: boolean;
|
||||
displayMode?: ArticleListDisplayMode;
|
||||
hasNextPage?: boolean;
|
||||
isFetchingNextPage?: boolean;
|
||||
fetchNextPage?: () => void;
|
||||
data: ArticleOverview[];
|
||||
horizontal?: boolean;
|
||||
infiniteScroll?: boolean;
|
||||
displayMode?: ArticleListDisplayMode;
|
||||
hasNextPage?: boolean;
|
||||
isFetchingNextPage?: boolean;
|
||||
fetchNextPage?: () => void;
|
||||
};
|
||||
|
||||
type ArticleListComponent = React.FC<ArticleListProps> & {
|
||||
HorizontalSeparator: typeof HorizontalSeparator;
|
||||
VerticalSeparator: typeof VerticalSeparator;
|
||||
LoadingIndicator: typeof LoadingIndicator;
|
||||
HorizontalSeparator: typeof HorizontalSeparator;
|
||||
VerticalSeparator: typeof VerticalSeparator;
|
||||
LoadingIndicator: typeof LoadingIndicator;
|
||||
};
|
||||
|
||||
const keyExtractor = (item: ArticleOverview) => item.id;
|
||||
|
||||
const selectDisplayComponent = (mode: ArticleListDisplayMode) => {
|
||||
switch (mode) {
|
||||
case "card":
|
||||
return ArticleOverviewCard;
|
||||
case "magazine":
|
||||
return ArticleMagazineCard;
|
||||
case "text-only":
|
||||
return ArticleTextOnlyCard;
|
||||
default:
|
||||
throw new Error(`Unknown display mode: ${mode}`);
|
||||
}
|
||||
switch (mode) {
|
||||
case "card":
|
||||
return ArticleOverviewCard;
|
||||
case "magazine":
|
||||
return ArticleMagazineCard;
|
||||
case "text-only":
|
||||
return ArticleTextOnlyCard;
|
||||
default:
|
||||
throw new Error(`Unknown display mode: ${mode}`);
|
||||
}
|
||||
};
|
||||
|
||||
const ArticleList: ArticleListComponent = (props: ArticleListProps) => {
|
||||
const {
|
||||
data,
|
||||
displayMode = "magazine",
|
||||
horizontal = false,
|
||||
infiniteScroll = false,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
fetchNextPage,
|
||||
refreshing,
|
||||
...rest
|
||||
} = props;
|
||||
const {
|
||||
data,
|
||||
displayMode = "magazine",
|
||||
horizontal = false,
|
||||
infiniteScroll = false,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
fetchNextPage,
|
||||
refreshing,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const renderItem = useCallback(
|
||||
({ item }: { item: ArticleOverview }) => {
|
||||
const itemWidth = horizontal ? screenWidth * 0.7 : undefined;
|
||||
const DisplayComponent = selectDisplayComponent(displayMode);
|
||||
const renderItem = useCallback(
|
||||
({ item }: { item: ArticleOverview }) => {
|
||||
const itemWidth = horizontal ? screenWidth * 0.7 : undefined;
|
||||
const DisplayComponent = selectDisplayComponent(displayMode);
|
||||
|
||||
return (
|
||||
<View style={{ width: itemWidth }}>
|
||||
<DisplayComponent data={item} />
|
||||
</View>
|
||||
);
|
||||
},
|
||||
[horizontal, displayMode]
|
||||
);
|
||||
return (
|
||||
<View style={{ width: itemWidth }}>
|
||||
<DisplayComponent data={item} />
|
||||
</View>
|
||||
);
|
||||
},
|
||||
[horizontal, displayMode],
|
||||
);
|
||||
|
||||
const handleOnEndReached = useCallback(async () => {
|
||||
if (infiniteScroll && hasNextPage && !isFetchingNextPage && fetchNextPage) {
|
||||
fetchNextPage();
|
||||
}
|
||||
}, [hasNextPage, isFetchingNextPage, fetchNextPage, infiniteScroll]);
|
||||
const handleOnEndReached = useCallback(async () => {
|
||||
if (infiniteScroll && hasNextPage && !isFetchingNextPage && fetchNextPage) {
|
||||
fetchNextPage();
|
||||
}
|
||||
}, [hasNextPage, isFetchingNextPage, fetchNextPage, infiniteScroll]);
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
{...rest}
|
||||
data={data}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={keyExtractor}
|
||||
ItemSeparatorComponent={horizontal ? HorizontalSeparator : VerticalSeparator}
|
||||
horizontal={horizontal}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
initialNumToRender={5}
|
||||
onEndReachedThreshold={0.5}
|
||||
removeClippedSubviews={true}
|
||||
onEndReached={handleOnEndReached}
|
||||
refreshing={refreshing}
|
||||
ListFooterComponent={infiniteScroll ? LoadingIndicator : undefined}
|
||||
ListEmptyComponent={() => <Text>Pas d’articles disponibles pour le moment.</Text>}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<FlatList
|
||||
{...rest}
|
||||
data={data}
|
||||
horizontal={horizontal}
|
||||
ItemSeparatorComponent={horizontal ? HorizontalSeparator : VerticalSeparator}
|
||||
initialNumToRender={5}
|
||||
keyExtractor={keyExtractor}
|
||||
ListEmptyComponent={() => <Text>Pas d’articles disponibles pour le moment.</Text>}
|
||||
ListFooterComponent={infiniteScroll ? LoadingIndicator : undefined}
|
||||
onEndReached={handleOnEndReached}
|
||||
onEndReachedThreshold={0.5}
|
||||
refreshing={refreshing}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={renderItem}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
ArticleList.HorizontalSeparator = HorizontalSeparator;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from "react";
|
||||
|
||||
import { Link } from "expo-router";
|
||||
import { Card, XStack, YStack } from "tamagui";
|
||||
|
||||
@@ -10,36 +8,36 @@ import { SourceReferencePill } from "@/ui/components/content/source/SourceRefere
|
||||
import { Caption, Text } from "@/ui/components/typography";
|
||||
|
||||
type ArticleMagazineCardProps = {
|
||||
data: ArticleOverview;
|
||||
data: ArticleOverview;
|
||||
};
|
||||
|
||||
export const ArticleMagazineCard = (props: ArticleMagazineCardProps) => {
|
||||
const { data } = props;
|
||||
const relativeTime = useRelativeTime(data.publishedAt);
|
||||
const { data } = props;
|
||||
const relativeTime = useRelativeTime(data.publishedAt);
|
||||
|
||||
return (
|
||||
<Card width="100%" backgroundColor="transparent" borderRadius="$4" padding={0}>
|
||||
<Link href={`/(authed)/(tabs)/articles/${data.id}`}>
|
||||
<XStack flexDirection="row" gap="$3" alignItems="center">
|
||||
<YStack flex={1} gap="$2">
|
||||
<Text numberOfLines={2} fontWeight="600" fontSize="$5">
|
||||
{data.title}
|
||||
</Text>
|
||||
<Text size="$3" numberOfLines={2} color="$colorHover">
|
||||
{data.excerpt}
|
||||
</Text>
|
||||
</YStack>
|
||||
return (
|
||||
<Card backgroundColor="transparent" borderRadius="$4" padding={0} width="100%">
|
||||
<Link href={`/(authed)/(tabs)/articles/${data.id}`}>
|
||||
<XStack alignItems="center" flexDirection="row" gap="$3">
|
||||
<YStack flex={1} gap="$2">
|
||||
<Text fontSize="$5" fontWeight="600" numberOfLines={2}>
|
||||
{data.title}
|
||||
</Text>
|
||||
<Text color="$colorHover" numberOfLines={2} size="$3">
|
||||
{data.excerpt}
|
||||
</Text>
|
||||
</YStack>
|
||||
|
||||
{data.image && <ArticleCoverImage uri={data.image} width={120} height={90} />}
|
||||
</XStack>
|
||||
</Link>
|
||||
{data.image && <ArticleCoverImage height={90} uri={data.image} width={120} />}
|
||||
</XStack>
|
||||
</Link>
|
||||
|
||||
<YStack marginTop="$3">
|
||||
<XStack justifyContent="space-between" alignItems="center">
|
||||
<SourceReferencePill data={data.source} />
|
||||
<Caption>{relativeTime}</Caption>
|
||||
</XStack>
|
||||
</YStack>
|
||||
</Card>
|
||||
);
|
||||
<YStack marginTop="$3">
|
||||
<XStack alignItems="center" justifyContent="space-between">
|
||||
<SourceReferencePill data={data.source} />
|
||||
<Caption>{relativeTime}</Caption>
|
||||
</XStack>
|
||||
</YStack>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from "react";
|
||||
|
||||
import { Link } from "expo-router";
|
||||
import { Card, XStack, YStack } from "tamagui";
|
||||
|
||||
@@ -10,35 +8,33 @@ import { SourceReferencePill } from "@/ui/components/content/source/SourceRefere
|
||||
import { Caption, Text } from "@/ui/components/typography";
|
||||
|
||||
type ArticleOverviewCardProps = {
|
||||
data: ArticleOverview;
|
||||
data: ArticleOverview;
|
||||
};
|
||||
|
||||
export const ArticleOverviewCard = (props: ArticleOverviewCardProps) => {
|
||||
const { data } = props;
|
||||
const relativeTime = useRelativeTime(data.publishedAt);
|
||||
const { data } = props;
|
||||
const relativeTime = useRelativeTime(data.publishedAt);
|
||||
|
||||
return (
|
||||
<Card backgroundColor="transparent">
|
||||
<Link href={`/(authed)/(tabs)/articles/${data.id}`} asChild>
|
||||
<>
|
||||
{data.image && <ArticleCoverImage uri={data.image} width="100%" height={200} />}
|
||||
<YStack marginTop="$2" gap="$2">
|
||||
<Text numberOfLines={2} fontWeight="600" fontSize="$5">
|
||||
{data.title}
|
||||
</Text>
|
||||
<Text size="$3" numberOfLines={2}>
|
||||
{data.excerpt}
|
||||
</Text>
|
||||
</YStack>
|
||||
</>
|
||||
</Link>
|
||||
return (
|
||||
<Card backgroundColor="transparent">
|
||||
<Link asChild href={`/(authed)/(tabs)/articles/${data.id}`}>
|
||||
{data.image && <ArticleCoverImage height={200} uri={data.image} width="100%" />}
|
||||
<YStack gap="$2" marginTop="$2">
|
||||
<Text fontSize="$5" fontWeight="600" numberOfLines={2}>
|
||||
{data.title}
|
||||
</Text>
|
||||
<Text numberOfLines={2} size="$3">
|
||||
{data.excerpt}
|
||||
</Text>
|
||||
</YStack>
|
||||
</Link>
|
||||
|
||||
<YStack marginTop="$2">
|
||||
<XStack justifyContent="space-between" alignItems="center">
|
||||
<SourceReferencePill data={data.source} />
|
||||
<Caption>{relativeTime}</Caption>
|
||||
</XStack>
|
||||
</YStack>
|
||||
</Card>
|
||||
);
|
||||
<YStack marginTop="$2">
|
||||
<XStack alignItems="center" justifyContent="space-between">
|
||||
<SourceReferencePill data={data.source} />
|
||||
<Caption>{relativeTime}</Caption>
|
||||
</XStack>
|
||||
</YStack>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback } from "react";
|
||||
import { useCallback } from "react";
|
||||
|
||||
import ContentLoader, { Circle, Rect } from "react-content-loader/native";
|
||||
import { Dimensions, FlatList } from "react-native";
|
||||
@@ -10,120 +10,122 @@ const { width: screenWidth } = Dimensions.get("window");
|
||||
const data: number[] = new Array(5).fill(0);
|
||||
|
||||
type ArticleSkeletonListProps = {
|
||||
horizontal?: boolean;
|
||||
displayMode?: ArticleListDisplayMode;
|
||||
horizontal?: boolean;
|
||||
displayMode?: ArticleListDisplayMode;
|
||||
};
|
||||
|
||||
const OverviewCardSkeleton = (props: any) => (
|
||||
<ContentLoader
|
||||
speed={1}
|
||||
interval={0.3}
|
||||
backgroundColor="#D4D5D8"
|
||||
foregroundColor="white"
|
||||
height={350}
|
||||
animate={true}
|
||||
width="100%"
|
||||
{...props}
|
||||
>
|
||||
<Rect x="0" y="0" rx="8" ry="8" width="100%" height="200" />
|
||||
<Rect x="0" y="216" rx="4" ry="4" width="80%" height="10" />
|
||||
<Rect x="0" y="232" rx="4" ry="4" width="100%" height="10" />
|
||||
<ContentLoader
|
||||
animate={true}
|
||||
backgroundColor="#D4D5D8"
|
||||
foregroundColor="white"
|
||||
height={350}
|
||||
interval={0.3}
|
||||
speed={1}
|
||||
width="100%"
|
||||
{...props}
|
||||
>
|
||||
<Rect height="200" rx="8" ry="8" width="100%" x="0" y="0" />
|
||||
<Rect height="10" rx="4" ry="4" width="80%" x="0" y="216" />
|
||||
<Rect height="10" rx="4" ry="4" width="100%" x="0" y="232" />
|
||||
|
||||
<Rect x="0" y="256" rx="4" ry="4" width="100%" height="10" />
|
||||
<Rect x="0" y="272" rx="4" ry="4" width="60%" height="10" />
|
||||
<Rect height="10" rx="4" ry="4" width="100%" x="0" y="256" />
|
||||
<Rect height="10" rx="4" ry="4" width="60%" x="0" y="272" />
|
||||
|
||||
<Circle cx="10" cy="310" r="9" />
|
||||
<Rect x="30" y="305" rx="4" ry="4" width="15%" height="10" />
|
||||
<Rect x="215" y="305" rx="4" ry="4" width="20%" height="10" />
|
||||
</ContentLoader>
|
||||
<Circle cx="10" cy="310" r="9" />
|
||||
<Rect height="10" rx="4" ry="4" width="15%" x="30" y="305" />
|
||||
<Rect height="10" rx="4" ry="4" width="20%" x="215" y="305" />
|
||||
</ContentLoader>
|
||||
);
|
||||
|
||||
const MagazineCardSkeleton = (props: any) => (
|
||||
<ContentLoader
|
||||
speed={1.5}
|
||||
backgroundColor="#D4D5D8"
|
||||
foregroundColor="white"
|
||||
height={140}
|
||||
animate={true}
|
||||
width="100%"
|
||||
{...props}
|
||||
>
|
||||
<Rect x="235" y="0" rx="8" ry="8" width="120" height="90" />
|
||||
<ContentLoader
|
||||
animate={true}
|
||||
backgroundColor="#D4D5D8"
|
||||
foregroundColor="white"
|
||||
height={140}
|
||||
speed={1.5}
|
||||
width="100%"
|
||||
{...props}
|
||||
>
|
||||
<Rect height="90" rx="8" ry="8" width="120" x="235" y="0" />
|
||||
|
||||
<Rect x="0" y="0" rx="4" ry="4" width="54%" height="10" />
|
||||
<Rect x="0" y="16" rx="4" ry="4" width="56%" height="10" />
|
||||
<Rect x="0" y="40" rx="4" ry="4" width="55%" height="10" />
|
||||
<Rect x="0" y="56" rx="4" ry="4" width="55%" height="10" />
|
||||
<Rect x="0" y="72" rx="4" ry="4" width="55%" height="10" />
|
||||
<Rect height="10" rx="4" ry="4" width="54%" x="0" y="0" />
|
||||
<Rect height="10" rx="4" ry="4" width="56%" x="0" y="16" />
|
||||
<Rect height="10" rx="4" ry="4" width="55%" x="0" y="40" />
|
||||
<Rect height="10" rx="4" ry="4" width="55%" x="0" y="56" />
|
||||
<Rect height="10" rx="4" ry="4" width="55%" x="0" y="72" />
|
||||
|
||||
<Circle cx="10" cy="110" r="9" />
|
||||
<Rect x="30" y="105" rx="4" ry="4" width="15%" height="10" />
|
||||
<Rect x="315" y="105" rx="4" ry="4" width="40" height="10" />
|
||||
</ContentLoader>
|
||||
<Circle cx="10" cy="110" r="9" />
|
||||
<Rect height="10" rx="4" ry="4" width="15%" x="30" y="105" />
|
||||
<Rect height="10" rx="4" ry="4" width="40" x="315" y="105" />
|
||||
</ContentLoader>
|
||||
);
|
||||
|
||||
const TextOnlyCardSkeleton = (props: any) => (
|
||||
<ContentLoader
|
||||
speed={1.5}
|
||||
backgroundColor="#D4D5D8"
|
||||
foregroundColor="white"
|
||||
height={150}
|
||||
animate={true}
|
||||
width="100%"
|
||||
{...props}
|
||||
>
|
||||
<Rect x="0" y="16" rx="4" ry="4" width="80%" height="10" />
|
||||
<Rect x="0" y="32" rx="4" ry="4" width="100%" height="10" />
|
||||
<ContentLoader
|
||||
animate={true}
|
||||
backgroundColor="#D4D5D8"
|
||||
foregroundColor="white"
|
||||
height={150}
|
||||
speed={1.5}
|
||||
width="100%"
|
||||
{...props}
|
||||
>
|
||||
<Rect height="10" rx="4" ry="4" width="80%" x="0" y="16" />
|
||||
<Rect height="10" rx="4" ry="4" width="100%" x="0" y="32" />
|
||||
|
||||
<Rect x="0" y="56" rx="4" ry="4" width="100%" height="10" />
|
||||
<Rect x="0" y="72" rx="4" ry="4" width="60%" height="10" />
|
||||
<Rect height="10" rx="4" ry="4" width="100%" x="0" y="56" />
|
||||
<Rect height="10" rx="4" ry="4" width="60%" x="0" y="72" />
|
||||
|
||||
<Circle cx="10" cy="110" r="9" />
|
||||
<Rect x="30" y="105" rx="4" ry="4" width="15%" height="10" />
|
||||
<Rect x="215" y="105" rx="4" ry="4" width="20%" height="10" />
|
||||
</ContentLoader>
|
||||
<Circle cx="10" cy="110" r="9" />
|
||||
<Rect height="10" rx="4" ry="4" width="15%" x="30" y="105" />
|
||||
<Rect height="10" rx="4" ry="4" width="20%" x="215" y="105" />
|
||||
</ContentLoader>
|
||||
);
|
||||
|
||||
const keyExtractor = (_: number, index: number) => index.toString();
|
||||
|
||||
const selectSkeletonComponent = (displayMode: ArticleListDisplayMode) => {
|
||||
switch (displayMode) {
|
||||
case "magazine":
|
||||
return MagazineCardSkeleton;
|
||||
case "text-only":
|
||||
return TextOnlyCardSkeleton;
|
||||
default:
|
||||
return OverviewCardSkeleton;
|
||||
}
|
||||
switch (displayMode) {
|
||||
case "magazine":
|
||||
return MagazineCardSkeleton;
|
||||
case "text-only":
|
||||
return TextOnlyCardSkeleton;
|
||||
default:
|
||||
return OverviewCardSkeleton;
|
||||
}
|
||||
};
|
||||
|
||||
export const ArticleSkeletonList = (props: ArticleSkeletonListProps) => {
|
||||
const { horizontal = false, displayMode = "magazine" } = props;
|
||||
const { horizontal = false, displayMode = "magazine" } = props;
|
||||
|
||||
const ItemSeparator = horizontal ? ArticleList.HorizontalSeparator : ArticleList.VerticalSeparator;
|
||||
const ItemSeparator = horizontal
|
||||
? ArticleList.HorizontalSeparator
|
||||
: ArticleList.VerticalSeparator;
|
||||
|
||||
const renderItem = useCallback(() => {
|
||||
const itemWidth = horizontal ? screenWidth * 0.7 : screenWidth;
|
||||
const SkeletonComponent = selectSkeletonComponent(displayMode);
|
||||
|
||||
return (
|
||||
<View style={{ width: itemWidth }}>
|
||||
<SkeletonComponent />
|
||||
</View>
|
||||
);
|
||||
}, [horizontal, displayMode]);
|
||||
const renderItem = useCallback(() => {
|
||||
const itemWidth = horizontal ? screenWidth * 0.7 : screenWidth;
|
||||
const SkeletonComponent = selectSkeletonComponent(displayMode);
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
data={data}
|
||||
scrollEnabled={false}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={keyExtractor}
|
||||
ItemSeparatorComponent={ItemSeparator}
|
||||
horizontal={horizontal}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={{ paddingBottom: 0 }}
|
||||
removeClippedSubviews={true}
|
||||
/>
|
||||
<View style={{ width: itemWidth }}>
|
||||
<SkeletonComponent />
|
||||
</View>
|
||||
);
|
||||
}, [horizontal, displayMode]);
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
contentContainerStyle={{ paddingBottom: 0 }}
|
||||
data={data}
|
||||
horizontal={horizontal}
|
||||
ItemSeparatorComponent={ItemSeparator}
|
||||
keyExtractor={keyExtractor}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={renderItem}
|
||||
scrollEnabled={false}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from "react";
|
||||
|
||||
import { Link } from "expo-router";
|
||||
import { Card, XStack, YStack } from "tamagui";
|
||||
|
||||
@@ -9,34 +7,34 @@ import { SourceReferencePill } from "@/ui/components/content/source/SourceRefere
|
||||
import { Caption, Text } from "@/ui/components/typography";
|
||||
|
||||
type ArticleTextOnlyCardProps = {
|
||||
data: ArticleOverview;
|
||||
data: ArticleOverview;
|
||||
};
|
||||
|
||||
export const ArticleTextOnlyCard = (props: ArticleTextOnlyCardProps) => {
|
||||
const { data } = props;
|
||||
const relativeTime = useRelativeTime(data.publishedAt);
|
||||
const { data } = props;
|
||||
const relativeTime = useRelativeTime(data.publishedAt);
|
||||
|
||||
return (
|
||||
<Card width="100%" backgroundColor="transparent" borderRadius="$4" padding={0}>
|
||||
<Link href={`/(authed)/(tabs)/articles/${data.id}`}>
|
||||
<XStack flexDirection="row" gap="$3" alignItems="center">
|
||||
<YStack flex={1} gap="$2">
|
||||
<Text numberOfLines={2} fontWeight="600" fontSize="$5">
|
||||
{data.title}
|
||||
</Text>
|
||||
<Text size="$3" numberOfLines={2} color="$colorHover">
|
||||
{data.excerpt}
|
||||
</Text>
|
||||
</YStack>
|
||||
</XStack>
|
||||
</Link>
|
||||
return (
|
||||
<Card backgroundColor="transparent" borderRadius="$4" padding={0} width="100%">
|
||||
<Link href={`/(authed)/(tabs)/articles/${data.id}`}>
|
||||
<XStack alignItems="center" flexDirection="row" gap="$3">
|
||||
<YStack flex={1} gap="$2">
|
||||
<Text fontSize="$5" fontWeight="600" numberOfLines={2}>
|
||||
{data.title}
|
||||
</Text>
|
||||
<Text color="$colorHover" numberOfLines={2} size="$3">
|
||||
{data.excerpt}
|
||||
</Text>
|
||||
</YStack>
|
||||
</XStack>
|
||||
</Link>
|
||||
|
||||
<YStack marginTop="$3">
|
||||
<XStack justifyContent="space-between" alignItems="center">
|
||||
<SourceReferencePill data={data.source} />
|
||||
<Caption>{relativeTime}</Caption>
|
||||
</XStack>
|
||||
</YStack>
|
||||
</Card>
|
||||
);
|
||||
<YStack marginTop="$3">
|
||||
<XStack alignItems="center" justifyContent="space-between">
|
||||
<SourceReferencePill data={data.source} />
|
||||
<Caption>{relativeTime}</Caption>
|
||||
</XStack>
|
||||
</YStack>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export { ArticleCategoryPill } from "@/ui/components/content/article/ArticleCategoryPill";
|
||||
export { ArticleCoverImage } from "@/ui/components/content/article/ArticleCoverImage";
|
||||
export { ArticleList } from "@/ui/components/content/article/ArticleList";
|
||||
export { ArticleSkeletonList } from "@/ui/components/content/article/ArticleSkeleton";
|
||||
export { ArticleMagazineCard } from "@/ui/components/content/article/ArticleMagazineCard";
|
||||
export { ArticleOverviewCard } from "@/ui/components/content/article/ArticleOverviewCard";
|
||||
export { ArticleSkeletonList } from "@/ui/components/content/article/ArticleSkeleton";
|
||||
export { ArticleTextOnlyCard } from "@/ui/components/content/article/ArticleTextOnlyCard";
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from "react";
|
||||
|
||||
import { Link } from "expo-router";
|
||||
import { Card, XStack, YStack } from "tamagui";
|
||||
|
||||
@@ -8,41 +6,41 @@ import { useRelativeTime } from "@/hooks/use-relative-time";
|
||||
import { Caption, Text } from "@/ui/components/typography";
|
||||
|
||||
type BookmarkCardProps = {
|
||||
data: Bookmark;
|
||||
data: Bookmark;
|
||||
};
|
||||
|
||||
export const BookmarkCard = (props: BookmarkCardProps) => {
|
||||
const { data } = props;
|
||||
const relativeTime = useRelativeTime(data.createdAt);
|
||||
const { data } = props;
|
||||
const relativeTime = useRelativeTime(data.createdAt);
|
||||
|
||||
return (
|
||||
<Card width="100%" backgroundColor="$gray7" borderRadius="$4" padding="$4">
|
||||
<XStack gap="$4" justifyContent="space-between">
|
||||
<YStack>
|
||||
<XStack flexDirection="row" gap="$3" alignItems="center">
|
||||
<Link href={`/(authed)/(tabs)/bookmarks/${data.id}`}>
|
||||
<YStack flex={1} gap="$2">
|
||||
<Text numberOfLines={2} fontWeight="600" fontSize="$5">
|
||||
{data.name}
|
||||
</Text>
|
||||
{data.description && (
|
||||
<Text size="$3" numberOfLines={2} color="$colorHover">
|
||||
{data.description}
|
||||
</Text>
|
||||
)}
|
||||
</YStack>
|
||||
</Link>
|
||||
</XStack>
|
||||
return (
|
||||
<Card backgroundColor="$gray7" borderRadius="$4" padding="$4" width="100%">
|
||||
<XStack gap="$4" justifyContent="space-between">
|
||||
<YStack>
|
||||
<XStack alignItems="center" flexDirection="row" gap="$3">
|
||||
<Link href={`/(authed)/(tabs)/bookmarks/${data.id}`}>
|
||||
<YStack flex={1} gap="$2">
|
||||
<Text fontSize="$5" fontWeight="600" numberOfLines={2}>
|
||||
{data.name}
|
||||
</Text>
|
||||
{data.description && (
|
||||
<Text color="$colorHover" numberOfLines={2} size="$3">
|
||||
{data.description}
|
||||
</Text>
|
||||
)}
|
||||
</YStack>
|
||||
</Link>
|
||||
</XStack>
|
||||
|
||||
<YStack marginTop="$3">
|
||||
<XStack justifyContent="space-between" alignItems="center">
|
||||
<Caption>{data.isPublic}</Caption>
|
||||
<Caption>{data.articlesCount} articles</Caption>
|
||||
<Caption>{relativeTime}</Caption>
|
||||
</XStack>
|
||||
</YStack>
|
||||
</YStack>
|
||||
<YStack marginTop="$3">
|
||||
<XStack alignItems="center" justifyContent="space-between">
|
||||
<Caption>{data.isPublic}</Caption>
|
||||
<Caption>{data.articlesCount} articles</Caption>
|
||||
<Caption>{relativeTime}</Caption>
|
||||
</XStack>
|
||||
</Card>
|
||||
);
|
||||
</YStack>
|
||||
</YStack>
|
||||
</XStack>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,13 +5,15 @@ import { CreateBookmarkSheet } from "@/ui/components/content/bookmark/CreateBook
|
||||
import { Heading, Text } from "@/ui/components/typography";
|
||||
|
||||
export const BookmarkEmptyState = () => {
|
||||
return (
|
||||
<YStack flex={1} alignItems="center" justifyContent="center" gap="$2">
|
||||
<BookmarkIllustration width={250} height={250} />
|
||||
<Heading alignSelf="center">Empty Bookmarks</Heading>
|
||||
<Text textAlign="center">Create a bookmark to save your favorite articles and access them later.</Text>
|
||||
return (
|
||||
<YStack alignItems="center" flex={1} gap="$2" justifyContent="center">
|
||||
<BookmarkIllustration height={250} width={250} />
|
||||
<Heading alignSelf="center">Empty Bookmarks</Heading>
|
||||
<Text textAlign="center">
|
||||
Create a bookmark to save your favorite articles and access them later.
|
||||
</Text>
|
||||
|
||||
<CreateBookmarkSheet />
|
||||
</YStack>
|
||||
);
|
||||
<CreateBookmarkSheet />
|
||||
</YStack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,58 +10,66 @@ import { BookmarkEmptyState } from "@/ui/components/content/bookmark/BookmarkEmp
|
||||
const VerticalSeparator = () => <YStack height="$0.75" />;
|
||||
|
||||
const LoadingIndicator = () => (
|
||||
<>
|
||||
<YStack height="$1" />
|
||||
<ActivityIndicator />
|
||||
<YStack height="$1" />
|
||||
</>
|
||||
<>
|
||||
<YStack height="$1" />
|
||||
<ActivityIndicator />
|
||||
<YStack height="$1" />
|
||||
</>
|
||||
);
|
||||
|
||||
type BookmarkListProps = Omit<FlatListProps<Bookmark>, "renderItem"> & {
|
||||
data: Bookmark[];
|
||||
infiniteScroll?: boolean;
|
||||
hasNextPage?: boolean;
|
||||
isFetchingNextPage?: boolean;
|
||||
fetchNextPage?: () => void;
|
||||
data: Bookmark[];
|
||||
infiniteScroll?: boolean;
|
||||
hasNextPage?: boolean;
|
||||
isFetchingNextPage?: boolean;
|
||||
fetchNextPage?: () => void;
|
||||
};
|
||||
|
||||
type BookmarkListComponent = React.FC<BookmarkListProps> & {
|
||||
VerticalSeparator: typeof VerticalSeparator;
|
||||
LoadingIndicator: typeof LoadingIndicator;
|
||||
VerticalSeparator: typeof VerticalSeparator;
|
||||
LoadingIndicator: typeof LoadingIndicator;
|
||||
};
|
||||
|
||||
const keyExtractor = (item: Bookmark) => item.id;
|
||||
|
||||
const renderItem = ({ item }: { item: Bookmark }) => {
|
||||
return <BookmarkCard data={item} />;
|
||||
return <BookmarkCard data={item} />;
|
||||
};
|
||||
|
||||
const BookmarkList: BookmarkListComponent = (props: BookmarkListProps) => {
|
||||
const { data, infiniteScroll = false, hasNextPage, isFetchingNextPage, fetchNextPage, refreshing, ...rest } = props;
|
||||
const {
|
||||
data,
|
||||
infiniteScroll = false,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
fetchNextPage,
|
||||
refreshing,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const handleOnEndReached = useCallback(async () => {
|
||||
if (infiniteScroll && hasNextPage && !isFetchingNextPage && fetchNextPage) {
|
||||
fetchNextPage();
|
||||
}
|
||||
}, [hasNextPage, isFetchingNextPage, fetchNextPage, infiniteScroll]);
|
||||
const handleOnEndReached = useCallback(async () => {
|
||||
if (infiniteScroll && hasNextPage && !isFetchingNextPage && fetchNextPage) {
|
||||
fetchNextPage();
|
||||
}
|
||||
}, [hasNextPage, isFetchingNextPage, fetchNextPage, infiniteScroll]);
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
{...rest}
|
||||
data={data}
|
||||
renderItem={renderItem}
|
||||
onEndReached={handleOnEndReached}
|
||||
keyExtractor={keyExtractor}
|
||||
ItemSeparatorComponent={VerticalSeparator}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
initialNumToRender={5}
|
||||
onEndReachedThreshold={0.5}
|
||||
removeClippedSubviews={true}
|
||||
refreshing={refreshing}
|
||||
ListEmptyComponent={<BookmarkEmptyState />}
|
||||
ListFooterComponent={infiniteScroll && refreshing ? LoadingIndicator : undefined}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<FlatList
|
||||
{...rest}
|
||||
data={data}
|
||||
ItemSeparatorComponent={VerticalSeparator}
|
||||
initialNumToRender={5}
|
||||
keyExtractor={keyExtractor}
|
||||
ListEmptyComponent={<BookmarkEmptyState />}
|
||||
ListFooterComponent={infiniteScroll && refreshing ? LoadingIndicator : undefined}
|
||||
onEndReached={handleOnEndReached}
|
||||
onEndReachedThreshold={0.5}
|
||||
refreshing={refreshing}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={renderItem}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
BookmarkList.VerticalSeparator = VerticalSeparator;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { joiResolver } from "@hookform/resolvers/joi";
|
||||
import { Sheet } from "@tamagui/sheet";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { Button, YStack } from "tamagui";
|
||||
@@ -13,92 +12,92 @@ import { FormSwitch, FormTextArea, FormTextInput } from "@/ui/components/control
|
||||
import { SubmitButton } from "@/ui/components/controls/SubmitButton";
|
||||
|
||||
export const CreateBookmarkSheet = () => {
|
||||
const { mutate, isPending } = useCreateBookmark();
|
||||
const [open, setOpen] = useState(false);
|
||||
const { mutate, isPending } = useCreateBookmark();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { control, handleSubmit, formState } = useForm<BookmarkPayload>({
|
||||
resolver: joiResolver(BookmarkPayloadSchema),
|
||||
});
|
||||
const { control, handleSubmit, formState } = useForm<BookmarkPayload>({
|
||||
resolver: joiResolver(BookmarkPayloadSchema),
|
||||
});
|
||||
|
||||
const onSubmit = (data: BookmarkPayload) => {
|
||||
mutate(data, {
|
||||
onSuccess: () => {
|
||||
Toast.show({
|
||||
text1: "Félicitations !",
|
||||
text2: "Votre signet a été créé avec succès.",
|
||||
type: "success",
|
||||
});
|
||||
setOpen(false);
|
||||
},
|
||||
onError: (error: ErrorResponse) => {
|
||||
Toast.show({
|
||||
text1: "Erreur",
|
||||
text2: safeMessage(error),
|
||||
type: "error",
|
||||
});
|
||||
},
|
||||
const onSubmit = (data: BookmarkPayload) => {
|
||||
mutate(data, {
|
||||
onError: (error: ErrorResponse) => {
|
||||
Toast.show({
|
||||
text1: "Erreur",
|
||||
text2: safeMessage(error),
|
||||
type: "error",
|
||||
});
|
||||
};
|
||||
},
|
||||
onSuccess: () => {
|
||||
Toast.show({
|
||||
text1: "Félicitations !",
|
||||
text2: "Votre signet a été créé avec succès.",
|
||||
type: "success",
|
||||
});
|
||||
setOpen(false);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<YStack alignItems="center" justifyContent="center">
|
||||
<Button onPress={() => setOpen(true)}>Ajouter un signet</Button>
|
||||
return (
|
||||
<YStack alignItems="center" justifyContent="center">
|
||||
<Button onPress={() => setOpen(true)}>Ajouter un signet</Button>
|
||||
|
||||
<Sheet
|
||||
modal={true}
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
snapPointsMode="percent"
|
||||
snapPoints={[65, 90]}
|
||||
dismissOnOverlayPress={true}
|
||||
dismissOnSnapToBottom={true}
|
||||
animation="medium"
|
||||
>
|
||||
<Sheet.Overlay
|
||||
animation="lazy"
|
||||
backgroundColor="rgba(0, 0, 0, 0.8)"
|
||||
enterStyle={{ opacity: 0 }}
|
||||
exitStyle={{ opacity: 0 }}
|
||||
/>
|
||||
<Sheet.Frame
|
||||
flex={1}
|
||||
backgroundColor="$background"
|
||||
padding="$4"
|
||||
gap="$4"
|
||||
alignItems="center"
|
||||
justifyContent="flex-start"
|
||||
>
|
||||
<YStack width="100%">
|
||||
<Sheet.Handle theme="accent" />
|
||||
<FormTextInput
|
||||
name="name"
|
||||
control={control}
|
||||
label="Name"
|
||||
caption="Enter a name for your bookmark."
|
||||
placeholder="My awesome bookmark"
|
||||
/>
|
||||
<FormTextArea
|
||||
name="description"
|
||||
control={control}
|
||||
caption="Describe your bookmark for easy retrieval."
|
||||
label="Description"
|
||||
placeholder="A brief description..."
|
||||
/>
|
||||
<FormSwitch
|
||||
name="isPublic"
|
||||
control={control}
|
||||
label="Public"
|
||||
description="A public bookmark is visible and accessible to other users"
|
||||
/>
|
||||
</YStack>
|
||||
<SubmitButton
|
||||
label="Créer le signet"
|
||||
onPress={handleSubmit(onSubmit)}
|
||||
isPending={isPending}
|
||||
isValid={formState.isValid}
|
||||
/>
|
||||
</Sheet.Frame>
|
||||
</Sheet>
|
||||
</YStack>
|
||||
);
|
||||
<Sheet
|
||||
animation="medium"
|
||||
dismissOnOverlayPress={true}
|
||||
dismissOnSnapToBottom={true}
|
||||
modal={true}
|
||||
onOpenChange={setOpen}
|
||||
open={open}
|
||||
snapPoints={[65, 90]}
|
||||
snapPointsMode="percent"
|
||||
>
|
||||
<Sheet.Overlay
|
||||
animation="lazy"
|
||||
backgroundColor="rgba(0, 0, 0, 0.8)"
|
||||
enterStyle={{ opacity: 0 }}
|
||||
exitStyle={{ opacity: 0 }}
|
||||
/>
|
||||
<Sheet.Frame
|
||||
alignItems="center"
|
||||
backgroundColor="$background"
|
||||
flex={1}
|
||||
gap="$4"
|
||||
justifyContent="flex-start"
|
||||
padding="$4"
|
||||
>
|
||||
<YStack width="100%">
|
||||
<Sheet.Handle theme="accent" />
|
||||
<FormTextInput
|
||||
caption="Enter a name for your bookmark."
|
||||
control={control}
|
||||
label="Name"
|
||||
name="name"
|
||||
placeholder="My awesome bookmark"
|
||||
/>
|
||||
<FormTextArea
|
||||
caption="Describe your bookmark for easy retrieval."
|
||||
control={control}
|
||||
label="Description"
|
||||
name="description"
|
||||
placeholder="A brief description..."
|
||||
/>
|
||||
<FormSwitch
|
||||
control={control}
|
||||
description="A public bookmark is visible and accessible to other users"
|
||||
label="Public"
|
||||
name="isPublic"
|
||||
/>
|
||||
</YStack>
|
||||
<SubmitButton
|
||||
isPending={isPending}
|
||||
isValid={formState.isValid}
|
||||
label="Créer le signet"
|
||||
onPress={handleSubmit(onSubmit)}
|
||||
/>
|
||||
</Sheet.Frame>
|
||||
</Sheet>
|
||||
</YStack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { joiResolver } from "@hookform/resolvers/joi";
|
||||
import { Sheet } from "@tamagui/sheet";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { Button, YStack } from "tamagui";
|
||||
|
||||
import { useUpdateBookmark } from "@/api/request/feed-management/bookmark";
|
||||
import { Bookmark, BookmarkPayload, BookmarkPayloadSchema } from "@/api/schema/feed-management/bookmark";
|
||||
import {
|
||||
Bookmark,
|
||||
BookmarkPayload,
|
||||
BookmarkPayloadSchema,
|
||||
} from "@/api/schema/feed-management/bookmark";
|
||||
import { ErrorResponse, safeMessage } from "@/api/shared";
|
||||
import { FormSwitch } from "@/ui/components/controls/forms/Switch";
|
||||
import { FormTextArea } from "@/ui/components/controls/forms/TextArea";
|
||||
@@ -15,101 +18,101 @@ import { FormTextInput } from "@/ui/components/controls/forms/TextInput";
|
||||
import { SubmitButton } from "@/ui/components/controls/SubmitButton";
|
||||
|
||||
type UpdateBookmarkSheetProps = {
|
||||
data: Bookmark;
|
||||
data: Bookmark;
|
||||
};
|
||||
|
||||
export const UpdateBookmarkSheet = (props: UpdateBookmarkSheetProps) => {
|
||||
const { data } = props;
|
||||
const { mutate, isPending } = useUpdateBookmark(data.id);
|
||||
const [open, setOpen] = useState(false);
|
||||
const { data } = props;
|
||||
const { mutate, isPending } = useUpdateBookmark(data.id);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { control, handleSubmit, formState, reset } = useForm<BookmarkPayload>({
|
||||
resolver: joiResolver(BookmarkPayloadSchema),
|
||||
});
|
||||
const { control, handleSubmit, formState, reset } = useForm<BookmarkPayload>({
|
||||
resolver: joiResolver(BookmarkPayloadSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
reset({ ...data });
|
||||
}, [data, reset]);
|
||||
useEffect(() => {
|
||||
reset({ ...data });
|
||||
}, [data, reset]);
|
||||
|
||||
const onSubmit = (data: BookmarkPayload) => {
|
||||
mutate(data, {
|
||||
onSuccess: () => {
|
||||
Toast.show({
|
||||
text1: "Félicitations !",
|
||||
text2: "Votre signet a été modifié avec succès.",
|
||||
type: "success",
|
||||
});
|
||||
setOpen(false);
|
||||
},
|
||||
onError: (error: ErrorResponse) => {
|
||||
Toast.show({
|
||||
text1: "Erreur",
|
||||
text2: safeMessage(error),
|
||||
type: "error",
|
||||
});
|
||||
},
|
||||
const onSubmit = (data: BookmarkPayload) => {
|
||||
mutate(data, {
|
||||
onError: (error: ErrorResponse) => {
|
||||
Toast.show({
|
||||
text1: "Erreur",
|
||||
text2: safeMessage(error),
|
||||
type: "error",
|
||||
});
|
||||
};
|
||||
},
|
||||
onSuccess: () => {
|
||||
Toast.show({
|
||||
text1: "Félicitations !",
|
||||
text2: "Votre signet a été modifié avec succès.",
|
||||
type: "success",
|
||||
});
|
||||
setOpen(false);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<YStack alignItems="center" justifyContent="center">
|
||||
<Button onPress={() => setOpen(true)}>Modifier</Button>
|
||||
return (
|
||||
<YStack alignItems="center" justifyContent="center">
|
||||
<Button onPress={() => setOpen(true)}>Modifier</Button>
|
||||
|
||||
<Sheet
|
||||
modal={true}
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
snapPointsMode="percent"
|
||||
snapPoints={[65, 90]}
|
||||
dismissOnOverlayPress={true}
|
||||
dismissOnSnapToBottom={true}
|
||||
animation="medium"
|
||||
>
|
||||
<Sheet.Overlay
|
||||
animation="lazy"
|
||||
backgroundColor="rgba(0, 0, 0, 0.8)"
|
||||
enterStyle={{ opacity: 0 }}
|
||||
exitStyle={{ opacity: 0 }}
|
||||
/>
|
||||
<Sheet.Frame
|
||||
flex={1}
|
||||
backgroundColor="$background"
|
||||
padding="$4"
|
||||
gap="$4"
|
||||
alignItems="center"
|
||||
justifyContent="flex-start"
|
||||
>
|
||||
<YStack width="100%">
|
||||
<Sheet.Handle theme="accent" />
|
||||
<FormTextInput
|
||||
name="name"
|
||||
control={control}
|
||||
label="Name"
|
||||
caption="Enter a name for your bookmark."
|
||||
placeholder="My awesome bookmark"
|
||||
/>
|
||||
<FormTextArea
|
||||
name="description"
|
||||
control={control}
|
||||
caption="Describe your bookmark for easy retrieval."
|
||||
label="Description"
|
||||
placeholder="A brief description..."
|
||||
/>
|
||||
<FormSwitch
|
||||
name="isPublic"
|
||||
control={control}
|
||||
label="Public"
|
||||
description="A public bookmark is visible and accessible to other users"
|
||||
/>
|
||||
</YStack>
|
||||
<SubmitButton
|
||||
label="Modifier le signet"
|
||||
onPress={handleSubmit(onSubmit)}
|
||||
isPending={isPending}
|
||||
isValid={formState.isValid}
|
||||
/>
|
||||
</Sheet.Frame>
|
||||
</Sheet>
|
||||
</YStack>
|
||||
);
|
||||
<Sheet
|
||||
animation="medium"
|
||||
dismissOnOverlayPress={true}
|
||||
dismissOnSnapToBottom={true}
|
||||
modal={true}
|
||||
onOpenChange={setOpen}
|
||||
open={open}
|
||||
snapPoints={[65, 90]}
|
||||
snapPointsMode="percent"
|
||||
>
|
||||
<Sheet.Overlay
|
||||
animation="lazy"
|
||||
backgroundColor="rgba(0, 0, 0, 0.8)"
|
||||
enterStyle={{ opacity: 0 }}
|
||||
exitStyle={{ opacity: 0 }}
|
||||
/>
|
||||
<Sheet.Frame
|
||||
alignItems="center"
|
||||
backgroundColor="$background"
|
||||
flex={1}
|
||||
gap="$4"
|
||||
justifyContent="flex-start"
|
||||
padding="$4"
|
||||
>
|
||||
<YStack width="100%">
|
||||
<Sheet.Handle theme="accent" />
|
||||
<FormTextInput
|
||||
caption="Enter a name for your bookmark."
|
||||
control={control}
|
||||
label="Name"
|
||||
name="name"
|
||||
placeholder="My awesome bookmark"
|
||||
/>
|
||||
<FormTextArea
|
||||
caption="Describe your bookmark for easy retrieval."
|
||||
control={control}
|
||||
label="Description"
|
||||
name="description"
|
||||
placeholder="A brief description..."
|
||||
/>
|
||||
<FormSwitch
|
||||
control={control}
|
||||
description="A public bookmark is visible and accessible to other users"
|
||||
label="Public"
|
||||
name="isPublic"
|
||||
/>
|
||||
</YStack>
|
||||
<SubmitButton
|
||||
isPending={isPending}
|
||||
isValid={formState.isValid}
|
||||
label="Modifier le signet"
|
||||
onPress={handleSubmit(onSubmit)}
|
||||
/>
|
||||
</Sheet.Frame>
|
||||
</Sheet>
|
||||
</YStack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,57 +6,57 @@ import { Button, GetProps } from "tamagui";
|
||||
import { useFollowSource, useUnfollowSource } from "@/api/request/feed-management/source";
|
||||
|
||||
type SourceFollowButtonProps = GetProps<typeof Button> & {
|
||||
id: string;
|
||||
name: string;
|
||||
followed: boolean;
|
||||
id: string;
|
||||
name: string;
|
||||
followed: boolean;
|
||||
};
|
||||
|
||||
export const SourceFollowButton = (props: SourceFollowButtonProps) => {
|
||||
const { id, followed, name, ...rest } = props;
|
||||
const [isFollowed, setIsFollowed] = useState<boolean>(followed);
|
||||
const { mutate: follow, isPending: following } = useFollowSource(id);
|
||||
const { mutate: unfollow, isPending: unfollowing } = useUnfollowSource(id);
|
||||
const loading = following || unfollowing;
|
||||
const { id, followed, name, ...rest } = props;
|
||||
const [isFollowed, setIsFollowed] = useState<boolean>(followed);
|
||||
const { mutate: follow, isPending: following } = useFollowSource(id);
|
||||
const { mutate: unfollow, isPending: unfollowing } = useUnfollowSource(id);
|
||||
const loading = following || unfollowing;
|
||||
|
||||
const handlePress = useCallback(() => {
|
||||
if (isFollowed) {
|
||||
Alert.alert(
|
||||
"Confirmation",
|
||||
`Êtes-vous sûr de vouloir ne plus suivre ${name} ?`,
|
||||
[
|
||||
{
|
||||
text: "Annuler",
|
||||
style: "cancel",
|
||||
},
|
||||
{
|
||||
text: "Ne plus suivre",
|
||||
style: "destructive",
|
||||
onPress: () => {
|
||||
unfollow();
|
||||
setIsFollowed(prev => !prev);
|
||||
},
|
||||
},
|
||||
],
|
||||
{ cancelable: false }
|
||||
);
|
||||
} else {
|
||||
follow();
|
||||
setIsFollowed(prev => !prev);
|
||||
}
|
||||
}, [isFollowed, name, unfollow, follow, setIsFollowed]);
|
||||
const handlePress = useCallback(() => {
|
||||
if (isFollowed) {
|
||||
Alert.alert(
|
||||
"Confirmation",
|
||||
`Êtes-vous sûr de vouloir ne plus suivre ${name} ?`,
|
||||
[
|
||||
{
|
||||
style: "cancel",
|
||||
text: "Annuler",
|
||||
},
|
||||
{
|
||||
onPress: () => {
|
||||
unfollow();
|
||||
setIsFollowed((prev) => !prev);
|
||||
},
|
||||
style: "destructive",
|
||||
text: "Ne plus suivre",
|
||||
},
|
||||
],
|
||||
{ cancelable: false },
|
||||
);
|
||||
} else {
|
||||
follow();
|
||||
setIsFollowed((prev) => !prev);
|
||||
}
|
||||
}, [isFollowed, name, unfollow, follow]);
|
||||
|
||||
return (
|
||||
<Button
|
||||
size="$2"
|
||||
theme={isFollowed ? "alt1" : "surface1"}
|
||||
chromeless={isFollowed}
|
||||
disabled={loading}
|
||||
onPress={handlePress}
|
||||
minWidth={80}
|
||||
paddingHorizontal="$2"
|
||||
{...rest}
|
||||
>
|
||||
{loading ? <ActivityIndicator /> : isFollowed ? "Suivi" : "Suivre"}
|
||||
</Button>
|
||||
);
|
||||
return (
|
||||
<Button
|
||||
chromeless={isFollowed}
|
||||
disabled={loading}
|
||||
minWidth={80}
|
||||
onPress={handlePress}
|
||||
paddingHorizontal="$2"
|
||||
size="$2"
|
||||
theme={isFollowed ? "alt1" : "surface1"}
|
||||
{...rest}
|
||||
>
|
||||
{loading ? <ActivityIndicator /> : isFollowed ? "Suivi" : "Suivre"}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,43 +10,43 @@ const HorizontalSeparator = () => <XStack width="$1" />;
|
||||
const VerticalSeparator = () => <YStack height="$0.5" />;
|
||||
|
||||
type SourceOverviewListProps = Omit<FlatListProps<SourceOverview>, "renderItem"> & {
|
||||
data: SourceOverview[];
|
||||
horizontal?: boolean;
|
||||
infiniteScroll?: boolean;
|
||||
data: SourceOverview[];
|
||||
horizontal?: boolean;
|
||||
infiniteScroll?: boolean;
|
||||
};
|
||||
|
||||
type SourceOverviewListComponent = React.FC<SourceOverviewListProps> & {
|
||||
HorizontalSeparator: typeof HorizontalSeparator;
|
||||
VerticalSeparator: typeof VerticalSeparator;
|
||||
HorizontalSeparator: typeof HorizontalSeparator;
|
||||
VerticalSeparator: typeof VerticalSeparator;
|
||||
};
|
||||
|
||||
const keyExtractor = (item: SourceOverview) => item.name;
|
||||
|
||||
const SourceList: SourceOverviewListComponent = (props: SourceOverviewListProps) => {
|
||||
const { data, horizontal = false, ...rest } = props;
|
||||
const { data, horizontal = false, ...rest } = props;
|
||||
|
||||
const renderItem = useCallback(
|
||||
({ item }: { item: SourceOverview }) => {
|
||||
return <SourceOverviewCard data={item} horizontal={horizontal} />;
|
||||
},
|
||||
[horizontal]
|
||||
);
|
||||
const renderItem = useCallback(
|
||||
({ item }: { item: SourceOverview }) => {
|
||||
return <SourceOverviewCard data={item} horizontal={horizontal} />;
|
||||
},
|
||||
[horizontal],
|
||||
);
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
{...rest}
|
||||
data={data}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={keyExtractor}
|
||||
ItemSeparatorComponent={horizontal ? HorizontalSeparator : VerticalSeparator}
|
||||
horizontal={horizontal}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
initialNumToRender={5}
|
||||
onEndReachedThreshold={0.5}
|
||||
removeClippedSubviews={true}
|
||||
ListEmptyComponent={() => <Paragraph>Pas de sources disponibles pour le moment.</Paragraph>}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<FlatList
|
||||
{...rest}
|
||||
data={data}
|
||||
horizontal={horizontal}
|
||||
ItemSeparatorComponent={horizontal ? HorizontalSeparator : VerticalSeparator}
|
||||
initialNumToRender={5}
|
||||
keyExtractor={keyExtractor}
|
||||
ListEmptyComponent={() => <Paragraph>Pas de sources disponibles pour le moment.</Paragraph>}
|
||||
onEndReachedThreshold={0.5}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={renderItem}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
SourceList.HorizontalSeparator = HorizontalSeparator;
|
||||
|
||||
@@ -7,70 +7,74 @@ import { SourceProfileImage } from "@/ui/components/content/source/SourceProfile
|
||||
import { Text } from "@/ui/components/typography";
|
||||
|
||||
const SourceCardFrame = styled(YStack, {
|
||||
alignItems: "center",
|
||||
gap: "$2",
|
||||
borderRadius: "$4",
|
||||
alignItems: "center",
|
||||
borderRadius: "$4",
|
||||
gap: "$2",
|
||||
|
||||
variants: {
|
||||
horizontal: {
|
||||
true: {
|
||||
maxWidth: 100,
|
||||
flexShrink: 0,
|
||||
},
|
||||
false: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
gap: "$4",
|
||||
paddingVertical: "$2",
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
horizontal: {
|
||||
false: {
|
||||
flexDirection: "row",
|
||||
gap: "$4",
|
||||
justifyContent: "space-between",
|
||||
paddingVertical: "$2",
|
||||
width: "100%",
|
||||
},
|
||||
true: {
|
||||
flexShrink: 0,
|
||||
maxWidth: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const);
|
||||
|
||||
type SourceCardProps = GetProps<typeof SourceCardFrame> & {
|
||||
data: SourceOverview;
|
||||
horizontal?: boolean;
|
||||
data: SourceOverview;
|
||||
horizontal?: boolean;
|
||||
};
|
||||
|
||||
export const SourceOverviewCard = (props: SourceCardProps) => {
|
||||
const { data, horizontal = true, ...rest } = props;
|
||||
const { data, horizontal = true, ...rest } = props;
|
||||
|
||||
const nameFontSize = horizontal ? "$3" : "$4";
|
||||
const nameFontSize = horizontal ? "$3" : "$4";
|
||||
|
||||
return (
|
||||
<SourceCardFrame horizontal={horizontal} {...rest}>
|
||||
<Link href={`/(authed)/(tabs)/sources/${data.name}`}>
|
||||
<SourceProfileImage name={data.name} image={data.image} size={horizontal ? 65 : 50} />
|
||||
</Link>
|
||||
return (
|
||||
<SourceCardFrame horizontal={horizontal} {...rest}>
|
||||
<Link href={`/(authed)/(tabs)/sources/${data.name}`}>
|
||||
<SourceProfileImage image={data.image} name={data.name} size={horizontal ? 65 : 50} />
|
||||
</Link>
|
||||
|
||||
<Link href={`/(authed)/(tabs)/sources/${data.name}`} asChild>
|
||||
{horizontal ? (
|
||||
<Text
|
||||
fontSize={nameFontSize}
|
||||
fontWeight="bold"
|
||||
numberOfLines={1}
|
||||
textAlign="center"
|
||||
maxWidth="100%"
|
||||
>
|
||||
{data.displayName ?? data.name}
|
||||
</Text>
|
||||
) : (
|
||||
<YStack flex={1} gap="$1">
|
||||
<XStack alignItems="center" gap="$1">
|
||||
<Text fontSize={nameFontSize} fontWeight="bold" numberOfLines={1}>
|
||||
{data.displayName ?? data.name}
|
||||
</Text>
|
||||
</XStack>
|
||||
<Link asChild href={`/(authed)/(tabs)/sources/${data.name}`}>
|
||||
{horizontal ? (
|
||||
<Text
|
||||
fontSize={nameFontSize}
|
||||
fontWeight="bold"
|
||||
maxWidth="100%"
|
||||
numberOfLines={1}
|
||||
textAlign="center"
|
||||
>
|
||||
{data.displayName ?? data.name}
|
||||
</Text>
|
||||
) : (
|
||||
<YStack flex={1} gap="$1">
|
||||
<XStack alignItems="center" gap="$1">
|
||||
<Text fontSize={nameFontSize} fontWeight="bold" numberOfLines={1}>
|
||||
{data.displayName ?? data.name}
|
||||
</Text>
|
||||
</XStack>
|
||||
|
||||
<Text color="$accent6" fontSize="$3" numberOfLines={1}>
|
||||
{data.url}
|
||||
</Text>
|
||||
</YStack>
|
||||
)}
|
||||
</Link>
|
||||
<Text color="$accent6" fontSize="$3" numberOfLines={1}>
|
||||
{data.url}
|
||||
</Text>
|
||||
</YStack>
|
||||
)}
|
||||
</Link>
|
||||
|
||||
<SourceFollowButton id={data.id} name={data.displayName ?? data.name} followed={data.followed} />
|
||||
</SourceCardFrame>
|
||||
);
|
||||
<SourceFollowButton
|
||||
followed={data.followed}
|
||||
id={data.id}
|
||||
name={data.displayName ?? data.name}
|
||||
/>
|
||||
</SourceCardFrame>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,32 +1,30 @@
|
||||
import type React from "react";
|
||||
|
||||
import { GetProps, Image, styled } from "tamagui";
|
||||
|
||||
const StyledImage = styled(Image, {
|
||||
borderRadius: "$12",
|
||||
backgroundColor: "white",
|
||||
backgroundColor: "white",
|
||||
borderRadius: "$12",
|
||||
});
|
||||
|
||||
type SourceAvatarProps = GetProps<typeof StyledImage> & {
|
||||
image: string;
|
||||
name: string;
|
||||
size?: number;
|
||||
image: string;
|
||||
name: string;
|
||||
size?: number;
|
||||
};
|
||||
|
||||
export const SourceProfileImage = (props: SourceAvatarProps) => {
|
||||
const { image, name, size = 50, ...rest } = props;
|
||||
const { image, name, size = 50, ...rest } = props;
|
||||
|
||||
return (
|
||||
<StyledImage
|
||||
accessibilityLabel={name}
|
||||
source={{
|
||||
uri: image,
|
||||
cache: "force-cache",
|
||||
}}
|
||||
objectFit="contain"
|
||||
width={size}
|
||||
height={size}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<StyledImage
|
||||
accessibilityLabel={name}
|
||||
height={size}
|
||||
objectFit="contain"
|
||||
source={{
|
||||
cache: "force-cache",
|
||||
uri: image,
|
||||
}}
|
||||
width={size}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from "react";
|
||||
|
||||
import { Link } from "expo-router";
|
||||
import { Avatar, GetProps, XStack } from "tamagui";
|
||||
|
||||
@@ -7,31 +5,31 @@ import { SourceReference } from "@/api/schema/feed-management/source";
|
||||
import { Text } from "@/ui/components/typography";
|
||||
|
||||
type SourceReferencePillProps = GetProps<typeof XStack> & {
|
||||
data: SourceReference;
|
||||
data: SourceReference;
|
||||
};
|
||||
|
||||
export function SourceReferencePill(props: SourceReferencePillProps) {
|
||||
const { data, ...rest } = props;
|
||||
const { data, ...rest } = props;
|
||||
|
||||
return (
|
||||
<Link href={`/(authed)/(tabs)/sources/${data.name}`}>
|
||||
<XStack alignItems="center" gap="$2" justifyContent="flex-start" {...rest}>
|
||||
<Avatar circular size="$1">
|
||||
<Avatar.Image
|
||||
accessibilityLabel={data.name}
|
||||
objectFit="contain"
|
||||
backgroundColor="white"
|
||||
source={{
|
||||
uri: data.image,
|
||||
cache: "force-cache",
|
||||
}}
|
||||
/>
|
||||
<Avatar.Fallback backgroundColor="$gray10" />
|
||||
</Avatar>
|
||||
<Text size="$2" fontWeight="bold">
|
||||
{data.displayName ?? data.name}
|
||||
</Text>
|
||||
</XStack>
|
||||
</Link>
|
||||
);
|
||||
return (
|
||||
<Link href={`/(authed)/(tabs)/sources/${data.name}`}>
|
||||
<XStack alignItems="center" gap="$2" justifyContent="flex-start" {...rest}>
|
||||
<Avatar circular size="$1">
|
||||
<Avatar.Image
|
||||
accessibilityLabel={data.name}
|
||||
backgroundColor="white"
|
||||
objectFit="contain"
|
||||
source={{
|
||||
cache: "force-cache",
|
||||
uri: data.image,
|
||||
}}
|
||||
/>
|
||||
<Avatar.Fallback backgroundColor="$gray10" />
|
||||
</Avatar>
|
||||
<Text fontWeight="bold" size="$2">
|
||||
{data.displayName ?? data.name}
|
||||
</Text>
|
||||
</XStack>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback } from "react";
|
||||
import { useCallback } from "react";
|
||||
|
||||
import ContentLoader, { Circle, Rect } from "react-content-loader/native";
|
||||
import { FlatList } from "react-native";
|
||||
@@ -9,74 +9,74 @@ import { SourceList } from "@/ui/components/content/source/SourceList";
|
||||
const data: number[] = new Array(5).fill(0);
|
||||
|
||||
type SourceSkeletonListProps = {
|
||||
horizontal?: boolean;
|
||||
horizontal?: boolean;
|
||||
};
|
||||
|
||||
const VerticalSkeleton = (props: any) => (
|
||||
<ContentLoader
|
||||
speed={1.5}
|
||||
backgroundColor="#D4D5D8"
|
||||
foregroundColor="white"
|
||||
height={70}
|
||||
animate={true}
|
||||
width="100%"
|
||||
{...props}
|
||||
>
|
||||
<Circle cx="25" cy="30" r="25" />
|
||||
<Rect x="70" y="10" rx="4" ry="4" width="25%" height="10" />
|
||||
<Rect x="70" y="40" rx="4" ry="4" width="45%" height="10" />
|
||||
<Rect x="280" y="15" rx="4" ry="4" width="20%" height="30" />
|
||||
</ContentLoader>
|
||||
<ContentLoader
|
||||
animate={true}
|
||||
backgroundColor="#D4D5D8"
|
||||
foregroundColor="white"
|
||||
height={70}
|
||||
speed={1.5}
|
||||
width="100%"
|
||||
{...props}
|
||||
>
|
||||
<Circle cx="25" cy="30" r="25" />
|
||||
<Rect height="10" rx="4" ry="4" width="25%" x="70" y="10" />
|
||||
<Rect height="10" rx="4" ry="4" width="45%" x="70" y="40" />
|
||||
<Rect height="30" rx="4" ry="4" width="20%" x="280" y="15" />
|
||||
</ContentLoader>
|
||||
);
|
||||
|
||||
const HorizontalSkeleton = (props: any) => (
|
||||
<ContentLoader
|
||||
speed={1.5}
|
||||
backgroundColor="#D4D5D8"
|
||||
foregroundColor="white"
|
||||
height={180}
|
||||
animate={true}
|
||||
width={110}
|
||||
{...props}
|
||||
>
|
||||
<Circle cx="60" cy="40" r="33" />
|
||||
<Rect x="10" y="85" rx="4" ry="4" width="100" height="10" />
|
||||
<Rect x="25" y="105" rx="8" ry="8" width="70" height="25" />
|
||||
</ContentLoader>
|
||||
<ContentLoader
|
||||
animate={true}
|
||||
backgroundColor="#D4D5D8"
|
||||
foregroundColor="white"
|
||||
height={180}
|
||||
speed={1.5}
|
||||
width={110}
|
||||
{...props}
|
||||
>
|
||||
<Circle cx="60" cy="40" r="33" />
|
||||
<Rect height="10" rx="4" ry="4" width="100" x="10" y="85" />
|
||||
<Rect height="25" rx="8" ry="8" width="70" x="25" y="105" />
|
||||
</ContentLoader>
|
||||
);
|
||||
|
||||
const keyExtractor = (_: number, index: number) => index.toString();
|
||||
|
||||
const selectSkeletonComponent = (horizontal: boolean) => {
|
||||
return horizontal ? (
|
||||
<HorizontalSkeleton />
|
||||
) : (
|
||||
<YStack width="100%">
|
||||
<VerticalSkeleton />
|
||||
</YStack>
|
||||
);
|
||||
return horizontal ? (
|
||||
<HorizontalSkeleton />
|
||||
) : (
|
||||
<YStack width="100%">
|
||||
<VerticalSkeleton />
|
||||
</YStack>
|
||||
);
|
||||
};
|
||||
|
||||
export const SourceSkeletonList = (props: SourceSkeletonListProps) => {
|
||||
const { horizontal = false } = props;
|
||||
const { horizontal = false } = props;
|
||||
|
||||
const ItemSeparator = horizontal ? SourceList.HorizontalSeparator : SourceList.VerticalSeparator;
|
||||
const ItemSeparator = horizontal ? SourceList.HorizontalSeparator : SourceList.VerticalSeparator;
|
||||
|
||||
const renderItem = useCallback(() => {
|
||||
return selectSkeletonComponent(horizontal);
|
||||
}, [horizontal]);
|
||||
const renderItem = useCallback(() => {
|
||||
return selectSkeletonComponent(horizontal);
|
||||
}, [horizontal]);
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
data={data}
|
||||
scrollEnabled={false}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={keyExtractor}
|
||||
ItemSeparatorComponent={ItemSeparator}
|
||||
horizontal={horizontal}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={{ paddingBottom: 0 }}
|
||||
removeClippedSubviews={true}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<FlatList
|
||||
contentContainerStyle={{ paddingBottom: 0 }}
|
||||
data={data}
|
||||
horizontal={horizontal}
|
||||
ItemSeparatorComponent={ItemSeparator}
|
||||
keyExtractor={keyExtractor}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={renderItem}
|
||||
scrollEnabled={false}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export { SourceList } from "@/ui/components/content/source/SourceList";
|
||||
export { SourceFollowButton } from "@/ui/components/content/source/SourceFollowButton";
|
||||
export { SourceList } from "@/ui/components/content/source/SourceList";
|
||||
export { SourceOverviewCard } from "@/ui/components/content/source/SourceOverviewCard";
|
||||
export { SourceProfileImage } from "@/ui/components/content/source/SourceProfileImage";
|
||||
export { SourceReferencePill } from "@/ui/components/content/source/SourceReferencePill";
|
||||
export { SourceSkeletonList } from "@/ui/components/content/source/SourceSkeleton";
|
||||
export { SourceProfileImage } from "@/ui/components/content/source/SourceProfileImage";
|
||||
export { SourceOverviewCard } from "@/ui/components/content/source/SourceOverviewCard";
|
||||
|
||||
@@ -2,24 +2,24 @@ import { ArrowLeft } from "@tamagui/lucide-icons";
|
||||
import { Button, ButtonProps } from "tamagui";
|
||||
|
||||
type BackButtonProps = {
|
||||
onPress: () => void;
|
||||
onPress: () => void;
|
||||
};
|
||||
|
||||
export const BackButton = (props: BackButtonProps & ButtonProps) => {
|
||||
const { onPress, ...rest } = props;
|
||||
const { onPress, ...rest } = props;
|
||||
|
||||
return (
|
||||
<Button
|
||||
chromeless
|
||||
alignSelf="flex-start"
|
||||
size="$4"
|
||||
width="$4"
|
||||
height="$4"
|
||||
borderRadius="$12"
|
||||
// backgroundColor="$gray6"
|
||||
icon={<ArrowLeft size="$1" />}
|
||||
onPress={onPress}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Button
|
||||
alignSelf="flex-start"
|
||||
borderRadius="$12"
|
||||
chromeless
|
||||
height="$4"
|
||||
icon={<ArrowLeft size="$1" />}
|
||||
onPress={onPress}
|
||||
// backgroundColor="$gray6"
|
||||
size="$4"
|
||||
width="$4"
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { Button, ButtonProps } from "tamagui";
|
||||
|
||||
type IconButtonProps = {
|
||||
onPress: () => void;
|
||||
onPress: () => void;
|
||||
};
|
||||
|
||||
export const IconButton = (props: IconButtonProps & ButtonProps) => {
|
||||
const { onPress, ...rest } = props;
|
||||
const { onPress, ...rest } = props;
|
||||
|
||||
return (
|
||||
<Button
|
||||
chromeless
|
||||
alignSelf="flex-start"
|
||||
size="$4"
|
||||
width="$4"
|
||||
height="$4"
|
||||
borderRadius="$12"
|
||||
onPress={onPress}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Button
|
||||
alignSelf="flex-start"
|
||||
borderRadius="$12"
|
||||
chromeless
|
||||
height="$4"
|
||||
onPress={onPress}
|
||||
size="$4"
|
||||
width="$4"
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,27 +2,27 @@ import { ActivityIndicator } from "react-native";
|
||||
import { Button, GetProps, styled } from "tamagui";
|
||||
|
||||
const StyledButton = styled(Button, {
|
||||
fontWeight: "bold",
|
||||
width: "100%",
|
||||
fontWeight: "bold",
|
||||
width: "100%",
|
||||
});
|
||||
|
||||
type SubmitButtonProps = GetProps<typeof StyledButton> & {
|
||||
label: string;
|
||||
isValid: boolean;
|
||||
isPending: boolean;
|
||||
label: string;
|
||||
isValid: boolean;
|
||||
isPending: boolean;
|
||||
};
|
||||
|
||||
export const SubmitButton = (props: SubmitButtonProps) => {
|
||||
const { isValid, isPending, label, ...rest } = props;
|
||||
const { isValid, isPending, label, ...rest } = props;
|
||||
|
||||
return (
|
||||
<StyledButton
|
||||
disabled={isPending}
|
||||
theme={!isValid || isPending ? "disabled" : "accent"}
|
||||
fontWeight="bold"
|
||||
{...rest}
|
||||
>
|
||||
{isPending ? <ActivityIndicator /> : label}
|
||||
</StyledButton>
|
||||
);
|
||||
return (
|
||||
<StyledButton
|
||||
disabled={isPending}
|
||||
fontWeight="bold"
|
||||
theme={!isValid || isPending ? "disabled" : "accent"}
|
||||
{...rest}
|
||||
>
|
||||
{isPending ? <ActivityIndicator /> : label}
|
||||
</StyledButton>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,22 +4,22 @@ import { Input, InputProps } from "@/ui/components/controls/forms/Input";
|
||||
import { withController } from "@/ui/components/controls/forms/withController";
|
||||
|
||||
export const EmailInput = (props: InputProps) => {
|
||||
const { label = "Email", caption, error, onChangeText, ...rest } = props;
|
||||
const { label = "Email", caption, error, onChangeText, ...rest } = props;
|
||||
|
||||
return (
|
||||
<Input
|
||||
label={label}
|
||||
caption={caption}
|
||||
error={error}
|
||||
leadingAdornment={Mail}
|
||||
onChangeText={onChangeText}
|
||||
keyboardType="email-address"
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
placeholder="votre@email.com..."
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Input
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
caption={caption}
|
||||
error={error}
|
||||
keyboardType="email-address"
|
||||
label={label}
|
||||
leadingAdornment={Mail}
|
||||
onChangeText={onChangeText}
|
||||
placeholder="votre@email.com..."
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const FormEmailInput = withController<InputProps>(EmailInput);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user