added 260907 version of worker toolkit

This commit is contained in:
2026-09-08 20:30:51 -04:00
parent cbd3f0f8ca
commit 97aca37663
1283 changed files with 142951 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
import { render, screen } from "@testing-library/react-native";
import React from "react";
import App from "../src/App";
jest.useFakeTimers();
describe("<App />", () => {
it("renders title", async () => {
render(<App />);
expect(screen.getByText("Welcome to Flaredown")).toBeDefined();
});
});

View File

@@ -0,0 +1,38 @@
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import React from "react";
import { Provider } from "react-native-paper";
import { Theme } from "./Theme";
import CreateAccountScreen from "./screens/CreateAccountScreen";
import Dashboard from "./screens/Dashboard";
import LoginScreen from "./screens/LoginScreen";
import ResetPasswordScreen from "./screens/ResetPasswordScreen";
const Stack = createNativeStackNavigator();
export default function App() {
return (
<Provider theme={Theme}>
<NavigationContainer>
<Stack.Navigator
initialRouteName="LoginScreen"
screenOptions={{
headerShown: false,
}}
>
<Stack.Screen name="LoginScreen" component={LoginScreen} />
<Stack.Screen
name="CreateAccountScreen"
component={CreateAccountScreen}
/>
<Stack.Screen name="Dashboard" component={Dashboard} />
<Stack.Screen
name="ResetPasswordScreen"
component={ResetPasswordScreen}
/>
</Stack.Navigator>
</NavigationContainer>
</Provider>
);
}

View File

@@ -0,0 +1,8 @@
import { DefaultTheme } from "react-native-paper";
export const Theme = {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
},
};

View File

@@ -0,0 +1,23 @@
import React from "react";
import { TouchableOpacity, StyleSheet } from "react-native";
import { getStatusBarHeight } from "react-native-status-bar-height";
export default function BackButton({ goBack }) {
return (
<TouchableOpacity onPress={goBack} style={styles.container}>
{/* <Text>{"< Back"}</Text> */}
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
container: {
position: "absolute",
top: 20 + getStatusBarHeight(),
left: 4,
},
image: {
width: 24,
height: 24,
},
});

View File

@@ -0,0 +1,31 @@
import React from "react";
import { StyleSheet, KeyboardAvoidingView, View } from "react-native";
import { Theme } from "../Theme";
export default function Background({ children }) {
return (
<View style={styles.background}>
<KeyboardAvoidingView style={styles.container} behavior="padding">
{children}
</KeyboardAvoidingView>
</View>
);
}
const styles = StyleSheet.create({
background: {
flex: 1,
width: "100%",
backgroundColor: Theme.colors.background,
},
container: {
flex: 1,
padding: 20,
width: "100%",
maxWidth: 500,
alignSelf: "center",
alignItems: "center",
justifyContent: "center",
},
});

View File

@@ -0,0 +1,35 @@
import React from "react";
import { StyleSheet } from "react-native";
import { Button as PaperButton } from "react-native-paper";
import { Theme } from "../Theme";
export default function Button({ mode, style = {}, children, ...props }) {
return (
<PaperButton
style={[
styles.button,
mode === "outlined" && { backgroundColor: Theme.colors.surface },
style,
]}
labelStyle={styles.text}
mode={mode}
{...props}
>
{children}
</PaperButton>
);
}
const styles = StyleSheet.create({
button: {
width: "100%",
marginVertical: 10,
paddingVertical: 2,
},
text: {
fontWeight: "bold",
fontSize: 15,
lineHeight: 26,
},
});

View File

@@ -0,0 +1,15 @@
import React from "react";
import { StyleSheet } from "react-native";
import { Card } from "react-native-paper";
export default function Background({ children }) {
return <Card style={styles.card}>{children}</Card>;
}
const styles = StyleSheet.create({
card: {
padding: 30,
width: "100%",
maxWidth: 500,
},
});

View File

@@ -0,0 +1,47 @@
import React from "react";
import { View, Text, StyleSheet, Linking } from "react-native";
import Button from "./Button";
import Link from "./Link";
const currentYear = new Date().getFullYear();
export default function Footer({ navigation }) {
const handleHelpPress = () => {
Linking.openURL(`mailto:contact@flaredown.com`);
};
const handleTosPress = () => {
navigation.navigate("ToS");
};
const handlePrivacyPress = () => {
navigation.navigate("PrivacyPolicy");
};
return (
<View style={styles.footer}>
<Button onPress={handleHelpPress} mode="outlined">
Help & Feedback
</Button>
<View style={styles.links}>
<Link onPress={handleTosPress}>Terms of Service</Link>
<Link onPress={handlePrivacyPress}>Privacy Policy</Link>
</View>
<Text>Flaredown © {currentYear}</Text>
</View>
);
}
const styles = StyleSheet.create({
footer: {
marginTop: 10,
gap: 10,
alignItems: "center",
},
links: {
flexDirection: "row",
gap: 10,
display: "flex",
},
});

View File

