Renamed src to app and added login api action and remix-flat-routes to vite config main

This commit is contained in:
Nikolaj Frey 2024-04-29 20:44:09 +10:00
parent 4668eba03d
commit 2947c7f38d
69 changed files with 232 additions and 146 deletions

2
remix/.gitignore vendored
View File

@ -11,3 +11,5 @@ api/index.js
api/index.js.map
pnpm-lock.yaml
tmp/*

View File

@ -0,0 +1,21 @@
// query mongodb for user objects with the email provided
// if user exists, return true
// if user does not exist, return false
import { createConnection } from './mongodb/connection';
export const checkUserExists = async ({ email }) => {
const client = await createConnection();
const db = client.db('auth');
const collection = db.collection('users');
const user = await collection.findOne({ email });
if (user) {
return true;
}
return false;
}

View File

@ -0,0 +1,7 @@
import { MongoClient } from 'mongodb';
export const createConnection = async () => {
const client = new MongoClient(process.env.MONGODB_URI, {});
await client.connect();
return client;
};

View File

@ -2,7 +2,7 @@ import React, { useState } from 'react';
import { Flex, Button, FormControl, Input, Spinner, Link } from '@chakra-ui/react';
import { useFetcher } from '@remix-run/react';
import { useLogin } from '~/api/v1/login/Login';
import { useApi } from '~/hooks/useApi';
export const Login = (props) => {
const [username, setUsername] = useState('');
@ -10,24 +10,22 @@ export const Login = (props) => {
const [loading, setLoading] = useState(false);
const api = useFetcher();
const api = useApi();
const { login } = useLogin();
const login = api.v1.login;
const handleLogin = (e) => {
const handleLogin = async (e) => {
e?.preventDefault();
setLoading(true);
login(username, password)
.then((response) => {
console.log('nik response 123', response);
setLoading(false);
})
.catch((error) => {
console.error('nik error 123', error);
setLoading(false);
});
const loginResp = await login({ username, password });
if (loginResp) {
console.log('nik loginResp', loginResp);
} else {
console.error('nik no loginResp', loginResp);
}
console.log('nik username', username);
console.log('nik password', password);

View File

@ -135,6 +135,12 @@ export const Nav = (props) => {
></Icon> */}
</Center>
)}
{/* TODO - Add conditional only show if loggedIn */}
<Center transform={['', 'scaleX(-100%)']} cursor="pointer">
<Link to="/logout">
<Icon size="12px" name="🗝️"></Icon>
</Link>
</Center>
<Center transform={['', 'scaleX(-100%)']} cursor="pointer">
<Link to="/login">
<Icon size="12px" name="🌈"></Icon>

View File

