Compare commits
11 Commits
50fc385c2b
...
develop/de
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3f13cc41bf | ||
cb8643b405 | |||
1157bf0fda | |||
![]() |
2375c144a8 | ||
![]() |
24c62d940c | ||
![]() |
e38e775fee | ||
![]() |
db9d3fbffd | ||
![]() |
368af36acd | ||
d283b8274f | |||
7a8bd60185 | |||
a94ead44ba |
44
001_documentation/Requirements/REQ0021/index.md
Normal file
44
001_documentation/Requirements/REQ0021/index.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
tags: docker
|
||||
---
|
||||
|
||||
# description
|
||||
|
||||
docker and public endpoint
|
||||
|
||||
## strategy
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A["next build"]
|
||||
B["docker build"]
|
||||
A --> B --> deploy --> run
|
||||
```
|
||||
|
||||
1. build on develop machine
|
||||
1. docker build on develop machine (include source code)
|
||||
1. push image to production machine
|
||||
1. run image to production machine
|
||||
|
||||
## endpoint table
|
||||
|
||||
| function | address | | status |
|
||||
| -------- | ---------------------------------------------------------------------------- | ---- | ---------------------------------------------------- |
|
||||
| DB | [https://demo_ls_db.louislabs.com](https://demo_ls_db.louislabs.com) | 3013 |  |
|
||||
| cms | [https://demo_ls_cms.louislabs.com](https://demo_ls_cms.louislabs.com) | 3011 |  |
|
||||
| mobile | [https://demo_ls_mobile.louislabs.com](https://demo_ls_mobile.louislabs.com) | 3012 |  |
|
||||
| T.B.A. | T.B.A. | 3014 | |
|
||||
| T.B.A. | T.B.A. | 3015 | |
|
||||
| T.B.A. | T.B.A. | 3016 | |
|
||||
| T.B.A. | T.B.A. | 3017 | |
|
||||
| T.B.A. | T.B.A. | 3018 | |
|
||||
| T.B.A. | T.B.A. | 3019 | |
|
||||
| T.B.A. | T.B.A. | 3010 | |
|
||||
|
||||
## status panel
|
||||
|
||||
[https://status.iamon99.com/status/demo-ls](https://status.iamon99.com/status/demo-ls)
|
||||
|
||||
## TODO
|
||||
|
||||
pending separated ssl certificate, currently using shared affine.louislabs.com
|
@@ -22,3 +22,4 @@
|
||||
- [REQ0018: family photo of frameworks](./REQ0018/index.md)
|
||||
- [REQ0019: System architecture](./REQ0019/index.md)
|
||||
- [REQ0020: Mobile login flow](./REQ0020/index.md)
|
||||
- [REQ0021: description](./REQ0021/index.md)
|
||||
|
2
002_source/.gitignore
vendored
2
002_source/.gitignore
vendored
@@ -2,3 +2,5 @@
|
||||
**/*.log
|
||||
**/_archive
|
||||
**/_del
|
||||
|
||||
**/*copy*
|
||||
|
40
002_source/cms/.dockerignore
Normal file
40
002_source/cms/.dockerignore
Normal file
@@ -0,0 +1,40 @@
|
||||
# Ignore all files and directories by default
|
||||
|
||||
-
|
||||
|
||||
# Except for the following:
|
||||
|
||||
!.dockerignore
|
||||
!.gitignore
|
||||
!.env.example
|
||||
!.git/
|
||||
!.env
|
||||
!.vscode/
|
||||
!.idea/
|
||||
|
||||
# Node.js specific
|
||||
|
||||
node_modules/
|
||||
|
||||
# Build artifacts
|
||||
|
||||
dist/
|
||||
build/
|
||||
|
||||
# Logs
|
||||
|
||||
logs/
|
||||
\*.log
|
||||
|
||||
# Environment files
|
||||
|
||||
.env\*
|
||||
|
||||
# Cache files
|
||||
|
||||
\*.cache
|
||||
|
||||
# OS generated files
|
||||
|
||||
.DS_Store
|
||||
Thumbs.db
|
@@ -87,6 +87,7 @@ module.exports = {
|
||||
'**/*.bak',
|
||||
'**/*copy.*',
|
||||
'**/*copy*.*',
|
||||
'**/*draft*/**',
|
||||
//
|
||||
],
|
||||
overrides: [
|
||||
|
2
002_source/cms/.gitignore
vendored
2
002_source/cms/.gitignore
vendored
@@ -3,6 +3,8 @@
|
||||
**/*log
|
||||
**/*tmp
|
||||
|
||||
.pnpm-store
|
||||
|
||||
.env
|
||||
.env.production
|
||||
|
||||
|
17
002_source/cms/Dockerfile
Normal file
17
002_source/cms/Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
# Use official Node 18 base image
|
||||
FROM node:22-slim
|
||||
|
||||
# Install pnpm globally
|
||||
RUN npm install -g pnpm
|
||||
|
||||
# Copy your application code (optional, comment out if not needed)
|
||||
COPY . /app
|
||||
|
||||
RUN cd /app && pnpm i
|
||||
RUN cd /app && pnpm run build
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Default command (optional)
|
||||
CMD ["./scripts/docker/entrypoint.sh"]
|
@@ -10,7 +10,10 @@
|
||||
"dev": "next dev -H 0.0.0.0",
|
||||
"build": "next build",
|
||||
"build:w": "pnpx nodemon --ext ts,tsx,json,mjs,js,jsx --delay 15 --exec \"pnpm run build\"",
|
||||
"start": "next start",
|
||||
"docker:push": "docker push 192.168.10.61:5000/demo_ls_cms ",
|
||||
"docker:build": "docker build -t 192.168.10.61:5000/demo_ls_cms .",
|
||||
"build:docker": "pnpm run build && pnpm run docker:build && pnpm run docker:push",
|
||||
"start": "next start -H 0.0.0.0",
|
||||
"lint": "next lint --quiet",
|
||||
"lint:fix": "next lint --fix",
|
||||
"lint:w": "pnpx nodemon --ext ts,tsx,json,mjs,js,jsx --delay 5 --exec \"pnpm run lint\"",
|
||||
@@ -117,4 +120,4 @@
|
||||
"protobufjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,5 +3,11 @@
|
||||
set -ex
|
||||
|
||||
reset
|
||||
rm -rf .next
|
||||
|
||||
# rm -rf .next
|
||||
|
||||
# pnpm run typecheck
|
||||
# pnpm run lint
|
||||
|
||||
# echo "check ok"
|
||||
pnpm run build
|
||||
|
18
002_source/cms/scripts/docker/entrypoint.sh
Normal file → Executable file
18
002_source/cms/scripts/docker/entrypoint.sh
Normal file → Executable file
@@ -1,15 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# set -x
|
||||
set -x
|
||||
|
||||
# nvm use 20
|
||||
nvm alias default 18
|
||||
nvm use default
|
||||
node -v
|
||||
while true; do
|
||||
pnpm run start
|
||||
|
||||
npm i -D
|
||||
echo "command restarted"
|
||||
|
||||
npm run dev
|
||||
sleep 5
|
||||
|
||||
done
|
||||
|
||||
# pnpm i -D
|
||||
|
||||
# pnpm run start
|
||||
|
||||
# while true; do
|
||||
# if [ "$NODE_ENV" = "development" ]; then
|
||||
|
@@ -1,8 +1,26 @@
|
||||
// src/app/dashboard/students/page.tsx
|
||||
|
||||
'use client';
|
||||
|
||||
import type { Student } from '@/db/Students/type.d';
|
||||
|
||||
import { dayjs } from '@/lib/dayjs';
|
||||
|
||||
const sampleBillingAddress = {
|
||||
city: 'string',
|
||||
country: 'string',
|
||||
line1: 'string',
|
||||
line2: 'string',
|
||||
state: 'string',
|
||||
zipCode: 'string',
|
||||
//
|
||||
id: 'string',
|
||||
collectionId: 'string',
|
||||
collectionName: 'string',
|
||||
updated: 'string',
|
||||
created: 'string',
|
||||
};
|
||||
|
||||
export const SampleStudents = [
|
||||
{
|
||||
id: 'STU-005',
|
||||
@@ -13,6 +31,12 @@ export const SampleStudents = [
|
||||
quota: 50,
|
||||
status: 'active',
|
||||
createdAt: dayjs().subtract(1, 'hour').toDate(),
|
||||
billingAddress: sampleBillingAddress,
|
||||
state: 'active',
|
||||
timezone: '',
|
||||
language: '',
|
||||
currency: '',
|
||||
collectionId: '',
|
||||
},
|
||||
{
|
||||
id: 'STU-004',
|
||||
@@ -23,6 +47,12 @@ export const SampleStudents = [
|
||||
quota: 100,
|
||||
status: 'active',
|
||||
createdAt: dayjs().subtract(3, 'hour').toDate(),
|
||||
billingAddress: sampleBillingAddress,
|
||||
state: 'active',
|
||||
timezone: '',
|
||||
language: '',
|
||||
currency: '',
|
||||
collectionId: '',
|
||||
},
|
||||
{
|
||||
id: 'STU-003',
|
||||
@@ -33,6 +63,12 @@ export const SampleStudents = [
|
||||
quota: 10,
|
||||
status: 'blocked',
|
||||
createdAt: dayjs().subtract(1, 'hour').subtract(1, 'day').toDate(),
|
||||
billingAddress: sampleBillingAddress,
|
||||
state: 'active',
|
||||
timezone: '',
|
||||
language: '',
|
||||
currency: '',
|
||||
collectionId: '',
|
||||
},
|
||||
{
|
||||
id: 'STU-002',
|
||||
@@ -43,6 +79,12 @@ export const SampleStudents = [
|
||||
quota: 0,
|
||||
status: 'pending',
|
||||
createdAt: dayjs().subtract(7, 'hour').subtract(1, 'day').toDate(),
|
||||
billingAddress: sampleBillingAddress,
|
||||
state: 'active',
|
||||
timezone: '',
|
||||
language: '',
|
||||
currency: '',
|
||||
collectionId: '',
|
||||
},
|
||||
{
|
||||
id: 'STU-001',
|
||||
@@ -53,5 +95,11 @@ export const SampleStudents = [
|
||||
quota: 50,
|
||||
status: 'active',
|
||||
createdAt: dayjs().subtract(2, 'hour').subtract(2, 'day').toDate(),
|
||||
billingAddress: sampleBillingAddress,
|
||||
state: 'active',
|
||||
timezone: '',
|
||||
language: '',
|
||||
currency: '',
|
||||
collectionId: '',
|
||||
},
|
||||
] satisfies Student[];
|
||||
|
@@ -120,12 +120,12 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
||||
tempFilter.push(`phone ~ "%${phone}%"`);
|
||||
}
|
||||
|
||||
let preFinalListOption = { filter: '' };
|
||||
let preFinalListOption = { filter: '', sort: '' };
|
||||
if (tempFilter.length > 0) {
|
||||
preFinalListOption = { filter: tempFilter.join(' && ') };
|
||||
preFinalListOption.filter = tempFilter.join(' && ');
|
||||
}
|
||||
if (tempSortDir.length > 0) {
|
||||
preFinalListOption = { ...preFinalListOption, sort: tempSortDir };
|
||||
preFinalListOption.sort = tempSortDir;
|
||||
}
|
||||
setListOption(preFinalListOption);
|
||||
}, [sortDir, email, phone, state]);
|
||||
|
@@ -122,12 +122,12 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
||||
tempFilter.push(`phone ~ "%${phone}%"`);
|
||||
}
|
||||
|
||||
let preFinalListOption = { filter: '' };
|
||||
const preFinalListOption = { filter: '', sort: '' };
|
||||
if (tempFilter.length > 0) {
|
||||
preFinalListOption = { filter: tempFilter.join(' && ') };
|
||||
preFinalListOption.filter = tempFilter.join(' && ');
|
||||
}
|
||||
if (tempSortDir.length > 0) {
|
||||
preFinalListOption = { ...preFinalListOption, sort: tempSortDir };
|
||||
preFinalListOption.sort = tempSortDir;
|
||||
}
|
||||
setListOption(preFinalListOption);
|
||||
}, [sortDir, email, phone, state]);
|
||||
|
@@ -22,7 +22,7 @@ import isDevelopment from '@/lib/check-is-development';
|
||||
import { logger } from '@/lib/default-logger';
|
||||
import { pb } from '@/lib/pb';
|
||||
import ErrorDisplay from '@/components/dashboard/error';
|
||||
import { defaultUserMeta } from '@/components/dashboard/user_meta/_constants';
|
||||
import { defaultDBUserMeta, defaultUserMeta } from '@/components/dashboard/user_meta/_constants';
|
||||
import type { UserMeta } from '@/components/dashboard/user_meta/type.d';
|
||||
import { UserMetasFilters } from '@/components/dashboard/user_meta/user-metas-filters';
|
||||
import { UserMetasPagination } from '@/components/dashboard/user_meta/user-metas-pagination';
|
||||
@@ -62,7 +62,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
||||
.getList(currentPage + 1, rowsPerPage, listOption);
|
||||
const { items, totalItems } = models;
|
||||
const tempUserMeta: UserMeta[] = items.map((lt) => {
|
||||
return { ...defaultUserMeta, ...lt };
|
||||
return lt as unknown as UserMeta;
|
||||
});
|
||||
|
||||
setUserMetaData(tempUserMeta);
|
||||
@@ -115,12 +115,12 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
||||
tempFilter.push(`phone ~ "%${phone}%"`);
|
||||
}
|
||||
|
||||
let preFinalListOption = { filter: '' };
|
||||
const preFinalListOption = { filter: '', sort: '' };
|
||||
if (tempFilter.length > 0) {
|
||||
preFinalListOption = { filter: tempFilter.join(' && ') };
|
||||
preFinalListOption.filter = tempFilter.join(' && ');
|
||||
}
|
||||
if (tempSortDir.length > 0) {
|
||||
preFinalListOption = { ...preFinalListOption, sort: tempSortDir };
|
||||
preFinalListOption.sort = tempSortDir;
|
||||
}
|
||||
setListOption(preFinalListOption);
|
||||
}, [sortDir, email, phone, state]);
|
||||
|
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import type { UserMeta } from '@/db/UserMetas/type';
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
import Card from '@mui/material/Card';
|
||||
import CardHeader from '@mui/material/CardHeader';
|
||||
@@ -13,8 +14,9 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { PropertyItem } from '@/components/core/property-item';
|
||||
import { PropertyList } from '@/components/core/property-list';
|
||||
|
||||
// import { CrCategory } from '@/components/dashboard/cr/categories/type';
|
||||
import type { UserMeta } from '@/components/dashboard/user_meta/type_move.d';
|
||||
// import type { UserMeta } from '@/components/dashboard/user_meta/type_move.d';
|
||||
|
||||
export default function BasicDetailCard({
|
||||
userMeta,
|
||||
|
@@ -12,6 +12,8 @@ import { SampleNotifications } from '@/app/dashboard/Sample/Notifications';
|
||||
import SamplePaymentCard from '@/app/dashboard/Sample/PaymentCard';
|
||||
import SampleSecurityCard from '@/app/dashboard/Sample/SecurityCard';
|
||||
import { COL_USER_METAS } from '@/constants';
|
||||
import { defaultBillingAddress } from '@/db/billingAddress/constant';
|
||||
import type { UserMeta } from '@/db/UserMetas/type';
|
||||
import Box from '@mui/material/Box';
|
||||
import Link from '@mui/material/Link';
|
||||
import Stack from '@mui/material/Stack';
|
||||
@@ -20,7 +22,8 @@ import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/Arrow
|
||||
import type { RecordModel } from 'pocketbase';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { config } from '@/config';
|
||||
// TODO: remove me
|
||||
// import { config } from '@/config';
|
||||
import { paths } from '@/paths';
|
||||
import { logger } from '@/lib/default-logger';
|
||||
import { pb } from '@/lib/pb';
|
||||
@@ -28,7 +31,7 @@ import { toast } from '@/components/core/toaster';
|
||||
import ErrorDisplay from '@/components/dashboard/error';
|
||||
import { defaultUserMeta } from '@/components/dashboard/user_meta/_constants';
|
||||
import { Notifications } from '@/components/dashboard/user_meta/notifications';
|
||||
import type { UserMeta } from '@/components/dashboard/user_meta/type_move.d';
|
||||
// import type { UserMeta } from '@/components/dashboard/user_meta/type_move.d';
|
||||
import FormLoading from '@/components/loading';
|
||||
|
||||
import BasicDetailCard from './BasicDetailCard';
|
||||
@@ -109,7 +112,7 @@ export default function Page(): React.JSX.Element {
|
||||
spacing={3}
|
||||
sx={{ alignItems: 'flex-start' }}
|
||||
>
|
||||
<TitleCard teacherRecord={userMeta} />
|
||||
<TitleCard teacherRecord={{ ...userMeta, billingAddress: defaultBillingAddress }} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Grid
|
||||
|
@@ -145,7 +145,7 @@ export default function Page(): React.JSX.Element {
|
||||
spacing={2}
|
||||
sx={{ alignItems: 'center', flexWrap: 'wrap' }}
|
||||
>
|
||||
<Typography variant="h4">{showLessonCategory.name}</Typography>
|
||||
<Typography variant="h4">{showLessonCategory.word}</Typography>
|
||||
<Chip
|
||||
icon={
|
||||
<CheckCircleIcon
|
||||
@@ -221,7 +221,7 @@ export default function Page(): React.JSX.Element {
|
||||
),
|
||||
},
|
||||
{ key: 'word_c', value: showLessonCategory.word_c },
|
||||
{ key: 'Pos', value: showLessonCategory.pos },
|
||||
{ key: 'Pos', value: showLessonCategory.id },
|
||||
{
|
||||
key: 'Visible',
|
||||
value: (
|
||||
|
@@ -31,8 +31,8 @@ import { useUser } from '@/hooks/use-user';
|
||||
import { DynamicLogo } from '@/components/core/logo';
|
||||
import { toast } from '@/components/core/toaster';
|
||||
|
||||
import { oAuthProviders } from '../o-auth-providers.tsx';
|
||||
import type { OAuthProvider } from '../OAuthProvider';
|
||||
import { oAuthProviders } from '../oAuthProviders';
|
||||
|
||||
// interface OAuthProvider {
|
||||
// id: 'google' | 'discord' | 'github';
|
||||
|
@@ -30,8 +30,8 @@ import { useUser } from '@/hooks/use-user';
|
||||
import { DynamicLogo } from '@/components/core/logo';
|
||||
import { toast } from '@/components/core/toaster';
|
||||
|
||||
import { oAuthProviders } from '../o-auth-providers.tsx';
|
||||
import type { OAuthProvider } from '../OAuthProvider';
|
||||
import { oAuthProviders } from '../oAuthProviders';
|
||||
|
||||
export function SignUpForm(): React.JSX.Element {
|
||||
const router = useRouter();
|
||||
@@ -195,7 +195,7 @@ export function SignUpForm(): React.JSX.Element {
|
||||
<InputLabel>{t('email-address')}:</InputLabel>
|
||||
<OutlinedInput
|
||||
{...field}
|
||||
placeholder={`${t('e.g.') } name@example.com`}
|
||||
placeholder={`${t('e.g.')} name@example.com`}
|
||||
type="email"
|
||||
disabled={isPending}
|
||||
/>
|
||||
|
@@ -16,16 +16,36 @@ const user = {
|
||||
name: 'Sofia Rivers',
|
||||
avatar: '/assets/avatar.png',
|
||||
email: 'sofia@devias.io',
|
||||
collectionId: '123321',
|
||||
} satisfies User;
|
||||
|
||||
export function CommentAdd(): React.JSX.Element {
|
||||
return (
|
||||
<Stack direction="row" spacing={2} sx={{ alignItems: 'flex-start' }}>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={2}
|
||||
sx={{ alignItems: 'flex-start' }}
|
||||
>
|
||||
<Avatar src={user.avatar} />
|
||||
<Stack spacing={3} sx={{ flex: '1 1 auto' }}>
|
||||
<OutlinedInput multiline placeholder="Add a comment" rows={3} />
|
||||
<Stack direction="row" spacing={3} sx={{ alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
|
||||
<Stack
|
||||
spacing={3}
|
||||
sx={{ flex: '1 1 auto' }}
|
||||
>
|
||||
<OutlinedInput
|
||||
multiline
|
||||
placeholder="Add a comment"
|
||||
rows={3}
|
||||
/>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={3}
|
||||
sx={{ alignItems: 'center', justifyContent: 'space-between' }}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={1}
|
||||
sx={{ alignItems: 'center' }}
|
||||
>
|
||||
<IconButton sx={{ display: { sm: 'none' } }}>
|
||||
<PlusIcon />
|
||||
</IconButton>
|
||||
|
@@ -19,6 +19,7 @@ const user = {
|
||||
name: 'Sofia Rivers',
|
||||
avatar: '/assets/avatar.png',
|
||||
email: 'sofia@devias.io',
|
||||
collectionId: '123321',
|
||||
} satisfies User;
|
||||
|
||||
export interface MessageAddProps {
|
||||
@@ -57,8 +58,15 @@ export function MessageAdd({ disabled = false, onSend }: MessageAddProps): React
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flex: '0 0 auto', px: 3, py: 1 }}>
|
||||
<Avatar src={user.avatar} sx={{ display: { xs: 'none', sm: 'inline' } }} />
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={2}
|
||||
sx={{ alignItems: 'center', flex: '0 0 auto', px: 3, py: 1 }}
|
||||
>
|
||||
<Avatar
|
||||
src={user.avatar}
|
||||
sx={{ display: { xs: 'none', sm: 'inline' } }}
|
||||
/>
|
||||
<OutlinedInput
|
||||
disabled={disabled}
|
||||
onChange={handleChange}
|
||||
@@ -67,7 +75,11 @@ export function MessageAdd({ disabled = false, onSend }: MessageAddProps): React
|
||||
sx={{ flex: '1 1 auto' }}
|
||||
value={content}
|
||||
/>
|
||||
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={1}
|
||||
sx={{ alignItems: 'center' }}
|
||||
>
|
||||
<Tooltip title="Send">
|
||||
<span>
|
||||
<IconButton
|
||||
@@ -84,24 +96,40 @@ export function MessageAdd({ disabled = false, onSend }: MessageAddProps): React
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Stack direction="row" spacing={1} sx={{ display: { xs: 'none', sm: 'flex' } }}>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={1}
|
||||
sx={{ display: { xs: 'none', sm: 'flex' } }}
|
||||
>
|
||||
<Tooltip title="Attach photo">
|
||||
<span>
|
||||
<IconButton disabled={disabled} edge="end" onClick={handleAttach}>
|
||||
<IconButton
|
||||
disabled={disabled}
|
||||
edge="end"
|
||||
onClick={handleAttach}
|
||||
>
|
||||
<CameraIcon />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip title="Attach file">
|
||||
<span>
|
||||
<IconButton disabled={disabled} edge="end" onClick={handleAttach}>
|
||||
<IconButton
|
||||
disabled={disabled}
|
||||
edge="end"
|
||||
onClick={handleAttach}
|
||||
>
|
||||
<PaperclipIcon />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<input hidden ref={fileInputRef} type="file" />
|
||||
<input
|
||||
hidden
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@ const user = {
|
||||
name: 'Sofia Rivers',
|
||||
avatar: '/assets/avatar.png',
|
||||
email: 'sofia@devias.io',
|
||||
collectionId: '123321',
|
||||
} satisfies User;
|
||||
|
||||
export interface MessageBoxProps {
|
||||
@@ -38,8 +39,14 @@ export function MessageBox({ message }: MessageBoxProps): React.JSX.Element {
|
||||
mr: position === 'left' ? 'auto' : 0,
|
||||
}}
|
||||
>
|
||||
<Avatar src={message.author.avatar} sx={{ '--Avatar-size': '32px' }} />
|
||||
<Stack spacing={1} sx={{ flex: '1 1 auto' }}>
|
||||
<Avatar
|
||||
src={message.author.avatar}
|
||||
sx={{ '--Avatar-size': '32px' }}
|
||||
/>
|
||||
<Stack
|
||||
spacing={1}
|
||||
sx={{ flex: '1 1 auto' }}
|
||||
>
|
||||
<Card
|
||||
sx={{
|
||||
px: 2,
|
||||
@@ -52,7 +59,11 @@ export function MessageBox({ message }: MessageBoxProps): React.JSX.Element {
|
||||
>
|
||||
<Stack spacing={1}>
|
||||
<div>
|
||||
<Link color="inherit" sx={{ cursor: 'pointer' }} variant="subtitle2">
|
||||
<Link
|
||||
color="inherit"
|
||||
sx={{ cursor: 'pointer' }}
|
||||
variant="subtitle2"
|
||||
>
|
||||
{message.author.name}
|
||||
</Link>
|
||||
</div>
|
||||
@@ -66,14 +77,21 @@ export function MessageBox({ message }: MessageBoxProps): React.JSX.Element {
|
||||
/>
|
||||
) : null}
|
||||
{message.type === 'text' ? (
|
||||
<Typography color="inherit" variant="body1">
|
||||
<Typography
|
||||
color="inherit"
|
||||
variant="body1"
|
||||
>
|
||||
{message.content}
|
||||
</Typography>
|
||||
) : null}
|
||||
</Stack>
|
||||
</Card>
|
||||
<Box sx={{ display: 'flex', justifyContent: position === 'right' ? 'flex-end' : 'flex-start', px: 2 }}>
|
||||
<Typography color="text.secondary" noWrap variant="caption">
|
||||
<Typography
|
||||
color="text.secondary"
|
||||
noWrap
|
||||
variant="caption"
|
||||
>
|
||||
{dayjs(message.createdAt).fromNow()}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
@@ -3,6 +3,7 @@
|
||||
import * as React from 'react';
|
||||
import RouterLink from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { MarkOneAsRead } from '@/db/Notifications/mark-one-as-read';
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
import Badge from '@mui/material/Badge';
|
||||
import Box from '@mui/material/Box';
|
||||
@@ -25,6 +26,7 @@ import type { NavItemConfig } from '@/types/nav';
|
||||
import type { NavColor } from '@/types/settings';
|
||||
import type { User } from '@/types/user';
|
||||
import { paths } from '@/paths';
|
||||
import { logger } from '@/lib/default-logger';
|
||||
import { isNavItemActive } from '@/lib/is-nav-item-active';
|
||||
import { useDialog } from '@/hooks/use-dialog';
|
||||
import { usePopover } from '@/hooks/use-popover';
|
||||
@@ -33,6 +35,7 @@ import { Dropdown } from '@/components/core/dropdown/dropdown';
|
||||
import { DropdownPopover } from '@/components/core/dropdown/dropdown-popover';
|
||||
import { DropdownTrigger } from '@/components/core/dropdown/dropdown-trigger';
|
||||
import { Logo } from '@/components/core/logo';
|
||||
import { toast } from '@/components/core/toaster';
|
||||
import { SearchDialog } from '@/components/dashboard/layout/search-dialog';
|
||||
import type { ColorScheme } from '@/styles/theme/types';
|
||||
|
||||
@@ -183,6 +186,29 @@ function SearchButton(): React.JSX.Element {
|
||||
|
||||
function NotificationsButton(): React.JSX.Element {
|
||||
const popover = usePopover<HTMLButtonElement>();
|
||||
const [listLength, setListLength] = React.useState<number>(0);
|
||||
|
||||
function handleMarkAllAsRead(): void {
|
||||
// try {
|
||||
// await MarkOneAsRead(id);
|
||||
// toast.success('Notification marked as read');
|
||||
// } catch (error) {
|
||||
// logger.debug(error);
|
||||
// toast.error('Something went wrong');
|
||||
// }
|
||||
}
|
||||
|
||||
function handleRemoveOne(id: string, cb: () => void): void {
|
||||
MarkOneAsRead(id)
|
||||
.then(() => {
|
||||
toast.success('Notification marked as read');
|
||||
cb();
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.debug(error);
|
||||
toast.error('Something went wrong');
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
@@ -190,15 +216,14 @@ function NotificationsButton(): React.JSX.Element {
|
||||
<Badge
|
||||
color="error"
|
||||
sx={{
|
||||
'& .MuiBadge-dot': {
|
||||
borderRadius: '50%',
|
||||
'& .MuiBadge-badge': {
|
||||
height: '20px',
|
||||
width: '20px',
|
||||
right: '6px',
|
||||
top: '6px',
|
||||
height: '10px',
|
||||
width: '10px',
|
||||
},
|
||||
}}
|
||||
variant="dot"
|
||||
badgeContent={listLength}
|
||||
>
|
||||
<IconButton
|
||||
onClick={popover.handleOpen}
|
||||
@@ -212,6 +237,9 @@ function NotificationsButton(): React.JSX.Element {
|
||||
anchorEl={popover.anchorRef.current}
|
||||
onClose={popover.handleClose}
|
||||
open={popover.open}
|
||||
onMarkAllAsRead={handleMarkAllAsRead}
|
||||
onRemoveOne={handleRemoveOne}
|
||||
setListLength={setListLength}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
@@ -1,6 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import type { Notification } from '@/db/Notifications/type';
|
||||
|
||||
import { dayjs } from '@/lib/dayjs';
|
||||
|
||||
// import type { Notification } from './type.d.tsx';
|
||||
|
||||
export const SampleNotifications = [
|
||||
@@ -11,6 +14,11 @@ export const SampleNotifications = [
|
||||
type: 'new_job',
|
||||
author: { id: '0001', collectionId: '0001', name: 'Jie Yan', avatar: '/assets/avatar-8.png' },
|
||||
job: { title: 'Remote React / React Native Developer' },
|
||||
//
|
||||
created: '',
|
||||
link: '',
|
||||
NOTI_ID: '',
|
||||
updated: '',
|
||||
},
|
||||
{
|
||||
id: 'EV-003',
|
||||
@@ -19,6 +27,11 @@ export const SampleNotifications = [
|
||||
type: 'new_job',
|
||||
author: { id: '0001', collectionId: '0001', name: 'Fran Perez', avatar: '/assets/avatar-5.png' },
|
||||
job: { title: 'Senior Golang Backend Engineer' },
|
||||
//
|
||||
created: '',
|
||||
link: '',
|
||||
NOTI_ID: '',
|
||||
updated: '',
|
||||
},
|
||||
{
|
||||
id: 'EV-002',
|
||||
@@ -27,6 +40,11 @@ export const SampleNotifications = [
|
||||
type: 'new_feature',
|
||||
author: { id: '0001', collectionId: '0001', name: 'Fran Perez', avatar: '/assets/avatar-5.png' },
|
||||
description: 'Logistics management is now available',
|
||||
//
|
||||
created: '',
|
||||
link: '',
|
||||
NOTI_ID: '',
|
||||
updated: '',
|
||||
},
|
||||
{
|
||||
id: 'EV-001',
|
||||
@@ -35,5 +53,10 @@ export const SampleNotifications = [
|
||||
type: 'new_company',
|
||||
author: { id: '0001', collectionId: '002', name: 'Jie Yan', avatar: '/assets/avatar-8.png' },
|
||||
company: { name: 'Stripe' },
|
||||
//
|
||||
created: '',
|
||||
link: '',
|
||||
NOTI_ID: '',
|
||||
updated: '',
|
||||
},
|
||||
] satisfies Notification[];
|
||||
|
@@ -6,11 +6,12 @@ import Badge from '@mui/material/Badge';
|
||||
import Box from '@mui/material/Box';
|
||||
|
||||
import type { User } from '@/types/user';
|
||||
import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
|
||||
import { usePopover } from '@/hooks/use-popover';
|
||||
import { useUser } from '@/hooks/use-user';
|
||||
|
||||
import { UserPopover } from '../../user-popover/user-popover';
|
||||
import { useUser } from '@/hooks/use-user';
|
||||
import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
|
||||
|
||||
// import { NotificationsButton } from './notifications-button';
|
||||
|
||||
// TODO:remove me
|
||||
@@ -50,7 +51,7 @@ export function UserButton(): React.JSX.Element {
|
||||
}}
|
||||
variant="dot"
|
||||
>
|
||||
<Avatar src={getImageUrlFromFile(user.collectionId, user.id, user.avatar)} />
|
||||
<Avatar src={getImageUrlFromFile(user.collectionId || '', user.id, user.avatar)} />
|
||||
</Badge>
|
||||
</Box>
|
||||
<UserPopover
|
||||
|
@@ -240,7 +240,7 @@ export function AccountDetails(): React.JSX.Element {
|
||||
</Stack>
|
||||
</Box>
|
||||
<Avatar
|
||||
src={getImageUrlFromFile(user.collectionId, user.id, user.avatar)}
|
||||
src={getImageUrlFromFile(user.collectionId || '', user.id, user.avatar)}
|
||||
sx={{ '--Avatar-size': '100px' }}
|
||||
/>
|
||||
</Box>
|
||||
|
@@ -21,7 +21,7 @@ import FormLoading from '@/components/loading';
|
||||
|
||||
import ErrorDisplay from '../../error';
|
||||
import { NavItem } from './nav-item';
|
||||
import { navItems } from './navItems';
|
||||
import { navItems } from './nav-items';
|
||||
|
||||
export function SideNav(): React.JSX.Element {
|
||||
const router = useRouter();
|
||||
@@ -99,7 +99,7 @@ export function SideNav(): React.JSX.Element {
|
||||
spacing={2}
|
||||
sx={{ alignItems: 'center' }}
|
||||
>
|
||||
<Avatar src={getImageUrlFromFile(user.collectionId, user.id, user.avatar)}>{user.name}</Avatar>
|
||||
<Avatar src={getImageUrlFromFile(user.collectionId || '', user.id, user.avatar)}>{user.name}</Avatar>
|
||||
<div>
|
||||
<Typography variant="subtitle1">{user.name}</Typography>
|
||||
<Typography
|
||||
|
@@ -18,7 +18,7 @@ import { UsersThree as UsersThreeIcon } from '@phosphor-icons/react/dist/ssr/Use
|
||||
import type { NavItemConfig } from '@/types/nav';
|
||||
import { isNavItemActive } from '@/lib/is-nav-item-active';
|
||||
|
||||
import { navItems } from './navItems';
|
||||
import { navItems } from './nav-items';
|
||||
|
||||
const icons = {
|
||||
'credit-card': CreditCardIcon,
|
||||
|
@@ -2,7 +2,10 @@
|
||||
// default variable value for customer
|
||||
// empty valur for customer
|
||||
|
||||
import { defaultBillingAddress } from '@/db/billingAddress/constant';
|
||||
|
||||
import { dayjs } from '@/lib/dayjs';
|
||||
|
||||
import type { Student } from './type.d';
|
||||
|
||||
export const defaultStudent: Student = {
|
||||
@@ -14,6 +17,12 @@ export const defaultStudent: Student = {
|
||||
quota: 0,
|
||||
status: 'pending',
|
||||
createdAt: dayjs().toDate(),
|
||||
billingAddress: defaultBillingAddress,
|
||||
state: 'pending',
|
||||
timezone: '',
|
||||
language: '',
|
||||
currency: '',
|
||||
collectionId: '',
|
||||
};
|
||||
|
||||
export const emptyLpCategory: Student = {
|
||||
|
@@ -119,6 +119,7 @@ export interface StudentFiltersProps {
|
||||
filters?: Filters;
|
||||
sortDir?: SortDir;
|
||||
fullData: Student[];
|
||||
//
|
||||
}
|
||||
|
||||
// RULES: available filter options for student data
|
||||
@@ -126,4 +127,5 @@ export interface Filters {
|
||||
email?: string;
|
||||
phone?: string;
|
||||
state?: string;
|
||||
status?: string;
|
||||
}
|
||||
|
@@ -2,7 +2,10 @@
|
||||
// default variable value for customer
|
||||
// empty valur for customer
|
||||
|
||||
import { defaultBillingAddress } from '@/db/billingAddress/constant';
|
||||
|
||||
import { dayjs } from '@/lib/dayjs';
|
||||
|
||||
import type { Teacher } from './type.d';
|
||||
|
||||
export const defaultTeacher: Teacher = {
|
||||
@@ -14,6 +17,12 @@ export const defaultTeacher: Teacher = {
|
||||
quota: 0,
|
||||
status: 'pending',
|
||||
createdAt: dayjs().toDate(),
|
||||
billingAddress: defaultBillingAddress,
|
||||
state: 'pending',
|
||||
// timezone: '',
|
||||
// language: '',
|
||||
// currency: '',
|
||||
collectionId: '',
|
||||
};
|
||||
|
||||
export const emptyLpCategory: Teacher = {
|
||||
|
@@ -181,7 +181,9 @@ export function TeacherEditForm(): React.JSX.Element {
|
||||
//
|
||||
reset({ ...defaultValues, ...result });
|
||||
|
||||
setBillingAddressId(result.billingAddress.id);
|
||||
if (result?.billingAddress !== undefined) {
|
||||
setBillingAddressId(result.billingAddress.id);
|
||||
}
|
||||
|
||||
if (result.avatar) {
|
||||
const fetchResult = await fetch(getImageUrlFromFile(result.collectionId, result.id, result.avatar));
|
||||
|
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { BillingAddress } from '@/db/billingAddress/type';
|
||||
|
||||
// RULES: sorting direction for teacher lists
|
||||
export type SortDir = 'asc' | 'desc';
|
||||
|
||||
@@ -14,7 +16,10 @@ export interface Teacher {
|
||||
email: string;
|
||||
phone?: string;
|
||||
quota: number;
|
||||
|
||||
//
|
||||
// billingAddress: BillingAddress[] | [];
|
||||
billingAddress: BillingAddress | Record<string, never>;
|
||||
//
|
||||
// status is obsoleted, replace by state
|
||||
status: 'pending' | 'active' | 'blocked';
|
||||
state: 'pending' | 'active' | 'blocked';
|
||||
@@ -44,10 +49,13 @@ export interface CreateFormProps {
|
||||
timezone: string;
|
||||
language: string;
|
||||
currency: string;
|
||||
// NOTE: fix this to file, change outside world
|
||||
avatar?: File | null;
|
||||
// quota?: number;
|
||||
state?: 'pending' | 'active' | 'blocked';
|
||||
meta: Record<string, null>;
|
||||
|
||||
// TODO: obsolete
|
||||
meta?: Record<string, null>;
|
||||
}
|
||||
|
||||
// RULES: form data structure for editing existing teacher
|
@@ -1,20 +1,50 @@
|
||||
// RULES:
|
||||
// default variable value for customer
|
||||
// empty valur for customer
|
||||
// empty value for customer
|
||||
|
||||
import { defaultBillingAddress } from '@/db/billingAddress/constant';
|
||||
import type { DBUserMeta, UserMeta } from '@/db/UserMetas/type';
|
||||
|
||||
import { dayjs } from '@/lib/dayjs';
|
||||
|
||||
import type { UserMeta } from './type.d';
|
||||
// import type { UserMeta } from './type.d';
|
||||
|
||||
export const defaultUserMeta: UserMeta = {
|
||||
id: '',
|
||||
name: '',
|
||||
avatar: undefined,
|
||||
avatar: '',
|
||||
email: '',
|
||||
phone: undefined,
|
||||
phone: '',
|
||||
quota: 0,
|
||||
status: 'pending',
|
||||
state: 'pending',
|
||||
collectionId: '',
|
||||
createdAt: dayjs().toDate(),
|
||||
//
|
||||
// billingAddress: defaultBillingAddress,
|
||||
// state: 'active',
|
||||
// timezone: '',
|
||||
// language: '',
|
||||
// currency: '',
|
||||
// collectionId: '',
|
||||
};
|
||||
|
||||
export const defaultDBUserMeta: DBUserMeta = {
|
||||
id: '',
|
||||
name: '',
|
||||
avatar: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
quota: 0,
|
||||
status: 'pending',
|
||||
created: '1900-01-01',
|
||||
state: 'active',
|
||||
timezone: '',
|
||||
language: '',
|
||||
currency: '',
|
||||
collectionId: '',
|
||||
company: '',
|
||||
expand: {},
|
||||
};
|
||||
|
||||
export const emptyLpCategory: UserMeta = {
|
||||
|
@@ -3,6 +3,7 @@
|
||||
import * as React from 'react';
|
||||
import RouterLink from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { createTeacher } from '@/db/Teachers/Create';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
import Box from '@mui/material/Box';
|
||||
@@ -26,11 +27,12 @@ import { Controller, useForm } from 'react-hook-form';
|
||||
import { z as zod } from 'zod';
|
||||
|
||||
import { paths } from '@/paths';
|
||||
import isDevelopment from '@/lib/check-is-development';
|
||||
import { logger } from '@/lib/default-logger';
|
||||
import { base64ToFile } from '@/lib/file-to-base64';
|
||||
import { Option } from '@/components/core/option';
|
||||
import { toast } from '@/components/core/toaster';
|
||||
import { createTeacher } from '@/db/Teachers/Create';
|
||||
import isDevelopment from '@/lib/check-is-development';
|
||||
import type { CreateFormProps } from '@/components/dashboard/teacher/type.d';
|
||||
|
||||
function fileToBase64(file: Blob): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -45,6 +47,9 @@ function fileToBase64(file: Blob): Promise<string> {
|
||||
});
|
||||
}
|
||||
|
||||
// NOTE: face user side
|
||||
// avatar should be the string inside image box,
|
||||
// so avatar is string here
|
||||
const schema = zod.object({
|
||||
avatar: zod.string().optional(),
|
||||
name: zod.string().min(1, 'Name is required').max(255),
|
||||
@@ -101,13 +106,25 @@ export function TeacherCreateForm(): React.JSX.Element {
|
||||
const onSubmit = React.useCallback(
|
||||
async (values: Values): Promise<void> => {
|
||||
try {
|
||||
// Use standard create method from db/Customers/Create
|
||||
const record = await createTeacher(values);
|
||||
toast.success('Customer created');
|
||||
const temp: CreateFormProps = {
|
||||
name: values.name,
|
||||
email: values.email,
|
||||
timezone: values.timezone,
|
||||
language: values.language,
|
||||
currency: values.currency,
|
||||
taxId: values.taxId,
|
||||
avatar: values.avatar ? await base64ToFile(values.avatar) : null,
|
||||
state: 'pending',
|
||||
meta: {},
|
||||
};
|
||||
|
||||
// some convert process here...
|
||||
const record = await createTeacher(temp);
|
||||
toast.success('Teacher created');
|
||||
router.push(paths.dashboard.teachers.details(record.id));
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
toast.error('Failed to create customer');
|
||||
toast.error('Failed to create teacher');
|
||||
}
|
||||
},
|
||||
[router]
|
||||
@@ -157,7 +174,7 @@ export function TeacherCreateForm(): React.JSX.Element {
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
src={avatar}
|
||||
src={''}
|
||||
sx={{
|
||||
'--Avatar-size': '100px',
|
||||
'--Icon-fontSize': 'var(--icon-fontSize-lg)',
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import type { CreateForm, Vocabulary } from './type';
|
||||
|
||||
export const defaultVocabulary: Vocabulary = {
|
||||
id: 'default-vocabulary-id',
|
||||
image: undefined,
|
||||
sound: undefined,
|
||||
word: '',
|
||||
@@ -11,11 +10,15 @@ export const defaultVocabulary: Vocabulary = {
|
||||
cat_id: 'default-category-id',
|
||||
category: 'default-category',
|
||||
lesson_type_id: 'default-lesson-type-id',
|
||||
visible: 'visible',
|
||||
//
|
||||
id: 'default-vocabulary-id',
|
||||
collectionId: '',
|
||||
};
|
||||
|
||||
export const VocabularyCreateFormDefault: CreateForm = {
|
||||
image: undefined,
|
||||
sound: undefined,
|
||||
image: null,
|
||||
sound: '',
|
||||
word: '',
|
||||
word_c: '',
|
||||
sample_e: '',
|
||||
@@ -23,6 +26,7 @@ export const VocabularyCreateFormDefault: CreateForm = {
|
||||
cat_id: '',
|
||||
category: '',
|
||||
lesson_type_id: '',
|
||||
visible: 'visible',
|
||||
};
|
||||
|
||||
export const emptyVocabulary: Vocabulary = {
|
||||
|
@@ -3,7 +3,7 @@
|
||||
export interface Vocabulary {
|
||||
created?: string;
|
||||
updated?: string;
|
||||
image: string;
|
||||
image: string | undefined;
|
||||
sound?: string;
|
||||
word?: string;
|
||||
word_c?: string;
|
||||
@@ -22,6 +22,7 @@ export interface Vocabulary {
|
||||
//
|
||||
};
|
||||
};
|
||||
isEmpty?: boolean;
|
||||
//
|
||||
id: string;
|
||||
collectionId: string;
|
||||
@@ -62,3 +63,18 @@ export interface EditFormProps {
|
||||
export interface Helloworld {
|
||||
helloworld: string;
|
||||
}
|
||||
|
||||
// RULES: for use with vocabulary-create-form.tsx
|
||||
// when you update, please take a look into `vocabulary-create-form.tsx`
|
||||
export interface CreateForm {
|
||||
image: File[] | null;
|
||||
sound: string;
|
||||
word: string;
|
||||
word_c: string;
|
||||
sample_e: string;
|
||||
sample_c: string;
|
||||
cat_id: string;
|
||||
category: string;
|
||||
lesson_type_id: string;
|
||||
visible: string;
|
||||
}
|
||||
|
@@ -6,12 +6,16 @@ export const defaultNotification: Notification = {
|
||||
id: '',
|
||||
read: false,
|
||||
type: '',
|
||||
author: {},
|
||||
job: {},
|
||||
//
|
||||
// TODO: troubleshoot
|
||||
// author: {},
|
||||
job: { title: '' },
|
||||
description: '',
|
||||
NOTI_ID: '',
|
||||
created: '',
|
||||
updated: '',
|
||||
link: '',
|
||||
createdAt: new Date(),
|
||||
};
|
||||
|
||||
export const emptyNotification: Notification = {
|
||||
|
@@ -16,6 +16,11 @@ export interface Notification {
|
||||
company?: { name: string };
|
||||
to_user_id?: User;
|
||||
link: string;
|
||||
//
|
||||
NOTI_ID: string;
|
||||
//
|
||||
created: string;
|
||||
updated: string;
|
||||
}
|
||||
|
||||
export interface CreateFormProps {
|
||||
|
12
002_source/cms/src/db/UserMetas/type.d.ts
vendored
12
002_source/cms/src/db/UserMetas/type.d.ts
vendored
@@ -7,7 +7,7 @@ import type { BillingAddress } from '@/components/dashboard/user_meta/type.d';
|
||||
export interface DBUserMeta {
|
||||
name: string;
|
||||
//
|
||||
// NOTE: obslete "avatar" and use "avatar_file"
|
||||
// NOTE: obsolete "avatar" and use "avatar_file"
|
||||
avatar?: string;
|
||||
avatar_file?: string;
|
||||
//
|
||||
@@ -35,20 +35,24 @@ export interface DBUserMeta {
|
||||
|
||||
// UserMeta type definitions
|
||||
export interface UserMeta {
|
||||
id: string;
|
||||
name: string;
|
||||
avatar: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
quota: number;
|
||||
state: 'active' | 'blocked' | 'pending';
|
||||
// TODO: obsolete "status" field
|
||||
status: 'active' | 'blocked' | 'pending';
|
||||
//
|
||||
id: string;
|
||||
collectionId: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface UpdateUserMeta {
|
||||
name?: string;
|
||||
//
|
||||
// NOTE: obslete "avatar" and use "avatar_file"
|
||||
// NOTE: obsolete "avatar" and use "avatar_file"
|
||||
// avatar_file?: string;
|
||||
avatar: File | null;
|
||||
//
|
||||
@@ -57,7 +61,7 @@ export interface UpdateUserMeta {
|
||||
quota?: number;
|
||||
company?: string;
|
||||
//
|
||||
// relation handle seperately
|
||||
// relation handle separately
|
||||
// billingAddress: BillingAddress | Record<string, never>;
|
||||
|
||||
// status is obsoleted, replace by state
|
||||
|
@@ -8,9 +8,10 @@
|
||||
// - Must handle errors gracefully and provide user feedback
|
||||
// - Should integrate with the authentication system
|
||||
//
|
||||
import { pb } from '@/lib/pb';
|
||||
import { COL_USERS } from '@/constants';
|
||||
|
||||
import type { User } from '@/types/user';
|
||||
import { pb } from '@/lib/pb';
|
||||
|
||||
export async function createUser(userData: Partial<User>): Promise<{
|
||||
data?: User;
|
||||
@@ -18,7 +19,7 @@ export async function createUser(userData: Partial<User>): Promise<{
|
||||
}> {
|
||||
try {
|
||||
const data = await pb.collection(COL_USERS).create(userData);
|
||||
return { data };
|
||||
return { data: data as unknown as User };
|
||||
} catch (error) {
|
||||
return { error: error as Error };
|
||||
}
|
||||
|
@@ -11,8 +11,8 @@ import { COL_VOCABULARIES } from '@/constants';
|
||||
|
||||
import { pb } from '@/lib/pb';
|
||||
|
||||
import type { Vocabulary, VocabularyCreate } from './type';
|
||||
import type { CreateForm, Vocabulary } from './type';
|
||||
|
||||
export default function createVocabulary(data: VocabularyCreate): Promise<Vocabulary> {
|
||||
export default function createVocabulary(data: CreateForm): Promise<Vocabulary> {
|
||||
return pb.collection(COL_VOCABULARIES).create(data);
|
||||
}
|
||||
|
@@ -11,8 +11,8 @@ import { COL_VOCABULARIES } from '@/constants';
|
||||
|
||||
import { pb } from '@/lib/pb';
|
||||
|
||||
import type { Vocabularies } from './type';
|
||||
import type { Vocabulary } from './type';
|
||||
|
||||
export default function getAllVocabularies(): Promise<Vocabularies> {
|
||||
export default function getAllVocabularies(): Promise<Vocabulary[]> {
|
||||
return pb.collection(COL_VOCABULARIES).getFullList();
|
||||
}
|
||||
|
14
002_source/cms/src/db/billingAddress/constant.ts
Normal file
14
002_source/cms/src/db/billingAddress/constant.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export const defaultBillingAddress = {
|
||||
city: '',
|
||||
country: '',
|
||||
line1: '',
|
||||
line2: '',
|
||||
state: '',
|
||||
zipCode: '',
|
||||
//
|
||||
id: '',
|
||||
collectionId: '',
|
||||
collectionName: '',
|
||||
updated: '',
|
||||
created: '',
|
||||
};
|
@@ -1,6 +1,10 @@
|
||||
import PocketBase from 'pocketbase';
|
||||
|
||||
if (!process.env.NEXT_PUBLIC_POCKETBASE_URL) throw new Error('the pocketbase url cannot empty');
|
||||
if (process.env.NEXT_PUBLIC_POCKETBASE_URL) {
|
||||
console.log(process.env.NEXT_PUBLIC_POCKETBASE_URL);
|
||||
} else {
|
||||
throw new Error('the pocketbase url cannot empty');
|
||||
}
|
||||
export const POCKETBASE_URL: string = process.env.NEXT_PUBLIC_POCKETBASE_URL;
|
||||
|
||||
export const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL);
|
||||
|
@@ -1,10 +1,12 @@
|
||||
export interface User {
|
||||
id: string;
|
||||
name?: string;
|
||||
avatar: string;
|
||||
|
||||
// comply original superbase example
|
||||
avatar?: string | undefined;
|
||||
email?: string;
|
||||
|
||||
collectionId: string;
|
||||
collectionId?: string;
|
||||
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
8
002_source/docker/dc.sh
Executable file
8
002_source/docker/dc.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
docker compose \
|
||||
-f docker-compose.yml \
|
||||
-f docker-compose.db.yml \
|
||||
$@
|
@@ -1,3 +1,5 @@
|
||||
# REQ0021
|
||||
|
||||
# related to ./scripts/dc_dev.sh
|
||||
# consider we are starting in project/002_source/docker, see dc_dev.sh
|
||||
|
||||
@@ -7,9 +9,9 @@ volumes:
|
||||
|
||||
services:
|
||||
pocketbase:
|
||||
# image: ghcr.io/muchobien/pocketbase:latest
|
||||
image: 192.168.10.61:5000/demo_cms_pocketbase:latest
|
||||
build:
|
||||
context: ./pocketbase/docker
|
||||
context: ../pocketbase/docker
|
||||
args:
|
||||
- VERSION=0.26.6 # Specify the PocketBase version here
|
||||
# hostname: pocketbase
|
||||
@@ -17,7 +19,7 @@ services:
|
||||
# environment:
|
||||
# ENCRYPTION: example #optional
|
||||
ports:
|
||||
- 8090:8090
|
||||
- 3013:8090
|
||||
volumes:
|
||||
# group custom persistant data inside docker directory
|
||||
- ./volumes/pocketbase/pb_data:/pb_data
|
||||
@@ -27,16 +29,16 @@ services:
|
||||
- ../pocketbase/pb_hooks:/pb_hooks
|
||||
|
||||
# TODO: resume healthcheck in prod
|
||||
# healthcheck:
|
||||
# #optional (recommended) since v0.10.0
|
||||
# test: wget --no-verbose --tries=1 --spider http://localhost:8090/api/health || exit 1
|
||||
# interval: 5s
|
||||
# timeout: 5s
|
||||
# retries: 5
|
||||
healthcheck:
|
||||
#optional (recommended) since v0.10.0
|
||||
test: wget --no-verbose --tries=1 --spider http://localhost:8090/api/health || exit 1
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: 0.1
|
||||
cpus: '0.1'
|
||||
reservations:
|
||||
cpus: 0.01
|
||||
cpus: '0.01'
|
||||
|
@@ -1,22 +1,22 @@
|
||||
volumes:
|
||||
shared:
|
||||
dist:
|
||||
# REQ0021
|
||||
|
||||
services:
|
||||
cms:
|
||||
image: 192.168.10.61:5000/cms_ubuntu
|
||||
# build: ./cms
|
||||
image: 192.168.10.61:5000/demo_ls_cms
|
||||
build: ../cms
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- ./cms:/app
|
||||
- ../cms:/app
|
||||
ports:
|
||||
- 3000:3000
|
||||
- 3011:3000
|
||||
working_dir: /app
|
||||
command: ./scripts/docker/entrypoint.sh
|
||||
# command: /app/scripts/docker/entrypoint.sh
|
||||
command: sleep infinity
|
||||
depends_on:
|
||||
pocketbase:
|
||||
condition: service_healthy
|
||||
user: 1000:1000
|
||||
healthcheck:
|
||||
#optional (recommended) since v0.10.0
|
||||
test: wget --no-verbose --tries=1 --spider http://localhost:3000 || exit 1
|
||||
@@ -30,43 +30,78 @@ services:
|
||||
reservations:
|
||||
cpus: '0.01'
|
||||
|
||||
doc:
|
||||
build: ./doc
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- ./doc:/app
|
||||
ports:
|
||||
- 3001:3000
|
||||
working_dir: /app
|
||||
command: ./scripts/docker/entrypoint.sh
|
||||
healthcheck:
|
||||
#optional (recommended) since v0.10.0
|
||||
test: wget --no-verbose --tries=1 --spider http://localhost:3000 || exit 1
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
# doc:
|
||||
# build: ../doc
|
||||
# env_file:
|
||||
# - .env
|
||||
# volumes:
|
||||
# - ./doc:/app
|
||||
# ports:
|
||||
# - 3001:3000
|
||||
# working_dir: /app
|
||||
# command: ./scripts/docker/entrypoint.sh
|
||||
# healthcheck:
|
||||
# #optional (recommended) since v0.10.0
|
||||
# test: wget --no-verbose --tries=1 --spider http://localhost:3000 || exit 1
|
||||
# interval: 5s
|
||||
# timeout: 5s
|
||||
# retries: 5
|
||||
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.5'
|
||||
reservations:
|
||||
cpus: '0.01'
|
||||
# deploy:
|
||||
# resources:
|
||||
# limits:
|
||||
# cpus: '0.5'
|
||||
# reservations:
|
||||
# cpus: '0.01'
|
||||
|
||||
# api_ts:
|
||||
# # TODO: review this config, the api_ts volumes should be place inside 002_source/docker/volumes
|
||||
# # image: 192.168.10.61:5000/api_ts_ubuntu
|
||||
# build: ../api_ts
|
||||
# volumes:
|
||||
# - ../api_ts:/app
|
||||
# working_dir: /app
|
||||
# # env_file:
|
||||
# # - .env
|
||||
# environment:
|
||||
# - NODE_ENV=production
|
||||
# - PB_HOSTNAME=pocketbase
|
||||
# - PB_USERNAME=admin@123.com
|
||||
# - PB_PASSWORD=Aa12345678
|
||||
# ports:
|
||||
# - 8080:3000
|
||||
# # command: sleep infinity
|
||||
# command: ./entrypoint.sh
|
||||
# # depends_on:
|
||||
# # pocketbase:
|
||||
# # condition: service_healthy
|
||||
# # healthcheck:
|
||||
# # #optional (recommended) since v0.10.0
|
||||
# # test: wget --no-verbose --tries=1 --spider http://localhost:3000 || exit 1
|
||||
# # interval: 5s
|
||||
# # timeout: 5s
|
||||
# # retries: 5
|
||||
|
||||
# deploy:
|
||||
# resources:
|
||||
# limits:
|
||||
# cpus: 0.5
|
||||
# reservations:
|
||||
# cpus: 0.01
|
||||
|
||||
ionic_mobile:
|
||||
# image: node:20-bullseye-slim
|
||||
# build: ./ionic_mobile
|
||||
image: 192.168.10.61:5000/ionic_mobile_ubuntu
|
||||
image: 192.168.10.61:5000/demo_ionic_mobile
|
||||
build: ../ionic_mobile
|
||||
# user: 1000:1000
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- ./ionic_mobile:/app
|
||||
- ../ionic_mobile:/app
|
||||
ports:
|
||||
- 5173:5173
|
||||
- 3012:8080
|
||||
working_dir: /app
|
||||
command: ./scripts/docker/entrypoint.sh
|
||||
command: /app/scripts/docker/entrypoint.sh
|
||||
depends_on:
|
||||
pocketbase:
|
||||
condition: service_healthy
|
||||
@@ -83,38 +118,3 @@ services:
|
||||
cpus: '0.5'
|
||||
reservations:
|
||||
cpus: '0.01'
|
||||
|
||||
api_ts:
|
||||
# TODO: review this config, the api_ts volumes should be place inside 002_source/docker/volumes
|
||||
image: 192.168.10.61:5000/api_ts_ubuntu
|
||||
# build: ./api_ts
|
||||
volumes:
|
||||
- ../api_ts:/app
|
||||
working_dir: /app
|
||||
# env_file:
|
||||
# - .env
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- PB_HOSTNAME=pocketbase
|
||||
- PB_USERNAME=admin@123.com
|
||||
- PB_PASSWORD=Aa12345678
|
||||
ports:
|
||||
- 8080:3000
|
||||
# command: sleep infinity
|
||||
command: ./entrypoint.sh
|
||||
# depends_on:
|
||||
# pocketbase:
|
||||
# condition: service_healthy
|
||||
# healthcheck:
|
||||
# #optional (recommended) since v0.10.0
|
||||
# test: wget --no-verbose --tries=1 --spider http://localhost:3000 || exit 1
|
||||
# interval: 5s
|
||||
# timeout: 5s
|
||||
# retries: 5
|
||||
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: 0.5
|
||||
reservations:
|
||||
cpus: 0.01
|
||||
|
40
002_source/ionic_mobile/.dockerignore
Normal file
40
002_source/ionic_mobile/.dockerignore
Normal file
@@ -0,0 +1,40 @@
|
||||
# Ignore all files and directories by default
|
||||
|
||||
-
|
||||
|
||||
# Except for the following:
|
||||
|
||||
!.dockerignore
|
||||
!.gitignore
|
||||
!.env.example
|
||||
!.git/
|
||||
!.env
|
||||
!.vscode/
|
||||
!.idea/
|
||||
|
||||
# Node.js specific
|
||||
|
||||
node_modules/
|
||||
|
||||
# Build artifacts
|
||||
|
||||
dist/
|
||||
build/
|
||||
|
||||
# Logs
|
||||
|
||||
logs/
|
||||
\*.log
|
||||
|
||||
# Environment files
|
||||
|
||||
.env\*
|
||||
|
||||
# Cache files
|
||||
|
||||
\*.cache
|
||||
|
||||
# OS generated files
|
||||
|
||||
.DS_Store
|
||||
Thumbs.db
|
@@ -1,5 +1,17 @@
|
||||
FROM 192.168.10.61:5000/nvm_ubuntu:latest
|
||||
# Use official Node 18 base image
|
||||
FROM node:22-slim
|
||||
|
||||
# Install pnpm globally
|
||||
RUN npm install -g pnpm
|
||||
|
||||
# Copy your application code (optional, comment out if not needed)
|
||||
COPY . /app
|
||||
|
||||
RUN cd /app && npm i
|
||||
RUN cd /app && npm run build
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
RUN nvm install 22
|
||||
# Default command (optional)
|
||||
CMD ["./scripts/docker/entrypoint.sh"]
|
||||
|
176
002_source/ionic_mobile/package-lock.json
generated
176
002_source/ionic_mobile/package-lock.json
generated
@@ -28,6 +28,7 @@
|
||||
"@types/react-router": "^5.1.20",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"axios": "^1.8.1",
|
||||
"http-serve": "^1.0.1",
|
||||
"i18next": "^24.2.0",
|
||||
"ionicons": "^7.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
@@ -5815,6 +5816,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/colors": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
|
||||
"integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.1.90"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
@@ -6653,6 +6663,15 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/corser": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
|
||||
"integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/create-require": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
||||
@@ -7383,6 +7402,22 @@
|
||||
"safer-buffer": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ecstatic": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-2.2.2.tgz",
|
||||
"integrity": "sha512-F1g29y3I+abOS+M0AiK2O9R96AJ49Bc3kH696HtqnN+CL3YhpUnSzHNoUBQL03qDsN9Lr1XeKIxTqEH3BtiBgg==",
|
||||
"deprecated": "This package is unmaintained and deprecated. See the GH Issue 259.",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"he": "^1.1.1",
|
||||
"mime": "^1.2.11",
|
||||
"minimist": "^1.1.0",
|
||||
"url-join": "^2.0.2"
|
||||
},
|
||||
"bin": {
|
||||
"ecstatic": "lib/ecstatic.js"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
@@ -8037,6 +8072,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eventsource": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
|
||||
@@ -9681,7 +9722,6 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"he": "bin/he"
|
||||
@@ -9814,6 +9854,20 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/http-proxy": {
|
||||
"version": "1.18.1",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
|
||||
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eventemitter3": "^4.0.0",
|
||||
"follow-redirects": "^1.0.0",
|
||||
"requires-port": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/http-proxy-agent": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
|
||||
@@ -9829,6 +9883,26 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/http-serve": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/http-serve/-/http-serve-1.0.1.tgz",
|
||||
"integrity": "sha512-dlIVvTQitN2q/KX9s3r2flAiByI3jqjoIoJ90Qtin+u9m7oWNM88qjwrNxeEVfsI+AE3bQAAdC73WE/0RtnVaQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"colors": "1.0.3",
|
||||
"corser": "~2.0.0",
|
||||
"ecstatic": "^2.0.0",
|
||||
"http-proxy": "^1.8.1",
|
||||
"opener": "~1.4.0",
|
||||
"optimist": "0.6.x",
|
||||
"portfinder": "0.4.x",
|
||||
"union": "~0.4.3"
|
||||
},
|
||||
"bin": {
|
||||
"hs": "bin/http-serve",
|
||||
"http-serve": "bin/http-serve"
|
||||
}
|
||||
},
|
||||
"node_modules/http-signature": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz",
|
||||
@@ -12461,6 +12535,18 @@
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
@@ -12532,7 +12618,6 @@
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -13074,6 +13159,40 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/opener": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/opener/-/opener-1.4.3.tgz",
|
||||
"integrity": "sha512-4Im9TrPJcjAYyGR5gBe3yZnBzw5n3Bfh1ceHHGNOpMurINKc6RdSIPXMyon4BZacJbJc36lLkhipioGbWh5pwg==",
|
||||
"license": "(WTFPL OR MIT)",
|
||||
"bin": {
|
||||
"opener": "opener.js"
|
||||
}
|
||||
},
|
||||
"node_modules/optimist": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
|
||||
"integrity": "sha512-snN4O4TkigujZphWLN0E//nQmm7790RYaE53DdL7ZYwee2D8DDo9/EyYiKUfN3rneWUjhJnueija3G9I2i0h3g==",
|
||||
"license": "MIT/X11",
|
||||
"dependencies": {
|
||||
"minimist": "~0.0.1",
|
||||
"wordwrap": "~0.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/optimist/node_modules/minimist": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
|
||||
"integrity": "sha512-iotkTvxc+TwOm5Ieim8VnSNvCDjCK9S8G3scJ50ZthspSxa7jx50jkhYduuAtAjvfDUwSgOwf8+If99AlOEhyw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/optimist/node_modules/wordwrap": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
|
||||
"integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
@@ -13454,6 +13573,36 @@
|
||||
"integrity": "sha512-WBBeOgz4Jnrd7a1KEzSBUJqpTortKKCcp16j5KoF+4tNIyQHsmynj+qRSvS56/RVacVMbAqO8Qkfj3N84fpzEw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/portfinder": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-0.4.0.tgz",
|
||||
"integrity": "sha512-SZ3hp61WVhwNSS0gf0Fdrx5Yb/wl35qisHuPVM1S0StV8t5XlVZmmJy7/417OELJA7t6ecEmeEzvOaBwq3kCiQ==",
|
||||
"license": "MIT/X11",
|
||||
"dependencies": {
|
||||
"async": "0.9.0",
|
||||
"mkdirp": "0.5.x"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/portfinder/node_modules/async": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz",
|
||||
"integrity": "sha512-XQJ3MipmCHAIBBMFfu2jaSetneOrXbSyyqeU3Nod867oNOpS+i9FEms5PWgjMxSgBybRf2IVVLtr1YfrDO+okg=="
|
||||
},
|
||||
"node_modules/portfinder/node_modules/mkdirp": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.6"
|
||||
},
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
}
|
||||
},
|
||||
"node_modules/possible-typed-array-names": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
|
||||
@@ -14804,7 +14953,6 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/resize-observer-polyfill": {
|
||||
@@ -16887,6 +17035,22 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/union": {
|
||||
"version": "0.4.6",
|
||||
"resolved": "https://registry.npmjs.org/union/-/union-0.4.6.tgz",
|
||||
"integrity": "sha512-2qtrvSgD0GKotLRCNYkIMUOzoaHjXKCtbAP0kc5Po6D+RWTBb+BxlcHlHCYcf+Y+YM7eQicPgAg9mnWQvtoFVA==",
|
||||
"dependencies": {
|
||||
"qs": "~2.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/union/node_modules/qs": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz",
|
||||
"integrity": "sha512-f5M0HQqZWkzU8GELTY8LyMrGkr3bPjKoFtTkwUEqJQbcljbeK8M7mliP9Ia2xoOI6oMerp+QPS7oYJtpGmWe/A=="
|
||||
},
|
||||
"node_modules/unique-string": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz",
|
||||
@@ -17039,6 +17203,12 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url-join": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz",
|
||||
"integrity": "sha512-c2H1fIgpUdwFRIru9HFno5DT73Ok8hg5oOb5AT3ayIgvCRfxgs2jyt5Slw8kEB7j3QUr6yJmMPDT/odjk7jXow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/url-parse": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
|
@@ -3,12 +3,20 @@
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "==22"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite --host --cors --force --strictPort --clearScreen",
|
||||
"build": "tsc && vite build && npx cap copy",
|
||||
"build:docker": "pnpm run build && pnpm run docker:build && pnpm run docker:push",
|
||||
"build:w": "npx nodemon --ext ts,tsx,json,mjs,js,jsx --delay 15 --exec \"pnpm run build\"",
|
||||
"docker:push": "docker push 192.168.10.61:5000/demo_ionic_mobile",
|
||||
"docker:build": "docker build -t 192.168.10.61:5000/demo_ionic_mobile .",
|
||||
"ionic_build": "npx vite build && npx cap copy",
|
||||
"ionic_sync": "npx cap sync --inline",
|
||||
"ionic_pre_android_studio": "npm run ionic_build && npm run ionic_sync",
|
||||
"start": "npx http-serve dist",
|
||||
"preview": "vite preview",
|
||||
"test.e2e": "cypress run",
|
||||
"test.unit": "vitest",
|
||||
@@ -43,6 +51,7 @@
|
||||
"@types/react-router": "^5.1.20",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"axios": "^1.8.1",
|
||||
"http-serve": "^1.0.1",
|
||||
"i18next": "^24.2.0",
|
||||
"ionicons": "^7.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
@@ -89,8 +98,5 @@
|
||||
"vite": "~5.2.0",
|
||||
"vitest": "^0.34.6"
|
||||
},
|
||||
"description": "An Ionic project",
|
||||
"engines": {
|
||||
"node": "==22"
|
||||
}
|
||||
}
|
||||
"description": "An Ionic project"
|
||||
}
|
@@ -1,19 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
set -x
|
||||
|
||||
nvm use 22
|
||||
node -v
|
||||
while true; do
|
||||
npm run start
|
||||
|
||||
echo "command restarted"
|
||||
|
||||
sleep 5
|
||||
|
||||
done
|
||||
|
||||
# sleep infinity
|
||||
while true; do
|
||||
if [ "$NODE_ENV" = "development" ]; then
|
||||
npm i -D
|
||||
npm run dev
|
||||
else
|
||||
# temp solution
|
||||
npm run dev
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
done
|
||||
# # sleep infinity
|
||||
# while true; do
|
||||
# if [ "$NODE_ENV" = "development" ]; then
|
||||
# npm i -D
|
||||
# npm run dev
|
||||
# else
|
||||
# # temp solution
|
||||
# npm run dev
|
||||
# fi
|
||||
|
||||
# sleep 1
|
||||
# done
|
||||
|
@@ -20,9 +20,7 @@ EXPOSE 8090
|
||||
|
||||
COPY --from=downloader /pocketbase /usr/local/bin/pocketbase
|
||||
|
||||
ENTRYPOINT [
|
||||
"/usr/local/bin/pocketbase", "serve", "--http=0.0.0.0:8090", "--dir=/pb_data", "--publicDir=/pb_public", "--hooksDir=/pb_hooks"]
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/pocketbase", "serve", "--http=0.0.0.0:8090", "--dir=/pb_data", "--publicDir=/pb_public", "--hooksDir=/pb_hooks"]
|
||||
|
||||
# ---
|
||||
# RUN apk add sqlite entr
|
||||
|
85
002_source/pocketbase/package.json
Normal file
85
002_source/pocketbase/package.json
Normal file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"name": "lettersoup_app_poc",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "==22"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite --host --cors --force --strictPort --clearScreen",
|
||||
"build:docker": "pnpm run docker:build && pnpm run docker:push",
|
||||
"build:w": "not implemented",
|
||||
"docker:push": "docker push 192.168.10.61:5000/demo_ls_db",
|
||||
"docker:build": "docker build --build-arg VERSION=0.26.6 -t 192.168.10.61:5000/demo_ls_db ./docker"
|
||||
},
|
||||
"dependencies": {
|
||||
"@capacitor/android": "^6.2.0",
|
||||
"@capacitor/app": "6.0.2",
|
||||
"@capacitor/core": "6.2.0",
|
||||
"@capacitor/filesystem": "^6.0.2",
|
||||
"@capacitor/haptics": "6.0.2",
|
||||
"@capacitor/keyboard": "6.0.3",
|
||||
"@capacitor/splash-screen": "^6.0.3",
|
||||
"@capacitor/status-bar": "6.0.2",
|
||||
"@hookform/resolvers": "3.3.4",
|
||||
"@ionic/prettier-config": "^4.0.0",
|
||||
"@ionic/react": "^8.0.0",
|
||||
"@ionic/react-router": "^8.0.0",
|
||||
"@ionic/storage": "^4.0.0",
|
||||
"@lifeomic/attempt": "^3.1.0",
|
||||
"@tanstack/react-query": "^5.74.4",
|
||||
"@tanstack/react-query-devtools": "^5.74.6",
|
||||
"@types/lodash": "^4.17.16",
|
||||
"@types/react-router": "^5.1.20",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"axios": "^1.8.1",
|
||||
"http-serve": "^1.0.1",
|
||||
"i18next": "^24.2.0",
|
||||
"ionicons": "^7.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"pocketbase": "^0.26.0",
|
||||
"qr-code-styling": "^1.9.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "7.50.1",
|
||||
"react-i18next": "^15.2.0",
|
||||
"react-markdown": "^9.0.3",
|
||||
"react-router": "^5.3.4",
|
||||
"react-router-dom": "^5.3.4",
|
||||
"react-use": "^17.6.0",
|
||||
"react-use-audio-player": "^2.3.0-alpha.1",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"zod": "3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/assets": "^3.0.5",
|
||||
"@capacitor/cli": "6.2.0",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.4.1",
|
||||
"@testing-library/dom": ">=7.21.4",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@types/react": "^18.0.27",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"@vitejs/plugin-legacy": "^5.0.0",
|
||||
"@vitejs/plugin-react": "^4.0.1",
|
||||
"cypress": "^13.5.0",
|
||||
"eslint": "^9.20.1",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^5.1.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.19",
|
||||
"globals": "^15.15.0",
|
||||
"jsdom": "^22.1.0",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"prettier-plugin-unused-imports-configurable": "^1.14.2",
|
||||
"sass": "^1.88.0",
|
||||
"terser": "^5.4.0",
|
||||
"typescript": "^5.1.6",
|
||||
"typescript-eslint": "^8.24.0",
|
||||
"vite": "~5.2.0",
|
||||
"vitest": "^0.34.6"
|
||||
},
|
||||
"description": "An Ionic project"
|
||||
}
|
@@ -2,19 +2,15 @@
|
||||
|
||||
set -ex
|
||||
|
||||
# docker compose build
|
||||
# DOCKER_COMPOSE_FILES="-f docker-compose.db.yml"
|
||||
DOCKER_COMPOSE_FILES="-f ./docker-compose.db.yml -f ./docker-compose.yml"
|
||||
|
||||
# cd cms
|
||||
# # nvm use 18
|
||||
# npm run build
|
||||
# cd -
|
||||
cd docker
|
||||
docker compose $DOCKER_COMPOSE_FILES build
|
||||
docker compose $DOCKER_COMPOSE_FILES up -d
|
||||
|
||||
# cd ionic_mobile
|
||||
# npm run build
|
||||
# cd -
|
||||
docker compose $DOCKER_COMPOSE_FILES exec -it cms bash
|
||||
cd ..
|
||||
|
||||
docker compose up -d
|
||||
|
||||
docker compose logs -f
|
||||
|
||||
echo "done"
|
||||
|
Reference in New Issue
Block a user