@@ -0,0 +1,18 @@
import React from "react";
import { StyleSheet } from "react-native";
import { Text } from "react-native-paper";
import { Theme } from "../Theme";
export default function Header(props) {
return <Text style={styles.header} {...props} />;
}
const styles = StyleSheet.create({
header: {
fontSize: 21,
color: Theme.colors.primary,
fontWeight: "bold",
paddingVertical: 12,
},
});

View File

@@ -0,0 +1,33 @@
import React from "react";
import { StyleSheet, KeyboardAvoidingView, View } from "react-native";
import Footer from "./Footer";
import { Theme } from "../Theme";
export default function Background({ navigation, children }) {
return (
<View style={styles.background}>
<KeyboardAvoidingView style={styles.container} behavior="padding">
{children}
<Footer navigation={navigation} />
</KeyboardAvoidingView>
</View>
);
}
const styles = StyleSheet.create({
background: {
flex: 1,
width: "100%",
backgroundColor: Theme.colors.background,
},
container: {
flex: 1,
padding: 20,
width: "100%",
maxWidth: 500,
alignSelf: "center",
alignItems: "center",
justifyContent: "center",
},
});

View File

@@ -0,0 +1,24 @@
import React from "react";
import { StyleSheet, Text, TouchableOpacity } from "react-native";
import { Theme } from "../Theme";
interface LinkProps {
onPress: () => void;
children: React.ReactNode;
}
export default function Link({ onPress, children }: LinkProps) {
return (
<TouchableOpacity onPress={onPress}>
<Text style={styles.link}>{children}</Text>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
link: {
fontWeight: "bold",
color: Theme.colors.primary,
},
});

View File

@@ -0,0 +1,43 @@
import React from "react";
import { View, StyleSheet, Text } from "react-native";
import { TextInput as Input } from "react-native-paper";
import { Theme } from "../Theme";
export default function TextInput({ errorText, description = "", ...props }) {
return (
<View style={styles.container}>
<Input
style={styles.input}
selectionColor={Theme.colors.primary}
underlineColor="transparent"
mode="outlined"
{...props}
/>
{description && !errorText ? (
<Text style={styles.description}>{description}</Text>
) : null}
{errorText ? <Text style={styles.error}>{errorText}</Text> : null}
</View>
);
}
const styles = StyleSheet.create({
container: {
width: "100%",
marginVertical: 12,
},
input: {
backgroundColor: Theme.colors.surface,
},
description: {
fontSize: 13,
color: Theme.colors.secondary,
paddingTop: 8,
},
error: {
fontSize: 13,
color: Theme.colors.error,
paddingTop: 8,
},
});

View File

@@ -0,0 +1,6 @@
export function emailValidator(email) {
const re = /\S+@\S+\.\S+/;
if (!email) return "Email address is required.";
if (!re.test(email)) return "Please enter a valid email address.";
return "";
}

View File

@@ -0,0 +1,6 @@
export function passwordValidator(password) {
if (!password) return "Password is required.";
if (password.length < 8)
return "Password should contain at least 8 characters.";
return "";
}

View File

@@ -0,0 +1,4 @@
export function usernameValidator(name) {
if (!name) return "Username is required.";
return "";
}

View File

@@ -0,0 +1,92 @@
import React, { useState } from "react";
import { View, StyleSheet } from "react-native";
import { Text } from "react-native-paper";
import BackButton from "../components/BackButton";
import Button from "../components/Button";
import Card from "../components/Card";
import Header from "../components/Header";
import Layout from "../components/Layout";
import Link from "../components/Link";
import TextInput from "../components/TextInput";
import { emailValidator } from "../helpers/emailValidator";
import { passwordValidator } from "../helpers/passwordValidator";
import { usernameValidator } from "../helpers/usernameValidator";
export default function CreateAccountScreen({ navigation }) {
const [username, setUsername] = useState({ value: "", error: "" });
const [email, setEmail] = useState({ value: "", error: "" });
const [password, setPassword] = useState({ value: "", error: "" });
const onSignUpPressed = () => {
const usernameError = usernameValidator(username.value);
const emailError = emailValidator(email.value);
const passwordError = passwordValidator(password.value);
if (emailError || passwordError || usernameError) {
setUsername({ ...username, error: usernameError });
setEmail({ ...email, error: emailError });
setPassword({ ...password, error: passwordError });
return;
}
navigation.reset({
index: 0,
routes: [{ name: "Dashboard" }],
});
};
return (
<Layout navigation={navigation}>
<BackButton goBack={navigation.goBack} />
<Header>Create account</Header>
<Card>
<TextInput
label="Username"
returnKeyType="next"
value={username.value}
onChangeText={(text) => setUsername({ value: text, error: "" })}
error={!!username.error}
errorText={username.error}
/>
<TextInput
label="Email"
returnKeyType="next"
value={email.value}
onChangeText={(text) => setEmail({ value: text, error: "" })}
error={!!email.error}
errorText={email.error}
autoCapitalize="none"
autoCompleteType="email"
textContentType="emailAddress"
keyboardType="email-address"
/>
<TextInput
label="Password"
returnKeyType="done"
value={password.value}
onChangeText={(text) => setPassword({ value: text, error: "" })}
error={!!password.error}
errorText={password.error}
secureTextEntry
/>
<Button
mode="contained"
onPress={onSignUpPressed}
style={{ marginTop: 24 }}
>
Next
</Button>
<View style={styles.row}>
<Text>Already a member? </Text>
<Link onPress={() => navigation.replace("LoginScreen")}>Log in</Link>
</View>
</Card>
</Layout>
);
}
const styles = StyleSheet.create({
row: {
flexDirection: "row",
marginTop: 4,
},
});

