Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,60 @@ jobs:
NEXT_PUBLIC_SMART_ACCOUNT_WASM_HASH: ${{ secrets.NEXT_PUBLIC_SMART_ACCOUNT_WASM_HASH || 'a12e8fa9621efd20315753bd4007d974390e31fbcb4a7ddc4dd0a0dec728bf2e' }}
NEXT_PUBLIC_WEBAUTHN_VERIFIER_ADDRESS: ${{ secrets.NEXT_PUBLIC_WEBAUTHN_VERIFIER_ADDRESS || 'CBSHV66WG7UV6FQVUTB67P3DZUEJ2KJ5X6JKQH5MFRAAFNFJUAJVXJYV' }}
NEXT_PUBLIC_NATIVE_TOKEN_CONTRACT: ${{ secrets.NEXT_PUBLIC_NATIVE_TOKEN_CONTRACT || 'CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC' }}

test-e2e:
runs-on: ubuntu-latest
needs: build-and-lint

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Node.js 24.x
uses: actions/setup-node@v4
with:
node-version: 24.x

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

- name: Cache pnpm store
uses: actions/cache@v4
with:
path: ~/.local/share/pnpm/store
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build project
run: pnpm build
env:
NODE_ENV: production
NEXT_PUBLIC_STELLAR_RPC_URL: ${{ secrets.NEXT_PUBLIC_STELLAR_RPC_URL || 'https://soroban-testnet.stellar.org' }}
NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE: ${{ secrets.NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE || 'Test SDF Network ; September 2015' }}
NEXT_PUBLIC_SMART_ACCOUNT_WASM_HASH: ${{ secrets.NEXT_PUBLIC_SMART_ACCOUNT_WASM_HASH || 'a12e8fa9621efd20315753bd4007d974390e31fbcb4a7ddc4dd0a0dec728bf2e' }}
NEXT_PUBLIC_WEBAUTHN_VERIFIER_ADDRESS: ${{ secrets.NEXT_PUBLIC_WEBAUTHN_VERIFIER_ADDRESS || 'CBSHV66WG7UV6FQVUTB67P3DZUEJ2KJ5X6JKQH5MFRAAFNFJUAJVXJYV' }}
NEXT_PUBLIC_NATIVE_TOKEN_CONTRACT: ${{ secrets.NEXT_PUBLIC_NATIVE_TOKEN_CONTRACT || 'CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC' }}

- name: Install Playwright Chromium
run: pnpm exec playwright install chromium --with-deps

- name: Run E2E tests
run: pnpm run test:e2e
env:
CI: true
NEXT_PUBLIC_GRAPHQL_URL: http://localhost:3000/api/graphql
NEXT_PUBLIC_API_URL: http://localhost:3000

- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 7
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@

# testing
/coverage
/playwright-report
/test-results
/blob-report