@ -1,24 +1,24 @@
import React from "react"
import React from 'react';
// import { Sticky, StickyContainer } from "react-sticky"
import Sticky from "react-sticky-el"
import { Box, Flex } from "@chakra-ui/react"
import { useLocation, useMatches } from "@remix-run/react"
import Sticky from 'react-sticky-el';
import { Box, Flex } from '@chakra-ui/react';
import { useLocation, useMatches } from '@remix-run/react';
import { Thingtime } from "./Thingtime"
import { useThingtime } from "./useThingtime"
import { Thingtime } from './Thingtime';
import { useThingtime } from './useThingtime';
export const ThingtimeURL = (props) => {
const { getThingtime } = useThingtime()
const { getThingtime } = useThingtime();
const { pathname } = useLocation()
const { pathname } = useLocation();
const matches = useMatches()
const matches = useMatches();
const location = React.useMemo(() => {
return matches[matches.length - 1]
}, [matches])
return matches[matches.length - 1];
}, [matches]);
const path = React.useMemo(() => {
console.log("ThingtimeURL location", location)
console.log('ThingtimeURL location', location);
// const sanitisation = ["/things", "/edit", "/editor", "/code", "/coder"]
@ -31,64 +31,66 @@ export const ThingtimeURL = (props) => {
// })
// strip the leading /path1/path2 path1 section from the path
const pathPartOne = location?.pathname?.split("/")[2]
const pathPartOne = location?.pathname?.split('/')[2];
const path = pathPartOne?.replace(/\//g, ".")
const path = pathPartOne?.replace(/\//g, '.');
return path || "thingtime"
}, [location])
console.log('nik path', path);
return path || 'thingtime';
}, [location]);
const thing = React.useMemo(() => {
// remove /things/ from path
const ret = getThingtime(path)
const ret = getThingtime(path);
return ret
}, [path, getThingtime])
return ret;
}, [path, getThingtime]);
const inEditorMode = React.useMemo(() => {
if (pathname.slice(0, 7) === "/editor") {
return true
if (pathname.slice(0, 7) === '/editor') {
return true;
}
return false
}, [pathname])
return false;
}, [pathname]);
const inEditMode = React.useMemo(() => {
if (pathname.slice(0, 5) === "/edit") {
return true
if (pathname.slice(0, 5) === '/edit') {
return true;
}
return false
}, [pathname])
return false;
}, [pathname]);
const containerRef = React.useRef(null)
const editorRef = React.useRef(null)
const containerRef = React.useRef(null);
const editorRef = React.useRef(null);
React.useEffect(() => {
const scrollListener = () => {
if (containerRef?.current?.getBoundingClientRect) {
const { top } = containerRef?.current?.getBoundingClientRect()
const { top } = containerRef?.current?.getBoundingClientRect();
editorRef.current.style.top = `${-top}px`
}
editorRef.current.style.top = `${-top}px`;
}
};
window.addEventListener("scroll", scrollListener)
window.addEventListener('scroll', scrollListener);
return () => {
window.removeEventListener("scroll", scrollListener)
}
}, [])
window.removeEventListener('scroll', scrollListener);
};
}, []);
return (
<Flex
ref={containerRef}
// position="sticky"
position="relative"
alignItems={inEditorMode ? "flex-start" : "center"}
alignItems={inEditorMode ? 'flex-start' : 'center'}
justifyContent="center"
// overflow="scroll"
// height="auto"
flexDirection={inEditorMode ? "row" : "column"}
flexDirection={inEditorMode ? 'row' : 'column'}
maxWidth="100%"
// maxHeight="100vh"
>
@ -109,18 +111,12 @@ export const ThingtimeURL = (props) => {
path={path}
thing={thing}
render
chakras={{ marginY: "200px" }}
chakras={{ marginY: '200px' }}
// width="600px"
></Thingtime>
</Box>
)}
<Thingtime
edit={inEditMode}
path={path}
thing={thing}
chakras={{ marginY: "200px" }}
width="600px"
></Thingtime>
<Thingtime edit={inEditMode} path={path} thing={thing} chakras={{ marginY: '200px' }} width="600px"></Thingtime>
</Flex>
)
}
);
};

View File

