-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #47 from richardguerre/mobile-pwa
Add app/mobile-pwa + full integration into server
- Loading branch information
Showing
95 changed files
with
6,913 additions
and
2,095 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Logs | ||
logs | ||
*.log | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
pnpm-debug.log* | ||
lerna-debug.log* | ||
|
||
node_modules | ||
dist | ||
dist-ssr | ||
dev-dist | ||
*.local | ||
|
||
# Editor directories and files | ||
.vscode/* | ||
!.vscode/extensions.json | ||
.idea | ||
.DS_Store | ||
*.suo | ||
*.ntvs* | ||
*.njsproj | ||
*.sln | ||
*.sw? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# React + TypeScript + Vite | ||
|
||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. | ||
|
||
Currently, two official plugins are available: | ||
|
||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh | ||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh | ||
|
||
## Expanding the ESLint configuration | ||
|
||
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: | ||
|
||
- Configure the top-level `parserOptions` property like this: | ||
|
||
```js | ||
export default { | ||
// other rules... | ||
parserOptions: { | ||
ecmaVersion: 'latest', | ||
sourceType: 'module', | ||
project: ['./tsconfig.json', './tsconfig.node.json'], | ||
tsconfigRootDir: __dirname, | ||
}, | ||
} | ||
``` | ||
|
||
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` | ||
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` | ||
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
<!doctype html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<!-- Set theme color to background-200 in hex format. --> | ||
<meta name="theme-color" content="#e5e7eb" /> | ||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@unocss/reset/tailwind.min.css" /> | ||
<title>Flow</title> | ||
<!-- the follow 2 style tags are manual copies of what unocss generated in the Kanban View. It is missing some classes, but already prevents a lot of jerkiness coming from missing styles when loading the page. --> | ||
<style> | ||
.no-scrollbar::-webkit-scrollbar { | ||
display: none; | ||
} | ||
.no-scrollbar { | ||
-ms-overflow-style: none; /* IE and Edge */ | ||
scrollbar-width: none; /* Firefox */ | ||
} | ||
</style> | ||
<link rel="stylesheet" href="./uno.css" /> | ||
</head> | ||
<body class="bg-background-200 text-foreground-900"> | ||
<div id="root"> | ||
<!-- Initial loading indicator shown to avoid white screen. Copy-pasted from Loading.tsx. --> | ||
<div class="flex h-screen w-full items-center justify-center"> | ||
<div class="ring-primary-500/10 ring-5 rounded-full p-1"> | ||
<div class="relative h-24 w-24 overflow-clip rounded-full"> | ||
<svg | ||
width="246" | ||
height="113" | ||
viewBox="0 0 246 113" | ||
fill="none" | ||
xmlns="http://www.w3.org/2000/svg" | ||
class="absolute right-0 transform translate-y-24" | ||
> | ||
<path | ||
d="M41 14.6066C27.3331 14.6066 13.6669 0.0610556 0 0.0205112V113H246V0.0205112L245.998 0.0214844C232.332 8.44216 218.666 16.8624 205 14.6066C191.333 12.3498 177.667 -0.58383 164 0.0205112C150.333 0.624852 136.667 14.7672 123 14.6066C109.333 14.4451 95.6669 -0.0200333 82 0.0205112C68.3331 0.0610556 54.6669 14.6066 41 14.6066Z" | ||
fill="url(#bg-wave-gradient)" | ||
/> | ||
<defs> | ||
<linearGradient | ||
id="bg-wave-gradient" | ||
x1="0" | ||
y1="0" | ||
x2="0" | ||
y2="50" | ||
gradientUnits="userSpaceOnUse" | ||
> | ||
<stop stopColor="currentColor" class="text-primary-300" /> | ||
<stop offset="1" stopColor="currentColor" class="text-primary-400" /> | ||
</linearGradient> | ||
</defs> | ||
</svg> | ||
<svg | ||
width="246" | ||
height="113" | ||
viewBox="0 0 246 113" | ||
fill="none" | ||
xmlns="http://www.w3.org/2000/svg" | ||
class="absolute left-0 transform translate-y-24" | ||
> | ||
<path | ||
d="M41 14.6066C27.3331 14.6066 13.6669 0.0610556 0 0.0205112V113H246V0.0205112L245.998 0.0214844C232.332 8.44216 218.666 16.8624 205 14.6066C191.333 12.3498 177.667 -0.58383 164 0.0205112C150.333 0.624852 136.667 14.7672 123 14.6066C109.333 14.4451 95.6669 -0.0200333 82 0.0205112C68.3331 0.0610556 54.6669 14.6066 41 14.6066Z" | ||
fill="url(#fg-wave-gradient)" | ||
/> | ||
<defs> | ||
<linearGradient | ||
id="fg-wave-gradient" | ||
x1="0" | ||
y1="0" | ||
x2="0" | ||
y2="113" | ||
gradientUnits="userSpaceOnUse" | ||
> | ||
<stop stopColor="currentColor" class="text-primary-200" /> | ||
<stop offset="1" stopColor="currentColor" class="text-primary-400" /> | ||
</linearGradient> | ||
</defs> | ||
</svg> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
<script type="module" src="/src/index.tsx"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
{ | ||
"name": "@flowdev/mobile-pwa", | ||
"private": true, | ||
"version": "0.0.0", | ||
"type": "module", | ||
"scripts": { | ||
"setup:relay": "mkdir -p src/relay/__generated__", | ||
"relay": "cd ../.. && relay-compiler && echo '✅ Relay compiled!'", | ||
"relay:watch": "cd ../.. && relay-compiler --watch", | ||
"dev": "bun run relay && bunx --bun vite --port 5173", | ||
"print-schema": "cd ../server && DATABASE_URL=this_makes_it_run_wo_server_envs ORIGIN=same_as_db_url bun run print-schema", | ||
"prebuild": "bun run print-schema && bun run setup:relay && bun run relay", | ||
"build": "bunx vite -v && bunx rollup -v && NODE_ENV=production bunx --bun vite build", | ||
"preview": "vite preview", | ||
"env:example": "cp -n .env.example .env || true", | ||
"env:codespaces": "cp .env.codespaces .env" | ||
}, | ||
"dependencies": { | ||
"@flowdev/error-boundary": "workspace:*", | ||
"@flowdev/icons": "workspace:*", | ||
"@flowdev/plugin": "workspace:*", | ||
"@flowdev/relay": "workspace:*", | ||
"@flowdev/tiptap": "workspace:*", | ||
"@flowdev/ui": "workspace:*", | ||
"@flowdev/unocss": "workspace:*", | ||
"@unocss/runtime": "0.60.3", | ||
"dayjs": "1.11.7", | ||
"framer-motion": "10.12.10", | ||
"graphql-sse": "2.3.0", | ||
"react": "^18.3.1", | ||
"react-dom": "^18.3.1", | ||
"react-hook-form": "^7.52.1", | ||
"react-router-dom": "6.6.2", | ||
"react-sortablejs": "6.1.4", | ||
"sortablejs": "1.15.0" | ||
}, | ||
"devDependencies": { | ||
"@types/react": "^18.3.1", | ||
"@types/react-dom": "^18.3.0", | ||
"@vite-pwa/assets-generator": "^0.2.4", | ||
"@vitejs/plugin-react": "^4.2.1", | ||
"eslint": "^8.57.0", | ||
"sharp": "0.33.5", | ||
"sharp-ico": "0.1.5", | ||
"typescript": "^5.2.2", | ||
"vite": "^5.2.10", | ||
"vite-plugin-pwa": "^0.20.0", | ||
"workbox-core": "^7.1.0" | ||
} | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { defineConfig, minimal2023Preset as preset } from "@vite-pwa/assets-generator/config"; | ||
|
||
export default defineConfig({ | ||
headLinkOptions: { | ||
preset: "2023", | ||
}, | ||
preset, | ||
images: ["public/favicon.svg"], | ||
}); |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
import { useEffect, useState } from "react"; | ||
import { graphql, useFragment, useMutation } from "@flowdev/relay"; | ||
import { Day_day$key } from "@flowdev/mobile-pwa/relay/__generated__/Day_day.graphql"; | ||
import { TaskCard } from "./TaskCard"; | ||
import { dayjs } from "@flowdev/mobile-pwa/dayjs"; | ||
import { ReactSortable, Sortable } from "react-sortablejs"; | ||
import { DayContent_day$key } from "@flowdev/mobile-pwa/relay/__generated__/DayContent_day.graphql"; | ||
import { DayUpdateTaskDateMutation } from "@flowdev/mobile-pwa/relay/__generated__/DayUpdateTaskDateMutation.graphql"; | ||
import { environment } from "@flowdev/mobile-pwa/relay/environment"; | ||
|
||
type DayProps = { | ||
day: Day_day$key; | ||
}; | ||
|
||
export const Day = (props: DayProps) => { | ||
const day = useFragment( | ||
graphql` | ||
fragment Day_day on Day { | ||
date | ||
...DayContent_day | ||
} | ||
`, | ||
props.day, | ||
); | ||
|
||
return ( | ||
<div className="flex h-full flex-col shrink-0"> | ||
<div className="px-4 pt-4"> | ||
<div className="active:text-primary-600 text-3xl font-semibold"> | ||
{dayOfWeekArr[dayjs(day.date).day()]} | ||
</div> | ||
<div className="text-foreground-700 text-base">{dayjs(day.date).format("MMMM D")}</div> | ||
</div> | ||
<DayContent day={day} /> | ||
</div> | ||
); | ||
}; | ||
|
||
type UpdateTaskDateInfo = { | ||
movedTaskId: string; | ||
htmlParent: HTMLElement; | ||
} | null; | ||
|
||
type DayContentProps = { | ||
day: DayContent_day$key; | ||
}; | ||
|
||
export const DayContent = (props: DayContentProps) => { | ||
const day = useFragment( | ||
graphql` | ||
fragment DayContent_day on Day { | ||
date | ||
tasks { | ||
__typename | ||
id | ||
...TaskCard_task | ||
} | ||
} | ||
`, | ||
props.day, | ||
); | ||
|
||
const [udpateTaskDate] = useMutation<DayUpdateTaskDateMutation>(graphql` | ||
mutation DayUpdateTaskDateMutation($input: MutationUpdateTaskDateInput!) { | ||
updateTaskDate(input: $input) { | ||
...Day_day | ||
} | ||
} | ||
`); | ||
|
||
const [tasks, setTasks] = useState(structuredClone(Array.from(day.tasks))); | ||
const [updateTaskDateInfo, setUpdateTaskDateInfo] = useState<UpdateTaskDateInfo>(null); | ||
|
||
const handleTaskMove = (e: Sortable.SortableEvent) => { | ||
setUpdateTaskDateInfo({ | ||
htmlParent: e.to, | ||
movedTaskId: e.item.id, | ||
}); | ||
}; | ||
|
||
const setList = async (newList: typeof tasks) => { | ||
setTasks(newList.filter((task) => task.__typename === "Task")); // ignore item(s) that were dropped | ||
return; | ||
}; | ||
|
||
useEffect(() => { | ||
setTasks(structuredClone(Array.from(day.tasks))); | ||
}, [day.tasks]); | ||
|
||
useEffect(() => { | ||
if (!updateTaskDateInfo) return; // as the Lists component may be super-imposed on top of the Day component (in the IndexView), the Day component is still a drop target but needs to be ignored as the user is trying to move the task to the Lists component | ||
const newTasksOrder = Array.from(updateTaskDateInfo.htmlParent.children).map((task) => task.id); | ||
|
||
udpateTaskDate({ | ||
variables: { | ||
input: { | ||
id: updateTaskDateInfo.movedTaskId, | ||
date: updateTaskDateInfo.htmlParent.id, | ||
newTasksOrder, | ||
}, | ||
}, | ||
}); | ||
|
||
setUpdateTaskDateInfo(null); | ||
}, [updateTaskDateInfo]); | ||
|
||
return ( | ||
<ReactSortable | ||
id={day.date} | ||
className="no-scrollbar mt-4 flex flex-auto flex-col gap-4 overflow-y-scroll px-4 pb-4" | ||
list={tasks} | ||
setList={setList} | ||
animation={150} | ||
delayOnTouchOnly | ||
delay={100} | ||
group="shared" | ||
onEnd={handleTaskMove} | ||
> | ||
{tasks.map((task) => ( | ||
<TaskCard key={task.id} task={task} /> | ||
))} | ||
</ReactSortable> | ||
); | ||
}; | ||
|
||
const dayOfWeekArr = [ | ||
"Sunday", | ||
"Monday", | ||
"Tuesday", | ||
"Wednesday", | ||
"Thursday", | ||
"Friday", | ||
"Saturday", | ||
] as const; | ||
|
||
export const createVirtualTask = (props: { date: string }) => { | ||
const tempId = `Task_${Math.random()}`; | ||
environment.commitUpdate((store) => { | ||
const createdTask = store | ||
.create(tempId, "Task") | ||
.setValue(tempId, "id") | ||
.setValue("", "title") | ||
.setValue(new Date().toISOString(), "createdAt") | ||
.setValue("TODO", "status") | ||
.setValue(null, "completedAt") | ||
.setValue(props.date, "date") | ||
.setValue(null, "item") | ||
.setValue(null, "durationInMinutes") | ||
.setLinkedRecords([], "pluginDatas") | ||
.setLinkedRecords([], "subtasks"); | ||
|
||
const dayRecord = store.get(`Day_${props.date}`); | ||
const dayTasks = dayRecord?.getLinkedRecords("tasks"); | ||
// This adds the new task to the top of the list | ||
dayRecord?.setLinkedRecords([createdTask, ...(dayTasks ?? [])], "tasks"); | ||
}); | ||
}; | ||
|
||
export const deleteVirtualTask = (tempId: string) => { | ||
environment.commitUpdate((store) => { | ||
const task = store.get(tempId); | ||
if (!task) return; | ||
const day = store.get(`Day_${task.getValue("date")}`); | ||
const dayTasks = day?.getLinkedRecords("tasks"); | ||
day?.setLinkedRecords(dayTasks?.filter((task) => task.getDataID() !== tempId) ?? [], "tasks"); | ||
// store.delete(tempId); this causes a render issue so for now I'm not including it. | ||
}); | ||
}; |
Oops, something went wrong.