prepared folderstructure for monorepo with express.js
This commit is contained in:
43
apps/client/.gitignore
vendored
Normal file
43
apps/client/.gitignore
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# Expo
|
||||
.expo/
|
||||
dist/
|
||||
web-build/
|
||||
expo-env.d.ts
|
||||
|
||||
# Native
|
||||
.kotlin/
|
||||
*.orig.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
|
||||
# Metro
|
||||
.metro-health-check*
|
||||
|
||||
# debug
|
||||
npm-debug.*
|
||||
yarn-debug.*
|
||||
yarn-error.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
app-example
|
||||
|
||||
# generated native folders
|
||||
/ios
|
||||
/android
|
||||
50
apps/client/app.json
Normal file
50
apps/client/app.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"expo": {
|
||||
"jsEngine": "hermes",
|
||||
"name": "caldav",
|
||||
"slug": "caldav",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/images/icon.png",
|
||||
"scheme": "caldav",
|
||||
"userInterfaceStyle": "automatic",
|
||||
"newArchEnabled": true,
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"backgroundColor": "#E6F4FE",
|
||||
"foregroundImage": "./assets/images/android-icon-foreground.png",
|
||||
"backgroundImage": "./assets/images/android-icon-background.png",
|
||||
"monochromeImage": "./assets/images/android-icon-monochrome.png"
|
||||
},
|
||||
"edgeToEdgeEnabled": true,
|
||||
"predictiveBackGestureEnabled": false
|
||||
},
|
||||
"web": {
|
||||
"output": "static",
|
||||
"favicon": "./assets/images/favicon.png",
|
||||
"bundler": "metro"
|
||||
},
|
||||
"plugins": [
|
||||
"expo-router",
|
||||
[
|
||||
"expo-splash-screen",
|
||||
{
|
||||
"image": "./assets/images/splash-icon.png",
|
||||
"imageWidth": 200,
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff",
|
||||
"dark": {
|
||||
"backgroundColor": "#000000"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"experiments": {
|
||||
"typedRoutes": true,
|
||||
"reactCompiler": true
|
||||
}
|
||||
}
|
||||
}
|
||||
9
apps/client/babel.config.js
Normal file
9
apps/client/babel.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: [
|
||||
["babel-preset-expo", { jsxImportSource: "nativewind" }],
|
||||
"nativewind/babel",
|
||||
],
|
||||
};
|
||||
};
|
||||
10
apps/client/eslint.config.js
Normal file
10
apps/client/eslint.config.js
Normal file
@@ -0,0 +1,10 @@
|
||||
// https://docs.expo.dev/guides/using-eslint/
|
||||
const { defineConfig } = require('eslint/config');
|
||||
const expoConfig = require('eslint-config-expo/flat');
|
||||
|
||||
module.exports = defineConfig([
|
||||
expoConfig,
|
||||
{
|
||||
ignores: ['dist/*'],
|
||||
},
|
||||
]);
|
||||
3
apps/client/global.css
Normal file
3
apps/client/global.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
6
apps/client/metro.config.js
Normal file
6
apps/client/metro.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const { getDefaultConfig } = require("expo/metro-config");
|
||||
const { withNativeWind } = require('nativewind/metro');
|
||||
|
||||
const config = getDefaultConfig(__dirname)
|
||||
|
||||
module.exports = withNativeWind(config, { input: './global.css' })
|
||||
1
apps/client/nativewind-env.d.ts
vendored
Normal file
1
apps/client/nativewind-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="nativewind/types" />
|
||||
50
apps/client/package.json
Normal file
50
apps/client/package.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "@caldav/client",
|
||||
"main": "expo-router/entry",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"reset-project": "node ./scripts/reset-project.js",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"web": "expo start --web",
|
||||
"lint": "expo lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@caldav/shared": "*",
|
||||
"@expo/vector-icons": "^15.0.3",
|
||||
"@react-navigation/bottom-tabs": "^7.4.0",
|
||||
"@react-navigation/elements": "^2.6.3",
|
||||
"@react-navigation/native": "^7.1.8",
|
||||
"@shopify/flash-list": "^2.0.2",
|
||||
"expo": "~54.0.25",
|
||||
"expo-constants": "~18.0.10",
|
||||
"expo-font": "~14.0.9",
|
||||
"expo-haptics": "~15.0.7",
|
||||
"expo-image": "~3.0.10",
|
||||
"expo-linking": "~8.0.9",
|
||||
"expo-router": "~6.0.15",
|
||||
"expo-splash-screen": "~31.0.11",
|
||||
"expo-status-bar": "~3.0.8",
|
||||
"expo-symbols": "~1.0.7",
|
||||
"expo-system-ui": "~6.0.8",
|
||||
"expo-web-browser": "~15.0.9",
|
||||
"nativewind": "^4.2.1",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-native": "0.81.5",
|
||||
"react-native-gesture-handler": "~2.28.0",
|
||||
"react-native-reanimated": "~4.1.1",
|
||||
"react-native-safe-area-context": "5.6.0",
|
||||
"react-native-screens": "~4.16.0",
|
||||
"react-native-web": "~0.21.0",
|
||||
"react-native-worklets": "0.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "~19.1.0",
|
||||
"eslint-config-expo": "~10.0.0",
|
||||
"prettier-plugin-tailwindcss": "^0.5.11",
|
||||
"tailwindcss": "^3.4.17"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
28
apps/client/src/Constants.tsx
Normal file
28
apps/client/src/Constants.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
export const MONTHS = [
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
] as const;
|
||||
|
||||
export type Month = (typeof MONTHS)[number];
|
||||
|
||||
export const DAYS = [
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
"Wednesday",
|
||||
"Thursday",
|
||||
"Friday",
|
||||
"Saturday",
|
||||
"Sunday",
|
||||
] as const;
|
||||
|
||||
export type Day = (typeof DAYS)[number];
|
||||
20
apps/client/src/Themes.tsx
Normal file
20
apps/client/src/Themes.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
type Theme = {
|
||||
chatBot: string,
|
||||
primeFg: string,
|
||||
primeBg: string,
|
||||
messageBorderBg: string,
|
||||
placeholderBg: string,
|
||||
calenderBg: string,
|
||||
}
|
||||
|
||||
const defaultLight: Theme = {
|
||||
chatBot: "#DE6C20",
|
||||
primeFg: "#3B3329",
|
||||
primeBg: "#FFEEDE",
|
||||
messageBorderBg: "#FFFFFF",
|
||||
placeholderBg: "#D9D9D9",
|
||||
calenderBg: "#FBD5B2",
|
||||
}
|
||||
|
||||
let currentTheme: Theme = defaultLight;
|
||||
export default currentTheme;
|
||||
308
apps/client/src/app/Calender.tsx
Normal file
308
apps/client/src/app/Calender.tsx
Normal file
@@ -0,0 +1,308 @@
|
||||
import { Animated, Modal, Pressable, Text, View } from "react-native";
|
||||
import { DAYS, MONTHS, Month } from "../Constants";
|
||||
import Header from "../components/Header";
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import currentTheme from "../Themes";
|
||||
import BaseBackground from "../components/BaseBackground";
|
||||
import { FlashList } from "@shopify/flash-list";
|
||||
|
||||
// TODO: month selection dropdown menu
|
||||
|
||||
const Calendar = () => {
|
||||
const [monthIndex, setMonthIndex] = useState(0);
|
||||
const [currentYear, setCurrentYear] = useState(new Date().getFullYear());
|
||||
|
||||
const changeMonth = (delta: number) => {
|
||||
setMonthIndex((prev) => {
|
||||
const newIndex = prev + delta;
|
||||
if (newIndex > 11) {
|
||||
setCurrentYear((y) => y + 1);
|
||||
return 0;
|
||||
}
|
||||
if (newIndex < 0) {
|
||||
setCurrentYear((y) => y - 1);
|
||||
return 11;
|
||||
}
|
||||
return newIndex;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseBackground>
|
||||
<CalendarHeader
|
||||
changeMonth={changeMonth}
|
||||
monthIndex={monthIndex}
|
||||
currentYear={currentYear}
|
||||
/>
|
||||
<WeekDaysLine />
|
||||
<CalendarGrid month={MONTHS[monthIndex]} year={currentYear} />
|
||||
</BaseBackground>
|
||||
);
|
||||
};
|
||||
|
||||
type MonthSelectorProps = {
|
||||
modalVisible: boolean;
|
||||
onClose: () => void;
|
||||
position: { top: number; left: number; width: number };
|
||||
};
|
||||
|
||||
const MonthSelector = ({
|
||||
modalVisible,
|
||||
onClose,
|
||||
position,
|
||||
}: MonthSelectorProps) => {
|
||||
const heightAnim = useRef(new Animated.Value(0)).current;
|
||||
type ItemType = { id: string; text: string };
|
||||
const listRef = useRef<React.ComponentRef<typeof FlashList<ItemType>>>(null);
|
||||
const [monthSelectorData, setMonthSelectorData] = useState(() => {
|
||||
const initial = [];
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
initial.push({ id: i.toString(), text: `number ${i}` });
|
||||
}
|
||||
return initial;
|
||||
});
|
||||
|
||||
const appendToTestData = (
|
||||
startIndex: number,
|
||||
numberOfEntries: number,
|
||||
appendToStart: boolean,
|
||||
) => {
|
||||
// create new data
|
||||
const newData = [];
|
||||
for (let i = 0; i < numberOfEntries; i++) {
|
||||
const newIndex = startIndex + i + 1;
|
||||
const newEntry = {
|
||||
id: newIndex + "",
|
||||
text: `number ${newIndex}`,
|
||||
};
|
||||
if (appendToStart) {
|
||||
newData.unshift(newEntry);
|
||||
} else {
|
||||
newData.push(newEntry);
|
||||
}
|
||||
}
|
||||
// add new data
|
||||
if (appendToStart) {
|
||||
setMonthSelectorData([...newData, ...monthSelectorData]);
|
||||
} else {
|
||||
setMonthSelectorData([...monthSelectorData, ...newData]);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (modalVisible) {
|
||||
Animated.timing(heightAnim, {
|
||||
toValue: 200,
|
||||
duration: 200,
|
||||
useNativeDriver: false,
|
||||
}).start();
|
||||
} else {
|
||||
// reset on close
|
||||
heightAnim.setValue(0);
|
||||
}
|
||||
}, [modalVisible]);
|
||||
|
||||
const renderItem = ({ item }: { item: ItemType }) => (
|
||||
<Pressable>
|
||||
<View className="w-full flex justify-center items-center">
|
||||
<Text className="text-3xl">{item.text}</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={modalVisible}
|
||||
transparent={true}
|
||||
animationType="none"
|
||||
onRequestClose={onClose}
|
||||
>
|
||||
<Pressable className="flex-1" onPress={onClose}>
|
||||
<Animated.View
|
||||
className="absolute bg-white border-2 border-solid rounded-lg overflow-hidden"
|
||||
style={{
|
||||
top: position.top,
|
||||
left: position.left,
|
||||
width: position.width,
|
||||
height: heightAnim,
|
||||
}}
|
||||
>
|
||||
<FlashList
|
||||
className="w-full"
|
||||
ref={listRef}
|
||||
keyExtractor={(item) => item.id}
|
||||
data={monthSelectorData}
|
||||
initialScrollIndex={5}
|
||||
onEndReachedThreshold={0.5}
|
||||
onEndReached={() =>
|
||||
appendToTestData(monthSelectorData.length, 10, false)
|
||||
}
|
||||
onStartReachedThreshold={0.5}
|
||||
onStartReached={() =>
|
||||
appendToTestData(monthSelectorData.length, 10, true)
|
||||
}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
type CalendarHeaderProps = {
|
||||
changeMonth: (delta: number) => void;
|
||||
monthIndex: number;
|
||||
currentYear: number;
|
||||
};
|
||||
|
||||
const CalendarHeader = (props: CalendarHeaderProps) => {
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [dropdownPosition, setDropdownPosition] = useState({
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: 0,
|
||||
});
|
||||
const containerRef = useRef<View>(null);
|
||||
|
||||
const prevMonth = () => props.changeMonth(-1);
|
||||
const nextMonth = () => props.changeMonth(1);
|
||||
|
||||
const measureAndOpen = () => {
|
||||
containerRef.current?.measureInWindow((x, y, width, height) => {
|
||||
setDropdownPosition({ top: y + height, left: x, width });
|
||||
setModalVisible(true);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Header className="flex flex-row items-center justify-between">
|
||||
<ChangeMonthButton onPress={prevMonth} title={"<"} />
|
||||
<View
|
||||
ref={containerRef}
|
||||
className="relative flex flex-row items-center justify-around"
|
||||
>
|
||||
<Text className="text-4xl">
|
||||
{MONTHS[props.monthIndex]} {props.currentYear}
|
||||
</Text>
|
||||
<Pressable
|
||||
className={
|
||||
"flex justify-center items-center bg-white w-12 h-12 p-2 " +
|
||||
"border border-solid rounded-full ml-2"
|
||||
}
|
||||
style={{
|
||||
borderColor: currentTheme.primeFg,
|
||||
}}
|
||||
onPress={measureAndOpen}
|
||||
>
|
||||
<Text className="text-4xl">v</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
<MonthSelector
|
||||
modalVisible={modalVisible}
|
||||
onClose={() => setModalVisible(false)}
|
||||
position={dropdownPosition}
|
||||
/>
|
||||
<ChangeMonthButton onPress={nextMonth} title={">"} />
|
||||
</Header>
|
||||
);
|
||||
};
|
||||
|
||||
type ChangeMonthButtonProps = {
|
||||
onPress: () => void;
|
||||
title: string;
|
||||
};
|
||||
|
||||
const ChangeMonthButton = (props: ChangeMonthButtonProps) => (
|
||||
<Pressable
|
||||
onPress={props.onPress}
|
||||
className={
|
||||
"w-16 h-16 bg-white rounded-full flex items-center " +
|
||||
"justify-center border border-solid border-1 mx-2"
|
||||
}
|
||||
style={{
|
||||
borderColor: currentTheme.primeFg,
|
||||
}}
|
||||
>
|
||||
<Text className="text-4xl">{props.title}</Text>
|
||||
</Pressable>
|
||||
);
|
||||
|
||||
const WeekDaysLine = () => (
|
||||
<View className="flex flex-row items-center justify-around px-2 gap-2">
|
||||
{/* TODO: px and gap need fine tuning to perfectly align with the grid */}
|
||||
{DAYS.map((day, i) => (
|
||||
<Text key={i}>{day.substring(0, 2).toUpperCase()}</Text>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
|
||||
type CalendarGridProps = {
|
||||
month: Month;
|
||||
year: number;
|
||||
};
|
||||
|
||||
const CalendarGrid = (props: CalendarGridProps) => {
|
||||
const { baseDate, dateOffset } = useMemo(() => {
|
||||
const monthIndex = MONTHS.indexOf(props.month);
|
||||
const base = new Date(props.year, monthIndex, 1);
|
||||
const offset = base.getDay() === 0 ? 6 : base.getDay() - 1;
|
||||
return { baseDate: base, dateOffset: offset };
|
||||
}, [props.month, props.year]);
|
||||
|
||||
// TODO: create array beforehand in a useMemo
|
||||
const createDateFromOffset = (offset: number): Date => {
|
||||
const date = new Date(baseDate);
|
||||
date.setDate(date.getDate() + offset);
|
||||
return date;
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
className="h-full flex-1 flex-col flex-wrap gap-2 p-2"
|
||||
style={{
|
||||
backgroundColor: currentTheme.calenderBg,
|
||||
}}
|
||||
>
|
||||
{Array.from({ length: 6 }).map((_, i) => (
|
||||
<View
|
||||
key={i}
|
||||
className="w-full flex-1 flex-row justify-around items-center gap-2"
|
||||
>
|
||||
{Array.from({ length: 7 }).map((_, j) => (
|
||||
<SingleDay
|
||||
key={j}
|
||||
date={createDateFromOffset(i * 7 + j - dateOffset)}
|
||||
month={props.month}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
type SingleDayProps = {
|
||||
date: Date;
|
||||
month: Month;
|
||||
};
|
||||
|
||||
const SingleDay = (props: SingleDayProps) => {
|
||||
const isSameMonth = MONTHS[props.date.getMonth()] === props.month;
|
||||
|
||||
return (
|
||||
<View
|
||||
className="h-full flex-1 aspect-auto rounded-xl items-center"
|
||||
style={{
|
||||
backgroundColor: currentTheme.primeBg,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
className={`text-xl ` + (isSameMonth ? "text-black" : "text-black/50")}
|
||||
>
|
||||
{props.date.getDate()}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Calendar;
|
||||
349
apps/client/src/app/Chat.tsx
Normal file
349
apps/client/src/app/Chat.tsx
Normal file
@@ -0,0 +1,349 @@
|
||||
import { View, Text, TextInput } from "react-native";
|
||||
import currentTheme from "../Themes";
|
||||
import { useState } from "react";
|
||||
import Header from "../components/Header";
|
||||
import BaseBackground from "../components/BaseBackground";
|
||||
import { FlashList } from "@shopify/flash-list";
|
||||
|
||||
// TODO: better shadows for everything
|
||||
// (maybe with extra library because of differences between android and ios)
|
||||
// TODO: max width for messages
|
||||
// TODO: create new messages
|
||||
|
||||
type BubbleSide = "left" | "right";
|
||||
type ChatMessageProps = {
|
||||
side: BubbleSide;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
type MessageData = {
|
||||
id: string;
|
||||
side: BubbleSide;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
// NOTE: only for testing
|
||||
const getRandomInt = (min: number, max: number) => {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
};
|
||||
|
||||
const randomWidth = () => getRandomInt(100, 400);
|
||||
const randomHeight = () => getRandomInt(50, 100);
|
||||
|
||||
const messages: MessageData[] = [
|
||||
// {{{
|
||||
{
|
||||
id: "1",
|
||||
side: "left",
|
||||
width: randomWidth(),
|
||||
height: randomHeight(),
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
side: "right",
|
||||
width: randomWidth(),
|
||||
height: randomHeight(),
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
side: "left",
|
||||
width: randomWidth(),
|
||||
height: randomHeight(),
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
side: "right",
|
||||
width: randomWidth(),
|
||||
height: randomHeight(),
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
side: "left",
|
||||
width: randomWidth(),
|
||||
height: randomHeight(),
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
side: "right",
|
||||
width: randomWidth(),
|
||||
height: randomHeight(),
|
||||
},
|
||||
{
|
||||
id: "7",
|
||||
side: "left",
|
||||
width: randomWidth(),
|
||||
height: randomHeight(),
|
||||
},
|
||||
{
|
||||
id: "8",
|
||||
side: "right",
|
||||
width: randomWidth(),
|
||||
height: randomHeight(),
|
||||
},
|
||||
{
|
||||
id: "9",
|
||||
side: "left",
|
||||
width: randomWidth(),
|
||||
height: randomHeight(),
|
||||
},
|
||||
{
|
||||
id: "10",
|
||||
side: "right",
|
||||
width: randomWidth(),
|
||||
height: randomHeight(),
|
||||
},
|
||||
// {
|
||||
// id: "11",
|
||||
// side: "left",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "12",
|
||||
// side: "right",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "13",
|
||||
// side: "left",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "14",
|
||||
// side: "right",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "15",
|
||||
// side: "left",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "16",
|
||||
// side: "right",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "17",
|
||||
// side: "left",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "18",
|
||||
// side: "right",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "19",
|
||||
// side: "left",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "20",
|
||||
// side: "right",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "21",
|
||||
// side: "left",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "22",
|
||||
// side: "right",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "23",
|
||||
// side: "left",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "24",
|
||||
// side: "right",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "25",
|
||||
// side: "left",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "26",
|
||||
// side: "right",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "27",
|
||||
// side: "left",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "28",
|
||||
// side: "right",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "29",
|
||||
// side: "left",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "30",
|
||||
// side: "right",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "31",
|
||||
// side: "left",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "32",
|
||||
// side: "right",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "33",
|
||||
// side: "left",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
// {
|
||||
// id: "34",
|
||||
// side: "right",
|
||||
// width: randomWidth(),
|
||||
// height: randomHeight(),
|
||||
// },
|
||||
//, width: randomWidth, height: getRandomInt(50, 500) }}}
|
||||
];
|
||||
|
||||
const Chat = () => {
|
||||
return (
|
||||
<BaseBackground>
|
||||
<ChatHeader />
|
||||
<FlashList
|
||||
data={messages}
|
||||
renderItem={({ item }) => (
|
||||
<ChatMessage
|
||||
side={item.side}
|
||||
width={item.width}
|
||||
height={item.height}
|
||||
/>
|
||||
)}
|
||||
maintainVisibleContentPosition={{
|
||||
autoscrollToBottomThreshold: 0.2,
|
||||
startRenderingFromBottom: true,
|
||||
}}
|
||||
keyExtractor={(item) => item.id}
|
||||
// extraData={selectedId} might need this later for re-rendering
|
||||
/>
|
||||
<ChatInput />
|
||||
</BaseBackground>
|
||||
);
|
||||
};
|
||||
|
||||
const ChatHeader = () => {
|
||||
return (
|
||||
<Header className="flex flex-row items-center">
|
||||
<View
|
||||
className="ml-3 w-12 h-12 rounded-3xl border border-solid"
|
||||
style={{
|
||||
backgroundColor: currentTheme.placeholderBg,
|
||||
borderColor: currentTheme.primeFg,
|
||||
}}
|
||||
></View>
|
||||
<Text className="text-lg pl-3">CalChat</Text>
|
||||
<View
|
||||
className="h-2 bg-black"
|
||||
style={{
|
||||
shadowColor: "#000",
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 5,
|
||||
},
|
||||
shadowOpacity: 0.34,
|
||||
shadowRadius: 6.27,
|
||||
|
||||
elevation: 10,
|
||||
}}
|
||||
/>
|
||||
</Header>
|
||||
);
|
||||
};
|
||||
|
||||
const ChatInput = () => {
|
||||
const [text, onChangeText] = useState("Nachricht");
|
||||
|
||||
return (
|
||||
<View className="flex flex-row w-full h-8 my-2">
|
||||
<TextInput
|
||||
className="w-4/5 h-full border border-solid rounded-2xl mx-2 px-2"
|
||||
style={{
|
||||
backgroundColor: currentTheme.messageBorderBg,
|
||||
}}
|
||||
onChangeText={onChangeText}
|
||||
value={text}
|
||||
/>
|
||||
<View
|
||||
className="w-8 h-full rounded-2xl"
|
||||
style={{
|
||||
backgroundColor: currentTheme.placeholderBg,
|
||||
}}
|
||||
></View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const ChatMessage = (props: ChatMessageProps) => {
|
||||
const borderColor =
|
||||
props.side === "left" ? currentTheme.chatBot : currentTheme.primeFg;
|
||||
const selfSide =
|
||||
props.side === "left"
|
||||
? "self-start ml-2 rounded-bl-sm"
|
||||
: "self-end mr-2 rounded-br-sm";
|
||||
|
||||
return (
|
||||
<View
|
||||
className={
|
||||
`bg-white border-2 border-solid rounded-xl my-2 ` + `${selfSide}`
|
||||
}
|
||||
style={{
|
||||
width: props.width,
|
||||
height: props.height,
|
||||
borderColor: borderColor,
|
||||
|
||||
elevation: 8,
|
||||
}}
|
||||
>
|
||||
<Text className="p-1">Lorem Ipsum Dolor sit amet</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Chat;
|
||||
49
apps/client/src/app/Hello_World.tsx
Normal file
49
apps/client/src/app/Hello_World.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Button, Text, View } from 'react-native';
|
||||
|
||||
// const styles = StyleSheet.create({
|
||||
// container: {
|
||||
// alignItems: 'center',
|
||||
// },
|
||||
// text: {
|
||||
// fontSize: 40
|
||||
// }
|
||||
// })
|
||||
|
||||
type HelloWorldProps = {
|
||||
text: string;
|
||||
aNumber: number;
|
||||
}
|
||||
|
||||
const HelloWorld = (props: HelloWorldProps) => {
|
||||
return (
|
||||
<View className='flex-1 items-center justify-center'>
|
||||
<Text>{props.text} : {props.aNumber}</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const Counter = () => {
|
||||
const [count, setCount] = useState<number>(0);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Button
|
||||
onPress={() => setCount(count + 1)}
|
||||
title={`You tabbed me ${count} times`}/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const ManyHelloes = () => {
|
||||
return (
|
||||
<View>
|
||||
<HelloWorld text="first number" aNumber={1}/>
|
||||
<HelloWorld text="second number" aNumber={2}/>
|
||||
<HelloWorld text="third number" aNumber={3}/>
|
||||
<Counter/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default ManyHelloes;
|
||||
6
apps/client/src/app/_layout.tsx
Normal file
6
apps/client/src/app/_layout.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Stack } from "expo-router";
|
||||
import "../../global.css";
|
||||
|
||||
export default function RootLayout() {
|
||||
return <Stack screenOptions={{ headerShown: false }} />;
|
||||
}
|
||||
10
apps/client/src/app/index.tsx
Normal file
10
apps/client/src/app/index.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
import Chat from "./Chat";
|
||||
import Calender from "./Calender";
|
||||
|
||||
export default function Index() {
|
||||
return (
|
||||
// <Chat />
|
||||
<Calender />
|
||||
);
|
||||
}
|
||||
23
apps/client/src/components/BaseBackground.tsx
Normal file
23
apps/client/src/components/BaseBackground.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { View } from "react-native"
|
||||
import currentTheme from "../Themes"
|
||||
import { ReactNode } from "react";
|
||||
|
||||
type BaseBackgroundProps = {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const BaseBackground = (props: BaseBackgroundProps) => {
|
||||
return (
|
||||
<View
|
||||
className={`h-full ${props.className}`}
|
||||
style={{
|
||||
backgroundColor: currentTheme.primeBg,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default BaseBackground;
|
||||
39
apps/client/src/components/Header.tsx
Normal file
39
apps/client/src/components/Header.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { View } from "react-native";
|
||||
import currentTheme from "../Themes";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
type HeaderProps = {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const Header = (props: HeaderProps) => {
|
||||
return (
|
||||
<View>
|
||||
<View
|
||||
className={`w-full h-32 pt-10 pb-4 ${props.className}`}
|
||||
style={{
|
||||
backgroundColor: currentTheme.chatBot,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</View>
|
||||
<View
|
||||
className="h-2 bg-black"
|
||||
style={{
|
||||
shadowColor: "#000",
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 5,
|
||||
},
|
||||
shadowOpacity: 0.34,
|
||||
shadowRadius: 6.27,
|
||||
|
||||
elevation: 10,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
10
apps/client/tailwind.config.js
Normal file
10
apps/client/tailwind.config.js
Normal file
@@ -0,0 +1,10 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
// NOTE: Update this to include the paths to all files that contain Nativewind classes.
|
||||
content: ["./app/**/*.{js,jsx,ts,tsx}", "./src/**/*.{js,jsx,ts,tsx}"],
|
||||
presets: [require("nativewind/preset")],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
19
apps/client/tsconfig.json
Normal file
19
apps/client/tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "CommonJS",
|
||||
"outDir": "dist",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".expo/types/**/*.ts",
|
||||
"expo-env.d.ts",
|
||||
"nativewind-env.d.ts"
|
||||
]
|
||||
}
|
||||
13
apps/server/package.json
Normal file
13
apps/server/package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@caldav/server",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@caldav/shared": "*"
|
||||
}
|
||||
}
|
||||
13
apps/server/tsconfig.json
Normal file
13
apps/server/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "CommonJS",
|
||||
"outDir": "dist",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Reference in New Issue
Block a user