View File

@@ -0,0 +1,15 @@
import React from "react";
import { View } from "react-native";
import Layout from "../components/Layout";
import Link from "../components/Link";
export default function Dashboard({ navigation }) {
return (
<Layout navigation={navigation}>
<View>
<Link onPress={() => navigation.replace("LoginScreen")}>Log out</Link>
</View>
</Layout>
);
}

View File

@@ -0,0 +1,95 @@
import React, { useState } from "react";
import { TouchableOpacity, StyleSheet, View } from "react-native";
import { Text } from "react-native-paper";
import { Theme } from "../Theme";
import BackButton from "../components/BackButton";
import Button from "../components/Button";
import Card from "../components/Card";
import Header from "../components/Header";
import Layout from "../components/Layout";
import Link from "../components/Link";
import TextInput from "../components/TextInput";
import { emailValidator } from "../helpers/emailValidator";
import { passwordValidator } from "../helpers/passwordValidator";
export default function LoginScreen({ navigation }) {
const [email, setEmail] = useState({ value: "", error: "" });
const [password, setPassword] = useState({ value: "", error: "" });
const onLoginPressed = () => {
const emailError = emailValidator(email.value);
const passwordError = passwordValidator(password.value);
if (emailError || passwordError) {
setEmail({ ...email, error: emailError });
setPassword({ ...password, error: passwordError });
return;
}
navigation.reset({
index: 0,
routes: [{ name: "Dashboard" }],
});
};
return (
<Layout navigation={navigation}>
<BackButton goBack={navigation.goBack} />
<Header>Welcome to Flaredown</Header>
<Card>
<TextInput
label="Email"
returnKeyType="next"
value={email.value}
onChangeText={(text) => setEmail({ value: text, error: "" })}
error={!!email.error}
errorText={email.error}
autoCapitalize="none"
autoCompleteType="email"
textContentType="emailAddress"
keyboardType="email-address"
/>
<TextInput
label="Password"
returnKeyType="done"
value={password.value}
onChangeText={(text) => setPassword({ value: text, error: "" })}
error={!!password.error}
errorText={password.error}
secureTextEntry
/>
<View style={styles.forgotPassword}>
<TouchableOpacity
onPress={() => navigation.navigate("ResetPasswordScreen")}
>
<Text style={styles.forgot}>Forgot password</Text>
</TouchableOpacity>
</View>
<Button mode="contained" onPress={onLoginPressed}>
Log in
</Button>
<View style={styles.row}>
<Text>Not a member yet? </Text>
<Link onPress={() => navigation.replace("CreateAccountScreen")}>
Sign up now
</Link>
</View>
</Card>
</Layout>
);
}
const styles = StyleSheet.create({
forgotPassword: {
width: "100%",
alignItems: "flex-end",
marginBottom: 10,
},
row: {
flexDirection: "row",
marginTop: 4,
},
forgot: {
fontSize: 13,
color: Theme.colors.secondary,
},
});

View File

@@ -0,0 +1,51 @@
import React, { useState } from "react";
import BackButton from "../components/BackButton";
import Button from "../components/Button";
import Card from "../components/Card";
import Header from "../components/Header";
import Layout from "../components/Layout";
import TextInput from "../components/TextInput";
import { emailValidator } from "../helpers/emailValidator";
export default function ResetPasswordScreen({ navigation }) {
const [email, setEmail] = useState({ value: "", error: "" });
const sendResetPasswordEmail = () => {
const emailError = emailValidator(email.value);
if (emailError) {
setEmail({ ...email, error: emailError });
return;
}
navigation.navigate("LoginScreen");
};
return (
<Layout navigation={navigation}>
<BackButton goBack={navigation.goBack} />
<Header>Reset password</Header>
<Card>
<TextInput
label="Email"
returnKeyType="done"
value={email.value}
onChangeText={(text) => setEmail({ value: text, error: "" })}
error={!!email.error}
errorText={email.error}
autoCapitalize="none"
autoCompleteType="email"
textContentType="emailAddress"
keyboardType="email-address"
description="You will receive an email with the reset link."
/>
<Button
mode="contained"
onPress={sendResetPasswordEmail}
style={{ marginTop: 16 }}
>
Continue
</Button>
</Card>
</Layout>
);
}