@ -29,7 +29,7 @@ export const RainbowText = (props) => {
userSelect="none"
outline="none"
contentEditable={props?.ce}
spellcheck="false"
spellCheck="false"
>
{props?.children}
</Text>

View File

@ -1,7 +1,7 @@
// modify window / globalThis to support any properties
// so there's no property does not exist on window typescript errors
// Path: app/src/global-types.d.ts
// Path: app/global-types.d.ts
declare global {
interface Window {
[key: string]: any;

View File

@ -0,0 +1,28 @@
import { useCallback, useEffect, useRef } from 'react';
import { useAsyncFetcher } from './useAsyncFetcher';
export function useApi() {
const asyncFetcher = useAsyncFetcher();
const v1 = {
login: useCallback(
async (args) => {
const { username, password } = args;
console.log('nik submitting with username', username);
console.log('nik submitting with password', password);
const ret = asyncFetcher.submit({ username, password }, { action: '/api/v1/login' });
return ret;
},
[asyncFetcher]
)
};
const ret = {
v1
};
return ret;
}

View File

@ -0,0 +1,44 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useFetcher } from '@remix-run/react';
export function useAsyncFetcher() {
let resolveRef = useRef<any>();
let promiseRef = useRef<Promise<any>>();
let fetcher = useFetcher();
if (!promiseRef.current) {
promiseRef.current = new Promise((resolve) => {
resolveRef.current = resolve;
});
}
const resetResolver = useCallback(() => {
promiseRef.current = new Promise((resolve) => {
resolveRef.current = resolve;
});
}, [promiseRef, resolveRef]);
const [defaultOpts, setDefaultOpts] = useState({
method: 'POST',
encType: 'application/json'
})
const submit = useCallback(
async (data, opts) => {
// @ts-ignore
fetcher.submit(data, { ...defaultOpts, ...opts });
return promiseRef.current;
},
[fetcher, promiseRef]
);
useEffect(() => {
if (fetcher.data && fetcher.state === 'idle') {
resolveRef.current(fetcher.data);
resetResolver();
}
}, [fetcher, resetResolver]);
return { ...fetcher, submit };
}

View File

@ -13,7 +13,7 @@ export const action = async ({ request }) => {
'Content-Type': 'application/json'
},
body: {
message: 'Hello, World! $ Action'
message: 'Hello Thingtime!'
},
cache: {
revalidate: 60

View File

@ -0,0 +1,37 @@
import { checkUserExists } from "~/api/utils/checkUserExists";
export default function Index() {
return <div>Login</div>;
}
export const action = async ({ request }) => {
console.log('nik request', request);
// get remix action body
const body = await request.json();
const { username, password } = body;
console.log('nik body', body)
console.log('nik username', username);
console.log('nik password', password);
return {
status: 200,
headers: {
'Content-Type': 'application/json'
},
body: {
message: 'Hello, World!',
username,
password
},
cache: {
revalidate: 60
}
};
};

View File

@ -0,0 +1,17 @@
import { Flex } from '@chakra-ui/react';
import React from 'react';
import { Login } from '~/components/Login/Login';
import { useApi } from '~/hooks/useApi';
export default function login() {
const template = (
<>
<Flex alignItems="center" justifyContent="center" width="100%" height="100%" minHeight="100vh">
<Login></Login>
</Flex>
</>
);
return template;
}

View File

@ -11,6 +11,7 @@
"dependencies": {
"@chakra-ui/react": "^2.7.1",
"@editorjs/editorjs": "^2.27.2",
"@emotion/react": "^11.11.4",
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-regular-svg-icons": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0",
@ -29,6 +30,7 @@
"gradient-path": "^2.3.0",
"isbot": "latest",
"lodash-es": "^4.17.21",
"mongodb": "^6.5.0",
"react": "^18.2.0",
"react-click-away-listener": "^2.2.3",
"react-contenteditable": "^3.3.7",
@ -59,6 +61,7 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-unused-imports": "^2.0.0",
"remix-flat-routes": "^0.6.4",
"typescript": "^4.9.3",
"vite": "^5.1.0",
"vite-tsconfig-paths": "^4.3.2"

View File

@ -1,14 +0,0 @@
/**
* @type {import('@remix-run/dev').AppConfig}
*/
module.exports = {
ignoredRouteFiles: ['**/.*'],
// future: {
// unstable_dev: true
// appServerPort: 3999
// }
appDirectory: 'src'
// assetsBuildDirectory: "public/build",
// serverBuildPath: "build/index.js",
// publicPath: "/build/",
};

View File

@ -1,28 +0,0 @@
import React from 'react';
import axios from 'axios';
import { useFetcher } from '@remix-run/react';
export const useLogin = () => {
const fetcher = useFetcher();
const login = React.useCallback(async (email, password) => {
try {
fetcher.submit(
{ email, password },
{
method: 'post',
action: '/api/v1/login'
}
);
// const response = await axios.post('/api/v1/login', { email, password });
// console.log('nik response', response.data);
// return response.data;
} catch (error) {
return error;
}
}, []);
return { login };
};

View File

@ -1,14 +0,0 @@
export const action = async ({ request }) => {
return {
status: 200,
headers: {
'Content-Type': 'application/json'
},
body: {
message: 'Hello, World!'
},
cache: {
revalidate: 60
}
};
};

View File

@ -1,21 +0,0 @@
import { Flex } from "@chakra-ui/react"
import { Login } from "~/components/Login/Login"
export default function login() {
const template = (
<>
<Flex
alignItems="center"
justifyContent="center"
width="100%"
height="100%"
minHeight="100vh"
>
<Login></Login>
</Flex>
</>
)
return template
}

View File

@ -15,7 +15,7 @@
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"~/*": ["./src/*"]
"~/*": ["./app/*"]
},
// Remix takes care of building everything in `remix build`.

View File

@ -2,11 +2,10 @@ import { vitePlugin as remix } from '@remix-run/dev';
import { installGlobals } from '@remix-run/node';
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
import { flatRoutes } from 'remix-flat-routes'
installGlobals();
// set app path to src/
export default defineConfig({
// define web socket port
@ -18,8 +17,13 @@ export default defineConfig({
},
plugins: [
remix({
routes: async defineRoutes => {
return flatRoutes('routes', defineRoutes)
},
// app path
appDirectory: 'src'
appDirectory: 'app'
}),
tsconfigPaths()
]