Compare commits
No commits in common. "8a74d3e2c9d56e97ca4473e1d7252fd6c5347ec5" and "681440eb4ddbae50f32a7b2306eb3b4fb7a7ca97" have entirely different histories.
8a74d3e2c9
...
681440eb4d
50 changed files with 5282 additions and 19136 deletions
|
|
@ -5,3 +5,4 @@
|
||||||
```
|
```
|
||||||
npx pocketbase-typegen --db ./pb_data/data.db --out app/src/lib/pocketbase-types.ts
|
npx pocketbase-typegen --db ./pb_data/data.db --out app/src/lib/pocketbase-types.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
13
app/.eslintignore
Normal file
13
app/.eslintignore
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Ignore files for PNPM, NPM and YARN
|
||||||
|
pnpm-lock.yaml
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
31
app/.eslintrc.cjs
Normal file
31
app/.eslintrc.cjs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
/** @type { import("eslint").Linter.FlatConfig } */
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:svelte/recommended',
|
||||||
|
'prettier'
|
||||||
|
],
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: ['@typescript-eslint'],
|
||||||
|
parserOptions: {
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
extraFileExtensions: ['.svelte']
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2017: true,
|
||||||
|
node: true
|
||||||
|
},
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['*.svelte'],
|
||||||
|
parser: 'svelte-eslint-parser',
|
||||||
|
parserOptions: {
|
||||||
|
parser: '@typescript-eslint/parser'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
43
app/.gitignore
vendored
43
app/.gitignore
vendored
|
|
@ -1,35 +1,10 @@
|
||||||
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
# Expo
|
|
||||||
.expo/
|
|
||||||
dist/
|
|
||||||
web-build/
|
|
||||||
|
|
||||||
# Native
|
|
||||||
*.orig.*
|
|
||||||
*.jks
|
|
||||||
*.p8
|
|
||||||
*.p12
|
|
||||||
*.key
|
|
||||||
*.mobileprovision
|
|
||||||
|
|
||||||
# Metro
|
|
||||||
.metro-health-check*
|
|
||||||
|
|
||||||
# debug
|
|
||||||
npm-debug.*
|
|
||||||
yarn-debug.*
|
|
||||||
yarn-error.*
|
|
||||||
|
|
||||||
# macOS
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.pem
|
node_modules
|
||||||
|
/build
|
||||||
# local env files
|
/.svelte-kit
|
||||||
.env*.local
|
/package
|
||||||
|
.env
|
||||||
# typescript
|
.env.*
|
||||||
*.tsbuildinfo
|
!.env.example
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
||||||
|
|
|
||||||
1
app/.npmrc
Normal file
1
app/.npmrc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
engine-strict=true
|
||||||
13
app/.prettierignore
Normal file
13
app/.prettierignore
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Ignore files for PNPM, NPM and YARN
|
||||||
|
pnpm-lock.yaml
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
8
app/.prettierrc
Normal file
8
app/.prettierrc
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 100,
|
||||||
|
"plugins": ["prettier-plugin-svelte"],
|
||||||
|
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||||
|
}
|
||||||
51
app/App.tsx
51
app/App.tsx
|
|
@ -1,51 +0,0 @@
|
||||||
import 'react-native-gesture-handler';
|
|
||||||
import React from "react";
|
|
||||||
import { StyleSheet, useWindowDimensions } from "react-native";
|
|
||||||
import { NavigationContainer } from "@react-navigation/native";
|
|
||||||
import { PaperProvider, Text } from "react-native-paper";
|
|
||||||
import { BottomNavigation } from "./src/BottomNavigation";
|
|
||||||
import { DrawerNavigation } from "./src/DrawerNavigation";
|
|
||||||
|
|
||||||
const linking = {
|
|
||||||
prefixes: [
|
|
||||||
/* your linking prefixes */
|
|
||||||
"musclecat://",
|
|
||||||
"https://musclecat.pi.korz.tech",
|
|
||||||
"https://musclecat.pi4.korz.tech",
|
|
||||||
],
|
|
||||||
config: {
|
|
||||||
/* configuration for matching screens with paths */
|
|
||||||
screens: {
|
|
||||||
Home: {
|
|
||||||
screens: {
|
|
||||||
List: "lists/:listId?",
|
|
||||||
Task: "tasks/:taskId"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Notifications: "notifications",
|
|
||||||
Profile: "profile",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function App() {
|
|
||||||
const dimensions = useWindowDimensions();
|
|
||||||
//const NavigationComponent = dimensions.width < 700 ? BottomNavigation : DrawerNavigation;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NavigationContainer linking={linking} fallback={<Text>Loading...</Text>}>
|
|
||||||
<PaperProvider>
|
|
||||||
<BottomNavigation />
|
|
||||||
</PaperProvider>
|
|
||||||
</NavigationContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: "#fff",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -1,18 +1,38 @@
|
||||||
# Musclecat app
|
# create-svelte
|
||||||
|
|
||||||
Routes:
|
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
|
||||||
|
|
||||||
- `/`: show all root-level lists
|
## Creating a project
|
||||||
- `/list/<id>`: show all direct sublists and tasks of list with `<id>`
|
|
||||||
- `/task/<id>`: show task info for `<id>`
|
|
||||||
- `/history`: show all completed tasks
|
|
||||||
- `/profile`: show profile information and settings
|
|
||||||
|
|
||||||
on List view (including root):
|
If you're seeing this, you've probably already done this step. Congrats!
|
||||||
|
|
||||||
- Show action button for picking a random available task
|
```bash
|
||||||
- Gray-out scheduled tasks that are not yet available
|
# create a new project in the current directory
|
||||||
|
npm create svelte@latest
|
||||||
|
|
||||||
on Task view:
|
# create a new project in my-app
|
||||||
|
npm create svelte@latest my-app
|
||||||
|
```
|
||||||
|
|
||||||
- Show action button for starting task, if scheduled
|
## Developing
|
||||||
|
|
||||||
|
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# or start the server and open the app in a new browser tab
|
||||||
|
npm run dev -- --open
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
To create a production version of your app:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
You can preview the production build with `npm run preview`.
|
||||||
|
|
||||||
|
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
||||||
|
|
|
||||||
30
app/app.json
30
app/app.json
|
|
@ -1,30 +0,0 @@
|
||||||
{
|
|
||||||
"expo": {
|
|
||||||
"name": "app",
|
|
||||||
"slug": "app",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"orientation": "portrait",
|
|
||||||
"icon": "./assets/icon.png",
|
|
||||||
"userInterfaceStyle": "light",
|
|
||||||
"splash": {
|
|
||||||
"image": "./assets/splash.png",
|
|
||||||
"resizeMode": "contain",
|
|
||||||
"backgroundColor": "#ffffff"
|
|
||||||
},
|
|
||||||
"assetBundlePatterns": [
|
|
||||||
"**/*"
|
|
||||||
],
|
|
||||||
"ios": {
|
|
||||||
"supportsTablet": true
|
|
||||||
},
|
|
||||||
"android": {
|
|
||||||
"adaptiveIcon": {
|
|
||||||
"foregroundImage": "./assets/adaptive-icon.png",
|
|
||||||
"backgroundColor": "#ffffff"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"web": {
|
|
||||||
"favicon": "./assets/favicon.png"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 46 KiB |
|
|
@ -1,10 +0,0 @@
|
||||||
module.exports = function(api) {
|
|
||||||
api.cache(true);
|
|
||||||
return {
|
|
||||||
presets: ['babel-preset-expo'],
|
|
||||||
plugins: [
|
|
||||||
'@babel/plugin-proposal-export-namespace-from',
|
|
||||||
'react-native-reanimated/plugin',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
22
app/capacitor.config.ts
Normal file
22
app/capacitor.config.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
import { CapacitorConfig } from '@capacitor/cli';
|
||||||
|
|
||||||
|
const appId = 'dev.korz.musclecat';
|
||||||
|
const appName = 'Musclecat';
|
||||||
|
const server = process.argv.includes('-hmr') ? {
|
||||||
|
'url': 'http://192.168.178.25:5173', // always have http:// in url
|
||||||
|
'cleartext': true
|
||||||
|
} : {};
|
||||||
|
const webDir = 'build';
|
||||||
|
|
||||||
|
const config: CapacitorConfig = {
|
||||||
|
appId,
|
||||||
|
appName,
|
||||||
|
webDir,
|
||||||
|
server
|
||||||
|
};
|
||||||
|
|
||||||
|
if (process.argv.includes('-hmr')) console.log('WARNING: running capacitor with livereload config', config);
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
|
||||||
19566
app/package-lock.json
generated
19566
app/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,41 +1,42 @@
|
||||||
{
|
{
|
||||||
"name": "app",
|
"name": "app",
|
||||||
"version": "1.0.0",
|
"version": "0.0.1",
|
||||||
"main": "node_modules/expo/AppEntry.js",
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "expo start",
|
"dev": "vite dev --host",
|
||||||
"android": "expo start --android",
|
"build": "vite build",
|
||||||
"ios": "expo start --ios",
|
"preview": "vite preview",
|
||||||
"web": "expo start --web"
|
"test": "npm run test:integration && npm run test:unit",
|
||||||
},
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
"dependencies": {
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
"@babel/plugin-proposal-export-namespace-from": "^7.18.9",
|
"lint": "prettier --check . && eslint .",
|
||||||
"@expo/webpack-config": "^19.0.0",
|
"format": "prettier --write .",
|
||||||
"@react-native-masked-view/masked-view": "0.2.9",
|
"test:integration": "playwright test",
|
||||||
"@react-navigation/drawer": "^6.6.6",
|
"test:unit": "vitest"
|
||||||
"@react-navigation/native": "^6.1.9",
|
|
||||||
"@react-navigation/stack": "^6.3.20",
|
|
||||||
"expo": "~49.0.15",
|
|
||||||
"expo-screen-orientation": "~6.0.6",
|
|
||||||
"expo-status-bar": "~1.6.0",
|
|
||||||
"pocketbase": "^0.20.1",
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0",
|
|
||||||
"react-native": "0.72.6",
|
|
||||||
"react-native-gesture-handler": "~2.12.0",
|
|
||||||
"react-native-paper": "^5.11.5",
|
|
||||||
"react-native-reanimated": "~3.3.0",
|
|
||||||
"react-native-render-html": "^6.3.4",
|
|
||||||
"react-native-safe-area-context": "4.6.3",
|
|
||||||
"react-native-screens": "~3.22.0",
|
|
||||||
"react-native-web": "~0.19.6",
|
|
||||||
"react-responsive": "^9.0.2",
|
|
||||||
"swr": "^2.2.4"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.20.0",
|
"@capacitor/cli": "^5.5.1",
|
||||||
"@types/react": "~18.2.14",
|
"@playwright/test": "^1.28.1",
|
||||||
"typescript": "^5.1.3"
|
"@sveltejs/adapter-static": "^2.0.3",
|
||||||
|
"@sveltejs/kit": "^1.27.4",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||||
|
"@typescript-eslint/parser": "^6.0.0",
|
||||||
|
"eslint": "^8.28.0",
|
||||||
|
"eslint-config-prettier": "^9.0.0",
|
||||||
|
"eslint-plugin-svelte": "^2.30.0",
|
||||||
|
"prettier": "^3.0.0",
|
||||||
|
"prettier-plugin-svelte": "^3.0.0",
|
||||||
|
"svelte": "^4.2.7",
|
||||||
|
"svelte-check": "^3.6.0",
|
||||||
|
"svelte-preprocess": "^5.1.1",
|
||||||
|
"tslib": "^2.4.1",
|
||||||
|
"typescript": "^5.0.0",
|
||||||
|
"vite": "^4.5.1",
|
||||||
|
"vitest": "^0.32.2"
|
||||||
},
|
},
|
||||||
"private": true
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@capacitor/core": "^5.5.1",
|
||||||
|
"pocketbase": "^0.19.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
app/playwright.config.ts
Normal file
12
app/playwright.config.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import type { PlaywrightTestConfig } from '@playwright/test';
|
||||||
|
|
||||||
|
const config: PlaywrightTestConfig = {
|
||||||
|
webServer: {
|
||||||
|
command: 'npm run build && npm run preview',
|
||||||
|
port: 4173
|
||||||
|
},
|
||||||
|
testDir: 'tests',
|
||||||
|
testMatch: /(.+\.)?(test|spec)\.[jt]s/
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
import { createMaterialBottomTabNavigator } from "react-native-paper/react-navigation";
|
|
||||||
import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
|
|
||||||
import { ScreenList, Notifications, Profile } from "./screens";
|
|
||||||
|
|
||||||
const Tab = createMaterialBottomTabNavigator();
|
|
||||||
|
|
||||||
export function BottomNavigation() {
|
|
||||||
return (
|
|
||||||
<Tab.Navigator initialRouteName="Home">
|
|
||||||
<Tab.Screen
|
|
||||||
name="Home"
|
|
||||||
component={ScreenList}
|
|
||||||
options={{
|
|
||||||
tabBarLabel: "Home",
|
|
||||||
tabBarIcon: ({ color }) => (
|
|
||||||
<MaterialCommunityIcons name="home" color={color} size={26} />
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Tab.Screen
|
|
||||||
name="Notifications"
|
|
||||||
component={Notifications}
|
|
||||||
options={{
|
|
||||||
tabBarLabel: "History",
|
|
||||||
tabBarIcon: ({ color }) => (
|
|
||||||
<MaterialCommunityIcons name="history" color={color} size={26} />
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Tab.Screen
|
|
||||||
name="Profile"
|
|
||||||
component={Profile}
|
|
||||||
options={{
|
|
||||||
tabBarLabel: "Profile",
|
|
||||||
tabBarIcon: ({ color }) => (
|
|
||||||
<MaterialCommunityIcons name="account" color={color} size={26} />
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Tab.Navigator>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import * as React from "react";
|
|
||||||
import { createDrawerNavigator } from "@react-navigation/drawer";
|
|
||||||
import { ScreenList, Notifications, Profile } from "./screens";
|
|
||||||
|
|
||||||
const Drawer = createDrawerNavigator();
|
|
||||||
|
|
||||||
export function DrawerNavigation() {
|
|
||||||
return (
|
|
||||||
<Drawer.Navigator
|
|
||||||
initialRouteName="Feed"
|
|
||||||
screenOptions={{
|
|
||||||
drawerType: "permanent",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Drawer.Screen name="Feed" component={ScreenList} />
|
|
||||||
<Drawer.Screen name="Notifications" component={Notifications} />
|
|
||||||
<Drawer.Screen name="Profile" component={Profile} />
|
|
||||||
</Drawer.Navigator>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
import PocketBase from "pocketbase";
|
|
||||||
import type { TypedPocketBase } from "./pocketbase-types";
|
|
||||||
|
|
||||||
export const pb = new PocketBase("http://127.0.0.1:8090") as TypedPocketBase;
|
|
||||||
12
app/src/app.d.ts
vendored
Normal file
12
app/src/app.d.ts
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
// See https://kit.svelte.dev/docs/types#app
|
||||||
|
// for information about these interfaces
|
||||||
|
declare global {
|
||||||
|
namespace App {
|
||||||
|
// interface Error {}
|
||||||
|
// interface Locals {}
|
||||||
|
// interface PageData {}
|
||||||
|
// interface Platform {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
||||||
16
app/src/app.html
Normal file
16
app/src/app.html
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Segoe+UI:wght@400;600&display=swap"
|
||||||
|
/>
|
||||||
|
%sveltekit.head%
|
||||||
|
</head>
|
||||||
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
7
app/src/index.test.ts
Normal file
7
app/src/index.test.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
|
||||||
|
describe('sum test', () => {
|
||||||
|
it('adds 1 + 2 to equal 3', () => {
|
||||||
|
expect(1 + 2).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
4
app/src/lib/api.ts
Normal file
4
app/src/lib/api.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
import PocketBase from "pocketbase";
|
||||||
|
import type { TypedPocketBase } from "./pocketbase-types"
|
||||||
|
|
||||||
|
export const pb = new PocketBase('http://127.0.0.1:8090') as TypedPocketBase
|
||||||
1
app/src/lib/index.ts
Normal file
1
app/src/lib/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
// place files you want to import through the `$lib` alias in this folder.
|
||||||
46
app/src/routes/+layout.svelte
Normal file
46
app/src/routes/+layout.svelte
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { pb } from '$lib/api';
|
||||||
|
import '../theme/global.css';
|
||||||
|
/* Theme variables */
|
||||||
|
import '../theme/variables.css';
|
||||||
|
|
||||||
|
async function goToRandomTask() {
|
||||||
|
const task = await pb
|
||||||
|
.collection('tasks')
|
||||||
|
.getFirstListItem('', { sort: '@random', fields: 'id' });
|
||||||
|
goto(`/tasks/${task.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let listsLoading = pb.collection("lists").getList(1, 50);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="topbar">
|
||||||
|
<div class="app-name">
|
||||||
|
<img width="30" height="30" alt="Musclecat logo" src="/logo.webp" />
|
||||||
|
<h1>Musceclat</h1>
|
||||||
|
</div>
|
||||||
|
<div class="search">
|
||||||
|
<input type="search" placeholder="Search tasks" />
|
||||||
|
<button on:click={goToRandomTask}>Zufällig</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="sidebar">
|
||||||
|
<div class="sidebar-heading">Lists</div>
|
||||||
|
{#await listsLoading}
|
||||||
|
<div class="list-name selected">Loading...</div>
|
||||||
|
{:then lists}
|
||||||
|
{#each lists.items as list}
|
||||||
|
<div class="list-name">{list.name}</div>
|
||||||
|
{/each}
|
||||||
|
{/await}
|
||||||
|
<div class="progress-bar-container">
|
||||||
|
<div class="progress-bar">70%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
1
app/src/routes/+layout.ts
Normal file
1
app/src/routes/+layout.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export const ssr = false;
|
||||||
18
app/src/routes/+page.svelte
Normal file
18
app/src/routes/+page.svelte
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
|
export let data: PageData;
|
||||||
|
|
||||||
|
let { tasks } = data;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h2>Haushalt</h2>
|
||||||
|
<ul class="task-list">
|
||||||
|
{#each tasks.items as task}
|
||||||
|
<li>
|
||||||
|
{task.name}
|
||||||
|
<span>{task.expand?.list.name}</span>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
<button>Neue Aufgabe</button>
|
||||||
30
app/src/routes/+page.ts
Normal file
30
app/src/routes/+page.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { pb } from '$lib/api';
|
||||||
|
import { ClientResponseError, type ListResult } from 'pocketbase';
|
||||||
|
import type { PageLoad } from './$types';
|
||||||
|
import { error } from '@sveltejs/kit';
|
||||||
|
import type { IconsResponse, ListsResponse, TasksResponse } from '$lib/pocketbase-types';
|
||||||
|
|
||||||
|
interface Expand {
|
||||||
|
icon?: IconsResponse;
|
||||||
|
list: ListsResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const load: PageLoad = async function () {
|
||||||
|
try {
|
||||||
|
const tasks: ListResult<TasksResponse<Expand>> = await pb.collection('tasks').getList(1, 50, {
|
||||||
|
expand: "icon,list"
|
||||||
|
});
|
||||||
|
/*const icon = task.expand?.icon;
|
||||||
|
const iconUrl =
|
||||||
|
icon &&
|
||||||
|
pb.files.getUrl(icon, icon.image, {
|
||||||
|
thumb: '100x100'
|
||||||
|
});*/
|
||||||
|
return { tasks };
|
||||||
|
} catch (ex) {
|
||||||
|
if (ex instanceof ClientResponseError) {
|
||||||
|
throw error(ex.status, ex.response.message);
|
||||||
|
}
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
};
|
||||||
51
app/src/routes/lists/+page.svelte
Normal file
51
app/src/routes/lists/+page.svelte
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { ListsResponse } from '$lib/pocketbase-types';
|
||||||
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
|
export let data: PageData;
|
||||||
|
|
||||||
|
const rootLists = data.lists.filter((l) => !l.parent);
|
||||||
|
const children = data.lists.reduce((acc, l) => {
|
||||||
|
if (acc.has(l.parent)) {
|
||||||
|
acc.get(l.parent)?.push(l);
|
||||||
|
} else {
|
||||||
|
acc.set(l.parent, [l]);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, new Map<string, ListsResponse[]>());
|
||||||
|
|
||||||
|
// TODO: Replace with navigation logic, such as using a Svelte store or a Router
|
||||||
|
const goToTasks = (taskId: string) => {
|
||||||
|
console.log(`Redirecting to tasks for list ${taskId}`);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Task Lists</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<ion-list>
|
||||||
|
{#each rootLists as list}
|
||||||
|
<ion-item lines="full">
|
||||||
|
<ion-label>
|
||||||
|
<h2>{list.name}</h2>
|
||||||
|
<ion-list>
|
||||||
|
{#each children.get(list.id) as childList}
|
||||||
|
<ion-item lines="full">
|
||||||
|
<ion-label>
|
||||||
|
<h2>{childList.name}</h2>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
{/each}
|
||||||
|
</ion-list>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
{/each}
|
||||||
|
</ion-list>
|
||||||
|
</ion-content>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Additional styles if necessary */
|
||||||
|
</style>
|
||||||
7
app/src/routes/lists/+page.ts
Normal file
7
app/src/routes/lists/+page.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { pb } from '$lib/api';
|
||||||
|
import type { PageLoad } from './$types';
|
||||||
|
|
||||||
|
export const load: PageLoad = async function () {
|
||||||
|
const lists = await pb.collection('lists').getFullList();
|
||||||
|
return { lists };
|
||||||
|
};
|
||||||
93
app/src/routes/login/+page.svelte
Normal file
93
app/src/routes/login/+page.svelte
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { pb } from '$lib/api';
|
||||||
|
|
||||||
|
let email = '';
|
||||||
|
let password = '';
|
||||||
|
|
||||||
|
const handleLogin = async (event: Event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
email = email.trim();
|
||||||
|
password = password.trim();
|
||||||
|
|
||||||
|
if (email && password) {
|
||||||
|
const resp = await pb.collection('users').authWithPassword(email, password);
|
||||||
|
console.log('Logged in:', resp);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form class="login-container" on:submit={handleLogin}>
|
||||||
|
<h1>Musceclat Login</h1>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">E-Mail:</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
placeholder="Enter your email"
|
||||||
|
required
|
||||||
|
bind:value={email}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Password:</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
placeholder="Enter your password"
|
||||||
|
required
|
||||||
|
bind:value={password}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button type="submit">Login</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.login-container {
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
.login-container h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
color: #202020;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
color: #606060;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.form-group input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background-color: #0078d4;
|
||||||
|
color: white;
|
||||||
|
padding: 15px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
width: 100%;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background-color: #005ea6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
9
app/src/routes/tasks/[id]/+page.svelte
Normal file
9
app/src/routes/tasks/[id]/+page.svelte
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
|
export let data: PageData;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h2>Task: {data.task.name}</h2>
|
||||||
|
<img width="100" height="100" alt="icon" src={data.iconUrl} />
|
||||||
|
{@html data.task.description}
|
||||||
27
app/src/routes/tasks/[id]/+page.ts
Normal file
27
app/src/routes/tasks/[id]/+page.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { pb } from '$lib/api';
|
||||||
|
import { ClientResponseError } from 'pocketbase';
|
||||||
|
import type { PageLoad } from './$types';
|
||||||
|
import { error } from '@sveltejs/kit';
|
||||||
|
import type { IconsResponse, TasksResponse } from '$lib/pocketbase-types';
|
||||||
|
|
||||||
|
interface Expand {
|
||||||
|
icon?: IconsResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const load: PageLoad = async function ({ params: { id } }) {
|
||||||
|
try {
|
||||||
|
const task: TasksResponse<Expand> = await pb.collection('tasks').getOne(id, { expand: 'icon' });
|
||||||
|
const icon = task.expand?.icon;
|
||||||
|
const iconUrl =
|
||||||
|
icon &&
|
||||||
|
pb.files.getUrl(icon, icon.image, {
|
||||||
|
thumb: '100x100'
|
||||||
|
});
|
||||||
|
return { task, iconUrl };
|
||||||
|
} catch (ex) {
|
||||||
|
if (ex instanceof ClientResponseError) {
|
||||||
|
throw error(ex.status, ex.response.message);
|
||||||
|
}
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,262 +0,0 @@
|
||||||
import { StyleSheet, ScrollView, View } from "react-native";
|
|
||||||
import {
|
|
||||||
Appbar,
|
|
||||||
Text,
|
|
||||||
Avatar,
|
|
||||||
IconButton,
|
|
||||||
Card,
|
|
||||||
List,
|
|
||||||
useTheme,
|
|
||||||
} from "react-native-paper";
|
|
||||||
import { createStackNavigator } from "@react-navigation/stack";
|
|
||||||
import RenderHtml from "react-native-render-html";
|
|
||||||
import { HomeScreenNavigationProp } from "./types";
|
|
||||||
import useSWR from "swr";
|
|
||||||
import { pb } from "../api";
|
|
||||||
|
|
||||||
function ListItem(props: { name: string; onPress(): void }) {
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
style={{ marginHorizontal: 8, marginBottom: 8 }}
|
|
||||||
onPress={props.onPress}
|
|
||||||
>
|
|
||||||
<Card.Title
|
|
||||||
title={props.name}
|
|
||||||
subtitle="A list"
|
|
||||||
left={(props) => <Avatar.Icon {...props} icon="clipboard-list" />}
|
|
||||||
right={(props) => (
|
|
||||||
<IconButton {...props} icon="dots-vertical" onPress={() => {}} />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function TaskItem(props: { name: string; onPress(): void }) {
|
|
||||||
return (
|
|
||||||
<List.Item
|
|
||||||
title={props.name}
|
|
||||||
description="A task"
|
|
||||||
left={(props) => <List.Icon {...props} icon="clipboard-check-outline" />}
|
|
||||||
onPress={props.onPress}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ListResponse {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
parentId: string | null;
|
|
||||||
lists: Array<{
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}>;
|
|
||||||
tasks: Array<{
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function listFetcher(id: string): Promise<ListResponse> {
|
|
||||||
if (id === "all") {
|
|
||||||
const lists = await pb.collection("lists").getList(0, 50, {
|
|
||||||
filter: pb.filter("parent = null"),
|
|
||||||
});
|
|
||||||
const tasks = await pb.collection("tasks").getList(0, 50, {
|
|
||||||
filter: pb.filter("list = null"),
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
name: "Lists",
|
|
||||||
parentId: null,
|
|
||||||
lists: lists.items.map((l) => ({ id: l.id, name: l.name })),
|
|
||||||
tasks: tasks.items.map((l) => ({ id: l.id, name: l.name })),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const list = await pb.collection("lists").getOne(id);
|
|
||||||
const lists = await pb.collection("lists").getList(0, 50, {
|
|
||||||
filter: pb.filter("parent = {:id}", { id }),
|
|
||||||
});
|
|
||||||
const tasks = await pb.collection("tasks").getList(0, 50, {
|
|
||||||
filter: pb.filter("list = {:id}", { id }),
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
name: list.name,
|
|
||||||
parentId: list.parent,
|
|
||||||
lists: lists.items.map((l) => ({ id: l.id, name: l.name })),
|
|
||||||
tasks: tasks.items.map((l) => ({ id: l.id, name: l.name })),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function ListContent({ route, navigation }: HomeScreenNavigationProp<"List">) {
|
|
||||||
const theme = useTheme();
|
|
||||||
const { listId } = route.params ?? {};
|
|
||||||
|
|
||||||
const { isLoading, data, error } = useSWR(listId || "all", listFetcher);
|
|
||||||
if (isLoading) {
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={[styles.padded, { backgroundColor: theme.colors.background }]}
|
|
||||||
>
|
|
||||||
<Text>Loading...</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (error || !data) {
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={[styles.padded, { backgroundColor: theme.colors.background }]}
|
|
||||||
>
|
|
||||||
<Text>Something went wrong: {error.toString()}</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { name, parentId, lists, tasks } = data;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Appbar.Header elevated>
|
|
||||||
{Boolean(listId) && (
|
|
||||||
<Appbar.BackAction
|
|
||||||
onPress={() =>
|
|
||||||
navigation.canGoBack()
|
|
||||||
? navigation.pop()
|
|
||||||
: navigation.replace("List", { listId: parentId })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Appbar.Content title={name} />
|
|
||||||
<Appbar.Action icon="dice-multiple-outline" onPress={() => {}} />
|
|
||||||
<Appbar.Action icon="magnify" onPress={() => {}} />
|
|
||||||
</Appbar.Header>
|
|
||||||
<ScrollView
|
|
||||||
style={[styles.container, { backgroundColor: theme.colors.background }]}
|
|
||||||
>
|
|
||||||
{lists.map((l) => (
|
|
||||||
<ListItem
|
|
||||||
key={l.id}
|
|
||||||
name={l.name}
|
|
||||||
onPress={() => navigation.push("List", { listId: l.id })}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
{tasks.length > 0 && (
|
|
||||||
<List.Section>
|
|
||||||
<List.Subheader>Tasks</List.Subheader>
|
|
||||||
{tasks.map((t) => (
|
|
||||||
<TaskItem
|
|
||||||
key={t.id}
|
|
||||||
name={t.name}
|
|
||||||
onPress={() => navigation.push("Task", { taskId: t.id })}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</List.Section>
|
|
||||||
)}
|
|
||||||
</ScrollView>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TaskResponse {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
listId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function taskFetcher(id: string): Promise<TaskResponse> {
|
|
||||||
const task = await pb.collection("tasks").getOne(id);
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
name: task.name,
|
|
||||||
description: task.description,
|
|
||||||
listId: task.list,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function TaskContent({ route, navigation }: HomeScreenNavigationProp<"Task">) {
|
|
||||||
const theme = useTheme();
|
|
||||||
const { taskId } = route.params;
|
|
||||||
|
|
||||||
const { isLoading, data, error } = useSWR(taskId, taskFetcher);
|
|
||||||
if (isLoading) {
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={[styles.padded, { backgroundColor: theme.colors.background }]}
|
|
||||||
>
|
|
||||||
<Text>Loading...</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (error || !data) {
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={[styles.padded, { backgroundColor: theme.colors.background }]}
|
|
||||||
>
|
|
||||||
<Text>Something went wrong: {error.toString()}</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { name, description, listId } = data;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Appbar.Header elevated>
|
|
||||||
<Appbar.BackAction
|
|
||||||
onPress={() =>
|
|
||||||
navigation.canGoBack()
|
|
||||||
? navigation.pop()
|
|
||||||
: navigation.replace("List", { listId })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Appbar.Content title={name} />
|
|
||||||
</Appbar.Header>
|
|
||||||
<View
|
|
||||||
style={[styles.padded, { backgroundColor: theme.colors.background }]}
|
|
||||||
>
|
|
||||||
<Text variant="bodyLarge">
|
|
||||||
<RenderHtml source={{ html: description }} />
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Stack = createStackNavigator();
|
|
||||||
|
|
||||||
export function ScreenList() {
|
|
||||||
return (
|
|
||||||
<Stack.Navigator
|
|
||||||
initialRouteName="List"
|
|
||||||
screenOptions={{ headerShown: false }}
|
|
||||||
>
|
|
||||||
<Stack.Screen
|
|
||||||
name="List"
|
|
||||||
component={ListContent as any}
|
|
||||||
options={{ headerShown: false }}
|
|
||||||
/>
|
|
||||||
<Stack.Screen
|
|
||||||
name="Task"
|
|
||||||
component={TaskContent as any}
|
|
||||||
options={{ headerShown: false }}
|
|
||||||
/>
|
|
||||||
</Stack.Navigator>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
paddingVertical: 8,
|
|
||||||
//backgroundColor: "#fff",
|
|
||||||
/*alignItems: "center",
|
|
||||||
justifyContent: "center",*/
|
|
||||||
},
|
|
||||||
padded: {
|
|
||||||
padding: 16,
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import { StyleSheet, View } from "react-native";
|
|
||||||
import { Text } from "react-native-paper";
|
|
||||||
|
|
||||||
export function Notifications() {
|
|
||||||
return (
|
|
||||||
<View style={styles.container}>
|
|
||||||
<Text variant="headlineMedium">Updates!</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: "#fff",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
import { StyleSheet, View } from "react-native";
|
|
||||||
import { useNavigation } from "@react-navigation/native";
|
|
||||||
import { Text } from "react-native-paper";
|
|
||||||
import { Appbar } from "react-native-paper";
|
|
||||||
|
|
||||||
export function Profile() {
|
|
||||||
const navigation = useNavigation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Appbar.Header>
|
|
||||||
{navigation.canGoBack() && <Appbar.BackAction onPress={() => navigation.goBack()} />}
|
|
||||||
<Appbar.Content title="Title" />
|
|
||||||
<Appbar.Action icon="calendar" onPress={() => navigation.navigate("Profile")} />
|
|
||||||
<Appbar.Action icon="magnify" onPress={() => {}} />
|
|
||||||
</Appbar.Header>
|
|
||||||
<View style={styles.container}>
|
|
||||||
<Text variant="headlineMedium">Profile!</Text>
|
|
||||||
</View>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
/*flex: 1,
|
|
||||||
backgroundColor: "#fff",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",*/
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
export * from "./List";
|
|
||||||
export * from "./Notifications";
|
|
||||||
export * from "./Profile";
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
import type {
|
|
||||||
NavigatorScreenParams,
|
|
||||||
ParamListBase,
|
|
||||||
} from "@react-navigation/native";
|
|
||||||
import type { StackScreenProps } from "@react-navigation/stack";
|
|
||||||
|
|
||||||
export interface RootBottomParamList extends ParamListBase {
|
|
||||||
Home: NavigatorScreenParams<HomeStackParamList>;
|
|
||||||
Notifications: undefined;
|
|
||||||
Profile: undefined;
|
|
||||||
Task: { id: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
interface HomeStackParamList extends ParamListBase {
|
|
||||||
List: { listId: string | null };
|
|
||||||
Task: { taskId: string | null };
|
|
||||||
}
|
|
||||||
|
|
||||||
export type HomeScreenNavigationProp<
|
|
||||||
RouteName extends keyof HomeStackParamList = keyof HomeStackParamList,
|
|
||||||
> = StackScreenProps<HomeStackParamList, RouteName>;
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
namespace ReactNavigation {
|
|
||||||
interface RootParamList extends RootBottomParamList {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
178
app/src/theme/global.css
Normal file
178
app/src/theme/global.css
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: "Segoe UI", sans-serif;
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
color: #202020;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
height: calc(100vh - 20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
flex: 0 0 300px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
|
||||||
|
margin-right: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-heading {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #202020;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-name {
|
||||||
|
padding: 10px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-name:hover {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar-container {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #e1e1e1;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin: 20px 0;
|
||||||
|
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 20px;
|
||||||
|
background-color: #0078d4;
|
||||||
|
width: 70%;
|
||||||
|
/* Dynamic value here */
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: width 0.5s ease-in-out;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.5em;
|
||||||
|
color: #202020;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-logo {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
margin-right: 10px;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search input {
|
||||||
|
padding: 15px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 10px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search button {
|
||||||
|
background-color: #0078d4;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 15px;
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search button:hover {
|
||||||
|
background-color: #005ea6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #202020;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list li {
|
||||||
|
list-style: none;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
padding: 15px;
|
||||||
|
margin: 10px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list li:hover {
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list li span {
|
||||||
|
display: block;
|
||||||
|
color: #606060;
|
||||||
|
margin-top: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content button {
|
||||||
|
background-color: #0078d4;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 15px;
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content button:hover {
|
||||||
|
background-color: #005ea6;
|
||||||
|
}
|
||||||
BIN
app/static/favicon.png
Normal file
BIN
app/static/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
BIN
app/static/logo.webp
Normal file
BIN
app/static/logo.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 326 KiB |
18
app/svelte.config.js
Normal file
18
app/svelte.config.js
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import adapter from '@sveltejs/adapter-static';
|
||||||
|
import preprocess from 'svelte-preprocess';
|
||||||
|
|
||||||
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
|
const config = {
|
||||||
|
preprocess: preprocess(),
|
||||||
|
|
||||||
|
kit: {
|
||||||
|
adapter: adapter({
|
||||||
|
pages: 'build',
|
||||||
|
assets: 'build',
|
||||||
|
fallback: 'index.html',
|
||||||
|
precompress: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
6
app/tests/test.ts
Normal file
6
app/tests/test.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
|
test('index page has expected h1', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page.getByRole('heading', { name: 'Welcome to SvelteKit' })).toBeVisible();
|
||||||
|
});
|
||||||
|
|
@ -1,7 +1,18 @@
|
||||||
{
|
{
|
||||||
"extends": "expo/tsconfig.base",
|
"extends": "./.svelte-kit/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"strict": true
|
"allowJs": true,
|
||||||
},
|
"checkJs": true,
|
||||||
"include": ["src", "types"]
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"strict": true,
|
||||||
|
"moduleResolution": "bundler"
|
||||||
|
}
|
||||||
|
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||||
|
//
|
||||||
|
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||||
|
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||||
}
|
}
|
||||||
|
|
|
||||||
9
app/vite.config.ts
Normal file
9
app/vite.config.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [sveltekit()],
|
||||||
|
test: {
|
||||||
|
include: ['src/**/*.{test,spec}.{js,ts}']
|
||||||
|
}
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue