Skip to content

Commit

Permalink
Merge pull request #35 from Reynard-G/release/staging
Browse files Browse the repository at this point in the history
Add User Settings functionality + build optimizations
  • Loading branch information
Reynard-G authored Oct 3, 2024
2 parents e76570c + 4a0648b commit 4d4c23c
Show file tree
Hide file tree
Showing 38 changed files with 3,617 additions and 2,301 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deploy.production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ jobs:
if: ${{ github.ref == 'refs/heads/release/production' }}
run: flyctl deploy --remote-only --config ./fly.production.toml
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN_PRODUCTION }}
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN_PRODUCTION }}
2 changes: 1 addition & 1 deletion .github/workflows/deploy.staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ jobs:
- name: 🚀 Deploy Staging
run: flyctl deploy --remote-only --config ./fly.staging.toml
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN_STAGING }}
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN_STAGING }}
37 changes: 37 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: ⬣ Lint
on:
push:
branches:
- development
- release/staging
- release/production
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
lint:
name: ⬣ Lint
runs-on: ubuntu-latest

steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4

- name: ⎔ Setup node
uses: actions/setup-node@v4
with:
node-version: 20

- name: ⎔ Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9

- name: 📥 Install dependencies
run: pnpm install

- name: 🔬 Lint
run: pnpm run lint && pnpm run typecheck
3 changes: 0 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ LABEL fly_launch_runtime="Remix"
# Remix app lives here
WORKDIR /app

# Set production environment
ENV NODE_ENV="production"

# Install pnpm
ARG PNPM_VERSION=9.5.0
RUN npm install -g pnpm@$PNPM_VERSION
Expand Down
194 changes: 194 additions & 0 deletions app/components/ChangePasswordButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import * as VisuallyHidden from "@radix-ui/react-visually-hidden";
import { useFetcher } from "@remix-run/react";
import { IconKey } from "@tabler/icons-react";
import React, { useEffect, useState } from "react";
import { toast } from "sonner";

import { Button } from "~/components/ui/button";
import {
Dialog,
DialogContent,
DialogTitle,
DialogTrigger,
} from "~/components/ui/dialog";
import { Input } from "~/components/ui/input";
import { SpokeSpinner } from "~/components/ui/spinner";
import { action } from "~/routes/app.$server.transactions";

interface ChangePasswordButtonProps {
label: string;
variant?:
| "default"
| "destructive"
| "outline"
| "secondary"
| "ghost"
| "link";
children?: React.ReactNode;
}

export default function ChangePasswordButton({
label,
variant = "default",
children,
}: ChangePasswordButtonProps) {
const fetcher = useFetcher<typeof action>();
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false);
const [oldPassword, setOldPassword] = useState<string>("");
const [newPassword, setNewPassword] = useState<string>("");
const [confirmNewPassword, setConfirmNewPassword] = useState<string>("");

const isSubmitting = fetcher.state === "submitting";

useEffect(() => {
if (fetcher.data && !fetcher.data.success && fetcher.state === "idle") {
setOldPassword("");
setNewPassword("");
setConfirmNewPassword("");
setIsDialogOpen(false);
toast.error(fetcher.data.message);
} else if (
fetcher.data &&
fetcher.data.success &&
fetcher.state === "idle"
) {
setOldPassword("");
setNewPassword("");
setConfirmNewPassword("");
setIsDialogOpen(false);
toast.success(fetcher.data.message);
}
}, [fetcher.data, fetcher.state]);

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (
!isSubmitting &&
newPassword === confirmNewPassword &&
newPassword.length > 0 &&
oldPassword !== newPassword
) {
fetcher.submit(e.currentTarget, { method: "post" });
}
};