# next.js
/.next/
Expand Down
19 changes: 15 additions & 4 deletions components/bounty-detail/bounty-detail-sidebar-cta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export function SidebarCTA({ bounty, onCancelled }: SidebarCTAProps) {
const isFinalized = bounty.status === "COMPLETED";
const submissionCount = bounty._count?.submissions ?? 0;

const { hasJoined, isPastDeadline, joinMutation, handleJoin } =
const { walletAddress, hasJoined, isPastDeadline, joinMutation, handleJoin } =
useCompetitionJoinState(bounty);

const handleCopy = async () => {
Expand Down Expand Up @@ -184,8 +184,14 @@ export function SidebarCTA({ bounty, onCancelled }: SidebarCTAProps) {
</Button>
) : (
<Button
data-testid="apply-to-bounty-btn"
className="w-full h-11 font-bold tracking-wide"
disabled={!canAct || isPastDeadline || joinMutation.isPending}
disabled={
!canAct ||
isPastDeadline ||
joinMutation.isPending ||
!walletAddress
}
size="lg"
onClick={() => void handleJoin()}
>
Expand Down Expand Up @@ -384,7 +390,7 @@ export function MobileCTA({ bounty, onCancelled }: MobileCTAProps) {
const canCancel =
isCreator && (bounty.status === "OPEN" || bounty.status === "IN_PROGRESS");

const { hasJoined, isPastDeadline, joinMutation, handleJoin } =
const { walletAddress, hasJoined, isPastDeadline, joinMutation, handleJoin } =
useCompetitionJoinState(bounty);

const label = () => {
Expand All @@ -407,9 +413,14 @@ export function MobileCTA({ bounty, onCancelled }: MobileCTAProps) {
<FcfsClaimButton bounty={bounty} />
) : isCompetition ? (
<Button
data-testid="apply-to-bounty-btn"
className="w-full h-11 font-bold tracking-wide"
disabled={
!canAct || hasJoined || isPastDeadline || joinMutation.isPending
!canAct ||
hasJoined ||
isPastDeadline ||
joinMutation.isPending ||
!walletAddress
}
size="lg"
onClick={() => void handleJoin()}
Expand Down
30 changes: 20 additions & 10 deletions components/bounty/application-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ interface ApplicationDialogProps {
onApply: (data: {
coverLetter: string;
portfolioUrl?: string;
}) => Promise<boolean>;
}) => Promise<void>;
trigger: ReactNode;
}

Expand Down Expand Up @@ -75,15 +75,12 @@ export function ApplicationDialog({

try {
const portfolioUrl = values.portfolioUrl.trim();
const success = await onApply({
await onApply({
coverLetter: values.coverLetter,
portfolioUrl: portfolioUrl.length > 0 ? portfolioUrl : undefined,
});

if (success) {
setOpen(false);
form.reset();
}
setOpen(false);
form.reset();
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : "Unknown error";
Expand All @@ -98,7 +95,10 @@ export function ApplicationDialog({
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogTrigger asChild>{trigger}</DialogTrigger>
<DialogContent className="sm:max-w-131.25 bg-background text-foreground border-border">
<DialogContent
data-testid="application-dialog"
className="sm:max-w-131.25 bg-background text-foreground border-border"
>
<DialogHeader>
<DialogTitle>Apply for Bounty</DialogTitle>
<DialogDescription>
Expand All @@ -116,6 +116,7 @@ export function ApplicationDialog({
render={({ field }) => (
<Textarea
{...field}
data-testid="cover-letter-input"
placeholder="Explain why you are a good fit..."
className="min-h-37.5"
/>
Expand All @@ -130,6 +131,7 @@ export function ApplicationDialog({
render={({ field }) => (
<Input
{...field}
data-testid="portfolio-url-input"
placeholder="https://..."
value={field.value ?? ""}
/>
Expand All @@ -139,19 +141,27 @@ export function ApplicationDialog({

<DialogFooter>
{form.formState.errors.root?.message ? (
<p className="text-destructive mr-auto text-sm">
<p
data-testid="application-error"
className="text-destructive mr-auto text-sm"
>
{form.formState.errors.root.message}
</p>
) : null}

<Button
type="button"
variant="ghost"
data-testid="application-cancel-btn"
onClick={() => handleOpenChange(false)}
>
Cancel
</Button>
<Button type="submit" disabled={loading}>
<Button
type="submit"
data-testid="submit-application-btn"
disabled={loading}
>
{loading ? "Submitting..." : "Submit Application"}
</Button>
</DialogFooter>
Expand Down
2 changes: 2 additions & 0 deletions components/bounty/bounty-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ export function BountyCard({

return (
<Card
data-testid="bounty-card"
data-bounty-id={bounty.id}
className={cn(
"overflow-hidden w-full max-w-sm h-full rounded-lg cursor-pointer transition-all duration-300",
"flex flex-col relative", // Add relative for bookmark button positioning
Expand Down
Loading
Loading