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,8 @@
bower_components
node_modules
dist
tmp
.git
README.md
LICENSE
.gitignore

View File

@@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ["universe/native"],
};

View File

@@ -0,0 +1,35 @@
# 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
*.pem
# local env files
.env*.local
# typescript
*.tsbuildinfo

View File

@@ -0,0 +1 @@
v18.18.0

View File

@@ -0,0 +1,5 @@
import registerRootComponent from "expo/build/launch/registerRootComponent";
import App from "./src/App";
registerRootComponent(App);

View File

@@ -0,0 +1,33 @@
# pull base image
FROM node:18.18.0
# set our node environment, either development or production
# defaults to production, compose overrides this to development on build and run
ARG NODE_ENV=production
ENV NODE_ENV $NODE_ENV
# default to port 19006 for node, and 19001 and 19002 (tests) for debug
ARG PORT=19006
ENV PORT $PORT
EXPOSE $PORT 19001 19002
# install global packages
ENV NPM_CONFIG_PREFIX=/home/node/.npm-global
ENV PATH /home/node/.npm-global/bin:$PATH
RUN npm i --unsafe-perm --allow-root -g npm@latest expo-cli@latest
# install dependencies first, in a different location for easier app bind mounting for local development
# due to default /opt permissions we have to create the dir with root and change perms
RUN mkdir /opt/native
WORKDIR /opt/native
ENV PATH /opt/native/.bin:$PATH
COPY ./package.json ./package-lock.json ./
RUN npm install
# copy in our source code last, as it changes the most
WORKDIR /opt/native/app
# for development, we bind mount volumes; comment out for production
COPY . .
ENTRYPOINT ["npm", "run"]
CMD ["web"]

View File

@@ -0,0 +1,28 @@
{
"expo": {
"name": "native",
"slug": "native",
"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.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -0,0 +1,6 @@
module.exports = function (api) {
api.cache(true);
return {
presets: ["babel-preset-expo"],
};
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,50 @@
{
"name": "flaredown",
"version": "1.0.0",
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"test": "jest",
"tsc": "tsc",
"lint": "eslint . && prettier -c .",
"lint:fix": "eslint . --fix && prettier . --write"
},
"dependencies": {
"@expo/webpack-config": "^19.0.0",
"@react-navigation/native": "^6.1.9",
"@react-navigation/native-stack": "^6.9.17",
"@types/react": "~18.2.14",
"expo": "~49.0.13",
"expo-status-bar": "~1.6.0",
"jest": "^29.2.1",
"jest-expo": "~49.0.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-native": "0.72.6",
"react-native-paper": "^5.11.7",
"react-native-status-bar-height": "^2.6.0",
"react-native-web": "~0.19.6",
"typescript": "^5.1.3",
"webpack": "^5.64.4",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.2.1"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@testing-library/react-native": "^12.4.3",
"@types/jest": "^29.5.11",
"eslint": "^8.56.0",
"eslint-config-universe": "^12.0.0",
"prettier": "^3.1.1"
},
"jest": {
"preset": "jest-expo",
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg)"
]
},
"private": true
}

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>
);
}

View File

@@ -0,0 +1,6 @@
{
"compilerOptions": {
"jsx": "react"
},
"extends": "expo/tsconfig.base"
}

View File

@@ -0,0 +1,21 @@
const createExpoWebpackConfigAsync = require("@expo/webpack-config");
module.exports = async function (env, argv) {
const config = await createExpoWebpackConfigAsync(env, argv);
// If you want to add a new alias to the config.
config.entry = ["./AppEntry.js"];
// Maybe you want to turn off compression in dev mode.
if (config.mode === "development") {
config.devServer.compress = false;
}
// Or prevent minimizing the bundle when you build.
if (config.mode === "production") {
config.optimization.minimize = false;
}
// Finally return the new config for the CLI to use.
return config;
};