return (
<div className="flex flex-col space-y-2">
<label className="text-sm font-medium leading-none">{label}</label>

<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogTrigger asChild>
<Button
aria-label={label}
variant={variant}
className="w-fit focus:ring-0"
>
{children}
</Button>
</DialogTrigger>

<DialogContent className="p-0" aria-describedby={undefined}>
<VisuallyHidden.Root>
<DialogTitle>Change Password</DialogTitle>
</VisuallyHidden.Root>

<div className="mx-auto mb-2 mt-5 flex h-16 w-16 items-center justify-center rounded-full bg-[#323232]">
<IconKey size={30} aria-hidden="true" />
</div>

<fetcher.Form
method="post"
action="?/change_password"
className="space-y-4"
onSubmit={handleSubmit}
>
<h1 className="mb-2 text-center text-lg font-semibold">
Change Password
</h1>

<div className="space-y-3 px-12">
<div className="space-y-2">
<label
className="text-sm font-medium leading-none"
htmlFor="old_password"
>
Old Password
<Input
required
type="password"
name="old_password"
value={oldPassword}
placeholder="Old Password"
onChange={(e) => setOldPassword(e.target.value)}
/>
</label>
</div>

<div className="space-y-2">
<label
className="text-sm font-medium leading-none"
htmlFor="new_password"
>
New Password
<Input
required
type="password"
name="new_password"
value={newPassword}
placeholder="New Password"
onChange={(e) => setNewPassword(e.target.value)}
/>
</label>
</div>

<div className="space-y-2">
<label
className="text-sm font-medium leading-none"
htmlFor="confirm_new_password"
>
Confirm New Password
<Input
required
type="password"
name="confirm_new_password"
value={confirmNewPassword}
placeholder="Confirm New Password"
onChange={(e) => setConfirmNewPassword(e.target.value)}
/>
</label>
</div>

<div className="!mb-4 !mt-6 flex justify-end gap-2">
<Button
type="button"
variant="destructive"
className="w-fit"
onClick={() => setIsDialogOpen(false)}
>
Cancel
</Button>

<Button
type="submit"
name="_action"
value="change_password"
variant="outline"
disabled={
isSubmitting ||
newPassword !== confirmNewPassword ||
newPassword.length == 0 ||
oldPassword === newPassword
}
className="w-fit"
>
{isSubmitting && <SpokeSpinner size="sm" className="mr-1" />}
Change Password
</Button>
</div>
</div>
</fetcher.Form>
</DialogContent>
</Dialog>
</div>
);
}
4 changes: 3 additions & 1 deletion app/components/DataTable/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ export function DataTable<TData>({
}: DataTableProps<TData>) {
const navigation = useNavigation();

const isLoading = navigation.state === "loading";
const isLoading =
navigation.state === "loading" &&
navigation.location.pathname.includes("transactions");

return (
<div
Expand Down
30 changes: 11 additions & 19 deletions app/components/Dialog/CreateTransactionsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,12 @@ import { Skeleton } from "~/components/ui/skeleton";
import { SpokeSpinner } from "~/components/ui/spinner";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
import { useMediaQuery } from "~/hooks/use-media-query";
import { type loader } from "~/routes/app.$server.transactions";
import { action, type loader } from "~/routes/app.$server.transactions";
import { NonSensitiveUser } from "~/types/User";

export type CreateTransactionsDialogFetcherResponse = {
success: boolean;
message?: string;
};

export default function CreateTransactionsDialog() {
const { allUsers } = useLoaderData<typeof loader>();
const fetcher = useFetcher<CreateTransactionsDialogFetcherResponse>();
const fetcher = useFetcher<typeof action>();
const isDesktop = useMediaQuery("(min-width: 768px)");
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false);
const [isUsersPopoverOpen, setIsUsersPopoverOpen] = useState<boolean>(false);
Expand Down Expand Up @@ -107,18 +102,14 @@ export default function CreateTransactionsDialog() {
);

useEffect(() => {
if (fetcher.data && fetcher.state === "idle") {
if (fetcher.data.success) {
try {
toast.success(fetcher.data.message || "Transaction successful");
setIsDialogOpen(false);
} catch (error) {
console.error("Error submitting transaction:", error);
toast.error("Error submitting transaction. Please try again.");
}
} else {
toast.error(fetcher.data.message || "Error submitting transaction");
}
if (fetcher.data && !fetcher.data.success && fetcher.state === "idle") {
toast.error(fetcher.data.message);
} else if (
fetcher.data &&
fetcher.data.success &&
fetcher.state === "idle"
) {
toast.success(fetcher.data.message);
}
}, [fetcher.data, fetcher.state]);

Expand Down Expand Up @@ -321,6 +312,7 @@ export default function CreateTransactionsDialog() {
<div className="border-t border-[#313131]">
<div className="flex justify-between gap-5 px-12 py-6">
<Button
type="button"
variant="outline"
className="w-full"
onClick={() => setIsDialogOpen(false)}
Expand Down
Loading

0 comments on commit 4d4c23c

Please sign in to comment.